diff --git a/ExampleMod/ExampleMod.cs b/ExampleMod/ExampleMod.cs index 4f51280..31ed0dc 100644 --- a/ExampleMod/ExampleMod.cs +++ b/ExampleMod/ExampleMod.cs @@ -10,6 +10,13 @@ namespace ExampleMod; public class ExampleMod : IMod { public static RegisteredBlock? RubyOre; + public static RegisteredBlock? RubyStone; + public static RegisteredBlock? RubyWoodPlanks; + public static RegisteredBlock? RubySand; + 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? RubyPickaxeItem; @@ -91,6 +98,95 @@ public class ExampleMod : IMod { } + private sealed class RubyLampBlock : WeaveLoader.API.Block.Block + { + private readonly bool _isLit; + + public RubyLampBlock(bool isLit) + { + _isLit = isLit; + } + + public override void OnPlace(BlockUpdateContext context) + { + if (context.IsClientSide) + return; + + if (_isLit) + { + if (!context.HasNeighborSignal()) + context.ScheduleTick(4); + } + else if (context.HasNeighborSignal()) + { + context.SetBlock(new Identifier("examplemod:ruby_lamp_lit")); + } + } + + public override void OnNeighborChanged(BlockNeighborChangedContext context) + { + if (context.Block.IsClientSide) + return; + + if (_isLit) + { + if (!context.Block.HasNeighborSignal()) + context.Block.ScheduleTick(4); + } + else if (context.Block.HasNeighborSignal()) + { + context.Block.SetBlock(new Identifier("examplemod:ruby_lamp_lit")); + } + } + + public override void OnScheduledTick(BlockTickContext context) + { + if (!_isLit || context.Block.IsClientSide) + return; + + if (!context.Block.HasNeighborSignal()) + context.Block.SetBlock(new Identifier("examplemod:ruby_lamp")); + } + } + + private sealed class RubySandBlock : FallingBlock + { + private static readonly int FlowingLavaId = IdHelper.GetBlockNumericId("minecraft:flowing_lava"); + private static readonly int LavaId = IdHelper.GetBlockNumericId("minecraft:lava"); + + public override void OnPlace(BlockUpdateContext context) + { + TryHarden(context); + } + + public override void OnNeighborChanged(BlockNeighborChangedContext context) + { + TryHarden(context.Block); + } + + private static void TryHarden(BlockUpdateContext context) + { + if (context.IsClientSide || RubyStone == null) + return; + + if (HasLavaAtOrAdjacent(context)) + context.SetBlock(RubyStone.NumericId); + } + + private static bool HasLavaAtOrAdjacent(BlockUpdateContext context) + { + static bool IsLava(int blockId) => blockId == FlowingLavaId || blockId == LavaId; + + return IsLava(context.GetBlockId()) || + IsLava(context.GetBlockId(-1, 0, 0)) || + IsLava(context.GetBlockId(1, 0, 0)) || + IsLava(context.GetBlockId(0, -1, 0)) || + IsLava(context.GetBlockId(0, 1, 0)) || + IsLava(context.GetBlockId(0, 0, -1)) || + IsLava(context.GetBlockId(0, 0, 1)); + } + } + public void OnInitialize() { RubyOre = Registry.Block.Register("examplemod:ruby_ore", @@ -105,6 +201,95 @@ public class ExampleMod : IMod .RequiredTool(ToolType.Pickaxe) .InCreativeTab(CreativeTab.BuildingBlocks)); + RubyStone = Registry.Block.Register("examplemod:ruby_stone", + new BlockProperties() + .Material(MaterialType.Stone) + .Hardness(1.5f) + .Resistance(10f) + .Sound(SoundType.Stone) + .Icon("examplemod:ruby_stone") + .Name("Ruby Stone") + .RequiredHarvestLevel(1) + .RequiredTool(ToolType.Pickaxe) + .InCreativeTab(CreativeTab.BuildingBlocks)); + + RubyWoodPlanks = Registry.Block.Register("examplemod:ruby_wood_planks", + new BlockProperties() + .Material(MaterialType.Wood) + .Hardness(2.0f) + .Resistance(5f) + .Sound(SoundType.Wood) + .Icon("examplemod:ruby_wood_planks") + .Name("Ruby Wood Planks") + .InCreativeTab(CreativeTab.BuildingBlocks)); + + RubySand = Registry.Block.Register("examplemod:ruby_sand", + new RubySandBlock(), + new BlockProperties() + .Material(MaterialType.Sand) + .Hardness(0.5f) + .Resistance(2.5f) + .Sound(SoundType.Sand) + .Icon("examplemod:ruby_sand") + .Name("Ruby Sand") + .RequiredTool(ToolType.Shovel) + .InCreativeTab(CreativeTab.BuildingBlocks)); + + RubyStoneSlab = (RegisteredSlabBlock)Registry.Block.Register("examplemod:ruby_stone_slab", + new SlabBlock(), + new BlockProperties() + .Material(MaterialType.Stone) + .Hardness(1.5f) + .Resistance(10f) + .Sound(SoundType.Stone) + .Icon("examplemod:ruby_stone") + .Name("Ruby Stone Slab") + .RequiredHarvestLevel(1) + .RequiredTool(ToolType.Pickaxe) + .InCreativeTab(CreativeTab.BuildingBlocks)); + + RubyWoodSlab = (RegisteredSlabBlock)Registry.Block.Register("examplemod:ruby_wood_slab", + new SlabBlock(), + new BlockProperties() + .Material(MaterialType.Wood) + .Hardness(2.0f) + .Resistance(5f) + .Sound(SoundType.Wood) + .Icon("examplemod:ruby_wood_planks") + .Name("Ruby Wood Slab") + .InCreativeTab(CreativeTab.BuildingBlocks)); + + RubyLamp = Registry.Block.Register("examplemod:ruby_lamp", new RubyLampBlock(false), + new BlockProperties() + .Material(MaterialType.Glass) + .Hardness(0.3f) + .Resistance(1.5f) + .Sound(SoundType.Glass) + .Icon("examplemod:ruby_lamp") + .Name("Ruby Lamp") + .RequiredHarvestLevel(0) + .RequiredTool(ToolType.Pickaxe) + .AcceptsRedstonePower() + .InCreativeTab(CreativeTab.BuildingBlocks)); + + RubyLampLit = Registry.Block.Register("examplemod:ruby_lamp_lit", + new RubyLampBlock(true) + { + DropAsBlockId = new Identifier("examplemod:ruby_lamp"), + CloneAsBlockId = new Identifier("examplemod:ruby_lamp") + }, + new BlockProperties() + .Material(MaterialType.Glass) + .Hardness(0.3f) + .Resistance(1.5f) + .Sound(SoundType.Glass) + .Icon("examplemod:ruby_lamp_on") + .LightLevel(1.0f) + .Name("Ruby Lamp") + .RequiredHarvestLevel(0) + .RequiredTool(ToolType.Pickaxe) + .AcceptsRedstonePower()); + Orichalcum = Registry.Block.Register("examplemod:orichalcum_ore", new BlockProperties() .Material(MaterialType.Metal) diff --git a/ExampleMod/assets/blocks/ruby_lamp.png b/ExampleMod/assets/blocks/ruby_lamp.png new file mode 100644 index 0000000..c535708 Binary files /dev/null and b/ExampleMod/assets/blocks/ruby_lamp.png differ diff --git a/ExampleMod/assets/blocks/ruby_lamp_on.png b/ExampleMod/assets/blocks/ruby_lamp_on.png new file mode 100644 index 0000000..6508554 Binary files /dev/null and b/ExampleMod/assets/blocks/ruby_lamp_on.png differ diff --git a/ExampleMod/assets/blocks/ruby_sand.png b/ExampleMod/assets/blocks/ruby_sand.png new file mode 100644 index 0000000..3d333e4 Binary files /dev/null and b/ExampleMod/assets/blocks/ruby_sand.png differ diff --git a/ExampleMod/assets/blocks/ruby_stone.png b/ExampleMod/assets/blocks/ruby_stone.png new file mode 100644 index 0000000..d4ab77b Binary files /dev/null and b/ExampleMod/assets/blocks/ruby_stone.png differ diff --git a/ExampleMod/assets/blocks/ruby_wood_planks.png b/ExampleMod/assets/blocks/ruby_wood_planks.png new file mode 100644 index 0000000..e7fe9c0 Binary files /dev/null and b/ExampleMod/assets/blocks/ruby_wood_planks.png differ diff --git a/ExampleMod/assets/lang/en-GB.lang b/ExampleMod/assets/lang/en-GB.lang index 794d23f..6823015 100644 --- a/ExampleMod/assets/lang/en-GB.lang +++ b/ExampleMod/assets/lang/en-GB.lang @@ -4,6 +4,14 @@ # This file documents the expected format for future multi-locale support. block.examplemod.ruby_ore=Ruby Ore +block.examplemod.ruby_stone=Ruby Stone +block.examplemod.ruby_wood_planks=Ruby Wood Planks +block.examplemod.ruby_stone_slab=Ruby Stone Slab +block.examplemod.ruby_stone_slab_double=Ruby Stone Slab +block.examplemod.ruby_wood_slab=Ruby Wood Slab +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 item.examplemod.ruby=Ruby item.examplemod.ruby_sword=Ruby Sword @@ -12,3 +20,5 @@ item.examplemod.ruby_pickaxe=Ruby Pickaxe item.examplemod.ruby_axe=Ruby Axe item.examplemod.ruby_hoe=Ruby Hoe item.examplemod.ruby_wand=Ruby Wand + +block.examplemod.ruby_sand=Ruby Sand diff --git a/WeaveLoader.API/Block/BlockProperties.cs b/WeaveLoader.API/Block/BlockProperties.cs index 81ed605..6ffad85 100644 --- a/WeaveLoader.API/Block/BlockProperties.cs +++ b/WeaveLoader.API/Block/BlockProperties.cs @@ -65,6 +65,7 @@ public class BlockProperties internal string? NameValue; internal int RequiredHarvestLevelValue = -1; internal ToolType RequiredToolValue = ToolType.None; + internal bool AcceptsRedstonePowerValue; public BlockProperties Material(MaterialType material) { MaterialValue = material; return this; } public BlockProperties Hardness(float hardness) { HardnessValue = hardness; return this; } @@ -82,4 +83,6 @@ public class BlockProperties public BlockProperties RequiredHarvestLevel(int level) { RequiredHarvestLevelValue = level; return this; } /// Tool type required to harvest this block (e.g. Pickaxe for stone-like blocks). public BlockProperties RequiredTool(ToolType tool) { RequiredToolValue = tool; return this; } + /// Marks the block as one that can receive redstone power. Stored for future block callbacks. + public BlockProperties AcceptsRedstonePower(bool accepts = true) { AcceptsRedstonePowerValue = accepts; return this; } } diff --git a/WeaveLoader.API/Block/BlockRegistry.cs b/WeaveLoader.API/Block/BlockRegistry.cs index 8fd8434..c9a35b7 100644 --- a/WeaveLoader.API/Block/BlockRegistry.cs +++ b/WeaveLoader.API/Block/BlockRegistry.cs @@ -20,6 +20,19 @@ public class RegisteredBlock } } +public class RegisteredSlabBlock : RegisteredBlock +{ + public Identifier DoubleStringId { get; } + public int DoubleNumericId { get; } + + internal RegisteredSlabBlock(Identifier id, Identifier doubleId, int numericId, int doubleNumericId) + : base(id, numericId) + { + DoubleStringId = doubleId; + DoubleNumericId = doubleNumericId; + } +} + /// /// Block registration via the WeaveLoader registry. /// Accessed through . @@ -48,7 +61,8 @@ public static class BlockRegistry properties.LightBlockValue, properties.NameValue ?? "", properties.RequiredHarvestLevelValue, - (int)properties.RequiredToolValue); + (int)properties.RequiredToolValue, + properties.AcceptsRedstonePowerValue ? 1 : 0); if (numericId < 0) throw new InvalidOperationException($"Failed to register block '{id}'. No free IDs or invalid parameters."); @@ -67,6 +81,140 @@ public static class BlockRegistry return new RegisteredBlock(id, numericId); } + public static RegisteredBlock Register(Identifier id, Block managedBlock, BlockProperties properties) + { + if (managedBlock is SlabBlock) + return RegisterSlab(id, properties); + + if (managedBlock is FallingBlock) + return RegisterFalling(id, managedBlock, properties); + + int numericId = NativeInterop.native_register_managed_block( + id.ToString(), + (int)properties.MaterialValue, + properties.HardnessValue, + properties.ResistanceValue, + (int)properties.SoundValue, + properties.IconValue, + properties.LightEmissionValue, + properties.LightBlockValue, + properties.NameValue ?? "", + properties.RequiredHarvestLevelValue, + (int)properties.RequiredToolValue, + properties.AcceptsRedstonePowerValue ? 1 : 0); + + if (numericId < 0) + throw new InvalidOperationException($"Failed to register managed block '{id}'."); + + if (properties.CreativeTabValue != CreativeTab.None) + { + NativeInterop.native_add_to_creative(numericId, 1, 0, (int)properties.CreativeTabValue); + } + + ManagedBlockDispatcher.RegisterBlock(id, numericId, managedBlock); + + int dropNumericId = -1; + if (managedBlock.DropAsBlockId is Identifier dropId) + dropNumericId = IdHelper.GetBlockNumericId(dropId); + + int cloneNumericId = -1; + if (managedBlock.CloneAsBlockId is Identifier cloneId) + cloneNumericId = IdHelper.GetBlockNumericId(cloneId); + NativeInterop.native_configure_managed_block(numericId, dropNumericId, cloneNumericId); + + lock (s_lock) + { + s_idByNumeric[numericId] = id; + } + + return new RegisteredBlock(id, numericId); + } + + public static RegisteredBlock RegisterFalling(Identifier id, BlockProperties properties) + => RegisterFalling(id, null, properties); + + private static RegisteredBlock RegisterFalling(Identifier id, Block? managedBlock, BlockProperties properties) + { + int numericId = NativeInterop.native_register_falling_block( + id.ToString(), + (int)properties.MaterialValue, + properties.HardnessValue, + properties.ResistanceValue, + (int)properties.SoundValue, + properties.IconValue, + properties.LightEmissionValue, + properties.LightBlockValue, + properties.NameValue ?? "", + properties.RequiredHarvestLevelValue, + (int)properties.RequiredToolValue, + properties.AcceptsRedstonePowerValue ? 1 : 0); + + if (numericId < 0) + throw new InvalidOperationException($"Failed to register falling block '{id}'."); + + if (properties.CreativeTabValue != CreativeTab.None) + { + NativeInterop.native_add_to_creative(numericId, 1, 0, (int)properties.CreativeTabValue); + } + + if (managedBlock != null) + { + ManagedBlockDispatcher.RegisterBlock(id, numericId, managedBlock); + + int dropNumericId = -1; + if (managedBlock.DropAsBlockId is Identifier dropId) + dropNumericId = IdHelper.GetBlockNumericId(dropId); + + int cloneNumericId = -1; + if (managedBlock.CloneAsBlockId is Identifier cloneId) + cloneNumericId = IdHelper.GetBlockNumericId(cloneId); + + NativeInterop.native_configure_managed_block(numericId, dropNumericId, cloneNumericId); + } + + lock (s_lock) + { + s_idByNumeric[numericId] = id; + } + return new RegisteredBlock(id, numericId); + } + + public static RegisteredSlabBlock RegisterSlab(Identifier id, BlockProperties properties) + { + Identifier doubleId = new($"{id}_double"); + int numericId = NativeInterop.native_register_slab_block( + id.ToString(), + (int)properties.MaterialValue, + properties.HardnessValue, + properties.ResistanceValue, + (int)properties.SoundValue, + properties.IconValue, + properties.LightEmissionValue, + properties.LightBlockValue, + properties.NameValue ?? "", + properties.RequiredHarvestLevelValue, + (int)properties.RequiredToolValue, + properties.AcceptsRedstonePowerValue ? 1 : 0, + out int doubleNumericId); + + if (numericId < 0) + throw new InvalidOperationException($"Failed to register slab block '{id}'."); + if (doubleNumericId < 0) + throw new InvalidOperationException($"Failed to resolve generated slab pair '{doubleId}'."); + + if (properties.CreativeTabValue != CreativeTab.None) + { + NativeInterop.native_add_to_creative(numericId, 1, 0, (int)properties.CreativeTabValue); + } + + lock (s_lock) + { + s_idByNumeric[numericId] = id; + s_idByNumeric[doubleNumericId] = doubleId; + } + + return new RegisteredSlabBlock(id, doubleId, numericId, doubleNumericId); + } internal static bool TryGetIdentifier(int numericId, out Identifier id) { lock (s_lock) diff --git a/WeaveLoader.API/Block/CustomBlock.cs b/WeaveLoader.API/Block/CustomBlock.cs new file mode 100644 index 0000000..2c49757 --- /dev/null +++ b/WeaveLoader.API/Block/CustomBlock.cs @@ -0,0 +1,226 @@ +using System.Runtime.InteropServices; + +namespace WeaveLoader.API.Block; + +public abstract class Block +{ + public Identifier? Id { get; internal set; } + public int NumericId { get; internal set; } = -1; + + public Identifier? DropAsBlockId { get; init; } + public Identifier? CloneAsBlockId { get; init; } + + public virtual void OnPlace(BlockUpdateContext context) + { + } + + public virtual void OnNeighborChanged(BlockNeighborChangedContext context) + { + } + + public virtual void OnScheduledTick(BlockTickContext context) + { + } +} + +public class FallingBlock : Block +{ +} + +public class SlabBlock : Block +{ +} + +public readonly struct BlockUpdateContext +{ + public int BlockId { get; } + public bool IsClientSide { get; } + public nint NativeLevelPtr { get; } + public int X { get; } + public int Y { get; } + public int Z { get; } + + internal BlockUpdateContext(int blockId, bool isClientSide, nint nativeLevelPtr, int x, int y, int z) + { + BlockId = blockId; + IsClientSide = isClientSide; + NativeLevelPtr = nativeLevelPtr; + X = x; + Y = y; + Z = z; + } + + public bool HasNeighborSignal() + { + if (NativeLevelPtr == 0) + return false; + + return NativeInterop.native_level_has_neighbor_signal(NativeLevelPtr, X, Y, Z) != 0; + } + + public bool SetBlock(Identifier id, int data = 0, int flags = 2) + { + int numericId = IdHelper.GetBlockNumericId(id); + if (numericId < 0) + return false; + + return SetBlock(numericId, data, flags); + } + + public bool SetBlock(int numericBlockId, int data = 0, int flags = 2) + { + if (NativeLevelPtr == 0 || numericBlockId < 0) + return false; + + return NativeInterop.native_level_set_tile(NativeLevelPtr, X, Y, Z, numericBlockId, data, flags) != 0; + } + + public bool ScheduleTick(int delay) + { + if (NativeLevelPtr == 0 || delay < 0) + return false; + + return NativeInterop.native_level_schedule_tick(NativeLevelPtr, X, Y, Z, BlockId, delay) != 0; + } + + public int GetBlockId(int offsetX = 0, int offsetY = 0, int offsetZ = 0) + { + if (NativeLevelPtr == 0) + return -1; + + return NativeInterop.native_level_get_tile(NativeLevelPtr, X + offsetX, Y + offsetY, Z + offsetZ); + } +} + +public readonly struct BlockNeighborChangedContext +{ + public BlockUpdateContext Block { get; } + public int NeighborBlockId { get; } + + internal BlockNeighborChangedContext(BlockUpdateContext block, int neighborBlockId) + { + Block = block; + NeighborBlockId = neighborBlockId; + } +} + +public readonly struct BlockTickContext +{ + public BlockUpdateContext Block { get; } + + internal BlockTickContext(BlockUpdateContext block) + { + Block = block; + } +} + +[StructLayout(LayoutKind.Sequential)] +internal struct BlockUpdateNativeArgs +{ + public int BlockId; + public int IsClientSide; + public nint LevelPtr; + public int X; + public int Y; + public int Z; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct BlockNeighborChangedNativeArgs +{ + public BlockUpdateNativeArgs Block; + public int NeighborBlockId; +} + +internal static class ManagedBlockDispatcher +{ + private static readonly object s_lock = new(); + private static readonly Dictionary s_blocks = new(); + + internal static void RegisterBlock(Identifier id, int numericId, Block block) + { + block.Id = id; + block.NumericId = numericId; + + lock (s_lock) + { + s_blocks[numericId] = block; + } + } + + internal static int HandlePlace(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockUpdateNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.BlockId, out block); + } + + if (block == null) + return 0; + + block.OnPlace(new BlockUpdateContext( + nativeArgs.BlockId, + nativeArgs.IsClientSide != 0, + nativeArgs.LevelPtr, + nativeArgs.X, + nativeArgs.Y, + nativeArgs.Z)); + return 1; + } + + internal static int HandleNeighborChanged(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockNeighborChangedNativeArgs 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.OnNeighborChanged(new BlockNeighborChangedContext(update, nativeArgs.NeighborBlockId)); + return 1; + } + + internal static int HandleScheduledTick(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockUpdateNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.BlockId, out block); + } + + if (block == null) + return 0; + + block.OnScheduledTick(new BlockTickContext(new BlockUpdateContext( + nativeArgs.BlockId, + nativeArgs.IsClientSide != 0, + nativeArgs.LevelPtr, + nativeArgs.X, + nativeArgs.Y, + nativeArgs.Z))); + return 1; + } +} diff --git a/WeaveLoader.API/NativeInterop.cs b/WeaveLoader.API/NativeInterop.cs index d09cd0d..4fcfd07 100644 --- a/WeaveLoader.API/NativeInterop.cs +++ b/WeaveLoader.API/NativeInterop.cs @@ -22,7 +22,54 @@ internal static class NativeInterop int lightBlock, string displayName, int requiredHarvestLevel, - int requiredTool); + int requiredTool, + int acceptsRedstonePower); + + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + internal static extern int native_register_managed_block( + string namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + string iconName, + float lightEmission, + int lightBlock, + string displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower); + + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + internal static extern int native_register_falling_block( + string namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + string iconName, + float lightEmission, + int lightBlock, + string displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower); + + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + internal static extern int native_register_slab_block( + string namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + string iconName, + float lightEmission, + int lightBlock, + string displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower, + out int doubleNumericBlockId); [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern int native_register_item( @@ -78,6 +125,12 @@ internal static class NativeInterop int harvestLevel, float destroySpeed); + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern void native_configure_managed_block( + int numericBlockId, + int dropNumericBlockId, + int cloneNumericBlockId); + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int native_configure_custom_tool_item( int numericItemId, @@ -145,6 +198,18 @@ internal static class NativeInterop double spawnForward, double spawnUp); + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int native_level_has_neighbor_signal(nint levelPtr, int x, int y, int z); + + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int native_level_set_tile(nint levelPtr, int x, int y, int z, int blockId, int data, int flags); + + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int native_level_schedule_tick(nint levelPtr, int x, int y, int z, int blockId, int delay); + + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int native_level_get_tile(nint levelPtr, int x, int y, int z); + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern void native_subscribe_event(string eventName, IntPtr managedFnPtr); diff --git a/WeaveLoader.API/Registry.cs b/WeaveLoader.API/Registry.cs index a2c0807..407047a 100644 --- a/WeaveLoader.API/Registry.cs +++ b/WeaveLoader.API/Registry.cs @@ -17,6 +17,15 @@ public static class Registry { public static RegisteredBlock Register(Identifier id, BlockProperties properties) => BlockRegistry.Register(id, properties); + + public static RegisteredBlock Register(Identifier id, WeaveLoader.API.Block.Block block, BlockProperties properties) + => BlockRegistry.Register(id, block, properties); + + public static RegisteredBlock RegisterFalling(Identifier id, BlockProperties properties) + => BlockRegistry.Register(id, new FallingBlock(), properties); + + public static RegisteredSlabBlock RegisterSlab(Identifier id, BlockProperties properties) + => (RegisteredSlabBlock)BlockRegistry.Register(id, new SlabBlock(), properties); } /// Item registration. Call Register() with a namespaced ID and ItemProperties. diff --git a/WeaveLoader.Core/WeaveLoaderCore.cs b/WeaveLoader.Core/WeaveLoaderCore.cs index be06be7..db23e6d 100644 --- a/WeaveLoader.Core/WeaveLoaderCore.cs +++ b/WeaveLoader.Core/WeaveLoaderCore.cs @@ -1,6 +1,7 @@ using System.Runtime.InteropServices; using WeaveLoader.API; using WeaveLoader.API.Events; +using WeaveLoader.API.Block; using WeaveLoader.API.Item; namespace WeaveLoader.Core; @@ -121,6 +122,45 @@ public static class WeaveLoaderCore } } + public static int OnBlockPlace(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandlePlace(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockPlace EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnBlockNeighborChanged(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandleNeighborChanged(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockNeighborChanged EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnBlockTick(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandleScheduledTick(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockTick EXCEPTION: {ex}"); + return 0; + } + } + [StructLayout(LayoutKind.Sequential)] private struct WorldLoadedNativeArgs { diff --git a/WeaveLoader.Launcher/WeaveLoader.Launcher.csproj b/WeaveLoader.Launcher/WeaveLoader.Launcher.csproj index ba7098b..966a738 100644 --- a/WeaveLoader.Launcher/WeaveLoader.Launcher.csproj +++ b/WeaveLoader.Launcher/WeaveLoader.Launcher.csproj @@ -12,15 +12,16 @@ ..\build false false + $(MSBuildThisFileDirectory)..\WeaveLoaderRuntime\build\$(Configuration) - - diff --git a/WeaveLoaderRuntime/CMakeLists.txt b/WeaveLoaderRuntime/CMakeLists.txt index d7a073c..d55a874 100644 --- a/WeaveLoaderRuntime/CMakeLists.txt +++ b/WeaveLoaderRuntime/CMakeLists.txt @@ -90,6 +90,8 @@ add_library(WeaveLoaderRuntime SHARED src/CustomPickaxeRegistry.cpp src/CustomToolMaterialRegistry.cpp src/CustomBlockRegistry.cpp + src/CustomSlabRegistry.cpp + src/ManagedBlockRegistry.cpp src/CreativeInventory.cpp src/FurnaceRecipeRegistry.cpp src/MainMenuOverlay.cpp @@ -120,6 +122,7 @@ endif() target_compile_definitions(WeaveLoaderRuntime PRIVATE WIN32_LEAN_AND_MEAN _CRT_SECURE_NO_WARNINGS + $<$:WEAVELOADER_DEBUG_BUILD> ) if(MSVC) diff --git a/WeaveLoaderRuntime/src/CrashHandler.cpp b/WeaveLoaderRuntime/src/CrashHandler.cpp index cbf6fb2..a97731c 100644 --- a/WeaveLoaderRuntime/src/CrashHandler.cpp +++ b/WeaveLoaderRuntime/src/CrashHandler.cpp @@ -372,14 +372,16 @@ static LONG WINAPI VectoredHandler(EXCEPTION_POINTERS* ep) return EXCEPTION_CONTINUE_SEARCH; // TODO: Remove this suppression once the animated texture pack fault is fixed - // at the source. For now, a vectored handler sees first-chance exceptions - // before local __try/__except blocks run, and logging those handled faults - // as crashes causes severe log spam and stalls under Windows. - // - // Keep vectored reporting only for fail-fast/noncontinuable cases that will - // not normally make it to the top-level unhandled filter. + // at the source. For release builds, keep vectored reporting only for + // fail-fast/noncontinuable cases so handled first-chance faults do not spam + // logs and stall the process. In debug builds, log all fatal exceptions to + // make crash investigation easier. +#ifdef _DEBUG + WriteCrashReport("VectoredExceptionHandler", ep->ExceptionRecord, ep->ContextRecord); +#else if (ShouldWriteVectoredReport(ep->ExceptionRecord)) WriteCrashReport("VectoredExceptionHandler", ep->ExceptionRecord, ep->ContextRecord); +#endif return EXCEPTION_CONTINUE_SEARCH; } diff --git a/WeaveLoaderRuntime/src/CustomBlockRegistry.cpp b/WeaveLoaderRuntime/src/CustomBlockRegistry.cpp index 0e668af..fbc0cd4 100644 --- a/WeaveLoaderRuntime/src/CustomBlockRegistry.cpp +++ b/WeaveLoaderRuntime/src/CustomBlockRegistry.cpp @@ -8,11 +8,12 @@ namespace namespace CustomBlockRegistry { - void Register(int blockId, int requiredHarvestLevel, int requiredTool) + void Register(int blockId, int requiredHarvestLevel, int requiredTool, bool acceptsRedstonePower) { Definition def; def.requiredHarvestLevel = requiredHarvestLevel; def.requiredTool = static_cast(requiredTool); + def.acceptsRedstonePower = acceptsRedstonePower; g_definitions[blockId] = def; } diff --git a/WeaveLoaderRuntime/src/CustomBlockRegistry.h b/WeaveLoaderRuntime/src/CustomBlockRegistry.h index de35d0a..fd3941d 100644 --- a/WeaveLoaderRuntime/src/CustomBlockRegistry.h +++ b/WeaveLoaderRuntime/src/CustomBlockRegistry.h @@ -14,8 +14,9 @@ namespace CustomBlockRegistry { int requiredHarvestLevel = -1; ToolType requiredTool = ToolType::None; + bool acceptsRedstonePower = false; }; - void Register(int blockId, int requiredHarvestLevel, int requiredTool); + void Register(int blockId, int requiredHarvestLevel, int requiredTool, bool acceptsRedstonePower); const Definition* Find(int blockId); } diff --git a/WeaveLoaderRuntime/src/CustomSlabRegistry.cpp b/WeaveLoaderRuntime/src/CustomSlabRegistry.cpp new file mode 100644 index 0000000..ca9148a --- /dev/null +++ b/WeaveLoaderRuntime/src/CustomSlabRegistry.cpp @@ -0,0 +1,50 @@ +#include "CustomSlabRegistry.h" + +#include +#include + +namespace +{ + std::unordered_map g_definitions; +} + +namespace CustomSlabRegistry +{ + void Register(int halfBlockId, int fullBlockId, int descriptionId, bool woodFamily, const wchar_t* iconName) + { + Definition def; + def.halfBlockId = halfBlockId; + def.fullBlockId = fullBlockId; + def.descriptionId = descriptionId; + def.woodFamily = woodFamily; + if (iconName) + def.iconName = iconName; + g_definitions[halfBlockId] = def; + g_definitions[fullBlockId] = def; + } + + const Definition* Find(int blockId) + { + const auto it = g_definitions.find(blockId); + if (it == g_definitions.end()) + return nullptr; + return &it->second; + } + + void ForEachUnique(void (*fn)(const Definition&, void*), void* userData) + { + if (!fn) + return; + + std::unordered_set seenHalfIds; + for (const auto& entry : g_definitions) + { + const Definition& def = entry.second; + if (def.halfBlockId < 0) + continue; + if (!seenHalfIds.insert(def.halfBlockId).second) + continue; + fn(def, userData); + } + } +} diff --git a/WeaveLoaderRuntime/src/CustomSlabRegistry.h b/WeaveLoaderRuntime/src/CustomSlabRegistry.h new file mode 100644 index 0000000..6da4583 --- /dev/null +++ b/WeaveLoaderRuntime/src/CustomSlabRegistry.h @@ -0,0 +1,18 @@ +#pragma once +#include + +namespace CustomSlabRegistry +{ + struct Definition + { + int halfBlockId = -1; + int fullBlockId = -1; + int descriptionId = -1; + bool woodFamily = false; + std::wstring iconName; + }; + + void Register(int halfBlockId, int fullBlockId, int descriptionId, bool woodFamily, const wchar_t* iconName); + const Definition* Find(int blockId); + void ForEachUnique(void (*fn)(const Definition&, void*), void* userData); +} diff --git a/WeaveLoaderRuntime/src/DotNetHost.cpp b/WeaveLoaderRuntime/src/DotNetHost.cpp index dc3b765..b87ff5c 100644 --- a/WeaveLoaderRuntime/src/DotNetHost.cpp +++ b/WeaveLoaderRuntime/src/DotNetHost.cpp @@ -24,6 +24,9 @@ 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_BlockOnPlace = nullptr; +static managed_entry_fn fn_BlockNeighborChanged = nullptr; +static managed_entry_fn fn_BlockTick = nullptr; static managed_entry_fn fn_EntitySummoned = nullptr; static bool LoadHostfxr() @@ -182,6 +185,9 @@ bool DotNetHost::Initialize() ok &= resolve(L"Shutdown", &fn_Shutdown); ok &= resolve(L"OnItemMineBlock", &fn_ItemMineBlock); ok &= resolve(L"OnItemUse", &fn_ItemUse); + ok &= resolve(L"OnBlockPlace", &fn_BlockOnPlace); + ok &= resolve(L"OnBlockNeighborChanged", &fn_BlockNeighborChanged); + ok &= resolve(L"OnBlockTick", &fn_BlockTick); ok &= resolve(L"OnEntitySummoned", &fn_EntitySummoned); if (!ok) @@ -247,6 +253,27 @@ int DotNetHost::CallItemUse(const void* args, int sizeBytes) return fn_ItemUse(const_cast(args), sizeBytes); } +int DotNetHost::CallBlockOnPlace(const void* args, int sizeBytes) +{ + if (!fn_BlockOnPlace || !args || sizeBytes <= 0) + return 0; + return fn_BlockOnPlace(const_cast(args), sizeBytes); +} + +int DotNetHost::CallBlockNeighborChanged(const void* args, int sizeBytes) +{ + if (!fn_BlockNeighborChanged || !args || sizeBytes <= 0) + return 0; + return fn_BlockNeighborChanged(const_cast(args), sizeBytes); +} + +int DotNetHost::CallBlockTick(const void* args, int sizeBytes) +{ + if (!fn_BlockTick || !args || sizeBytes <= 0) + return 0; + return fn_BlockTick(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 e756de5..7afca6c 100644 --- a/WeaveLoaderRuntime/src/DotNetHost.h +++ b/WeaveLoaderRuntime/src/DotNetHost.h @@ -17,5 +17,8 @@ namespace DotNetHost void CallShutdown(); int CallItemMineBlock(const void* args, int sizeBytes); int CallItemUse(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); void CallEntitySummoned(int entityNumericId, float x, float y, float z); } diff --git a/WeaveLoaderRuntime/src/GameHooks.cpp b/WeaveLoaderRuntime/src/GameHooks.cpp index 268dc8b..73f251d 100644 --- a/WeaveLoaderRuntime/src/GameHooks.cpp +++ b/WeaveLoaderRuntime/src/GameHooks.cpp @@ -7,6 +7,8 @@ #include "CustomPickaxeRegistry.h" #include "CustomToolMaterialRegistry.h" #include "CustomBlockRegistry.h" +#include "ManagedBlockRegistry.h" +#include "CustomSlabRegistry.h" #include "LogUtil.h" #include #include @@ -47,6 +49,28 @@ namespace GameHooks PickaxeCanDestroySpecial_fn Original_PickaxeItemCanDestroySpecial = nullptr; PickaxeGetDestroySpeed_fn Original_ShovelItemGetDestroySpeed = nullptr; PickaxeCanDestroySpecial_fn Original_ShovelItemCanDestroySpecial = nullptr; + TileOnPlace_fn Original_TileOnPlace = nullptr; + TileNeighborChanged_fn Original_TileNeighborChanged = nullptr; + TileTick_fn Original_TileTick = nullptr; + LevelSetTileAndDataDispatch_fn Original_LevelSetTileAndData = nullptr; + LevelSetDataDispatch_fn Original_LevelSetData = nullptr; + LevelUpdateNeighborsAtDispatch_fn Original_LevelUpdateNeighborsAt = nullptr; + ServerLevelTickPendingTicks_fn Original_ServerLevelTickPendingTicks = nullptr; + TileGetResource_fn Original_TileGetResource = nullptr; + TileCloneTileId_fn Original_TileCloneTileId = nullptr; + TileGetTextureFaceData_fn Original_StoneSlabGetTexture = nullptr; + TileGetTextureFaceData_fn Original_WoodSlabGetTexture = nullptr; + TileGetResource_fn Original_StoneSlabGetResource = nullptr; + TileGetResource_fn Original_WoodSlabGetResource = nullptr; + TileGetDescriptionId_fn Original_StoneSlabGetDescriptionId = nullptr; + TileGetDescriptionId_fn Original_WoodSlabGetDescriptionId = nullptr; + TileGetAuxName_fn Original_StoneSlabGetAuxName = nullptr; + TileGetAuxName_fn Original_WoodSlabGetAuxName = nullptr; + TileRegisterIcons_fn Original_StoneSlabRegisterIcons = nullptr; + TileRegisterIcons_fn Original_WoodSlabRegisterIcons = nullptr; + StoneSlabItemGetIcon_fn Original_StoneSlabItemGetIcon = nullptr; + StoneSlabItemGetDescriptionId_fn Original_StoneSlabItemGetDescriptionId = nullptr; + TileCloneTileId_fn Original_HalfSlabCloneTileId = nullptr; PlayerCanDestroy_fn Original_PlayerCanDestroy = nullptr; GameModeUseItem_fn Original_ServerPlayerGameModeUseItem = nullptr; GameModeUseItem_fn Original_MultiPlayerGameModeUseItem = nullptr; @@ -76,6 +100,7 @@ namespace GameHooks static constexpr ptrdiff_t kLevelIsClientSideOffset = 0x268; static constexpr ptrdiff_t kItemIdOffset = 0x20; static constexpr ptrdiff_t kTileIdOffset = 0x28; + static constexpr ptrdiff_t kTileIconOffset = 0x78; static constexpr ptrdiff_t kEntityXOffset = 0x78; static constexpr ptrdiff_t kEntityYOffset = 0x80; static constexpr ptrdiff_t kEntityZOffset = 0x88; @@ -117,7 +142,24 @@ namespace GameHooks static std::vector s_spawnedEntities; static int s_outOfWorldGuardLogCount = 0; static int s_pendingServerUseItemId = -1; + static LevelGetTile_fn s_levelGetTile = nullptr; + + struct ManagedScheduledTick + { + void* levelPtr; + int x; + int y; + int z; + int blockId; + int remainingTicks; + }; + + static std::vector s_managedScheduledTicks; static ULONGLONG s_pendingServerUseExpiryMs = 0; + static TileGetTextureFaceData_fn s_tileGetTextureFaceData = nullptr; + static bool s_preInitCalled = false; + static bool s_initCalled = false; + static bool s_postInitCalled = false; static void EnsurePageResourcesInitialized() { @@ -196,6 +238,53 @@ namespace GameHooks return (p + bytes) <= end; } + static void* TryReadTileIcon(void* tilePtr) + { + if (!tilePtr) + return nullptr; + + const void* iconSlot = static_cast(tilePtr) + kTileIconOffset; + if (!IsReadableRange(iconSlot, sizeof(void*))) + return nullptr; + + void* iconPtr = *reinterpret_cast(iconSlot); + if (!IsCanonicalUserPtr(iconPtr)) + return nullptr; + + return iconPtr; + } + + static void PatchSingleSlabIcon(const CustomSlabRegistry::Definition& def, void*) + { + if (def.iconName.empty() || !s_tileTilesArray || !IsReadableRange(s_tileTilesArray, sizeof(void*))) + return; + + void* modIcon = ModAtlas::LookupModIcon(def.iconName); + if (!modIcon) + return; + + const void* arrayPtr = *reinterpret_cast(s_tileTilesArray); + if (!arrayPtr || !IsReadableRange(arrayPtr, sizeof(void*) * 4096)) + return; + + auto* tiles = reinterpret_cast(const_cast(arrayPtr)); + const int ids[] = { def.halfBlockId, def.fullBlockId }; + for (int blockId : ids) + { + if (blockId < 0 || blockId >= 4096) + continue; + void* tilePtr = const_cast(tiles[blockId]); + if (!tilePtr || !IsReadableRange(static_cast(tilePtr) + kTileIconOffset, sizeof(void*))) + continue; + *reinterpret_cast(static_cast(tilePtr) + kTileIconOffset) = modIcon; + } + } + + static void PatchCustomSlabIcons() + { + CustomSlabRegistry::ForEachUnique(&PatchSingleSlabIcon, nullptr); + } + static std::wstring BuildVirtualAtlasPath(int atlasType, int page) { std::wstring base = L"/modloader/"; @@ -423,6 +512,39 @@ namespace GameHooks s_entitySetPos = reinterpret_cast(entitySetPos); } + void SetBlockHelperSymbols(void* tileGetTextureFaceData) + { + s_tileGetTextureFaceData = reinterpret_cast(tileGetTextureFaceData); + } + + void SetManagedBlockDispatchSymbols(void* levelGetTile) + { + s_levelGetTile = reinterpret_cast(levelGetTile); + } + + void EnqueueManagedBlockTick(void* levelPtr, int x, int y, int z, int blockId, int delay) + { + if (!levelPtr || blockId < 0 || !ManagedBlockRegistry::IsManaged(blockId)) + return; + + const int normalizedDelay = delay > 0 ? delay : 1; + for (ManagedScheduledTick& tick : s_managedScheduledTicks) + { + if (tick.levelPtr == levelPtr && + tick.x == x && + tick.y == y && + tick.z == z && + tick.blockId == blockId) + { + if (normalizedDelay < tick.remainingTicks) + tick.remainingTicks = normalizedDelay; + return; + } + } + + s_managedScheduledTicks.push_back({ levelPtr, x, y, z, blockId, normalizedDelay }); + } + static bool IsInventoryObjectPtr(void* objectPtr) { if (!objectPtr || !s_inventoryVtable || !IsReadableRange(objectPtr, sizeof(void*))) @@ -851,6 +973,344 @@ namespace GameHooks return nullptr; } + static int TryReadTileId(void* tilePtr) + { + if (!tilePtr || !IsReadableRange(static_cast(tilePtr) + kTileIdOffset, sizeof(int))) + return -1; + return *reinterpret_cast(static_cast(tilePtr) + kTileIdOffset); + } + + static void DispatchManagedBlockUpdate(void* tilePtr, void* level, int x, int y, int z, int eventKind, int neighborBlockId) + { + const int blockId = TryReadTileId(tilePtr); + 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; + + struct BlockUpdateNativeArgs + { + int blockId; + int isClientSide; + void* levelPtr; + int x; + int y; + int z; + }; + struct BlockNeighborChangedNativeArgs + { + BlockUpdateNativeArgs block; + int neighborBlockId; + }; + + if (eventKind == 0) + { + BlockUpdateNativeArgs args{ blockId, isClientSide, level, x, y, z }; + DotNetHost::CallBlockOnPlace(&args, sizeof(args)); + } + else if (eventKind == 1) + { + BlockNeighborChangedNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.neighborBlockId = neighborBlockId; + DotNetHost::CallBlockNeighborChanged(&args, sizeof(args)); + } + else if (eventKind == 2) + { + BlockUpdateNativeArgs args{ blockId, isClientSide, level, x, y, z }; + DotNetHost::CallBlockTick(&args, sizeof(args)); + } + } + + static void DispatchManagedBlockById(int blockId, void* level, int x, int y, int z, int eventKind, int neighborBlockId) + { + 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; + + struct BlockUpdateNativeArgs + { + int blockId; + int isClientSide; + void* levelPtr; + int x; + int y; + int z; + }; + struct BlockNeighborChangedNativeArgs + { + BlockUpdateNativeArgs block; + int neighborBlockId; + }; + + if (eventKind == 0) + { + BlockUpdateNativeArgs args{ blockId, isClientSide, level, x, y, z }; + DotNetHost::CallBlockOnPlace(&args, sizeof(args)); + } + else if (eventKind == 1) + { + BlockNeighborChangedNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.neighborBlockId = neighborBlockId; + DotNetHost::CallBlockNeighborChanged(&args, sizeof(args)); + } + else if (eventKind == 2) + { + BlockUpdateNativeArgs args{ blockId, isClientSide, level, x, y, z }; + DotNetHost::CallBlockTick(&args, sizeof(args)); + } + } + + void __fastcall Hooked_TileOnPlace(void* thisPtr, void* level, int x, int y, int z) + { + if (Original_TileOnPlace) + Original_TileOnPlace(thisPtr, level, x, y, z); + DispatchManagedBlockUpdate(thisPtr, level, x, y, z, 0, 0); + } + + void __fastcall Hooked_TileNeighborChanged(void* thisPtr, void* level, int x, int y, int z, int type) + { + if (Original_TileNeighborChanged) + Original_TileNeighborChanged(thisPtr, level, x, y, z, type); + DispatchManagedBlockUpdate(thisPtr, level, x, y, z, 1, type); + } + + void __fastcall Hooked_TileTick(void* thisPtr, void* level, int x, int y, int z, void* random) + { + if (Original_TileTick) + Original_TileTick(thisPtr, level, x, y, z, random); + DispatchManagedBlockUpdate(thisPtr, level, x, y, z, 2, 0); + } + + bool __fastcall Hooked_LevelSetTileAndData(void* thisPtr, int x, int y, int z, int tile, int data, int updateFlags) + { + const bool result = Original_LevelSetTileAndData + ? Original_LevelSetTileAndData(thisPtr, x, y, z, tile, data, updateFlags) + : false; + + if (result && tile > 0) + DispatchManagedBlockById(tile, thisPtr, x, y, z, 0, 0); + + return result; + } + + bool __fastcall Hooked_LevelSetData(void* thisPtr, int x, int y, int z, int data, int updateFlags, bool forceUpdate) + { + const bool result = Original_LevelSetData + ? Original_LevelSetData(thisPtr, x, y, z, data, updateFlags, forceUpdate) + : false; + + return result; + } + + void __fastcall Hooked_LevelUpdateNeighborsAt(void* thisPtr, int x, int y, int z, int type) + { + if (Original_LevelUpdateNeighborsAt) + Original_LevelUpdateNeighborsAt(thisPtr, x, y, z, type); + + if (!s_levelGetTile) + return; + + static const int kNeighborOffsets[6][3] = { + {-1, 0, 0}, {1, 0, 0}, + {0, -1, 0}, {0, 1, 0}, + {0, 0, -1}, {0, 0, 1} + }; + + for (const auto& offset : kNeighborOffsets) + { + const int nx = x + offset[0]; + const int ny = y + offset[1]; + const int nz = z + offset[2]; + const int neighborBlockId = s_levelGetTile(thisPtr, nx, ny, nz); + DispatchManagedBlockById(neighborBlockId, thisPtr, nx, ny, nz, 1, type); + } + } + + bool __fastcall Hooked_ServerLevelTickPendingTicks(void* thisPtr, bool force) + { + const bool originalResult = Original_ServerLevelTickPendingTicks + ? Original_ServerLevelTickPendingTicks(thisPtr, force) + : false; + + bool anyPending = false; + for (auto it = s_managedScheduledTicks.begin(); it != s_managedScheduledTicks.end();) + { + if (it->levelPtr != thisPtr) + { + ++it; + continue; + } + + --it->remainingTicks; + if (it->remainingTicks <= 0) + { + DispatchManagedBlockById(it->blockId, it->levelPtr, it->x, it->y, it->z, 2, 0); + it = s_managedScheduledTicks.erase(it); + } + else + { + anyPending = true; + ++it; + } + } + + return originalResult || anyPending; + } + + int __fastcall Hooked_TileGetResource(void* thisPtr, int data, void* random, int playerBonusLevel) + { + const ManagedBlockRegistry::Definition* def = ManagedBlockRegistry::Find(TryReadTileId(thisPtr)); + if (def && def->dropBlockId >= 0) + return def->dropBlockId; + return Original_TileGetResource ? Original_TileGetResource(thisPtr, data, random, playerBonusLevel) : 0; + } + + int __fastcall Hooked_TileCloneTileId(void* thisPtr, void* level, int x, int y, int z) + { + const ManagedBlockRegistry::Definition* def = ManagedBlockRegistry::Find(TryReadTileId(thisPtr)); + if (def && def->cloneBlockId >= 0) + return def->cloneBlockId; + return Original_TileCloneTileId ? Original_TileCloneTileId(thisPtr, level, x, y, z) : TryReadTileId(thisPtr); + } + + void* __fastcall Hooked_StoneSlabGetTexture(void* thisPtr, int face, int data) + { + if (CustomSlabRegistry::Find(TryReadTileId(thisPtr))) + { + if (void* iconPtr = TryReadTileIcon(thisPtr)) + return iconPtr; + } + return Original_StoneSlabGetTexture ? Original_StoneSlabGetTexture(thisPtr, face, data) : nullptr; + } + + void* __fastcall Hooked_WoodSlabGetTexture(void* thisPtr, int face, int data) + { + if (CustomSlabRegistry::Find(TryReadTileId(thisPtr))) + { + if (void* iconPtr = TryReadTileIcon(thisPtr)) + return iconPtr; + } + return Original_WoodSlabGetTexture ? Original_WoodSlabGetTexture(thisPtr, face, data) : nullptr; + } + + int __fastcall Hooked_StoneSlabGetResource(void* thisPtr, int data, void* random, int playerBonusLevel) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def) + return def->halfBlockId; + return Original_StoneSlabGetResource ? Original_StoneSlabGetResource(thisPtr, data, random, playerBonusLevel) : 0; + } + + int __fastcall Hooked_WoodSlabGetResource(void* thisPtr, int data, void* random, int playerBonusLevel) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def) + return def->halfBlockId; + return Original_WoodSlabGetResource ? Original_WoodSlabGetResource(thisPtr, data, random, playerBonusLevel) : 0; + } + + unsigned int __fastcall Hooked_StoneSlabGetDescriptionId(void* thisPtr, int data) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def && def->descriptionId >= 0) + return static_cast(def->descriptionId); + return Original_StoneSlabGetDescriptionId ? Original_StoneSlabGetDescriptionId(thisPtr, data) : 0; + } + + unsigned int __fastcall Hooked_WoodSlabGetDescriptionId(void* thisPtr, int data) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def && def->descriptionId >= 0) + return static_cast(def->descriptionId); + return Original_WoodSlabGetDescriptionId ? Original_WoodSlabGetDescriptionId(thisPtr, data) : 0; + } + + int __fastcall Hooked_StoneSlabGetAuxName(void* thisPtr, int auxValue) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def && def->descriptionId >= 0) + return def->descriptionId; + return Original_StoneSlabGetAuxName ? Original_StoneSlabGetAuxName(thisPtr, auxValue) : 0; + } + + int __fastcall Hooked_WoodSlabGetAuxName(void* thisPtr, int auxValue) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def && def->descriptionId >= 0) + return def->descriptionId; + return Original_WoodSlabGetAuxName ? Original_WoodSlabGetAuxName(thisPtr, auxValue) : 0; + } + + void __fastcall Hooked_StoneSlabRegisterIcons(void* thisPtr, void* iconRegister) + { + if (Original_StoneSlabRegisterIcons) + Original_StoneSlabRegisterIcons(thisPtr, iconRegister); + } + + void __fastcall Hooked_WoodSlabRegisterIcons(void* thisPtr, void* iconRegister) + { + if (Original_WoodSlabRegisterIcons) + Original_WoodSlabRegisterIcons(thisPtr, iconRegister); + } + + void* __fastcall Hooked_StoneSlabItemGetIcon(void* thisPtr, int auxValue) + { + if (thisPtr && IsReadableRange(static_cast(thisPtr) + kItemIdOffset, sizeof(int))) + { + const int itemId = *reinterpret_cast(static_cast(thisPtr) + kItemIdOffset); + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(itemId); + if (def && s_tileTilesArray && IsReadableRange(s_tileTilesArray, sizeof(void*))) + { + const void* arrayPtr = *reinterpret_cast(s_tileTilesArray); + if (arrayPtr && IsReadableRange(arrayPtr, sizeof(void*) * 4096)) + { + auto* tiles = reinterpret_cast(const_cast(arrayPtr)); + if (def->halfBlockId >= 0 && def->halfBlockId < 4096) + { + void* halfTile = const_cast(tiles[def->halfBlockId]); + void* iconPtr = TryReadTileIcon(halfTile); + if (iconPtr) + return iconPtr; + } + } + } + } + + return Original_StoneSlabItemGetIcon + ? Original_StoneSlabItemGetIcon(thisPtr, auxValue) + : nullptr; + } + + unsigned int __fastcall Hooked_StoneSlabItemGetDescriptionId(void* thisPtr, void* itemInstanceSharedPtr) + { + if (thisPtr && IsReadableRange(static_cast(thisPtr) + kItemIdOffset, sizeof(int))) + { + const int itemId = *reinterpret_cast(static_cast(thisPtr) + kItemIdOffset); + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(itemId); + if (def && def->descriptionId >= 0) + return static_cast(def->descriptionId); + } + + return Original_StoneSlabItemGetDescriptionId + ? Original_StoneSlabItemGetDescriptionId(thisPtr, itemInstanceSharedPtr) + : 0; + } + + int __fastcall Hooked_HalfSlabCloneTileId(void* thisPtr, void* level, int x, int y, int z) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def) + return def->halfBlockId; + return Original_HalfSlabCloneTileId ? Original_HalfSlabCloneTileId(thisPtr, level, x, y, z) : TryReadTileId(thisPtr); + } + void __fastcall Hooked_LoadUVs(void* thisPtr) { LogUtil::Log("[WeaveLoader] Hooked_LoadUVs: ENTER (textureMap=%p)", thisPtr); @@ -858,6 +1318,7 @@ namespace GameHooks Original_LoadUVs(thisPtr); LogUtil::Log("[WeaveLoader] Hooked_LoadUVs: original returned, creating mod icons"); ModAtlas::CreateModIcons(thisPtr); + PatchCustomSlabIcons(); LogUtil::Log("[WeaveLoader] Hooked_LoadUVs: DONE"); } @@ -1585,11 +2046,13 @@ namespace GameHooks { LogUtil::Log("[WeaveLoader] Hook: RunStaticCtors -- calling PreInit"); DotNetHost::CallPreInit(); + s_preInitCalled = true; Original_RunStaticCtors(); LogUtil::Log("[WeaveLoader] Hook: RunStaticCtors complete -- calling Init"); DotNetHost::CallInit(); + s_initCalled = true; // Inject mod strings directly into the game's StringTable vector. // This is necessary because the compiler inlines GetString at call @@ -1653,8 +2116,26 @@ namespace GameHooks // fully populated. Copy it to our mod icons so getSourceHeight() works. ModAtlas::FixupModIcons(); - LogUtil::Log("[WeaveLoader] Hook: Minecraft::init complete -- calling PostInit"); - DotNetHost::CallPostInit(); + if (!s_preInitCalled) + { + LogUtil::Log("[WeaveLoader] Hook: Minecraft::init -- late PreInit fallback"); + DotNetHost::CallPreInit(); + s_preInitCalled = true; + } + if (!s_initCalled) + { + LogUtil::Log("[WeaveLoader] Hook: Minecraft::init -- late Init fallback"); + DotNetHost::CallInit(); + s_initCalled = true; + ModStrings::InjectAllIntoGameTable(); + } + + if (!s_postInitCalled) + { + LogUtil::Log("[WeaveLoader] Hook: Minecraft::init complete -- calling PostInit"); + DotNetHost::CallPostInit(); + s_postInitCalled = true; + } } void __fastcall Hooked_ExitGame(void* thisPtr) diff --git a/WeaveLoaderRuntime/src/GameHooks.h b/WeaveLoaderRuntime/src/GameHooks.h index b8d02ec..b023a67 100644 --- a/WeaveLoaderRuntime/src/GameHooks.h +++ b/WeaveLoaderRuntime/src/GameHooks.h @@ -27,6 +27,22 @@ typedef void (__fastcall *ItemInstanceMineBlock_fn)(void* thisPtr, void* level, 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 *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); +typedef bool (__fastcall *ServerLevelTickPendingTicks_fn)(void* thisPtr, bool force); +typedef int (__fastcall *LevelGetTile_fn)(void* thisPtr, int x, int y, int z); +typedef int (__fastcall *TileGetResource_fn)(void* thisPtr, int data, void* random, int playerBonusLevel); +typedef int (__fastcall *TileCloneTileId_fn)(void* thisPtr, void* level, int x, int y, int z); +typedef void* (__fastcall *TileGetTextureFaceData_fn)(void* thisPtr, int face, int data); +typedef unsigned int (__fastcall *TileGetDescriptionId_fn)(void* thisPtr, int data); +typedef int (__fastcall *TileGetAuxName_fn)(void* thisPtr, int auxValue); +typedef void (__fastcall *TileRegisterIcons_fn)(void* thisPtr, void* iconRegister); +typedef void* (__fastcall *StoneSlabItemGetIcon_fn)(void* thisPtr, int auxValue); +typedef unsigned int (__fastcall *StoneSlabItemGetDescriptionId_fn)(void* thisPtr, void* itemInstanceSharedPtr); typedef bool (__fastcall *PlayerCanDestroy_fn)(void* thisPtr, void* tilePtr); typedef bool (__fastcall *GameModeUseItem_fn)(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly); typedef void (__fastcall *MinecraftSetLevel_fn)(void* thisPtr, void* level, int message, void* forceInsertPlayerSharedPtr, bool doForceStatsSave, bool bPrimaryPlayerSignedOut); @@ -75,6 +91,28 @@ namespace GameHooks extern PickaxeCanDestroySpecial_fn Original_PickaxeItemCanDestroySpecial; extern PickaxeGetDestroySpeed_fn Original_ShovelItemGetDestroySpeed; extern PickaxeCanDestroySpecial_fn Original_ShovelItemCanDestroySpecial; + extern TileOnPlace_fn Original_TileOnPlace; + extern TileNeighborChanged_fn Original_TileNeighborChanged; + extern TileTick_fn Original_TileTick; + extern LevelSetTileAndDataDispatch_fn Original_LevelSetTileAndData; + extern LevelSetDataDispatch_fn Original_LevelSetData; + extern LevelUpdateNeighborsAtDispatch_fn Original_LevelUpdateNeighborsAt; + extern ServerLevelTickPendingTicks_fn Original_ServerLevelTickPendingTicks; + extern TileGetResource_fn Original_TileGetResource; + extern TileCloneTileId_fn Original_TileCloneTileId; + extern TileGetTextureFaceData_fn Original_StoneSlabGetTexture; + extern TileGetTextureFaceData_fn Original_WoodSlabGetTexture; + extern TileGetResource_fn Original_StoneSlabGetResource; + extern TileGetResource_fn Original_WoodSlabGetResource; + extern TileGetDescriptionId_fn Original_StoneSlabGetDescriptionId; + extern TileGetDescriptionId_fn Original_WoodSlabGetDescriptionId; + extern TileGetAuxName_fn Original_StoneSlabGetAuxName; + extern TileGetAuxName_fn Original_WoodSlabGetAuxName; + extern TileRegisterIcons_fn Original_StoneSlabRegisterIcons; + extern TileRegisterIcons_fn Original_WoodSlabRegisterIcons; + extern StoneSlabItemGetIcon_fn Original_StoneSlabItemGetIcon; + extern StoneSlabItemGetDescriptionId_fn Original_StoneSlabItemGetDescriptionId; + extern TileCloneTileId_fn Original_HalfSlabCloneTileId; extern PlayerCanDestroy_fn Original_PlayerCanDestroy; extern GameModeUseItem_fn Original_ServerPlayerGameModeUseItem; extern GameModeUseItem_fn Original_MultiPlayerGameModeUseItem; @@ -115,6 +153,28 @@ namespace GameHooks bool __fastcall Hooked_PickaxeItemCanDestroySpecial(void* thisPtr, void* tilePtr); float __fastcall Hooked_ShovelItemGetDestroySpeed(void* thisPtr, void* itemInstanceSharedPtr, void* tilePtr); bool __fastcall Hooked_ShovelItemCanDestroySpecial(void* thisPtr, void* tilePtr); + 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_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); + bool __fastcall Hooked_ServerLevelTickPendingTicks(void* thisPtr, bool force); + int __fastcall Hooked_TileGetResource(void* thisPtr, int data, void* random, int playerBonusLevel); + int __fastcall Hooked_TileCloneTileId(void* thisPtr, void* level, int x, int y, int z); + void* __fastcall Hooked_StoneSlabGetTexture(void* thisPtr, int face, int data); + void* __fastcall Hooked_WoodSlabGetTexture(void* thisPtr, int face, int data); + int __fastcall Hooked_StoneSlabGetResource(void* thisPtr, int data, void* random, int playerBonusLevel); + int __fastcall Hooked_WoodSlabGetResource(void* thisPtr, int data, void* random, int playerBonusLevel); + unsigned int __fastcall Hooked_StoneSlabGetDescriptionId(void* thisPtr, int data); + unsigned int __fastcall Hooked_WoodSlabGetDescriptionId(void* thisPtr, int data); + int __fastcall Hooked_StoneSlabGetAuxName(void* thisPtr, int auxValue); + int __fastcall Hooked_WoodSlabGetAuxName(void* thisPtr, int auxValue); + void __fastcall Hooked_StoneSlabRegisterIcons(void* thisPtr, void* iconRegister); + void __fastcall Hooked_WoodSlabRegisterIcons(void* thisPtr, void* iconRegister); + void* __fastcall Hooked_StoneSlabItemGetIcon(void* thisPtr, int auxValue); + unsigned int __fastcall Hooked_StoneSlabItemGetDescriptionId(void* thisPtr, void* itemInstanceSharedPtr); + int __fastcall Hooked_HalfSlabCloneTileId(void* thisPtr, void* level, int x, int y, int z); bool __fastcall Hooked_PlayerCanDestroy(void* thisPtr, void* tilePtr); bool __fastcall Hooked_ServerPlayerGameModeUseItem(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly); bool __fastcall Hooked_MultiPlayerGameModeUseItem(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly); @@ -140,6 +200,9 @@ namespace GameHooks void* livingEntityGetViewVector, void* entityLerpMotion, void* entitySetPos); + void SetBlockHelperSymbols(void* tileGetTextureFaceData); + void SetManagedBlockDispatchSymbols(void* levelGetTile); + void EnqueueManagedBlockTick(void* levelPtr, int x, int y, int z, int blockId, int delay); bool SummonEntityByNumericId(int entityNumericId, double x, double y, double z); bool ConsumePlayerResource(void* playerPtr, int itemId, int count); bool DamageItemInstance(void* itemInstancePtr, int amount, void* ownerSharedPtr); diff --git a/WeaveLoaderRuntime/src/GameObjectFactory.cpp b/WeaveLoaderRuntime/src/GameObjectFactory.cpp index 7e3337a..a391237 100644 --- a/WeaveLoaderRuntime/src/GameObjectFactory.cpp +++ b/WeaveLoaderRuntime/src/GameObjectFactory.cpp @@ -1,4 +1,5 @@ #include "GameObjectFactory.h" +#include "CustomSlabRegistry.h" #include "SymbolResolver.h" #include "PdbParser.h" #include "LogUtil.h" @@ -17,9 +18,12 @@ typedef void* (__fastcall *TileSetSoundType_fn)(void* thisPtr, const void* sound typedef void* (__fastcall *TileSetIconName_fn)(void* thisPtr, const std::wstring& name); // Tile* Tile::setDescriptionId(unsigned int) — public virtual typedef void* (__fastcall *TileSetDescriptionId_fn)(void* thisPtr, unsigned int id); +typedef void* (__fastcall *TileSetLightBlock_fn)(void* thisPtr, int value); +typedef void* (__fastcall *TileSetLightEmission_fn)(void* thisPtr, float value); // TileItem::TileItem(int id) typedef void (__fastcall *TileItemCtor_fn)(void* thisPtr, int id); +typedef void (__fastcall *HeavyTileCtor_fn)(void* thisPtr, int id, bool isSolidRender); // Item::Item(int id) — protected ctor typedef void (__fastcall *ItemCtor_fn)(void* thisPtr, int id); @@ -29,8 +33,12 @@ typedef void (__fastcall *ShovelCtor_fn)(void* thisPtr, int id, const void* tier typedef void (__fastcall *HoeCtor_fn)(void* thisPtr, int id, const void* tier); typedef void (__fastcall *HatchetCtor_fn)(void* thisPtr, int id, const void* tier); typedef void (__fastcall *WeaponCtor_fn)(void* thisPtr, int id, const void* tier); +typedef void (__fastcall *StoneSlabCtor_fn)(void* thisPtr, int id, bool fullSize); +typedef void (__fastcall *WoodSlabCtor_fn)(void* thisPtr, int id, bool fullSize); +typedef void (__fastcall *StoneSlabItemCtor_fn)(void* thisPtr, int id, void* halfTile, void* fullTile, bool full); // Item* Item::setIconName(const std::wstring&) typedef void* (__fastcall *ItemSetIconName_fn)(void* thisPtr, const std::wstring& name); +typedef void* (__fastcall *ItemSetUseDescriptionId_fn)(void* thisPtr, unsigned int id); // Item::getDescriptionId(int) — used to extract the descriptionId field offset typedef unsigned int (__fastcall *ItemGetDescriptionId_fn)(void* thisPtr, int auxData); @@ -40,8 +48,11 @@ static TileSetFloat_fn fnSetExplodeable = nullptr; static TileSetSoundType_fn fnSetSoundType = nullptr; static TileSetIconName_fn fnTileSetIconName= nullptr; static TileSetDescriptionId_fn fnTileSetDescriptionId = nullptr; +static TileSetLightBlock_fn fnTileSetLightBlock = nullptr; +static TileSetLightEmission_fn fnTileSetLightEmission = nullptr; static TileItemCtor_fn fnTileItemCtor = nullptr; +static HeavyTileCtor_fn fnHeavyTileCtor = nullptr; static ItemCtor_fn fnItemCtor = nullptr; static PickaxeCtor_fn fnPickaxeCtor = nullptr; @@ -49,7 +60,11 @@ static ShovelCtor_fn fnShovelCtor = nullptr; static HoeCtor_fn fnHoeCtor = nullptr; static HatchetCtor_fn fnHatchetCtor = nullptr; static WeaponCtor_fn fnWeaponCtor = nullptr; +static StoneSlabCtor_fn fnStoneSlabCtor = nullptr; +static WoodSlabCtor_fn fnWoodSlabCtor = nullptr; +static StoneSlabItemCtor_fn fnStoneSlabItemCtor = nullptr; static ItemSetIconName_fn fnItemSetIconName= nullptr; +static ItemSetUseDescriptionId_fn fnItemSetUseDescriptionId = nullptr; static int s_itemDescIdOffset = -1; // offset of descriptionId field in Item, extracted from getDescriptionId // Store ADDRESSES of Material*/SoundType* statics so we can dereference lazily @@ -61,6 +76,9 @@ static void** s_tierAddrs[5] = {}; static const int TILE_ALLOC_SIZE = 1024; static const int ITEM_ALLOC_SIZE = 1024; static const int TILEITEM_ALLOC_SIZE = 1024; +static const int STONE_SLAB_TILE_ALLOC_SIZE = 128; +static const int WOOD_SLAB_TILE_ALLOC_SIZE = 120; +static const int STONE_SLAB_TILE_ITEM_ALLOC_SIZE = 192; static bool s_resolved = false; static std::unordered_map s_createdItems; @@ -117,9 +135,14 @@ bool ResolveSymbols(SymbolResolver& resolver) "?setIconName@Tile@@MEAAPEAV1@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@@Z"); fnTileSetDescriptionId = (TileSetDescriptionId_fn)resolver.Resolve( "?setDescriptionId@Tile@@UEAAPEAV1@I@Z"); + fnTileSetLightBlock = (TileSetLightBlock_fn)resolver.Resolve( + "?setLightBlock@Tile@@MEAAPEAV1@H@Z"); + fnTileSetLightEmission = (TileSetLightEmission_fn)resolver.Resolve( + "?setLightEmission@Tile@@MEAAPEAV1@M@Z"); // TileItem constructor fnTileItemCtor = (TileItemCtor_fn)resolver.Resolve("??0TileItem@@QEAA@H@Z"); + fnHeavyTileCtor = (HeavyTileCtor_fn)resolver.Resolve("??0HeavyTile@@QEAA@H_N@Z"); // Item constructor — protected (IEAA not QEAA) fnItemCtor = (ItemCtor_fn)resolver.Resolve("??0Item@@IEAA@H@Z"); @@ -128,10 +151,15 @@ bool ResolveSymbols(SymbolResolver& resolver) fnHoeCtor = (HoeCtor_fn)resolver.Resolve("??0HoeItem@@QEAA@HPEBVTier@Item@@@Z"); fnHatchetCtor = (HatchetCtor_fn)resolver.Resolve("??0HatchetItem@@QEAA@HPEBVTier@Item@@@Z"); fnWeaponCtor = (WeaponCtor_fn)resolver.Resolve("??0WeaponItem@@QEAA@HPEBVTier@Item@@@Z"); + fnStoneSlabCtor = (StoneSlabCtor_fn)resolver.Resolve("??0StoneSlabTile@@QEAA@H_N@Z"); + fnWoodSlabCtor = (WoodSlabCtor_fn)resolver.Resolve("??0WoodSlabTile@@QEAA@H_N@Z"); + fnStoneSlabItemCtor = (StoneSlabItemCtor_fn)resolver.Resolve("??0StoneSlabTileItem@@QEAA@HPEAVHalfSlabTile@@0_N@Z"); // Item::setIconName fnItemSetIconName = (ItemSetIconName_fn)resolver.Resolve( "?setIconName@Item@@QEAAPEAV1@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@@Z"); + fnItemSetUseDescriptionId = (ItemSetUseDescriptionId_fn)resolver.Resolve( + "?setUseDescriptionId@Item@@QEAAPEAV1@I@Z"); // Item::setDescriptionId is inlined — extract the field offset from getDescriptionId instead. // getDescriptionId(int) is "mov eax, [rcx+offset]; ret" so we parse the offset from its opcodes. void* fnItemGetDescId = resolver.Resolve("?getDescriptionId@Item@@UEAAIH@Z"); @@ -214,14 +242,21 @@ bool ResolveSymbols(SymbolResolver& resolver) logSym("setExplodeable", (void*)fnSetExplodeable); logSym("setSoundType", (void*)fnSetSoundType); logSym("Tile::setIconName", (void*)fnTileSetIconName); + logSym("Tile::setLightBlock", (void*)fnTileSetLightBlock); + logSym("Tile::setLightEmission", (void*)fnTileSetLightEmission); logSym("TileItem::TileItem", (void*)fnTileItemCtor); + logSym("HeavyTile::HeavyTile", (void*)fnHeavyTileCtor); logSym("Item::Item", (void*)fnItemCtor); logSym("PickaxeItem::PickaxeItem", (void*)fnPickaxeCtor); logSym("ShovelItem::ShovelItem", (void*)fnShovelCtor); logSym("HoeItem::HoeItem", (void*)fnHoeCtor); logSym("HatchetItem::HatchetItem", (void*)fnHatchetCtor); logSym("WeaponItem::WeaponItem", (void*)fnWeaponCtor); + logSym("StoneSlabTile::StoneSlabTile", (void*)fnStoneSlabCtor); + logSym("WoodSlabTile::WoodSlabTile", (void*)fnWoodSlabCtor); + logSym("StoneSlabTileItem::StoneSlabTileItem", (void*)fnStoneSlabItemCtor); logSym("Item::setIconName", (void*)fnItemSetIconName); + logSym("Item::setUseDescriptionId", (void*)fnItemSetUseDescriptionId); logSym("Material::stone addr", (void*)s_materialAddrs[1]); logSym("SOUND_STONE addr", (void*)s_soundAddrs[1]); @@ -247,8 +282,44 @@ bool ResolveSymbols(SymbolResolver& resolver) return s_resolved; } +static void ApplyTileCommon( + void* tile, + float hardness, + float resistance, + int soundType, + const wchar_t* iconName, + float lightEmission, + int lightBlock, + int descriptionId) +{ + if (fnSetDestroyTime) + fnSetDestroyTime(tile, hardness); + + if (fnSetExplodeable) + fnSetExplodeable(tile, resistance); + + void* sound = GetSound(soundType); + if (fnSetSoundType && sound) + fnSetSoundType(tile, sound); + + if (fnTileSetLightEmission) + fnTileSetLightEmission(tile, lightEmission); + + if (fnTileSetLightBlock) + fnTileSetLightBlock(tile, lightBlock); + + if (fnTileSetIconName && iconName) + { + std::wstring name(iconName); + fnTileSetIconName(tile, name); + } + + if (fnTileSetDescriptionId && descriptionId >= 0) + fnTileSetDescriptionId(tile, static_cast(descriptionId)); +} + bool CreateTile(int tileId, int materialType, float hardness, float resistance, - int soundType, const wchar_t* iconName, int descriptionId) + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId) { if (!s_resolved || !fnTileCtor) { @@ -270,27 +341,7 @@ bool CreateTile(int tileId, int materialType, float hardness, float resistance, void* tile = ::operator new(TILE_ALLOC_SIZE); memset(tile, 0, TILE_ALLOC_SIZE); fnTileCtor(tile, tileId, mat, true); - - if (fnSetDestroyTime) - fnSetDestroyTime(tile, hardness); - - if (fnSetExplodeable) - fnSetExplodeable(tile, resistance); - - void* sound = GetSound(soundType); - if (fnSetSoundType && sound) - fnSetSoundType(tile, sound); - - if (fnTileSetIconName && iconName) - { - std::wstring name(iconName); - fnTileSetIconName(tile, name); - } - - if (fnTileSetDescriptionId && descriptionId >= 0) - { - fnTileSetDescriptionId(tile, static_cast(descriptionId)); - } + ApplyTileCommon(tile, hardness, resistance, soundType, iconName, lightEmission, lightBlock, descriptionId); LogUtil::Log("[WeaveLoader] Created Tile id=%d (material=%d, icon=%ls, descId=%d)", tileId, materialType, iconName ? iconName : L"", descriptionId); @@ -308,6 +359,102 @@ bool CreateTile(int tileId, int materialType, float hardness, float resistance, return true; } +bool CreateManagedTile(int tileId, int materialType, float hardness, float resistance, + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId) +{ + return CreateTile(tileId, materialType, hardness, resistance, soundType, iconName, lightEmission, lightBlock, descriptionId); +} + +bool CreateFallingTile(int tileId, int materialType, float hardness, float resistance, + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId) +{ + if (!s_resolved || !fnHeavyTileCtor) + { + LogUtil::Log("[WeaveLoader] CreateFallingTile: symbols not resolved"); + return false; + } + + void* tile = ::operator new(TILE_ALLOC_SIZE); + memset(tile, 0, TILE_ALLOC_SIZE); + fnHeavyTileCtor(tile, tileId, true); + ApplyTileCommon(tile, hardness, resistance, soundType, iconName, lightEmission, lightBlock, descriptionId); + + if (fnTileItemCtor) + { + void* tileItem = ::operator new(TILEITEM_ALLOC_SIZE); + memset(tileItem, 0, TILEITEM_ALLOC_SIZE); + fnTileItemCtor(tileItem, tileId - 256); + } + + LogUtil::Log("[WeaveLoader] Created FallingTile id=%d (icon=%ls, descId=%d)", + tileId, iconName ? iconName : L"", descriptionId); + return true; +} + +bool CreateSlabPair(int halfTileId, int fullTileId, int materialType, float hardness, float resistance, + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId) +{ + if (!s_resolved || !fnStoneSlabItemCtor || (!fnStoneSlabCtor && !fnWoodSlabCtor)) + { + LogUtil::Log("[WeaveLoader] CreateSlabPair: symbols not resolved"); + return false; + } + + const bool woodFamily = (materialType == 2); + if ((woodFamily && !fnWoodSlabCtor) || (!woodFamily && !fnStoneSlabCtor)) + { + LogUtil::Log("[WeaveLoader] CreateSlabPair: missing %s slab ctor", woodFamily ? "wood" : "stone"); + return false; + } + const int tileAllocSize = woodFamily ? WOOD_SLAB_TILE_ALLOC_SIZE : STONE_SLAB_TILE_ALLOC_SIZE; + + void* halfTile = ::operator new(tileAllocSize); + void* fullTile = ::operator new(tileAllocSize); + memset(halfTile, 0, tileAllocSize); + memset(fullTile, 0, tileAllocSize); + + if (woodFamily) + { + fnWoodSlabCtor(halfTile, halfTileId, false); + fnWoodSlabCtor(fullTile, fullTileId, true); + } + else + { + fnStoneSlabCtor(halfTile, halfTileId, false); + fnStoneSlabCtor(fullTile, fullTileId, true); + } + + ApplyTileCommon(halfTile, hardness, resistance, soundType, iconName, lightEmission, lightBlock, descriptionId); + ApplyTileCommon(fullTile, hardness, resistance, soundType, iconName, lightEmission, lightBlock, descriptionId); + + void* slabItem = ::operator new(STONE_SLAB_TILE_ITEM_ALLOC_SIZE); + memset(slabItem, 0, STONE_SLAB_TILE_ITEM_ALLOC_SIZE); + fnStoneSlabItemCtor(slabItem, halfTileId - 256, halfTile, fullTile, false); + + if (fnItemSetIconName) + { + std::wstring name = (iconName && iconName[0]) ? iconName : L"MISSING_ICON_ITEM"; + fnItemSetIconName(slabItem, name); + } + + if (s_itemDescIdOffset > 0 && descriptionId >= 0) + { + *reinterpret_cast(static_cast(slabItem) + s_itemDescIdOffset) = + static_cast(descriptionId); + } + + if (fnItemSetUseDescriptionId && descriptionId >= 0) + { + fnItemSetUseDescriptionId(slabItem, static_cast(descriptionId)); + } + + CustomSlabRegistry::Register(halfTileId, fullTileId, descriptionId, woodFamily, iconName); + + LogUtil::Log("[WeaveLoader] Created SlabPair half=%d full=%d family=%s (icon=%ls, descId=%d)", + halfTileId, fullTileId, woodFamily ? "wood" : "stone", iconName ? iconName : L"", descriptionId); + return true; +} + bool CreateItem(int itemId, int maxStackSize, int maxDamage, const wchar_t* iconName, int descriptionId) { if (!s_resolved || !fnItemCtor) diff --git a/WeaveLoaderRuntime/src/GameObjectFactory.h b/WeaveLoaderRuntime/src/GameObjectFactory.h index 08c3553..cb44fa9 100644 --- a/WeaveLoaderRuntime/src/GameObjectFactory.h +++ b/WeaveLoaderRuntime/src/GameObjectFactory.h @@ -13,7 +13,13 @@ namespace GameObjectFactory // iconName is the texture atlas key (e.g. "ruby_ore"). // descriptionId: if >= 0, call setDescriptionId on the Tile for localization. bool CreateTile(int tileId, int materialType, float hardness, float resistance, - int soundType, const wchar_t* iconName, int descriptionId = -1); + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId = -1); + bool CreateManagedTile(int tileId, int materialType, float hardness, float resistance, + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId = -1); + bool CreateFallingTile(int tileId, int materialType, float hardness, float resistance, + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId = -1); + bool CreateSlabPair(int halfTileId, int fullTileId, int materialType, float hardness, float resistance, + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId = -1); // Create an Item game object. itemId is the FINAL id (256 + constructor param). // The Item is registered in Item::items[itemId]. diff --git a/WeaveLoaderRuntime/src/HookManager.cpp b/WeaveLoaderRuntime/src/HookManager.cpp index fd9687f..1e3e6c4 100644 --- a/WeaveLoaderRuntime/src/HookManager.cpp +++ b/WeaveLoaderRuntime/src/HookManager.cpp @@ -7,6 +7,7 @@ #include "MainMenuOverlay.h" #include "GameObjectFactory.h" #include "FurnaceRecipeRegistry.h" +#include "NativeExports.h" #include "LogUtil.h" #include @@ -292,6 +293,274 @@ bool HookManager::Install(const SymbolResolver& symbols) } } + if (symbols.pLevelSetTileAndData) + { + if (MH_CreateHook(symbols.pLevelSetTileAndData, + reinterpret_cast(&GameHooks::Hooked_LevelSetTileAndData), + reinterpret_cast(&GameHooks::Original_LevelSetTileAndData)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Level::setTileAndData"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Level::setTileAndData (managed block callbacks)"); + } + } + + if (symbols.pLevelSetData) + { + if (MH_CreateHook(symbols.pLevelSetData, + reinterpret_cast(&GameHooks::Hooked_LevelSetData), + reinterpret_cast(&GameHooks::Original_LevelSetData)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Level::setData"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Level::setData (managed block callbacks)"); + } + } + + if (symbols.pLevelUpdateNeighborsAt) + { + if (MH_CreateHook(symbols.pLevelUpdateNeighborsAt, + reinterpret_cast(&GameHooks::Hooked_LevelUpdateNeighborsAt), + reinterpret_cast(&GameHooks::Original_LevelUpdateNeighborsAt)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Level::updateNeighborsAt"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Level::updateNeighborsAt (managed block callbacks)"); + } + } + + if (symbols.pServerLevelTickPendingTicks) + { + if (MH_CreateHook(symbols.pServerLevelTickPendingTicks, + reinterpret_cast(&GameHooks::Hooked_ServerLevelTickPendingTicks), + reinterpret_cast(&GameHooks::Original_ServerLevelTickPendingTicks)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook ServerLevel::tickPendingTicks"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked ServerLevel::tickPendingTicks (managed block callbacks)"); + } + } + + if (symbols.pTileGetResource) + { + if (MH_CreateHook(symbols.pTileGetResource, + reinterpret_cast(&GameHooks::Hooked_TileGetResource), + reinterpret_cast(&GameHooks::Original_TileGetResource)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::getResource"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::getResource (managed block drops)"); + } + } + + if (symbols.pTileCloneTileId) + { + if (MH_CreateHook(symbols.pTileCloneTileId, + reinterpret_cast(&GameHooks::Hooked_TileCloneTileId), + reinterpret_cast(&GameHooks::Original_TileCloneTileId)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::cloneTileId"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::cloneTileId (managed block pick-block)"); + } + } + + if (symbols.pStoneSlabGetTexture) + { + if (MH_CreateHook(symbols.pStoneSlabGetTexture, + reinterpret_cast(&GameHooks::Hooked_StoneSlabGetTexture), + reinterpret_cast(&GameHooks::Original_StoneSlabGetTexture)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTile::getTexture"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTile::getTexture (custom slabs)"); + } + } + + if (symbols.pWoodSlabGetTexture) + { + if (MH_CreateHook(symbols.pWoodSlabGetTexture, + reinterpret_cast(&GameHooks::Hooked_WoodSlabGetTexture), + reinterpret_cast(&GameHooks::Original_WoodSlabGetTexture)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook WoodSlabTile::getTexture"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked WoodSlabTile::getTexture (custom slabs)"); + } + } + + if (symbols.pStoneSlabGetResource) + { + if (MH_CreateHook(symbols.pStoneSlabGetResource, + reinterpret_cast(&GameHooks::Hooked_StoneSlabGetResource), + reinterpret_cast(&GameHooks::Original_StoneSlabGetResource)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTile::getResource"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTile::getResource (custom slabs)"); + } + } + + if (symbols.pWoodSlabGetResource) + { + if (MH_CreateHook(symbols.pWoodSlabGetResource, + reinterpret_cast(&GameHooks::Hooked_WoodSlabGetResource), + reinterpret_cast(&GameHooks::Original_WoodSlabGetResource)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook WoodSlabTile::getResource"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked WoodSlabTile::getResource (custom slabs)"); + } + } + + if (symbols.pStoneSlabGetDescriptionId) + { + if (MH_CreateHook(symbols.pStoneSlabGetDescriptionId, + reinterpret_cast(&GameHooks::Hooked_StoneSlabGetDescriptionId), + reinterpret_cast(&GameHooks::Original_StoneSlabGetDescriptionId)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTile::getDescriptionId"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTile::getDescriptionId (custom slabs)"); + } + } + + if (symbols.pWoodSlabGetDescriptionId) + { + if (MH_CreateHook(symbols.pWoodSlabGetDescriptionId, + reinterpret_cast(&GameHooks::Hooked_WoodSlabGetDescriptionId), + reinterpret_cast(&GameHooks::Original_WoodSlabGetDescriptionId)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook WoodSlabTile::getDescriptionId"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked WoodSlabTile::getDescriptionId (custom slabs)"); + } + } + + if (symbols.pStoneSlabGetAuxName) + { + if (MH_CreateHook(symbols.pStoneSlabGetAuxName, + reinterpret_cast(&GameHooks::Hooked_StoneSlabGetAuxName), + reinterpret_cast(&GameHooks::Original_StoneSlabGetAuxName)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTile::getAuxName"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTile::getAuxName (custom slabs)"); + } + } + + if (symbols.pWoodSlabGetAuxName) + { + if (MH_CreateHook(symbols.pWoodSlabGetAuxName, + reinterpret_cast(&GameHooks::Hooked_WoodSlabGetAuxName), + reinterpret_cast(&GameHooks::Original_WoodSlabGetAuxName)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook WoodSlabTile::getAuxName"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked WoodSlabTile::getAuxName (custom slabs)"); + } + } + + if (symbols.pStoneSlabRegisterIcons) + { + if (MH_CreateHook(symbols.pStoneSlabRegisterIcons, + reinterpret_cast(&GameHooks::Hooked_StoneSlabRegisterIcons), + reinterpret_cast(&GameHooks::Original_StoneSlabRegisterIcons)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTile::registerIcons"); + } + else + { + MH_EnableHook(symbols.pStoneSlabRegisterIcons); + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTile::registerIcons (custom slabs)"); + } + } + + if (symbols.pWoodSlabRegisterIcons) + { + if (MH_CreateHook(symbols.pWoodSlabRegisterIcons, + reinterpret_cast(&GameHooks::Hooked_WoodSlabRegisterIcons), + reinterpret_cast(&GameHooks::Original_WoodSlabRegisterIcons)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook WoodSlabTile::registerIcons"); + } + else + { + MH_EnableHook(symbols.pWoodSlabRegisterIcons); + LogUtil::Log("[WeaveLoader] Hooked WoodSlabTile::registerIcons (custom slabs)"); + } + } + + if (symbols.pHalfSlabCloneTileId) + { + if (MH_CreateHook(symbols.pHalfSlabCloneTileId, + reinterpret_cast(&GameHooks::Hooked_HalfSlabCloneTileId), + reinterpret_cast(&GameHooks::Original_HalfSlabCloneTileId)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook HalfSlabTile::cloneTileId"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked HalfSlabTile::cloneTileId (custom slabs)"); + } + } + + if (symbols.pStoneSlabItemGetDescriptionId) + { + if (MH_CreateHook(symbols.pStoneSlabItemGetDescriptionId, + reinterpret_cast(&GameHooks::Hooked_StoneSlabItemGetDescriptionId), + reinterpret_cast(&GameHooks::Original_StoneSlabItemGetDescriptionId)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTileItem::getDescriptionId"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTileItem::getDescriptionId (custom slabs)"); + } + } + + if (symbols.pStoneSlabItemGetIcon) + { + if (MH_CreateHook(symbols.pStoneSlabItemGetIcon, + reinterpret_cast(&GameHooks::Hooked_StoneSlabItemGetIcon), + reinterpret_cast(&GameHooks::Original_StoneSlabItemGetIcon)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTileItem::getIcon"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTileItem::getIcon (custom slabs)"); + } + } + if (symbols.pPlayerCanDestroy) { if (MH_CreateHook(symbols.pPlayerCanDestroy, @@ -336,6 +605,14 @@ bool HookManager::Install(const SymbolResolver& symbols) GameHooks::SetAtlasLocationPointers(symbols.pTextureAtlasLocationBlocks, symbols.pTextureAtlasLocationItems); GameHooks::SetTileTilesArray(symbols.pTileTiles); + GameHooks::SetBlockHelperSymbols(symbols.pTileGetTextureFaceData); + GameHooks::SetManagedBlockDispatchSymbols(symbols.pLevelGetTile); + NativeExports::SetLevelInteropSymbols( + symbols.pLevelHasNeighborSignal, + symbols.pLevelSetTileAndData, + symbols.pServerLevelAddToTickNextTick ? symbols.pServerLevelAddToTickNextTick + : symbols.pLevelAddToTickNextTick, + symbols.pLevelGetTile); if (symbols.pTexturesBindTextureResource) { diff --git a/WeaveLoaderRuntime/src/ManagedBlockRegistry.cpp b/WeaveLoaderRuntime/src/ManagedBlockRegistry.cpp new file mode 100644 index 0000000..2d1fc5d --- /dev/null +++ b/WeaveLoaderRuntime/src/ManagedBlockRegistry.cpp @@ -0,0 +1,35 @@ +#include "ManagedBlockRegistry.h" +#include + +namespace +{ + std::unordered_map g_definitions; +} + +namespace ManagedBlockRegistry +{ + void Register(int blockId) + { + g_definitions.try_emplace(blockId); + } + + void Configure(int blockId, int dropBlockId, int cloneBlockId) + { + Definition& def = g_definitions[blockId]; + def.dropBlockId = dropBlockId; + def.cloneBlockId = cloneBlockId; + } + + const Definition* Find(int blockId) + { + const auto it = g_definitions.find(blockId); + if (it == g_definitions.end()) + return nullptr; + return &it->second; + } + + bool IsManaged(int blockId) + { + return g_definitions.find(blockId) != g_definitions.end(); + } +} diff --git a/WeaveLoaderRuntime/src/ManagedBlockRegistry.h b/WeaveLoaderRuntime/src/ManagedBlockRegistry.h new file mode 100644 index 0000000..cde91e6 --- /dev/null +++ b/WeaveLoaderRuntime/src/ManagedBlockRegistry.h @@ -0,0 +1,15 @@ +#pragma once + +namespace ManagedBlockRegistry +{ + struct Definition + { + int dropBlockId = -1; + int cloneBlockId = -1; + }; + + void Register(int blockId); + void Configure(int blockId, int dropBlockId, int cloneBlockId); + const Definition* Find(int blockId); + bool IsManaged(int blockId); +} diff --git a/WeaveLoaderRuntime/src/NativeExports.cpp b/WeaveLoaderRuntime/src/NativeExports.cpp index a50dfac..1e8fffb 100644 --- a/WeaveLoaderRuntime/src/NativeExports.cpp +++ b/WeaveLoaderRuntime/src/NativeExports.cpp @@ -7,12 +7,35 @@ #include "CustomPickaxeRegistry.h" #include "CustomToolMaterialRegistry.h" #include "CustomBlockRegistry.h" +#include "CustomSlabRegistry.h" +#include "ManagedBlockRegistry.h" #include "ModStrings.h" #include "LogUtil.h" #include #include #include +namespace +{ + using LevelHasNeighborSignal_fn = bool (__fastcall *)(void* thisPtr, int x, int y, int z); + using LevelSetTileAndData_fn = bool (__fastcall *)(void* thisPtr, int x, int y, int z, int blockId, int data, int flags); + using LevelAddToTickNextTick_fn = void (__fastcall *)(void* thisPtr, int x, int y, int z, int blockId, int delay); + using LevelGetTile_fn = int (__fastcall *)(void* thisPtr, int x, int y, int z); + + LevelHasNeighborSignal_fn s_levelHasNeighborSignal = nullptr; + LevelSetTileAndData_fn s_levelSetTileAndData = nullptr; + LevelAddToTickNextTick_fn s_levelAddToTickNextTick = nullptr; + LevelGetTile_fn s_levelGetTile = nullptr; +} + +void NativeExports::SetLevelInteropSymbols(void* hasNeighborSignal, void* setTileAndData, void* addToTickNextTick, void* getTile) +{ + s_levelHasNeighborSignal = reinterpret_cast(hasNeighborSignal); + s_levelSetTileAndData = reinterpret_cast(setTileAndData); + s_levelAddToTickNextTick = reinterpret_cast(addToTickNextTick); + s_levelGetTile = reinterpret_cast(getTile); +} + static std::wstring Utf8ToWide(const char* utf8) { if (!utf8 || !utf8[0]) return std::wstring(); @@ -36,6 +59,36 @@ static int ResolveRecipeId(const char* namespacedId, bool preferItem) return (blockId >= 0) ? blockId : itemId; } +static bool PrepareBlockRegistration( + const char* namespacedId, + const char* iconName, + const char* displayName, + int* outId, + int* outDescId, + std::wstring* outIcon) +{ + if (!namespacedId || !outId || !outDescId || !outIcon) + return false; + + *outId = IdRegistry::Instance().Register(IdRegistry::Type::Block, namespacedId); + if (*outId < 0) + { + LogUtil::Log("[WeaveLoader] Failed to allocate block ID for '%s'", namespacedId); + return false; + } + + *outIcon = Utf8ToWide(iconName); + *outDescId = -1; + if (displayName && displayName[0]) + { + *outDescId = ModStrings::AllocateId(); + std::wstring wName = Utf8ToWide(displayName); + ModStrings::Register(*outDescId, wName.c_str()); + } + + return true; +} + extern "C" { @@ -50,44 +103,162 @@ int native_register_block( int lightBlock, const char* displayName, int requiredHarvestLevel, - int requiredTool) + int requiredTool, + int acceptsRedstonePower) { if (!namespacedId) return -1; - int id = IdRegistry::Instance().Register(IdRegistry::Type::Block, namespacedId); - if (id < 0) - { - LogUtil::Log("[WeaveLoader] Failed to allocate block ID for '%s'", namespacedId); + int id = -1; + int descId = -1; + std::wstring wIcon; + if (!PrepareBlockRegistration(namespacedId, iconName, displayName, &id, &descId, &wIcon)) return -1; - } LogUtil::Log("[WeaveLoader] Registered block '%s' -> ID %d (hardness=%.1f, resistance=%.1f)", namespacedId, id, hardness, resistance); - std::wstring wIcon = Utf8ToWide(iconName); - - int descId = -1; - if (displayName && displayName[0]) - { - descId = ModStrings::AllocateId(); - std::wstring wName = Utf8ToWide(displayName); - ModStrings::Register(descId, wName.c_str()); - } - if (!GameObjectFactory::CreateTile(id, materialId, hardness, resistance, - soundType, wIcon.empty() ? nullptr : wIcon.c_str(), descId)) + soundType, wIcon.empty() ? nullptr : wIcon.c_str(), lightEmission, lightBlock, descId)) { LogUtil::Log("[WeaveLoader] Warning: failed to create game Tile for block '%s' id=%d", namespacedId, id); } - if (requiredHarvestLevel >= 0 || requiredTool != 0) + if (requiredHarvestLevel >= 0 || requiredTool != 0 || acceptsRedstonePower != 0) { - CustomBlockRegistry::Register(id, requiredHarvestLevel, requiredTool); + CustomBlockRegistry::Register(id, requiredHarvestLevel, requiredTool, acceptsRedstonePower != 0); } return id; } +int native_register_managed_block( + const char* namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + const char* iconName, + float lightEmission, + int lightBlock, + const char* displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower) +{ + if (!namespacedId) return -1; + + int id = -1; + int descId = -1; + std::wstring wIcon; + if (!PrepareBlockRegistration(namespacedId, iconName, displayName, &id, &descId, &wIcon)) + return -1; + + if (!GameObjectFactory::CreateManagedTile(id, materialId, hardness, resistance, + soundType, wIcon.empty() ? nullptr : wIcon.c_str(), lightEmission, lightBlock, descId)) + { + LogUtil::Log("[WeaveLoader] Warning: failed to create managed Tile for '%s' id=%d", namespacedId, id); + } + + ManagedBlockRegistry::Register(id); + + if (requiredHarvestLevel >= 0 || requiredTool != 0 || acceptsRedstonePower != 0) + { + CustomBlockRegistry::Register(id, requiredHarvestLevel, requiredTool, acceptsRedstonePower != 0); + } + + return id; +} + +int native_register_falling_block( + const char* namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + const char* iconName, + float lightEmission, + int lightBlock, + const char* displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower) +{ + int id = -1; + int descId = -1; + std::wstring wIcon; + if (!PrepareBlockRegistration(namespacedId, iconName, displayName, &id, &descId, &wIcon)) + return -1; + + if (!GameObjectFactory::CreateFallingTile(id, materialId, hardness, resistance, + soundType, wIcon.empty() ? nullptr : wIcon.c_str(), lightEmission, lightBlock, descId)) + { + LogUtil::Log("[WeaveLoader] Warning: failed to create falling Tile for '%s' id=%d", namespacedId, id); + } + + ManagedBlockRegistry::Register(id); + + if (requiredHarvestLevel >= 0 || requiredTool != 0 || acceptsRedstonePower != 0) + CustomBlockRegistry::Register(id, requiredHarvestLevel, requiredTool, acceptsRedstonePower != 0); + + return id; +} + +int native_register_slab_block( + const char* namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + const char* iconName, + float lightEmission, + int lightBlock, + const char* displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower, + int* outDoubleBlockNumericId) +{ + if (!namespacedId) return -1; + + int halfId = -1; + int descId = -1; + std::wstring wIcon; + if (!PrepareBlockRegistration(namespacedId, iconName, displayName, &halfId, &descId, &wIcon)) + return -1; + + std::string fullName = std::string(namespacedId) + "_double"; + int fullId = IdRegistry::Instance().Register(IdRegistry::Type::Block, fullName.c_str()); + if (fullId < 0) + { + LogUtil::Log("[WeaveLoader] Failed to allocate double slab block ID for '%s'", namespacedId); + return -1; + } + + if (outDoubleBlockNumericId) + *outDoubleBlockNumericId = fullId; + + if (!GameObjectFactory::CreateSlabPair(halfId, fullId, materialId, hardness, resistance, + soundType, wIcon.empty() ? nullptr : wIcon.c_str(), lightEmission, lightBlock, descId)) + { + LogUtil::Log("[WeaveLoader] Warning: failed to create slab pair for '%s' half=%d full=%d", namespacedId, halfId, fullId); + } + + if (requiredHarvestLevel >= 0 || requiredTool != 0 || acceptsRedstonePower != 0) + { + CustomBlockRegistry::Register(halfId, requiredHarvestLevel, requiredTool, acceptsRedstonePower != 0); + CustomBlockRegistry::Register(fullId, requiredHarvestLevel, requiredTool, acceptsRedstonePower != 0); + } + + return halfId; +} + +void native_configure_managed_block(int numericBlockId, int dropNumericBlockId, int cloneNumericBlockId) +{ + if (numericBlockId < 0) + return; + ManagedBlockRegistry::Configure(numericBlockId, dropNumericBlockId, cloneNumericBlockId); +} + int native_register_item( const char* namespacedId, int maxStackSize, @@ -460,6 +631,38 @@ int native_summon_entity_by_id(int numericEntityId, double x, double y, double z return 1; } +int native_level_has_neighbor_signal(void* levelPtr, int x, int y, int z) +{ + return (levelPtr && s_levelHasNeighborSignal && s_levelHasNeighborSignal(levelPtr, x, y, z)) ? 1 : 0; +} + +int native_level_set_tile(void* levelPtr, int x, int y, int z, int blockId, int data, int flags) +{ + return (levelPtr && s_levelSetTileAndData && s_levelSetTileAndData(levelPtr, x, y, z, blockId, data, flags)) ? 1 : 0; +} + +int native_level_schedule_tick(void* levelPtr, int x, int y, int z, int blockId, int delay) +{ + if (!levelPtr) + return 0; + if (ManagedBlockRegistry::IsManaged(blockId)) + { + GameHooks::EnqueueManagedBlockTick(levelPtr, x, y, z, blockId, delay); + return 1; + } + if (!s_levelAddToTickNextTick) + return 0; + s_levelAddToTickNextTick(levelPtr, x, y, z, blockId, delay); + return 1; +} + +int native_level_get_tile(void* levelPtr, int x, int y, int z) +{ + if (!levelPtr || !s_levelGetTile) + return -1; + return s_levelGetTile(levelPtr, x, y, z); +} + int native_summon_entity(const char* namespacedId, double x, double y, double z) { if (!namespacedId || !namespacedId[0]) diff --git a/WeaveLoaderRuntime/src/NativeExports.h b/WeaveLoaderRuntime/src/NativeExports.h index 3d8cf8a..c1a3379 100644 --- a/WeaveLoaderRuntime/src/NativeExports.h +++ b/WeaveLoaderRuntime/src/NativeExports.h @@ -1,5 +1,10 @@ #pragma once +namespace NativeExports +{ + void SetLevelInteropSymbols(void* hasNeighborSignal, void* setTileAndData, void* addToTickNextTick, void* getTile); +} + /// Exported C functions callable from C# via P/Invoke. /// All registration functions accept namespaced string IDs and delegate /// to IdRegistry for numeric ID allocation. @@ -16,7 +21,48 @@ extern "C" int lightBlock, const char* displayName, int requiredHarvestLevel, - int requiredTool); + int requiredTool, + int acceptsRedstonePower); + __declspec(dllexport) int native_register_managed_block( + const char* namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + const char* iconName, + float lightEmission, + int lightBlock, + const char* displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower); + __declspec(dllexport) int native_register_falling_block( + const char* namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + const char* iconName, + float lightEmission, + int lightBlock, + const char* displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower); + __declspec(dllexport) int native_register_slab_block( + const char* namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + const char* iconName, + float lightEmission, + int lightBlock, + const char* displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower, + int* outDoubleBlockNumericId); __declspec(dllexport) int native_register_item( const char* namespacedId, @@ -60,6 +106,10 @@ extern "C" int numericItemId, int harvestLevel, float destroySpeed); + __declspec(dllexport) void native_configure_managed_block( + int numericBlockId, + int dropNumericBlockId, + int cloneNumericBlockId); __declspec(dllexport) int native_configure_custom_tool_item( int numericItemId, int toolKind, @@ -99,6 +149,10 @@ extern "C" __declspec(dllexport) int native_spawn_entity_from_player_look(void* playerPtr, void* playerSharedPtr, int numericEntityId, double speed, double spawnForward, double spawnUp); __declspec(dllexport) int native_summon_entity(const char* namespacedId, double x, double y, double z); __declspec(dllexport) int native_summon_entity_by_id(int numericEntityId, double x, double y, double z); + __declspec(dllexport) int native_level_has_neighbor_signal(void* levelPtr, int x, int y, int z); + __declspec(dllexport) int native_level_set_tile(void* levelPtr, int x, int y, int z, int blockId, int data, int flags); + __declspec(dllexport) int native_level_schedule_tick(void* levelPtr, int x, int y, int z, int blockId, int delay); + __declspec(dllexport) int native_level_get_tile(void* levelPtr, int x, int y, int z); __declspec(dllexport) void native_subscribe_event( const char* eventName, diff --git a/WeaveLoaderRuntime/src/PdbParser.cpp b/WeaveLoaderRuntime/src/PdbParser.cpp index 0e5d224..311f597 100644 --- a/WeaveLoaderRuntime/src/PdbParser.cpp +++ b/WeaveLoaderRuntime/src/PdbParser.cpp @@ -282,6 +282,98 @@ uint32_t FindSymbolRVA(const char* decoratedName) return 0; } +uint32_t FindSymbolRVAByName(const char* exactName) +{ + if (!s_open || !exactName || !exactName[0]) return 0; + + // 1) Search global symbol stream for exact name matches on data/thread symbols. + { + const PDB::ArrayView records = s_globalStream->GetRecords(); + for (const PDB::HashRecord& hashRecord : records) + { + const PDB::CodeView::DBI::Record* record = s_globalStream->GetRecord(*s_symbolRecords, hashRecord); + uint16_t section = 0; + uint32_t offset = 0; + const char* name = GetGlobalSymName(record, section, offset); + + if (!name || strcmp(name, exactName) != 0) + continue; + + uint32_t rva = s_sectionStream->ConvertSectionOffsetToRVA(section, offset); + if (rva != 0) return rva; + } + } + + // 2) Search per-module symbol streams for exact name matches. + { + const PDB::ArrayView modules = s_moduleStream->GetModules(); + for (const PDB::ModuleInfoStream::Module& mod : modules) + { + if (!mod.HasSymbolStream()) + continue; + + const PDB::ModuleSymbolStream modSymStream = mod.CreateSymbolStream(*s_rawFile); + uint32_t foundRVA = 0; + + modSymStream.ForEachSymbol([&](const PDB::CodeView::DBI::Record* record) + { + if (foundRVA != 0) return; + + const char* name = nullptr; + uint16_t section = 0; + uint32_t offset = 0; + + switch (record->header.kind) + { + case PDB::CodeView::DBI::SymbolRecordKind::S_LPROC32: + name = record->data.S_LPROC32.name; + section = record->data.S_LPROC32.section; + offset = record->data.S_LPROC32.offset; + break; + case PDB::CodeView::DBI::SymbolRecordKind::S_GPROC32: + name = record->data.S_GPROC32.name; + section = record->data.S_GPROC32.section; + offset = record->data.S_GPROC32.offset; + break; + case PDB::CodeView::DBI::SymbolRecordKind::S_LPROC32_ID: + name = record->data.S_LPROC32_ID.name; + section = record->data.S_LPROC32_ID.section; + offset = record->data.S_LPROC32_ID.offset; + break; + case PDB::CodeView::DBI::SymbolRecordKind::S_GPROC32_ID: + name = record->data.S_GPROC32_ID.name; + section = record->data.S_GPROC32_ID.section; + offset = record->data.S_GPROC32_ID.offset; + break; + case PDB::CodeView::DBI::SymbolRecordKind::S_LDATA32: + name = record->data.S_LDATA32.name; + section = record->data.S_LDATA32.section; + offset = record->data.S_LDATA32.offset; + break; + case PDB::CodeView::DBI::SymbolRecordKind::S_GDATA32: + name = record->data.S_GDATA32.name; + section = record->data.S_GDATA32.section; + offset = record->data.S_GDATA32.offset; + break; + default: + return; + } + + if (name && strcmp(name, exactName) == 0) + { + uint32_t rva = s_sectionStream->ConvertSectionOffsetToRVA(section, offset); + if (rva != 0) foundRVA = rva; + } + }); + + if (foundRVA != 0) + return foundRVA; + } + } + + return 0; +} + void DumpMatching(const char* substring) { if (!s_open) return; diff --git a/WeaveLoaderRuntime/src/PdbParser.h b/WeaveLoaderRuntime/src/PdbParser.h index a3901ef..1c9e560 100644 --- a/WeaveLoaderRuntime/src/PdbParser.h +++ b/WeaveLoaderRuntime/src/PdbParser.h @@ -8,6 +8,10 @@ namespace PdbParser // Returns the RVA for a decorated symbol name, or 0 on failure. uint32_t FindSymbolRVA(const char* decoratedName); + // Returns the RVA for an exact procedure/data name as stored in module/global + // symbol streams (for example "Tile::onPlace"), or 0 on failure. + uint32_t FindSymbolRVAByName(const char* exactName); + // Logs all symbols whose name contains the given substring (for debugging). void DumpMatching(const char* substring); @@ -19,6 +23,5 @@ namespace PdbParser // Returns true if found. outName receives the symbol name, outOffset // the byte distance from the symbol's start address. bool FindNameByRVA(uint32_t rva, char* outName, size_t nameSize, uint32_t* outOffset); - void Close(); } diff --git a/WeaveLoaderRuntime/src/SymbolResolver.cpp b/WeaveLoaderRuntime/src/SymbolResolver.cpp index 2db242d..380eca2 100644 --- a/WeaveLoaderRuntime/src/SymbolResolver.cpp +++ b/WeaveLoaderRuntime/src/SymbolResolver.cpp @@ -34,6 +34,29 @@ static const char* SYM_PICKAXEITEM_GETDESTROYSPEED = "?getDestroySpeed@PickaxeIt static const char* SYM_PICKAXEITEM_CANDESTROYSPECIAL = "?canDestroySpecial@PickaxeItem@@UEAA_NPEAVTile@@@Z"; static const char* SYM_SHOVELITEM_GETDESTROYSPEED = "?getDestroySpeed@ShovelItem@@UEAAMV?$shared_ptr@VItemInstance@@@std@@PEAVTile@@@Z"; static const char* SYM_SHOVELITEM_CANDESTROYSPECIAL = "?canDestroySpecial@ShovelItem@@UEAA_NPEAVTile@@@Z"; +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_LEVEL_UPDATE_NEIGHBORS_AT = "?updateNeighborsAt@Level@@QEAAXHHHH@Z"; +static const char* SYM_SERVERLEVEL_TICKPENDINGTICKS = "?tickPendingTicks@ServerLevel@@UEAA_N_N@Z"; +static const char* SYM_LEVEL_GETTILE = "?getTile@Level@@UEAAHHHH@Z"; +static const char* SYM_LEVEL_SETDATA = "?setData@Level@@UEAA_NHHHHH_N@Z"; +static const char* SYM_TILE_GETRESOURCE = "?getResource@Tile@@UEAAHHPEAVRandom@@H@Z"; +static const char* SYM_TILE_CLONETILEID = "?cloneTileId@Tile@@UEAAHPEAVLevel@@HHH@Z"; +static const char* SYM_TILE_GETTEXTURE_FACEDATA = "?getTexture@Tile@@UEAAPEAVIcon@@HH@Z"; +static const char* SYM_STONESLAB_GETTEXTURE = "?getTexture@StoneSlabTile@@UEAAPEAVIcon@@HH@Z"; +static const char* SYM_WOODSLAB_GETTEXTURE = "?getTexture@WoodSlabTile@@UEAAPEAVIcon@@HH@Z"; +static const char* SYM_STONESLAB_GETRESOURCE = "?getResource@StoneSlabTile@@UEAAHHPEAVRandom@@H@Z"; +static const char* SYM_WOODSLAB_GETRESOURCE = "?getResource@WoodSlabTile@@UEAAHHPEAVRandom@@H@Z"; +static const char* SYM_STONESLAB_GETDESCRIPTIONID = "?getDescriptionId@StoneSlabTile@@UEAAIH@Z"; +static const char* SYM_WOODSLAB_GETDESCRIPTIONID = "?getDescriptionId@WoodSlabTile@@UEAAIH@Z"; +static const char* SYM_STONESLAB_GETAUXNAME = "?getAuxName@StoneSlabTile@@UEAAHH@Z"; +static const char* SYM_WOODSLAB_GETAUXNAME = "?getAuxName@WoodSlabTile@@UEAAHH@Z"; +static const char* SYM_STONESLAB_REGISTERICONS = "?registerIcons@StoneSlabTile@@UEAAXPEAVIconRegister@@@Z"; +static const char* SYM_WOODSLAB_REGISTERICONS = "?registerIcons@WoodSlabTile@@UEAAXPEAVIconRegister@@@Z"; +static const char* SYM_STONESLABITEM_GETICON = "?getIcon@StoneSlabTileItem@@UEAAPEAVIcon@@H@Z"; +static const char* SYM_STONESLABITEM_GETDESCRIPTIONID = "?getDescriptionId@StoneSlabTileItem@@UEAAIV?$shared_ptr@VItemInstance@@@std@@@Z"; +static const char* SYM_HALFSLAB_CLONETILEID = "?cloneTileId@HalfSlabTile@@UEAAHPEAVLevel@@HHH@Z"; static const char* SYM_PLAYER_CANDESTROY = "?canDestroy@Player@@QEAA_NPEAVTile@@@Z"; static const char* SYM_SERVER_PLAYER_GAMEMODE_USEITEM = "?useItem@ServerPlayerGameMode@@QEAA_NV?$shared_ptr@VPlayer@@@std@@PEAVLevel@@V?$shared_ptr@VItemInstance@@@3@_N@Z"; static const char* SYM_MULTI_PLAYER_GAMEMODE_USEITEM = "?useItem@MultiPlayerGameMode@@UEAA_NV?$shared_ptr@VPlayer@@@std@@PEAVLevel@@V?$shared_ptr@VItemInstance@@@3@_N@Z"; @@ -61,6 +84,23 @@ static const char* SYM_ABSTRACTCONTAINERMENU_BROADCASTCHANGES = "?broadcastChang static const char* SYM_TEXATLAS_BLOCKS = "?LOCATION_BLOCKS@TextureAtlas@@2VResourceLocation@@A"; static const char* SYM_TEXATLAS_ITEMS = "?LOCATION_ITEMS@TextureAtlas@@2VResourceLocation@@A"; static const char* SYM_TILE_TILES = "?tiles@Tile@@2PEAPEAV1@EA"; +static const char* SYM_LEVEL_HASNEIGHBORSIGNAL = "?hasNeighborSignal@Level@@QEAA_NHHH@Z"; +static const char* SYM_LEVEL_SETTILEANDDATA = "?setTileAndData@Level@@UEAA_NHHHHHH@Z"; +static const char* SYM_LEVEL_ADDTOTICKNEXTTICK = "?addToTickNextTick@Level@@UEAAXHHHHH@Z"; +static const char* SYM_SERVERLEVEL_ADDTOTICKNEXTTICK = "?addToTickNextTick@ServerLevel@@UEAAXHHHHH@Z"; + +static void* ResolveExactProcName(uintptr_t moduleBase, const char* exactName) +{ + uint32_t rva = PdbParser::FindSymbolRVAByName(exactName); + if (rva == 0) + return nullptr; + return reinterpret_cast(moduleBase + rva); +} + +static bool IsStub31000(uintptr_t moduleBase, void* ptr) +{ + return ptr == reinterpret_cast(moduleBase + 0x31000u); +} bool SymbolResolver::Initialize() { @@ -147,6 +187,29 @@ bool SymbolResolver::ResolveGameFunctions() pPickaxeItemCanDestroySpecial = Resolve(SYM_PICKAXEITEM_CANDESTROYSPECIAL); pShovelItemGetDestroySpeed = Resolve(SYM_SHOVELITEM_GETDESTROYSPEED); pShovelItemCanDestroySpecial = Resolve(SYM_SHOVELITEM_CANDESTROYSPECIAL); + pTileOnPlace = Resolve(SYM_TILE_ONPLACE); + pTileNeighborChanged = Resolve(SYM_TILE_NEIGHBORCHANGED); + pTileTick = Resolve(SYM_TILE_TICK); + pLevelUpdateNeighborsAt = Resolve(SYM_LEVEL_UPDATE_NEIGHBORS_AT); + pServerLevelTickPendingTicks = Resolve(SYM_SERVERLEVEL_TICKPENDINGTICKS); + pLevelGetTile = Resolve(SYM_LEVEL_GETTILE); + pLevelSetData = Resolve(SYM_LEVEL_SETDATA); + pTileGetResource = Resolve(SYM_TILE_GETRESOURCE); + pTileCloneTileId = Resolve(SYM_TILE_CLONETILEID); + pTileGetTextureFaceData = Resolve(SYM_TILE_GETTEXTURE_FACEDATA); + pStoneSlabGetTexture = Resolve(SYM_STONESLAB_GETTEXTURE); + pWoodSlabGetTexture = Resolve(SYM_WOODSLAB_GETTEXTURE); + pStoneSlabGetResource = Resolve(SYM_STONESLAB_GETRESOURCE); + pWoodSlabGetResource = Resolve(SYM_WOODSLAB_GETRESOURCE); + pStoneSlabGetDescriptionId = Resolve(SYM_STONESLAB_GETDESCRIPTIONID); + pWoodSlabGetDescriptionId = Resolve(SYM_WOODSLAB_GETDESCRIPTIONID); + pStoneSlabGetAuxName = Resolve(SYM_STONESLAB_GETAUXNAME); + pWoodSlabGetAuxName = Resolve(SYM_WOODSLAB_GETAUXNAME); + pStoneSlabRegisterIcons = Resolve(SYM_STONESLAB_REGISTERICONS); + pWoodSlabRegisterIcons = Resolve(SYM_WOODSLAB_REGISTERICONS); + pStoneSlabItemGetIcon = Resolve(SYM_STONESLABITEM_GETICON); + pStoneSlabItemGetDescriptionId = Resolve(SYM_STONESLABITEM_GETDESCRIPTIONID); + pHalfSlabCloneTileId = Resolve(SYM_HALFSLAB_CLONETILEID); pPlayerCanDestroy = Resolve(SYM_PLAYER_CANDESTROY); pServerPlayerGameModeUseItem = Resolve(SYM_SERVER_PLAYER_GAMEMODE_USEITEM); pMultiPlayerGameModeUseItem = Resolve(SYM_MULTI_PLAYER_GAMEMODE_USEITEM); @@ -176,6 +239,24 @@ bool SymbolResolver::ResolveGameFunctions() pTextureAtlasLocationBlocks = Resolve(SYM_TEXATLAS_BLOCKS); pTextureAtlasLocationItems = Resolve(SYM_TEXATLAS_ITEMS); pTileTiles = Resolve(SYM_TILE_TILES); + pLevelHasNeighborSignal = Resolve(SYM_LEVEL_HASNEIGHBORSIGNAL); + pLevelSetTileAndData = Resolve(SYM_LEVEL_SETTILEANDDATA); + pLevelAddToTickNextTick = Resolve(SYM_LEVEL_ADDTOTICKNEXTTICK); + pServerLevelAddToTickNextTick = Resolve(SYM_SERVERLEVEL_ADDTOTICKNEXTTICK); + + // Some public symbols in this build resolve to stub bodies. Prefer exact + // module procedure names from the PDB where those exist. + if (pShovelItemGetDestroySpeed == nullptr) + pShovelItemGetDestroySpeed = ResolveExactProcName(m_moduleBase, "DiggerItem::getDestroySpeed"); + if (IsStub31000(m_moduleBase, pTileOnPlace)) + pTileOnPlace = ResolveExactProcName(m_moduleBase, "Tile::onPlace"); + if (IsStub31000(m_moduleBase, pTileNeighborChanged)) + pTileNeighborChanged = ResolveExactProcName(m_moduleBase, "Tile::neighborChanged"); + if (IsStub31000(m_moduleBase, pTileTick)) + pTileTick = ResolveExactProcName(m_moduleBase, "Tile::tick"); + if (IsStub31000(m_moduleBase, pWoodSlabRegisterIcons)) + pWoodSlabRegisterIcons = ResolveExactProcName(m_moduleBase, "WoodSlabTile::registerIcons"); + if (!pOperatorNew) pOperatorNew = GetProcAddress(GetModuleHandleA("vcruntime140.dll"), SYM_OPERATOR_NEW); if (!pOperatorNew) pOperatorNew = GetProcAddress(GetModuleHandleA("vcruntime140d.dll"), SYM_OPERATOR_NEW); if (!pOperatorNew) pOperatorNew = GetProcAddress(GetModuleHandle(nullptr), SYM_OPERATOR_NEW); @@ -218,6 +299,29 @@ bool SymbolResolver::ResolveGameFunctions() logSym("PickaxeItem::canDestroySpecial", pPickaxeItemCanDestroySpecial); logSym("ShovelItem::getDestroySpeed", pShovelItemGetDestroySpeed); logSym("ShovelItem::canDestroySpecial", pShovelItemCanDestroySpecial); + logSym("Tile::onPlace", pTileOnPlace); + logSym("Tile::neighborChanged", pTileNeighborChanged); + logSym("Tile::tick", pTileTick); + logSym("Level::updateNeighborsAt", pLevelUpdateNeighborsAt); + logSym("ServerLevel::tickPendingTicks", pServerLevelTickPendingTicks); + logSym("Level::getTile", pLevelGetTile); + logSym("Level::setData", pLevelSetData); + logSym("Tile::getResource", pTileGetResource); + logSym("Tile::cloneTileId", pTileCloneTileId); + logSym("Tile::getTexture(face,data)", pTileGetTextureFaceData); + logSym("StoneSlabTile::getTexture", pStoneSlabGetTexture); + logSym("WoodSlabTile::getTexture", pWoodSlabGetTexture); + logSym("StoneSlabTile::getResource", pStoneSlabGetResource); + logSym("WoodSlabTile::getResource", pWoodSlabGetResource); + logSym("StoneSlabTile::getDescriptionId", pStoneSlabGetDescriptionId); + logSym("WoodSlabTile::getDescriptionId", pWoodSlabGetDescriptionId); + logSym("StoneSlabTile::getAuxName", pStoneSlabGetAuxName); + logSym("WoodSlabTile::getAuxName", pWoodSlabGetAuxName); + logSym("StoneSlabTile::registerIcons", pStoneSlabRegisterIcons); + logSym("WoodSlabTile::registerIcons", pWoodSlabRegisterIcons); + logSym("StoneSlabTileItem::getIcon", pStoneSlabItemGetIcon); + logSym("StoneSlabTileItem::getDescriptionId", pStoneSlabItemGetDescriptionId); + logSym("HalfSlabTile::cloneTileId", pHalfSlabCloneTileId); logSym("Player::canDestroy", pPlayerCanDestroy); logSym("ServerPlayerGameMode::useItem", pServerPlayerGameModeUseItem); logSym("MultiPlayerGameMode::useItem", pMultiPlayerGameModeUseItem); @@ -243,6 +347,10 @@ bool SymbolResolver::ResolveGameFunctions() logSym("TextureAtlas::LOCATION_BLOCKS", pTextureAtlasLocationBlocks); logSym("TextureAtlas::LOCATION_ITEMS", pTextureAtlasLocationItems); logSym("Tile::tiles", pTileTiles); + logSym("Level::hasNeighborSignal", pLevelHasNeighborSignal); + logSym("Level::setTileAndData", pLevelSetTileAndData); + logSym("Level::addToTickNextTick", pLevelAddToTickNextTick); + logSym("ServerLevel::addToTickNextTick", pServerLevelAddToTickNextTick); bool ok = pRunStaticCtors && pMinecraftTick && pMinecraftInit; if (ok) diff --git a/WeaveLoaderRuntime/src/SymbolResolver.h b/WeaveLoaderRuntime/src/SymbolResolver.h index ddfe491..e988eaa 100644 --- a/WeaveLoaderRuntime/src/SymbolResolver.h +++ b/WeaveLoaderRuntime/src/SymbolResolver.h @@ -39,6 +39,29 @@ public: void* pPickaxeItemCanDestroySpecial = nullptr; // PickaxeItem::canDestroySpecial(Tile*) void* pShovelItemGetDestroySpeed = nullptr; // ShovelItem::getDestroySpeed(shared_ptr,Tile*) void* pShovelItemCanDestroySpecial = nullptr; // ShovelItem::canDestroySpecial(Tile*) + void* pTileOnPlace = nullptr; // Tile::onPlace(Level*,int,int,int) + void* pTileNeighborChanged = nullptr; // Tile::neighborChanged(Level*,int,int,int,int) + void* pTileTick = nullptr; // Tile::tick(Level*,int,int,int,Random*) + void* pLevelUpdateNeighborsAt = nullptr; // Level::updateNeighborsAt(int,int,int,int) + void* pServerLevelTickPendingTicks = nullptr; // ServerLevel::tickPendingTicks(bool) + void* pLevelGetTile = nullptr; // Level::getTile(int,int,int) + void* pLevelSetData = nullptr; // Level::setData(int,int,int,int,int,bool) + void* pTileGetResource = nullptr; // Tile::getResource(int,Random*,int) + void* pTileCloneTileId = nullptr; // Tile::cloneTileId(Level*,int,int,int) + void* pTileGetTextureFaceData = nullptr; // Tile::getTexture(int,int) + void* pStoneSlabGetTexture = nullptr; // StoneSlabTile::getTexture(int,int) + void* pWoodSlabGetTexture = nullptr; // WoodSlabTile::getTexture(int,int) + void* pStoneSlabGetResource = nullptr; // StoneSlabTile::getResource(int,Random*,int) + void* pWoodSlabGetResource = nullptr; // WoodSlabTile::getResource(int,Random*,int) + void* pStoneSlabGetDescriptionId = nullptr; // StoneSlabTile::getDescriptionId(int) + void* pWoodSlabGetDescriptionId = nullptr; // WoodSlabTile::getDescriptionId(int) + void* pStoneSlabGetAuxName = nullptr; // StoneSlabTile::getAuxName(int) + void* pWoodSlabGetAuxName = nullptr; // WoodSlabTile::getAuxName(int) + void* pStoneSlabRegisterIcons = nullptr; // StoneSlabTile::registerIcons(IconRegister*) + void* pWoodSlabRegisterIcons = nullptr; // WoodSlabTile::registerIcons(IconRegister*) + void* pStoneSlabItemGetIcon = nullptr; // StoneSlabTileItem::getIcon(int) + void* pStoneSlabItemGetDescriptionId = nullptr; // StoneSlabTileItem::getDescriptionId(shared_ptr) + void* pHalfSlabCloneTileId = nullptr; // HalfSlabTile::cloneTileId(Level*,int,int,int) void* pPlayerCanDestroy = nullptr; // Player::canDestroy(Tile*) void* pServerPlayerGameModeUseItem = nullptr; // ServerPlayerGameMode::useItem(shared_ptr,Level*,shared_ptr,bool) void* pMultiPlayerGameModeUseItem = nullptr; // MultiPlayerGameMode::useItem(shared_ptr,Level*,shared_ptr,bool) @@ -64,6 +87,10 @@ public: void* pTextureAtlasLocationBlocks = nullptr; // TextureAtlas::LOCATION_BLOCKS void* pTextureAtlasLocationItems = nullptr; // TextureAtlas::LOCATION_ITEMS void* pTileTiles = nullptr; // Tile::tiles (Tile*[]) for tile id lookup + void* pLevelHasNeighborSignal = nullptr; // Level::hasNeighborSignal(int,int,int) + void* pLevelSetTileAndData = nullptr; // Level::setTileAndData(int,int,int,int,int,int) + void* pLevelAddToTickNextTick = nullptr; // Level::addToTickNextTick(int,int,int,int,int) + void* pServerLevelAddToTickNextTick = nullptr; // ServerLevel::addToTickNextTick(int,int,int,int,int) private: uintptr_t m_moduleBase = 0; diff --git a/WeaveLoaderRuntime/src/dllmain.cpp b/WeaveLoaderRuntime/src/dllmain.cpp index 6dceac3..9d75989 100644 --- a/WeaveLoaderRuntime/src/dllmain.cpp +++ b/WeaveLoaderRuntime/src/dllmain.cpp @@ -25,6 +25,9 @@ static std::string GetDllDirectory(HMODULE hModule) DWORD WINAPI InitThread(LPVOID lpParam) { LogUtil::Log("[WeaveLoader] InitThread started (module=%p)", g_hModule); +#ifdef WEAVELOADER_DEBUG_BUILD + LogUtil::Log("[WeaveLoader] Loader is running in debug mode"); +#endif std::string baseDir = GetDllDirectory(g_hModule); LogUtil::Log("[WeaveLoader] Runtime DLL directory: %s", baseDir.c_str());