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(); } }