From ba6ea33504c511c6860d4e9f23530d64d9958408 Mon Sep 17 00:00:00 2001 From: WhiteFang Date: Wed, 3 Aug 2022 21:11:35 +0200 Subject: [PATCH] [RecoverEmptyContainers] Fixed an exploit granting infinite containers when drinking a Potion/brew and cancelling the ability/usage of it; Bumped version to v1.0.1 --- CHANGELOG.md | 3 + .../Hooks/UseConsumableHook.cs | 69 -------- RecoverEmptyContainers/Plugin.cs | 9 +- .../RecoverEmptyContainers.csproj | 4 +- .../Systems/RecoverEmptyContainersSystem.cs | 149 ++++++++++++++++++ Shared/VModCharacter.cs | 12 ++ .../RecoverEmptyContainers/manifest.json | 2 +- 7 files changed, 176 insertions(+), 72 deletions(-) delete mode 100644 RecoverEmptyContainers/Hooks/UseConsumableHook.cs create mode 100644 RecoverEmptyContainers/Systems/RecoverEmptyContainersSystem.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index bc87d05..3ff58a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ * Initial release ## Recover Empty Containers +### v1.0.1 +* Fixed an exploit where you could get infinite empty containers when cancelling the ability usage of the Potion/Brew + ### v1.0.0 * Initial release diff --git a/RecoverEmptyContainers/Hooks/UseConsumableHook.cs b/RecoverEmptyContainers/Hooks/UseConsumableHook.cs deleted file mode 100644 index 3adff25..0000000 --- a/RecoverEmptyContainers/Hooks/UseConsumableHook.cs +++ /dev/null @@ -1,69 +0,0 @@ -using HarmonyLib; -using ProjectM; -using ProjectM.Network; -using System.Collections.Generic; -using Unity.Collections; -using Unity.Entities; -using VMods.Shared; -using Wetstone.API; - -namespace VMods.RecoverEmptyContainers -{ - [HarmonyPatch] - public class TestHook - { - #region Consts - - private static readonly Dictionary RecipeItemToReturnedItemMapping = new() - { - [new PrefabGUID(-1322000172)] = new PrefabGUID(-810738866),// Water-filled Canteen -> Empty Canteen - [new PrefabGUID(-1382451936)] = new PrefabGUID(-437611596),// Water-filled Bottle -> Empty Glass Bottle - }; - - #endregion - - #region Public Methods - - [HarmonyPatch(typeof(UseConsumableSystem), nameof(UseConsumableSystem.CastAbility))] - [HarmonyPostfix] - public static void CastAbility(UseConsumableSystem __instance, InventoryBuffer inventoryItem, FromCharacter fromCharacter, NativeHashMap prefabLookupMap, Entity itemEntity, bool removeByItemEntity, ref bool shouldConsumeItem) - { - if(!VWorld.IsServer) - { - return; - } - - var server = VWorld.Server; - var entityManager = server.EntityManager; - var gameDataSystem = server.GetExistingSystem(); - - foreach(var kvp in gameDataSystem.RecipeHashLookupMap) - { - var recipeData = kvp.Value; - if(entityManager.HasComponent(recipeData.Entity)) - { - var outputBuffer = entityManager.GetBuffer(recipeData.Entity); - foreach(var output in outputBuffer) - { - if(output.Guid == inventoryItem.ItemType) - { - if(entityManager.HasComponent(recipeData.Entity)) - { - var requirements = entityManager.GetBuffer(recipeData.Entity); - foreach(var requirement in requirements) - { - if(RecipeItemToReturnedItemMapping.TryGetValue(requirement.Guid, out var returnItemGUID)) - { - Utils.TryGiveItem(entityManager, gameDataSystem.ItemHashLookupMap, fromCharacter.Character, returnItemGUID, requirement.Stacks, out _, out _, dropRemainder: true); - } - } - } - } - } - } - } - } - - #endregion - } -} diff --git a/RecoverEmptyContainers/Plugin.cs b/RecoverEmptyContainers/Plugin.cs index d49fd68..00ec137 100644 --- a/RecoverEmptyContainers/Plugin.cs +++ b/RecoverEmptyContainers/Plugin.cs @@ -10,7 +10,7 @@ namespace VMods.RecoverEmptyContainers [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] [BepInDependency("xyz.molenzwiebel.wetstone")] [Reloadable] - public class Plugin : BasePlugin + public class Plugin : BasePlugin, IRunOnInitialized { #region Variables @@ -28,6 +28,7 @@ public sealed override void Load() return; } Utils.Initialize(Log, PluginInfo.PLUGIN_NAME); + RecoverEmptyContainersConfig.Initialize(Config); _hooks = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly()); @@ -35,6 +36,11 @@ public sealed override void Load() Log.LogInfo($"Plugin {PluginInfo.PLUGIN_NAME} (v{PluginInfo.PLUGIN_VERSION}) is loaded!"); } + public void OnGameInitialized() + { + RecoverEmptyContainersSystem.Initialize(); + } + public sealed override bool Unload() { if(VWorld.IsClient) @@ -42,6 +48,7 @@ public sealed override bool Unload() return true; } _hooks?.UnpatchSelf(); + RecoverEmptyContainersSystem.Deinitialize(); Config.Clear(); Utils.Deinitialize(); return true; diff --git a/RecoverEmptyContainers/RecoverEmptyContainers.csproj b/RecoverEmptyContainers/RecoverEmptyContainers.csproj index 3972288..d34fcae 100644 --- a/RecoverEmptyContainers/RecoverEmptyContainers.csproj +++ b/RecoverEmptyContainers/RecoverEmptyContainers.csproj @@ -4,7 +4,7 @@ VMods.RecoverEmptyContainers VMods.RecoverEmptyContainers Allows a player to recover an empty container when consuming a potion/brew - 1.0.0 + 1.0.1 true latest False @@ -17,7 +17,9 @@ + + diff --git a/RecoverEmptyContainers/Systems/RecoverEmptyContainersSystem.cs b/RecoverEmptyContainers/Systems/RecoverEmptyContainersSystem.cs new file mode 100644 index 0000000..856b122 --- /dev/null +++ b/RecoverEmptyContainers/Systems/RecoverEmptyContainersSystem.cs @@ -0,0 +1,149 @@ +using ProjectM; +using ProjectM.Scripting; +using System.Collections.Generic; +using Unity.Entities; +using VMods.Shared; +using Wetstone.API; + +namespace VMods.RecoverEmptyContainers +{ + public static class RecoverEmptyContainersSystem + { + #region Variables + + private static readonly Dictionary BuffToEmptyContainerMapping = new(); + + private static readonly Dictionary RecipeItemToReturnedItemMapping = new() + { + [new PrefabGUID(-1322000172)] = new PrefabGUID(-810738866),// Water-filled Canteen -> Empty Canteen + [new PrefabGUID(-1382451936)] = new PrefabGUID(-437611596),// Water-filled Bottle -> Empty Glass Bottle + }; + + #endregion + + #region Public Methods + + public static void Initialize() + { + BuildBuffToEmptyContainerMapping(); + + BuffSystemHook.ProcessBuffEvent += OnProcessBuffEvent; + } + + public static void Deinitialize() + { + BuffSystemHook.ProcessBuffEvent -= OnProcessBuffEvent; + + BuffToEmptyContainerMapping.Clear(); + } + + #endregion + + #region Private Methods + + private static void OnProcessBuffEvent(Entity entity, PrefabGUID buffGUID) + { + var server = VWorld.Server; + var entityManager = server.EntityManager; + + if(!entityManager.HasComponent(entity) || !entityManager.HasComponent(entity)) + { + return; + } + + var entityOwner = entityManager.GetComponentData(entity); + var entityCreator = entityManager.GetComponentData(entity); + var entityCreatorEntity = entityCreator.Creator._Entity; + if(!entityManager.HasComponent(entityCreatorEntity)) + { + return; + } + + var abilityOwner = entityManager.GetComponentData(entityCreatorEntity); + var abilityGroupEntity = abilityOwner.AbilityGroup._Entity; + if(!entityManager.HasComponent(abilityGroupEntity)) + { + return; + } + + var abilityGroupPrefabGUID = entityManager.GetComponentData(abilityGroupEntity); + if(!BuffToEmptyContainerMapping.TryGetValue(abilityGroupPrefabGUID, out var returnData)) + { + return; + } + + var gameDataSystem = server.GetExistingSystem()._GameDataSystem; + + Utils.TryGiveItem(entityManager, gameDataSystem.ItemHashLookupMap, entityOwner.Owner, returnData.itemGUID, returnData.stackCount, out _, out _, dropRemainder: true); + } + + private static void BuildBuffToEmptyContainerMapping() + { + BuffToEmptyContainerMapping.Clear(); + + var server = VWorld.Server; + var entityManager = server.EntityManager; + var gameDataSystem = server.GetExistingSystem()._GameDataSystem; + var prefabCollectionSystem = server.GetExistingSystem(); + + var duplicateBuffs = new List(); + + foreach(var recipeKvp in gameDataSystem.RecipeHashLookupMap) + { + var recipeEntity = recipeKvp.Value.Entity; + if(!entityManager.HasComponent(recipeEntity) || !entityManager.HasComponent(recipeEntity)) + { + continue; + } + + // Find the returned item + PrefabGUID? returnItemGUID = null; + int returnItemStackCount = 0; + var requirementBuffer = entityManager.GetBuffer(recipeEntity); + foreach(var requirement in requirementBuffer) + { + if(RecipeItemToReturnedItemMapping.TryGetValue(requirement.Guid, out var prefabGUID)) + { + returnItemGUID = prefabGUID; + returnItemStackCount = requirement.Stacks; + break; + } + } + + // Check if we've found a returnable item + if(!returnItemGUID.HasValue) + { + continue; + } + + // Find the buff that belongs to this item + var outputBuffer = entityManager.GetBuffer(recipeEntity); + foreach(var output in outputBuffer) + { + var outputEntity = prefabCollectionSystem.PrefabLookupMap[output.Guid]; + if(!entityManager.HasComponent(outputEntity)) + { + continue; + } + + var castAbilityOnConsuime = entityManager.GetComponentData(outputEntity); + var abilityGUID = castAbilityOnConsuime.AbilityGuid; + +#if DEBUG + Utils.Logger.LogMessage($"Matched Buff {prefabCollectionSystem.PrefabNameLookupMap[abilityGUID]} -> {prefabCollectionSystem.PrefabNameLookupMap[returnItemGUID.Value]}"); +#endif + if(BuffToEmptyContainerMapping.ContainsKey(abilityGUID)) + { + Utils.Logger.LogWarning($"Found duplicate Buff {prefabCollectionSystem.PrefabNameLookupMap[abilityGUID]}. Remove it!"); + duplicateBuffs.Add(abilityGUID); + } + BuffToEmptyContainerMapping[abilityGUID] = (returnItemGUID.Value, returnItemStackCount); + } + } + + duplicateBuffs.ForEach(x => BuffToEmptyContainerMapping.Remove(x)); + } + + #endregion + } +} diff --git a/Shared/VModCharacter.cs b/Shared/VModCharacter.cs index d705c71..e8ab8ec 100644 --- a/Shared/VModCharacter.cs +++ b/Shared/VModCharacter.cs @@ -43,6 +43,18 @@ public VModCharacter(Entity userEntity, Entity charEntity, EntityManager? entity }; } + public VModCharacter(Entity charEntity, EntityManager? entityManager = null) + { + entityManager ??= Utils.CurrentWorld.EntityManager; + Character = entityManager.Value.GetComponentData(charEntity); + User = entityManager.Value.GetComponentData(Character.UserEntity._Entity); + FromCharacter = new FromCharacter() + { + User = Character.UserEntity._Entity, + Character = User.LocalCharacter._Entity, + }; + } + #endregion #region Public Methods diff --git a/Thunderstone/RecoverEmptyContainers/manifest.json b/Thunderstone/RecoverEmptyContainers/manifest.json index ecc1f3a..89e6388 100644 --- a/Thunderstone/RecoverEmptyContainers/manifest.json +++ b/Thunderstone/RecoverEmptyContainers/manifest.json @@ -1,7 +1,7 @@ { "name": "VMods_Recover_Empty_Containers", "description": "A mod that allows players to recover empty containers when drinking potions or brews.", - "version_number": "1.0.0", + "version_number": "1.0.1", "dependencies": [ "BepInEx-BepInExPack_V_Rising-1.0.0", "molenzwiebel-Wetstone-1.1.0"