ams-mtc: add ams-mtc

This commit is contained in:
souldbminersmwc
2025-11-09 15:44:33 -05:00
parent ac42e7af43
commit a76c2b33b6
3765 changed files with 568544 additions and 16 deletions

View File

@@ -0,0 +1,383 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "../amsmitm_fs_utils.hpp"
#include "../amsmitm_initialization.hpp"
#include "fs_shim.h"
#include "fs_mitm_service.hpp"
#include "fsmitm_boot0storage.hpp"
#include "fsmitm_calibration_binary_storage.hpp"
#include "fsmitm_layered_romfs_storage.hpp"
#include "fsmitm_save_utils.hpp"
#include "fsmitm_readonly_layered_filesystem.hpp"
namespace ams::mitm::fs {
using namespace ams::fs;
namespace {
constexpr const ams::fs::Path AtmosphereHblWebContentDirPath = fs::MakeConstantPath("/atmosphere/hbl_html/");
constexpr const char ProgramWebContentDir[] = "/manual_html/";
constinit os::SdkMutex g_boot0_detect_lock;
constinit bool g_detected_boot0_kind = false;
constinit bool g_is_boot0_custom_public_key = false;
constinit fssrv::impl::ProgramIndexMapInfoManager g_program_index_map_info_manager;
bool IsBoot0CustomPublicKey(::FsStorage &storage) {
if (AMS_UNLIKELY(!g_detected_boot0_kind)) {
std::scoped_lock lk(g_boot0_detect_lock);
if (AMS_LIKELY(!g_detected_boot0_kind)) {
g_is_boot0_custom_public_key = DetectBoot0CustomPublicKey(storage);
g_detected_boot0_kind = true;
}
}
return g_is_boot0_custom_public_key;
}
bool GetSettingsItemBooleanValue(const char *name, const char *key) {
u8 tmp = 0;
AMS_ABORT_UNLESS(settings::fwdbg::GetSettingsItemValue(std::addressof(tmp), sizeof(tmp), name, key) == sizeof(tmp));
return (tmp != 0);
}
template<typename... Arguments>
constexpr ALWAYS_INLINE auto MakeSharedFileSystem(Arguments &&... args) {
return sf::CreateSharedObjectEmplaced<ams::fssrv::sf::IFileSystem, ams::fssrv::impl::FileSystemInterfaceAdapter>(std::forward<Arguments>(args)...);
}
template<typename... Arguments>
constexpr ALWAYS_INLINE auto MakeSharedStorage(Arguments &&... args) {
return sf::CreateSharedObjectEmplaced<ams::fssrv::sf::IStorage, ams::fssrv::impl::StorageInterfaceAdapter>(std::forward<Arguments>(args)...);
}
Result OpenHblWebContentFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> &out, ncm::ProgramId program_id) {
/* Verify eligibility. */
bool is_hbl;
R_UNLESS(R_SUCCEEDED(ams::pm::info::IsHblProgramId(std::addressof(is_hbl), program_id)), sm::mitm::ResultShouldForwardToSession());
R_UNLESS(is_hbl, sm::mitm::ResultShouldForwardToSession());
/* Hbl html directory must exist. */
{
FsDir d;
R_UNLESS(R_SUCCEEDED(mitm::fs::OpenSdDirectory(std::addressof(d), AtmosphereHblWebContentDirPath.GetString(), fs::OpenDirectoryMode_Directory)), sm::mitm::ResultShouldForwardToSession());
fsDirClose(std::addressof(d));
}
/* Open the SD card using fs.mitm's session. */
FsFileSystem sd_fs;
R_TRY(fsOpenSdCardFileSystem(std::addressof(sd_fs)));
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(sd_fs.s))};
std::unique_ptr<fs::fsa::IFileSystem> sd_ifs = std::make_unique<fs::RemoteFileSystem>(sd_fs);
auto subdir_fs = std::make_unique<fssystem::SubDirectoryFileSystem>(std::move(sd_ifs));
R_TRY(subdir_fs->Initialize(AtmosphereHblWebContentDirPath));
out.SetValue(MakeSharedFileSystem(std::make_shared<fs::ReadOnlyFileSystem>(std::move(subdir_fs)), false), target_object_id);
R_SUCCEED();
}
Result OpenProgramSpecificWebContentFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> &out, ncm::ProgramId program_id, FsFileSystemType filesystem_type, Service *fwd, const fssrv::sf::Path *path, bool with_id) {
/* Directory must exist. */
R_UNLESS(HasSdManualHtmlContent(program_id), sm::mitm::ResultShouldForwardToSession());
/* Open the SD card using fs.mitm's session. */
FsFileSystem sd_fs;
R_TRY(fsOpenSdCardFileSystem(std::addressof(sd_fs)));
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(sd_fs.s))};
std::unique_ptr<fs::fsa::IFileSystem> sd_ifs = std::make_unique<fs::RemoteFileSystem>(sd_fs);
/* Format the subdirectory path. */
char program_web_content_raw_path[0x100];
FormatAtmosphereSdPath(program_web_content_raw_path, sizeof(program_web_content_raw_path), program_id, ProgramWebContentDir);
ams::fs::Path program_web_content_path;
R_TRY(program_web_content_path.SetShallowBuffer(program_web_content_raw_path));
/* Make a new filesystem. */
{
auto subdir_fs = std::make_unique<fssystem::SubDirectoryFileSystem>(std::move(sd_ifs));
R_TRY(subdir_fs->Initialize(program_web_content_path));
std::shared_ptr<fs::fsa::IFileSystem> new_fs = nullptr;
/* Try to open the existing fs. */
FsFileSystem base_fs;
bool opened_base_fs = false;
if (with_id) {
opened_base_fs = R_SUCCEEDED(fsOpenFileSystemWithIdFwd(fwd, std::addressof(base_fs), static_cast<u64>(program_id), filesystem_type, path->str));
} else {
opened_base_fs = R_SUCCEEDED(fsOpenFileSystemWithPatchFwd(fwd, std::addressof(base_fs), static_cast<u64>(program_id), filesystem_type));
}
if (opened_base_fs) {
/* Create a layered adapter. */
new_fs = std::make_shared<ReadOnlyLayeredFileSystem>(std::move(subdir_fs), std::make_unique<fs::RemoteFileSystem>(base_fs));
} else {
/* Without an existing FS, just make a read only adapter to the subdirectory. */
new_fs = std::make_shared<fs::ReadOnlyFileSystem>(std::move(subdir_fs));
}
out.SetValue(MakeSharedFileSystem(std::move(new_fs), false), target_object_id);
}
R_SUCCEED();
}
Result OpenWebContentFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> &out, ncm::ProgramId client_program_id, ncm::ProgramId program_id, FsFileSystemType filesystem_type, Service *fwd, const fssrv::sf::Path *path, bool with_id, bool try_program_specific) {
/* Check first that we're a web applet opening web content. */
R_UNLESS(ncm::IsWebAppletId(client_program_id), sm::mitm::ResultShouldForwardToSession());
R_UNLESS(filesystem_type == FsFileSystemType_ContentManual, sm::mitm::ResultShouldForwardToSession());
/* Try to mount the HBL web filesystem. If this succeeds then we're done. */
R_SUCCEED_IF(R_SUCCEEDED(OpenHblWebContentFileSystem(out, program_id)));
/* If program specific override shouldn't be attempted, fall back. */
R_UNLESS(try_program_specific, sm::mitm::ResultShouldForwardToSession());
/* If we're not opening a HBL filesystem, just try to open a generic one. */
R_RETURN(OpenProgramSpecificWebContentFileSystem(out, program_id, filesystem_type, fwd, path, with_id));
}
}
bool HasSdManualHtmlContent(ncm::ProgramId program_id) {
/* Directory must exist. */
FsDir d;
if (R_SUCCEEDED(OpenAtmosphereSdDirectory(std::addressof(d), program_id, ProgramWebContentDir, fs::OpenDirectoryMode_Directory))) {
::fsDirClose(std::addressof(d));
return true;
} else {
return false;
}
}
Result FsMitmService::OpenFileSystemWithPatch(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id, u32 _filesystem_type) {
R_RETURN(OpenWebContentFileSystem(out, m_client_info.program_id, program_id, static_cast<FsFileSystemType>(_filesystem_type), m_forward_service.get(), nullptr, false, m_client_info.override_status.IsProgramSpecific()));
}
Result FsMitmService::OpenFileSystemWithId(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, const fssrv::sf::Path &path, ncm::ProgramId program_id, u32 _filesystem_type) {
R_RETURN(OpenWebContentFileSystem(out, m_client_info.program_id, program_id, static_cast<FsFileSystemType>(_filesystem_type), m_forward_service.get(), std::addressof(path), true, m_client_info.override_status.IsProgramSpecific()));
}
Result FsMitmService::OpenSdCardFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out) {
/* We only care about redirecting this for NS/emummc. */
R_UNLESS(m_client_info.program_id == ncm::SystemProgramId::Ns, sm::mitm::ResultShouldForwardToSession());
R_UNLESS(emummc::IsActive(), sm::mitm::ResultShouldForwardToSession());
/* Create a new SD card filesystem. */
FsFileSystem sd_fs;
R_TRY(fsOpenSdCardFileSystemFwd(m_forward_service.get(), std::addressof(sd_fs)));
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(sd_fs.s))};
/* Return output filesystem. */
auto redir_fs = std::make_shared<fssystem::DirectoryRedirectionFileSystem>(std::make_unique<RemoteFileSystem>(sd_fs));
R_TRY(redir_fs->InitializeWithFixedPath("/Nintendo", emummc::GetNintendoDirPath()));
out.SetValue(MakeSharedFileSystem(std::move(redir_fs), false), target_object_id);
R_SUCCEED();
}
Result FsMitmService::OpenSaveDataFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, u8 _space_id, const fs::SaveDataAttribute &attribute) {
/* We only want to intercept saves for games, right now. */
const bool is_game_or_hbl = m_client_info.override_status.IsHbl() || ncm::IsApplicationId(m_client_info.program_id);
R_UNLESS(is_game_or_hbl, sm::mitm::ResultShouldForwardToSession());
/* Only redirect if the appropriate system setting is set. */
R_UNLESS(GetSettingsItemBooleanValue("atmosphere", "fsmitm_redirect_saves_to_sd"), sm::mitm::ResultShouldForwardToSession());
/* Only redirect if the specific title being accessed has a redirect save flag. */
R_UNLESS(cfg::HasContentSpecificFlag(m_client_info.program_id, "redirect_save"), sm::mitm::ResultShouldForwardToSession());
/* Only redirect account savedata. */
R_UNLESS(attribute.type == fs::SaveDataType::Account, sm::mitm::ResultShouldForwardToSession());
/* Get enum type for space id. */
auto space_id = static_cast<FsSaveDataSpaceId>(_space_id);
/* Verify we can open the save. */
static_assert(sizeof(fs::SaveDataAttribute) == sizeof(::FsSaveDataAttribute));
FsFileSystem save_fs;
R_UNLESS(R_SUCCEEDED(fsOpenSaveDataFileSystemFwd(m_forward_service.get(), std::addressof(save_fs), space_id, reinterpret_cast<const FsSaveDataAttribute *>(std::addressof(attribute)))), sm::mitm::ResultShouldForwardToSession());
std::unique_ptr<fs::fsa::IFileSystem> save_ifs = std::make_unique<fs::RemoteFileSystem>(save_fs);
/* Mount the SD card using fs.mitm's session. */
FsFileSystem sd_fs;
R_TRY(fsOpenSdCardFileSystem(std::addressof(sd_fs)));
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(sd_fs.s))};
std::shared_ptr<fs::fsa::IFileSystem> sd_ifs = std::make_shared<fs::RemoteFileSystem>(sd_fs);
/* Verify that we can open the save directory, and that it exists. */
const ncm::ProgramId application_id = attribute.program_id == ncm::InvalidProgramId ? m_client_info.program_id : attribute.program_id;
char save_dir_raw_path[0x100];
R_TRY(mitm::fs::SaveUtil::GetDirectorySaveDataPath(save_dir_raw_path, sizeof(save_dir_raw_path), application_id, space_id, attribute));
ams::fs::Path save_dir_path;
R_TRY(save_dir_path.SetShallowBuffer(save_dir_raw_path));
/* Check if this is the first time we're making the save. */
bool is_new_save = false;
{
fs::DirectoryEntryType ent;
R_TRY_CATCH(sd_ifs->GetEntryType(std::addressof(ent), save_dir_path)) {
R_CATCH(fs::ResultPathNotFound) { is_new_save = true; }
R_CATCH_ALL() { /* ... */ }
} R_END_TRY_CATCH;
}
/* Ensure the directory exists. */
R_TRY(fssystem::EnsureDirectory(sd_ifs.get(), save_dir_path));
/* Create directory savedata filesystem. */
auto subdir_fs = std::make_unique<fssystem::SubDirectoryFileSystem>(sd_ifs);
R_TRY(subdir_fs->Initialize(save_dir_path));
std::shared_ptr<fssystem::DirectorySaveDataFileSystem> dirsave_ifs = std::make_shared<fssystem::DirectorySaveDataFileSystem>(std::move(subdir_fs));
/* Ensure correct directory savedata filesystem state. */
R_TRY(dirsave_ifs->Initialize(true, true, true));
/* If it's the first time we're making the save, copy existing savedata over. */
if (is_new_save) {
/* TODO: Check error? */
fs::DirectoryEntry work_entry;
constexpr const fs::Path root_path = fs::MakeConstantPath("/");
u8 savedata_copy_buffer[2_KB];
fssystem::CopyDirectoryRecursively(dirsave_ifs.get(), save_ifs.get(), root_path, root_path, std::addressof(work_entry), savedata_copy_buffer, sizeof(savedata_copy_buffer));
}
/* Set output. */
out.SetValue(MakeSharedFileSystem(std::move(dirsave_ifs), false), target_object_id);
R_SUCCEED();
}
Result FsMitmService::OpenBisStorage(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, u32 _bis_partition_id) {
const ::FsBisPartitionId bis_partition_id = static_cast<::FsBisPartitionId>(_bis_partition_id);
/* Try to open a storage for the partition. */
FsStorage bis_storage;
R_TRY(fsOpenBisStorageFwd(m_forward_service.get(), std::addressof(bis_storage), bis_partition_id));
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(bis_storage.s))};
const bool is_sysmodule = ncm::IsSystemProgramId(m_client_info.program_id);
const bool is_hbl = m_client_info.override_status.IsHbl();
const bool can_write_bis = is_sysmodule || (is_hbl && GetSettingsItemBooleanValue("atmosphere", "enable_hbl_bis_write"));
/* Allow HBL to write to boot1 (safe firm) + package2. */
/* This is needed to not break compatibility with ChoiDujourNX, which does not check for write access before beginning an update. */
/* TODO: get fixed so that this can be turned off without causing bricks :/ */
const bool is_package2 = (FsBisPartitionId_BootConfigAndPackage2Part1 <= bis_partition_id && bis_partition_id <= FsBisPartitionId_BootConfigAndPackage2Part6);
const bool is_boot1 = bis_partition_id == FsBisPartitionId_BootPartition2Root;
const bool can_write_bis_for_choi_support = is_hbl && (is_package2 || is_boot1);
/* Set output storage. */
if (bis_partition_id == FsBisPartitionId_BootPartition1Root) {
if (IsBoot0CustomPublicKey(bis_storage)) {
out.SetValue(MakeSharedStorage(std::make_shared<CustomPublicKeyBoot0Storage>(bis_storage, m_client_info, spl::GetSocType())), target_object_id);
} else {
out.SetValue(MakeSharedStorage(std::make_shared<Boot0Storage>(bis_storage, m_client_info)), target_object_id);
}
} else if (bis_partition_id == FsBisPartitionId_CalibrationBinary) {
out.SetValue(MakeSharedStorage(std::make_shared<CalibrationBinaryStorage>(bis_storage, m_client_info)), target_object_id);
} else {
if (can_write_bis || can_write_bis_for_choi_support) {
/* We can write, so create a writable storage. */
out.SetValue(MakeSharedStorage(std::make_shared<RemoteStorage>(bis_storage)), target_object_id);
} else {
/* We can only read, so create a readable storage. */
std::unique_ptr<ams::fs::IStorage> unique_bis = std::make_unique<RemoteStorage>(bis_storage);
out.SetValue(MakeSharedStorage(std::make_shared<ReadOnlyStorageAdapter>(std::move(unique_bis))), target_object_id);
}
}
R_SUCCEED();
}
Result FsMitmService::OpenDataStorageByCurrentProcess(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out) {
/* Only mitm if we should override contents for the current process. */
R_UNLESS(m_client_info.override_status.IsProgramSpecific(), sm::mitm::ResultShouldForwardToSession());
/* Only mitm if there is actually an override romfs. */
R_UNLESS(mitm::fs::HasSdRomfsContent(m_client_info.program_id), sm::mitm::ResultShouldForwardToSession());
/* Try to open the process romfs. */
FsStorage data_storage;
R_TRY(fsOpenDataStorageByCurrentProcessFwd(m_forward_service.get(), std::addressof(data_storage)));
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(data_storage.s))};
/* Get a layered storage for the process romfs. */
out.SetValue(MakeSharedStorage(GetLayeredRomfsStorage(m_client_info.program_id, data_storage, true)), target_object_id);
R_SUCCEED();
}
Result FsMitmService::OpenDataStorageByDataId(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, ncm::DataId _data_id, u8 storage_id) {
/* Only mitm if we should override contents for the current process. */
R_UNLESS(m_client_info.override_status.IsProgramSpecific(), sm::mitm::ResultShouldForwardToSession());
/* TODO: Decide how to handle DataId vs ProgramId for this API. */
const ncm::ProgramId data_id = {_data_id.value};
/* Only mitm if there is actually an override romfs. */
R_UNLESS(mitm::fs::HasSdRomfsContent(data_id), sm::mitm::ResultShouldForwardToSession());
/* Try to open the data id. */
FsStorage data_storage;
R_TRY(fsOpenDataStorageByDataIdFwd(m_forward_service.get(), std::addressof(data_storage), static_cast<u64>(data_id), static_cast<NcmStorageId>(storage_id)));
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(data_storage.s))};
/* Get a layered storage for the data id. */
out.SetValue(MakeSharedStorage(GetLayeredRomfsStorage(data_id, data_storage, false)), target_object_id);
R_SUCCEED();
}
Result FsMitmService::OpenDataStorageWithProgramIndex(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, u8 program_index) {
/* Only mitm if we should override contents for the current process. */
R_UNLESS(m_client_info.override_status.IsProgramSpecific(), sm::mitm::ResultShouldForwardToSession());
/* Get the relevant program id. */
const ncm::ProgramId program_id = g_program_index_map_info_manager.GetProgramId(m_client_info.program_id, program_index);
/* If we don't know about the program or don't have content, forward. */
R_UNLESS(program_id != ncm::InvalidProgramId, sm::mitm::ResultShouldForwardToSession());
R_UNLESS(mitm::fs::HasSdRomfsContent(program_id), sm::mitm::ResultShouldForwardToSession());
/* Try to open the process romfs. */
FsStorage data_storage;
R_TRY(fsOpenDataStorageWithProgramIndexFwd(m_forward_service.get(), std::addressof(data_storage), program_index));
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(data_storage.s))};
/* Get a layered storage for the process romfs. */
out.SetValue(MakeSharedStorage(GetLayeredRomfsStorage(program_id, data_storage, true)), target_object_id);
R_SUCCEED();
}
Result FsMitmService::RegisterProgramIndexMapInfo(const sf::InBuffer &info_buffer, s32 info_count) {
/* Try to register with FS. */
R_TRY(fsRegisterProgramIndexMapInfoFwd(m_forward_service.get(), info_buffer.GetPointer(), info_buffer.GetSize(), info_count));
/* Register with ourselves. */
R_ABORT_UNLESS(g_program_index_map_info_manager.Reset(reinterpret_cast<const fs::ProgramIndexMapInfo *>(info_buffer.GetPointer()), info_count));
R_SUCCEED();
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
#include <stratosphere/fssrv/fssrv_interface_adapters.hpp>
#define AMS_FS_MITM_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 7, Result, OpenFileSystemWithPatch, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id, u32 _filesystem_type), (out, program_id, _filesystem_type), hos::Version_2_0_0) \
AMS_SF_METHOD_INFO(C, H, 8, Result, OpenFileSystemWithId, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, const fssrv::sf::Path &path, ncm::ProgramId program_id, u32 _filesystem_type), (out, path, program_id, _filesystem_type), hos::Version_2_0_0) \
AMS_SF_METHOD_INFO(C, H, 18, Result, OpenSdCardFileSystem, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 51, Result, OpenSaveDataFileSystem, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, u8 space_id, const ams::fs::SaveDataAttribute &attribute), (out, space_id, attribute)) \
AMS_SF_METHOD_INFO(C, H, 12, Result, OpenBisStorage, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, u32 bis_partition_id), (out, bis_partition_id)) \
AMS_SF_METHOD_INFO(C, H, 200, Result, OpenDataStorageByCurrentProcess, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 202, Result, OpenDataStorageByDataId, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, ncm::DataId data_id, u8 storage_id), (out, data_id, storage_id)) \
AMS_SF_METHOD_INFO(C, H, 205, Result, OpenDataStorageWithProgramIndex, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, u8 program_index), (out, program_index), hos::Version_7_0_0) \
AMS_SF_METHOD_INFO(C, H, 810, Result, RegisterProgramIndexMapInfo, (const sf::InBuffer &info_buffer, s32 info_count), (info_buffer, info_count), hos::Version_7_0_0)
AMS_SF_DEFINE_MITM_INTERFACE(ams::mitm::fs, IFsMitmInterface, AMS_FS_MITM_INTERFACE_INFO, 0x7DF34ED2)
namespace ams::mitm::fs {
class FsMitmService : public sf::MitmServiceImplBase {
public:
using MitmServiceImplBase::MitmServiceImplBase;
public:
static constexpr ALWAYS_INLINE bool ShouldMitmProgramId(const ncm::ProgramId program_id) {
/* We want to mitm everything that isn't a system-module. */
if (!ncm::IsSystemProgramId(program_id)) {
return true;
}
/* We want to mitm ns, to intercept SD card requests and program index map info registration. */
if (program_id == ncm::SystemProgramId::Ns) {
return true;
}
/* We want to mitm settings, to intercept CAL0. */
if (program_id == ncm::SystemProgramId::Settings) {
return true;
}
/* We want to mitm sdb, to support sd-romfs redirection of common system archives (like system font, etc). */
/* NOTE: In 16.0.0+, this was moved to glue. */
if (program_id == ncm::SystemProgramId::Sdb || program_id == ncm::SystemProgramId::Glue) {
return true;
}
return false;
}
static bool ShouldMitm(const sm::MitmProcessInfo &client_info) {
static std::atomic_bool has_launched_qlaunch = false;
/* TODO: intercepting everything seems to cause issues with sleep mode, for some reason. */
/* Figure out why, and address it. */
/* TODO: This may be because pre-rewrite code really mismanaged domain objects in a way that would cause bad things. */
/* Need to verify if this is fixed now. */
if (client_info.program_id == ncm::SystemAppletId::Qlaunch || client_info.program_id == ncm::SystemAppletId::MaintenanceMenu) {
has_launched_qlaunch = true;
}
return has_launched_qlaunch || ShouldMitmProgramId(client_info.program_id);
}
public:
/* Overridden commands. */
Result OpenFileSystemWithPatch(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id, u32 _filesystem_type);
Result OpenFileSystemWithId(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, const fssrv::sf::Path &path, ncm::ProgramId program_id, u32 _filesystem_type);
Result OpenSdCardFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out);
Result OpenSaveDataFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, u8 space_id, const ams::fs::SaveDataAttribute &attribute);
Result OpenBisStorage(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, u32 bis_partition_id);
Result OpenDataStorageByCurrentProcess(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out);
Result OpenDataStorageByDataId(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, ncm::DataId data_id, u8 storage_id);
Result OpenDataStorageWithProgramIndex(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, u8 program_index);
Result RegisterProgramIndexMapInfo(const sf::InBuffer &info_buffer, s32 info_count);
};
static_assert(IsIFsMitmInterface<FsMitmService>);
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fs_shim.h"
/* Missing fsp-srv commands. */
static Result _fsOpenSession(Service *s, Service* out, u32 cmd_id) {
return serviceDispatch(s, cmd_id,
.out_num_objects = 1,
.out_objects = out,
);
}
Result fsOpenSdCardFileSystemFwd(Service* s, FsFileSystem* out) {
return _fsOpenSession(s, &out->s, 18);
}
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partition_id) {
const u32 tmp = partition_id;
return serviceDispatchIn(s, 12, tmp,
.out_num_objects = 1,
.out_objects = &out->s,
);
}
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out) {
return _fsOpenSession(s, &out->s, 200);
}
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, NcmStorageId storage_id) {
const struct {
u8 storage_id;
u64 data_id;
} in = { storage_id, data_id };
return serviceDispatchIn(s, 202, in,
.out_num_objects = 1,
.out_objects = &out->s,
);
}
Result fsOpenDataStorageWithProgramIndexFwd(Service* s, FsStorage* out, u8 program_index) {
return serviceDispatchIn(s, 205, program_index,
.out_num_objects = 1,
.out_objects = &out->s,
);
}
Result fsRegisterProgramIndexMapInfoFwd(Service* s, const void *buf, size_t buf_size, s32 count) {
return serviceDispatchIn(s, 810, count,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In },
.buffers = { { buf, buf_size } },
);
}
Result fsOpenSaveDataFileSystemFwd(Service* s, FsFileSystem* out, FsSaveDataSpaceId save_data_space_id, const FsSaveDataAttribute *attr) {
const struct {
u8 save_data_space_id;
u8 pad[7];
FsSaveDataAttribute attr;
} in = { (u8)save_data_space_id, {0}, *attr };
return serviceDispatchIn(s, 51, in,
.out_num_objects = 1,
.out_objects = &out->s,
);
}
Result fsOpenFileSystemWithPatchFwd(Service* s, FsFileSystem* out, u64 id, FsFileSystemType fsType) {
const struct {
u32 fsType;
u64 id;
} in = { fsType, id };
return serviceDispatchIn(s, 7, in,
.out_num_objects = 1,
.out_objects = &out->s
);
}
Result fsOpenFileSystemWithIdFwd(Service* s, FsFileSystem* out, u64 id, FsFileSystemType fsType, const char* contentPath) {
const struct {
u32 fsType;
u64 id;
} in = { fsType, id };
return serviceDispatchIn(s, 8, in,
.buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In },
.buffers = { { contentPath, FS_MAX_PATH } },
.out_num_objects = 1,
.out_objects = &out->s
);
}

