Decompiled source of SkillLimitExtenderValheim1Compat v1.0.2

patchers\SkillLimitExtenderValheim1Compat\SkillLimitExtenderValheim1Compat.Patcher.dll

Decompiled 2 days ago
using 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();
		}
	}
}