Decompiled source of ConditionalConfigSyncOptionalPatch v1.0.0

patchers/ConditionalConfigSyncOptionalPatch/ConditionalConfigSyncOptionalPatch.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using BepInEx;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("ConditionalConfigSyncOptionalPatch")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ConditionalConfigSyncOptionalPatch")]
[assembly: AssemblyTitle("ConditionalConfigSyncOptionalPatch")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ConditionalConfigSyncOptionalPatch
{
	public static class Patcher
	{
		private sealed class PatchSummary
		{
			public int ModRequiredAssignments;
		}

		private enum PatchStatus
		{
			NotApplicable,
			Patched,
			AlreadyPatched,
			SafetyStop
		}

		private sealed class PatchResult
		{
			public PatchStatus Status { get; }

			public string Message { get; }

			private PatchResult(PatchStatus status, string message)
			{
				Status = status;
				Message = message;
			}

			public static PatchResult Patched()
			{
				return new PatchResult(PatchStatus.Patched, string.Empty);
			}

			public static PatchResult AlreadyPatched()
			{
				return new PatchResult(PatchStatus.AlreadyPatched, string.Empty);
			}

			public static PatchResult NotApplicable(string message)
			{
				return new PatchResult(PatchStatus.NotApplicable, message);
			}

			public static PatchResult SafetyStop(string message)
			{
				return new PatchResult(PatchStatus.SafetyStop, message);
			}

			public static PatchResult SafetyStop(Exception exception)
			{
				return new PatchResult(PatchStatus.SafetyStop, exception.ToString());
			}
		}

		private const string TargetAssembly = "assembly_valheim.dll";

		private const string PatcherDllName = "ConditionalConfigSyncOptionalPatch.dll";

		private const string ConditionalConfigSyncPluginName = "ConditionalConfigSync.Plugin.dll";

		private const string ConditionalConfigSyncDllName = "ConditionalConfigSync.dll";

		private const string BackupSuffix = ".ccs-original.bak";

		private const string MarkerSuffix = ".ccs-patched.sha256";

		private static readonly ManualLogSource Log = Logger.CreateLogSource("CCS Optional Patch");

		public static IEnumerable<string> TargetDLLs
		{
			get
			{
				InstallCompatibilityPatches();
				yield return "assembly_valheim.dll";
			}
		}

		public static void Patch(AssemblyDefinition assembly)
		{
		}

		private static void InstallCompatibilityPatches()
		{
			string text = Path.Combine(Paths.BepInExRootPath, "plugins");
			if (!Directory.Exists(text))
			{
				throw new DirectoryNotFoundException("BepInEx plugin directory was not found: " + text);
			}
			Log.LogInfo((object)("Scanning BepInEx plugins for ConditionalConfigSync ModRequired assignments: " + text));
			string[] files = Directory.GetFiles(text, "*.dll", SearchOption.AllDirectories);
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			string[] array = files;
			foreach (string text2 in array)
			{
				string fileName = Path.GetFileName(text2);
				if (string.Equals(fileName, "ConditionalConfigSyncOptionalPatch.dll", StringComparison.OrdinalIgnoreCase))
				{
					Log.LogInfo((object)("Skipping patcher itself: " + text2));
					num4++;
					continue;
				}
				if (string.Equals(fileName, "ConditionalConfigSync.dll", StringComparison.OrdinalIgnoreCase))
				{
					Log.LogInfo((object)("Skipping ConditionalConfigSync library: " + text2));
					num4++;
					continue;
				}
				if (string.Equals(fileName, "ConditionalConfigSync.Plugin.dll", StringComparison.OrdinalIgnoreCase))
				{
					Log.LogInfo((object)("Skipping ConditionalConfigSync plugin: " + text2));
					num4++;
					continue;
				}
				num++;
				Log.LogInfo((object)("Examining plugin: " + text2));
				PatchResult patchResult = PatchPlugin(text2);
				switch (patchResult.Status)
				{
				case PatchStatus.Patched:
					num2++;
					break;
				case PatchStatus.AlreadyPatched:
					num3++;
					break;
				case PatchStatus.SafetyStop:
					throw new InvalidOperationException(patchResult.Message);
				}
			}
			Log.LogInfo((object)("ConfigSync scan complete. " + $"Examined: {num}, " + $"patched: {num2}, " + $"already patched: {num3}, " + $"skipped: {num4}."));
		}

		private static PatchResult PatchPlugin(string dllPath)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			string fileName = Path.GetFileName(dllPath);
			string path = dllPath + ".ccs-patched.sha256";
			string text = dllPath + ".ccs-original.bak";
			string b = ComputeSha256(dllPath);
			if (File.Exists(path) && string.Equals(File.ReadAllText(path).Trim(), b, StringComparison.OrdinalIgnoreCase))
			{
				Log.LogInfo((object)(fileName + " is already patched."));
				return PatchResult.AlreadyPatched();
			}
			ModuleDefinition val;
			try
			{
				val = ModuleDefinition.ReadModule(dllPath, new ReaderParameters
				{
					InMemory = true
				});
			}
			catch (Exception ex)
			{
				Log.LogInfo((object)(fileName + ": could not read assembly: " + ex.Message));
				return PatchResult.NotApplicable(fileName + ": could not read assembly.");
			}
			try
			{
				PatchSummary patchSummary = PatchModule(val, fileName);
				if (patchSummary.ModRequiredAssignments == 0)
				{
					Log.LogInfo((object)(fileName + ": no matching ModRequired assignment found."));
					return PatchResult.NotApplicable(fileName + ": no matching ModRequired assignment found.");
				}
				Log.LogInfo((object)(fileName + ": found " + $"{patchSummary.ModRequiredAssignments} " + "ModRequired=true assignment(s)."));
				string text2 = dllPath + ".ccs.tmp";
				if (File.Exists(text2))
				{
					File.Delete(text2);
				}
				try
				{
					val.Write(text2);
				}
				catch
				{
					if (File.Exists(text2))
					{
						File.Delete(text2);
					}
					throw;
				}
				if (!File.Exists(text))
				{
					File.Copy(dllPath, text, overwrite: false);
					Log.LogInfo((object)(fileName + ": original backed up to " + Path.GetFileName(text)));
				}
				try
				{
					File.Copy(text2, dllPath, overwrite: true);
				}
				catch
				{
					if (File.Exists(text))
					{
						File.Copy(text, dllPath, overwrite: true);
					}
					throw;
				}
				finally
				{
					if (File.Exists(text2))
					{
						File.Delete(text2);
					}
				}
				string text3 = ComputeSha256(dllPath);
				File.WriteAllText(path, text3 + Environment.NewLine);
				Log.LogInfo((object)(fileName + ": PATCHED " + $"{patchSummary.ModRequiredAssignments} " + "ModRequired=true assignment(s) to false."));
				Log.LogInfo((object)(fileName + ": patched SHA256 = " + text3));
				return PatchResult.Patched();
			}
			catch (InvalidOperationException ex2)
			{
				return PatchResult.SafetyStop(fileName + ": " + ex2.Message);
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"{fileName}: patching failed: {arg}");
				return PatchResult.NotApplicable(fileName + ": patching failed.");
			}
			finally
			{
				val.Dispose();
			}
		}

		private static PatchSummary PatchModule(ModuleDefinition module, string fileName)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0241: Unknown result type (might be due to invalid IL or missing references)
			PatchSummary patchSummary = new PatchSummary();
			foreach (TypeDefinition item in GetTypeTree((IEnumerable<TypeDefinition>)module.Types))
			{
				Enumerator<MethodDefinition> enumerator2 = item.Methods.GetEnumerator();
				try
				{
					while (enumerator2.MoveNext())
					{
						MethodDefinition current = enumerator2.Current;
						if (!current.HasBody)
						{
							continue;
						}
						IList<Instruction> instructions = (IList<Instruction>)current.Body.Instructions;
						for (int i = 0; i < instructions.Count; i++)
						{
							Instruction val = instructions[i];
							if (!IsModRequiredSetterCall(val))
							{
								continue;
							}
							Log.LogInfo((object)(fileName + ": FOUND ModRequired setter"));
							Log.LogInfo((object)("  Method: " + ((MemberReference)current).FullName));
							Log.LogInfo((object)$"  IL: {val}");
							object operand = val.Operand;
							MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null);
							if (val2 != null)
							{
								Log.LogInfo((object)("  Setter: " + ((MemberReference)val2).FullName));
								Log.LogInfo((object)("  Declaring type: " + ((MemberReference)((MemberReference)val2).DeclaringType).FullName));
								Log.LogInfo((object)("  Declaring type scope: " + $"{((MemberReference)val2).DeclaringType.Scope}"));
								Log.LogInfo((object)("  Return type: " + ((MemberReference)val2.ReturnType).FullName));
								Log.LogInfo((object)("  Parameter count: " + $"{val2.Parameters.Count}"));
								if (val2.Parameters.Count == 1)
								{
									Log.LogInfo((object)("  Parameter type: " + ((MemberReference)((ParameterReference)val2.Parameters[0]).ParameterType).FullName));
								}
							}
							if (i == 0)
							{
								throw new InvalidOperationException("Found ModRequired setter in " + ((MemberReference)current).FullName + ", but it has no preceding instruction.");
							}
							Instruction val3 = instructions[i - 1];
							Log.LogInfo((object)("  Previous IL: " + $"{val3}"));
							if (!IsBooleanTrue(val3))
							{
								Log.LogWarning((object)"  ModRequired setter found, but the preceding instruction does not load boolean true.");
								continue;
							}
							Log.LogInfo((object)($"  Changing {val3} " + "to ldc.i4.0"));
							val3.OpCode = OpCodes.Ldc_I4_0;
							val3.Operand = null;
							patchSummary.ModRequiredAssignments++;
							Log.LogInfo((object)("  PATCHED " + ((MemberReference)current).FullName + " " + $"at IL offset 0x{val.Offset:X4}"));
						}
					}
				}
				finally
				{
					((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose();
				}
			}
			return patchSummary;
		}

		private static bool IsModRequiredSetterCall(Instruction instruction)
		{
			//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)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			if (instruction.OpCode != OpCodes.Call && instruction.OpCode != OpCodes.Callvirt)
			{
				return false;
			}
			object operand = instruction.Operand;
			MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null);
			if (val == null)
			{
				return false;
			}
			if (!string.Equals(((MemberReference)val).Name, "set_ModRequired", StringComparison.Ordinal))
			{
				return false;
			}
			if (!string.Equals(((MemberReference)val.ReturnType).FullName, "System.Void", StringComparison.Ordinal))
			{
				return false;
			}
			if (val.Parameters.Count != 1)
			{
				return false;
			}
			if (!string.Equals(((MemberReference)((ParameterReference)val.Parameters[0]).ParameterType).FullName, "System.Boolean", StringComparison.Ordinal))
			{
				return false;
			}
			return true;
		}

		private static bool IsBooleanTrue(Instruction instruction)
		{
			//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)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			if (instruction.OpCode == OpCodes.Ldc_I4_1)
			{
				return true;
			}
			if (instruction.OpCode == OpCodes.Ldc_I4 && instruction.Operand is int num && num == 1)
			{
				return true;
			}
			return false;
		}

		private static IEnumerable<TypeDefinition> GetTypeTree(IEnumerable<TypeDefinition> types)
		{
			foreach (TypeDefinition type in types)
			{
				yield return type;
				foreach (TypeDefinition item in GetTypeTree((IEnumerable<TypeDefinition>)type.NestedTypes))
				{
					yield return item;
				}
			}
		}

		private static string ComputeSha256(string path)
		{
			using SHA256 sHA = SHA256.Create();
			using FileStream inputStream = File.OpenRead(path);
			return BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", string.Empty);
		}
	}
}