288 lines
8.3 KiB
C++
288 lines
8.3 KiB
C++
/*
|
|
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
|
*/
|
|
|
|
#include <keel/media.hpp>
|
|
|
|
#ifndef OX_BARE_METAL
|
|
|
|
#include <fstream>
|
|
|
|
#include <ox/std/trace.hpp>
|
|
|
|
namespace keel {
|
|
|
|
ox::Result<char*> loadRom(ox::StringViewCR path) noexcept {
|
|
std::ifstream file(std::string(toStdStringView(path)), std::ios::binary | std::ios::ate);
|
|
if (!file.good()) {
|
|
oxErrorf("Could not find ROM file: {}", path);
|
|
return ox::Error(1, "Could not find ROM file");
|
|
}
|
|
try {
|
|
auto const size = file.tellg();
|
|
file.seekg(0, std::ios::beg);
|
|
auto buff = new char[static_cast<std::size_t>(size)];
|
|
file.read(buff, size);
|
|
return buff;
|
|
} catch (std::ios_base::failure const&e) {
|
|
oxErrorf("Could not read ROM file due to file IO failure: {}", e.what());
|
|
return ox::Error(2, "Could not read ROM file");
|
|
} catch (std::bad_alloc const&e) {
|
|
oxErrorf("Could not read ROM file due to new failure: {}", e.what());
|
|
return ox::Error(2, "Could not allocate memory for ROM file");
|
|
}
|
|
}
|
|
|
|
void unloadRom(char *rom) noexcept {
|
|
ox::safeDelete(rom);
|
|
}
|
|
|
|
static void clearUuidMap(Context &ctx) noexcept {
|
|
ctx.uuidToPath.clear();
|
|
ctx.pathToUuid.clear();
|
|
}
|
|
|
|
void createUuidMapping(Context &ctx, ox::StringViewCR filePath, ox::UUID const&uuid) noexcept {
|
|
ctx.pathToUuid[filePath] = uuid;
|
|
ctx.uuidToPath[uuid.toString()] = filePath;
|
|
}
|
|
|
|
static ox::Error buildUuidMap(Context &ctx, ox::StringViewCR path, DuplicateSet *duplicates) noexcept {
|
|
OX_REQUIRE(files, ctx.rom->ls(path));
|
|
for (auto const&f : files) {
|
|
OX_REQUIRE_M(filePath, ox::join("/", ox::Array<ox::StringView, 2>{path, f}));
|
|
OX_REQUIRE(stat, ctx.rom->stat(filePath));
|
|
if (stat.fileType == ox::FileType::NormalFile) {
|
|
ox::Array<char, K1HdrSz> buff;
|
|
OX_RETURN_ERROR(
|
|
ctx.rom->read(filePath, 0, buff.size(), buff));
|
|
auto const [uuid, err] = readUuidHeader(buff);
|
|
if (!err) {
|
|
// check for duplication
|
|
if (duplicates && ctx.uuidToPath[uuid.toString()].len()) {
|
|
auto &dl = (*duplicates)[uuid];
|
|
if (dl.empty()) {
|
|
dl.emplace_back(ctx.uuidToPath[uuid.toString()]);
|
|
}
|
|
dl.emplace_back(filePath);
|
|
} else {
|
|
createUuidMapping(ctx, filePath, uuid);
|
|
}
|
|
}
|
|
} else if (stat.fileType == ox::FileType::Directory) {
|
|
if (!beginsWith(f, ".")) {
|
|
OX_RETURN_ERROR(buildUuidMap(ctx, filePath, duplicates));
|
|
}
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
static ox::Error buildUuidMap(Context &ctx, DuplicateSet *duplicates) noexcept {
|
|
if (!ctx.rom) {
|
|
return ox::Error{1, "No ROM FS"};
|
|
}
|
|
return buildUuidMap(ctx, "", duplicates);
|
|
}
|
|
|
|
ox::Result<ox::UUID> pathToUuid(Context &ctx, ox::StringViewCR path) noexcept {
|
|
#ifndef OX_BARE_METAL
|
|
auto const out = ctx.pathToUuid.at(path);
|
|
if (out.error) {
|
|
return ox::Error{1, "Path not found"};
|
|
}
|
|
return *out.value;
|
|
#else
|
|
return ox::Error(1, "UUID to path conversion not supported on this platform");
|
|
#endif
|
|
}
|
|
|
|
ox::Result<ox::UUID> getUuid(Context &ctx, ox::FileAddress const&fileAddr) noexcept {
|
|
OX_REQUIRE(path, fileAddr.getPath());
|
|
return getUuid(ctx, path);
|
|
}
|
|
|
|
ox::Result<ox::UUID> getUuid(Context &ctx, ox::StringViewCR path) noexcept {
|
|
if (beginsWith(path, "uuid://")) {
|
|
auto const uuid = substr(path, 7);
|
|
return ox::UUID::fromString(uuid);
|
|
} else {
|
|
return pathToUuid(ctx, path);
|
|
}
|
|
}
|
|
|
|
ox::Result<ox::CStringView> getPath(Context &ctx, ox::FileAddress const&fileAddr) noexcept {
|
|
OX_REQUIRE(path, fileAddr.getPath());
|
|
if (beginsWith(path, "uuid://")) {
|
|
auto const uuid = substr(path, 7);
|
|
#ifndef OX_BARE_METAL
|
|
OX_REQUIRE_M(out, ctx.uuidToPath.at(uuid));
|
|
return ox::CStringView{*out};
|
|
#else
|
|
return ox::Error(1, "UUID to path conversion not supported on this platform");
|
|
#endif
|
|
} else {
|
|
return ox::CStringView{path};
|
|
}
|
|
}
|
|
|
|
ox::Result<ox::CStringView> getPath(Context &ctx, ox::CStringViewCR fileId) noexcept {
|
|
if (beginsWith(fileId, "uuid://")) {
|
|
auto const uuid = substr(fileId, 7);
|
|
#ifndef OX_BARE_METAL
|
|
OX_REQUIRE_M(out, ctx.uuidToPath.at(uuid));
|
|
return ox::CStringView{*out};
|
|
#else
|
|
return ox::Error(1, "UUID to path conversion not supported on this platform");
|
|
#endif
|
|
} else {
|
|
return ox::CStringView{fileId};
|
|
}
|
|
}
|
|
|
|
ox::Error updatePath(Context &ctx, ox::StringViewCR oldPath, ox::StringViewCR newPath) noexcept {
|
|
#ifndef OX_BARE_METAL
|
|
if (auto const r = ctx.pathToUuid.at(oldPath); r.ok()) {
|
|
auto const ustr = r.value->toString();
|
|
ctx.pathToUuid[newPath] = *r.value;
|
|
ctx.pathToUuid.erase(oldPath);
|
|
ctx.uuidToPath[ustr] = newPath;
|
|
return {};
|
|
}
|
|
return ctx.assetManager.updateAssetId(oldPath, newPath);
|
|
#else
|
|
return ox::Error(1, "updating path is not supported on this platform");
|
|
#endif
|
|
}
|
|
|
|
ox::Result<ox::CStringView> uuidUrlToPath(Context &ctx, ox::StringViewCR uuid) noexcept {
|
|
#ifndef OX_BARE_METAL
|
|
OX_REQUIRE_M(out, ctx.uuidToPath.at(substr(uuid, 7)));
|
|
return ox::CStringView(*out);
|
|
#else
|
|
return ox::Error(1, "UUID to path conversion not supported on this platform");
|
|
#endif
|
|
}
|
|
|
|
ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::StringViewCR uuid) noexcept {
|
|
#ifndef OX_BARE_METAL
|
|
OX_REQUIRE_M(out, ctx.uuidToPath.at(uuid));
|
|
return ox::CStringView(*out);
|
|
#else
|
|
return ox::Error(1, "UUID to path conversion not supported on this platform");
|
|
#endif
|
|
}
|
|
|
|
ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::UUID const&uuid) noexcept {
|
|
#ifndef OX_BARE_METAL
|
|
OX_REQUIRE_M(out, ctx.uuidToPath.at(uuid.toString()));
|
|
return ox::CStringView(*out);
|
|
#else
|
|
return ox::Error(1, "UUID to path conversion not supported on this platform");
|
|
#endif
|
|
}
|
|
|
|
ox::Error reloadAsset(Context &ctx, ox::StringViewCR assetId) noexcept {
|
|
if (beginsWith(assetId, "uuid://")) {
|
|
return ctx.assetManager.reloadAsset(substr(assetId, 7));
|
|
} else {
|
|
auto const [uuid, uuidErr] = getUuid(ctx, assetId);
|
|
if (!uuidErr) {
|
|
return ctx.assetManager.reloadAsset(uuid.toString());
|
|
} else {
|
|
return ctx.assetManager.reloadAsset(assetId);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#include <keel/context.hpp>
|
|
|
|
#define MEM_ROM reinterpret_cast<char*>(0x0800'0000)
|
|
|
|
namespace keel {
|
|
|
|
static void clearUuidMap(Context&) noexcept {
|
|
}
|
|
|
|
static ox::Error buildUuidMap(Context&, DuplicateSet*) noexcept {
|
|
return {};
|
|
}
|
|
|
|
ox::Result<char*> loadRom(ox::StringViewCR) noexcept {
|
|
// put the header in the wrong order to prevent mistaking this code for the
|
|
// media section
|
|
constexpr auto headerP2 = "R_______________";
|
|
constexpr auto headerP1 = "KEEL_MEDIA_HEADE";
|
|
constexpr auto headerP1Len = ox::strlen(headerP2);
|
|
constexpr auto headerP2Len = ox::strlen(headerP1);
|
|
constexpr auto headerLen = headerP1Len + headerP2Len;
|
|
for (auto current = MEM_ROM; current < reinterpret_cast<char*>(0x0a000000); current += headerLen) {
|
|
if (ox::memcmp(current, headerP1, headerP1Len) == 0 &&
|
|
ox::memcmp(current + headerP1Len, headerP2, headerP2Len) == 0) {
|
|
return current + headerLen;
|
|
}
|
|
}
|
|
return ox::Error(1);
|
|
}
|
|
|
|
void unloadRom(char*) noexcept {
|
|
}
|
|
|
|
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::StringViewCR path) noexcept {
|
|
OX_REQUIRE(stat, ctx.rom->stat(path));
|
|
OX_REQUIRE(buff, static_cast<ox::MemFS&>(*ctx.rom).directAccess(path));
|
|
PreloadPtr p;
|
|
OX_RETURN_ERROR(ox::readMC({buff, static_cast<std::size_t>(stat.size)}, p));
|
|
return static_cast<std::size_t>(p.preloadAddr) + ctx.preloadSectionOffset;
|
|
}
|
|
|
|
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const&addr) noexcept {
|
|
OX_REQUIRE(stat, ctx.rom->stat(addr));
|
|
OX_REQUIRE(buff, static_cast<ox::MemFS&>(*ctx.rom).directAccess(addr));
|
|
PreloadPtr p;
|
|
OX_RETURN_ERROR(ox::readMC({buff, static_cast<std::size_t>(stat.size)}, p));
|
|
return static_cast<std::size_t>(p.preloadAddr) + ctx.preloadSectionOffset;
|
|
}
|
|
|
|
ox::Error reloadAsset(keel::Context&, ox::StringView) noexcept {
|
|
return ox::Error(1, "reloadAsset is unsupported on this platform");
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
namespace keel {
|
|
|
|
ox::Error setRomFs(Context &ctx, ox::UPtr<ox::FileSystem> &&fs, DuplicateSet &duplicateSet) noexcept {
|
|
ctx.rom = std::move(fs);
|
|
clearUuidMap(ctx);
|
|
return buildUuidMap(ctx, &duplicateSet);
|
|
}
|
|
|
|
ox::Error setRomFs(Context &ctx, ox::UPtr<ox::FileSystem> &&fs) noexcept {
|
|
ctx.rom = std::move(fs);
|
|
clearUuidMap(ctx);
|
|
return buildUuidMap(ctx, nullptr);
|
|
}
|
|
|
|
ox::Result<ox::UPtr<ox::FileSystem>> loadRomFs(ox::StringViewCR path) noexcept {
|
|
auto const lastDot = ox::lastIndexOf(path, '.');
|
|
if (!lastDot.error && substr(path, lastDot.value) == ".oxfs") {
|
|
OX_REQUIRE(rom, loadRom(path));
|
|
return {ox::make_unique<ox::FileSystem32>(rom, 32 * ox::units::MB, unloadRom)};
|
|
} else {
|
|
#ifdef OX_HAS_PASSTHROUGHFS
|
|
return {ox::make_unique<ox::PassThroughFS>(path)};
|
|
#else
|
|
return ox::Error(2);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
}
|