From d4f76033909dc372042ec7dc4f6f622fc3cfe79b Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Wed, 11 Mar 2026 19:56:21 -0500 Subject: [PATCH] feat(blockstates): rotation profiles and placement tracking --- ExampleMod/ExampleMod.cs | 2 + .../examplemod/blockstates/ruby_chair.json | 19 + WeaveLoader.API/Assets/ModelResolver.cs | 266 ++++++++++++ WeaveLoader.API/Block/BlockProperties.cs | 20 + WeaveLoader.API/Block/BlockRegistry.cs | 45 +- WeaveLoader.API/NativeInterop.cs | 12 + WeaveLoaderRuntime/src/GameHooks.cpp | 385 +++++++++++++++++- WeaveLoaderRuntime/src/GameHooks.h | 5 + WeaveLoaderRuntime/src/HookManager.cpp | 16 + WeaveLoaderRuntime/src/ModelRegistry.cpp | 66 ++- WeaveLoaderRuntime/src/ModelRegistry.h | 4 + WeaveLoaderRuntime/src/NativeExports.cpp | 10 + WeaveLoaderRuntime/src/NativeExports.h | 8 + .../src/Symbols/SymbolGroups.cpp | 6 + WeaveLoaderRuntime/src/Symbols/SymbolGroups.h | 2 + 15 files changed, 840 insertions(+), 26 deletions(-) create mode 100644 ExampleMod/assets/examplemod/blockstates/ruby_chair.json diff --git a/ExampleMod/ExampleMod.cs b/ExampleMod/ExampleMod.cs index 7d98164..72af762 100644 --- a/ExampleMod/ExampleMod.cs +++ b/ExampleMod/ExampleMod.cs @@ -244,6 +244,8 @@ public class ExampleMod : IMod .Resistance(5f) .Sound(SoundType.Wood) .Model("examplemod:block/ruby_chair") + .BlockState("examplemod:ruby_chair") + .RotationProfile(BlockRotationProfile.Facing) .Name(Text.Translatable("block.examplemod.ruby_chair")) .InCreativeTab(CreativeTab.Decoration)); diff --git a/ExampleMod/assets/examplemod/blockstates/ruby_chair.json b/ExampleMod/assets/examplemod/blockstates/ruby_chair.json new file mode 100644 index 0000000..b8d1687 --- /dev/null +++ b/ExampleMod/assets/examplemod/blockstates/ruby_chair.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=north": { + "model": "examplemod:block/ruby_chair" + }, + "facing=east": { + "model": "examplemod:block/ruby_chair", + "y": 90 + }, + "facing=south": { + "model": "examplemod:block/ruby_chair", + "y": 180 + }, + "facing=west": { + "model": "examplemod:block/ruby_chair", + "y": 270 + } + } +} diff --git a/WeaveLoader.API/Assets/ModelResolver.cs b/WeaveLoader.API/Assets/ModelResolver.cs index 4b63fb0..dfa9779 100644 --- a/WeaveLoader.API/Assets/ModelResolver.cs +++ b/WeaveLoader.API/Assets/ModelResolver.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.Json; namespace WeaveLoader.API.Assets; @@ -43,6 +44,82 @@ internal static class ModelResolver } } + internal static void ApplyBlockStates(Identifier id, Block.BlockProperties properties) + { + if (string.IsNullOrWhiteSpace(ModContext.ModFolder)) + return; + + if (!TryGetBlockStateFilePath(id, properties.BlockStateValue, out string blockStatePath)) + return; + if (!File.Exists(blockStatePath)) + return; + + try + { + using var doc = JsonDocument.Parse(File.ReadAllText(blockStatePath)); + if (!doc.RootElement.TryGetProperty("variants", out JsonElement variants) || + variants.ValueKind != JsonValueKind.Object) + { + return; + } + + var variantProps = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var variant in variants.EnumerateObject()) + { + string key = NormalizeVariantKey(variant.Name); + CollectVariantProps(variant.Name, variantProps); + JsonElement entry = variant.Value; + if (entry.ValueKind == JsonValueKind.Array) + { + if (entry.GetArrayLength() == 0) + continue; + entry = entry[0]; + } + if (entry.ValueKind != JsonValueKind.Object) + continue; + + if (!entry.TryGetProperty("model", out JsonElement modelEl) || + modelEl.ValueKind != JsonValueKind.String) + continue; + + string? modelName = modelEl.GetString(); + if (string.IsNullOrWhiteSpace(modelName)) + continue; + + int xRot = 0; + int yRot = 0; + if (entry.TryGetProperty("x", out JsonElement xEl) && xEl.ValueKind == JsonValueKind.Number) + xRot = xEl.GetInt32(); + if (entry.TryGetProperty("y", out JsonElement yEl) && yEl.ValueKind == JsonValueKind.Number) + yRot = yEl.GetInt32(); + + if (!TryLoadModelFromValue(id, modelName!, ModelKind.Block, out ModelData? model) || model == null) + continue; + + if (string.IsNullOrWhiteSpace(properties.IconValue) && !string.IsNullOrWhiteSpace(model.IconName)) + properties.IconValue = model.IconName; + + var boxes = model.Boxes.Count > 0 + ? RotateBoxes(model.Boxes, xRot, yRot) + : new List(); + + properties.ModelVariants ??= new Dictionary>(StringComparer.OrdinalIgnoreCase); + properties.ModelVariants[key] = boxes; + } + + if (properties.RotationProfileValue == Block.BlockRotationProfile.None) + { + var inferred = InferRotationProfile(variantProps, blockStatePath); + if (inferred != Block.BlockRotationProfile.None) + properties.RotationProfileValue = inferred; + } + } + catch (Exception ex) + { + Logger.Warning($"Failed to parse blockstate JSON '{blockStatePath}': {ex.Message}"); + } + } + internal static void ApplyItemModel(Identifier id, Item.ItemProperties properties) { if (!ShouldResolveModel(properties.ModelValue, properties.IconValue, ModelKind.Item)) @@ -130,6 +207,11 @@ internal static class ModelResolver return !string.IsNullOrWhiteSpace(data.IconName) || data.Boxes.Count > 0; } + private static bool TryLoadModelFromValue(Identifier id, string modelValue, ModelKind kind, out ModelData? model) + { + return TryLoadModel(id, modelValue, kind, out model); + } + private static bool TryGetModelFilePath(Identifier id, string? modelValue, ModelKind kind, out string modelPath, out string modelNamespace) { modelPath = ""; @@ -172,6 +254,81 @@ internal static class ModelResolver return true; } + private static bool TryGetBlockStateFilePath(Identifier id, string? blockStateValue, out string blockStatePath) + { + blockStatePath = ""; + string ns = id.Namespace; + string rel = id.Path; + + if (!string.IsNullOrWhiteSpace(blockStateValue)) + { + string raw = blockStateValue!; + int colon = raw.IndexOf(':'); + if (colon >= 0) + { + ns = raw[..colon]; + rel = raw[(colon + 1)..]; + } + else + { + rel = raw; + } + } + + rel = rel.Replace('\\', '/'); + if (rel.StartsWith("blockstates/", StringComparison.OrdinalIgnoreCase)) + rel = rel["blockstates/".Length..]; + + string modRoot = ModContext.ModFolder ?? ""; + if (string.IsNullOrWhiteSpace(modRoot)) + return false; + + blockStatePath = Path.Combine(modRoot, "assets", ns, "blockstates", rel.Replace('/', Path.DirectorySeparatorChar) + ".json"); + return true; + } + + private static void CollectVariantProps(string rawKey, HashSet props) + { + if (string.IsNullOrWhiteSpace(rawKey)) + return; + + foreach (string part in rawKey.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + int eq = part.IndexOf('='); + if (eq <= 0) + continue; + props.Add(part[..eq]); + } + } + + private static Block.BlockRotationProfile InferRotationProfile(HashSet props, string blockStatePath) + { + bool hasFacing = props.Contains("facing"); + bool hasHalf = props.Contains("half"); + bool hasOpen = props.Contains("open"); + bool hasHinge = props.Contains("hinge"); + bool hasRotation = props.Contains("rotation"); + + if (hasFacing && hasHalf && hasOpen && hasHinge) + return Block.BlockRotationProfile.Door; + if (hasFacing && hasHalf && hasOpen) + return Block.BlockRotationProfile.Trapdoor; + if (hasRotation) + return Block.BlockRotationProfile.StandingSign; + if (hasFacing) + return Block.BlockRotationProfile.Facing; + + string file = Path.GetFileNameWithoutExtension(blockStatePath); + if (file.EndsWith("wall_sign", StringComparison.OrdinalIgnoreCase) || + file.EndsWith("wall_hanging_sign", StringComparison.OrdinalIgnoreCase)) + return Block.BlockRotationProfile.WallSign; + if (file.EndsWith("sign", StringComparison.OrdinalIgnoreCase) || + file.EndsWith("hanging_sign", StringComparison.OrdinalIgnoreCase)) + return Block.BlockRotationProfile.StandingSign; + + return Block.BlockRotationProfile.None; + } + private static bool TryParseTextures(string modelPath, out Dictionary textures) { textures = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -266,6 +423,115 @@ internal static class ModelResolver box.X1 >= 1.0f - eps && box.Y1 >= 1.0f - eps && box.Z1 >= 1.0f - eps; } + private static string NormalizeVariantKey(string raw) + { + if (string.IsNullOrWhiteSpace(raw)) + return ""; + string[] parts = raw.Split(',', StringSplitOptions.RemoveEmptyEntries); + var pairs = new List<(string key, string value)>(); + foreach (string part in parts) + { + int eq = part.IndexOf('='); + if (eq <= 0 || eq >= part.Length - 1) + continue; + string key = part[..eq].Trim().ToLowerInvariant(); + string value = part[(eq + 1)..].Trim().ToLowerInvariant(); + if (key.Length == 0 || value.Length == 0) + continue; + pairs.Add((key, value)); + } + if (pairs.Count == 0) + return ""; + pairs.Sort((a, b) => string.CompareOrdinal(a.key, b.key)); + return string.Join(",", pairs.Select(p => $"{p.key}={p.value}")); + } + + private static List RotateBoxes(List boxes, int xRot, int yRot) + { + int xr = ((xRot % 360) + 360) % 360; + int yr = ((yRot % 360) + 360) % 360; + if (xr == 0 && yr == 0) + return new List(boxes); + + var result = new List(boxes.Count); + foreach (var box in boxes) + { + float x0 = MathF.Min(box.X0, box.X1); + float y0 = MathF.Min(box.Y0, box.Y1); + float z0 = MathF.Min(box.Z0, box.Z1); + float x1 = MathF.Max(box.X0, box.X1); + float y1 = MathF.Max(box.Y0, box.Y1); + float z1 = MathF.Max(box.Z0, box.Z1); + + Span<(float x, float y, float z)> pts = stackalloc (float, float, float)[8]; + int i = 0; + for (int xi = 0; xi < 2; xi++) + for (int yi = 0; yi < 2; yi++) + for (int zi = 0; zi < 2; zi++) + { + float x = xi == 0 ? x0 : x1; + float y = yi == 0 ? y0 : y1; + float z = zi == 0 ? z0 : z1; + (x, y, z) = RotatePoint(x, y, z, xr, yr); + pts[i++] = (x, y, z); + } + + float rx0 = pts[0].x, ry0 = pts[0].y, rz0 = pts[0].z; + float rx1 = pts[0].x, ry1 = pts[0].y, rz1 = pts[0].z; + for (int j = 1; j < pts.Length; j++) + { + var p = pts[j]; + if (p.x < rx0) rx0 = p.x; + if (p.y < ry0) ry0 = p.y; + if (p.z < rz0) rz0 = p.z; + if (p.x > rx1) rx1 = p.x; + if (p.y > ry1) ry1 = p.y; + if (p.z > rz1) rz1 = p.z; + } + + result.Add(new ModelBox { X0 = rx0, Y0 = ry0, Z0 = rz0, X1 = rx1, Y1 = ry1, Z1 = rz1 }); + } + + return result; + } + + private static (float x, float y, float z) RotatePoint(float x, float y, float z, int xRot, int yRot) + { + float px = x; + float py = y; + float pz = z; + + switch (xRot) + { + case 90: + (py, pz) = (1.0f - pz, py); + break; + case 180: + py = 1.0f - py; + pz = 1.0f - pz; + break; + case 270: + (py, pz) = (pz, 1.0f - py); + break; + } + + switch (yRot) + { + case 90: + (px, pz) = (pz, 1.0f - px); + break; + case 180: + px = 1.0f - px; + pz = 1.0f - pz; + break; + case 270: + (px, pz) = (1.0f - pz, px); + break; + } + + return (px, py, pz); + } + private static string? SelectTexture(Dictionary textures, ModelKind kind) { string[] keys = kind == ModelKind.Item diff --git a/WeaveLoader.API/Block/BlockProperties.cs b/WeaveLoader.API/Block/BlockProperties.cs index 2132ad8..ff647a5 100644 --- a/WeaveLoader.API/Block/BlockProperties.cs +++ b/WeaveLoader.API/Block/BlockProperties.cs @@ -52,6 +52,19 @@ public enum SoundType Snow = 9 } +/// +/// Rotation/profile mapping used when resolving blockstate variants. +/// +public enum BlockRotationProfile +{ + None = 0, + Facing = 1, + WallSign = 2, + StandingSign = 3, + Trapdoor = 4, + Door = 5 +} + /// /// Fluent builder for defining block properties. /// @@ -73,7 +86,10 @@ public class BlockProperties internal ToolType RequiredToolValue = ToolType.None; internal bool AcceptsRedstonePowerValue; internal List? ModelBoxes; + internal Dictionary>? ModelVariants; internal bool ModelIsFullCube; + internal BlockRotationProfile RotationProfileValue = BlockRotationProfile.None; + internal string? BlockStateValue; public BlockProperties Material(MaterialType material) { MaterialValue = material; return this; } public BlockProperties Hardness(float hardness) { HardnessValue = hardness; return this; } @@ -106,4 +122,8 @@ public class BlockProperties 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; } + /// Optional blockstate JSON name (e.g. "examplemod:ruby_chair"). + public BlockProperties BlockState(string blockStateName) { BlockStateValue = blockStateName; return this; } + /// Rotation/profile mapping for blockstate variants. + public BlockProperties RotationProfile(BlockRotationProfile profile) { RotationProfileValue = profile; return this; } } diff --git a/WeaveLoader.API/Block/BlockRegistry.cs b/WeaveLoader.API/Block/BlockRegistry.cs index a2a62cb..eb8a5b6 100644 --- a/WeaveLoader.API/Block/BlockRegistry.cs +++ b/WeaveLoader.API/Block/BlockRegistry.cs @@ -51,6 +51,7 @@ public static class BlockRegistry public static RegisteredBlock Register(Identifier id, BlockProperties properties) { Assets.ModelResolver.ApplyBlockModel(id, properties); + Assets.ModelResolver.ApplyBlockStates(id, properties); ApplyModelLightDefaults(properties); int numericId = NativeInterop.native_register_block( id.ToString(), @@ -72,10 +73,7 @@ public static class BlockRegistry throw new InvalidOperationException($"Failed to register block '{id}'. No free IDs or invalid parameters."); } - if (properties.ModelBoxes != null && properties.ModelBoxes.Count > 0) - { - NativeInterop.native_register_block_model(numericId, properties.ModelBoxes.ToArray(), properties.ModelBoxes.Count); - } + RegisterBlockModels(numericId, properties); AddToCreative(id, numericId, properties); @@ -96,6 +94,7 @@ public static class BlockRegistry return RegisterFalling(id, managedBlock, properties); Assets.ModelResolver.ApplyBlockModel(id, properties); + Assets.ModelResolver.ApplyBlockStates(id, properties); ApplyModelLightDefaults(properties); int numericId = NativeInterop.native_register_managed_block( id.ToString(), @@ -117,10 +116,7 @@ public static class BlockRegistry throw new InvalidOperationException($"Failed to register managed block '{id}'."); } - if (properties.ModelBoxes != null && properties.ModelBoxes.Count > 0) - { - NativeInterop.native_register_block_model(numericId, properties.ModelBoxes.ToArray(), properties.ModelBoxes.Count); - } + RegisterBlockModels(numericId, properties); AddToCreative(id, numericId, properties); @@ -149,6 +145,7 @@ public static class BlockRegistry private static RegisteredBlock RegisterFalling(Identifier id, Block? managedBlock, BlockProperties properties) { Assets.ModelResolver.ApplyBlockModel(id, properties); + Assets.ModelResolver.ApplyBlockStates(id, properties); ApplyModelLightDefaults(properties); int numericId = NativeInterop.native_register_falling_block( id.ToString(), @@ -170,10 +167,7 @@ public static class BlockRegistry throw new InvalidOperationException($"Failed to register falling block '{id}'."); } - if (properties.ModelBoxes != null && properties.ModelBoxes.Count > 0) - { - NativeInterop.native_register_block_model(numericId, properties.ModelBoxes.ToArray(), properties.ModelBoxes.Count); - } + RegisterBlockModels(numericId, properties); AddToCreative(id, numericId, properties); @@ -202,6 +196,7 @@ public static class BlockRegistry public static RegisteredSlabBlock RegisterSlab(Identifier id, BlockProperties properties) { Assets.ModelResolver.ApplyBlockModel(id, properties); + Assets.ModelResolver.ApplyBlockStates(id, properties); ApplyModelLightDefaults(properties); Identifier doubleId = new($"{id}_double"); int numericId = NativeInterop.native_register_slab_block( @@ -230,10 +225,7 @@ public static class BlockRegistry throw new InvalidOperationException($"Failed to resolve generated slab pair '{doubleId}'."); } - if (properties.ModelBoxes != null && properties.ModelBoxes.Count > 0) - { - NativeInterop.native_register_block_model(numericId, properties.ModelBoxes.ToArray(), properties.ModelBoxes.Count); - } + RegisterBlockModels(numericId, properties); AddToCreative(id, numericId, properties); @@ -257,6 +249,27 @@ public static class BlockRegistry if (!properties.ModelIsFullCube) properties.LightBlockValue = 0; } + + private static void RegisterBlockModels(int numericId, BlockProperties properties) + { + if (properties.ModelBoxes != null && properties.ModelBoxes.Count > 0) + { + NativeInterop.native_register_block_model(numericId, properties.ModelBoxes.ToArray(), properties.ModelBoxes.Count); + } + + if (properties.ModelVariants != null) + { + foreach (var variant in properties.ModelVariants) + { + var boxes = variant.Value; + if (boxes.Count == 0) + continue; + NativeInterop.native_register_block_model_variant(numericId, variant.Key, boxes.ToArray(), boxes.Count); + } + } + + NativeInterop.native_register_block_rotation_profile(numericId, (int)properties.RotationProfileValue); + } internal static bool TryGetIdentifier(int numericId, out Identifier id) { lock (s_lock) diff --git a/WeaveLoader.API/NativeInterop.cs b/WeaveLoader.API/NativeInterop.cs index 7c532e9..58d2b60 100644 --- a/WeaveLoader.API/NativeInterop.cs +++ b/WeaveLoader.API/NativeInterop.cs @@ -77,6 +77,18 @@ internal static class NativeInterop [In] Assets.ModelBox[] boxes, int count); + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + internal static extern void native_register_block_model_variant( + int blockId, + string key, + [In] Assets.ModelBox[] boxes, + int count); + + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern void native_register_block_rotation_profile( + int blockId, + int profile); + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern int native_register_item( string namespacedId, diff --git a/WeaveLoaderRuntime/src/GameHooks.cpp b/WeaveLoaderRuntime/src/GameHooks.cpp index 64da674..842ab7d 100644 --- a/WeaveLoaderRuntime/src/GameHooks.cpp +++ b/WeaveLoaderRuntime/src/GameHooks.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ namespace GameHooks { + static bool IsReadableRange(const void* ptr, size_t bytes); RunStaticCtors_fn Original_RunStaticCtors = nullptr; MinecraftTick_fn Original_MinecraftTick = nullptr; MinecraftInit_fn Original_MinecraftInit = nullptr; @@ -56,6 +58,7 @@ namespace GameHooks TextureGetSourceDim_fn Original_ClockTextureGetSourceWidth = nullptr; TextureGetSourceDim_fn Original_ClockTextureGetSourceHeight = nullptr; ItemInstanceMineBlock_fn Original_ItemInstanceMineBlock = nullptr; + ItemInstanceUseOn_fn Original_ItemInstanceUseOn = nullptr; ItemInstanceSave_fn Original_ItemInstanceSave = nullptr; ItemInstanceLoad_fn Original_ItemInstanceLoad = nullptr; ItemMineBlock_fn Original_ItemMineBlock = nullptr; @@ -70,6 +73,7 @@ namespace GameHooks LevelSetTileAndDataDispatch_fn Original_LevelSetTileAndData = nullptr; LevelSetDataDispatch_fn Original_LevelSetData = nullptr; LevelUpdateNeighborsAtDispatch_fn Original_LevelUpdateNeighborsAt = nullptr; + LevelGetData_fn Level_GetData = nullptr; ServerLevelTickPendingTicks_fn Original_ServerLevelTickPendingTicks = nullptr; TileGetResource_fn Original_TileGetResource = nullptr; McRegionChunkStorageLoad_fn Original_McRegionChunkStorageLoad = nullptr; @@ -241,6 +245,11 @@ namespace GameHooks static std::atomic s_tileIdOffsetTried{false}; static int s_tileIdOffset = -1; + static std::atomic s_tileRendererLevelOffsetTried{false}; + static int s_tileRendererLevelOffset = -1; + static std::atomic s_entityYawOffsetTried{false}; + static int s_entityYawOffset = -1; + static int s_rotationLogCount = 0; static bool ReadFileToString(const char* path, std::string& out) { @@ -332,6 +341,71 @@ namespace GameHooks return true; } + static bool TryResolveTileRendererLevelOffset() + { + bool expected = false; + if (!s_tileRendererLevelOffsetTried.compare_exchange_strong(expected, true)) + return s_tileRendererLevelOffset >= 0; + + const char* baseDir = LogUtil::GetBaseDir(); + if (!baseDir || baseDir[0] == '\0') + return false; + + std::string json; + std::string path = std::string(baseDir) + "metadata\\offsets.json"; + if (!ReadFileToString(path.c_str(), json)) + { + path = std::string(baseDir) + "offsets.json"; + if (!ReadFileToString(path.c_str(), json)) + return false; + } + + int offset = -1; + if (!ExtractOffsetForField(json, "TileRenderer", "level", offset)) + return false; + if (offset <= 0) + return false; + + s_tileRendererLevelOffset = offset; + LogUtil::Log("[WeaveLoader] ModelRegistry: TileRenderer.level offset = 0x%X", s_tileRendererLevelOffset); + return true; + } + + static bool TryResolveEntityYawOffset() + { + bool expected = false; + if (!s_entityYawOffsetTried.compare_exchange_strong(expected, true)) + return s_entityYawOffset >= 0; + + const char* baseDir = LogUtil::GetBaseDir(); + if (!baseDir || baseDir[0] == '\0') + return false; + + std::string json; + std::string path = std::string(baseDir) + "metadata\\offsets.json"; + if (!ReadFileToString(path.c_str(), json)) + { + path = std::string(baseDir) + "offsets.json"; + if (!ReadFileToString(path.c_str(), json)) + return false; + } + + int offset = -1; + if (!ExtractOffsetForField(json, "Entity", "yRot", offset)) + { + if (s_rotationLogCount < 20) + { + LogUtil::Log("[WeaveLoader] Rotation: failed to read Entity.yRot offset"); + s_rotationLogCount++; + } + return false; + } + + s_entityYawOffset = offset; + LogUtil::Log("[WeaveLoader] ModelRegistry: Entity.yRot offset = 0x%X", s_entityYawOffset); + return true; + } + int GetTileId(void* tilePtr) { if (!tilePtr) @@ -341,6 +415,176 @@ namespace GameHooks return *reinterpret_cast(reinterpret_cast(tilePtr) + s_tileIdOffset); } + void* GetTileRendererLevel(void* rendererPtr) + { + if (!rendererPtr) + return nullptr; + if (s_tileRendererLevelOffset < 0 && !TryResolveTileRendererLevelOffset()) + return nullptr; + if (s_tileRendererLevelOffset <= 0) + return nullptr; + return *reinterpret_cast(reinterpret_cast(rendererPtr) + s_tileRendererLevelOffset); + } + + static bool TryGetEntityYaw(void* entityPtr, float& outYaw) + { + if (!entityPtr) + return false; + if (s_entityYawOffset < 0 && !TryResolveEntityYawOffset()) + return false; + + const char* base = reinterpret_cast(entityPtr) + s_entityYawOffset; + if (!IsReadableRange(base, sizeof(float))) + return false; + + outYaw = *reinterpret_cast(base); + return true; + } + + static const char* FacingFromDoorDir(int dir) + { + switch (dir & 3) + { + case 0: return "east"; + case 1: return "south"; + case 2: return "west"; + default: return "north"; + } + } + + static const char* FacingFromFacingDir(int dir) + { + switch (dir & 3) + { + case 0: return "east"; + case 1: return "north"; + case 2: return "west"; + default: return "south"; + } + } + + static const char* FacingFromTrapdoorDir(int dir) + { + switch (dir & 3) + { + case 0: return "north"; + case 1: return "south"; + case 2: return "west"; + default: return "east"; + } + } + + static const char* FacingFromWallSignFace(int face) + { + switch (face) + { + case 2: return "north"; + case 3: return "south"; + case 4: return "west"; + case 5: return "east"; + default: return "north"; + } + } + + struct StateProp + { + std::string name; + std::string value; + }; + + static void AppendProp(std::vector& props, const char* name, const char* value) + { + props.push_back(StateProp{ std::string(name), std::string(value) }); + } + + static int GetDoorCompositeData(void* levelPtr, int x, int y, int z, int data) + { + if (!levelPtr || !Level_GetData) + return data; + + const bool isUpper = (data & 8) != 0; + const int lowerData = isUpper ? Level_GetData(levelPtr, x, y - 1, z) : data; + const int upperData = isUpper ? data : Level_GetData(levelPtr, x, y + 1, z); + const bool rightHinge = (upperData & 1) != 0; + return (lowerData & 7) | (isUpper ? 8 : 0) | (rightHinge ? 16 : 0); + } + + static std::string BuildStateKey(int profile, int data, void* levelPtr, int x, int y, int z) + { + std::vector props; + data &= 15; + switch (profile) + { + case 1: // Facing + { + AppendProp(props, "facing", FacingFromFacingDir(data)); + break; + } + case 2: // WallSign + { + AppendProp(props, "facing", FacingFromWallSignFace(data)); + break; + } + case 3: // StandingSign + { + int rot = data & 15; + std::string rotStr = std::to_string(rot); + AppendProp(props, "rotation", rotStr.c_str()); + break; + } + case 4: // Trapdoor + { + AppendProp(props, "facing", FacingFromTrapdoorDir(data)); + AppendProp(props, "half", (data & 8) != 0 ? "top" : "bottom"); + AppendProp(props, "open", (data & 4) != 0 ? "true" : "false"); + break; + } + case 5: // Door + { + const int composite = GetDoorCompositeData(levelPtr, x, y, z, data); + AppendProp(props, "facing", FacingFromDoorDir(composite)); + AppendProp(props, "half", (composite & 8) != 0 ? "upper" : "lower"); + AppendProp(props, "hinge", (composite & 16) != 0 ? "right" : "left"); + AppendProp(props, "open", (composite & 4) != 0 ? "true" : "false"); + break; + } + default: + break; + } + + if (props.empty()) + return std::string(); + + std::sort(props.begin(), props.end(), [](const StateProp& a, const StateProp& b) + { + return a.name < b.name; + }); + + std::string key; + for (size_t i = 0; i < props.size(); ++i) + { + if (i > 0) + key.push_back(','); + key.append(props[i].name); + key.push_back('='); + key.append(props[i].value); + } + return key; + } + + static bool TryGetModelForState(int blockId, int data, void* levelPtr, int x, int y, int z, const std::vector*& outBoxes) + { + if (data < 0) + data = 0; + const int profile = ModelRegistry::GetRotationProfile(blockId); + const std::string key = profile != 0 ? BuildStateKey(profile, data, levelPtr, x, y, z) : std::string(); + if (!key.empty() && ModelRegistry::TryGetModelVariant(blockId, key.c_str(), outBoxes)) + return true; + if (ModelRegistry::TryGetModelVariant(blockId, "", outBoxes)) + return true; + return ModelRegistry::TryGetModel(blockId, outBoxes); + } + bool RenderModelInWorld(void* renderer, void* tilePtr, int x, int y, int z, const std::vector& boxes) { if (!renderer || !tilePtr || boxes.empty()) @@ -395,6 +639,9 @@ namespace GameHooks static int s_itemMineBlockHookCalls = 0; static void* s_currentLevel = nullptr; static thread_local void* s_activeUseLevel = nullptr; + static thread_local void* s_lastUsePlayer = nullptr; + static thread_local void* s_lastUseLevel = nullptr; + static thread_local ULONGLONG s_lastUseTimeMs = 0; static LevelAddEntity_fn s_levelAddEntity = nullptr; static EntityIONewById_fn s_entityIoNewById = nullptr; static EntityMoveTo_fn s_entityMoveTo = nullptr; @@ -1631,6 +1878,30 @@ namespace GameHooks DispatchManagedBlockUpdate(thisPtr, level, x, y, z, 2, 0); } + static bool TryGetPlacementPlayer(void* levelPtr, void*& outPlayerPtr) + { + (void)levelPtr; + if (!s_lastUsePlayer) + return false; + + const ULONGLONG nowMs = GetTickCount64(); + if (nowMs < s_lastUseTimeMs || nowMs - s_lastUseTimeMs > 500) + return false; + + outPlayerPtr = s_lastUsePlayer; + return true; + } + + static int ComputeFacingDataFromYaw(float yaw) + { + return static_cast(std::floor(((yaw + 180.0f) * 4.0f) / 360.0f - 0.5f)) & 3; + } + + static int ComputeStandingSignDataFromYaw(float yaw) + { + return static_cast(std::floor(((yaw + 180.0f) * 16.0f) / 360.0f + 0.5f)) & 15; + } + bool __fastcall Hooked_LevelSetTileAndData(void* thisPtr, int x, int y, int z, int tile, int data, int updateFlags) { const int oldBlockId = s_levelGetTile ? s_levelGetTile(thisPtr, x, y, z) : -1; @@ -1644,6 +1915,51 @@ namespace GameHooks if (result && tile > 0) DispatchManagedBlockById(tile, thisPtr, x, y, z, 0, 0); + if (result && tile > 0 && Original_LevelSetData) + { + const int profile = ModelRegistry::GetRotationProfile(tile); + if (profile == 1 || profile == 3) + { + if (s_rotationLogCount < 20) + { + LogUtil::Log("[WeaveLoader] Rotation: tile=%d profile=%d data=%d pos=(%d,%d,%d)", tile, profile, data, x, y, z); + s_rotationLogCount++; + } + void* playerPtr = nullptr; + if (TryGetPlacementPlayer(thisPtr, playerPtr)) + { + float yaw = 0.0f; + if (TryGetEntityYaw(playerPtr, yaw)) + { + int newData = data; + if (profile == 1) + newData = ComputeFacingDataFromYaw(yaw); + else + newData = ComputeStandingSignDataFromYaw(yaw); + + if (newData != data) + Original_LevelSetData(thisPtr, x, y, z, newData, updateFlags, false); + + if (s_rotationLogCount < 20) + { + LogUtil::Log("[WeaveLoader] Rotation: yaw=%.2f data=%d -> %d", yaw, data, newData); + s_rotationLogCount++; + } + } + else if (s_rotationLogCount < 20) + { + LogUtil::Log("[WeaveLoader] Rotation: no yaw (offsets loaded=%s)", s_entityYawOffset >= 0 ? "true" : "false"); + s_rotationLogCount++; + } + } + else if (s_rotationLogCount < 20) + { + LogUtil::Log("[WeaveLoader] Rotation: no placement player (lastUse=%p)", s_lastUsePlayer); + s_rotationLogCount++; + } + } + } + return result; } @@ -2408,6 +2724,24 @@ namespace GameHooks Original_ItemInstanceMineBlock(thisPtr, level, tile, x, y, z, ownerSharedPtr); } + bool __fastcall Hooked_ItemInstanceUseOn(void* thisPtr, void* playerSharedPtr, void* level, int x, int y, int z, int face, float clickX, float clickY, float clickZ, bool bTestUseOnOnly) + { + if (!bTestUseOnOnly) + { + void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr); + if (playerPtr) + { + s_lastUsePlayer = playerPtr; + s_lastUseLevel = level; + s_lastUseTimeMs = GetTickCount64(); + } + } + + if (Original_ItemInstanceUseOn) + return Original_ItemInstanceUseOn(thisPtr, playerSharedPtr, level, x, y, z, face, clickX, clickY, clickZ, bTestUseOnOnly); + return false; + } + void* __fastcall Hooked_ItemInstanceSave(void* thisPtr, void* compoundTagPtr) { // Namespace marker now lives on ItemInstance::tag, so it must be present @@ -2699,6 +3033,16 @@ namespace GameHooks s_pendingServerUseItemId = -1; s_pendingServerUseExpiryMs = 0; } + if (!effectiveTestUseOnly) + { + void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr); + if (playerPtr) + { + s_lastUsePlayer = playerPtr; + s_lastUseLevel = level; + s_lastUseTimeMs = nowMs; + } + } if (s_serverUseLogCount < 40) { LogUtil::Log("[WeaveLoader] UseHook ServerPlayerGameMode::useItem test=%d effective=%d item=%d itemPtr=%p playerShared=%p level=%p", @@ -2726,6 +3070,16 @@ namespace GameHooks s_pendingServerUseItemId = itemId; s_pendingServerUseExpiryMs = GetTickCount64() + 1000; } + if (!bTestUseOnly) + { + void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr); + if (playerPtr) + { + s_lastUsePlayer = playerPtr; + s_lastUseLevel = level; + s_lastUseTimeMs = GetTickCount64(); + } + } if (s_multiUseLogCount < 40) { LogUtil::Log("[WeaveLoader] UseHook MultiPlayerGameMode::useItem test=%d item=%d itemPtr=%p playerShared=%p level=%p", @@ -3027,10 +3381,24 @@ namespace GameHooks { const std::vector* boxes = nullptr; int tileId = GetTileId(tilePtr); - if (tileId >= 0 && ModelRegistry::TryGetModel(tileId, boxes) && boxes && !boxes->empty()) + if (tileId >= 0) { - if (RenderModelInWorld(thisPtr, tilePtr, x, y, z, *boxes)) - return true; + int data = forceData; + void* levelPtr = nullptr; + if (data < 0 || ModelRegistry::GetRotationProfile(tileId) != 0) + { + levelPtr = GetTileRendererLevel(thisPtr); + if (!levelPtr && s_currentLevel) + levelPtr = s_currentLevel; + if (data < 0 && levelPtr && Level_GetData) + data = Level_GetData(levelPtr, x, y, z); + } + + if (TryGetModelForState(tileId, data, levelPtr, x, y, z, boxes) && boxes && !boxes->empty()) + { + if (RenderModelInWorld(thisPtr, tilePtr, x, y, z, *boxes)) + return true; + } } return Original_TileRendererTesselateInWorld @@ -3042,7 +3410,7 @@ namespace GameHooks { const std::vector* boxes = nullptr; int tileId = GetTileId(tilePtr); - if (tileId >= 0 && ModelRegistry::TryGetModel(tileId, boxes) && boxes && !boxes->empty()) + if (tileId >= 0 && TryGetModelForState(tileId, data, nullptr, 0, 0, 0, boxes) && boxes && !boxes->empty()) { if (Original_TileRendererRenderTile && Tile_SetShape) { @@ -3068,7 +3436,8 @@ namespace GameHooks { const std::vector* boxes = nullptr; int tileId = GetTileId(thisPtr); - if (tileId >= 0 && ModelRegistry::TryGetModel(tileId, boxes) && boxes && !boxes->empty() && AABB_NewTemp && boxesPtr && boxPtr) + int data = Level_GetData && levelPtr ? Level_GetData(levelPtr, x, y, z) : 0; + if (tileId >= 0 && TryGetModelForState(tileId, data, levelPtr, x, y, z, boxes) && boxes && !boxes->empty() && AABB_NewTemp && boxesPtr && boxPtr) { auto list = reinterpret_cast*>(boxesPtr); const AABBRaw* clipBox = reinterpret_cast(boxPtr); @@ -3197,7 +3566,8 @@ namespace GameHooks const std::vector* boxes = nullptr; int tileId = GetTileId(thisPtr); - if (tileId < 0 || !ModelRegistry::TryGetModel(tileId, boxes) || !boxes || boxes->empty()) + int data = Level_GetData && levelPtr ? Level_GetData(levelPtr, x, y, z) : 0; + if (tileId < 0 || !TryGetModelForState(tileId, data, levelPtr, x, y, z, boxes) || !boxes || boxes->empty()) { return Original_TileClip ? Original_TileClip(thisPtr, levelPtr, x, y, z, aPtr, bPtr) : nullptr; } @@ -3280,7 +3650,8 @@ namespace GameHooks continue; const std::vector* boxes = nullptr; - if (!ModelRegistry::TryGetModel(tileId, boxes) || !boxes || boxes->empty()) + int data = Level_GetData ? Level_GetData(thisPtr, x, y, z) : 0; + if (!TryGetModelForState(tileId, data, thisPtr, x, y, z, boxes) || !boxes || boxes->empty()) continue; for (const auto& box : *boxes) diff --git a/WeaveLoaderRuntime/src/GameHooks.h b/WeaveLoaderRuntime/src/GameHooks.h index d239bb2..bf7e55e 100644 --- a/WeaveLoaderRuntime/src/GameHooks.h +++ b/WeaveLoaderRuntime/src/GameHooks.h @@ -25,6 +25,7 @@ typedef void (__fastcall *ItemRendererRenderItemBillboard_fn)(void* thisPtr, voi typedef void (__fastcall *AnimatedTextureCycleFrames_fn)(void* thisPtr); typedef int (__fastcall *TextureGetSourceDim_fn)(void* thisPtr); typedef void (__fastcall *ItemInstanceMineBlock_fn)(void* thisPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr); +typedef bool (__fastcall *ItemInstanceUseOn_fn)(void* thisPtr, void* playerSharedPtr, void* level, int x, int y, int z, int face, float clickX, float clickY, float clickZ, bool bTestUseOnOnly); typedef void* (__fastcall *ItemInstanceSave_fn)(void* thisPtr, void* compoundTagPtr); typedef void (__fastcall *ItemInstanceLoad_fn)(void* thisPtr, void* compoundTagPtr); typedef bool (__fastcall *ItemMineBlock_fn)(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr); @@ -38,6 +39,7 @@ typedef bool (__fastcall *LevelSetDataDispatch_fn)(void* thisPtr, int x, int y, 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 *LevelGetData_fn)(void* thisPtr, int x, int y, int z); typedef void* (__fastcall *McRegionChunkStorageLoad_fn)(void* thisPtr, void* level, int x, int z); typedef void (__fastcall *McRegionChunkStorageSave_fn)(void* thisPtr, void* level, void* levelChunk); typedef int (__fastcall *TileGetResource_fn)(void* thisPtr, int data, void* random, int playerBonusLevel); @@ -115,6 +117,7 @@ namespace GameHooks extern TextureGetSourceDim_fn Original_ClockTextureGetSourceWidth; extern TextureGetSourceDim_fn Original_ClockTextureGetSourceHeight; extern ItemInstanceMineBlock_fn Original_ItemInstanceMineBlock; + extern ItemInstanceUseOn_fn Original_ItemInstanceUseOn; extern ItemInstanceSave_fn Original_ItemInstanceSave; extern ItemInstanceLoad_fn Original_ItemInstanceLoad; extern ItemMineBlock_fn Original_ItemMineBlock; @@ -129,6 +132,7 @@ namespace GameHooks extern LevelSetTileAndDataDispatch_fn Original_LevelSetTileAndData; extern LevelSetDataDispatch_fn Original_LevelSetData; extern LevelUpdateNeighborsAtDispatch_fn Original_LevelUpdateNeighborsAt; + extern LevelGetData_fn Level_GetData; extern ServerLevelTickPendingTicks_fn Original_ServerLevelTickPendingTicks; extern TileGetResource_fn Original_TileGetResource; extern McRegionChunkStorageLoad_fn Original_McRegionChunkStorageLoad; @@ -206,6 +210,7 @@ namespace GameHooks int __fastcall Hooked_ClockTextureGetSourceWidth(void* thisPtr); int __fastcall Hooked_ClockTextureGetSourceHeight(void* thisPtr); void __fastcall Hooked_ItemInstanceMineBlock(void* thisPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr); + bool __fastcall Hooked_ItemInstanceUseOn(void* thisPtr, void* playerSharedPtr, void* level, int x, int y, int z, int face, float clickX, float clickY, float clickZ, bool bTestUseOnOnly); void* __fastcall Hooked_ItemInstanceSave(void* thisPtr, void* compoundTagPtr); void __fastcall Hooked_ItemInstanceLoad(void* thisPtr, void* compoundTagPtr); bool __fastcall Hooked_ItemMineBlock(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr); diff --git a/WeaveLoaderRuntime/src/HookManager.cpp b/WeaveLoaderRuntime/src/HookManager.cpp index 1764b73..ca903ca 100644 --- a/WeaveLoaderRuntime/src/HookManager.cpp +++ b/WeaveLoaderRuntime/src/HookManager.cpp @@ -45,6 +45,8 @@ bool HookManager::Install(const SymbolResolver& symbols) reinterpret_cast(symbols.Tile.pVec3NewTemp); GameHooks::HitResult_Ctor = reinterpret_cast(symbols.Tile.pHitResultCtor); + GameHooks::Level_GetData = + reinterpret_cast(symbols.Level.pLevelGetData); if (symbols.Core.pRunStaticCtors) { @@ -110,6 +112,20 @@ bool HookManager::Install(const SymbolResolver& symbols) } } + if (symbols.Item.pItemInstanceUseOn) + { + if (MH_CreateHook(symbols.Item.pItemInstanceUseOn, + reinterpret_cast(&GameHooks::Hooked_ItemInstanceUseOn), + reinterpret_cast(&GameHooks::Original_ItemInstanceUseOn)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemInstance::useOn"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked ItemInstance::useOn (placement tracking)"); + } + } + if (symbols.Item.pItemInstanceSave) { if (MH_CreateHook(symbols.Item.pItemInstanceSave, diff --git a/WeaveLoaderRuntime/src/ModelRegistry.cpp b/WeaveLoaderRuntime/src/ModelRegistry.cpp index 0ce4750..ea0a39a 100644 --- a/WeaveLoaderRuntime/src/ModelRegistry.cpp +++ b/WeaveLoaderRuntime/src/ModelRegistry.cpp @@ -2,10 +2,18 @@ #include "LogUtil.h" #include #include +#include namespace { - std::unordered_map> g_models; + struct BlockModelEntry + { + std::vector base; + std::unordered_map> variants; + int rotationProfile = 0; + }; + + std::unordered_map g_models; std::mutex g_mutex; } @@ -21,18 +29,70 @@ void ModelRegistry::RegisterBlockModel(int blockId, const ModelBox* boxes, int c { std::lock_guard guard(g_mutex); - g_models[blockId] = std::move(data); + g_models[blockId].base = std::move(data); } LogUtil::Log("[WeaveLoader] ModelRegistry: registered %d box(es) for block %d", count, blockId); } +void ModelRegistry::RegisterBlockModelVariant(int blockId, const char* key, const ModelBox* boxes, int count) +{ + if (blockId < 0 || !key || !boxes || count <= 0) + return; + + std::vector data; + data.reserve(static_cast(count)); + for (int i = 0; i < count; i++) + data.push_back(boxes[i]); + + { + std::lock_guard guard(g_mutex); + g_models[blockId].variants[std::string(key)] = std::move(data); + } + + LogUtil::Log("[WeaveLoader] ModelRegistry: registered variant '%s' (%d box(es)) for block %d", key, count, blockId); +} + +void ModelRegistry::SetRotationProfile(int blockId, int profile) +{ + if (blockId < 0) + return; + std::lock_guard guard(g_mutex); + g_models[blockId].rotationProfile = profile; +} + +int ModelRegistry::GetRotationProfile(int blockId) +{ + std::lock_guard guard(g_mutex); + auto it = g_models.find(blockId); + if (it == g_models.end()) + return 0; + return it->second.rotationProfile; +} + bool ModelRegistry::TryGetModel(int blockId, const std::vector*& outBoxes) { std::lock_guard guard(g_mutex); auto it = g_models.find(blockId); if (it == g_models.end()) return false; - outBoxes = &it->second; + if (it->second.base.empty()) + return false; + outBoxes = &it->second.base; + return true; +} + +bool ModelRegistry::TryGetModelVariant(int blockId, const char* key, const std::vector*& outBoxes) +{ + if (!key) + return false; + std::lock_guard guard(g_mutex); + auto it = g_models.find(blockId); + if (it == g_models.end()) + return false; + auto vit = it->second.variants.find(std::string(key)); + if (vit == it->second.variants.end()) + return false; + outBoxes = &vit->second; return true; } diff --git a/WeaveLoaderRuntime/src/ModelRegistry.h b/WeaveLoaderRuntime/src/ModelRegistry.h index 671250c..8745b77 100644 --- a/WeaveLoaderRuntime/src/ModelRegistry.h +++ b/WeaveLoaderRuntime/src/ModelRegistry.h @@ -15,5 +15,9 @@ struct ModelBox namespace ModelRegistry { void RegisterBlockModel(int blockId, const ModelBox* boxes, int count); + void RegisterBlockModelVariant(int blockId, const char* key, const ModelBox* boxes, int count); + void SetRotationProfile(int blockId, int profile); + int GetRotationProfile(int blockId); bool TryGetModel(int blockId, const std::vector*& outBoxes); + bool TryGetModelVariant(int blockId, const char* key, const std::vector*& outBoxes); } diff --git a/WeaveLoaderRuntime/src/NativeExports.cpp b/WeaveLoaderRuntime/src/NativeExports.cpp index bff677a..d7489b7 100644 --- a/WeaveLoaderRuntime/src/NativeExports.cpp +++ b/WeaveLoaderRuntime/src/NativeExports.cpp @@ -312,6 +312,16 @@ void native_register_block_model(int blockId, const ModelBox* boxes, int count) ModelRegistry::RegisterBlockModel(blockId, boxes, count); } +void native_register_block_model_variant(int blockId, const char* key, const ModelBox* boxes, int count) +{ + ModelRegistry::RegisterBlockModelVariant(blockId, key, boxes, count); +} + +void native_register_block_rotation_profile(int blockId, int profile) +{ + ModelRegistry::SetRotationProfile(blockId, profile); +} + void native_configure_managed_block(int numericBlockId, int dropNumericBlockId, int cloneNumericBlockId) { if (numericBlockId < 0) diff --git a/WeaveLoaderRuntime/src/NativeExports.h b/WeaveLoaderRuntime/src/NativeExports.h index 4d606cc..ff86cdc 100644 --- a/WeaveLoaderRuntime/src/NativeExports.h +++ b/WeaveLoaderRuntime/src/NativeExports.h @@ -96,6 +96,14 @@ extern "C" int blockId, const ModelBox* boxes, int count); + __declspec(dllexport) void native_register_block_model_variant( + int blockId, + const char* key, + const ModelBox* boxes, + int count); + __declspec(dllexport) void native_register_block_rotation_profile( + int blockId, + int profile); __declspec(dllexport) int native_register_item( const char* namespacedId, diff --git a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp index 0c0001b..6b655ab 100644 --- a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp +++ b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp @@ -63,6 +63,7 @@ namespace static const char* SYM_ITEMINSTANCE_GETICON = "?getIcon@ItemInstance@@QEAAPEAVIcon@@XZ"; static const char* SYM_ITEMINSTANCE_MINEBLOCK = "?mineBlock@ItemInstance@@QEAAXPEAVLevel@@HHHHV?$shared_ptr@VPlayer@@@std@@@Z"; + static const char* SYM_ITEMINSTANCE_USEON = "?useOn@ItemInstance@@QEAA_NV?$shared_ptr@VPlayer@@@std@@PEAVLevel@@HHHHMMM_N@Z"; static const char* SYM_ITEMINSTANCE_SAVE = "?save@ItemInstance@@QEAAPEAVCompoundTag@@PEAV2@@Z"; static const char* SYM_ITEMINSTANCE_LOAD = "?load@ItemInstance@@QEAAXPEAVCompoundTag@@@Z"; static const char* SYM_ITEMINSTANCE_HURTANDBREAK = "?hurtAndBreak@ItemInstance@@QEAAXHV?$shared_ptr@VLivingEntity@@@std@@@Z"; @@ -112,6 +113,7 @@ namespace 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_GETDATA = "?getData@Level@@UEAAHHHH@Z"; static const char* SYM_LEVEL_SETDATA = "?setData@Level@@UEAA_NHHHHH_N@Z"; static const char* SYM_LEVEL_CLIP = "?clip@Level@@QEAAPEAVHitResult@@PEAVVec3@@0_N1@Z"; static const char* SYM_MCREGIONCHUNKSTORAGE_LOAD = "?load@McRegionChunkStorage@@UEAAPEAVLevelChunk@@PEAVLevel@@HH@Z"; @@ -296,6 +298,7 @@ bool ItemSymbols::Resolve(SymbolResolver& resolver) { pItemInstanceGetIcon = resolver.Resolve(SYM_ITEMINSTANCE_GETICON); pItemInstanceMineBlock = resolver.Resolve(SYM_ITEMINSTANCE_MINEBLOCK); + pItemInstanceUseOn = resolver.Resolve(SYM_ITEMINSTANCE_USEON); pItemInstanceSave = resolver.Resolve(SYM_ITEMINSTANCE_SAVE); pItemInstanceLoad = resolver.Resolve(SYM_ITEMINSTANCE_LOAD); pItemInstanceHurtAndBreak = resolver.Resolve(SYM_ITEMINSTANCE_HURTANDBREAK); @@ -314,6 +317,7 @@ void ItemSymbols::Log() const { LogSym("ItemInstance::getIcon", pItemInstanceGetIcon); LogSym("ItemInstance::mineBlock", pItemInstanceMineBlock); + LogSym("ItemInstance::useOn", pItemInstanceUseOn); LogSym("ItemInstance::save", pItemInstanceSave); LogSym("ItemInstance::load", pItemInstanceLoad); LogSym("ItemInstance::hurtAndBreak", pItemInstanceHurtAndBreak); @@ -420,6 +424,7 @@ bool LevelSymbols::Resolve(SymbolResolver& resolver) pLevelUpdateNeighborsAt = resolver.Resolve(SYM_LEVEL_UPDATE_NEIGHBORS_AT); pServerLevelTickPendingTicks = resolver.Resolve(SYM_SERVERLEVEL_TICKPENDINGTICKS); pLevelGetTile = resolver.Resolve(SYM_LEVEL_GETTILE); + pLevelGetData = resolver.Resolve(SYM_LEVEL_GETDATA); pLevelSetData = resolver.Resolve(SYM_LEVEL_SETDATA); pLevelClip = resolver.Resolve(SYM_LEVEL_CLIP); pMcRegionChunkStorageLoad = resolver.Resolve(SYM_MCREGIONCHUNKSTORAGE_LOAD); @@ -445,6 +450,7 @@ void LevelSymbols::Log() const LogSym("Level::updateNeighborsAt", pLevelUpdateNeighborsAt); LogSym("ServerLevel::tickPendingTicks", pServerLevelTickPendingTicks); LogSym("Level::getTile", pLevelGetTile); + LogSym("Level::getData", pLevelGetData); LogSym("Level::setData", pLevelSetData); LogSym("Level::clip", pLevelClip); LogSym("McRegionChunkStorage::load", pMcRegionChunkStorageLoad); diff --git a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h index 369a279..fbddbe2 100644 --- a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h +++ b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h @@ -77,6 +77,7 @@ struct ItemSymbols { void* pItemInstanceGetIcon = nullptr; void* pItemInstanceMineBlock = nullptr; + void* pItemInstanceUseOn = nullptr; void* pItemInstanceSave = nullptr; void* pItemInstanceLoad = nullptr; void* pItemInstanceHurtAndBreak = nullptr; @@ -138,6 +139,7 @@ struct LevelSymbols void* pLevelUpdateNeighborsAt = nullptr; void* pServerLevelTickPendingTicks = nullptr; void* pLevelGetTile = nullptr; + void* pLevelGetData = nullptr; void* pLevelSetData = nullptr; void* pLevelClip = nullptr; void* pMcRegionChunkStorageLoad = nullptr;