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