/// <summary> /// Schedules a patch method instance to be run. /// </summary> /// <param name="when">When to run the patch.</param> /// <param name="instance">The patch method instance to run.</param> internal static void AddHandler(uint when, IPatchMethodInstance instance) { if (when == RunAt.Immediately) { // Now now now! instance.Run(GetImmediateInstance()); } else { lock (PSharedData.GetLock(PRegistry.KEY_POSTLOAD_LOCK)) { InitMaster(); if (patches == null) { patches = new Dictionary <uint, PrivateRunList>(8); } if (!patches.TryGetValue(when, out PrivateRunList atTime)) { patches.Add(when, atTime = new List <IPatchMethodInstance>(16)); // Register our mod in the master list if (!master.TryGetValue(when, out SharedRunList existing)) { master.Add(when, existing = new List <Action <uint> >(8)); } existing.Add(RunThisMod); } atTime.Add(instance); } } }
/// <summary> /// Registers a method which will be run after PLib and all mods load. It will be /// passed a HarmonyInstance which can be used to make late patches. /// </summary> /// <param name="callback">The method to invoke.</param> public static void RegisterPostload(PostLoadHandler callback) { if (callback == null) { throw new ArgumentNullException("callback"); } // Some others used this call before the library was initialized if (!PLibInit) { InitLibrary(false); LogWarning("PUtil.InitLibrary was not called before using RegisterPostload!"); } lock (PSharedData.GetLock(PRegistry.KEY_POSTLOAD_LOCK)) { // Get list holding postload information var list = PSharedData.GetData <IList <PostLoadHandler> >(PRegistry. KEY_POSTLOAD_TABLE); if (list == null) { PSharedData.PutData(PRegistry.KEY_POSTLOAD_TABLE, list = new List <PostLoadHandler>(16)); } list.Add(callback); string name = Assembly.GetCallingAssembly()?.GetName()?.Name; if (name != null) { PRegistry.LogPatchDebug("Registered post-load handler for " + name); } } }
/// <summary> /// Executes all post-load handlers. /// </summary> internal static void ExecutePostload() { IList <PostLoadHandler> postload = null; lock (PSharedData.GetLock(PRegistry.KEY_POSTLOAD_LOCK)) { // Get list holding postload information var list = PSharedData.GetData <IList <PostLoadHandler> >(PRegistry. KEY_POSTLOAD_TABLE); if (list != null) { postload = new List <PostLoadHandler>(list); } } // If there were any, run them if (postload != null) { var hInst = Harmony.HarmonyInstance.Create("PLib.PostLoad"); PRegistry.LogPatchDebug("Executing {0:D} post-load handler(s)".F(postload. Count)); foreach (var handler in postload) { handler?.Invoke(hInst); } } }
/// <summary> /// Registers a PAction with the action manager. There is no corresponding Unregister /// call, so avoid spamming PActions. /// /// This call should occur after PUtil.InitLibrary() during the mod OnLoad(). If called /// earlier, it may fail with InvalidOperationException, and if called later, the /// user's custom key bind (if applicable) will be discarded. /// </summary> /// <param name="identifier">The identifier for this action.</param> /// <param name="title">The action's title.</param> /// <param name="binding">The default key binding for this action.</param> /// <returns>The action thus registered.</returns> /// <exception cref="InvalidOperationException">If PLib is not yet initialized.</exception> public static PAction Register(string identifier, LocString title, PKeyBinding binding = null) { // In case this call is used before the library was initialized if (!PUtil.PLibInit) { PUtil.InitLibrary(false); PUtil.LogWarning("PUtil.InitLibrary was not called before using " + "PAction.Register!"); } int actionID; PAction action; lock (PSharedData.GetLock(PRegistry.KEY_ACTION_LOCK)) { actionID = PSharedData.GetData <int>(PRegistry.KEY_ACTION_ID); if (actionID <= 0) { throw new InvalidOperationException("PAction action ID is not set!"); } PSharedData.PutData(PRegistry.KEY_ACTION_ID, actionID + 1); } action = new PAction(actionID, identifier, title); PActionManager.ConfigureTitle(action); action.AddKeyBinding(binding ?? new PKeyBinding()); return(action); }
/// <summary> /// Registers a PAction with the action manager. There is no corresponding Unregister /// call, so avoid spamming PActions. /// /// This call should occur after PUtil.LogModInit() during the mod OnLoad(). If called /// earlier, it may fail with InvalidOperationException, and if called later, the /// user's custom key bind (if applicable) will be discarded. /// </summary> /// <param name="identifier">The identifier for this action.</param> /// <param name="title">The action's title.</param> /// <param name="binding">The default key binding for this action.</param> /// <returns>The action thus registered.</returns> /// <exception cref="InvalidOperationException">If PLib is not yet initialized.</exception> public static PAction Register(string identifier, LocString title, PKeyBinding binding = null) { object locker = PSharedData.GetData <object>(PRegistry.KEY_ACTION_LOCK); int actionID; if (locker == null) { throw new InvalidOperationException("PAction.Register called before PLib loaded!"); } PAction action; lock (locker) { actionID = PSharedData.GetData <int>(PRegistry.KEY_ACTION_ID); if (actionID <= 0) { throw new InvalidOperationException("PAction action ID is not set!"); } PSharedData.PutData(PRegistry.KEY_ACTION_ID, actionID + 1); } action = new PAction(actionID, identifier, title); PActionManager.ConfigureTitle(action); action.AddKeyBinding(binding ?? new PKeyBinding()); return(action); }
#pragma warning disable IDE0051 // Remove unused private members /// <summary> /// Finds the latest patch and applies only it. /// </summary> private static void ApplyLatest() { if (instance != null) { object latest = null; Version latestVer = null; try { foreach (var pair in instance.Patches) { var patch = pair.Value; var patchVer = new Version(pair.Key); if (latestVer == null || latestVer.CompareTo(patchVer) < 0) { // First element or newer version latest = patch; latestVer = patchVer; } } } catch (ArgumentOutOfRangeException e) { // .NET 3.5 please PUtil.LogException(e); } catch (FormatException e) { PUtil.LogException(e); } catch (OverflowException e) { PUtil.LogException(e); } if (latest != null) { // Store the winning version PSharedData.PutData(KEY_VERSION, latestVer.ToString()); try { Traverse.Create(latest).CallMethod("Apply", instance.PLibInstance); } catch (ArgumentException e) { PUtil.LogException(e); } catch (Exception e) { if (e.Message?.ToLower() == "cannot get method value without method") { // Cannot get method value without method PUtil.LogError("The first PLib mod in the load order did not " + "use PUtil.InitLibrary(). PLib cannot patch correctly!"); } else { PUtil.LogException(e); } } } // Reduce memory usage by cleaning up the patch list instance.Patches.Clear(); } else { #if DEBUG LogPatchWarning("ApplyLatest invoked with no Instance!"); #endif } }
/// <summary> /// Updates the action system based on the current actions registered. /// </summary> public void UpdateMaxAction() { object locker = PSharedData.GetData <object>(PRegistry.KEY_ACTION_LOCK); if (locker != null) { lock (locker) { maxPAction = Math.Max(0, PSharedData.GetData <int>(PRegistry. KEY_ACTION_ID)); } } }
/// <summary> /// Initializes the master list of post load patches to apply. /// </summary> private static void InitMaster() { if (master == null) { var newMaster = PSharedData.GetData <IDictionary <uint, SharedRunList> >( PRegistry.KEY_POSTLOAD_ENHANCED); if (newMaster == null) { PSharedData.PutData(PRegistry.KEY_POSTLOAD_ENHANCED, newMaster = new Dictionary <uint, SharedRunList>(8)); } master = newMaster; } }
/// <summary> /// Applies the latest patch version. /// </summary> private static void DoApplyLatest() { object latest = null; Version latestVer = null; try { foreach (var pair in instance.Patches) { var patch = pair.Value; var patchVer = new Version(pair.Key); if (latestVer == null || latestVer.CompareTo(patchVer) < 0) { // First element or newer version latest = patch; latestVer = patchVer; } } } catch (ArgumentOutOfRangeException e) { // .NET 3.5 please PUtil.LogException(e); } catch (FormatException e) { PUtil.LogException(e); } catch (OverflowException e) { PUtil.LogException(e); } if (latest != null) { // Store the winning version PSharedData.PutData(KEY_VERSION, latestVer.ToString()); try { var applyMethod = Traverse.Create(latest).Method(nameof(PLibPatches.Apply), new Type[] { typeof(HarmonyInstance) }); // Raise warning if a bad patch made it in somehow if (applyMethod.MethodExists()) { applyMethod.GetValue(instance.PLibInstance); } else { LogPatchWarning("The first PLib mod in the load order did not use " + "PUtil.InitLibrary()!"); } } catch (Exception e) { PUtil.LogException(e); } } // Reduce memory usage by cleaning up the patch list instance.Patches.Clear(); }
#pragma warning disable IDE0051 // Remove unused private members /// <summary> /// Finds the latest patch and applies only it. /// </summary> private static void ApplyLatest() { if (instance != null) { object latest = null; Version latestVer = null; try { foreach (var pair in instance.Patches) { var patch = pair.Value; var patchVer = new Version(pair.Key); if (latestVer == null || latestVer.CompareTo(patchVer) < 0) { // First element or newer version latest = patch; latestVer = patchVer; } } } catch (ArgumentOutOfRangeException e) { // .NET 3.5 please PUtil.LogException(e); } catch (FormatException e) { PUtil.LogException(e); } catch (OverflowException e) { PUtil.LogException(e); } if (latest != null) { // Store the winning version PSharedData.PutData(KEY_VERSION, latestVer.ToString()); try { Traverse.Create(latest).CallMethod("Apply", instance.PLibInstance); } catch (ArgumentException e) { PUtil.LogException(e); } } // Reduce memory usage by cleaning up the patch list instance.Patches.Clear(); } else { #if DEBUG LogPatchWarning("ApplyLatest invoked with no Instance!"); #endif } }
/// <summary> /// Runs all patches for all mods at the given time. Only to be run by the forwarded /// instance! /// </summary> /// <param name="when">The runtime (do not use Immediate) of patches to run.</param> internal static void RunAll(uint when) { SharedRunList toRun; lock (PSharedData.GetLock(PRegistry.KEY_POSTLOAD_LOCK)) { InitMaster(); if (!master.TryGetValue(when, out toRun)) { toRun = null; } } if (toRun != null) { PRegistry.LogPatchDebug("Executing handlers for stage {1} from {0:D} mod(s)". F(toRun.Count, RunAt.ToString(when))); foreach (var modHandler in toRun) { modHandler?.Invoke(when); } } }
/// <summary> /// Registers a method which will be run after PLib and all mods load. It will be /// passed a HarmonyInstance which can be used to make late patches. /// </summary> /// <param name="callback">The method to invoke.</param> public static void RegisterPostload(PostLoadHandler callback) { if (callback == null) { throw new ArgumentNullException("callback"); } lock (PSharedData.GetLock(PRegistry.KEY_POSTLOAD_LOCK)) { // Get list holding postload information var list = PSharedData.GetData <IList <PostLoadHandler> >(PRegistry. KEY_POSTLOAD_TABLE); if (list == null) { PSharedData.PutData(PRegistry.KEY_POSTLOAD_TABLE, list = new List <PostLoadHandler>(16)); } list.Add(callback); string name = Assembly.GetCallingAssembly()?.GetName()?.Name; if (name != null) { PRegistry.LogPatchDebug("Registered post-load handler for " + name); } } }
/// <summary> /// Executes all post-load handlers. /// </summary> internal static void ExecutePostload() { IList <PostLoadHandler> postload = null; lock (PSharedData.GetLock(PRegistry.KEY_POSTLOAD_LOCK)) { // Get list holding postload information var list = PSharedData.GetData <IList <PostLoadHandler> >(PRegistry. KEY_POSTLOAD_TABLE); if (list != null) { postload = new List <PostLoadHandler>(list); } } // If there were any, run them if (postload != null) { var hInst = Harmony.HarmonyInstance.Create("PLib.PostLoad"); PRegistry.LogPatchDebug("Executing {0:D} post-load handler(s)".F(postload. Count)); foreach (var handler in postload) { try { handler?.Invoke(hInst); } catch (Exception e) { var method = handler.Method; // Say which mod's postload crashed if (method != null) { PRegistry.LogPatchWarning("Postload handler for {0} failed:".F( method.DeclaringType.Assembly?.GetName()?.Name ?? "?")); } LogException(e); } } } }