Decompiled source of TechtonicaDirectConnect v1.0.103
plugins/TechtonicaDirectConnect.dll
Decompiled 7 months ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Mirror; using Mirror.FizzySteam; using UnityEngine; using UnityEngine.SceneManagement; using kcp2k; [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: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 TechtonicaDirectConnect { public static class InventorySyncPatches { private static bool _patchesApplied; private static object _lastHitDestructibleAction; private static object _lastMOLEAction; private static object _lastTakeAllAction; private static ManualLogSource Log => Plugin.Log; public static void ApplyPatches(Harmony harmony) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Expected O, but got Unknown //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Expected O, but got Unknown //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Expected O, but got Unknown //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Expected O, but got Unknown //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Expected O, but got Unknown //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Expected O, but got Unknown //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_0347: Expected O, but got Unknown //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_0394: Expected O, but got Unknown if (_patchesApplied) { return; } try { MethodInfo methodInfo = AccessTools.Method("TakeAllInfo:ProcessOnClient", (Type[])null, (Type[])null); if (methodInfo != null) { HarmonyMethod val = new HarmonyMethod(typeof(InventorySyncPatches), "TakeAllInfo_ProcessOnClient_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"[InventorySync] Patched TakeAllInfo.ProcessOnClient"); } MethodInfo methodInfo2 = AccessTools.Method("HitDestructibleInfo:ProcessOnClient", (Type[])null, (Type[])null); if (methodInfo2 != null) { HarmonyMethod val2 = new HarmonyMethod(typeof(InventorySyncPatches), "HitDestructibleInfo_ProcessOnClient_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo2, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"[InventorySync] Patched HitDestructibleInfo.ProcessOnClient"); } MethodInfo methodInfo3 = AccessTools.Method("MOLEActionInfo:ProcessOnClient", (Type[])null, (Type[])null); if (methodInfo3 != null) { HarmonyMethod val3 = new HarmonyMethod(typeof(InventorySyncPatches), "MOLEActionInfo_ProcessOnClient_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo3, val3, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"[InventorySync] Patched MOLEActionInfo.ProcessOnClient (prefix)"); } MethodInfo methodInfo4 = AccessTools.Method("CraftActionInfo:ProcessOnClient", (Type[])null, (Type[])null); if (methodInfo4 == null) { methodInfo4 = AccessTools.Method("CraftInfo:ProcessOnClient", (Type[])null, (Type[])null); } if (methodInfo4 != null) { HarmonyMethod val4 = new HarmonyMethod(typeof(InventorySyncPatches), "CraftInfo_ProcessOnClient_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo4, val4, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)("[InventorySync] Patched " + methodInfo4.DeclaringType.Name + ".ProcessOnClient")); } else { Log.LogWarning((object)"[InventorySync] CraftActionInfo/CraftInfo.ProcessOnClient not found"); } MethodInfo methodInfo5 = AccessTools.Method("NetworkBuildInfo:ProcessOnClient", (Type[])null, (Type[])null); if (methodInfo5 != null) { HarmonyMethod val5 = new HarmonyMethod(typeof(InventorySyncPatches), "SimpleBuildInfo_ProcessOnClient_Postfix", (Type[])null); harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, val5, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)("[InventorySync] Patched " + methodInfo5.DeclaringType.Name + ".ProcessOnClient")); } else { Log.LogWarning((object)"[InventorySync] NetworkBuildInfo.ProcessOnClient not found"); } MethodInfo methodInfo6 = AccessTools.Method("ExchangeMachineActionInfo:ProcessOnClient", (Type[])null, (Type[])null); if (methodInfo6 == null) { methodInfo6 = AccessTools.Method("ExchangeMachineInfo:ProcessOnClient", (Type[])null, (Type[])null); } if (methodInfo6 != null) { HarmonyMethod val6 = new HarmonyMethod(typeof(InventorySyncPatches), "ExchangeMachineInfo_ProcessOnClient_Postfix", (Type[])null); harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, val6, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)("[InventorySync] Patched " + methodInfo6.DeclaringType.Name + ".ProcessOnClient")); } else { Log.LogWarning((object)"[InventorySync] ExchangeMachineActionInfo/ExchangeMachineInfo.ProcessOnClient not found"); } MethodInfo methodInfo7 = AccessTools.Method("NetworkMessageRelay:UserCode_HitDestructableResponse", (Type[])null, (Type[])null); if (methodInfo7 != null) { HarmonyMethod val7 = new HarmonyMethod(typeof(InventorySyncPatches), "HitDestructableResponse_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo7, val7, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"[InventorySync] Patched NetworkMessageRelay.UserCode_HitDestructableResponse"); } else { Log.LogWarning((object)"[InventorySync] UserCode_HitDestructableResponse not found"); } MethodInfo methodInfo8 = AccessTools.Method("NetworkMessageRelay:UserCode_HitVoxelResponse", (Type[])null, (Type[])null); if (methodInfo8 != null) { HarmonyMethod val8 = new HarmonyMethod(typeof(InventorySyncPatches), "HitVoxelResponse_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo8, val8, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"[InventorySync] Patched NetworkMessageRelay.UserCode_HitVoxelResponse"); } MethodInfo methodInfo9 = AccessTools.Method("NetworkMessageRelay:UserCode_MOLEResponse", (Type[])null, (Type[])null); if (methodInfo9 != null) { HarmonyMethod val9 = new HarmonyMethod(typeof(InventorySyncPatches), "MOLEResponse_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo9, val9, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"[InventorySync] Patched NetworkMessageRelay.UserCode_MOLEResponse"); } MethodInfo methodInfo10 = AccessTools.Method("NetworkMessageRelay:UserCode_TakeAllResponse", (Type[])null, (Type[])null); if (methodInfo10 != null) { HarmonyMethod val10 = new HarmonyMethod(typeof(InventorySyncPatches), "TakeAllResponse_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo10, val10, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"[InventorySync] Patched NetworkMessageRelay.UserCode_TakeAllResponse"); } PatchIncomingActionRPCs(harmony); _patchesApplied = true; Log.LogInfo((object)"[InventorySync] All inventory sync patches applied successfully"); } catch (Exception ex) { Log.LogError((object)("[InventorySync] Error applying patches: " + ex.Message)); } } private static void PatchIncomingActionRPCs(Harmony harmony) { //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Expected O, but got Unknown Type type = AccessTools.TypeByName("NetworkMessageRelay"); if (type == null) { Log.LogWarning((object)"[InventorySync] NetworkMessageRelay type not found, cannot patch incoming RPCs"); return; } string[] obj = new string[20] { "HitDestructable", "ActivateMachine", "MOLEAction", "TakeAllFromMachine", "Craft", "RpcBuildSimpleMachine", "RpcBuildConveyor", "EraseMachine", "RotateMachine", "SetFilter", "HarvestPlant", "ExchangeMachineInventory", "SwapVariant", "Replace", "Shoot", "Ping", "ActivatePower", "DeactivateTech", "TempConveyor", "HitVoxel" }; int num = 0; string[] array = obj; foreach (string text in array) { MethodInfo method = type.GetMethod(text, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { method = type.GetMethod("UserCode_" + text, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (method != null) { try { HarmonyMethod val = new HarmonyMethod(typeof(InventorySyncPatches), "FixIncomingActionTick_Prefix", (Type[])null); harmony.Patch((MethodBase)method, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; Log.LogInfo((object)("[InventorySync] Patched incoming RPC: " + method.Name)); } catch (Exception ex) { Log.LogWarning((object)("[InventorySync] Failed to patch " + text + ": " + ex.Message)); } } } Log.LogInfo((object)$"[InventorySync] Patched {num} incoming action RPCs for tick fix"); } public static void FixIncomingActionTick_Prefix(object __0, MethodBase __originalMethod) { try { Log.LogInfo((object)("[InventorySync] RPC PREFIX CALLED: " + (__originalMethod?.Name ?? "null") + ", arg0=" + (__0?.GetType()?.Name ?? "null"))); if (!NetworkClient.active || NetworkServer.active) { Log.LogInfo((object)"[InventorySync] Skipping - not a client"); return; } if (__0 == null) { Log.LogInfo((object)"[InventorySync] No info object in first parameter"); return; } string name = __0.GetType().Name; Log.LogInfo((object)("[InventorySync] Processing info object of type: " + name)); Type type = AccessTools.TypeByName("MachineManager"); if (type == null) { Log.LogWarning((object)"[InventorySync] MachineManager type not found"); return; } object obj = type.GetField("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj == null) { Log.LogWarning((object)"[InventorySync] MachineManager.instance is null"); return; } FieldInfo field = type.GetField("curTick", BindingFlags.Instance | BindingFlags.Public); if (field == null) { Log.LogWarning((object)"[InventorySync] curTick field not found"); return; } int num = (int)field.GetValue(obj); FieldInfo field2 = __0.GetType().GetField("tick", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field2 != null) { int num2 = (int)field2.GetValue(__0); Log.LogInfo((object)$"[InventorySync] {__originalMethod?.Name}: oldTick={num2}, currentTick={num}"); if (num2 != num) { field2.SetValue(__0, num); Log.LogInfo((object)$"[InventorySync] FIXED incoming {__originalMethod?.Name} tick: {num2} -> {num}"); } } else { Log.LogWarning((object)("[InventorySync] tick field not found on " + name)); } } catch (Exception ex) { Log.LogWarning((object)("[InventorySync] FixIncomingActionTick error: " + ex.Message)); } } public static void StorePendingAction(object action) { if (action != null) { switch (action.GetType().Name) { case "HitDestructibleAction": _lastHitDestructibleAction = action; break; case "MOLEAction": _lastMOLEAction = action; break; case "TakeAllAction": _lastTakeAllAction = action; break; } } } public static bool HitDestructableResponse_Prefix(object[] __args) { try { if (!NetworkClient.active || NetworkServer.active) { return true; } Array array = ((__args.Length > 1) ? (__args[1] as Array) : null); if (array != null && array.Length > 0) { Log.LogInfo((object)$"[InventorySync] HitDestructableResponse: Got {array.Length} items from server, using original handler"); return true; } Log.LogInfo((object)"[InventorySync] HitDestructableResponse: Empty response from headless server, calculating items locally"); Type type = AccessTools.TypeByName("Player"); object obj = null; obj = type?.GetField("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj == null && (Object)(object)NetworkClient.localPlayer != (Object)null) { NetworkIdentity localPlayer = NetworkClient.localPlayer; if ((Object)(object)localPlayer != (Object)null && (Object)(object)((Component)localPlayer).gameObject != (Object)null) { MethodInfo method = typeof(GameObject).GetMethod("GetComponent", new Type[0]); if (method != null && type != null) { obj = method.MakeGenericMethod(type).Invoke(((Component)localPlayer).gameObject, null); if (obj != null) { Log.LogInfo((object)"[InventorySync] Found Player via NetworkClient.localPlayer"); } } } } if (obj == null && type != null) { MethodInfo method2 = typeof(Object).GetMethod("FindObjectOfType", new Type[0]); if (method2 != null) { obj = method2.MakeGenericMethod(type).Invoke(null, null); if (obj != null) { Log.LogInfo((object)"[InventorySync] Found Player via FindObjectOfType"); } } } if (obj == null) { Log.LogWarning((object)"[InventorySync] HitDestructableResponse: Could not find Player through any method"); return false; } object obj2 = type.GetProperty("networkedPlayer")?.GetValue(obj); if (obj2 == null) { Log.LogWarning((object)"[InventorySync] HitDestructableResponse: networkedPlayer is null"); return false; } object obj3 = obj2.GetType().GetField("pendingActions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj2); object obj4 = null; if (obj3 != null && obj3 is IEnumerable enumerable) { foreach (object item in enumerable) { if (item?.GetType()?.Name == "HitDestructibleAction") { obj4 = item.GetType().GetMethod("GetInfo")?.Invoke(item, null); break; } } } if (obj4 == null && _lastHitDestructibleAction != null) { obj4 = _lastHitDestructibleAction.GetType().GetMethod("GetInfo")?.Invoke(_lastHitDestructibleAction, null); } if (obj4 == null) { Log.LogWarning((object)"[InventorySync] HitDestructableResponse: Could not find pending HitDestructible action"); ClearPendingHitDestructible(obj2); return false; } CalculateAndAddHitDestructibleRewards(obj4, obj); ClearPendingHitDestructible(obj2); return false; } catch (Exception ex) { Log.LogWarning((object)("[InventorySync] HitDestructableResponse_Prefix error: " + ex.Message)); return true; } } private static void ClearPendingHitDestructible(object networkedPlayer) { try { networkedPlayer.GetType().GetMethod("ClearPendingAction", BindingFlags.Instance | BindingFlags.Public); _lastHitDestructibleAction = null; } catch { } } private static void CalculateAndAddHitDestructibleRewards(object hitDestructibleInfo, object playerInstance) { try { if (!(hitDestructibleInfo.GetType().GetField("propLookups")?.GetValue(hitDestructibleInfo) is Array array) || array.Length == 0) { Log.LogInfo((object)"[InventorySync] CalculateRewards: No propLookups"); return; } FieldInfo field = hitDestructibleInfo.GetType().GetField("hitStrength"); float num = ((field != null) ? ((float)field.GetValue(hitDestructibleInfo)) : 100f); Type type = AccessTools.TypeByName("PropManager"); object obj = type?.GetField("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj == null) { Log.LogWarning((object)"[InventorySync] CalculateRewards: PropManager.instance is null"); return; } object obj2 = AccessTools.TypeByName("Player").GetProperty("inventory")?.GetValue(playerInstance); if (obj2 == null) { Log.LogWarning((object)"[InventorySync] CalculateRewards: Player.inventory is null"); return; } Type type2 = AccessTools.TypeByName("DestructibleData"); MethodInfo method = type.GetMethod("GetDestructibleData", BindingFlags.Instance | BindingFlags.Public, null, new Type[2] { typeof(int), type2.MakeByRefType() }, null); Type type3 = AccessTools.TypeByName("UIManager"); object obj3 = type3?.GetField("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); object obj4 = (type3?.GetField("hud", BindingFlags.Instance | BindingFlags.Public))?.GetValue(obj3); int num2 = 0; bool flag = default(bool); for (int i = 0; i < array.Length; i++) { object value = array.GetValue(i); FieldInfo field2 = value.GetType().GetField("typeID"); if (field2 == null) { continue; } int num3 = (int)field2.GetValue(value); object[] array2 = new object[2] { num3, null }; object obj5 = method?.Invoke(obj, array2); int num4; if (obj5 is bool) { flag = (bool)obj5; num4 = 1; } else { num4 = 0; } if (((uint)num4 & (flag ? 1u : 0u)) == 0) { continue; } object obj6 = array2[1]; if (obj6 == null) { continue; } MethodInfo method2 = obj6.GetType().GetMethod("WouldBeDestroyed"); bool flag2 = true; if (method2 != null) { flag2 = (bool)method2.Invoke(obj6, new object[2] { value, num }); } if (!flag2 || !(obj6.GetType().GetField("rewards")?.GetValue(obj6) is IList list) || list.Count <= 0) { continue; } foreach (object item in list) { FieldInfo? field3 = item.GetType().GetField("resource"); FieldInfo field4 = item.GetType().GetField("quantity"); object obj7 = field3?.GetValue(item); object obj8 = field4?.GetValue(item); if (obj7 != null && obj8 != null) { MethodInfo method3 = obj2.GetType().GetMethod("AddResources", BindingFlags.Instance | BindingFlags.Public, null, new Type[2] { obj7.GetType(), typeof(int) }, null); if (method3 != null) { method3.Invoke(obj2, new object[2] { obj7, obj8 }); num2++; obj4?.GetType().GetMethod("OnCollectResources")?.Invoke(obj4, new object[2] { obj7, obj8 }); Log.LogInfo((object)$"[InventorySync] Added reward: {obj7} x{obj8}"); } } } } Log.LogInfo((object)$"[InventorySync] CalculateRewards: Added {num2} reward items"); } catch (Exception ex) { Log.LogWarning((object)("[InventorySync] CalculateRewards error: " + ex.Message)); } } public static bool HitVoxelResponse_Prefix(object[] __args) { try { if (!NetworkClient.active || NetworkServer.active) { return true; } Array array = ((__args.Length > 3) ? (__args[3] as Array) : null); if (array != null && array.Length > 0) { return true; } Log.LogInfo((object)"[InventorySync] HitVoxelResponse: Empty response, client will calculate ore locally"); return false; } catch (Exception ex) { Log.LogWarning((object)("[InventorySync] HitVoxelResponse_Prefix error: " + ex.Message)); return true; } } public static bool MOLEResponse_Prefix(object[] __args) { try { if (!NetworkClient.active || NetworkServer.active) { return true; } Array array = ((__args.Length > 1) ? (__args[1] as Array) : null); if (array != null && array.Length > 0) { return true; } Log.LogInfo((object)"[InventorySync] MOLEResponse: Empty response, client handles ore locally"); return false; } catch (Exception ex) { Log.LogWarning((object)("[InventorySync] MOLEResponse_Prefix error: " + ex.Message)); return true; } } public static bool TakeAllResponse_Prefix(object[] __args) { try { if (!NetworkClient.active || NetworkServer.active) { return true; } Array array = ((__args.Length > 1) ? (__args[1] as Array) : null); if (array != null && array.Length > 0) { return true; } Log.LogInfo((object)"[InventorySync] TakeAllResponse: Empty response from headless server"); return false; } catch (Exception ex) { Log.LogWarning((object)("[InventorySync] TakeAllResponse_Prefix error: " + ex.Message)); return true; } } public static bool TakeAllInfo_ProcessOnClient_Prefix(object __instance) { Log.LogInfo((object)("[InventorySync] TakeAllInfo_ProcessOnClient_Prefix CALLED! instance=" + __instance?.GetType()?.Name)); try { if (!NetworkClient.active || NetworkServer.active) { Log.LogInfo((object)$"[InventorySync] TakeAll: Skipping - NetworkClient.active={NetworkClient.active}, NetworkServer.active={NetworkServer.active}"); return true; } MethodInfo method = __instance.GetType().GetMethod("TakeAll"); if (method == null) { Log.LogWarning((object)"[InventorySync] TakeAll: TakeAll method not found!"); return true; } Log.LogInfo((object)"[InventorySync] TakeAll: Calling TakeAll() method..."); object obj = method.Invoke(__instance, null); Log.LogInfo((object)("[InventorySync] TakeAll: TakeAll() returned: " + (obj?.GetType()?.Name ?? "null"))); IList list = obj as IList; if (list == null || list.Count == 0) { Log.LogInfo((object)$"[InventorySync] TakeAll: No items returned (items={list != null}, count={list?.Count ?? 0})"); return false; } Log.LogInfo((object)$"[InventorySync] TakeAll: Got {list.Count} item stacks to add"); Type type = AccessTools.TypeByName("Player"); Log.LogInfo((object)("[InventorySync] TakeAll: playerType=" + (type?.Name ?? "null"))); object obj2 = null; obj2 = type?.GetField("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj2 != null) { Log.LogInfo((object)"[InventorySync] TakeAll: Got player via Player.instance"); } if (obj2 == null && (Object)(object)NetworkClient.localPlayer != (Object)null) { obj2 = ((Component)NetworkClient.localPlayer).gameObject.GetComponent(type); if (obj2 != null) { Log.LogInfo((object)"[InventorySync] TakeAll: Got player via NetworkClient.localPlayer"); } } if (obj2 == null) { obj2 = Object.FindObjectOfType(type); if (obj2 != null) { Log.LogInfo((object)"[InventorySync] TakeAll: Got player via FindObjectOfType"); } } if (obj2 == null) { Log.LogWarning((object)"[InventorySync] TakeAll: Could not find Player instance via any method!"); return false; } object obj3 = type.GetProperty("inventory")?.GetValue(obj2); if (obj3 == null) { Log.LogWarning((object)"[InventorySync] TakeAll: Player.inventory is NULL!"); return false; } Log.LogInfo((object)("[InventorySync] TakeAll: Got inventory of type " + obj3.GetType().Name)); MethodInfo method2 = obj3.GetType().GetMethod("AddResources", BindingFlags.Instance | BindingFlags.Public, null, new Type[2] { AccessTools.TypeByName("ResourceInfo"), typeof(int) }, null); if (method2 == null) { method2 = obj3.GetType().GetMethod("AddResources", BindingFlags.Instance | BindingFlags.Public); Log.LogInfo((object)$"[InventorySync] TakeAll: AddResources fallback lookup = {method2 != null}"); } Log.LogInfo((object)("[InventorySync] TakeAll: AddResources method = " + (method2?.Name ?? "null"))); int num = 0; foreach (object item in list) { PropertyInfo? property = item.GetType().GetProperty("info"); FieldInfo field = item.GetType().GetField("count"); object obj4 = property?.GetValue(item); object obj5 = field?.GetValue(item); Log.LogInfo((object)string.Format("[InventorySync] TakeAll: Item - info={0}, count={1}, infoType={2}", obj4 != null, obj5, obj4?.GetType()?.Name ?? "null")); if (obj4 != null && obj5 != null) { try { method2?.Invoke(obj3, new object[2] { obj4, obj5 }); num++; Log.LogInfo((object)$"[InventorySync] TakeAll: Added item #{num}"); } catch (Exception ex) { Log.LogWarning((object)("[InventorySync] Failed to add resource: " + ex.Message)); } } } Log.LogInfo((object)$"[InventorySync] TakeAll: Added {num} item stacks to inventory"); return false; } catch (Exception ex2) { Log.LogWarning((object)("[InventorySync] TakeAllInfo_Prefix error: " + ex2.Message)); return true; } } public static void HitDestructibleInfo_ProcessOnClient_Prefix(object __instance) { Log.LogInfo((object)("[InventorySync] HitDestructibleInfo_ProcessOnClient_Prefix CALLED! instance=" + __instance?.GetType()?.Name)); try { if (!NetworkClient.active || NetworkServer.active) { Log.LogInfo((object)$"[InventorySync] HitDestructible: Skipping - NetworkClient.active={NetworkClient.active}, NetworkServer.active={NetworkServer.active}"); return; } FieldInfo field = __instance.GetType().GetField("propLookups"); if (field == null) { Log.LogWarning((object)"[InventorySync] HitDestructible: propLookups field not found!"); return; } Array array = field.GetValue(__instance) as Array; if (array == null || array.Length == 0) { Log.LogInfo((object)$"[InventorySync] HitDestructible: No propLookups (null={array == null})"); return; } Log.LogInfo((object)$"[InventorySync] HitDestructible: Processing {array.Length} prop lookups"); Type type = AccessTools.TypeByName("PropManager"); object obj = type?.GetField("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj == null) { Log.LogWarning((object)"[InventorySync] HitDestructible: PropManager.instance is NULL!"); return; } Type type2 = AccessTools.TypeByName("Player"); object obj2 = null; obj2 = type2?.GetField("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj2 == null && (Object)(object)NetworkClient.localPlayer != (Object)null) { obj2 = ((Component)NetworkClient.localPlayer).gameObject.GetComponent(type2); } if (obj2 == null) { obj2 = Object.FindObjectOfType(type2); } if (obj2 == null) { Log.LogWarning((object)"[InventorySync] HitDestructible: Could not find Player!"); return; } Log.LogInfo((object)"[InventorySync] HitDestructible: Got player instance"); object obj3 = type2.GetProperty("inventory")?.GetValue(obj2); if (obj3 == null) { Log.LogWarning((object)"[InventorySync] HitDestructible: Player.inventory is NULL!"); return; } Type type3 = AccessTools.TypeByName("DestructibleData"); MethodInfo methodInfo = null; if (type3 != null) { methodInfo = type.GetMethod("GetDestructibleData", BindingFlags.Instance | BindingFlags.Public, null, new Type[2] { typeof(int), type3.MakeByRefType() }, null); Log.LogInfo((object)$"[InventorySync] HitDestructible: GetDestructibleData(int, out) method found: {methodInfo != null}"); } else { Log.LogWarning((object)"[InventorySync] HitDestructible: DestructibleData type not found"); } int num = 0; bool flag = default(bool); for (int i = 0; i < array.Length; i++) { object value = array.GetValue(i); try { FieldInfo field2 = value.GetType().GetField("typeID"); if (field2 == null) { Log.LogWarning((object)"[InventorySync] HitDestructible: typeID field not found in InstanceLookup"); continue; } int num2 = (int)field2.GetValue(value); object[] array2 = new object[2] { num2, null }; object obj4 = methodInfo?.Invoke(obj, array2); int num3; if (obj4 is bool) { flag = (bool)obj4; num3 = 1; } else { num3 = 0; } if (((uint)num3 & (flag ? 1u : 0u)) == 0) { continue; } object obj5 = array2[1]; if (obj5 == null || !(obj5.GetType().GetField("rewards")?.GetValue(obj5) is IList list) || list.Count <= 0) { continue; } foreach (object item in list) { FieldInfo? field3 = item.GetType().GetField("resource"); FieldInfo field4 = item.GetType().GetField("quantity"); object obj6 = field3?.GetValue(item); object obj7 = field4?.GetValue(item); if (obj6 == null || obj7 == null) { continue; } try { MethodInfo method = obj3.GetType().GetMethod("AddResources", BindingFlags.Instance | BindingFlags.Public, null, new Type[2] { obj6.GetType(), typeof(int) }, null); if (method != null) { method.Invoke(obj3, new object[2] { obj6, obj7 }); num++; } else { Log.LogWarning((object)("[InventorySync] HitDestructible: AddResources method not found for type " + obj6.GetType().Name)); } } catch (Exception ex) { Log.LogWarning((object)("[InventorySync] HitDestructible: AddResources failed: " + ex.Message)); } } } catch { } } if (num > 0) { Log.LogInfo((object)$"[InventorySync] HitDestructible: Added {num} item stacks to inventory"); } } catch (Exception ex2) { Log.LogWarning((object)("[InventorySync] HitDestructibleInfo_Prefix error: " + ex2.Message)); } } public static void MOLEActionInfo_ProcessOnClient_Prefix(object __instance) { try { if (NetworkClient.active && !NetworkServer.active) { FieldInfo field = __instance.GetType().GetField("canMineOre"); if (field != null) { bool flag = (bool)field.GetValue(__instance); field.SetValue(__instance, true); Log.LogInfo((object)$"[InventorySync] MOLE: Set canMineOre={flag}->true (client will check locally)"); } else { Log.LogWarning((object)"[InventorySync] MOLE: canMineOre field not found!"); } FieldInfo field2 = __instance.GetType().GetField("digPos"); if (field2 != null) { object value = field2.GetValue(__instance); Log.LogInfo((object)$"[InventorySync] MOLE: digPos={value}"); } } } catch (Exception ex) { Log.LogWarning((object)("[InventorySync] MOLEActionInfo_Prefix error: " + ex.Message)); } } public static bool CraftInfo_ProcessOnClient_Prefix(object __instance) { Log.LogInfo((object)("[InventorySync] CraftInfo_ProcessOnClient_Prefix CALLED! instance=" + __instance?.GetType()?.Name)); try { if (!NetworkClient.active || NetworkServer.active) { Log.LogInfo((object)"[InventorySync] Craft: Skipping - not remote client"); return true; } FieldInfo[] fields = __instance.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public); string text = string.Join(", ", Array.ConvertAll(fields, (FieldInfo f) => $"{f.Name}={f.GetValue(__instance)}")); Log.LogInfo((object)("[InventorySync] CraftInfo fields: " + text)); Type type = AccessTools.TypeByName("Player"); object obj = type?.GetField("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj == null && (Object)(object)NetworkClient.localPlayer != (Object)null) { obj = ((Component)NetworkClient.localPlayer).gameObject.GetComponent(type); } if (obj == null) { obj = Object.FindObjectOfType(type); } if (obj == null) { Log.LogWarning((object)"[InventorySync] Craft: Could not find Player!"); return true; } object obj2 = type.GetProperty("crafting")?.GetValue(obj); if (obj2 == null) { Log.LogWarning((object)"[InventorySync] Craft: Player.crafting is null!"); return true; } object obj3 = __instance.GetType().GetField("recipe")?.GetValue(__instance); if (obj3 != null) { Log.LogInfo((object)("[InventorySync] Craft: Found recipe of type " + obj3.GetType().Name)); MethodInfo method = obj2.GetType().GetMethod("Craft", BindingFlags.Instance | BindingFlags.Public); if (method != null) { Log.LogInfo((object)"[InventorySync] Craft: Calling Craft method..."); try { method.Invoke(obj2, new object[1] { obj3 }); Log.LogInfo((object)"[InventorySync] Craft: Craft method executed!"); } catch (Exception ex) { Log.LogWarning((object)("[InventorySync] Craft method failed: " + (ex.InnerException?.Message ?? ex.Message))); } } else { MethodInfo[] methods = obj2.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public); string text2 = string.Join(", ", Array.ConvertAll(methods, (MethodInfo m) => m.Name)); Log.LogInfo((object)("[InventorySync] Craft: Available methods: " + text2)); } } return true; } catch (Exception ex2) { Log.LogWarning((object)("[InventorySync] CraftInfo_Prefix error: " + ex2.Message)); return true; } } public static void SimpleBuildInfo_ProcessOnClient_Postfix(object __instance) { try { if (NetworkClient.active && !NetworkServer.active) { Log.LogInfo((object)"[InventorySync] SimpleBuildInfo postfix - build action processed"); FieldInfo field = __instance.GetType().GetField("machineType"); if (!(field == null)) { field.GetValue(__instance); } } } catch (Exception ex) { Log.LogWarning((object)("[InventorySync] SimpleBuildInfo_Postfix error: " + ex.Message)); } } public static void ExchangeMachineInfo_ProcessOnClient_Postfix(object __instance) { try { if (!NetworkClient.active || NetworkServer.active) { return; } FieldInfo field = __instance.GetType().GetField("addingToMachine"); FieldInfo field2 = __instance.GetType().GetField("quantity"); FieldInfo field3 = __instance.GetType().GetField("resID"); if (field == null || field2 == null || field3 == null) { Log.LogWarning((object)"[InventorySync] ExchangeMachine: Required fields not found"); return; } bool num = (bool)field.GetValue(__instance); int num2 = (int)field2.GetValue(__instance); int num3 = (int)field3.GetValue(__instance); if (num) { return; } Log.LogInfo((object)$"[InventorySync] ExchangeMachine: Taking {num2} of resID {num3} from machine"); MethodInfo methodInfo = AccessTools.TypeByName("SaveState")?.GetMethod("GetResInfoFromId", BindingFlags.Static | BindingFlags.Public); if (methodInfo == null) { Log.LogWarning((object)"[InventorySync] ExchangeMachine: GetResInfoFromId not found"); return; } object obj = methodInfo.Invoke(null, new object[1] { num3 }); if (obj == null) { Log.LogWarning((object)$"[InventorySync] ExchangeMachine: No ResourceInfo for resID {num3}"); return; } Type type = AccessTools.TypeByName("Player"); object obj2 = null; obj2 = type?.GetField("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj2 == null && (Object)(object)NetworkClient.localPlayer != (Object)null) { obj2 = ((Component)NetworkClient.localPlayer).gameObject.GetComponent(type); } if (obj2 == null) { obj2 = Object.FindObjectOfType(type); } if (obj2 == null) { Log.LogWarning((object)"[InventorySync] ExchangeMachine: Could not find Player!"); return; } object obj3 = type.GetProperty("inventory")?.GetValue(obj2); if (obj3 == null) { Log.LogWarning((object)"[InventorySync] ExchangeMachine: Player.inventory is null!"); return; } MethodInfo method = obj3.GetType().GetMethod("AddResources", BindingFlags.Instance | BindingFlags.Public, null, new Type[2] { obj.GetType(), typeof(int) }, null); if (method != null) { method.Invoke(obj3, new object[2] { obj, num2 }); Log.LogInfo((object)$"[InventorySync] ExchangeMachine: Added {num2} items to inventory"); } else { Log.LogWarning((object)("[InventorySync] ExchangeMachine: AddResources method not found for type " + obj.GetType().Name)); } } catch (Exception ex) { Log.LogWarning((object)("[InventorySync] ExchangeMachineInfo_Postfix error: " + ex.Message)); } } } [BepInPlugin("com.certifried.techtonicadirectconnect", "Techtonica Direct Connect", "1.0.103")] [BepInProcess("Techtonica.exe")] public class Plugin : BaseUnityPlugin { private static Plugin _instance; private Harmony _harmony; private bool _showConnectUI; private string _serverAddress = ""; private string _serverPort = "6968"; private string _statusMessage = ""; private bool _isConnecting; private GUIStyle _windowStyle; private GUIStyle _labelStyle; private GUIStyle _textFieldStyle; private GUIStyle _buttonStyle; private GUIStyle _statusStyle; private Rect _windowRect = new Rect((float)(Screen.width / 2 - 200), (float)(Screen.height / 2 - 100), 400f, 200f); private static KcpTransport _kcpTransport; private static Transport _originalTransport; private static bool _isDirectConnectActive; private const int VK_F11 = 122; private const int VK_ESCAPE = 27; private bool _f11WasPressed; private bool _escWasPressed; private float _debugTimer; private bool _updateRunning; private int _lastToggleFrame = -1; private string _pendingServerAddress = ""; private int _pendingServerPort = 6968; private static float _loadingStuckTimer = 0f; private static bool _loadingMonitorActive = false; private static string _lastLoadingState = ""; private static bool _finishLoadingCalled = false; private static int _lastLoggedSecond = -1; private static int _updateHeartbeat = 0; private static bool _loadingMonitorDebugLogged = false; private static int _monitorCheckCount = 0; public static ManualLogSource Log { get; private set; } public static ConfigEntry<int> DefaultPort { get; private set; } public static ConfigEntry<string> LastServerAddress { get; private set; } public static ConfigEntry<KeyCode> ConnectHotkey { get; private set; } public static Plugin Instance => _instance; [DllImport("user32.dll")] private static extern short GetAsyncKeyState(int vKey); private void Awake() { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown _instance = this; Log = ((BaseUnityPlugin)this).Logger; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; DefaultPort = ((BaseUnityPlugin)this).Config.Bind<int>("General", "DefaultPort", 6968, "Default server port"); LastServerAddress = ((BaseUnityPlugin)this).Config.Bind<string>("General", "LastServerAddress", "", "Last connected server address"); ConnectHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "ConnectHotkey", (KeyCode)292, "Hotkey to open connect dialog"); _serverAddress = LastServerAddress.Value; _serverPort = DefaultPort.Value.ToString(); _harmony = new Harmony("com.certifried.techtonicadirectconnect"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); NullSafetyPatches.ApplyPatches(_harmony); InventorySyncPatches.ApplyPatches(_harmony); TickFixPatches.ApplyPatches(_harmony); Log.LogInfo((object)"[Techtonica Direct Connect] v1.0.103 loaded!"); Log.LogInfo((object)"[Techtonica Direct Connect] Press F11 to open connect dialog"); } private void Update() { _updateHeartbeat++; if (!_updateRunning) { _updateRunning = true; Log.LogInfo((object)"[DirectConnect] Update() is running!"); } if (_updateHeartbeat % 300 == 0 && NetworkClient.active) { Log.LogInfo((object)$"[DirectConnect] HEARTBEAT: frame={_updateHeartbeat}, monitorActive={_loadingMonitorActive}, timer={_loadingStuckTimer:F1}s, finishCalled={_finishLoadingCalled}"); } bool keyDown = Input.GetKeyDown((KeyCode)292); bool keyDown2 = Input.GetKeyDown((KeyCode)27); bool flag = (GetAsyncKeyState(122) & 0x8000) != 0; bool flag2 = (GetAsyncKeyState(27) & 0x8000) != 0; _debugTimer += Time.deltaTime; if (_debugTimer > 5f) { _debugTimer = 0f; Log.LogInfo((object)$"[DirectConnect] F11: Unity={keyDown}, Win={flag}, UI={_showConnectUI}"); } if ((keyDown || (flag && !_f11WasPressed)) && Time.frameCount != _lastToggleFrame) { _lastToggleFrame = Time.frameCount; _showConnectUI = !_showConnectUI; if (_showConnectUI) { _statusMessage = ""; } Log.LogInfo((object)$"[DirectConnect] UI toggled via Update: {_showConnectUI}"); } _f11WasPressed = flag; bool flag3 = keyDown2 || (flag2 && !_escWasPressed); if (_showConnectUI && flag3) { _showConnectUI = false; Log.LogInfo((object)"[DirectConnect] UI closed via ESC"); } _escWasPressed = flag2; CheckLoadingMonitor(); } private void CheckLoadingMonitor() { _monitorCheckCount++; if (!NetworkClient.active) { if (_monitorCheckCount % 300 == 0 && _loadingMonitorActive) { Log.LogWarning((object)"[DirectConnect] Monitor: NetworkClient not active!"); } return; } if (_finishLoadingCalled) { if (_monitorCheckCount % 300 == 0) { Log.LogInfo((object)"[DirectConnect] Monitor: finish already called, skipping"); } return; } try { Type type = AccessTools.TypeByName("LoadingUI"); if (type == null) { if (!_loadingMonitorDebugLogged) { Log.LogWarning((object)"[DirectConnect] LoadingUI type not found!"); _loadingMonitorDebugLogged = true; } return; } object obj = null; FieldInfo fieldInfo = AccessTools.Field(type, "instance"); if (fieldInfo != null) { obj = fieldInfo.GetValue(null); } if (obj == null) { PropertyInfo propertyInfo = AccessTools.Property(type, "Instance"); if (propertyInfo != null) { obj = propertyInfo.GetValue(null); } } if (obj == null) { obj = Object.FindObjectOfType(type); } if (obj == null) { if (_loadingMonitorActive && _monitorCheckCount % 60 == 0) { Log.LogWarning((object)"[DirectConnect] Monitor: LoadingUI instance became null!"); } else if (!_loadingMonitorDebugLogged) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); Log.LogWarning((object)("[DirectConnect] LoadingUI.instance is null. Available fields: " + string.Join(", ", fields.Select((FieldInfo f) => f.Name)))); _loadingMonitorDebugLogged = true; } return; } Component val = (Component)((obj is Component) ? obj : null); bool flag = (Object)(object)val != (Object)null && val.gameObject.activeInHierarchy; bool flag2 = false; FieldInfo field = type.GetField("_loaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { flag2 = (bool)field.GetValue(obj); } catch { } } bool flag3 = flag && !flag2; if (_monitorCheckCount % 60 == 0 && _loadingMonitorActive) { Log.LogInfo((object)$"[DirectConnect] Monitor check: goActive={flag}, _loaded={flag2}, isActive={flag3}, timer={_loadingStuckTimer:F1}s"); } if (!flag3) { if (_loadingMonitorActive) { Log.LogWarning((object)$"[DirectConnect] Monitor: Loading done or inactive! goActive={flag}, _loaded={flag2}"); } _loadingMonitorActive = false; _loadingStuckTimer = 0f; return; } string text = ""; string[] array = new string[5] { "currentState", "loadingState", "state", "currentLoadingState", "loadState" }; foreach (string name in array) { FieldInfo field2 = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field2 != null) { text = field2.GetValue(obj)?.ToString() ?? ""; if (!string.IsNullOrEmpty(text)) { break; } } } if (!_loadingMonitorActive && !_loadingMonitorDebugLogged) { Log.LogInfo((object)$"[DirectConnect] LoadingUI found! Active={flag3}, State='{text}'"); FieldInfo[] fields2 = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Log.LogInfo((object)("[DirectConnect] LoadingUI fields: " + string.Join(", ", fields2.Select((FieldInfo f) => f.Name)))); _loadingMonitorDebugLogged = true; } if (!flag3) { return; } if (!_loadingMonitorActive) { _loadingMonitorActive = true; _loadingStuckTimer = 0f; _lastLoadingState = text; Log.LogInfo((object)("[DirectConnect] Loading monitor started at state: '" + text + "'")); } _loadingStuckTimer += Time.unscaledDeltaTime; int num = (int)_loadingStuckTimer; if (num > 0 && num != _lastLoggedSecond) { _lastLoggedSecond = num; Log.LogInfo((object)$"[DirectConnect] Loading monitor: {_loadingStuckTimer:F2}s at '{text}' (deltaTime={Time.deltaTime:F4})"); } if (((!NullSafetyPatches._saveLoaded || !(_loadingStuckTimer > 10f)) && !(_loadingStuckTimer > 30f)) || _finishLoadingCalled) { return; } _finishLoadingCalled = true; Log.LogWarning((object)$"[DirectConnect] Loading stuck for {_loadingStuckTimer:F1}s at '{text}' - forcing completion!"); MethodInfo methodInfo = AccessTools.Method(type, "OnFinishLoading", (Type[])null, (Type[])null); if (methodInfo != null) { try { methodInfo.Invoke(obj, null); Log.LogInfo((object)"[DirectConnect] Successfully called LoadingUI.OnFinishLoading()!"); if (Time.timeScale == 0f) { Time.timeScale = 1f; Log.LogInfo((object)"[DirectConnect] Restored Time.timeScale to 1"); } FieldInfo field3 = type.GetField("confirmedLoad", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field3 != null) { field3.SetValue(obj, true); Log.LogInfo((object)"[DirectConnect] Set confirmedLoad = true"); } if (field != null) { field.SetValue(obj, true); Log.LogInfo((object)"[DirectConnect] Set _loaded = true"); } FieldInfo field4 = type.GetField("cg", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field4 != null) { object value = field4.GetValue(obj); if (value != null) { Type type2 = value.GetType(); type2.GetProperty("alpha")?.SetValue(value, 0f); type2.GetProperty("blocksRaycasts")?.SetValue(value, false); Log.LogInfo((object)"[DirectConnect] Set CanvasGroup alpha=0, hidden (but gameObject still active)"); } } EnsureNetworkMessageRelayExists(); return; } catch (Exception ex) { Log.LogError((object)("[DirectConnect] Error calling OnFinishLoading: " + ex.Message)); return; } } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Log.LogInfo((object)("[DirectConnect] OnFinishLoading not found. Available methods: " + string.Join(", ", methods.Select((MethodInfo m) => m.Name)))); } catch (Exception ex2) { Log.LogError((object)("[DirectConnect] Loading monitor error: " + ex2.Message)); } } private void EnsureNetworkMessageRelayExists() { //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("NetworkMessageRelay"); if (type == null) { Log.LogWarning((object)"[DirectConnect] NetworkMessageRelay type not found"); return; } FieldInfo field = type.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { Log.LogWarning((object)"[DirectConnect] Could not find NetworkMessageRelay.instance field"); return; } object value = field.GetValue(null); if (value != null) { object obj = ((value is MonoBehaviour) ? value : null); bool flag = (Object)(object)((obj != null) ? ((Component)obj).GetComponent<NetworkIdentity>() : null) != (Object)null; Log.LogInfo((object)$"[DirectConnect] NetworkMessageRelay.instance already exists (hasNetworkIdentity={flag})"); return; } Object[] array = Object.FindObjectsOfType(type); Log.LogInfo((object)$"[DirectConnect] Found {array.Length} NetworkMessageRelay objects in scene"); Object[] array2 = array; foreach (Object val in array2) { MonoBehaviour val2 = (MonoBehaviour)(object)((val is MonoBehaviour) ? val : null); if ((Object)(object)val2 != (Object)null) { NetworkIdentity component = ((Component)val2).GetComponent<NetworkIdentity>(); Log.LogInfo((object)$"[DirectConnect] Found relay: {((Object)val2).name}, hasNetworkIdentity={(Object)(object)component != (Object)null}, netId={((component != null) ? component.netId : 0u)}"); if ((Object)(object)component != (Object)null) { field.SetValue(null, val); Log.LogInfo((object)$"[DirectConnect] Using scene NetworkMessageRelay with netId {component.netId}"); return; } } } Log.LogWarning((object)"[DirectConnect] No networked scene relay found - creating dummy (commands may not work!)"); GameObject val3 = new GameObject("DirectConnect_DummyNetworkMessageRelay"); Object.DontDestroyOnLoad((Object)val3); Component value2 = val3.AddComponent(type); field.SetValue(null, value2); Log.LogInfo((object)"[DirectConnect] Created dummy NetworkMessageRelay instance"); } catch (Exception ex) { Log.LogError((object)("[DirectConnect] Failed to create NetworkMessageRelay: " + ex.Message)); } } private void OnGUI() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Expected O, but got Unknown //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: 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_001d: Invalid comparison between Unknown and I4 Event current = Event.current; if (current != null && (int)current.type == 4 && (int)current.keyCode == 292) { if (Time.frameCount != _lastToggleFrame) { _lastToggleFrame = Time.frameCount; _showConnectUI = !_showConnectUI; if (_showConnectUI) { _statusMessage = ""; } Log.LogInfo((object)$"[DirectConnect] UI toggled via Event.current: {_showConnectUI}"); } current.Use(); } if (_showConnectUI) { InitStyles(); _windowRect = GUI.Window(12345, _windowRect, new WindowFunction(DrawConnectWindow), "Direct Connect", _windowStyle); } } private void InitStyles() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected O, but got Unknown //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Expected O, but got Unknown //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Expected O, but got Unknown //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Expected O, but got Unknown //IL_0236: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Expected O, but got Unknown if (_windowStyle == null) { _windowStyle = new GUIStyle(GUI.skin.window); _windowStyle.normal.background = MakeTexture(2, 2, new Color(0.1f, 0.1f, 0.15f, 0.95f)); _windowStyle.fontSize = 16; _windowStyle.fontStyle = (FontStyle)1; _windowStyle.normal.textColor = new Color(0.65f, 0.55f, 0.98f); _windowStyle.padding = new RectOffset(15, 15, 25, 15); _labelStyle = new GUIStyle(GUI.skin.label); _labelStyle.fontSize = 14; _labelStyle.normal.textColor = Color.white; _textFieldStyle = new GUIStyle(GUI.skin.textField); _textFieldStyle.fontSize = 14; _textFieldStyle.normal.background = MakeTexture(2, 2, new Color(0.15f, 0.15f, 0.2f, 1f)); _textFieldStyle.normal.textColor = Color.white; _textFieldStyle.padding = new RectOffset(10, 10, 8, 8); _buttonStyle = new GUIStyle(GUI.skin.button); _buttonStyle.fontSize = 14; _buttonStyle.fontStyle = (FontStyle)1; _buttonStyle.normal.background = MakeTexture(2, 2, new Color(0.49f, 0.23f, 0.93f, 1f)); _buttonStyle.hover.background = MakeTexture(2, 2, new Color(0.59f, 0.33f, 1f, 1f)); _buttonStyle.normal.textColor = Color.white; _buttonStyle.hover.textColor = Color.white; _buttonStyle.padding = new RectOffset(15, 15, 10, 10); _statusStyle = new GUIStyle(GUI.skin.label); _statusStyle.fontSize = 12; _statusStyle.alignment = (TextAnchor)4; _statusStyle.wordWrap = true; } } private Texture2D MakeTexture(int width, int height, Color color) { //IL_000f: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown Color[] array = (Color[])(object)new Color[width * height]; for (int i = 0; i < array.Length; i++) { array[i] = color; } Texture2D val = new Texture2D(width, height); val.SetPixels(array); val.Apply(); return val; } private void DrawConnectWindow(int windowID) { //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Expected O, but got Unknown //IL_025b: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) GUILayout.Space(10f); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label("Server:", _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }); _serverAddress = GUILayout.TextField(_serverAddress, _textFieldStyle, Array.Empty<GUILayoutOption>()); GUILayout.EndHorizontal(); GUILayout.Space(10f); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label("Port:", _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }); _serverPort = GUILayout.TextField(_serverPort, _textFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }); GUILayout.EndHorizontal(); GUILayout.Space(15f); if (NetworkClient.active) { _statusStyle.normal.textColor = new Color(0.2f, 0.83f, 0.6f); GUILayout.Label("Connected to " + NetworkManager.singleton?.networkAddress, _statusStyle, Array.Empty<GUILayoutOption>()); GUILayout.Space(10f); if (GUILayout.Button("Disconnect", _buttonStyle, Array.Empty<GUILayoutOption>())) { Disconnect(); } } else { if (!string.IsNullOrEmpty(_statusMessage)) { _statusStyle.normal.textColor = ((_statusMessage.Contains("Error") || _statusMessage.Contains("Failed")) ? new Color(0.97f, 0.44f, 0.44f) : new Color(0.65f, 0.55f, 0.98f)); GUILayout.Label(_statusMessage, _statusStyle, Array.Empty<GUILayoutOption>()); GUILayout.Space(5f); } GUI.enabled = !_isConnecting && !string.IsNullOrWhiteSpace(_serverAddress); if (GUILayout.Button(_isConnecting ? "Connecting..." : "Connect", _buttonStyle, Array.Empty<GUILayoutOption>())) { Connect(); } GUI.enabled = true; } GUILayout.Space(10f); GUIStyle val = new GUIStyle(_buttonStyle); val.normal.background = MakeTexture(2, 2, new Color(0.3f, 0.3f, 0.35f, 1f)); if (GUILayout.Button("Close", val, Array.Empty<GUILayoutOption>())) { _showConnectUI = false; } GUI.DragWindow(new Rect(0f, 0f, 10000f, 30f)); } private void Connect() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(_serverAddress)) { _statusMessage = "Please enter a server address"; return; } if (!int.TryParse(_serverPort, out var result) || result < 1 || result > 65535) { _statusMessage = "Invalid port number"; return; } try { Scene val = SceneManager.GetActiveScene(); string name = ((Scene)(ref val)).name; Log.LogInfo((object)("[DirectConnect] Current scene: " + name)); for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); Log.LogInfo((object)$"[DirectConnect] Loaded scene {i}: {((Scene)(ref sceneAt)).name} (active: {sceneAt == SceneManager.GetActiveScene()})"); } bool flag = false; for (int j = 0; j < SceneManager.sceneCount; j++) { val = SceneManager.GetSceneAt(j); string name2 = ((Scene)(ref val)).name; if (name2.Contains("Player") || name2.Contains("Voxeland")) { flag = true; break; } } if (!flag) { Log.LogInfo((object)"[DirectConnect] Not in game world - triggering JoinGameAsClient to load game scenes..."); _statusMessage = "Loading game..."; _pendingServerAddress = _serverAddress; _pendingServerPort = result; if (EnableDirectConnect(result)) { NetworkManager singleton = NetworkManager.singleton; if ((Object)(object)singleton != (Object)null) { singleton.networkAddress = _serverAddress; Log.LogInfo((object)("[DirectConnect] Set network address to: " + _serverAddress)); } try { FlowManager.JoinGameAsClient((Action)delegate { Log.LogInfo((object)"[DirectConnect] JoinGameAsClient callback - scene loaded!"); }); Log.LogInfo((object)"[DirectConnect] Called FlowManager.JoinGameAsClient successfully"); return; } catch (Exception arg) { Log.LogError((object)$"[DirectConnect] Failed to call JoinGameAsClient: {arg}"); _statusMessage = "Failed to load game - try loading a save first"; return; } } _statusMessage = "Failed to initialize connection"; } else { DoConnect(_serverAddress, result); } } catch (Exception ex) { _statusMessage = "Error: " + ex.Message; _isConnecting = false; Log.LogError((object)$"[DirectConnect] Connection error: {ex}"); } } private IEnumerator CheckConnection() { float timeout = 15f; float elapsed = 0f; while (elapsed < timeout) { if (NetworkClient.isConnected) { Log.LogInfo((object)"[DirectConnect] Connected! Sending ready signal..."); _statusMessage = "Connected! Joining game..."; if (!NetworkClient.ready) { try { NetworkClient.Ready(); Log.LogInfo((object)"[DirectConnect] Sent Ready signal"); } catch (Exception ex) { Log.LogWarning((object)("[DirectConnect] Ready failed: " + ex.Message)); } } yield return (object)new WaitForSeconds(0.5f); try { if (NetworkClient.ready && (Object)(object)NetworkClient.localPlayer == (Object)null) { NetworkClient.AddPlayer(); Log.LogInfo((object)"[DirectConnect] Requested player spawn"); } } catch (Exception ex2) { Log.LogWarning((object)("[DirectConnect] AddPlayer failed: " + ex2.Message)); } _statusMessage = "Connected!"; _isConnecting = false; yield return (object)new WaitForSeconds(1f); _showConnectUI = false; yield break; } if (!NetworkClient.active && !_isConnecting) { _statusMessage = "Connection failed"; yield break; } elapsed += 0.5f; yield return (object)new WaitForSeconds(0.5f); } if (!NetworkClient.isConnected) { _statusMessage = "Connection timed out"; _isConnecting = false; NetworkManager singleton = NetworkManager.singleton; if (singleton != null) { singleton.StopClient(); } } } private IEnumerator ConnectAfterSceneLoad() { Log.LogInfo((object)"[DirectConnect] ConnectAfterSceneLoad - waiting for game world..."); _statusMessage = "Loading game world..."; float maxWait = 30f; float elapsed = 0f; while (elapsed < maxWait) { bool flag = false; for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); string name = ((Scene)(ref sceneAt)).name; if (name.Contains("Player") || name.Contains("Voxeland")) { flag = true; break; } } if (flag) { Log.LogInfo((object)"[DirectConnect] Game world loaded! Starting connection..."); yield return (object)new WaitForSeconds(2f); DoConnect(_pendingServerAddress, _pendingServerPort); yield break; } elapsed += 0.5f; yield return (object)new WaitForSeconds(0.5f); } _statusMessage = "Timeout waiting for game to load"; Log.LogError((object)"[DirectConnect] Timeout waiting for game world to load"); } private IEnumerator WaitForGameWorldAndConnect(int port) { Log.LogInfo((object)"[DirectConnect] WaitForGameWorldAndConnect - polling for game world..."); _statusMessage = "Loading game world..."; _pendingServerAddress = _serverAddress; _pendingServerPort = port; float maxWait = 60f; float elapsed = 0f; while (elapsed < maxWait) { bool flag = false; for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); string name = ((Scene)(ref sceneAt)).name; Log.LogInfo((object)("[DirectConnect] Checking scene: " + name)); if (name.Contains("Player") || name.Contains("Voxeland")) { flag = true; break; } } if (flag) { Log.LogInfo((object)"[DirectConnect] Game world loaded! Starting connection..."); yield return (object)new WaitForSeconds(3f); DoConnect(_pendingServerAddress, _pendingServerPort); yield break; } elapsed += 1f; _statusMessage = $"Loading game world... ({elapsed:0}s)"; yield return (object)new WaitForSeconds(1f); } _statusMessage = "Timeout waiting for game to load"; Log.LogError((object)"[DirectConnect] Timeout waiting for game world to load"); } private void DoConnect(string address, int port) { try { _isConnecting = true; _statusMessage = "Connecting..."; LastServerAddress.Value = address; if (!EnableDirectConnect(port)) { _statusMessage = "Failed to initialize connection"; _isConnecting = false; return; } NetworkManager singleton = NetworkManager.singleton; if ((Object)(object)singleton == (Object)null) { _statusMessage = "Error: NetworkManager not found"; _isConnecting = false; return; } string text = address; if (!IPAddress.TryParse(address, out IPAddress _)) { try { _statusMessage = "Resolving " + address + "..."; Log.LogInfo((object)("[DirectConnect] Resolving hostname: " + address)); IPAddress[] hostAddresses = Dns.GetHostAddresses(address); foreach (IPAddress iPAddress in hostAddresses) { if (iPAddress.AddressFamily == AddressFamily.InterNetwork) { text = iPAddress.ToString(); Log.LogInfo((object)("[DirectConnect] Resolved " + address + " -> " + text)); break; } } } catch (Exception ex) { Log.LogWarning((object)("[DirectConnect] DNS resolution failed: " + ex.Message + ", trying direct connection...")); } } singleton.networkAddress = text; singleton.StartClient(); Log.LogInfo((object)($"[DirectConnect] Connecting to {text}:{port}..." + ((text != address) ? (" (resolved from " + address + ")") : ""))); ((MonoBehaviour)this).StartCoroutine(CheckConnection()); } catch (Exception ex2) { _statusMessage = "Error: " + ex2.Message; _isConnecting = false; Log.LogError((object)$"[DirectConnect] Connection error: {ex2}"); } } private void Disconnect() { try { NetworkManager singleton = NetworkManager.singleton; if (singleton != null) { singleton.StopClient(); } DisableDirectConnect(); NetworkRelayLinkingPatches.Reset(); _statusMessage = "Disconnected"; Log.LogInfo((object)"[DirectConnect] Disconnected"); } catch (Exception arg) { Log.LogError((object)$"[DirectConnect] Disconnect error: {arg}"); } } public void ShowConnectUI() { _showConnectUI = true; _statusMessage = ""; Log.LogInfo((object)"[DirectConnect] Opening connect UI from main menu"); } private static bool EnableDirectConnect(int port) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown try { NetworkManager singleton = NetworkManager.singleton; if ((Object)(object)singleton == (Object)null) { Log.LogError((object)"[DirectConnect] NetworkManager not found!"); return false; } FieldInfo field = typeof(NetworkManager).GetField("transport", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { Log.LogError((object)"[DirectConnect] Could not find transport field!"); return false; } object? value = field.GetValue(singleton); _originalTransport = (Transport)((value is Transport) ? value : null); if ((Object)(object)_kcpTransport == (Object)null) { GameObject val = new GameObject("DirectConnect_KcpTransport"); Object.DontDestroyOnLoad((Object)val); _kcpTransport = val.AddComponent<KcpTransport>(); } _kcpTransport.Port = (ushort)port; _kcpTransport.NoDelay = true; _kcpTransport.Interval = 10u; _kcpTransport.Timeout = 30000; _kcpTransport.DualMode = false; field.SetValue(singleton, _kcpTransport); typeof(Transport).GetField("activeTransport", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(null, _kcpTransport); _isDirectConnectActive = true; Log.LogInfo((object)$"[DirectConnect] Switched to KCP transport on port {port}"); return true; } catch (Exception arg) { Log.LogError((object)$"[DirectConnect] Failed to enable: {arg}"); return false; } } private static void DisableDirectConnect() { if ((Object)(object)_originalTransport != (Object)null && (Object)(object)NetworkManager.singleton != (Object)null) { try { typeof(NetworkManager).GetField("transport", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(NetworkManager.singleton, _originalTransport); typeof(Transport).GetField("activeTransport", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(null, _originalTransport); _isDirectConnectActive = false; Log.LogInfo((object)"[DirectConnect] Restored original transport"); } catch (Exception arg) { Log.LogError((object)$"[DirectConnect] Failed to restore transport: {arg}"); } } } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } if ((Object)(object)_kcpTransport != (Object)null) { Object.Destroy((Object)(object)((Component)_kcpTransport).gameObject); } } } public static class PluginInfo { public const string PLUGIN_GUID = "com.certifried.techtonicadirectconnect"; public const string PLUGIN_NAME = "Techtonica Direct Connect"; public const string PLUGIN_VERSION = "1.0.103"; } [HarmonyPatch] public static class NetworkRelayLinkingPatches { private static bool _relayLinked; private static bool _loggedOnce; public static void Reset() { _relayLinked = false; _loggedOnce = false; NullSafetyPatches._sendActionLogged = false; NullSafetyPatches._serverRelayNetId = 2u; NullSafetyPatches._actionCount = 0; } [HarmonyPrefix] [HarmonyPatch(typeof(NetworkClient), "OnSpawn")] public static bool OnSpawn_Prefix(SpawnMessage message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) if (message.assetId == Guid.Empty && message.sceneId == 0L) { if (!_loggedOnce) { Plugin.Log.LogInfo((object)$"[DirectConnect] Intercepted spawn for netId {message.netId} with no assetId/sceneId - will try to link scene relay"); _loggedOnce = true; } if (_relayLinked) { return false; } NullSafetyPatches._serverRelayNetId = message.netId; Plugin.Log.LogInfo((object)$"[DirectConnect] Server relay netId detected: {message.netId}"); if (LinkSceneRelayToNetId(message.netId)) { _relayLinked = true; return false; } } return true; } private static bool LinkSceneRelayToNetId(uint targetNetId) { //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Expected O, but got Unknown //IL_0111: Unknown result type (might be due to invalid IL or missing references) try { Type type = AccessTools.TypeByName("NetworkMessageRelay"); if (type == null) { Plugin.Log.LogWarning((object)"[DirectConnect] NetworkMessageRelay type not found"); return false; } Object[] array = Object.FindObjectsOfType(type); Plugin.Log.LogInfo((object)$"[DirectConnect] Found {array.Length} NetworkMessageRelay objects in scene"); NetworkIdentity val = null; object obj = null; Object[] array2 = array; foreach (Object val2 in array2) { MonoBehaviour val3 = (MonoBehaviour)(object)((val2 is MonoBehaviour) ? val2 : null); if (!((Object)(object)val3 == (Object)null)) { val = ((Component)val3).GetComponent<NetworkIdentity>(); if ((Object)(object)val != (Object)null) { obj = val2; Plugin.Log.LogInfo((object)("[DirectConnect] Found scene relay '" + ((Object)val3).name + "' with identity")); break; } Plugin.Log.LogInfo((object)("[DirectConnect] Relay '" + ((Object)val3).name + "' has no NetworkIdentity")); } } if (obj == null || (Object)(object)val == (Object)null) { Plugin.Log.LogInfo((object)$"[DirectConnect] No scene relay found - creating networked relay for netId {targetNetId}"); GameObject val4 = new GameObject("DirectConnect_NetworkMessageRelay"); Object.DontDestroyOnLoad((Object)val4); val = val4.AddComponent<NetworkIdentity>(); obj = val4.AddComponent(type); FieldInfo field = typeof(NetworkBehaviour).GetField("<netIdentity>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { field.SetValue(obj, val); Plugin.Log.LogInfo((object)"[DirectConnect] Set netIdentity backing field on relay"); } else { Plugin.Log.LogWarning((object)"[DirectConnect] Could not find netIdentity backing field!"); } PropertyInfo property = typeof(NetworkIdentity).GetProperty("NetworkBehaviours", BindingFlags.Instance | BindingFlags.Public); if (property != null) { MethodInfo setMethod = property.GetSetMethod(nonPublic: true); if (setMethod != null) { NetworkBehaviour[] array3 = (NetworkBehaviour[])(object)new NetworkBehaviour[1] { (NetworkBehaviour)((obj is NetworkBehaviour) ? obj : null) }; setMethod.Invoke(val, new object[1] { array3 }); Plugin.Log.LogInfo((object)"[DirectConnect] Set NetworkBehaviours array on identity"); } else { Plugin.Log.LogWarning((object)"[DirectConnect] Could not get NetworkBehaviours setter!"); } } else { Plugin.Log.LogWarning((object)"[DirectConnect] Could not find NetworkBehaviours property!"); } PropertyInfo property2 = typeof(NetworkBehaviour).GetProperty("ComponentIndex", BindingFlags.Instance | BindingFlags.Public); if (property2 != null) { MethodInfo setMethod2 = property2.GetSetMethod(nonPublic: true); if (setMethod2 != null) { setMethod2.Invoke(obj, new object[1] { (byte)0 }); Plugin.Log.LogInfo((object)"[DirectConnect] Set ComponentIndex = 0 on relay"); } else { Plugin.Log.LogWarning((object)"[DirectConnect] Could not get ComponentIndex setter!"); } } else { Plugin.Log.LogWarning((object)"[DirectConnect] Could not find ComponentIndex property!"); } Plugin.Log.LogInfo((object)"[DirectConnect] Created NetworkMessageRelay with NetworkIdentity"); } Plugin.Log.LogInfo((object)$"[DirectConnect] Linking relay to netId {targetNetId}"); bool flag = false; FieldInfo field2 = typeof(NetworkIdentity).GetField("_netId", BindingFlags.Instance | BindingFlags.NonPublic); if (field2 == null) { field2 = typeof(NetworkIdentity).GetField("netId", BindingFlags.Instance | BindingFlags.NonPublic); } if (field2 != null) { field2.SetValue(val, targetNetId); Plugin.Log.LogInfo((object)$"[DirectConnect] Set identity._netId = {targetNetId} via field"); flag = true; } else { PropertyInfo property3 = typeof(NetworkIdentity).GetProperty("netId", BindingFlags.Instance | BindingFlags.Public); if (property3 != null) { MethodInfo setMethod3 = property3.GetSetMethod(nonPublic: true); if (setMethod3 != null) { setMethod3.Invoke(val, new object[1] { targetNetId }); Plugin.Log.LogInfo((object)$"[DirectConnect] Set identity.netId = {targetNetId} via property setter"); flag = true; } } } if (!flag) { Plugin.Log.LogError((object)"[DirectConnect] Could not set netId on identity!"); return false; } if (!NetworkClient.spawned.ContainsKey(targetNetId)) { NetworkClient.spawned[targetNetId] = val; Plugin.Log.LogInfo((object)$"[DirectConnect] Added identity to NetworkClient.spawned[{targetNetId}]"); } FieldInfo field3 = type.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field3 != null) { field3.SetValue(null, obj); Plugin.Log.LogInfo((object)"[DirectConnect] Set NetworkMessageRelay.instance"); } FieldInfo field4 = typeof(NetworkIdentity).GetField("isClient", BindingFlags.Instance | BindingFlags.NonPublic); if (field4 != null) { field4.SetValue(val, true); } FieldInfo field5 = typeof(NetworkIdentity).GetField("_hasAuthority", BindingFlags.Instance | BindingFlags.NonPublic); if (field5 != null) { field5.SetValue(val, true); Plugin.Log.LogInfo((object)"[DirectConnect] Set hasAuthority = true"); } PropertyInfo property4 = typeof(NetworkIdentity).GetProperty("connectionToServer", BindingFlags.Instance | BindingFlags.Public); if (property4 != null && NetworkClient.connection != null) { MethodInfo setMethod4 = property4.GetSetMethod(nonPublic: true); if (setMethod4 != null) { setMethod4.Invoke(val, new object[1] { NetworkClient.connection }); Plugin.Log.LogInfo((object)"[DirectConnect] Set connectionToServer = NetworkClient.connection (via property)"); } else { try { property4.SetValue(val, NetworkClient.connection); Plugin.Log.LogInfo((object)"[DirectConnect] Set connectionToServer = NetworkClient.connection (direct)"); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[DirectConnect] Could not set connectionToServer: " + ex.Message)); } } } else { Plugin.Log.LogWarning((object)$"[DirectConnect] Could not set connectionToServer: prop={property4 != null}, connection={NetworkClient.connection != null}"); } Plugin.Log.LogInfo((object)$"[DirectConnect] Successfully linked NetworkMessageRelay to server's netId {targetNetId}!"); return true; } catch (Exception arg) { Plugin.Log.LogError((object)$"[DirectConnect] Failed to link scene relay: {arg}"); return false; } } } [HarmonyPatch] public static class FizzyFacepunchPatches { [HarmonyPrefix] [HarmonyPatch(typeof(FizzyFacepunch), "ClientEarlyUpdate")] public static bool ClientEarlyUpdate_Prefix() { return false; } [HarmonyPrefix] [HarmonyPatch(typeof(FizzyFacepunch), "ClientLateUpdate")] public static bool ClientLateUpdate_Prefix() { return false; } [HarmonyPrefix] [HarmonyPatch(typeof(FizzyFacepunch), "ServerEarlyUpdate")] public static bool ServerEarlyUpdate_Prefix() { return false; } [HarmonyPrefix] [HarmonyPatch(typeof(FizzyFacepunch), "ServerLateUpdate")] public static bool ServerLateUpdate_Prefix() { return false; } } [HarmonyPatch] public static class MainMenuPatches { [HarmonyPostfix] [HarmonyPatch(typeof(MainMenuUI), "RefreshHiddenButtonState")] public static void RefreshHiddenButtonState_Postfix(MainMenuUI __instance) { try { if (AccessTools.Field(typeof(MainMenuUI), "menuSelections")?.GetValue(__instance) is MainMenuItem[] array && array.Length > 3) { ((Component)array[3]).gameObject.SetActive(true); Plugin.Log.LogInfo((object)"[DirectConnect] Enabled 'Join Multiplayer' button in main menu"); } } catch (Exception arg) { Plugin.Log.LogError((object)$"[DirectConnect] Failed to patch main menu: {arg}"); } } [HarmonyPrefix] [HarmonyPatch(typeof(MainMenuUI), "JoinMultiplayerAsClient")] public static bool JoinMultiplayerAsClient_Prefix() { try { Plugin.Log.LogInfo((object)"[DirectConnect] 'Join Multiplayer' clicked - showing connect dialog"); Plugin.Instance.ShowConnectUI(); return false; } catch (Exception arg) { Plugin.Log.LogError((object)$"[DirectConnect] Error in JoinMultiplayerAsClient: {arg}"); return true; } } } public static class NullSafetyPatches { private static bool _patchesApplied = false; public static uint _serverRelayNetId = 2u; public static bool _sendActionLogged = false; public static int _actionCount = 0; private static readonly Dictionary<string, string> _actionTypeToCommand = new Dictionary<string, string> { { "ConveyorBuildAction", "BuildConveyorCommand" }, { "HVCBuildAction", "BuildHvcCommand" }, { "MassTransitBuildAction", "BuildMassTransitCommand" }, { "MassTransitChainBuildAction", "BuildMassTransitChainCommand" }, { "ResearchCoreBuildAction", "BuildResearchCoreCommand" }, { "SimpleBuildAction", "BuildSimpleMachineCommand" }, { "ExchangeMachineAction", "ExchangeMachineInvCommand" }, { "RotateMachineAction", "RotateMachineCommand" }, { "SwapVariantAction", "SwapVariantCommand" }, { "EraseMachineAction", "EraseMachineCommand" }, { "ChangeFilterAction", "SetFilterCommand" }, { "HarvestPlantAction", "HarvestPlantCommand" }, { "TakeAllAction", "TakeAllCommand" }, { "HitDestructibleAction", "HitDestructableCommand" }, { "HitVoxelAction", "HitVoxelCommand" }, { "RepairAction", "RepairMachineCommand" }, { "CheatAction", "CheatCommand" }, { "CraftAction", "CraftCommand" }, { "ActivatePowerAction", "ActivatePowerCommand" }, { "ScanAction", "ScanCommand" }, { "UnlockTechAction", "UnlockTechCommand" }, { "DeactivateTechAction", "DeactivateTechCommand" }, { "ModifyMouseBufferAction", "ModifyMouseBufferCommand" }, { "InteractAction", "InteractCommand" }, { "ActivateMachineAction", "ActivateMachineCommand" }, { "MOLEAction", "MOLECommand" }, { "SortInventoryAction", "SortCommand" }, { "TempConveyorAction", "UpdateTempConveyorsCommand" }, { "UpgradeProductionTerminalAction", "UpgradeResearchCommand" }, { "UnlockDatabankEntryAction", "UnlockDatabankEntryCommand" }, { "QuestStatusAction", "CompleteQuestCommand" }, { "ExplosiveBuildAction", "BuildExplosiveCommand" }, { "ExplodeAction", "ExplodeCommand" }, { "CompleteResourceGateAction", "CompleteResourceGateCommand" }, { "CompleteRepairElevatorAction", "CompleteRepairElevatorCommand" }, { "CheatUpgradeAction", "CheatUpgradeCommand" }, { "MainReceiverAction", "MainReceiverCommand" }, { "BuildWithRecipeAction", "BuildMachineWithRecipeCommand" }, { "MultiPlaceBuildAction", "BuildMultiPlaceCommand" }, { "ReplaceAction", "ReplaceCommand" }, { "ResizeStorageLimitAction", "ResizeStorageLimitCommand" }, { "ShootAction", "ShootCommand" }, { "MultiplayerGameAction", "MultiplayerGameCommand" }, { "SetNexusTeamAction", "SetNexusTeamCommand" }, { "MultiplayerGameLootNexusAction", "MultiplayerGameLootNexusCommand" }, { "AsteroidsAction", "AsteroidsCommand" }, { "PingAction", "PingCommand" }, { "OmniseekablePingAction", "OmniseekablePingCommand" }, { "SandPumpBuildAction", "BuildSandPumpCommand" } }; private static int _sendCommandCount = 0; private static bool _requestSimTickHandled = false; public static bool _saveLoaded = false; public static void ApplyPatches(Harmony harmony) { //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02c1: Expected O, but got Unknown //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02d8: Expected O, but got Unknown //IL_04a7: Unknown result type (might be due to invalid IL or missing references) //IL_04ae: Expected O, but got Unknown //IL_0520: Unknown result type (might be due to invalid IL or missing references) //IL_0527: Expected O, but got Unknown //IL_05aa: Unknown result type (might be due to invalid IL or missing references) //IL_05b1: Expected O, but got Unknown //IL_0623: Unknown result type (might be due to invalid IL or missing references) //IL_062a: Expected O, but got Unknown //IL_069c: Unknown result type (might be due to invalid IL or missing references) //IL_06a3: Expected O, but got Unknown //IL_0715: Unknown result type (might be due to invalid IL or missing references) //IL_071c: Expected O, but got Unknown //IL_078e: Unknown result type (might be due to invalid IL or missing references) //IL_0795: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_03d7: Unknown result type (might be due to invalid IL or missing references) //IL_03de: Expected O, but got Unknown //IL_0382: Unknown result type (might be due to invalid IL or missing references) //IL_0389: Expected O, but got Unknown //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Expected O, but got Unknown //IL_042e: Unknown result type (might be due to invalid IL or missing references) //IL_0435: Expected O, but got Unknown //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Expected O, but got Unknown //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Expected O, but got Unknown if (_patchesApplied) { return; } try { Type type = AccessTools.TypeByName("ThirdPersonDisplayAnimator"); if (type != null) { MethodInfo methodInfo = AccessTools.Method(type, "Update", (Type[])null, (Type[])null); if (methodInfo != null) { HarmonyMethod val = new HarmonyMethod(typeof(NullSafetyPatches), "Skip_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched ThirdPersonDisplayAnimator.Update to skip"); } MethodInfo methodInfo2 = AccessTools.Method(type, "UpdateSillyStuff", (Type[])null, (Type[])null); if (methodInfo2 != null) { HarmonyMethod val2 = new HarmonyMethod(typeof(NullSafetyPatches), "Skip_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo2, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched ThirdPersonDisplayAnimator.UpdateSillyStuff to skip"); } } else { Plugin.Log.LogWarning((object)"[DirectConnect] ThirdPersonDisplayAnimator type not found"); } Type type2 = AccessTools.TypeByName("NetworkMessageRelay"); if (type2 != null) { MethodInfo methodInfo3 = AccessTools.Method(type2, "SendNetworkAction", (Type[])null, (Type[])null); if (methodInfo3 != null) { HarmonyMethod val3 = new HarmonyMethod(typeof(NullSafetyPatches), "SendNetworkAction_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo3, val3, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched NetworkMessageRelay.SendNetworkAction (complete replacement)"); } MethodInfo methodInfo4 = AccessTools.Method(type2, "CmdSendNetworkAction", (Type[])null, (Type[])null); if (methodInfo4 != null) { HarmonyMethod val4 = new HarmonyMethod(typeof(NullSafetyPatches), "CmdSendNetworkAction_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo4, val4, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched NetworkMessageRelay.CmdSendNetworkAction"); } else { Plugin.Log.LogWarning((object)"[DirectConnect] CmdSendNetworkAction method not found - checking for other command names"); MethodInfo[] methods = type2.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo5 in methods) { if (methodInfo5.Name.Contains("Cmd") || methodInfo5.Name.Contains("Command") || methodInfo5.Name.Contains("Send")) { Plugin.Log.LogInfo((object)("[DirectConnect] Found method: " + methodInfo5.Name)); } } } } try { MethodInfo method = typeof(NetworkBehaviour).GetMethod("SendCommandInternal", BindingFlags.Instance | BindingFlags.NonPublic); if (method != null) { HarmonyMethod val5 = new HarmonyMethod(typeof(NullSafetyPatches), "SendCommandInternal_Prefix", (Type[])null); harmony.Patch((MethodBase)method, val5, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched Mirror.NetworkBehaviour.SendCommandInternal"); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[DirectConnect] Failed to patch SendCommandInternal (non-critical): " + ex.Message)); } if (type2 != null) { MethodInfo methodInfo6 = AccessTools.Method(type2, "RequestCurrentSimTick", (Type[])null, (Type[])null); if (methodInfo6 != null) { HarmonyMethod val6 = new HarmonyMethod(typeof(NullSafetyPatches), "RequestCurrentSimTick_Prefix", (Type[])null); HarmonyMethod val7 = new HarmonyMethod(typeof(NullSafetyPatches), "RequestCurrentSimTick_Finalizer", (Type[])null); harmony.Patch((MethodBase)methodInfo6, val6, (HarmonyMethod)null, (HarmonyMethod)null, val7, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched NetworkMessageRelay.RequestCurrentSimTick with prefix and finalizer"); } MethodInfo methodInfo7 = AccessTools.Method(type2, "UserCode_ProcessCurrentSimTick", (Type[])null, (Type[])null); if (methodInfo7 != null) { HarmonyMethod val8 = new HarmonyMethod(typeof(NullSafetyPatches), "ProcessCurrentSimTick_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo7, val8, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched NetworkMessageRelay.UserCode_ProcessCurrentSimTick with prefix"); } } Type type3 = AccessTools.TypeByName("NetworkedPlayer"); if (type3 != null) { MethodInfo methodInfo8 = AccessTools.Method(type3, "OnStopClient", (Type[])null, (Type[])null); if (methodInfo8 != null) { HarmonyMethod val9 = new HarmonyMethod(typeof(NullSafetyPatches), "OnStopClient_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo8, val9, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched NetworkedPlayer.OnStopClient for disconnect tracking"); } } MethodInfo methodInfo9 = AccessTools.Method(typeof(NetworkManager), "OnClientDisconnect", (Type[])null, (Type[])null); if (methodInfo9 != null) { HarmonyMethod val10 = new HarmonyMethod(typeof(NullSafetyPatches), "OnClientDisconnect_Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo9, val10, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched NetworkManager.OnClientDisconnect for disconnect tracking"); } try { MethodInfo method2 = typeof(NetworkClient).GetMethod("OnObjectSpawn", BindingFlags.Static | BindingFlags.NonPublic); if (method2 != null) { HarmonyMethod val11 = new HarmonyMethod(typeof(NullSafetyPatches), "OnObjectSpawn_Prefix", (Type[])null); harmony.Patch((MethodBase)method2, val11, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched NetworkClient.OnObjectSpawn for spawn tracking"); } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("[DirectConnect] Failed to patch OnObjectSpawn: " + ex2.Message)); } try { MethodInfo method3 = typeof(NetworkClient).GetMethod("OnObjectDestroy", BindingFlags.Static | BindingFlags.NonPublic); if (method3 != null) { HarmonyMethod val12 = new HarmonyMethod(typeof(NullSafetyPatches), "OnObjectDestroy_Prefix", (Type[])null); harmony.Patch((MethodBase)method3, val12, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched NetworkClient.OnObjectDestroy for destroy tracking"); } } catch (Exception ex3) { Plugin.Log.LogWarning((object)("[DirectConnect] Failed to patch OnObjectDestroy: " + ex3.Message)); } try { MethodInfo method4 = typeof(NetworkIdentity).GetMethod("HandleRemoteCall", BindingFlags.Instance | BindingFlags.NonPublic); if (method4 != null) { HarmonyMethod val13 = new HarmonyMethod(typeof(NullSafetyPatches), "HandleRemoteCall_Prefix", (Type[])null); harmony.Patch((MethodBase)method4, val13, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched NetworkIdentity.HandleRemoteCall for RPC tracking"); } } catch (Exception ex4) { Plugin.Log.LogWarning((object)("[DirectConnect] Failed to patch HandleRemoteCall: " + ex4.Message)); } try { MethodInfo method5 = typeof(NetworkClient).GetMethod("Connect", new Type[1] { typeof(string) }); if (method5 != null) { HarmonyMethod val14 = new HarmonyMethod(typeof(NullSafetyPatches), "NetworkClient_Connect_Prefix", (Type[])null); harmony.Patch((MethodBase)method5, val14, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched NetworkClient.Connect"); } } catch (Exception ex5) { Plugin.Log.LogWarning((object)("[DirectConnect] Failed to patch NetworkClient.Connect: " + ex5.Message)); } try { MethodInfo method6 = typeof(KcpTransport).GetMethod("OnClientConnected", BindingFlags.Instance | BindingFlags.NonPublic); if (method6 != null) { HarmonyMethod val15 = new HarmonyMethod(typeof(NullSafetyPatches), "KCP_OnClientConnected_Prefix", (Type[])null); harmony.Patch((MethodBase)method6, val15, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched KcpTransport.OnClientConnected"); } } catch (Exception ex6) { Plugin.Log.LogWarning((object)("[DirectConnect] Failed to patch KcpTransport.OnClientConnected: " + ex6.Message)); } try { MethodInfo method7 = typeof(KcpTransport).GetMethod("OnClientDisconnected", BindingFlags.Instance | BindingFlags.NonPublic); if (method7 != null) { HarmonyMethod val16 = new HarmonyMethod(typeof(NullSafetyPatches), "KCP_OnClientDisconnected_Prefix", (Type[])null); harmony.Patch((MethodBase)method7, val16, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched KcpTransport.OnClientDisconnected"); } } catch (Exception ex7) { Plugin.Log.LogWarning((object)("[DirectConnect] Failed to patch KcpTransport.OnClientDisconnected: " + ex7.Message)); } try { MethodInfo method8 = typeof(KcpClient).GetMethod("Disconnect", BindingFlags.Instance | BindingFlags.Public); if (method8 != null) { HarmonyMethod val17 = new HarmonyMethod(typeof(NullSafetyPatches), "KcpClient_Disconnect_Prefix", (Type[])null); harmony.Patch((MethodBase)method8, val17, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched KcpClient.Disconnect"); } } catch (Exception ex8) { Plugin.Log.LogWarning((object)("[DirectConnect] Failed to patch KcpClient.Disconnect: " + ex8.Message)); } try { MethodInfo method9 = typeof(KcpConnection).GetMethod("Disconnect", BindingFlags.Instance | BindingFlags.Public); if (method9 != null) { HarmonyMethod val18 = new HarmonyMethod(typeof(NullSafetyPatches), "KcpConnection_Disconnect_Prefix", (Type[])null); harmony.Patch((MethodBase)method9, val18, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[DirectConnect] Patched KcpConnection.Disconnect"); } } catch (Exception ex9) { Plugin.Log.LogWarning((object)("[DirectConnect] Failed to patch KcpConnection.Disconnect: " + ex9.Message)); } _patchesApplied = true; } catch (Exception arg) { Plugin.Log.LogError((object)$"[DirectConnect] Failed to apply null safety patches: {arg}"); } } public static bool Skip_Prefix() { return false; } public static bool SendNetworkAction_Prefix(object __instance, object action) { _actionCount++; string text = action?.GetType()?.Name ?? "null"; try { Plugin.Log.LogInfo((object)$"[DirectConnect] SendNetworkAction #{_actionCount}: {text}"); object obj = ((__instance is NetworkBehaviour) ? __instance : null); if ((Object)(object)((obj != null) ? ((NetworkBehaviour)obj).netIdentity : null) == (Object)null) { Plugin.Log.LogError((object)("[DirectConnect] SendNetworkAction: netIdentity is NULL! Cannot send " + text)); return false; } if (!_actionTypeToCommand.TryGetValue(text, out var value)) { Plugin.Log.LogError((object)("[DirectConnect] Unknown action type: " + text + ". Cannot send to server!")); return false; } if (SendCommandDirectly(__instance, action, value, text)) { Plugin.Log.LogInfo((object)("[DirectConnect] " + value + " sent successfully via SendCommandInternal!")); } else { Plugin.Log.LogError((object)("[DirectConnect] Failed to send " + value + "!")); } return false; } catch (Exception ex) { Plugin.Log.LogError((object)("[DirectConnect] SendNetworkAction_Prefix error for " + text + ": " + ex.Message)); Plugin.Log.LogError((object)("[DirectConnect] Stack: " + ex.StackTrace)); return false; } } private static bool SendCommandDirectly(object relayInstance, object action, string commandName, string actionTypeName) { //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) try { NetworkBehaviour val = (Netwo
plugins/kcp2k.dll
Decompiled 7 months agousing System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Security; using System.Security.Permissions; using WhereAllocation; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("kcp2k.Tests")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [CompilerGenerated] [EditorBrowsable(EditorBrowsableState.Never)] [GeneratedCode("Unity.MonoScriptGenerator.MonoScriptInfoGenerator", null)] internal class UnitySourceGeneratedAssemblyMonoScriptTypes_v1 { private struct MonoScriptData { public byte[] FilePathsData; public byte[] TypesData; public int TotalTypes; public int TotalFiles; public bool IsEditorOnly; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static MonoScriptData Get() { MonoScriptData result = default(MonoScriptData); result.FilePathsData = new byte[1087] { 0, 0, 0, 1, 0, 0, 0, 65, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 104, 105, 103, 104, 108, 101, 118, 101, 108, 92, 75, 99, 112, 67, 108, 105, 101, 110, 116, 46, 99, 115, 0, 0, 0, 1, 0, 0, 0, 75, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 104, 105, 103, 104, 108, 101, 118, 101, 108, 92, 75, 99, 112, 67, 108, 105, 101, 110, 116, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 46, 99, 115, 0, 0, 0, 1, 0, 0, 0, 69, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 104, 105, 103, 104, 108, 101, 118, 101, 108, 92, 75, 99, 112, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 46, 99, 115, 0, 0, 0, 1, 0, 0, 0, 65, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 104, 105, 103, 104, 108, 101, 118, 101, 108, 92, 75, 99, 112, 83, 101, 114, 118, 101, 114, 46, 99, 115, 0, 0, 0, 1, 0, 0, 0, 75, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 104, 105, 103, 104, 108, 101, 118, 101, 108, 92, 75, 99, 112, 83, 101, 114, 118, 101, 114, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 46, 99, 115, 0, 0, 0, 1, 0, 0, 0, 59, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 104, 105, 103, 104, 108, 101, 118, 101, 108, 92, 76, 111, 103, 46, 99, 115, 0, 0, 0, 1, 0, 0, 0, 92, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 104, 105, 103, 104, 108, 101, 118, 101, 108, 92, 78, 111, 110, 65, 108, 108, 111, 99, 92, 75, 99, 112, 67, 108, 105, 101, 110, 116, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 78, 111, 110, 65, 108, 108, 111, 99, 46, 99, 115, 0, 0, 0, 1, 0, 0, 0, 82, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 104, 105, 103, 104, 108, 101, 118, 101, 108, 92, 78, 111, 110, 65, 108, 108, 111, 99, 92, 75, 99, 112, 67, 108, 105, 101, 110, 116, 78, 111, 110, 65, 108, 108, 111, 99, 46, 99, 115, 0, 0, 0, 1, 0, 0, 0, 92, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 104, 105, 103, 104, 108, 101, 118, 101, 108, 92, 78, 111, 110, 65, 108, 108, 111, 99, 92, 75, 99, 112, 83, 101, 114, 118, 101, 114, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 78, 111, 110, 65, 108, 108, 111, 99, 46, 99, 115, 0, 0, 0, 1, 0, 0, 0, 82, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 104, 105, 103, 104, 108, 101, 118, 101, 108, 92, 78, 111, 110, 65, 108, 108, 111, 99, 92, 75, 99, 112, 83, 101, 114, 118, 101, 114, 78, 111, 110, 65, 108, 108, 111, 99, 46, 99, 115, 0, 0, 0, 2, 0, 0, 0, 53, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 107, 99, 112, 92, 75, 99, 112, 46, 99, 115, 0, 0, 0, 1, 0, 0, 0, 54, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 107, 99, 112, 92, 80, 111, 111, 108, 46, 99, 115, 0, 0, 0, 1, 0, 0, 0, 57, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 107, 99, 112, 92, 83, 101, 103, 109, 101, 110, 116, 46, 99, 115, 0, 0, 0, 1, 0, 0, 0, 55, 92, 65, 115, 115, 101, 116, 115, 92, 77, 105, 114, 114, 111, 114, 92, 82, 117, 110, 116, 105, 109, 101, 92, 84, 114, 97, 110, 115, 112, 111, 114, 116, 92, 75, 67, 80, 92, 107, 99, 112, 50, 107, 92, 107, 99, 112, 92, 85, 116, 105, 108, 115, 46, 99, 115 }; result.TypesData = new byte[355] { 0, 0, 0, 0, 15, 107, 99, 112, 50, 107, 124, 75, 99, 112, 67, 108, 105, 101, 110, 116, 0, 0, 0, 0, 25, 107, 99, 112, 50, 107, 124, 75, 99, 112, 67, 108, 105, 101, 110, 116, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 0, 0, 0, 0, 19, 107, 99, 112, 50, 107, 124, 75, 99, 112, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 0, 0, 0, 0, 15, 107, 99, 112, 50, 107, 124, 75, 99, 112, 83, 101, 114, 118, 101, 114, 0, 0, 0, 0, 25, 107, 99, 112, 50, 107, 124, 75, 99, 112, 83, 101, 114, 118, 101, 114, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 0, 0, 0, 0, 9, 107, 99, 112, 50, 107, 124, 76, 111, 103, 0, 0, 0, 0, 33, 107, 99, 112, 50, 107, 124, 75, 99, 112, 67, 108, 105, 101, 110, 116, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 78, 111, 110, 65, 108, 108, 111, 99, 0, 0, 0, 0, 23, 107, 99, 112, 50, 107, 124, 75, 99, 112, 67, 108, 105, 101, 110, 116, 78, 111, 110, 65, 108, 108, 111, 99, 0, 0, 0, 0, 33, 107, 99, 112, 50, 107, 124, 75, 99, 112, 83, 101, 114, 118, 101, 114, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 78, 111, 110, 65, 108, 108, 111, 99, 0, 0, 0, 0, 23, 107, 99, 112, 50, 107, 124, 75, 99, 112, 83, 101, 114, 118, 101, 114, 78, 111, 110, 65, 108, 108, 111, 99, 0, 0, 0, 0, 9, 107, 99, 112, 50, 107, 124, 75, 99, 112, 0, 0, 0, 0, 17, 107, 99, 112, 50, 107, 46, 75, 99, 112, 124, 65, 99, 107, 73, 116, 101, 109, 0, 0, 0, 0, 10, 107, 99, 112, 50, 107, 124, 80, 111, 111, 108, 0, 0, 0, 0, 13, 107, 99, 112, 50, 107, 124, 83, 101, 103, 109, 101, 110, 116, 0, 0, 0, 0, 11, 107, 99, 112, 50, 107, 124, 85, 116, 105, 108, 115 }; result.TotalFiles = 14; result.TotalTypes = 15; result.IsEditorOnly = false; return result; } } namespace kcp2k; public enum KcpChannel : byte { Reliable = 1, Unreliable } public class KcpClient { public Action OnConnected; public Action<ArraySegment<byte>> OnData; public Action OnDisconnected; public KcpClientConnection connection; public bool connected; public KcpClient(Action OnConnected, Action<ArraySegment<byte>> OnData, Action OnDisconnected) { this.OnConnected = OnConnected; this.OnData = OnData; this.OnDisconnected = OnDisconnected; } protected virtual KcpClientConnection CreateConnection() { return new KcpClientConnection(); } public void Connect(string address, ushort port, bool noDelay, uint interval, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = 32u, uint receiveWindowSize = 128u, int timeout = 10000) { if (connected) { Log.Warning("KCP: client already connected!"); return; } connection = CreateConnection(); connection.OnAuthenticated = delegate { Log.Info("KCP: OnClientConnected"); connected = true; OnConnected(); }; connection.OnData = delegate(ArraySegment<byte> message) { OnData(message); }; connection.OnDisconnected = delegate { Log.Info("KCP: OnClientDisconnected"); connected = false; connection = null; OnDisconnected(); }; connection.Connect(address, port, noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout); } public void Send(ArraySegment<byte> segment, KcpChannel channel) { if (connected) { connection.SendData(segment, channel); } else { Log.Warning("KCP: can't send because client not connected!"); } } public void Disconnect() { if (connected) { connection?.Disconnect(); } } public void TickIncoming() { connection?.RawReceive(); connection?.TickIncoming(); } public void TickOutgoing() { connection?.TickOutgoing(); } public void Tick() { TickIncoming(); TickOutgoing(); } public void Pause() { connection?.Pause(); } public void Unpause() { connection?.Unpause(); } } public class KcpClientConnection : KcpConnection { private readonly byte[] rawReceiveBuffer = new byte[1200]; public static bool ResolveHostname(string hostname, out IPAddress[] addresses) { try { addresses = Dns.GetHostAddresses(hostname); return addresses.Length >= 1; } catch (SocketException) { Log.Info("Failed to resolve host: " + hostname); addresses = null; return false; } } protected virtual void CreateRemoteEndPoint(IPAddress[] addresses, ushort port) { remoteEndPoint = new IPEndPoint(addresses[0], port); } protected virtual int ReceiveFrom(byte[] buffer) { return socket.ReceiveFrom(buffer, ref remoteEndPoint); } public void Connect(string host, ushort port, bool noDelay, uint interval = 100u, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = 32u, uint receiveWindowSize = 128u, int timeout = 10000) { Log.Info($"KcpClient: connect to {host}:{port}"); if (ResolveHostname(host, out var addresses)) { CreateRemoteEndPoint(addresses, port); socket = new Socket(remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); socket.Connect(remoteEndPoint); SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout); SendHandshake(); RawReceive(); } else { OnDisconnected(); } } public void RawReceive() { try { if (socket == null) { return; } while (socket.Poll(0, SelectMode.SelectRead)) { int num = ReceiveFrom(rawReceiveBuffer); if (num <= rawReceiveBuffer.Length) { RawInput(rawReceiveBuffer, num); continue; } Log.Error($"KCP ClientConnection: message of size {num} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting."); Disconnect(); } } catch (SocketException) { } } protected override void Dispose() { socket.Close(); socket = null; } protected override void RawSend(byte[] data, int length) { socket.Send(data, length, SocketFlags.None); } } internal enum KcpState { Connected, Authenticated, Disconnected } public abstract class KcpConnection { protected Socket socket; protected EndPoint remoteEndPoint; internal Kcp kcp; private KcpState state = KcpState.Disconnected; public Action OnAuthenticated; public Action<ArraySegment<byte>> OnData; public Action OnDisconnected; private bool paused; public const int DEFAULT_TIMEOUT = 10000; public int timeout = 10000; private uint lastReceiveTime; private readonly Stopwatch refTime = new Stopwatch(); private const int CHANNEL_HEADER_SIZE = 1; public const int ReliableMaxMessageSize = 149224; public const int UnreliableMaxMessageSize = 1199; private byte[] kcpMessageBuffer = new byte[149225]; private byte[] kcpSendBuffer = new byte[149225]; private byte[] rawSendBuffer = new byte[1200]; public const int PING_INTERVAL = 1000; private uint lastPingTime; internal const int QueueDisconnectThreshold = 10000; public int SendQueueCount => kcp.snd_queue.Count; public int ReceiveQueueCount => kcp.rcv_queue.Count; public int SendBufferCount => kcp.snd_buf.Count; public int ReceiveBufferCount => kcp.rcv_buf.Count; public uint MaxSendRate => kcp.snd_wnd * kcp.mtu * 1000 / kcp.interval; public uint MaxReceiveRate => kcp.rcv_wnd * kcp.mtu * 1000 / kcp.interval; protected void SetupKcp(bool noDelay, uint interval = 100u, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = 32u, uint receiveWindowSize = 128u, int timeout = 10000) { kcp = new Kcp(0u, RawSendReliable); kcp.SetNoDelay(noDelay ? 1u : 0u, interval, fastResend, !congestionWindow); kcp.SetWindowSize(sendWindowSize, receiveWindowSize); kcp.SetMtu(1199u); this.timeout = timeout; state = KcpState.Connected; refTime.Start(); } private void HandleTimeout(uint time) { if (time >= lastReceiveTime + timeout) { Log.Warning($"KCP: Connection timed out after not receiving any message for {timeout}ms. Disconnecting."); Disconnect(); } } private void HandleDeadLink() { if (kcp.state == -1) { Log.Warning("KCP Connection dead_link detected. Disconnecting."); Disconnect(); } } private void HandlePing(uint time) { if (time >= lastPingTime + 1000) { SendPing(); lastPingTime = time; } } private void HandleChoked() { int num = kcp.rcv_queue.Count + kcp.snd_queue.Count + kcp.rcv_buf.Count + kcp.snd_buf.Count; if (num >= 10000) { Log.Warning("KCP: disconnecting connection because it can't process data fast enough.\n" + $"Queue total {num}>{10000}. rcv_queue={kcp.rcv_queue.Count} snd_queue={kcp.snd_queue.Count} rcv_buf={kcp.rcv_buf.Count} snd_buf={kcp.snd_buf.Count}\n" + "* Try to Enable NoDelay, decrease INTERVAL, disable Congestion Window (= enable NOCWND!), increase SEND/RECV WINDOW or compress data.\n* Or perhaps the network is simply too slow on our end, or on the other end.\n"); kcp.snd_queue.Clear(); Disconnect(); } } private bool ReceiveNextReliable(out KcpHeader header, out ArraySegment<byte> message) { int num = kcp.PeekSize(); if (num > 0) { if (num <= kcpMessageBuffer.Length) { int num2 = kcp.Receive(kcpMessageBuffer, num); if (num2 >= 0) { header = (KcpHeader)kcpMessageBuffer[0]; message = new ArraySegment<byte>(kcpMessageBuffer, 1, num - 1); lastReceiveTime = (uint)refTime.ElapsedMilliseconds; return true; } Log.Warning($"Receive failed with error={num2}. closing connection."); Disconnect(); } else { Log.Warning($"KCP: possible allocation attack for msgSize {num} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection."); Disconnect(); } } message = default(ArraySegment<byte>); header = KcpHeader.Disconnect; return false; } private void TickIncoming_Connected(uint time) { HandleTimeout(time); HandleDeadLink(); HandlePing(time); HandleChoked(); if (ReceiveNextReliable(out var header, out var _)) { switch (header) { case KcpHeader.Handshake: Log.Info("KCP: received handshake"); state = KcpState.Authenticated; OnAuthenticated?.Invoke(); break; case KcpHeader.Data: case KcpHeader.Disconnect: Log.Warning($"KCP: received invalid header {header} while Connected. Disconnecting the connection."); Disconnect(); break; case KcpHeader.Ping: break; } } } private void TickIncoming_Authenticated(uint time) { HandleTimeout(time); HandleDeadLink(); HandlePing(time); HandleChoked(); KcpHeader header; ArraySegment<byte> message; while (!paused && ReceiveNextReliable(out header, out message)) { switch (header) { case KcpHeader.Handshake: Log.Warning($"KCP: received invalid header {header} while Authenticated. Disconnecting the connection."); Disconnect(); break; case KcpHeader.Data: if (message.Count > 0) { OnData?.Invoke(message); break; } Log.Warning("KCP: received empty Data message while Authenticated. Disconnecting the connection."); Disconnect(); break; case KcpHeader.Disconnect: Log.Info("KCP: received disconnect message"); Disconnect(); break; } } } public void TickIncoming() { uint time = (uint)refTime.ElapsedMilliseconds; try { switch (state) { case KcpState.Connected: TickIncoming_Connected(time); break; case KcpState.Authenticated: TickIncoming_Authenticated(time); break; case KcpState.Disconnected: break; } } catch (SocketException arg) { Log.Info($"KCP Connection: Disconnecting because {arg}. This is fine."); Disconnect(); } catch (ObjectDisposedException arg2) { Log.Info($"KCP Connection: Disconnecting because {arg2}. This is fine."); Disconnect(); } catch (Exception ex) { Log.Error(ex.ToString()); Disconnect(); } } public void TickOutgoing() { uint currentTimeMilliSeconds = (uint)refTime.ElapsedMilliseconds; try { switch (state) { case KcpState.Connected: case KcpState.Authenticated: kcp.Update(currentTimeMilliSeconds); break; } } catch (SocketException arg) { Log.Info($"KCP Connection: Disconnecting because {arg}. This is fine."); Disconnect(); } catch (ObjectDisposedException arg2) { Log.Info($"KCP Connection: Disconnecting because {arg2}. This is fine."); Disconnect(); } catch (Exception ex) { Log.Error(ex.ToString()); Disconnect(); } } public void RawInput(byte[] buffer, int msgLength) { if (msgLength <= 0) { return; } byte b = buffer[0]; switch (b) { case 1: { int num = kcp.Input(buffer, 1, msgLength - 1); if (num != 0) { Log.Warning($"Input failed with error={num} for buffer with length={msgLength - 1}"); } break; } case 2: if (state == KcpState.Authenticated) { if (!paused) { ArraySegment<byte> obj = new ArraySegment<byte>(buffer, 1, msgLength - 1); OnData?.Invoke(obj); } lastReceiveTime = (uint)refTime.ElapsedMilliseconds; } else { Log.Warning($"KCP: received unreliable message in state {state}. Disconnecting the connection."); Disconnect(); } break; default: Log.Info($"Disconnecting connection because of invalid channel header: {b}"); Disconnect(); break; } } protected abstract void RawSend(byte[] data, int length); private void RawSendReliable(byte[] data, int length) { rawSendBuffer[0] = 1; Buffer.BlockCopy(data, 0, rawSendBuffer, 1, length); RawSend(rawSendBuffer, length + 1); } private void SendReliable(KcpHeader header, ArraySegment<byte> content) { if (1 + content.Count <= kcpSendBuffer.Length) { kcpSendBuffer[0] = (byte)header; if (content.Count > 0) { Buffer.BlockCopy(content.Array, content.Offset, kcpSendBuffer, 1, content.Count); } int num = kcp.Send(kcpSendBuffer, 0, 1 + content.Count); if (num < 0) { Log.Warning($"Send failed with error={num} for content with length={content.Count}"); } } else { Log.Error($"Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={149224}"); } } private void SendUnreliable(ArraySegment<byte> message) { if (message.Count <= 1199) { rawSendBuffer[0] = 2; Buffer.BlockCopy(message.Array, 0, rawSendBuffer, 1, message.Count); RawSend(rawSendBuffer, message.Count + 1); } else { Log.Error($"Failed to send unreliable message of size {message.Count} because it's larger than UnreliableMaxMessageSize={1199}"); } } public void SendHandshake() { Log.Info("KcpConnection: sending Handshake to other end!"); SendReliable(KcpHeader.Handshake, default(ArraySegment<byte>)); } public void SendData(ArraySegment<byte> data, KcpChannel channel) { if (data.Count == 0) { Log.Warning("KcpConnection: tried sending empty message. This should never happen. Disconnecting."); Disconnect(); return; } switch (channel) { case KcpChannel.Reliable: SendReliable(KcpHeader.Data, data); break; case KcpChannel.Unreliable: SendUnreliable(data); break; } } private void SendPing() { SendReliable(KcpHeader.Ping, default(ArraySegment<byte>)); } private void SendDisconnect() { SendReliable(KcpHeader.Disconnect, default(ArraySegment<byte>)); } protected virtual void Dispose() { } public void Disconnect() { if (state == KcpState.Disconnected) { return; } if (socket.Connected) { try { SendDisconnect(); kcp.Flush(); } catch (SocketException) { } catch (ObjectDisposedException) { } } Log.Info("KCP Connection: Disconnected."); state = KcpState.Disconnected; OnDisconnected?.Invoke(); } public EndPoint GetRemoteEndPoint() { return remoteEndPoint; } public void Pause() { paused = true; } public void Unpause() { paused = false; lastReceiveTime = (uint)refTime.ElapsedMilliseconds; } } public enum KcpHeader : byte { Handshake = 1, Ping, Data, Disconnect } public class KcpServer { public Action<int> OnConnected; public Action<int, ArraySegment<byte>> OnData; public Action<int> OnDisconnected; public bool DualMode; public bool NoDelay; public uint Interval; public int FastResend; public bool CongestionWindow; public uint SendWindowSize; public uint ReceiveWindowSize; public int Timeout; protected Socket socket; private EndPoint newClientEP; private readonly byte[] rawReceiveBuffer = new byte[1200]; public Dictionary<int, KcpServerConnection> connections = new Dictionary<int, KcpServerConnection>(); private HashSet<int> connectionsToRemove = new HashSet<int>(); public KcpServer(Action<int> OnConnected, Action<int, ArraySegment<byte>> OnData, Action<int> OnDisconnected, bool DualMode, bool NoDelay, uint Interval, int FastResend = 0, bool CongestionWindow = true, uint SendWindowSize = 32u, uint ReceiveWindowSize = 128u, int Timeout = 10000) { this.OnConnected = OnConnected; this.OnData = OnData; this.OnDisconnected = OnDisconnected; this.DualMode = DualMode; this.NoDelay = NoDelay; this.Interval = Interval; this.FastResend = FastResend; this.CongestionWindow = CongestionWindow; this.SendWindowSize = SendWindowSize; this.ReceiveWindowSize = ReceiveWindowSize; this.Timeout = Timeout; newClientEP = (DualMode ? new IPEndPoint(IPAddress.IPv6Any, 0) : new IPEndPoint(IPAddress.Any, 0)); } public bool IsActive() { return socket != null; } public void Start(ushort port) { if (socket != null) { Log.Warning("KCP: server already started!"); } if (DualMode) { socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); socket.DualMode = true; socket.Bind(new IPEndPoint(IPAddress.IPv6Any, port)); } else { socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); socket.Bind(new IPEndPoint(IPAddress.Any, port)); } } public void Send(int connectionId, ArraySegment<byte> segment, KcpChannel channel) { if (connections.TryGetValue(connectionId, out var value)) { value.SendData(segment, channel); } } public void Disconnect(int connectionId) { if (connections.TryGetValue(connectionId, out var value)) { value.Disconnect(); } } public string GetClientAddress(int connectionId) { if (connections.TryGetValue(connectionId, out var value)) { return (value.GetRemoteEndPoint() as IPEndPoint).Address.ToString(); } return ""; } protected virtual int ReceiveFrom(byte[] buffer, out int connectionHash) { int result = socket.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP); connectionHash = newClientEP.GetHashCode(); return result; } protected virtual KcpServerConnection CreateConnection() { return new KcpServerConnection(socket, newClientEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout); } public void TickIncoming() { while (socket != null && socket.Poll(0, SelectMode.SelectRead)) { try { int connectionId; int num = ReceiveFrom(rawReceiveBuffer, out connectionId); if (num <= rawReceiveBuffer.Length) { if (!connections.TryGetValue(connectionId, out var connection)) { connection = CreateConnection(); connection.OnAuthenticated = delegate { connection.SendHandshake(); connections.Add(connectionId, connection); Log.Info($"KCP: server added connection({connectionId})"); connection.OnData = delegate(ArraySegment<byte> message) { OnData(connectionId, message); }; connection.OnDisconnected = delegate { connectionsToRemove.Add(connectionId); Log.Info($"KCP: OnServerDisconnected({connectionId})"); OnDisconnected(connectionId); }; Log.Info($"KCP: OnServerConnected({connectionId})"); OnConnected(connectionId); }; connection.RawInput(rawReceiveBuffer, num); connection.TickIncoming(); } else { connection.RawInput(rawReceiveBuffer, num); } } else { Log.Error($"KCP Server: message of size {num} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting connectionId={connectionId}."); Disconnect(connectionId); } } catch (SocketException) { } } foreach (KcpServerConnection value in connections.Values) { value.TickIncoming(); } foreach (int item in connectionsToRemove) { connections.Remove(item); } connectionsToRemove.Clear(); } public void TickOutgoing() { foreach (KcpServerConnection value in connections.Values) { value.TickOutgoing(); } } public void Tick() { TickIncoming(); TickOutgoing(); } public void Stop() { socket?.Close(); socket = null; } public void Pause() { foreach (KcpServerConnection value in connections.Values) { value.Pause(); } } public void Unpause() { foreach (KcpServerConnection value in connections.Values) { value.Unpause(); } } } public class KcpServerConnection : KcpConnection { public KcpServerConnection(Socket socket, EndPoint remoteEndPoint, bool noDelay, uint interval = 100u, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = 32u, uint receiveWindowSize = 128u, int timeout = 10000) { base.socket = socket; base.remoteEndPoint = remoteEndPoint; SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout); } protected override void RawSend(byte[] data, int length) { socket.SendTo(data, 0, length, SocketFlags.None, remoteEndPoint); } } public static class Log { public static Action<string> Info = Console.WriteLine; public static Action<string> Warning = Console.WriteLine; public static Action<string> Error = Console.Error.WriteLine; } public class KcpClientConnectionNonAlloc : KcpClientConnection { private IPEndPointNonAlloc reusableEP; protected override void CreateRemoteEndPoint(IPAddress[] addresses, ushort port) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown reusableEP = new IPEndPointNonAlloc(addresses[0], (int)port); base.CreateRemoteEndPoint(addresses, port); } protected override int ReceiveFrom(byte[] buffer) { return Extensions.ReceiveFrom_NonAlloc(socket, buffer, reusableEP); } } public class KcpClientNonAlloc : KcpClient { public KcpClientNonAlloc(Action OnConnected, Action<ArraySegment<byte>> OnData, Action OnDisconnected) : base(OnConnected, OnData, OnDisconnected) { } protected override KcpClientConnection CreateConnection() { return new KcpClientConnectionNonAlloc(); } } public class KcpServerConnectionNonAlloc : KcpServerConnection { private IPEndPointNonAlloc reusableSendEndPoint; public KcpServerConnectionNonAlloc(Socket socket, EndPoint remoteEndpoint, IPEndPointNonAlloc reusableSendEndPoint, bool noDelay, uint interval = 100u, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = 32u, uint receiveWindowSize = 128u, int timeout = 10000) : base(socket, remoteEndpoint, noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout) { this.reusableSendEndPoint = reusableSendEndPoint; } protected override void RawSend(byte[] data, int length) { Extensions.SendTo_NonAlloc(socket, data, 0, length, SocketFlags.None, reusableSendEndPoint); } } public class KcpServerNonAlloc : KcpServer { private IPEndPointNonAlloc reusableClientEP; public KcpServerNonAlloc(Action<int> OnConnected, Action<int, ArraySegment<byte>> OnData, Action<int> OnDisconnected, bool DualMode, bool NoDelay, uint Interval, int FastResend = 0, bool CongestionWindow = true, uint SendWindowSize = 32u, uint ReceiveWindowSize = 128u, int Timeout = 10000) : base(OnConnected, OnData, OnDisconnected, DualMode, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown reusableClientEP = (DualMode ? new IPEndPointNonAlloc(IPAddress.IPv6Any, 0) : new IPEndPointNonAlloc(IPAddress.Any, 0)); } protected override int ReceiveFrom(byte[] buffer, out int connectionHash) { int result = Extensions.ReceiveFrom_NonAlloc(socket, buffer, 0, buffer.Length, SocketFlags.None, reusableClientEP); SocketAddress temp = reusableClientEP.temp; connectionHash = temp.GetHashCode(); return result; } protected override KcpServerConnection CreateConnection() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown IPEndPoint iPEndPoint = reusableClientEP.DeepCopyIPEndPoint(); IPEndPointNonAlloc reusableSendEndPoint = new IPEndPointNonAlloc(iPEndPoint.Address, iPEndPoint.Port); return new KcpServerConnectionNonAlloc(socket, iPEndPoint, reusableSendEndPoint, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout); } } public class Kcp { internal struct AckItem { internal uint serialNumber; internal uint timestamp; } public const int RTO_NDL = 30; public const int RTO_MIN = 100; public const int RTO_DEF = 200; public const int RTO_MAX = 60000; public const int CMD_PUSH = 81; public const int CMD_ACK = 82; public const int CMD_WASK = 83; public const int CMD_WINS = 84; public const int ASK_SEND = 1; public const int ASK_TELL = 2; public const int WND_SND = 32; public const int WND_RCV = 128; public const int MTU_DEF = 1200; public const int ACK_FAST = 3; public const int INTERVAL = 100; public const int OVERHEAD = 24; public const int DEADLINK = 20; public const int THRESH_INIT = 2; public const int THRESH_MIN = 2; public const int PROBE_INIT = 7000; public const int PROBE_LIMIT = 120000; public const int FASTACK_LIMIT = 5; internal int state; private readonly uint conv; internal uint mtu; internal uint mss; internal uint snd_una; internal uint snd_nxt; internal uint rcv_nxt; internal uint ssthresh; internal int rx_rttval; internal int rx_srtt; internal int rx_rto; internal int rx_minrto; internal uint snd_wnd; internal uint rcv_wnd; internal uint rmt_wnd; internal uint cwnd; internal uint probe; internal uint interval; internal uint ts_flush; internal uint xmit; internal uint nodelay; internal bool updated; internal uint ts_probe; internal uint probe_wait; internal uint dead_link; internal uint incr; internal uint current; internal int fastresend; internal int fastlimit; internal bool nocwnd; internal readonly Queue<Segment> snd_queue = new Queue<Segment>(16); internal readonly Queue<Segment> rcv_queue = new Queue<Segment>(16); internal readonly List<Segment> snd_buf = new List<Segment>(16); internal readonly List<Segment> rcv_buf = new List<Segment>(16); internal readonly List<AckItem> acklist = new List<AckItem>(16); internal byte[] buffer; private readonly Action<byte[], int> output; private readonly Pool<Segment> SegmentPool = new Pool<Segment>(() => new Segment(), delegate(Segment segment) { segment.Reset(); }, 32); public int WaitSnd => snd_buf.Count + snd_queue.Count; public Kcp(uint conv, Action<byte[], int> output) { this.conv = conv; this.output = output; snd_wnd = 32u; rcv_wnd = 128u; rmt_wnd = 128u; mtu = 1200u; mss = mtu - 24; rx_rto = 200; rx_minrto = 100; interval = 100u; ts_flush = 100u; ssthresh = 2u; fastlimit = 5; dead_link = 20u; buffer = new byte[(mtu + 24) * 3]; } private Segment SegmentNew() { return SegmentPool.Take(); } private void SegmentDelete(Segment seg) { SegmentPool.Return(seg); } public int Receive(byte[] buffer, int len) { if (len < 0) { throw new NotSupportedException("Receive ispeek for negative len is not supported!"); } if (rcv_queue.Count == 0) { return -1; } if (len < 0) { len = -len; } int num = PeekSize(); if (num < 0) { return -2; } if (num > len) { return -3; } bool flag = rcv_queue.Count >= rcv_wnd; int num2 = 0; len = 0; while (rcv_queue.Count > 0) { Segment segment = rcv_queue.Dequeue(); Buffer.BlockCopy(segment.data.GetBuffer(), 0, buffer, num2, (int)segment.data.Position); num2 += (int)segment.data.Position; len += (int)segment.data.Position; uint frg = segment.frg; SegmentDelete(segment); if (frg == 0) { break; } } int num3 = 0; foreach (Segment item in rcv_buf) { if (item.sn == rcv_nxt && rcv_queue.Count < rcv_wnd) { num3++; rcv_queue.Enqueue(item); rcv_nxt++; continue; } break; } rcv_buf.RemoveRange(0, num3); if (rcv_queue.Count < rcv_wnd && flag) { probe |= 2u; } return len; } public int PeekSize() { int num = 0; if (rcv_queue.Count == 0) { return -1; } Segment segment = rcv_queue.Peek(); if (segment.frg == 0) { return (int)segment.data.Position; } if (rcv_queue.Count < segment.frg + 1) { return -1; } foreach (Segment item in rcv_queue) { num += (int)item.data.Position; if (item.frg == 0) { break; } } return num; } public int Send(byte[] buffer, int offset, int len) { if (len < 0) { return -1; } int num = (int)((len <= mss) ? 1 : ((len + mss - 1) / mss)); if (num >= 128) { return -2; } if (num == 0) { num = 1; } for (int i = 0; i < num; i++) { int num2 = ((len > (int)mss) ? ((int)mss) : len); Segment segment = SegmentNew(); if (len > 0) { segment.data.Write(buffer, offset, num2); } segment.frg = (byte)(num - i - 1); snd_queue.Enqueue(segment); offset += num2; len -= num2; } return 0; } private void UpdateAck(int rtt) { if (rx_srtt == 0) { rx_srtt = rtt; rx_rttval = rtt / 2; } else { int num = rtt - rx_srtt; if (num < 0) { num = -num; } rx_rttval = (3 * rx_rttval + num) / 4; rx_srtt = (7 * rx_srtt + rtt) / 8; if (rx_srtt < 1) { rx_srtt = 1; } } int value = rx_srtt + Math.Max((int)interval, 4 * rx_rttval); rx_rto = Utils.Clamp(value, rx_minrto, 60000); } internal void ShrinkBuf() { if (snd_buf.Count > 0) { Segment segment = snd_buf[0]; snd_una = segment.sn; } else { snd_una = snd_nxt; } } internal void ParseAck(uint sn) { if (Utils.TimeDiff(sn, snd_una) < 0 || Utils.TimeDiff(sn, snd_nxt) >= 0) { return; } for (int i = 0; i < snd_buf.Count; i++) { Segment segment = snd_buf[i]; if (sn == segment.sn) { snd_buf.RemoveAt(i); SegmentDelete(segment); break; } if (Utils.TimeDiff(sn, segment.sn) < 0) { break; } } } private void ParseUna(uint una) { int num = 0; foreach (Segment item in snd_buf) { if (Utils.TimeDiff(una, item.sn) > 0) { num++; SegmentDelete(item); continue; } break; } snd_buf.RemoveRange(0, num); } private void ParseFastack(uint sn, uint ts) { if (Utils.TimeDiff(sn, snd_una) < 0 || Utils.TimeDiff(sn, snd_nxt) >= 0) { return; } foreach (Segment item in snd_buf) { if (Utils.TimeDiff(sn, item.sn) < 0) { break; } if (sn != item.sn) { item.fastack++; } } } private void AckPush(uint sn, uint ts) { acklist.Add(new AckItem { serialNumber = sn, timestamp = ts }); } private void ParseData(Segment newseg) { uint sn = newseg.sn; if (Utils.TimeDiff(sn, rcv_nxt + rcv_wnd) >= 0 || Utils.TimeDiff(sn, rcv_nxt) < 0) { SegmentDelete(newseg); return; } InsertSegmentInReceiveBuffer(newseg); MoveReceiveBufferDataToReceiveQueue(); } internal void InsertSegmentInReceiveBuffer(Segment newseg) { bool flag = false; int num; for (num = rcv_buf.Count - 1; num >= 0; num--) { Segment segment = rcv_buf[num]; if (segment.sn == newseg.sn) { flag = true; break; } if (Utils.TimeDiff(newseg.sn, segment.sn) > 0) { break; } } if (!flag) { rcv_buf.Insert(num + 1, newseg); } else { SegmentDelete(newseg); } } private void MoveReceiveBufferDataToReceiveQueue() { int num = 0; foreach (Segment item in rcv_buf) { if (item.sn == rcv_nxt && rcv_queue.Count < rcv_wnd) { num++; rcv_queue.Enqueue(item); rcv_nxt++; continue; } break; } rcv_buf.RemoveRange(0, num); } public int Input(byte[] data, int offset, int size) { uint earlier = snd_una; uint num = 0u; uint ts = 0u; int num2 = 0; if (data == null || size < 24) { return -1; } while (true) { uint c = 0u; uint c2 = 0u; uint c3 = 0u; uint c4 = 0u; uint c5 = 0u; ushort c6 = 0; byte c7 = 0; byte c8 = 0; if (size < 24) { break; } offset += Utils.Decode32U(data, offset, ref c5); if (c5 != conv) { return -1; } offset += Utils.Decode8u(data, offset, ref c7); offset += Utils.Decode8u(data, offset, ref c8); offset += Utils.Decode16U(data, offset, ref c6); offset += Utils.Decode32U(data, offset, ref c); offset += Utils.Decode32U(data, offset, ref c2); offset += Utils.Decode32U(data, offset, ref c4); offset += Utils.Decode32U(data, offset, ref c3); size -= 24; if (size < c3 || c3 < 0) { return -2; } if (c7 != 81 && c7 != 82 && c7 != 83 && c7 != 84) { return -3; } rmt_wnd = c6; ParseUna(c4); ShrinkBuf(); switch (c7) { case 82: if (Utils.TimeDiff(current, c) >= 0) { UpdateAck(Utils.TimeDiff(current, c)); } ParseAck(c2); ShrinkBuf(); if (num2 == 0) { num2 = 1; num = c2; ts = c; } else if (Utils.TimeDiff(c2, num) > 0) { num = c2; ts = c; } break; case 81: if (Utils.TimeDiff(c2, rcv_nxt + rcv_wnd) >= 0) { break; } AckPush(c2, c); if (Utils.TimeDiff(c2, rcv_nxt) >= 0) { Segment segment = SegmentNew(); segment.conv = c5; segment.cmd = c7; segment.frg = c8; segment.wnd = c6; segment.ts = c; segment.sn = c2; segment.una = c4; if (c3 != 0) { segment.data.Write(data, offset, (int)c3); } ParseData(segment); } break; case 83: probe |= 2u; break; default: return -3; case 84: break; } offset += (int)c3; size -= (int)c3; } if (num2 != 0) { ParseFastack(num, ts); } if (Utils.TimeDiff(snd_una, earlier) > 0 && cwnd < rmt_wnd) { if (cwnd < ssthresh) { cwnd++; incr += mss; } else { if (incr < mss) { incr = mss; } incr += mss * mss / incr + mss / 16; if ((cwnd + 1) * mss <= incr) { cwnd = (incr + mss - 1) / ((mss == 0) ? 1 : mss); } } if (cwnd > rmt_wnd) { cwnd = rmt_wnd; incr = rmt_wnd * mss; } } return 0; } private uint WndUnused() { if (rcv_queue.Count < rcv_wnd) { return rcv_wnd - (uint)rcv_queue.Count; } return 0u; } public void Flush() { int offset = 0; bool flag = false; if (!updated) { return; } Segment segment = SegmentNew(); segment.conv = conv; segment.cmd = 82u; segment.wnd = WndUnused(); segment.una = rcv_nxt; foreach (AckItem item in acklist) { MakeSpace(24); segment.sn = item.serialNumber; segment.ts = item.timestamp; offset += segment.Encode(buffer, offset); } acklist.Clear(); if (rmt_wnd == 0) { if (probe_wait == 0) { probe_wait = 7000u; ts_probe = current + probe_wait; } else if (Utils.TimeDiff(current, ts_probe) >= 0) { if (probe_wait < 7000) { probe_wait = 7000u; } probe_wait += probe_wait / 2; if (probe_wait > 120000) { probe_wait = 120000u; } ts_probe = current + probe_wait; probe |= 1u; } } else { ts_probe = 0u; probe_wait = 0u; } if ((probe & (true ? 1u : 0u)) != 0) { segment.cmd = 83u; MakeSpace(24); offset += segment.Encode(buffer, offset); } if ((probe & 2u) != 0) { segment.cmd = 84u; MakeSpace(24); offset += segment.Encode(buffer, offset); } probe = 0u; uint num = Math.Min(snd_wnd, rmt_wnd); if (!nocwnd) { num = Math.Min(cwnd, num); } while (Utils.TimeDiff(snd_nxt, snd_una + num) < 0 && snd_queue.Count != 0) { Segment segment2 = snd_queue.Dequeue(); segment2.conv = conv; segment2.cmd = 81u; segment2.wnd = segment.wnd; segment2.ts = current; segment2.sn = snd_nxt++; segment2.una = rcv_nxt; segment2.resendts = current; segment2.rto = rx_rto; segment2.fastack = 0u; segment2.xmit = 0u; snd_buf.Add(segment2); } uint num2 = ((fastresend > 0) ? ((uint)fastresend) : uint.MaxValue); uint num3 = ((nodelay == 0) ? ((uint)rx_rto >> 3) : 0u); int num4 = 0; foreach (Segment item2 in snd_buf) { bool flag2 = false; if (item2.xmit == 0) { flag2 = true; item2.xmit++; item2.rto = rx_rto; item2.resendts = (uint)((int)current + item2.rto) + num3; } else if (Utils.TimeDiff(current, item2.resendts) >= 0) { flag2 = true; item2.xmit++; xmit++; if (nodelay == 0) { item2.rto += Math.Max(item2.rto, rx_rto); } else { int num5 = ((nodelay < 2) ? item2.rto : rx_rto); item2.rto += num5 / 2; } item2.resendts = current + (uint)item2.rto; flag = true; } else if (item2.fastack >= num2 && (item2.xmit <= fastlimit || fastlimit <= 0)) { flag2 = true; item2.xmit++; item2.fastack = 0u; item2.resendts = current + (uint)item2.rto; num4++; } if (flag2) { item2.ts = current; item2.wnd = segment.wnd; item2.una = rcv_nxt; int space2 = 24 + (int)item2.data.Position; MakeSpace(space2); offset += item2.Encode(buffer, offset); if (item2.data.Position > 0) { Buffer.BlockCopy(item2.data.GetBuffer(), 0, buffer, offset, (int)item2.data.Position); offset += (int)item2.data.Position; } if (item2.xmit >= dead_link) { state = -1; } } } SegmentDelete(segment); FlushBuffer(); if (num4 > 0) { uint num6 = snd_nxt - snd_una; ssthresh = num6 / 2; if (ssthresh < 2) { ssthresh = 2u; } cwnd = ssthresh + num2; incr = cwnd * mss; } if (flag) { ssthresh = num / 2; if (ssthresh < 2) { ssthresh = 2u; } cwnd = 1u; incr = mss; } if (cwnd < 1) { cwnd = 1u; incr = mss; } void FlushBuffer() { if (offset > 0) { output(buffer, offset); } } void MakeSpace(int space) { if (offset + space > mtu) { output(buffer, offset); offset = 0; } } } public void Update(uint currentTimeMilliSeconds) { current = currentTimeMilliSeconds; if (!updated) { updated = true; ts_flush = current; } int num = Utils.TimeDiff(current, ts_flush); if (num >= 10000 || num < -10000) { ts_flush = current; num = 0; } if (num >= 0) { ts_flush += interval; if (Utils.TimeDiff(current, ts_flush) >= 0) { ts_flush = current + interval; } Flush(); } } public uint Check(uint current_) { uint num = ts_flush; int num2 = int.MaxValue; int num3 = int.MaxValue; if (!updated) { return current_; } if (Utils.TimeDiff(current_, num) >= 10000 || Utils.TimeDiff(current_, num) < -10000) { num = current_; } if (Utils.TimeDiff(current_, num) >= 0) { return current_; } num2 = Utils.TimeDiff(num, current_); foreach (Segment item in snd_buf) { int num4 = Utils.TimeDiff(item.resendts, current_); if (num4 <= 0) { return current_; } if (num4 < num3) { num3 = num4; } } uint num5 = (uint)((num3 < num2) ? num3 : num2); if (num5 >= interval) { num5 = interval; } return current_ + num5; } public void SetMtu(uint mtu) { if (mtu < 50 || mtu < 24) { throw new ArgumentException("MTU must be higher than 50 and higher than OVERHEAD"); } buffer = new byte[(mtu + 24) * 3]; this.mtu = mtu; mss = mtu - 24; } public void SetInterval(uint interval) { if (interval > 5000) { interval = 5000u; } else if (interval < 10) { interval = 10u; } this.interval = interval; } public void SetNoDelay(uint nodelay, uint interval = 100u, int resend = 0, bool nocwnd = false) { this.nodelay = nodelay; if (nodelay != 0) { rx_minrto = 30; } else { rx_minrto = 100; } if (interval >= 0) { if (interval > 5000) { interval = 5000u; } else if (interval < 10) { interval = 10u; } this.interval = interval; } if (resend >= 0) { fastresend = resend; } this.nocwnd = nocwnd; } public void SetWindowSize(uint sendWindow, uint receiveWindow) { if (sendWindow != 0) { snd_wnd = sendWindow; } if (receiveWindow != 0) { rcv_wnd = Math.Max(receiveWindow, 128u); } } } public class Pool<T> { private readonly Stack<T> objects = new Stack<T>(); private readonly Func<T> objectGenerator; private readonly Action<T> objectResetter; public int Count => objects.Count; public Pool(Func<T> objectGenerator, Action<T> objectResetter, int initialCapacity) { this.objectGenerator = objectGenerator; this.objectResetter = objectResetter; for (int i = 0; i < initialCapacity; i++) { objects.Push(objectGenerator()); } } public T Take() { if (objects.Count <= 0) { return objectGenerator(); } return objects.Pop(); } public void Return(T item) { objectResetter(item); objects.Push(item); } public void Clear() { objects.Clear(); } } internal class Segment { internal uint conv; internal uint cmd; internal uint frg; internal uint wnd; internal uint ts; internal uint sn; internal uint una; internal uint resendts; internal int rto; internal uint fastack; internal uint xmit; internal MemoryStream data = new MemoryStream(1200); internal int Encode(byte[] ptr, int offset) { int num = offset; offset += Utils.Encode32U(ptr, offset, conv); offset += Utils.Encode8u(ptr, offset, (byte)cmd); offset += Utils.Encode8u(ptr, offset, (byte)frg); offset += Utils.Encode16U(ptr, offset, (ushort)wnd); offset += Utils.Encode32U(ptr, offset, ts); offset += Utils.Encode32U(ptr, offset, sn); offset += Utils.Encode32U(ptr, offset, una); offset += Utils.Encode32U(ptr, offset, (uint)data.Position); return offset - num; } internal void Reset() { conv = 0u; cmd = 0u; frg = 0u; wnd = 0u; ts = 0u; sn = 0u; una = 0u; rto = 0; xmit = 0u; resendts = 0u; fastack = 0u; data.SetLength(0L); } } public static class Utils { public static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value > max) { return max; } return value; } public static int Encode8u(byte[] p, int offset, byte c) { p[offset] = c; return 1; } public static int Decode8u(byte[] p, int offset, ref byte c) { c = p[offset]; return 1; } public static int Encode16U(byte[] p, int offset, ushort w) { p[offset] = (byte)w; p[1 + offset] = (byte)(w >> 8); return 2; } public static int Decode16U(byte[] p, int offset, ref ushort c) { ushort num = 0; num |= p[offset]; num |= (ushort)(p[1 + offset] << 8); c = num; return 2; } public static int Encode32U(byte[] p, int offset, uint l) { p[offset] = (byte)l; p[1 + offset] = (byte)(l >> 8); p[2 + offset] = (byte)(l >> 16); p[3 + offset] = (byte)(l >> 24); return 4; } public static int Decode32U(byte[] p, int offset, ref uint c) { uint num = 0u; num |= p[offset]; num |= (uint)(p[1 + offset] << 8); num |= (uint)(p[2 + offset] << 16); num |= (uint)(p[3 + offset] << 24); c = num; return 4; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int TimeDiff(uint later, uint earlier) { return (int)(later - earlier); } }