static void Main(string[] args) { if (args.Length < 2) { Console.WriteLine("Usage: Hooker.exe [GameName_Data directory] [hooks file]"); return; } var dataPath = args[0]; var inStream = File.Open("Assembly-CSharp.dll", FileMode.Open, FileAccess.Read); var scriptAssembly = AssemblyDefinition.ReadAssembly(inStream); var hooker = new Hooker(scriptAssembly.MainModule); using (var fs = new FileStream(args[1], FileMode.Open, FileAccess.Read)) using (var sr = new StreamReader(fs)) while (!sr.EndOfStream) { var line = sr.ReadLine(); var dotI = line.IndexOf('.'); if (dotI > 0) { hooker.AddHookBySuffix(line.Substring(0, dotI), line.Substring(dotI + 1)); } } scriptAssembly.Write("Assembly-CSharp.out.dll"); foreach (var assemblyName in new []{"Assembly-CSharp", "HookRegistry", "Newtonsoft.Json"}) { var srcName = assemblyName + ".dll"; if (File.Exists(assemblyName + ".out.dll")) { srcName = assemblyName + ".out.dll"; } File.Copy(srcName, Path.Combine(dataPath, @"Managed", assemblyName + ".dll"), true); } }
public static Hooker New(ModuleDefinition module, HookSubOptions options) { /* * These things have to be recreated for every module! * This is because a reference is specifically generated from a certain module and cannot be reused * by another module! */ // Fetch types and references TypeDefinition _hookRegistryType = options.HookRegistryType; // RuntimeMethodHandle gives us information about the hooked function. // By doing this import we FAIL EARLY if the Type could not be found. (headache prevention) // var rmhTypeRef = module.Import(typeof(RuntimeMethodHandle)); // Look for the HookRegistry.onCall(..) method var onCallMethod = _hookRegistryType.Methods.First(mi => mi.Name.Equals("OnCall")); var onCallMethodRef = module.Import(onCallMethod); // The (reference) name for in the manifest of the hooked library. The runtime loads all referenced libraries // into application domain. Once in application domain, methods and types can resolve. // var hrAssemblyName = AssemblyNameReference.Parse(options.HooksRegistryAssembly.FullName); // By adding a new reference (pointing to our HookRegistry assembly), we are certain // that Module can resolve the calls to our hooks. // MARK; CECIL DOES THIS FOR US // module.AssemblyReferences.Add(hrAssemblyName); var newObj = new Hooker { Module = module, onCallMethodRef = onCallMethodRef, }; return(newObj); }
static void Main(string[] args) { var dataPath = "D:\\Program Files (x86)\\Hearthstone\\Hearthstone_Data"; foreach (var s in new[] { "Assembly-CSharp-firstpass", "Assembly-CSharp" }) { var inStream = File.Open(s + ".dll", FileMode.Open, FileAccess.Read); var scriptAssembly = AssemblyDefinition.ReadAssembly(inStream); var hooker = new Hooker(scriptAssembly.MainModule); using (var fs = new FileStream("../../example_hooks", FileMode.Open, FileAccess.Read)) using (var sr = new StreamReader(fs)) while (!sr.EndOfStream) { var line = sr.ReadLine(); var dotI = line.IndexOf('.'); if (dotI > 0) { hooker.AddHookBySuffix(line.Substring(0, dotI), line.Substring(dotI + 1)); } } scriptAssembly.Write(s + ".out.dll"); } foreach (var assemblyName in new [] { "Assembly-CSharp", "Assembly-CSharp-firstpass", "HookRegistry", "Newtonsoft.Json" }) { var srcName = assemblyName + ".dll"; if (File.Exists(assemblyName + ".out.dll")) { srcName = assemblyName + ".out.dll"; } File.Copy(srcName, Path.Combine(dataPath, @"Managed", assemblyName + ".dll"), true); } }
public static Hooker New(ModuleDefinition module, HookSubOptions options) { /* * These things have to be recreated for every module! * This is because a reference is specifically generated from a certain module and cannot be reused * by another module! */ // Fetch types and references TypeDefinition _hookRegistryType = options.HookRegistryType; // Look for the HookRegistry.onCall(..) method var onCallMethod = _hookRegistryType.Methods.First(mi => mi.Name.Equals("OnCall")); var onCallMethodRef = module.Import(onCallMethod); var newObj = new Hooker { Module = module, onCallMethodRef = onCallMethodRef, }; return(newObj); }
static void Main(string[] args) { if (args.Length < 2) { Console.WriteLine("Usage: Hooker.exe [GameName_Data directory] [hooks file]"); return; } var dataPath = args[0]; foreach (var s in new[] { "Assembly-CSharp-firstpass", "Assembly-CSharp" }) { var inStream = File.Open(s + ".dll", FileMode.Open, FileAccess.Read); var scriptAssembly = AssemblyDefinition.ReadAssembly(inStream); var hooker = new Hooker(scriptAssembly.MainModule); using (var fs = new FileStream(args[1], FileMode.Open, FileAccess.Read)) using (var sr = new StreamReader(fs)) while (!sr.EndOfStream) { var line = sr.ReadLine(); var dotI = line.IndexOf('.'); if (dotI > 0) { hooker.AddHookBySuffix(line.Substring(0, dotI), line.Substring(dotI + 1)); } } scriptAssembly.Write(s + ".out.dll"); } foreach (var assemblyName in new [] { "Assembly-CSharp", "Assembly-CSharp-firstpass", "HookRegistry", "Newtonsoft.Json" }) { var srcName = assemblyName + ".dll"; if (File.Exists(assemblyName + ".out.dll")) { srcName = assemblyName + ".out.dll"; } File.Copy(srcName, Path.Combine(dataPath, @"Managed", assemblyName + ".dll"), true); } }
public void TryHook() { // Validate all options CheckOptions(); // Copy our injected library to the location of the 'to patch' assemblies CopyHooksLibrary(); // Find all needed types FindNecessaryTypes(); // Find all method fullnames that the hookregistry expects FetchExpectedMethods(); // Read all hook functions into memory var hookEntries = ReadHooksFile(_options.HooksFilePath); // Initialise the AssemblyStore with the given path. // All assemblies are parsed from their own location. var asStore = AssemblyStore.Get(_options.GamePath); // Loop all libraries looking for methods to hook ! important - core // Library is a reference to the filename of the assembly file containing the actual // code we want to patch. foreach (AssemblyStore.LIB_TYPE library in AssemblyStore.GetAllLibraryTypes()) { // Skip invalid lib! if (library == AssemblyStore.LIB_TYPE.INVALID) { continue; } // Full path to current assembly string libraryPath = library.GetPath(); // Full path to processed assembly string libraryOutPath = library.GetPathOut(); // Load the assembly file AssemblyDefinition assembly; AssemblyStore.GetAssembly(library, out assembly); if (assembly.HasPatchMark()) { Program.Log.Warn(ASSEMBLY_ALREADY_PATCHED, libraryPath); continue; } // Construct a hooker wrapper around the main Module of the assembly. // The wrapper facilitates hooking into method calls. ModuleDefinition mainModule = assembly.MainModule; Hooker wrapper = Hooker.New(mainModule, _options); Program.Log.Info(CHECKING_ASSEMBLY, libraryPath); // Keep track of hooked methods bool isHooked = false; // Loop each hook entry looking for registered types and methods foreach (HOOK_ENTRY hookEntry in hookEntries) { try { wrapper.AddHookBySuffix(hookEntry.TypeName, hookEntry.MethodName, ExpectedMethods); isHooked = true; } catch (MissingMethodException) { // The method is not found in the current assembly. // This is no error because we run all hook entries against all libraries! } } try { // Only save if the file actually changed! if (isHooked) { // Generate backup from original file library.Backup(); // Save the manipulated assembly library.Save(); // TODO UNCOMMENT // Overwrite the original with the hooked one File.Copy(libraryOutPath, libraryPath, true); } else { Program.Log.Debug(ASSEMBLY_NOT_PATCHED, libraryPath); } } catch (IOException e) { // The file could be locked! Notify user. // .. or certain libraries could not be resolved.. // Try to find the path throwing an exception.. but this method is not foolproof! var path = typeof(IOException).GetField("_maybeFullPath", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase)?.GetValue(e); Program.Log.Exception(ERR_WRITE_FILE, null, e?.ToString()); throw; } } // End foreach LIB_TYPE }
public void TryHook(GameKB gameKnowledge) { // Validate all command line options. CheckOptions(); // Copy our injected library to the location of the 'to hook' assemblies. CopyHooksLibrary(); // FindNecessaryTypes(); // Find all expected method fullnames by HookRegistry. FetchExpectedMethods(); var hookEntries = ReadHooksFile(_options.HooksFilePath); // Iterate all libraries known for the provided game. // An assembly blueprint will be created from the yielded filenames. // The blueprints will be edited, saved and eventually replaces the original assembly. foreach (string libraryFilePath in gameKnowledge) { var libBackupPath = AssemblyHelper.GetPathBackup(libraryFilePath); var libPatchedPath = AssemblyHelper.GetPathOut(libraryFilePath); // Load the assembly file AssemblyDefinition assembly = AssemblyHelper.LoadAssembly(libraryFilePath, gameKnowledge.LibraryPath); if (assembly.HasPatchMark()) { Program.Log.Warn(ASSEMBLY_ALREADY_PATCHED, libraryFilePath); continue; } // Construct a hooker wrapper around the main Module of the assembly. // The wrapper facilitates hooking into method calls. ModuleDefinition mainModule = assembly.MainModule; Hooker wrapper = Hooker.New(mainModule, _options); Program.Log.Info(CHECKING_ASSEMBLY, libraryFilePath); // Keep track of hooked methods bool isHooked = false; // Loop each hook entry looking for registered types and methods foreach (HOOK_ENTRY hookEntry in hookEntries) { try { wrapper.AddHookBySuffix(hookEntry.TypeName, hookEntry.MethodName, ExpectedMethods); isHooked = true; } catch (MissingMethodException) { // The method is not found in the current assembly. // This is no error because we run all hook entries against all libraries! } } try { // Only save if the file actually changed! if (isHooked) { // Generate backup from original file try { // This throws if the file already exists. File.Copy(libraryFilePath, libBackupPath, false); } catch (Exception) { // Do nothing } // Save the manipulated assembly. assembly.Save(libPatchedPath); // Overwrite the original with the hooked one File.Copy(libPatchedPath, libraryFilePath, true); } else { Program.Log.Debug(ASSEMBLY_NOT_PATCHED, libraryFilePath); } } catch (IOException e) { // The file could be locked! Notify user. // .. or certain libraries could not be resolved.. // Try to find the path throwing an exception.. but this method is not foolproof! var path = typeof(IOException).GetField("_maybeFullPath", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase)?.GetValue(e); Program.Log.Exception(ERR_WRITE_FILE, null, e?.ToString()); throw; } } }