View File

@@ -0,0 +1,31 @@
/**
* @file fs_shim.h
* @brief Filesystem Services (fs) IPC wrapper for fs.mitm.
* @author SciresM
* @copyright libnx Authors
*/
#pragma once
#include <switch.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Missing fsp-srv commands. */
Result fsOpenSdCardFileSystemFwd(Service* s, FsFileSystem* out);
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partition_id);
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out);
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, NcmStorageId storage_id);
Result fsOpenDataStorageWithProgramIndexFwd(Service* s, FsStorage* out, u8 program_index);
Result fsRegisterProgramIndexMapInfoFwd(Service* s, const void *buf, size_t buf_size, s32 count);
Result fsOpenSaveDataFileSystemFwd(Service* s, FsFileSystem* out, FsSaveDataSpaceId save_data_space_id, const FsSaveDataAttribute *attr);
Result fsOpenFileSystemWithPatchFwd(Service* s, FsFileSystem* out, u64 id, FsFileSystemType fsType);
Result fsOpenFileSystemWithIdFwd(Service* s, FsFileSystem* out, u64 id, FsFileSystemType fsType, const char* contentPath);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,267 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "fsmitm_boot0storage.hpp"
namespace ams::mitm::fs {
using namespace ams::fs;
namespace {
constinit os::SdkMutex g_boot0_access_mutex;
constinit bool g_custom_public_key = false;
constinit u8 g_boot0_bct_buffer[Boot0Storage::BctEndOffset];
/* Recognize special public key (https://gist.github.com/SciresM/16b63ac1d80494522bdba2c57995257c). */
/* P = 19 */
/* Q = 1696986749729493925354392349339746171297507422986462747526968361144447230710192316397327889522451749459854070558277878297255552508603806832852079596337539247651161831569525505882103311631577368514276343192042634740927726070847704397913856975832811679847928433261678072951551065705680482548543833651752439700272736498378724153330763357721354498194000536297732323628263256733931353143625854828275237159155585342783077681713929284136658773985266864804093157854331138230313706015557050002740810464618031715670281442110238274404626065924786185264268216336867948322976979393032640085259926883014490947373494538254895109731 */
/* N = 0xFF696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696959 */
/* E = 0x10001 */
/* D = 6512128715229088976470211610075969347035078304643231077895577077900787352712063823560162578441773733649014439616165727455431015055675770987914713980812453585413988983206576233689754710500864883529402371292948326392791238474661859182717295176679567362482790015587820446999760239570255254879359445627372805817473978644067558931078225451477635089763009580492462097185005355990612929951162366081631888011031830742459571000203341001926135389508196521518687349554188686396554248868403128728646457247197407637887195043486221295751496667162366700477934591694110831002874992896076061627516220934290742867397720103040314639313 */
constexpr const u8 CustomPublicKey[0x100] = {
0x59, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0xFF,
};
}
bool Boot0Storage::CanModifyBctPublicKey() {
if (exosphere::IsRcmBugPatched()) {
/* RCM bug patched. */
/* Only allow NS to update the BCT pubks. */
/* AutoRCM on a patched unit will cause a brick, so homebrew should NOT be allowed to write. */
return m_client_info.program_id == ncm::SystemProgramId::Ns;
} else {
/* RCM bug unpatched. */
/* Allow homebrew but not NS to update the BCT pubks. */
return m_client_info.override_status.IsHbl();
}
}
Result Boot0Storage::Read(s64 offset, void *_buffer, size_t size) {
std::scoped_lock lk{g_boot0_access_mutex};
AMS_ABORT_UNLESS(!g_custom_public_key);
/* Check if we have nothing to do. */
R_SUCCEED_IF(size == 0);
R_RETURN(Base::Read(offset, _buffer, size));
}
Result Boot0Storage::Write(s64 offset, const void *_buffer, size_t size) {
std::scoped_lock lk{g_boot0_access_mutex};
AMS_ABORT_UNLESS(!g_custom_public_key);
const u8 *buffer = static_cast<const u8 *>(_buffer);
/* Check if we have nothing to do. */
R_SUCCEED_IF(size == 0);
/* Protect the EKS region from writes. */
if (offset <= EksStart) {
if (offset + size < EksStart) {
/* Fall through, no need to do anything here. */
} else {
if (offset + size > EksEnd) {
/* Perform portion of write falling past end of keyblobs. */
const s64 diff = EksEnd - offset;
R_TRY(Base::Write(EksEnd, buffer + diff, size - diff));
}
/* Adjust size to avoid writing end of data. */
size = EksStart - offset;
}
} else if (offset < EksEnd) {
/* Ignore writes falling strictly within the region. */
R_SUCCEED_IF(offset + size <= EksEnd);
/* Only write past the end of the region. */
const s64 diff = EksEnd - offset;
buffer += diff;
size -= diff;
offset = EksEnd;
}
/* If we have nothing to write, succeed immediately. */
R_SUCCEED_IF(size == 0);
/* We want to protect AutoRCM from NS on ipatched units. If we can modify bct pubks or we're not touching any of them, proceed. */
if (this->CanModifyBctPublicKey() || offset >= BctEndOffset || (util::AlignUp(offset, BctSize) >= BctEndOffset && (offset % BctSize) >= BctPubkEnd)) {
R_RETURN(Base::Write(offset, buffer, size));
}
/* Handle any data written past the end of the pubk region. */
if (offset + size > BctEndOffset) {
const u64 diff = BctEndOffset - offset;
R_TRY(Base::Write(BctEndOffset, buffer + diff, size - diff));
size = diff;
}
/* Read in the current BCT region. */
R_TRY(Base::Read(0, g_boot0_bct_buffer, BctEndOffset));
/* Update the bct buffer. */
for (u64 cur_offset = offset; cur_offset < BctEndOffset && cur_offset < offset + size; cur_offset++) {
const u64 cur_bct_relative_ofs = cur_offset % BctSize;
if (cur_bct_relative_ofs < BctPubkStart || BctPubkEnd <= cur_bct_relative_ofs) {
g_boot0_bct_buffer[cur_offset] = buffer[cur_offset - offset];
}
}
R_RETURN(Base::Write(0, g_boot0_bct_buffer, BctEndOffset));
}
CustomPublicKeyBoot0Storage::CustomPublicKeyBoot0Storage(FsStorage &s, const sm::MitmProcessInfo &c, spl::SocType soc) : Base(s), m_client_info(c), m_soc_type(soc) {
std::scoped_lock lk{g_boot0_access_mutex};
/* We're custom public key. */
g_custom_public_key = true;
}
Result CustomPublicKeyBoot0Storage::Read(s64 offset, void *_buffer, size_t size) {
std::scoped_lock lk{g_boot0_access_mutex};
AMS_ABORT_UNLESS(g_custom_public_key);
/* Check if we have nothing to do. */
R_SUCCEED_IF(size == 0);
u8 *buffer = static_cast<u8 *>(_buffer);
/* Check if we're reading the first BCTs for NS. */
/* If we are, we want to lie about the contents of BCT0/1 so that they validate. */
if (offset < 0x8000 && m_client_info.program_id == ncm::SystemProgramId::Ns) {
R_TRY(Base::Read(0, g_boot0_bct_buffer, Boot0Storage::BctEndOffset));
/* Determine the readable size. */
const size_t readable_bct01_size = std::min<size_t>(0x8000, offset + size) - offset;
std::memcpy(buffer, g_boot0_bct_buffer + 0x8000 + offset, readable_bct01_size);
/* Advance. */
buffer += readable_bct01_size;
offset += readable_bct01_size;
size -= readable_bct01_size;
}
/* Check if we have nothing to do. */
R_SUCCEED_IF(size == 0);
/* Perform whatever remains of the read. */
R_RETURN(Base::Read(offset, buffer, size));
}
Result CustomPublicKeyBoot0Storage::Write(s64 offset, const void *_buffer, size_t size) {
std::scoped_lock lk{g_boot0_access_mutex};
AMS_ABORT_UNLESS(g_custom_public_key);
const u8 *buffer = static_cast<const u8 *>(_buffer);
/* Check if we have nothing to do. */
R_SUCCEED_IF(size == 0);
/* Drop writes to the first BCTs. */
if (offset < 0x8000) {
/* Determine the writable size. */
const size_t writable_bct01_size = std::min<size_t>(0x8000, offset + size) - offset;
/* Advance. */
buffer += writable_bct01_size;
offset += writable_bct01_size;
size -= writable_bct01_size;
}
/* Check if we have nothing to do. */
R_SUCCEED_IF(size == 0);
/* Similarly, we want to drop writes to the end of boot0, where custom bootloader lives. */
R_SUCCEED_IF(offset >= 0x380000);
if (offset + size >= 0x380000) {
size = 0x380000 - offset;
}
/* On erista, we want to protect the EKS region. */
if (m_soc_type == spl::SocType_Erista) {
if (offset <= Boot0Storage::EksStart) {
if (offset + size < Boot0Storage::EksStart) {
/* Fall through, no need to do anything here. */
} else {
if (offset + size > Boot0Storage::EksEnd) {
/* Perform portion of write falling past end of keyblobs. */
const s64 diff = Boot0Storage::EksEnd - offset;
R_TRY(Base::Write(Boot0Storage::EksEnd, buffer + diff, size - diff));
}
/* Adjust size to avoid writing end of data. */
size = Boot0Storage::EksStart - offset;
}
} else if (offset < Boot0Storage::EksEnd) {
/* Ignore writes falling strictly within the region. */
R_SUCCEED_IF(offset + size <= Boot0Storage::EksEnd);
/* Only write past the end of the region. */
const s64 diff = Boot0Storage::EksEnd - offset;
buffer += diff;
size -= diff;
offset = Boot0Storage::EksEnd;
}
}
/* If we have nothing to write, succeed immediately. */
R_SUCCEED_IF(size == 0);
/* Perform whatever remains of the write. */
R_RETURN(Base::Write(offset, buffer, size));
}
bool DetectBoot0CustomPublicKey(::FsStorage &storage) {
/* Determine public key offset. */
const size_t bct_pubk_offset = spl::GetSocType() == spl::SocType_Mariko ? 0x10 : 0x210;
u8 work_buffer[0x400];
/* Read BCT-Normal-Main. */
R_ABORT_UNLESS(::fsStorageRead(std::addressof(storage), 0, work_buffer, sizeof(work_buffer)));
/* Check for custom public key. */
if (std::memcmp(work_buffer + bct_pubk_offset, CustomPublicKey, sizeof(CustomPublicKey)) != 0) {
return false;
}
/* Read BCT-Safe-Main. */
R_ABORT_UNLESS(::fsStorageRead(std::addressof(storage), 0x4000, work_buffer, sizeof(work_buffer)));
/* Check for custom public key. */
if (std::memcmp(work_buffer + bct_pubk_offset, CustomPublicKey, sizeof(CustomPublicKey)) != 0) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::mitm::fs {
template<class Base, size_t SectorSize>
class SectoredStorageAdapter : public Base {
static_assert(std::is_base_of<ams::fs::IStorage, Base>::value);
private:
u8 m_sector_buf[SectorSize];
public:
/* Inherit constructors. */
using Base::Base;
public:
virtual Result Read(s64 offset, void *_buffer, size_t size) override {
u8 *buffer = static_cast<u8 *>(_buffer);
const s64 seek = util::AlignDown(offset, SectorSize);
const s64 sector_ofs = offset - seek;
/* Check if we have nothing to do. */
if (size == 0) {
R_SUCCEED();
}
/* Fast case. */
if (sector_ofs == 0 && util::IsAligned(size, SectorSize)) {
R_RETURN(Base::Read(offset, buffer, size));
}
R_TRY(Base::Read(seek, m_sector_buf, SectorSize));
if (size + sector_ofs <= SectorSize) {
/* Staying within the sector. */
std::memcpy(buffer, m_sector_buf + sector_ofs, size);
} else {
/* Leaving the sector. */
const size_t size_in_sector = SectorSize - sector_ofs;
std::memcpy(buffer, m_sector_buf + sector_ofs, size_in_sector);
size -= size_in_sector;
/* Read as many guaranteed aligned sectors as we can. */
const size_t aligned_remaining_size = util::AlignDown(size, SectorSize);
if (aligned_remaining_size) {
R_TRY(Base::Read(offset + size_in_sector, buffer + size_in_sector, aligned_remaining_size));
size -= aligned_remaining_size;
}
/* Read any leftover data. */
if (size) {
R_TRY(Base::Read(offset + size_in_sector + aligned_remaining_size, m_sector_buf, SectorSize));
std::memcpy(buffer + size_in_sector + aligned_remaining_size, m_sector_buf, size);
}
}
R_SUCCEED();
}
virtual Result Write(s64 offset, const void *_buffer, size_t size) override {
const u8 *buffer = static_cast<const u8 *>(_buffer);
const s64 seek = util::AlignDown(offset, SectorSize);
const s64 sector_ofs = offset - seek;
/* Check if we have nothing to do. */
if (size == 0) {
R_SUCCEED();
}
/* Fast case. */
if (sector_ofs == 0 && util::IsAligned(size, SectorSize)) {
R_RETURN(Base::Write(offset, buffer, size));
}
/* Load existing sector data. */
R_TRY(Base::Read(seek, m_sector_buf, SectorSize));
if (size + sector_ofs <= SectorSize) {
/* Staying within the sector. */
std::memcpy(m_sector_buf + sector_ofs, buffer, size);
R_TRY(Base::Write(seek, m_sector_buf, SectorSize));
} else {
/* Leaving the sector. */
const size_t size_in_sector = SectorSize - sector_ofs;
std::memcpy(m_sector_buf + sector_ofs, buffer, size_in_sector);
R_TRY(Base::Write(seek, m_sector_buf, SectorSize));
size -= size_in_sector;
/* Write as many guaranteed aligned sectors as we can. */
const size_t aligned_remaining_size = util::AlignDown(size, SectorSize);
if (aligned_remaining_size) {
R_TRY(Base::Write(offset + size_in_sector, buffer + size_in_sector, aligned_remaining_size));
size -= aligned_remaining_size;
}
/* Write any leftover data. */
if (size) {
R_TRY(Base::Read(offset + size_in_sector + aligned_remaining_size, m_sector_buf, SectorSize));
std::memcpy(m_sector_buf, buffer + size_in_sector + aligned_remaining_size, size);
R_TRY(Base::Write(offset + size_in_sector + aligned_remaining_size, m_sector_buf, SectorSize));
}
}
R_SUCCEED();
}
};
/* Represents an RCM-preserving BOOT0 partition. */
class Boot0Storage : public SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200> {
public:
using Base = SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200>;
static constexpr s64 BctEndOffset = 0xFC000;
static constexpr s64 BctSize = static_cast<s64>(ams::updater::BctSize);
static constexpr s64 BctPubkStart = 0x210;
static constexpr s64 BctPubkSize = 0x100;
static constexpr s64 BctPubkEnd = BctPubkStart + BctPubkSize;
static constexpr s64 EksStart = 0x180000;
static constexpr s64 EksSize = static_cast<s64>(ams::updater::EksSize);
static constexpr s64 EksEnd = EksStart + EksSize;
private:
sm::MitmProcessInfo m_client_info;
private:
bool CanModifyBctPublicKey();
public:
Boot0Storage(FsStorage &s, const sm::MitmProcessInfo &c) : Base(s), m_client_info(c) { /* ... */ }
public:
virtual Result Read(s64 offset, void *_buffer, size_t size) override;
virtual Result Write(s64 offset, const void *_buffer, size_t size) override;
};
class CustomPublicKeyBoot0Storage : public SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200> {
public:
using Base = SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200>;
private:
sm::MitmProcessInfo m_client_info;
spl::SocType m_soc_type;
public:
CustomPublicKeyBoot0Storage(FsStorage &s, const sm::MitmProcessInfo &c, spl::SocType soc);
public:
virtual Result Read(s64 offset, void *_buffer, size_t size) override;
virtual Result Write(s64 offset, const void *_buffer, size_t size) override;
};
bool DetectBoot0CustomPublicKey(::FsStorage &storage);
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "fsmitm_calibration_binary_storage.hpp"
namespace ams::mitm::fs {
using namespace ams::fs;
namespace {
constinit os::SdkMutex g_cal0_access_mutex;
}
Result CalibrationBinaryStorage::Read(s64 offset, void *_buffer, size_t size) {
/* Acquire exclusive calibration binary access. */
std::scoped_lock lk(g_cal0_access_mutex);
/* Get u8 buffer. */
u8 *buffer = static_cast<u8 *>(_buffer);
/* Succeed on zero-size. */
R_SUCCEED_IF(size == 0);
/* Handle the blank region. */
if (m_read_blank) {
if (BlankStartOffset <= offset && offset < BlankEndOffset) {
const size_t blank_size = std::min(size, static_cast<size_t>(BlankEndOffset - offset));
mitm::ReadFromBlankCalibrationBinary(offset, buffer, blank_size);
size -= blank_size;
buffer += blank_size;
offset += blank_size;
}
}
/* Succeed if we're done. */
R_SUCCEED_IF(size == 0);
/* Handle any in-between data. */
if (BlankEndOffset <= offset && offset < FakeSecureStartOffset) {
const size_t mid_size = std::min(size, static_cast<size_t>(FakeSecureStartOffset - offset));
R_TRY(Base::Read(offset, buffer, mid_size));
size -= mid_size;
buffer += mid_size;
offset += mid_size;
}
/* Succeed if we're done. */
R_SUCCEED_IF(size == 0);
/* Handle the secure region. */
if (FakeSecureStartOffset <= offset && offset < FakeSecureEndOffset) {
const size_t fake_size = std::min(size, static_cast<size_t>(FakeSecureEndOffset - offset));
mitm::ReadFromFakeSecureBackupStorage(offset, buffer, fake_size);
size -= fake_size;
buffer += fake_size;
offset += fake_size;
}
/* Succeed if we're done. */
R_SUCCEED_IF(size == 0);
/* Handle any remaining data. */
R_RETURN(Base::Read(offset, buffer, size));
}
Result CalibrationBinaryStorage::Write(s64 offset, const void *_buffer, size_t size) {
/* Acquire exclusive calibration binary access. */
std::scoped_lock lk(g_cal0_access_mutex);
/* Get const u8 buffer. */
const u8 *buffer = static_cast<const u8 *>(_buffer);
/* Succeed on zero-size. */
R_SUCCEED_IF(size == 0);
/* Only allow writes if we should. */
R_UNLESS(m_allow_writes, fs::ResultUnsupportedOperation());
/* Handle the blank region. */
if (m_read_blank) {
if (BlankStartOffset <= offset && offset < BlankEndOffset) {
const size_t blank_size = std::min(size, static_cast<size_t>(BlankEndOffset - offset));
mitm::WriteToBlankCalibrationBinary(offset, buffer, blank_size);
size -= blank_size;
buffer += blank_size;
offset += blank_size;
}
}
/* Succeed if we're done. */
R_SUCCEED_IF(size == 0);
/* Handle any in-between data. */
if (BlankEndOffset <= offset && offset < FakeSecureStartOffset) {
const size_t mid_size = std::min(size, static_cast<size_t>(FakeSecureStartOffset - offset));
R_TRY(Base::Write(offset, buffer, mid_size));
size -= mid_size;
buffer += mid_size;
offset += mid_size;
}
/* Succeed if we're done. */
R_SUCCEED_IF(size == 0);
/* Handle the secure region. */
if (FakeSecureStartOffset <= offset && offset < FakeSecureEndOffset) {
const size_t fake_size = std::min(size, static_cast<size_t>(FakeSecureEndOffset - offset));
mitm::WriteToFakeSecureBackupStorage(offset, buffer, fake_size);
size -= fake_size;
buffer += fake_size;
offset += fake_size;
}
/* Succeed if we're done. */
R_SUCCEED_IF(size == 0);
/* Handle any remaining data. */
R_RETURN(Base::Write(offset, buffer, size));
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "fsmitm_boot0storage.hpp"
#include "../amsmitm_prodinfo_utils.hpp"
namespace ams::mitm::fs {
/* Represents a protected calibration binary partition. */
class CalibrationBinaryStorage : public SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200> {
public:
using Base = SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200>;
static constexpr s64 BlankStartOffset = 0x0;
static constexpr s64 BlankSize = static_cast<s64>(CalibrationBinarySize);
static constexpr s64 BlankEndOffset = BlankStartOffset + BlankSize;
static constexpr s64 FakeSecureStartOffset = SecureCalibrationInfoBackupOffset;
static constexpr s64 FakeSecureSize = static_cast<s64>(SecureCalibrationBinaryBackupSize);
static constexpr s64 FakeSecureEndOffset = FakeSecureStartOffset + FakeSecureSize;
private:
sm::MitmProcessInfo m_client_info;
bool m_read_blank;
bool m_allow_writes;
public:
CalibrationBinaryStorage(FsStorage &s, const sm::MitmProcessInfo &c)
: Base(s), m_client_info(c),
m_read_blank(mitm::ShouldReadBlankCalibrationBinary()),
m_allow_writes(mitm::IsWriteToCalibrationBinaryAllowed())
{
/* ... */
}
public:
virtual Result Read(s64 offset, void *_buffer, size_t size) override;
virtual Result Write(s64 offset, const void *_buffer, size_t size) override;
};
}

View File

@@ -0,0 +1,432 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "../amsmitm_initialization.hpp"
#include "../amsmitm_fs_utils.hpp"
#include "fsmitm_layered_romfs_storage.hpp"
namespace ams::mitm::fs {
namespace {
constinit os::SdkMutex g_mq_lock;
constinit bool g_started_req_thread;
constinit uintptr_t g_mq_storage[2];
os::MessageQueue g_req_mq(g_mq_storage + 0, 1);
os::MessageQueue g_ack_mq(g_mq_storage + 1, 1);
class LayeredRomfsStorageHolder : public util::IntrusiveRedBlackTreeBaseNode<LayeredRomfsStorageHolder> {
public:
using RedBlackKeyType = u64;
private:
LayeredRomfsStorageImpl *m_impl;
u32 m_reference_count;
bool m_second_chance;
bool m_process_romfs;
public:
LayeredRomfsStorageHolder(LayeredRomfsStorageImpl *impl, bool process_rom) : m_impl(impl), m_reference_count(1), m_second_chance(true), m_process_romfs(process_rom) {
/* ... */
}
~LayeredRomfsStorageHolder() {
delete m_impl;
}
constexpr LayeredRomfsStorageImpl *GetImpl() const { return m_impl; }
constexpr ncm::ProgramId GetProgramId() const { return m_impl->GetProgramId(); }
constexpr u32 GetReferenceCount() const { return m_reference_count; }
void OpenReferenceImpl() { ++m_reference_count; }
void CloseReferenceImpl() { --m_reference_count; }
bool GetSecondChanceImpl() const { return m_second_chance; }
void SetSecondChanceImpl(bool sc) { m_second_chance = sc; }
bool IsProcessRomfs() const { return m_process_romfs; }
static constexpr ALWAYS_INLINE int Compare(const RedBlackKeyType &lval, const LayeredRomfsStorageHolder &rhs) {
const auto rval = rhs.GetProgramId().value;
if (lval < rval) {
return -1;
} else if (lval == rval) {
return 0;
} else {
return 1;
}
}
static constexpr ALWAYS_INLINE int Compare(const LayeredRomfsStorageHolder &lhs, const LayeredRomfsStorageHolder &rhs) {
return Compare(lhs.GetProgramId().value, rhs);
}
};
using LayeredRomfsStorageSet = typename util::IntrusiveRedBlackTreeBaseTraits<LayeredRomfsStorageHolder>::TreeType<LayeredRomfsStorageHolder>;
constinit os::SdkRecursiveMutex g_storage_set_mutex;
constinit LayeredRomfsStorageSet g_storage_set;
constinit os::SdkMutex g_initialization_mutex;
void OpenReference(LayeredRomfsStorageImpl *impl) {
std::scoped_lock lk(g_storage_set_mutex);
auto it = g_storage_set.find_key(impl->GetProgramId().value);
AMS_ABORT_UNLESS(it != g_storage_set.end());
it->OpenReferenceImpl();
}
void CloseReference(LayeredRomfsStorageImpl *impl) {
std::scoped_lock lk(g_storage_set_mutex);
auto it = g_storage_set.find_key(impl->GetProgramId().value);
AMS_ABORT_UNLESS(it != g_storage_set.end());
AMS_ABORT_UNLESS(it->GetReferenceCount() > 0);
it->CloseReferenceImpl();
}
void RomfsInitializerThreadFunction(void *) {
while (true) {
uintptr_t storage_uptr = 0;
g_req_mq.Receive(std::addressof(storage_uptr));
auto *impl = reinterpret_cast<LayeredRomfsStorageImpl *>(storage_uptr);
g_ack_mq.Send(storage_uptr);
std::scoped_lock lk(g_initialization_mutex);
impl->InitializeImpl();
/* Close the initial reference. */
CloseReference(impl);
}
}
void RomfsFinalizerThreadFunction(void *) {
while (true) {
{
std::scoped_lock lk(g_storage_set_mutex);
auto it = g_storage_set.begin();
while (it != g_storage_set.end()) {
if (it->GetReferenceCount() > 0) {
it->SetSecondChanceImpl(true);
++it;
} else if (it->GetSecondChanceImpl()) {
it->SetSecondChanceImpl(false);
++it;
} else {
auto *holder = std::addressof(*it);
it = g_storage_set.erase(it);
delete holder;
}
}
}
os::SleepThread(TimeSpan::FromMilliSeconds(500));
}
}
constexpr size_t RomfsInitializerThreadStackSize = 0x8000;
os::ThreadType g_romfs_initializer_thread;
os::ThreadType g_romfs_finalizer_thread;
alignas(os::ThreadStackAlignment) u8 g_romfs_initializer_thread_stack[RomfsInitializerThreadStackSize];
alignas(os::ThreadStackAlignment) u8 g_romfs_finalizer_thread_stack[os::MemoryPageSize];
void RequestInitializeStorage(uintptr_t storage_uptr) {
std::scoped_lock lk(g_mq_lock);
if (AMS_UNLIKELY(!g_started_req_thread)) {
R_ABORT_UNLESS(os::CreateThread(std::addressof(g_romfs_initializer_thread), RomfsInitializerThreadFunction, nullptr, g_romfs_initializer_thread_stack, sizeof(g_romfs_initializer_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(mitm_fs, RomFileSystemInitializeThread)));
os::SetThreadNamePointer(std::addressof(g_romfs_initializer_thread), AMS_GET_SYSTEM_THREAD_NAME(mitm_fs, RomFileSystemInitializeThread));
os::StartThread(std::addressof(g_romfs_initializer_thread));
R_ABORT_UNLESS(os::CreateThread(std::addressof(g_romfs_finalizer_thread), RomfsFinalizerThreadFunction, nullptr, g_romfs_finalizer_thread_stack, sizeof(g_romfs_finalizer_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(mitm_fs, RomFileSystemInitializeThread)));
os::SetThreadNamePointer(std::addressof(g_romfs_finalizer_thread), AMS_GET_SYSTEM_THREAD_NAME(mitm_fs, RomFileSystemFinalizeThread));
os::StartThread(std::addressof(g_romfs_finalizer_thread));
g_started_req_thread = true;
}
g_req_mq.Send(storage_uptr);
uintptr_t ack = 0;
g_ack_mq.Receive(std::addressof(ack));
AMS_ABORT_UNLESS(ack == storage_uptr);
}
class LayeredRomfsStorage : public ams::fs::IStorage {
private:
LayeredRomfsStorageImpl *m_impl;
public:
LayeredRomfsStorage(LayeredRomfsStorageImpl *impl) : m_impl(impl) {
OpenReference(m_impl);
}
virtual ~LayeredRomfsStorage() {
CloseReference(m_impl);
}
virtual Result Read(s64 offset, void *buffer, size_t size) override {
R_RETURN(m_impl->Read(offset, buffer, size));
}
virtual Result GetSize(s64 *out_size) override {
R_RETURN(m_impl->GetSize(out_size));
}
virtual Result Flush() override {
R_RETURN(m_impl->Flush());
}
virtual Result OperateRange(void *dst, size_t dst_size, ams::fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
R_RETURN(m_impl->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
}
virtual Result Write(s64 offset, const void *buffer, size_t size) override {
/* TODO: Better result code? */
AMS_UNUSED(offset, buffer, size);
R_THROW(ams::fs::ResultUnsupportedOperation())
}
virtual Result SetSize(s64 size) override {
/* TODO: Better result code? */
AMS_UNUSED(size);
R_THROW(ams::fs::ResultUnsupportedOperation())
}
};
}
using namespace ams::fs;
std::shared_ptr<ams::fs::IStorage> GetLayeredRomfsStorage(ncm::ProgramId program_id, ::FsStorage &data_storage, bool is_process_romfs) {
/*Prepare to find or create a new storage. */
LayeredRomfsStorageImpl *impl = nullptr;
{
std::scoped_lock lk(g_storage_set_mutex);
/* Find an existing storage. */
if (auto it = g_storage_set.find_key(program_id.value); it != g_storage_set.end()) {
return std::make_shared<LayeredRomfsStorage>(it->GetImpl());
}
/* We don't have an existing storage. If we're creating process romfs, free any unreferenced process romfs. */
/* This should help prevent too much memory in use at any time. */
if (is_process_romfs) {
auto it = g_storage_set.begin();
while (it != g_storage_set.end()) {
if (it->GetReferenceCount() > 0 || !it->IsProcessRomfs()) {
++it;
} else {
auto *holder = std::addressof(*it);
it = g_storage_set.erase(it);
delete holder;
}
}
}
/* Create a new storage. */
{
::FsFile data_file;
if (R_SUCCEEDED(OpenAtmosphereSdFile(std::addressof(data_file), program_id, "romfs.bin", OpenMode_Read))) {
impl = new LayeredRomfsStorageImpl(std::make_unique<ReadOnlyStorageAdapter>(new RemoteStorage(data_storage)), std::make_unique<ReadOnlyStorageAdapter>(new FileStorage(new RemoteFile(data_file))), program_id);
} else {
impl = new LayeredRomfsStorageImpl(std::make_unique<ReadOnlyStorageAdapter>(new RemoteStorage(data_storage)), nullptr, program_id);
}
}
/* Insert holder. Reference count will now be one. */
g_storage_set.insert(*(new LayeredRomfsStorageHolder(impl, is_process_romfs)));
}
/* Begin initialization. When this finishes, a decref will occur. */
AMS_ABORT_UNLESS(impl != nullptr);
impl->BeginInitialize();
/* Return a new shared storage for the impl. */
return std::make_shared<LayeredRomfsStorage>(impl);
}
void FinalizeLayeredRomfsStorage(ncm::ProgramId program_id) {
std::scoped_lock lk(g_initialization_mutex);
std::scoped_lock lk2(g_storage_set_mutex);
/* Find an existing storage. */
if (auto it = g_storage_set.find_key(program_id.value); it != g_storage_set.end()) {
/* We need to delete the process romfs. Require invariant that it is unreferenced, by this point. */
AMS_ABORT_UNLESS(it->GetReferenceCount() == 0);
auto *holder = std::addressof(*it);
it = g_storage_set.erase(it);
delete holder;
}
}
LayeredRomfsStorageImpl::LayeredRomfsStorageImpl(std::unique_ptr<IStorage> s_r, std::unique_ptr<IStorage> f_r, ncm::ProgramId pr_id) : m_storage_romfs(std::move(s_r)), m_file_romfs(std::move(f_r)), m_initialize_event(os::EventClearMode_ManualClear), m_program_id(std::move(pr_id)), m_is_initialized(false), m_started_initialize(false) {
/* ... */
}
LayeredRomfsStorageImpl::~LayeredRomfsStorageImpl() {
for (size_t i = 0; i < m_source_infos.size(); i++) {
m_source_infos[i].Cleanup();
}
}
void LayeredRomfsStorageImpl::BeginInitialize() {
AMS_ABORT_UNLESS(!m_started_initialize);
RequestInitializeStorage(reinterpret_cast<uintptr_t>(this));
m_started_initialize = true;
}
void LayeredRomfsStorageImpl::InitializeImpl() {
/* Build new virtual romfs. */
romfs::Builder builder(m_program_id);
if (mitm::IsInitialized()) {
builder.AddSdFiles();
}
if (m_file_romfs) {
builder.AddStorageFiles(m_file_romfs.get(), romfs::DataSourceType::File);
}
if (m_storage_romfs) {
builder.AddStorageFiles(m_storage_romfs.get(), romfs::DataSourceType::Storage);
}
builder.Build(std::addressof(m_source_infos));
m_is_initialized = true;
m_initialize_event.Signal();
}
Result LayeredRomfsStorageImpl::Read(s64 offset, void *buffer, size_t size) {
/* Check if we can succeed immediately. */
R_SUCCEED_IF(size == 0);
/* Ensure we're initialized. */
if (!m_is_initialized) {
m_initialize_event.Wait();
}
/* Validate offset/size. */
const s64 virt_size = this->GetSize();
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
R_UNLESS(offset < virt_size, fs::ResultInvalidOffset());
if (static_cast<size_t>(virt_size - offset) < size) {
size = static_cast<size_t>(virt_size - offset);
}
/* Find first source info via binary search. */
auto it = std::lower_bound(m_source_infos.begin(), m_source_infos.end(), offset);
u8 *cur_dst = static_cast<u8 *>(buffer);
/* Our operator < compares against start of info instead of end, so we need to subtract one from lower bound. */
it--;
size_t read_so_far = 0;
while (read_so_far < size) {
const auto &cur_source = *it;
AMS_ABORT_UNLESS(offset >= cur_source.virtual_offset);
if (offset < cur_source.virtual_offset + cur_source.size) {
const s64 offset_within_source = offset - cur_source.virtual_offset;
const size_t cur_read_size = std::min(size - read_so_far, static_cast<size_t>(cur_source.size - offset_within_source));
switch (cur_source.source_type) {
case romfs::DataSourceType::Storage:
R_ABORT_UNLESS(m_storage_romfs->Read(cur_source.storage_source_info.offset + offset_within_source, cur_dst, cur_read_size));
break;
case romfs::DataSourceType::File:
R_ABORT_UNLESS(m_file_romfs->Read(cur_source.file_source_info.offset + offset_within_source, cur_dst, cur_read_size));
break;
case romfs::DataSourceType::LooseSdFile:
{
FsFile file;
R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdRomfsFile(std::addressof(file), m_program_id, cur_source.loose_source_info.path, OpenMode_Read));
ON_SCOPE_EXIT { fsFileClose(std::addressof(file)); };
u64 out_read = 0;
R_ABORT_UNLESS(fsFileRead(std::addressof(file), offset_within_source, cur_dst, cur_read_size, FsReadOption_None, std::addressof(out_read)));
AMS_ABORT_UNLESS(out_read == cur_read_size);
}
break;
case romfs::DataSourceType::Memory:
std::memcpy(cur_dst, cur_source.memory_source_info.data + offset_within_source, cur_read_size);
break;
case romfs::DataSourceType::Metadata:
{
size_t out_read = 0;
R_ABORT_UNLESS(cur_source.metadata_source_info.file->Read(std::addressof(out_read), offset_within_source, cur_dst, cur_read_size));
AMS_ABORT_UNLESS(out_read == cur_read_size);
}
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
read_so_far += cur_read_size;
cur_dst += cur_read_size;
offset += cur_read_size;
} else {
/* Explicitly handle padding. */
const auto &next_source = *(++it);
const size_t padding_size = static_cast<size_t>(next_source.virtual_offset - offset);
std::memset(cur_dst, 0, padding_size);
read_so_far += padding_size;
cur_dst += padding_size;
offset += padding_size;
}
}
R_SUCCEED();
}
Result LayeredRomfsStorageImpl::GetSize(s64 *out_size) {
/* Ensure we're initialized. */
if (!m_is_initialized) {
m_initialize_event.Wait();
}
*out_size = this->GetSize();
R_SUCCEED();
}
Result LayeredRomfsStorageImpl::Flush() {
R_SUCCEED();
}
Result LayeredRomfsStorageImpl::OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
AMS_UNUSED(offset, src, src_size);
switch (op_id) {
case OperationId::Invalidate:
case OperationId::QueryRange:
if (size == 0) {
if (op_id == OperationId::QueryRange) {
R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
R_UNLESS(dst_size == sizeof(QueryRangeInfo), fs::ResultInvalidSize());
reinterpret_cast<QueryRangeInfo *>(dst)->Clear();
}
R_SUCCEED();
}
/* TODO: How to deal with this? */
R_THROW(fs::ResultUnsupportedOperation());
default:
R_THROW(fs::ResultUnsupportedOperation());
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "fsmitm_romfs.hpp"
namespace ams::mitm::fs {
class LayeredRomfsStorageImpl {
private:
romfs::Builder::SourceInfoVector m_source_infos;
std::unique_ptr<ams::fs::IStorage> m_storage_romfs;
std::unique_ptr<ams::fs::IStorage> m_file_romfs;
os::Event m_initialize_event;
ncm::ProgramId m_program_id;
bool m_is_initialized;
bool m_started_initialize;
protected:
inline s64 GetSize() const {
const auto &back = m_source_infos.back();
return back.virtual_offset + back.size;
}
public:
LayeredRomfsStorageImpl(std::unique_ptr<ams::fs::IStorage> s_r, std::unique_ptr<ams::fs::IStorage> f_r, ncm::ProgramId pr_id);
~LayeredRomfsStorageImpl();
void BeginInitialize();
void InitializeImpl();
constexpr ncm::ProgramId GetProgramId() const { return m_program_id; }
Result Read(s64 offset, void *buffer, size_t size);
Result GetSize(s64 *out_size);
Result Flush();
Result OperateRange(void *dst, size_t dst_size, ams::fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size);
};
std::shared_ptr<ams::fs::IStorage> GetLayeredRomfsStorage(ncm::ProgramId program_id, ::FsStorage &data_storage, bool is_process_romfs);
void FinalizeLayeredRomfsStorage(ncm::ProgramId program_id);
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "fsmitm_module.hpp"
#include "fs_mitm_service.hpp"
namespace ams::mitm::fs {
namespace {
enum PortIndex {
PortIndex_Mitm,
PortIndex_Count,
};
constexpr sm::ServiceName MitmServiceName = sm::ServiceName::Encode("fsp-srv");
struct ServerOptions {
static constexpr size_t PointerBufferSize = 0x800;
static constexpr size_t MaxDomains = 0x40;
static constexpr size_t MaxDomainObjects = 0x4000;
static constexpr bool CanDeferInvokeRequest = false;
static constexpr bool CanManageMitmServers = true;
};
constexpr size_t MaxSessions = 61;
class ServerManager final : public sf::hipc::ServerManager<PortIndex_Count, ServerOptions, MaxSessions> {
private:
virtual Result OnNeedsToAccept(int port_index, Server *server) override;
};
ServerManager g_server_manager;
Result ServerManager::OnNeedsToAccept(int port_index, Server *server) {
/* Acknowledge the mitm session. */
std::shared_ptr<::Service> fsrv;
sm::MitmProcessInfo client_info;
server->AcknowledgeMitmSession(std::addressof(fsrv), std::addressof(client_info));
switch (port_index) {
case PortIndex_Mitm:
R_RETURN(this->AcceptMitmImpl(server, sf::CreateSharedObjectEmplaced<IFsMitmInterface, FsMitmService>(decltype(fsrv)(fsrv), client_info), fsrv));
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
constexpr size_t TotalThreads = 5;
static_assert(TotalThreads >= 1, "TotalThreads");
constexpr size_t NumExtraThreads = TotalThreads - 1;
constexpr size_t ThreadStackSize = mitm::ModuleTraits<fs::MitmModule>::StackSize;
alignas(os::MemoryPageSize) u8 g_extra_thread_stacks[NumExtraThreads][ThreadStackSize];
os::ThreadType g_extra_threads[NumExtraThreads];
void LoopServerThread(void *) {
/* Loop forever, servicing our services. */
g_server_manager.LoopProcess();
}
void ProcessForServerOnAllThreads() {
/* Initialize threads. */
if constexpr (NumExtraThreads > 0) {
const s32 priority = os::GetThreadCurrentPriority(os::GetCurrentThread());
for (size_t i = 0; i < NumExtraThreads; i++) {
R_ABORT_UNLESS(os::CreateThread(g_extra_threads + i, LoopServerThread, nullptr, g_extra_thread_stacks[i], ThreadStackSize, priority));
}
}
/* Start extra threads. */
if constexpr (NumExtraThreads > 0) {
for (size_t i = 0; i < NumExtraThreads; i++) {
os::StartThread(g_extra_threads + i);
}
}
/* Loop this thread. */
LoopServerThread(nullptr);
/* Wait for extra threads to finish. */
if constexpr (NumExtraThreads > 0) {
for (size_t i = 0; i < NumExtraThreads; i++) {
os::WaitThread(g_extra_threads + i);
}
}
}
}
void MitmModule::ThreadFunction(void *) {
/* Create fs mitm. */
R_ABORT_UNLESS((g_server_manager.RegisterMitmServer<FsMitmService>(PortIndex_Mitm, MitmServiceName)));
/* Process for the server. */
ProcessForServerOnAllThreads();
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "../amsmitm_module.hpp"
namespace ams::mitm::fs {
DEFINE_MITM_MODULE_CLASS(0x8000, AMS_GET_SYSTEM_THREAD_PRIORITY(fs, WorkerThreadPool) - 1);
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::mitm::fs {
class ReadOnlyLayeredFileSystem : public ams::fs::fsa::IFileSystem {
private:
ams::fs::ReadOnlyFileSystem m_fs_1;
ams::fs::ReadOnlyFileSystem m_fs_2;
public:
explicit ReadOnlyLayeredFileSystem(std::unique_ptr<ams::fs::fsa::IFileSystem> a, std::unique_ptr<ams::fs::fsa::IFileSystem> b) : m_fs_1(std::move(a)), m_fs_2(std::move(b)) { /* ... */ }
virtual ~ReadOnlyLayeredFileSystem() { /* ... */ }
private:
virtual Result DoCreateFile(const ams::fs::Path &path, s64 size, int flags) override final {
AMS_UNUSED(path, size, flags);
R_THROW(ams::fs::ResultUnsupportedOperation())
}
virtual Result DoDeleteFile(const ams::fs::Path &path) override final {
AMS_UNUSED(path);
R_THROW(ams::fs::ResultUnsupportedOperation())
}
virtual Result DoCreateDirectory(const ams::fs::Path &path) override final {
AMS_UNUSED(path);
R_THROW(ams::fs::ResultUnsupportedOperation())
}
virtual Result DoDeleteDirectory(const ams::fs::Path &path) override final {
AMS_UNUSED(path);
R_THROW(ams::fs::ResultUnsupportedOperation())
}
virtual Result DoDeleteDirectoryRecursively(const ams::fs::Path &path) override final {
AMS_UNUSED(path);
R_THROW(ams::fs::ResultUnsupportedOperation())
}
virtual Result DoRenameFile(const ams::fs::Path &old_path, const ams::fs::Path &new_path) override final {
AMS_UNUSED(old_path, new_path);
R_THROW(ams::fs::ResultUnsupportedOperation())
}
virtual Result DoRenameDirectory(const ams::fs::Path &old_path, const ams::fs::Path &new_path) override final {
AMS_UNUSED(old_path, new_path);
R_THROW(ams::fs::ResultUnsupportedOperation())
}
virtual Result DoGetEntryType(ams::fs::DirectoryEntryType *out, const ams::fs::Path &path) override final {
R_SUCCEED_IF(R_SUCCEEDED(m_fs_1.GetEntryType(out, path)));
R_RETURN(m_fs_2.GetEntryType(out, path));
}
virtual Result DoOpenFile(std::unique_ptr<ams::fs::fsa::IFile> *out_file, const ams::fs::Path &path, ams::fs::OpenMode mode) override final {
R_SUCCEED_IF(R_SUCCEEDED(m_fs_1.OpenFile(out_file, path, mode)));
R_RETURN(m_fs_2.OpenFile(out_file, path, mode));
}
virtual Result DoOpenDirectory(std::unique_ptr<ams::fs::fsa::IDirectory> *out_dir, const ams::fs::Path &path, ams::fs::OpenDirectoryMode mode) override final {
R_SUCCEED_IF(R_SUCCEEDED(m_fs_1.OpenDirectory(out_dir, path, mode)));
R_RETURN(m_fs_2.OpenDirectory(out_dir, path, mode));
}
virtual Result DoCommit() override final {
R_SUCCEED();
}
virtual Result DoGetFreeSpaceSize(s64 *out, const ams::fs::Path &path) {
AMS_UNUSED(out, path);
R_THROW(ams::fs::ResultUnsupportedOperation())
}
virtual Result DoGetTotalSpaceSize(s64 *out, const ams::fs::Path &path) {
AMS_UNUSED(out, path);
R_THROW(ams::fs::ResultUnsupportedOperation())
}
virtual Result DoCleanDirectoryRecursively(const ams::fs::Path &path) {
AMS_UNUSED(path);
R_THROW(ams::fs::ResultUnsupportedOperation())
}
virtual Result DoGetFileTimeStampRaw(ams::fs::FileTimeStampRaw *out, const ams::fs::Path &path) {
R_SUCCEED_IF(R_SUCCEEDED(m_fs_1.GetFileTimeStampRaw(out, path)));
R_RETURN(m_fs_2.GetFileTimeStampRaw(out, path));
}
};
}

View File

@@ -0,0 +1,379 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::mitm::fs::romfs {
enum class DataSourceType : u8 {
Storage,
File,
LooseSdFile,
Metadata,
Memory,
};
enum AllocationType {
AllocationType_FileName,
AllocationType_DirName,
AllocationType_FullPath,
AllocationType_SourceInfo,
AllocationType_BuildFileContext,
AllocationType_BuildDirContext,
AllocationType_TableCache,
AllocationType_DirPointerArray,
AllocationType_DirContextSet,
AllocationType_FileContextSet,
AllocationType_Memory,
AllocationType_Count,
};
void *AllocateTracked(AllocationType type, size_t size);
void FreeTracked(AllocationType type, void *p, size_t size);
template<typename T, typename... Args>
T *AllocateTyped(AllocationType type, Args &&... args) {
void *mem = AllocateTracked(type, sizeof(T));
return std::construct_at(static_cast<T *>(mem), std::forward<Args>(args)...);
}
template<AllocationType AllocType, typename T>
class TrackedAllocator {
public:
using value_type = T;
template<typename U>
struct rebind {
using other = TrackedAllocator<AllocType, U>;
};
public:
TrackedAllocator() = default;
T *allocate(size_t n) {
return static_cast<T *>(AllocateTracked(AllocType, sizeof(T) * n));
}
void deallocate(T *p, size_t n) {
FreeTracked(AllocType, p, sizeof(T) * n);
}
};
struct SourceInfo {
s64 virtual_offset;
s64 size;
union {
struct {
s64 offset;
} storage_source_info;
struct {
s64 offset;
} file_source_info;
struct {
char *path;
} loose_source_info;
struct {
ams::fs::fsa::IFile *file;
} metadata_source_info;
struct {
u8 *data;
} memory_source_info;
};
DataSourceType source_type;
bool cleaned_up;
SourceInfo(s64 v_o, s64 sz, DataSourceType type, s64 p_o) : virtual_offset(v_o), size(sz), source_type(type), cleaned_up(false) {
switch (this->source_type) {
case DataSourceType::Storage:
this->storage_source_info.offset = p_o;
break;
case DataSourceType::File:
this->file_source_info.offset = p_o;
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
SourceInfo(s64 v_o, s64 sz, DataSourceType type, void *arg) : virtual_offset(v_o), size(sz), source_type(type), cleaned_up(false) {
switch (this->source_type) {
case DataSourceType::LooseSdFile:
this->loose_source_info.path = static_cast<char *>(arg);
break;
case DataSourceType::Metadata:
this->metadata_source_info.file = static_cast<ams::fs::fsa::IFile *>(arg);
break;
case DataSourceType::Memory:
this->memory_source_info.data = static_cast<u8 *>(arg);
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
void Cleanup() {
AMS_ABORT_UNLESS(!this->cleaned_up);
this->cleaned_up = true;
switch (this->source_type) {
case DataSourceType::Storage:
case DataSourceType::File:
break;
case DataSourceType::Metadata:
delete this->metadata_source_info.file;
break;
case DataSourceType::LooseSdFile:
FreeTracked(AllocationType_FullPath, this->loose_source_info.path, std::strlen(this->loose_source_info.path) + 1);
break;
case DataSourceType::Memory:
FreeTracked(AllocationType_Memory, this->memory_source_info.data, this->size);
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
};
constexpr inline bool operator<(const SourceInfo &lhs, const SourceInfo &rhs) {
return lhs.virtual_offset < rhs.virtual_offset;
}
constexpr inline bool operator<(const SourceInfo &lhs, const s64 rhs) {
return lhs.virtual_offset <= rhs;
}
struct BuildFileContext;
struct BuildDirectoryContext {
NON_COPYABLE(BuildDirectoryContext);
NON_MOVEABLE(BuildDirectoryContext);
char *path;
union {
BuildDirectoryContext *parent;
};
union {
BuildDirectoryContext *child;
struct {
u32 parent_offset;
u32 child_offset;
};
};
union {
BuildDirectoryContext *sibling;
u32 sibling_offset;
};
union {
BuildFileContext *file;
u32 file_offset;
};
u32 path_len;
u32 entry_offset;
u32 hash_value;
struct RootTag{};
BuildDirectoryContext(RootTag) : parent(nullptr), child(nullptr), sibling(nullptr), file(nullptr), path_len(0), entry_offset(0), hash_value(0xFFFFFFFF) {
this->path = static_cast<char *>(AllocateTracked(AllocationType_DirName, 1));
this->path[0] = '\x00';
}
BuildDirectoryContext(const char *entry_name, size_t entry_name_len) : parent(nullptr), child(nullptr), sibling(nullptr), file(nullptr), entry_offset(0) {
this->path_len = entry_name_len;
this->path = static_cast<char *>(AllocateTracked(AllocationType_DirName, this->path_len + 1));
std::memcpy(this->path, entry_name, entry_name_len);
this->path[this->path_len] = '\x00';
}
~BuildDirectoryContext() {
if (this->path != nullptr) {
FreeTracked(AllocationType_DirName, this->path, this->path_len + 1);
this->path = nullptr;
}
}
void operator delete(void *p) {
FreeTracked(AllocationType_BuildDirContext, p, sizeof(BuildDirectoryContext));
}
size_t GetPathLength() const {
if (this->parent == nullptr) {
return 0;
}
return this->parent->GetPathLength() + 1 + this->path_len;
}
size_t GetPath(char *dst) const {
if (this->parent == nullptr) {
dst[0] = '\x00';
return 0;
}
const size_t parent_len = this->parent->GetPath(dst);
dst[parent_len] = '/';
std::memcpy(dst + parent_len + 1, this->path, this->path_len);
dst[parent_len + 1 + this->path_len] = '\x00';
return parent_len + 1 + this->path_len;
}
bool HasHashMark() const {
return reinterpret_cast<uintptr_t>(this->sibling) & UINT64_C(0x8000000000000000);
}
void SetHashMark() {
this->sibling = reinterpret_cast<BuildDirectoryContext *>(reinterpret_cast<uintptr_t>(this->sibling) | UINT64_C(0x8000000000000000));
}
void ClearHashMark() {
this->sibling = reinterpret_cast<BuildDirectoryContext *>(reinterpret_cast<uintptr_t>(this->sibling) & ~UINT64_C(0x8000000000000000));
}
};
struct BuildFileContext {
NON_COPYABLE(BuildFileContext);
NON_MOVEABLE(BuildFileContext);
char *path;
BuildDirectoryContext *parent;
union {
BuildFileContext *sibling;
u32 sibling_offset;
};
s64 offset;
s64 size;
s64 orig_offset;
u32 path_len;
u32 entry_offset;
u32 hash_value;
DataSourceType source_type;
BuildFileContext(const char *entry_name, size_t entry_name_len, s64 sz, s64 o_o, DataSourceType type) : parent(nullptr), sibling(nullptr), offset(0), size(sz), orig_offset(o_o), entry_offset(0), hash_value(0xFFFFFFFF), source_type(type) {
this->path_len = entry_name_len;
this->path = static_cast<char *>(AllocateTracked(AllocationType_FileName, this->path_len + 1));
std::memcpy(this->path, entry_name, entry_name_len);
this->path[this->path_len] = 0;
}
~BuildFileContext() {
if (this->path != nullptr) {
FreeTracked(AllocationType_FileName, this->path, this->path_len + 1);
this->path = nullptr;
}
}
void operator delete(void *p) {
FreeTracked(AllocationType_BuildFileContext, p, sizeof(BuildFileContext));
}
size_t GetPathLength() const {
if (this->parent == nullptr) {
return 0;
}
return this->parent->GetPathLength() + 1 + this->path_len;
}
size_t GetPath(char *dst) const {
if (this->parent == nullptr) {
dst[0] = '\x00';
return 0;
}
const size_t parent_len = this->parent->GetPath(dst);
dst[parent_len] = '/';
std::memcpy(dst + parent_len + 1, this->path, this->path_len);
dst[parent_len + 1 + this->path_len] = '\x00';
return parent_len + 1 + this->path_len;
}
bool HasHashMark() const {
return reinterpret_cast<uintptr_t>(this->sibling) & UINT64_C(0x8000000000000000);
}
void SetHashMark() {
this->sibling = reinterpret_cast<BuildFileContext *>(reinterpret_cast<uintptr_t>(this->sibling) | UINT64_C(0x8000000000000000));
}
void ClearHashMark() {
this->sibling = reinterpret_cast<BuildFileContext *>(reinterpret_cast<uintptr_t>(this->sibling) & ~UINT64_C(0x8000000000000000));
}
};
class DirectoryTableReader;
class FileTableReader;
class Builder {
NON_COPYABLE(Builder);
NON_MOVEABLE(Builder);
public:
using SourceInfoVector = std::vector<SourceInfo, TrackedAllocator<AllocationType_SourceInfo, SourceInfo>>;
private:
template<typename T>
struct Comparator {
static constexpr inline int Compare(const char *a, const char *b) {
unsigned char c1{}, c2{};
while ((c1 = *a++) == (c2 = *b++)) {
if (c1 == '\x00') {
return 0;
}
}
return (c1 - c2);
}
constexpr bool operator()(const std::unique_ptr<T> &lhs, const std::unique_ptr<T> &rhs) const {
char lhs_path[ams::fs::EntryNameLengthMax + 1];
char rhs_path[ams::fs::EntryNameLengthMax + 1];
lhs->GetPath(lhs_path);
rhs->GetPath(rhs_path);
return Comparator::Compare(lhs_path, rhs_path) < 0;
}
};
template<AllocationType AllocType, typename T>
using ContextSet = std::set<std::unique_ptr<T>, Comparator<T>, TrackedAllocator<AllocType, std::unique_ptr<T>>>;
private:
ncm::ProgramId m_program_id;
BuildDirectoryContext *m_root;
ContextSet<AllocationType_DirContextSet, BuildDirectoryContext> m_directories;
ContextSet<AllocationType_FileContextSet, BuildFileContext> m_files;
size_t m_num_dirs;
size_t m_num_files;
size_t m_dir_table_size;
size_t m_file_table_size;
size_t m_dir_hash_table_size;
size_t m_file_hash_table_size;
size_t m_file_partition_size;
::FsDirectoryEntry m_dir_entry;
DataSourceType m_cur_source_type;
private:
void VisitDirectory(FsFileSystem *fs, BuildDirectoryContext *parent);
void VisitDirectory(BuildDirectoryContext *parent, u32 parent_offset, DirectoryTableReader &dir_table, FileTableReader &file_table);
void AddDirectory(BuildDirectoryContext **out, BuildDirectoryContext *parent_ctx, std::unique_ptr<BuildDirectoryContext> file_ctx);
void AddFile(BuildDirectoryContext *parent_ctx, std::unique_ptr<BuildFileContext> file_ctx);
public:
Builder(ncm::ProgramId pr_id);
~Builder();
void AddSdFiles();
void AddStorageFiles(ams::fs::IStorage *storage, DataSourceType source_type);
void Build(SourceInfoVector *out_infos);
};
Result ConfigureDynamicHeap(u64 *out_size, ncm::ProgramId program_id, const cfg::OverrideStatus &status, bool is_application);
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "fsmitm_save_utils.hpp"
namespace ams::mitm::fs {
using namespace ams::fs;
namespace {
Result GetSaveDataSpaceIdString(const char **out_str, u8 space_id) {
switch (static_cast<SaveDataSpaceId>(space_id)) {
case SaveDataSpaceId::System:
case SaveDataSpaceId::ProperSystem:
*out_str = "sys";
break;
case SaveDataSpaceId::User:
*out_str = "user";
break;
case SaveDataSpaceId::SdSystem:
*out_str = "sd_sys";
break;
case SaveDataSpaceId::Temporary:
*out_str = "temp";
break;
case SaveDataSpaceId::SdUser:
*out_str = "sd_user";
break;
case SaveDataSpaceId::SafeMode:
*out_str = "safe";
break;
default:
R_THROW(fs::ResultInvalidSaveDataSpaceId());
}
R_SUCCEED();
}
Result GetSaveDataTypeString(const char **out_str, SaveDataType save_data_type) {
switch (save_data_type) {
case SaveDataType::System:
*out_str = "system";
break;
case SaveDataType::Account:
*out_str = "account";
break;
case SaveDataType::Bcat:
*out_str = "bcat";
break;
case SaveDataType::Device:
*out_str = "device";
break;
case SaveDataType::Temporary:
*out_str = "temp";
break;
case SaveDataType::Cache:
*out_str = "cache";
break;
case SaveDataType::SystemBcat:
*out_str = "system_bcat";
break;
default:
/* TODO: Better result? */
R_THROW(fs::ResultInvalidArgument());
}
R_SUCCEED();
}
constexpr inline bool IsEmptyAccountId(const UserId &uid) {
return uid == InvalidUserId;
}
}
Result SaveUtil::GetDirectorySaveDataPath(char *dst, size_t dst_size, ncm::ProgramId program_id, u8 space_id, const fs::SaveDataAttribute &attribute) {
/* Saves should be separate for emunand vs sysnand. */
const char *emummc_str = emummc::IsActive() ? "emummc" : "sysmmc";
/* Get space_id, save_data_type strings. */
const char *space_id_str, *save_type_str;
R_TRY(GetSaveDataSpaceIdString(&space_id_str, space_id));
R_TRY(GetSaveDataTypeString(&save_type_str, attribute.type));
/* Initialize the path. */
const bool is_system = attribute.system_save_data_id != InvalidSystemSaveDataId && IsEmptyAccountId(attribute.user_id);
size_t out_path_len;
if (is_system) {
out_path_len = static_cast<size_t>(util::SNPrintf(dst, dst_size, "/atmosphere/saves/%s/%s/%s/%016lx", emummc_str, space_id_str, save_type_str, attribute.system_save_data_id));
} else {
out_path_len = static_cast<size_t>(util::SNPrintf(dst, dst_size, "/atmosphere/saves/%s/%s/%s/%016lx/%016lx%016lx", emummc_str, space_id_str, save_type_str, static_cast<u64>(program_id), attribute.user_id.data[1], attribute.user_id.data[0]));
}
R_UNLESS(out_path_len < dst_size, fs::ResultTooLongPath());
R_SUCCEED();
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::mitm::fs {
class SaveUtil {
public:
static Result GetDirectorySaveDataPath(char *dst, size_t dst_size, ncm::ProgramId program_id, u8 space_id, const ams::fs::SaveDataAttribute &attribute);
};
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define NX_SERVICE_ASSUME_NON_DOMAIN
#include "service_guard.h"
#include "memlet.h"
static Service g_memletSrv;
NX_GENERATE_SERVICE_GUARD(memlet);
Result _memletInitialize(void) {
return smGetService(&g_memletSrv, "memlet");
}
void _memletCleanup(void) {
serviceClose(&g_memletSrv);
}
Service* memletGetServiceSession(void) {
return &g_memletSrv;
}
Result memletCreateAppletSharedMemory(Handle *out_shmem_h, u64 *out_size, u64 desired_size) {
return serviceDispatchInOut(&g_memletSrv, 65000, desired_size, *out_size,
.out_handle_attrs = { SfOutHandleAttr_HipcMove },
.out_handles = out_shmem_h,
);
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#ifdef __cplusplus
extern "C" {
#endif
Result memletInitialize(void);
void memletExit(void);
Service* memletGetServiceSession(void);
Result memletCreateAppletSharedMemory(Handle *out_shmem_h, u64 *out_size, u64 desired_size);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,65 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <switch/types.h>
#include <switch/result.h>
#include <switch/kernel/mutex.h>
#include <switch/sf/service.h>
#include <switch/services/sm.h>
typedef struct ServiceGuard {
Mutex mutex;
u32 refCount;
} ServiceGuard;
NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g)
{
mutexLock(&g->mutex);
return (g->refCount++) == 0;
}
NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void))
{
if (R_FAILED(rc)) {
cleanupFunc();
--g->refCount;
}
mutexUnlock(&g->mutex);
return rc;
}
NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void))
{
mutexLock(&g->mutex);
if (g->refCount && (--g->refCount) == 0)
cleanupFunc();
mutexUnlock(&g->mutex);
}
#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \
\
static ServiceGuard g_##name##Guard; \
NX_INLINE Result _##name##Initialize _paramdecl; \
static void _##name##Cleanup(void); \
\
Result name##Initialize _paramdecl \
{ \
Result rc = 0; \
if (serviceGuardBeginInit(&g_##name##Guard)) \
rc = _##name##Initialize _parampass; \
return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \
} \
\
void name##Exit(void) \
{ \
serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \
}
#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ())
#ifdef __cplusplus
}
#endif