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));
}
}
}
}