using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Threading; using BepInEx; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using LCBridge.Patches; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LCBridge")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0")] [assembly: AssemblyProduct("LCBridge")] [assembly: AssemblyTitle("LCBridge")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LCBridge { public static class BridgeServer { private static TcpListener _listener; private static Thread _acceptThread; private static volatile bool _running; private static readonly List _clients = new List(); private static readonly object _lock = new object(); public static void Start(int port) { if (!_running) { _running = true; _listener = new TcpListener(IPAddress.Loopback, port); _listener.Start(); _acceptThread = new Thread(AcceptLoop) { IsBackground = true, Name = "LCBridge-Accept" }; _acceptThread.Start(); } } public static void Stop() { _running = false; try { _listener?.Stop(); } catch { } lock (_lock) { foreach (TcpClient client in _clients) { try { client.Close(); } catch { } } _clients.Clear(); } } private static void AcceptLoop() { while (_running) { try { TcpClient tcpClient = _listener.AcceptTcpClient(); if (Handshake(tcpClient)) { lock (_lock) { _clients.Add(tcpClient); } ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"Оверлей подключился к мосту."); } } else { try { tcpClient.Close(); } catch { } } } catch { if (!_running) { break; } } } } private static bool Handshake(TcpClient client) { try { NetworkStream stream = client.GetStream(); byte[] array = new byte[4096]; int num = stream.Read(array, 0, array.Length); if (num <= 0) { return false; } string text = Encoding.UTF8.GetString(array, 0, num); string text2 = null; string[] array2 = text.Split(new string[1] { "\r\n" }, StringSplitOptions.None); foreach (string text3 in array2) { if (text3.StartsWith("Sec-WebSocket-Key:", StringComparison.OrdinalIgnoreCase)) { text2 = text3.Substring("Sec-WebSocket-Key:".Length).Trim(); break; } } if (text2 == null) { return false; } string text4; using (SHA1 sHA = SHA1.Create()) { text4 = Convert.ToBase64String(sHA.ComputeHash(Encoding.UTF8.GetBytes(text2 + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))); } string s = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: " + text4 + "\r\n\r\n"; byte[] bytes = Encoding.UTF8.GetBytes(s); stream.Write(bytes, 0, bytes.Length); return true; } catch { return false; } } public static void Broadcast(string text) { byte[] array = EncodeTextFrame(text); List list = null; lock (_lock) { foreach (TcpClient client in _clients) { try { if (!client.Connected) { (list ?? (list = new List())).Add(client); } else { client.GetStream().Write(array, 0, array.Length); } } catch { (list ?? (list = new List())).Add(client); } } if (list == null) { return; } foreach (TcpClient item in list) { _clients.Remove(item); try { item.Close(); } catch { } } } } private static byte[] EncodeTextFrame(string text) { byte[] bytes = Encoding.UTF8.GetBytes(text); int num = bytes.Length; byte[] array; if (num <= 125) { array = new byte[2] { 0, (byte)num }; } else if (num <= 65535) { array = new byte[4] { 0, 126, (byte)((num >> 8) & 0xFF), (byte)(num & 0xFF) }; } else { array = new byte[10] { 0, 127, 0, 0, 0, 0, 0, 0, 0, 0 }; for (int i = 0; i < 8; i++) { array[9 - i] = (byte)((num >> 8 * i) & 0xFF); } } array[0] = 129; byte[] array2 = new byte[array.Length + bytes.Length]; Buffer.BlockCopy(array, 0, array2, 0, array.Length); Buffer.BlockCopy(bytes, 0, array2, array.Length, bytes.Length); return array2; } } public class BridgeTicker : MonoBehaviour { private float _timer; private const float Interval = 1f; private string _lastPayload; private int _lastMobCount = -1; private void Update() { _timer += Time.deltaTime; if (!(_timer < 1f)) { _timer = 0f; GameState.TickStats(); RunStats.Tick(); string text = BuildJson(); if (text != _lastPayload) { _lastPayload = text; BridgeServer.Broadcast(text); } } } private string BuildJson() { (int alive, int total) crew = GameState.GetCrew(); int item = crew.alive; int item2 = crew.total; int deaths = GameState.GetDeaths(); int localHealth = GameState.GetLocalHealth(); string moonName = GameState.GetMoonName(); string weatherTweaksWeather = GameState.GetWeatherTweaksWeather(); string s = ((!string.IsNullOrEmpty(weatherTweaksWeather)) ? weatherTweaksWeather : GameState.GetVanillaWeather()); string brutalEvent = GameState.GetBrutalEvent(); (List outside, List inside) monsters = GameState.GetMonsters(); List item3 = monsters.outside; List item4 = monsters.inside; List traps = GameState.GetTraps(); bool onMoon = GameState.GetOnMoon(); bool loading = GameState.GetLoading(); bool inGame = GameState.GetInGame(); int resetToken = GameState.GetResetToken(); int levelScrap = GameState.GetLevelScrap(); string topKiller = GameState.GetTopKiller(); string topMonster = GameState.GetTopMonster(); string deadliestEvent = GameState.GetDeadliestEvent(); int num = item3.Count + item4.Count; if (num != _lastMobCount) { _lastMobCount = num; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)string.Format("[monsters] улица={0} ({1}) | комплекс={2} ({3})", item3.Count, string.Join(",", item3), item4.Count, string.Join(",", item4))); } } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append('{'); stringBuilder.Append("\"type\":\"bridge\","); stringBuilder.Append("\"deaths\":").Append(deaths).Append(','); stringBuilder.Append("\"alive\":").Append(item).Append(','); stringBuilder.Append("\"total\":").Append(item2).Append(','); stringBuilder.Append("\"health\":").Append(localHealth).Append(','); stringBuilder.Append("\"moonName\":").Append(JsonStr(moonName)).Append(','); stringBuilder.Append("\"weatherFull\":").Append(JsonStr(s)).Append(','); stringBuilder.Append("\"brutalEvent\":").Append(JsonStr(brutalEvent ?? "")).Append(','); stringBuilder.Append("\"onMoon\":").Append(onMoon ? "true" : "false").Append(','); stringBuilder.Append("\"loading\":").Append(loading ? "true" : "false").Append(','); stringBuilder.Append("\"inGame\":").Append(inGame ? "true" : "false").Append(','); stringBuilder.Append("\"resetToken\":").Append(resetToken).Append(','); stringBuilder.Append("\"levelScrap\":").Append(levelScrap).Append(','); stringBuilder.Append("\"topKiller\":").Append(JsonStr(topKiller ?? "")).Append(','); stringBuilder.Append("\"topMonster\":").Append(JsonStr(topMonster ?? "")).Append(','); stringBuilder.Append("\"deadliestEvent\":").Append(JsonStr(deadliestEvent ?? "")).Append(','); stringBuilder.Append("\"monstersOutside\":").Append(JsonArr(item3)).Append(','); stringBuilder.Append("\"monstersInside\":").Append(JsonArr(item4)).Append(','); stringBuilder.Append("\"traps\":").Append(JsonArr(traps)).Append(','); stringBuilder.Append("\"run\":").Append(RunStats.ToJson()); stringBuilder.Append('}'); return stringBuilder.ToString(); } private static string JsonArr(List items) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append('['); for (int i = 0; i < items.Count; i++) { if (i > 0) { stringBuilder.Append(','); } stringBuilder.Append(JsonStr(items[i])); } stringBuilder.Append(']'); return stringBuilder.ToString(); } private static string JsonStr(string s) { if (s == null) { return "\"\""; } StringBuilder stringBuilder = new StringBuilder(s.Length + 2); stringBuilder.Append('"'); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture)); } else { stringBuilder.Append(c); } } stringBuilder.Append('"'); return stringBuilder.ToString(); } } internal static class EnemyResolver { private static readonly string[] _turretMarkers = new string[3] { "ToilHeadController", "MantiToilController", "TurretHeadController" }; private static readonly string[] _slayerMarkers = new string[3] { "ToilSlayerController", "MantiSlayerController", "SlayerController" }; public static string Resolve(object enemyAiObj) { string baseName = GetBaseName(enemyAiObj); if (string.IsNullOrEmpty(baseName)) { return null; } try { switch (TurretKind(enemyAiObj)) { case 2: return baseName + "+Turret+Slayer"; case 1: return baseName + "+Turret"; } } catch { } return baseName; } private static string GetBaseName(object enemyAiObj) { try { EnemyAI val = (EnemyAI)((enemyAiObj is EnemyAI) ? enemyAiObj : null); if ((Object)(object)val != (Object)null && (Object)(object)val.enemyType != (Object)null && !string.IsNullOrEmpty(val.enemyType.enemyName)) { return val.enemyType.enemyName; } } catch { } return null; } private static int TurretKind(object enemyAiObj) { MonoBehaviour val = (MonoBehaviour)((enemyAiObj is MonoBehaviour) ? enemyAiObj : null); if ((Object)(object)val == (Object)null) { return 0; } Component[] componentsInChildren = ((Component)val).GetComponentsInChildren(true); if (componentsInChildren == null) { return 0; } int num = 0; Component[] array = componentsInChildren; foreach (Component val2 in array) { if ((Object)(object)val2 == (Object)null) { continue; } string name = ((object)val2).GetType().Name; if (string.IsNullOrEmpty(name)) { continue; } string[] slayerMarkers = _slayerMarkers; foreach (string value in slayerMarkers) { if (name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return 2; } } if (num != 0) { continue; } slayerMarkers = _turretMarkers; foreach (string value2 in slayerMarkers) { if (name.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0) { num = 1; break; } } } return num; } } public static class GameState { private static int _deaths = 0; private static readonly HashSet _deadThisRound = new HashSet(); private static int _resetToken = 0; private static readonly Dictionary _killerCounts = new Dictionary(); private static readonly Dictionary _eventDeaths = new Dictionary(); private static readonly Dictionary _monsterSeen = new Dictionary(); private static int _landedScrap; private static bool _wasLanded; private static bool _scrapLocked; private static Type _emType; private static FieldInfo _curEventsField; private static bool _bcSearched; private static readonly Dictionary _nameMethodCache = new Dictionary(); private static Type _wtVarsType; private static MethodInfo _wtGetCurrent; private static bool _wtSearched; public static int GetResetToken() { return _resetToken; } public static void RegisterDeath(PlayerControllerB p, string killer = null) { bool flag = false; try { int item = (int)p.playerClientId; if (_deadThisRound.Add(item)) { _deaths++; flag = true; } } catch { _deaths++; flag = true; } if (!flag) { return; } try { if (!string.IsNullOrEmpty(killer)) { _killerCounts.TryGetValue(killer, out var value); _killerCounts[killer] = value + 1; } string brutalEvent = GetBrutalEvent(); if (!string.IsNullOrEmpty(brutalEvent) && brutalEvent != "—") { _eventDeaths.TryGetValue(brutalEvent, out var value2); _eventDeaths[brutalEvent] = value2 + 1; } RunStats.OnDeath(killer); } catch { } } public static void OnNewRound() { _deadThisRound.Clear(); } public static void ResetDeaths() { _deaths = 0; _deadThisRound.Clear(); _resetToken++; _killerCounts.Clear(); _eventDeaths.Clear(); _monsterSeen.Clear(); RunStats.ResetRun(); } public static int GetDeaths() { return _deaths; } public static void TickStats() { try { RoundManager instance = RoundManager.Instance; if ((Object)(object)instance == (Object)null || instance.SpawnedEnemies == null) { return; } HashSet hashSet = new HashSet(); foreach (EnemyAI spawnedEnemy in instance.SpawnedEnemies) { if (!((Object)(object)spawnedEnemy == (Object)null) && !spawnedEnemy.isEnemyDead) { string text = EnemyResolver.Resolve(spawnedEnemy); if (text != null) { hashSet.Add(text); } } } foreach (string item in hashSet) { _monsterSeen.TryGetValue(item, out var value); _monsterSeen[item] = value + 1; } } catch { } } private static string TopOf(Dictionary dict) { string result = null; int num = 0; foreach (KeyValuePair item in dict) { if (item.Value > num) { num = item.Value; result = item.Key; } } return result; } public static string GetTopKiller() { return TopOf(_killerCounts); } public static string GetTopMonster() { return TopOf(_monsterSeen); } public static string GetDeadliestEvent() { return TopOf(_eventDeaths); } public static (int alive, int total) GetCrew() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return (alive: 0, total: 0); } int num = 0; int num2 = 0; PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if (!((Object)(object)val == (Object)null) && (val.isPlayerControlled || val.isPlayerDead)) { num++; if (!val.isPlayerDead) { num2++; } } } if (num == 0) { num = 1; } return (alive: num2, total: num); } catch { return (alive: 0, total: 0); } } public static int GetLocalHealth() { try { PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null) { return 0; } return val.health; } catch { return 0; } } public static (List outside, List inside) GetMonsters() { List list = new List(); List list2 = new List(); try { RoundManager instance = RoundManager.Instance; if ((Object)(object)instance == (Object)null || instance.SpawnedEnemies == null) { return (outside: list, inside: list2); } Dictionary dictionary = new Dictionary(); Dictionary dictionary2 = new Dictionary(); foreach (EnemyAI spawnedEnemy in instance.SpawnedEnemies) { if ((Object)(object)spawnedEnemy == (Object)null || spawnedEnemy.isEnemyDead) { continue; } string key = "Unknown"; try { string text = EnemyResolver.Resolve(spawnedEnemy); if (!string.IsNullOrEmpty(text)) { key = text; } } catch { } Dictionary obj2 = (spawnedEnemy.isOutside ? dictionary : dictionary2); obj2.TryGetValue(key, out var value); obj2[key] = value + 1; } foreach (KeyValuePair item in dictionary) { list.Add((item.Value > 1) ? $"{item.Key} x{item.Value}" : item.Key); } foreach (KeyValuePair item2 in dictionary2) { list2.Add((item2.Value > 1) ? $"{item2.Key} x{item2.Value}" : item2.Key); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("GetMonsters fail: " + ex.Message)); } } return (outside: list, inside: list2); } public static List GetTraps() { List list = new List(); try { Dictionary counts = new Dictionary(); CountType("Turret"); CountType("Landmine"); CountType("Spike Trap"); foreach (KeyValuePair item in counts) { list.Add((item.Value > 1) ? $"{item.Key} x{item.Value}" : item.Key); } void CountType(string label) where T : Object { try { T[] array = Object.FindObjectsOfType(); if (array != null && array.Length != 0) { counts.TryGetValue(label, out var value); counts[label] = value + array.Length; } } catch { } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("GetTraps fail: " + ex.Message)); } } return list; } public static int GetLevelScrap() { try { RoundManager instance = RoundManager.Instance; if (GetOnMoon()) { if (!_wasLanded) { _wasLanded = true; _scrapLocked = false; _landedScrap = 0; } if (!_scrapLocked && (Object)(object)instance != (Object)null) { int num = (int)instance.totalScrapValueInLevel; if (num > 0) { _landedScrap = num; _scrapLocked = true; } } } else { _wasLanded = false; _scrapLocked = false; _landedScrap = 0; } return _landedScrap; } catch { return _landedScrap; } } private static string GetEventName(object ev) { Type type = ev.GetType(); if (!_nameMethodCache.TryGetValue(type, out var value)) { value = type.GetMethod("Name", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); _nameMethodCache[type] = value; } if (value != null) { try { return value.Invoke(ev, null) as string; } catch { } } return ExtractName(ev); } public static string GetBrutalEvent() { try { if (!_bcSearched) { _bcSearched = true; _emType = FindTypeByFullName("BrutalCompanyMinus.Minus.EventManager") ?? FindTypeFuzzy("BrutalCompany", new string[1] { "EventManager" }); if (_emType != null) { _curEventsField = _emType.GetField("currentEvents", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[reflection] BCMER EventManager=" + _emType.FullName + ", currentEvents field=" + ((_curEventsField != null) ? "OK" : "НЕ НАЙДЕНО"))); } } else { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)"[reflection] BCMER EventManager не найден (мод выключен?)"); } } } if (_curEventsField == null) { return null; } if (!(_curEventsField.GetValue(null) is IEnumerable enumerable)) { return null; } List list = new List(); foreach (object item in enumerable) { if (item != null) { string eventName = GetEventName(item); if (!string.IsNullOrEmpty(eventName)) { list.Add(eventName); } } } if (list.Count == 0) { return null; } return string.Join(", ", list); } catch (Exception ex) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogDebug((object)("GetBrutalEvent fail: " + ex.Message)); } return null; } } public static string GetWeatherTweaksWeather() { try { if (!_wtSearched) { _wtSearched = true; _wtVarsType = FindTypeByFullName("WeatherTweaks.Variables") ?? FindTypeFuzzy("WeatherTweaks", new string[1] { "Variables" }); if (_wtVarsType != null) { _wtGetCurrent = _wtVarsType.GetMethod("GetCurrentWeather", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[reflection] WeatherTweaks Variables=" + _wtVarsType.FullName + ", GetCurrentWeather=" + ((_wtGetCurrent != null) ? "OK" : "НЕ НАЙДЕНО"))); } } else { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)"[reflection] WeatherTweaks Variables не найден (мод выключен?)"); } } } if (_wtGetCurrent == null) { return null; } object obj = _wtGetCurrent.Invoke(null, null); if (obj == null) { return null; } string text = ExtractName(obj); return string.IsNullOrEmpty(text) ? null : text; } catch (Exception ex) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogDebug((object)("GetWeatherTweaks fail: " + ex.Message)); } return null; } } public static string GetVanillaWeather() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.currentLevel == (Object)null) { return "None"; } return ((object)Unsafe.As(ref instance.currentLevel.currentWeather)/*cast due to .constrained prefix*/).ToString(); } catch { return "None"; } } public static string GetMoonName() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.currentLevel == (Object)null) { return "—"; } return instance.currentLevel.PlanetName; } catch { return "—"; } } public static bool GetOnMoon() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.currentLevel == (Object)null) { return false; } if (!instance.shipHasLanded) { return false; } string text = (instance.currentLevel.PlanetName ?? "").ToLowerInvariant(); if (text.Contains("gordion") || text.Contains("company")) { return false; } return true; } catch { return false; } } public static bool GetLoading() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return false; } return instance.travellingToNewLevel; } catch { return false; } } public static bool GetInGame() { try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return false; } return instance.shipHasLanded || instance.travellingToNewLevel; } catch { return false; } } public static int GetDayCount() { try { TimeOfDay instance = TimeOfDay.Instance; if ((Object)(object)instance == (Object)null) { return 1; } return (instance.daysUntilDeadline < 0) ? 1 : (3 - instance.daysUntilDeadline); } catch { return 1; } } public static int GetQuotaIndexSafe() { try { TimeOfDay instance = TimeOfDay.Instance; if ((Object)(object)instance == (Object)null) { return 1; } return instance.timesFulfilledQuota + 1; } catch { return 1; } } public static int GetShipScrapSafe() { try { int num = 0; GameObject[] array = GameObject.FindGameObjectsWithTag("PhysicsProp"); for (int i = 0; i < array.Length; i++) { GrabbableObject component = array[i].GetComponent(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.itemProperties == (Object)null) && component.itemProperties.isScrap && (component.isInShipRoom || component.isInElevator)) { num += component.scrapValue; } } return num; } catch { return 0; } } public static List> GetShipScrapItems() { List> list = new List>(); try { GameObject[] array = GameObject.FindGameObjectsWithTag("PhysicsProp"); for (int i = 0; i < array.Length; i++) { GrabbableObject component = array[i].GetComponent(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.itemProperties == (Object)null) && component.itemProperties.isScrap && (component.isInShipRoom || component.isInElevator)) { list.Add(new KeyValuePair(((Object)component).GetInstanceID(), component.scrapValue)); } } } catch { } return list; } public static List> GetMonsterInstances() { List> list = new List>(); try { RoundManager instance = RoundManager.Instance; if ((Object)(object)instance == (Object)null || instance.SpawnedEnemies == null) { return list; } foreach (EnemyAI spawnedEnemy in instance.SpawnedEnemies) { if (!((Object)(object)spawnedEnemy == (Object)null) && !spawnedEnemy.isEnemyDead) { string text = EnemyResolver.Resolve(spawnedEnemy); if (text != null) { list.Add(new KeyValuePair(((Object)spawnedEnemy).GetInstanceID(), text)); } } } } catch { } return list; } public static (List outside, List inside) GetMonstersRaw() { List list = new List(); List list2 = new List(); try { RoundManager instance = RoundManager.Instance; if ((Object)(object)instance == (Object)null || instance.SpawnedEnemies == null) { return (outside: list, inside: list2); } foreach (EnemyAI spawnedEnemy in instance.SpawnedEnemies) { if (!((Object)(object)spawnedEnemy == (Object)null) && !spawnedEnemy.isEnemyDead) { string text = EnemyResolver.Resolve(spawnedEnemy); if (text != null) { (spawnedEnemy.isOutside ? list : list2).Add(text); } } } } catch { } return (outside: list, inside: list2); } public static bool GetInsideFactorySafe() { try { PlayerControllerB val = StartOfRound.Instance?.localPlayerController; return (Object)(object)val != (Object)null && val.isInsideFactory; } catch { return false; } } private static Type FindTypeByFullName(string fullName) { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = null; try { type = assembly.GetType(fullName, throwOnError: false); } catch { } if (type != null) { return type; } } } catch { } return null; } private static Type FindTypeFuzzy(string asmNameContains, string[] typeNameCandidates) { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { string text = assembly.GetName().Name ?? ""; if (text.IndexOf(asmNameContains, StringComparison.OrdinalIgnoreCase) < 0) { continue; } Type[] source; try { source = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { source = ex.Types.Where((Type t) => t != null).ToArray(); } foreach (string cand in typeNameCandidates) { Type type = source.FirstOrDefault((Type t) => string.Equals(t.Name, cand, StringComparison.OrdinalIgnoreCase)); if (type != null) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[reflection] нашёл тип " + type.FullName + " в " + text)); } return type; } } } } catch (Exception ex2) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogDebug((object)("FindTypeFuzzy fail: " + ex2.Message)); } } return null; } private static object ReadStaticMember(Type t, string name) { try { FieldInfo field = t.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { if (field.IsStatic) { return field.GetValue(null); } object singletonInstance = GetSingletonInstance(t); if (singletonInstance != null) { return field.GetValue(singletonInstance); } } PropertyInfo property = t.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead) { MethodInfo? getMethod = property.GetGetMethod(nonPublic: true); if ((object)getMethod != null && getMethod.IsStatic) { return property.GetValue(null); } object singletonInstance2 = GetSingletonInstance(t); if (singletonInstance2 != null) { return property.GetValue(singletonInstance2); } } } catch { } return null; } private static object GetSingletonInstance(Type t) { try { PropertyInfo propertyInfo = t.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public) ?? t.GetProperty("instance", BindingFlags.Static | BindingFlags.Public); if (propertyInfo != null) { return propertyInfo.GetValue(null); } FieldInfo fieldInfo = t.GetField("Instance", BindingFlags.Static | BindingFlags.Public) ?? t.GetField("instance", BindingFlags.Static | BindingFlags.Public); if (fieldInfo != null) { return fieldInfo.GetValue(null); } } catch { } return null; } private static string ExtractName(object val) { if (val == null) { return null; } try { if (val is string result) { return result; } if (val.GetType().IsEnum) { return val.ToString(); } Type type = val.GetType(); PropertyInfo propertyInfo = type.GetProperty("Name") ?? type.GetProperty("name"); if (propertyInfo != null) { string text = propertyInfo.GetValue(val) as string; if (!string.IsNullOrEmpty(text)) { return text; } } FieldInfo fieldInfo = type.GetField("Name") ?? type.GetField("name"); if (fieldInfo != null) { string text2 = fieldInfo.GetValue(val) as string; if (!string.IsNullOrEmpty(text2)) { return text2; } } string text3 = val.ToString(); if (!string.IsNullOrEmpty(text3) && text3 != type.FullName && text3 != type.Name) { return text3; } } catch { } return null; } } [BepInPlugin("gdlp.lcbridge", "LCBridge", "1.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string GUID = "gdlp.lcbridge"; public const string NAME = "LCBridge"; public const string VERSION = "1.1.0"; private Harmony _harmony; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } private void Awake() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; int value = ((BaseUnityPlugin)this).Config.Bind("Server", "WebSocketPort", 8181, "Порт WebSocket-сервера моста. Оверлей должен слушать этот же порт.").Value; Log.LogInfo((object)"LCBridge v1.1.0 запускается..."); _harmony = new Harmony("gdlp.lcbridge"); _harmony.PatchAll(typeof(PlayerControllerB_Patches)); _harmony.PatchAll(typeof(StartOfRound_Patches)); Log.LogInfo((object)"Harmony-патчи применены."); try { BridgeServer.Start(value); Log.LogInfo((object)$"WebSocket-сервер моста запущен на ws://localhost:{value}"); } catch (Exception arg) { Log.LogError((object)$"Не удалось запустить сервер: {arg}"); } ((Component)this).gameObject.AddComponent(); Log.LogInfo((object)"LCBridge готов."); } private void OnDestroy() { try { BridgeServer.Stop(); } catch { } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } public static class RunStats { public class QuotaSlice { public int index; public int scrapStart; public int scrapEnd; public int seconds; public int deaths; } private class MoonStat { public int visits; public int profit; public int seconds; } private static readonly List _quotas = new List(); private static QuotaSlice _curQuota; private static int _lastQuotaIndex = -1; private static readonly Dictionary _moons = new Dictionary(); private static string _curMoon; private static int _moonScrapStart; private static readonly Dictionary _monsterTime = new Dictionary(); private static readonly Dictionary _monsterCount = new Dictionary(); private static readonly HashSet _seenMonsterIds = new HashSet(); private static int _peakMonsters; private static readonly Dictionary _collectedScrap = new Dictionary(); private static int _secInside; private static int _secOutside; private static readonly List _timeline = new List(); private static int _lastDayLogged = -1; private static string _lastEventLogged; private static int _runSeconds; public static void ResetRun() { _quotas.Clear(); _curQuota = null; _lastQuotaIndex = -1; _moons.Clear(); _curMoon = null; _moonScrapStart = 0; _monsterTime.Clear(); _peakMonsters = 0; _monsterCount.Clear(); _seenMonsterIds.Clear(); _collectedScrap.Clear(); _secInside = 0; _secOutside = 0; _timeline.Clear(); _lastDayLogged = -1; _lastEventLogged = null; _runSeconds = 0; } public static void OnDeath(string killer) { try { int dayCount = GameState.GetDayCount(); string moonName = GameState.GetMoonName(); string arg = (string.IsNullOrEmpty(killer) ? "?" : killer); _timeline.Add($"{dayCount}|death|{arg}@{moonName}"); if (_curQuota != null) { _curQuota.deaths++; } } catch { } } public static void Tick() { try { if (!GameState.GetInGame()) { return; } _runSeconds++; int dayCount = GameState.GetDayCount(); int quotaIndexSafe = GameState.GetQuotaIndexSafe(); bool onMoon = GameState.GetOnMoon(); string moonName = GameState.GetMoonName(); string brutalEvent = GameState.GetBrutalEvent(); foreach (KeyValuePair shipScrapItem in GameState.GetShipScrapItems()) { if (!_collectedScrap.ContainsKey(shipScrapItem.Key)) { _collectedScrap[shipScrapItem.Key] = shipScrapItem.Value; } } int num = 0; foreach (int value in _collectedScrap.Values) { num += value; } if (quotaIndexSafe != _lastQuotaIndex) { if (_curQuota != null) { _curQuota.scrapEnd = num; _quotas.Add(_curQuota); } _curQuota = new QuotaSlice { index = quotaIndexSafe, scrapStart = num, scrapEnd = num, seconds = 0, deaths = 0 }; _lastQuotaIndex = quotaIndexSafe; } if (_curQuota != null) { _curQuota.seconds++; _curQuota.scrapEnd = num; } if (onMoon && !string.IsNullOrEmpty(moonName)) { if (_curMoon != moonName) { _curMoon = moonName; _moonScrapStart = num; if (!_moons.ContainsKey(moonName)) { _moons[moonName] = new MoonStat(); } _moons[moonName].visits++; } MoonStat moonStat = _moons[moonName]; moonStat.seconds++; int num2 = num - _moonScrapStart; if (num2 > 0) { moonStat.profit += num2; _moonScrapStart = num; } } else { _curMoon = null; } List> monsterInstances = GameState.GetMonsterInstances(); foreach (KeyValuePair item in monsterInstances) { Add(_monsterTime, item.Value); if (_seenMonsterIds.Add(item.Key)) { Add(_monsterCount, item.Value); } } int count = monsterInstances.Count; if (count > _peakMonsters) { _peakMonsters = count; } if (onMoon) { if (GameState.GetInsideFactorySafe()) { _secInside++; } else { _secOutside++; } } if (dayCount != _lastDayLogged && dayCount > 0) { _lastDayLogged = dayCount; _timeline.Add($"{dayCount}|day|{moonName}"); } if (!string.IsNullOrEmpty(brutalEvent) && brutalEvent != "—" && brutalEvent != _lastEventLogged) { _lastEventLogged = brutalEvent; _timeline.Add($"{dayCount}|event|{brutalEvent}"); } } catch { } } private static void Add(Dictionary d, string k) { if (!string.IsNullOrEmpty(k)) { d.TryGetValue(k, out var value); d[k] = value + 1; } } public static string ToJson() { try { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append('{'); stringBuilder.Append("\"quotas\":["); List list = new List(_quotas); if (_curQuota != null) { list.Add(_curQuota); } for (int i = 0; i < list.Count; i++) { QuotaSlice quotaSlice = list[i]; if (i > 0) { stringBuilder.Append(','); } int value = Math.Max(0, quotaSlice.scrapEnd - quotaSlice.scrapStart); stringBuilder.Append('{').Append("\"i\":").Append(quotaSlice.index) .Append(',') .Append("\"money\":") .Append(value) .Append(',') .Append("\"sec\":") .Append(quotaSlice.seconds) .Append(',') .Append("\"deaths\":") .Append(quotaSlice.deaths) .Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"moons\":["); int num = 0; foreach (KeyValuePair item in _moons.OrderByDescending((KeyValuePair x) => x.Value.profit)) { if (num++ > 0) { stringBuilder.Append(','); } stringBuilder.Append('{').Append("\"name\":").Append(JsonStr(item.Key)) .Append(',') .Append("\"visits\":") .Append(item.Value.visits) .Append(',') .Append("\"profit\":") .Append(item.Value.profit) .Append(',') .Append("\"sec\":") .Append(item.Value.seconds) .Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"monsters\":["); int num2 = 0; foreach (KeyValuePair item2 in _monsterCount.OrderByDescending((KeyValuePair x) => x.Value).Take(20)) { if (num2++ > 0) { stringBuilder.Append(','); } _monsterTime.TryGetValue(item2.Key, out var value2); stringBuilder.Append('{').Append("\"name\":").Append(JsonStr(item2.Key)) .Append(',') .Append("\"count\":") .Append(item2.Value) .Append(',') .Append("\"sec\":") .Append(value2) .Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"peak\":").Append(_peakMonsters).Append(','); stringBuilder.Append("\"inside\":").Append(_secInside).Append(','); stringBuilder.Append("\"outside\":").Append(_secOutside).Append(','); stringBuilder.Append("\"runSec\":").Append(_runSeconds).Append(','); stringBuilder.Append("\"timeline\":["); for (int num3 = 0; num3 < _timeline.Count && num3 < 60; num3++) { if (num3 > 0) { stringBuilder.Append(','); } stringBuilder.Append(JsonStr(_timeline[num3])); } stringBuilder.Append(']'); stringBuilder.Append('}'); return stringBuilder.ToString(); } catch { return "{}"; } } private static string JsonStr(string s) { if (s == null) { return "\"\""; } StringBuilder stringBuilder = new StringBuilder("\""); foreach (char c in s) { switch (c) { case '"': case '\\': stringBuilder.Append('\\').Append(c); break; case '\n': case '\r': stringBuilder.Append(' '); break; default: stringBuilder.Append(c); break; } } stringBuilder.Append('"'); return stringBuilder.ToString(); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "LCBridge"; public const string PLUGIN_NAME = "LCBridge"; public const string PLUGIN_VERSION = "1.1.0"; } } namespace LCBridge.Patches { [HarmonyPatch(typeof(PlayerControllerB))] public static class PlayerControllerB_Patches { [HarmonyPatch("KillPlayer")] [HarmonyPostfix] public static void OnKillPlayer(PlayerControllerB __instance, CauseOfDeath causeOfDeath) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance != (Object)null && __instance.isPlayerDead) { string killer = ResolveKiller(__instance, causeOfDeath); GameState.RegisterDeath(__instance, killer); } } private static string ResolveKiller(PlayerControllerB player, CauseOfDeath cause) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected I4, but got Unknown //IL_00a3: 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_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) try { if ((int)cause != 2) { switch (cause - 5) { case 4: return "Утопление"; case 0: return "Удушье"; case 8: return "Огонь"; case 6: return "Ток"; case 3: return "Раздавлен"; default: { RoundManager instance = RoundManager.Instance; if ((Object)(object)instance == (Object)null || instance.SpawnedEnemies == null) { return "Неизвестно"; } Vector3 position = ((Component)player).transform.position; float num = 25f; string text = null; foreach (EnemyAI spawnedEnemy in instance.SpawnedEnemies) { if (!((Object)(object)spawnedEnemy == (Object)null) && !spawnedEnemy.isEnemyDead) { float num2 = Vector3.Distance(position, ((Component)spawnedEnemy).transform.position); if (num2 < num) { num = num2; text = EnemyResolver.Resolve(spawnedEnemy); } } } return string.IsNullOrEmpty(text) ? "Неизвестно" : text; } } } return "Падение"; } catch { return "Неизвестно"; } } } [HarmonyPatch(typeof(StartOfRound))] public static class StartOfRound_Patches { [HarmonyPatch("ResetShip")] [HarmonyPostfix] public static void OnResetShip() { GameState.ResetDeaths(); } [HarmonyPatch("SetTimeAndPlanetToSavedSettings")] [HarmonyPostfix] public static void OnLoadSavedSettings() { try { StartOfRound instance = StartOfRound.Instance; if (!((Object)(object)instance == (Object)null) && ((instance.gameStats != null && instance.gameStats.daysSpent <= 0) || ((Object)(object)TimeOfDay.Instance != (Object)null && TimeOfDay.Instance.timesFulfilledQuota <= 0 && TimeOfDay.Instance.daysUntilDeadline >= 3))) { GameState.ResetDeaths(); } } catch { } } [HarmonyPatch("StartGame")] [HarmonyPostfix] public static void OnStartGame() { GameState.OnNewRound(); } } }