Decompiled source of Network Crash Fix v0.1.1

NetworkCrashFix.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PurrNet.Packing;
using PurrNet.Transports;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("NetworkCrashFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1+fb81219d84911970e13790bb8a26803c6c3fbfcf")]
[assembly: AssemblyProduct("NetworkCrashFix")]
[assembly: AssemblyTitle("NetworkCrashFix")]
[assembly: AssemblyVersion("0.1.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 NetworkCrashFix
{
	[BepInPlugin("nelphei.ontogether.networkcrashfix", "Network Crash Fix", "0.1.1")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "nelphei.ontogether.networkcrashfix";

		public const string PluginName = "Network Crash Fix";

		public const string PluginVersion = "0.1.1";

		internal static ManualLogSource Log;

		internal static Settings Settings;

		private Harmony _harmony;

		private GameObject _host;

		private void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Settings = new Settings(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("nelphei.ontogether.networkcrashfix");
			bool num = SafeReads.Apply(_harmony);
			_host = new GameObject("NetworkCrashFix");
			((Object)_host).hideFlags = (HideFlags)61;
			Object.DontDestroyOnLoad((Object)(object)_host);
			_host.AddComponent<Reporter>();
			if (num)
			{
				Log.LogInfo((object)"Network Crash Fix 0.1.1 loaded. The end of each network packet is read safely.");
			}
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			if ((Object)(object)_host != (Object)null)
			{
				Object.Destroy((Object)(object)_host);
			}
		}
	}
	internal sealed class Reporter : MonoBehaviour
	{
		private const float ReportSeconds = 300f;

		private bool _loggedFirst;

		private long _reported;

		private float _nextReport = 300f;

		private void Update()
		{
			long count = SafeReads.Count;
			if (!_loggedFirst && count > 0)
			{
				_loggedFirst = true;
				Plugin.Log.LogInfo((object)"Made the first read at the end of a network packet safe. Without the fix, reads like this could crash the game.");
			}
			if (Time.unscaledTime >= _nextReport)
			{
				_nextReport = Time.unscaledTime + 300f;
				if (Plugin.Settings.DebugLog.Value && count > _reported)
				{
					Plugin.Log.LogInfo((object)($"Made {count - _reported} reads at the end of a packet safe in the last " + $"{5f:0} minutes."));
				}
				_reported = count;
			}
		}
	}
	internal static class SafeReads
	{
		public static long Count;

		private static FieldRef<BitPacker, byte[]> _buffer;

		private static FieldRef<BitPacker, int> _position;

		public static bool Apply(Harmony harmony)
		{
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Expected O, but got Unknown
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(BitPacker), "ReadBits", new Type[1] { typeof(byte) }, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(BitPacker), "ReadBitsWithoutChecks", new Type[1] { typeof(byte) }, (Type[])null);
			if (methodInfo == null || methodInfo2 == null)
			{
				Plugin.Log.LogError((object)"Couldn't find the game's packet reader (a game update may have changed it), so the fix is off.");
				return false;
			}
			try
			{
				_buffer = AccessTools.FieldRefAccess<BitPacker, byte[]>("_buffer");
				_position = AccessTools.FieldRefAccess<BitPacker, int>("_positionInBits");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Couldn't reach the packet reader's data (" + ex.Message + "), so the fix is off."));
				return false;
			}
			if (!MatchesGame(out var problem))
			{
				Plugin.Log.LogError((object)("The fix doesn't read the same values as the game (" + problem + "), so it's off."));
				return false;
			}
			harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(SafeReads), "WithoutChecksPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(SafeReads), "ReadBitsPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			CheckPatched();
			return true;
		}

		private static bool WithoutChecksPrefix(BitPacker __instance, byte bits, ref ulong __result)
		{
			byte[] array = _buffer.Invoke(__instance);
			int num = _position.Invoke(__instance);
			int num2 = num >> 3;
			if (array == null || num2 + 9 <= array.Length || num2 >= array.Length)
			{
				return true;
			}
			__result = Read(array, array.Length, num, bits);
			_position.Invoke(__instance) = num + bits;
			Count++;
			return false;
		}

		private static bool ReadBitsPrefix(BitPacker __instance, byte bits, ref ulong __result)
		{
			byte[] array = _buffer.Invoke(__instance);
			int num = _position.Invoke(__instance);
			int num2 = num >> 3;
			if (array == null || num2 + 9 <= array.Length)
			{
				return true;
			}
			__instance.EnsureBitsExist((int)bits);
			array = _buffer.Invoke(__instance);
			if (num2 >= array.Length)
			{
				return true;
			}
			__result = Read(array, array.Length, num, bits);
			_position.Invoke(__instance) = num + bits;
			Count++;
			return false;
		}

		internal static ulong Read(byte[] buffer, int length, int position, byte bits)
		{
			int num = position >> 3;
			int num2 = position & 7;
			int num3 = Math.Min(8, length - num);
			ulong num4 = 0uL;
			for (int i = 0; i < num3; i++)
			{
				num4 |= (ulong)buffer[num + i] << i * 8;
			}
			ulong num5 = num4 >> num2;
			if (bits + num2 - 64 > 0 && num + 8 < length)
			{
				num5 |= (ulong)buffer[num + 8] << 64 - num2;
			}
			ulong num6 = (ulong)((bits == 64) ? (-1) : ((1L << (int)bits) - 1));
			return num5 & num6;
		}

		private static bool MatchesGame(out string problem)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			Random random = new Random(1234);
			byte[] array = new byte[48];
			random.NextBytes(array);
			BitPacker val = new BitPacker(0);
			val.MakeWrapper(new ByteData(array, 0, array.Length));
			for (int i = 0; (i >> 3) + 9 <= array.Length; i++)
			{
				for (int j = 0; j <= 64; j++)
				{
					val.SetBitPosition(i);
					ulong num = val.ReadBitsWithoutChecks((byte)j);
					int length = i + j + 7 >> 3;
					if (Read(array, array.Length, i, (byte)j) != num || Read(array, length, i, (byte)j) != num)
					{
						problem = $"{j} bits from bit {i}";
						return false;
					}
				}
			}
			problem = null;
			return true;
		}

		private static void CheckPatched()
		{
			//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)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: 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_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				byte[] array = new byte[5] { 165, 60, 240, 15, 153 };
				BitPacker val = new BitPacker(0);
				val.MakeWrapper(new ByteData(array, 0, array.Length));
				val.ResetMode(true);
				long count = Count;
				val.SetBitPosition(3);
				ulong num = val.ReadBits((byte)29);
				bool flag = Count > count && num == Read(array, array.Length, 3, 29);
				count = Count;
				val.SetBitPosition(5);
				byte[] array2 = new byte[4];
				val.ReadBytes((Span<byte>)array2);
				bool flag2 = Count >= count + array2.Length;
				for (int i = 0; i < array2.Length; i++)
				{
					flag2 &= array2[i] == (byte)Read(array, array.Length, 5 + 8 * i, 8);
				}
				if (!flag)
				{
					Plugin.Log.LogWarning((object)"Checked the fix: ReadBits doesn't go through it on this system, so the crash can still happen.");
				}
				if (!flag2)
				{
					Plugin.Log.LogWarning((object)("Checked the fix: ReadBytes doesn't go through it on this system. ReadBits (where the crashes happened) " + (flag ? "does." : "doesn't either.")));
				}
				if (flag && flag2)
				{
					Plugin.Log.LogInfo((object)"Checked the fix: the game's ReadBits and ReadBytes both go through it and read the same values as before.");
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"Couldn't check the fix after patching: {arg}");
			}
			finally
			{
				Count = 0L;
			}
		}
	}
	internal sealed class Settings
	{
		public readonly ConfigEntry<bool> DebugLog;

		public Settings(ConfigFile config)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			DebugLog = config.Bind<bool>("Debug", "DebugLog", false, "Every 5 minutes, write to the BepInEx log how many reads at the end of a packet the fix made safe.");
			RemoveOld(config, new ConfigDefinition("General", "Enabled"), new ConfigDefinition("Display", "ShowIcon"));
		}

		private static void RemoveOld(ConfigFile config, params ConfigDefinition[] old)
		{
			try
			{
				if (AccessTools.Property(typeof(ConfigFile), "OrphanedEntries")?.GetValue(config) is Dictionary<ConfigDefinition, string> dictionary)
				{
					bool flag = false;
					foreach (ConfigDefinition key in old)
					{
						flag |= dictionary.Remove(key);
					}
					if (flag)
					{
						config.Save();
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogDebug((object)("Couldn't tidy old settings out of the settings file: " + ex.Message));
			}
		}
	}
}