Use DVD overlay system & runtime Aurora texture replacements

This commit is contained in:
Luke Street
2026-05-29 21:18:04 -06:00
parent bd8b3aafeb
commit ae11304c66
13 changed files with 451 additions and 137 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
[submodule "extern/aurora"]
path = extern/aurora
url = https://github.com/Lurs/aurora.git
url = https://github.com/encounter/aurora.git
+1 -1
+2
View File
@@ -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
+12
View File
@@ -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
@@ -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;
+10 -9
View File
@@ -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<u8*>(hd->data()), static_cast<u32>(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);
+3 -4
View File
@@ -25,7 +25,6 @@
#include "dusk/logging.h"
#if DUSK_TPHD
#include "dusk/tphd/HdAssetLayer.hpp"
#include <aurora/hd_texture.hpp>
#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<u8*>((*hd)->data());
+38
View File
@@ -0,0 +1,38 @@
#include "dusk/texture_replacements.hpp"
#include <aurora/texture.hpp>
#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();
}
}
+357 -68
View File
@@ -1,23 +1,30 @@
#include "HdAssetLayer.hpp"
#include <algorithm>
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <list>
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <string>
#include <system_error>
#include <unordered_map>
#include <vector>
#include <aurora/hd_texture.hpp>
#include <aurora/dvd.h>
#include <aurora/texture.hpp>
#include <dolphin/dvd.h>
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_iostream.h>
#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<std::vector<u8>>& g_mountBuffers() {
static auto* p = new std::list<std::vector<u8>>{};
return *p;
}
std::unordered_map<s32, const std::vector<u8>*>& g_entryNumToBytes() {
static auto* p = new std::unordered_map<s32, const std::vector<u8>*>{};
std::list<std::vector<u8>>& g_textureBuffers() {
static auto* p = new std::list<std::vector<u8>>{};
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<HdArcRange>& g_arcRanges() {
static auto* p = new std::vector<HdArcRange>{};
return *p;
}
struct HdOverlayEntry {
std::string dvdPath;
std::filesystem::path arcPath;
std::filesystem::path packPath;
size_t size = 0;
s32 overlayEntryNum = -1;
};
std::list<HdOverlayEntry>& g_overlayEntries() {
static auto* p = new std::list<HdOverlayEntry>{};
return *p;
}
std::unordered_map<s32, HdOverlayEntry*>& g_entryNumToOverlay() {
static auto* p = new std::unordered_map<s32, HdOverlayEntry*>{};
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<SDL_IOStream, SDL_IODeleter>;
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<size_t> 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<std::vector<u8>> tryDecodeYaz0(std::span<const u8> bytes) {
return decoded;
}
std::optional<std::vector<u8>> 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<u8> buf(static_cast<size_t>(len));
size_t got = std::fread(buf.data(), 1, buf.size(), f);
std::fclose(f);
if (got != buf.size()) return std::nullopt;
std::optional<std::vector<u8>> 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<u8> 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<TphdPack> 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<const u8> 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<u8>& arcBytes,
const std::vector<ArcFileInfo>& 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<u8> arcBytes, const std::vector<ArcFileInfo>& 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<u8>& 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<std::vector<u8>*> 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<HdOverlayEntry*>(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<SDL_IOStream*>(handle);
if (stream == nullptr) return;
SDL_CloseIO(stream);
}
int64_t overlay_read(void* handle, uint8_t* buf, size_t len) {
auto* stream = static_cast<SDL_IOStream*>(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<int64_t>(got);
}
int64_t overlay_seek(void* handle, int64_t offset, int32_t whence) {
auto* stream = static_cast<SDL_IOStream*>(handle);
if (stream == nullptr) {
return -1;
}
const Sint64 pos = SDL_SeekIO(stream, offset, static_cast<SDL_IOWhence>(whence));
return pos < 0 ? -1 : static_cast<int64_t>(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<AuroraOverlayFile> 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<std::vector<u8>*> 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<std::vector<u8>*> 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<std::vector<u8>*> tryLoadHdArchive(std::string_view gcPath) {
auto hdPackPath = hdArcPath;
hdPackPath.replace_extension(".pack.gz");
std::optional<TphdPack> 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<u8>* 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<const void*>(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<u8>* 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<uint8_t*>(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<const u8>(arcSpan.data(), arcSpan.size()));
register_hd_textures_for_arc(arcSpan, hdFiles, *hdPack, label);
}
const std::vector<u8>* 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<size_t> find_registered_hd_archive_remaining(const void* ptr) {
if (ptr == nullptr) return std::nullopt;
const auto p = reinterpret_cast<uintptr_t>(ptr);
std::lock_guard lk{g_cacheMutex};
for (const auto& range : g_arcRanges()) {
const auto begin = reinterpret_cast<uintptr_t>(range.begin);
const auto end = begin + range.size;
if (p >= begin && p < end) {
return end - p;
}
}
return std::nullopt;
}
}
+13 -9
View File
@@ -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<std::vector<u8>*> 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<std::vector<u8>*> 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<u8>* bytes);
const std::vector<u8>* 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<size_t> find_registered_hd_archive_remaining(const void* ptr);
}
+2 -2
View File
@@ -11,6 +11,7 @@
#include "dusk/config.hpp"
#include "dusk/settings.h"
#include "dusk/texture_replacements.hpp"
#include <algorithm>
#include <string>
@@ -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<bool>(value));
aurora_set_texture_replacements_enabled(static_cast<bool>(value));
texture_replacements::set_enabled(static_cast<bool>(value));
break;
}
}
-12
View File
@@ -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;
}
+12 -18
View File
@@ -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<dusk::ui::Overlay>(), true, true);
dusk::ui::push_document(std::make_unique<dusk::ui::MenuBar>(), 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<dusk::ui::CrashReportWindow>());
@@ -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;