FS: Put files in standard locations

This commit is contained in:
Tony Wasserka 2024-03-24 10:53:38 +01:00
parent f15d7c53a4
commit c600e2d4c0
9 changed files with 121 additions and 42 deletions

View file

@ -9,6 +9,38 @@
namespace Settings { namespace Settings {
// Path to use for configuration files
struct PathConfigDir : Config::Option {
static constexpr const char* name = "PathConfigDir";
using type = std::string;
static type default_value() { return default_val; }
static type default_val;
};
// Path for read-only data files created during installation
struct PathImmutableDataDir : Config::Option {
static constexpr const char* name = "PathImmutableDataDir";
using type = std::string;
static type default_value() { return default_val; }
static type default_val;
};
// Path for data that must be writable
struct PathDataDir : Config::Option {
static constexpr const char* name = "PathDataDir";
using type = std::string;
static type default_value() { return default_val; }
static type default_val;
};
// Path for cache data that can be deleted without impacting normal operation
struct PathCacheDir : Config::Option {
static constexpr const char* name = "PathCacheDir";
using type = std::string;
static type default_value() { return default_val; }
static type default_val;
};
enum class CPUEngine { enum class CPUEngine {
NARMive, NARMive,
}; };
@ -96,7 +128,11 @@ struct EnableAudioEmulation : Config::BooleanOption<EnableAudioEmulation> {
}; };
struct Settings : Config::Options<CPUEngineTag, struct Settings : Config::Options<PathConfigDir,
PathImmutableDataDir,
PathDataDir,
PathCacheDir,
CPUEngineTag,
InitialApplicationTag, InitialApplicationTag,
BootToHomeMenu, BootToHomeMenu,
UseNativeHID, UseNativeHID,

View file

@ -29,6 +29,8 @@
#include <range/v3/algorithm/any_of.hpp> #include <range/v3/algorithm/any_of.hpp>
#include <range/v3/algorithm/equal.hpp> #include <range/v3/algorithm/equal.hpp>
#include <range/v3/algorithm/none_of.hpp> #include <range/v3/algorithm/none_of.hpp>
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/split.hpp>
#include <chrono> #include <chrono>
#include <codecvt> #include <codecvt>
@ -162,6 +164,30 @@ int main(int argc, char* argv[]) {
return 0; return 0;
} }
// Prefer XDG_DATA_DIRS, then /usr/local/share, then /usr/share
auto data_dirs = std::string { getenv("XDG_DATA_DIRS") ? getenv("XDG_DATA_DIRS") : "/usr/local/share:/usr/share" };
for (auto data_dir : data_dirs | ranges::views::split(':')) {
auto candidate = ranges::to<std::string>(data_dir) + "/mikage";
if (std::filesystem::exists(candidate)) {
settings.set<Settings::PathImmutableDataDir>(std::move(candidate));
break;
}
}
auto fill_home_path = [](std::string path) {
auto home_dir = getenv("HOME");
if (!home_dir) {
throw std::runtime_error("Could not determine path to home directory. Please set the HOME environment variable");
}
if (!path.starts_with("/HOME~/")) {
return path;
}
return path.replace(0, 6, home_dir);
};
settings.set<Settings::PathConfigDir>(fill_home_path(settings.get<Settings::PathConfigDir>()));
settings.set<Settings::PathDataDir>(fill_home_path(settings.get<Settings::PathDataDir>()));
settings.set<Settings::PathCacheDir>(fill_home_path(settings.get<Settings::PathCacheDir>()));
settings.set<Settings::DumpFrames>(vm["dump_frames"].as<bool>()); settings.set<Settings::DumpFrames>(vm["dump_frames"].as<bool>());
settings.set<Settings::BootToHomeMenu>(vm["launch_menu"].as<bool>()); settings.set<Settings::BootToHomeMenu>(vm["launch_menu"].as<bool>());
if (enable_debugging) { if (enable_debugging) {
@ -234,6 +260,8 @@ if (bootstrap_nand) // Experimental system bootstrapper
throw std::runtime_error("Failed to read update partition RomFS"); throw std::runtime_error("Failed to read update partition RomFS");
} }
std::filesystem::path content_dir = settings.get<Settings::PathDataDir>() + "/data";
for (uint32_t metadata_offset = 0; metadata_offset < level3_header.file_metadata_size;) { for (uint32_t metadata_offset = 0; metadata_offset < level3_header.file_metadata_size;) {
FileFormat::RomFSFileMetadata file_metadata; FileFormat::RomFSFileMetadata file_metadata;
std::tie(result, bytes_read) = romfs->Read(file_context, lv3_offset + level3_header.file_metadata_offset + metadata_offset, sizeof(file_metadata), HLE::PXI::FS::FileBufferInHostMemory(file_metadata)); std::tie(result, bytes_read) = romfs->Read(file_context, lv3_offset + level3_header.file_metadata_offset + metadata_offset, sizeof(file_metadata), HLE::PXI::FS::FileBufferInHostMemory(file_metadata));
@ -264,8 +292,7 @@ if (bootstrap_nand) // Experimental system bootstrapper
fprintf(stderr, "CIA header size: %#x\n", cia_header.header_size); fprintf(stderr, "CIA header size: %#x\n", cia_header.header_size);
std::filesystem::path content_dir = "./data"; InstallCIA(content_dir, *frontend_logger, keydb, file_context, *cia_file);
InstallCIA(std::move(content_dir), *frontend_logger, keydb, file_context, *cia_file);
romfs = cia_file->ReleaseParentAndClose(); romfs = cia_file->ReleaseParentAndClose();
} }
@ -274,23 +301,27 @@ if (bootstrap_nand) // Experimental system bootstrapper
metadata_offset += (file_metadata.name_size + 3) & ~3; metadata_offset += (file_metadata.name_size + 3) & ~3;
} }
// Copy substitute titles
std::filesystem::copy( settings.get<Settings::PathImmutableDataDir>() + "/nand/title", content_dir,
std::filesystem::copy_options::skip_existing | std::filesystem::copy_options::recursive);
// Initial system setup requires title 0004001000022400 to be present. // Initial system setup requires title 0004001000022400 to be present.
// Normally, this is the Camera app, which isn't bundled in game update // Normally, this is the Camera app, which isn't bundled in game update
// partitions. Since the setup only reads the ExeFS icon that aren't // partitions. Since the setup only reads the ExeFS icon that aren't
// specific to the Camera app (likely to perform general checks), we // specific to the Camera app (likely to perform general checks), we
// use the Download Play app as a drop-in replacement if needed. // use the Download Play app as a drop-in replacement if needed.
if (!std::filesystem::exists("./data/00040010/00022400")) { if (!std::filesystem::exists(content_dir / "00040010/00022400")) {
std::filesystem::copy("./data/00040010/00022100", "./data/00040010/00022400", std::filesystem::copy_options::recursive); std::filesystem::copy(content_dir / "00040010/00022100", content_dir / "00040010/00022400", std::filesystem::copy_options::recursive);
} }
std::filesystem::create_directories("./data/ro/sys"); std::filesystem::create_directories(content_dir / "ro/sys");
std::filesystem::create_directories("./data/rw/sys"); std::filesystem::create_directories(content_dir / "rw/sys");
std::filesystem::create_directories("./data/twlp"); std::filesystem::create_directories(content_dir / "twlp");
// Create dummy HWCAL0, required for cfg module to work without running initial system setup first // Create dummy HWCAL0, required for cfg module to work without running initial system setup first
char zero[128]{}; char zero[128]{};
{ {
std::ofstream hwcal0("./data/ro/sys/HWCAL0.dat"); std::ofstream hwcal0(content_dir / "ro/sys/HWCAL0.dat");
const auto size = 2512; const auto size = 2512;
for (unsigned i = 0; i < size / sizeof(zero); ++i) { for (unsigned i = 0; i < size / sizeof(zero); ++i) {
hwcal0.write(zero, sizeof(zero)); hwcal0.write(zero, sizeof(zero));
@ -301,7 +332,7 @@ if (bootstrap_nand) // Experimental system bootstrapper
// Set up dummy rw/sys/SecureInfo_A with region EU // Set up dummy rw/sys/SecureInfo_A with region EU
// TODO: Let the user select the region // TODO: Let the user select the region
{ {
std::ofstream info("./data/rw/sys/SecureInfo_A"); std::ofstream info(content_dir / "rw/sys/SecureInfo_A");
info.write(zero, sizeof(zero)); info.write(zero, sizeof(zero));
info.write(zero, sizeof(zero)); info.write(zero, sizeof(zero));
char region = 2; // Europe char region = 2; // Europe

View file

@ -50,14 +50,9 @@ using OS::ClientSession;
using OS::ServerSession; using OS::ServerSession;
using OS::Thread; using OS::Thread;
static std::filesystem::path GetRootDataDirectory() {
return "./data/";
}
// TODO: Steel diver fails to boot if this directory doesn't exist - should make sure to create it automatically! // TODO: Steel diver fails to boot if this directory doesn't exist - should make sure to create it automatically!
static std::filesystem::path HostSdmcDirectory() { static std::filesystem::path HostSdmcDirectory(Settings::Settings& settings) {
auto path_str = "./data/sdmc"; return GetRootDataDirectory(settings) / "sdmc/";
return std::filesystem::path(path_str);
} }
/** /**
@ -100,13 +95,13 @@ class RomfsFile : public File {
std::string filename; std::string filename;
public: public:
RomfsFile(ProgramInfo& program_info) : program_info(program_info) { RomfsFile(FSContext& context, ProgramInfo& program_info) : program_info(program_info) {
if (program_info.media_type == 0) { if (program_info.media_type == 0) {
// TODO: The content ID is hardcoded currently. // TODO: The content ID is hardcoded currently.
uint32_t content_id = 0; uint32_t content_id = 0;
filename = [&]() -> std::string { filename = [&]() -> std::string {
std::stringstream filename; std::stringstream filename;
filename << "data/"; filename << GetRootDataDirectory(context.settings).string() << "/";
filename << std::hex << std::setw(8) << std::setfill('0') << (program_info.program_id >> 32); filename << std::hex << std::setw(8) << std::setfill('0') << (program_info.program_id >> 32);
filename << "/"; filename << "/";
filename << std::hex << std::setw(8) << std::setfill('0') << (program_info.program_id & 0xFFFFFFFF); filename << std::hex << std::setw(8) << std::setfill('0') << (program_info.program_id & 0xFFFFFFFF);
@ -1061,7 +1056,7 @@ public:
} }
static std::string GetBaseFileParentPath(FSContext& context, uint64_t saveid) { static std::string GetBaseFileParentPath(FSContext& context, uint64_t saveid) {
return fmt::format("./data/data/{:016x}/sysdata/{:08x}", GetId0(context), saveid & 0xFFFFFFFF); return (GetRootDataDirectory(context.settings) / fmt::format("data/{:016x}/sysdata/{:08x}", GetId0(context), saveid & 0xFFFFFFFF)).string();
} }
static std::string GetBaseFilePath(FSContext& context, uint64_t saveid) { static std::string GetBaseFilePath(FSContext& context, uint64_t saveid) {
@ -1381,9 +1376,9 @@ public:
uint32_t program_id_high = (program_info.program_id >> 32); uint32_t program_id_high = (program_info.program_id >> 32);
uint32_t program_id_low = (program_info.program_id & 0xFFFFFFFF); uint32_t program_id_low = (program_info.program_id & 0xFFFFFFFF);
if (program_info.media_type == 1 /* SD */) { if (program_info.media_type == 1 /* SD */) {
return fmt::format("data/sdmc/Nintendo 3DS/{:016x}/{:016x}/title/{:08x}/{:08x}/data", GetId0(context), GetId1(context), program_id_high, program_id_low); return HostSdmcDirectory(context.settings) / fmt::format("Nintendo 3DS/{:016x}/{:016x}/title/{:08x}/{:08x}/data", GetId0(context), GetId1(context), program_id_high, program_id_low);
} else if (program_info.media_type == 2 /* Gamecard */) { } else if (program_info.media_type == 2 /* Gamecard */) {
return fmt::format("data/card_savedata/{:08x}/{:08x}/data", program_id_high, program_id_low); return GetRootDataDirectory(context.settings) / fmt::format("card_savedata/{:08x}/{:08x}/data", program_id_high, program_id_low);
} else { } else {
throw std::runtime_error("Unknown media type"); throw std::runtime_error("Unknown media type");
} }
@ -1465,9 +1460,9 @@ private:
uint32_t extdata_id_high = info.save_id >> 32; uint32_t extdata_id_high = info.save_id >> 32;
if (info.media_type == Platform::FS::MediaType::NAND) { if (info.media_type == Platform::FS::MediaType::NAND) {
return fmt::format("data/data/{:016x}/extdata/{:08x}/{:08x}", GetId0(context), extdata_id_high, extdata_id_low); return (GetRootDataDirectory(context.settings) / fmt::format("data/{:016x}/extdata/{:08x}/{:08x}", GetId0(context), extdata_id_high, extdata_id_low)).string();
} else { } else {
return (HostSdmcDirectory() / fmt::format("Nintendo 3DS/{:016x}/{:016x}/extdata/{:08x}/{:08x}", GetId0(context), GetId1(context), extdata_id_high, extdata_id_low)).string(); return (HostSdmcDirectory(context.settings) / fmt::format("Nintendo 3DS/{:016x}/{:016x}/extdata/{:08x}/{:08x}", GetId0(context), GetId1(context), extdata_id_high, extdata_id_low)).string();
} }
}); });
} }
@ -1876,7 +1871,8 @@ static auto BindMemFn(Func f, Class* c) {
FakeFS::FakeFS(FakeThread& thread) FakeFS::FakeFS(FakeThread& thread)
: process(thread.GetParentProcess()), : process(thread.GetParentProcess()),
logger(*thread.GetLogger()) { logger(*thread.GetLogger()),
settings(process.GetOS().settings) {
OS::Result result; OS::Result result;
@ -2548,7 +2544,7 @@ OpenArchive(FakeThread& thread, FSContext& context, ProcessId process_id, uint32
case 0x00000009: case 0x00000009:
{ {
auto archive = std::unique_ptr<Archive>(new ArchiveHostDir(HostSdmcDirectory())); auto archive = std::unique_ptr<Archive>(new ArchiveHostDir(HostSdmcDirectory(context.settings)));
return std::make_tuple(RESULT_OK, std::move(archive)); return std::make_tuple(RESULT_OK, std::move(archive));
} }
@ -2577,13 +2573,13 @@ OpenArchive(FakeThread& thread, FSContext& context, ProcessId process_id, uint32
case 0x1234567d: case 0x1234567d:
{ {
auto archive = std::unique_ptr<Archive>(new ArchiveHostDir(GetRootDataDirectory() / "rw")); auto archive = std::unique_ptr<Archive>(new ArchiveHostDir(GetRootDataDirectory(context.settings) / "rw"));
return std::make_tuple(RESULT_OK, std::move(archive)); return std::make_tuple(RESULT_OK, std::move(archive));
} }
case 0x1234567e: case 0x1234567e:
{ {
auto archive = std::unique_ptr<Archive>(new ArchiveHostDir(GetRootDataDirectory() / "ro")); auto archive = std::unique_ptr<Archive>(new ArchiveHostDir(GetRootDataDirectory(context.settings) / "ro"));
return std::make_tuple(RESULT_OK, std::move(archive)); return std::make_tuple(RESULT_OK, std::move(archive));
} }
@ -2597,7 +2593,7 @@ OpenArchive(FakeThread& thread, FSContext& context, ProcessId process_id, uint32
// TWL photo. Opened by mset during initial system setup // TWL photo. Opened by mset during initial system setup
case 0x567890ac: case 0x567890ac:
{ {
auto archive = std::unique_ptr<Archive>(new ArchiveHostDir(GetRootDataDirectory() / "twlp")); auto archive = std::unique_ptr<Archive>(new ArchiveHostDir(GetRootDataDirectory(context.settings) / "twlp"));
return std::make_tuple(RESULT_OK, std::move(archive)); return std::make_tuple(RESULT_OK, std::move(archive));
} }

View file

@ -214,6 +214,7 @@ public: // TODO: Un-public-ize
FakeProcess& process; FakeProcess& process;
spdlog::logger& logger; spdlog::logger& logger;
FileContext file_context { logger }; FileContext file_context { logger };
Settings::Settings& settings;
public: public:
FakeFS(FakeThread& thread); FakeFS(FakeThread& thread);

View file

@ -137,4 +137,8 @@ ValidatedHostPath PathValidator::ValidateAndGetSandboxedTreePath(const Utf8PathT
return ValidatedHostPath { std::move(new_path) }; return ValidatedHostPath { std::move(new_path) };
} }
std::filesystem::path GetRootDataDirectory(Settings::Settings& settings) {
return std::filesystem::path { settings.get<Settings::PathDataDir>() } / "data/";
}
} }

View file

@ -95,4 +95,6 @@ protected:
virtual std::filesystem::path GetSandboxHostRoot() const = 0; virtual std::filesystem::path GetSandboxHostRoot() const = 0;
}; };
std::filesystem::path GetRootDataDirectory(Settings::Settings& settings);
} // namespace HLE } // namespace HLE

