diff --git a/.gitmodules b/.gitmodules index 7dcdc438ef..b386c1754a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "extern/aurora"] path = extern/aurora - url = https://github.com/Lurs/aurora.git + url = https://github.com/encounter/aurora.git diff --git a/extern/aurora b/extern/aurora index f00825d9f5..e30fe08670 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit f00825d9f50abc0fcc355cc06a10012ad3f8b2ed +Subproject commit e30fe08670992bfa4bf21bf3afc694d7e8d0d191 diff --git a/files.cmake b/files.cmake index d586c22551..ce650e70e6 100644 --- a/files.cmake +++ b/files.cmake @@ -1454,6 +1454,8 @@ set(DUSK_FILES src/dusk/speedrun.cpp src/dusk/string.cpp src/dusk/stubs.cpp + include/dusk/texture_replacements.hpp + src/dusk/texture_replacements.cpp src/dusk/update_check.cpp src/dusk/update_check.hpp #src/dusk/m_Do_ext_dusk.cpp diff --git a/include/dusk/texture_replacements.hpp b/include/dusk/texture_replacements.hpp new file mode 100644 index 0000000000..ffb3fe8d62 --- /dev/null +++ b/include/dusk/texture_replacements.hpp @@ -0,0 +1,12 @@ +#ifndef DUSK_TEXTURE_REPLACEMENTS_HPP +#define DUSK_TEXTURE_REPLACEMENTS_HPP + +namespace dusk::texture_replacements { + +void reload(); +void set_enabled(bool enabled); +void shutdown(); + +} + +#endif diff --git a/libs/JSystem/src/JKernel/JKRArchivePub.cpp b/libs/JSystem/src/JKernel/JKRArchivePub.cpp index b4f5077e17..731a049457 100644 --- a/libs/JSystem/src/JKernel/JKRArchivePub.cpp +++ b/libs/JSystem/src/JKernel/JKRArchivePub.cpp @@ -9,10 +9,6 @@ #include "JSystem/JKernel/JKRMemArchive.h" #include "JSystem/JUtility/JUTAssert.h" -#if TARGET_PC && DUSK_TPHD -#include "dusk/tphd/HdAssetLayer.hpp" -#endif - JKRArchive* JKRArchive::check_mount_already(s32 entryNum, JKRHeap* heap) { if (heap == NULL) { heap = JKRGetCurrentHeap(); @@ -33,15 +29,6 @@ JKRArchive* JKRArchive::check_mount_already(s32 entryNum, JKRHeap* heap) { JKRArchive* JKRArchive::mount(const char* path, EMountMode mountMode, JKRHeap* heap, EMountDirection mountDirection) { -#if TARGET_PC && DUSK_TPHD - // TPHD arc redirect. - if (path != NULL) { - if (auto hdBuf = dusk::tphd::tryLoadHdArchive(path)) { - return mount((*hdBuf)->data(), heap, mountDirection); - } - } -#endif - s32 entryNum = DVDConvertPathToEntrynum(path); if (entryNum < 0) return NULL; diff --git a/libs/JSystem/src/JKernel/JKRMemArchive.cpp b/libs/JSystem/src/JKernel/JKRMemArchive.cpp index 984b2f0ccc..8c7c6714c1 100644 --- a/libs/JSystem/src/JKernel/JKRMemArchive.cpp +++ b/libs/JSystem/src/JKernel/JKRMemArchive.cpp @@ -18,15 +18,6 @@ JKRMemArchive::JKRMemArchive(s32 entryNum, JKRArchive::EMountDirection mountDire : JKRArchive(entryNum, MOUNT_MEM) { mIsMounted = false; mMountDirection = mountDirection; -#if DUSK_TPHD - // TPHD arc redirect by entrynum. - if (const auto* hd = dusk::tphd::getHdBytesForEntryNum(entryNum)) { - if (!open(const_cast(hd->data()), static_cast(hd->size()), - JKRMEMBREAK_FLAG_UNKNOWN0)) { - return; - } - } else -#endif if (!open(entryNum, mMountDirection)) { return; } @@ -80,8 +71,13 @@ bool JKRMemArchive::open(s32 entryNum, JKRArchive::EMountDirection mountDirectio mIsOpen = false; mMountDirection = mountDirection; +#ifdef TARGET_PC + u32 loadedSize = 0; +#endif if (mMountDirection == JKRArchive::MOUNT_DIRECTION_HEAD) { +#ifndef TARGET_PC u32 loadedSize; +#endif mArcHeader = (SArcHeader *)JKRDvdToMainRam( entryNum, NULL, EXPAND_SWITCH_UNKNOWN1, 0, mHeap, JKRDvdRipper::ALLOC_DIRECTION_FORWARD, 0, (int *)&mCompression, &loadedSize); @@ -90,7 +86,9 @@ bool JKRMemArchive::open(s32 entryNum, JKRArchive::EMountDirection mountDirectio } } else { +#ifndef TARGET_PC u32 loadedSize; +#endif mArcHeader = (SArcHeader *)JKRDvdToMainRam( entryNum, NULL, EXPAND_SWITCH_UNKNOWN1, 0, mHeap, JKRDvdRipper::ALLOC_DIRECTION_BACKWARD, 0, (int *)&mCompression, &loadedSize); @@ -103,6 +101,9 @@ bool JKRMemArchive::open(s32 entryNum, JKRArchive::EMountDirection mountDirectio mMountMode = UNKNOWN_MOUNT_MODE; } else { +#if DUSK_TPHD + dusk::tphd::register_mounted_hd_archive(entryNum, mArcHeader, loadedSize); +#endif JUT_ASSERT(438, mArcHeader->signature == 'RARC'); mArcInfoBlock = (SArcDataInfo *)((u8 *)mArcHeader + mArcHeader->header_length); mNodes = (SDIDirEntry *)((u8 *)&mArcInfoBlock->num_nodes + mArcInfoBlock->node_offset); diff --git a/src/d/d_resorce.cpp b/src/d/d_resorce.cpp index 8042af2eb8..1189b665a6 100644 --- a/src/d/d_resorce.cpp +++ b/src/d/d_resorce.cpp @@ -25,7 +25,6 @@ #include "dusk/logging.h" #if DUSK_TPHD #include "dusk/tphd/HdAssetLayer.hpp" -#include #endif #endif @@ -652,8 +651,8 @@ int dRes_info_c::setRes() { // registered arc-range size; getSize() would return 0/undefined. void* mArcHdr = ((JKRMemArchive*)mArchive)->mArcHeader; size_t arcSize = 0; - if (size_t hdRem = 0; aurora::gfx::hd_find_arc_range(mArcHdr, &hdRem)) { - arcSize = hdRem; + if (auto hdRem = dusk::tphd::find_registered_hd_archive_remaining(mArcHdr)) { + arcSize = *hdRem; } else { arcSize = JKRGetRootHeap()->getSize(mArcHdr); } @@ -1040,7 +1039,7 @@ int dRes_control_c::setObjectRes(char const* i_arcName, void* i_archiveRes, u32 #if DUSK_TPHD // HD hook for second JKRMemArchive constructor (see below) const std::string hdPath = std::format("/res/Object/{}.arc", i_arcName); - if (auto hd = dusk::tphd::tryLoadHdArchive(hdPath)) { + if (auto hd = dusk::tphd::try_load_hd_archive(hdPath)) { DuskLog.info("[TPHD] setObjectRes redirect: {} -> HD ({} bytes)", i_arcName, (*hd)->size()); i_archiveRes = const_cast((*hd)->data()); diff --git a/src/dusk/texture_replacements.cpp b/src/dusk/texture_replacements.cpp new file mode 100644 index 0000000000..5477a38dd9 --- /dev/null +++ b/src/dusk/texture_replacements.cpp @@ -0,0 +1,38 @@ +#include "dusk/texture_replacements.hpp" + +#include + +#include "dusk/logging.h" +#include "dusk/main.h" +#include "dusk/settings.h" + +namespace dusk::texture_replacements { +namespace { +aurora::texture::ReplacementGroup s_directoryGroup; +} + +void reload() { + aurora::texture::unregister_replacements(s_directoryGroup); + s_directoryGroup.registrations.clear(); + + if (!getSettings().game.enableTextureReplacements) { + return; + } + + const auto root = ConfigPath / "texture_replacements"; + s_directoryGroup = aurora::texture::load_replacement_directory(root); + DuskLog.info("Texture replacement directory loaded: {} registration(s)", + s_directoryGroup.registrations.size()); +} + +void set_enabled(bool enabled) { + getSettings().game.enableTextureReplacements.setValue(enabled); + reload(); +} + +void shutdown() { + aurora::texture::unregister_replacements(s_directoryGroup); + s_directoryGroup.registrations.clear(); +} + +} diff --git a/src/dusk/tphd/HdAssetLayer.cpp b/src/dusk/tphd/HdAssetLayer.cpp index 3df2acd563..aec5b5a661 100644 --- a/src/dusk/tphd/HdAssetLayer.cpp +++ b/src/dusk/tphd/HdAssetLayer.cpp @@ -1,23 +1,30 @@ #include "HdAssetLayer.hpp" #include -#include +#include #include #include +#include #include #include #include +#include +#include #include #include -#include +#include +#include #include +#include +#include #include "JSystem/J3DGraphLoader/J3DModelLoader.h" #include "JSystem/JKernel/JKRArchive.h" #include "JSystem/JKernel/JKRDecomp.h" #include "JSystem/JUtility/JUTTexture.h" #include "dusk/endian.h" +#include "dusk/io.hpp" #include "dusk/logging.h" #include "AddrLib.hpp" #include "GtxParser.hpp" @@ -39,16 +46,96 @@ std::list>& g_mountBuffers() { static auto* p = new std::list>{}; return *p; } -std::unordered_map*>& g_entryNumToBytes() { - static auto* p = new std::unordered_map*>{}; + +std::list>& g_textureBuffers() { + static auto* p = new std::list>{}; return *p; } +aurora::texture::ReplacementGroup& g_textureReplacementGroup() { + static auto* p = new aurora::texture::ReplacementGroup{}; + return *p; +} + +struct HdArcRange { + const void* begin = nullptr; + size_t size = 0; + std::string label; +}; + +std::vector& g_arcRanges() { + static auto* p = new std::vector{}; + return *p; +} + +struct HdOverlayEntry { + std::string dvdPath; + std::filesystem::path arcPath; + std::filesystem::path packPath; + size_t size = 0; + s32 overlayEntryNum = -1; +}; + +std::list& g_overlayEntries() { + static auto* p = new std::list{}; + return *p; +} + +std::unordered_map& g_entryNumToOverlay() { + static auto* p = new std::unordered_map{}; + return *p; +} + +bool g_overlayCallbacksRegistered = false; + +void clear_hd_texture_registrations_locked() { + aurora::texture::unregister_replacements(g_textureReplacementGroup()); + g_textureReplacementGroup().registrations.clear(); + g_textureBuffers().clear(); +} + +void register_hd_arc_range_locked(const void* begin, size_t size, std::string_view label) { + if (begin == nullptr || size == 0) return; + g_arcRanges().push_back({ + .begin = begin, + .size = size, + .label = std::string(label), + }); +} + bool endsWithSuffix(std::string_view s, std::string_view suffix) { return s.size() >= suffix.size() && s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0; } +struct SDL_IODeleter { + void operator()(SDL_IOStream* io) const { + if (io != nullptr) { + SDL_CloseIO(io); + } + } +}; + +using IOStream = std::unique_ptr; + +IOStream open_stream(const std::filesystem::path& path) { + const auto pathString = io::fs_path_to_string(path); + return IOStream{SDL_IOFromFile(pathString.c_str(), "rb")}; +} + +std::optional get_file_size(const std::filesystem::path& path) { + auto stream = open_stream(path); + if (stream == nullptr) { + return std::nullopt; + } + + const Sint64 size = SDL_GetIOSize(stream.get()); + if (size < 0) { + return std::nullopt; + } + return size; +} + // On-disk Yaz0 file header. struct Yaz0Header { /* 0x00 */ char magic[4]; // "Yaz0" @@ -72,20 +159,38 @@ std::optional> tryDecodeYaz0(std::span bytes) { return decoded; } -std::optional> readWholeFile(const std::filesystem::path& path) { - std::FILE* f = std::fopen(path.string().c_str(), "rb"); - if (!f) return std::nullopt; - std::fseek(f, 0, SEEK_END); - long len = std::ftell(f); - std::fseek(f, 0, SEEK_SET); - if (len < 0) { std::fclose(f); return std::nullopt; } - std::vector buf(static_cast(len)); - size_t got = std::fread(buf.data(), 1, buf.size(), f); - std::fclose(f); - if (got != buf.size()) return std::nullopt; +std::optional> read_file(const std::filesystem::path& path) { + auto stream = open_stream(path); + if (stream == nullptr) { + return std::nullopt; + } + const Sint64 len = SDL_GetIOSize(stream.get()); + if (len < 0) { + return std::nullopt; + } + std::vector buf(len); + size_t total = 0; + while (total < buf.size()) { + const size_t got = SDL_ReadIO(stream.get(), buf.data() + total, buf.size() - total); + if (got == 0) { + break; + } + total += got; + } + if (total != buf.size() || SDL_GetIOStatus(stream.get()) == SDL_IO_STATUS_ERROR) { + return std::nullopt; + } return buf; } +std::optional load_pack_from_file(const std::filesystem::path& path) { + auto raw = read_file(path); + if (!raw) { + return std::nullopt; + } + return TphdPack::loadFromMemory(*raw); +} + // Extract the path portion under "res/" from JSystem's absolute path. // Example: "/arcName/res/Stage/D_SB10/R00_00.arc" -> "res/Stage/D_SB10/R00_00.arc" std::string_view extractResPath(std::string_view gcPath) { @@ -318,13 +423,26 @@ void registerHdSurface(const Gx2FormatMapping& m, const GtxSurface& s, decoded.mipCount, s.mipCount, decoded.bytes.size(), gtxName, surfaceIdx); - aurora::gfx::HdReplacement r; - r.bytes = std::move(decoded.bytes); - r.width = s.width; - r.height = s.height; - r.gxFormat = m.newGxFormat; - r.mipCount = std::max(decoded.mipCount, 1u); - aurora::gfx::hd_register_replacement(pixelPtr, std::move(r)); + if (decoded.bytes.empty() || pixelPtr == nullptr) { + return; + } + + std::lock_guard lk{g_cacheMutex}; + g_textureBuffers().emplace_back(std::move(decoded.bytes)); + const auto& bytes = g_textureBuffers().back(); + auto registration = aurora::texture::register_replacement( + aurora::texture::TexturePointerKey{.data = pixelPtr}, + aurora::texture::RawTextureReplacement{ + .bytes = {bytes.data(), bytes.size()}, + .width = s.width, + .height = s.height, + .mipCount = std::max(decoded.mipCount, 1u), + .gxFormat = m.newGxFormat, + .label = gtxName, + }); + if (registration.id != 0) { + g_textureReplacementGroup().registrations.push_back(std::move(registration)); + } } // Lightweight RARC walker that returns per-file offsets without copying @@ -431,12 +549,10 @@ u32 bmdSlotBtiOffset(std::span bmd, u32 slotIdx) { // HD surface, and register the decoded bytes with aurora under the absolute // pointer that GXInitTexObj will later receive. // -// arcBytes must be the STABLE cache vector — its data() must not move after -// this call, or aurora's pointer lookups will miss. -void registerHdTexturesForArc(std::vector& arcBytes, - const std::vector& files, - const TphdPack& pack, - std::string_view arcLabel) { +// arcBytes must point at the mounted archive bytes the game will later use; +// aurora's pointer lookups depend on those addresses staying valid. +void register_hd_textures_for_arc(std::span arcBytes, const std::vector& files, + const TphdPack& pack, std::string_view arcLabel) { ZoneScoped; ZoneText(arcLabel.data(), arcLabel.size()); size_t bmdReg = 0; @@ -526,21 +642,8 @@ void registerHdTexturesForArc(std::vector& arcBytes, arcLabel, bmdReg, btiReg); } -} - -void setHdContentPath(std::filesystem::path contentPath) { - g_contentPath = std::move(contentPath); - std::lock_guard lk(g_cacheMutex); - g_mountBuffers().clear(); - g_entryNumToBytes().clear(); - aurora::gfx::hd_clear_replacements(); - aurora::gfx::hd_clear_arc_ranges(); - HdLog.info("HD content path set to: {}", - g_contentPath.empty() ? "(disabled)" : g_contentPath.string()); -} - // HD arcs whose Wii-U layouts don't match the GC UI pipeline. -static constexpr std::string_view kHdSkipList[] = { +constexpr std::string_view kHdSkipList[] = { "res/Object/LogoUs.arc", "res/Object/balloon2D.arc", "res/Object/Coach2D.arc", @@ -556,20 +659,181 @@ static constexpr std::string_view kHdSkipList[] = { "res/FieldMap/res-d.arc", }; -std::optional*> tryLoadHdArchive(std::string_view gcPath) { +bool is_skipped_path(std::string_view resPath) { + // Skip incompatible TPHD layout archives + if (resPath.starts_with("res/Layout/") || + resPath.starts_with("res/LayoutRevo/")) { + return true; + } + for (auto skip : kHdSkipList) { + if (resPath == skip) return true; + } + return false; +} + +void* overlay_open(void* userData) { + auto* entry = static_cast(userData); + if (entry == nullptr) return nullptr; + + SDL_IOStream* stream = open_stream(entry->arcPath).release(); + if (stream == nullptr) { + HdLog.warn("HD overlay open failed: {} ({})", + entry->arcPath.string(), SDL_GetError()); + return nullptr; + } + + return stream; +} + +void overlay_close(void* handle) { + auto* stream = static_cast(handle); + if (stream == nullptr) return; + SDL_CloseIO(stream); +} + +int64_t overlay_read(void* handle, uint8_t* buf, size_t len) { + auto* stream = static_cast(handle); + if (stream == nullptr || buf == nullptr) { + return -1; + } + if (len == 0) { + return 0; + } + const size_t got = SDL_ReadIO(stream, buf, len); + if (got == 0 && SDL_GetIOStatus(stream) == SDL_IO_STATUS_ERROR) { + return -1; + } + return static_cast(got); +} + +int64_t overlay_seek(void* handle, int64_t offset, int32_t whence) { + auto* stream = static_cast(handle); + if (stream == nullptr) { + return -1; + } + const Sint64 pos = SDL_SeekIO(stream, offset, static_cast(whence)); + return pos < 0 ? -1 : static_cast(pos); +} + +void ensure_overlay_callbacks_registered() { + if (g_overlayCallbacksRegistered) { + return; + } + static constexpr AuroraOverlayCallbacks callbacks{ + .open = overlay_open, + .close = overlay_close, + .read = overlay_read, + .seek = overlay_seek, + }; + aurora_dvd_overlay_callbacks(&callbacks); + g_overlayCallbacksRegistered = true; +} + +void rebuild_hd_overlay_locked() { + if (g_contentPath.empty()) { + if (g_overlayCallbacksRegistered) { + aurora_dvd_overlay_files(nullptr, 0); + } + return; + } + + std::error_code ec; + const auto resRoot = g_contentPath / "res"; + if (!std::filesystem::is_directory(resRoot, ec)) { + HdLog.warn("HD content path has no res directory: {}", g_contentPath.string()); + return; + } + + const s32 baseEntryCount = aurora_dvd_base_entry_count(); + if (baseEntryCount <= 0) { + HdLog.warn("DVD overlay skipped because no base DVD FST is loaded yet"); + return; + } + + ensure_overlay_callbacks_registered(); + + std::vector overlayFiles; + s32 nextEntryNum = baseEntryCount; + + for (std::filesystem::recursive_directory_iterator it(resRoot, ec), end; + !ec && it != end; it.increment(ec)) { + const bool regularFile = it->is_regular_file(ec); + if (ec) { + ec.clear(); + continue; + } + if (!regularFile) continue; + + const auto& arcPath = it->path(); + const std::string filename = arcPath.filename().string(); + if (!endsWithSuffixCI(filename, ".arc")) continue; + + const auto rel = arcPath.lexically_relative(g_contentPath); + const std::string resPath = rel.generic_string(); + if (resPath.empty() || is_skipped_path(resPath)) continue; + + const auto fileSize = get_file_size(arcPath); + if (!fileSize.has_value()) { + HdLog.warn("HD overlay file size failed: {} ({})", + arcPath.string(), SDL_GetError()); + continue; + } + + auto& entry = g_overlayEntries().emplace_back(); + entry.dvdPath = "/" + resPath; + entry.arcPath = arcPath; + entry.packPath = arcPath; + entry.packPath.replace_extension(".pack.gz"); + entry.size = *fileSize; + entry.overlayEntryNum = nextEntryNum++; + + overlayFiles.push_back({ + .fileName = entry.dvdPath.c_str(), + .userData = &entry, + .size = entry.size, + .entryNum = entry.overlayEntryNum, + }); + } + + aurora_dvd_overlay_files(overlayFiles.data(), overlayFiles.size()); + + for (auto& entry : g_overlayEntries()) { + const s32 entryNum = DVDConvertPathToEntrynum(entry.dvdPath.c_str()); + if (entryNum < 0) { + HdLog.warn("HD overlay entry was not accepted by DVD FST: {}", entry.dvdPath); + continue; + } + g_entryNumToOverlay()[entryNum] = &entry; + } + + HdLog.info("HD DVD overlay registered {} arcs from {}", + overlayFiles.size(), g_contentPath.string()); +} + +} + +void set_hd_content_path(std::filesystem::path contentPath) { + g_contentPath = std::move(contentPath); + std::lock_guard lk{g_cacheMutex}; + clear_hd_texture_registrations_locked(); + g_mountBuffers().clear(); + g_overlayEntries().clear(); + g_entryNumToOverlay().clear(); + g_arcRanges().clear(); + rebuild_hd_overlay_locked(); + HdLog.info("HD content path set to: {}", + g_contentPath.empty() ? "(disabled)" : g_contentPath.string()); +} + +std::optional*> try_load_hd_archive(std::string_view gcPath) { if (g_contentPath.empty()) return std::nullopt; std::string_view resPath = extractResPath(gcPath); if (resPath.empty()) return std::nullopt; - for (auto skip : kHdSkipList) { - if (resPath == skip) return std::nullopt; - } + if (is_skipped_path(resPath)) return std::nullopt; std::filesystem::path hdArcPath = g_contentPath / std::string(resPath); - if (!std::filesystem::exists(hdArcPath)) { - return std::nullopt; // no HD override — vanilla GC path - } ZoneScoped; #ifdef TRACY_ENABLE { @@ -578,9 +842,8 @@ std::optional*> tryLoadHdArchive(std::string_view gcPath) { } #endif - auto hdBytesOpt = readWholeFile(hdArcPath); + auto hdBytesOpt = read_file(hdArcPath); if (!hdBytesOpt) { - HdLog.warn("HD arc read failed: {}", hdArcPath.string()); return std::nullopt; } @@ -597,44 +860,70 @@ std::optional*> tryLoadHdArchive(std::string_view gcPath) { auto hdPackPath = hdArcPath; hdPackPath.replace_extension(".pack.gz"); std::optional hdPack; - if (std::filesystem::exists(hdPackPath)) { - hdPack = TphdPack::loadFromFile(hdPackPath); - if (!hdPack) HdLog.warn("HD pack failed to load: {}", hdPackPath.string()); - } + hdPack = load_pack_from_file(hdPackPath); // std::list keeps element addresses stable for aurora's pointer map. std::vector* mountBytes; std::string filename = hdArcPath.filename().string(); { - std::lock_guard lk(g_cacheMutex); + std::lock_guard lk{g_cacheMutex}; g_mountBuffers().emplace_back(std::move(*hdBytesOpt)); mountBytes = &g_mountBuffers().back(); + register_hd_arc_range_locked(mountBytes->data(), mountBytes->size(), filename); } HdLog.info("HD arc mount buffer allocated: {} at {} ({} bytes, pack.gz={})", filename, static_cast(mountBytes->data()), mountBytes->size(), hdPack ? "yes" : "no"); - aurora::gfx::hd_register_arc_range(mountBytes->data(), mountBytes->size(), - filename); if (hdPack) { - registerHdTexturesForArc(*mountBytes, hdFiles, *hdPack, filename); + register_hd_textures_for_arc(*mountBytes, hdFiles, *hdPack, filename); } return mountBytes; } -void registerHdBytesForEntryNum(s32 entryNum, const std::vector* bytes) { - if (entryNum < 0 || bytes == nullptr) return; - std::lock_guard lk(g_cacheMutex); - g_entryNumToBytes()[entryNum] = bytes; +void register_mounted_hd_archive(s32 entryNum, void* arcBytes, size_t arcSize) { + if (entryNum < 0 || arcBytes == nullptr || arcSize == 0) return; + + std::filesystem::path packPath; + std::string label; + { + std::lock_guard lk{g_cacheMutex}; + auto it = g_entryNumToOverlay().find(entryNum); + if (it == g_entryNumToOverlay().end()) return; + packPath = it->second->packPath; + label = it->second->arcPath.filename().string(); + } + + auto arcSpan = std::span{static_cast(arcBytes), arcSize}; + { + std::lock_guard lk{g_cacheMutex}; + register_hd_arc_range_locked(arcSpan.data(), arcSpan.size(), label); + } + + auto hdPack = load_pack_from_file(packPath); + if (!hdPack) { + return; + } + + auto hdFiles = parseRarcFiles(std::span(arcSpan.data(), arcSpan.size())); + register_hd_textures_for_arc(arcSpan, hdFiles, *hdPack, label); } -const std::vector* getHdBytesForEntryNum(s32 entryNum) { - if (entryNum < 0) return nullptr; - std::lock_guard lk(g_cacheMutex); - auto it = g_entryNumToBytes().find(entryNum); - return (it != g_entryNumToBytes().end()) ? it->second : nullptr; +std::optional find_registered_hd_archive_remaining(const void* ptr) { + if (ptr == nullptr) return std::nullopt; + + const auto p = reinterpret_cast(ptr); + std::lock_guard lk{g_cacheMutex}; + for (const auto& range : g_arcRanges()) { + const auto begin = reinterpret_cast(range.begin); + const auto end = begin + range.size; + if (p >= begin && p < end) { + return end - p; + } + } + return std::nullopt; } } diff --git a/src/dusk/tphd/HdAssetLayer.hpp b/src/dusk/tphd/HdAssetLayer.hpp index 8361bf7e87..11530fc745 100644 --- a/src/dusk/tphd/HdAssetLayer.hpp +++ b/src/dusk/tphd/HdAssetLayer.hpp @@ -13,17 +13,21 @@ namespace dusk::tphd { // Configure the base directory for HD asset overrides. `contentPath` should // point at a Wii-U `content/` directory (the parent of `res/`). Empty path // disables HD overrides. -void setHdContentPath(std::filesystem::path contentPath); +void set_hd_content_path(std::filesystem::path contentPath); -// Returns a pointer to the cached HD archive bytes if an HD variant exists -// for the requested GC path, or std::nullopt otherwise. Caller must not -// outlive the next setHdContentPath() call. -std::optional*> tryLoadHdArchive(std::string_view gcPath); +// Returns a pointer to cached HD archive bytes for packed sub-archives that +// are mounted from an already-loaded RARC rather than through the DVD layer. +// Caller must not outlive the next setHdContentPath() call. +std::optional*> try_load_hd_archive(std::string_view gcPath); -// HD bytes lookup by DVD entry number, used by JKRMemArchive's entryNum -// constructor to substitute HD content. -void registerHdBytesForEntryNum(s32 entryNum, const std::vector* bytes); -const std::vector* getHdBytesForEntryNum(s32 entryNum); +// Called after JKRMemArchive has loaded an overlaid DVD archive into its final +// heap address so HD texture replacements can be registered against the +// pointers the game will actually use. +void register_mounted_hd_archive(s32 entryNum, void* arcBytes, size_t arcSize); + +// 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); } diff --git a/src/dusk/ui/graphics_tuner.cpp b/src/dusk/ui/graphics_tuner.cpp index 22f0013a49..57fa3ee329 100644 --- a/src/dusk/ui/graphics_tuner.cpp +++ b/src/dusk/ui/graphics_tuner.cpp @@ -11,6 +11,7 @@ #include "dusk/config.hpp" #include "dusk/settings.h" +#include "dusk/texture_replacements.hpp" #include #include @@ -98,8 +99,7 @@ void set_value(GraphicsOption option, int value) { getSettings().game.bloomMultiplier.setValue(std::clamp(value, 0, 100) / 100.0f); break; case GraphicsOption::TextureReplacements: - getSettings().game.enableTextureReplacements.setValue(static_cast(value)); - aurora_set_texture_replacements_enabled(static_cast(value)); + texture_replacements::set_enabled(static_cast(value)); break; } } diff --git a/src/m_Do/m_Do_dvd_thread.cpp b/src/m_Do/m_Do_dvd_thread.cpp index 1851b14669..08197ed30b 100644 --- a/src/m_Do/m_Do_dvd_thread.cpp +++ b/src/m_Do/m_Do_dvd_thread.cpp @@ -16,10 +16,6 @@ #include "m_Do/m_Do_ext.h" #include "os_report.h" -#if DUSK_TPHD -#include "dusk/tphd/HdAssetLayer.hpp" -#endif - s32 mDoDvdThd::main(void* param_0) { JKRThread(OSGetCurrentThread(), 0); #if TARGET_PC @@ -111,14 +107,6 @@ static s32 my_DVDConvertPathToEntrynum(char const* path) { JUT_WARN(437, "can\'t open:[%s]\n", path); } } -#endif -#if DUSK_TPHD - // TPHD arc redirect: cache HD bytes by entry number. - if (entrynum >= 0 && path != NULL) { - if (auto hdBytes = dusk::tphd::tryLoadHdArchive(path)) { - dusk::tphd::registerHdBytesForEntryNum(entrynum, *hdBytes); - } - } #endif return entrynum; } diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 51e339056f..30bbf4fd97 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -84,12 +84,12 @@ #include "dusk/config.hpp" #include "dusk/speedrun.h" #include "dusk/settings.h" +#include "dusk/texture_replacements.hpp" #include "dusk/io.hpp" #include "dusk/version.hpp" #include "dusk/discord_presence.hpp" #if DUSK_TPHD #include "dusk/tphd/HdAssetLayer.hpp" -#include "dusk/tphd/TphdPack.hpp" #endif #include "tracy/Tracy.hpp" #include "f_pc/f_pc_draw.h" @@ -583,19 +583,11 @@ int game_main(int argc, char* argv[]) { config.desiredBackend = ResolveDesiredBackend(parsed_arg_options); config.logCallback = &aurora_log_callback; config.logLevel = startupLogLevel; - // 256 MB is GC-sized. HD-asset injection (HD BMDs + HD pixel buffers - // registered via aurora::gfx::hd_register_replacement) blows past - // that for stages+Link, so bump to 1 GB when TPHD is on. -#if DUSK_TPHD config.mem1Size = 1024 * 1024 * 1024; -#elif TARGET_PC - config.mem1Size = 256 * 1024 * 1024; -#endif config.mem2Size = 24 * 1024 * 1024; config.allowJoystickBackgroundEvents = dusk::getSettings().game.allowBackgroundInput; config.pauseOnFocusLost = dusk::getSettings().game.pauseOnFocusLost; config.imGuiInitCallback = &aurora_imgui_init_callback; - config.allowTextureReplacements = dusk::getSettings().game.enableTextureReplacements; config.allowTextureDumps = false; auroraInfo = aurora_initialize(argc, argv, &config); } @@ -645,6 +637,7 @@ int game_main(int argc, char* argv[]) { return 0; } + dusk::texture_replacements::reload(); dusk::ui::initialize(); dusk::ui::push_document(std::make_unique(), true, true); dusk::ui::push_document(std::make_unique(), false); @@ -742,6 +735,15 @@ int game_main(int argc, char* argv[]) { dusk::IsGameLaunched = true; } +#if DUSK_TPHD + { + const std::string& hdPath = dusk::getSettings().backend.hdContentPath; + if (!hdPath.empty()) { + dusk::tphd::set_hd_content_path(hdPath); + } + } +#endif + #if DUSK_ENABLE_SENTRY_NATIVE if (dusk::crash_reporting::get_consent() == dusk::crash_reporting::Consent::Unknown) { dusk::ui::push_document(std::make_unique()); @@ -757,15 +759,6 @@ int game_main(int argc, char* argv[]) { OSInit(); -#if DUSK_TPHD - { - const std::string& hdPath = dusk::getSettings().backend.hdContentPath; - if (!hdPath.empty()) { - dusk::tphd::setHdContentPath(hdPath); - } - } -#endif - mDoMain::sPowerOnTime = OSGetTime(); // Reset Data @@ -802,6 +795,7 @@ int game_main(int argc, char* argv[]) { dusk::discord::shutdown(); #endif dusk::ui::shutdown(); + dusk::texture_replacements::shutdown(); aurora_shutdown(); return 0;