public static ModMetadata GenerateForMod(string modFile) { ModMetadata meta = new ModMetadata(); meta.isEnabled = false; meta.modPath = modFile; ModuleDefinition modDef = ModuleDefinition.ReadModule(modFile); IEnumerable <TypeDefinition> getTypes = modDef.GetTypes(); Type patchType = typeof(MonoMod.MonoModPatch); Type constructorType = typeof(MonoMod.MonoModConstructor); Type originalNameType = typeof(MonoMod.MonoModOriginalName); Type ignoreType = typeof(MonoMod.MonoModIgnore); string origPrefix = "orig_"; DebugLogger.Log(string.Empty); DebugLogger.Log("Generating metadata for " + Path.GetFileName(modFile)); //Foreach type in the mod dll foreach (TypeDefinition checkType in getTypes) { //If the type has a custom attribute if (checkType.HasCustomAttributes) { HashSet <CustomAttribute> attributes = new HashSet <CustomAttribute>(checkType.CustomAttributes); //Foreach custom attribute foreach (CustomAttribute ct in attributes) { try { //If the attribute is [MonoModPatch] if (ct.AttributeType.Name == patchType.Name) { string originalClassName = ct.ConstructorArguments[0].Value as string; HashSet <string> changedFunctions = new HashSet <string>(); DebugLogger.Log(string.Empty); DebugLogger.Log("Adding " + originalClassName); List <MethodDefinition> methods = new List <MethodDefinition>(checkType.Methods); //Foreach method in this type foreach (MethodDefinition methodDef in methods) { if (methodDef.IsConstructor) { continue; } if (methodDef.Name.StartsWith(origPrefix)) { string methodEntry = originalClassName + "->" + methodDef.Name.Remove(0, origPrefix.Length); DebugLogger.Log("Adding method " + methodEntry); changedFunctions.Add(methodEntry); } else if (methodDef.HasCustomAttributes) { HashSet <CustomAttribute> methodAttributes = new HashSet <CustomAttribute>(methodDef.CustomAttributes); //Foreach custom attribute on the method foreach (CustomAttribute c in methodAttributes) { if (c.AttributeType.Name == ignoreType.Name) { string methodEntry = originalClassName + "->" + methodDef.Name; DebugLogger.Log("Ignoring Method " + methodEntry); break; } else if (c.AttributeType.Name == constructorType.Name) { string methodEntry = originalClassName + "->" + "ctor_" + originalClassName; DebugLogger.Log("Adding Constructor " + methodEntry); changedFunctions.Add(methodEntry); break; } else if (c.AttributeType.Name == originalNameType.Name) { string methodEntry = originalClassName + "->" + methodDef.Name; DebugLogger.Log("Adding Original Method " + methodEntry); changedFunctions.Add(methodEntry); break; } } } } meta.modifiedClasses.Add(originalClassName, changedFunctions); //We're done looking through attributes of this class, break break; } } catch (System.Exception e) { DebugLogger.Log(e); } } } } meta.isPatch = meta.modifiedClasses.Count > 0; if (meta.isPatch) { meta.isDirty = true; } modDef.Dispose(); return(meta); }
public static void PatchGame() { string executablePath = Assembly.GetEntryAssembly().Location; string executableDirectory = Directory.GetParent(executablePath).FullName; string monoModPath = Path.Combine(executableDirectory, "MonoMod.exe"); string hookGenPath = Path.Combine(executableDirectory, "MonoMod.RuntimeDetour.HookGen.exe"); string runtimeDetourDLL = "MonoMod.RuntimeDetour.dll"; string mmUtilsDLL = "MonoMod.Utils.dll"; string jsonDLL = "YamlDotNet.dll"; string gameDirectory = Directory.GetParent(GameManager.exePath).FullName; string hashesFolder = Path.Combine(gameDirectory, "PartialityHashes"); string modDependencies = Path.Combine(gameDirectory, "ModDependencies"); string dataDirectory = Path.Combine(gameDirectory, Path.GetFileNameWithoutExtension(GameManager.exePath) + "_Data"); string managedFolder = Path.Combine(dataDirectory, "Managed"); string codeDll = Path.Combine(managedFolder, "Assembly-CSharp.dll"); string hookGenDLL = Path.Combine(managedFolder, "HOOKS-Assembly-CSharp.dll"); string engineDll = Path.Combine(managedFolder, "UnityEngine.dll"); string coreModuleDLL = Path.Combine(managedFolder, "UnityEngine.CoreModule.dll"); string backupFolder = managedFolder + "_backup"; Process currentProcess = Process.GetCurrentProcess(); Process monomodProcess = new Process(); monomodProcess.StartInfo.FileName = monoModPath; monomodProcess.StartInfo.CreateNoWindow = true; monomodProcess.StartInfo.UseShellExecute = false; monomodProcess.StartInfo.RedirectStandardOutput = true; //Create backup if there isn't one if (!Directory.Exists(backupFolder)) { Directory.CreateDirectory(backupFolder); CopyFilesRecursively(managedFolder, backupFolder); } GameManager.CreatePartialityDirectories(); //Install the default patch for PartialityLauncher { string engineDLLName = "UnityEngine.dll"; if (File.Exists(coreModuleDLL)) { engineDLLName = "UnityEngine.CoreModule.dll"; engineDll = coreModuleDLL; } string moddedDLL = Path.Combine(Path.GetDirectoryName(engineDll), "patched" + engineDLLName); string defaultPatchLocation = Path.Combine(executableDirectory, "PartialityPatch.dll"); string partialityModLocation = Path.Combine(Path.GetDirectoryName(engineDll), "Partiality.dll"); bool shouldPatch = false; if (!File.Exists(Path.Combine(hashesFolder, "ENGINEHASH.hash"))) { shouldPatch = true; } else { shouldPatch = !ModMetadata.CompareHashes(defaultPatchLocation, Path.Combine(hashesFolder, "ENGINEHASH.hash")); } //Delete mod if it exists if (File.Exists(partialityModLocation)) { File.Delete(partialityModLocation); } //Copy mod to folder with assembly-chsharp.dll File.Copy(Path.Combine(executableDirectory, "Partiality.dll"), partialityModLocation); if (shouldPatch) { //Restore backup File.Delete(engineDll); File.Copy(Path.Combine(backupFolder, engineDLLName), engineDll); //Set monomod arguments to "[UnityEngine.dll] [PartialityPatch.dll] [patched_UnityEngine.dll]" //monomodProcess.StartInfo.Arguments = ('"' + engineDll + '"') + " " + ('"' + defaultPatchLocation + '"') + " " + ('"' + moddedDLL + '"'); monomodProcess.StartInfo.Arguments = $"\"{engineDll}\" \"{defaultPatchLocation}\" \"{moddedDLL}\""; monomodProcess.Start(); string mmoutput = monomodProcess.StandardOutput.ReadToEnd(); DebugLogger.Log(mmoutput); Console.WriteLine(mmoutput); monomodProcess.WaitForExit(); int exitCode = monomodProcess.ExitCode; Console.WriteLine("MMEC:" + exitCode); Console.WriteLine(mmoutput); //Replace file if (File.Exists(moddedDLL)) { //Move modded .dll over original .dll File.Delete(engineDll); File.Copy(moddedDLL, engineDll); File.Delete(moddedDLL); } byte[] newHash = ChecksumHasher.ComputeHash(File.ReadAllBytes(defaultPatchLocation)); File.WriteAllBytes(Path.Combine(hashesFolder, "ENGINEHASH.hash"), newHash); } } //Install custom patches { string[] files = Directory.GetFiles(modDependencies); //Copy mod dependencies foreach (string dependency in files) { string fileName = Path.GetFileName(dependency); //Delete the dependency if it already exists if (File.Exists(Path.Combine(managedFolder, fileName))) { File.Delete(Path.Combine(managedFolder, fileName)); } //Copy the file File.Copy(dependency, Path.Combine(managedFolder, fileName)); } bool shouldPatch = false; string moddedDLL = Path.Combine(Path.GetDirectoryName(engineDll), "patched_Assembly-CSharp.dll"); string epListLocation = Path.Combine(hashesFolder, "ENABLEDPATCHES.enp"); //Check if we have the same enabled/disabled mods as last time, if we do, then { string totalEnabledPatches = "Partiality+"; foreach (ModMetadata md in GameManager.modMetas) { if ((md.isStandalone || md.isPatch) && md.isEnabled) { totalEnabledPatches += Path.GetFileNameWithoutExtension(md.modPath) + "+"; } } DebugLogger.Log(totalEnabledPatches); if (File.Exists(epListLocation)) { string getList = File.ReadAllText(epListLocation); shouldPatch = getList != totalEnabledPatches; if (shouldPatch) { File.WriteAllText(epListLocation, totalEnabledPatches); } } else { shouldPatch = true; File.WriteAllText(epListLocation, totalEnabledPatches); } } //If all the same mods are enabled, check if any mods are dirty. If they are, we gotta re-patch. if (!shouldPatch) { foreach (ModMetadata md in GameManager.modMetas) { if (md.isDirty) { shouldPatch = true; break; } } } if (shouldPatch) { DebugLogger.Log("Patching Assembly-CSharp"); string backupDll = Path.Combine(backupFolder, "Assembly-CSharp.dll"); foreach (ModMetadata md in GameManager.modMetas) { if (md.isStandalone && md.isEnabled) { backupDll = md.modPath; } } //Restore backup File.Delete(codeDll); File.Copy(backupDll, codeDll); List <string> failedPatches = new List <string>(); foreach (ModMetadata md in GameManager.modMetas) { if (md.isPatch && md.isEnabled) { //monomodProcess.StartInfo.Arguments = ('"' + codeDll + '"') + " " + ('"' + md.modPath + '"') + " " + ('"' + moddedDLL + '"'); monomodProcess.StartInfo.Arguments = $"\"{codeDll}\" \"{md.modPath}\" \"{moddedDLL}\""; monomodProcess.Start(); monomodProcess.WaitForExit(); string mmoutput = monomodProcess.StandardOutput.ReadToEnd(); int exitCode = monomodProcess.ExitCode; DebugLogger.Log("MMEC:" + exitCode); DebugLogger.Log(mmoutput); if (exitCode != 0) { failedPatches.Add(Path.GetFileNameWithoutExtension(md.modPath)); } //Replace file if (File.Exists(moddedDLL)) { //Move modded .dll over original .dll File.Delete(codeDll); File.Copy(moddedDLL, codeDll); File.Delete(moddedDLL); } } } if (failedPatches.Count > 0) { MessageBox.Show("Some mods failed to apply correctly! Please send your LOG.txt (in the Partiality folder) to someone who can help, probably from the people who made the mod."); } //Set mods to all not be dirty, and save them. foreach (ModMetadata md in GameManager.modMetas) { md.isDirty = false; } try { GameManager.SaveAllMetadata(); } catch (System.Exception e) { DebugLogger.Log(e); } } } //HookGen stuff { //Delete Legacy DLL if (File.Exists(Path.Combine(managedFolder, "HOOKS-Assembly-CSharp.dll"))) { File.Delete(Path.Combine(managedFolder, "HOOKS-Assembly-CSharp.dll")); } if (File.Exists(hookGenDLL)) { File.Delete(hookGenDLL); } //Delete files if they existed, so we can update them. if (File.Exists(Path.Combine(managedFolder, runtimeDetourDLL))) { File.Delete(Path.Combine(managedFolder, runtimeDetourDLL)); } if (File.Exists(Path.Combine(managedFolder, mmUtilsDLL))) { File.Delete(Path.Combine(managedFolder, mmUtilsDLL)); } if (File.Exists(Path.Combine(managedFolder, jsonDLL))) { File.Delete(Path.Combine(managedFolder, jsonDLL)); } //Copy files File.Copy(Path.Combine(executableDirectory, runtimeDetourDLL), Path.Combine(managedFolder, runtimeDetourDLL)); File.Copy(Path.Combine(executableDirectory, mmUtilsDLL), Path.Combine(managedFolder, mmUtilsDLL)); File.Copy(Path.Combine(executableDirectory, jsonDLL), Path.Combine(managedFolder, jsonDLL)); string pathIn = codeDll; string pathOut = hookGenDLL; using (MonoModder mm = new MonoModder { InputPath = pathIn, OutputPath = pathOut }) { mm.Read(); mm.MapDependencies(); if (File.Exists(pathOut)) { mm.Log(string.Format("Clearing {0}", pathOut)); File.Delete(pathOut); } mm.Log("[HookGen] Starting HookGenerator"); HookGenerator gen = new HookGenerator(mm, Path.GetFileName(pathOut)); using (ModuleDefinition mOut = gen.OutputModule) { gen.HookPrivate = true; gen.Generate(); mOut.Write(pathOut); } mm.Log("[HookGen] Done."); } } //File.WriteAllText( gameDirectory + "\\PARTIALITY_OUTPUT.txt", ); }