mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-08 10:03:06 -04:00
Use DVD overlay system & runtime Aurora texture replacements
This commit is contained in:
+1
-1
@@ -1,3 +1,3 @@
|
||||
[submodule "extern/aurora"]
|
||||
path = extern/aurora
|
||||
url = https://github.com/Lurs/aurora.git
|
||||
url = https://github.com/encounter/aurora.git
|
||||
|
||||
Vendored
+1
-1
Submodule extern/aurora updated: f00825d9f5...e30fe08670
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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());
|
||||
|
||||
@@ -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
@@ -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,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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user