mirror of
https://github.com/encounter/aurora.git
synced 2026-07-09 18:19:33 -07:00
DVD file overlay support (#216)
* DVD file overlay support Fixes #163 Basically allows games to "overlay" a set of file paths over the DVD. These are provided as a flat list of absolute paths, and when an overlayed file is read, the game gets the ability to provide its own read/seek callbacks. The current implementation rebuilds the FST. This means EntryNums will not be consistent with the original disc, but it does avoid rewriting a significant chunk of the existing DVD code that relies on the FST. * Comments and a guard * EntryNum handling rewrite Game now assigns entrynums for added files, and original disc entrynums are preserved. * Split FST logic into another file in dvd lib * Fix overlay userdata 0 Dusklight was using integer indexes for the userdata of overlay files. This meant file 0 was a null void*, and that meant the DVD code didn't treat it as an overlaid file. Add an extra bool to avoid getting confused like this. * Make entryNum only validated if not replacing file * Make FST rebuilds thread safe with concurrent readers Since I kinda would like to eventually allow mods to be loaded without game restart (where practical) * Doc updates * Remove debug logs * Aurora-allocated entry nums & dir support * Add warning for resetting cwd --------- Co-authored-by: Luke Street <luke@street.dev>
This commit is contained in:
committed by
GitHub
parent
7ba0d010cc
commit
7dd107e1a4
+169
-130
@@ -1,5 +1,7 @@
|
||||
#include <aurora/dvd.h>
|
||||
#include <dolphin/dvd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <dolphin/os.h>
|
||||
#include <nod.h>
|
||||
#include <SDL3/SDL_iostream.h>
|
||||
@@ -8,36 +10,81 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dvd.hpp"
|
||||
|
||||
#include "../../internal.hpp"
|
||||
|
||||
using namespace aurora::dvd::impl;
|
||||
|
||||
namespace aurora::dvd::impl {
|
||||
NodHandle* s_partition = nullptr;
|
||||
std::vector<FSTEntry> s_fstEntries;
|
||||
// Map from public FST entryNums (matching base disc, Aurora-assigned for new overlay entries)
|
||||
// To the current FST indexes (that we use for navigating the tree).
|
||||
// Unfilled spots are given the k_invalidFstEntry value.
|
||||
std::vector<FstIndex> s_entryNumToFstIndex;
|
||||
s32 s_baseEntryCount = 0;
|
||||
FstIndex s_currentDir = 0;
|
||||
std::string s_currentPath = "/";
|
||||
BOOL s_autoInvalidation = FALSE;
|
||||
BOOL s_autoFatalMessaging = FALSE;
|
||||
DVDDiskID s_diskID = {};
|
||||
DVDLowCallback s_resetCoverCallback = nullptr;
|
||||
bool s_initialized = false;
|
||||
bool s_overlayCallbacksSet = false;
|
||||
AuroraOverlayCallbacks s_overlayCallbacks;
|
||||
std::mutex s_fstLock;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct FSTEntry {
|
||||
std::string name;
|
||||
bool isDir = false;
|
||||
u32 parent = 0;
|
||||
u32 nextOrLength = 0;
|
||||
class CommandDataBase {
|
||||
public:
|
||||
virtual ~CommandDataBase() = default;
|
||||
virtual int64_t read(uint8_t *buf, size_t len) = 0;
|
||||
virtual int64_t seek(int64_t offset, int32_t whence) = 0;
|
||||
};
|
||||
|
||||
struct IterateContext {
|
||||
std::vector<FSTEntry>* entries = nullptr;
|
||||
std::vector<std::pair<u32, u32>> dirStack;
|
||||
class CommandDataNod final : public CommandDataBase {
|
||||
public:
|
||||
NodHandle* handle;
|
||||
explicit CommandDataNod(NodHandle* nod_handle) : handle(nod_handle) { }
|
||||
~CommandDataNod() override {
|
||||
nod_free(handle);
|
||||
}
|
||||
|
||||
int64_t read(uint8_t* buf, size_t len) override {
|
||||
return nod_read(handle, buf, len);
|
||||
}
|
||||
|
||||
int64_t seek(int64_t offset, int32_t whence) override {
|
||||
return nod_seek(handle, offset, whence);
|
||||
}
|
||||
};
|
||||
|
||||
NodHandle* s_disc = nullptr;
|
||||
NodHandle* s_partition = nullptr;
|
||||
std::vector<FSTEntry> s_fstEntries;
|
||||
s32 s_currentDir = 0;
|
||||
std::string s_currentPath = "/";
|
||||
BOOL s_autoInvalidation = FALSE;
|
||||
BOOL s_autoFatalMessaging = FALSE;
|
||||
DVDDiskID s_diskID = {};
|
||||
DVDLowCallback s_resetCoverCallback = nullptr;
|
||||
bool s_initialized = false;
|
||||
class CommandDataOverlay final : public CommandDataBase {
|
||||
public:
|
||||
void* handle;
|
||||
explicit CommandDataOverlay(void* handle) : handle(handle) { }
|
||||
~CommandDataOverlay() override {
|
||||
s_overlayCallbacks.close(handle);
|
||||
}
|
||||
|
||||
int64_t read(uint8_t* buf, size_t len) override {
|
||||
return s_overlayCallbacks.read(handle, buf, len);
|
||||
}
|
||||
|
||||
int64_t seek(int64_t offset, int32_t whence) override {
|
||||
return s_overlayCallbacks.seek(handle, offset, whence);
|
||||
}
|
||||
};
|
||||
|
||||
CommandDataNod* s_disc;
|
||||
|
||||
void clearState() {
|
||||
if (s_partition != nullptr) {
|
||||
@@ -45,17 +92,26 @@ void clearState() {
|
||||
s_partition = nullptr;
|
||||
}
|
||||
if (s_disc != nullptr) {
|
||||
nod_free(s_disc);
|
||||
delete s_disc;
|
||||
s_disc = nullptr;
|
||||
}
|
||||
s_fstEntries.clear();
|
||||
s_entryNumToFstIndex.clear();
|
||||
s_baseEntryCount = 0;
|
||||
s_currentDir = 0;
|
||||
s_currentPath = "/";
|
||||
s_diskID = {};
|
||||
s_initialized = false;
|
||||
}
|
||||
|
||||
bool isValidEntryIndex(s32 entry) { return entry >= 0 && static_cast<size_t>(entry) < s_fstEntries.size(); }
|
||||
bool isValidEntryNum(s32 entry) {
|
||||
return entry >= 0 && static_cast<size_t>(entry) < s_entryNumToFstIndex.size() &&
|
||||
s_entryNumToFstIndex[entry] != k_invalidFstEntry;
|
||||
}
|
||||
|
||||
bool isValidFstIndex(FstIndex entry) {
|
||||
return entry >= 0 && static_cast<size_t>(entry) < s_fstEntries.size();
|
||||
}
|
||||
|
||||
bool isAligned(const void* addr, uintptr_t align) {
|
||||
return (reinterpret_cast<uintptr_t>(addr) & (align - 1)) == 0;
|
||||
@@ -102,80 +158,12 @@ void sdlStreamClose(void* userData) {
|
||||
SDL_CloseIO(io);
|
||||
}
|
||||
|
||||
u32 fstCallback(u32 index, NodNodeKind kind, const char* name, u32 size, void* userData) {
|
||||
auto* ctx = static_cast<IterateContext*>(userData);
|
||||
|
||||
while (!ctx->dirStack.empty() && index >= ctx->dirStack.back().second) {
|
||||
ctx->dirStack.pop_back();
|
||||
}
|
||||
|
||||
if (ctx->entries->size() <= index) {
|
||||
ctx->entries->resize(index + 1);
|
||||
}
|
||||
|
||||
FSTEntry& entry = (*ctx->entries)[index];
|
||||
entry.name = (name != nullptr) ? name : "";
|
||||
entry.isDir = (kind == NOD_NODE_KIND_DIRECTORY);
|
||||
entry.parent = ctx->dirStack.empty() ? 0 : ctx->dirStack.back().first;
|
||||
entry.nextOrLength = size;
|
||||
|
||||
if (entry.isDir) {
|
||||
ctx->dirStack.emplace_back(index, size);
|
||||
}
|
||||
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
bool rebuildFST() {
|
||||
if (s_partition == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s_fstEntries.clear();
|
||||
IterateContext ctx{};
|
||||
ctx.entries = &s_fstEntries;
|
||||
nod_partition_iterate_fst(s_partition, fstCallback, &ctx);
|
||||
|
||||
if (s_fstEntries.empty()) {
|
||||
FSTEntry root;
|
||||
root.name = "";
|
||||
root.isDir = true;
|
||||
root.parent = 0;
|
||||
root.nextOrLength = 1;
|
||||
s_fstEntries.push_back(std::move(root));
|
||||
}
|
||||
|
||||
s_fstEntries[0].name.clear();
|
||||
s_fstEntries[0].isDir = true;
|
||||
s_fstEntries[0].parent = 0;
|
||||
if (s_fstEntries[0].nextOrLength < 1 || s_fstEntries[0].nextOrLength > s_fstEntries.size()) {
|
||||
s_fstEntries[0].nextOrLength = static_cast<u32>(s_fstEntries.size());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool nameEqualsIgnoreCase(const std::string& lhs, const char* rhs, size_t rhsLen) {
|
||||
if (lhs.size() != rhsLen) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < rhsLen; ++i) {
|
||||
char lc = lhs[i];
|
||||
char rc = rhs[i];
|
||||
if (lc >= 'a' && lc <= 'z') {
|
||||
lc = static_cast<char>(lc - 'a' + 'A');
|
||||
}
|
||||
if (rc >= 'a' && rc <= 'z') {
|
||||
rc = static_cast<char>(rc - 'a' + 'A');
|
||||
}
|
||||
if (lc != rc) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return aurora::dvd::impl::nameEqualsIgnoreCase(lhs, std::string_view(rhs, rhsLen));
|
||||
}
|
||||
|
||||
s32 findInDir(s32 dirEntry, const char* name, size_t nameLen) {
|
||||
if (!isValidEntryIndex(dirEntry) || !s_fstEntries[dirEntry].isDir) {
|
||||
FstIndex findInDir(FstIndex dirEntry, const char* name, size_t nameLen) {
|
||||
if (!isValidFstIndex(dirEntry) || !s_fstEntries[dirEntry].isDir) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -183,7 +171,7 @@ s32 findInDir(s32 dirEntry, const char* name, size_t nameLen) {
|
||||
u32 i = static_cast<u32>(dirEntry) + 1;
|
||||
while (i < childEnd && i < s_fstEntries.size()) {
|
||||
if (nameEqualsIgnoreCase(s_fstEntries[i].name, name, nameLen)) {
|
||||
return static_cast<s32>(i);
|
||||
return static_cast<FstIndex>(i);
|
||||
}
|
||||
|
||||
if (s_fstEntries[i].isDir) {
|
||||
@@ -196,16 +184,16 @@ s32 findInDir(s32 dirEntry, const char* name, size_t nameLen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string buildDirPath(s32 entryNum) {
|
||||
if (entryNum <= 0 || !isValidEntryIndex(entryNum)) {
|
||||
std::string buildDirPath(FstIndex entryNum) {
|
||||
if (entryNum <= 0 || !isValidFstIndex(entryNum)) {
|
||||
return "/";
|
||||
}
|
||||
|
||||
std::vector<std::string> parts;
|
||||
s32 cur = entryNum;
|
||||
while (cur > 0 && isValidEntryIndex(cur)) {
|
||||
FstIndex cur = entryNum;
|
||||
while (cur > 0 && isValidFstIndex(cur)) {
|
||||
parts.push_back(s_fstEntries[cur].name);
|
||||
s32 parent = static_cast<s32>(s_fstEntries[cur].parent);
|
||||
auto parent = s_fstEntries[cur].parent;
|
||||
if (parent == cur) {
|
||||
break;
|
||||
}
|
||||
@@ -220,7 +208,7 @@ std::string buildDirPath(s32 entryNum) {
|
||||
return out;
|
||||
}
|
||||
|
||||
s32 readFromHandle(NodHandle* handle, void* out, s32 length, s32 offset, u32* transferredOut) {
|
||||
s32 readFromHandle(CommandDataBase* handle, void* out, s32 length, s32 offset, u32* transferredOut) {
|
||||
if (transferredOut != nullptr) {
|
||||
*transferredOut = 0;
|
||||
}
|
||||
@@ -230,7 +218,7 @@ s32 readFromHandle(NodHandle* handle, void* out, s32 length, s32 offset, u32* tr
|
||||
if (length == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (nod_seek(handle, offset, 0) < 0) {
|
||||
if (handle->seek(offset, 0) < 0) {
|
||||
return DVD_RESULT_FATAL_ERROR;
|
||||
}
|
||||
|
||||
@@ -238,7 +226,7 @@ s32 readFromHandle(NodHandle* handle, void* out, s32 length, s32 offset, u32* tr
|
||||
s32 totalRead = 0;
|
||||
s32 remaining = length;
|
||||
while (remaining > 0) {
|
||||
const int64_t read = nod_read(handle, writePtr + totalRead, static_cast<size_t>(remaining));
|
||||
const int64_t read = handle->read(writePtr + totalRead, static_cast<size_t>(remaining));
|
||||
if (read < 0) {
|
||||
return DVD_RESULT_FATAL_ERROR;
|
||||
}
|
||||
@@ -277,9 +265,9 @@ bool isCommandBlockIdle(const DVDCommandBlock* block) {
|
||||
return block != nullptr && block->state != DVD_STATE_BUSY && block->state != DVD_STATE_WAITING;
|
||||
}
|
||||
|
||||
NodHandle* getCommandHandle(DVDCommandBlock* block) {
|
||||
CommandDataBase* getCommandHandle(DVDCommandBlock* block) {
|
||||
if (block != nullptr && block->userData != nullptr) {
|
||||
return static_cast<NodHandle*>(block->userData);
|
||||
return static_cast<CommandDataBase*>(block->userData);
|
||||
}
|
||||
return s_disc;
|
||||
}
|
||||
@@ -360,20 +348,23 @@ bool aurora_dvd_open(const char* disc_path) {
|
||||
.preloader_threads = 1,
|
||||
};
|
||||
|
||||
NodResult result = nod_disc_open_stream(&stream, &options, &s_disc);
|
||||
if (result != NOD_RESULT_OK || s_disc == nullptr) {
|
||||
NodHandle* discHandle;
|
||||
NodResult result = nod_disc_open_stream(&stream, &options, &discHandle);
|
||||
if (result != NOD_RESULT_OK || discHandle == nullptr) {
|
||||
clearState();
|
||||
return false;
|
||||
}
|
||||
|
||||
result = nod_disc_open_partition_kind(s_disc, NOD_PARTITION_KIND_DATA, nullptr, &s_partition);
|
||||
s_disc = new CommandDataNod(discHandle);
|
||||
|
||||
result = nod_disc_open_partition_kind(s_disc->handle, NOD_PARTITION_KIND_DATA, nullptr, &s_partition);
|
||||
if (result != NOD_RESULT_OK || s_partition == nullptr) {
|
||||
clearState();
|
||||
return false;
|
||||
}
|
||||
|
||||
NodDiscHeader header{};
|
||||
if (nod_disc_header(s_disc, &header) == NOD_RESULT_OK) {
|
||||
if (nod_disc_header(s_disc->handle, &header) == NOD_RESULT_OK) {
|
||||
std::memcpy(s_diskID.gameName, header.game_id, sizeof(s_diskID.gameName));
|
||||
std::memcpy(s_diskID.company, header.game_id + sizeof(s_diskID.gameName), sizeof(s_diskID.company));
|
||||
s_diskID.diskNumber = header.disc_num;
|
||||
@@ -443,8 +434,8 @@ int DVDSeekAbsAsyncPrio(DVDCommandBlock* block, s32 offset, DVDCBCallback callba
|
||||
ASSERTMSGLINE(0x7AC, !(offset & (4 - 1)), "DVDSeekAbs(): offset must be a multiple of 4.");
|
||||
|
||||
beginCommand(block, DVD_COMMAND_SEEK, nullptr, 0, static_cast<u32>(offset), callback);
|
||||
NodHandle* handle = getCommandHandle(block);
|
||||
const int64_t seek = handle != nullptr ? nod_seek(handle, static_cast<int64_t>(offset), 0) : -1;
|
||||
auto handle = getCommandHandle(block);
|
||||
const int64_t seek = handle != nullptr ? handle->seek(static_cast<int64_t>(offset), 0) : -1;
|
||||
const s32 result = (seek < 0) ? DVD_RESULT_FATAL_ERROR : DVD_RESULT_GOOD;
|
||||
finishCommand(block, result, 0);
|
||||
if (callback != nullptr) {
|
||||
@@ -685,11 +676,13 @@ int DVDSetAutoFatalMessaging(BOOL enable) {
|
||||
}
|
||||
|
||||
s32 DVDConvertPathToEntrynum(const char* pathPtr) {
|
||||
std::lock_guard lock(s_fstLock);
|
||||
|
||||
if (!s_initialized || pathPtr == nullptr || s_fstEntries.empty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
s32 current = 0;
|
||||
FstIndex current = 0;
|
||||
const char* p = pathPtr;
|
||||
if (*p == '/') {
|
||||
++p;
|
||||
@@ -705,7 +698,7 @@ s32 DVDConvertPathToEntrynum(const char* pathPtr) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isValidEntryIndex(current) || !s_fstEntries[current].isDir) {
|
||||
if (!isValidFstIndex(current) || !s_fstEntries[current].isDir) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -720,7 +713,7 @@ s32 DVDConvertPathToEntrynum(const char* pathPtr) {
|
||||
} else if (compLen == 2 && p[0] == '.' && p[1] == '.') {
|
||||
current = static_cast<s32>(s_fstEntries[current].parent);
|
||||
} else {
|
||||
const s32 found = findInDir(current, p, compLen);
|
||||
const FstIndex found = findInDir(current, p, compLen);
|
||||
if (found < 0) {
|
||||
return -1;
|
||||
}
|
||||
@@ -729,28 +722,46 @@ s32 DVDConvertPathToEntrynum(const char* pathPtr) {
|
||||
p = compEnd;
|
||||
}
|
||||
|
||||
return current;
|
||||
assert(isValidFstIndex(current));
|
||||
return s_fstEntries[current].origEntryNum;
|
||||
}
|
||||
|
||||
BOOL DVDFastOpen(s32 entrynum, DVDFileInfo* fileInfo) {
|
||||
if (!s_initialized || fileInfo == nullptr || !isValidEntryIndex(entrynum) || s_partition == nullptr) {
|
||||
std::lock_guard lock(s_fstLock);
|
||||
|
||||
if (!s_initialized || fileInfo == nullptr || !isValidEntryNum(entrynum) || s_partition == nullptr) {
|
||||
return FALSE;
|
||||
}
|
||||
if (s_fstEntries[entrynum].isDir) {
|
||||
|
||||
const auto fstIndex = s_entryNumToFstIndex[entrynum];
|
||||
assert(fstIndex >= 0);
|
||||
|
||||
const auto& entry = s_fstEntries[fstIndex];
|
||||
if (entry.isDir) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::memset(fileInfo, 0, sizeof(*fileInfo));
|
||||
fileInfo->startAddr = 0;
|
||||
fileInfo->length = s_fstEntries[entrynum].nextOrLength;
|
||||
fileInfo->length = entry.nextOrLength;
|
||||
|
||||
NodHandle* handle = nullptr;
|
||||
NodResult result = nod_partition_open_file(s_partition, static_cast<u32>(entrynum), &handle);
|
||||
if (result != NOD_RESULT_OK || handle == nullptr) {
|
||||
return FALSE;
|
||||
if (entry.isOverlay) {
|
||||
const auto handle = s_overlayCallbacks.open(entry.overlayData);
|
||||
if (!handle) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
fileInfo->cb.userData = new CommandDataOverlay(handle);
|
||||
} else {
|
||||
NodHandle* handle = nullptr;
|
||||
NodResult result = nod_partition_open_file(s_partition, entry.origEntryNum, &handle);
|
||||
if (result != NOD_RESULT_OK || handle == nullptr) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
fileInfo->cb.userData = new CommandDataNod(handle);
|
||||
}
|
||||
|
||||
fileInfo->cb.userData = handle;
|
||||
fileInfo->cb.state = DVD_STATE_END;
|
||||
return TRUE;
|
||||
}
|
||||
@@ -768,7 +779,7 @@ BOOL DVDClose(DVDFileInfo* fileInfo) {
|
||||
return FALSE;
|
||||
}
|
||||
if (fileInfo->cb.userData != nullptr) {
|
||||
nod_free(static_cast<NodHandle*>(fileInfo->cb.userData));
|
||||
delete static_cast<CommandDataBase*>(fileInfo->cb.userData);
|
||||
fileInfo->cb.userData = nullptr;
|
||||
}
|
||||
fileInfo->cb.state = DVD_STATE_END;
|
||||
@@ -788,11 +799,20 @@ BOOL DVDGetCurrentDir(char* path, u32 maxlen) {
|
||||
|
||||
BOOL DVDChangeDir(const char* dirName) {
|
||||
s32 entry = DVDConvertPathToEntrynum(dirName);
|
||||
if (!isValidEntryIndex(entry) || !s_fstEntries[entry].isDir) {
|
||||
|
||||
std::lock_guard lock(s_fstLock);
|
||||
|
||||
if (!isValidEntryNum(entry)) {
|
||||
return FALSE;
|
||||
}
|
||||
s_currentDir = entry;
|
||||
s_currentPath = buildDirPath(entry);
|
||||
|
||||
const auto fstIndex = s_entryNumToFstIndex[entry];
|
||||
if (!s_fstEntries[fstIndex].isDir) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
s_currentDir = fstIndex;
|
||||
s_currentPath = buildDirPath(fstIndex);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -856,12 +876,20 @@ s32 DVDGetFileInfoStatus(const DVDFileInfo* fileInfo) {
|
||||
}
|
||||
|
||||
BOOL DVDFastOpenDir(s32 entrynum, DVDDir* dir) {
|
||||
if (!isValidEntryIndex(entrynum) || dir == nullptr || !s_fstEntries[entrynum].isDir) {
|
||||
std::lock_guard lock(s_fstLock);
|
||||
|
||||
if (!isValidEntryNum(entrynum) || dir == nullptr) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const auto fstIndex = s_entryNumToFstIndex[entrynum];
|
||||
if (!s_fstEntries[fstIndex].isDir) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
dir->entryNum = static_cast<u32>(entrynum);
|
||||
dir->location = static_cast<u32>(entrynum) + 1;
|
||||
dir->next = s_fstEntries[entrynum].nextOrLength;
|
||||
dir->location = static_cast<u32>(fstIndex) + 1;
|
||||
dir->next = s_fstEntries[fstIndex].nextOrLength;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -877,13 +905,16 @@ int DVDReadDir(DVDDir* dir, DVDDirEntry* dirent) {
|
||||
if (dir == nullptr || dirent == nullptr) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::lock_guard lock(s_fstLock);
|
||||
|
||||
if (dir->location >= dir->next || dir->location >= s_fstEntries.size()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const u32 index = dir->location;
|
||||
FSTEntry& entry = s_fstEntries[index];
|
||||
dirent->entryNum = index;
|
||||
dirent->entryNum = static_cast<u32>(entry.origEntryNum);
|
||||
dirent->isDir = entry.isDir ? TRUE : FALSE;
|
||||
dirent->name = entry.name.empty() ? nullptr : entry.name.data();
|
||||
|
||||
@@ -905,7 +936,15 @@ void DVDRewindDir(DVDDir* dir) {
|
||||
if (dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
dir->location = dir->entryNum + 1;
|
||||
|
||||
std::lock_guard lock(s_fstLock);
|
||||
const s32 entryNum = static_cast<s32>(dir->entryNum);
|
||||
if (!isValidEntryNum(entryNum)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto fstIndex = s_entryNumToFstIndex[entryNum];
|
||||
dir->location = static_cast<u32>(fstIndex) + 1;
|
||||
}
|
||||
|
||||
void* DVDGetFSTLocation(void) {
|
||||
@@ -998,7 +1037,7 @@ BOOL DVDLowRead(void* addr, u32 length, u32 offset, DVDLowCallback callback) {
|
||||
}
|
||||
|
||||
BOOL DVDLowSeek(u32 offset, DVDLowCallback callback) {
|
||||
const int64_t seek = s_disc != nullptr ? nod_seek(s_disc, static_cast<int64_t>(offset), 0) : -1;
|
||||
const int64_t seek = s_disc != nullptr ? s_disc->seek(static_cast<int64_t>(offset), 0) : -1;
|
||||
if (callback != nullptr) {
|
||||
callback(static_cast<u32>((seek >= 0) ? DVD_RESULT_GOOD : DVD_RESULT_FATAL_ERROR));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include <nod.h>
|
||||
|
||||
#include <dolphin/os.h>
|
||||
#include <aurora/dvd.h>
|
||||
#include <dolphin/dvd.h>
|
||||
|
||||
#include "../../internal.hpp"
|
||||
|
||||
namespace aurora::dvd::impl {
|
||||
|
||||
inline Module Log("aurora::dvd");
|
||||
|
||||
using FstIndex = s32;
|
||||
constexpr s32 k_invalidFstEntry = -1;
|
||||
|
||||
struct FSTEntry {
|
||||
std::string name;
|
||||
bool isDir = false;
|
||||
FstIndex parent = 0;
|
||||
u32 nextOrLength = 0;
|
||||
void* overlayData = nullptr;
|
||||
bool isOverlay = false;
|
||||
s32 origEntryNum = 0;
|
||||
};
|
||||
|
||||
struct IterateNode {
|
||||
std::string name;
|
||||
bool isDir;
|
||||
s32 originalEntryNum;
|
||||
u32 size;
|
||||
void* overlayData;
|
||||
bool isOverlay;
|
||||
std::vector<std::shared_ptr<IterateNode>> children;
|
||||
|
||||
IterateNode(std::string name, bool isDir, u32 size, s32 originalEntryNum, void* overlayData)
|
||||
: name(std::move(name)), isDir(isDir), size(size), originalEntryNum(originalEntryNum), overlayData(overlayData), isOverlay(true) {}
|
||||
|
||||
IterateNode(std::string name, bool isDir, u32 size, s32 originalEntryNum)
|
||||
: name(std::move(name)), isDir(isDir), size(size), originalEntryNum(originalEntryNum), overlayData(nullptr), isOverlay(false) {}
|
||||
};
|
||||
|
||||
struct IterateContext {
|
||||
std::shared_ptr<IterateNode> root;
|
||||
std::vector<std::pair<std::shared_ptr<IterateNode>, u32>> dirStack;
|
||||
};
|
||||
|
||||
extern NodHandle* s_partition;
|
||||
extern std::vector<FSTEntry> s_fstEntries;
|
||||
// Map from public FST entryNums (matching base disc, Aurora-assigned for new overlay files)
|
||||
// To the current FST indexes (that we use for navigating the tree).
|
||||
// Unfilled spots are given the k_invalidFstEntry value.
|
||||
extern std::vector<FstIndex> s_entryNumToFstIndex;
|
||||
extern s32 s_baseEntryCount;
|
||||
extern FstIndex s_currentDir;
|
||||
extern std::string s_currentPath;
|
||||
extern BOOL s_autoInvalidation;
|
||||
extern BOOL s_autoFatalMessaging;
|
||||
extern DVDDiskID s_diskID;
|
||||
extern DVDLowCallback s_resetCoverCallback;
|
||||
extern bool s_initialized;
|
||||
extern bool s_overlayCallbacksSet;
|
||||
extern AuroraOverlayCallbacks s_overlayCallbacks;
|
||||
extern std::mutex s_fstLock;
|
||||
|
||||
bool rebuildFST();
|
||||
bool nameEqualsIgnoreCase(std::string_view lhs, std::string_view rhs);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
#include "dvd.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace aurora::dvd::impl;
|
||||
|
||||
namespace {
|
||||
|
||||
struct OverlayFileEntry {
|
||||
std::string fileName;
|
||||
void* userData;
|
||||
u32 size;
|
||||
s32 entryNum = k_invalidFstEntry;
|
||||
size_t sourceIndex = 0;
|
||||
};
|
||||
|
||||
std::vector<OverlayFileEntry> s_overlayFiles;
|
||||
std::unordered_map<std::string, s32> s_overlayEntryNums;
|
||||
s32 s_nextOverlayEntryNum = 0;
|
||||
s32 s_overlayEntryNumBase = 0;
|
||||
|
||||
std::string normalizeOverlayPath(std::string_view path) {
|
||||
std::string normalized;
|
||||
normalized.reserve(path.size());
|
||||
bool lastWasSlash = false;
|
||||
for (char ch : path) {
|
||||
if (ch == '\\') {
|
||||
ch = '/';
|
||||
}
|
||||
if (ch == '/') {
|
||||
if (lastWasSlash) {
|
||||
continue;
|
||||
}
|
||||
lastWasSlash = true;
|
||||
normalized.push_back('/');
|
||||
continue;
|
||||
}
|
||||
lastWasSlash = false;
|
||||
if (ch >= 'A' && ch <= 'Z') {
|
||||
ch = static_cast<char>(ch - 'A' + 'a');
|
||||
}
|
||||
normalized.push_back(ch);
|
||||
}
|
||||
if (normalized.size() > 1 && normalized.back() == '/') {
|
||||
normalized.pop_back();
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
void syncOverlayEntryAllocator() {
|
||||
if (s_overlayEntryNumBase == s_baseEntryCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_overlayEntryNums.clear();
|
||||
s_overlayEntryNumBase = s_baseEntryCount;
|
||||
s_nextOverlayEntryNum = s_baseEntryCount;
|
||||
}
|
||||
|
||||
s32 allocateOverlayEntryNum(std::string_view path) {
|
||||
syncOverlayEntryAllocator();
|
||||
|
||||
std::string normalized = normalizeOverlayPath(path);
|
||||
auto it = s_overlayEntryNums.find(normalized);
|
||||
if (it != s_overlayEntryNums.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const s32 entryNum = s_nextOverlayEntryNum++;
|
||||
s_overlayEntryNums.emplace(std::move(normalized), entryNum);
|
||||
return entryNum;
|
||||
}
|
||||
|
||||
|
||||
u32 fstCallback(u32 index, NodNodeKind kind, const char* name, u32 size, void* userData) {
|
||||
auto* ctx = static_cast<IterateContext*>(userData);
|
||||
|
||||
while (index >= ctx->dirStack.back().second) {
|
||||
ctx->dirStack.pop_back();
|
||||
}
|
||||
|
||||
const auto newEntry = std::make_shared<IterateNode>(
|
||||
name,
|
||||
(kind == NOD_NODE_KIND_DIRECTORY),
|
||||
size,
|
||||
index);
|
||||
|
||||
const auto& curDir = ctx->dirStack.back().first;
|
||||
curDir->children.push_back(newEntry);
|
||||
|
||||
if (newEntry->isDir) {
|
||||
ctx->dirStack.emplace_back(newEntry, size);
|
||||
}
|
||||
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
IterateNode* findNode(const IterateNode& node, const std::string_view name) {
|
||||
for (const auto& child : node.children) {
|
||||
if (nameEqualsIgnoreCase(child->name, name)) {
|
||||
return child.get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void mergeOverlayFileIntoContext(const IterateContext& context, OverlayFileEntry& overlayFile) {
|
||||
IterateNode* node = context.root.get();
|
||||
std::string_view filePath = overlayFile.fileName;
|
||||
std::string currentPath;
|
||||
|
||||
assert(filePath.starts_with('/'));
|
||||
filePath = filePath.substr(1);
|
||||
while (true) {
|
||||
const auto nextDelim = filePath.find('/');
|
||||
if (nextDelim == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto segment = filePath.substr(0, nextDelim);
|
||||
filePath = filePath.substr(nextDelim + 1);
|
||||
currentPath += '/';
|
||||
currentPath.append(segment);
|
||||
|
||||
const auto existingNode = findNode(*node, segment);
|
||||
if (existingNode) {
|
||||
if (!existingNode->isDir) {
|
||||
Log.error("Overlay file {} needs directory that's already a file!", overlayFile.fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
node = existingNode;
|
||||
} else {
|
||||
const s32 entryNum = allocateOverlayEntryNum(currentPath);
|
||||
const auto newNode = std::make_shared<IterateNode>(std::string(segment), true, 0, entryNum);
|
||||
node->children.push_back(newNode);
|
||||
node = newNode.get();
|
||||
}
|
||||
}
|
||||
|
||||
// Remainder of fileName is the actual file name, and node is the directory we're in.
|
||||
|
||||
std::string fullFilePath = currentPath;
|
||||
fullFilePath += '/';
|
||||
fullFilePath.append(filePath);
|
||||
|
||||
auto newNode = IterateNode(std::string(filePath), false, overlayFile.size, k_invalidFstEntry, overlayFile.userData);
|
||||
const auto existingNode = findNode(*node, filePath);
|
||||
if (existingNode) {
|
||||
if (existingNode->isDir) {
|
||||
Log.error("Overlay file {} overlaps directory with same name!", overlayFile.fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
newNode.originalEntryNum = existingNode->originalEntryNum;
|
||||
overlayFile.entryNum = newNode.originalEntryNum;
|
||||
|
||||
// Replace existing disc entry.
|
||||
*existingNode = std::move(newNode);
|
||||
} else {
|
||||
// Add new entry.
|
||||
newNode.originalEntryNum = allocateOverlayEntryNum(fullFilePath);
|
||||
overlayFile.entryNum = newNode.originalEntryNum;
|
||||
|
||||
node->children.emplace_back(std::make_shared<IterateNode>(std::move(newNode)));
|
||||
}
|
||||
}
|
||||
|
||||
void mergeOverlayFilesIntoContext(const IterateContext& context) {
|
||||
for (auto& overlayFile : s_overlayFiles) {
|
||||
mergeOverlayFileIntoContext(context, overlayFile);
|
||||
}
|
||||
}
|
||||
|
||||
void makeFstRecursive(IterateNode& node, FstIndex parent) {
|
||||
if (node.originalEntryNum != k_invalidFstEntry) {
|
||||
if (s_entryNumToFstIndex.size() <= node.originalEntryNum) {
|
||||
s_entryNumToFstIndex.resize(node.originalEntryNum + 1, k_invalidFstEntry);
|
||||
}
|
||||
|
||||
auto& map = s_entryNumToFstIndex[node.originalEntryNum];
|
||||
if (map != k_invalidFstEntry) {
|
||||
Log.error("File {} with entry num {} already exists in map!", node.name, node.originalEntryNum);
|
||||
return;
|
||||
}
|
||||
|
||||
map = static_cast<FstIndex>(s_fstEntries.size());
|
||||
}
|
||||
|
||||
if (!node.isDir) {
|
||||
assert(node.children.empty());
|
||||
assert(node.originalEntryNum != k_invalidFstEntry);
|
||||
|
||||
s_fstEntries.emplace_back(node.name, false, parent, node.size, node.overlayData, node.isOverlay, node.originalEntryNum);
|
||||
return;
|
||||
}
|
||||
|
||||
std::ranges::sort(node.children, [](const auto& a, const auto& b) { return a->name < b->name; });
|
||||
|
||||
const FstIndex ourIndex = static_cast<FstIndex>(s_fstEntries.size());
|
||||
s_fstEntries.emplace_back(node.name, true, parent, 0, node.overlayData, node.isOverlay, node.originalEntryNum);
|
||||
|
||||
for (const auto& child : node.children) {
|
||||
makeFstRecursive(*child, ourIndex);
|
||||
}
|
||||
|
||||
s_fstEntries[ourIndex].nextOrLength = static_cast<u32>(s_fstEntries.size());
|
||||
}
|
||||
|
||||
void makeFstFromContext(const IterateContext& context) {
|
||||
makeFstRecursive(*context.root, 0);
|
||||
}
|
||||
|
||||
s32 calcEntryCount(const IterateNode& node) {
|
||||
s32 counter = 1;
|
||||
|
||||
for (const auto& child : node.children) {
|
||||
counter += calcEntryCount(*child);
|
||||
}
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
bool validateOverlayFile(const AuroraOverlayFile& file) {
|
||||
const std::string_view name(file.fileName);
|
||||
|
||||
if (!name.starts_with('/')) {
|
||||
Log.error("Overlay path {} does not start with /", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.size > std::numeric_limits<u32>::max()) {
|
||||
Log.error("Overlay file sizes above 4 GiB are not supported: {}", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace aurora::dvd::impl {
|
||||
|
||||
bool rebuildFST() {
|
||||
using namespace std::string_literals;
|
||||
|
||||
if (s_partition == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard lock(s_fstLock);
|
||||
|
||||
s32 currentDirEntryNum = k_invalidFstEntry;
|
||||
const std::string currentPath = s_currentPath;
|
||||
if (s_currentDir >= 0 && static_cast<size_t>(s_currentDir) < s_fstEntries.size() && s_fstEntries[s_currentDir].isDir) {
|
||||
currentDirEntryNum = s_fstEntries[s_currentDir].origEntryNum;
|
||||
}
|
||||
|
||||
s_fstEntries.clear();
|
||||
s_entryNumToFstIndex.clear();
|
||||
IterateContext ctx;
|
||||
ctx.root = std::make_shared<IterateNode>(""s, true, 0, 0);
|
||||
ctx.dirStack.emplace_back(ctx.root, std::numeric_limits<u32>::max());
|
||||
|
||||
nod_partition_iterate_fst(s_partition, fstCallback, &ctx);
|
||||
s_baseEntryCount = calcEntryCount(*ctx.root);
|
||||
syncOverlayEntryAllocator();
|
||||
mergeOverlayFilesIntoContext(ctx);
|
||||
makeFstFromContext(ctx);
|
||||
|
||||
if (currentDirEntryNum >= 0 && static_cast<size_t>(currentDirEntryNum) < s_entryNumToFstIndex.size()) {
|
||||
const FstIndex currentDir = s_entryNumToFstIndex[currentDirEntryNum];
|
||||
if (currentDir >= 0 && static_cast<size_t>(currentDir) < s_fstEntries.size() && s_fstEntries[currentDir].isDir) {
|
||||
s_currentDir = currentDir;
|
||||
s_currentPath = currentPath;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentDirEntryNum != k_invalidFstEntry) {
|
||||
Log.warn("Current DVD directory {} with entryNum {} was lost during FST rebuild; resetting to root",
|
||||
currentPath, currentDirEntryNum);
|
||||
}
|
||||
|
||||
s_currentDir = 0;
|
||||
s_currentPath = "/";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool nameEqualsIgnoreCase(const std::string_view lhs, const std::string_view rhs) {
|
||||
if (lhs.size() != rhs.size()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < rhs.size(); ++i) {
|
||||
char lc = lhs[i];
|
||||
char rc = rhs[i];
|
||||
if (lc >= 'a' && lc <= 'z') {
|
||||
lc = static_cast<char>(lc - 'a' + 'A');
|
||||
}
|
||||
if (rc >= 'a' && rc <= 'z') {
|
||||
rc = static_cast<char>(rc - 'a' + 'A');
|
||||
}
|
||||
if (lc != rc) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
s32 aurora_dvd_base_entry_count() {
|
||||
return s_baseEntryCount;
|
||||
}
|
||||
|
||||
void aurora_dvd_overlay_files(const AuroraOverlayFile* files, size_t nFiles, s32* outEntryNums) {
|
||||
if (!s_overlayCallbacksSet) {
|
||||
Log.fatal("aurora_dvd_overlay_callbacks not called before aurora_dvd_overlay_files!");
|
||||
}
|
||||
|
||||
s_overlayFiles.clear();
|
||||
if (outEntryNums != nullptr) {
|
||||
std::fill_n(outEntryNums, nFiles, k_invalidFstEntry);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < nFiles; i++) {
|
||||
const auto& file = files[i];
|
||||
|
||||
if (!validateOverlayFile(file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
s_overlayFiles.emplace_back(file.fileName, file.userData, static_cast<u32>(file.size), k_invalidFstEntry, i);
|
||||
}
|
||||
|
||||
rebuildFST();
|
||||
|
||||
if (outEntryNums != nullptr) {
|
||||
for (const auto& file : s_overlayFiles) {
|
||||
if (file.entryNum != k_invalidFstEntry) {
|
||||
outEntryNums[file.sourceIndex] = file.entryNum;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void aurora_dvd_overlay_callbacks(const AuroraOverlayCallbacks* callbacks) {
|
||||
s_overlayCallbacks = *callbacks;
|
||||
s_overlayCallbacksSet = true;
|
||||
}
|
||||
Reference in New Issue
Block a user