Decompiled source of LaunchControl v1.0.5
plugins/LaunchControl/LaunchControl.dll
Decompiled 2 months ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Logging; using HarmonyLib; using LitJson; using Ostranauts.Bit.Commands; using Ostranauts.Bit.Interactions; using Ostranauts.Bit.Items; using Ostranauts.Bit.Items.Categories; using Ostranauts.Bit.MegaTooltip; using Ostranauts.Bit.PatchSystem; using Ostranauts.Bit.Patches; using Ostranauts.Bit.PersistentData; using Ostranauts.Bit.Pledges; using Ostranauts.Bit.Tasks; using Ostranauts.Core; using Ostranauts.Core.Models; using Ostranauts.Tools; using Ostranauts.UI.MegaToolTip; using Ostranauts.UI.MegaToolTip.Interfaces; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyCompany("LaunchControl")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+a10d488c79868d3728b6483db48cf54daf215dee")] [assembly: AssemblyProduct("LaunchControl")] [assembly: AssemblyTitle("LaunchControl")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace Ostranauts.Bit { public class LaunchControl : MonoBehaviour { private static LaunchControl _instance; private MegaTooltipManager _megaTooltipManager; private CommandManager _commandManager; private ItemManager _itemManager; private PersistentDataManager _persistentDataManager; private InteractionManager _interactionManager; private PledgeManager _pledgeManager; private TaskDataProvider _taskDataProvider; private PatchManager _patchManager; public ItemManager Items => _itemManager; public CommandManager Commands => _commandManager; public static LaunchControl Instance { get { return _instance; } private set { _instance = value; } } public MegaTooltipManager MegaTooltipManager => _megaTooltipManager; public PersistentDataManager PersistentData => _persistentDataManager; public InteractionManager Interactions => _interactionManager; public PledgeManager Pledges => _pledgeManager; public TaskDataProvider Tasks => _taskDataProvider; public PatchManager PatchManager => _patchManager; public static event Action OnGameReady; private void Awake() { //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Expected O, but got Unknown if ((Object)(object)_instance != (Object)null && (Object)(object)_instance != (Object)(object)this) { LaunchControlPlugin.Logger.LogWarning((object)"Multiple LaunchControl instances detected! Destroying duplicate."); Object.Destroy((Object)(object)((Component)this).gameObject); return; } _instance = this; _megaTooltipManager = new MegaTooltipManager(((Component)this).gameObject); _itemManager = new ItemManager(); _commandManager = new CommandManager(); _interactionManager = new InteractionManager(); _pledgeManager = new PledgeManager(); _pledgeManager.SetLogger(LaunchControlPlugin.Logger); try { _persistentDataManager = PersistentDataManager.Instance; _persistentDataManager.SetLogger(LaunchControlPlugin.Logger); PersistentDataPatches.InitializeLoadListener(); LaunchControlPlugin.Logger.LogInfo((object)"PersistentData system initialized"); } catch (Exception ex) { LaunchControlPlugin.Logger.LogError((object)("Failed to initialize PersistentData system: " + ex.Message)); LaunchControlPlugin.Logger.LogError((object)ex.StackTrace); } try { _taskDataProvider = TaskDataProvider.Instance; TaskDataHandler taskDataHandler = new TaskDataHandler(); taskDataHandler.Logger = LaunchControlPlugin.Logger; _persistentDataManager.RegisterHandler("LaunchControl.tasks", taskDataHandler); LaunchControlPlugin.Logger.LogInfo((object)"Task data system initialized"); } catch (Exception ex2) { LaunchControlPlugin.Logger.LogError((object)("Failed to initialize Task data system: " + ex2.Message)); LaunchControlPlugin.Logger.LogError((object)ex2.StackTrace); } try { _patchManager = new PatchManager(LaunchControlPlugin.Logger); LaunchControlPlugin.Logger.LogInfo((object)"Patch system initialized"); } catch (Exception ex3) { LaunchControlPlugin.Logger.LogError((object)("Failed to initialize Patch system: " + ex3.Message)); LaunchControlPlugin.Logger.LogError((object)ex3.StackTrace); } try { CrewSim.OnGameFinishedLoading.AddListener(new UnityAction(OnCrewSimGameFinishedLoading)); LaunchControlPlugin.Logger.LogInfo((object)"Subscribed to game load complete event"); } catch (Exception ex4) { LaunchControlPlugin.Logger.LogError((object)("Failed to subscribe to game load event: " + ex4.Message)); LaunchControlPlugin.Logger.LogError((object)ex4.StackTrace); } LaunchControlPlugin.Logger.LogInfo((object)"LaunchControl instance initialized"); } private void OnCrewSimGameFinishedLoading() { LaunchControlPlugin.Logger.LogInfo((object)"Game finished loading - firing OnGameReady event"); LaunchControl.OnGameReady?.Invoke(); } public static bool RegisterPledgeType(string pledgeTypeName, Type pledgeClass) { if ((Object)(object)_instance == (Object)null) { Debug.LogError((object)"LaunchControl.RegisterPledgeType called before LaunchControl was initialized!"); return false; } return _instance._pledgeManager.RegisterPledgeType(pledgeTypeName, pledgeClass); } public static ModuleRegistration RegisterItemModule(Type moduleType, UISetupCallback setupUI) { if ((Object)(object)_instance == (Object)null) { Debug.LogError((object)"LaunchControl.RegisterItemModule called before LaunchControl was initialized!"); return null; } return _instance._megaTooltipManager.RegisterItemModule(moduleType, setupUI); } public static ModuleRegistration RegisterPersonModule(Type moduleType, UISetupCallback setupUI) { if ((Object)(object)_instance == (Object)null) { Debug.LogError((object)"LaunchControl.RegisterPersonModule called before LaunchControl was initialized!"); return null; } return _instance._megaTooltipManager.RegisterPersonModule(moduleType, setupUI); } private void OnDestroy() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (CrewSim.OnGameFinishedLoading != null) { CrewSim.OnGameFinishedLoading.RemoveListener(new UnityAction(OnCrewSimGameFinishedLoading)); } PersistentDataPatches.CleanupLoadListener(); } } [BepInPlugin("com.ostranauts.LaunchControl", "LaunchControl", "1.0.0")] public class LaunchControlPlugin : BaseUnityPlugin { private delegate void InitDelegate(); [CompilerGenerated] private sealed class <WaitForDataHandlerLoad>d__6 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public LaunchControlPlugin <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WaitForDataHandlerLoad>d__6(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; LaunchControlPlugin launchControlPlugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } if (!DataHandler.bLoaded) { <>2__current = null; <>1__state = 1; return true; } Logger.LogInfo((object)"DataHandler loading complete, initializing item categories..."); launchControlPlugin.InitializeItemCategories(); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } internal static ManualLogSource Logger; private Harmony _harmony; private GameObject _launchControlObject; private void Awake() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown Logger = ((BaseUnityPlugin)this).Logger; Logger.LogInfo((object)"Plugin com.ostranauts.LaunchControl v1.0.0 is loading..."); _launchControlObject = new GameObject("LaunchControl"); Object.DontDestroyOnLoad((Object)(object)_launchControlObject); _launchControlObject.AddComponent<LaunchControl>(); Logger.LogInfo((object)"LaunchControl singleton initialized"); _harmony = new Harmony("com.ostranauts.LaunchControl"); _harmony.PatchAll(typeof(LaunchControlPlugin).Assembly); IEnumerable<MethodBase> patchedMethods = _harmony.GetPatchedMethods(); int num = 0; foreach (MethodBase item in patchedMethods) { num++; Logger.LogInfo((object)("Patched method: " + item.DeclaringType?.Name + "." + item.Name)); } Logger.LogInfo((object)$"LaunchControl patches applied successfully! ({num} methods patched)"); RegisterBuiltInCommands(); Logger.LogInfo((object)"LaunchControl loaded successfully!"); Logger.LogInfo((object)" Command registration system available"); Logger.LogInfo((object)" Call LaunchControl.RegisterCommand(name, callback) from your mods"); } private void Start() { ((MonoBehaviour)this).StartCoroutine(WaitForDataHandlerLoad()); } private IEnumerator WaitForDataHandlerLoad() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WaitForDataHandlerLoad>d__6(0) { <>4__this = this }; } private void InitializeItemCategories() { if ((Object)(object)LaunchControl.Instance == (Object)null) { Logger.LogError((object)"Cannot initialize item categories: LaunchControl instance or ItemCategoryManager is null"); return; } if (DataHandler.dictCOs == null || DataHandler.dictCOs.Count == 0) { Logger.LogWarning((object)"DataHandler.dictCOs is not yet available. Skipping item category initialization."); return; } try { Logger.LogInfo((object)"Initializing item categories after DataHandler load..."); DefaultCategories.Initialize(LaunchControl.Instance.Items.Categories); LaunchControl.Instance.Items.Categories.PopulateFromPredicates(); Logger.LogInfo((object)"Item category system initialized successfully"); } catch (Exception ex) { Logger.LogError((object)("Failed to initialize item categories: " + ex.Message)); Logger.LogError((object)ex.StackTrace); } } private void RegisterBuiltInCommands() { _launchControlObject.AddComponent<ListSpritesCommand>(); Logger.LogInfo((object)"Built-in commands registered"); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } Logger.LogInfo((object)"LaunchControl unloaded"); } } public static class PluginInfo { public const string PLUGIN_GUID = "com.ostranauts.LaunchControl"; public const string PLUGIN_NAME = "LaunchControl"; public const string PLUGIN_VERSION = "1.0.0"; } public static class DevConsole { private static FieldInfo _myLogField; private static PropertyInfo _instanceProperty; private static bool _initialized; public static void Output(string message) { try { if (!_initialized) { Initialize(); } if ((object)_instanceProperty == null || (object)_myLogField == null) { Debug.Log((object)("[LaunchControl Console] " + message)); return; } object value = _instanceProperty.GetValue(null, null); if (value == null) { Debug.Log((object)("[LaunchControl Console] " + message)); return; } string text = _myLogField.GetValue(value) as string; if (text == null) { text = ""; } string value2 = text + "\n" + message; _myLogField.SetValue(value, value2); LaunchControlPlugin.Logger.LogInfo((object)("[Console] " + message)); } catch (Exception ex) { LaunchControlPlugin.Logger.LogError((object)("Failed to output to console: " + ex.Message)); Debug.Log((object)("[LaunchControl Console] " + message)); } } private static void Initialize() { try { Type type = Type.GetType("ConsoleToGUI, Assembly-CSharp"); if ((object)type == null) { LaunchControlPlugin.Logger.LogWarning((object)"Could not find ConsoleToGUI type"); _initialized = true; return; } _instanceProperty = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public); if ((object)_instanceProperty == null) { LaunchControlPlugin.Logger.LogWarning((object)"Could not find ConsoleToGUI.instance property"); _initialized = true; return; } _myLogField = type.GetField("myLog", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if ((object)_myLogField == null) { LaunchControlPlugin.Logger.LogWarning((object)"Could not find ConsoleToGUI.myLog field"); _initialized = true; } else { LaunchControlPlugin.Logger.LogInfo((object)"DevConsole initialized successfully"); _initialized = true; } } catch (Exception ex) { LaunchControlPlugin.Logger.LogError((object)("Failed to initialize DevConsole: " + ex.Message)); _initialized = true; } } } public static class SpriteUtility { private static Dictionary<string, Sprite> _cachedSprites = new Dictionary<string, Sprite>(); private static Dictionary<string, GameObject> _cachedPrefabs = new Dictionary<string, GameObject>(); public static Sprite FindSprite(string prefabName, string[] keywords, string pathHint = null, bool useCache = true) { if (string.IsNullOrEmpty(prefabName)) { LaunchControlPlugin.Logger.LogWarning((object)"FindSprite: prefabName is null or empty"); return null; } if (keywords == null || keywords.Length == 0) { LaunchControlPlugin.Logger.LogWarning((object)"FindSprite: keywords is null or empty"); return null; } string text = string.Join(",", keywords); string key = prefabName + "::" + text + "::" + (pathHint ?? "any"); if (useCache && _cachedSprites.TryGetValue(key, out var value)) { return value; } GameObject val = LoadPrefab(prefabName, useCache); if ((Object)(object)val == (Object)null) { LaunchControlPlugin.Logger.LogWarning((object)("FindSprite: Could not load prefab '" + prefabName + "'")); return null; } GameObject val2 = val; if (!string.IsNullOrEmpty(pathHint)) { GameObject val3 = FindChildByPath(val, pathHint); if ((Object)(object)val3 != (Object)null) { val2 = val3; } else { LaunchControlPlugin.Logger.LogWarning((object)("FindSprite: Path hint '" + pathHint + "' not found in prefab '" + prefabName + "', searching entire prefab")); } } Image[] componentsInChildren = val2.GetComponentsInChildren<Image>(true); foreach (Image val4 in componentsInChildren) { if ((Object)(object)val4 == (Object)null || (Object)(object)val4.sprite == (Object)null) { continue; } string text2 = ((Object)val4.sprite).name.ToLower(); bool flag = true; foreach (string text3 in keywords) { if (!text2.Contains(text3.ToLower().Trim())) { flag = false; break; } } if (flag) { if (useCache) { _cachedSprites[key] = val4.sprite; } LaunchControlPlugin.Logger.LogInfo((object)("FindSprite: Found sprite '" + ((Object)val4.sprite).name + "' in prefab '" + prefabName + "' at path '" + GetGameObjectPath(((Component)val4).gameObject, val) + "'")); return val4.sprite; } } LaunchControlPlugin.Logger.LogWarning((object)("FindSprite: No sprite matching keywords '" + text + "' found in prefab '" + prefabName + "'")); return null; } public static Sprite GetSpriteFromPrefab(string prefabPath, string childPath = null, bool useCache = true) { if (string.IsNullOrEmpty(prefabPath)) { LaunchControlPlugin.Logger.LogWarning((object)"GetSpriteFromPrefab: prefabPath is null or empty"); return null; } string key = prefabPath + "::" + (childPath ?? "root"); if (useCache && _cachedSprites.TryGetValue(key, out var value)) { return value; } GameObject val = LoadPrefab(prefabPath, useCache); if ((Object)(object)val == (Object)null) { LaunchControlPlugin.Logger.LogWarning((object)("GetSpriteFromPrefab: Could not load prefab '" + prefabPath + "'")); return null; } GameObject val2 = (string.IsNullOrEmpty(childPath) ? val : FindChildByPath(val, childPath)); if ((Object)(object)val2 == (Object)null) { LaunchControlPlugin.Logger.LogWarning((object)("GetSpriteFromPrefab: Could not find child at path '" + childPath + "' in prefab '" + prefabPath + "'")); return null; } Image component = val2.GetComponent<Image>(); if ((Object)(object)component != (Object)null && (Object)(object)component.sprite != (Object)null) { if (useCache) { _cachedSprites[key] = component.sprite; } LaunchControlPlugin.Logger.LogInfo((object)("GetSpriteFromPrefab: Found sprite '" + ((Object)component.sprite).name + "' at '" + prefabPath + "/" + childPath + "'")); return component.sprite; } LaunchControlPlugin.Logger.LogWarning((object)("GetSpriteFromPrefab: No Image component with sprite found at '" + prefabPath + "/" + childPath + "'")); return null; } public static GameObject FindChildByPath(GameObject parent, string path) { if ((Object)(object)parent == (Object)null || string.IsNullOrEmpty(path)) { return null; } Transform val = parent.transform; string[] array = path.Split(new char[1] { '/' }); foreach (string text in array) { if (!string.IsNullOrEmpty(text)) { Transform val2 = val.Find(text); if ((Object)(object)val2 == (Object)null) { return null; } val = val2; } } return ((Component)val).gameObject; } public static Sprite FindSpriteByName(string spriteName, bool useCache = true) { if (string.IsNullOrEmpty(spriteName)) { return null; } string key = "name::" + spriteName; if (useCache && _cachedSprites.TryGetValue(key, out var value)) { return value; } Sprite[] array = Resources.FindObjectsOfTypeAll<Sprite>(); foreach (Sprite val in array) { if ((Object)(object)val != (Object)null && ((Object)val).name == spriteName) { if (useCache) { _cachedSprites[key] = val; } return val; } } return null; } public static Sprite FindSpriteByKeywords(string[] keywords, bool useCache = true) { if (keywords == null || keywords.Length == 0) { return null; } string text = string.Join(",", keywords); string key = "global::" + text; if (useCache && _cachedSprites.TryGetValue(key, out var value)) { return value; } Sprite[] array = Resources.FindObjectsOfTypeAll<Sprite>(); foreach (Sprite val in array) { if ((Object)(object)val == (Object)null) { continue; } string text2 = ((Object)val).name.ToLower(); bool flag = true; foreach (string text3 in keywords) { if (!text2.Contains(text3.ToLower())) { flag = false; break; } } if (flag) { if (useCache) { _cachedSprites[key] = val; } LaunchControlPlugin.Logger.LogInfo((object)("FindSpriteByKeywords: Found sprite '" + ((Object)val).name + "'")); return val; } } LaunchControlPlugin.Logger.LogWarning((object)("FindSpriteByKeywords: No sprite matching keywords '" + text + "' found")); return null; } public static Sprite LoadSprite(string resourcePath, bool useCache = true) { if (string.IsNullOrEmpty(resourcePath)) { return null; } string key = "resource::" + resourcePath; if (useCache && _cachedSprites.TryGetValue(key, out var value)) { return value; } Sprite val = Resources.Load<Sprite>(resourcePath); if ((Object)(object)val != (Object)null && useCache) { _cachedSprites[key] = val; } return val; } public static List<Sprite> GetAllSpritesInHierarchy(GameObject root, string childPath = null) { List<Sprite> list = new List<Sprite>(); GameObject val = (string.IsNullOrEmpty(childPath) ? root : FindChildByPath(root, childPath)); if ((Object)(object)val == (Object)null) { return list; } Image[] componentsInChildren = val.GetComponentsInChildren<Image>(true); foreach (Image val2 in componentsInChildren) { if ((Object)(object)val2 != (Object)null && (Object)(object)val2.sprite != (Object)null) { list.Add(val2.sprite); } } return list; } public static void ClearCache() { _cachedSprites.Clear(); _cachedPrefabs.Clear(); LaunchControlPlugin.Logger.LogInfo((object)"SpriteUtility: Cache cleared"); } public static string GetCacheStats() { return $"SpriteUtility Cache: {_cachedSprites.Count} sprites, {_cachedPrefabs.Count} prefabs"; } public static List<string> GetAllLoadedSprites(int maxResults = 100) { List<string> list = new List<string>(); Sprite[] array = Resources.FindObjectsOfTypeAll<Sprite>(); int num = 0; Sprite[] array2 = array; foreach (Sprite val in array2) { if ((Object)(object)val != (Object)null) { list.Add(((Object)val).name); num++; if (num >= maxResults) { break; } } } return list; } public static List<string> GetAllResourcePrefabs(int maxResults = 50) { List<string> list = new List<string>(); GameObject[] array = Resources.LoadAll<GameObject>(""); int num = 0; GameObject[] array2 = array; foreach (GameObject val in array2) { if ((Object)(object)val != (Object)null) { list.Add(((Object)val).name); num++; if (num >= maxResults) { break; } } } return list; } private static GameObject LoadPrefab(string prefabPath, bool useCache) { if (useCache && _cachedPrefabs.TryGetValue(prefabPath, out var value)) { return value; } GameObject val = Resources.Load<GameObject>(prefabPath); if ((Object)(object)val != (Object)null && useCache) { _cachedPrefabs[prefabPath] = val; } return val; } private static string GetGameObjectPath(GameObject obj, GameObject root) { if ((Object)(object)obj == (Object)null || (Object)(object)obj == (Object)(object)root) { return ""; } string text = ((Object)obj).name; Transform parent = obj.transform.parent; while ((Object)(object)parent != (Object)null && (Object)(object)((Component)parent).gameObject != (Object)(object)root) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } } public class GameObjectPath { private List<string> _pathParts = new List<string>(); public GameObjectPath() { } public GameObjectPath(string initialPath) { if (!string.IsNullOrEmpty(initialPath)) { _pathParts.AddRange(initialPath.Split(new char[1] { '/' })); } } public GameObjectPath Child(string childName) { _pathParts.Add(childName); return this; } public override string ToString() { string[] array = new string[_pathParts.Count]; for (int i = 0; i < _pathParts.Count; i++) { array[i] = _pathParts[i]; } return string.Join("/", array); } public static implicit operator string(GameObjectPath path) { return path.ToString(); } public static GameObjectPath Build(string root = null) { return new GameObjectPath(root); } } } namespace Ostranauts.Bit.Tasks { public class TaskDataHandler : PersistentDataHandler { private const string DATA_FILENAME = "task_data.json"; public override string ModuleName => "LaunchControl.tasks"; public override bool CanSave() { return true; } public override bool CanLoad() { return true; } public override void Save(string moduleFolder) { try { Dictionary<string, Dictionary<string, object>> allData = TaskDataProvider.Instance.GetAllData(); if (allData == null || allData.Count == 0) { base.Logger.LogInfo((object)"[TaskDataHandler] No task data to save"); return; } Dictionary<string, Dictionary<string, string>> dictionary = new Dictionary<string, Dictionary<string, string>>(); foreach (KeyValuePair<string, Dictionary<string, object>> item in allData) { Dictionary<string, string> dictionary2 = new Dictionary<string, string>(); foreach (KeyValuePair<string, object> item2 in item.Value) { if (item2.Value != null) { try { string value = JsonMapper.ToJson(item2.Value); dictionary2[item2.Key] = value; } catch (Exception ex) { base.Logger.LogWarning((object)$"[TaskDataHandler] Failed to serialize value for key '{item2.Key}': {ex.Message}"); } } } if (dictionary2.Count > 0) { dictionary[item.Key] = dictionary2; } } string contents = JsonMapper.ToJson((object)dictionary); File.WriteAllText(Path.Combine(moduleFolder, "task_data.json"), contents); base.Logger.LogInfo((object)$"[TaskDataHandler] Saved {dictionary.Count} task data entries"); } catch (Exception ex2) { base.Logger.LogError((object)$"[TaskDataHandler] Error saving task data: {ex2.Message}\n{ex2.StackTrace}"); throw; } } public override void Load(string moduleFolder) { try { string path = Path.Combine(moduleFolder, "task_data.json"); if (!File.Exists(path)) { base.Logger.LogInfo((object)"[TaskDataHandler] No task data file found (this is normal for new saves)"); return; } Dictionary<string, Dictionary<string, string>> dictionary = JsonMapper.ToObject<Dictionary<string, Dictionary<string, string>>>(File.ReadAllText(path)); if (dictionary == null) { base.Logger.LogWarning((object)"[TaskDataHandler] Failed to deserialize task data"); return; } Dictionary<string, Dictionary<string, object>> dictionary2 = new Dictionary<string, Dictionary<string, object>>(); foreach (KeyValuePair<string, Dictionary<string, string>> item in dictionary) { Dictionary<string, object> dictionary3 = new Dictionary<string, object>(); foreach (KeyValuePair<string, string> item2 in item.Value) { try { JsonData value = JsonMapper.ToObject(item2.Value); dictionary3[item2.Key] = value; } catch (Exception ex) { base.Logger.LogWarning((object)$"[TaskDataHandler] Failed to deserialize value for key '{item2.Key}': {ex.Message}"); } } if (dictionary3.Count > 0) { dictionary2[item.Key] = dictionary3; } } TaskDataProvider.Instance.SetAllData(dictionary2); base.Logger.LogInfo((object)$"[TaskDataHandler] Loaded {dictionary2.Count} task data entries"); } catch (Exception ex2) { base.Logger.LogError((object)$"[TaskDataHandler] Error loading task data: {ex2.Message}\n{ex2.StackTrace}"); throw; } } public override void LoadFromMemory(string saveFolderPath, Dictionary<string, byte[]> dictFiles) { try { string value = "mods/" + ModuleName + "/task_data.json"; byte[] array = null; foreach (KeyValuePair<string, byte[]> dictFile in dictFiles) { if (dictFile.Key.Equals(value, StringComparison.OrdinalIgnoreCase)) { array = dictFile.Value; break; } } if (array == null) { base.Logger.LogInfo((object)"[TaskDataHandler] No task data file found in memory (this is normal for new saves)"); return; } Dictionary<string, Dictionary<string, string>> dictionary = JsonMapper.ToObject<Dictionary<string, Dictionary<string, string>>>(Encoding.UTF8.GetString(array)); if (dictionary == null) { base.Logger.LogWarning((object)"[TaskDataHandler] Failed to deserialize task data from memory"); return; } Dictionary<string, Dictionary<string, object>> dictionary2 = new Dictionary<string, Dictionary<string, object>>(); foreach (KeyValuePair<string, Dictionary<string, string>> item in dictionary) { Dictionary<string, object> dictionary3 = new Dictionary<string, object>(); foreach (KeyValuePair<string, string> item2 in item.Value) { try { JsonData value2 = JsonMapper.ToObject(item2.Value); dictionary3[item2.Key] = value2; } catch (Exception ex) { base.Logger.LogWarning((object)$"[TaskDataHandler] Failed to deserialize value for key '{item2.Key}': {ex.Message}"); } } if (dictionary3.Count > 0) { dictionary2[item.Key] = dictionary3; } } TaskDataProvider.Instance.SetAllData(dictionary2); base.Logger.LogInfo((object)$"[TaskDataHandler] Loaded {dictionary2.Count} task data entries from memory"); } catch (Exception ex2) { base.Logger.LogError((object)$"[TaskDataHandler] Error loading task data from memory: {ex2.Message}\n{ex2.StackTrace}"); throw; } } } public class TaskDataProvider { private static TaskDataProvider _instance; private Dictionary<string, Dictionary<string, object>> _taskData; public static TaskDataProvider Instance { get { if (_instance == null) { _instance = new TaskDataProvider(); } return _instance; } } private TaskDataProvider() { _taskData = new Dictionary<string, Dictionary<string, object>>(); } private string GetTaskKey(Task2 task) { if (task == null) { throw new ArgumentNullException("task"); } return string.Format("{0}:{1}:{2}:{3}:{4}", task.strDuty ?? "null", task.strInteraction ?? "null", task.strTargetCOID ?? "null", task.nTile, task.strTileShip ?? "null").GetHashCode().ToString(); } public void SetData<T>(Task2 task, string key, T value) { if (task == null) { throw new ArgumentNullException("task"); } if (string.IsNullOrEmpty(key)) { throw new ArgumentException("Key cannot be null or empty", "key"); } string taskKey = GetTaskKey(task); if (!_taskData.ContainsKey(taskKey)) { _taskData[taskKey] = new Dictionary<string, object>(); } _taskData[taskKey][key] = value; } public T GetData<T>(Task2 task, string key) { if (task == null || string.IsNullOrEmpty(key)) { return default(T); } string taskKey = GetTaskKey(task); if (!_taskData.ContainsKey(taskKey)) { return default(T); } if (!_taskData[taskKey].ContainsKey(key)) { return default(T); } object obj = _taskData[taskKey][key]; if (obj == null) { return default(T); } try { return (T)obj; } catch (InvalidCastException) { return default(T); } } public bool HasData(Task2 task, string key) { if (task == null || string.IsNullOrEmpty(key)) { return false; } string taskKey = GetTaskKey(task); if (_taskData.ContainsKey(taskKey)) { return _taskData[taskKey].ContainsKey(key); } return false; } public void RemoveData(Task2 task, string key) { if (task == null || string.IsNullOrEmpty(key)) { return; } string taskKey = GetTaskKey(task); if (_taskData.ContainsKey(taskKey)) { _taskData[taskKey].Remove(key); if (_taskData[taskKey].Count == 0) { _taskData.Remove(taskKey); } } } public void ClearTask(Task2 task) { if (task != null) { string taskKey = GetTaskKey(task); _taskData.Remove(taskKey); } } public void CleanupOrphanedData() { if (CrewSim.objInstance == null || CrewSim.objInstance.workManager == null) { return; } HashSet<string> hashSet = new HashSet<string>(); WorkManager workManager = CrewSim.objInstance.workManager; FieldInfo field = typeof(WorkManager).GetField("dictTasks2", BindingFlags.Instance | BindingFlags.NonPublic); if ((object)field != null && field.GetValue(workManager) is Dictionary<string, List<Task2>> dictionary) { foreach (KeyValuePair<string, List<Task2>> item in dictionary) { List<Task2> value = item.Value; if (value == null) { continue; } for (int i = 0; i < value.Count; i++) { Task2 val = value[i]; if (val != null) { try { string taskKey = GetTaskKey(val); hashSet.Add(taskKey); } catch (Exception) { } } } } } FieldInfo field2 = typeof(WorkManager).GetField("aTasksActive", BindingFlags.Instance | BindingFlags.NonPublic); if ((object)field2 != null && field2.GetValue(workManager) is List<Task2> list) { for (int j = 0; j < list.Count; j++) { Task2 val2 = list[j]; if (val2 != null) { try { string taskKey2 = GetTaskKey(val2); hashSet.Add(taskKey2); } catch (Exception) { } } } } List<string> list2 = new List<string>(); foreach (KeyValuePair<string, Dictionary<string, object>> taskDatum in _taskData) { if (!hashSet.Contains(taskDatum.Key)) { list2.Add(taskDatum.Key); } } for (int k = 0; k < list2.Count; k++) { _taskData.Remove(list2[k]); } LaunchControlPlugin.Logger.LogInfo((object)$"[TaskDataProvider] Cleaned up {list2.Count} orphaned task data entries"); } internal Dictionary<string, Dictionary<string, object>> GetAllData() { return _taskData; } internal void SetAllData(Dictionary<string, Dictionary<string, object>> data) { if (data == null) { _taskData = new Dictionary<string, Dictionary<string, object>>(); } else { _taskData = data; } } } } namespace Ostranauts.Bit.Pledges { public class PledgeManager { private readonly Dictionary<string, Type> _customPledgeTypes; private ManualLogSource _logger; public int RegisteredCount => _customPledgeTypes.Count; public PledgeManager() { _customPledgeTypes = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase); } public void SetLogger(ManualLogSource logger) { _logger = logger; } public bool RegisterPledgeType(string pledgeTypeName, Type pledgeClass) { if (string.IsNullOrEmpty(pledgeTypeName)) { LogError("Cannot register pledge with null or empty type name"); return false; } if ((object)pledgeClass == null) { LogError("Cannot register pledge '" + pledgeTypeName + "' with null class type"); return false; } if (!typeof(Pledge2).IsAssignableFrom(pledgeClass)) { LogError("Cannot register pledge '" + pledgeTypeName + "': Class " + pledgeClass.Name + " does not inherit from Pledge2"); return false; } if ((object)pledgeClass.GetConstructor(Type.EmptyTypes) == null) { LogError("Cannot register pledge '" + pledgeTypeName + "': Class " + pledgeClass.Name + " does not have a parameterless constructor"); return false; } if (_customPledgeTypes.ContainsKey(pledgeTypeName)) { LogWarning("Pledge type '" + pledgeTypeName + "' already registered, overwriting with " + pledgeClass.Name); _customPledgeTypes[pledgeTypeName] = pledgeClass; } else { _customPledgeTypes.Add(pledgeTypeName, pledgeClass); LogInfo("Registered custom pledge type '" + pledgeTypeName + "' -> " + pledgeClass.Name); } return true; } public Pledge2 CreatePledge(string pledgeTypeName) { if (string.IsNullOrEmpty(pledgeTypeName)) { return null; } if (!_customPledgeTypes.TryGetValue(pledgeTypeName, out var value)) { return null; } try { object? obj = Activator.CreateInstance(value); return (Pledge2)((obj is Pledge2) ? obj : null); } catch (Exception ex) { LogError("Failed to create pledge instance for type '" + pledgeTypeName + "': " + ex.Message); return null; } } public bool IsRegistered(string pledgeTypeName) { if (!string.IsNullOrEmpty(pledgeTypeName)) { return _customPledgeTypes.ContainsKey(pledgeTypeName); } return false; } public IEnumerable<string> GetRegisteredPledgeTypes() { return _customPledgeTypes.Keys; } internal void Clear() { _customPledgeTypes.Clear(); } private void LogInfo(string message) { if (_logger != null) { _logger.LogInfo((object)("[PledgeManager] " + message)); } else { Debug.Log((object)("[PledgeManager] " + message)); } } private void LogWarning(string message) { if (_logger != null) { _logger.LogWarning((object)("[PledgeManager] " + message)); } else { Debug.LogWarning((object)("[PledgeManager] " + message)); } } private void LogError(string message) { if (_logger != null) { _logger.LogError((object)("[PledgeManager] " + message)); } else { Debug.LogError((object)("[PledgeManager] " + message)); } } } } namespace Ostranauts.Bit.PersistentData { public abstract class ListPersistentDataHandler<T> : PersistentDataHandler { protected virtual string FileName => "data.json"; protected abstract List<T> GetDataList(); protected abstract void SetDataList(List<T> dataList); protected virtual void ClearData() { } protected virtual bool ValidateItem(T item) { return item != null; } protected virtual T ProcessLoadedItem(T item) { return item; } public sealed override void Save(string modFolderPath) { try { List<T> dataList = GetDataList(); string path = Path.Combine(modFolderPath, FileName); if (dataList == null || dataList.Count == 0) { if (File.Exists(path)) { File.Delete(path); LogInfo("Deleted empty " + FileName); } else { LogInfo("No data to save"); } return; } List<T> list = new List<T>(); foreach (T item in dataList) { if (ValidateItem(item)) { list.Add(item); } } if (list.Count == 0) { LogInfo("No valid items to save after filtering"); if (File.Exists(path)) { File.Delete(path); } } else { string contents = SerializeList(list); File.WriteAllText(path, contents); LogInfo($"Saved {list.Count} item(s) to {FileName}"); } } catch (Exception ex) { LogError("Failed to save " + FileName + ": " + ex.Message); throw; } } public sealed override void Load(string modFolderPath) { try { ClearData(); string path = Path.Combine(modFolderPath, FileName); if (!File.Exists(path)) { LogInfo("No " + FileName + " found (this is normal for saves without data)"); return; } string json = File.ReadAllText(path); LoadFromJson(json); } catch (Exception ex) { LogError("Failed to load " + FileName + ": " + ex.Message); throw; } } public sealed override void LoadFromMemory(string saveFolderPath, Dictionary<string, byte[]> dictFiles) { try { ClearData(); string key = "mods/" + ModuleName + "/" + FileName; if (!dictFiles.ContainsKey(key)) { LogInfo("No " + FileName + " found in compressed save (this is normal for saves without data)"); return; } byte[] bytes = dictFiles[key]; string @string = Encoding.UTF8.GetString(bytes); LoadFromJson(@string); } catch (Exception ex) { LogError("Failed to load " + FileName + " from memory: " + ex.Message); throw; } } private void LoadFromJson(string json) { List<T> list = DeserializeList(json); if (list == null || list.Count == 0) { LogWarning(FileName + " exists but contains no valid data"); return; } List<T> list2 = new List<T>(); foreach (T item in list) { T val = ProcessLoadedItem(item); if (val != null) { list2.Add(val); } } SetDataList(list2); LogInfo($"Loaded {list2.Count} item(s) from {FileName}"); } protected virtual string SerializeList(List<T> list) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown StringBuilder stringBuilder = new StringBuilder(); JsonWriter val = new JsonWriter(stringBuilder); val.PrettyPrint = true; val.IndentValue = 2; JsonMapper.ToJson((object)list, val); return stringBuilder.ToString(); } protected virtual List<T> DeserializeList(string json) { try { return new List<T>(JsonMapper.ToObject<T[]>(json)); } catch (Exception ex) { LogWarning("Failed to deserialize using LitJson: " + ex.Message); return new List<T>(); } } } public abstract class PersistentDataHandler { public abstract string ModuleName { get; } public ManualLogSource Logger { get; set; } public abstract bool CanSave(); public abstract bool CanLoad(); public abstract void Save(string modFolderPath); public abstract void Load(string modFolderPath); public virtual void LoadFromMemory(string saveFolderPath, Dictionary<string, byte[]> dictFiles) { string text = "mods/" + ModuleName + "/"; bool flag = false; foreach (KeyValuePair<string, byte[]> dictFile in dictFiles) { if (dictFile.Key.StartsWith(text, StringComparison.OrdinalIgnoreCase)) { flag = true; string text2 = dictFile.Key.Substring(text.Length); string fileName = Path.GetFileName(text2); LogInfo("Loading file from memory: " + text2); string text3 = Path.Combine(Path.GetTempPath(), "Ostranauts_ModLoad_" + Guid.NewGuid()); Directory.CreateDirectory(text3); string path = Path.Combine(text3, fileName); try { File.WriteAllBytes(path, dictFile.Value); } catch (Exception ex) { LogError("Failed to write temp file: " + ex.Message); } } } if (!flag) { LogInfo("No files found in compressed save (this is normal for saves without mod data)"); } else { LogWarning("LoadFromMemory was called but handler does not override it. Consider overriding LoadFromMemory() for better compressed save support."); } } protected void LogInfo(string message) { ManualLogSource logger = Logger; if (logger != null) { logger.LogInfo((object)("[" + ModuleName + "] " + message)); } } protected void LogWarning(string message) { ManualLogSource logger = Logger; if (logger != null) { logger.LogWarning((object)("[" + ModuleName + "] " + message)); } } protected void LogError(string message) { ManualLogSource logger = Logger; if (logger != null) { logger.LogError((object)("[" + ModuleName + "] " + message)); } } } public class PersistentDataManager { private static PersistentDataManager _instance; private readonly Dictionary<string, PersistentDataHandler> _handlers; private ManualLogSource _logger; public static PersistentDataManager Instance { get { if (_instance == null) { _instance = new PersistentDataManager(); } return _instance; } } private PersistentDataManager() { _handlers = new Dictionary<string, PersistentDataHandler>(); } public void SetLogger(ManualLogSource logger) { _logger = logger; } public void RegisterHandler(string moduleName, PersistentDataHandler handler) { if (string.IsNullOrEmpty(moduleName)) { ManualLogSource logger = _logger; if (logger != null) { logger.LogError((object)"[PersistentData] Cannot register handler with null or empty module name"); } return; } if (handler == null) { ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogError((object)("[PersistentData] Cannot register null handler for module '" + moduleName + "'")); } return; } if (moduleName != handler.ModuleName) { ManualLogSource logger3 = _logger; if (logger3 != null) { logger3.LogError((object)("[PersistentData] Module name mismatch: parameter '" + moduleName + "' != handler.ModuleName '" + handler.ModuleName + "'")); } return; } if (_handlers.ContainsKey(moduleName)) { ManualLogSource logger4 = _logger; if (logger4 != null) { logger4.LogWarning((object)("[PersistentData] Handler for module '" + moduleName + "' is already registered. Replacing.")); } } _handlers[moduleName] = handler; ManualLogSource logger5 = _logger; if (logger5 != null) { logger5.LogInfo((object)("[PersistentData] Registered handler for module '" + moduleName + "'")); } } public void UnregisterHandler(string moduleName) { if (!string.IsNullOrEmpty(moduleName) && _handlers.Remove(moduleName)) { ManualLogSource logger = _logger; if (logger != null) { logger.LogInfo((object)("[PersistentData] Unregistered handler for module '" + moduleName + "'")); } } } public int GetHandlerCount() { return _handlers.Count; } public void SaveAll(string saveFolderPath) { if (string.IsNullOrEmpty(saveFolderPath)) { ManualLogSource logger = _logger; if (logger != null) { logger.LogError((object)"[PersistentData] SaveAll called with null or empty save folder path"); } return; } List<PersistentDataHandler> list = new List<PersistentDataHandler>(_handlers.Values); if (list.Count == 0) { ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogInfo((object)"[PersistentData] No handlers registered, skipping save"); } return; } ManualLogSource logger3 = _logger; if (logger3 != null) { logger3.LogInfo((object)$"[PersistentData] Saving data for {list.Count} module(s)..."); } string text = Path.Combine(saveFolderPath, "mods"); try { if (!Directory.Exists(text)) { Directory.CreateDirectory(text); } } catch (Exception ex) { ManualLogSource logger4 = _logger; if (logger4 != null) { logger4.LogError((object)("[PersistentData] Failed to create mods folder: " + ex.Message)); } return; } int num = 0; int num2 = 0; int num3 = 0; foreach (PersistentDataHandler item in list) { try { if (!item.CanSave()) { ManualLogSource logger5 = _logger; if (logger5 != null) { logger5.LogInfo((object)("[PersistentData] Skipping save for module '" + item.ModuleName + "' (CanSave returned false)")); } num2++; continue; } string text2 = Path.Combine(text, item.ModuleName); if (!Directory.Exists(text2)) { Directory.CreateDirectory(text2); } item.Save(text2); num++; ManualLogSource logger6 = _logger; if (logger6 != null) { logger6.LogInfo((object)("[PersistentData] Successfully saved data for module '" + item.ModuleName + "'")); } } catch (Exception ex2) { num3++; ManualLogSource logger7 = _logger; if (logger7 != null) { logger7.LogError((object)("[PersistentData] Error saving data for module '" + item.ModuleName + "': " + ex2.Message + "\n" + ex2.StackTrace)); } ManualLogSource logger8 = item.Logger; if (logger8 != null) { logger8.LogError((object)("Save failed: " + ex2.Message)); } } } ManualLogSource logger9 = _logger; if (logger9 != null) { logger9.LogInfo((object)$"[PersistentData] Save complete: {num} succeeded, {num2} skipped, {num3} errors"); } } public void LoadAll(string saveFolderPath) { if (string.IsNullOrEmpty(saveFolderPath)) { ManualLogSource logger = _logger; if (logger != null) { logger.LogError((object)"[PersistentData] LoadAll called with null or empty save folder path"); } return; } List<PersistentDataHandler> list = new List<PersistentDataHandler>(_handlers.Values); if (list.Count == 0) { ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogInfo((object)"[PersistentData] No handlers registered, skipping load"); } return; } ManualLogSource logger3 = _logger; if (logger3 != null) { logger3.LogInfo((object)$"[PersistentData] Loading data for {list.Count} module(s)..."); } string text = Path.Combine(saveFolderPath, "mods"); if (!Directory.Exists(text)) { ManualLogSource logger4 = _logger; if (logger4 != null) { logger4.LogInfo((object)"[PersistentData] No mods folder found in save (this is normal for older saves)"); } return; } int num = 0; int num2 = 0; int num3 = 0; foreach (PersistentDataHandler item in list) { try { if (!item.CanLoad()) { ManualLogSource logger5 = _logger; if (logger5 != null) { logger5.LogInfo((object)("[PersistentData] Skipping load for module '" + item.ModuleName + "' (CanLoad returned false)")); } num2++; continue; } string text2 = Path.Combine(text, item.ModuleName); if (!Directory.Exists(text2)) { ManualLogSource logger6 = _logger; if (logger6 != null) { logger6.LogInfo((object)("[PersistentData] No save data found for module '" + item.ModuleName + "' (folder doesn't exist)")); } num2++; continue; } item.Load(text2); num++; ManualLogSource logger7 = _logger; if (logger7 != null) { logger7.LogInfo((object)("[PersistentData] Successfully loaded data for module '" + item.ModuleName + "'")); } } catch (Exception ex) { num3++; ManualLogSource logger8 = _logger; if (logger8 != null) { logger8.LogError((object)("[PersistentData] Error loading data for module '" + item.ModuleName + "': " + ex.Message + "\n" + ex.StackTrace)); } ManualLogSource logger9 = item.Logger; if (logger9 != null) { logger9.LogError((object)("Load failed: " + ex.Message)); } } } ManualLogSource logger10 = _logger; if (logger10 != null) { logger10.LogInfo((object)$"[PersistentData] Load complete: {num} succeeded, {num2} skipped, {num3} errors"); } } public void LoadAllFromMemory(string saveFolderPath, Dictionary<string, byte[]> dictFiles) { if (string.IsNullOrEmpty(saveFolderPath)) { ManualLogSource logger = _logger; if (logger != null) { logger.LogError((object)"[PersistentData] LoadAllFromMemory called with null or empty save folder path"); } return; } if (dictFiles == null || dictFiles.Count == 0) { ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogInfo((object)"[PersistentData] No files in dictFiles, skipping load from memory"); } return; } List<PersistentDataHandler> list = new List<PersistentDataHandler>(_handlers.Values); if (list.Count == 0) { ManualLogSource logger3 = _logger; if (logger3 != null) { logger3.LogInfo((object)"[PersistentData] No handlers registered, skipping load from memory"); } return; } ManualLogSource logger4 = _logger; if (logger4 != null) { logger4.LogInfo((object)$"[PersistentData] Loading data from memory for {list.Count} module(s)..."); } int num = 0; int num2 = 0; int num3 = 0; foreach (PersistentDataHandler item in list) { try { if (!item.CanLoad()) { ManualLogSource logger5 = _logger; if (logger5 != null) { logger5.LogInfo((object)("[PersistentData] Skipping load from memory for module '" + item.ModuleName + "' (CanLoad returned false)")); } num2++; continue; } string value = "mods/" + item.ModuleName + "/"; bool flag = false; foreach (string key in dictFiles.Keys) { if (key.StartsWith(value, StringComparison.OrdinalIgnoreCase)) { flag = true; break; } } if (!flag) { ManualLogSource logger6 = _logger; if (logger6 != null) { logger6.LogInfo((object)("[PersistentData] No save data found in memory for module '" + item.ModuleName + "'")); } num2++; continue; } item.LoadFromMemory(saveFolderPath, dictFiles); num++; ManualLogSource logger7 = _logger; if (logger7 != null) { logger7.LogInfo((object)("[PersistentData] Successfully loaded data from memory for module '" + item.ModuleName + "'")); } } catch (Exception ex) { num3++; ManualLogSource logger8 = _logger; if (logger8 != null) { logger8.LogError((object)("[PersistentData] Error loading data from memory for module '" + item.ModuleName + "': " + ex.Message + "\n" + ex.StackTrace)); } ManualLogSource logger9 = item.Logger; if (logger9 != null) { logger9.LogError((object)("Load from memory failed: " + ex.Message)); } } } ManualLogSource logger10 = _logger; if (logger10 != null) { logger10.LogInfo((object)$"[PersistentData] Load from memory complete: {num} succeeded, {num2} skipped, {num3} errors"); } } } } namespace Ostranauts.Bit.PatchSystem { public static class FieldPathResolver { public static List<string> ParsePath(string path) { if (string.IsNullOrEmpty(path)) { return new List<string>(); } List<string> list = new List<string>(); StringBuilder stringBuilder = new StringBuilder(); bool flag = false; foreach (char c in path) { if (flag) { stringBuilder.Append(c); flag = false; continue; } switch (c) { case '\\': flag = true; break; case '.': if (stringBuilder.Length > 0) { list.Add(stringBuilder.ToString()); stringBuilder.Length = 0; } break; default: stringBuilder.Append(c); break; } } if (stringBuilder.Length > 0) { list.Add(stringBuilder.ToString()); } return list; } public static JsonData GetNestedField(JsonData root, string fieldPath) { if (root == null || string.IsNullOrEmpty(fieldPath)) { return null; } List<string> list = ParsePath(fieldPath); if (list.Count == 0) { return null; } JsonData val = root; foreach (string item in list) { if (val == null || !val.IsObject) { return null; } if (!HasKey(val, item)) { return null; } val = val[item]; } return val; } public static bool SetNestedField(JsonData root, string fieldPath, object value, bool createIfMissing = true) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown if (root == null || !root.IsObject || string.IsNullOrEmpty(fieldPath)) { return false; } List<string> list = ParsePath(fieldPath); if (list.Count == 0) { return false; } JsonData val = root; for (int i = 0; i < list.Count - 1; i++) { string text = list[i]; if (!HasKey(val, text)) { if (!createIfMissing) { return false; } val[text] = new JsonData(); val[text].SetJsonType((JsonType)1); } val = val[text]; if (!val.IsObject) { return false; } } string text2 = list[list.Count - 1]; val[text2] = ConvertToJsonData(value); return true; } public static bool RemoveNestedField(JsonData root, string fieldPath) { if (root == null || !root.IsObject || string.IsNullOrEmpty(fieldPath)) { return false; } List<string> list = ParsePath(fieldPath); if (list.Count == 0) { return false; } JsonData val = root; for (int i = 0; i < list.Count - 1; i++) { string text = list[i]; if (!HasKey(val, text)) { return false; } val = val[text]; if (!val.IsObject) { return false; } } string key = list[list.Count - 1]; if (!HasKey(val, key)) { return false; } if (val.IsObject) { IDictionary dictionary = (IDictionary)val; if (dictionary != null) { dictionary.Remove(key); return true; } } return false; } public static bool GetParentAndField(JsonData root, string fieldPath, out JsonData parent, out string fieldName) { parent = null; fieldName = null; if (root == null || !root.IsObject || string.IsNullOrEmpty(fieldPath)) { return false; } List<string> list = ParsePath(fieldPath); if (list.Count == 0) { return false; } if (list.Count == 1) { parent = root; fieldName = list[0]; return true; } JsonData val = root; for (int i = 0; i < list.Count - 1; i++) { string text = list[i]; if (!HasKey(val, text)) { return false; } val = val[text]; if (!val.IsObject) { return false; } } parent = val; fieldName = list[list.Count - 1]; return true; } private static bool HasKey(JsonData jsonData, string key) { if (jsonData == null || !jsonData.IsObject) { return false; } foreach (string key2 in jsonData.Keys) { if (key2 == key) { return true; } } return false; } private static JsonData ConvertToJsonData(object value) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown if (value == null) { return null; } if (value is JsonData) { return (JsonData)value; } return JsonMapper.ToObject(JsonMapper.ToJson(value)); } public static bool FieldExists(JsonData root, string fieldPath) { return GetNestedField(root, fieldPath) != null; } public static JsonType? GetFieldType(JsonData root, string fieldPath) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) JsonData nestedField = GetNestedField(root, fieldPath); if (nestedField == null) { return null; } return nestedField.GetJsonType(); } } public class PatchTarget { public string File { get; set; } public string StrName { get; set; } public List<string> StrNames { get; set; } public Dictionary<string, object> AdditionalMatches { get; set; } public List<string> GetStrNamePatterns() { List<string> list = new List<string>(); if (!string.IsNullOrEmpty(StrName)) { list.Add(StrName); } if (StrNames != null && StrNames.Count > 0) { list.AddRange(StrNames); } return list; } public static PatchTarget FromJsonData(JsonData json) { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Expected O, but got Unknown //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Expected O, but got Unknown if (json == null || !json.IsObject) { throw new ArgumentException("Invalid target JSON"); } PatchTarget patchTarget = new PatchTarget(); if (HasKey(json, "file")) { patchTarget.File = ((object)json["file"]).ToString(); } if (HasKey(json, "strName")) { if (json["strName"].IsString) { patchTarget.StrName = ((object)json["strName"]).ToString(); } else if (json["strName"].IsArray) { patchTarget.StrNames = new List<string>(); foreach (JsonData item in (IEnumerable)json["strName"]) { JsonData val = item; patchTarget.StrNames.Add(((object)val).ToString()); } } } if (HasKey(json, "strNames") && json["strNames"].IsArray) { if (patchTarget.StrNames == null) { patchTarget.StrNames = new List<string>(); } foreach (JsonData item2 in (IEnumerable)json["strNames"]) { JsonData val2 = item2; patchTarget.StrNames.Add(((object)val2).ToString()); } } patchTarget.AdditionalMatches = new Dictionary<string, object>(); foreach (string key in json.Keys) { if (key != "file" && key != "strName" && key != "strNames") { patchTarget.AdditionalMatches[key] = json[key]; } } return patchTarget; } public bool Validate(out string error) { error = null; if (GetStrNamePatterns().Count == 0) { error = "Target must specify at least one strName pattern"; return false; } return true; } private static bool HasKey(JsonData jsonData, string key) { if (jsonData == null || !jsonData.IsObject) { return false; } foreach (string key2 in jsonData.Keys) { if (key2 == key) { return true; } } return false; } } public class JsonPatchFile { public PatchTarget Target { get; set; } public List<PatchOperation> Operations { get; set; } public string SourceFilePath { get; set; } public string InferredDataType { get; set; } public static List<JsonPatchFile> FromJson(string jsonString, string sourceFilePath = null) { if (string.IsNullOrEmpty(jsonString)) { throw new ArgumentException("JSON string cannot be null or empty"); } JsonData val; try { val = JsonMapper.ToObject(jsonString); } catch (Exception ex) { throw new ArgumentException("Failed to parse JSON: " + ex.Message, ex); } List<JsonPatchFile> list = new List<JsonPatchFile>(); if (val.IsArray) { for (int i = 0; i < val.Count; i++) { JsonPatchFile item = FromJsonData(val[i], sourceFilePath); list.Add(item); } } else { if (!val.IsObject) { throw new ArgumentException("Patch file must be either an object or an array of objects"); } JsonPatchFile item2 = FromJsonData(val, sourceFilePath); list.Add(item2); } return list; } public static JsonPatchFile FromJsonData(JsonData json, string sourceFilePath = null) { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown if (json == null || !json.IsObject) { throw new ArgumentException("Invalid patch file JSON"); } JsonPatchFile jsonPatchFile = new JsonPatchFile { SourceFilePath = sourceFilePath }; if (!HasKey(json, "target")) { throw new ArgumentException("Patch file missing required 'target' field"); } jsonPatchFile.Target = PatchTarget.FromJsonData(json["target"]); if (!HasKey(json, "operations")) { throw new ArgumentException("Patch file missing required 'operations' field"); } if (!json["operations"].IsArray) { throw new ArgumentException("'operations' field must be an array"); } jsonPatchFile.Operations = new List<PatchOperation>(); foreach (JsonData item in (IEnumerable)json["operations"]) { JsonData json2 = item; jsonPatchFile.Operations.Add(PatchOperation.FromJsonData(json2)); } if (jsonPatchFile.Operations.Count == 0) { throw new ArgumentException("Patch file must have at least one operation"); } return jsonPatchFile; } public bool Validate(out List<string> errors) { errors = new List<string>(); if (Target == null) { errors.Add("Patch file has no target"); return false; } if (!Target.Validate(out var error)) { errors.Add("Target validation failed: " + error); } if (Operations == null || Operations.Count == 0) { errors.Add("Patch file has no operations"); return false; } for (int i = 0; i < Operations.Count; i++) { if (!Operations[i].Validate(out var error2)) { errors.Add($"Operation {i} validation failed: {error2}"); } } return errors.Count == 0; } private static bool HasKey(JsonData jsonData, string key) { if (jsonData == null || !jsonData.IsObject) { return false; } foreach (string key2 in jsonData.Keys) { if (key2 == key) { return true; } } return false; } } public class PatchApplier { private readonly ManualLogSource _logger; public PatchApplier(ManualLogSource logger = null) { _logger = logger; } public bool ApplyOperation(JsonData jsonObject, PatchOperation operation, string targetName) { if (jsonObject == null || !jsonObject.IsObject) { LogError("Cannot apply operation to non-object JSON for target '" + targetName + "'"); return false; } try { switch (operation.Op) { case PatchOperationType.Append: return ApplyAppend(jsonObject, operation, targetName); case PatchOperationType.Prepend: return ApplyPrepend(jsonObject, operation, targetName); case PatchOperationType.Insert: return ApplyInsert(jsonObject, operation, targetName); case PatchOperationType.Remove: return ApplyRemove(jsonObject, operation, targetName); case PatchOperationType.Set: return ApplySet(jsonObject, operation, targetName); case PatchOperationType.Replace: return ApplyReplace(jsonObject, operation, targetName); default: LogError($"Unknown operation type: {operation.Op}"); return false; } } catch (Exception ex) { LogError($"Exception applying {operation.Op} operation to '{targetName}' field '{operation.Field}': {ex.Message}"); return false; } } public int ApplyOperations(JsonData jsonObject, List<PatchOperation> operations, string targetName) { int num = 0; for (int i = 0; i < operations.Count; i++) { PatchOperation patchOperation = operations[i]; if (ApplyOperation(jsonObject, patchOperation, targetName)) { num++; } else { LogWarning($"Operation {i} ({patchOperation.Op}) failed for target '{targetName}'"); } } return num; } private bool ApplyAppend(JsonData jsonObject, PatchOperation operation, string targetName) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) JsonData nestedField = FieldPathResolver.GetNestedField(jsonObject, operation.Field); if (nestedField == null) { LogError("Field '" + operation.Field + "' not found in target '" + targetName + "' for append operation"); return false; } if (!nestedField.IsArray) { LogError($"Field '{operation.Field}' in target '{targetName}' is not an array (type: {nestedField.GetJsonType()})"); return false; } JsonData val = ConvertToJsonData(operation.Value); nestedField.Add((object)val); LogInfo("Appended value to '" + operation.Field + "' in target '" + targetName + "'"); return true; } private bool ApplyPrepend(JsonData jsonObject, PatchOperation operation, string targetName) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) JsonData nestedField = FieldPathResolver.GetNestedField(jsonObject, operation.Field); if (nestedField == null) { LogError("Field '" + operation.Field + "' not found in target '" + targetName + "' for prepend operation"); return false; } if (!nestedField.IsArray) { LogError($"Field '{operation.Field}' in target '{targetName}' is not an array (type: {nestedField.GetJsonType()})"); return false; } JsonData val = ConvertToJsonData(operation.Value); JsonData val2 = new JsonData(); val2.SetJsonType((JsonType)2); val2.Add((object)val); for (int i = 0; i < nestedField.Count; i++) { val2.Add((object)nestedField[i]); } if (!FieldPathResolver.SetNestedField(jsonObject, operation.Field, val2, createIfMissing: false)) { LogError("Failed to set field '" + operation.Field + "' after prepend"); return false; } LogInfo("Prepended value to '" + operation.Field + "' in target '" + targetName + "'"); return true; } private bool ApplyInsert(JsonData jsonObject, PatchOperation operation, string targetName) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Expected O, but got Unknown if (!operation.Index.HasValue) { LogError("Insert operation requires index for field '" + operation.Field + "' in target '" + targetName + "'"); return false; } JsonData nestedField = FieldPathResolver.GetNestedField(jsonObject, operation.Field); if (nestedField == null) { LogError("Field '" + operation.Field + "' not found in target '" + targetName + "' for insert operation"); return false; } if (!nestedField.IsArray) { LogError($"Field '{operation.Field}' in target '{targetName}' is not an array (type: {nestedField.GetJsonType()})"); return false; } int value = operation.Index.Value; if (value < 0 || value > nestedField.Count) { LogError($"Insert index {value} out of range for field '{operation.Field}' (length: {nestedField.Count}) in target '{targetName}'"); return false; } JsonData val = ConvertToJsonData(operation.Value); JsonData val2 = new JsonData(); val2.SetJsonType((JsonType)2); for (int i = 0; i < nestedField.Count; i++) { if (i == value) { val2.Add((object)val); } val2.Add((object)nestedField[i]); } if (value == nestedField.Count) { val2.Add((object)val); } if (!FieldPathResolver.SetNestedField(jsonObject, operation.Field, val2, createIfMissing: false)) { LogError("Failed to set field '" + operation.Field + "' after insert"); return false; } LogInfo($"Inserted value at index {value} in '{operation.Field}' in target '{targetName}'"); return true; } private bool ApplyRemove(JsonData jsonObject, PatchOperation operation, string targetName) { //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Expected O, but got Unknown //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Expected O, but got Unknown JsonData nestedField = FieldPathResolver.GetNestedField(jsonObject, operation.Field); if (nestedField == null) { if (operation.Value == null && !operation.Index.HasValue) { LogInfo("Field '" + operation.Field + "' doesn't exist in target '" + targetName + "', nothing to remove"); return true; } LogError("Field '" + operation.Field + "' not found in target '" + targetName + "' for remove operation"); return false; } if (operation.Value == null && !operation.Index.HasValue) { if (FieldPathResolver.RemoveNestedField(jsonObject, operation.Field)) { LogInfo("Removed field '" + operation.Field + "' from target '" + targetName + "'"); return true; } LogError("Failed to remove field '" + operation.Field + "' from target '" + targetName + "'"); return false; } if (!nestedField.IsArray) { LogError("Field '" + operation.Field + "' in target '" + targetName + "' is not an array for remove operation"); return false; } if (operation.Index.HasValue) { int value = operation.Index.Value; if (value < 0 || value >= nestedField.Count) { LogError($"Remove index {value} out of range for field '{operation.Field}' (length: {nestedField.Count}) in target '{targetName}'"); return false; } JsonData val = new JsonData(); val.SetJsonType((JsonType)2); for (int i = 0; i < nestedField.Count; i++) { if (i != value) { val.Add((object)nestedField[i]); } } if (!FieldPathResolver.SetNestedField(jsonObject, operation.Field, val, createIfMissing: false)) { LogError("Failed to set field '" + operation.Field + "' after remove by index"); return false; } LogInfo($"Removed element at index {value} from '{operation.Field}' in target '{targetName}'"); return true; } if (operation.Value != null) { string text = ConvertToString(operation.Value); bool flag = false; JsonData val2 = new JsonData(); val2.SetJsonType((JsonType)2); for (int j = 0; j < nestedField.Count; j++) { if (ConvertToString(nestedField[j]) == text && !flag) { flag = true; } else { val2.Add((object)nestedField[j]); } } if (!flag) { LogWarning("Value '" + text + "' not found in field '" + operation.Field + "' of target '" + targetName + "'"); return false; } if (!FieldPathResolver.SetNestedField(jsonObject, operation.Field, val2, createIfMissing: false)) { LogError("Failed to set field '" + operation.Field + "' after remove by value"); return false; } LogInfo("Removed value '" + text + "' from '" + operation.Field + "' in target '" + targetName + "'"); return true; } LogError("Remove operation must specify either value or index"); return false; } private bool ApplySet(JsonData jsonObject, PatchOperation operation, string targetName) { if (FieldPathResolver.SetNestedField(jsonObject, operation.Field, operation.Value)) { LogInfo("Set field '" + operation.Field + "' in target '" + targetName + "'"); return true; } LogError("Failed to set field '" + operation.Field + "' in target '" + targetName + "'"); return false; } private bool ApplyReplace(JsonData jsonObject, PatchOperation operation, string targetName) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown JsonData nestedField = FieldPathResolver.GetNestedField(jsonObject, operation.Field); if (nestedField == null) { LogError("Field '" + operation.Field + "' not found in target '" + targetName + "' for replace operation"); return false; } if (!nestedField.IsArray) { LogError("Field '" + operation.Field + "' in target '" + targetName + "' is not an array for replace operation"); return false; } string text = ConvertToString(operation.OldValue); bool flag = false; JsonData val = new JsonData(); val.SetJsonType((JsonType)2); for (int i = 0; i < nestedField.Count; i++) { if (ConvertToString(nestedField[i]) == text && !flag) { val.Add((object)ConvertToJsonData(operation.Value)); flag = true; } else { val.Add((object)nestedField[i]); } } if (!flag) { LogWarning("Old value '" + text + "' not found in field '" + operation.Field + "' of target '" + targetName + "'"); return false; } if (!FieldPathResolver.SetNestedField(jsonObject, operation.Field, val, createIfMissing: false)) { LogError("Failed to set field '" + operation.Field + "' after replace"); return false; } LogInfo("Replaced '" + text + "' with new value in '" + operation.Field + "' in target '" + targetName + "'"); return true; } private JsonData ConvertToJsonData(object value) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown if (value == null) { return null; } if (value is JsonData) { return (JsonData)value; } return JsonMapper.ToObject(JsonMapper.ToJson(value)); } private string ConvertToString(object value) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown if (value == null) { return ""; } if (value is JsonData) { JsonData val = (JsonData)value; if (val.IsString) { return ((object)val).ToString(); } return JsonMapper.ToJson((object)val); } return value.ToString(); } private void LogInfo(string message) { if (_logger != null) { _logger.LogInfo((object)("[PatchApplier] " + message)); } } private void LogWarning(string message) { if (_logger != null) { _logger.LogWarning((object)("[PatchApplier] " + message)); } } private void LogError(string message) { if (_logger != null) { _logger.LogError((object)("[PatchApplier] " + message)); } } } public class PatchLoader { private readonly ManualLogSource _logger; public PatchLoader(ManualLogSource logger = null) { _logger = logger; } public List<string> FindPatchFiles(string modFolderPath, string[] ignorePatterns = null) { List<string> list = new List<string>(); string text = Path.Combine(modFolderPath, "Data"); if (!Directory.Exists(text)) { text = Path.Combine(modFolderPath, "data"); } if (!Directory.Exists(text)) { return list; } try { string[] files = Directory.GetFiles(text, "*.patch", SearchOption.AllDirectories); foreach (string text2 in files) { string text3 = text2.Replace('\\', '/'); bool flag = false; if (ignorePatterns != null) { foreach (string value in ignorePatterns) { if (text3.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { flag = true; break; } } } if (!flag) { list.Add(text2); } } list.Sort(delegate(string a, string b) { string? fileName = Path.GetFileName(a); string fileName2 = Path.GetFileName(b); return string.Compare(fileName, fileName2, StringComparison.OrdinalIgnoreCase); }); } catch (Exception ex) { LogError("Error scanning for patch files in " + text + ": " + ex.Message); } return list; } public List<JsonPatchFile> LoadPatchFile(string filePath) { List<JsonPatchFile> list = new List<JsonPatchFile>(); if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) { LogError("Patch file not found: " + filePath); return list; } try { foreach (JsonPatchFile item in JsonPatchFile.FromJson(File.ReadAllText(filePath), filePath)) { if (string.IsNullOrEmpty(item.Target.File)) { item.InferredDataType = InferDataTypeFromPath(filePath); } if (!item.Validate(out var errors)) { LogError("Patch validation failed in " + Path.GetFileName(filePath)); foreach (string item2 in errors) { LogError(" - " + item2); } } else { list.Add(item); } } return list; } catch (Exception ex) { LogError("Error loading patch file " + filePath + ": " + ex.Message); LogError(ex.StackTrace); return list; } } public List<JsonPatchFile> LoadAllPatchFiles(string modFolderPath, string[] ignorePatterns = null) { List<JsonPatchFile> list = new List<JsonPatchFile>(); foreach (string item in FindPatchFiles(modFolderPath, ignorePatterns)) { List<JsonPatchFile> list2 = LoadPatchFile(item); if (list2 != null && list2.Count > 0) { list.AddRange(list2); } } return list; } public string InferDataTypeFromPath(string filePath) { if (string.IsNullOrEmpty(filePath)) { return null; } try { string text = filePath.Replace('\\', '/'); int num = text.IndexOf("/data/", StringComparison.OrdinalIgnoreCase); if (num < 0) { num = text.IndexOf("/Data/", StringComparison.OrdinalIgnoreCase); } if (num >= 0) { string text2 = text.Substring(num + 6); int num2 = text2.IndexOf('/'); if (num2 > 0) { return text2.Substring(0, num2).ToLowerInvariant(); } } } catch (Exception ex) { LogWarning("Error inferring data type from path " + filePath + ": " + ex.Message); } return null; } public string MapDataTypeToDictionary(string dataType) { if (string.IsNullOrEmpty(dataType)) { return null; } switch (dataType.ToLowerInvariant()) { case "ads": return "dictAds"; case "ai_training": return "dictAIPersonalities"; case "attackmodes": return "dictAModes"; case "audioemitters": return "dictAudioEmitters"; case "careers": return "dictCareers"; case "chargeprofiles": return "dictChargeProfiles"; case "colors": return "dictJsonColors"; case "conditions": return "dictConds"; case "cos": case "condowners": return "dictCOs"; case "condrules": return "dictCondRules"; case "condtrigs": return "dictCTs"; case "context": return "dictContext"; case "cooverlays": return "dictCOOverlays"; case "crime": return "dictCrimes"; case "dialogue": return "dictDialogue"; case "factions": return "dictFactions"; case "flaws": return "dictFlaws"; case "gasrespires": return "dictGasRespires"; case "guipropmaps": return "dictGUIPropMapUnparsed"; case "headlines": return "dictHeadlines"; case "homeworlds": return "dictHomeworlds"; case "info": return "dictInfoNodes"; case "installables": return "dictInstallables"; case "interaction_overrides": return "dictIAOverrides"; case "interactions": return "dictInteractions"; case "items": return "dictItemDefs"; case "jobitems": return "dictJobitems"; case "jobs": return "dictJobs"; case "ledgerdefs": return "dictLedgerDefs"; case "lifeevents": return "dictLifeEvents"; case "lights": return "dictLights"; case "loot": return "dictLoot"; case "music": return "dictMusic"; case "parallax": return "dictParallax"; case "pda_apps": return "dictPDAAppIcons"; case "personspecs": return "dictPersonSpecs"; case "pledges": return "dictPledges"; case "plot_beat_overrides": return "dictPlotBeatOverrides"; case "plot_beats": return "dictPlotBeats"; case "plot_manager": return "dictPlotManager"; case "plots": return "dictPlots"; case "powerinfos": return "dictPowerInfo"; case "rooms": return "dictRoomSpecsTemp"; case "ships": return "dictShips"; case "shipspecs": return "dictShipSpecs"; case "slot_effects": return "dictSlotEffects"; case "slots": return "dictSlots"; case "star_systems": return "dictStarSystems"; case "strings": return "dictStrings"; case "tickers": return "dictTickers"; case "tips": return "dictTips"; case "tokens": return "dictJsonTokens"; case "transit": return "dictTransit"; case "verbs": return "dictJsonVerbs"; case "wounds": return "dictWounds"; case "zone_triggers": return "dictZoneTriggers"; default: return null; } } private void LogInfo(string message) { if (_logger != null) { _logger.LogInfo((object)("[PatchLoader] " + message)); } } private void LogWarning(string message) { if (_logger != null) { _logger.LogWarning((object)("[PatchLoader] " + message)); } } private void LogError(string message) { if (_logger != null) { _logger.LogError((object)("[PatchLoader] " + message)); } } } public class PatchManager { private class QueuedMod { public string FolderPath; public string[] IgnorePatterns; } private readonly ManualLogSource _logger; private readonly PatchLoader _loader; private readonly PatchApplier _applier; private int _totalPatchesLoaded; private int _totalPatchesApplied; private int _totalOperationsApplied; private List<QueuedMod> _queuedMods = new List<QueuedMod>(); private bool _loadCompleteHooked; public PatchManager(ManualLogSource logger) { _logger = logger; _loader = new PatchLoader(logger); _applier = new PatchApplier(logger); } public void QueueModForPatching(string modFolderPath, string[] ignorePatterns = null) { if (!_loadCompleteHooked) { DataHandler.LoadComplete = (Action)Delegate.Combine(DataHandler.LoadComplete, new Action(OnLoadComplete)); _loadCompleteHooked = true; } _queuedMods.Add(new QueuedMod { FolderPath = modFolderPath, IgnorePatterns = ignorePatterns }); } private void OnLoadComplete() { try { LogInfo($"JSON Patch System: Processing {_queuedMods.Count} mod(s)"); foreach (QueuedMod queuedMod in _queuedMods) { ApplyPatchesForMod(queuedMod.FolderPath, queuedMod.IgnorePatterns); } _queuedMods.Clear(); DataHandler.LoadComplete = (Action)Delegate.Remove(DataHandler.LoadComplete, new Action(OnLoadComplete)); _loadCompleteHooked = false; if (_totalPatchesApplied > 0) { LogInfo($"JSON Patch System: Applied {_totalPatchesApplied} patch file(s) with {_totalOperationsApplied} operation(s)"); } } catch (Exception ex) { LogError("JSON Patch System error: " + ex.Message); LogError(ex.StackTrace); } } public void ApplyPatchesForMod(string modFolderPath, string[] ignorePatterns = null) { try { List<JsonPatchFile> list = _loader.LoadAllPatchFiles(modFolderPath, ignorePatterns); if (list.Count == 0) { return; } _totalPatchesLoaded += list.Count; foreach (JsonPatchFile item in list) { ApplyPatch(item); } } catch (Exception ex) { LogError("Error processing patches for mod " + modFolderPath + ": " + ex.Message); LogError(ex.StackTrace); } } private void ApplyPatch(JsonPatchFile patch) { if (patch == null || patch.Target == null || patch.Operations == null) { LogError("Invalid patch file"); return; } try { string fileName = Path.GetFileName(patch.SourceFilePath); string text = DetermineTargetDictionary(patch); if (string.IsNullOrEmpty(text)) { LogError("Patch '" + fileName + "': Could not determine target dictionary"); return; } object dataHandlerDictionary = GetDataHandlerDictionary(text); if (dataHandlerDictionary == null) { LogError("Patch '" + fileName + "': Could not access " + text); return; } List<string> list = FindMatchingTargets(dataHandlerDictionary, patch.Target); if (list.Count == 0) { LogWarning("Patch '" + fileName + "': No matching targets found"); return; } int num = 0; foreach (string item in list) { if (ApplyPatchToTarget(dataHandlerDictionary, item, patch.Operations)) { num++; } } if (num > 0) { _totalPatchesApplied++; LogInfo($"Patch '{fileName}': Applied to {num} target(s)"); } } catch (Exception ex) { LogError("Error applying patch: " + ex.Message); LogError(ex.StackTrace); } } private string DetermineTargetDictionary(JsonPatchFile patch) { if (!string.IsNullOrEmpty(patch.Target.File)) { string text = _loader.InferDataTypeFromPath(patch.Target.File); if (!string.IsNullOrEmpty(text)) { return _loader.MapDataTypeToDictionary(text); } } if (!string.IsNullOrEmpty(patch.InferredDataType)) { return _loader.MapDataTypeToDictionary(patch.InferredDataType); } return null; } private object GetDataHandlerDictionary(string dictionaryName) { try { FieldInfo field = typeof(DataHandler).GetField(dictionaryName, BindingFlags.Static | BindingFlags.Public); if ((object)field == null) { LogError("DataHandler." + dictionaryName + " field not found"); return null; } return field.GetValue(null); } catch (Exception ex) { LogError("Error accessing DataHandler." + dictionaryName + ": " + ex.Message); return null; } } private List<string> FindMatchingTargets(object dictionary, PatchTarget target) { List<string> strNamePatterns = target.GetStrNamePatterns(); if (dictionary is Dictionary<string, JsonData> dictionary2) { return TargetMatcher.FindMatchingKeysInJsonDict(dictionary2, strNamePatterns); } Type type = dictionary.GetType(); if (type.IsGenericType && (object)type.GetGenericTypeDefinition() == typeof(Dictionary<, >) && (object)type.GetGenericArguments()[0] == typeof(string)) { PropertyInfo property = type.GetProperty("Keys"); if ((object)property == null) { LogError("Keys property not found on dictionary"); return new List<string>(); } object value = property.GetValue(dictionary, null); if (value == null) { LogError("Keys collection is null"); return new List<string>(); } if (value is IEnumerable enumerable) { List<string> list = new List<string>(); { foreach (object item in enumerable) { string text = item.ToString(); foreach (string item2 in strNamePatterns) { if (TargetMatcher.Matches(text, item2)) { list.Add(text); break; } } } return list; } } LogError("Keys is not IEnumerable"); return new List<string>(); } LogWarning("Unsupported dictionary type: " + type.Name); return new List<string>(); } private bool ApplyPatchToTarget(object dictionary, string targetKey, List<PatchOperation> operations) { try { if (dictionary is Dictionary<string, JsonData> dictionary2) { if (!dictionary2.ContainsKey(targetKey)) { LogError("Target '" + targetKey + "' not found in dictionary"); return false; } JsonData jsonObject = dictionary2[targetKey]; int num = _applier.ApplyOperations(jsonObject, operations, targetKey); _totalOperationsApplied += num; return num > 0; } Type type = dictionary.GetType(); if (type.IsGenericType && (object)type.GetGenericTypeDefinition() == typeof(Dictionary<, >) && (object)type.GetGenericArguments()[0] == typeof(string)) { PropertyInfo property = type.GetProperty("Item"); if ((object)property == null) { LogError("Cannot access dictionary indexer for typed dictionary"); return false; } object value = property.GetValue(dictionary, new object[1] { targetKey }); if (value == null) { LogError("Target '" + targetKey + "' not found in typed dictionary"); return false; } int num2 = ApplyOperationsToTypedObject(value, operations, targetKey); _totalOperationsApplied += num2; return num2 > 0; } LogError("Unsupported dictionary type: " + type.Name); return false; } catch (Exception ex) { LogError("Error applying patch to target '" + targetKey + "': " + ex.Message); LogError("Stack trace: " + ex.StackTrace); return false; } } private int ApplyOperationsToTypedObject(object targetObject, List<PatchOperation> operations, string targetName) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown int num = 0; Type type = targetObject.GetType(); foreach (PatchOperation operation in operations) { try { FieldInfo field = type.GetField(operation.Field, BindingFlags.Instance | BindingFlags.Public); PropertyInfo propertyInfo = null; if ((object)field == null) { propertyInfo = type.GetProperty(operation.Field, BindingFlags.Instance | BindingFlags.Public); } if ((object)field == null && (object)propertyInfo == null) { LogError("Field or property '" + operation.Field + "' not found on type " + type.Name); continue; } object obj = (((object)field != null) ? field.GetValue(targetObject) : propertyInfo.GetValue(targetObject, null)); if (operation.Op == PatchOperationType.Append && obj is Array) { Array array = (Array)obj; Type elementType = array.GetType().GetElementType(); object value = ConvertValue((JsonData)operation.Value, elementType); Array array2 = Array.CreateInstance(elementType, array.Length + 1); Array.Copy(array, array2, array.Length); array2.SetValue(value, array.Length); if ((object)field != null) { field.SetValue(targetObject, array2); } else { propertyInfo.SetValue(targetObject, array2, null); } num++; } else if (operation.Op == PatchOperationType.Set) { Type targetType = (((object)field != null) ? field.FieldType : propertyInfo.PropertyType); object value2 = ConvertValue((JsonData)operation.Value, targetType); if ((object)field != null) { field.SetValue(targetObject, value2); } else { propertyInfo.SetValue(targetObject, value2, null); } num++; } else { LogWarning($"Operation '{operation.Op}' not yet supported for typed objects"); } } catch (Exception ex) { LogError("Error applying operation to typed object: " + ex.Message); } } return num; } private object ConvertValue(JsonData value, Type targetType) { if (value == null) { return null; } if (value.IsString) { string text = (string)value; if ((object)targetType == typeof(string)) { return text; } return Convert.ChangeType(text, targetType); } if (value.IsInt) { return Convert.ChangeType((int)value, targetType); } if (value.IsLong) { return Convert.ChangeType((long)value, targetType); } if (value.IsDouble) { return Convert.ChangeType((double)value, targetType); } if (value.IsBoolean) { return Convert.ChangeType((bool)value, targetType); } string text2 = JsonMapper.ToJson((object)value); return typeof(JsonMapper).GetMethod("ToObject", new Type[1] { typeof(string) }).MakeGenericMethod(targetType).Invoke(null, new object[1] { text2 }); } public void LogStatistics() { LogInfo("=== JSON Patch System Statistics ==="); LogInfo($"Total patches loaded: {_totalPatchesLoaded}"); LogInfo($"Total patches applied: {_totalPatchesApplied}"); LogInfo($"Total operations applied: {_totalOperationsApplied}"); } private void LogInfo(string message) { if (_logger != null) { _logger.LogInfo((object)("[PatchManager] " + message)); } } private void LogWarning(string message) { if (_logger != null) { _logger.LogWarning((object)("[PatchManager] " + message)); } } private void LogError(string message) { if (_logger != null) { _logger.LogError((object)("[PatchManager] " + message)); } } } public enum PatchOperationType { Append, Prepend, Insert, Remove, Set, Replace } public class PatchOperation { public PatchOperationType Op { get; set; } public string Field { get; set; } public object Value { get; set; } public object OldValue { get; set; } public int? Index { get; set; } public static PatchOperationType ParseOperationType(string opString) { if (string.IsNullOrEmpty(opString)) { throw new ArgumentException("Operation type cannot be null or empty"); } return opString.ToLowerInvariant() switch { "append" => PatchOperationType.Append, "prepend" => PatchOperationType.Prepend, "insert" => PatchOperationType.Insert, "remove" => PatchOperationType.Remove, "set" => PatchOperationType.Set, "replace" => PatchOperationType.Replace, _ => throw new ArgumentException("Unknown operation type: " + opString), }; } public static PatchOperation FromJsonData(JsonData json) { if (json == null || !json.IsObject) { throw new ArgumentException("Invalid operation JSON"); } PatchOperation patchOperation = new PatchOperation(); if (!HasKey(json, "op")) { throw new ArgumentException("Operation missing required 'op' field"); } patchOperation.Op = ParseOperationType(((object)json["op"]).ToString()); if (HasKey(json, "field")) { patchOperation.Field = ((object)json["field"]).ToString(); } if (HasKey(json, "value")) { patchOperation.Value = json["value"]; } if (HasKey(json, "old_value")) { patchOperation.OldValue = json["old_value"]; } if (HasKey(json, "index") && json["index"].IsInt) { patchOperation.Index = (int)json["index"]; } return patchOperation; } public bool Validate(out string error) { error = null; if (string.IsNullOrEmpty(Field)) { error = "Field path is required"; return false; } switch (Op) { case PatchOperationType.Append: case PatchOperationType.Prepend: if (Value == null) { error = $"{Op} operation requires 'value' field"; return false; } break; case PatchOperationType.Insert: if (Value == null) { error = "Insert operation requires 'value' field"; return false; } if (!Index.HasValue) { error = "Insert operation requires 'index' field"; return false; } break; case PatchOperationType.Set: if (Value == null) { error = "Set operation requires 'value' field"; return false; } break; case PatchOperationType.Replace: if (Value == null || OldValue == null) { error = "Replace operation requires both 'value' and 'old_value' fields"; return false; } break; } return true; } private static bool HasKey(JsonData jsonData, string key) { if (jsonData == null || !jsonData.IsObject) { return false; } foreach (string key2 in jsonData.Keys) { if (key2 == key) { return true; } } return false; } } public static class TargetMatcher { public static string WildcardToRegex(string pattern) { if (string.IsNullOrEmpty(pattern)) { return "^$"; } string text = Regex.Escape(pattern); text = text.Replace("\\*", ".*"); return "^" + text + "$"; } public static bool Matches(string value, string pattern) { if (string.IsNullOrEmpty(value)) { return false; } if (string.IsNullOrEmpty(pattern)) { return false; } if (pattern.IndexOf('*') == -1) { return value == pattern; } string pattern2 = WildcardToRegex(pattern); return Regex.IsMatch(value, pattern2); } public static List<string> FindMatchingKeys<T>(Dictionary<string, T> dictionary, string pattern) { List<string> list = new List<string>(); if (dictionary == null || dictionary.Count == 0) { return list; } foreach (KeyValuePair<string, T> item in dictionary) { string key = item.Key; if (Matches(key, pattern)) { list.Add(key); } } return list; } public static List<string> FindMatchingKeys<T>(Dictionary<string, T> dictionary, List<string> patterns) { HashSet<string> hashSet = new HashSet<string>(); if (dictionary == null || dictionary.Count == 0 || patterns == null || patterns.Count == 0) { return new List<string>(); } foreach (string pattern in patterns) { foreach (string item in FindMatchingKeys(dictionary, pattern)) { hashSet.Add(item); } } return new List<string>(hashSet); } public static List<string> FindMatchingKeysInJsonDict(Dictionary<string, JsonData> dictionary, string pattern) { List<string> list = new List<string>(); if (dictionary == null || dictionary.Count == 0) { return list; } foreach (KeyValuePair<string, JsonData> item in dictionary) { string key = item.Key; if (Matches(key, pattern)) { list.Add(key); continue; } JsonData value = item.Value; if (value != null && value.IsObject && HasKey(value, "strName") && Matches(((object)value["strName"]).ToString(), pattern)) { list.Add(key); } } return list; } public static List<string> FindMatchingKeysInJsonDict(Dictionary<string, JsonData> dictionary, List<string> patterns) { HashSet<string> hashSet = new HashSet<string>(); if (dictionary == null || dictionary.Count == 0 || patterns == null || patterns.Count == 0) { return new List<string>(); } foreach (string pattern in patterns) { foreach (string item in FindMatchingKeysInJsonDict(dictionary, pattern)) { hashSet.Add(item); } } return new List<string>(hashSet); } public static List<string> FindMatchingKeysGeneric(object dictionary, List<string> patterns) { if (dictionary == null || patterns == null || patterns.Count == 0) { return new List<string>(); } if (dictionary is Dictionary<string, JsonData> dictionary2) { return FindMatchingKeysInJsonDict(dictionary2, patterns); } HashSet<string> hashSet = new HashSet<string>(); Type type = dictionary.GetType(); if (type.IsGenericType && (object)type.GetGenericTypeDefinition() == typeof(Dictionary<, >)) { PropertyInfo property = type.GetProperty("Keys"); if ((object)property != null && property.GetValue(dictionary, null) is IEnumerable enumerable) { foreach (object item in enumerable) { string text = item.ToString(); foreach (string pattern in patterns) { if (Matches(text, pattern)) { hashSet.Add(text); break; } } } } } return new List<string>(hashSet); } public static bool HasWildcard(string pattern) { if (!string.IsNullOrEmpty(pattern)) { return pattern.Contains("*"); } return false; } public static bool HasWildcards(List<string> patterns) { if (patterns == null || patterns.Count == 0) { return false; } foreach (string pattern in patterns) { if (HasWildcard(pattern)) { return true; } } return false; } private s