From faada7fbc470da8e1b8aa659dbe27a0bb060d605 Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Thu, 12 Mar 2026 20:25:45 -0500 Subject: [PATCH] feat(api): expand block/item hooks and debug tools --- ExampleMod/ExampleMod.cs | 171 +++- ExampleMod/assets/examplemod/lang/en-GB.lang | 2 + WeaveLoader.API/Block/CustomBlock.cs | 621 +++++++++++ WeaveLoader.API/Item/CustomItem.cs | 433 +++++++- WeaveLoader.Core/WeaveLoaderCore.cs | 195 ++++ WeaveLoaderRuntime/src/DotNetHost.cpp | 136 +++ WeaveLoaderRuntime/src/DotNetHost.h | 15 + WeaveLoaderRuntime/src/GameHooks.cpp | 962 +++++++++++++++++- WeaveLoaderRuntime/src/GameHooks.h | 54 + WeaveLoaderRuntime/src/HookManager.cpp | 241 +++++ .../src/Symbols/SymbolGroups.cpp | 68 ++ WeaveLoaderRuntime/src/Symbols/SymbolGroups.h | 16 + 12 files changed, 2883 insertions(+), 31 deletions(-) diff --git a/ExampleMod/ExampleMod.cs b/ExampleMod/ExampleMod.cs index 0f536e7..81063e7 100644 --- a/ExampleMod/ExampleMod.cs +++ b/ExampleMod/ExampleMod.cs @@ -16,12 +16,14 @@ public class ExampleMod : IMod public static RegisteredBlock? RubyWoodPlanks; public static RegisteredBlock? RubyChair; public static RegisteredBlock? RubySand; + public static RegisteredBlock? DebugHooksBlock; public static RegisteredSlabBlock? RubyStoneSlab; public static RegisteredSlabBlock? RubyWoodSlab; public static RegisteredBlock? RubyLamp; public static RegisteredBlock? RubyLampLit; public static RegisteredBlock? Orichalcum; public static RegisteredItem? Ruby; + public static RegisteredItem? DebugHooksItemEntry; public static RegisteredItem? RubyPickaxeItem; public static RegisteredItem? RubyShovelItem; public static RegisteredItem? RubyHoeItem; @@ -37,9 +39,6 @@ public class ExampleMod : IMod public override UseItemResult OnUseItem(UseItemContext context) { - if (context.IsTestUseOnly) - return UseItemResult.CancelVanilla; - long now = Environment.TickCount64; ref long nextUseAtMs = ref context.IsClientSide ? ref _nextClientUseAtMs : ref _nextServerUseAtMs; if (now < nextUseAtMs) @@ -190,6 +189,153 @@ public class ExampleMod : IMod } } + private sealed class DebugHooks : WeaveLoader.API.Block.Block + { + private static void Log(string hook, BlockUpdateContext block, string? extra = null) + { + if (string.IsNullOrWhiteSpace(extra)) + Logger.Info($"DebugHooks::{hook} at ({block.X}, {block.Y}, {block.Z}) blockId={block.BlockId} client={block.IsClientSide}"); + else + Logger.Info($"DebugHooks::{hook} at ({block.X}, {block.Y}, {block.Z}) blockId={block.BlockId} client={block.IsClientSide} {extra}"); + } + + public override void OnPlace(BlockUpdateContext context) + { + Log("OnPlace", context); + } + + public override void OnNeighborChanged(BlockNeighborChangedContext context) + { + Log("OnNeighborChanged", context.Block, $"neighborId={context.NeighborBlockId}"); + } + + public override void OnScheduledTick(BlockTickContext context) + { + Log("OnScheduledTick", context.Block); + } + + public override BlockActionResult OnUse(BlockUseContext context) + { + Log("OnUse", context.Block, + $"face={context.Face} click=({context.ClickX:0.00},{context.ClickY:0.00},{context.ClickZ:0.00}) soundOnly={context.SoundOnly}"); + return BlockActionResult.ContinueVanilla; + } + + public override void OnStepOn(BlockEntityContext context) + { + Log("OnStepOn", context.Block, $"entityPtr=0x{context.NativeEntityPtr.ToString("X")}"); + } + + public override void OnEntityInsideTile(BlockEntityContext context) + { + Log("OnEntityInsideTile", context.Block, $"entityPtr=0x{context.NativeEntityPtr.ToString("X")}"); + } + + public override void OnFallOn(BlockFallContext context) + { + Log("OnFallOn", context.Block, $"entityPtr=0x{context.NativeEntityPtr.ToString("X")} fall={context.FallDistance:0.00}"); + } + + public override void OnRemoving(BlockRemovingContext context) + { + Log("OnRemoving", context.Block, $"blockData={context.BlockData}"); + } + + public override void OnRemoved(BlockRemoveContext context) + { + Log("OnRemoved", context.Block, $"removedId={context.RemovedBlockId} removedData={context.RemovedBlockData}"); + } + + public override void OnDestroyed(BlockDestroyContext context) + { + Log("OnDestroyed", context.Block, $"blockData={context.BlockData}"); + } + + public override void OnPlayerDestroy(BlockPlayerDestroyContext context) + { + Log("OnPlayerDestroy", context.Block, + $"playerPtr=0x{context.NativePlayerPtr.ToString("X")} blockData={context.BlockData}"); + } + + public override void OnPlayerWillDestroy(BlockPlayerWillDestroyContext context) + { + Log("OnPlayerWillDestroy", context.Block, + $"playerPtr=0x{context.NativePlayerPtr.ToString("X")} blockData={context.BlockData}"); + } + + public override void OnPlacedBy(BlockPlacedByContext context) + { + Log("OnPlacedBy", context.Block, + $"placerPtr=0x{context.NativePlacerPtr.ToString("X")} itemPtr=0x{context.NativeItemInstancePtr.ToString("X")}"); + } + + } + + private sealed class DebugHooksItem : Item + { + private static void Log(string hook, string? extra = null) + { + if (string.IsNullOrWhiteSpace(extra)) + Logger.Info($"DebugItem::{hook}"); + else + Logger.Info($"DebugItem::{hook} {extra}"); + } + + public override MineBlockResult OnMineBlock(MineBlockContext context) + { + Log("OnMineBlock", $"itemId={context.ItemId} tileId={context.TileId} pos=({context.X},{context.Y},{context.Z})"); + return MineBlockResult.ContinueVanilla; + } + + public override UseItemResult OnUseItem(UseItemContext context) + { + Log("OnUseItem", + $"itemId={context.ItemId} client={context.IsClientSide} " + + $"itemPtr=0x{context.NativeItemInstancePtr.ToString("X")} playerPtr=0x{context.NativePlayerPtr.ToString("X")}"); + return UseItemResult.ContinueVanilla; + } + + public override ItemActionResult OnUseOn(UseOnItemContext context) + { + Log("OnUseOn", + $"itemId={context.ItemId} client={context.IsClientSide} " + + $"pos=({context.X},{context.Y},{context.Z}) face={context.Face} " + + $"click=({context.ClickX:0.00},{context.ClickY:0.00},{context.ClickZ:0.00}) " + + $"playerPtr=0x{context.NativePlayerPtr.ToString("X")} itemPtr=0x{context.NativeItemInstancePtr.ToString("X")}"); + return ItemActionResult.ContinueVanilla; + } + + public override ItemActionResult OnInteractEntity(ItemEntityInteractionContext context) + { + Log("OnInteractEntity", + $"itemId={context.ItemId} playerPtr=0x{context.NativePlayerPtr.ToString("X")} " + + $"targetPtr=0x{context.NativeTargetEntityPtr.ToString("X")} itemPtr=0x{context.NativeItemInstancePtr.ToString("X")}"); + return ItemActionResult.ContinueVanilla; + } + + public override ItemActionResult OnHurtEntity(ItemEntityInteractionContext context) + { + Log("OnHurtEntity", + $"itemId={context.ItemId} playerPtr=0x{context.NativePlayerPtr.ToString("X")} " + + $"targetPtr=0x{context.NativeTargetEntityPtr.ToString("X")} itemPtr=0x{context.NativeItemInstancePtr.ToString("X")}"); + return ItemActionResult.ContinueVanilla; + } + + public override void OnInventoryTick(ItemInventoryTickContext context) + { + Log("OnInventoryTick", + $"itemId={context.ItemId} slot={context.Slot} selected={context.IsSelected} client={context.IsClientSide} " + + $"ownerPtr=0x{context.NativeOwnerEntityPtr.ToString("X")} itemPtr=0x{context.NativeItemInstancePtr.ToString("X")}"); + } + + public override void OnCraftedBy(ItemCraftedByContext context) + { + Log("OnCraftedBy", + $"itemId={context.ItemId} amount={context.Amount} client={context.IsClientSide} " + + $"playerPtr=0x{context.NativePlayerPtr.ToString("X")} itemPtr=0x{context.NativeItemInstancePtr.ToString("X")}"); + } + } + public void OnInitialize() { GameEvents.OnWorldLoaded += (_, e) => @@ -262,6 +408,17 @@ public class ExampleMod : IMod .InCreativeTab(CreativeTab.BuildingBlocks) .Prepend()); + DebugHooksBlock = Registry.Block.Register("examplemod:debug_hooks", + new DebugHooks(), + new BlockProperties() + .Material(MaterialType.Stone) + .Hardness(1.0f) + .Resistance(5.0f) + .Sound(SoundType.Stone) + .Icon("examplemod:block/ruby_stone") + .Name(Text.Translatable("block.examplemod.debug_hooks")) + .InCreativeTab(CreativeTab.BuildingBlocks)); + RubyStoneSlab = (RegisteredSlabBlock)Registry.Block.Register("examplemod:ruby_stone_slab", new SlabBlock(), new BlockProperties() @@ -336,6 +493,14 @@ public class ExampleMod : IMod .Name(Text.Translatable("item.examplemod.ruby")) .InCreativeTab(CreativeTab.Materials)); + DebugHooksItemEntry = Registry.Item.Register("examplemod:debug_item", + new DebugHooksItem(), + new ItemProperties() + .MaxStackSize(1) + .Icon("examplemod:item/ruby") + .Name(Text.Translatable("item.examplemod.debug_item")) + .InCreativeTab(CreativeTab.ToolsAndWeapons)); + Registry.Item.RegisterToolMaterial("examplemod:ruby_material", new ToolMaterialDefinition() .BaseTier(ToolTier.Diamond) diff --git a/ExampleMod/assets/examplemod/lang/en-GB.lang b/ExampleMod/assets/examplemod/lang/en-GB.lang index cfdc972..bfa7238 100644 --- a/ExampleMod/assets/examplemod/lang/en-GB.lang +++ b/ExampleMod/assets/examplemod/lang/en-GB.lang @@ -11,7 +11,9 @@ block.examplemod.ruby_wood_slab_double=Ruby Wood Slab block.examplemod.ruby_lamp=Ruby Lamp block.examplemod.ruby_lamp_lit=Ruby Lamp block.examplemod.orichalcum_ore=Orichalcum Ore +block.examplemod.debug_hooks=Debug Hooks Block item.examplemod.ruby=Ruby +item.examplemod.debug_item=Debug Hooks Item item.examplemod.ruby_sword=Ruby Sword item.examplemod.ruby_shovel=Ruby Shovel item.examplemod.ruby_pickaxe=Ruby Pickaxe diff --git a/WeaveLoader.API/Block/CustomBlock.cs b/WeaveLoader.API/Block/CustomBlock.cs index 2c49757..49fa965 100644 --- a/WeaveLoader.API/Block/CustomBlock.cs +++ b/WeaveLoader.API/Block/CustomBlock.cs @@ -2,35 +2,139 @@ using System.Runtime.InteropServices; namespace WeaveLoader.API.Block; +/// +/// Base class for managed custom blocks. +/// Mods can inherit and override callbacks for block behavior. +/// public abstract class Block { + /// The namespaced ID used during registration. public Identifier? Id { get; internal set; } + + /// The numeric runtime ID allocated by the game. public int NumericId { get; internal set; } = -1; + /// Optional override for the block to drop when broken. public Identifier? DropAsBlockId { get; init; } + + /// Optional override for the block to clone via pick-block. public Identifier? CloneAsBlockId { get; init; } + /// + /// Called when the block is placed in the world. + /// public virtual void OnPlace(BlockUpdateContext context) { } + /// + /// Called when a neighboring block changes. + /// public virtual void OnNeighborChanged(BlockNeighborChangedContext context) { } + /// + /// Called when a scheduled tick for this block fires. + /// public virtual void OnScheduledTick(BlockTickContext context) { } + + /// + /// Called when the block is used (right-click). + /// Return to run vanilla logic, + /// or to skip vanilla handling. + /// + public virtual BlockActionResult OnUse(BlockUseContext context) => BlockActionResult.ContinueVanilla; + + /// + /// Called when an entity steps on the block. + /// + public virtual void OnStepOn(BlockEntityContext context) + { + } + + /// + /// Called when an entity is inside the block's volume. + /// + public virtual void OnEntityInsideTile(BlockEntityContext context) + { + } + + /// + /// Called when an entity falls onto the block. + /// + public virtual void OnFallOn(BlockFallContext context) + { + } + + /// + /// Called before the block is removed from the world. + /// + public virtual void OnRemoving(BlockRemovingContext context) + { + } + + /// + /// Called after the block is removed from the world. + /// + public virtual void OnRemoved(BlockRemoveContext context) + { + } + + /// + /// Called when the block is destroyed by the world (non-player). + /// + public virtual void OnDestroyed(BlockDestroyContext context) + { + } + + /// + /// Called when the block is destroyed by a player. + /// + public virtual void OnPlayerDestroy(BlockPlayerDestroyContext context) + { + } + + /// + /// Called just before a player destroys the block. + /// + public virtual void OnPlayerWillDestroy(BlockPlayerWillDestroyContext context) + { + } + + /// + /// Called when the block is placed by a player with an item. + /// + public virtual void OnPlacedBy(BlockPlacedByContext context) + { + } + } +/// Managed falling block base class. public class FallingBlock : Block { } +/// Managed slab block base class. public class SlabBlock : Block { } +/// +/// Result of managed block action callbacks. +/// +public enum BlockActionResult +{ + ContinueVanilla = 0, + CancelVanilla = 1 +} + +/// +/// Runtime context for block update callbacks. +/// public readonly struct BlockUpdateContext { public int BlockId { get; } @@ -58,6 +162,9 @@ public readonly struct BlockUpdateContext return NativeInterop.native_level_has_neighbor_signal(NativeLevelPtr, X, Y, Z) != 0; } + /// + /// Set the block at this context's position. + /// public bool SetBlock(Identifier id, int data = 0, int flags = 2) { int numericId = IdHelper.GetBlockNumericId(id); @@ -67,6 +174,9 @@ public readonly struct BlockUpdateContext return SetBlock(numericId, data, flags); } + /// + /// Set the block at this context's position using a numeric ID. + /// public bool SetBlock(int numericBlockId, int data = 0, int flags = 2) { if (NativeLevelPtr == 0 || numericBlockId < 0) @@ -75,6 +185,9 @@ public readonly struct BlockUpdateContext return NativeInterop.native_level_set_tile(NativeLevelPtr, X, Y, Z, numericBlockId, data, flags) != 0; } + /// + /// Schedule a tick for this block after a delay (in ticks). + /// public bool ScheduleTick(int delay) { if (NativeLevelPtr == 0 || delay < 0) @@ -83,6 +196,9 @@ public readonly struct BlockUpdateContext return NativeInterop.native_level_schedule_tick(NativeLevelPtr, X, Y, Z, BlockId, delay) != 0; } + /// + /// Read a block ID relative to this context's position. + /// public int GetBlockId(int offsetX = 0, int offsetY = 0, int offsetZ = 0) { if (NativeLevelPtr == 0) @@ -92,6 +208,9 @@ public readonly struct BlockUpdateContext } } +/// +/// Runtime context for block neighbor-change callback. +/// public readonly struct BlockNeighborChangedContext { public BlockUpdateContext Block { get; } @@ -104,6 +223,9 @@ public readonly struct BlockNeighborChangedContext } } +/// +/// Runtime context for scheduled tick callback. +/// public readonly struct BlockTickContext { public BlockUpdateContext Block { get; } @@ -114,6 +236,161 @@ public readonly struct BlockTickContext } } +/// +/// Runtime context for block use callback. +/// +public readonly struct BlockUseContext +{ + public BlockUpdateContext Block { get; } + public nint NativePlayerPtr { get; } + public int Face { get; } + public float ClickX { get; } + public float ClickY { get; } + public float ClickZ { get; } + public bool SoundOnly { get; } + + internal BlockUseContext(BlockUpdateContext block, nint nativePlayerPtr, int face, float clickX, float clickY, float clickZ, bool soundOnly) + { + Block = block; + NativePlayerPtr = nativePlayerPtr; + Face = face; + ClickX = clickX; + ClickY = clickY; + ClickZ = clickZ; + SoundOnly = soundOnly; + } +} + +/// +/// Runtime context for entity-on-block callbacks. +/// +public readonly struct BlockEntityContext +{ + public BlockUpdateContext Block { get; } + public nint NativeEntityPtr { get; } + + internal BlockEntityContext(BlockUpdateContext block, nint nativeEntityPtr) + { + Block = block; + NativeEntityPtr = nativeEntityPtr; + } +} + +/// +/// Runtime context for block fall-on callback. +/// +public readonly struct BlockFallContext +{ + public BlockUpdateContext Block { get; } + public nint NativeEntityPtr { get; } + public float FallDistance { get; } + + internal BlockFallContext(BlockUpdateContext block, nint nativeEntityPtr, float fallDistance) + { + Block = block; + NativeEntityPtr = nativeEntityPtr; + FallDistance = fallDistance; + } +} + +/// +/// Runtime context for block removing callback. +/// +public readonly struct BlockRemovingContext +{ + public BlockUpdateContext Block { get; } + public int BlockData { get; } + + internal BlockRemovingContext(BlockUpdateContext block, int blockData) + { + Block = block; + BlockData = blockData; + } +} + +/// +/// Runtime context for block removed callback. +/// +public readonly struct BlockRemoveContext +{ + public BlockUpdateContext Block { get; } + public int RemovedBlockId { get; } + public int RemovedBlockData { get; } + + internal BlockRemoveContext(BlockUpdateContext block, int removedBlockId, int removedBlockData) + { + Block = block; + RemovedBlockId = removedBlockId; + RemovedBlockData = removedBlockData; + } +} + +/// +/// Runtime context for block destroyed callback. +/// +public readonly struct BlockDestroyContext +{ + public BlockUpdateContext Block { get; } + public int BlockData { get; } + + internal BlockDestroyContext(BlockUpdateContext block, int blockData) + { + Block = block; + BlockData = blockData; + } +} + +/// +/// Runtime context for player-destroyed callback. +/// +public readonly struct BlockPlayerDestroyContext +{ + public BlockUpdateContext Block { get; } + public nint NativePlayerPtr { get; } + public int BlockData { get; } + + internal BlockPlayerDestroyContext(BlockUpdateContext block, nint nativePlayerPtr, int blockData) + { + Block = block; + NativePlayerPtr = nativePlayerPtr; + BlockData = blockData; + } +} + +/// +/// Runtime context for player-will-destroy callback. +/// +public readonly struct BlockPlayerWillDestroyContext +{ + public BlockUpdateContext Block { get; } + public nint NativePlayerPtr { get; } + public int BlockData { get; } + + internal BlockPlayerWillDestroyContext(BlockUpdateContext block, nint nativePlayerPtr, int blockData) + { + Block = block; + NativePlayerPtr = nativePlayerPtr; + BlockData = blockData; + } +} + +/// +/// Runtime context for block placed-by callback. +/// +public readonly struct BlockPlacedByContext +{ + public BlockUpdateContext Block { get; } + public nint NativePlacerPtr { get; } + public nint NativeItemInstancePtr { get; } + + internal BlockPlacedByContext(BlockUpdateContext block, nint nativePlacerPtr, nint nativeItemInstancePtr) + { + Block = block; + NativePlacerPtr = nativePlacerPtr; + NativeItemInstancePtr = nativeItemInstancePtr; + } +} + [StructLayout(LayoutKind.Sequential)] internal struct BlockUpdateNativeArgs { @@ -132,6 +409,79 @@ internal struct BlockNeighborChangedNativeArgs public int NeighborBlockId; } +[StructLayout(LayoutKind.Sequential)] +internal struct BlockUseNativeArgs +{ + public BlockUpdateNativeArgs Block; + public nint PlayerPtr; + public int Face; + public float ClickX; + public float ClickY; + public float ClickZ; + public int SoundOnly; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct BlockEntityNativeArgs +{ + public BlockUpdateNativeArgs Block; + public nint EntityPtr; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct BlockFallNativeArgs +{ + public BlockUpdateNativeArgs Block; + public nint EntityPtr; + public float FallDistance; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct BlockRemovingNativeArgs +{ + public BlockUpdateNativeArgs Block; + public int BlockData; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct BlockRemoveNativeArgs +{ + public BlockUpdateNativeArgs Block; + public int RemovedBlockId; + public int RemovedBlockData; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct BlockDestroyNativeArgs +{ + public BlockUpdateNativeArgs Block; + public int BlockData; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct BlockPlayerDestroyNativeArgs +{ + public BlockUpdateNativeArgs Block; + public nint PlayerPtr; + public int BlockData; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct BlockPlayerWillDestroyNativeArgs +{ + public BlockUpdateNativeArgs Block; + public nint PlayerPtr; + public int BlockData; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct BlockPlacedByNativeArgs +{ + public BlockUpdateNativeArgs Block; + public nint PlacerPtr; + public nint ItemInstancePtr; +} + internal static class ManagedBlockDispatcher { private static readonly object s_lock = new(); @@ -223,4 +573,275 @@ internal static class ManagedBlockDispatcher nativeArgs.Z))); return 1; } + + internal static int HandleUse(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockUseNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block); + } + + if (block == null) + return 0; + + var update = new BlockUpdateContext( + nativeArgs.Block.BlockId, + nativeArgs.Block.IsClientSide != 0, + nativeArgs.Block.LevelPtr, + nativeArgs.Block.X, + nativeArgs.Block.Y, + nativeArgs.Block.Z); + + var result = block.OnUse(new BlockUseContext( + update, + nativeArgs.PlayerPtr, + nativeArgs.Face, + nativeArgs.ClickX, + nativeArgs.ClickY, + nativeArgs.ClickZ, + nativeArgs.SoundOnly != 0)); + + return result == BlockActionResult.CancelVanilla ? 2 : 1; + } + + internal static int HandleStepOn(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockEntityNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block); + } + + if (block == null) + return 0; + + var update = new BlockUpdateContext( + nativeArgs.Block.BlockId, + nativeArgs.Block.IsClientSide != 0, + nativeArgs.Block.LevelPtr, + nativeArgs.Block.X, + nativeArgs.Block.Y, + nativeArgs.Block.Z); + block.OnStepOn(new BlockEntityContext(update, nativeArgs.EntityPtr)); + return 1; + } + + internal static int HandleEntityInsideTile(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockEntityNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block); + } + + if (block == null) + return 0; + + var update = new BlockUpdateContext( + nativeArgs.Block.BlockId, + nativeArgs.Block.IsClientSide != 0, + nativeArgs.Block.LevelPtr, + nativeArgs.Block.X, + nativeArgs.Block.Y, + nativeArgs.Block.Z); + block.OnEntityInsideTile(new BlockEntityContext(update, nativeArgs.EntityPtr)); + return 1; + } + + internal static int HandleFallOn(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockFallNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block); + } + + if (block == null) + return 0; + + var update = new BlockUpdateContext( + nativeArgs.Block.BlockId, + nativeArgs.Block.IsClientSide != 0, + nativeArgs.Block.LevelPtr, + nativeArgs.Block.X, + nativeArgs.Block.Y, + nativeArgs.Block.Z); + block.OnFallOn(new BlockFallContext(update, nativeArgs.EntityPtr, nativeArgs.FallDistance)); + return 1; + } + + internal static int HandleRemoving(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockRemovingNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block); + } + + if (block == null) + return 0; + + var update = new BlockUpdateContext( + nativeArgs.Block.BlockId, + nativeArgs.Block.IsClientSide != 0, + nativeArgs.Block.LevelPtr, + nativeArgs.Block.X, + nativeArgs.Block.Y, + nativeArgs.Block.Z); + block.OnRemoving(new BlockRemovingContext(update, nativeArgs.BlockData)); + return 1; + } + + internal static int HandleRemoved(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockRemoveNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block); + } + + if (block == null) + return 0; + + var update = new BlockUpdateContext( + nativeArgs.Block.BlockId, + nativeArgs.Block.IsClientSide != 0, + nativeArgs.Block.LevelPtr, + nativeArgs.Block.X, + nativeArgs.Block.Y, + nativeArgs.Block.Z); + block.OnRemoved(new BlockRemoveContext(update, nativeArgs.RemovedBlockId, nativeArgs.RemovedBlockData)); + return 1; + } + + internal static int HandleDestroyed(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockDestroyNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block); + } + + if (block == null) + return 0; + + var update = new BlockUpdateContext( + nativeArgs.Block.BlockId, + nativeArgs.Block.IsClientSide != 0, + nativeArgs.Block.LevelPtr, + nativeArgs.Block.X, + nativeArgs.Block.Y, + nativeArgs.Block.Z); + block.OnDestroyed(new BlockDestroyContext(update, nativeArgs.BlockData)); + return 1; + } + + internal static int HandlePlayerDestroy(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockPlayerDestroyNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block); + } + + if (block == null) + return 0; + + var update = new BlockUpdateContext( + nativeArgs.Block.BlockId, + nativeArgs.Block.IsClientSide != 0, + nativeArgs.Block.LevelPtr, + nativeArgs.Block.X, + nativeArgs.Block.Y, + nativeArgs.Block.Z); + block.OnPlayerDestroy(new BlockPlayerDestroyContext(update, nativeArgs.PlayerPtr, nativeArgs.BlockData)); + return 1; + } + + internal static int HandlePlayerWillDestroy(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockPlayerWillDestroyNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block); + } + + if (block == null) + return 0; + + var update = new BlockUpdateContext( + nativeArgs.Block.BlockId, + nativeArgs.Block.IsClientSide != 0, + nativeArgs.Block.LevelPtr, + nativeArgs.Block.X, + nativeArgs.Block.Y, + nativeArgs.Block.Z); + block.OnPlayerWillDestroy(new BlockPlayerWillDestroyContext(update, nativeArgs.PlayerPtr, nativeArgs.BlockData)); + return 1; + } + + internal static int HandlePlacedBy(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockPlacedByNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block); + } + + if (block == null) + return 0; + + var update = new BlockUpdateContext( + nativeArgs.Block.BlockId, + nativeArgs.Block.IsClientSide != 0, + nativeArgs.Block.LevelPtr, + nativeArgs.Block.X, + nativeArgs.Block.Y, + nativeArgs.Block.Z); + block.OnPlacedBy(new BlockPlacedByContext(update, nativeArgs.PlacerPtr, nativeArgs.ItemInstancePtr)); + return 1; + } + + } diff --git a/WeaveLoader.API/Item/CustomItem.cs b/WeaveLoader.API/Item/CustomItem.cs index 7a9ce27..368a108 100644 --- a/WeaveLoader.API/Item/CustomItem.cs +++ b/WeaveLoader.API/Item/CustomItem.cs @@ -27,6 +27,41 @@ public abstract class Item /// or to skip vanilla handling. /// public virtual UseItemResult OnUseItem(UseItemContext context) => UseItemResult.ContinueVanilla; + + /// + /// Called when this item is used on a block. + /// Return to run vanilla logic, + /// or to skip vanilla handling. + /// + public virtual ItemActionResult OnUseOn(UseOnItemContext context) => ItemActionResult.ContinueVanilla; + + /// + /// Called when this item interacts with an entity (right-click interaction). + /// Return to run vanilla logic, + /// or to skip vanilla handling. + /// + public virtual ItemActionResult OnInteractEntity(ItemEntityInteractionContext context) => ItemActionResult.ContinueVanilla; + + /// + /// Called when this item hurts an entity. + /// Return to run vanilla logic, + /// or to skip vanilla handling. + /// + public virtual ItemActionResult OnHurtEntity(ItemEntityInteractionContext context) => ItemActionResult.ContinueVanilla; + + /// + /// Called each tick while this item is in an inventory. + /// + public virtual void OnInventoryTick(ItemInventoryTickContext context) + { + } + + /// + /// Called when this item is crafted by a player. + /// + public virtual void OnCraftedBy(ItemCraftedByContext context) + { + } } /// @@ -47,6 +82,15 @@ public enum UseItemResult CancelVanilla = 1 } +/// +/// Result of managed item action callbacks. +/// +public enum ItemActionResult +{ + ContinueVanilla = 0, + CancelVanilla = 1 +} + /// /// Tool tier used by native tool constructors. /// @@ -117,16 +161,14 @@ public readonly struct MineBlockContext public readonly struct UseItemContext { public int ItemId { get; } - public bool IsTestUseOnly { get; } public bool IsClientSide { get; } public nint NativeItemInstancePtr { get; } public nint NativePlayerPtr { get; } public nint NativePlayerSharedPtr { get; } - internal UseItemContext(int itemId, bool isTestUseOnly, bool isClientSide, nint nativeItemInstancePtr, nint nativePlayerPtr, nint nativePlayerSharedPtr) + internal UseItemContext(int itemId, bool isClientSide, nint nativeItemInstancePtr, nint nativePlayerPtr, nint nativePlayerSharedPtr) { ItemId = itemId; - IsTestUseOnly = isTestUseOnly; IsClientSide = isClientSide; NativeItemInstancePtr = nativeItemInstancePtr; NativePlayerPtr = nativePlayerPtr; @@ -177,6 +219,197 @@ public readonly struct UseItemContext } } +/// +/// Runtime context for item use-on-block callback. +/// +public readonly struct UseOnItemContext +{ + public int ItemId { get; } + public bool IsClientSide { get; } + public nint NativeItemInstancePtr { get; } + public nint NativePlayerPtr { get; } + public nint NativePlayerSharedPtr { get; } + public nint NativeLevelPtr { get; } + public int X { get; } + public int Y { get; } + public int Z { get; } + public int Face { get; } + public float ClickX { get; } + public float ClickY { get; } + public float ClickZ { get; } + + internal UseOnItemContext( + int itemId, + bool isClientSide, + nint nativeItemInstancePtr, + nint nativePlayerPtr, + nint nativePlayerSharedPtr, + nint nativeLevelPtr, + int x, + int y, + int z, + int face, + float clickX, + float clickY, + float clickZ) + { + ItemId = itemId; + IsClientSide = isClientSide; + NativeItemInstancePtr = nativeItemInstancePtr; + NativePlayerPtr = nativePlayerPtr; + NativePlayerSharedPtr = nativePlayerSharedPtr; + NativeLevelPtr = nativeLevelPtr; + X = x; + Y = y; + Z = z; + Face = face; + ClickX = clickX; + ClickY = clickY; + ClickZ = clickZ; + } + + public bool ConsumeInventoryItem(Identifier id, int count = 1) + { + if (NativePlayerPtr == 0 || count <= 0) + return false; + + int numericId = IdHelper.GetItemNumericId(id); + if (numericId < 0) + return false; + + return NativeInterop.native_consume_item_from_player(NativePlayerPtr, numericId, count) != 0; + } + + public bool DamageItem(int amount) + { + if (NativeItemInstancePtr == 0 || NativePlayerSharedPtr == 0 || amount <= 0) + return false; + + return NativeInterop.native_damage_item_instance(NativeItemInstancePtr, amount, NativePlayerSharedPtr) != 0; + } + + public bool SpawnEntityFromLook(Identifier id, double speed = 1.4, double spawnForward = 1.0, double spawnUp = 1.2) + { + int numericEntityId = IdHelper.GetEntityNumericId(id); + if (numericEntityId < 0) + return false; + + return SpawnEntityFromLook(numericEntityId, speed, spawnForward, spawnUp); + } + + public bool SpawnEntityFromLook(int numericEntityId, double speed = 1.4, double spawnForward = 1.0, double spawnUp = 1.2) + { + if (NativePlayerPtr == 0 || numericEntityId < 0) + return false; + + return NativeInterop.native_spawn_entity_from_player_look( + NativePlayerPtr, + NativePlayerSharedPtr, + numericEntityId, + speed, + spawnForward, + spawnUp) != 0; + } +} + +/// +/// Runtime context for item/entity interactions. +/// +public readonly struct ItemEntityInteractionContext +{ + public int ItemId { get; } + public nint NativeItemInstancePtr { get; } + public nint NativePlayerPtr { get; } + public nint NativePlayerSharedPtr { get; } + public nint NativeTargetEntityPtr { get; } + + internal ItemEntityInteractionContext( + int itemId, + nint nativeItemInstancePtr, + nint nativePlayerPtr, + nint nativePlayerSharedPtr, + nint nativeTargetEntityPtr) + { + ItemId = itemId; + NativeItemInstancePtr = nativeItemInstancePtr; + NativePlayerPtr = nativePlayerPtr; + NativePlayerSharedPtr = nativePlayerSharedPtr; + NativeTargetEntityPtr = nativeTargetEntityPtr; + } + + public bool DamageItem(int amount) + { + if (NativeItemInstancePtr == 0 || NativePlayerSharedPtr == 0 || amount <= 0) + return false; + + return NativeInterop.native_damage_item_instance(NativeItemInstancePtr, amount, NativePlayerSharedPtr) != 0; + } +} + +/// +/// Runtime context for inventory tick callbacks. +/// +public readonly struct ItemInventoryTickContext +{ + public int ItemId { get; } + public nint NativeItemInstancePtr { get; } + public nint NativeLevelPtr { get; } + public nint NativeOwnerEntityPtr { get; } + public int Slot { get; } + public bool IsSelected { get; } + public bool IsClientSide { get; } + + internal ItemInventoryTickContext( + int itemId, + nint nativeItemInstancePtr, + nint nativeLevelPtr, + nint nativeOwnerEntityPtr, + int slot, + bool isSelected, + bool isClientSide) + { + ItemId = itemId; + NativeItemInstancePtr = nativeItemInstancePtr; + NativeLevelPtr = nativeLevelPtr; + NativeOwnerEntityPtr = nativeOwnerEntityPtr; + Slot = slot; + IsSelected = isSelected; + IsClientSide = isClientSide; + } +} + +/// +/// Runtime context for crafted-by callbacks. +/// +public readonly struct ItemCraftedByContext +{ + public int ItemId { get; } + public nint NativeItemInstancePtr { get; } + public nint NativeLevelPtr { get; } + public nint NativePlayerPtr { get; } + public nint NativePlayerSharedPtr { get; } + public int Amount { get; } + public bool IsClientSide { get; } + + internal ItemCraftedByContext( + int itemId, + nint nativeItemInstancePtr, + nint nativeLevelPtr, + nint nativePlayerPtr, + nint nativePlayerSharedPtr, + int amount, + bool isClientSide) + { + ItemId = itemId; + NativeItemInstancePtr = nativeItemInstancePtr; + NativeLevelPtr = nativeLevelPtr; + NativePlayerPtr = nativePlayerPtr; + NativePlayerSharedPtr = nativePlayerSharedPtr; + Amount = amount; + IsClientSide = isClientSide; + } +} + [StructLayout(LayoutKind.Sequential)] internal struct MineBlockNativeArgs { @@ -191,13 +424,64 @@ internal struct MineBlockNativeArgs internal struct UseItemNativeArgs { public int ItemId; - public int IsTestUseOnly; public int IsClientSide; public nint ItemInstancePtr; public nint PlayerPtr; public nint PlayerSharedPtr; } +[StructLayout(LayoutKind.Sequential)] +internal struct UseOnItemNativeArgs +{ + public int ItemId; + public int IsClientSide; + public nint ItemInstancePtr; + public nint PlayerPtr; + public nint PlayerSharedPtr; + public nint LevelPtr; + public int X; + public int Y; + public int Z; + public int Face; + public float ClickX; + public float ClickY; + public float ClickZ; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct ItemEntityInteractionNativeArgs +{ + public int ItemId; + public nint ItemInstancePtr; + public nint PlayerPtr; + public nint PlayerSharedPtr; + public nint TargetEntityPtr; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct ItemInventoryTickNativeArgs +{ + public int ItemId; + public nint ItemInstancePtr; + public nint LevelPtr; + public nint OwnerEntityPtr; + public int Slot; + public int IsSelected; + public int IsClientSide; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct ItemCraftedByNativeArgs +{ + public int ItemId; + public nint ItemInstancePtr; + public nint LevelPtr; + public nint PlayerPtr; + public nint PlayerSharedPtr; + public int Amount; + public int IsClientSide; +} + internal static class ManagedItemDispatcher { private static readonly object s_lock = new(); @@ -259,7 +543,6 @@ internal static class ManagedItemDispatcher var result = item.OnUseItem(new UseItemContext( nativeArgs.ItemId, - nativeArgs.IsTestUseOnly != 0, nativeArgs.IsClientSide != 0, nativeArgs.ItemInstancePtr, nativeArgs.PlayerPtr, @@ -268,4 +551,144 @@ internal static class ManagedItemDispatcher // 0 = no managed item, 1 = continue vanilla, 2 = cancel vanilla. return result == UseItemResult.CancelVanilla ? 2 : 1; } + + internal static int HandleUseOn(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + UseOnItemNativeArgs nativeArgs = Marshal.PtrToStructure(args); + + Item? item; + lock (s_lock) + { + s_items.TryGetValue(nativeArgs.ItemId, out item); + } + + if (item == null) + return 0; + + var result = item.OnUseOn(new UseOnItemContext( + nativeArgs.ItemId, + nativeArgs.IsClientSide != 0, + nativeArgs.ItemInstancePtr, + nativeArgs.PlayerPtr, + nativeArgs.PlayerSharedPtr, + nativeArgs.LevelPtr, + nativeArgs.X, + nativeArgs.Y, + nativeArgs.Z, + nativeArgs.Face, + nativeArgs.ClickX, + nativeArgs.ClickY, + nativeArgs.ClickZ)); + + return result == ItemActionResult.CancelVanilla ? 2 : 1; + } + + internal static int HandleInteractEntity(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + ItemEntityInteractionNativeArgs nativeArgs = Marshal.PtrToStructure(args); + + Item? item; + lock (s_lock) + { + s_items.TryGetValue(nativeArgs.ItemId, out item); + } + + if (item == null) + return 0; + + var result = item.OnInteractEntity(new ItemEntityInteractionContext( + nativeArgs.ItemId, + nativeArgs.ItemInstancePtr, + nativeArgs.PlayerPtr, + nativeArgs.PlayerSharedPtr, + nativeArgs.TargetEntityPtr)); + + return result == ItemActionResult.CancelVanilla ? 2 : 1; + } + + internal static int HandleHurtEntity(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + ItemEntityInteractionNativeArgs nativeArgs = Marshal.PtrToStructure(args); + + Item? item; + lock (s_lock) + { + s_items.TryGetValue(nativeArgs.ItemId, out item); + } + + if (item == null) + return 0; + + var result = item.OnHurtEntity(new ItemEntityInteractionContext( + nativeArgs.ItemId, + nativeArgs.ItemInstancePtr, + nativeArgs.PlayerPtr, + nativeArgs.PlayerSharedPtr, + nativeArgs.TargetEntityPtr)); + + return result == ItemActionResult.CancelVanilla ? 2 : 1; + } + + internal static int HandleInventoryTick(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + ItemInventoryTickNativeArgs nativeArgs = Marshal.PtrToStructure(args); + + Item? item; + lock (s_lock) + { + s_items.TryGetValue(nativeArgs.ItemId, out item); + } + + if (item == null) + return 0; + + item.OnInventoryTick(new ItemInventoryTickContext( + nativeArgs.ItemId, + nativeArgs.ItemInstancePtr, + nativeArgs.LevelPtr, + nativeArgs.OwnerEntityPtr, + nativeArgs.Slot, + nativeArgs.IsSelected != 0, + nativeArgs.IsClientSide != 0)); + return 1; + } + + internal static int HandleCraftedBy(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + ItemCraftedByNativeArgs nativeArgs = Marshal.PtrToStructure(args); + + Item? item; + lock (s_lock) + { + s_items.TryGetValue(nativeArgs.ItemId, out item); + } + + if (item == null) + return 0; + + item.OnCraftedBy(new ItemCraftedByContext( + nativeArgs.ItemId, + nativeArgs.ItemInstancePtr, + nativeArgs.LevelPtr, + nativeArgs.PlayerPtr, + nativeArgs.PlayerSharedPtr, + nativeArgs.Amount, + nativeArgs.IsClientSide != 0)); + return 1; + } } diff --git a/WeaveLoader.Core/WeaveLoaderCore.cs b/WeaveLoader.Core/WeaveLoaderCore.cs index 3834b95..b78c53d 100644 --- a/WeaveLoader.Core/WeaveLoaderCore.cs +++ b/WeaveLoader.Core/WeaveLoaderCore.cs @@ -123,6 +123,71 @@ public static class WeaveLoaderCore } } + public static int OnItemUseOn(IntPtr args, int sizeBytes) + { + try + { + return ManagedItemDispatcher.HandleUseOn(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnItemUseOn EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnItemInteractEntity(IntPtr args, int sizeBytes) + { + try + { + return ManagedItemDispatcher.HandleInteractEntity(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnItemInteractEntity EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnItemHurtEntity(IntPtr args, int sizeBytes) + { + try + { + return ManagedItemDispatcher.HandleHurtEntity(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnItemHurtEntity EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnItemInventoryTick(IntPtr args, int sizeBytes) + { + try + { + return ManagedItemDispatcher.HandleInventoryTick(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnItemInventoryTick EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnItemCraftedBy(IntPtr args, int sizeBytes) + { + try + { + return ManagedItemDispatcher.HandleCraftedBy(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnItemCraftedBy EXCEPTION: {ex}"); + return 0; + } + } + public static int OnBlockPlace(IntPtr args, int sizeBytes) { try @@ -162,6 +227,136 @@ public static class WeaveLoaderCore } } + public static int OnBlockUse(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandleUse(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockUse EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnBlockStepOn(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandleStepOn(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockStepOn EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnBlockEntityInsideTile(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandleEntityInsideTile(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockEntityInsideTile EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnBlockFallOn(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandleFallOn(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockFallOn EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnBlockRemoving(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandleRemoving(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockRemoving EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnBlockRemoved(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandleRemoved(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockRemoved EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnBlockDestroyed(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandleDestroyed(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockDestroyed EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnBlockPlayerDestroy(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandlePlayerDestroy(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockPlayerDestroy EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnBlockPlayerWillDestroy(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandlePlayerWillDestroy(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockPlayerWillDestroy EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnBlockPlacedBy(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandlePlacedBy(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockPlacedBy EXCEPTION: {ex}"); + return 0; + } + } + [StructLayout(LayoutKind.Sequential)] private struct WorldLoadedNativeArgs { diff --git a/WeaveLoaderRuntime/src/DotNetHost.cpp b/WeaveLoaderRuntime/src/DotNetHost.cpp index b87ff5c..04f426f 100644 --- a/WeaveLoaderRuntime/src/DotNetHost.cpp +++ b/WeaveLoaderRuntime/src/DotNetHost.cpp @@ -24,9 +24,24 @@ static managed_entry_fn fn_Tick = nullptr; static managed_entry_fn fn_Shutdown = nullptr; static managed_entry_fn fn_ItemMineBlock = nullptr; static managed_entry_fn fn_ItemUse = nullptr; +static managed_entry_fn fn_ItemUseOn = nullptr; +static managed_entry_fn fn_ItemInteractEntity = nullptr; +static managed_entry_fn fn_ItemHurtEntity = nullptr; +static managed_entry_fn fn_ItemInventoryTick = nullptr; +static managed_entry_fn fn_ItemCraftedBy = nullptr; static managed_entry_fn fn_BlockOnPlace = nullptr; static managed_entry_fn fn_BlockNeighborChanged = nullptr; static managed_entry_fn fn_BlockTick = nullptr; +static managed_entry_fn fn_BlockUse = nullptr; +static managed_entry_fn fn_BlockStepOn = nullptr; +static managed_entry_fn fn_BlockEntityInsideTile = nullptr; +static managed_entry_fn fn_BlockFallOn = nullptr; +static managed_entry_fn fn_BlockRemoving = nullptr; +static managed_entry_fn fn_BlockRemoved = nullptr; +static managed_entry_fn fn_BlockDestroyed = nullptr; +static managed_entry_fn fn_BlockPlayerDestroy = nullptr; +static managed_entry_fn fn_BlockPlayerWillDestroy = nullptr; +static managed_entry_fn fn_BlockPlacedBy = nullptr; static managed_entry_fn fn_EntitySummoned = nullptr; static bool LoadHostfxr() @@ -185,9 +200,24 @@ bool DotNetHost::Initialize() ok &= resolve(L"Shutdown", &fn_Shutdown); ok &= resolve(L"OnItemMineBlock", &fn_ItemMineBlock); ok &= resolve(L"OnItemUse", &fn_ItemUse); + ok &= resolve(L"OnItemUseOn", &fn_ItemUseOn); + ok &= resolve(L"OnItemInteractEntity", &fn_ItemInteractEntity); + ok &= resolve(L"OnItemHurtEntity", &fn_ItemHurtEntity); + ok &= resolve(L"OnItemInventoryTick", &fn_ItemInventoryTick); + ok &= resolve(L"OnItemCraftedBy", &fn_ItemCraftedBy); ok &= resolve(L"OnBlockPlace", &fn_BlockOnPlace); ok &= resolve(L"OnBlockNeighborChanged", &fn_BlockNeighborChanged); ok &= resolve(L"OnBlockTick", &fn_BlockTick); + ok &= resolve(L"OnBlockUse", &fn_BlockUse); + ok &= resolve(L"OnBlockStepOn", &fn_BlockStepOn); + ok &= resolve(L"OnBlockEntityInsideTile", &fn_BlockEntityInsideTile); + ok &= resolve(L"OnBlockFallOn", &fn_BlockFallOn); + ok &= resolve(L"OnBlockRemoving", &fn_BlockRemoving); + ok &= resolve(L"OnBlockRemoved", &fn_BlockRemoved); + ok &= resolve(L"OnBlockDestroyed", &fn_BlockDestroyed); + ok &= resolve(L"OnBlockPlayerDestroy", &fn_BlockPlayerDestroy); + ok &= resolve(L"OnBlockPlayerWillDestroy", &fn_BlockPlayerWillDestroy); + ok &= resolve(L"OnBlockPlacedBy", &fn_BlockPlacedBy); ok &= resolve(L"OnEntitySummoned", &fn_EntitySummoned); if (!ok) @@ -253,6 +283,41 @@ int DotNetHost::CallItemUse(const void* args, int sizeBytes) return fn_ItemUse(const_cast(args), sizeBytes); } +int DotNetHost::CallItemUseOn(const void* args, int sizeBytes) +{ + if (!fn_ItemUseOn || !args || sizeBytes <= 0) + return 0; + return fn_ItemUseOn(const_cast(args), sizeBytes); +} + +int DotNetHost::CallItemInteractEntity(const void* args, int sizeBytes) +{ + if (!fn_ItemInteractEntity || !args || sizeBytes <= 0) + return 0; + return fn_ItemInteractEntity(const_cast(args), sizeBytes); +} + +int DotNetHost::CallItemHurtEntity(const void* args, int sizeBytes) +{ + if (!fn_ItemHurtEntity || !args || sizeBytes <= 0) + return 0; + return fn_ItemHurtEntity(const_cast(args), sizeBytes); +} + +int DotNetHost::CallItemInventoryTick(const void* args, int sizeBytes) +{ + if (!fn_ItemInventoryTick || !args || sizeBytes <= 0) + return 0; + return fn_ItemInventoryTick(const_cast(args), sizeBytes); +} + +int DotNetHost::CallItemCraftedBy(const void* args, int sizeBytes) +{ + if (!fn_ItemCraftedBy || !args || sizeBytes <= 0) + return 0; + return fn_ItemCraftedBy(const_cast(args), sizeBytes); +} + int DotNetHost::CallBlockOnPlace(const void* args, int sizeBytes) { if (!fn_BlockOnPlace || !args || sizeBytes <= 0) @@ -274,6 +339,77 @@ int DotNetHost::CallBlockTick(const void* args, int sizeBytes) return fn_BlockTick(const_cast(args), sizeBytes); } +int DotNetHost::CallBlockUse(const void* args, int sizeBytes) +{ + if (!fn_BlockUse || !args || sizeBytes <= 0) + return 0; + return fn_BlockUse(const_cast(args), sizeBytes); +} + +int DotNetHost::CallBlockStepOn(const void* args, int sizeBytes) +{ + if (!fn_BlockStepOn || !args || sizeBytes <= 0) + return 0; + return fn_BlockStepOn(const_cast(args), sizeBytes); +} + +int DotNetHost::CallBlockEntityInsideTile(const void* args, int sizeBytes) +{ + if (!fn_BlockEntityInsideTile || !args || sizeBytes <= 0) + return 0; + return fn_BlockEntityInsideTile(const_cast(args), sizeBytes); +} + +int DotNetHost::CallBlockFallOn(const void* args, int sizeBytes) +{ + if (!fn_BlockFallOn || !args || sizeBytes <= 0) + return 0; + return fn_BlockFallOn(const_cast(args), sizeBytes); +} + +int DotNetHost::CallBlockRemoving(const void* args, int sizeBytes) +{ + if (!fn_BlockRemoving || !args || sizeBytes <= 0) + return 0; + return fn_BlockRemoving(const_cast(args), sizeBytes); +} + +int DotNetHost::CallBlockRemoved(const void* args, int sizeBytes) +{ + if (!fn_BlockRemoved || !args || sizeBytes <= 0) + return 0; + return fn_BlockRemoved(const_cast(args), sizeBytes); +} + +int DotNetHost::CallBlockDestroyed(const void* args, int sizeBytes) +{ + if (!fn_BlockDestroyed || !args || sizeBytes <= 0) + return 0; + return fn_BlockDestroyed(const_cast(args), sizeBytes); +} + +int DotNetHost::CallBlockPlayerDestroy(const void* args, int sizeBytes) +{ + if (!fn_BlockPlayerDestroy || !args || sizeBytes <= 0) + return 0; + return fn_BlockPlayerDestroy(const_cast(args), sizeBytes); +} + +int DotNetHost::CallBlockPlayerWillDestroy(const void* args, int sizeBytes) +{ + if (!fn_BlockPlayerWillDestroy || !args || sizeBytes <= 0) + return 0; + return fn_BlockPlayerWillDestroy(const_cast(args), sizeBytes); +} + +int DotNetHost::CallBlockPlacedBy(const void* args, int sizeBytes) +{ + if (!fn_BlockPlacedBy || !args || sizeBytes <= 0) + return 0; + return fn_BlockPlacedBy(const_cast(args), sizeBytes); +} + + void DotNetHost::CallEntitySummoned(int entityNumericId, float x, float y, float z) { if (!fn_EntitySummoned) diff --git a/WeaveLoaderRuntime/src/DotNetHost.h b/WeaveLoaderRuntime/src/DotNetHost.h index 7afca6c..40471e4 100644 --- a/WeaveLoaderRuntime/src/DotNetHost.h +++ b/WeaveLoaderRuntime/src/DotNetHost.h @@ -17,8 +17,23 @@ namespace DotNetHost void CallShutdown(); int CallItemMineBlock(const void* args, int sizeBytes); int CallItemUse(const void* args, int sizeBytes); + int CallItemUseOn(const void* args, int sizeBytes); + int CallItemInteractEntity(const void* args, int sizeBytes); + int CallItemHurtEntity(const void* args, int sizeBytes); + int CallItemInventoryTick(const void* args, int sizeBytes); + int CallItemCraftedBy(const void* args, int sizeBytes); int CallBlockOnPlace(const void* args, int sizeBytes); int CallBlockNeighborChanged(const void* args, int sizeBytes); int CallBlockTick(const void* args, int sizeBytes); + int CallBlockUse(const void* args, int sizeBytes); + int CallBlockStepOn(const void* args, int sizeBytes); + int CallBlockEntityInsideTile(const void* args, int sizeBytes); + int CallBlockFallOn(const void* args, int sizeBytes); + int CallBlockRemoving(const void* args, int sizeBytes); + int CallBlockRemoved(const void* args, int sizeBytes); + int CallBlockDestroyed(const void* args, int sizeBytes); + int CallBlockPlayerDestroy(const void* args, int sizeBytes); + int CallBlockPlayerWillDestroy(const void* args, int sizeBytes); + int CallBlockPlacedBy(const void* args, int sizeBytes); void CallEntitySummoned(int entityNumericId, float x, float y, float z); } diff --git a/WeaveLoaderRuntime/src/GameHooks.cpp b/WeaveLoaderRuntime/src/GameHooks.cpp index 1e8606c..4f80b19 100644 --- a/WeaveLoaderRuntime/src/GameHooks.cpp +++ b/WeaveLoaderRuntime/src/GameHooks.cpp @@ -14,6 +14,7 @@ #include "WorldIdRemap.h" #include "ModelRegistry.h" #include "ItemRenderRegistry.h" +#include "PdbParser.h" #include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include namespace GameHooks { @@ -66,6 +68,10 @@ namespace GameHooks ItemInstanceUseOn_fn Original_ItemInstanceUseOn = nullptr; ItemInstanceSave_fn Original_ItemInstanceSave = nullptr; ItemInstanceLoad_fn Original_ItemInstanceLoad = nullptr; + ItemInstanceInventoryTick_fn Original_ItemInstanceInventoryTick = nullptr; + ItemInstanceOnCraftedBy_fn Original_ItemInstanceOnCraftedBy = nullptr; + ItemInstanceInteractEnemy_fn Original_ItemInstanceInteractEnemy = nullptr; + ItemInstanceHurtEnemy_fn Original_ItemInstanceHurtEnemy = nullptr; ItemMineBlock_fn Original_ItemMineBlock = nullptr; ItemMineBlock_fn Original_DiggerItemMineBlock = nullptr; PickaxeGetDestroySpeed_fn Original_PickaxeItemGetDestroySpeed = nullptr; @@ -75,6 +81,18 @@ namespace GameHooks TileOnPlace_fn Original_TileOnPlace = nullptr; TileNeighborChanged_fn Original_TileNeighborChanged = nullptr; TileTick_fn Original_TileTick = nullptr; + TileUse_fn Original_TileUse = nullptr; + TileStepOn_fn Original_TileStepOn = nullptr; + TileEntityInside_fn Original_TileEntityInside = nullptr; + TileFallOn_fn Original_TileFallOn = nullptr; + TileOnRemoving_fn Original_TileOnRemoving = nullptr; + TileOnRemove_fn Original_TileOnRemove = nullptr; + TileDestroy_fn Original_TileDestroy = nullptr; + TilePlayerDestroy_fn Original_TilePlayerDestroy = nullptr; + TilePlayerWillDestroy_fn Original_TilePlayerWillDestroy = nullptr; + TileSetPlacedBy_fn Original_TileSetPlacedBy = nullptr; + TileSharedAction_fn Original_TileSharedAction = nullptr; + TileSharedLifecycle_fn Original_TileSharedLifecycle = nullptr; LevelSetTileAndDataDispatch_fn Original_LevelSetTileAndData = nullptr; LevelSetDataDispatch_fn Original_LevelSetData = nullptr; LevelUpdateNeighborsAtDispatch_fn Original_LevelUpdateNeighborsAt = nullptr; @@ -104,6 +122,8 @@ namespace GameHooks ServerPlayerGameModeUseItemOn_fn Original_ServerPlayerGameModeUseItemOn = nullptr; MultiPlayerGameModeUseItemOn_fn Original_MultiPlayerGameModeUseItemOn = nullptr; MinecraftSetLevel_fn Original_MinecraftSetLevel = nullptr; + EntityPlayStepSound_fn Original_EntityPlayStepSound = nullptr; + EntityCheckInsideTiles_fn Original_EntityCheckInsideTiles = nullptr; TexturesBindTextureResource_fn Original_TexturesBindTextureResource = nullptr; TexturesLoadTextureByName_fn Original_TexturesLoadTextureByName = nullptr; TexturesLoadTextureByIndex_fn Original_TexturesLoadTextureByIndex = nullptr; @@ -257,6 +277,10 @@ namespace GameHooks static int s_tileRendererLevelOffset = -1; static std::atomic s_entityYawOffsetTried{false}; static int s_entityYawOffset = -1; + static std::atomic s_entityLevelOffsetTried{false}; + static int s_entityLevelOffset = -1; + static std::atomic s_entityBbOffsetTried{false}; + static int s_entityBbOffset = -1; static int s_rotationLogCount = 0; static bool ReadFileToString(const char* path, std::string& out) @@ -414,6 +438,67 @@ namespace GameHooks return true; } + static bool TryResolveEntityLevelOffset() + { + bool expected = false; + if (!s_entityLevelOffsetTried.compare_exchange_strong(expected, true)) + return s_entityLevelOffset >= 0; + + const char* baseDir = LogUtil::GetBaseDir(); + if (!baseDir || baseDir[0] == '\0') + return false; + + std::string json; + std::string path = std::string(baseDir) + "metadata\\offsets.json"; + if (!ReadFileToString(path.c_str(), json)) + { + path = std::string(baseDir) + "offsets.json"; + if (!ReadFileToString(path.c_str(), json)) + return false; + } + + int offset = -1; + if (!ExtractOffsetForField(json, "Entity", "level", offset)) + return false; + if (offset <= 0) + return false; + + s_entityLevelOffset = offset; + LogUtil::Log("[WeaveLoader] ModelRegistry: Entity.level offset = 0x%X", s_entityLevelOffset); + return true; + } + + static bool TryResolveEntityBbOffset() + { + bool expected = false; + if (!s_entityBbOffsetTried.compare_exchange_strong(expected, true)) + return s_entityBbOffset >= 0; + + const char* baseDir = LogUtil::GetBaseDir(); + if (!baseDir || baseDir[0] == '\0') + return false; + + std::string json; + std::string path = std::string(baseDir) + "metadata\\offsets.json"; + if (!ReadFileToString(path.c_str(), json)) + { + path = std::string(baseDir) + "offsets.json"; + if (!ReadFileToString(path.c_str(), json)) + return false; + } + + int offset = -1; + if (!ExtractOffsetForField(json, "Entity", "bb", offset)) + return false; + if (offset <= 0) + return false; + + s_entityBbOffset = offset; + LogUtil::Log("[WeaveLoader] ModelRegistry: Entity.bb offset = 0x%X", s_entityBbOffset); + return true; + } + + int GetTileId(void* tilePtr) { if (!tilePtr) @@ -449,6 +534,52 @@ namespace GameHooks return true; } + static bool TryGetEntityLevel(void* entityPtr, void*& outLevelPtr) + { + outLevelPtr = nullptr; + if (!entityPtr) + return false; + if (s_entityLevelOffset < 0 && !TryResolveEntityLevelOffset()) + return false; + + const char* base = reinterpret_cast(entityPtr) + s_entityLevelOffset; + if (!IsReadableRange(base, sizeof(void*))) + return false; + + outLevelPtr = *reinterpret_cast(base); + return outLevelPtr != nullptr; + } + + static bool TryGetEntityAABB(void* entityPtr, AABBRaw& outBox) + { + if (!entityPtr) + return false; + if (s_entityBbOffset < 0 && !TryResolveEntityBbOffset()) + return false; + + const char* base = reinterpret_cast(entityPtr) + s_entityBbOffset; + if (!IsReadableRange(base, sizeof(void*))) + return false; + + void* boxPtr = *reinterpret_cast(base); + if (!IsReadableRange(boxPtr, sizeof(AABBRaw))) + return false; + + outBox = *reinterpret_cast(boxPtr); + return true; + } + + static uint64_t MakeEntityInsideKey(void* levelPtr, void* entityPtr, int x, int y, int z) + { + uint64_t h = static_cast(reinterpret_cast(entityPtr)); + h ^= static_cast(reinterpret_cast(levelPtr)) * 0x9E3779B97F4A7C15ULL; + h ^= static_cast(static_cast(x) * 73856093LL); + h ^= static_cast(static_cast(y) * 19349663LL); + h ^= static_cast(static_cast(z) * 83492791LL); + return h; + } + + static const char* FacingFromDoorDir(int dir) { switch (dir & 3) @@ -651,7 +782,13 @@ namespace GameHooks static thread_local void* s_activeUseLevel = nullptr; static thread_local void* s_lastUsePlayer = nullptr; static thread_local void* s_lastUseLevel = nullptr; + static thread_local void* s_lastUseItemInstance = nullptr; static thread_local ULONGLONG s_lastUseTimeMs = 0; + static thread_local void* s_lastPlacedLevel = nullptr; + static thread_local int s_lastPlacedX = 0; + static thread_local int s_lastPlacedY = 0; + static thread_local int s_lastPlacedZ = 0; + static thread_local ULONGLONG s_lastPlacedTimeMs = 0; static LevelAddEntity_fn s_levelAddEntity = nullptr; static EntityIONewById_fn s_entityIoNewById = nullptr; static EntityMoveTo_fn s_entityMoveTo = nullptr; @@ -720,6 +857,9 @@ namespace GameHooks static int s_outOfWorldGuardLogCount = 0; static int s_pendingServerUseItemId = -1; static LevelGetTile_fn s_levelGetTile = nullptr; + static std::atomic s_tickCounter{0}; + static std::unordered_map s_entityInsideSeen; + static uint32_t s_entityInsideCleanupTick = 0; struct ManagedScheduledTick { @@ -1211,13 +1351,60 @@ namespace GameHooks struct UseItemNativeArgs { int itemId; - int isTestUseOnly; int isClientSide; void* itemInstancePtr; void* playerPtr; void* playerSharedPtr; }; + struct UseOnItemNativeArgs + { + int itemId; + int isClientSide; + void* itemInstancePtr; + void* playerPtr; + void* playerSharedPtr; + void* levelPtr; + int x; + int y; + int z; + int face; + float clickX; + float clickY; + float clickZ; + }; + + struct ItemEntityInteractionNativeArgs + { + int itemId; + void* itemInstancePtr; + void* playerPtr; + void* playerSharedPtr; + void* targetEntityPtr; + }; + + struct ItemInventoryTickNativeArgs + { + int itemId; + void* itemInstancePtr; + void* levelPtr; + void* ownerEntityPtr; + int slot; + int isSelected; + int isClientSide; + }; + + struct ItemCraftedByNativeArgs + { + int itemId; + void* itemInstancePtr; + void* levelPtr; + void* playerPtr; + void* playerSharedPtr; + int amount; + int isClientSide; + }; + struct ItemRenderNativeArgs { int itemId; @@ -1231,10 +1418,86 @@ namespace GameHooks float alpha; }; + struct BlockUpdateNativeArgs + { + int blockId; + int isClientSide; + void* levelPtr; + int x; + int y; + int z; + }; + + struct BlockUseNativeArgs + { + BlockUpdateNativeArgs block; + void* playerPtr; + int face; + float clickX; + float clickY; + float clickZ; + int soundOnly; + }; + + + struct BlockEntityNativeArgs + { + BlockUpdateNativeArgs block; + void* entityPtr; + }; + + struct BlockFallNativeArgs + { + BlockUpdateNativeArgs block; + void* entityPtr; + float fallDistance; + }; + + struct BlockRemovingNativeArgs + { + BlockUpdateNativeArgs block; + int blockData; + }; + + struct BlockRemoveNativeArgs + { + BlockUpdateNativeArgs block; + int removedBlockId; + int removedBlockData; + }; + + struct BlockDestroyNativeArgs + { + BlockUpdateNativeArgs block; + int blockData; + }; + + struct BlockPlayerDestroyNativeArgs + { + BlockUpdateNativeArgs block; + void* playerPtr; + int blockData; + }; + + struct BlockPlayerWillDestroyNativeArgs + { + BlockUpdateNativeArgs block; + void* playerPtr; + int blockData; + }; + + struct BlockPlacedByNativeArgs + { + BlockUpdateNativeArgs block; + void* placerPtr; + void* itemInstancePtr; + }; + static bool IsFireballFamilyEntityId(int entityNumericId); static bool LooksLikeEntityPtr(void* candidate); static void* DecodeItemInstancePtrFromSharedArg(void* sharedArg); static void* DecodePlayerPtrFromSharedArg(void* sharedArg); + static bool WasRecentlyPlaced(void* levelPtr, int x, int y, int z); static bool TryReadItemId(void* itemInstancePtr, int& outItemId) { @@ -1407,6 +1670,9 @@ namespace GameHooks static int TryDispatchUseItemFromSharedItemArg(void* itemInstanceSharedPtr, void* playerSharedPtr, bool bTestUseOnly, const char* sourceTag) { + if (bTestUseOnly) + return 0; + void* itemInstancePtr = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr); void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr); if (!itemInstancePtr) @@ -1424,7 +1690,7 @@ namespace GameHooks *reinterpret_cast(static_cast(s_activeUseLevel) + kLevelIsClientSideOffset) ? 1 : 0; } - UseItemNativeArgs args{ itemId, bTestUseOnly ? 1 : 0, isClientSide, itemInstancePtr, playerPtr, playerSharedPtr }; + UseItemNativeArgs args{ itemId, isClientSide, itemInstancePtr, playerPtr, playerSharedPtr }; return DotNetHost::CallItemUse(&args, sizeof(args)); } @@ -1945,15 +2211,6 @@ namespace GameHooks if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; - struct BlockUpdateNativeArgs - { - int blockId; - int isClientSide; - void* levelPtr; - int x; - int y; - int z; - }; struct BlockNeighborChangedNativeArgs { BlockUpdateNativeArgs block; @@ -1988,15 +2245,6 @@ namespace GameHooks if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; - struct BlockUpdateNativeArgs - { - int blockId; - int isClientSide; - void* levelPtr; - int x; - int y; - int z; - }; struct BlockNeighborChangedNativeArgs { BlockUpdateNativeArgs block; @@ -2022,6 +2270,104 @@ namespace GameHooks } } + enum class TileSharedActionKind + { + Unknown = 0, + StepOn, + EntityInside, + FallOn + }; + + enum class TileSharedLifecycleKind + { + Unknown = 0, + Destroyed + }; + + static std::unordered_map s_tileSharedActionCache; + static std::unordered_map s_tileSharedLifecycleCache; + static std::mutex s_tileSharedActionMutex; + static std::mutex s_tileSharedLifecycleMutex; + static uintptr_t s_moduleBase = 0; + + static bool TryResolveCallerName(void* returnAddr, char* outName, size_t nameSize, uint32_t* outOffset) + { + if (!returnAddr) + return false; + if (!s_moduleBase) + s_moduleBase = reinterpret_cast(GetModuleHandleA(nullptr)); + if (!s_moduleBase) + return false; + + const uintptr_t addr = reinterpret_cast(returnAddr); + if (addr < s_moduleBase) + return false; + + const uint32_t rva = static_cast(addr - s_moduleBase); + return PdbParser::FindNameByRVA(rva, outName, nameSize, outOffset); + } + + static TileSharedActionKind ResolveTileSharedActionKind(void* returnAddr) + { + if (!returnAddr) + return TileSharedActionKind::Unknown; + + { + std::lock_guard guard(s_tileSharedActionMutex); + auto it = s_tileSharedActionCache.find(returnAddr); + if (it != s_tileSharedActionCache.end()) + return it->second; + } + + TileSharedActionKind kind = TileSharedActionKind::Unknown; + char name[128] = {}; + uint32_t offset = 0; + if (TryResolveCallerName(returnAddr, name, sizeof(name), &offset)) + { + if (std::strstr(name, "Entity::move") != nullptr) + kind = TileSharedActionKind::StepOn; + else if (std::strstr(name, "Entity::checkInsideTiles") != nullptr) + kind = TileSharedActionKind::EntityInside; + else if (std::strstr(name, "LivingEntity::checkFallDamage") != nullptr) + kind = TileSharedActionKind::FallOn; + } + + { + std::lock_guard guard(s_tileSharedActionMutex); + s_tileSharedActionCache[returnAddr] = kind; + } + return kind; + } + + static TileSharedLifecycleKind ResolveTileSharedLifecycleKind(void* returnAddr) + { + if (!returnAddr) + return TileSharedLifecycleKind::Unknown; + + { + std::lock_guard guard(s_tileSharedLifecycleMutex); + auto it = s_tileSharedLifecycleCache.find(returnAddr); + if (it != s_tileSharedLifecycleCache.end()) + return it->second; + } + + TileSharedLifecycleKind kind = TileSharedLifecycleKind::Unknown; + char name[128] = {}; + uint32_t offset = 0; + if (TryResolveCallerName(returnAddr, name, sizeof(name), &offset)) + { + if (std::strstr(name, "ServerPlayerGameMode::destroyBlock") != nullptr || + std::strstr(name, "MultiPlayerGameMode::destroyBlock") != nullptr) + kind = TileSharedLifecycleKind::Destroyed; + } + + { + std::lock_guard guard(s_tileSharedLifecycleMutex); + s_tileSharedLifecycleCache[returnAddr] = kind; + } + return kind; + } + void __fastcall Hooked_TileOnPlace(void* thisPtr, void* level, int x, int y, int z) { if (Original_TileOnPlace) @@ -2043,10 +2389,385 @@ namespace GameHooks DispatchManagedBlockUpdate(thisPtr, level, x, y, z, 2, 0); } - static bool TryGetPlacementPlayer(void* levelPtr, void*& outPlayerPtr) + bool __fastcall Hooked_TileUse(void* thisPtr, void* level, int x, int y, int z, void* playerSharedPtr, int face, float clickX, float clickY, float clickZ, bool soundOnly) { - (void)levelPtr; - if (!s_lastUsePlayer) + const int blockId = TryReadTileId(thisPtr); + if (ManagedBlockRegistry::IsManaged(blockId)) + { + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr); + BlockUseNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.playerPtr = playerPtr; + args.face = face; + args.clickX = clickX; + args.clickY = clickY; + args.clickZ = clickZ; + args.soundOnly = soundOnly ? 1 : 0; + + int action = DotNetHost::CallBlockUse(&args, sizeof(args)); + if (action == 2) + return true; + } + + if (Original_TileUse) + return Original_TileUse(thisPtr, level, x, y, z, playerSharedPtr, face, clickX, clickY, clickZ, soundOnly); + return false; + } + + void __fastcall Hooked_TileSharedAction(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr, float fallDistance) + { + const TileSharedActionKind kind = ResolveTileSharedActionKind(_ReturnAddress()); + if (kind == TileSharedActionKind::Unknown) + { + if (Original_TileSharedAction) + Original_TileSharedAction(thisPtr, level, x, y, z, entitySharedPtr, fallDistance); + return; + } + + const int blockId = TryReadTileId(thisPtr); + if (!ManagedBlockRegistry::IsManaged(blockId)) + { + if (Original_TileSharedAction) + Original_TileSharedAction(thisPtr, level, x, y, z, entitySharedPtr, fallDistance); + return; + } + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + if (Original_TileSharedAction) + Original_TileSharedAction(thisPtr, level, x, y, z, entitySharedPtr, fallDistance); + + if (kind == TileSharedActionKind::StepOn) + { + void* entityPtr = DecodeEntityPtrFromSharedArg(entitySharedPtr); + BlockEntityNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.entityPtr = entityPtr; + DotNetHost::CallBlockStepOn(&args, sizeof(args)); + } + else if (kind == TileSharedActionKind::FallOn) + { + void* entityPtr = DecodeEntityPtrFromSharedArg(entitySharedPtr); + BlockFallNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.entityPtr = entityPtr; + args.fallDistance = fallDistance; + DotNetHost::CallBlockFallOn(&args, sizeof(args)); + } + } + + void __fastcall Hooked_TileSharedLifecycle(void* thisPtr, void* level, int x, int y, int z, void* arg5, void* arg6) + { + if (Original_TileSharedLifecycle) + Original_TileSharedLifecycle(thisPtr, level, x, y, z, arg5, arg6); + + TileSharedLifecycleKind kind = ResolveTileSharedLifecycleKind(_ReturnAddress()); + + const int blockId = TryReadTileId(thisPtr); + if (!ManagedBlockRegistry::IsManaged(blockId)) + return; + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + if (kind == TileSharedLifecycleKind::Unknown) + return; + + if (kind == TileSharedLifecycleKind::Destroyed) + { + BlockDestroyNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.blockData = static_cast(reinterpret_cast(arg5)); + DotNetHost::CallBlockDestroyed(&args, sizeof(args)); + } + } + + void __fastcall Hooked_EntityPlayStepSound(void* thisPtr, int xt, int yt, int zt, int t) + { + if (Original_EntityPlayStepSound) + Original_EntityPlayStepSound(thisPtr, xt, yt, zt, t); + + if (t <= 0 || !ManagedBlockRegistry::IsManaged(t)) + return; + + void* level = nullptr; + if (!TryGetEntityLevel(thisPtr, level)) + return; + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + BlockEntityNativeArgs args{}; + args.block = { t, isClientSide, level, xt, yt, zt }; + args.entityPtr = thisPtr; + DotNetHost::CallBlockStepOn(&args, sizeof(args)); + } + + void __fastcall Hooked_EntityCheckInsideTiles(void* thisPtr) + { + if (Original_EntityCheckInsideTiles) + Original_EntityCheckInsideTiles(thisPtr); + + if (!thisPtr || !s_levelGetTile) + return; + + void* level = nullptr; + if (!TryGetEntityLevel(thisPtr, level)) + return; + + AABBRaw box{}; + if (!TryGetEntityAABB(thisPtr, box)) + return; + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + const double eps = 1e-7; + int minX = static_cast(std::floor(box.x0 + eps)); + int minY = static_cast(std::floor(box.y0 + eps)); + int minZ = static_cast(std::floor(box.z0 + eps)); + int maxX = static_cast(std::floor(box.x1 - eps)); + int maxY = static_cast(std::floor(box.y1 - eps)); + int maxZ = static_cast(std::floor(box.z1 - eps)); + + if (maxY < 0 || minY > 255) + return; + minY = (std::max)(minY, 0); + maxY = (std::min)(maxY, 255); + + const uint32_t tick = s_tickCounter.load(std::memory_order_relaxed); + if (tick != s_entityInsideCleanupTick && s_entityInsideSeen.size() > 200000) + { + s_entityInsideSeen.clear(); + s_entityInsideCleanupTick = tick; + } + + for (int y = minY; y <= maxY; ++y) + { + for (int z = minZ; z <= maxZ; ++z) + { + for (int x = minX; x <= maxX; ++x) + { + const int blockId = s_levelGetTile(level, x, y, z); + if (blockId <= 0 || !ManagedBlockRegistry::IsManaged(blockId)) + continue; + + if (!Intersects(&box, static_cast(x), static_cast(y), static_cast(z), + static_cast(x + 1), static_cast(y + 1), static_cast(z + 1))) + continue; + + const uint64_t key = MakeEntityInsideKey(level, thisPtr, x, y, z); + auto it = s_entityInsideSeen.find(key); + if (it != s_entityInsideSeen.end() && it->second == tick) + continue; + s_entityInsideSeen[key] = tick; + + BlockEntityNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.entityPtr = thisPtr; + DotNetHost::CallBlockEntityInsideTile(&args, sizeof(args)); + } + } + } + } + + void __fastcall Hooked_TileStepOn(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr) + { + if (Original_TileStepOn) + Original_TileStepOn(thisPtr, level, x, y, z, entitySharedPtr); + + const int blockId = TryReadTileId(thisPtr); + if (!ManagedBlockRegistry::IsManaged(blockId)) + return; + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + void* entityPtr = DecodeEntityPtrFromSharedArg(entitySharedPtr); + BlockEntityNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.entityPtr = entityPtr; + DotNetHost::CallBlockStepOn(&args, sizeof(args)); + } + + void __fastcall Hooked_TileEntityInside(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr) + { + if (Original_TileEntityInside) + Original_TileEntityInside(thisPtr, level, x, y, z, entitySharedPtr); + + (void)thisPtr; + (void)level; + (void)x; + (void)y; + (void)z; + (void)entitySharedPtr; + } + + void __fastcall Hooked_TileFallOn(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr, float fallDistance) + { + if (Original_TileFallOn) + Original_TileFallOn(thisPtr, level, x, y, z, entitySharedPtr, fallDistance); + + const int blockId = TryReadTileId(thisPtr); + if (!ManagedBlockRegistry::IsManaged(blockId)) + return; + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + void* entityPtr = DecodeEntityPtrFromSharedArg(entitySharedPtr); + BlockFallNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.entityPtr = entityPtr; + args.fallDistance = fallDistance; + DotNetHost::CallBlockFallOn(&args, sizeof(args)); + } + + void __fastcall Hooked_TileOnRemoving(void* thisPtr, void* level, int x, int y, int z, int data) + { + if (Original_TileOnRemoving) + Original_TileOnRemoving(thisPtr, level, x, y, z, data); + + const int blockId = TryReadTileId(thisPtr); + if (!ManagedBlockRegistry::IsManaged(blockId)) + return; + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + BlockRemovingNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.blockData = data; + DotNetHost::CallBlockRemoving(&args, sizeof(args)); + } + + void __fastcall Hooked_TileOnRemove(void* thisPtr, void* level, int x, int y, int z, int id, int data) + { + if (Original_TileOnRemove) + Original_TileOnRemove(thisPtr, level, x, y, z, id, data); + + const int blockId = TryReadTileId(thisPtr); + if (!ManagedBlockRegistry::IsManaged(blockId)) + return; + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + BlockRemoveNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.removedBlockId = id; + args.removedBlockData = data; + DotNetHost::CallBlockRemoved(&args, sizeof(args)); + } + + void __fastcall Hooked_TileDestroy(void* thisPtr, void* level, int x, int y, int z, int data) + { + if (Original_TileDestroy) + Original_TileDestroy(thisPtr, level, x, y, z, data); + + const int blockId = TryReadTileId(thisPtr); + if (!ManagedBlockRegistry::IsManaged(blockId)) + return; + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + BlockDestroyNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.blockData = data; + DotNetHost::CallBlockDestroyed(&args, sizeof(args)); + } + + void __fastcall Hooked_TilePlayerDestroy(void* thisPtr, void* level, void* playerSharedPtr, int x, int y, int z, int data) + { + if (Original_TilePlayerDestroy) + Original_TilePlayerDestroy(thisPtr, level, playerSharedPtr, x, y, z, data); + + const int blockId = TryReadTileId(thisPtr); + if (!ManagedBlockRegistry::IsManaged(blockId)) + return; + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr); + BlockPlayerDestroyNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.playerPtr = playerPtr; + args.blockData = data; + DotNetHost::CallBlockPlayerDestroy(&args, sizeof(args)); + } + + void __fastcall Hooked_TilePlayerWillDestroy(void* thisPtr, void* level, int x, int y, int z, int data, void* playerSharedPtr) + { + if (Original_TilePlayerWillDestroy) + Original_TilePlayerWillDestroy(thisPtr, level, x, y, z, data, playerSharedPtr); + + const int blockId = TryReadTileId(thisPtr); + if (!ManagedBlockRegistry::IsManaged(blockId)) + return; + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr); + BlockPlayerWillDestroyNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.playerPtr = playerPtr; + args.blockData = data; + DotNetHost::CallBlockPlayerWillDestroy(&args, sizeof(args)); + } + + void __fastcall Hooked_TileSetPlacedBy(void* thisPtr, void* level, int x, int y, int z, void* livingEntitySharedPtr, void* itemInstanceSharedPtr) + { + if (Original_TileSetPlacedBy) + Original_TileSetPlacedBy(thisPtr, level, x, y, z, livingEntitySharedPtr, itemInstanceSharedPtr); + + if (WasRecentlyPlaced(level, x, y, z)) + return; + + const int blockId = TryReadTileId(thisPtr); + if (!ManagedBlockRegistry::IsManaged(blockId)) + return; + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + void* placerPtr = DecodeEntityPtrFromSharedArg(livingEntitySharedPtr); + void* itemInstancePtr = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr); + BlockPlacedByNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.placerPtr = placerPtr; + args.itemInstancePtr = itemInstancePtr; + DotNetHost::CallBlockPlacedBy(&args, sizeof(args)); + + s_lastPlacedLevel = level; + s_lastPlacedX = x; + s_lastPlacedY = y; + s_lastPlacedZ = z; + s_lastPlacedTimeMs = GetTickCount64(); + } + + static bool TryGetPlacementContext(void* levelPtr, void*& outPlayerPtr, void*& outItemInstancePtr) + { + if (!s_lastUsePlayer || !s_lastUseLevel || s_lastUseLevel != levelPtr) return false; const ULONGLONG nowMs = GetTickCount64(); @@ -2054,9 +2775,25 @@ namespace GameHooks return false; outPlayerPtr = s_lastUsePlayer; + outItemInstancePtr = s_lastUseItemInstance; return true; } + static bool TryGetPlacementPlayer(void* levelPtr, void*& outPlayerPtr) + { + void* unusedItem = nullptr; + return TryGetPlacementContext(levelPtr, outPlayerPtr, unusedItem); + } + + static bool WasRecentlyPlaced(void* levelPtr, int x, int y, int z) + { + if (s_lastPlacedLevel != levelPtr || s_lastPlacedX != x || s_lastPlacedY != y || s_lastPlacedZ != z) + return false; + + const ULONGLONG nowMs = GetTickCount64(); + return nowMs >= s_lastPlacedTimeMs && (nowMs - s_lastPlacedTimeMs) <= 500; + } + static int ComputeFacingDataFromYaw(float yaw) { return static_cast(std::floor(((yaw + 180.0f) * 4.0f) / 360.0f - 0.5f)) & 3; @@ -2070,6 +2807,10 @@ namespace GameHooks bool __fastcall Hooked_LevelSetTileAndData(void* thisPtr, int x, int y, int z, int tile, int data, int updateFlags) { const int oldBlockId = s_levelGetTile ? s_levelGetTile(thisPtr, x, y, z) : -1; + const int oldBlockData = Level_GetData && thisPtr ? Level_GetData(thisPtr, x, y, z) : 0; + int isClientSide = 0; + if (thisPtr && IsReadableRange(static_cast(thisPtr) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(thisPtr) + kLevelIsClientSideOffset) ? 1 : 0; int effectiveData = data; if (tile > 0) { @@ -2112,6 +2853,15 @@ namespace GameHooks } } + const bool isChange = oldBlockId > 0 && (oldBlockId != tile || oldBlockData != effectiveData); + if (isChange && ManagedBlockRegistry::IsManaged(oldBlockId)) + { + BlockRemovingNativeArgs args{}; + args.block = { oldBlockId, isClientSide, thisPtr, x, y, z }; + args.blockData = oldBlockData; + DotNetHost::CallBlockRemoving(&args, sizeof(args)); + } + const bool result = Original_LevelSetTileAndData ? Original_LevelSetTileAndData(thisPtr, x, y, z, tile, effectiveData, updateFlags) : false; @@ -2119,9 +2869,40 @@ namespace GameHooks if (result && s_levelGetTile) WorldIdRemap::MarkChunkDirtyByBlockUpdate(x, z, oldBlockId, tile); + if (result && isChange && ManagedBlockRegistry::IsManaged(oldBlockId)) + { + BlockRemoveNativeArgs args{}; + args.block = { oldBlockId, isClientSide, thisPtr, x, y, z }; + args.removedBlockId = oldBlockId; + args.removedBlockData = oldBlockData; + DotNetHost::CallBlockRemoved(&args, sizeof(args)); + } + if (result && tile > 0) + { DispatchManagedBlockById(tile, thisPtr, x, y, z, 0, 0); + if (ManagedBlockRegistry::IsManaged(tile) && !WasRecentlyPlaced(thisPtr, x, y, z)) + { + void* placerPtr = nullptr; + void* itemInstancePtr = nullptr; + if (TryGetPlacementContext(thisPtr, placerPtr, itemInstancePtr)) + { + BlockPlacedByNativeArgs args{}; + args.block = { tile, isClientSide, thisPtr, x, y, z }; + args.placerPtr = placerPtr; + args.itemInstancePtr = itemInstancePtr; + DotNetHost::CallBlockPlacedBy(&args, sizeof(args)); + + s_lastPlacedLevel = thisPtr; + s_lastPlacedX = x; + s_lastPlacedY = y; + s_lastPlacedZ = z; + s_lastPlacedTimeMs = GetTickCount64(); + } + } + } + return result; } @@ -3031,10 +3812,41 @@ namespace GameHooks { s_lastUsePlayer = playerPtr; s_lastUseLevel = level; + s_lastUseItemInstance = thisPtr; s_lastUseTimeMs = GetTickCount64(); } } + int itemId = 0; + if (!bTestUseOnOnly && TryReadItemId(thisPtr, itemId)) + { + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + { + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + } + + void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr); + UseOnItemNativeArgs args{}; + args.itemId = itemId; + args.isClientSide = isClientSide; + args.itemInstancePtr = thisPtr; + args.playerPtr = playerPtr; + args.playerSharedPtr = playerSharedPtr; + args.levelPtr = level; + args.x = x; + args.y = y; + args.z = z; + args.face = face; + args.clickX = clickX; + args.clickY = clickY; + args.clickZ = clickZ; + + int action = DotNetHost::CallItemUseOn(&args, sizeof(args)); + if (action == 2) + return true; + } + if (Original_ItemInstanceUseOn) return Original_ItemInstanceUseOn(thisPtr, playerSharedPtr, level, x, y, z, face, clickX, clickY, clickZ, bTestUseOnOnly); return false; @@ -3058,6 +3870,105 @@ namespace GameHooks WorldIdRemap::RemapItemInstanceFromTag(thisPtr, compoundTagPtr); } + void __fastcall Hooked_ItemInstanceInventoryTick(void* thisPtr, void* level, void* ownerSharedPtr, int slot, bool selected) + { + if (Original_ItemInstanceInventoryTick) + Original_ItemInstanceInventoryTick(thisPtr, level, ownerSharedPtr, slot, selected); + + int itemId = 0; + if (!TryReadItemId(thisPtr, itemId)) + return; + + void* ownerPtr = DecodeEntityPtrFromSharedArg(ownerSharedPtr); + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + ItemInventoryTickNativeArgs args{}; + args.itemId = itemId; + args.itemInstancePtr = thisPtr; + args.levelPtr = level; + args.ownerEntityPtr = ownerPtr; + args.slot = slot; + args.isSelected = selected ? 1 : 0; + args.isClientSide = isClientSide; + DotNetHost::CallItemInventoryTick(&args, sizeof(args)); + } + + void __fastcall Hooked_ItemInstanceOnCraftedBy(void* thisPtr, void* level, void* playerSharedPtr, int amount) + { + if (Original_ItemInstanceOnCraftedBy) + Original_ItemInstanceOnCraftedBy(thisPtr, level, playerSharedPtr, amount); + + int itemId = 0; + if (!TryReadItemId(thisPtr, itemId)) + return; + + void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr); + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + ItemCraftedByNativeArgs args{}; + args.itemId = itemId; + args.itemInstancePtr = thisPtr; + args.levelPtr = level; + args.playerPtr = playerPtr; + args.playerSharedPtr = playerSharedPtr; + args.amount = amount; + args.isClientSide = isClientSide; + DotNetHost::CallItemCraftedBy(&args, sizeof(args)); + } + + bool __fastcall Hooked_ItemInstanceInteractEnemy(void* thisPtr, void* playerSharedPtr, void* targetSharedPtr) + { + int itemId = 0; + if (TryReadItemId(thisPtr, itemId)) + { + void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr); + void* targetPtr = DecodeEntityPtrFromSharedArg(targetSharedPtr); + + ItemEntityInteractionNativeArgs args{}; + args.itemId = itemId; + args.itemInstancePtr = thisPtr; + args.playerPtr = playerPtr; + args.playerSharedPtr = playerSharedPtr; + args.targetEntityPtr = targetPtr; + + int action = DotNetHost::CallItemInteractEntity(&args, sizeof(args)); + if (action == 2) + return true; + } + + if (Original_ItemInstanceInteractEnemy) + return Original_ItemInstanceInteractEnemy(thisPtr, playerSharedPtr, targetSharedPtr); + return false; + } + + void __fastcall Hooked_ItemInstanceHurtEnemy(void* thisPtr, void* targetSharedPtr, void* playerSharedPtr) + { + int itemId = 0; + if (TryReadItemId(thisPtr, itemId)) + { + void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr); + void* targetPtr = DecodeEntityPtrFromSharedArg(targetSharedPtr); + + ItemEntityInteractionNativeArgs args{}; + args.itemId = itemId; + args.itemInstancePtr = thisPtr; + args.playerPtr = playerPtr; + args.playerSharedPtr = playerSharedPtr; + args.targetEntityPtr = targetPtr; + + int action = DotNetHost::CallItemHurtEntity(&args, sizeof(args)); + if (action == 2) + return; + } + + if (Original_ItemInstanceHurtEnemy) + Original_ItemInstanceHurtEnemy(thisPtr, targetSharedPtr, playerSharedPtr); + } + bool __fastcall Hooked_ItemMineBlock(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr) { s_itemMineBlockHookCalls++; @@ -3338,6 +4249,7 @@ namespace GameHooks { s_lastUsePlayer = playerPtr; s_lastUseLevel = level; + s_lastUseItemInstance = itemInstancePtr; s_lastUseTimeMs = nowMs; } } @@ -3375,6 +4287,7 @@ namespace GameHooks { s_lastUsePlayer = playerPtr; s_lastUseLevel = level; + s_lastUseItemInstance = itemInstancePtr; s_lastUseTimeMs = GetTickCount64(); } } @@ -3404,6 +4317,7 @@ namespace GameHooks { s_lastUsePlayer = playerPtr; s_lastUseLevel = level; + s_lastUseItemInstance = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr); s_lastUseTimeMs = GetTickCount64(); } } @@ -3422,6 +4336,7 @@ namespace GameHooks { s_lastUsePlayer = playerPtr; s_lastUseLevel = level; + s_lastUseItemInstance = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr); s_lastUseTimeMs = GetTickCount64(); } } @@ -3571,6 +4486,7 @@ namespace GameHooks void __fastcall Hooked_MinecraftTick(void* thisPtr, bool bFirst, bool bUpdateTextures) { + s_tickCounter.fetch_add(1, std::memory_order_relaxed); ModAtlas::PollAsyncBuild(); CullSpawnedEntitiesBelowWorld(); Original_MinecraftTick(thisPtr, bFirst, bUpdateTextures); diff --git a/WeaveLoaderRuntime/src/GameHooks.h b/WeaveLoaderRuntime/src/GameHooks.h index 4e091f6..2a7a129 100644 --- a/WeaveLoaderRuntime/src/GameHooks.h +++ b/WeaveLoaderRuntime/src/GameHooks.h @@ -32,12 +32,28 @@ typedef void (__fastcall *ItemInstanceMineBlock_fn)(void* thisPtr, void* level, typedef bool (__fastcall *ItemInstanceUseOn_fn)(void* thisPtr, void* playerSharedPtr, void* level, int x, int y, int z, int face, float clickX, float clickY, float clickZ, bool bTestUseOnOnly); typedef void* (__fastcall *ItemInstanceSave_fn)(void* thisPtr, void* compoundTagPtr); typedef void (__fastcall *ItemInstanceLoad_fn)(void* thisPtr, void* compoundTagPtr); +typedef void (__fastcall *ItemInstanceInventoryTick_fn)(void* thisPtr, void* level, void* ownerSharedPtr, int slot, bool selected); +typedef void (__fastcall *ItemInstanceOnCraftedBy_fn)(void* thisPtr, void* level, void* playerSharedPtr, int amount); +typedef bool (__fastcall *ItemInstanceInteractEnemy_fn)(void* thisPtr, void* playerSharedPtr, void* targetSharedPtr); +typedef void (__fastcall *ItemInstanceHurtEnemy_fn)(void* thisPtr, void* targetSharedPtr, void* playerSharedPtr); typedef bool (__fastcall *ItemMineBlock_fn)(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr); typedef float (__fastcall *PickaxeGetDestroySpeed_fn)(void* thisPtr, void* itemInstanceSharedPtr, void* tilePtr); typedef bool (__fastcall *PickaxeCanDestroySpecial_fn)(void* thisPtr, void* tilePtr); typedef void (__fastcall *TileOnPlace_fn)(void* thisPtr, void* level, int x, int y, int z); typedef void (__fastcall *TileNeighborChanged_fn)(void* thisPtr, void* level, int x, int y, int z, int type); typedef void (__fastcall *TileTick_fn)(void* thisPtr, void* level, int x, int y, int z, void* random); +typedef bool (__fastcall *TileUse_fn)(void* thisPtr, void* level, int x, int y, int z, void* playerSharedPtr, int face, float clickX, float clickY, float clickZ, bool soundOnly); +typedef void (__fastcall *TileStepOn_fn)(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr); +typedef void (__fastcall *TileEntityInside_fn)(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr); +typedef void (__fastcall *TileFallOn_fn)(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr, float fallDistance); +typedef void (__fastcall *TileOnRemoving_fn)(void* thisPtr, void* level, int x, int y, int z, int data); +typedef void (__fastcall *TileOnRemove_fn)(void* thisPtr, void* level, int x, int y, int z, int id, int data); +typedef void (__fastcall *TileDestroy_fn)(void* thisPtr, void* level, int x, int y, int z, int data); +typedef void (__fastcall *TilePlayerDestroy_fn)(void* thisPtr, void* level, void* playerSharedPtr, int x, int y, int z, int data); +typedef void (__fastcall *TilePlayerWillDestroy_fn)(void* thisPtr, void* level, int x, int y, int z, int data, void* playerSharedPtr); +typedef void (__fastcall *TileSetPlacedBy_fn)(void* thisPtr, void* level, int x, int y, int z, void* livingEntitySharedPtr, void* itemInstanceSharedPtr); +typedef void (__fastcall *TileSharedAction_fn)(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr, float fallDistance); +typedef void (__fastcall *TileSharedLifecycle_fn)(void* thisPtr, void* level, int x, int y, int z, void* arg5, void* arg6); typedef bool (__fastcall *LevelSetTileAndDataDispatch_fn)(void* thisPtr, int x, int y, int z, int tile, int data, int updateFlags); typedef bool (__fastcall *LevelSetDataDispatch_fn)(void* thisPtr, int x, int y, int z, int data, int updateFlags, bool forceUpdate); typedef void (__fastcall *LevelUpdateNeighborsAtDispatch_fn)(void* thisPtr, int x, int y, int z, int type); @@ -61,6 +77,8 @@ typedef bool (__fastcall *MultiPlayerGameModeUseItemOn_fn)(void* thisPtr, void* typedef bool (__fastcall *ServerPlayerGameModeUseItemOn_fn)(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, int x, int y, int z, int face, float clickX, float clickY, float clickZ, bool bTestUseOnOnly, bool* pbUsedItem); typedef void (__fastcall *MinecraftSetLevel_fn)(void* thisPtr, void* level, int message, void* forceInsertPlayerSharedPtr, bool doForceStatsSave, bool bPrimaryPlayerSignedOut); typedef bool (__fastcall *LevelAddEntity_fn)(void* thisPtr, void* entitySharedPtr); +typedef void (__fastcall *EntityPlayStepSound_fn)(void* thisPtr, int xt, int yt, int zt, int t); +typedef void (__fastcall *EntityCheckInsideTiles_fn)(void* thisPtr); typedef void (__fastcall *EntityMoveTo_fn)(void* thisPtr, double x, double y, double z, float yRot, float xRot); typedef void (__fastcall *EntityIONewById_fn)(void* outSharedPtr, int entityNumericId, void* level); typedef void (__fastcall *EntitySetPos_fn)(void* thisPtr, double x, double y, double z); @@ -130,6 +148,10 @@ namespace GameHooks extern ItemInstanceUseOn_fn Original_ItemInstanceUseOn; extern ItemInstanceSave_fn Original_ItemInstanceSave; extern ItemInstanceLoad_fn Original_ItemInstanceLoad; + extern ItemInstanceInventoryTick_fn Original_ItemInstanceInventoryTick; + extern ItemInstanceOnCraftedBy_fn Original_ItemInstanceOnCraftedBy; + extern ItemInstanceInteractEnemy_fn Original_ItemInstanceInteractEnemy; + extern ItemInstanceHurtEnemy_fn Original_ItemInstanceHurtEnemy; extern ItemMineBlock_fn Original_ItemMineBlock; extern ItemMineBlock_fn Original_DiggerItemMineBlock; extern PickaxeGetDestroySpeed_fn Original_PickaxeItemGetDestroySpeed; @@ -139,6 +161,18 @@ namespace GameHooks extern TileOnPlace_fn Original_TileOnPlace; extern TileNeighborChanged_fn Original_TileNeighborChanged; extern TileTick_fn Original_TileTick; + extern TileUse_fn Original_TileUse; + extern TileStepOn_fn Original_TileStepOn; + extern TileEntityInside_fn Original_TileEntityInside; + extern TileFallOn_fn Original_TileFallOn; + extern TileOnRemoving_fn Original_TileOnRemoving; + extern TileOnRemove_fn Original_TileOnRemove; + extern TileDestroy_fn Original_TileDestroy; + extern TilePlayerDestroy_fn Original_TilePlayerDestroy; + extern TilePlayerWillDestroy_fn Original_TilePlayerWillDestroy; + extern TileSetPlacedBy_fn Original_TileSetPlacedBy; + extern TileSharedAction_fn Original_TileSharedAction; + extern TileSharedLifecycle_fn Original_TileSharedLifecycle; extern LevelSetTileAndDataDispatch_fn Original_LevelSetTileAndData; extern LevelSetDataDispatch_fn Original_LevelSetData; extern LevelUpdateNeighborsAtDispatch_fn Original_LevelUpdateNeighborsAt; @@ -168,6 +202,8 @@ namespace GameHooks extern ServerPlayerGameModeUseItemOn_fn Original_ServerPlayerGameModeUseItemOn; extern MultiPlayerGameModeUseItemOn_fn Original_MultiPlayerGameModeUseItemOn; extern MinecraftSetLevel_fn Original_MinecraftSetLevel; + extern EntityPlayStepSound_fn Original_EntityPlayStepSound; + extern EntityCheckInsideTiles_fn Original_EntityCheckInsideTiles; extern TexturesBindTextureResource_fn Original_TexturesBindTextureResource; extern TexturesLoadTextureByName_fn Original_TexturesLoadTextureByName; extern TexturesLoadTextureByIndex_fn Original_TexturesLoadTextureByIndex; @@ -229,6 +265,10 @@ namespace GameHooks bool __fastcall Hooked_ItemInstanceUseOn(void* thisPtr, void* playerSharedPtr, void* level, int x, int y, int z, int face, float clickX, float clickY, float clickZ, bool bTestUseOnOnly); void* __fastcall Hooked_ItemInstanceSave(void* thisPtr, void* compoundTagPtr); void __fastcall Hooked_ItemInstanceLoad(void* thisPtr, void* compoundTagPtr); + void __fastcall Hooked_ItemInstanceInventoryTick(void* thisPtr, void* level, void* ownerSharedPtr, int slot, bool selected); + void __fastcall Hooked_ItemInstanceOnCraftedBy(void* thisPtr, void* level, void* playerSharedPtr, int amount); + bool __fastcall Hooked_ItemInstanceInteractEnemy(void* thisPtr, void* playerSharedPtr, void* targetSharedPtr); + void __fastcall Hooked_ItemInstanceHurtEnemy(void* thisPtr, void* targetSharedPtr, void* playerSharedPtr); bool __fastcall Hooked_ItemMineBlock(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr); bool __fastcall Hooked_DiggerItemMineBlock(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr); float __fastcall Hooked_PickaxeItemGetDestroySpeed(void* thisPtr, void* itemInstanceSharedPtr, void* tilePtr); @@ -238,6 +278,18 @@ namespace GameHooks void __fastcall Hooked_TileOnPlace(void* thisPtr, void* level, int x, int y, int z); void __fastcall Hooked_TileNeighborChanged(void* thisPtr, void* level, int x, int y, int z, int type); void __fastcall Hooked_TileTick(void* thisPtr, void* level, int x, int y, int z, void* random); + bool __fastcall Hooked_TileUse(void* thisPtr, void* level, int x, int y, int z, void* playerSharedPtr, int face, float clickX, float clickY, float clickZ, bool soundOnly); + void __fastcall Hooked_TileStepOn(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr); + void __fastcall Hooked_TileEntityInside(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr); + void __fastcall Hooked_TileFallOn(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr, float fallDistance); + void __fastcall Hooked_TileOnRemoving(void* thisPtr, void* level, int x, int y, int z, int data); + void __fastcall Hooked_TileOnRemove(void* thisPtr, void* level, int x, int y, int z, int id, int data); + void __fastcall Hooked_TileDestroy(void* thisPtr, void* level, int x, int y, int z, int data); + void __fastcall Hooked_TilePlayerDestroy(void* thisPtr, void* level, void* playerSharedPtr, int x, int y, int z, int data); + void __fastcall Hooked_TilePlayerWillDestroy(void* thisPtr, void* level, int x, int y, int z, int data, void* playerSharedPtr); + void __fastcall Hooked_TileSetPlacedBy(void* thisPtr, void* level, int x, int y, int z, void* livingEntitySharedPtr, void* itemInstanceSharedPtr); + void __fastcall Hooked_TileSharedAction(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr, float fallDistance); + void __fastcall Hooked_TileSharedLifecycle(void* thisPtr, void* level, int x, int y, int z, void* arg5, void* arg6); bool __fastcall Hooked_LevelSetTileAndData(void* thisPtr, int x, int y, int z, int tile, int data, int updateFlags); bool __fastcall Hooked_LevelSetData(void* thisPtr, int x, int y, int z, int data, int updateFlags, bool forceUpdate); void __fastcall Hooked_LevelUpdateNeighborsAt(void* thisPtr, int x, int y, int z, int type); @@ -266,6 +318,8 @@ namespace GameHooks bool __fastcall Hooked_ServerPlayerGameModeUseItemOn(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, int x, int y, int z, int face, float clickX, float clickY, float clickZ, bool bTestUseOnOnly, bool* pbUsedItem); bool __fastcall Hooked_MultiPlayerGameModeUseItemOn(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, int x, int y, int z, int face, void* hitVec3Ptr, bool bTestUseOnly, bool* pbUsedItem); void __fastcall Hooked_MinecraftSetLevel(void* thisPtr, void* level, int message, void* forceInsertPlayerSharedPtr, bool doForceStatsSave, bool bPrimaryPlayerSignedOut); + void __fastcall Hooked_EntityPlayStepSound(void* thisPtr, int xt, int yt, int zt, int t); + void __fastcall Hooked_EntityCheckInsideTiles(void* thisPtr); void __fastcall Hooked_TexturesBindTextureResource(void* thisPtr, void* resourcePtr); int __fastcall Hooked_TexturesLoadTextureByName(void* thisPtr, int texId, const std::wstring& resourceName); int __fastcall Hooked_TexturesLoadTextureByIndex(void* thisPtr, int idx); diff --git a/WeaveLoaderRuntime/src/HookManager.cpp b/WeaveLoaderRuntime/src/HookManager.cpp index c4f3c9d..088fb22 100644 --- a/WeaveLoaderRuntime/src/HookManager.cpp +++ b/WeaveLoaderRuntime/src/HookManager.cpp @@ -184,6 +184,91 @@ bool HookManager::Install(const SymbolResolver& symbols) } } + if (symbols.Item.pItemInstanceInventoryTick) + { + if (MH_CreateHook(symbols.Item.pItemInstanceInventoryTick, + reinterpret_cast(&GameHooks::Hooked_ItemInstanceInventoryTick), + reinterpret_cast(&GameHooks::Original_ItemInstanceInventoryTick)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemInstance::inventoryTick"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked ItemInstance::inventoryTick (managed item callbacks)"); + } + } + + if (symbols.Item.pItemInstanceOnCraftedBy) + { + if (MH_CreateHook(symbols.Item.pItemInstanceOnCraftedBy, + reinterpret_cast(&GameHooks::Hooked_ItemInstanceOnCraftedBy), + reinterpret_cast(&GameHooks::Original_ItemInstanceOnCraftedBy)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemInstance::onCraftedBy"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked ItemInstance::onCraftedBy (managed item callbacks)"); + } + } + + if (symbols.Item.pItemInstanceInteractEnemy) + { + if (MH_CreateHook(symbols.Item.pItemInstanceInteractEnemy, + reinterpret_cast(&GameHooks::Hooked_ItemInstanceInteractEnemy), + reinterpret_cast(&GameHooks::Original_ItemInstanceInteractEnemy)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemInstance::interactEnemy"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked ItemInstance::interactEnemy (managed item callbacks)"); + } + } + + if (symbols.Item.pItemInstanceHurtEnemy) + { + if (MH_CreateHook(symbols.Item.pItemInstanceHurtEnemy, + reinterpret_cast(&GameHooks::Hooked_ItemInstanceHurtEnemy), + reinterpret_cast(&GameHooks::Original_ItemInstanceHurtEnemy)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemInstance::hurtEnemy"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked ItemInstance::hurtEnemy (managed item callbacks)"); + } + } + + if (symbols.Entity.pEntityPlayStepSound) + { + if (MH_CreateHook(symbols.Entity.pEntityPlayStepSound, + reinterpret_cast(&GameHooks::Hooked_EntityPlayStepSound), + reinterpret_cast(&GameHooks::Original_EntityPlayStepSound)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Entity::playStepSound"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Entity::playStepSound (step-on callbacks)"); + } + } + + if (symbols.Entity.pEntityCheckInsideTiles) + { + if (MH_CreateHook(symbols.Entity.pEntityCheckInsideTiles, + reinterpret_cast(&GameHooks::Hooked_EntityCheckInsideTiles), + reinterpret_cast(&GameHooks::Original_EntityCheckInsideTiles)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Entity::checkInsideTiles"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Entity::checkInsideTiles (entity-inside callbacks)"); + } + } + + if (symbols.Item.pItemInstanceGetIcon) { if (MH_CreateHook(symbols.Item.pItemInstanceGetIcon, @@ -478,6 +563,162 @@ bool HookManager::Install(const SymbolResolver& symbols) } } + if (symbols.Tile.pTileUse) + { + if (MH_CreateHook(symbols.Tile.pTileUse, + reinterpret_cast(&GameHooks::Hooked_TileUse), + reinterpret_cast(&GameHooks::Original_TileUse)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::use"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::use (managed block callbacks)"); + } + } + + + void* sharedActionTarget = symbols.Tile.pTileStepOn + ? symbols.Tile.pTileStepOn + : symbols.Tile.pTileFallOn; + int sharedActionCount = 0; + if (sharedActionTarget) + { + if (symbols.Tile.pTileStepOn == sharedActionTarget) sharedActionCount++; + if (symbols.Tile.pTileFallOn == sharedActionTarget) sharedActionCount++; + } + + const bool useSharedActionHook = sharedActionTarget && sharedActionCount >= 2; + if (useSharedActionHook) + { + if (MH_CreateHook(sharedActionTarget, + reinterpret_cast(&GameHooks::Hooked_TileSharedAction), + reinterpret_cast(&GameHooks::Original_TileSharedAction)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook shared Tile action stub"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked shared Tile action stub (stepOn/fallOn)"); + } + } + else + { + if (symbols.Tile.pTileStepOn) + { + if (MH_CreateHook(symbols.Tile.pTileStepOn, + reinterpret_cast(&GameHooks::Hooked_TileStepOn), + reinterpret_cast(&GameHooks::Original_TileStepOn)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::stepOn"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::stepOn (managed block callbacks)"); + } + } + + if (symbols.Tile.pTileFallOn) + { + if (MH_CreateHook(symbols.Tile.pTileFallOn, + reinterpret_cast(&GameHooks::Hooked_TileFallOn), + reinterpret_cast(&GameHooks::Original_TileFallOn)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::fallOn"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::fallOn (managed block callbacks)"); + } + } + } + + void* sharedLifecycleTarget = symbols.Tile.pTileDestroy + ? symbols.Tile.pTileDestroy + : symbols.Tile.pTileTick; + int sharedLifecycleCount = 0; + if (sharedLifecycleTarget) + { + if (symbols.Tile.pTileDestroy == sharedLifecycleTarget) sharedLifecycleCount++; + if (symbols.Tile.pTileOnRemoving == sharedLifecycleTarget) sharedLifecycleCount++; + if (symbols.Tile.pTileOnRemove == sharedLifecycleTarget) sharedLifecycleCount++; + if (symbols.Tile.pTileTick == sharedLifecycleTarget) sharedLifecycleCount++; + } + + const bool useSharedLifecycleHook = sharedLifecycleTarget && sharedLifecycleCount >= 2; + if (useSharedLifecycleHook) + { + if (MH_CreateHook(sharedLifecycleTarget, + reinterpret_cast(&GameHooks::Hooked_TileSharedLifecycle), + reinterpret_cast(&GameHooks::Original_TileSharedLifecycle)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook shared Tile lifecycle stub"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked shared Tile lifecycle stub (destroy)"); + } + } + else + { + if (symbols.Tile.pTileDestroy) + { + if (MH_CreateHook(symbols.Tile.pTileDestroy, + reinterpret_cast(&GameHooks::Hooked_TileDestroy), + reinterpret_cast(&GameHooks::Original_TileDestroy)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::destroy"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::destroy (managed block callbacks)"); + } + } + + } + + if (symbols.Tile.pTilePlayerDestroy) + { + if (MH_CreateHook(symbols.Tile.pTilePlayerDestroy, + reinterpret_cast(&GameHooks::Hooked_TilePlayerDestroy), + reinterpret_cast(&GameHooks::Original_TilePlayerDestroy)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::playerDestroy"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::playerDestroy (managed block callbacks)"); + } + } + + if (symbols.Tile.pTilePlayerWillDestroy) + { + if (MH_CreateHook(symbols.Tile.pTilePlayerWillDestroy, + reinterpret_cast(&GameHooks::Hooked_TilePlayerWillDestroy), + reinterpret_cast(&GameHooks::Original_TilePlayerWillDestroy)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::playerWillDestroy"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::playerWillDestroy (managed block callbacks)"); + } + } + + if (symbols.Tile.pTileSetPlacedBy) + { + if (MH_CreateHook(symbols.Tile.pTileSetPlacedBy, + reinterpret_cast(&GameHooks::Hooked_TileSetPlacedBy), + reinterpret_cast(&GameHooks::Original_TileSetPlacedBy)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::setPlacedBy"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::setPlacedBy (managed block callbacks)"); + } + } + if (symbols.Level.pServerLevelTickPendingTicks) { if (MH_CreateHook(symbols.Level.pServerLevelTickPendingTicks, diff --git a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp index e2d739e..5b54793 100644 --- a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp +++ b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp @@ -67,6 +67,10 @@ namespace static const char* SYM_ITEMINSTANCE_SAVE = "?save@ItemInstance@@QEAAPEAVCompoundTag@@PEAV2@@Z"; static const char* SYM_ITEMINSTANCE_LOAD = "?load@ItemInstance@@QEAAXPEAVCompoundTag@@@Z"; static const char* SYM_ITEMINSTANCE_HURTANDBREAK = "?hurtAndBreak@ItemInstance@@QEAAXHV?$shared_ptr@VLivingEntity@@@std@@@Z"; + static const char* SYM_ITEMINSTANCE_INVENTORYTICK = "?inventoryTick@ItemInstance@@QEAAXPEAVLevel@@V?$shared_ptr@VEntity@@@std@@H_N@Z"; + static const char* SYM_ITEMINSTANCE_ONCRAFTEDBY = "?onCraftedBy@ItemInstance@@QEAAXPEAVLevel@@V?$shared_ptr@VPlayer@@@std@@H@Z"; + static const char* SYM_ITEMINSTANCE_INTERACTENEMY = "?interactEnemy@ItemInstance@@QEAA_NV?$shared_ptr@VPlayer@@@std@@V?$shared_ptr@VLivingEntity@@@3@@Z"; + static const char* SYM_ITEMINSTANCE_HURTENEMY = "?hurtEnemy@ItemInstance@@QEAAXV?$shared_ptr@VLivingEntity@@@std@@V?$shared_ptr@VPlayer@@@3@@Z"; static const char* SYM_ITEM_MINEBLOCK = "?mineBlock@Item@@UEAA_NV?$shared_ptr@VItemInstance@@@std@@PEAVLevel@@HHHHV?$shared_ptr@VLivingEntity@@@3@@Z"; static const char* SYM_DIGGERITEM_MINEBLOCK = "?mineBlock@DiggerItem@@UEAA_NV?$shared_ptr@VItemInstance@@@std@@PEAVLevel@@HHHHV?$shared_ptr@VLivingEntity@@@3@@Z"; static const char* SYM_PICKAXEITEM_GETDESTROYSPEED = "?getDestroySpeed@PickaxeItem@@UEAAMV?$shared_ptr@VItemInstance@@@std@@PEAVTile@@@Z"; @@ -81,6 +85,16 @@ namespace static const char* SYM_TILE_ONPLACE = "?onPlace@Tile@@UEAAXPEAVLevel@@HHH@Z"; static const char* SYM_TILE_NEIGHBORCHANGED = "?neighborChanged@Tile@@UEAAXPEAVLevel@@HHHH@Z"; static const char* SYM_TILE_TICK = "?tick@Tile@@UEAAXPEAVLevel@@HHHPEAVRandom@@@Z"; + static const char* SYM_TILE_USE = "?use@Tile@@UEAA_NPEAVLevel@@HHHV?$shared_ptr@VPlayer@@@std@@HMMM_N@Z"; + static const char* SYM_TILE_STEPON = "?stepOn@Tile@@UEAAXPEAVLevel@@HHHV?$shared_ptr@VEntity@@@std@@@Z"; + static const char* SYM_TILE_ENTITYINSIDE = "?entityInside@Tile@@UEAAXPEAVLevel@@HHHV?$shared_ptr@VEntity@@@std@@@Z"; + static const char* SYM_TILE_FALLON = "?fallOn@Tile@@UEAAXPEAVLevel@@HHHV?$shared_ptr@VEntity@@@std@@M@Z"; + static const char* SYM_TILE_ONREMOVING = "?onRemoving@Tile@@UEAAXPEAVLevel@@HHHH@Z"; + static const char* SYM_TILE_ONREMOVE = "?onRemove@Tile@@UEAAXPEAVLevel@@HHHHH@Z"; + static const char* SYM_TILE_DESTROY = "?destroy@Tile@@UEAAXPEAVLevel@@HHHH@Z"; + static const char* SYM_TILE_PLAYERDESTROY = "?playerDestroy@Tile@@UEAAXPEAVLevel@@V?$shared_ptr@VPlayer@@@std@@HHHH@Z"; + static const char* SYM_TILE_PLAYERWILLDESTROY = "?playerWillDestroy@Tile@@UEAAXPEAVLevel@@HHHHV?$shared_ptr@VPlayer@@@std@@@Z"; + static const char* SYM_TILE_SETPLACEDBY = "?setPlacedBy@Tile@@UEAAXPEAVLevel@@HHHV?$shared_ptr@VLivingEntity@@@std@@V?$shared_ptr@VItemInstance@@@4@@Z"; static const char* SYM_TILE_GETRESOURCE = "?getResource@Tile@@UEAAHHPEAVRandom@@H@Z"; static const char* SYM_TILE_GETPLACEDONFACEDATAVALUE = "?getPlacedOnFaceDataValue@Tile@@UEAAHPEAVLevel@@HHHHMMMH@Z"; static const char* SYM_TILE_CLONETILEID = "?cloneTileId@Tile@@UEAAHPEAVLevel@@HHH@Z"; @@ -138,7 +152,9 @@ namespace static const char* SYM_LEVEL_ADDENTITY = "?addEntity@Level@@UEAA_NV?$shared_ptr@VEntity@@@std@@@Z"; static const char* SYM_ENTITYIO_NEWBYID = "?newById@EntityIO@@SA?AV?$shared_ptr@VEntity@@@std@@HPEAVLevel@@@Z"; static const char* SYM_ENTITY_MOVETO = "?moveTo@Entity@@QEAAXNNNMM@Z"; + static const char* SYM_ENTITY_CHECKINSIDETILES = "?checkInsideTiles@Entity@@MEAAXXZ"; static const char* SYM_ENTITY_SETPOS = "?setPos@Entity@@QEAAXNNN@Z"; + static const char* SYM_ENTITY_PLAYSTEPSOUND = "?playStepSound@Entity@@MEAAXHHHH@Z"; static const char* SYM_LIVINGENTITY_GETLOOKANGLE = "?getLookAngle@LivingEntity@@UEAAPEAVVec3@@XZ"; static const char* SYM_ENTITY_GETLOOKANGLE = "?getLookAngle@Entity@@UEAAPEAVVec3@@XZ"; static const char* SYM_LIVINGENTITY_GETVIEWVECTOR = "?getViewVector@LivingEntity@@UEAAPEAVVec3@@M@Z"; @@ -311,6 +327,10 @@ bool ItemSymbols::Resolve(SymbolResolver& resolver) pItemInstanceSave = resolver.Resolve(SYM_ITEMINSTANCE_SAVE); pItemInstanceLoad = resolver.Resolve(SYM_ITEMINSTANCE_LOAD); pItemInstanceHurtAndBreak = resolver.Resolve(SYM_ITEMINSTANCE_HURTANDBREAK); + pItemInstanceInventoryTick = resolver.Resolve(SYM_ITEMINSTANCE_INVENTORYTICK); + pItemInstanceOnCraftedBy = resolver.Resolve(SYM_ITEMINSTANCE_ONCRAFTEDBY); + pItemInstanceInteractEnemy = resolver.Resolve(SYM_ITEMINSTANCE_INTERACTENEMY); + pItemInstanceHurtEnemy = resolver.Resolve(SYM_ITEMINSTANCE_HURTENEMY); pItemMineBlock = resolver.Resolve(SYM_ITEM_MINEBLOCK); pDiggerItemMineBlock = resolver.Resolve(SYM_DIGGERITEM_MINEBLOCK); pPickaxeItemGetDestroySpeed = resolver.Resolve(SYM_PICKAXEITEM_GETDESTROYSPEED); @@ -334,6 +354,10 @@ void ItemSymbols::Log() const LogSym("ItemInstance::save", pItemInstanceSave); LogSym("ItemInstance::load", pItemInstanceLoad); LogSym("ItemInstance::hurtAndBreak", pItemInstanceHurtAndBreak); + LogSym("ItemInstance::inventoryTick", pItemInstanceInventoryTick); + LogSym("ItemInstance::onCraftedBy", pItemInstanceOnCraftedBy); + LogSym("ItemInstance::interactEnemy", pItemInstanceInteractEnemy); + LogSym("ItemInstance::hurtEnemy", pItemInstanceHurtEnemy); LogSym("Item::mineBlock", pItemMineBlock); LogSym("DiggerItem::mineBlock", pDiggerItemMineBlock); LogSym("PickaxeItem::getDestroySpeed", pPickaxeItemGetDestroySpeed); @@ -351,6 +375,16 @@ bool TileSymbols::Resolve(SymbolResolver& resolver) pTileOnPlace = resolver.Resolve(SYM_TILE_ONPLACE); pTileNeighborChanged = resolver.Resolve(SYM_TILE_NEIGHBORCHANGED); pTileTick = resolver.Resolve(SYM_TILE_TICK); + pTileUse = resolver.Resolve(SYM_TILE_USE); + pTileStepOn = resolver.Resolve(SYM_TILE_STEPON); + pTileEntityInside = resolver.Resolve(SYM_TILE_ENTITYINSIDE); + pTileFallOn = resolver.Resolve(SYM_TILE_FALLON); + pTileOnRemoving = resolver.Resolve(SYM_TILE_ONREMOVING); + pTileOnRemove = resolver.Resolve(SYM_TILE_ONREMOVE); + pTileDestroy = resolver.Resolve(SYM_TILE_DESTROY); + pTilePlayerDestroy = resolver.Resolve(SYM_TILE_PLAYERDESTROY); + pTilePlayerWillDestroy = resolver.Resolve(SYM_TILE_PLAYERWILLDESTROY); + pTileSetPlacedBy = resolver.Resolve(SYM_TILE_SETPLACEDBY); pTileGetResource = resolver.Resolve(SYM_TILE_GETRESOURCE); pTileGetPlacedOnFaceDataValue = resolver.Resolve(SYM_TILE_GETPLACEDONFACEDATAVALUE); pTileCloneTileId = resolver.Resolve(SYM_TILE_CLONETILEID); @@ -391,6 +425,26 @@ bool TileSymbols::Resolve(SymbolResolver& resolver) pTileNeighborChanged = resolver.ResolveExact("Tile::neighborChanged"); if (resolver.IsStub(pTileTick)) pTileTick = resolver.ResolveExact("Tile::tick"); + if (resolver.IsStub(pTileUse)) + pTileUse = resolver.ResolveExact("Tile::use"); + if (resolver.IsStub(pTileStepOn)) + pTileStepOn = resolver.ResolveExact("Tile::stepOn"); + if (resolver.IsStub(pTileEntityInside)) + pTileEntityInside = resolver.ResolveExact("Tile::entityInside"); + if (resolver.IsStub(pTileFallOn)) + pTileFallOn = resolver.ResolveExact("Tile::fallOn"); + if (resolver.IsStub(pTileOnRemoving)) + pTileOnRemoving = resolver.ResolveExact("Tile::onRemoving"); + if (resolver.IsStub(pTileOnRemove)) + pTileOnRemove = resolver.ResolveExact("Tile::onRemove"); + if (resolver.IsStub(pTileDestroy)) + pTileDestroy = resolver.ResolveExact("Tile::destroy"); + if (resolver.IsStub(pTilePlayerDestroy)) + pTilePlayerDestroy = resolver.ResolveExact("Tile::playerDestroy"); + if (resolver.IsStub(pTilePlayerWillDestroy)) + pTilePlayerWillDestroy = resolver.ResolveExact("Tile::playerWillDestroy"); + if (resolver.IsStub(pTileSetPlacedBy)) + pTileSetPlacedBy = resolver.ResolveExact("Tile::setPlacedBy"); if (resolver.IsStub(pWoodSlabRegisterIcons)) pWoodSlabRegisterIcons = resolver.ResolveExact("WoodSlabTile::registerIcons"); if (resolver.IsStub(pTileClip)) @@ -403,6 +457,16 @@ void TileSymbols::Log() const LogSym("Tile::onPlace", pTileOnPlace); LogSym("Tile::neighborChanged", pTileNeighborChanged); LogSym("Tile::tick", pTileTick); + LogSym("Tile::use", pTileUse); + LogSym("Tile::stepOn", pTileStepOn); + LogSym("Tile::entityInside", pTileEntityInside); + LogSym("Tile::fallOn", pTileFallOn); + LogSym("Tile::onRemoving", pTileOnRemoving); + LogSym("Tile::onRemove", pTileOnRemove); + LogSym("Tile::destroy", pTileDestroy); + LogSym("Tile::playerDestroy", pTilePlayerDestroy); + LogSym("Tile::playerWillDestroy", pTilePlayerWillDestroy); + LogSym("Tile::setPlacedBy", pTileSetPlacedBy); LogSym("Tile::getResource", pTileGetResource); LogSym("Tile::getPlacedOnFaceDataValue", pTileGetPlacedOnFaceDataValue); LogSym("Tile::cloneTileId", pTileCloneTileId); @@ -501,8 +565,10 @@ bool EntitySymbols::Resolve(SymbolResolver& resolver) pServerPlayerGameModeUseItemOn = resolver.Resolve(SYM_SERVER_PLAYER_GAMEMODE_USEITEMON); pMultiPlayerGameModeUseItemOn = resolver.Resolve(SYM_MULTI_PLAYER_GAMEMODE_USEITEMON); pLevelAddEntity = resolver.Resolve(SYM_LEVEL_ADDENTITY); + pEntityPlayStepSound = resolver.Resolve(SYM_ENTITY_PLAYSTEPSOUND); pEntityIONewById = resolver.Resolve(SYM_ENTITYIO_NEWBYID); pEntityMoveTo = resolver.Resolve(SYM_ENTITY_MOVETO); + pEntityCheckInsideTiles = resolver.Resolve(SYM_ENTITY_CHECKINSIDETILES); pEntitySetPos = resolver.Resolve(SYM_ENTITY_SETPOS); pEntityGetLookAngle = resolver.Resolve(SYM_LIVINGENTITY_GETLOOKANGLE); pLivingEntityGetPos = resolver.Resolve(SYM_LIVINGENTITY_GETPOS); @@ -524,8 +590,10 @@ void EntitySymbols::Log() const LogSym("ServerPlayerGameMode::useItemOn", pServerPlayerGameModeUseItemOn); LogSym("MultiPlayerGameMode::useItemOn", pMultiPlayerGameModeUseItemOn); LogSym("Level::addEntity", pLevelAddEntity); + LogSym("Entity::playStepSound", pEntityPlayStepSound); LogSym("EntityIO::newById", pEntityIONewById); LogSym("Entity::moveTo", pEntityMoveTo); + LogSym("Entity::checkInsideTiles", pEntityCheckInsideTiles); LogSym("Entity::setPos", pEntitySetPos); LogSym("LivingEntity/Entity::getLookAngle", pEntityGetLookAngle); LogSym("LivingEntity::getPos", pLivingEntityGetPos); diff --git a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h index a0f07af..c09b7e3 100644 --- a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h +++ b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h @@ -81,6 +81,10 @@ struct ItemSymbols void* pItemInstanceSave = nullptr; void* pItemInstanceLoad = nullptr; void* pItemInstanceHurtAndBreak = nullptr; + void* pItemInstanceInventoryTick = nullptr; + void* pItemInstanceOnCraftedBy = nullptr; + void* pItemInstanceInteractEnemy = nullptr; + void* pItemInstanceHurtEnemy = nullptr; void* pItemMineBlock = nullptr; void* pDiggerItemMineBlock = nullptr; void* pPickaxeItemGetDestroySpeed = nullptr; @@ -101,6 +105,16 @@ struct TileSymbols void* pTileOnPlace = nullptr; void* pTileNeighborChanged = nullptr; void* pTileTick = nullptr; + void* pTileUse = nullptr; + void* pTileStepOn = nullptr; + void* pTileEntityInside = nullptr; + void* pTileFallOn = nullptr; + void* pTileOnRemoving = nullptr; + void* pTileOnRemove = nullptr; + void* pTileDestroy = nullptr; + void* pTilePlayerDestroy = nullptr; + void* pTilePlayerWillDestroy = nullptr; + void* pTileSetPlacedBy = nullptr; void* pTileGetResource = nullptr; void* pTileGetPlacedOnFaceDataValue = nullptr; void* pTileCloneTileId = nullptr; @@ -173,9 +187,11 @@ struct EntitySymbols void* pServerPlayerGameModeUseItemOn = nullptr; void* pMultiPlayerGameModeUseItemOn = nullptr; void* pLevelAddEntity = nullptr; + void* pEntityPlayStepSound = nullptr; void* pEntityIONewById = nullptr; void* pEntityMoveTo = nullptr; void* pEntitySetPos = nullptr; + void* pEntityCheckInsideTiles = nullptr; void* pEntityGetLookAngle = nullptr; void* pLivingEntityGetPos = nullptr; void* pLivingEntityGetViewVector = nullptr;