diff --git a/src/d/d_particle.cpp b/src/d/d_particle.cpp index 66e321c99d..ce8dc2f912 100644 --- a/src/d/d_particle.cpp +++ b/src/d/d_particle.cpp @@ -30,6 +30,10 @@ #include "dusk/math.h" #endif +#if DUSK_TPHD +#include "dusk/tphd/HdAssetLayer.hpp" +#endif + #if DEBUG //#pragma nosyminline on #endif @@ -1205,6 +1209,10 @@ void dPa_control_c::createCommon(void const* param_0) { mHeap = mDoExt_createSolidHeapFromSystem(0, 0); JKRHEAP_NAME(mHeap, "dPa_control_c::mHeap"); JUT_ASSERT(2518, mHeap != NULL); +#if DUSK_TPHD + dusk::tphd::register_hd_particle_textures("common-r", (void*)param_0, + m_resHeap->getSize((void*)param_0)); +#endif mCommonResMng = JKR_NEW_ARGS (mHeap, 0) JPAResourceManager(param_0, mHeap); JUT_ASSERT(2521, mCommonResMng != NULL); mCommonResMng->swapTexture(mDoGph_gInf_c::getFrameBufferTimg(), "dummy"); @@ -1231,6 +1239,13 @@ void dPa_control_c::createRoomScene() { mSceneHeap = mDoExt_createSolidHeapFromGame(0, 0); JKRHEAP_NAME(mSceneHeap, "dPa_control_c::mSceneHeap"); JUT_ASSERT(2573, mSceneHeap != NULL); +#if DUSK_TPHD + { + char stem[16]; + std::snprintf(stem, sizeof(stem), "Pscene%03u", static_cast(field_0x18)); + dusk::tphd::register_hd_particle_textures(stem, m_sceneRes, m_resHeap->getSize(m_sceneRes)); + } +#endif mSceneResMng = JKR_NEW_ARGS (mSceneHeap, 0) JPAResourceManager(m_sceneRes, mSceneHeap); JUT_ASSERT(2576, mSceneResMng != NULL); mSceneResMng->swapTexture(mDoGph_gInf_c::getFrameBufferTimg(), "dummy"); diff --git a/src/dusk/tphd/HdAssetLayer.cpp b/src/dusk/tphd/HdAssetLayer.cpp index 4a92eb7937..e5c37cd3c2 100644 --- a/src/dusk/tphd/HdAssetLayer.cpp +++ b/src/dusk/tphd/HdAssetLayer.cpp @@ -476,6 +476,14 @@ void registerHdSurface(const Gx2FormatMapping& m, const GtxSurface& s, } } +void applyTimgAttributes(ResTIMG* timg, const GtxSurface& s, s32 imageOffset) { + timg->imageOffset = imageOffset; + 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; +} + bool register_hd_bti_replacement_for_buffer(const TphdPack& pack, std::string_view resourceName, void* buffer, size_t resourceSize, bool replaceExistingPointer) { if (buffer == nullptr || resourceSize < 0x20 || !endsWithSuffixCI(resourceName, ".bti")) { @@ -503,10 +511,7 @@ bool register_hd_bti_replacement_for_buffer(const TphdPack& pack, std::string_vi } auto* timg = reinterpret_cast(buffer); - timg->imageOffset = 0x20; - const u8 hdMips = static_cast(std::clamp(s.mipCount, 1u, 11u)); - timg->mipmapCount = hdMips; - timg->maxLOD = static_cast((hdMips - 1) * 8); + applyTimgAttributes(timg, s, 0x20); registerHdSurface(*m, s, static_cast(buffer) + 0x20, gtx->name, 0, replaceExistingPointer); return true; } @@ -572,11 +577,7 @@ size_t register_hd_bmd_textures_for_buffer(const TphdPack& pack, std::string_vie } 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; + applyTimgAttributes(timg, s, static_cast(newImgOff)); registerHdSurface(*m, s, bmdBytes.data() + btiAbs + newImgOff, gtx->name, i, replaceExistingPointer); ++reg; @@ -894,6 +895,50 @@ void rebuild_hd_overlay_locked() { } +void register_hd_particle_textures(std::string_view jpcStem, void* jpcBuffer, size_t jpcSize) { + if (g_contentPath.empty() || jpcBuffer == nullptr || jpcSize < 0x10) return; + auto* jpc = static_cast(jpcBuffer); + if (std::memcmp(jpc, "JPAC2-10", 8) != 0) return; + + const std::filesystem::path sidecar = + g_contentPath / "tex" / "Particle" / (std::string(jpcStem) + ".jpc.gtx.gz"); + auto gz = read_file(sidecar); + if (!gz) return; + auto gfx2 = decompressGzip(*gz); + if (!gfx2) return; + auto surfaces = parseGtx(*gfx2); + if (surfaces.empty()) return; + + // JPAC2-10 header: texture count @ +0x0A (BE u16), texture table @ +0x0C (BE u32). + const u32 texCnt = *reinterpret_cast(jpc + 0x0A); + const u32 texTableOff = *reinterpret_cast(jpc + 0x0C); + if (texCnt != surfaces.size()) { + HdLog.warn("HD particle {}: jpc texCnt {} != sidecar surfaces {} -> skip", + jpcStem, texCnt, surfaces.size()); + return; + } + + size_t reg = 0; + u32 off = texTableOff; + for (u32 i = 0; i < texCnt && off + 0x40 <= jpcSize; ++i) { + const u32 entrySize = *reinterpret_cast(jpc + off + 4); + const char* texName = reinterpret_cast(jpc + off + 0x0C); + const auto& s = surfaces[i]; + const Gx2FormatMapping* m = s.baseData.empty() ? nullptr : findFormatMapping(s.format); + if (m != nullptr && std::strncmp(texName, "dummy", 5) != 0) { + auto* timg = reinterpret_cast(jpc + off + 0x20); + const s32 stored = timg->imageOffset; + const s32 imgOff = stored ? stored : 0x20; + applyTimgAttributes(timg, s, imgOff); + const void* pixelPtr = reinterpret_cast(timg) + imgOff; + registerHdSurface(*m, s, pixelPtr, jpcStem, i, true); + ++reg; + } + off += entrySize ? entrySize : 0x40; + } + HdLog.info("registerHdParticle[{}]: {}/{} textures registered", jpcStem, reg, texCnt); +} + void set_hd_content_path(std::filesystem::path contentPath) { g_contentPath = std::move(contentPath); std::lock_guard lk{g_cacheMutex}; diff --git a/src/dusk/tphd/HdAssetLayer.hpp b/src/dusk/tphd/HdAssetLayer.hpp index b25a80915f..815dfe8ca0 100644 --- a/src/dusk/tphd/HdAssetLayer.hpp +++ b/src/dusk/tphd/HdAssetLayer.hpp @@ -30,6 +30,9 @@ void register_mounted_hd_archive(s32 entryNum, void* arcBytes, size_t arcSize); void register_copied_hd_resource(s32 entryNum, std::string_view resourceName, void* buffer, size_t resourceSize); +// Register HD particle textures which live in a loose `tex/Particle/.jpc.gtx.gz` sidecar +void register_hd_particle_textures(std::string_view jpcStem, void* jpcBuffer, size_t jpcSize); + // 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. std::optional find_registered_hd_archive_remaining(const void* ptr);