public void TryRestore(GameKB gameKnowledge) { // Check the options CheckOptions(); // Iterate all known libraries for the game. foreach (string gameLibFilePath in gameKnowledge.LibraryFilePaths) { string backupLibPath = AssemblyHelper.GetPathBackup(gameLibFilePath); if (File.Exists(backupLibPath)) { // Restore the original file. try { File.Copy(backupLibPath, gameLibFilePath, true); Program.Log.Info(FILE_RESTORED, backupLibPath); } catch (Exception e) { // This is actually really bad.. but we'll continue to restore other originals. Program.Log.Exception(ERR_RESTORE_FILE, e, backupLibPath); } } } }
void CopyHooksLibrary(GameKB knowledgeBase) { var libTargetPath = Path.Combine(knowledgeBase.LibraryPath, Path.GetFileName(_options.HooksRegistryFilePath)); try { // Copy the HookRegistry library next to the libraries of the game! File.Copy(_options.HooksRegistryFilePath, libTargetPath, true); // The HookRegistry library file is copied next to the game libraries because // hooking a library will add a dependancy towards HookRegistry. // HookRegistry is designed to have NO DEPENDANCIES. If you built it with // additional dependancies, they will need to be copied manually to the library // directory! // Update registry file path to be around the targetted libraries. _options.HooksRegistryFilePath = libTargetPath; // And reload assembly definition _options.HooksRegistryAssembly = AssemblyDefinition.ReadAssembly( _options.HooksRegistryFilePath); } catch (Exception) { Program.Log.Warn(ERR_COPY_HLIB, libTargetPath); } }
void ProcessHookRegistry(GameKB gameKnowledge) { // We load the HookRegistry dll in a seperate app domain, which allows for dynamic unloading // when needed. // HookRegistry COULD lock referenced DLL's which we want to overwrite, so unloading releases // the locks HookRegistry held. // Isolated domain where the library will be loaded into. var testingDomain = AppDomain.CreateDomain("HR_Testing"); using (Program.Log.OpenBlock("Testing Hookregistry library")) { try { // Create an instance of our own assembly in a new appdomain. object instance = testingDomain .CreateInstanceAndUnwrap(typeof(HookRegistryTester).Assembly.FullName, typeof(HookRegistryTester).FullName); /* All methods used are actually executed in the testing domain! */ var hrTester = (HookRegistryTester)instance; hrTester.Analyze(_options.HooksRegistryFilePath, gameKnowledge.LibraryPath); // Load all data from the tester. ExpectedMethods = hrTester.ExpectedMethods; ReferencedLibraryPaths = hrTester.ReferencedAssemblyFiles.ToArray(); } // Exceptions will flow back into our own AppDomain when unhandled. catch (Exception) { Program.Log.Warn("FAIL Testing"); throw; } finally { AppDomain.Unload(testingDomain); } } }
static int Main(string[] args) { bool succeeded = YanderePrepare.PrepareMod(); if (!succeeded) { Console.ReadLine(); return(0); } if (File.Exists(Environment.CurrentDirectory + "\\hooked")) { LaunchGame(); return(0); } string[] actions = CustomArgs(args); //Console.ReadLine(); // Operation string invokedOperation = ""; // General options. GeneralOptions generalOptions = null; // Options specific to the action to perform. object invokedOperationOptions = null; var opts = new Options(); // Must check for null, because the parser won't.. if (actions == null || actions.Length == 0) { Console.WriteLine(opts.GetUsage("help")); goto ERROR; } CommandLine.Parser.Default.ParseArgumentsStrict(actions, opts, (verb, subOptions) => { // Action to store correct information for further instructing the processor. invokedOperation = verb; invokedOperationOptions = subOptions; }, () => { // Failed attempt at parsing the provided arguments. Environment.Exit(-2); }); try { // Process general options generalOptions = (GeneralOptions)invokedOperationOptions; Prepare(generalOptions); } catch (Exception e) { Log.Exception(e.Message, e); goto ERROR; } // Use knowledge about the game HearthStone. Game knowledge is defined in the shared code // project KnowledgeBase. See `GameKnowledgeBase.HSKB` for more information. // Change the following line if you want to hook another game. var gameKnowledge = new GameKB(generalOptions.GamePath, new YandereSimulatorKB()); try { switch (invokedOperation) { case OPERATION_HOOK: Log.Info("Hooking operation started!\n"); var hookHelper = new HookHelper((HookSubOptions)invokedOperationOptions); hookHelper.TryHook(gameKnowledge); Log.Info("Hooked the game libraries!"); break; case OPERATION_RESTORE: Log.Info("Restore operation started!\n"); var restore = new Restore((RestoreSubOptions)invokedOperationOptions); restore.TryRestore(gameKnowledge); Log.Info("Restored the original game libraries!"); break; default: throw new ArgumentException("Invalid verb processed"); } } catch (Exception e) { Log.Exception(EXCEPTION_MSG, e); goto ERROR; } //Console.ReadLine(); // All OK if (args.Length == 0) { LaunchGame(); } return(0); ERROR: var tprocess = new System.Diagnostics.Process(); var tstartInfo = new System.Diagnostics.ProcessStartInfo { FileName = Environment.CurrentDirectory + "\\unhook.bat" }; tprocess.StartInfo = tstartInfo; tprocess.Start(); Console.ReadLine(); return(1); }
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; } } }
public void TryHook(GameKB gameKnowledge) { // Validate all command line options. CheckOptions(); // Test HookRegistry library. ProcessHookRegistry(gameKnowledge); // Copy our injected library to the location of the 'to hook' assemblies. CopyLibraries(gameKnowledge); List <HOOK_ENTRY> hookEntries = ReadHooksFile(_options.HooksFilePath); using (Program.Log.OpenBlock("Parsing libary files")) { // 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.LibraryFilePaths) { using (Program.Log.OpenBlock(libraryFilePath)) { if (!File.Exists(libraryFilePath)) { Program.Log.Warn("File does not exist!"); continue; } string libBackupPath = AssemblyHelper.GetPathBackup(libraryFilePath); string libPatchedPath = AssemblyHelper.GetPathOut(libraryFilePath); AssemblyDefinition assembly = null; try { // Load the assembly file assembly = AssemblyHelper.LoadAssembly(libraryFilePath, gameKnowledge.LibraryPath); } catch (BadImageFormatException e) { Program.Log.Warn("Library file is possibly encrypted!"); Program.Log.Info("Library skipped because it cannot be read."); Program.Log.Debug("Full exception: {0}", e.Message); continue; } if (assembly.HasPatchMark()) { Program.Log.Warn(ASSEMBLY_ALREADY_PATCHED); continue; } // Construct a hooker wrapper around the main Module of the assembly. // The wrapper facilitates hooking into method calls. ModuleDefinition mainModule = assembly.MainModule; var wrapper = HookLogic.New(mainModule, _options); // 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.Warn(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! object path = typeof(IOException).GetField("_maybeFullPath", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase)?.GetValue(e); Program.Log.Warn("Could not write patched data to file `{0}`!", path); throw e; } } } } }
void CopyLibraries(GameKB knowledgeBase) { // The original folder containing HookRegistry library. string origHRFolderPath = Path.GetDirectoryName(_options.HooksRegistryFilePath); // The library folder of our game. string gameLibFolder = knowledgeBase.LibraryPath; // List of all assemblies to copy to the game library folder. IEnumerable <string> assembliesToCopy = new List <string>(ReferencedLibraryPaths) { _options.HooksRegistryFilePath }; // Only keep unique entries. assembliesToCopy = assembliesToCopy.Distinct(); // If TRUE, existing files in gameLibFolder will be overwritten if a dependancy has the // same name. bool overwriteDependancies = _options.OverwriteDependancies; using (Program.Log.OpenBlock("Copying HookRegistry dependancies")) { Program.Log.Info("Source directory `{0}`", origHRFolderPath); Program.Log.Info("Target directory `{0}`", gameLibFolder); foreach (string referencedLibPath in ReferencedLibraryPaths) { // Only copy the libraries which come from the same path as HookRegistry originally. string libFolderPath = Path.GetDirectoryName(referencedLibPath); if (!libFolderPath.Equals(origHRFolderPath)) { continue; } // Construct name for library file under the game library folder. string libFileName = Path.GetFileName(referencedLibPath); string targetLibPath = Path.Combine(gameLibFolder, libFileName); try { if (!overwriteDependancies && File.Exists(targetLibPath)) { Program.Log.Info("Skipped `{0}`", libFileName); } else { File.Copy(referencedLibPath, targetLibPath, true); Program.Log.Info("SUCCESS Copied binary `{0}`", libFileName); if (referencedLibPath == _options.HooksRegistryFilePath) { // Update the options object to reflect the copied library. _options.HooksRegistryFilePath = targetLibPath; _options.HooksRegistryAssemblyBlueprint = AssemblyHelper.LoadAssembly(targetLibPath, gameLibFolder); } } } catch (Exception) { Program.Log.Warn("FAIL Error copying `{0}`. Manual copy is needed!", libFileName); } } } }
static int Main(string[] args) { // Operation string invokedOperation = ""; // General options. GeneralOptions generalOptions = null; // Options specific to the action to perform. object invokedOperationOptions = null; var opts = new Options(); // Must check for null, because the parser won't.. if (args == null || args.Length == 0) { Console.WriteLine(opts.GetUsage("help")); goto ERROR; } CommandLine.Parser.Default.ParseArgumentsStrict(args, opts, (verb, subOptions) => { // Action to store correct information for further instructing the processor. invokedOperation = verb; invokedOperationOptions = subOptions; }, () => { // Failed attempt at parsing the provided arguments. Environment.Exit(-2); }); try { // Process general options generalOptions = (GeneralOptions)invokedOperationOptions; Prepare(generalOptions); } catch (Exception e) { Log.Exception(e.Message, e); goto ERROR; } // Use knowledge about the game HearthStone. Game knowledge is defined in the shared code // project KnowledgeBase. See `GameKnowledgeBase.HSKB` for more information. // Change the following line if you want to hook another game. GameKB gameKnowledge = HSKB.Get(generalOptions.GamePath); try { switch (invokedOperation) { case OPERATION_HOOK: var hookHelper = new HookHelper((HookSubOptions)invokedOperationOptions); hookHelper.TryHook(gameKnowledge); Log.Info("Succesfully hooked the game libraries!"); break; case OPERATION_RESTORE: var restore = new Restore((RestoreSubOptions)invokedOperationOptions); restore.TryRestore(gameKnowledge); Log.Info("Succesfully restored the original game libraries!"); break; default: throw new ArgumentException("Invalid verb processed"); } } catch (Exception e) { Log.Exception(EXCEPTION_MSG, e); goto ERROR; } // All OK return(0); ERROR: return(1); }