From a2ddd3b58015a9eaffdd2361a3e063af1c893b72 Mon Sep 17 00:00:00 2001 From: Lurs <2795933+Lurs@users.noreply.github.com> Date: Sun, 31 May 2026 09:31:37 +0200 Subject: [PATCH] change register_copied_hd_bti to register_copied_hd_resource to load BMDs as well for weapom textures --- libs/JSystem/src/JKernel/JKRArchivePub.cpp | 12 +- src/dusk/tphd/HdAssetLayer.cpp | 194 ++++++++++----------- src/dusk/tphd/HdAssetLayer.hpp | 8 +- 3 files changed, 99 insertions(+), 115 deletions(-) diff --git a/libs/JSystem/src/JKernel/JKRArchivePub.cpp b/libs/JSystem/src/JKernel/JKRArchivePub.cpp index b1ac093cc5..1716bba286 100644 --- a/libs/JSystem/src/JKernel/JKRArchivePub.cpp +++ b/libs/JSystem/src/JKernel/JKRArchivePub.cpp @@ -13,7 +13,7 @@ #include "dusk/tphd/HdAssetLayer.hpp" namespace { -void register_copied_hd_bti( +void register_copied_hd_resource( JKRArchive* archive, JKRArchive::SDIFileEntry* fileEntry, void* buffer, u32 resourceSize) { if (archive == NULL || fileEntry == NULL || buffer == NULL || resourceSize == 0 || archive->mStringTable == NULL) @@ -21,7 +21,7 @@ void register_copied_hd_bti( return; } - dusk::tphd::register_copied_hd_bti(archive->mEntryNum, + dusk::tphd::register_copied_hd_resource(archive->mEntryNum, archive->mStringTable + fileEntry->getNameOffset(), buffer, resourceSize); } } // namespace @@ -215,7 +215,7 @@ u32 JKRArchive::readResource(void* buffer, u32 bufferSize, u32 type, const char* u32 resourceSize; fetchResource(buffer, bufferSize, fileEntry, &resourceSize); #if DUSK_TPHD - register_copied_hd_bti(this, fileEntry, buffer, resourceSize); + register_copied_hd_resource(this, fileEntry, buffer, resourceSize); #endif return resourceSize; } @@ -236,7 +236,7 @@ u32 JKRArchive::readResource(void* buffer, u32 bufferSize, const char* path) { u32 resourceSize; fetchResource(buffer, bufferSize, fileEntry, &resourceSize); #if DUSK_TPHD - register_copied_hd_bti(this, fileEntry, buffer, resourceSize); + register_copied_hd_resource(this, fileEntry, buffer, resourceSize); #endif return resourceSize; } @@ -251,7 +251,7 @@ u32 JKRArchive::readIdxResource(void* buffer, u32 bufferSize, u32 index) { u32 resourceSize; fetchResource(buffer, bufferSize, fileEntry, &resourceSize); #if DUSK_TPHD - register_copied_hd_bti(this, fileEntry, buffer, resourceSize); + register_copied_hd_resource(this, fileEntry, buffer, resourceSize); #endif return resourceSize; } @@ -266,7 +266,7 @@ u32 JKRArchive::readResource(void* buffer, u32 bufferSize, u16 id) { u32 resourceSize; fetchResource(buffer, bufferSize, fileEntry, &resourceSize); #if DUSK_TPHD - register_copied_hd_bti(this, fileEntry, buffer, resourceSize); + register_copied_hd_resource(this, fileEntry, buffer, resourceSize); #endif return resourceSize; } diff --git a/src/dusk/tphd/HdAssetLayer.cpp b/src/dusk/tphd/HdAssetLayer.cpp index f506051d24..d0247ff897 100644 --- a/src/dusk/tphd/HdAssetLayer.cpp +++ b/src/dusk/tphd/HdAssetLayer.cpp @@ -510,6 +510,79 @@ bool register_hd_bti_replacement_for_buffer(const TphdPack& pack, std::string_vi return true; } +// Absolute offset of slot `slotIdx`'s BTI header within a BMD's TEX1 block. +// Returns 0 on failure (the TEX1 table never sits at offset 0, so 0 is a +// safe sentinel). +u32 bmdSlotBtiOffset(std::span bmd, u32 slotIdx) { + constexpr size_t kBlocksOffset = offsetof(J3DModelFileData, mBlocks); // = 0x20 + if (bmd.size() < kBlocksOffset || + std::memcmp(bmd.data(), "J3D2", 4) != 0) return 0; + + const auto* fileData = reinterpret_cast(bmd.data()); + const u32 numSections = fileData->mBlockNum; + size_t pos = kBlocksOffset; + + for (u32 i = 0; i < numSections && pos + sizeof(J3DModelBlock) <= bmd.size(); ++i) { + const auto* blk = reinterpret_cast(bmd.data() + pos); + const u32 blockSize = blk->mBlockSize; + if (blk->mBlockType == 'TEX1') { + const auto* tex1 = reinterpret_cast(bmd.data() + pos); + const u16 numTex = tex1->mTextureNum; + if (slotIdx >= numTex) return 0; + const size_t btiAbs = pos + static_cast(tex1->mpTextureRes) + slotIdx * 0x20; + if (btiAbs + 0x20 > bmd.size()) return 0; + return static_cast(btiAbs); + } + if (blockSize == 0) break; + pos += blockSize; + } + return 0; +} + +size_t register_hd_bmd_textures_for_buffer(const TphdPack& pack, std::string_view resourceName, + void* buffer, size_t resourceSize, bool replaceExistingPointer) { + if (buffer == nullptr || resourceSize < 0x20) return 0; + if (!endsWithSuffixCI(resourceName, ".bmd") && + !endsWithSuffixCI(resourceName, ".bdl")) return 0; + + const TmpkEntry* gtx = findGtxBySuffix(pack, resourceName); + if (gtx == nullptr) return 0; + + std::span bmdBytes(static_cast(buffer), resourceSize); + auto surfaces = parseGtx(gtx->data); + size_t reg = 0; + for (u32 i = 0; i < surfaces.size(); ++i) { + const auto& s = surfaces[i]; + if (s.baseData.empty()) continue; + const Gx2FormatMapping* m = findFormatMapping(s.format); + if (!m) continue; + + // HD-stub BMDs collapse every BTI's imageOffset to the same + // pixel address. Rewrite each to be slot-unique so our pointer + // map doesn't overwrite. + const u32 btiAbs = bmdSlotBtiOffset(bmdBytes, i); + if (btiAbs == 0) continue; + auto* timg = reinterpret_cast(bmdBytes.data() + btiAbs); + if (timg->imageOffset == 0) { + HdLog.debug("Skip cross-arc placeholder slot {} in {}: " + "imageOffset==0", + i, gtx->name); + continue; + } + + const u32 newImgOff = 0x20 + i * 0x20; + timg->imageOffset = static_cast(newImgOff); + const u8 hdMips = static_cast(std::clamp(s.mipCount, 1u, 11u)); + timg->mipmapCount = hdMips; + timg->maxLOD = static_cast((hdMips - 1) * 8); + timg->maxAnisotropy = GX_ANISO_4; + registerHdSurface(*m, s, bmdBytes.data() + btiAbs + newImgOff, gtx->name, i, + replaceExistingPointer); + ++reg; + } + return reg; +} + // Lightweight RARC walker that returns per-file offsets without copying // arc bytes — we need absolute pointers into the cached HD arc bytes // (stable address) to match what the game later passes to GXInitTexObj. @@ -581,35 +654,6 @@ std::vector parseRarcFiles(std::span arc) { return out; } -// Absolute offset of slot `slotIdx`'s BTI header within a BMD's TEX1 block. -// Returns 0 on failure (the TEX1 table never sits at offset 0, so 0 is a -// safe sentinel). -u32 bmdSlotBtiOffset(std::span bmd, u32 slotIdx) { - constexpr size_t kBlocksOffset = offsetof(J3DModelFileData, mBlocks); // = 0x20 - if (bmd.size() < kBlocksOffset || - std::memcmp(bmd.data(), "J3D2", 4) != 0) return 0; - - const auto* fileData = reinterpret_cast(bmd.data()); - const u32 numSections = fileData->mBlockNum; - size_t pos = kBlocksOffset; - - for (u32 i = 0; i < numSections && pos + sizeof(J3DModelBlock) <= bmd.size(); ++i) { - const auto* blk = reinterpret_cast(bmd.data() + pos); - const u32 blockSize = blk->mBlockSize; - if (blk->mBlockType == 'TEX1') { - const auto* tex1 = reinterpret_cast(bmd.data() + pos); - const u16 numTex = tex1->mTextureNum; - if (slotIdx >= numTex) return 0; - const size_t btiAbs = pos + static_cast(tex1->mpTextureRes) + slotIdx * 0x20; - if (btiAbs + 0x20 > bmd.size()) return 0; - return static_cast(btiAbs); - } - if (blockSize == 0) break; - pos += blockSize; - } - return 0; -} - // Walk the HD arc, pair BMDs with their pack.gz GTX entries, deswizzle each // HD surface, and register the decoded bytes with aurora under the absolute // pointer that GXInitTexObj will later receive. @@ -625,82 +669,16 @@ void register_hd_textures_for_arc(std::span arcBytes, const std::vector bmdBytes(arcBytes.data() + f.dataOffset, f.dataSize); - auto surfaces = parseGtx(gtx->data); - - for (u32 i = 0; i < surfaces.size(); ++i) { - const auto& s = surfaces[i]; - if (s.baseData.empty()) continue; - - const Gx2FormatMapping* m = findFormatMapping(s.format); - if (!m) continue; - - // HD-stub BMDs collapse every BTI's imageOffset to the same - // pixel address. Rewrite each to be slot-unique so our pointer - // map doesn't overwrite. - const u32 btiAbs = bmdSlotBtiOffset(bmdBytes, i); - if (btiAbs == 0) continue; - - auto* timg = reinterpret_cast( - arcBytes.data() + f.dataOffset + btiAbs); - if (timg->imageOffset == 0) { - HdLog.debug("Skip cross-arc placeholder slot {} in {}: " - "imageOffset==0", - i, gtx->name); - continue; - } - - const u32 newImgOff = 0x20 + i * 0x20; - timg->imageOffset = static_cast(newImgOff); - const u8 hdMips = static_cast(std::clamp(s.mipCount, 1u, 11u)); - timg->mipmapCount = hdMips; - timg->maxLOD = static_cast((hdMips - 1) * 8); - //timg->maxAnisotropy = 16; - //timg->LODBias = -50; - registerHdSurface(*m, s, - arcBytes.data() + f.dataOffset + btiAbs + newImgOff, - gtx->name, i); - ++bmdReg; - } + bmdReg += register_hd_bmd_textures_for_buffer(pack, f.path, arcBytes.data() + f.dataOffset, f.dataSize, false); } // Phase B: standalone .bti files. Each BTI is its own arc entry; the // game loads it via JUTTexture (or similar) which calls GXInitTexObj // with `(u8*)resTIMG + imageOffset`. Register that exact pointer. for (const auto& f : files) { - if (!endsWithSuffix(f.path, ".bti")) continue; - if (f.dataSize < 0x20) continue; - - const TmpkEntry* gtx = findGtxBySuffix(pack, f.path); - if (!gtx) continue; - - auto surfaces = parseGtx(gtx->data); - if (surfaces.empty()) continue; - const auto& s = surfaces[0]; - if (s.baseData.empty()) continue; - - const Gx2FormatMapping* m = findFormatMapping(s.format); - if (!m) continue; - - // HD-stub BTIs put garbage in imageOffset. Write 0x20 so BOTH - // consumer paths land on the same address (JUTTexture::storeTIMG and - // direct-access helpers like dKyr_set_btitex_common). Both compute - // i_img + 0x20, matching where we register below. - auto* timg = reinterpret_cast(arcBytes.data() + f.dataOffset); - timg->imageOffset = 0x20; - const u8 hdMips = static_cast(std::clamp(s.mipCount, 1u, 11u)); - timg->mipmapCount = hdMips; - timg->maxLOD = static_cast((hdMips - 1) * 8); - //timg->maxAnisotropy = 16; - //timg->LODBias = -50; - registerHdSurface(*m, s, arcBytes.data() + f.dataOffset + 0x20, - gtx->name, 0); - ++btiReg; + if (register_hd_bti_replacement_for_buffer(pack, f.path, arcBytes.data() + f.dataOffset, f.dataSize, false)) { + ++btiReg; + } } HdLog.info("registerHdTextures[{}]: {} BMD-slot, {} standalone-BTI replacements", @@ -1020,12 +998,14 @@ void register_mounted_hd_archive(s32 entryNum, void* arcBytes, size_t arcSize) { register_hd_textures_for_arc(arcSpan, hdFiles, *hdPack, label); } -void register_copied_hd_bti(s32 entryNum, std::string_view resourceName, void* buffer, +void register_copied_hd_resource(s32 entryNum, std::string_view resourceName, void* buffer, size_t resourceSize) { - if (entryNum < 0 || buffer == nullptr || resourceSize < 0x20 || - !endsWithSuffixCI(resourceName, ".bti")) { - return; - } + if (entryNum < 0 || buffer == nullptr || resourceSize < 0x20) return; + + const bool isBti = endsWithSuffixCI(resourceName, ".bti"); + const bool isBmd = endsWithSuffixCI(resourceName, ".bmd") || + endsWithSuffixCI(resourceName, ".bdl"); + if (!isBti && !isBmd) return; std::filesystem::path packPath; { @@ -1042,7 +1022,11 @@ void register_copied_hd_bti(s32 entryNum, std::string_view resourceName, void* b return; } - register_hd_bti_replacement_for_buffer(*hdPack, resourceName, buffer, resourceSize, true); + if (isBti) { + register_hd_bti_replacement_for_buffer(*hdPack, resourceName, buffer, resourceSize, true); + } else { + register_hd_bmd_textures_for_buffer(*hdPack, resourceName, buffer, resourceSize, true); + } } std::optional find_registered_hd_archive_remaining(const void* ptr) { diff --git a/src/dusk/tphd/HdAssetLayer.hpp b/src/dusk/tphd/HdAssetLayer.hpp index e00cd572b3..b25a80915f 100644 --- a/src/dusk/tphd/HdAssetLayer.hpp +++ b/src/dusk/tphd/HdAssetLayer.hpp @@ -25,10 +25,10 @@ std::optional*> try_load_hd_archive(std::string_view gcPath); // pointers the game will actually use. void register_mounted_hd_archive(s32 entryNum, void* arcBytes, size_t arcSize); -// Called after JKRArchive copies a BTI resource into caller-owned memory, such -// as item icons read out of an ARAM-mounted archive. -void register_copied_hd_bti(s32 entryNum, std::string_view resourceName, void* buffer, - size_t resourceSize); +// Called after JKRArchive copies a resource (BTI item icon, BMD/BDL item +// model, etc.) into caller-owned memory. pp +void register_copied_hd_resource(s32 entryNum, std::string_view resourceName, void* buffer, + size_t resourceSize); // Returns bytes remaining in a registered HD archive range that contains ptr. // Used for debug heap accounting because some HD buffers are not JKR-owned.