public static void Entry(bool verbosity) { Logger.Initialize(verbosity); Logger.Log($"Starting ModLoaderLibrary Version {MLVersion.ToString()}"); Logger.Log("Beginning Patches...", Logger.LogType.Debug); try { var harmony = HarmonyInstance.Create("com.catcherben.modloader"); harmony.PatchAll(Assembly.GetExecutingAssembly()); Logger.Log("Finished MLL Patching.", Logger.LogType.Debug); } catch (Exception e) { Logger.Log($"Exception when patching: {e.Message}", Logger.LogType.Error); while (e.InnerException != null) { Logger.Log($"Internal Exception Message: {e.InnerException.Message}", Logger.LogType.Error); e = e.InnerException; } } // All mod-loading code now occurs in a harmony patch, now that ACEO supports modding files, etc. Logger.Log("ModLoaderLibrary initialization finished."); }
private static void PrintHelp() { Console.WriteLine($"ModLoader {MLVersion.ToString()}"); Console.WriteLine("Command line arguments:\r\n"); Console.WriteLine("-h, --help"); Console.WriteLine("Prints this help menu.\r\n"); Console.WriteLine("-v, --verbose"); Console.WriteLine("Enables verbose debug output to log and console.\r\n"); Console.WriteLine("-ss, --start"); Console.WriteLine("Start the game through Steam. Note: As of Alpha 26.1, you cannot start the game any other way.\r\n"); Console.WriteLine("-p, --patch"); Console.WriteLine("Patches the game with ModLoaderLibrary\r\n"); Console.WriteLine("-e, --enums"); Console.WriteLine("Injects enums found in mods\r\n"); Console.WriteLine("-b, --backupname"); Console.WriteLine("Specifies custom name to backup assembly file to\r\n"); Console.WriteLine("-sp, --steampath"); Console.WriteLine("Specifies path to Steam.\r\n\r\n\r\n"); Console.WriteLine("To start ACEO normally from ModLoader, run \"ModLoader [-s or -ss]\""); Console.WriteLine("To start ACEO with full modding support, run \"ModLoader -p -e [-s or -ss]\""); Console.WriteLine("If you are a mod developer, please refer to the project's Git readme at https://github.com/bdgmb2/aceo-modloader"); Console.WriteLine("\r\nPress any key to exit..."); Console.ReadKey(); }
private void Patch() { try { Logger.Log("Patching Assembly-CSharp" + LibExtension, Logger.LogType.Debug); // This is the ExitPoint class we're patching var exitPointIL = module.Types.Single(x => x.Name == "Utils").Methods.Single(x => x.Name == "QuitGame").Body; var exitPointProcessor = exitPointIL.GetILProcessor(); // Remove all instructions (It's only a call to Application.Quit() and a 'ret') exitPointIL.Instructions.Clear(); // Load the ModLoaderLibrary assembly exitPointIL.Instructions.Add(exitPointProcessor.Create(OpCodes.Ldstr, "ModLoader/MLL" + LibExtension)); exitPointIL.Instructions.Add(exitPointProcessor.Create(OpCodes.Call, module.ImportReference(typeof(Assembly).GetMethod("LoadFile", new[] { typeof(string) })))); // Get the ModLoader class in the assembly exitPointIL.Instructions.Add(exitPointProcessor.Create(OpCodes.Ldstr, "ModLoaderLibrary.ModLoader")); exitPointIL.Instructions.Add(exitPointProcessor.Create(OpCodes.Callvirt, module.ImportReference(typeof(Assembly).GetMethod("GetType", new[] { typeof(string) })))); // Get the Exit() function in the ModLoader class exitPointIL.Instructions.Add(exitPointProcessor.Create(OpCodes.Ldstr, "Exit")); exitPointIL.Instructions.Add(exitPointProcessor.Create(OpCodes.Callvirt, module.ImportReference(typeof(Type).GetMethod("GetMethod", new[] { typeof(string) })))); // Call the Exit() function exitPointIL.Instructions.Add(exitPointProcessor.Create(OpCodes.Ldnull)); exitPointIL.Instructions.Add(exitPointProcessor.Create(OpCodes.Ldnull)); exitPointIL.Instructions.Add(exitPointProcessor.Create(OpCodes.Callvirt, module.ImportReference(typeof(MethodBase).GetMethod("Invoke", new[] { typeof(object), typeof(object[]) })))); exitPointIL.Instructions.Add(exitPointProcessor.Create(OpCodes.Pop)); // Add the 'Application.Quit()' back in exitPointIL.Instructions.Add(exitPointProcessor.Create(OpCodes.Call, module.ImportReference(typeof(UnityEngine.Application).GetMethod("Quit")))); // Add the 'ret' back in exitPointIL.Instructions.Add(exitPointProcessor.Create(OpCodes.Ret)); // Ok we're done with the exitpoint. // This is the entry point method we're patching var entryPointIL = module.Types.Single(x => x.Name == "GameVersionLabelUI").Methods.Single(x => x.Name == "Awake").Body; var entryPointProcessor = entryPointIL.GetILProcessor(); // Remove the 'ret' at the end of the instruction list in the method entryPointIL.Instructions.RemoveAt(entryPointIL.Instructions.Count - 1); // Add ModLoader Message (since we're at the end of GameVersionLabelUI) var gameVerReference = module.Types.Single(x => x.Name == "GameVersionLabelUI"); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Ldarg_0)); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Ldfld, module.ImportReference(gameVerReference.Fields.Single(x => x.Name == "versionLabelText")))); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Dup)); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Callvirt, module.ImportReference(typeof(UnityEngine.UI.Text).GetMethod("get_text")))); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Ldstr, " - ModLoader " + MLVersion.ToString())); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Call, module.ImportReference(typeof(System.String).GetMethod("Concat", new [] { typeof(string), typeof(string) })))); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Callvirt, module.ImportReference(typeof(UnityEngine.UI.Text).GetMethod("set_text", new [] { typeof(string) })))); // Load Harmony entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Ldstr, "ModLoader/0Harmony" + LibExtension)); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Call, module.ImportReference(typeof(Assembly).GetMethod("LoadFile", new[] { typeof(string) })))); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Pop)); // Load the ModLoaderLibrary assembly entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Ldstr, "ModLoader/MLL" + LibExtension)); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Call, module.ImportReference(typeof(Assembly).GetMethod("LoadFile", new[] { typeof(string) })))); // Get the ModLoader class in the assembly entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Ldstr, "ModLoaderLibrary.ModLoader")); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Callvirt, module.ImportReference(typeof(Assembly).GetMethod("GetType", new[] { typeof(string) })))); // Get the Entry() function in the ModLoader class entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Ldstr, "Entry")); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Callvirt, module.ImportReference(typeof(Type).GetMethod("GetMethod", new[] { typeof(string) })))); // Call the Entry() function entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Ldnull)); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Ldc_I4_1)); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Newarr, module.ImportReference(typeof(Object)))); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Dup)); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Ldc_I4_0)); if (Logger.logDebug) { entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Ldc_I4_1)); } else { entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Ldc_I4_0)); } entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Box, module.ImportReference(typeof(Boolean)))); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Stelem_Ref)); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Callvirt, module.ImportReference(typeof(MethodBase).GetMethod("Invoke", new[] { typeof(object), typeof(object[]) })))); entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Pop)); // Add the 'ret' back in entryPointIL.Instructions.Add(entryPointProcessor.Create(OpCodes.Ret)); Logger.Log("Done.", Logger.LogType.Debug); } catch (Exception ex) { Logger.Log("Problem when patching assembly: " + ex.Message + ", Reverting.", Logger.LogType.Error); module.Dispose(); RevertAssembly(); throw; } }
private static void Main(string[] args) { bool logVerbose = false; string logLocation = "", backupName = "", assmOutput = "", steamPath = ""; bool patching = false, enums = false, launchGame = false; if (args.Length == 0) { PrintHelp(); return; } // PARSE ARGUMENTS // This would be a foreach, but I'm doing lookahead to check some arguments for (int i = 0; i < args.Length; i++) { // For simplicity string arg = args[i]; switch (arg) { // Turn the verbosity up case "-v": case "--verbose": logVerbose = true; break; // Custom log output location case "-l": case "--log": if (i + 1 >= args.Length) { Console.WriteLine("[ERROR] Log is missing file argument. Exiting."); return; } else { logLocation = args[i + 1]; } break; // Patch the assembly case "-p": case "--patch": patching = true; break; // Inject enums case "-e": case "--enums": if (!patching) { Console.WriteLine("Cannot inject enums without patching library. Please run with \"-p\""); return; } else { enums = true; } break; // Custom assembly backup name case "-b": case "--backupname": if (!patching) { Console.WriteLine( "Cannot specify custom backup name without patching library. Please run with \"-p\""); return; } else if (i + 1 >= args.Length) { Console.WriteLine("[ERROR] Backup file name is missing argument. Exiting."); return; } else { backupName = args[i + 1]; } break; // Custom patched assembly output name case "-o": case "--output": if (i + 1 >= args.Length) { Console.WriteLine("[ERROR] Library output is missing file argument. Exiting."); return; } else { assmOutput = args[i + 1]; } break; // Launch game through Steam case "-s": case "--start": launchGame = true; break; // Specify Steam Path case "-sp": case "--steampath": if (i + 1 >= args.Length) { Console.WriteLine("[ERROR] Steam Path is missing argument. Exiting."); return; } else { steamPath = args[i + 1]; } break; // Show help menu case "-h": case "--help": default: PrintHelp(); return; } } // INITIATE LOGGER if (logLocation != "") { Logger.Initialize(logVerbose, logLocation); } else { Logger.Initialize(logVerbose); } Logger.Log($"Starting ACEO ModLoader version {MLVersion.ToString()}"); // INITIATE PATCHER Patcher patcher; try { patcher = new Patcher { IsPatching = patching, IsInjectingEnums = enums, IsLaunchingGame = launchGame }; if (backupName != "") { patcher.BackupFilename = backupName; } if (assmOutput != "") { patcher.AssemblyName = assmOutput; } if (steamPath != "") { patcher.SteamDirectory = steamPath; } } catch (Exception ex) { Logger.Log($"Critical error when starting up: {ex.Message}", Logger.LogType.Error); Logger.Log("Press any key to continue."); Console.ReadKey(); return; } // RUN LOGIC try { patcher.Run(); } catch (Exception) { Logger.Log("Attempting to restore backup..."); patcher.RevertAssembly(); Logger.Log("ModLoader encountered a problem and has stopped. If you think there is a bug in ModLoader, please submit an issue to the ModLoader Github repository.", Logger.LogType.Error); Console.WriteLine("Press any key to continue."); Console.ReadKey(); } Logger.Log("ModLoader Exiting..."); }