#include "NativeExports.h" #include "IdRegistry.h" #include "CreativeInventory.h" #include "GameObjectFactory.h" #include "FurnaceRecipeRegistry.h" #include "GameHooks.h" #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(); int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, nullptr, 0); if (len <= 0) return std::wstring(); std::wstring result(len - 1, 0); MultiByteToWideChar(CP_UTF8, 0, utf8, -1, &result[0], len); return result; } static int ResolveRecipeId(const char* namespacedId, bool preferItem) { if (!namespacedId || !namespacedId[0]) return -1; int itemId = IdRegistry::Instance().GetNumericId(IdRegistry::Type::Item, namespacedId); int blockId = IdRegistry::Instance().GetNumericId(IdRegistry::Type::Block, namespacedId); if (preferItem) return (itemId >= 0) ? itemId : blockId; 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" { int native_register_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; LogUtil::Log("[WeaveLoader] Registered block '%s' -> ID %d (hardness=%.1f, resistance=%.1f)", namespacedId, id, hardness, resistance); if (!GameObjectFactory::CreateTile(id, materialId, hardness, resistance, 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 || acceptsRedstonePower != 0) { 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, int maxDamage, const char* iconName, const char* displayName) { if (!namespacedId) return -1; int id = IdRegistry::Instance().Register(IdRegistry::Type::Item, namespacedId); if (id < 0) { LogUtil::Log("[WeaveLoader] Failed to allocate item ID for '%s'", namespacedId); return -1; } LogUtil::Log("[WeaveLoader] Registered item '%s' -> ID %d (stack=%d, durability=%d)", namespacedId, id, maxStackSize, maxDamage); 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::CreateItem(id, maxStackSize, maxDamage, wIcon.empty() ? nullptr : wIcon.c_str(), descId)) { LogUtil::Log("[WeaveLoader] Warning: failed to create game Item for '%s' id=%d", namespacedId, id); } return id; } int native_register_pickaxe_item( const char* namespacedId, int tier, int maxDamage, const char* iconName, const char* displayName) { if (!namespacedId) return -1; int id = IdRegistry::Instance().Register(IdRegistry::Type::Item, namespacedId); if (id < 0) { LogUtil::Log("[WeaveLoader] Failed to allocate pickaxe item ID for '%s'", namespacedId); return -1; } LogUtil::Log("[WeaveLoader] Registered pickaxe item '%s' -> ID %d (tier=%d, durability=%d)", namespacedId, id, tier, maxDamage); 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::CreatePickaxeItem(id, tier, maxDamage, wIcon.empty() ? nullptr : wIcon.c_str(), descId)) { LogUtil::Log("[WeaveLoader] Warning: failed to create native PickaxeItem for '%s' id=%d", namespacedId, id); } return id; } int native_register_shovel_item( const char* namespacedId, int tier, int maxDamage, const char* iconName, const char* displayName) { if (!namespacedId) return -1; int id = IdRegistry::Instance().Register(IdRegistry::Type::Item, namespacedId); if (id < 0) return -1; 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::CreateShovelItem(id, tier, maxDamage, wIcon.empty() ? nullptr : wIcon.c_str(), descId)) LogUtil::Log("[WeaveLoader] Warning: failed to create native ShovelItem for '%s' id=%d", namespacedId, id); return id; } int native_register_hoe_item( const char* namespacedId, int tier, int maxDamage, const char* iconName, const char* displayName) { if (!namespacedId) return -1; int id = IdRegistry::Instance().Register(IdRegistry::Type::Item, namespacedId); if (id < 0) return -1; 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::CreateHoeItem(id, tier, maxDamage, wIcon.empty() ? nullptr : wIcon.c_str(), descId)) LogUtil::Log("[WeaveLoader] Warning: failed to create native HoeItem for '%s' id=%d", namespacedId, id); return id; } int native_register_axe_item( const char* namespacedId, int tier, int maxDamage, const char* iconName, const char* displayName) { if (!namespacedId) return -1; int id = IdRegistry::Instance().Register(IdRegistry::Type::Item, namespacedId); if (id < 0) return -1; 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::CreateAxeItem(id, tier, maxDamage, wIcon.empty() ? nullptr : wIcon.c_str(), descId)) LogUtil::Log("[WeaveLoader] Warning: failed to create native HatchetItem for '%s' id=%d", namespacedId, id); return id; } int native_register_sword_item( const char* namespacedId, int tier, int maxDamage, const char* iconName, const char* displayName) { if (!namespacedId) return -1; int id = IdRegistry::Instance().Register(IdRegistry::Type::Item, namespacedId); if (id < 0) return -1; 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::CreateSwordItem(id, tier, maxDamage, wIcon.empty() ? nullptr : wIcon.c_str(), descId)) LogUtil::Log("[WeaveLoader] Warning: failed to create native WeaponItem for '%s' id=%d", namespacedId, id); return id; } int native_configure_custom_pickaxe_item( int numericItemId, int harvestLevel, float destroySpeed) { return native_configure_custom_tool_item( numericItemId, static_cast(CustomToolMaterialRegistry::ToolKind::Pickaxe), harvestLevel, destroySpeed, 0.0f); } int native_configure_custom_tool_item( int numericItemId, int toolKind, int harvestLevel, float destroySpeed, float attackDamage) { if (numericItemId < 0 || harvestLevel < 0 || destroySpeed <= 0.0f) return 0; const auto kind = static_cast(toolKind); CustomToolMaterialRegistry::Register(numericItemId, kind, harvestLevel, destroySpeed, attackDamage); if (kind == CustomToolMaterialRegistry::ToolKind::Pickaxe) { CustomPickaxeRegistry::Register(numericItemId, harvestLevel, destroySpeed); } void* itemPtr = GameObjectFactory::FindItem(numericItemId); if (itemPtr) { switch (kind) { case CustomToolMaterialRegistry::ToolKind::Pickaxe: case CustomToolMaterialRegistry::ToolKind::Shovel: case CustomToolMaterialRegistry::ToolKind::Axe: if (destroySpeed > 0.0f) *reinterpret_cast(static_cast(itemPtr) + 0xA0) = destroySpeed; if (attackDamage > 0.0f) *reinterpret_cast(static_cast(itemPtr) + 0xA4) = attackDamage; break; case CustomToolMaterialRegistry::ToolKind::Sword: if (attackDamage > 0.0f) *reinterpret_cast(static_cast(itemPtr) + 0x98) = attackDamage; break; case CustomToolMaterialRegistry::ToolKind::Hoe: break; } } LogUtil::Log("[WeaveLoader] Configured custom tool item id=%d (kind=%d harvest=%d speed=%.2f attack=%.2f)", numericItemId, toolKind, harvestLevel, destroySpeed, attackDamage); return 1; } int native_allocate_description_id() { return ModStrings::AllocateId(); } void native_register_string(int descriptionId, const char* displayName) { if (!displayName) return; std::wstring wName = Utf8ToWide(displayName); ModStrings::Register(descriptionId, wName.c_str()); } int native_register_entity( const char* namespacedId, float width, float height, int trackingRange) { if (!namespacedId) return -1; int id = IdRegistry::Instance().Register(IdRegistry::Type::Entity, namespacedId); if (id < 0) { LogUtil::Log("[WeaveLoader] Failed to allocate entity ID for '%s'", namespacedId); return -1; } LogUtil::Log("[WeaveLoader] Registered entity '%s' -> ID %d (%.1fx%.1f)", namespacedId, id, width, height); return id; } void native_add_shaped_recipe( const char* resultId, int resultCount, const char* pattern, const char* ingredientIds) { LogUtil::Log("[WeaveLoader] Added shaped recipe: %dx %s", resultCount, resultId); } void native_add_furnace_recipe( const char* inputId, const char* outputId, float xp) { int inputNumeric = ResolveRecipeId(inputId, false); int outputNumeric = ResolveRecipeId(outputId, true); if (inputNumeric < 0) { LogUtil::Log("[WeaveLoader] Failed furnace recipe: unknown input '%s'", inputId ? inputId : "(null)"); return; } if (outputNumeric < 0) { LogUtil::Log("[WeaveLoader] Failed furnace recipe: unknown output '%s'", outputId ? outputId : "(null)"); return; } if (!FurnaceRecipeRegistry::AddRecipe(inputNumeric, outputNumeric, xp)) { LogUtil::Log("[WeaveLoader] Failed furnace recipe: %s -> %s (%.1f xp)", inputId, outputId, xp); return; } LogUtil::Log("[WeaveLoader] Added furnace recipe: %s[%d] -> %s[%d] (%.1f xp)", inputId, inputNumeric, outputId, outputNumeric, xp); } void native_log(const char* message, int level) { if (message) LogUtil::Log("%s", message); } int native_get_block_id(const char* namespacedId) { if (!namespacedId) return -1; return IdRegistry::Instance().GetNumericId(IdRegistry::Type::Block, namespacedId); } int native_get_item_id(const char* namespacedId) { if (!namespacedId) return -1; return IdRegistry::Instance().GetNumericId(IdRegistry::Type::Item, namespacedId); } int native_get_entity_id(const char* namespacedId) { if (!namespacedId) return -1; return IdRegistry::Instance().GetNumericId(IdRegistry::Type::Entity, namespacedId); } int native_consume_item_from_player(void* playerPtr, int numericItemId, int count) { if (numericItemId < 0 || count <= 0) return 0; return GameHooks::ConsumePlayerResource(playerPtr, numericItemId, count) ? 1 : 0; } int native_damage_item_instance(void* itemInstancePtr, int amount, void* ownerSharedPtr) { if (amount <= 0) return 0; return GameHooks::DamageItemInstance(itemInstancePtr, amount, ownerSharedPtr) ? 1 : 0; } int native_spawn_entity_from_player_look(void* playerPtr, void* playerSharedPtr, int numericEntityId, double speed, double spawnForward, double spawnUp) { if (numericEntityId < 0) return 0; return GameHooks::SummonEntityFromPlayerLook(playerPtr, playerSharedPtr, numericEntityId, speed, spawnForward, spawnUp) ? 1 : 0; } int native_summon_entity_by_id(int numericEntityId, double x, double y, double z) { if (!GameHooks::SummonEntityByNumericId(numericEntityId, x, y, z)) { LogUtil::Log("[WeaveLoader] Summon failed: entity=%d at (%.2f, %.2f, %.2f)", numericEntityId, x, y, z); return 0; } LogUtil::Log("[WeaveLoader] Summoned entity=%d at (%.2f, %.2f, %.2f)", numericEntityId, x, y, 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]) return 0; const int entityNumericId = IdRegistry::Instance().GetNumericId(IdRegistry::Type::Entity, namespacedId); if (entityNumericId < 0) { LogUtil::Log("[WeaveLoader] Summon failed: unknown entity id '%s'", namespacedId); return 0; } return native_summon_entity_by_id(entityNumericId, x, y, z); } void native_subscribe_event(const char* eventName, void* managedFnPtr) { LogUtil::Log("[WeaveLoader] Event subscription: %s", eventName ? eventName : "(null)"); } void native_add_to_creative(int numericId, int count, int auxValue, int groupIndex) { CreativeInventory::AddPending(numericId, count, auxValue, groupIndex); } } // extern "C"