View file

@ -1,3 +1,4 @@
#include "fs_common.hpp"
#include "pxi.hpp" #include "pxi.hpp"
#include "cryptopp/aes.h" #include "cryptopp/aes.h"
#include "cryptopp/modes.h" #include "cryptopp/modes.h"
@ -114,7 +115,7 @@ FakePXI::FakePXI(FakeThread& thread)
// Search for installed NAND titles // Search for installed NAND titles
// TODO: Move titles to ./data/title // TODO: Move titles to ./data/title
{ {
std::filesystem::path base_path = "./data/"; std::filesystem::path base_path = GetRootDataDirectory(os.settings);
auto parse_title_id_part = [](const std::string& filename) -> std::optional<uint32_t> { auto parse_title_id_part = [](const std::string& filename) -> std::optional<uint32_t> {
// Expect an 8-digit zero-padded hexadecimal number // Expect an 8-digit zero-padded hexadecimal number
@ -270,7 +271,7 @@ FileFormat::ExHeader GetExtendedHeader(Thread& thread, const Platform::FS::Progr
uint32_t content_id = 0; uint32_t content_id = 0;
const std::string filename = Meta::invoke([&] { const std::string filename = Meta::invoke([&] {
std::stringstream filename; std::stringstream filename;
filename << "data/"; filename << GetRootDataDirectory(thread.GetOS().settings).string();
filename << std::hex << std::setw(8) << std::setfill('0') << (title_info.program_id >> 32); filename << std::hex << std::setw(8) << std::setfill('0') << (title_info.program_id >> 32);
filename << "/"; filename << "/";
filename << std::hex << std::setw(8) << std::setfill('0') << (title_info.program_id & 0xFFFFFFFF); filename << std::hex << std::setw(8) << std::setfill('0') << (title_info.program_id & 0xFFFFFFFF);

View file

@ -283,18 +283,13 @@ class ArchiveSystemSaveData final : public Archive {
} }
public: public:
ArchiveSystemSaveData(uint32_t media_type) { ArchiveSystemSaveData(Settings::Settings& settings, uint32_t media_type) {
switch (media_type) { switch (media_type) {
case 0: case 0:
base_path = "./data/data/";
// TODO: This ID is actually generated from data in moveable.sed // TODO: This ID is actually generated from data in moveable.sed
std::array<uint8_t, 0x10> id0; std::array<uint8_t, 0x10> id0;
ranges::fill(id0, 0); ranges::fill(id0, 0);
for (auto byte : id0) base_path = (GetRootDataDirectory(settings) / "data" / fmt::format("{:02x}", fmt::join(id0, "")) / "sysdata").string();
base_path += fmt::format("{:02x}", byte);
base_path += "/sysdata/";
break; break;
default: default:
@ -539,9 +534,10 @@ std::unique_ptr<File> OpenNCCHSubFile(Thread& thread, Platform::FS::ProgramInfo
return std::make_unique<DummyExeFSFile>(); return std::make_unique<DummyExeFSFile>();
} }
auto ncch_filename = fmt::format("./data/{:08x}/{:08x}/content/{:08x}.cxi", auto ncch_filename = GetRootDataDirectory(thread.GetOS().settings) /
fmt::format("{:08x}/{:08x}/content/{:08x}.cxi",
program_info.program_id >> 32, program_info.program_id & 0xFFFFFFFF, content_id); program_info.program_id >> 32, program_info.program_id & 0xFFFFFFFF, content_id);
ncch = std::make_unique<HostFile>(ncch_filename, HostFile::Default); ncch = std::make_unique<HostFile>(ncch_filename.string(), HostFile::Default);
} else if (gamecard && program_info.media_type == 2) { } else if (gamecard && program_info.media_type == 2) {
if (content_id > Meta::to_underlying(Loader::NCSDPartitionId::UpdateData)) { if (content_id > Meta::to_underlying(Loader::NCSDPartitionId::UpdateData)) {
throw Mikage::Exceptions::Invalid("Invalid NCSD partition index {}", content_id); throw Mikage::Exceptions::Invalid("Invalid NCSD partition index {}", content_id);
@ -873,6 +869,8 @@ static std::tuple<Result, uint64_t> OpenArchive(FakeThread& thread, Context& con
thread.GetLogger()->info(binary_path); thread.GetLogger()->info(binary_path);
auto& settings = thread.GetOS().settings;
switch (archive_id) { switch (archive_id) {
case 0x567890b0: case 0x567890b0:
{ {
@ -889,7 +887,7 @@ static std::tuple<Result, uint64_t> OpenArchive(FakeThread& thread, Context& con
// System SaveData stored on NAND // System SaveData stored on NAND
// TODO: Should we verify that not more than 4 bytes have been given? // TODO: Should we verify that not more than 4 bytes have been given?
auto media_type = path.Read<uint32_t>(thread, 0); auto media_type = path.Read<uint32_t>(thread, 0);
auto archive = std::make_unique<ArchiveSystemSaveData>(media_type); auto archive = std::make_unique<ArchiveSystemSaveData>(settings, media_type);
auto archive_handle = context.next_archive_handle++; auto archive_handle = context.next_archive_handle++;
context.archives.emplace(std::make_pair(archive_handle, std::move(archive))); context.archives.emplace(std::make_pair(archive_handle, std::move(archive)));
return std::make_tuple(RESULT_OK, archive_handle); return std::make_tuple(RESULT_OK, archive_handle);

View file

@ -2,6 +2,16 @@
namespace Settings { namespace Settings {
// std::filesystem doesn't understand "~", so we replace the defaults with
// "/HOME~" for safety. The frontend must replace this substring with the true
// home folder path on startup.
// If a buggy frontend doesn't do this, accessing "/HOME~" will reliably fail
// instead of constructing a relative path.
std::string PathConfigDir::default_val = "/HOME~/.config/mikage";
std::string PathImmutableDataDir::default_val = "/usr/share/mikage";
std::string PathDataDir::default_val = "/HOME~/.local/share/mikage";
std::string PathCacheDir::default_val = "/HOME~/.cache/mikage";
CPUEngine CPUEngineTag::default_val = CPUEngine::NARMive; CPUEngine CPUEngineTag::default_val = CPUEngine::NARMive;
} }