diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs index dba9f064b..03f2214d8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs @@ -9,7 +9,6 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Xml.Linq; using Barotrauma.IO; -using Barotrauma.LuaCs.Events; using Barotrauma.Steam; using Microsoft.Xna.Framework; using OneOf.Types; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 3ebfdd402..a6a9ecb07 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -30,10 +30,21 @@ namespace Barotrauma // == startup _servicesProvider = SetupServicesProvider(); _runStateMachine = SetupStateMachine(); - - _servicesProvider.GetService(); - + _servicesProvider.GetService(); SubscribeToLuaCsEvents(); + PatchEventMethods(); + } + + private void PatchEventMethods() + { + if (_servicesProvider.TryGetService(out var svc)) + { + svc.GenerateMethodHooks(); + Logger.LogDebug($"Patched methods."); + return; + } + + Logger.LogError($"Failed to find {nameof(EventPatchingService)}"); } private void SubscribeToLuaCsEvents() @@ -185,7 +196,7 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); // Extension/Sub Services servicesProvider.RegisterServiceType(ServiceLifetime.Transient); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventPatchingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventPatchingService.cs new file mode 100644 index 000000000..e8bd20593 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventPatchingService.cs @@ -0,0 +1,191 @@ +using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Events; +using Barotrauma.Networking; +using HarmonyLib; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection; +using MonoMod.RuntimeDetour; +using static Barotrauma.ContentPackageManager; + +namespace Barotrauma.LuaCs; + + +internal class EventPatchingService : IService +{ + public bool IsDisposed { get; private set; } + private static EventPatchingService _instance; + private IEventService _eventService; + private ILoggerService _loggerService; + /// + /// Key: Original Method, Value: Active Hook. + /// + private readonly ConcurrentDictionary _runtimeHooks = new(); + + #region METHODINFO + + private static readonly MethodInfo Screen_Select_Orig = + typeof(Screen).GetMethod(nameof(Screen.Select), BindingFlags.Instance | BindingFlags.Public); + + private static readonly MethodInfo EnabledPackages_SetCore_Orig = + typeof(ContentPackageManager.EnabledPackages).GetMethod(nameof(ContentPackageManager.EnabledPackages.SetCore), + BindingFlags.Static | BindingFlags.Public); + + private static readonly MethodInfo EnabledPackages_SetRegular_Orig = + typeof(ContentPackageManager.EnabledPackages).GetMethod(nameof(ContentPackageManager.EnabledPackages.SetRegular), + BindingFlags.Static | BindingFlags.Public); + + private static readonly MethodInfo Character_Create_Orig = + AccessTools.DeclaredMethod(typeof(Character), + nameof(Character.Create), new [] + { + typeof(CharacterPrefab), + typeof(Vector2), + typeof(string), + typeof(CharacterInfo), + typeof(ushort), + typeof(bool), + typeof(bool), + typeof(bool), + typeof(RagdollParams), + typeof(bool) + }); + + + #endregion + + public EventPatchingService(IEventService eventService, ILoggerService loggerService) + { + _eventService = eventService; + _loggerService = loggerService; + _instance = this; + } + + public void GenerateMethodHooks() + { + IService.CheckDisposed(this); + + if (!_runtimeHooks.IsEmpty) + { + _loggerService.LogError($"{nameof(GenerateMethodHooks)}: Hooks are already active!"); + return; + } + + _runtimeHooks.TryAdd(Screen_Select_Orig, new Hook( + Screen_Select_Orig, + this.Screen_Select_Post + )); + + _runtimeHooks.TryAdd(EnabledPackages_SetCore_Orig, new Hook( + EnabledPackages_SetCore_Orig, + typeof(EventPatchingService).GetMethod(nameof(EnabledPackages_SetCore_Post)) + )); + + _runtimeHooks.TryAdd(EnabledPackages_SetRegular_Orig, new Hook( + EnabledPackages_SetRegular_Orig, + typeof(EventPatchingService).GetMethod(nameof(EnabledPackages_SetRegular_Post)) + )); + + _runtimeHooks.TryAdd(Character_Create_Orig, new Hook( + Character_Create_Orig, + this.Character_Create_Post + )); + } + + public void CoroutineManager_Update_Post() + { + _eventService.PublishEvent(x => x.OnUpdate(Timing.TotalTime)); + _loggerService.ProcessLogs(); + } + + public void Screen_Select_Post(Action orig, Screen src) + { + orig(src); + _eventService.PublishEvent(x => x.OnScreenSelected(Screen.Selected)); + } + + public void PackageSource_Refresh_Post() + { + _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); + } + + public void ContentPackageManager_Init_Post() + { + _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); + _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + } + + public static void EnabledPackages_SetCore_Post(Action orig, CorePackage newCore) + { + orig(newCore); + _instance?._eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + } + + public static void EnabledPackages_SetRegular_Post(Action> orig, IReadOnlyList newRegular) + { + orig(newRegular); + _instance?._eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + } + +#if CLIENT + public void GameClient_ReadDataMessage_Pre(IReadMessage inc) + { + ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); + _eventService.PublishEvent(x => x.OnReceivedServerNetMessage(inc, header)); + inc.BitPosition -= 8; // rewind so the game can read the message + } + + public void SubEditorScreen_Selected_Post(Screen __instance) + { + _eventService.PublishEvent(x => x.OnScreenSelected(__instance)); + } + + public void PlayerInput_Update_Pre(double deltaTime) + { + _eventService.PublishEvent(x => x.OnKeyUpdate(deltaTime)); + } +#elif SERVER + public void GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc) + { + ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte(); + _eventService.PublishEvent(x => x.OnReceivedClientNetMessage(inc, header, sender)); + inc.BitPosition -= 8; // rewind so the game can read the message + } +#endif + + // Character.Create(), Line 1411. + public Character Character_Create_Post(Func orig, + CharacterPrefab prefab, Vector2 position, string seed, + CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, + bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, + RagdollParams ragdoll = null, bool spawnInitialItems = true) + { + Character result = orig(prefab, position, seed, characterInfo, id, isRemotePlayer, hasAi, createNetworkEvent, + ragdoll, spawnInitialItems); + _eventService.PublishEvent(x => x.OnCharacterCreated(result)); + return result; + } + + public void Character_GiveJobItems_Post(Character __instance, WayPoint spawnPoint, bool isPvPMode) + { + _eventService.PublishEvent(x => x.OnGiveCharacterJobItems(__instance, spawnPoint, isPvPMode)); + } + + public void Affliction_Update_Post(Affliction __instance, CharacterHealth characterHealth, Limb targetLimb, float deltaTime) + { + _eventService.PublishEvent(x => x.OnAfflictionUpdate(__instance, characterHealth, targetLimb, deltaTime)); + } + + public void Dispose() + { + IsDisposed = true; + foreach (var runtimeHook in _runtimeHooks) + { + runtimeHook.Value.Dispose(); + } + _runtimeHooks.Clear(); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs deleted file mode 100644 index 22186a47e..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Barotrauma.LuaCs; -using Barotrauma.LuaCs.Events; -using Barotrauma.Networking; -using HarmonyLib; -using Microsoft.Xna.Framework; -using System; -using static Barotrauma.ContentPackageManager; - -namespace Barotrauma.LuaCs; - -[HarmonyPatch] -internal class HarmonyEventPatchesService : IService -{ - public bool IsDisposed { get; private set; } - - private static IEventService _eventService; - private static ILoggerService _loggerService; - private readonly Harmony Harmony; - - public HarmonyEventPatchesService(IEventService eventService, ILoggerService loggerService) - { - _eventService = eventService; - _loggerService = loggerService; - Harmony = new Harmony("LuaCsForBarotrauma.Events"); - Harmony.PatchAll(typeof(HarmonyEventPatchesService)); - } - - [HarmonyPatch(typeof(CoroutineManager), nameof(CoroutineManager.Update)), HarmonyPostfix] - public static void CoroutineManager_Update_Post() - { - _eventService.PublishEvent(x => x.OnUpdate(Timing.TotalTime)); - _loggerService.ProcessLogs(); - } - - [HarmonyPatch(typeof(Screen), nameof(Screen.Select)), HarmonyPostfix] - public static void Screen_Selected_Post(Screen __instance) - { - _eventService.PublishEvent(x => x.OnScreenSelected(__instance)); - } - - [HarmonyPatch(typeof(ContentPackageManager.PackageSource), nameof(ContentPackageManager.PackageSource.Refresh)), HarmonyPostfix] - public static void PackageSource_Refresh_Post() - { - _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); - } - - [HarmonyPatch(typeof(ContentPackageManager), nameof(ContentPackageManager.Init)), HarmonyPostfix] - public static void ContentPackageManager_Init_Post() - { - _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); - _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); - } - - [HarmonyPatch(typeof(ContentPackageManager.EnabledPackages), nameof(ContentPackageManager.EnabledPackages.SetCore)), HarmonyPostfix] - public static void EnabledPackages_SetCore_Post() - { - _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); - } - - [HarmonyPatch(typeof(ContentPackageManager.EnabledPackages), nameof(ContentPackageManager.EnabledPackages.SetRegular)), HarmonyPostfix] - public static void EnabledPackages_SetRegular_Post() - { - _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); - } - - - -#if CLIENT - [HarmonyPatch(typeof(GameClient), "ReadDataMessage"), HarmonyPrefix] - public static void GameClient_ReadDataMessage_Pre(IReadMessage inc) - { - ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); - _eventService.PublishEvent(x => x.OnReceivedServerNetMessage(inc, header)); - inc.BitPosition -= 8; // rewind so the game can read the message - } - - [HarmonyPatch(typeof(SubEditorScreen), nameof(SubEditorScreen.Select), new Type[] { }), HarmonyPostfix] - public static void SubEditorScreen_Selected_Post(Screen __instance) - { - _eventService.PublishEvent(x => x.OnScreenSelected(__instance)); - } - - [HarmonyPatch(typeof(PlayerInput), nameof(PlayerInput.Update)), HarmonyPrefix] - public static void PlayerInput_Update_Pre(double deltaTime) - { - _eventService.PublishEvent(x => x.OnKeyUpdate(deltaTime)); - } -#elif SERVER - [HarmonyPatch(typeof(GameServer), "ReadDataMessage"), HarmonyPrefix] - public static void GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc) - { - ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte(); - _eventService.PublishEvent(x => x.OnReceivedClientNetMessage(inc, header, sender)); - inc.BitPosition -= 8; // rewind so the game can read the message - } -#endif - - [HarmonyPatch(typeof(Character), nameof(Character.Create), new[] { - typeof(CharacterPrefab), - typeof(Vector2), - typeof(string), - typeof(CharacterInfo), - typeof(ushort), - typeof(bool), - typeof(bool), - typeof(bool), - typeof(RagdollParams), - typeof(bool) - }), HarmonyPostfix] - public static void Character_Create_Post(Character __result) - { - _eventService.PublishEvent(x => x.OnCharacterCreated(__result)); - } - - [HarmonyPatch(typeof(Character), nameof(Character.GiveJobItems)), HarmonyPostfix] - public static void Character_GiveJobItems_Post(Character __instance, WayPoint spawnPoint, bool isPvPMode) - { - _eventService.PublishEvent(x => x.OnGiveCharacterJobItems(__instance, spawnPoint, isPvPMode)); - } - - [HarmonyPatch(typeof(Affliction), nameof(Affliction.Update)), HarmonyPostfix] - public static void Affliction_Update_Post(Affliction __instance, CharacterHealth characterHealth, Limb targetLimb, float deltaTime) - { - _eventService.PublishEvent(x => x.OnAfflictionUpdate(__instance, characterHealth, targetLimb, deltaTime)); - } - - public void Dispose() - { - Harmony.UnpatchSelf(); - IsDisposed = true; - } -}