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 {
// 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 {
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,
BootToHomeMenu,
UseNativeHID,

View file

@ -29,6 +29,8 @@
#include <range/v3/algorithm/any_of.hpp>
#include <range/v3/algorithm/equal.hpp>
#include <range/v3/algorithm/none_of.hpp>
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/split.hpp>
#include <chrono>
#include <codecvt>
@ -162,6 +164,30 @@ int main(int argc, char* argv[]) {
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::BootToHomeMenu>(vm["launch_menu"].as<bool>());
if (enable_debugging) {
@ -234,6 +260,8 @@ if (bootstrap_nand) // Experimental system bootstrapper
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;) {
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));
@ -264,8 +292,7 @@ if (bootstrap_nand) // Experimental system bootstrapper
fprintf(stderr, "CIA header size: %#x\n", cia_header.header_size);
std::filesystem::path content_dir = "./data";
InstallCIA(std::move(content_dir), *frontend_logger, keydb, file_context, *cia_file);
InstallCIA(content_dir, *frontend_logger, keydb, file_context, *cia_file);
romfs = cia_file->ReleaseParentAndClose();
}
@ -274,23 +301,27 @@ if (bootstrap_nand) // Experimental system bootstrapper
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.
// 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
// specific to the Camera app (likely to perform general checks), we
// use the Download Play app as a drop-in replacement if needed.
if (!std::filesystem::exists("./data/00040010/00022400")) {
std::filesystem::copy("./data/00040010/00022100", "./data/00040010/00022400", std::filesystem::copy_options::recursive);
if (!std::filesystem::exists(content_dir / "00040010/00022400")) {
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("./data/rw/sys");
std::filesystem::create_directories("./data/twlp");
std::filesystem::create_directories(content_dir / "ro/sys");
std::filesystem::create_directories(content_dir / "rw/sys");
std::filesystem::create_directories(content_dir / "twlp");
// Create dummy HWCAL0, required for cfg module to work without running initial system setup first
char zero[128]{};
{
std::ofstream hwcal0("./data/ro/sys/HWCAL0.dat");
std::ofstream hwcal0(content_dir / "ro/sys/HWCAL0.dat");
const auto size = 2512;
for (unsigned i = 0; i < size / sizeof(zero); ++i) {
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
// 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));
char region = 2; // Europe

View file

@ -50,14 +50,9 @@ using OS::ClientSession;
using OS::ServerSession;
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!
static std::filesystem::path HostSdmcDirectory() {
auto path_str = "./data/sdmc";
return std::filesystem::path(path_str);
static std::filesystem::path HostSdmcDirectory(Settings::Settings& settings) {
return GetRootDataDirectory(settings) / "sdmc/";
}
/**
@ -100,13 +95,13 @@ class RomfsFile : public File {
std::string filename;
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) {
// TODO: The content ID is hardcoded currently.
uint32_t content_id = 0;
filename = [&]() -> std::string {
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 << "/";
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) {
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) {
@ -1381,9 +1376,9 @@ public:
uint32_t program_id_high = (program_info.program_id >> 32);
uint32_t program_id_low = (program_info.program_id & 0xFFFFFFFF);
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 */) {
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 {
throw std::runtime_error("Unknown media type");
}
@ -1465,9 +1460,9 @@ private:
uint32_t extdata_id_high = info.save_id >> 32;
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 {
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)
: process(thread.GetParentProcess()),
logger(*thread.GetLogger()) {
logger(*thread.GetLogger()),
settings(process.GetOS().settings) {
OS::Result result;
@ -2548,7 +2544,7 @@ OpenArchive(FakeThread& thread, FSContext& context, ProcessId process_id, uint32
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));
}
@ -2577,13 +2573,13 @@ OpenArchive(FakeThread& thread, FSContext& context, ProcessId process_id, uint32
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));
}
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));
}
@ -2597,7 +2593,7 @@ OpenArchive(FakeThread& thread, FSContext& context, ProcessId process_id, uint32
// TWL photo. Opened by mset during initial system setup
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));
}

View file

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

View file

@ -137,4 +137,8 @@ ValidatedHostPath PathValidator::ValidateAndGetSandboxedTreePath(const Utf8PathT
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;
};
std::filesystem::path GetRootDataDirectory(Settings::Settings& settings);
} // namespace HLE

View file

@ -1,3 +1,4 @@
#include "fs_common.hpp"
#include "pxi.hpp"
#include "cryptopp/aes.h"
#include "cryptopp/modes.h"
@ -114,7 +115,7 @@ FakePXI::FakePXI(FakeThread& thread)
// Search for installed NAND titles
// 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> {
// 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;
const std::string filename = Meta::invoke([&] {
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 << "/";
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:
ArchiveSystemSaveData(uint32_t media_type) {
ArchiveSystemSaveData(Settings::Settings& settings, uint32_t media_type) {
switch (media_type) {
case 0:
base_path = "./data/data/";
// TODO: This ID is actually generated from data in moveable.sed
std::array<uint8_t, 0x10> id0;
ranges::fill(id0, 0);
for (auto byte : id0)
base_path += fmt::format("{:02x}", byte);
base_path += "/sysdata/";
base_path = (GetRootDataDirectory(settings) / "data" / fmt::format("{:02x}", fmt::join(id0, "")) / "sysdata").string();
break;
default:
@ -539,9 +534,10 @@ std::unique_ptr<File> OpenNCCHSubFile(Thread& thread, Platform::FS::ProgramInfo
return std::make_unique<DummyExeFSFile>();
}
auto ncch_filename = fmt::format("./data/{:08x}/{:08x}/content/{:08x}.cxi",
program_info.program_id >> 32, program_info.program_id & 0xFFFFFFFF, content_id);
ncch = std::make_unique<HostFile>(ncch_filename, HostFile::Default);
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);
ncch = std::make_unique<HostFile>(ncch_filename.string(), HostFile::Default);
} else if (gamecard && program_info.media_type == 2) {
if (content_id > Meta::to_underlying(Loader::NCSDPartitionId::UpdateData)) {
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);
auto& settings = thread.GetOS().settings;
switch (archive_id) {
case 0x567890b0:
{
@ -889,7 +887,7 @@ static std::tuple<Result, uint64_t> OpenArchive(FakeThread& thread, Context& con
// System SaveData stored on NAND
// TODO: Should we verify that not more than 4 bytes have been given?
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++;
context.archives.emplace(std::make_pair(archive_handle, std::move(archive)));
return std::make_tuple(RESULT_OK, archive_handle);

View file

@ -2,6 +2,16 @@
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;
}