using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("EasyDeliveryCoLanCoop")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+5f11cd6bb07c606d089baa3482b68acadd8593e1")] [assembly: AssemblyProduct("EasyDeliveryCoLanCoop")] [assembly: AssemblyTitle("EasyDeliveryCoLanCoop")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace EasyDeliveryCoLanCoop { internal static class CarVisualClone { internal static GameObject? TryCreateCarPrefab(Transform carRoot) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0031: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)carRoot == (Object)null) { return null; } try { GameObject val = new GameObject("EasyDeliveryCoLanCoop.CarPrefab." + ((Object)carRoot).name); ((Object)val).hideFlags = (HideFlags)52; val.transform.localScale = carRoot.lossyScale; CopyAudioSource(carRoot, val.transform); for (int i = 0; i < carRoot.childCount; i++) { CopyRecursive(carRoot.GetChild(i), val.transform); } val.SetActive(false); return val; } catch { return null; } } private static void CopyRecursive(Transform src, Transform dstParent) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) if (ShouldSkip(src)) { return; } GameObject val = new GameObject(((Object)src).name); ((Object)val).hideFlags = (HideFlags)52; val.transform.SetParent(dstParent, false); val.transform.localPosition = src.localPosition; val.transform.localRotation = src.localRotation; val.transform.localScale = src.localScale; MeshFilter component = ((Component)src).GetComponent(); MeshRenderer component2 = ((Component)src).GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)component.sharedMesh != (Object)null) { val.AddComponent().sharedMesh = component.sharedMesh; } if ((Object)(object)component2 != (Object)null) { MeshRenderer val2 = val.AddComponent(); Material[] sharedMaterials = ((Renderer)component2).sharedMaterials; Material[] array = (Material[])(object)new Material[sharedMaterials.Length]; for (int i = 0; i < sharedMaterials.Length; i++) { array[i] = (((Object)(object)sharedMaterials[i] != (Object)null) ? new Material(sharedMaterials[i]) : ((Material)null)); } ((Renderer)val2).sharedMaterials = array; ((Renderer)val2).shadowCastingMode = ((Renderer)component2).shadowCastingMode; ((Renderer)val2).receiveShadows = ((Renderer)component2).receiveShadows; ((Renderer)val2).lightProbeUsage = ((Renderer)component2).lightProbeUsage; ((Renderer)val2).reflectionProbeUsage = ((Renderer)component2).reflectionProbeUsage; } CopyAudioSource(src, val.transform); for (int j = 0; j < src.childCount; j++) { CopyRecursive(src.GetChild(j), val.transform); } } private static void CopyAudioSource(Transform src, Transform dst) { //IL_00fd: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)src == (Object)null || (Object)(object)dst == (Object)null) { return; } AudioSource component = ((Component)src).GetComponent(); if (!((Object)(object)component == (Object)null)) { AudioSource val = ((Component)dst).GetComponent(); if ((Object)(object)val == (Object)null) { val = ((Component)dst).gameObject.AddComponent(); } val.clip = component.clip; val.outputAudioMixerGroup = component.outputAudioMixerGroup; val.mute = component.mute; val.bypassEffects = component.bypassEffects; val.bypassListenerEffects = component.bypassListenerEffects; val.bypassReverbZones = component.bypassReverbZones; val.playOnAwake = false; val.loop = component.loop; val.priority = component.priority; val.volume = component.volume; val.pitch = component.pitch; val.panStereo = component.panStereo; val.spatialBlend = component.spatialBlend; val.reverbZoneMix = component.reverbZoneMix; val.dopplerLevel = component.dopplerLevel; val.spread = component.spread; val.rolloffMode = component.rolloffMode; val.minDistance = component.minDistance; val.maxDistance = component.maxDistance; ((Behaviour)val).enabled = ((Behaviour)component).enabled; } } private static bool ShouldSkip(Transform t) { string name = ((Object)t).name; if (!string.IsNullOrEmpty(name)) { string text = name.ToLowerInvariant(); if (text.Contains("wheelcollider") || text.Contains("collider") || text.Contains("trigger") || text.Contains("hitbox")) { return true; } if (text.Contains("debug") || text.Contains("gizmo")) { return true; } if (text == "sphere" || text.EndsWith("sphere")) { return true; } } if ((Object)(object)((Component)t).GetComponent() != (Object)null && (Object)(object)((Component)t).GetComponent() == (Object)null) { return true; } return false; } } internal static class ExternalAssetBundle { private static AssetBundle? _bundle; private static string? _loadedPath; private static float _nextRetryAt; private static bool _loggedLoadError; internal static GameObject? TryLoadGameObject(string assetName) { if (!Plugin.ExternalAssetsEnabled.Value) { return null; } if (string.IsNullOrWhiteSpace(assetName)) { return null; } AssetBundle val = EnsureLoaded(); if ((Object)(object)val == (Object)null) { return null; } try { GameObject val2 = val.LoadAsset(assetName); if ((Object)(object)val2 != (Object)null) { return val2; } string fileName = Path.GetFileName(assetName); if (!string.IsNullOrWhiteSpace(fileName) && !string.Equals(fileName, assetName, StringComparison.OrdinalIgnoreCase)) { return val.LoadAsset(fileName); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("ExternalAssetBundle: failed to load asset '" + assetName + "': " + ex.Message)); } return null; } internal static GameObject? TryLoadGameObjectByBaseName(string baseName) { if (!Plugin.ExternalAssetsEnabled.Value) { return null; } if (string.IsNullOrWhiteSpace(baseName)) { return null; } AssetBundle val = EnsureLoaded(); if ((Object)(object)val == (Object)null) { return null; } try { baseName = Path.GetFileNameWithoutExtension(baseName).Trim(); if (baseName.Length == 0) { return null; } string[] allAssetNames = val.GetAllAssetNames(); if (allAssetNames == null || allAssetNames.Length == 0) { return null; } foreach (string text in allAssetNames) { if (!string.IsNullOrEmpty(text) && string.Equals(Path.GetFileNameWithoutExtension(text), baseName, StringComparison.OrdinalIgnoreCase)) { GameObject val2 = val.LoadAsset(text); if ((Object)(object)val2 != (Object)null) { return val2; } } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("ExternalAssetBundle: TryLoadGameObjectByBaseName failed for '" + baseName + "': " + ex.Message)); } return null; } internal static void InvalidateIfConfigChanged() { string text = ResolveBundlePath(Plugin.ExternalAssetsBundlePath.Value); if (string.IsNullOrWhiteSpace(text)) { text = null; } if (!string.Equals(_loadedPath, text, StringComparison.OrdinalIgnoreCase)) { Unload(); } } private static AssetBundle? EnsureLoaded() { InvalidateIfConfigChanged(); if ((Object)(object)_bundle != (Object)null) { return _bundle; } if (Time.unscaledTime < _nextRetryAt) { return null; } _nextRetryAt = Time.unscaledTime + 2f; string text = ResolveBundlePath(Plugin.ExternalAssetsBundlePath.Value); if (string.IsNullOrWhiteSpace(text)) { return null; } try { if (!File.Exists(text)) { if (!_loggedLoadError) { _loggedLoadError = true; Plugin.Log.LogWarning((object)("ExternalAssetBundle: bundle not found at '" + text + "'.")); } return null; } AssetBundle val = AssetBundle.LoadFromFile(text); if ((Object)(object)val == (Object)null) { if (!_loggedLoadError) { _loggedLoadError = true; Plugin.Log.LogWarning((object)("ExternalAssetBundle: failed to load bundle from '" + text + "'.")); } return null; } _bundle = val; _loadedPath = text; _loggedLoadError = false; Plugin.Log.LogInfo((object)("ExternalAssetBundle: loaded '" + text + "'.")); return _bundle; } catch (Exception ex) { if (!_loggedLoadError) { _loggedLoadError = true; Plugin.Log.LogWarning((object)("ExternalAssetBundle: exception loading bundle: " + ex.Message)); } return null; } } private static void Unload() { try { if ((Object)(object)_bundle != (Object)null) { _bundle.Unload(false); } } catch { } _bundle = null; _loadedPath = null; _loggedLoadError = false; } private static string? ResolveBundlePath(string? raw) { if (string.IsNullOrWhiteSpace(raw)) { return null; } raw = raw.Trim(); string text = TryResolve(raw); if (File.Exists(text)) { return text; } if (!raw.EndsWith(".bundle", StringComparison.OrdinalIgnoreCase)) { string text2 = TryResolve(raw + ".bundle"); if (File.Exists(text2)) { return text2; } } return text; static string TryResolve(string candidate) { if (Path.IsPathRooted(candidate)) { return candidate; } string text3 = Path.Combine(Paths.BepInExRootPath, candidate); if (File.Exists(text3)) { return text3; } string text4 = Path.Combine(Paths.PluginPath, candidate); if (File.Exists(text4)) { return text4; } return Path.Combine(Paths.GameRootPath, candidate); } } } internal static class ExternalMods { internal static bool IsCustomTruckShopLoaded() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { if (string.Equals(assembly.GetName().Name, "CustomTruckShop", StringComparison.OrdinalIgnoreCase)) { return true; } } catch { } } return false; } } internal static class GameAccess { private static readonly BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static readonly string[] CarHornKeywords = new string[6] { "horn", "honk", "beep", "klaxon", "claxon", "signal" }; private static readonly string[] CarSkidKeywords = new string[10] { "skid", "drift", "tire", "screech", "slide", "brake", "squeak", "scrape", "friction", "slip" }; private static readonly string[] CarCrashKeywords = new string[11] { "crash", "impact", "hit", "collision", "bump", "metal", "smash", "thud", "clunk", "bang", "bonk" }; private static readonly string[] CarHornNoiseKeywords = new string[12] { "headlight", "light", "door", "engine", "motor", "idle", "rpm", "gear", "radio", "music", "wind", "road" }; private static readonly Dictionary LocalCarSfxPlayingBySource = new Dictionary(); private static readonly Dictionary LocalCarSfxNextLoopEmitAt = new Dictionary(); private static readonly Dictionary LoadedAudioClipByName = new Dictionary(StringComparer.OrdinalIgnoreCase); internal const byte CarSfxHorn = 1; internal const byte CarSfxSkid = 2; internal const byte CarSfxCrash = 3; private static bool _jobApplyDisabled; private static string? _jobApplyDisabledReason; private static bool _loggedPlayerVisualRootFail; private static bool _loggedMenuButtonDump; private static bool _loggedUiToolkitButtonDump; internal static bool TryReadLocalPlayerPose(out Vector3 pos, out Quaternion rot) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) pos = default(Vector3); rot = default(Quaternion); object? obj = TryFindObjectOfTypeByName("sCharacterController"); Component val = (Component)((obj is Component) ? obj : null); if (val != null) { try { if (TryGetCombinedRendererBounds(val.transform, out var bounds)) { pos = ((Bounds)(ref bounds)).center; rot = val.transform.rotation; return true; } pos = val.transform.position; rot = val.transform.rotation; return true; } catch { } } try { Camera val2 = Camera.main; if ((Object)(object)val2 == (Object)null) { val2 = Object.FindFirstObjectByType(); } if ((Object)(object)val2 == (Object)null) { return false; } pos = ((Component)val2).transform.position; rot = ((Component)val2).transform.rotation; return true; } catch { return false; } } internal static bool TryReadLocalPlayerControllerPose(out Vector3 pos, out Quaternion rot) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) pos = default(Vector3); rot = default(Quaternion); object? obj = TryFindObjectOfTypeByName("sCharacterController"); Component val = (Component)((obj is Component) ? obj : null); if (val != null) { try { pos = val.transform.position; rot = val.transform.rotation; return true; } catch { return false; } } return TryReadLocalPlayerPose(out pos, out rot); } internal static bool TryApplyLocalPlayerControllerPose(Vector3 pos, Quaternion rot) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) object? obj = TryFindObjectOfTypeByName("sCharacterController"); Component val = (Component)((obj is Component) ? obj : null); if (val == null) { return false; } try { val.transform.position = pos; val.transform.rotation = rot; return true; } catch { return false; } } internal static bool TryReadSaveId(out string saveId) { saveId = string.Empty; try { object obj = TryGetSaveSystemInstance(); if (obj == null) { return false; } Type type = obj.GetType(); string[] array = new string[8] { "saveSlot", "currentSlot", "slot", "saveName", "profileName", "currentProfile", "fileName", "saveFile" }; foreach (string name in array) { FieldInfo field = type.GetField(name, Any); if (field != null) { object value = field.GetValue(obj); if (value != null) { saveId = value.ToString() ?? string.Empty; if (!string.IsNullOrWhiteSpace(saveId)) { return true; } } } PropertyInfo property = type.GetProperty(name, Any); if (!(property != null)) { continue; } object value2 = property.GetValue(obj, null); if (value2 != null) { saveId = value2.ToString() ?? string.Empty; if (!string.IsNullOrWhiteSpace(saveId)) { return true; } } } Dictionary dictionary = TryReadSaveSystemSnapshot(); if (dictionary != null && dictionary.Count > 0) { foreach (KeyValuePair item in dictionary) { string key = item.Key; if (string.IsNullOrEmpty(key)) { continue; } string text = key.ToLowerInvariant(); if (text.Contains("saveslot") || text.EndsWith("slot") || text.Contains("savename") || text.Contains("profilename") || text.Contains("savefile")) { string value3 = item.Value; if (!string.IsNullOrWhiteSpace(value3)) { saveId = value3; return true; } } } } } catch { return false; } return false; } internal static bool TryReadCurrentMapBuildIndex(out int buildIndex) { buildIndex = -1; try { Dictionary dictionary = TryReadSaveSystemSnapshot(); if (dictionary == null || dictionary.Count == 0) { return false; } string[] array = new string[5] { "deliveryCurrentLastMapBuildIndex", "deliveryCurrentMapBuildIndex", "currentMapBuildIndex", "mapBuildIndex", "lastMapBuildIndex" }; for (int i = 0; i < array.Length; i++) { if (dictionary.TryGetValue(array[i], out var value) && !string.IsNullOrWhiteSpace(value) && TryParseIntLoose(value, out buildIndex) && buildIndex >= 0) { return true; } } foreach (KeyValuePair item in dictionary) { string key = item.Key; string value2 = item.Value; if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(value2)) { string text = key.ToLowerInvariant(); if (text.Contains("map") && text.Contains("index") && TryParseIntLoose(value2, out buildIndex) && buildIndex >= 0) { return true; } } } } catch { return false; } return false; } private static bool TryParseIntLoose(string raw, out int value) { value = 0; if (string.IsNullOrWhiteSpace(raw)) { return false; } if (int.TryParse(raw, out value)) { return true; } if (float.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { value = Mathf.RoundToInt(result); return true; } return false; } internal static bool IsGameplayWorldLoaded() { try { if (TryFindObjectOfTypeByName("jobBoard") != null) { return true; } if (TryFindObjectOfTypeByName("sCarController") != null) { return true; } if (TryFindObjectOfTypeByName("sDeliveryManager") != null) { return true; } if (TryFindObjectOfTypeByName("sCityManager") != null) { return true; } } catch { } return false; } private static bool TryGetCombinedRendererBounds(Transform root, out Bounds bounds) { //IL_0001: 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_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) bounds = default(Bounds); try { Renderer[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); if (componentsInChildren == null || componentsInChildren.Length == 0) { return false; } bool flag = false; Bounds val = default(Bounds); foreach (Renderer val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null) { continue; } string name = ((Object)((Component)val2).gameObject).name; if (string.IsNullOrEmpty(name) || !name.StartsWith("EasyDeliveryCoLanCoop.", StringComparison.Ordinal)) { if (!flag) { val = val2.bounds; flag = true; } else { ((Bounds)(ref val)).Encapsulate(val2.bounds); } } } if (!flag) { return false; } bounds = val; return true; } catch { return false; } } internal static bool TryFindLocalPlayerVisualRoot(out Transform visualRoot) { visualRoot = null; object? obj = TryFindObjectOfTypeByName("sCharacterController"); Component val = (Component)((obj is Component) ? obj : null); if (val != null) { Transform val2 = FindBestVisualRoot(val.transform); if ((Object)(object)val2 != (Object)null) { visualRoot = val2; return true; } } try { GameObject val3 = GameObject.Find("guy") ?? GameObject.Find("Guy") ?? GameObject.Find("player") ?? GameObject.Find("Player"); if ((Object)(object)val3 != (Object)null) { Transform val4 = FindBestVisualRoot(val3.transform); if ((Object)(object)val4 != (Object)null) { visualRoot = val4; return true; } } } catch { } if (!_loggedPlayerVisualRootFail) { _loggedPlayerVisualRootFail = true; Plugin.Log.LogWarning((object)"TryFindLocalPlayerVisualRoot failed; remote player may fall back to primitive visuals."); } return false; } internal static bool TryReadInCar(out bool inCar) { inCar = false; object obj = TryFindObjectOfTypeByName("sCarController"); if (obj == null) { return false; } PropertyInfo property = obj.GetType().GetProperty("GuyActive", Any); if (property != null && property.PropertyType == typeof(bool)) { try { inCar = (bool)property.GetValue(obj, null); return true; } catch { return false; } } FieldInfo field = obj.GetType().GetField("guyActive", Any); if (field != null && field.FieldType == typeof(bool)) { try { inCar = (bool)field.GetValue(obj); return true; } catch { return false; } } return false; } internal static bool TryReadCarState(out Vector3 pos, out Quaternion rot, out Vector3 vel, out Vector3 angVel, out bool hasCar) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: 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) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) pos = default(Vector3); rot = default(Quaternion); vel = default(Vector3); angVel = default(Vector3); hasCar = false; object obj = TryFindObjectOfTypeByName("sCarController"); if (obj == null) { return false; } object? obj2 = obj.GetType().GetField("rb", Any)?.GetValue(obj); Rigidbody val = (Rigidbody)((obj2 is Rigidbody) ? obj2 : null); if (val != null) { hasCar = true; pos = val.position; rot = val.rotation; vel = val.linearVelocity; angVel = val.angularVelocity; return true; } Component val2 = (Component)((obj is Component) ? obj : null); if (val2 != null) { hasCar = true; pos = val2.transform.position; rot = val2.transform.rotation; vel = Vector3.zero; angVel = Vector3.zero; return true; } return false; } internal static bool TryGetLocalCarSfxPulses(out List<(byte SfxId, string ClipName, string SourceName)> sfxEvents) { sfxEvents = new List<(byte, string, string)>(4); object obj = TryFindObjectOfTypeByName("sCarController"); Component val = (Component)((obj is Component) ? obj : null); if (val == null) { return false; } bool flag = Plugin.IsHornOnlyCarSoundMode(); bool flag2 = false; if (TryReadDirectLocalHornPulse(obj, out bool hornPressed, out string clipName, out string sourceName)) { flag2 = hornPressed; if (hornPressed) { sfxEvents.Add((1, clipName, sourceName)); } if (flag) { return hornPressed; } } bool flag3 = TryDetectCarSfxPulsesOnRoot(val.transform, sfxEvents); return flag2 || flag3; } private static bool TryReadDirectLocalHornPulse(object carController, out bool hornPressed, out string clipName, out string sourceName) { hornPressed = false; clipName = string.Empty; sourceName = string.Empty; try { if (carController == null) { return false; } Type type = carController.GetType(); FieldInfo field = type.GetField("player", Any); if (field == null || !(field.GetValue(carController) is int num) || num < 0) { return false; } Type type2 = type.Assembly.GetType("sInputManager"); if (type2 == null) { return false; } if (!(type2.GetField("players", Any)?.GetValue(null) is Array array) || num >= array.Length) { return false; } object value = array.GetValue(num); if (value == null) { return false; } FieldInfo field2 = value.GetType().GetField("hornPressed", Any); if (field2 == null) { return false; } object value2 = field2.GetValue(value); bool flag = default(bool); int num2; if (value2 is bool) { flag = (bool)value2; num2 = 1; } else { num2 = 0; } hornPressed = (byte)((uint)num2 & (flag ? 1u : 0u)) != 0; if (!hornPressed) { return true; } sourceName = "Headlights"; TryResolveLocalHornClipMetadata(carController, ref clipName, ref sourceName); return true; } catch { return false; } } private static void TryResolveLocalHornClipMetadata(object carController, ref string clipName, ref string sourceName) { try { Component val = (Component)((carController is Component) ? carController : null); if (val == null) { return; } Type type = carController.GetType().Assembly.GetType("Headlights"); if (type == null) { return; } Component componentInChildren = val.GetComponentInChildren(type, true); if (!((Object)(object)componentInChildren == (Object)null)) { object? obj = type.GetField("horn", Any)?.GetValue(componentInChildren); AudioClip val2 = (AudioClip)((obj is AudioClip) ? obj : null); if (val2 != null && (Object)(object)val2 != (Object)null && !string.IsNullOrWhiteSpace(((Object)val2).name)) { clipName = ((Object)val2).name; } object? obj2 = type.GetField("source", Any)?.GetValue(componentInChildren); AudioSource val3 = (AudioSource)((obj2 is AudioSource) ? obj2 : null); if (val3 != null && (Object)(object)val3 != (Object)null && (Object)(object)((Component)val3).gameObject != (Object)null) { sourceName = ((Object)((Component)val3).gameObject).name ?? sourceName; } } } catch { } } internal static bool TryPlayLikelyCarSfx(Transform root, byte sfxId, string? clipName = null, string? sourceName = null) { if ((Object)(object)root == (Object)null) { return false; } try { if (sfxId == 1) { if (TryPlayHeadlightsHorn(root, clipName, sourceName)) { return true; } string text = clipName ?? string.Empty; if (!string.IsNullOrWhiteSpace(text)) { AudioClip val = ResolveLoadedAudioClipByName(text); if ((Object)(object)val != (Object)null) { return TryPlayClipOnRootEmitter(root, val); } return false; } } AudioSource[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); if (componentsInChildren == null || componentsInChildren.Length == 0) { return false; } string text2 = clipName ?? string.Empty; string value = sourceName ?? string.Empty; if (!string.IsNullOrWhiteSpace(text2)) { foreach (AudioSource val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.clip == (Object)null) && string.Equals(((Object)val2.clip).name, text2, StringComparison.OrdinalIgnoreCase)) { return TryPlaySourcePulse(val2); } } } if (!string.IsNullOrWhiteSpace(value) && sfxId != 1) { foreach (AudioSource val3 in componentsInChildren) { if (!((Object)(object)val3 == (Object)null) && !((Object)(object)((Component)val3).gameObject == (Object)null) && ((Object)((Component)val3).gameObject).name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return TryPlaySourcePulse(val3); } } } switch (sfxId) { case 0: return false; case 1: return false; default: { AudioSource val4 = null; int num = int.MinValue; foreach (AudioSource val5 in componentsInChildren) { if (!((Object)(object)val5 == (Object)null)) { int num2 = ScoreLikelyCarSfxSource(val5, sfxId); if (num2 > num) { val4 = val5; num = num2; } } } if ((Object)(object)val4 == (Object)null || num <= 0) { return false; } return TryPlaySourcePulse(val4); } } } catch { return false; } } internal static bool TryPlayCarSfxAudibleFallback(Transform root, byte sfxId, string? clipName = null, string? sourceName = null) { if ((Object)(object)root == (Object)null) { return false; } try { if (sfxId == 1) { if (TryPlayHeadlightsHorn(root, clipName, sourceName)) { return true; } string text = clipName ?? string.Empty; if (!string.IsNullOrWhiteSpace(text)) { AudioClip val = ResolveLoadedAudioClipByName(text); if ((Object)(object)val != (Object)null) { return TryPlayClipOnRootEmitter(root, val); } return false; } } AudioSource[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); if (componentsInChildren == null || componentsInChildren.Length == 0) { return false; } AudioClip val2 = ResolveBestClipForSfx(componentsInChildren, sfxId, clipName, sourceName); if ((Object)(object)val2 == (Object)null) { return false; } AudioSource val3 = ((Component)root).GetComponent(); if ((Object)(object)val3 == (Object)null) { val3 = ((Component)root).gameObject.AddComponent(); } val3.playOnAwake = false; val3.loop = false; val3.spatialBlend = 1f; val3.volume = 0.95f; val3.pitch = 1f; val3.minDistance = 3f; val3.maxDistance = 22f; val3.Stop(); val3.clip = val2; val3.PlayOneShot(val2); return true; } catch { return false; } } private static AudioClip? ResolveBestClipForSfx(AudioSource[] sources, byte sfxId, string? clipName, string? sourceName) { if (sources == null || sources.Length == 0) { return null; } string text = clipName ?? string.Empty; string value = sourceName ?? string.Empty; if (!string.IsNullOrWhiteSpace(text)) { foreach (AudioSource val in sources) { if (!((Object)(object)((val != null) ? val.clip : null) == (Object)null) && string.Equals(((Object)val.clip).name, text, StringComparison.OrdinalIgnoreCase)) { return val.clip; } } } if (!string.IsNullOrWhiteSpace(value)) { foreach (AudioSource val2 in sources) { if (!((Object)(object)((val2 != null) ? val2.clip : null) == (Object)null) && !((Object)(object)((Component)val2).gameObject == (Object)null) && ((Object)((Component)val2).gameObject).name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return val2.clip; } } } AudioClip result = null; int num = int.MinValue; foreach (AudioSource val3 in sources) { if ((Object)(object)((val3 != null) ? val3.clip : null) == (Object)null) { continue; } int num2 = ScoreLikelyCarSfxSource(val3, sfxId); if (sfxId == 1) { if (!val3.loop) { num2 += 25; } if (val3.clip.length > 0.01f && val3.clip.length <= 2.5f) { num2 += 20; } } if (num2 > num) { num = num2; result = val3.clip; } } return result; } private static bool TryPlaySourcePulse(AudioSource src) { if ((Object)(object)src == (Object)null || (Object)(object)src.clip == (Object)null) { return false; } if (src.isPlaying) { src.Stop(); } src.Play(); return true; } private static bool TryPlayClipOnRootEmitter(Transform root, AudioClip clip) { if ((Object)(object)root == (Object)null || (Object)(object)clip == (Object)null) { return false; } try { AudioSource val = ((Component)root).GetComponent(); if ((Object)(object)val == (Object)null) { val = ((Component)root).gameObject.AddComponent(); } val.playOnAwake = false; val.loop = false; val.spatialBlend = 1f; val.volume = 0.95f; val.pitch = 1f; val.minDistance = 3f; val.maxDistance = 22f; val.PlayOneShot(clip); return true; } catch { return false; } } private static bool TryPlayHeadlightsHorn(Transform root, string? preferredClipName = null, string? preferredSourceName = null) { if ((Object)(object)root == (Object)null) { return false; } try { Type type = typeof(GameAccess).Assembly.GetType("Headlights"); if (type == null) { return false; } Component componentInChildren = ((Component)root).GetComponentInChildren(type, true); if ((Object)(object)componentInChildren == (Object)null) { return false; } FieldInfo field = type.GetField("horn", Any); object? obj = type.GetField("source", Any)?.GetValue(componentInChildren); AudioSource val = (AudioSource)((obj is AudioSource) ? obj : null); if (val == null || (Object)(object)val == (Object)null) { return false; } AudioClip val2 = null; string text = preferredClipName ?? string.Empty; string value = preferredSourceName ?? string.Empty; if (!string.IsNullOrWhiteSpace(text)) { val2 = ResolveLoadedAudioClipByName(text); } bool flag = !string.IsNullOrWhiteSpace(text); if ((Object)(object)val2 == (Object)null) { object? obj2 = field?.GetValue(componentInChildren); AudioClip val3 = (AudioClip)((obj2 is AudioClip) ? obj2 : null); if (val3 != null && (Object)(object)val3 != (Object)null) { val2 = val3; } } if (!string.IsNullOrWhiteSpace(text) && ((Object)(object)val2 == (Object)null || !string.Equals(((Object)val2).name, text, StringComparison.OrdinalIgnoreCase))) { AudioSource[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); foreach (AudioSource val4 in componentsInChildren) { if (!((Object)(object)((val4 != null) ? val4.clip : null) == (Object)null) && string.Equals(((Object)val4.clip).name, text, StringComparison.OrdinalIgnoreCase)) { val2 = val4.clip; break; } } } if ((Object)(object)val2 == (Object)null && !string.IsNullOrWhiteSpace(value)) { AudioSource[] componentsInChildren2 = ((Component)root).GetComponentsInChildren(true); foreach (AudioSource val5 in componentsInChildren2) { if (!((Object)(object)((val5 != null) ? val5.clip : null) == (Object)null) && !((Object)(object)((Component)val5).gameObject == (Object)null) && ((Object)((Component)val5).gameObject).name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { val2 = val5.clip; break; } } } if (flag) { if ((Object)(object)val2 == (Object)null) { return false; } if (!string.Equals(((Object)val2).name, text, StringComparison.OrdinalIgnoreCase)) { return false; } } if ((Object)(object)val2 == (Object)null) { return false; } val.PlayOneShot(val2); return true; } catch { return false; } } private static AudioClip? ResolveLoadedAudioClipByName(string clipName) { if (string.IsNullOrWhiteSpace(clipName)) { return null; } if (LoadedAudioClipByName.TryGetValue(clipName, out AudioClip value) && (Object)(object)value != (Object)null) { return value; } try { AudioClip[] array = Resources.FindObjectsOfTypeAll(); foreach (AudioClip val in array) { if (!((Object)(object)val == (Object)null) && !string.IsNullOrWhiteSpace(((Object)val).name) && string.Equals(((Object)val).name, clipName, StringComparison.OrdinalIgnoreCase)) { LoadedAudioClipByName[clipName] = val; return val; } } } catch { } return null; } private static bool TryDetectCarSfxPulsesOnRoot(Transform root, List<(byte SfxId, string ClipName, string SourceName)> sfxEvents) { if ((Object)(object)root == (Object)null) { return false; } try { bool result = false; HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); HashSet hashSet2 = new HashSet(); AudioSource[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); float unscaledTime = Time.unscaledTime; foreach (AudioSource val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } string text = (((Object)(object)val.clip != (Object)null) ? (((Object)val.clip).name ?? string.Empty) : string.Empty); string text2 = (((Object)(object)((Component)val).gameObject != (Object)null) ? (((Object)((Component)val).gameObject).name ?? string.Empty) : string.Empty); byte b = ClassifyCarSfxSource(val); if (b == 0 && Plugin.IsHornOnlyCarSoundMode() && IsLikelyHornFallbackPulse(val, text, text2)) { b = 1; } int instanceID = ((Object)val).GetInstanceID(); hashSet2.Add(instanceID); bool isPlaying = val.isPlaying; bool value; bool flag = LocalCarSfxPlayingBySource.TryGetValue(instanceID, out value) && value; bool flag2 = false; if (isPlaying && val.loop && (!LocalCarSfxNextLoopEmitAt.TryGetValue(instanceID, out var value2) || unscaledTime >= value2)) { flag2 = true; } if (isPlaying && (!flag || flag2)) { byte b2 = b; if (b2 == 0) { LocalCarSfxPlayingBySource[instanceID] = isPlaying; continue; } result = true; if (val.loop) { LocalCarSfxNextLoopEmitAt[instanceID] = unscaledTime + 1.25f; } string item = $"{b2}|{text}|{text2}"; if (hashSet.Add(item)) { sfxEvents.Add((b2, text, text2)); } } LocalCarSfxPlayingBySource[instanceID] = isPlaying; } if (LocalCarSfxPlayingBySource.Count > 0) { List list = new List(); foreach (KeyValuePair item2 in LocalCarSfxPlayingBySource) { if (!hashSet2.Contains(item2.Key)) { list.Add(item2.Key); } } for (int j = 0; j < list.Count; j++) { LocalCarSfxPlayingBySource.Remove(list[j]); } for (int k = 0; k < list.Count; k++) { LocalCarSfxNextLoopEmitAt.Remove(list[k]); } } return result; } catch { return false; } } private static byte ClassifyCarSfxSource(AudioSource src) { int num = ScoreLikelyCarSfxSource(src, 1); int num2 = ScoreLikelyCarSfxSource(src, 2); int num3 = ScoreLikelyCarSfxSource(src, 3); int num4 = Mathf.Max(num, Mathf.Max(num2, num3)); if (num4 < 60) { return 0; } if (num4 == num) { return 1; } if (num4 == num2) { return 2; } return 3; } private static int ScoreLikelyCarSfxSource(AudioSource src, byte sfxId) { if ((Object)(object)src == (Object)null) { return 0; } int num = 0; string value = (((Object)(object)((Component)src).gameObject != (Object)null) ? ((Object)((Component)src).gameObject).name : string.Empty); string value2 = (((Object)(object)src.clip != (Object)null) ? ((Object)src.clip).name : string.Empty); switch (sfxId) { case 1: if (ContainsAnyKeyword(value, CarHornKeywords)) { num += 100; } if (ContainsAnyKeyword(value2, CarHornKeywords)) { num += 80; } if (src.loop) { num -= 30; } break; case 2: if (ContainsAnyKeyword(value, CarSkidKeywords)) { num += 100; } if (ContainsAnyKeyword(value2, CarSkidKeywords)) { num += 80; } break; case 3: if (ContainsAnyKeyword(value, CarCrashKeywords)) { num += 100; } if (ContainsAnyKeyword(value2, CarCrashKeywords)) { num += 80; } if (src.loop) { num -= 40; } break; } if (src.spatialBlend > 0.3f) { num += 10; } if ((Object)(object)src.clip != (Object)null) { num += 5; } return num; } private static bool ContainsAnyKeyword(string? value, IReadOnlyList keywords) { if (string.IsNullOrWhiteSpace(value)) { return false; } for (int i = 0; i < keywords.Count; i++) { string value2 = keywords[i]; if (value.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static bool IsLikelyHornFallbackPulse(AudioSource src, string clipName, string sourceName) { if ((Object)(object)src == (Object)null) { return false; } if (src.loop) { return false; } if (ContainsAnyKeyword(clipName, CarHornKeywords) || ContainsAnyKeyword(sourceName, CarHornKeywords)) { return true; } if (ContainsAnyKeyword(clipName, CarSkidKeywords) || ContainsAnyKeyword(sourceName, CarSkidKeywords)) { return false; } if (ContainsAnyKeyword(clipName, CarCrashKeywords) || ContainsAnyKeyword(sourceName, CarCrashKeywords)) { return false; } if (ContainsAnyKeyword(clipName, CarHornNoiseKeywords) || ContainsAnyKeyword(sourceName, CarHornNoiseKeywords)) { return false; } AudioClip clip = src.clip; if ((Object)(object)clip == (Object)null) { return false; } float length = clip.length; if (length < 0.05f || length > 2.5f) { return false; } return src.volume >= 0.02f; } internal static bool TryReadHeldPayloadName(out string payloadName) { payloadName = string.Empty; object? obj = TryFindObjectOfTypeByName("sCharacterController"); Component val = (Component)((obj is Component) ? obj : null); if (val != null) { try { Type type = ((object)val).GetType(); Transform val2 = null; object? obj2 = type.GetField("pickupPoint", Any)?.GetValue(val); Transform val3 = (Transform)((obj2 is Transform) ? obj2 : null); if (val3 != null) { val2 = val3; } else { object? obj3 = type.GetProperty("pickupPoint", Any)?.GetValue(val, null); Transform val4 = (Transform)((obj3 is Transform) ? obj3 : null); if (val4 != null) { val2 = val4; } } if ((Object)(object)val2 != (Object)null) { for (int i = 0; i < val2.childCount; i++) { Transform child = val2.GetChild(i); if (!((Object)(object)child == (Object)null)) { string text = NormalizePayloadName(((Object)child).name); if (IsPayloadName(text)) { payloadName = text; return true; } } } } if (TryGetLikelyHeldObjectName(val, out string name)) { name = NormalizePayloadName(name); if (IsPayloadName(name)) { payloadName = name; return true; } } } catch { } } object? obj5 = TryFindObjectOfTypeByName("sCharacterInteraction"); Component val5 = (Component)((obj5 is Component) ? obj5 : null); if (val5 != null) { try { object? obj6 = ((object)val5).GetType().GetField("payloadPivot", Any)?.GetValue(val5); Transform val6 = (Transform)((obj6 is Transform) ? obj6 : null); if (val6 != null) { for (int j = 0; j < val6.childCount; j++) { Transform child2 = val6.GetChild(j); if (!((Object)(object)child2 == (Object)null)) { string text2 = NormalizePayloadName(((Object)child2).name); if (IsPayloadName(text2)) { payloadName = text2; return true; } } } } } catch { } } object obj8 = TryFindObjectOfTypeByName("sItemManager"); if (obj8 != null) { try { object? obj9 = obj8.GetType().GetField("heldItem", Any)?.GetValue(obj8); GameObject val7 = (GameObject)((obj9 is GameObject) ? obj9 : null); if (val7 != null && (Object)(object)val7 != (Object)null) { string text3 = NormalizePayloadName(((Object)val7).name); if (IsPayloadName(text3)) { payloadName = text3; return true; } } } catch { } } return false; } private static bool TryGetLikelyHeldObjectName(Component controller, out string name) { name = string.Empty; if ((Object)(object)controller == (Object)null) { return false; } try { Type type = ((object)controller).GetType(); string[] array = new string[4] { "heldItem", "held", "payload", "currentPayload" }; for (int i = 0; i < array.Length; i++) { if (TryGetUnityObjectName(type, controller, array[i], out name)) { return true; } } FieldInfo[] fields = type.GetFields(Any); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo == null) { continue; } string name2 = fieldInfo.Name; if (!string.IsNullOrEmpty(name2)) { string text = name2.ToLowerInvariant(); if ((text.Contains("held") || text.Contains("payload")) && TryConvertUnityObjectToName(fieldInfo.GetValue(controller), out name)) { return true; } } } } catch { return false; } return false; } private static bool TryGetUnityObjectName(Type type, object instance, string memberName, out string name) { name = string.Empty; try { FieldInfo field = type.GetField(memberName, Any); if (field != null) { return TryConvertUnityObjectToName(field.GetValue(instance), out name); } PropertyInfo property = type.GetProperty(memberName, Any); if (property != null) { return TryConvertUnityObjectToName(property.GetValue(instance, null), out name); } } catch { } return false; } private static bool TryConvertUnityObjectToName(object? v, out string name) { name = string.Empty; if (v == null) { return false; } GameObject val = (GameObject)((v is GameObject) ? v : null); if (val == null) { Transform val2 = (Transform)((v is Transform) ? v : null); if (val2 == null) { Component val3 = (Component)((v is Component) ? v : null); if (val3 != null) { name = (((Object)(object)val3.gameObject != (Object)null) ? ((Object)val3.gameObject).name : ((Object)val3).name); return !string.IsNullOrEmpty(name); } return false; } name = ((Object)val2).name; return !string.IsNullOrEmpty(name); } name = ((Object)val).name; return !string.IsNullOrEmpty(name); } private static bool IsPayloadName(string? name) { if (string.IsNullOrWhiteSpace(name)) { return false; } return name.TrimStart().StartsWith("PAYLOAD", StringComparison.OrdinalIgnoreCase); } private static string NormalizePayloadName(string? name) { if (name == null) { name = string.Empty; } name = name.Trim(); if (name.EndsWith("(Clone)", StringComparison.Ordinal)) { name = name.Substring(0, name.Length - "(Clone)".Length).Trim(); } return name; } internal static bool TryFindPayloadVisualRoot(string payloadName, out Transform visualRoot) { visualRoot = null; payloadName = NormalizePayloadName(payloadName); if (string.IsNullOrWhiteSpace(payloadName)) { return false; } try { GameObject[] array = Resources.FindObjectsOfTypeAll(); Transform val = null; int num = int.MinValue; foreach (GameObject val2 in array) { if ((Object)(object)val2 == (Object)null) { continue; } string text = NormalizePayloadName(((Object)val2).name); if (text.StartsWith("PAYLOAD", StringComparison.OrdinalIgnoreCase)) { int num2 = 0; if (string.Equals(text, payloadName, StringComparison.OrdinalIgnoreCase)) { num2 += 200; } else if (text.IndexOf(payloadName, StringComparison.OrdinalIgnoreCase) >= 0 || payloadName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { num2 += 80; } if (val2.GetComponentsInChildren(true).Length != 0) { num2 += 20; } if (val2.GetComponentsInChildren(true).Length != 0) { num2 += 20; } if (val2.activeInHierarchy) { num2 += 5; } if (num2 > num) { val = val2.transform; num = num2; } } } if ((Object)(object)val != (Object)null) { visualRoot = val; return true; } } catch { } return false; } internal static bool TryFindLocalCarVisualRoot(out Transform visualRoot) { visualRoot = null; object? obj = TryFindObjectOfTypeByName("sCarController"); Component val = (Component)((obj is Component) ? obj : null); if (val == null) { return false; } visualRoot = val.transform; return true; } private static Component? TryFindComponentByTypeName(Component root, string typeName) { try { Component[] componentsInChildren = root.GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { if (string.Equals(((object)val).GetType().Name, typeName, StringComparison.Ordinal)) { return val; } if (string.Equals(((object)val).GetType().FullName, typeName, StringComparison.Ordinal)) { return val; } } } } catch { } return null; } private static Transform LiftToReasonableScale(Transform candidate, Transform limitRoot) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_001a: Unknown result type (might be due to invalid IL or missing references) Transform val = candidate; for (int i = 0; i < 12; i++) { Vector3 lossyScale = val.lossyScale; float num = (lossyScale.x + lossyScale.y + lossyScale.z) / 3f; if (num >= 0.35f && num <= 3f) { return val; } if ((Object)(object)val.parent == (Object)null) { return val; } if ((Object)(object)val == (Object)(object)limitRoot) { return val; } val = val.parent; } return val; } internal static bool TryApplyCarState(Vector3 pos, Quaternion rot, Vector3 vel, Vector3 angVel) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) object obj = TryFindObjectOfTypeByName("sCarController"); if (obj == null) { return false; } object? obj2 = obj.GetType().GetField("rb", Any)?.GetValue(obj); Rigidbody val = (Rigidbody)((obj2 is Rigidbody) ? obj2 : null); if (val != null) { try { val.position = pos; val.rotation = rot; val.linearVelocity = vel; val.angularVelocity = angVel; } catch { } } Component val2 = (Component)((obj is Component) ? obj : null); if (val2 != null) { val2.transform.position = pos; val2.transform.rotation = rot; } return true; } internal static IReadOnlyList<(string Name, Vector3 LocalPos, Quaternion LocalRot)>? TryReadCarCargo() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) object obj = TryFindObjectOfTypeByName("sCarController"); if (obj == null) { return null; } object? obj2 = obj.GetType().GetField("payloadPivot", Any)?.GetValue(obj); Transform val = (Transform)((obj2 is Transform) ? obj2 : null); if (val == null) { return null; } List<(string, Vector3, Quaternion)> list = new List<(string, Vector3, Quaternion)>(); for (int i = 0; i < val.childCount; i++) { Transform child = val.GetChild(i); list.Add((((Object)child).name, child.localPosition, child.localRotation)); } return list; } internal static bool TryApplyCarCargo(IReadOnlyList<(string Name, Vector3 LocalPos, Quaternion LocalRot)> cargo) { //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) object obj = TryFindObjectOfTypeByName("sCarController"); if (obj == null) { return false; } object? obj2 = obj.GetType().GetField("payloadPivot", Any)?.GetValue(obj); Transform val = (Transform)((obj2 is Transform) ? obj2 : null); if (val == null) { return false; } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); for (int i = 0; i < val.childCount; i++) { dictionary[((Object)val.GetChild(i)).name] = val.GetChild(i); } for (int j = 0; j < cargo.Count; j++) { Transform value = null; if (!string.IsNullOrEmpty(cargo[j].Name)) { dictionary.TryGetValue(cargo[j].Name, out value); } if (value == null) { value = ((j < val.childCount) ? val.GetChild(j) : null); } if (!((Object)(object)value == (Object)null)) { value.localPosition = cargo[j].LocalPos; value.localRotation = cargo[j].LocalRot; } } return true; } internal static IReadOnlyList? TryReadJobBoardJobs() { object obj = TryFindObjectOfTypeByName("jobBoard"); if (obj == null) { return null; } if (!(obj.GetType().GetField("jobs", Any)?.GetValue(obj) is IEnumerable enumerable)) { return null; } List list = new List(); foreach (object item2 in enumerable) { if (item2 != null) { Type type = item2.GetType(); JobData item = new JobData { Name = type.GetField("name", Any)?.GetValue(item2)?.ToString(), From = type.GetField("from", Any)?.GetValue(item2)?.ToString(), To = type.GetField("to", Any)?.GetValue(item2)?.ToString(), StartingCityName = type.GetField("startingCityName", Any)?.GetValue(item2)?.ToString(), DestCityName = type.GetField("destCityName", Any)?.GetValue(item2)?.ToString(), DestinationIndex = ReadInt(type.GetField("destinationIndex", Any)?.GetValue(item2)), PayloadIndex = ReadInt(type.GetField("payloadIndex", Any)?.GetValue(item2)), Price = ReadFloat(type.GetField("price", Any)?.GetValue(item2)), Mass = ReadFloat(type.GetField("mass", Any)?.GetValue(item2)), TimeStart = ReadFloat(type.GetField("timeStart", Any)?.GetValue(item2)), IsChallenge = ReadBool(type.GetField("isChallenge", Any)?.GetValue(item2)), IsIntercity = ReadBool(type.GetField("isIntercity", Any)?.GetValue(item2)), Duration = ReadFloat(type.GetField("duration", Any)?.GetValue(item2)), Distance = ReadFloat(type.GetField("distance", Any)?.GetValue(item2)) }; list.Add(item); } } return list; } internal static bool TryApplyJobBoardJobs(IReadOnlyList jobs) { if (_jobApplyDisabled) { return false; } object obj = TryFindObjectOfTypeByName("jobBoard"); if (obj == null) { return false; } Type type = obj.GetType(); Type type2 = type.GetNestedType("Job", Any) ?? FindTypeInAssemblyCSharp("jobBoard+Job") ?? FindTypeInAssemblyCSharp("Job"); if (type2 == null) { return false; } IList list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(type2)); try { foreach (JobData job in jobs) { ConstructorInfo constructor = type2.GetConstructor(Any, null, Type.EmptyTypes, null); object obj2 = ((!(constructor != null)) ? FormatterServices.GetUninitializedObject(type2) : constructor.Invoke(Array.Empty())); if (obj2 != null) { SetField(type2, obj2, "name", job.Name); SetField(type2, obj2, "from", job.From); SetField(type2, obj2, "to", job.To); SetField(type2, obj2, "startingCityName", job.StartingCityName); SetField(type2, obj2, "destCityName", job.DestCityName); SetField(type2, obj2, "destinationIndex", job.DestinationIndex); SetField(type2, obj2, "payloadIndex", job.PayloadIndex); SetField(type2, obj2, "price", job.Price); SetField(type2, obj2, "mass", job.Mass); SetField(type2, obj2, "timeStart", job.TimeStart); SetField(type2, obj2, "isChallenge", job.IsChallenge); SetField(type2, obj2, "isIntercity", job.IsIntercity); SetField(type2, obj2, "duration", job.Duration); SetField(type2, obj2, "distance", job.Distance); list.Add(obj2); } } } catch (Exception ex) { _jobApplyDisabled = true; _jobApplyDisabledReason = ex.GetType().Name; Plugin.Log.LogWarning((object)("Job sync disabled (cannot construct " + type2.FullName + "). Reason=" + _jobApplyDisabledReason)); return false; } FieldInfo field = type.GetField("jobs", Any); if (field == null) { return false; } try { field.SetValue(obj, list); obj.GetType().GetMethod("UpdateJobCount", Any)?.Invoke(obj, Array.Empty()); return true; } catch { return false; } } internal static Dictionary TryReadSaveSystemSnapshot() { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); object obj = TryGetSaveSystemInstance(); if (obj == null) { return dictionary; } if (obj.GetType().GetField("data", Any)?.GetValue(obj) is IDictionary dictionary2) { foreach (DictionaryEntry item in dictionary2) { if (item.Key is string key) { dictionary[key] = item.Value?.ToString(); } } } return dictionary; } internal static bool TryApplySaveKey(string key, string value) { object obj = TryGetSaveSystemInstance(); if (obj == null) { return false; } MethodInfo method = obj.GetType().GetMethod("SetString", Any); if (method == null) { return false; } try { method.Invoke(obj, new object[2] { key, value }); return true; } catch { return false; } } internal static bool TryDeleteSaveKey(string key) { object obj = TryGetSaveSystemInstance(); if (obj == null) { return false; } MethodInfo method = obj.GetType().GetMethod("DeleteKey", Any); if (method == null) { return false; } try { method.Invoke(obj, new object[1] { key }); return true; } catch { return false; } } internal static bool TryReadDayNightTime(out float time) { time = 0f; object obj = TryFindObjectOfTypeByName("sDayNightCycle"); if (obj == null) { return false; } FieldInfo field = obj.GetType().GetField("time", Any); if (field == null) { return false; } if (field.GetValue(obj) is float num) { time = num; return true; } return false; } internal static bool TryApplyDayNightTime(float time) { object obj = TryFindObjectOfTypeByName("sDayNightCycle"); if (obj == null) { return false; } FieldInfo field = obj.GetType().GetField("time", Any); if (field == null) { return false; } try { field.SetValue(obj, time); return true; } catch { return false; } } internal static bool TryReadHudMoney(out int money) { money = 0; object obj = TryFindObjectOfTypeByName("sHUD"); if (obj == null) { return false; } FieldInfo field = obj.GetType().GetField("money", Any); if (field == null) { return false; } object value = field.GetValue(obj); if (value is int num) { money = num; return true; } if (value is float num2) { money = Mathf.RoundToInt(num2); return true; } return false; } internal static bool TryApplyHudMoney(int money) { object obj = TryFindObjectOfTypeByName("sHUD"); if (obj == null) { return false; } FieldInfo field = obj.GetType().GetField("money", Any); if (field == null) { return false; } try { if (field.FieldType == typeof(int)) { field.SetValue(obj, money); return true; } if (field.FieldType == typeof(float)) { field.SetValue(obj, (float)money); return true; } return false; } catch { return false; } } private static object? TryGetSaveSystemInstance() { Type type = FindTypeInAssemblyCSharp("sSaveSystem"); if (type == null) { return null; } return type.GetField("instance", Any)?.GetValue(null); } private static object? TryFindObjectOfTypeByName(string typeName) { Type type = FindTypeInAssemblyCSharp(typeName); if (type == null) { return null; } try { MethodInfo method = typeof(Object).GetMethod("FindObjectOfType", Any, null, new Type[1] { typeof(Type) }, null); if (method == null) { return null; } return method.Invoke(null, new object[1] { type }); } catch { return null; } } internal static bool TryApplyPlayerPrefString(string key, string value) { try { if (string.IsNullOrEmpty(key)) { return false; } PlayerPrefs.SetString(key, value ?? string.Empty); return true; } catch { return false; } } internal static bool TryDeletePlayerPref(string key) { try { if (string.IsNullOrEmpty(key)) { return false; } PlayerPrefs.DeleteKey(key); return true; } catch { return false; } } internal static void TrySavePlayerPrefs() { try { PlayerPrefs.Save(); } catch { } } internal static bool TryAutoEnterWorldFromMenu(string? preferredSaveId, out string reason) { reason = string.Empty; if (preferredSaveId == null) { preferredSaveId = string.Empty; } string slotNameHint = TryExtractSaveSlotName(preferredSaveId) ?? preferredSaveId; int? slotIndexHint = TryExtractSaveSlotIndex(preferredSaveId); if (TryClickUiToolkitButton(out string reason2)) { reason = reason2; return true; } if (TryClickMenuButton(out string reason3)) { reason = reason3; return true; } string[] array = new string[12] { "sMainMenu", "sMenu", "sMenuManager", "sSaveMenu", "sSaveSelect", "sSaveSelectMenu", "sSceneLoader", "sGameManager", "ChooseExe", "ScreenSystem", "DesktopDotExe", "ScreenProgram" }; string[] array2 = new string[12] { "Continue", "ContinueGame", "OnContinue", "OnContinueButton", "Play", "OnPlay", "OnPlayButton", "StartGame", "Load", "LoadGame", "LoadSave", "LoadFromSave" }; for (int i = 0; i < array.Length; i++) { object obj = TryFindObjectOfTypeByName(array[i]); if (obj == null) { continue; } Type type = obj.GetType(); MethodInfo[] methods = type.GetMethods(Any); foreach (string b in array2) { foreach (MethodInfo methodInfo in methods) { if (methodInfo == null || !string.Equals(methodInfo.Name, b, StringComparison.OrdinalIgnoreCase)) { continue; } try { if (methodInfo.GetParameters().Length != 0) { continue; } methodInfo.Invoke(obj, null); reason = "Invoked " + type.Name + "." + methodInfo.Name + "()"; return true; } catch { } } } } object obj3 = TryFindObjectOfTypeByName("ScreenSystem"); if (obj3 != null) { try { MethodInfo method = obj3.GetType().GetMethod("Resume", Any, null, new Type[1] { typeof(int) }, null); if (method != null) { method.Invoke(obj3, new object[1]); reason = "Invoked ScreenSystem.Resume(null)"; return true; } } catch { } } if (TryInvokeLikelyMenuHandler(preferredSaveId, out string reason4)) { reason = reason4; return true; } if (TryInvokeSubstringHeuristic(slotNameHint, slotIndexHint, out string reason5)) { reason = reason5; return true; } reason = "No known menu/loader method found. UGUI=" + reason3 + ". UITK=" + reason2 + ". Scan=" + reason4; return false; } internal static string NormalizeSaveIdForSyncCompare(string? saveId) { if (saveId == null) { saveId = string.Empty; } saveId = saveId.Trim(); if (saveId.Length == 0) { return string.Empty; } string text = TryExtractSaveSlotName(saveId); if (!string.IsNullOrWhiteSpace(text)) { return text.Trim().ToLowerInvariant(); } return Plugin.SanitizeFileName(saveId).Trim().ToLowerInvariant(); } private static int? TryExtractSaveSlotIndex(string preferredSaveId) { if (string.IsNullOrEmpty(preferredSaveId)) { return null; } try { Match match = Regex.Match(preferredSaveId, "file\\s*(\\d+)", RegexOptions.IgnoreCase); if (match.Success && match.Groups.Count > 1 && int.TryParse(match.Groups[1].Value, out var result)) { return result; } } catch { } return null; } private static string? TryExtractSaveSlotName(string preferredSaveId) { int? num = TryExtractSaveSlotIndex(preferredSaveId); if (!num.HasValue) { return null; } return $"file{num.Value}"; } private static bool TryClickMenuButton(out string reason) { reason = string.Empty; Type type = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { type = assembly.GetTypes().FirstOrDefault((Type t) => string.Equals(t.FullName, "UnityEngine.UI.Button", StringComparison.Ordinal)); if (type != null) { break; } } catch { } } if (type == null) { reason = "UnityEngine.UI.Button type not found"; return false; } Object[] array = TryResourcesFindObjectsOfTypeAll(type); if (array == null || array.Length == 0) { reason = "No UI buttons found"; return false; } (Object, Component, string, int)? tuple = null; foreach (Object val in array) { if (val == (Object)null) { continue; } Component val2 = (Component)(object)((val is Component) ? val : null); if (val2 == null) { continue; } string text = string.Empty; try { text = TryGetButtonLabelText(val2) ?? string.Empty; } catch { } string text2 = ((((Object)(object)val2.gameObject != (Object)null) ? ((Object)val2.gameObject).name : string.Empty) + " " + text).ToLowerInvariant(); if (text2.Length != 0) { int num2 = ScoreMenuAction(text2); if (num2 > 0 && (!tuple.HasValue || num2 > tuple.Value.Item4)) { tuple = (val, val2, text, num2); } } } if (tuple.HasValue) { try { Object item = tuple.Value.Item1; Component item2 = tuple.Value.Item2; string item3 = tuple.Value.Item3; object obj3 = ((object)item).GetType().GetProperty("onClick", Any)?.GetValue(item); if (obj3 != null) { MethodInfo method = obj3.GetType().GetMethod("Invoke", Any, null, Type.EmptyTypes, null); if (method != null) { method.Invoke(obj3, null); string arg = (((Object)(object)item2.gameObject != (Object)null) ? ((Object)item2.gameObject).name : "(null)"); reason = $"Clicked UI Button '{arg}' label='{item3}' score={tuple.Value.Item4}"; return true; } } } catch { } } if (!_loggedMenuButtonDump) { _loggedMenuButtonDump = true; try { List list = new List(); for (int num3 = 0; num3 < array.Length; num3++) { if (list.Count >= 12) { break; } Object obj5 = array[num3]; Component val3 = (Component)(object)((obj5 is Component) ? obj5 : null); if (val3 != null) { string text3 = (((Object)(object)val3.gameObject != (Object)null) ? ((Object)val3.gameObject).name : string.Empty); string text4 = string.Empty; try { text4 = TryGetButtonLabelText(val3) ?? string.Empty; } catch { } if (!string.IsNullOrWhiteSpace(text3) || !string.IsNullOrWhiteSpace(text4)) { list.Add(text3 + ":'" + text4 + "'"); } } } if (list.Count > 0) { reason = string.Format("No matching UI menu button found (total={0}). Samples: {1}", array.Length, string.Join(" | ", list)); } else { reason = $"No matching UI menu button found (total={array.Length})"; } } catch { reason = "No matching UI menu button found"; } } else { reason = "No matching UI menu button found"; } return false; } private static int ScoreMenuAction(string hay) { if (string.IsNullOrWhiteSpace(hay)) { return 0; } if (hay.Contains("new") || hay.Contains("нов") || hay.Contains("с нуля") || hay.Contains("new game") || hay.Contains("новая")) { return 0; } if (hay.Contains("continue") || hay.Contains("продолж")) { return 120; } if (hay.Contains("resume")) { return 110; } if (hay.Contains("load") || hay.Contains("загруз")) { return 100; } if (hay.Contains("play") || hay.Contains("играть")) { return 90; } if (hay.Contains("start game") || hay.Contains("startgame")) { return 80; } if (hay.Contains("start") || hay.Contains("начать") || hay.Contains("старт")) { return 60; } return 0; } private static bool TryClickUiToolkitButton(out string reason) { reason = string.Empty; Type type = FindTypeAnyAssembly("UnityEngine.UIElements.UIDocument") ?? FindTypeAnyAssembly("UnityEngine.UIElements.UIDocumentBehaviour"); Type type2 = FindTypeAnyAssembly("UnityEngine.UIElements.Button"); if (type == null || type2 == null) { reason = "UI Toolkit types not found"; return false; } Object[] array = TryResourcesFindObjectsOfTypeAll(type); if (array == null || array.Length == 0) { reason = "No UIDocument found"; return false; } string[] keys = new string[13] { "continue", "resume", "play", "start game", "startgame", "load", "load game", "loadgame", "продолж", "играть", "начать", "старт", "загруз" }; foreach (Object obj in array) { Component val = (Component)(object)((obj is Component) ? obj : null); if (val != null && !((Object)(object)val == (Object)null)) { object obj2 = null; try { obj2 = ((object)val).GetType().GetProperty("rootVisualElement", Any)?.GetValue(val, null); } catch { } if (obj2 != null && TryFindAndClickUiToolkitButton(obj2, type2, keys, out string reason2)) { reason = reason2; return true; } } } if (!_loggedUiToolkitButtonDump) { _loggedUiToolkitButtonDump = true; try { List list = new List(); int totalButtons = 0; foreach (Object obj4 in array) { Component val2 = (Component)(object)((obj4 is Component) ? obj4 : null); if (val2 == null) { continue; } object obj5 = null; try { obj5 = ((object)val2).GetType().GetProperty("rootVisualElement", Any)?.GetValue(val2, null); } catch { } if (obj5 != null) { CollectUiToolkitButtonSamples(obj5, type2, list, ref totalButtons, 14); if (list.Count >= 14) { break; } } } if (totalButtons > 0) { reason = string.Format("No matching UI Toolkit button found (docs={0}, totalButtons~={1}). Samples: {2}", array.Length, totalButtons, string.Join(" | ", list)); } else { reason = $"No matching UI Toolkit button found (docs={array.Length}, totalButtons~=0)"; } } catch { reason = "No matching UI Toolkit button found"; } } else { reason = "No matching UI Toolkit button found"; } return false; } private static void CollectUiToolkitButtonSamples(object rootVisualElement, Type buttonType, List samples, ref int totalButtons, int maxSamples) { Stack stack = new Stack(); stack.Push(rootVisualElement); while (stack.Count > 0) { object obj = stack.Pop(); if (obj == null) { continue; } Type type = obj.GetType(); if (buttonType.IsAssignableFrom(type)) { totalButtons++; if (samples.Count < maxSamples) { string text = TryGetStringProp(obj, "name") ?? string.Empty; string text2 = TryGetStringProp(obj, "text") ?? string.Empty; if (!string.IsNullOrWhiteSpace(text) || !string.IsNullOrWhiteSpace(text2)) { samples.Add(text + ":'" + text2 + "'"); } } } try { MethodInfo method = type.GetMethod("Children", Any, null, Type.EmptyTypes, null); if (!(method != null) || !(method.Invoke(obj, null) is IEnumerable enumerable)) { continue; } foreach (object item in enumerable) { if (item != null) { stack.Push(item); } } } catch { } } } private static bool TryFindAndClickUiToolkitButton(object rootVisualElement, Type buttonType, string[] keys, out string reason) { reason = string.Empty; Stack stack = new Stack(); stack.Push(rootVisualElement); (object, string, string, int)? tuple = null; while (stack.Count > 0) { object obj = stack.Pop(); if (obj == null) { continue; } Type type = obj.GetType(); if (buttonType.IsAssignableFrom(type)) { string text = TryGetStringProp(obj, "name") ?? string.Empty; string text2 = TryGetStringProp(obj, "text") ?? string.Empty; string text3 = (text + " " + text2).ToLowerInvariant(); bool flag = false; for (int i = 0; i < keys.Length; i++) { if (text3.Contains(keys[i].ToLowerInvariant())) { flag = true; break; } } if (flag) { int num = ScoreMenuAction(text3); if (num > 0 && (!tuple.HasValue || num > tuple.Value.Item4)) { tuple = (obj, text, text2, num); } } } try { MethodInfo method = type.GetMethod("Children", Any, null, Type.EmptyTypes, null); if (!(method != null) || !(method.Invoke(obj, null) is IEnumerable enumerable)) { continue; } foreach (object item in enumerable) { if (item != null) { stack.Push(item); } } } catch { } } if (tuple.HasValue && TryInvokeUiToolkitButtonClick(tuple.Value.Item1)) { reason = $"Clicked UI Toolkit Button name='{tuple.Value.Item2}' text='{tuple.Value.Item3}' score={tuple.Value.Item4}"; return true; } return false; } private static bool TryInvokeUiToolkitButtonClick(object button) { try { Type type = button.GetType(); MethodInfo method = type.GetMethod("Click", Any, null, Type.EmptyTypes, null); if (method != null) { method.Invoke(button, null); return true; } object obj = type.GetProperty("clickable", Any)?.GetValue(button, null); if (obj != null) { Type type2 = obj.GetType(); MethodInfo method2 = type2.GetMethod("Invoke", Any, null, Type.EmptyTypes, null); if (method2 != null) { method2.Invoke(obj, null); return true; } MethodInfo method3 = type2.GetMethod("SimulateSingleClick", Any); if (method3 != null) { ParameterInfo[] parameters = method3.GetParameters(); if (parameters.Length == 0) { method3.Invoke(obj, null); return true; } if (parameters.Length == 1) { method3.Invoke(obj, new object[1]); return true; } } } if ((type.GetField("clicked", Any) ?? type.GetField("k__BackingField", Any))?.GetValue(button) is Delegate obj2) { obj2.DynamicInvoke(); return true; } } catch { } return false; } private static string? TryGetStringProp(object o, string prop) { try { if (o.GetType().GetProperty(prop, Any)?.GetValue(o, null) is string result) { return result; } } catch { } return null; } private static Object[]? TryResourcesFindObjectsOfTypeAll(Type t) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown try { MethodInfo method = typeof(Resources).GetMethod("FindObjectsOfTypeAll", Any, null, new Type[1] { typeof(Type) }, null); if (method == null) { return null; } if (!(method.Invoke(null, new object[1] { t }) is Array array)) { return null; } Object[] array2 = (Object[])(object)new Object[array.Length]; for (int i = 0; i < array.Length; i++) { array2[i] = (Object)array.GetValue(i); } return array2; } catch { return null; } } private static string? TryGetButtonLabelText(Component button) { Type type = FindTypeAnyAssembly("UnityEngine.UI.Text") ?? FindTypeAnyAssembly("TMPro.TextMeshProUGUI"); if (type == null) { return null; } try { Component[] componentsInChildren = button.GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && type.IsInstanceOfType(val) && ((object)val).GetType().GetProperty("text", Any)?.GetValue(val) is string text && !string.IsNullOrWhiteSpace(text)) { return text.Trim(); } } } catch { } return null; } private static Type? FindTypeAnyAssembly(string fullName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { Type type = assembly.GetType(fullName, throwOnError: false); if (type != null) { return type; } } catch { } } return null; } private static bool TryInvokeLikelyMenuHandler(string preferredSaveId, out string reason) { //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) reason = string.Empty; TryExtractSaveSlotName(preferredSaveId); TryExtractSaveSlotIndex(preferredSaveId); Object[] array = TryResourcesFindObjectsOfTypeAll(typeof(MonoBehaviour)); if (array == null || array.Length == 0) { reason = "No MonoBehaviours found"; return false; } string[] array2 = new string[17] { "Continue", "ContinueGame", "OnContinue", "OnContinuePressed", "OnContinueButton", "Play", "OnPlay", "OnPlayPressed", "OnPlayButton", "StartGame", "OnStart", "OnStartPressed", "Load", "LoadGame", "LoadSave", "LoadFromSave", "LoadLast" }; foreach (Object obj in array) { MonoBehaviour val = (MonoBehaviour)(object)((obj is MonoBehaviour) ? obj : null); if (val == null || string.Equals(((object)val).GetType().Name, "ScreenSystem", StringComparison.OrdinalIgnoreCase)) { continue; } try { if (((Behaviour)val).isActiveAndEnabled && !((Object)(object)((Component)val).gameObject == (Object)null) && ((Component)val).gameObject.activeInHierarchy) { Scene scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).isLoaded) { goto IL_0151; } } } catch { goto IL_0151; } continue; IL_0151: string name = ((object)val).GetType().Name; string text = (((Object)(object)((Component)val).gameObject != (Object)null) ? ((Object)((Component)val).gameObject).name : string.Empty); string text2 = (name + " " + text).ToLowerInvariant(); if (!text2.Contains("menu") && !text2.Contains("main") && !text2.Contains("title") && !text2.Contains("save") && !text2.Contains("select") && !text2.Contains("loader") && !text2.Contains("scene") && !text2.Contains("start") && !text2.Contains("ui") && !text2.Contains("canvas") && !text2.Contains("front")) { continue; } MethodInfo[] methods; try { methods = ((object)val).GetType().GetMethods(Any); } catch { continue; } foreach (string b in array2) { foreach (MethodInfo methodInfo in methods) { if (methodInfo == null || !string.Equals(methodInfo.Name, b, StringComparison.OrdinalIgnoreCase)) { continue; } ParameterInfo[] parameters = methodInfo.GetParameters(); try { if (parameters.Length == 0) { methodInfo.Invoke(val, null); reason = "Invoked " + ((object)val).GetType().Name + "." + methodInfo.Name + "()"; return true; } if (parameters.Length == 1) { Type parameterType = parameters[0].ParameterType; if (parameterType.FullName != null && parameterType.FullName.Contains("EventSystems", StringComparison.OrdinalIgnoreCase)) { methodInfo.Invoke(val, new object[1]); reason = "Invoked " + ((object)val).GetType().Name + "." + methodInfo.Name + "(eventData:null)"; return true; } if (parameterType == typeof(object) || typeof(Object).IsAssignableFrom(parameterType)) { methodInfo.Invoke(val, new object[1]); reason = "Invoked " + ((object)val).GetType().Name + "." + methodInfo.Name + "(null)"; return true; } } } catch { } } } } reason = "No likely menu handler found in scan"; return false; } private static bool TryInvokeSubstringHeuristic(string slotNameHint, int? slotIndexHint, out string reason) { //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) reason = string.Empty; Object[] array = TryResourcesFindObjectsOfTypeAll(typeof(MonoBehaviour)); if (array == null || array.Length == 0) { reason = "No MonoBehaviours found (heuristic)"; return false; } (string, int)[] array2 = new(string, int)[6] { ("continue", 120), ("resume", 110), ("load", 100), ("playsave", 95), ("play", 90), ("startgame", 85) }; (MonoBehaviour, MethodInfo, int)? tuple = null; foreach (Object obj in array) { MonoBehaviour val = (MonoBehaviour)(object)((obj is MonoBehaviour) ? obj : null); if (val == null || string.Equals(((object)val).GetType().Name, "ScreenSystem", StringComparison.OrdinalIgnoreCase)) { continue; } try { if (((Behaviour)val).isActiveAndEnabled && !((Object)(object)((Component)val).gameObject == (Object)null) && ((Component)val).gameObject.activeInHierarchy) { Scene scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).isLoaded) { goto IL_0136; } } } catch { } continue; IL_0136: string name = ((object)val).GetType().Name; string text = (((Object)(object)((Component)val).gameObject != (Object)null) ? ((Object)((Component)val).gameObject).name : string.Empty); string text2 = (name + " " + text).ToLowerInvariant(); if (!text2.Contains("menu") && !text2.Contains("ui") && !text2.Contains("canvas") && !text2.Contains("title") && !text2.Contains("front") && !text2.Contains("save") && !text2.Contains("select")) { continue; } MethodInfo[] methods; try { methods = ((object)val).GetType().GetMethods(Any); } catch { continue; } foreach (MethodInfo methodInfo in methods) { if (methodInfo == null || methodInfo.IsSpecialName || methodInfo.IsGenericMethod || methodInfo.ReturnType != typeof(void)) { continue; } string name2 = methodInfo.Name; if (string.IsNullOrEmpty(name2) || string.Equals(name2, "Awake", StringComparison.OrdinalIgnoreCase) || string.Equals(name2, "Start", StringComparison.OrdinalIgnoreCase) || string.Equals(name2, "Update", StringComparison.OrdinalIgnoreCase) || string.Equals(name2, "FixedUpdate", StringComparison.OrdinalIgnoreCase) || string.Equals(name2, "LateUpdate", StringComparison.OrdinalIgnoreCase) || string.Equals(name2, "OnEnable", StringComparison.OrdinalIgnoreCase) || string.Equals(name2, "OnDisable", StringComparison.OrdinalIgnoreCase) || string.Equals(name2, "OnGUI", StringComparison.OrdinalIgnoreCase)) { continue; } string text3 = name2.ToLowerInvariant(); int num = 0; for (int k = 0; k < array2.Length; k++) { if (text3.Contains(array2[k].Item1)) { num = Math.Max(num, array2[k].Item2); break; } } if (num == 0) { continue; } if (text2.Contains("mainmenu") || text2.Contains("savemenu") || text2.Contains("save") || text2.Contains("sceneloader")) { num += 15; } if (text3.StartsWith("on", StringComparison.Ordinal)) { num += 5; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length > 1) { continue; } if (parameters.Length == 1) { Type parameterType = parameters[0].ParameterType; bool flag = parameterType == typeof(object) || typeof(Object).IsAssignableFrom(parameterType); if (!flag && parameterType.FullName != null) { flag = parameterType.FullName.Contains("EventSystems", StringComparison.OrdinalIgnoreCase); } if (!flag) { continue; } } if (!tuple.HasValue || num > tuple.Value.Item3) { tuple = (val, methodInfo, num); } } } if (!tuple.HasValue) { reason = "No heuristic candidate found"; return false; } try { MonoBehaviour item = tuple.Value.Item1; MethodInfo item2 = tuple.Value.Item2; if (string.Equals(((object)item).GetType().Name, "ScreenSystem", StringComparison.OrdinalIgnoreCase) && item2.Name.IndexOf("resume", StringComparison.OrdinalIgnoreCase) >= 0) { reason = "Skipped unsafe ScreenSystem.Resume heuristic"; return false; } ParameterInfo[] parameters2 = item2.GetParameters(); if (parameters2.Length == 0) { item2.Invoke(item, null); reason = $"Heuristic invoke {((object)item).GetType().Name}.{item2.Name}() score={tuple.Value.Item3}"; return true; } Type parameterType2 = parameters2[0].ParameterType; if (parameterType2.FullName != null && parameterType2.FullName.Contains("EventSystems", StringComparison.OrdinalIgnoreCase)) { item2.Invoke(item, new object[1]); reason = $"Heuristic invoke {((object)item).GetType().Name}.{item2.Name}(eventData:null) score={tuple.Value.Item3}"; return true; } item2.Invoke(item, new object[1]); reason = $"Heuristic invoke {((object)item).GetType().Name}.{item2.Name}(null) score={tuple.Value.Item3}"; return true; } catch { reason = "Heuristic invoke failed"; return false; } } private static Type? FindTypeInAssemblyCSharp(string typeName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (string.Equals(assembly.GetName().Name, "Assembly-CSharp", StringComparison.Ordinal)) { return assembly.GetTypes().FirstOrDefault((Type t) => string.Equals(t.Name, typeName, StringComparison.Ordinal) || string.Equals(t.FullName, typeName, StringComparison.Ordinal)); } } return null; } private static Transform? FindBestVisualRoot(Transform root) { Renderer[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); if (componentsInChildren == null || componentsInChildren.Length == 0) { return null; } Dictionary dictionary = new Dictionary(); foreach (Renderer val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } string name = ((Object)((Component)val).gameObject).name; if (!string.IsNullOrEmpty(name) && name.StartsWith("EasyDeliveryCoLanCoop.", StringComparison.Ordinal)) { continue; } Transform val2 = ((Component)val).transform; if (!((Object)(object)val2 == (Object)(object)root)) { while ((Object)(object)val2 != (Object)(object)root && (Object)(object)val2.parent != (Object)null && (Object)(object)val2.parent != (Object)(object)root) { val2 = val2.parent; } if ((Object)(object)val2.parent == (Object)null) { val2 = root; } } if (!dictionary.TryGetValue(val2, out var value)) { dictionary[val2] = 1; } else { dictionary[val2] = value + 1; } } Transform val3 = null; int num = 0; foreach (KeyValuePair item in dictionary) { if (item.Value > num) { val3 = item.Key; num = item.Value; } } return val3 ?? root; } private static int ReadInt(object? v) { if (v is int) { return (int)v; } if (v is short) { return (short)v; } if (v is byte) { return (byte)v; } if (v is float num) { return Mathf.RoundToInt(num); } return 0; } private static float ReadFloat(object? v) { if (v is float) { return (float)v; } if (v is int num) { return num; } if (v is double num2) { return (float)num2; } return 0f; } private static bool ReadBool(object? v) { if (v is bool) { return (bool)v; } return false; } private static void SetField(Type t, object instance, string fieldName, object? value) { FieldInfo field = t.GetField(fieldName, Any); if (field == null) { return; } try { if (field.FieldType == typeof(string)) { field.SetValue(instance, value?.ToString() ?? string.Empty); } else if (field.FieldType == typeof(int) && value is int num) { field.SetValue(instance, num); } else if (field.FieldType == typeof(float) && value is float num2) { field.SetValue(instance, num2); } else if (field.FieldType == typeof(bool) && value is bool flag) { field.SetValue(instance, flag); } } catch { } } } internal static class MaterialUtil { private static Material? _cached; private static float _nextRescanAt; internal static Material? GetSceneMaterialFallback() { if ((Object)(object)_cached != (Object)null) { return _cached; } if (Time.unscaledTime < _nextRescanAt) { return null; } _nextRescanAt = Time.unscaledTime + 1f; if (GameAccess.TryFindLocalCarVisualRoot(out Transform visualRoot)) { Material val = FindFirstMaterial(visualRoot); if ((Object)(object)val != (Object)null) { return _cached = val; } } if (GameAccess.TryFindLocalPlayerVisualRoot(out Transform visualRoot2)) { Material val2 = FindFirstMaterial(visualRoot2); if ((Object)(object)val2 != (Object)null) { return _cached = val2; } } Renderer val3 = Object.FindAnyObjectByType(); if ((Object)(object)val3 != (Object)null) { Material[] sharedMaterials = val3.sharedMaterials; if (sharedMaterials != null) { for (int i = 0; i < sharedMaterials.Length; i++) { if ((Object)(object)sharedMaterials[i] != (Object)null) { return _cached = sharedMaterials[i]; } } } } return null; } private static Material? FindFirstMaterial(Transform root) { Renderer[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } Material[] sharedMaterials = val.sharedMaterials; if (sharedMaterials == null) { continue; } for (int j = 0; j < sharedMaterials.Length; j++) { if ((Object)(object)sharedMaterials[j] != (Object)null) { return sharedMaterials[j]; } } } return null; } } internal static class MeshOnlyClone { internal static GameObject? TryCreateMeshRendererHierarchyPrefab(Transform sourceRoot) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if ((Object)(object)sourceRoot == (Object)null) { return null; } try { GameObject val = new GameObject("EasyDeliveryCoLanCoop.MeshOnlyPrefab." + ((Object)sourceRoot).name); ((Object)val).hideFlags = (HideFlags)52; CopyRecursive(sourceRoot, val.transform); val.SetActive(false); return val; } catch { return null; } } private static void CopyRecursive(Transform src, Transform dstParent) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(((Object)src).name); ((Object)val).hideFlags = (HideFlags)52; val.transform.SetParent(dstParent, false); val.transform.localPosition = src.localPosition; val.transform.localRotation = src.localRotation; val.transform.localScale = src.localScale; MeshFilter component = ((Component)src).GetComponent(); MeshRenderer component2 = ((Component)src).GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)component.sharedMesh != (Object)null) { val.AddComponent().sharedMesh = component.sharedMesh; } if ((Object)(object)component2 != (Object)null) { MeshRenderer val2 = val.AddComponent(); Material[] sharedMaterials = ((Renderer)component2).sharedMaterials; Material[] array = (Material[])(object)new Material[sharedMaterials.Length]; for (int i = 0; i < sharedMaterials.Length; i++) { array[i] = (((Object)(object)sharedMaterials[i] != (Object)null) ? new Material(sharedMaterials[i]) : ((Material)null)); } ((Renderer)val2).sharedMaterials = array; ((Renderer)val2).shadowCastingMode = ((Renderer)component2).shadowCastingMode; ((Renderer)val2).receiveShadows = ((Renderer)component2).receiveShadows; ((Renderer)val2).lightProbeUsage = ((Renderer)component2).lightProbeUsage; ((Renderer)val2).reflectionProbeUsage = ((Renderer)component2).reflectionProbeUsage; } for (int j = 0; j < src.childCount; j++) { CopyRecursive(src.GetChild(j), val.transform); } } } internal sealed class MoneyUiFormatter : MonoBehaviour { private static readonly BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private object? _hud; private object? _moneyText; private PropertyInfo? _textProp; private float _nextResolveAt; private float _nextUpdateAt; private void Update() { if (string.Equals(Plugin.GetEffectiveNetworkMode(), "Off", StringComparison.OrdinalIgnoreCase)) { _hud = null; _moneyText = null; _textProp = null; return; } float unscaledTime = Time.unscaledTime; if (unscaledTime >= _nextResolveAt) { _nextResolveAt = unscaledTime + 2f; ResolveTargets(); } if (!IsUnityObjectAlive(_moneyText) || _textProp == null) { _moneyText = null; _textProp = null; } else { if (unscaledTime < _nextUpdateAt) { return; } _nextUpdateAt = unscaledTime + 0.2f; if (!GameAccess.TryReadHudMoney(out var money)) { return; } string replacement = ((float)money).ToString("F2", CultureInfo.CurrentCulture); try { string obj = (_textProp.GetValue(_moneyText, null) as string) ?? string.Empty; string text = ReplaceNumberSegment(obj, replacement); if (!string.Equals(obj, text, StringComparison.Ordinal)) { _textProp.SetValue(_moneyText, text, null); } } catch { _moneyText = null; _textProp = null; } } } private void ResolveTargets() { if (!IsUnityObjectAlive(_hud)) { _hud = null; } if (_hud == null) { _hud = TryFindObjectOfTypeByName("sHUD"); } if (_hud == null) { _moneyText = null; _textProp = null; } else { if (_moneyText != null && _textProp != null) { return; } Type type = _hud.GetType(); (object, PropertyInfo)? tuple = FindMoneyTextOnObject(_hud, type); if (tuple.HasValue) { _moneyText = tuple.Value.Item1; _textProp = tuple.Value.Item2; return; } object? hud = _hud; Component val = (Component)((hud is Component) ? hud : null); if (val == null || !((Object)(object)val != (Object)null)) { return; } if (!GameAccess.TryReadHudMoney(out var money)) { money = 0; } string value = money.ToString(CultureInfo.CurrentCulture); Component[] componentsInChildren; try { componentsInChildren = val.GetComponentsInChildren(true); } catch { _hud = null; _moneyText = null; _textProp = null; return; } foreach (Component val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null || !TryGetWritableTextProperty(((object)val2).GetType(), out PropertyInfo prop)) { continue; } try { string text = prop.GetValue(val2, null) as string; if (string.IsNullOrWhiteSpace(text)) { continue; } GameObject gameObject = val2.gameObject; if (!((Object)(object)gameObject == (Object)null)) { string text2 = ((Object)gameObject).name?.ToLowerInvariant() ?? string.Empty; if (text2.Contains("money") || text2.Contains("cash") || text2.Contains("coins")) { _moneyText = val2; _textProp = prop; break; } if (text.Contains(value, StringComparison.Ordinal)) { _moneyText = val2; _textProp = prop; break; } } } catch { } } } } private static (object Target, PropertyInfo TextProp)? FindMoneyTextOnObject(object instance, Type t) { FieldInfo[] fields = t.GetFields(Any); foreach (FieldInfo fieldInfo in fields) { if (!(fieldInfo == null) && !string.IsNullOrEmpty(fieldInfo.Name) && fieldInfo.Name.ToLowerInvariant().Contains("money")) { object value; try { value = fieldInfo.GetValue(instance); } catch { continue; } if (value != null && TryGetWritableTextProperty(value.GetType(), out PropertyInfo prop)) { return (value, prop); } } } PropertyInfo[] properties = t.GetProperties(Any); foreach (PropertyInfo propertyInfo in properties) { if (!(propertyInfo == null) && !string.IsNullOrEmpty(propertyInfo.Name) && propertyInfo.Name.ToLowerInvariant().Contains("money")) { object value2; try { value2 = propertyInfo.GetValue(instance, null); } catch { continue; } if (value2 != null && TryGetWritableTextProperty(value2.GetType(), out PropertyInfo prop2)) { return (value2, prop2); } } } return null; } private static bool TryGetWritableTextProperty(Type t, out PropertyInfo prop) { prop = null; try { PropertyInfo property = t.GetProperty("text", Any); if (property == null) { return false; } if (property.PropertyType != typeof(string)) { return false; } if (!property.CanWrite) { return false; } prop = property; return true; } catch { return false; } } private static string ReplaceNumberSegment(string current, string replacement) { if (string.IsNullOrEmpty(current)) { return replacement; } int num = -1; for (int i = 0; i < current.Length; i++) { if (char.IsDigit(current[i])) { num = i; break; } } if (num < 0) { return replacement; } int num2 = -1; for (int num3 = current.Length - 1; num3 >= 0; num3--) { if (char.IsDigit(current[num3])) { num2 = num3; break; } } if (num2 < num) { return replacement; } return current.Substring(0, num) + replacement + current.Substring(num2 + 1); } private static object? TryFindObjectOfTypeByName(string typeName) { try { Type type = FindTypeInAssemblyCSharp(typeName); if (type == null) { return null; } MethodInfo method = typeof(Object).GetMethod("FindObjectOfType", Any, null, new Type[1] { typeof(Type) }, null); if (method == null) { return null; } return method.Invoke(null, new object[1] { type }); } catch { return null; } } private static Type? FindTypeInAssemblyCSharp(string typeName) { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (!(assembly == null) && string.Equals(assembly.GetName().Name, "Assembly-CSharp", StringComparison.Ordinal)) { return assembly.GetType(typeName, throwOnError: false, ignoreCase: false); } } } catch { } return null; } private static bool IsUnityObjectAlive(object? value) { if (value == null) { return false; } Object val = (Object)((value is Object) ? value : null); if (val != null) { return val != (Object)null; } return true; } } internal static class PayloadPrefabLibrary { private static readonly Dictionary Cache = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static GameObject CreatePayloadInstanceOrFallback(string payloadName, string instanceName) { payloadName = NormalizePayloadName(payloadName); GameObject payloadPrefab = GetPayloadPrefab(payloadName); GameObject val; if ((Object)(object)payloadPrefab != (Object)null) { val = Object.Instantiate(payloadPrefab); val.SetActive(true); } else { val = GameObject.CreatePrimitive((PrimitiveType)3); Material sceneMaterialFallback = MaterialUtil.GetSceneMaterialFallback(); if ((Object)(object)sceneMaterialFallback != (Object)null) { Renderer component = val.GetComponent(); if ((Object)(object)component != (Object)null) { component.sharedMaterial = sceneMaterialFallback; } } Rigidbody val2 = val.GetComponent(); if ((Object)(object)val2 == (Object)null) { val2 = val.AddComponent(); } val2.isKinematic = true; val2.useGravity = false; val2.interpolation = (RigidbodyInterpolation)1; } ((Object)val).name = instanceName; ((Object)val).hideFlags = (HideFlags)52; ConfigureNetworkDrivenPhysics(val); return val; } private static GameObject? GetPayloadPrefab(string payloadName) { if (string.IsNullOrWhiteSpace(payloadName)) { return null; } if (Cache.TryGetValue(payloadName, out GameObject value)) { return value; } GameObject val = null; val = ExternalAssetBundle.TryLoadGameObject(payloadName); if ((Object)(object)val == (Object)null && Plugin.ExternalAssetsEnabled.Value) { val = ExternalAssetBundle.TryLoadGameObjectByBaseName(payloadName); } if ((Object)(object)val == (Object)null && GameAccess.TryFindPayloadVisualRoot(payloadName, out Transform visualRoot)) { val = ((Component)visualRoot).gameObject; } GameObject val2 = null; if ((Object)(object)val != (Object)null) { val2 = PhysicsVisualClone.TryCreatePhysicsPrefab(val.transform); } Cache[payloadName] = val2; return val2; } internal static void ConfigureNetworkDrivenPhysics(GameObject root) { if ((Object)(object)root == (Object)null) { return; } try { Joint[] componentsInChildren = root.GetComponentsInChildren(true); foreach (Joint val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { Object.Destroy((Object)(object)val); } } Rigidbody[] componentsInChildren2 = root.GetComponentsInChildren(true); foreach (Rigidbody val2 in componentsInChildren2) { if (!((Object)(object)val2 == (Object)null)) { val2.isKinematic = true; val2.useGravity = false; val2.interpolation = (RigidbodyInterpolation)0; val2.collisionDetectionMode = (CollisionDetectionMode)0; val2.constraints = (RigidbodyConstraints)126; val2.detectCollisions = false; } } Collider[] componentsInChildren3 = root.GetComponentsInChildren(true); foreach (Collider val3 in componentsInChildren3) { if (!((Object)(object)val3 == (Object)null)) { val3.enabled = true; } } } catch { } } private static string NormalizePayloadName(string? name) { if (name == null) { name = string.Empty; } name = name.Trim(); if (name.EndsWith("(Clone)", StringComparison.Ordinal)) { name = name.Substring(0, name.Length - "(Clone)".Length).Trim(); } return name; } } internal static class PhysicsVisualClone { internal static GameObject? TryCreatePhysicsPrefab(Transform sourceRoot) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if ((Object)(object)sourceRoot == (Object)null) { return null; } try { GameObject val = new GameObject("EasyDeliveryCoLanCoop.PhysicsPrefab." + ((Object)sourceRoot).name); ((Object)val).hideFlags = (HideFlags)52; CopyRecursive(sourceRoot, val.transform); val.SetActive(false); return val; } catch { return null; } } private static void CopyRecursive(Transform src, Transform dstParent) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(((Object)src).name); ((Object)val).hideFlags = (HideFlags)52; val.transform.SetParent(dstParent, false); val.transform.localPosition = src.localPosition; val.transform.localRotation = src.localRotation; val.transform.localScale = src.localScale; CopyRenderers(src, val); CopyCollider(src, val); CopyRigidbody(src, val); for (int i = 0; i < src.childCount; i++) { CopyRecursive(src.GetChild(i), val.transform); } } private static void CopyRenderers(Transform src, GameObject dst) { //IL_0053: 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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) MeshFilter component = ((Component)src).GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)component.sharedMesh != (Object)null) { dst.AddComponent().sharedMesh = component.sharedMesh; } MeshRenderer component2 = ((Component)src).GetComponent(); if ((Object)(object)component2 != (Object)null) { MeshRenderer obj = dst.AddComponent(); ((Renderer)obj).sharedMaterials = ((Renderer)component2).sharedMaterials; ((Renderer)obj).shadowCastingMode = ((Renderer)component2).shadowCastingMode; ((Renderer)obj).receiveShadows = ((Renderer)component2).receiveShadows; ((Renderer)obj).lightProbeUsage = ((Renderer)component2).lightProbeUsage; ((Renderer)obj).reflectionProbeUsage = ((Renderer)component2).reflectionProbeUsage; } SkinnedMeshRenderer component3 = ((Component)src).GetComponent(); if ((Object)(object)component3 != (Object)null) { SkinnedMeshRenderer obj2 = dst.AddComponent(); obj2.sharedMesh = component3.sharedMesh; ((Renderer)obj2).sharedMaterials = ((Renderer)component3).sharedMaterials; ((Renderer)obj2).shadowCastingMode = ((Renderer)component3).shadowCastingMode; ((Renderer)obj2).receiveShadows = ((Renderer)component3).receiveShadows; ((Renderer)obj2).lightProbeUsage = ((Renderer)component3).lightProbeUsage; ((Renderer)obj2).reflectionProbeUsage = ((Renderer)component3).reflectionProbeUsage; } } private static void CopyCollider(Transform src, GameObject dst) { //IL_0018: 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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) BoxCollider component = ((Component)src).GetComponent(); if ((Object)(object)component != (Object)null) { BoxCollider obj = dst.AddComponent(); obj.center = component.center; obj.size = component.size; ((Collider)obj).isTrigger = ((Collider)component).isTrigger; ((Collider)obj).sharedMaterial = ((Collider)component).sharedMaterial; ((Collider)obj).enabled = ((Collider)component).enabled; } SphereCollider component2 = ((Component)src).GetComponent(); if ((Object)(object)component2 != (Object)null) { SphereCollider obj2 = dst.AddComponent(); obj2.center = component2.center; obj2.radius = component2.radius; ((Collider)obj2).isTrigger = ((Collider)component2).isTrigger; ((Collider)obj2).sharedMaterial = ((Collider)component2).sharedMaterial; ((Collider)obj2).enabled = ((Collider)component2).enabled; } CapsuleCollider component3 = ((Component)src).GetComponent(); if ((Object)(object)component3 != (Object)null) { CapsuleCollider obj3 = dst.AddComponent(); obj3.center = component3.center; obj3.radius = component3.radius; obj3.height = component3.height; obj3.direction = component3.direction; ((Collider)obj3).isTrigger = ((Collider)component3).isTrigger; ((Collider)obj3).sharedMaterial = ((Collider)component3).sharedMaterial; ((Collider)obj3).enabled = ((Collider)component3).enabled; } MeshCollider component4 = ((Component)src).GetComponent(); if ((Object)(object)component4 != (Object)null) { MeshCollider obj4 = dst.AddComponent(); obj4.sharedMesh = component4.sharedMesh; obj4.convex = component4.convex; obj4.cookingOptions = component4.cookingOptions; ((Collider)obj4).isTrigger = ((Collider)component4).isTrigger; ((Collider)obj4).sharedMaterial = ((Collider)component4).sharedMaterial; ((Collider)obj4).enabled = ((Collider)component4).enabled; } } private static void CopyRigidbody(Transform src, GameObject dst) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) Rigidbody component = ((Component)src).GetComponent(); if (!((Object)(object)component == (Object)null)) { Rigidbody obj = dst.AddComponent(); obj.mass = component.mass; obj.linearDamping = component.linearDamping; obj.angularDamping = component.angularDamping; obj.useGravity = component.useGravity; obj.isKinematic = component.isKinematic; obj.interpolation = component.interpolation; obj.collisionDetectionMode = component.collisionDetectionMode; obj.constraints = component.constraints; obj.detectCollisions = component.detectCollisions; } } } internal static class PlayerPositionsStore { private sealed class Entry { public Vector3 Pos; public Quaternion Rot; public float LastUpdateAt; } private static readonly Dictionary _entries = new Dictionary(StringComparer.Ordinal); private static string _currentSaveId = "default"; private static float _nextSaveIdRefreshAt; private static float _nextFlushAt; private static bool _dirty; private static bool _loggedSaveId; internal static string CurrentSaveId => _currentSaveId; internal static void HostRefreshSaveIdNow() { if (Plugin.PlayerPositionsEnabled.Value) { string source; string text = TryGetCurrentSaveId(out source); if (!string.Equals(text, _currentSaveId, StringComparison.Ordinal)) { _currentSaveId = text; _entries.Clear(); _dirty = false; TryLoadFromDisk(_currentSaveId); } } } internal static void HostTick(float now) { if (!Plugin.PlayerPositionsEnabled.Value) { return; } if (now >= _nextSaveIdRefreshAt) { _nextSaveIdRefreshAt = now + 2f; string source; string text = TryGetCurrentSaveId(out source); if (Plugin.DebugLogs.Value && (!_loggedSaveId || !string.Equals(text, _currentSaveId, StringComparison.Ordinal))) { _loggedSaveId = true; string filePath = GetFilePath(text); Plugin.Log.LogInfo((object)("Positions: saveId='" + text + "' source=" + source + " file='" + filePath + "'")); } if (!string.Equals(text, _currentSaveId, StringComparison.Ordinal)) { _currentSaveId = text; _entries.Clear(); _dirty = false; TryLoadFromDisk(_currentSaveId); } } if (now >= _nextFlushAt) { _nextFlushAt = now + 2f; if (_dirty) { TryFlushToDisk(_currentSaveId); } } } internal static void HostUpdatePlayer(string nickname, Vector3 pos, Quaternion rot, float now) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (Plugin.PlayerPositionsEnabled.Value) { nickname = Plugin.SanitizeNickname(nickname); if (!_entries.TryGetValue(nickname, out Entry value)) { value = new Entry(); _entries[nickname] = value; } value.Pos = pos; value.Rot = rot; value.LastUpdateAt = now; _dirty = true; } } internal static bool TryGetSnapshot(out Dictionary snapshot) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) snapshot = new Dictionary(StringComparer.Ordinal); if (!Plugin.PlayerPositionsEnabled.Value) { return false; } foreach (KeyValuePair entry in _entries) { snapshot[entry.Key] = (entry.Value.Pos, entry.Value.Rot); } return snapshot.Count > 0; } internal static bool TryGetForNickname(string nickname, out Vector3 pos, out Quaternion rot) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) nickname = Plugin.SanitizeNickname(nickname); if (_entries.TryGetValue(nickname, out Entry value)) { pos = value.Pos; rot = value.Rot; return true; } pos = default(Vector3); rot = default(Quaternion); return false; } private static string TryGetCurrentSaveId(out string source) { source = "fallback"; string text = string.Empty; if (GameAccess.TryReadCurrentMapBuildIndex(out var buildIndex) && buildIndex >= 0) { text = $"__map{buildIndex}"; } string value = Plugin.PlayerPositionsSaveIdOverride.Value; if (!string.IsNullOrWhiteSpace(value)) { source = "override"; return Plugin.SanitizeFileName(value + text); } if (GameAccess.TryReadSaveId(out string saveId) && !string.IsNullOrWhiteSpace(saveId)) { source = "auto"; return Plugin.SanitizeFileName(saveId + text); } if (!string.IsNullOrEmpty(text)) { return Plugin.SanitizeFileName("default" + text); } return "default"; } private static string GetFilePath(string saveId) { saveId = Plugin.SanitizeFileName(saveId); return Path.Combine(Path.Combine(Paths.ConfigPath, "EasyDeliveryCoLanCoop.Positions"), saveId + ".txt"); } private static void TryLoadFromDisk(string saveId) { //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) try { string filePath = GetFilePath(saveId); if (!File.Exists(filePath)) { return; } string[] array = File.ReadAllLines(filePath, Encoding.UTF8); Vector3 pos = default(Vector3); Quaternion rot = default(Quaternion); foreach (string text in array) { if (string.IsNullOrWhiteSpace(text) || text.StartsWith("#", StringComparison.Ordinal)) { continue; } string[] array2 = text.Split('\t'); if (array2.Length >= 9) { string key = Plugin.SanitizeNickname(array2[0]); if (TryParseFloat(array2[1], out var v) && TryParseFloat(array2[2], out var v2) && TryParseFloat(array2[3], out var v3) && TryParseFloat(array2[4], out var v4) && TryParseFloat(array2[5], out var v5) && TryParseFloat(array2[6], out var v6) && TryParseFloat(array2[7], out var v7)) { ((Vector3)(ref pos))..ctor(v, v2, v3); ((Quaternion)(ref rot))..ctor(v4, v5, v6, v7); _entries[key] = new Entry { Pos = pos, Rot = rot, LastUpdateAt = 0f }; } } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("PlayerPositionsStore load failed: " + ex.GetType().Name)); } } private static void TryFlushToDisk(string saveId) { try { string filePath = GetFilePath(saveId); string directoryName = Path.GetDirectoryName(filePath); if (!string.IsNullOrWhiteSpace(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } StringBuilder stringBuilder = new StringBuilder(1024); stringBuilder.AppendLine("# EasyDeliveryCoLanCoop player positions"); stringBuilder.AppendLine("# nickname\tpx\tpy\tpz\tqx\tqy\tqz\tqw"); foreach (KeyValuePair entry in _entries) { string key = entry.Key; Entry value = entry.Value; stringBuilder.Append(key).Append('\t').Append(value.Pos.x.ToString("R", CultureInfo.InvariantCulture)) .Append('\t') .Append(value.Pos.y.ToString("R", CultureInfo.InvariantCulture)) .Append('\t') .Append(value.Pos.z.ToString("R", CultureInfo.InvariantCulture)) .Append('\t') .Append(value.Rot.x.ToString("R", CultureInfo.InvariantCulture)) .Append('\t') .Append(value.Rot.y.ToString("R", CultureInfo.InvariantCulture)) .Append('\t') .Append(value.Rot.z.ToString("R", CultureInfo.InvariantCulture)) .Append('\t') .Append(value.Rot.w.ToString("R", CultureInfo.InvariantCulture)) .AppendLine(); } File.WriteAllText(filePath, stringBuilder.ToString(), Encoding.UTF8); _dirty = false; } catch (Exception ex) { Plugin.Log.LogWarning((object)("PlayerPositionsStore flush failed: " + ex.GetType().Name)); } } private static bool TryParseFloat(string s, out float v) { return float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out v); } } internal static class RemoteAvatars { private static readonly Dictionary Avatars = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary Labels = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary HeldPayloads = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary HeldPayloadNames = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary NextMaterialFixAt = new Dictionary(StringComparer.Ordinal); private static readonly HashSet PrimitiveFallback = new HashSet(StringComparer.Ordinal); internal static void ApplyPlayerPose(string key, string nickname, float px, float py, float pz, float qx, float qy, float qz, float qw, bool inCar, string? heldPayloadName) { //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_038a: Unknown result type (might be due to invalid IL or missing references) //IL_0396: Unknown result type (might be due to invalid IL or missing references) //IL_039b: Unknown result type (might be due to invalid IL or missing references) //IL_03a0: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(key)) { return; } if (!Avatars.TryGetValue(key, out GameObject value) || (Object)(object)value == (Object)null) { GameObject playerPrefab = RemoteVisualPrefabs.GetPlayerPrefab(); if ((Object)(object)playerPrefab != (Object)null) { value = Object.Instantiate(playerPrefab); value.SetActive(true); PrimitiveFallback.Remove(key); } else { value = GameObject.CreatePrimitive((PrimitiveType)1); PrimitiveFallback.Add(key); Material sceneMaterialFallback = MaterialUtil.GetSceneMaterialFallback(); if ((Object)(object)sceneMaterialFallback != (Object)null) { Renderer component = value.GetComponent(); if ((Object)(object)component != (Object)null) { component.sharedMaterial = sceneMaterialFallback; } } Collider val = default(Collider); if (value.TryGetComponent(ref val)) { Object.Destroy((Object)(object)val); } } ((Object)value).name = "EasyDeliveryCoLanCoop.RemotePlayer." + key; ((Object)value).hideFlags = (HideFlags)52; Avatars[key] = value; } if ((Object)(object)value != (Object)null && PrimitiveFallback.Contains(key)) { GameObject playerPrefab2 = RemoteVisualPrefabs.GetPlayerPrefab(); if ((Object)(object)playerPrefab2 != (Object)null) { GameObject val2 = Object.Instantiate(playerPrefab2); val2.SetActive(true); ((Object)val2).name = "EasyDeliveryCoLanCoop.RemotePlayer." + key; ((Object)val2).hideFlags = (HideFlags)52; if (Labels.TryGetValue(key, out TextMesh value2) && (Object)(object)value2 != (Object)null) { ((Component)value2).transform.SetParent(val2.transform, false); ((Component)value2).transform.localPosition = new Vector3(0f, 1.8f, 0f); ((Component)value2).transform.localRotation = Quaternion.identity; } Object.Destroy((Object)(object)value); value = val2; Avatars[key] = value; PrimitiveFallback.Remove(key); NextMaterialFixAt.Remove(key); Plugin.Log.LogInfo((object)("Upgraded remote player '" + key + "' from primitive to prefab.")); } else { ApplyFallbackMaterialToRoot(value); } } if ((Object)(object)value == (Object)null) { return; } if (!Labels.TryGetValue(key, out TextMesh value3) || (Object)(object)value3 == (Object)null) { GameObject val3 = new GameObject("EasyDeliveryCoLanCoop.RemotePlayerLabel." + key) { hideFlags = (HideFlags)52 }; val3.transform.SetParent(value.transform, false); val3.transform.localPosition = new Vector3(0f, 1.8f, 0f); val3.transform.localRotation = Quaternion.identity; value3 = val3.AddComponent(); value3.anchor = (TextAnchor)7; value3.alignment = (TextAlignment)1; value3.characterSize = 0.08f; value3.fontSize = 64; value3.richText = false; Labels[key] = value3; } value.SetActive(true); value.transform.position = new Vector3(px, py, pz); Quaternion val4 = default(Quaternion); ((Quaternion)(ref val4))..ctor(qx, qy, qz, qw); float num = ((Plugin.RemoteAvatarYawOffsetDegrees != null) ? Plugin.RemoteAvatarYawOffsetDegrees.Value : (-90f)); value.transform.rotation = val4 * Quaternion.Euler(0f, num, 0f); FixUnsupportedMaterials(key, value); ApplyHeldPayload(key, value, heldPayloadName); Renderer component2 = value.GetComponent(); if ((Object)(object)component2 != (Object)null && (Object)(object)component2.sharedMaterial != (Object)null) { Shader shader = component2.sharedMaterial.shader; if ((Object)(object)shader == (Object)null || !shader.isSupported) { Material sceneMaterialFallback2 = MaterialUtil.GetSceneMaterialFallback(); if ((Object)(object)sceneMaterialFallback2 != (Object)null) { component2.sharedMaterial = sceneMaterialFallback2; } } } if ((Object)(object)value3 != (Object)null) { value3.text = Plugin.SanitizeNickname(nickname); Camera main = Camera.main; if ((Object)(object)main != (Object)null) { ((Component)value3).transform.rotation = Quaternion.LookRotation(((Component)value3).transform.position - ((Component)main).transform.position); } } } private static void ApplyFallbackMaterialToRoot(GameObject go) { if ((Object)(object)go == (Object)null) { return; } Renderer component = go.GetComponent(); if (!((Object)(object)component == (Object)null)) { Material sceneMaterialFallback = MaterialUtil.GetSceneMaterialFallback(); if ((Object)(object)sceneMaterialFallback != (Object)null) { component.sharedMaterial = sceneMaterialFallback; } } } private static void FixUnsupportedMaterials(string key, GameObject go) { if ((Object)(object)go == (Object)null) { return; } float unscaledTime = Time.unscaledTime; if (NextMaterialFixAt.TryGetValue(key, out var value) && unscaledTime < value) { return; } NextMaterialFixAt[key] = unscaledTime + 2f; Material sceneMaterialFallback = MaterialUtil.GetSceneMaterialFallback(); if ((Object)(object)sceneMaterialFallback == (Object)null) { return; } try { Renderer[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } Material[] sharedMaterials = val.sharedMaterials; if (sharedMaterials == null || sharedMaterials.Length == 0) { continue; } bool flag = false; for (int j = 0; j < sharedMaterials.Length; j++) { Material val2 = sharedMaterials[j]; if (!((Object)(object)val2 == (Object)null)) { Shader shader = val2.shader; if ((Object)(object)shader == (Object)null || !shader.isSupported || string.Equals(((Object)shader).name, "Hidden/InternalErrorShader", StringComparison.Ordinal)) { sharedMaterials[j] = sceneMaterialFallback; flag = true; } } } if (flag) { val.sharedMaterials = sharedMaterials; } } } catch { } } internal static void CleanupMissing(HashSet aliveKeys) { List list = new List(); foreach (KeyValuePair avatar in Avatars) { if (!aliveKeys.Contains(avatar.Key)) { if ((Object)(object)avatar.Value != (Object)null) { Object.Destroy((Object)(object)avatar.Value); } if (Labels.TryGetValue(avatar.Key, out TextMesh value) && (Object)(object)value != (Object)null) { Object.Destroy((Object)(object)((Component)value).gameObject); } if (HeldPayloads.TryGetValue(avatar.Key, out GameObject value2) && (Object)(object)value2 != (Object)null) { Object.Destroy((Object)(object)value2); } list.Add(avatar.Key); } } foreach (string item in list) { Avatars.Remove(item); Labels.Remove(item); HeldPayloads.Remove(item); HeldPayloadNames.Remove(item); NextMaterialFixAt.Remove(item); PrimitiveFallback.Remove(item); } } internal static void Remove(string key) { if (!string.IsNullOrWhiteSpace(key)) { if (Avatars.TryGetValue(key, out GameObject value) && (Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } if (Labels.TryGetValue(key, out TextMesh value2) && (Object)(object)value2 != (Object)null) { Object.Destroy((Object)(object)((Component)value2).gameObject); } if (HeldPayloads.TryGetValue(key, out GameObject value3) && (Object)(object)value3 != (Object)null) { Object.Destroy((Object)(object)value3); } Avatars.Remove(key); Labels.Remove(key); HeldPayloads.Remove(key); HeldPayloadNames.Remove(key); NextMaterialFixAt.Remove(key); PrimitiveFallback.Remove(key); } } private static void ApplyHeldPayload(string key, GameObject avatarRoot, string? heldPayloadName) { //IL_019e: 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_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)avatarRoot == (Object)null) { return; } if (heldPayloadName == null) { heldPayloadName = string.Empty; } heldPayloadName = NormalizePayloadName(heldPayloadName); if (string.IsNullOrWhiteSpace(heldPayloadName) || !heldPayloadName.StartsWith("PAYLOAD", StringComparison.OrdinalIgnoreCase)) { if (HeldPayloads.TryGetValue(key, out GameObject value) && (Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } HeldPayloads.Remove(key); HeldPayloadNames.Remove(key); return; } if (HeldPayloadNames.TryGetValue(key, out string value2) && string.Equals(value2, heldPayloadName, StringComparison.Ordinal)) { if (HeldPayloads.TryGetValue(key, out GameObject value3) && (Object)(object)value3 != (Object)null) { value3.transform.SetParent(avatarRoot.transform, false); value3.transform.localPosition = new Vector3(Plugin.RemoteHeldPayloadOffsetX.Value, Plugin.RemoteHeldPayloadOffsetY.Value, Plugin.RemoteHeldPayloadOffsetZ.Value); value3.transform.localRotation = Quaternion.identity; float num = Mathf.Max(0.01f, Plugin.RemoteHeldPayloadUniformScale.Value); value3.transform.localScale = new Vector3(num, num, num); } return; } if (HeldPayloads.TryGetValue(key, out GameObject value4) && (Object)(object)value4 != (Object)null) { Object.Destroy((Object)(object)value4); } GameObject val = PayloadPrefabLibrary.CreatePayloadInstanceOrFallback(heldPayloadName, "EasyDeliveryCoLanCoop.RemoteHeldPayload." + key + "." + heldPayloadName); ((Object)val).hideFlags = (HideFlags)52; val.transform.SetParent(avatarRoot.transform, false); val.transform.localPosition = new Vector3(Plugin.RemoteHeldPayloadOffsetX.Value, Plugin.RemoteHeldPayloadOffsetY.Value, Plugin.RemoteHeldPayloadOffsetZ.Value); val.transform.localRotation = Quaternion.identity; float num2 = Mathf.Max(0.01f, Plugin.RemoteHeldPayloadUniformScale.Value); val.transform.localScale = new Vector3(num2, num2, num2); PayloadPrefabLibrary.ConfigureNetworkDrivenPhysics(val); HeldPayloads[key] = val; HeldPayloadNames[key] = heldPayloadName; } private static string NormalizePayloadName(string? name) { if (name == null) { name = string.Empty; } name = name.Trim(); if (name.EndsWith("(Clone)", StringComparison.Ordinal)) { name = name.Substring(0, name.Length - "(Clone)".Length).Trim(); } return name; } } internal static class RemoteCars { private sealed class RemoteCar { public readonly GameObject Root; public readonly Dictionary Cargo = new Dictionary(StringComparer.Ordinal); public readonly TextMesh Label; public readonly bool IsFallbackPrimitive; public RemoteCar(string key) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Expected O, but got Unknown //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) GameObject carPrefab = RemoteVisualPrefabs.GetCarPrefab(); if ((Object)(object)carPrefab != (Object)null) { Root = Object.Instantiate(carPrefab); Root.SetActive(true); IsFallbackPrimitive = false; } else { Root = GameObject.CreatePrimitive((PrimitiveType)3); IsFallbackPrimitive = true; Material sceneMaterialFallback = MaterialUtil.GetSceneMaterialFallback(); if ((Object)(object)sceneMaterialFallback != (Object)null) { Renderer component = Root.GetComponent(); if ((Object)(object)component != (Object)null) { component.sharedMaterial = sceneMaterialFallback; } } Root.transform.localScale = new Vector3(1.8f, 1.1f, 3.6f); Collider val = default(Collider); if (Root.TryGetComponent(ref val)) { Object.Destroy((Object)(object)val); } } ((Object)Root).name = "EasyDeliveryCoLanCoop.RemoteCar." + key; ((Object)Root).hideFlags = (HideFlags)52; EnsureNetworkPhysics(key, Root); GameObject val2 = new GameObject("EasyDeliveryCoLanCoop.RemoteCarLabel." + key); ((Object)val2).hideFlags = (HideFlags)52; val2.transform.SetParent(Root.transform, false); val2.transform.localPosition = new Vector3(0f, 1.3f, 0f); val2.transform.localRotation = Quaternion.identity; Label = val2.AddComponent(); Label.anchor = (TextAnchor)7; Label.alignment = (TextAlignment)1; Label.characterSize = 0.09f; Label.fontSize = 64; Label.richText = false; } } private static readonly Dictionary NextMaterialFixAt = new Dictionary(StringComparer.Ordinal); private static readonly HashSet ExactCollidersApplied = new HashSet(StringComparer.Ordinal); private static readonly Dictionary Cars = new Dictionary(StringComparer.Ordinal); internal static void Remove(string key) { if (string.IsNullOrWhiteSpace(key) || !Cars.TryGetValue(key, out RemoteCar value)) { return; } try { foreach (KeyValuePair item in value.Cargo) { if ((Object)(object)item.Value != (Object)null) { Object.Destroy((Object)(object)item.Value); } } } catch { } if ((Object)(object)value.Root != (Object)null) { Object.Destroy((Object)(object)value.Root); } Cars.Remove(key); NextMaterialFixAt.Remove(key); ExactCollidersApplied.Remove(key); } internal static void ApplyCarState(string key, Vector3 pos, Quaternion rot, string? nickname = null) { //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(key)) { return; } if (!Cars.TryGetValue(key, out RemoteCar value) || (Object)(object)value.Root == (Object)null) { value = new RemoteCar(key); Cars[key] = value; } if (value.IsFallbackPrimitive && (Object)(object)RemoteVisualPrefabs.GetCarPrefab() != (Object)null) { try { RemoteCar remoteCar = value; RemoteCar remoteCar2 = new RemoteCar(key); foreach (KeyValuePair item in remoteCar.Cargo) { if (!((Object)(object)item.Value == (Object)null)) { item.Value.transform.SetParent(remoteCar2.Root.transform, false); remoteCar2.Cargo[item.Key] = item.Value; } } Cars[key] = remoteCar2; if ((Object)(object)remoteCar.Root != (Object)null) { Object.Destroy((Object)(object)remoteCar.Root); } value = remoteCar2; } catch { } } value.Root.transform.SetPositionAndRotation(pos, rot); if (!value.IsFallbackPrimitive && GameAccess.TryFindLocalCarVisualRoot(out Transform visualRoot)) { value.Root.transform.localScale = visualRoot.lossyScale; } Renderer component = value.Root.GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)component.sharedMaterial != (Object)null) { Shader shader = component.sharedMaterial.shader; if ((Object)(object)shader == (Object)null || !shader.isSupported) { Material sceneMaterialFallback = MaterialUtil.GetSceneMaterialFallback(); if ((Object)(object)sceneMaterialFallback != (Object)null) { component.sharedMaterial = sceneMaterialFallback; } } } FixUnsupportedMaterials(key, value.Root); EnsureNetworkPhysics(key, value.Root); if ((Object)(object)value.Label != (Object)null) { string text = Plugin.SanitizeNickname(nickname); value.Label.text = text; Camera main = Camera.main; if ((Object)(object)main != (Object)null) { ((Component)value.Label).transform.rotation = Quaternion.LookRotation(((Component)value.Label).transform.position - ((Component)main).transform.position); } } } internal static void ApplyCargo(string carKey, IReadOnlyList<(string Name, Vector3 LocalPos, Quaternion LocalRot)> cargo) { //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(carKey)) { return; } if (!Cars.TryGetValue(carKey, out RemoteCar value) || (Object)(object)value.Root == (Object)null) { value = new RemoteCar(carKey); Cars[carKey] = value; } HashSet hashSet = new HashSet(StringComparer.Ordinal); for (int i = 0; i < cargo.Count; i++) { string text = cargo[i].Name; if (string.IsNullOrWhiteSpace(text)) { text = $"Cargo#{i}"; } string text2 = NormalizePayloadName(text); string text3 = $"{i}:{text2}"; hashSet.Add(text3); if (!value.Cargo.TryGetValue(text3, out GameObject value2) || (Object)(object)value2 == (Object)null) { value2 = PayloadPrefabLibrary.CreatePayloadInstanceOrFallback(text2, "EasyDeliveryCoLanCoop.RemoteCargo." + carKey + "." + text3); value2.transform.SetParent(value.Root.transform, false); value.Cargo[text3] = value2; } value2.transform.localPosition = cargo[i].LocalPos; value2.transform.localRotation = cargo[i].LocalRot; } List list = new List(); foreach (KeyValuePair item in value.Cargo) { if (!hashSet.Contains(item.Key)) { if ((Object)(object)item.Value != (Object)null) { Object.Destroy((Object)(object)item.Value); } list.Add(item.Key); } } foreach (string item2 in list) { value.Cargo.Remove(item2); } } internal static void PlayCarSfx(string key, byte sfxId, string? clipName = null, string? sourceName = null) { if (!string.IsNullOrWhiteSpace(key) && Cars.TryGetValue(key, out RemoteCar value) && !((Object)(object)value.Root == (Object)null) && !GameAccess.TryPlayLikelyCarSfx(value.Root.transform, sfxId, clipName, sourceName)) { GameAccess.TryPlayCarSfxAudibleFallback(value.Root.transform, sfxId, clipName, sourceName); } } private static string NormalizePayloadName(string? name) { if (name == null) { name = string.Empty; } name = name.Trim(); if (name.EndsWith("(Clone)", StringComparison.Ordinal)) { name = name.Substring(0, name.Length - "(Clone)".Length).Trim(); } return name; } private static void EnsureNetworkPhysics(string carKey, GameObject root) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_009e: 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_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)root == (Object)null) { return; } try { if (!ExactCollidersApplied.Contains(carKey) && GameAccess.TryFindLocalCarVisualRoot(out Transform visualRoot)) { TryMirrorCollidersFromLocal(root, visualRoot); ExactCollidersApplied.Add(carKey); } Collider[] componentsInChildren = root.GetComponentsInChildren(true); if (componentsInChildren == null || componentsInChildren.Length == 0) { if (TryGetCombinedRendererBounds(root, out var bounds)) { BoxCollider val = root.GetComponent(); if ((Object)(object)val == (Object)null) { val = root.AddComponent(); } Vector3 center = root.transform.InverseTransformPoint(((Bounds)(ref bounds)).center); float num = Mathf.Abs(root.transform.lossyScale.x); float num2 = Mathf.Abs(root.transform.lossyScale.y); float num3 = Mathf.Abs(root.transform.lossyScale.z); if (num < 0.0001f) { num = 1f; } if (num2 < 0.0001f) { num2 = 1f; } if (num3 < 0.0001f) { num3 = 1f; } val.center = center; val.size = new Vector3(Mathf.Max(0.1f, ((Bounds)(ref bounds)).size.x / num), Mathf.Max(0.1f, ((Bounds)(ref bounds)).size.y / num2), Mathf.Max(0.1f, ((Bounds)(ref bounds)).size.z / num3)); ((Collider)val).isTrigger = false; } } else { foreach (Collider val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null)) { val2.enabled = true; val2.isTrigger = false; } } } Rigidbody val3 = root.GetComponent(); if ((Object)(object)val3 == (Object)null) { val3 = root.AddComponent(); } val3.isKinematic = true; val3.useGravity = false; val3.interpolation = (RigidbodyInterpolation)1; val3.collisionDetectionMode = (CollisionDetectionMode)0; val3.constraints = (RigidbodyConstraints)0; val3.detectCollisions = true; } catch { } } private static void TryMirrorCollidersFromLocal(GameObject remoteRoot, Transform localRoot) { if ((Object)(object)remoteRoot == (Object)null || (Object)(object)localRoot == (Object)null) { return; } Transform[] componentsInChildren = ((Component)localRoot).GetComponentsInChildren(true); foreach (Transform val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).GetComponent("WheelCollider") != (Object)null)) { string relativePath = GetRelativePath(localRoot, val); Transform val2 = EnsurePath(remoteRoot.transform, relativePath, val); if (!((Object)(object)val2 == (Object)null)) { CopyCollider(val, val2); } } } } private static Transform? EnsurePath(Transform remoteRoot, string relPath, Transform srcRef) { //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)remoteRoot == (Object)null) { return null; } if (string.IsNullOrEmpty(relPath)) { return remoteRoot; } string[] array = relPath.Split('/'); Transform val = remoteRoot; Transform val2 = srcRef; List list = new List(); while ((Object)(object)val2 != (Object)null) { list.Add(val2); if ((Object)(object)val2.parent == (Object)null) { break; } val2 = val2.parent; } list.Reverse(); int num = 1; foreach (string text in array) { if (string.IsNullOrEmpty(text)) { continue; } Transform val3 = val.Find(text); if ((Object)(object)val3 == (Object)null) { val3 = new GameObject(text) { hideFlags = (HideFlags)52 }.transform; val3.SetParent(val, false); if (num < list.Count) { Transform val4 = list[num]; val3.localPosition = val4.localPosition; val3.localRotation = val4.localRotation; val3.localScale = val4.localScale; } } val = val3; num++; } return val; } private static void CopyCollider(Transform src, Transform dst) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)src == (Object)null) && !((Object)(object)dst == (Object)null)) { BoxCollider component = ((Component)src).GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)((Component)dst).GetComponent() == (Object)null) { BoxCollider obj = ((Component)dst).gameObject.AddComponent(); obj.center = component.center; obj.size = component.size; ((Collider)obj).isTrigger = false; ((Collider)obj).sharedMaterial = ((Collider)component).sharedMaterial; ((Collider)obj).enabled = ((Collider)component).enabled; } SphereCollider component2 = ((Component)src).GetComponent(); if ((Object)(object)component2 != (Object)null && (Object)(object)((Component)dst).GetComponent() == (Object)null) { SphereCollider obj2 = ((Component)dst).gameObject.AddComponent(); obj2.center = component2.center; obj2.radius = component2.radius; ((Collider)obj2).isTrigger = false; ((Collider)obj2).sharedMaterial = ((Collider)component2).sharedMaterial; ((Collider)obj2).enabled = ((Collider)component2).enabled; } CapsuleCollider component3 = ((Component)src).GetComponent(); if ((Object)(object)component3 != (Object)null && (Object)(object)((Component)dst).GetComponent() == (Object)null) { CapsuleCollider obj3 = ((Component)dst).gameObject.AddComponent(); obj3.center = component3.center; obj3.radius = component3.radius; obj3.height = component3.height; obj3.direction = component3.direction; ((Collider)obj3).isTrigger = false; ((Collider)obj3).sharedMaterial = ((Collider)component3).sharedMaterial; ((Collider)obj3).enabled = ((Collider)component3).enabled; } MeshCollider component4 = ((Component)src).GetComponent(); if ((Object)(object)component4 != (Object)null && (Object)(object)((Component)dst).GetComponent() == (Object)null) { MeshCollider obj4 = ((Component)dst).gameObject.AddComponent(); obj4.sharedMesh = component4.sharedMesh; obj4.convex = component4.convex; obj4.cookingOptions = component4.cookingOptions; ((Collider)obj4).isTrigger = false; ((Collider)obj4).sharedMaterial = ((Collider)component4).sharedMaterial; ((Collider)obj4).enabled = ((Collider)component4).enabled; } } } private static string GetRelativePath(Transform root, Transform node) { if ((Object)(object)root == (Object)null || (Object)(object)node == (Object)null || (Object)(object)root == (Object)(object)node) { return string.Empty; } List list = new List(); Transform val = node; while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); return string.Join("/", list); } private static bool TryGetCombinedRendererBounds(GameObject root, out Bounds bounds) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) bounds = default(Bounds); if ((Object)(object)root == (Object)null) { return false; } try { Renderer[] componentsInChildren = root.GetComponentsInChildren(true); if (componentsInChildren == null || componentsInChildren.Length == 0) { return false; } bool flag = false; Bounds val = default(Bounds); foreach (Renderer val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null)) { if (!flag) { val = val2.bounds; flag = true; } else { ((Bounds)(ref val)).Encapsulate(val2.bounds); } } } if (!flag) { return false; } bounds = val; return true; } catch { return false; } } private static void FixUnsupportedMaterials(string key, GameObject go) { if ((Object)(object)go == (Object)null) { return; } float unscaledTime = Time.unscaledTime; if (NextMaterialFixAt.TryGetValue(key, out var value) && unscaledTime < value) { return; } NextMaterialFixAt[key] = unscaledTime + 2f; Material sceneMaterialFallback = MaterialUtil.GetSceneMaterialFallback(); if ((Object)(object)sceneMaterialFallback == (Object)null) { return; } try { Renderer[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } Material[] sharedMaterials = val.sharedMaterials; if (sharedMaterials == null || sharedMaterials.Length == 0) { continue; } bool flag = false; for (int j = 0; j < sharedMaterials.Length; j++) { Material val2 = sharedMaterials[j]; if (!((Object)(object)val2 == (Object)null)) { Shader shader = val2.shader; if ((Object)(object)shader == (Object)null || !shader.isSupported || string.Equals(((Object)shader).name, "Hidden/InternalErrorShader", StringComparison.Ordinal)) { sharedMaterials[j] = sceneMaterialFallback; flag = true; } } } if (flag) { val.sharedMaterials = sharedMaterials; } } } catch { } } } internal static class RemoteVisualPrefabs { private static GameObject? _playerPrefab; private static GameObject? _carPrefab; private static bool _loggedPlayerPrefabInfo; private static bool _loggedCarPrefabInfo; private static bool _loggedExternalPlayer; private static bool _loggedExternalCar; internal static GameObject? GetPlayerPrefab() { if ((Object)(object)_playerPrefab != (Object)null) { return _playerPrefab; } if (Plugin.ExternalAssetsEnabled.Value) { GameObject val = ExternalAssetBundle.TryLoadGameObject(Plugin.ExternalPlayerPrefabAssetName.Value); if ((Object)(object)val != (Object)null) { _playerPrefab = RenderOnlyClone.TryCreateRenderOnlyPrefab(val.transform) ?? MeshOnlyClone.TryCreateMeshRendererHierarchyPrefab(val.transform); if ((Object)(object)_playerPrefab != (Object)null && !_loggedExternalPlayer) { _loggedExternalPlayer = true; Plugin.Log.LogInfo((object)("Using external Player prefab from AssetBundle: '" + Plugin.ExternalPlayerPrefabAssetName.Value + "'.")); } if ((Object)(object)_playerPrefab != (Object)null) { return _playerPrefab; } } } if (!GameAccess.TryFindLocalPlayerVisualRoot(out Transform visualRoot)) { return null; } _playerPrefab = RenderOnlyClone.TryCreateRenderOnlyPrefab(visualRoot) ?? (ExternalMods.IsCustomTruckShopLoaded() ? MeshOnlyClone.TryCreateMeshRendererHierarchyPrefab(visualRoot) : null); if (!_loggedPlayerPrefabInfo && (Object)(object)_playerPrefab != (Object)null) { _loggedPlayerPrefabInfo = true; LogPrefabInfo("Player", visualRoot, _playerPrefab); } return _playerPrefab; } internal static GameObject? GetCarPrefab() { if ((Object)(object)_carPrefab != (Object)null) { return _carPrefab; } if (Plugin.ExternalAssetsEnabled.Value) { GameObject val = ExternalAssetBundle.TryLoadGameObject(Plugin.ExternalCarPrefabAssetName.Value); if ((Object)(object)val != (Object)null) { _carPrefab = RenderOnlyClone.TryCreateRenderOnlyPrefab(val.transform) ?? MeshOnlyClone.TryCreateMeshRendererHierarchyPrefab(val.transform); if ((Object)(object)_carPrefab != (Object)null && !_loggedExternalCar) { _loggedExternalCar = true; Plugin.Log.LogInfo((object)("Using external Car prefab from AssetBundle: '" + Plugin.ExternalCarPrefabAssetName.Value + "'.")); } if ((Object)(object)_carPrefab != (Object)null) { return _carPrefab; } } } if (!GameAccess.TryFindLocalCarVisualRoot(out Transform visualRoot)) { return null; } _carPrefab = CarVisualClone.TryCreateCarPrefab(visualRoot) ?? (ExternalMods.IsCustomTruckShopLoaded() ? MeshOnlyClone.TryCreateMeshRendererHierarchyPrefab(visualRoot) : null) ?? RenderOnlyClone.TryCreateRenderOnlyPrefab(visualRoot); if (!_loggedCarPrefabInfo && (Object)(object)_carPrefab != (Object)null) { _loggedCarPrefabInfo = true; LogPrefabInfo("Car", visualRoot, _carPrefab); } return _carPrefab; } private static void LogPrefabInfo(string kind, Transform sourceRoot, GameObject prefab) { try { Renderer[] componentsInChildren = prefab.GetComponentsInChildren(true); HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (Renderer val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } Material[] sharedMaterials = val.sharedMaterials; if (sharedMaterials == null) { continue; } foreach (Material val2 in sharedMaterials) { if (!((Object)(object)val2 == (Object)null)) { Shader shader = val2.shader; hashSet.Add(((Object)(object)shader != (Object)null) ? ((Object)shader).name : ""); } } } Plugin.Log.LogInfo((object)string.Format("Prefab[{0}] sourceRoot='{1}', prefab='{2}', renderers={3}, shaders=[{4}]", kind, ((Object)sourceRoot).name, ((Object)prefab).name, componentsInChildren.Length, string.Join(", ", hashSet))); } catch { } } } internal static class RenderOnlyClone { internal static GameObject? TryCreateRenderOnlyPrefab(Transform sourceRoot) { if ((Object)(object)sourceRoot == (Object)null) { return null; } try { GameObject val = Object.Instantiate(((Component)sourceRoot).gameObject); ((Object)val).name = "EasyDeliveryCoLanCoop.RenderOnlyPrefab." + ((Object)sourceRoot).name; ((Object)val).hideFlags = (HideFlags)52; Transform[] componentsInChildren = val.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { ((Object)((Component)componentsInChildren[i]).gameObject).hideFlags = (HideFlags)52; } Component[] componentsInChildren2 = val.GetComponentsInChildren(true); foreach (Component val2 in componentsInChildren2) { if (!((Object)(object)val2 == (Object)null) && !(val2 is Transform) && !(val2 is Renderer) && !(val2 is MeshFilter)) { Object.Destroy((Object)(object)val2); } } val.SetActive(false); return val; } catch { return null; } } } public sealed class LanCoopManager : MonoBehaviour { private sealed class InitialSyncState { public IPEndPoint EndPoint; public float NextSendAt; public float UntilAt; } private sealed class PendingSaveFull { public string SaveId = string.Empty; public bool Wipe; public Dictionary Data = new Dictionary(StringComparer.Ordinal); public float NextTryAt; public float UntilAt; public bool AppliedToPlayerPrefs; } private float _nextTick; private float _tickInterval; private float _nextWorldSnapshotAt; private float _forceFullSnapshotUntil; private const float MaxWorldSnapshotsPerSecond = 10f; private float _nextDebugAt; private bool _applyingRemote; private UdpTransport? _transport; private LanDiscovery? _discovery; private readonly Dictionary _hostKnownPlayers = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _hostKnownCars = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _hostKnownNick = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _hostKnownClientVersion = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _hostClientLastSeenAt = new Dictionary(StringComparer.Ordinal); private float _nextHostPruneAt; private const float HostPruneIntervalSeconds = 1f; private readonly Dictionary _hostInitialSync = new Dictionary(StringComparer.Ordinal); private const float InitialSyncDurationSeconds = 3f; private const float InitialSyncIntervalSeconds = 0.5f; private string? _selfKey; private float _clientLastHostPacketAt; private IPEndPoint? _clientLastKnownServer; private int _clientReconnectAttempt; private float _clientNextReconnectTryAt; private bool _clientManualDisconnect; private float _nextHelloAt; private const float HelloIntervalSeconds = 1f; private bool _clientTeleportedFromHost; private float _nextClientCargoAt; private const float ClientCargoIntervalSeconds = 0.5f; private readonly Dictionary _nextCarSfxAt = new Dictionary(StringComparer.Ordinal); private int? _lastObservedMoney; private float _nextMoneyPollAt; private float _suppressMoneyEchoUntil; private const float MoneyPollIntervalSeconds = 0.2f; private bool _forceClientMode; private bool _forceHostMode; private bool _clientDiscoveryLocked; private bool _showConsole = true; private string _consoleInput = string.Empty; private Vector2 _consoleScroll; private readonly List _consoleLines = new List(); private const int MaxConsoleLines = 20; private bool _clientAutoEnterRequested; private bool _clientAutoEnterDone; private float _clientAutoEnterNextTryAt; private float _clientAutoEnterUntil; private string _clientLastSaveIdFromHost = string.Empty; private float _nextAutoEnterLogAt; private string _lastAutoEnterReason = string.Empty; private bool _autoEnterDisabledLogged; private PendingSaveFull? _pendingSaveFull; private int? _pendingMoneySet; private float _pendingMoneyNextTryAt; private float _pendingMoneyUntil; private (Vector3 Pos, Quaternion Rot)? _clientPendingTeleport; private float _clientPendingTeleportUntil; internal bool ClientDiscoveryLocked => _clientDiscoveryLocked; internal string? SelfKey => _selfKey; internal bool HasWelcome => !string.IsNullOrEmpty(_selfKey); public static LanCoopManager Instance { get; private set; } public bool IsHost { get { if (!_forceHostMode) { return string.Equals(Plugin.GetEffectiveNetworkMode(), "Host", StringComparison.OrdinalIgnoreCase); } return true; } } public bool IsClient { get { if (!_forceClientMode) { return string.Equals(Plugin.GetEffectiveNetworkMode(), "Client", StringComparison.OrdinalIgnoreCase); } return true; } } internal bool IsApplyingRemote => _applyingRemote; private void Awake() { if ((Object)(object)Instance != (Object)null) { Object.Destroy((Object)(object)this); } else { Instance = this; } } private void Start() { _tickInterval = 1f / (float)Mathf.Max(1, Plugin.TickRate.Value); StartNetworking(); } private void Update() { _transport?.Poll(); if (_transport != null) { _discovery?.Poll(this, _transport); } if (IsClient) { ClientAutoReconnectTick(); } if (_transport != null && !(Time.unscaledTime < _nextTick)) { _nextTick = Time.unscaledTime + _tickInterval; if (IsClient) { EnsureClientConnected(); } if (IsClient) { TryApplyPendingClientTeleport(); } if (IsClient) { TryApplyPendingSaveFull(); TryApplyPendingMoneySet(); TryAutoEnterWorld(); } if (IsClient) { SendClientMoneyDeltaIfChanged(); } if (IsClient) { SendLocalClientState(); } if (IsClient || IsHost) { SendLocalCarSfxIfNeeded(); } if (IsHost) { PlayerPositionsStore.HostTick(Time.unscaledTime); PruneDisconnectedClients(); BroadcastHostMoneyIfChanged(); SendPendingInitialSync(); BroadcastWorldSnapshot(); } } } private void OnGUI() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) try { int num = (_showConsole ? 420 : 220); int num2 = (_showConsole ? 260 : 78); int num3 = 10; int num4 = 10; GUI.Box(new Rect((float)num3, (float)num4, (float)num, (float)num2), "LAN Coop"); _showConsole = GUI.Toggle(new Rect((float)(num3 + num - 100), (float)(num4 + 2), 96f, 20f), _showConsole, "Console"); int num5 = num4 + 22; Rect val = default(Rect); ((Rect)(ref val))..ctor((float)(num3 + 10), (float)num5, 200f, 22f); if (_transport == null) { if (GUI.Button(val, "Connect to Host")) { _forceClientMode = true; _forceHostMode = false; _clientDiscoveryLocked = false; _clientAutoEnterRequested = true; _clientAutoEnterDone = false; _clientAutoEnterNextTryAt = 0f; _clientAutoEnterUntil = Time.unscaledTime + 30f; StartNetworking(); } string effectiveNetworkMode = Plugin.GetEffectiveNetworkMode(); GUI.Label(new Rect((float)(num3 + 10), (float)(num5 + 26), (float)(num - 20), 20f), "Mode=" + effectiveNetworkMode); if (_showConsole) { DrawConsole(num3, num4, num, num2); } } else if (IsClient) { string text = (HasWelcome ? "Connected" : "Connecting"); if (GUI.Button(val, "Disconnect (" + text + ")")) { Disconnect(); return; } string text2 = ((_transport.ServerEndPoint != null) ? _transport.ServerEndPoint.ToString() : "(discovery)"); GUI.Label(new Rect((float)(num3 + 10), (float)(num5 + 26), (float)(num - 20), 20f), "Server: " + text2); if (_showConsole) { DrawConsole(num3, num4, num, num2); } } else if (GUI.Button(val, "Stop Networking")) { Disconnect(); } else { GUI.Label(new Rect((float)(num3 + 10), (float)(num5 + 26), 200f, 20f), IsHost ? "Host" : "(custom)"); if (_showConsole) { DrawConsole(num3, num4, num, num2); } } } catch (Exception ex) { try { if (Plugin.Log != null) { Plugin.Log.LogError((object)ex); } else { Debug.LogException(ex); } } catch { } } } private void DrawConsole(float x, float y, float w, float h) { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Invalid comparison between Unknown and I4 //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Invalid comparison between Unknown and I4 //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Invalid comparison between Unknown and I4 float num = y + 48f; float num2 = h - 58f; if (!(num2 < 40f)) { Rect val = default(Rect); ((Rect)(ref val))..ctor(x + 10f, num, w - 20f, num2 - 26f); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(x + 10f, num + num2 - 24f, w - 20f, 22f); float num3 = 64f; Rect val3 = default(Rect); ((Rect)(ref val3))..ctor(((Rect)(ref val2)).x, ((Rect)(ref val2)).y, Mathf.Max(10f, ((Rect)(ref val2)).width - num3 - 6f), ((Rect)(ref val2)).height); Rect val4 = default(Rect); ((Rect)(ref val4))..ctor(((Rect)(ref val3)).xMax + 6f, ((Rect)(ref val2)).y, num3, ((Rect)(ref val2)).height); GUI.Box(new Rect(x + 8f, num - 2f, w - 16f, num2 + 2f), string.Empty); float num4 = (float)Mathf.Max(1, _consoleLines.Count) * 18f; _consoleScroll = GUI.BeginScrollView(val, _consoleScroll, new Rect(0f, 0f, ((Rect)(ref val)).width - 20f, num4)); for (int i = 0; i < _consoleLines.Count; i++) { GUI.Label(new Rect(0f, (float)i * 18f, ((Rect)(ref val)).width - 24f, 18f), _consoleLines[i]); } GUI.EndScrollView(); GUI.SetNextControlName("EDC_LAN_CONSOLE"); _consoleInput = GUI.TextField(val3, _consoleInput ?? string.Empty); if (GUI.Button(val4, "Run")) { string consoleInput = _consoleInput; _consoleInput = string.Empty; ExecuteConsoleCommand(consoleInput); GUI.FocusControl("EDC_LAN_CONSOLE"); } Event current = Event.current; if (current != null && (int)current.type == 4 && ((int)current.keyCode == 13 || (int)current.keyCode == 271) && GUI.GetNameOfFocusedControl() == "EDC_LAN_CONSOLE") { string consoleInput2 = _consoleInput; _consoleInput = string.Empty; ExecuteConsoleCommand(consoleInput2); current.Use(); } } } private void ConsoleLog(string msg) { if (msg == null) { msg = string.Empty; } _consoleLines.Add(msg); while (_consoleLines.Count > 20) { _consoleLines.RemoveAt(0); } _consoleScroll.y = 999999f; } private void ExecuteConsoleCommand(string? input) { if (input == null) { input = string.Empty; } input = input.Trim(); if (input.Length == 0) { return; } ConsoleLog("> " + input); string[] array = input.Split(new char[1] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); string text = array[0].Trim().ToLowerInvariant(); string text2 = ((array.Length > 1) ? array[1].Trim() : string.Empty); switch (text) { case "help": case "?": ConsoleLog("Commands: connect , disconnect, reconnect, autoreconnect , status, help"); break; case "status": { string text3 = (IsHost ? "Host" : (IsClient ? "Client" : "Off")); string text4 = ((_transport?.ServerEndPoint != null) ? _transport.ServerEndPoint.ToString() : "(none)"); bool flag = Plugin.AutoReconnectEnabled != null && Plugin.AutoReconnectEnabled.Value; ConsoleLog($"Mode={text3} Welcome={HasWelcome} Server={text4} AutoReconnect={flag}"); break; } case "disconnect": case "stop": Disconnect(); ConsoleLog("Disconnected."); break; case "reconnect": if (!IsClient && !_forceClientMode && !string.Equals(Plugin.GetEffectiveNetworkMode(), "Client", StringComparison.OrdinalIgnoreCase)) { ConsoleLog("Reconnect is for Client mode."); break; } ForceClientReconnect(); ConsoleLog("Reconnect requested."); break; case "autoreconnect": case "ar": { string text5 = text2.Trim().ToLowerInvariant(); if (text5.Length != 0) { switch (text5) { case "status": break; case "on": case "1": case "true": Plugin.AutoReconnectEnabled.Value = true; ConsoleLog("AutoReconnectEnabled=true"); ForceClientReconnect(); ConsoleLog("Reconnect requested."); return; case "off": case "0": case "false": Plugin.AutoReconnectEnabled.Value = false; ConsoleLog("AutoReconnectEnabled=false"); return; default: ConsoleLog("Usage: autoreconnect "); return; } } ConsoleLog($"AutoReconnectEnabled={Plugin.AutoReconnectEnabled.Value}"); break; } case "connect": { if (!TryParseEndpoint(text2, Plugin.Port.Value, out string ip2, out int port2, out string error2)) { ConsoleLog(error2); } else { StartManualConnect(ip2, port2); } break; } default: { if (TryParseEndpoint(input, Plugin.Port.Value, out string ip, out int port, out string _)) { StartManualConnect(ip, port); } else { ConsoleLog("Unknown command. Type 'help'."); } break; } } } private void ForceClientReconnect() { _clientManualDisconnect = false; if (_transport == null) { _forceClientMode = true; _forceHostMode = false; StartNetworking(); } _selfKey = null; _clientTeleportedFromHost = false; _clientPendingTeleport = null; _pendingSaveFull = null; _pendingMoneySet = null; _clientReconnectAttempt = 0; _clientNextReconnectTryAt = 0f; _nextHelloAt = 0f; _clientLastHostPacketAt = Time.unscaledTime; if (_transport == null) { return; } if (!_transport.HasServer) { if (_clientLastKnownServer != null) { try { _transport.SetServer(_clientLastKnownServer); } catch { } } else { try { if (IPAddress.TryParse(Plugin.HostAddress.Value, out IPAddress address)) { _transport.SetServer(new IPEndPoint(address, Plugin.Port.Value)); } } catch { } } } if (_transport.HasServer) { _transport.SendToServer(NetMessages.BuildHello()); } } private void StartManualConnect(string ip, int port) { _clientDiscoveryLocked = true; Plugin.HostAddress.Value = ip; Plugin.Port.Value = port; _forceClientMode = true; _forceHostMode = false; _clientAutoEnterRequested = true; _clientAutoEnterDone = false; _clientAutoEnterNextTryAt = 0f; _clientAutoEnterUntil = Time.unscaledTime + 30f; StartNetworking(); ConsoleLog($"Connecting to {ip}:{port} ..."); } private static bool TryParseEndpoint(string s, int defaultPort, out string ip, out int port, out string error) { ip = string.Empty; port = defaultPort; error = string.Empty; if (s == null) { s = string.Empty; } s = s.Trim(); if (s.Length == 0) { error = "Usage: connect "; return false; } string text = s; int result = defaultPort; int num = s.LastIndexOf(':'); if (num > 0 && num < s.Length - 1) { text = s.Substring(0, num); if (!int.TryParse(s.Substring(num + 1), out result) || result <= 0 || result > 65535) { error = "Invalid port"; return false; } } if (!IPAddress.TryParse(text, out IPAddress _)) { error = "Host must be an IP address (e.g. 192.168.0.10:27777)"; return false; } ip = text; port = result; return true; } private void SendClientMoneyDeltaIfChanged() { if (!Plugin.SharedMoneyEnabled.Value || _transport == null || !_transport.HasServer || !HasWelcome) { return; } float unscaledTime = Time.unscaledTime; if (unscaledTime < _nextMoneyPollAt) { return; } _nextMoneyPollAt = unscaledTime + 0.2f; if (unscaledTime < _suppressMoneyEchoUntil || IsApplyingRemote || !GameAccess.TryReadHudMoney(out var money)) { return; } if (!_lastObservedMoney.HasValue) { _lastObservedMoney = money; return; } int value = _lastObservedMoney.Value; if (money != value) { int num = money - value; _lastObservedMoney = money; if (num != 0) { _transport.SendToServer(NetMessages.BuildMoneyDelta(num)); } } } private void BroadcastHostMoneyIfChanged() { if (!Plugin.SharedMoneyEnabled.Value || _transport == null) { return; } float unscaledTime = Time.unscaledTime; if (!(unscaledTime < _nextMoneyPollAt)) { _nextMoneyPollAt = unscaledTime + 0.2f; if (GameAccess.TryReadHudMoney(out var money) && (!_lastObservedMoney.HasValue || _lastObservedMoney.Value != money)) { _lastObservedMoney = money; _transport.SendToAll(NetMessages.BuildMoneySet(money)); } } } private void TryApplyPendingClientTeleport() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (!_clientPendingTeleport.HasValue) { return; } bool inCar; if (Time.unscaledTime > _clientPendingTeleportUntil) { _clientPendingTeleport = null; } else if (!(GameAccess.TryReadInCar(out inCar) && inCar)) { (Vector3, Quaternion) value = _clientPendingTeleport.Value; if (GameAccess.TryApplyLocalPlayerControllerPose(value.Item1, value.Item2)) { _clientPendingTeleport = null; _clientTeleportedFromHost = true; Plugin.Log.LogInfo((object)"Applied saved position from host."); } } } private void SendPendingInitialSync() { if (!IsHost || _transport == null || (!Plugin.ClientReceivesHostSaveOnJoin.Value && !Plugin.PlayerPositionsEnabled.Value)) { return; } float unscaledTime = Time.unscaledTime; if (_hostInitialSync.Count == 0) { return; } List list = new List(_hostInitialSync.Keys); for (int i = 0; i < list.Count; i++) { string key = list[i]; if (!_hostInitialSync.TryGetValue(key, out InitialSyncState value)) { continue; } if (unscaledTime > value.UntilAt) { _hostInitialSync.Remove(key); } else { if (unscaledTime < value.NextSendAt) { continue; } value.NextSendAt = unscaledTime + 0.5f; PlayerPositionsStore.HostRefreshSaveIdNow(); string currentSaveId = PlayerPositionsStore.CurrentSaveId; if (Plugin.SharedMoneyEnabled.Value && GameAccess.TryReadHudMoney(out var money)) { _transport.SendTo(value.EndPoint, NetMessages.BuildMoneySet(money)); } if (Plugin.ClientReceivesHostSaveOnJoin.Value) { Dictionary dictionary = GameAccess.TryReadSaveSystemSnapshot(); if (dictionary != null) { Dictionary dictionary2 = new Dictionary(dictionary.Count, StringComparer.Ordinal); foreach (KeyValuePair item in dictionary) { if (Plugin.IsSaveKeyAllowedForWorldSync(item.Key)) { dictionary2[item.Key] = item.Value; } } byte[] payload = NetMessages.BuildSaveFull(currentSaveId, Plugin.ClientWipeLocalSaveOnJoin.Value, dictionary2); _transport.SendTo(value.EndPoint, payload); } } if (Plugin.PlayerPositionsEnabled.Value && PlayerPositionsStore.TryGetSnapshot(out Dictionary snapshot)) { byte[] payload2 = NetMessages.BuildPlayerLocations(currentSaveId, snapshot); _transport.SendTo(value.EndPoint, payload2); } } } } internal void NotifyClientRegistered() { _forceFullSnapshotUntil = Time.unscaledTime + 2f; } internal void TraceSnapshotReceive(int players, bool hasCar, int cargo) { if (Plugin.DebugLogs.Value && !(Time.unscaledTime < _nextDebugAt)) { _nextDebugAt = Time.unscaledTime + Mathf.Max(0.5f, Plugin.DebugLogIntervalSeconds.Value); Plugin.Log.LogInfo((object)$"Snapshot recv: players={players} hasCar={hasCar} cargo={cargo}"); } } private void TraceSnapshotSend(int players, bool hasCar, int cargo) { if (Plugin.DebugLogs.Value && !(Time.unscaledTime < _nextDebugAt)) { _nextDebugAt = Time.unscaledTime + Mathf.Max(0.5f, Plugin.DebugLogIntervalSeconds.Value); Plugin.Log.LogInfo((object)$"Snapshot send: players={players} hasCar={hasCar} cargo={cargo} clients={_hostKnownPlayers.Count}"); } } private void OnDestroy() { try { _transport?.Dispose(); } catch { } try { _discovery?.Dispose(); } catch { } if (Instance == this) { Instance = null; } } private void StartNetworking() { string a = Plugin.GetEffectiveNetworkMode().Trim(); if (_forceClientMode || _forceHostMode || !string.Equals(a, "Off", StringComparison.OrdinalIgnoreCase)) { StopNetworkingInternal(); _clientManualDisconnect = false; _clientLastHostPacketAt = Time.unscaledTime; _clientReconnectAttempt = 0; _clientNextReconnectTryAt = 0f; _transport = new UdpTransport(OnDatagram); if (IsHost) { _transport.StartHost(Plugin.Port.Value); } else if (IsClient) { _transport.StartClient(Plugin.HostAddress.Value, Plugin.Port.Value); } if (Plugin.AutoDiscovery.Value) { _discovery = new LanDiscovery(IsHost, Plugin.DiscoveryPort.Value, Plugin.Port.Value, Plugin.DiscoveryIntervalMs.Value); _discovery.Start(); } } } private void StopNetworkingInternal() { try { _transport?.Dispose(); } catch { } try { _discovery?.Dispose(); } catch { } _transport = null; _discovery = null; _hostKnownPlayers.Clear(); _hostKnownCars.Clear(); _hostKnownNick.Clear(); _hostKnownClientVersion.Clear(); _hostClientLastSeenAt.Clear(); _hostInitialSync.Clear(); _selfKey = null; _pendingSaveFull = null; _pendingMoneySet = null; } internal void Disconnect() { _clientManualDisconnect = true; StopNetworkingInternal(); _forceClientMode = false; _forceHostMode = false; _clientDiscoveryLocked = false; _clientAutoEnterRequested = false; _clientAutoEnterDone = false; _clientAutoEnterUntil = 0f; _clientLastSaveIdFromHost = string.Empty; } private void TryApplyPendingSaveFull() { if (_pendingSaveFull == null) { return; } float unscaledTime = Time.unscaledTime; if (unscaledTime > _pendingSaveFull.UntilAt) { _pendingSaveFull = null; } else { if (unscaledTime < _pendingSaveFull.NextTryAt) { return; } _pendingSaveFull.NextTryAt = unscaledTime + 0.5f; Dictionary local = GameAccess.TryReadSaveSystemSnapshot(); if (local == null) { if (!_pendingSaveFull.AppliedToPlayerPrefs) { int num = 0; try { foreach (KeyValuePair datum in _pendingSaveFull.Data) { if (Plugin.IsSaveKeyAllowedForWorldSync(datum.Key) && GameAccess.TryApplyPlayerPrefString(datum.Key, datum.Value)) { num++; } } GameAccess.TrySavePlayerPrefs(); _pendingSaveFull.AppliedToPlayerPrefs = true; if (Plugin.DebugLogs.Value) { Plugin.Log.LogInfo((object)$"Pre-applied SaveFull to PlayerPrefs (menu): keys={num} saveId='{_pendingSaveFull.SaveId}'"); } } catch (Exception ex) { if (Plugin.DebugLogs.Value) { Plugin.Log.LogWarning((object)("Pre-apply SaveFull to PlayerPrefs failed: " + ex.GetType().Name)); } _pendingSaveFull.AppliedToPlayerPrefs = true; } } TryAutoEnterWorld(); return; } WithRemoteApply(delegate { if (_pendingSaveFull != null) { if (_pendingSaveFull.Wipe) { foreach (KeyValuePair item in local) { if (Plugin.IsSaveKeyAllowedForWorldSync(item.Key)) { GameAccess.TryDeleteSaveKey(item.Key); } } } foreach (KeyValuePair datum2 in _pendingSaveFull.Data) { if (Plugin.IsSaveKeyAllowedForWorldSync(datum2.Key)) { GameAccess.TryApplySaveKey(datum2.Key, datum2.Value); } } if (Plugin.DebugLogs.Value) { Plugin.Log.LogInfo((object)$"Applied pending SaveFull: keys={_pendingSaveFull.Data.Count} saveId='{_pendingSaveFull.SaveId}'"); } _pendingSaveFull = null; } }); TryAutoEnterWorld(); } } private void TryApplyPendingMoneySet() { if (!_pendingMoneySet.HasValue) { return; } float unscaledTime = Time.unscaledTime; if (unscaledTime > _pendingMoneyUntil) { _pendingMoneySet = null; } else if (!(unscaledTime < _pendingMoneyNextTryAt)) { _pendingMoneyNextTryAt = unscaledTime + 0.3f; int value = _pendingMoneySet.Value; if (GameAccess.TryApplyHudMoney(value)) { NotifyRemoteMoneyApplied(value); _pendingMoneySet = null; } } } internal void OnSaveFullReceived(string saveId, bool wipe, Dictionary data) { if (IsClient) { _clientLastSaveIdFromHost = saveId ?? string.Empty; _pendingSaveFull = new PendingSaveFull { SaveId = (saveId ?? string.Empty), Wipe = wipe, Data = (data ?? new Dictionary(StringComparer.Ordinal)), NextTryAt = 0f, UntilAt = Time.unscaledTime + 25f }; TryApplyPendingSaveFull(); TryAutoEnterWorld(); } } internal void OnMoneySetReceived(int money) { if (IsClient) { _pendingMoneySet = money; _pendingMoneyNextTryAt = 0f; _pendingMoneyUntil = Time.unscaledTime + 25f; TryApplyPendingMoneySet(); TryAutoEnterWorld(); } } private void OnDatagram(IPEndPoint remote, byte[] data) { if (_transport == null) { return; } try { if (IsClient) { IPEndPoint serverEndPoint = _transport.ServerEndPoint; if (serverEndPoint != null && remote.Address.Equals(serverEndPoint.Address) && remote.Port == serverEndPoint.Port) { _clientLastHostPacketAt = Time.unscaledTime; _clientLastKnownServer = serverEndPoint; } } } catch { } try { NetMessages.ParseAndApplyIncoming(this, _transport, remote, data); } catch (Exception ex) { Plugin.Log.LogError((object)ex); } } internal void SetSelfKey(string key, string? hostVersion = null) { if (!string.IsNullOrWhiteSpace(key)) { _selfKey = key; _clientLastHostPacketAt = Time.unscaledTime; _clientReconnectAttempt = 0; _clientNextReconnectTryAt = 0f; _clientPendingTeleport = null; _clientTeleportedFromHost = false; if (!string.IsNullOrWhiteSpace(hostVersion) && !string.Equals(hostVersion, "0.2.19", StringComparison.Ordinal)) { Plugin.Log.LogWarning((object)("Version mismatch: host=" + hostVersion + ", client=0.2.19. Please use same mod version on all peers.")); } if (IsClient && Plugin.AutoEnterWorldOnConnect.Value) { _clientAutoEnterRequested = true; _clientAutoEnterDone = false; _clientAutoEnterNextTryAt = 0f; _clientAutoEnterUntil = Time.unscaledTime + 90f; } Plugin.Log.LogInfo((object)("SelfKey set: " + _selfKey)); } } private void ClientAutoReconnectTick() { if (!IsClient || !Plugin.AutoReconnectEnabled.Value || _clientManualDisconnect) { return; } float unscaledTime = Time.unscaledTime; if (_transport == null) { if (!(unscaledTime < _clientNextReconnectTryAt)) { StartNetworking(); } return; } if (_transport.ServerEndPoint != null) { _clientLastKnownServer = _transport.ServerEndPoint; } float num = Plugin.AutoReconnectLostSeconds.Value; if (num < 1f) { num = 1f; } if (HasWelcome) { float num2 = unscaledTime - _clientLastHostPacketAt; if (num2 >= num) { _selfKey = null; _clientTeleportedFromHost = false; _clientPendingTeleport = null; _pendingSaveFull = null; _pendingMoneySet = null; _clientLastSaveIdFromHost = string.Empty; ScheduleReconnect(unscaledTime, $"lost host ({num2:0.0}s no packets)"); } } else if (!_transport.HasServer && _clientLastKnownServer != null) { try { _transport.SetServer(_clientLastKnownServer); } catch { } } } private void ScheduleReconnect(float now, string why) { _clientReconnectAttempt = Math.Max(0, _clientReconnectAttempt); int num = Math.Max(1, _clientReconnectAttempt); float num2 = 1f * Mathf.Pow(2f, (float)Mathf.Min(5, num - 1)); float num3 = Plugin.AutoReconnectBackoffMaxSeconds.Value; if (num3 < 1f) { num3 = 1f; } num2 = Mathf.Min(num2, num3); num2 += Random.Range(0f, 0.35f); _clientNextReconnectTryAt = now + num2; _nextHelloAt = _clientNextReconnectTryAt; string arg = ((_transport?.ServerEndPoint != null) ? _transport.ServerEndPoint.ToString() : ((_clientLastKnownServer != null) ? _clientLastKnownServer.ToString() : "(unknown)")); Plugin.Log.LogWarning((object)$"AutoReconnect: {why}. Next Hello in {num2:0.0}s. Server={arg}"); } private void TryAutoEnterWorld() { if (!IsClient || !_clientAutoEnterRequested) { return; } if (!Plugin.AutoEnterWorldOnConnect.Value) { if (!_autoEnterDisabledLogged) { _autoEnterDisabledLogged = true; Plugin.Log.LogInfo((object)"AutoEnterWorld: disabled by config (AutoEnterWorldOnConnect=false)"); } } else { if (!HasWelcome) { return; } if (GameAccess.IsGameplayWorldLoaded()) { _clientAutoEnterDone = true; } else { if (_clientAutoEnterDone) { return; } float unscaledTime = Time.unscaledTime; if (!(unscaledTime > _clientAutoEnterUntil) && !(unscaledTime < _clientAutoEnterNextTryAt)) { _clientAutoEnterNextTryAt = unscaledTime + 0.75f; string reason; bool num = GameAccess.TryAutoEnterWorldFromMenu(_clientLastSaveIdFromHost, out reason); if (unscaledTime >= _nextAutoEnterLogAt || !string.Equals(_lastAutoEnterReason, reason, StringComparison.Ordinal)) { _nextAutoEnterLogAt = unscaledTime + 3f; _lastAutoEnterReason = reason ?? string.Empty; Plugin.Log.LogInfo((object)("AutoEnterWorld: " + reason)); } if (num) { _clientAutoEnterNextTryAt = unscaledTime + 2.5f; } } } } } private void EnsureClientConnected() { if (_transport == null || !_transport.HasServer || HasWelcome || (Plugin.AutoReconnectEnabled.Value && !_clientManualDisconnect && Time.unscaledTime < _clientNextReconnectTryAt) || Time.unscaledTime < _nextHelloAt) { return; } _nextHelloAt = Time.unscaledTime + 1f; _transport.SendToServer(NetMessages.BuildHello()); if (Plugin.AutoReconnectEnabled.Value && !_clientManualDisconnect) { _clientReconnectAttempt = Math.Min(_clientReconnectAttempt + 1, 50); float num = 1f * Mathf.Pow(2f, (float)Mathf.Min(5, _clientReconnectAttempt - 1)); float num2 = Plugin.AutoReconnectBackoffMaxSeconds.Value; if (num2 < 1f) { num2 = 1f; } num = Mathf.Min(num, num2); num += Random.Range(0f, 0.35f); _clientNextReconnectTryAt = Time.unscaledTime + num; } } private void BroadcastWorldSnapshot() { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) if (_transport == null) { return; } float num = 0.1f; if (Time.unscaledTime < _nextWorldSnapshotAt) { return; } _nextWorldSnapshotAt = Time.unscaledTime + num; bool flag = Time.unscaledTime < _forceFullSnapshotUntil; List list = new List(_hostKnownPlayers.Count + 1); if (GameAccess.TryReadLocalPlayerPose(out var pos, out var rot)) { GameAccess.TryReadInCar(out var inCar); string payloadName; string heldPayload = (GameAccess.TryReadHeldPayloadName(out payloadName) ? payloadName : string.Empty); list.Add(new PlayerPose("host", Plugin.SanitizeNickname(Plugin.Nickname.Value), pos.x, pos.y, pos.z, rot.x, rot.y, rot.z, rot.w, inCar, heldPayload)); if (Plugin.PlayerPositionsEnabled.Value && GameAccess.TryReadLocalPlayerControllerPose(out var pos2, out var rot2)) { PlayerPositionsStore.HostUpdatePlayer(Plugin.SanitizeNickname(Plugin.Nickname.Value), pos2, rot2, Time.unscaledTime); } } foreach (KeyValuePair hostKnownPlayer in _hostKnownPlayers) { list.Add(hostKnownPlayer.Value); } CarState? car = null; if (GameAccess.TryReadCarState(out var pos3, out var rot3, out var vel, out var angVel, out var hasCar) && hasCar) { car = new CarState(pos3.x, pos3.y, pos3.z, rot3.x, rot3.y, rot3.z, rot3.w, vel.x, vel.y, vel.z, angVel.x, angVel.y, angVel.z); } List list2 = null; IReadOnlyList<(string, Vector3, Quaternion)> readOnlyList = GameAccess.TryReadCarCargo(); if (readOnlyList != null) { list2 = new List(readOnlyList.Count); foreach (var item in readOnlyList) { list2.Add(new CargoItem(item.Item1, item.Item2.x, item.Item2.y, item.Item2.z, item.Item3.x, item.Item3.y, item.Item3.z, item.Item3.w)); } } float time; int money; byte[] payload = NetMessages.BuildWorldSnapshot(null, GameAccess.TryReadDayNightTime(out time) ? new float?(time) : ((float?)null), (Plugin.SharedMoneyEnabled.Value && GameAccess.TryReadHudMoney(out money)) ? new int?(money) : ((int?)null), flag ? GameAccess.TryReadJobBoardJobs() : null, list, car, list2); _transport.SendToAll(payload); TraceSnapshotSend(list.Count, car.HasValue, list2?.Count ?? 0); } private void SendLocalClientState() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) if (_transport == null || !_transport.HasServer || !GameAccess.TryReadLocalPlayerPose(out var pos, out var rot)) { return; } GameAccess.TryReadInCar(out var inCar); string payloadName; byte[] payload = NetMessages.BuildClientState(Plugin.Nickname.Value, pos.x, pos.y, pos.z, rot.x, rot.y, rot.z, rot.w, inCar, GameAccess.TryReadHeldPayloadName(out payloadName) ? payloadName : string.Empty); _transport.SendToServer(payload); if (GameAccess.TryReadCarState(out var pos2, out var rot2, out var _, out var _, out var hasCar) && hasCar) { byte[] payload2 = NetMessages.BuildClientCarState(pos2.x, pos2.y, pos2.z, rot2.x, rot2.y, rot2.z, rot2.w); _transport.SendToServer(payload2); } if (Time.unscaledTime >= _nextClientCargoAt) { _nextClientCargoAt = Time.unscaledTime + 0.5f; IReadOnlyList<(string, Vector3, Quaternion)> readOnlyList = GameAccess.TryReadCarCargo(); if (readOnlyList != null) { _transport.SendToServer(NetMessages.BuildClientCarCargo(readOnlyList)); } else { _transport.SendToServer(NetMessages.BuildClientCarCargo(Array.Empty<(string, Vector3, Quaternion)>())); } } } private void SendLocalCarSfxIfNeeded() { if (!Plugin.CarSoundSyncEnabled.Value || !GameAccess.TryReadCarState(out var _, out var _, out var _, out var _, out var hasCar) || !hasCar) { return; } float num = Plugin.CarSoundSyncMinIntervalSeconds.Value; if (num < 0.02f) { num = 0.02f; } if (!GameAccess.TryGetLocalCarSfxPulses(out List<(byte, string, string)> sfxEvents)) { return; } float unscaledTime = Time.unscaledTime; for (int i = 0; i < sfxEvents.Count; i++) { (byte, string, string) tuple = sfxEvents[i]; var (b, _, _) = tuple; if (Plugin.IsHornOnlyCarSoundMode() && b != 1) { continue; } string text = tuple.Item2 ?? string.Empty; string text2 = tuple.Item3 ?? string.Empty; string key = $"{b}|{text}|{text2}"; if (_nextCarSfxAt.TryGetValue(key, out var value) && unscaledTime < value) { continue; } _nextCarSfxAt[key] = unscaledTime + num; if (IsHost) { _transport?.SendToAll(NetMessages.BuildCarSfx("host", b, text, text2)); if (Plugin.DebugLogs.Value) { Plugin.Log.LogInfo((object)$"CarSfx send host: car=host sfx={CarSfxName(b)}({b}) clip='{text}' src='{text2}'"); } } else if (IsClient && _transport != null && _transport.HasServer && HasWelcome) { _transport.SendToServer(NetMessages.BuildClientCarSfx(b, text, text2)); if (Plugin.DebugLogs.Value) { Plugin.Log.LogInfo((object)$"CarSfx send client: sfx={CarSfxName(b)}({b}) clip='{text}' src='{text2}'"); } } } } internal void OnClientState(IPEndPoint remote, string nickname, float px, float py, float pz, float qx, float qy, float qz, float qw, bool inCar, string heldPayload, Vector3? controllerPos, Quaternion? controllerRot) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) string key = remote.ToString(); MarkClientSeen(key); string text = Plugin.SanitizeNickname(nickname); _hostKnownNick[key] = text; _hostKnownPlayers[key] = new PlayerPose(key, text, px, py, pz, qx, qy, qz, qw, inCar, heldPayload ?? string.Empty); if (Plugin.PlayerPositionsEnabled.Value) { Vector3 pos = (Vector3)(((??)controllerPos) ?? new Vector3(px, py, pz)); Quaternion rot = (Quaternion)(((??)controllerRot) ?? new Quaternion(qx, qy, qz, qw)); PlayerPositionsStore.HostUpdatePlayer(text, pos, rot, Time.unscaledTime); } RemoteAvatars.ApplyPlayerPose(key, text, px, py, pz, qx, qy, qz, qw, inCar, heldPayload); } internal void OnClientCarState(IPEndPoint remote, float px, float py, float pz, float qx, float qy, float qz, float qw) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) string text = remote.ToString(); MarkClientSeen(text); Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(px, py, pz); Quaternion val2 = default(Quaternion); ((Quaternion)(ref val2))..ctor(qx, qy, qz, qw); _hostKnownCars[text] = (val, val2); string value; string nickname = (_hostKnownNick.TryGetValue(text, out value) ? value : ""); RemoteCars.ApplyCarState("client." + text, val, val2, nickname); } internal void OnClientCarCargo(IPEndPoint remote, IReadOnlyList<(string Name, Vector3 LocalPos, Quaternion LocalRot)> cargo) { string text = remote.ToString(); MarkClientSeen(text); RemoteCars.ApplyCargo("client." + text, cargo); } internal void OnClientCarSfx(IPEndPoint remote, byte sfxId, string? clipName, string? sourceName) { if (!IsHost || (Plugin.IsHornOnlyCarSoundMode() && sfxId != 1)) { return; } string text = remote.ToString(); MarkClientSeen(text); string text2 = "client." + text; RemoteCars.PlayCarSfx(text2, sfxId, clipName, sourceName); if (clipName == null) { clipName = string.Empty; } if (sourceName == null) { sourceName = string.Empty; } if (Plugin.DebugLogs.Value) { Plugin.Log.LogInfo((object)$"CarSfx recv host: from={remote} car={text2} sfx={CarSfxName(sfxId)}({sfxId}) clip='{clipName}' src='{sourceName}'"); } if (_transport != null) { _transport.SendToAll(NetMessages.BuildCarSfx(text2, sfxId, clipName, sourceName)); if (Plugin.DebugLogs.Value) { Plugin.Log.LogInfo((object)$"CarSfx relay host: car={text2} sfx={CarSfxName(sfxId)}({sfxId}) clip='{clipName}' src='{sourceName}'"); } } } internal void OnCarSfx(string carKey, byte sfxId, string? clipName, string? sourceName) { if (IsClient && (string.IsNullOrWhiteSpace(_selfKey) || !string.Equals(carKey, "client." + _selfKey, StringComparison.Ordinal)) && (!Plugin.IsHornOnlyCarSoundMode() || sfxId == 1)) { RemoteCars.PlayCarSfx(carKey, sfxId, clipName, sourceName); if (clipName == null) { clipName = string.Empty; } if (sourceName == null) { sourceName = string.Empty; } if (Plugin.DebugLogs.Value) { Plugin.Log.LogInfo((object)$"CarSfx recv client: car={carKey} sfx={CarSfxName(sfxId)}({sfxId}) clip='{clipName}' src='{sourceName}'"); } } } private static string CarSfxName(byte sfxId) { return sfxId switch { 1 => "horn", 2 => "skid", 3 => "crash", _ => "unknown", }; } internal void OnClientReportedMoney(IPEndPoint remote, int reportedMoney) { if (!IsHost || !Plugin.SharedMoneyEnabled.Value) { return; } if (reportedMoney < 0) { reportedMoney = 0; } if (!GameAccess.TryReadHudMoney(out var money)) { if (!_lastObservedMoney.HasValue) { return; } money = _lastObservedMoney.Value; } if (reportedMoney > money) { WithRemoteApply(delegate { GameAccess.TryApplyHudMoney(reportedMoney); }); NotifyRemoteMoneyApplied(reportedMoney); if (_transport != null) { _transport.SendToAll(NetMessages.BuildMoneySet(reportedMoney)); } if (Plugin.DebugLogs.Value) { Plugin.Log.LogInfo((object)$"MoneyReport from {remote}: report={reportedMoney} total={reportedMoney}"); } } } internal void OnClientHello(IPEndPoint remote, string? clientVersion) { string key = remote.ToString(); MarkClientSeen(key); if (!string.IsNullOrWhiteSpace(clientVersion)) { _hostKnownClientVersion[key] = clientVersion; if (!string.Equals(clientVersion, "0.2.19", StringComparison.Ordinal)) { Plugin.Log.LogWarning((object)string.Format("Version mismatch: client {0} has {1}, host has {2}. Sound sync/features may not work.", remote, clientVersion, "0.2.19")); } } } internal void QueueInitialSync(IPEndPoint remote) { if (IsHost) { string key = remote.ToString(); _hostInitialSync[key] = new InitialSyncState { EndPoint = remote, NextSendAt = 0f, UntilAt = Time.unscaledTime + 3f }; } } internal void NotifyRemoteMoneyApplied(int money) { _lastObservedMoney = money; _suppressMoneyEchoUntil = Time.unscaledTime + 0.35f; } internal void OnPlayerLocations(string saveId, Dictionary locations) { if (IsClient && Plugin.PlayerPositionsEnabled.Value && Plugin.PlayerPositionsClientTeleportOnJoin.Value && !_clientTeleportedFromHost && (string.IsNullOrWhiteSpace(_clientLastSaveIdFromHost) || string.IsNullOrWhiteSpace(saveId) || string.Equals(GameAccess.NormalizeSaveIdForSyncCompare(_clientLastSaveIdFromHost), GameAccess.NormalizeSaveIdForSyncCompare(saveId), StringComparison.Ordinal))) { string key = Plugin.SanitizeNickname(Plugin.Nickname.Value); if (locations.TryGetValue(key, out (Vector3, Quaternion) value)) { _clientPendingTeleport = value; _clientPendingTeleportUntil = Time.unscaledTime + 5f; } } } private void MarkClientSeen(string key) { if (!string.IsNullOrWhiteSpace(key)) { _hostClientLastSeenAt[key] = Time.unscaledTime; } } private void PruneDisconnectedClients() { if (!IsHost || _transport == null) { return; } float unscaledTime = Time.unscaledTime; if (unscaledTime < _nextHostPruneAt) { return; } _nextHostPruneAt = unscaledTime + 1f; float num = ((Plugin.ClientTimeoutSeconds != null) ? Plugin.ClientTimeoutSeconds.Value : 6f); if (num < 1f) { num = 1f; } List list = new List(); foreach (KeyValuePair item in _hostClientLastSeenAt) { if (unscaledTime - item.Value > num) { list.Add(item.Key); } } for (int i = 0; i < list.Count; i++) { string text = list[i]; _hostClientLastSeenAt.Remove(text); _hostKnownPlayers.Remove(text); _hostKnownCars.Remove(text); _hostKnownNick.Remove(text); _hostKnownClientVersion.Remove(text); _hostInitialSync.Remove(text); RemoteAvatars.Remove(text); RemoteCars.Remove("client." + text); _transport.UnregisterClient(text); Plugin.Log.LogInfo((object)("Client disconnected (timeout): " + text)); } } internal void SendSaveDelta(string key, string value) { if (_transport != null) { byte[] payload = NetMessages.BuildSaveSet(key, value); if (IsHost) { _transport.SendToAll(payload); } else { _transport.SendToServer(payload); } } } internal void SendSaveDelete(string key) { if (_transport != null) { byte[] payload = NetMessages.BuildSaveDelete(key); if (IsHost) { _transport.SendToAll(payload); } else { _transport.SendToServer(payload); } } } internal void WithRemoteApply(Action action) { if (_applyingRemote) { action(); return; } _applyingRemote = true; try { action(); } finally { _applyingRemote = false; } } } internal sealed class LanDiscovery : IDisposable { private readonly bool _isHost; private readonly int _discoveryPort; private readonly int _gamePort; private readonly int _intervalMs; private UdpClient? _udp; private DateTime _nextAnnounceUtc; public LanDiscovery(bool isHost, int discoveryPort, int gamePort, int intervalMs) { _isHost = isHost; _discoveryPort = discoveryPort; _gamePort = gamePort; _intervalMs = Math.Max(100, intervalMs); } public void Start() { if (_udp == null) { if (_isHost) { _udp = new UdpClient(0); _udp.EnableBroadcast = true; _udp.Client.Blocking = false; _nextAnnounceUtc = DateTime.UtcNow; Plugin.Log.LogInfo((object)$"LAN discovery host broadcaster started (port {_discoveryPort})"); } else { _udp = new UdpClient(_discoveryPort); _udp.Client.Blocking = false; Plugin.Log.LogInfo((object)$"LAN discovery client listener started (port {_discoveryPort})"); } } } public void Dispose() { _udp?.Dispose(); _udp = null; } public void Poll(LanCoopManager mgr, UdpTransport transport) { if (_udp == null) { return; } if (_isHost) { if (DateTime.UtcNow < _nextAnnounceUtc) { return; } _nextAnnounceUtc = DateTime.UtcNow.AddMilliseconds(_intervalMs); byte[] array = NetMessages.BuildDiscoveryAnnounce(_gamePort); try { IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast, _discoveryPort); _udp.Send(array, array.Length, endPoint); return; } catch { return; } } while (_udp.Available > 0) { IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); byte[] data; try { data = _udp.Receive(ref remoteEP); } catch { break; } try { NetMessages.TryHandleDiscovery(mgr, transport, remoteEP, data); } catch { } } } } internal static class NetMessages { private const string DiscoveryMagic = "EDC_LAN_COOP_DISCOVERY"; internal static byte[] BuildHello() { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)1); binaryWriter.Write("EDC_LAN_COOP"); binaryWriter.Write("0.2.19"); return memoryStream.ToArray(); } internal static byte[] BuildWelcome(string selfKey) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)7); binaryWriter.Write(selfKey); binaryWriter.Write("0.2.19"); return memoryStream.ToArray(); } internal static byte[] BuildDiscoveryAnnounce(int gamePort) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)5); binaryWriter.Write("EDC_LAN_COOP_DISCOVERY"); binaryWriter.Write(gamePort); binaryWriter.Write("0.2.19"); return memoryStream.ToArray(); } internal static void TryHandleDiscovery(LanCoopManager mgr, UdpTransport transport, IPEndPoint remote, byte[] data) { if (!mgr.IsClient || mgr.HasWelcome || mgr.ClientDiscoveryLocked) { return; } using MemoryStream memoryStream = new MemoryStream(data); using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: true); if (memoryStream.Length >= 2 && binaryReader.ReadByte() == 5 && string.Equals(binaryReader.ReadString(), "EDC_LAN_COOP_DISCOVERY", StringComparison.Ordinal)) { int port = binaryReader.ReadInt32(); binaryReader.ReadString(); IPEndPoint iPEndPoint = new IPEndPoint(remote.Address, port); if (!transport.HasServer || transport.ServerEndPoint == null || !transport.ServerEndPoint.Equals(iPEndPoint)) { transport.SetServer(iPEndPoint); Plugin.Log.LogInfo((object)$"Discovered host: {iPEndPoint}"); } transport.SendToServer(BuildHello()); } } internal static byte[] BuildSaveSet(string key, string value) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)3); binaryWriter.Write(key); binaryWriter.Write(value); return memoryStream.ToArray(); } internal static byte[] BuildSaveDelete(string key) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)4); binaryWriter.Write(key); return memoryStream.ToArray(); } internal static byte[] BuildSaveFull(string saveId, bool wipeClientFirst, Dictionary save) { using MemoryStream memoryStream = new MemoryStream(16384); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)10); binaryWriter.Write(saveId ?? string.Empty); binaryWriter.Write(wipeClientFirst); binaryWriter.Write(save?.Count ?? 0); if (save != null) { foreach (KeyValuePair item in save) { binaryWriter.Write(item.Key ?? string.Empty); binaryWriter.Write(item.Value ?? string.Empty); } } return memoryStream.ToArray(); } internal static byte[] BuildPlayerLocations(string saveId, Dictionary positions) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) using MemoryStream memoryStream = new MemoryStream(8192); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)11); binaryWriter.Write(saveId ?? string.Empty); binaryWriter.Write(positions?.Count ?? 0); if (positions != null) { foreach (KeyValuePair position in positions) { binaryWriter.Write(position.Key ?? string.Empty); binaryWriter.Write(position.Value.Item1.x); binaryWriter.Write(position.Value.Item1.y); binaryWriter.Write(position.Value.Item1.z); binaryWriter.Write(position.Value.Item2.x); binaryWriter.Write(position.Value.Item2.y); binaryWriter.Write(position.Value.Item2.z); binaryWriter.Write(position.Value.Item2.w); } } return memoryStream.ToArray(); } internal static byte[] BuildMoneyDelta(int delta) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)12); binaryWriter.Write(delta); return memoryStream.ToArray(); } internal static byte[] BuildMoneySet(int money) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)13); binaryWriter.Write(money); return memoryStream.ToArray(); } internal static byte[] BuildClientState(string nickname, float px, float py, float pz, float qx, float qy, float qz, float qw, bool inCar, string heldPayloadName) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)6); binaryWriter.Write(Plugin.SanitizeNickname(nickname)); binaryWriter.Write(px); binaryWriter.Write(py); binaryWriter.Write(pz); binaryWriter.Write(qx); binaryWriter.Write(qy); binaryWriter.Write(qz); binaryWriter.Write(qw); binaryWriter.Write(inCar); binaryWriter.Write(heldPayloadName ?? string.Empty); if (GameAccess.TryReadLocalPlayerControllerPose(out var pos, out var rot)) { binaryWriter.Write(value: true); binaryWriter.Write(pos.x); binaryWriter.Write(pos.y); binaryWriter.Write(pos.z); binaryWriter.Write(rot.x); binaryWriter.Write(rot.y); binaryWriter.Write(rot.z); binaryWriter.Write(rot.w); } else { binaryWriter.Write(value: false); } if (Plugin.SharedMoneyEnabled.Value && GameAccess.TryReadHudMoney(out var money)) { binaryWriter.Write(value: true); binaryWriter.Write(money); } else { binaryWriter.Write(value: false); } return memoryStream.ToArray(); } internal static byte[] BuildClientCarState(float px, float py, float pz, float qx, float qy, float qz, float qw) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)8); binaryWriter.Write(px); binaryWriter.Write(py); binaryWriter.Write(pz); binaryWriter.Write(qx); binaryWriter.Write(qy); binaryWriter.Write(qz); binaryWriter.Write(qw); return memoryStream.ToArray(); } internal static byte[] BuildClientCarCargo(IReadOnlyList<(string Name, Vector3 LocalPos, Quaternion LocalRot)> cargo) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)9); binaryWriter.Write(cargo.Count); for (int i = 0; i < cargo.Count; i++) { binaryWriter.Write(cargo[i].Name ?? string.Empty); binaryWriter.Write(cargo[i].LocalPos.x); binaryWriter.Write(cargo[i].LocalPos.y); binaryWriter.Write(cargo[i].LocalPos.z); binaryWriter.Write(cargo[i].LocalRot.x); binaryWriter.Write(cargo[i].LocalRot.y); binaryWriter.Write(cargo[i].LocalRot.z); binaryWriter.Write(cargo[i].LocalRot.w); } return memoryStream.ToArray(); } internal static byte[] BuildClientCarSfx(byte sfxId, string? clipName = null, string? sourceName = null) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)14); binaryWriter.Write(sfxId); string text = clipName ?? string.Empty; string text2 = sourceName ?? string.Empty; binaryWriter.Write(text.Length > 0); if (text.Length > 0) { binaryWriter.Write(text); } binaryWriter.Write(text2.Length > 0); if (text2.Length > 0) { binaryWriter.Write(text2); } return memoryStream.ToArray(); } internal static byte[] BuildCarSfx(string carKey, byte sfxId, string? clipName = null, string? sourceName = null) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)15); binaryWriter.Write(carKey ?? string.Empty); binaryWriter.Write(sfxId); string text = clipName ?? string.Empty; string text2 = sourceName ?? string.Empty; binaryWriter.Write(text.Length > 0); if (text.Length > 0) { binaryWriter.Write(text); } binaryWriter.Write(text2.Length > 0); if (text2.Length > 0) { binaryWriter.Write(text2); } return memoryStream.ToArray(); } internal static byte[] BuildWorldSnapshot(Dictionary? save, float? dayTime, int? money, IReadOnlyList? jobs, IReadOnlyList? players, CarState? car, IReadOnlyList? cargo) { using MemoryStream memoryStream = new MemoryStream(16384); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write((byte)2); if (save == null) { binaryWriter.Write(0); } else { binaryWriter.Write(save.Count); foreach (KeyValuePair item in save) { binaryWriter.Write(item.Key); binaryWriter.Write(item.Value ?? string.Empty); } } binaryWriter.Write(dayTime.HasValue); if (dayTime.HasValue) { binaryWriter.Write(dayTime.Value); } binaryWriter.Write(money.HasValue); if (money.HasValue) { binaryWriter.Write(money.Value); } if (jobs == null) { binaryWriter.Write(0); } else { binaryWriter.Write(jobs.Count); foreach (JobData job in jobs) { binaryWriter.Write(job.Name ?? string.Empty); binaryWriter.Write(job.From ?? string.Empty); binaryWriter.Write(job.To ?? string.Empty); binaryWriter.Write(job.DestinationIndex); binaryWriter.Write(job.PayloadIndex); binaryWriter.Write(job.Price); binaryWriter.Write(job.Mass); binaryWriter.Write(job.TimeStart); binaryWriter.Write(job.IsChallenge); binaryWriter.Write(job.IsIntercity); binaryWriter.Write(job.Duration); binaryWriter.Write(job.Distance); binaryWriter.Write(job.StartingCityName ?? string.Empty); binaryWriter.Write(job.DestCityName ?? string.Empty); } } if (players == null) { binaryWriter.Write(0); } else { binaryWriter.Write(players.Count); foreach (PlayerPose player in players) { binaryWriter.Write(player.Key ?? string.Empty); binaryWriter.Write(player.Nick ?? string.Empty); binaryWriter.Write(player.Px); binaryWriter.Write(player.Py); binaryWriter.Write(player.Pz); binaryWriter.Write(player.Qx); binaryWriter.Write(player.Qy); binaryWriter.Write(player.Qz); binaryWriter.Write(player.Qw); binaryWriter.Write(player.InCar); binaryWriter.Write(player.HeldPayload ?? string.Empty); } } binaryWriter.Write(car.HasValue); if (car.HasValue) { CarState value = car.Value; binaryWriter.Write(value.Px); binaryWriter.Write(value.Py); binaryWriter.Write(value.Pz); binaryWriter.Write(value.Qx); binaryWriter.Write(value.Qy); binaryWriter.Write(value.Qz); binaryWriter.Write(value.Qw); binaryWriter.Write(value.Vx); binaryWriter.Write(value.Vy); binaryWriter.Write(value.Vz); binaryWriter.Write(value.Ax); binaryWriter.Write(value.Ay); binaryWriter.Write(value.Az); } if (cargo == null) { binaryWriter.Write(0); } else { binaryWriter.Write(cargo.Count); foreach (CargoItem item2 in cargo) { binaryWriter.Write(item2.Name ?? string.Empty); binaryWriter.Write(item2.Lpx); binaryWriter.Write(item2.Lpy); binaryWriter.Write(item2.Lpz); binaryWriter.Write(item2.Lqx); binaryWriter.Write(item2.Lqy); binaryWriter.Write(item2.Lqz); binaryWriter.Write(item2.Lqw); } } return memoryStream.ToArray(); } internal static void ParseAndApplyIncoming(LanCoopManager mgr, UdpTransport transport, IPEndPoint remote, byte[] data) { //IL_0530: Unknown result type (might be due to invalid IL or missing references) //IL_0568: Unknown result type (might be due to invalid IL or missing references) //IL_0903: Unknown result type (might be due to invalid IL or missing references) //IL_0905: Unknown result type (might be due to invalid IL or missing references) //IL_07a4: Unknown result type (might be due to invalid IL or missing references) //IL_07a6: Unknown result type (might be due to invalid IL or missing references) using MemoryStream memoryStream = new MemoryStream(data); BinaryReader br = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: true); try { switch ((MessageType)br.ReadByte()) { case MessageType.Hello: { string a = br.ReadString(); string clientVersion = null; if (memoryStream.Position < memoryStream.Length) { try { clientVersion = br.ReadString(); } catch { clientVersion = null; } } if (mgr.IsHost && string.Equals(a, "EDC_LAN_COOP", StringComparison.Ordinal)) { transport.RegisterClient(remote); Plugin.Log.LogInfo((object)$"Client registered: {remote}"); mgr.OnClientHello(remote, clientVersion); mgr.NotifyClientRegistered(); transport.SendTo(remote, BuildWelcome(remote.ToString())); if (Plugin.SharedMoneyEnabled.Value && GameAccess.TryReadHudMoney(out var money3)) { transport.SendTo(remote, BuildMoneySet(money3)); } mgr.QueueInitialSync(remote); } break; } case MessageType.Welcome: { if (!mgr.IsClient) { break; } string key2 = br.ReadString(); string hostVersion = null; if (memoryStream.Position < memoryStream.Length) { try { hostVersion = br.ReadString(); } catch { hostVersion = null; } } mgr.SetSelfKey(key2, hostVersion); break; } case MessageType.SaveSet: { string k = br.ReadString(); string v = br.ReadString(); if (!Plugin.IsSaveKeyAllowedForWorldSync(k)) { break; } if (mgr.IsHost) { mgr.WithRemoteApply(delegate { GameAccess.TryApplySaveKey(k, v); }); transport.SendToAll(BuildSaveSet(k, v)); } else { mgr.WithRemoteApply(delegate { GameAccess.TryApplySaveKey(k, v); }); } break; } case MessageType.SaveDelete: { string k2 = br.ReadString(); if (!Plugin.IsSaveKeyAllowedForWorldSync(k2)) { break; } if (mgr.IsHost) { mgr.WithRemoteApply(delegate { GameAccess.TryDeleteSaveKey(k2); }); transport.SendToAll(BuildSaveDelete(k2)); } else { mgr.WithRemoteApply(delegate { GameAccess.TryDeleteSaveKey(k2); }); } break; } case MessageType.WorldSnapshot: { if (!mgr.IsClient) { break; } int count = br.ReadInt32(); mgr.WithRemoteApply(delegate { //IL_03ce: Unknown result type (might be due to invalid IL or missing references) //IL_03f5: Unknown result type (might be due to invalid IL or missing references) //IL_0400: Unknown result type (might be due to invalid IL or missing references) //IL_0402: Unknown result type (might be due to invalid IL or missing references) //IL_049c: Unknown result type (might be due to invalid IL or missing references) //IL_049e: Unknown result type (might be due to invalid IL or missing references) try { for (int i = 0; i < count; i++) { string key3 = br.ReadString(); string value2 = br.ReadString(); if (Plugin.IsSaveKeyAllowedForWorldSync(key3)) { GameAccess.TryApplySaveKey(key3, value2); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Save snapshot apply failed: " + ex.GetType().Name)); } if (br.ReadBoolean()) { GameAccess.TryApplyDayNightTime(br.ReadSingle()); } if (br.ReadBoolean()) { int money4 = br.ReadInt32(); if (Plugin.SharedMoneyEnabled.Value) { mgr.OnMoneySetReceived(money4); } } try { int num9 = br.ReadInt32(); if (num9 > 0) { List list2 = new List(num9); for (int j = 0; j < num9; j++) { JobData item6 = new JobData { Name = br.ReadString(), From = br.ReadString(), To = br.ReadString(), DestinationIndex = br.ReadInt32(), PayloadIndex = br.ReadInt32(), Price = br.ReadSingle(), Mass = br.ReadSingle(), TimeStart = br.ReadSingle(), IsChallenge = br.ReadBoolean(), IsIntercity = br.ReadBoolean(), Duration = br.ReadSingle(), Distance = br.ReadSingle(), StartingCityName = br.ReadString(), DestCityName = br.ReadString() }; list2.Add(item6); } GameAccess.TryApplyJobBoardJobs(list2); } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Jobs snapshot apply failed: " + ex2.GetType().Name)); } HashSet hashSet = new HashSet(StringComparer.Ordinal); string nickname = ""; int num10 = br.ReadInt32(); for (int l = 0; l < num10; l++) { string text2 = br.ReadString(); string nick3 = br.ReadString(); float px3 = br.ReadSingle(); float py3 = br.ReadSingle(); float pz3 = br.ReadSingle(); float qx3 = br.ReadSingle(); float qy3 = br.ReadSingle(); float qz3 = br.ReadSingle(); float qw3 = br.ReadSingle(); bool inCar2 = br.ReadBoolean(); string heldPayloadName = br.ReadString(); if (string.Equals(text2, "host", StringComparison.Ordinal)) { nickname = Plugin.SanitizeNickname(nick3); } if (!string.IsNullOrEmpty(text2) && text2 != mgr.SelfKey) { hashSet.Add(text2); RemoteAvatars.ApplyPlayerPose(text2, Plugin.SanitizeNickname(nick3), px3, py3, pz3, qx3, qy3, qz3, qw3, inCar2, heldPayloadName); } } RemoteAvatars.CleanupMissing(hashSet); bool flag = br.ReadBoolean(); if (flag) { Vector3 pos = default(Vector3); ((Vector3)(ref pos))..ctor(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); Quaternion rot = default(Quaternion); ((Quaternion)(ref rot))..ctor(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); RemoteCars.ApplyCarState("host", pos, rot, nickname); } int num11 = br.ReadInt32(); if (num11 > 0) { List<(string, Vector3, Quaternion)> list3 = new List<(string, Vector3, Quaternion)>(num11); Vector3 item8 = default(Vector3); Quaternion item9 = default(Quaternion); for (int m = 0; m < num11; m++) { string item7 = br.ReadString(); ((Vector3)(ref item8))..ctor(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); ((Quaternion)(ref item9))..ctor(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); list3.Add((item7, item8, item9)); } RemoteCars.ApplyCargo("host", list3); } mgr.TraceSnapshotReceive(num10, flag, num11); }); break; } case MessageType.MoneyDelta: { if (!mgr.IsHost || !Plugin.SharedMoneyEnabled.Value) { break; } int num3 = br.ReadInt32(); if (num3 != 0 && GameAccess.TryReadHudMoney(out var money2)) { int next = money2 + num3; if (next < 0) { next = 0; } mgr.WithRemoteApply(delegate { GameAccess.TryApplyHudMoney(next); }); mgr.NotifyRemoteMoneyApplied(next); transport.SendToAll(BuildMoneySet(next)); if (Plugin.DebugLogs.Value) { Plugin.Log.LogInfo((object)$"MoneyDelta from {remote}: delta={num3} total={next}"); } } break; } case MessageType.MoneySet: if (mgr.IsClient && Plugin.SharedMoneyEnabled.Value) { int money = br.ReadInt32(); mgr.OnMoneySetReceived(money); } break; case MessageType.ClientState: { if (!mgr.IsHost) { break; } string nick = br.ReadString(); float px2 = br.ReadSingle(); float py2 = br.ReadSingle(); float pz2 = br.ReadSingle(); float qx2 = br.ReadSingle(); float qy2 = br.ReadSingle(); float qz2 = br.ReadSingle(); float qw2 = br.ReadSingle(); bool inCar = br.ReadBoolean(); string heldPayload = string.Empty; if (memoryStream.Position < memoryStream.Length) { try { heldPayload = br.ReadString(); } catch { heldPayload = string.Empty; } } Vector3? controllerPos = null; Quaternion? controllerRot = null; if (memoryStream.Position < memoryStream.Length) { try { if (br.ReadBoolean()) { controllerPos = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); controllerRot = new Quaternion(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); } } catch { controllerPos = null; controllerRot = null; } } int? num6 = null; if (memoryStream.Position < memoryStream.Length) { try { if (br.ReadBoolean() && memoryStream.Position < memoryStream.Length) { num6 = br.ReadInt32(); } } catch { num6 = null; } } mgr.OnClientState(remote, Plugin.SanitizeNickname(nick), px2, py2, pz2, qx2, qy2, qz2, qw2, inCar, heldPayload, controllerPos, controllerRot); if (num6.HasValue) { mgr.OnClientReportedMoney(remote, num6.Value); } break; } case MessageType.SaveFull: if (mgr.IsClient) { string text = br.ReadString(); bool wipe = br.ReadBoolean(); int num = br.ReadInt32(); Dictionary dictionary = new Dictionary(Math.Max(0, num), StringComparer.Ordinal); for (int num2 = 0; num2 < num; num2++) { string key = br.ReadString(); string value = br.ReadString(); dictionary[key] = value; } mgr.OnSaveFullReceived(text, wipe, dictionary); if (Plugin.DebugLogs.Value) { Plugin.Log.LogInfo((object)$"Received host SaveFull: keys={num} saveId='{text}'"); } } break; case MessageType.PlayerLocations: if (mgr.IsClient) { string saveId = br.ReadString(); int num7 = br.ReadInt32(); Dictionary dictionary2 = new Dictionary(Math.Max(0, num7), StringComparer.Ordinal); Vector3 item4 = default(Vector3); Quaternion item5 = default(Quaternion); for (int num8 = 0; num8 < num7; num8++) { string nick2 = br.ReadString(); ((Vector3)(ref item4))..ctor(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); ((Quaternion)(ref item5))..ctor(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); dictionary2[Plugin.SanitizeNickname(nick2)] = (item4, item5); } mgr.OnPlayerLocations(saveId, dictionary2); } break; case MessageType.ClientCarState: if (mgr.IsHost) { float px = br.ReadSingle(); float py = br.ReadSingle(); float pz = br.ReadSingle(); float qx = br.ReadSingle(); float qy = br.ReadSingle(); float qz = br.ReadSingle(); float qw = br.ReadSingle(); mgr.OnClientCarState(remote, px, py, pz, qx, qy, qz, qw); } break; case MessageType.ClientCarCargo: if (mgr.IsHost) { int num4 = br.ReadInt32(); List<(string, Vector3, Quaternion)> list = new List<(string, Vector3, Quaternion)>(Math.Max(0, num4)); Vector3 item2 = default(Vector3); Quaternion item3 = default(Quaternion); for (int num5 = 0; num5 < num4; num5++) { string item = br.ReadString(); ((Vector3)(ref item2))..ctor(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); ((Quaternion)(ref item3))..ctor(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); list.Add((item, item2, item3)); } mgr.OnClientCarCargo(remote, list); } break; case MessageType.ClientCarSfx: if (mgr.IsHost) { byte sfxId2 = br.ReadByte(); string clipName2 = null; string sourceName2 = null; if (memoryStream.Position < memoryStream.Length && br.ReadBoolean() && memoryStream.Position < memoryStream.Length) { clipName2 = br.ReadString(); } if (memoryStream.Position < memoryStream.Length && br.ReadBoolean() && memoryStream.Position < memoryStream.Length) { sourceName2 = br.ReadString(); } mgr.OnClientCarSfx(remote, sfxId2, clipName2, sourceName2); } break; case MessageType.CarSfx: if (mgr.IsClient) { string carKey = br.ReadString(); byte sfxId = br.ReadByte(); string clipName = null; string sourceName = null; if (memoryStream.Position < memoryStream.Length && br.ReadBoolean() && memoryStream.Position < memoryStream.Length) { clipName = br.ReadString(); } if (memoryStream.Position < memoryStream.Length && br.ReadBoolean() && memoryStream.Position < memoryStream.Length) { sourceName = br.ReadString(); } mgr.OnCarSfx(carKey, sfxId, clipName, sourceName); } break; case MessageType.DiscoveryAnnounce: break; } } finally { if (br != null) { ((IDisposable)br).Dispose(); } } } } internal enum MessageType : byte { Hello = 1, WorldSnapshot, SaveSet, SaveDelete, DiscoveryAnnounce, ClientState, Welcome, ClientCarState, ClientCarCargo, SaveFull, PlayerLocations, MoneyDelta, MoneySet, ClientCarSfx, CarSfx } internal sealed class JobData { public string? Name; public string? From; public string? To; public int DestinationIndex; public int PayloadIndex; public float Price; public float Mass; public float TimeStart; public bool IsChallenge; public bool IsIntercity; public float Duration; public float Distance; public string? StartingCityName; public string? DestCityName; } internal readonly struct PlayerPose { public readonly string? Key; public readonly string? Nick; public readonly float Px; public readonly float Py; public readonly float Pz; public readonly float Qx; public readonly float Qy; public readonly float Qz; public readonly float Qw; public readonly bool InCar; public readonly string? HeldPayload; public PlayerPose(string key, string nick, float px, float py, float pz, float qx, float qy, float qz, float qw, bool inCar, string heldPayload) { Key = key; Nick = nick; Px = px; Py = py; Pz = pz; Qx = qx; Qy = qy; Qz = qz; Qw = qw; InCar = inCar; HeldPayload = heldPayload; } } internal readonly struct CarState { public readonly float Px; public readonly float Py; public readonly float Pz; public readonly float Qx; public readonly float Qy; public readonly float Qz; public readonly float Qw; public readonly float Vx; public readonly float Vy; public readonly float Vz; public readonly float Ax; public readonly float Ay; public readonly float Az; public CarState(float px, float py, float pz, float qx, float qy, float qz, float qw, float vx, float vy, float vz, float ax, float ay, float az) { Px = px; Py = py; Pz = pz; Qx = qx; Qy = qy; Qz = qz; Qw = qw; Vx = vx; Vy = vy; Vz = vz; Ax = ax; Ay = ay; Az = az; } } internal readonly struct CargoItem { public readonly string? Name; public readonly float Lpx; public readonly float Lpy; public readonly float Lpz; public readonly float Lqx; public readonly float Lqy; public readonly float Lqz; public readonly float Lqw; public CargoItem(string name, float lpx, float lpy, float lpz, float lqx, float lqy, float lqz, float lqw) { Name = name; Lpx = lpx; Lpy = lpy; Lpz = lpz; Lqx = lqx; Lqy = lqy; Lqz = lqz; Lqw = lqw; } } internal sealed class UdpTransport : IDisposable { private readonly Action _onDatagram; private UdpClient? _udp; private IPEndPoint? _server; private readonly Dictionary _clients = new Dictionary(StringComparer.Ordinal); public bool IsHost { get; private set; } public bool HasServer => _server != null; public IPEndPoint? ServerEndPoint => _server; public UdpTransport(Action onDatagram) { _onDatagram = onDatagram; } public void StartHost(int port) { IsHost = true; _udp = new UdpClient(port); _udp.Client.Blocking = false; Plugin.Log.LogInfo((object)$"UDP host listening on {port}"); } public void StartClient(string host, int port) { IsHost = false; _udp = new UdpClient(0); _udp.Client.Blocking = false; if (!string.IsNullOrWhiteSpace(host)) { _server = new IPEndPoint(IPAddress.Parse(host), port); Plugin.Log.LogInfo((object)$"UDP client started. Server={_server}"); SendToServer(NetMessages.BuildHello()); } else { Plugin.Log.LogInfo((object)"UDP client started. Server is not set yet (waiting for discovery)"); } } public void SetServer(IPEndPoint server) { _server = server; Plugin.Log.LogInfo((object)$"Server set: {_server}"); } public void Dispose() { _udp?.Dispose(); _udp = null; _clients.Clear(); } public void Poll() { if (_udp == null) { return; } int num = 0; byte[] array = null; IPEndPoint iPEndPoint = null; while (_udp.Available > 0 && num < 128) { IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); byte[] array2; try { array2 = _udp.Receive(ref remoteEP); } catch { break; } num++; if (array2.Length != 0 && array2[0] == 2) { array = array2; iPEndPoint = remoteEP; } else { _onDatagram(remoteEP, array2); } } if (array != null && iPEndPoint != null) { _onDatagram(iPEndPoint, array); } } public void RegisterClient(IPEndPoint endpoint) { string key = endpoint.ToString(); _clients[key] = endpoint; } public void UnregisterClient(string key) { if (!string.IsNullOrWhiteSpace(key)) { _clients.Remove(key); } } public void SendToAll(byte[] payload) { if (_udp == null) { return; } foreach (KeyValuePair client in _clients) { try { _udp.Send(payload, payload.Length, client.Value); } catch { } } } public void SendToServer(byte[] payload) { if (_udp == null || _server == null) { return; } try { _udp.Send(payload, payload.Length, _server); } catch { } } public void SendTo(IPEndPoint endpoint, byte[] payload) { if (_udp == null) { return; } try { _udp.Send(payload, payload.Length, endpoint); } catch { } } } [BepInPlugin("coopmod.easydeliveryco.lancoop", "EasyDeliveryCoLanCoop", "0.2.19")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "coopmod.easydeliveryco.lancoop"; public const string PluginName = "EasyDeliveryCoLanCoop"; public const string PluginVersion = "0.2.19"; internal static ManualLogSource Log = null; internal static ConfigEntry Mode = null; internal static ConfigEntry Port = null; internal static ConfigEntry HostAddress = null; internal static ConfigEntry TickRate = null; internal static ConfigEntry ClientTimeoutSeconds = null; internal static ConfigEntry AutoDiscovery = null; internal static ConfigEntry DiscoveryPort = null; internal static ConfigEntry DiscoveryIntervalMs = null; internal static ConfigEntry SaveKeySyncEnabled = null; internal static ConfigEntry SaveKeyDenySubstrings = null; internal static ConfigEntry SaveKeyPersonalDenySubstrings = null; internal static ConfigEntry SharedMoneyEnabled = null; internal static ConfigEntry AutoEnterWorldOnConnect = null; internal static ConfigEntry AutoReconnectEnabled = null; internal static ConfigEntry AutoReconnectLostSeconds = null; internal static ConfigEntry AutoReconnectBackoffMaxSeconds = null; internal static ConfigEntry ClientReceivesHostSaveOnJoin = null; internal static ConfigEntry ClientWipeLocalSaveOnJoin = null; internal static ConfigEntry PlayerPositionsEnabled = null; internal static ConfigEntry PlayerPositionsSaveIdOverride = null; internal static ConfigEntry PlayerPositionsClientTeleportOnJoin = null; internal static ConfigEntry Nickname = null; internal static ConfigEntry RemoteAvatarYawOffsetDegrees = null; internal static ConfigEntry RemoteHeldPayloadOffsetX = null; internal static ConfigEntry RemoteHeldPayloadOffsetY = null; internal static ConfigEntry RemoteHeldPayloadOffsetZ = null; internal static ConfigEntry RemoteHeldPayloadUniformScale = null; internal static ConfigEntry CarSoundSyncEnabled = null; internal static ConfigEntry CarSoundSyncMode = null; internal static ConfigEntry CarSoundSyncHornOnly = null; internal static ConfigEntry CarSoundSyncMinIntervalSeconds = null; internal static ConfigEntry ExternalAssetsEnabled = null; internal static ConfigEntry ExternalAssetsBundlePath = null; internal static ConfigEntry ExternalPlayerPrefabAssetName = null; internal static ConfigEntry ExternalCarPrefabAssetName = null; internal static ConfigEntry DebugLogs = null; internal static ConfigEntry DebugLogIntervalSeconds = null; internal static string RuntimeModeOverride = string.Empty; private Harmony? _harmony; private GameObject? _managerGo; private void Awake() { //IL_0541: Unknown result type (might be due to invalid IL or missing references) //IL_054b: Expected O, but got Unknown //IL_056b: Unknown result type (might be due to invalid IL or missing references) //IL_0575: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; try { Application.runInBackground = true; } catch { } Mode = ((BaseUnityPlugin)this).Config.Bind("Network", "Mode", "Off", "Off | Host | Client"); Port = ((BaseUnityPlugin)this).Config.Bind("Network", "Port", 27777, "UDP port"); HostAddress = ((BaseUnityPlugin)this).Config.Bind("Network", "HostAddress", "127.0.0.1", "Server IP for Client mode"); TickRate = ((BaseUnityPlugin)this).Config.Bind("Network", "TickRate", 20, "Net update rate (Hz)"); ClientTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind("Network", "ClientTimeoutSeconds", 6f, "Host-side timeout (seconds) after which a silent client is considered disconnected and its ghost objects are removed"); AutoDiscovery = ((BaseUnityPlugin)this).Config.Bind("Discovery", "AutoDiscovery", true, "Enable LAN auto-discovery for clients"); DiscoveryPort = ((BaseUnityPlugin)this).Config.Bind("Discovery", "DiscoveryPort", 27778, "UDP port used for LAN discovery broadcast/listen"); DiscoveryIntervalMs = ((BaseUnityPlugin)this).Config.Bind("Discovery", "DiscoveryIntervalMs", 700, "Host broadcast interval (ms)"); SaveKeySyncEnabled = ((BaseUnityPlugin)this).Config.Bind("Sync", "SaveKeySyncEnabled", true, "Replicate sSaveSystem SetString/DeleteKey over the network (progress sync)"); SaveKeyDenySubstrings = ((BaseUnityPlugin)this).Config.Bind("Sync", "SaveKeyDenySubstrings", "pos,rot,camera,cam,look,player,guy,controller,car,vehicle,rb,transform", "Comma-separated substrings; matching keys will NOT be replicated/applied (case-insensitive). Use to avoid syncing per-player runtime state."); SaveKeyPersonalDenySubstrings = ((BaseUnityPlugin)this).Config.Bind("Sync", "SaveKeyPersonalDenySubstrings", "inventory,backpack,bag,hotbar,quickslot,helditem,equipped,loadout,playerinventory", "Additional comma-separated substrings treated as personal player state. These keys are excluded from host->client save snapshots and live save replication so each player keeps their own inventory/loadout."); SharedMoneyEnabled = ((BaseUnityPlugin)this).Config.Bind("Sync", "SharedMoneyEnabled", true, "If true, money becomes shared across all players. Clients send money changes to the host, and everyone displays the host's money."); AutoEnterWorldOnConnect = ((BaseUnityPlugin)this).Config.Bind("Network", "AutoEnterWorldOnConnect", true, "If true, when using the in-game Connect button, the client will try to automatically enter the host's world without going through the save menu (best-effort)."); AutoReconnectEnabled = ((BaseUnityPlugin)this).Config.Bind("Network", "AutoReconnectEnabled", true, "If true (Client mode), automatically retries connection to the last known host if the host becomes unreachable, without restarting the game."); AutoReconnectLostSeconds = ((BaseUnityPlugin)this).Config.Bind("Network", "AutoReconnectLostSeconds", 6f, "Client considers the connection lost if no packets are received from host for this many seconds (then handshake restarts)."); AutoReconnectBackoffMaxSeconds = ((BaseUnityPlugin)this).Config.Bind("Network", "AutoReconnectBackoffMaxSeconds", 12f, "Maximum delay between reconnect attempts (seconds)."); Log.LogInfo((object)$"Config: AutoEnterWorldOnConnect={AutoEnterWorldOnConnect.Value}"); ClientReceivesHostSaveOnJoin = ((BaseUnityPlugin)this).Config.Bind("Sync", "ClientReceivesHostSaveOnJoin", true, "If true, host sends a full save snapshot when the client joins, and the client applies it (so the session starts from host progress)."); ClientWipeLocalSaveOnJoin = ((BaseUnityPlugin)this).Config.Bind("Sync", "ClientWipeLocalSaveOnJoin", true, "If true (and ClientReceivesHostSaveOnJoin is true), the client first deletes its local allowed save keys before applying the host snapshot."); PlayerPositionsEnabled = ((BaseUnityPlugin)this).Config.Bind("Positions", "Enabled", true, "If true, host persists player positions by nickname per-save-id, and sends them to joining clients."); PlayerPositionsSaveIdOverride = ((BaseUnityPlugin)this).Config.Bind("Positions", "SaveIdOverride", "", "Optional stable id used to select the positions file. Leave empty to auto-detect; fallback is 'default'."); PlayerPositionsClientTeleportOnJoin = ((BaseUnityPlugin)this).Config.Bind("Positions", "ClientTeleportOnJoin", true, "If true, after receiving positions from host, client teleports its local player to the saved position for its nickname (best-effort)."); Nickname = ((BaseUnityPlugin)this).Config.Bind("Profile", "Nickname", Environment.UserName, "Player nickname shown above remote players/cars"); RemoteAvatarYawOffsetDegrees = ((BaseUnityPlugin)this).Config.Bind("Visual", "RemoteAvatarYawOffsetDegrees", -90f, "Yaw offset (degrees) applied to remote player avatars so they face correctly. Typical values: -90 or 90"); RemoteHeldPayloadOffsetX = ((BaseUnityPlugin)this).Config.Bind("Visual", "RemoteHeldPayloadOffsetX", 0f, "Local X offset for the held payload visual on remote players."); RemoteHeldPayloadOffsetY = ((BaseUnityPlugin)this).Config.Bind("Visual", "RemoteHeldPayloadOffsetY", 1.05f, "Local Y offset for the held payload visual on remote players."); RemoteHeldPayloadOffsetZ = ((BaseUnityPlugin)this).Config.Bind("Visual", "RemoteHeldPayloadOffsetZ", 0.35f, "Local Z offset for the held payload visual on remote players."); RemoteHeldPayloadUniformScale = ((BaseUnityPlugin)this).Config.Bind("Visual", "RemoteHeldPayloadUniformScale", 1f, "Uniform scale multiplier for the held payload visual on remote players."); CarSoundSyncEnabled = ((BaseUnityPlugin)this).Config.Bind("Sync", "CarSoundSyncEnabled", true, "If true, sync car sound events (currently horn/beep) across networked players."); CarSoundSyncMode = ((BaseUnityPlugin)this).Config.Bind("Sync", "CarSoundSyncMode", "All", "Car sound sync mode: All | HornOnly. 'All' syncs horn + tires + impacts."); CarSoundSyncHornOnly = ((BaseUnityPlugin)this).Config.Bind("Sync", "CarSoundSyncHornOnly", true, "If true, only horn/beep events are synced to avoid mixed engine/skid/crash noise."); CarSoundSyncMinIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind("Sync", "CarSoundSyncMinIntervalSeconds", 0.12f, "Minimum interval between synced local car sound events (seconds)."); ExternalAssetsEnabled = ((BaseUnityPlugin)this).Config.Bind("ExternalAssets", "Enabled", false, "If true, try to load Player/Car visual prefabs from an external AssetBundle (built from the extracted project). Falls back to in-game cloning if unavailable."); ExternalAssetsBundlePath = ((BaseUnityPlugin)this).Config.Bind("ExternalAssets", "BundlePath", "", "Path to AssetBundle file. Absolute path or relative to BepInEx folders. Example: 'plugins/EasyDeliveryCoLanCoop/edc_lancoop_models' (no extension required)."); ExternalPlayerPrefabAssetName = ((BaseUnityPlugin)this).Config.Bind("ExternalAssets", "PlayerPrefabAssetName", "", "Asset name/path inside the bundle for the player prefab. Example: 'Assets/Prefabs/Player.prefab'."); ExternalCarPrefabAssetName = ((BaseUnityPlugin)this).Config.Bind("ExternalAssets", "CarPrefabAssetName", "", "Asset name/path inside the bundle for the car/truck prefab. Example: 'Assets/Prefabs/Car.prefab'."); DebugLogs = ((BaseUnityPlugin)this).Config.Bind("Debug", "DebugLogs", false, "Enable extra network snapshot logging"); DebugLogIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind("Debug", "DebugLogIntervalSeconds", 2f, "Minimum seconds between debug log lines"); ApplyCommandLineModeOverride(); _harmony = new Harmony("coopmod.easydeliveryco.lancoop"); _harmony.PatchAll(typeof(Plugin).Assembly); _managerGo = new GameObject("EasyDeliveryCoLanCoop.Manager"); Object.DontDestroyOnLoad((Object)(object)_managerGo); ((Object)_managerGo).hideFlags = (HideFlags)61; _managerGo.AddComponent(); _managerGo.AddComponent(); Log.LogInfo((object)string.Format("{0} {1} loaded. Mode={2}, Port={3}", "EasyDeliveryCoLanCoop", "0.2.19", GetEffectiveNetworkMode(), Port.Value)); } private static void ApplyCommandLineModeOverride() { try { string[] commandLineArgs = Environment.GetCommandLineArgs(); for (int i = 0; i < commandLineArgs.Length; i++) { string text = commandLineArgs[i] ?? string.Empty; if (text.Equals("--lancoop-server", StringComparison.OrdinalIgnoreCase) || text.Equals("--lancoop-host", StringComparison.OrdinalIgnoreCase)) { RuntimeModeOverride = "Host"; Log.LogInfo((object)"Launch arg detected: Host mode override enabled (--lancoop-server)."); } else if (text.Equals("--lancoop-client", StringComparison.OrdinalIgnoreCase)) { RuntimeModeOverride = "Client"; Log.LogInfo((object)"Launch arg detected: Client mode override enabled (--lancoop-client)."); } else if (text.Equals("--lancoop-off", StringComparison.OrdinalIgnoreCase)) { RuntimeModeOverride = "Off"; Log.LogInfo((object)"Launch arg detected: Off mode override enabled (--lancoop-off)."); } } } catch { } } internal static string GetEffectiveNetworkMode() { if (!string.IsNullOrWhiteSpace(RuntimeModeOverride)) { return RuntimeModeOverride; } if (Mode == null) { return string.Empty; } return Mode.Value ?? string.Empty; } internal static bool IsSaveKeyAllowed(string key) { if (!SaveKeySyncEnabled.Value) { return false; } if (string.IsNullOrEmpty(key)) { return false; } string value = SaveKeyDenySubstrings.Value; if (string.IsNullOrWhiteSpace(value)) { return true; } string[] array = value.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0 && key.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { return false; } } return true; } internal static bool IsPersonalSaveKey(string key) { if (string.IsNullOrEmpty(key)) { return false; } if (ContainsAnyCsvSubstring(key, SaveKeyPersonalDenySubstrings?.Value)) { return true; } string text = key.ToLowerInvariant(); if (text.Contains("inventory") || text.Contains("backpack") || text.Contains("loadout")) { return true; } if (text.Contains("hotbar") || text.Contains("quickslot") || text.Contains("equipped")) { return true; } if (text.Contains("helditem") || text.Contains("playerinventory")) { return true; } return false; } internal static bool IsSaveKeyAllowedForWorldSync(string key) { if (!IsSaveKeyAllowed(key)) { return false; } if (IsPersonalSaveKey(key)) { return false; } return true; } private static bool ContainsAnyCsvSubstring(string key, string? csv) { if (string.IsNullOrWhiteSpace(csv)) { return false; } string[] array = csv.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0 && key.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } internal static string SanitizeNickname(string? nick) { if (nick == null) { nick = string.Empty; } nick = nick.Trim(); if (nick.Length == 0) { nick = "Player"; } if (nick.Length > 24) { nick = nick.Substring(0, 24); } return nick; } internal static bool IsHornOnlyCarSoundMode() { string text = CarSoundSyncMode?.Value?.Trim() ?? string.Empty; if (text.Equals("HornOnly", StringComparison.OrdinalIgnoreCase) || text.Equals("Horn", StringComparison.OrdinalIgnoreCase)) { return true; } if (text.Equals("All", StringComparison.OrdinalIgnoreCase) || text.Equals("Full", StringComparison.OrdinalIgnoreCase)) { return false; } if (CarSoundSyncHornOnly != null) { return CarSoundSyncHornOnly.Value; } return false; } internal static string SanitizeFileName(string? s) { if (s == null) { s = string.Empty; } s = s.Trim(); if (s.Length == 0) { return "default"; } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); StringBuilder stringBuilder = new StringBuilder(s.Length); foreach (char c in s) { bool flag = false; for (int j = 0; j < invalidFileNameChars.Length; j++) { if (c == invalidFileNameChars[j]) { flag = true; break; } } stringBuilder.Append(flag ? '_' : c); } string text = stringBuilder.ToString().Trim(); if (text.Length != 0) { return text; } return "default"; } private void OnDestroy() { try { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch { } try { if ((Object)(object)_managerGo != (Object)null) { Object.Destroy((Object)(object)_managerGo); } } catch { } } } } namespace EasyDeliveryCoLanCoop.Patches { [HarmonyPatch] internal static class SaveSystemPatches { private static Type? TargetType() { return AccessTools.TypeByName("sSaveSystem"); } [HarmonyTargetMethods] private static IEnumerable TargetMethods() { Type t = TargetType(); if (t == null) { yield break; } string[] array = new string[2] { "SetString", "DeleteKey" }; foreach (string text in array) { MethodInfo methodInfo = AccessTools.Method(t, text, (Type[])null, (Type[])null); if (methodInfo != null) { yield return methodInfo; } } } [HarmonyPrefix] private static void Prefix(MethodBase __originalMethod, object[] __args) { LanCoopManager instance = LanCoopManager.Instance; if ((Object)(object)instance == (Object)null || instance.IsApplyingRemote) { return; } if (__originalMethod.Name == "SetString" && __args.Length >= 2) { string text = __args[0]?.ToString() ?? string.Empty; string value = __args[1]?.ToString() ?? string.Empty; if (text.Length != 0 && Plugin.IsSaveKeyAllowedForWorldSync(text)) { instance.SendSaveDelta(text, value); } } else if (__originalMethod.Name == "DeleteKey" && __args.Length >= 1) { string text2 = __args[0]?.ToString() ?? string.Empty; if (text2.Length != 0 && Plugin.IsSaveKeyAllowedForWorldSync(text2)) { instance.SendSaveDelete(text2); } } } } }