Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SkillLimitExtenderValheim1Compat v1.0.2
patchers\SkillLimitExtenderValheim1Compat\SkillLimitExtenderValheim1Compat.Patcher.dll
Decompiled 2 days agousing System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Security.Cryptography; using BepInEx; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyTitle("SkillLimitExtenderValheim1Compat.Patcher")] [assembly: AssemblyDescription("Valheim 1.0 compatibility patch for SkillLimitExtender 1.2.0")] [assembly: AssemblyCompany("SkillLimitExtenderValheim1Compat contributors")] [assembly: AssemblyProduct("SkillLimitExtenderValheim1Compat")] [assembly: AssemblyFileVersion("1.0.2.0")] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("1.0.2.0")] namespace SkillLimitExtenderValheim1Compat; public static class Patcher { private const string TargetFile = "skill_Limit_Extender.dll"; private const string ExpectedVersion = "1.2.0"; private const string ExpectedOriginalSha256 = "30EF37FEFC03C91DAF7F5CE34D68B7CF404112849B87B085AF9AE0635FCA6880"; private const int ExpectedEverybodyLoads = 2; private const int ExpectedConsoleCtors = 2; public static IEnumerable<string> TargetDLLs => new string[0]; public static void Patch(AssemblyDefinition assembly) { } public static void Initialize() { Console.WriteLine("[SkillLimitExtenderValheim1Compat] Initializing compatibility patch."); string pluginPath = Paths.PluginPath; if (string.IsNullOrEmpty(pluginPath) || !Directory.Exists(pluginPath)) { throw new DirectoryNotFoundException("BepInEx plugin directory was not found: " + pluginPath); } string[] files = Directory.GetFiles(pluginPath, "skill_Limit_Extender.dll", SearchOption.AllDirectories); if (files.Length != 1) { throw new InvalidOperationException("Safety stop: expected exactly one skill_Limit_Extender.dll below " + pluginPath + ", found " + files.Length + "."); } string text = files[0]; Console.WriteLine("[SkillLimitExtenderValheim1Compat] Target: " + text); PatchFile(text); } private static void PatchFile(string targetPath) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Expected O, but got Unknown byte[] array = File.ReadAllBytes(targetPath); string text = ComputeSha256(array); string text2 = targetPath + ".slecompat.tmp"; if (File.Exists(text2)) { File.Delete(text2); } ReaderParameters val = new ReaderParameters(); val.InMemory = true; val.ReadSymbols = false; ReaderParameters val2 = val; int patchedEverybody = 0; int patchedConsole = 0; AssemblyDefinition val3 = AssemblyDefinition.ReadAssembly(targetPath, val2); try { VerifySkillLimitExtenderVersion(val3); CountLegacyReferences(val3, out var everybodyLoads, out var oldConsoleCtors); if (everybodyLoads == 0 && oldConsoleCtors == 0) { Console.WriteLine("[SkillLimitExtenderValheim1Compat] SkillLimitExtender 1.2.0 is already compatible; no changes needed."); return; } if (!string.Equals(text, "30EF37FEFC03C91DAF7F5CE34D68B7CF404112849B87B085AF9AE0635FCA6880", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("Safety stop: SkillLimitExtender 1.2.0 DLL hash does not match the supported original binary. Expected 30EF37FEFC03C91DAF7F5CE34D68B7CF404112849B87B085AF9AE0635FCA6880, found " + text + "."); } if (everybodyLoads != 2 || oldConsoleCtors != 2) { throw new InvalidOperationException("Safety stop: expected legacy shape (ZRoutedRpc.Everybody=" + 2 + ", ConsoleCommand ctor=" + 2 + "), found Everybody=" + everybodyLoads + ", ConsoleCommand ctor=" + oldConsoleCtors + "."); } ApplyPatch(val3, ref patchedEverybody, ref patchedConsole); if (patchedEverybody != 2 || patchedConsole != 2) { throw new InvalidOperationException("Patch application count mismatch."); } WriterParameters val4 = new WriterParameters(); val4.WriteSymbols = false; val3.Write(text2, val4); } finally { ((IDisposable)val3)?.Dispose(); } try { VerifyPatchedFile(text2); File.Delete(targetPath); File.Move(text2, targetPath); } catch { if (!File.Exists(targetPath)) { File.WriteAllBytes(targetPath, array); } if (File.Exists(text2)) { File.Delete(text2); } throw; } Console.WriteLine("[SkillLimitExtenderValheim1Compat] Patch successful :3 Patched " + patchedEverybody + " ZRoutedRpc.Everybody load(s) and " + patchedConsole + " ConsoleCommand constructor call(s)."); } private static void VerifyPatchedFile(string path) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ReaderParameters val = new ReaderParameters(); val.InMemory = true; val.ReadSymbols = false; AssemblyDefinition val2 = AssemblyDefinition.ReadAssembly(path, val); try { VerifySkillLimitExtenderVersion(val2); CountLegacyReferences(val2, out var everybodyLoads, out var oldConsoleCtors); if (everybodyLoads != 0 || oldConsoleCtors != 0) { throw new InvalidOperationException("Verification failed: patched DLL still contains legacy references. Everybody=" + everybodyLoads + ", ConsoleCommand ctor=" + oldConsoleCtors + "."); } } finally { ((IDisposable)val2)?.Dispose(); } } private static void CountLegacyReferences(AssemblyDefinition assembly, out int everybodyLoads, out int oldConsoleCtors) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) everybodyLoads = 0; oldConsoleCtors = 0; foreach (TypeDefinition item in AllTypes((IEnumerable<TypeDefinition>)assembly.MainModule.Types)) { Enumerator<MethodDefinition> enumerator2 = item.Methods.GetEnumerator(); try { while (enumerator2.MoveNext()) { MethodDefinition current2 = enumerator2.Current; if (!current2.HasBody) { continue; } Enumerator<Instruction> enumerator3 = current2.Body.Instructions.GetEnumerator(); try { while (enumerator3.MoveNext()) { Instruction current3 = enumerator3.Current; if (IsEverybodyLoad(current3)) { everybodyLoads++; } if (IsOldConsoleCtor(current3)) { oldConsoleCtors++; } } } finally { ((IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } private static void ApplyPatch(AssemblyDefinition assembly, ref int patchedEverybody, ref int patchedConsole) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) foreach (TypeDefinition item in AllTypes((IEnumerable<TypeDefinition>)assembly.MainModule.Types)) { Enumerator<MethodDefinition> enumerator2 = item.Methods.GetEnumerator(); try { while (enumerator2.MoveNext()) { MethodDefinition current2 = enumerator2.Current; if (!current2.HasBody) { continue; } List<Instruction> list = new List<Instruction>(); Enumerator<Instruction> enumerator3 = current2.Body.Instructions.GetEnumerator(); try { while (enumerator3.MoveNext()) { Instruction current3 = enumerator3.Current; if (IsEverybodyLoad(current3)) { current3.OpCode = OpCodes.Ldc_I8; current3.Operand = 0L; patchedEverybody++; } else if (IsOldConsoleCtor(current3)) { list.Add(current3); } } } finally { ((IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } foreach (Instruction item2 in list) { UpgradeConsoleCtor(assembly.MainModule, current2, item2); patchedConsole++; } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } } private static void VerifySkillLimitExtenderVersion(AssemblyDefinition assembly) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) TypeDefinition val = FindType((IEnumerable<TypeDefinition>)assembly.MainModule.Types, "SkillLimitExtender.VersionInfo"); if (val == null) { throw new InvalidOperationException("Safety stop: SkillLimitExtender.VersionInfo was not found."); } Enumerator<FieldDefinition> enumerator = val.Fields.GetEnumerator(); try { while (enumerator.MoveNext()) { FieldDefinition current = enumerator.Current; if (((MemberReference)current).Name == "Version" && current.HasConstant && current.Constant is string && (string)current.Constant == "1.2.0") { return; } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } throw new InvalidOperationException("Safety stop: this compatibility patch only supports SkillLimitExtender 1.2.0."); } private static string ComputeSha256(byte[] data) { using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(data); return BitConverter.ToString(array).Replace("-", string.Empty); } private static TypeDefinition FindType(IEnumerable<TypeDefinition> roots, string fullName) { foreach (TypeDefinition root in roots) { if (((MemberReference)root).FullName == fullName) { return root; } TypeDefinition val = FindType((IEnumerable<TypeDefinition>)root.NestedTypes, fullName); if (val != null) { return val; } } return null; } private static IEnumerable<TypeDefinition> AllTypes(IEnumerable<TypeDefinition> roots) { foreach (TypeDefinition type in roots) { yield return type; foreach (TypeDefinition item in AllTypes((IEnumerable<TypeDefinition>)type.NestedTypes)) { yield return item; } } } private static bool IsEverybodyLoad(Instruction ins) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) if (ins.OpCode != OpCodes.Ldsfld) { return false; } object operand = ins.Operand; FieldReference val = (FieldReference)((operand is FieldReference) ? operand : null); if (val != null && ((MemberReference)val).DeclaringType != null && ((MemberReference)((MemberReference)val).DeclaringType).FullName == "ZRoutedRpc" && ((MemberReference)val).Name == "Everybody" && val.FieldType != null) { return ((MemberReference)val.FieldType).FullName == "System.Int64"; } return false; } private static bool IsOldConsoleCtor(Instruction ins) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) if (ins.OpCode != OpCodes.Newobj) { return false; } object operand = ins.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); if (val == null || ((MemberReference)val).Name != ".ctor" || ((MemberReference)val).DeclaringType == null || ((MemberReference)((MemberReference)val).DeclaringType).FullName != "Terminal/ConsoleCommand" || val.Parameters.Count != 12) { return false; } string[] array = new string[12] { "System.String", "System.String", "Terminal/ConsoleEvent", "System.Boolean", "System.Boolean", "System.Boolean", "System.Boolean", "System.Boolean", "Terminal/ConsoleOptionsFetcher", "System.Boolean", "System.Boolean", "System.Boolean" }; for (int i = 0; i < array.Length; i++) { if (((MemberReference)((ParameterReference)val.Parameters[i]).ParameterType).FullName != array[i]) { return false; } } return true; } private static void UpgradeConsoleCtor(ModuleDefinition module, MethodDefinition method, Instruction call) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Expected O, but got Unknown //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Expected O, but got Unknown //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Expected O, but got Unknown //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Expected O, but got Unknown MethodReference val = (MethodReference)call.Operand; List<VariableDefinition> list = new List<VariableDefinition>(); Enumerator<ParameterDefinition> enumerator = val.Parameters.GetEnumerator(); try { while (enumerator.MoveNext()) { ParameterDefinition current = enumerator.Current; VariableDefinition val2 = new VariableDefinition(module.ImportReference(((ParameterReference)current).ParameterType)); method.Body.Variables.Add(val2); list.Add(val2); } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } method.Body.InitLocals = true; List<Instruction> list2 = new List<Instruction>(); for (int num = list.Count - 1; num >= 0; num--) { list2.Add(Instruction.Create(OpCodes.Stloc, list[num])); } for (int i = 0; i <= 7; i++) { list2.Add(Instruction.Create(OpCodes.Ldloc, list[i])); } list2.Add(Instruction.Create(OpCodes.Ldc_I4_0)); for (int j = 8; j < list.Count; j++) { list2.Add(Instruction.Create(OpCodes.Ldloc, list[j])); } MethodReference val3 = new MethodReference(".ctor", module.TypeSystem.Void, module.ImportReference(((MemberReference)val).DeclaringType)); val3.HasThis = true; val3.ExplicitThis = false; val3.CallingConvention = val.CallingConvention; for (int k = 0; k <= 7; k++) { val3.Parameters.Add(new ParameterDefinition(module.ImportReference(((ParameterReference)val.Parameters[k]).ParameterType))); } val3.Parameters.Add(new ParameterDefinition(module.TypeSystem.Boolean)); for (int l = 8; l < val.Parameters.Count; l++) { val3.Parameters.Add(new ParameterDefinition(module.ImportReference(((ParameterReference)val.Parameters[l]).ParameterType))); } ILProcessor iLProcessor = method.Body.GetILProcessor(); Instruction newTarget = list2[0]; foreach (Instruction item in list2) { iLProcessor.InsertBefore(call, item); } RedirectTargets(method, call, newTarget); call.Operand = val3; } private static void RedirectTargets(MethodDefinition method, Instruction oldTarget, Instruction newTarget) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) Enumerator<Instruction> enumerator = method.Body.Instructions.GetEnumerator(); try { while (enumerator.MoveNext()) { Instruction current = enumerator.Current; if (object.ReferenceEquals(current, newTarget)) { continue; } object operand = current.Operand; Instruction objA = (Instruction)((operand is Instruction) ? operand : null); if (object.ReferenceEquals(objA, oldTarget)) { current.Operand = newTarget; } else { if (!(current.Operand is Instruction[] array)) { continue; } for (int i = 0; i < array.Length; i++) { if (object.ReferenceEquals(array[i], oldTarget)) { array[i] = newTarget; } } } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } Enumerator<ExceptionHandler> enumerator2 = method.Body.ExceptionHandlers.GetEnumerator(); try { while (enumerator2.MoveNext()) { ExceptionHandler current2 = enumerator2.Current; if (object.ReferenceEquals(current2.TryStart, oldTarget)) { current2.TryStart = newTarget; } if (object.ReferenceEquals(current2.HandlerStart, oldTarget)) { current2.HandlerStart = newTarget; } if (object.ReferenceEquals(current2.FilterStart, oldTarget)) { current2.FilterStart = newTarget; } } } finally { ((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } } }