using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using MoreShipUpgrades.Managers; 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("LGUExpBridge")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Converts Lethal Company XP gains into LateGameUpgrades alternate currency")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+8c5521e37f37e3d76518a16d04e5d1051842f105")] [assembly: AssemblyProduct("LGUExpBridge")] [assembly: AssemblyTitle("LGUExpBridge")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LGUExpBridge { internal static class BXPSaveManager { private static readonly string SaveDir = Path.Combine(Application.persistentDataPath, "LGUExpBridge"); private static readonly string SavePath = Path.Combine(SaveDir, "bxp_saves.json"); private static Dictionary _saves; private static bool _reflResolved; private static FieldInfo _betterXpField; private static FieldInfo _levelField; private static MethodInfo _setBetterXP; private static MethodInfo _saveMethod; [MethodImpl(MethodImplOptions.NoInlining)] internal static void OnSaveLoaded() { if (!Plugin.BetterEXPLoaded) { return; } try { string currentSaveFile = GetCurrentSaveFile(); if (string.IsNullOrEmpty(currentSaveFile)) { Plugin.Log.LogWarning((object)"[BXPSaveManager] Could not determine current save file."); return; } LoadFromDisk(); int num = 0; if (_saves.ContainsKey(currentSaveFile)) { num = _saves[currentSaveFile]; Plugin.Log.LogInfo((object)$"[BXPSaveManager] Restored {num} BXP for save '{currentSaveFile}'."); } else { Plugin.Log.LogInfo((object)("[BXPSaveManager] New save '" + currentSaveFile + "' — starting at 0 BXP.")); } SetBXP(num); } catch (Exception ex) { Plugin.Log.LogError((object)("[BXPSaveManager] Error loading save BXP: " + ex.Message)); } } [MethodImpl(MethodImplOptions.NoInlining)] internal static void PersistCurrentBXP() { if (!Plugin.BetterEXPLoaded) { return; } try { string currentSaveFile = GetCurrentSaveFile(); if (!string.IsNullOrEmpty(currentSaveFile)) { int currentBXP = GetCurrentBXP(); LoadFromDisk(); _saves[currentSaveFile] = currentBXP; SaveToDisk(); Plugin.Log.LogInfo((object)$"[BXPSaveManager] Saved {currentBXP} BXP for save '{currentSaveFile}'."); } } catch (Exception ex) { Plugin.Log.LogError((object)("[BXPSaveManager] Error persisting BXP: " + ex.Message)); } } [MethodImpl(MethodImplOptions.NoInlining)] internal static void OnSaveReset() { if (!Plugin.BetterEXPLoaded) { return; } try { string currentSaveFile = GetCurrentSaveFile(); if (!string.IsNullOrEmpty(currentSaveFile)) { LoadFromDisk(); if (_saves.Remove(currentSaveFile)) { SaveToDisk(); Plugin.Log.LogInfo((object)("[BXPSaveManager] Cleared BXP data for reset save '" + currentSaveFile + "'.")); } SetBXP(0); } } catch (Exception ex) { Plugin.Log.LogError((object)("[BXPSaveManager] Error on save reset: " + ex.Message)); } } private static string GetCurrentSaveFile() { if ((Object)(object)GameNetworkManager.Instance == (Object)null) { return null; } return GameNetworkManager.Instance.currentSaveFileName; } [MethodImpl(MethodImplOptions.NoInlining)] private static int GetCurrentBXP() { ResolveReflection(); if (_betterXpField == null) { return 0; } return (int)_betterXpField.GetValue(null); } [MethodImpl(MethodImplOptions.NoInlining)] private static void SetBXP(int value) { ResolveReflection(); if (_setBetterXP != null) { _setBetterXP.Invoke(null, new object[1] { value }); } else { _betterXpField?.SetValue(null, value); _levelField?.SetValue(null, 0); } _saveMethod?.Invoke(null, null); } [MethodImpl(MethodImplOptions.NoInlining)] private static void ResolveReflection() { if (_reflResolved) { return; } Assembly assembly = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly2 in assemblies) { if (assembly2.GetName().Name == "LCBetterEXP") { assembly = assembly2; break; } } if (assembly == null) { _reflResolved = true; return; } Type type = assembly.GetType("LCBetterEXP.patches.XPPatch"); if (type != null) { _betterXpField = type.GetField("betterXp", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _levelField = type.GetField("level", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _setBetterXP = type.GetMethod("SetBetterXP", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } Type type2 = assembly.GetType("LCBetterEXP.patches.Saving"); if (type2 != null) { _saveMethod = type2.GetMethod("Save", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } _reflResolved = true; } private static void LoadFromDisk() { if (_saves != null) { return; } _saves = new Dictionary(); if (!File.Exists(SavePath)) { return; } try { string[] array = File.ReadAllLines(SavePath); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split('='); if (array2.Length == 2 && int.TryParse(array2[1].Trim(), out var result)) { _saves[array2[0].Trim()] = result; } } } catch { _saves = new Dictionary(); } } private static void SaveToDisk() { if (_saves == null) { return; } if (!Directory.Exists(SaveDir)) { Directory.CreateDirectory(SaveDir); } List list = new List(); foreach (KeyValuePair safe in _saves) { list.Add($"{safe.Key}={safe.Value}"); } File.WriteAllLines(SavePath, list); } } internal static class InsideCompletionPatch { private static FieldInfo _allNodesField; private static bool _resolved; private static bool _loggedSanitize; private static bool _loggedMissing; internal static void Apply(Harmony harmony) { //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown try { Assembly assembly = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly2 in assemblies) { if (assembly2.GetName().Name == "LCBetterEXP") { assembly = assembly2; break; } } if (assembly == null) { return; } Type type = assembly.GetType("LCBetterEXP.patches.InsideCompletion"); if (type == null) { Plugin.Log.LogWarning((object)"[InsideCompletionPatch] InsideCompletion type not found."); return; } _allNodesField = type.GetField("AllPlayerUnexploredNodes", BindingFlags.Static | BindingFlags.Public); if (_allNodesField == null) { Plugin.Log.LogWarning((object)"[InsideCompletionPatch] AllPlayerUnexploredNodes field not found."); return; } MethodInfo methodInfo = AccessTools.Method(type, "UpdatePlayer", (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Log.LogWarning((object)"[InsideCompletionPatch] UpdatePlayer method not found."); return; } _resolved = true; HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(InsideCompletionPatch), "Prefix", (Type[])null, (Type[])null)); harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[InsideCompletionPatch] Patched InsideCompletion.UpdatePlayer."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[InsideCompletionPatch] Failed to patch: " + ex.Message)); } } [MethodImpl(MethodImplOptions.NoInlining)] private static bool Prefix(PlayerControllerB player) { if (!_resolved) { return true; } if (!(_allNodesField.GetValue(null) is Dictionary> dictionary)) { return true; } if (!dictionary.ContainsKey(player)) { if (!_loggedMissing) { Plugin.Log.LogWarning((object)"[InsideCompletionPatch] Player not in BetterEXP tracking dictionary — skipping UpdatePlayer."); _loggedMissing = true; } return false; } List list = dictionary[player]; if (list == null) { return true; } for (int num = list.Count - 1; num >= 0; num--) { if ((Object)(object)list[num] == (Object)null) { list.RemoveAt(num); if (!_loggedSanitize) { Plugin.Log.LogWarning((object)"[InsideCompletionPatch] Removed destroyed AI node(s) from BetterEXP tracking — suppressing NRE storm."); _loggedSanitize = true; } } } return true; } } [BepInPlugin("Slayer6409.LGUExpBridge", "LGUExpBridge", "3.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private const string ModGUID = "Slayer6409.LGUExpBridge"; private const string ModName = "LGUExpBridge"; private const string ModVersion = "3.1.0"; internal static ManualLogSource Log; internal static ConfigEntry CurrencyMultiplier; internal static bool BetterEXPLoaded; private readonly Harmony _harmony = new Harmony("Slayer6409.LGUExpBridge"); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; CurrencyMultiplier = ((BaseUnityPlugin)this).Config.Bind("General", "CurrencyMultiplier", 1f, "Multiplier applied to BXP before granting as LGU currency.\n1.0 = exact 1:1 with BXP shown on screen. 2.0 = double."); BetterEXPLoaded = Chainloader.PluginInfos.ContainsKey("Swaggies.BetterEXP"); _harmony.PatchAll(); if (BetterEXPLoaded) { SaveLoadPatch.Apply(_harmony); ResetPatch.Apply(_harmony); InsideCompletionPatch.Apply(_harmony); } Log.LogInfo((object)("LGUExpBridge v3.1.0 loaded! " + $"BetterEXP detected={BetterEXPLoaded}, " + $"CurrencyMultiplier={CurrencyMultiplier.Value}")); } } internal static class ResetPatch { internal static void Apply(Harmony harmony) { TryPatch(harmony, typeof(GameNetworkManager), "ResetSavedGameValues"); TryPatch(harmony, typeof(StartOfRound), "ResetShipFurniture"); Plugin.Log.LogInfo((object)"[ResetPatch] Patch registration complete."); } private static void TryPatch(Harmony harmony, Type targetType, string methodName) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(targetType, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Log.LogWarning((object)("[ResetPatch] " + targetType.Name + "." + methodName + " not found.")); } else { HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(ResetPatch), "OnReset", (Type[])null, (Type[])null)); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)("[ResetPatch] Patched " + targetType.Name + "." + methodName + " successfully.")); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[ResetPatch] Failed to patch " + targetType.Name + "." + methodName + ": " + ex.Message)); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void OnReset() { if (Plugin.BetterEXPLoaded) { Plugin.Log.LogInfo((object)"[ResetPatch] Save reset detected — clearing per-save BXP."); BXPSaveManager.OnSaveReset(); } } } internal static class SaveLoadPatch { internal static void Apply(Harmony harmony) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(StartOfRound), "Start", (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Log.LogWarning((object)"[SaveLoadPatch] StartOfRound.Start not found."); return; } HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(SaveLoadPatch), "OnStartOfRoundStart", (Type[])null, (Type[])null)); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"[SaveLoadPatch] Patched StartOfRound.Start successfully."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[SaveLoadPatch] Failed to patch: " + ex.Message)); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void OnStartOfRoundStart() { Plugin.Log.LogInfo((object)"[SaveLoadPatch] StartOfRound.Start fired — restoring per-save BXP."); BXPSaveManager.OnSaveLoaded(); } } [HarmonyPatch(typeof(HUDManager), "SetPlayerLevel")] internal static class XPPatch { private static bool _reflectionResolved; private static FieldInfo _gainedDataField; private static FieldInfo _totalGainField; [HarmonyPrefix] internal static void Prefix(bool isDead, bool mostProfitable, bool allPlayersDead) { int num = 0; if (Plugin.BetterEXPLoaded) { num = ReadBetterEXPGain(); Plugin.Log.LogInfo((object)$"[XPPatch] BetterEXP totalGain={num}"); } else { if (isDead || (Object)(object)RoundManager.Instance == (Object)null) { return; } int scrapCollectedInLevel = RoundManager.Instance.scrapCollectedInLevel; num = Mathf.RoundToInt((float)scrapCollectedInLevel / 50f); if (mostProfitable) { num *= 2; } Plugin.Log.LogInfo((object)$"[XPPatch] Fallback: scrap={scrapCollectedInLevel}, bxpGain={num}"); } if (num <= 0) { Plugin.Log.LogInfo((object)$"[XPPatch] BXP gain is {num} — no currency granted."); } else if ((Object)(object)CurrencyManager.Instance == (Object)null) { Plugin.Log.LogWarning((object)"[XPPatch] CurrencyManager.Instance is null — skipping."); } else if (!((Object)(object)StartOfRound.Instance?.localPlayerController == (Object)null)) { float value = Plugin.CurrencyMultiplier.Value; int num2 = Mathf.RoundToInt((float)num * value); if (num2 > 0) { CurrencyManager.Instance.AddCurrencyAmount(num2, false); BXPSaveManager.PersistCurrentBXP(); Plugin.Log.LogInfo((object)($"[XPPatch] Granted {num2} LGU currency " + $"(BXP={num}, multiplier={value})")); } } } [MethodImpl(MethodImplOptions.NoInlining)] private static int ReadBetterEXPGain() { try { if (!_reflectionResolved) { Assembly assembly = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly2 in assemblies) { if (assembly2.GetName().Name == "LCBetterEXP") { assembly = assembly2; break; } } if (assembly == null) { Plugin.Log.LogWarning((object)"[XPPatch] Could not find LCBetterEXP assembly."); return 0; } Type type = assembly.GetType("LCBetterEXP.patches.VisualEXPPatch"); if (type == null) { Plugin.Log.LogWarning((object)"[XPPatch] Could not find VisualEXPPatch type."); return 0; } _gainedDataField = type.GetField("gainedBetterEXPData", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (_gainedDataField == null) { Plugin.Log.LogWarning((object)"[XPPatch] Could not find gainedBetterEXPData field."); return 0; } _totalGainField = _gainedDataField.FieldType.GetField("totalGain", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (_totalGainField == null) { Plugin.Log.LogWarning((object)"[XPPatch] Could not find totalGain field."); return 0; } _reflectionResolved = true; Plugin.Log.LogInfo((object)"[XPPatch] BetterEXP reflection resolved successfully."); } if (_gainedDataField == null || _totalGainField == null) { return 0; } object value = _gainedDataField.GetValue(null); if (value == null) { return 0; } return (int)_totalGainField.GetValue(value); } catch (Exception ex) { Plugin.Log.LogError((object)("[XPPatch] Reflection error reading BetterEXP data: " + ex.Message)); return 0; } } } public static class MyPluginInfo { public const string PLUGIN_GUID = "LGUExpBridge"; public const string PLUGIN_NAME = "LGUExpBridge"; public const string PLUGIN_VERSION = "1.0.0"; } }