mirror of
https://github.com/mikage-emu/mikage-dev.git
synced 2025-02-04 03:18:22 +01:00
FS: Put files in standard locations
This commit is contained in:
parent
f15d7c53a4
commit
c600e2d4c0
9 changed files with 121 additions and 42 deletions
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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/";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -95,4 +95,6 @@ protected:
|
|||
virtual std::filesystem::path GetSandboxHostRoot() const = 0;
|
||||
};
|
||||
|
||||
std::filesystem::path GetRootDataDirectory(Settings::Settings& settings);
|
||||
|
||||
} // namespace HLE
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue