mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-12-22 12:21:18 +00:00
ams_mitm: Implement savedata redirection
This commit is contained in:
parent
90367aea0d
commit
7835486a4d
23 changed files with 899 additions and 47 deletions
|
@ -18,6 +18,7 @@
|
|||
#include "fs_mitm_service.hpp"
|
||||
#include "fsmitm_boot0storage.hpp"
|
||||
#include "fsmitm_layered_romfs_storage.hpp"
|
||||
#include "fsmitm_save_utils.hpp"
|
||||
|
||||
namespace ams::mitm::fs {
|
||||
|
||||
|
@ -78,6 +79,70 @@ namespace ams::mitm::fs {
|
|||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FsMitmService::OpenSaveDataFileSystem(sf::Out<std::shared_ptr<IFileSystemInterface>> out, u8 _space_id, const FsSaveDataAttribute &attribute) {
|
||||
/* We only want to intercept saves for games, right now. */
|
||||
const bool is_game_or_hbl = this->client_info.override_status.IsHbl() || ncm::IsApplicationProgramId(this->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(this->client_info.program_id, "redirect_save"), sm::mitm::ResultShouldForwardToSession());
|
||||
|
||||
/* Only redirect account savedata. */
|
||||
R_UNLESS(attribute.save_data_type == FsSaveDataType_Account, sm::mitm::ResultShouldForwardToSession());
|
||||
|
||||
/* Get enum type for space id. */
|
||||
auto space_id = static_cast<FsSaveDataSpaceId>(_space_id);
|
||||
|
||||
/* Verify we can open the save. */
|
||||
FsFileSystem save_fs;
|
||||
R_UNLESS(R_SUCCEEDED(fsOpenSaveDataFileSystemFwd(this->forward_service.get(), &save_fs, space_id, &attribute)), sm::mitm::ResultShouldForwardToSession());
|
||||
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(&save_fs.s)};
|
||||
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(&sd_fs));
|
||||
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.application_id == 0 ? this->client_info.program_id : ncm::ProgramId{attribute.application_id};
|
||||
char save_dir_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(mitm::fs::SaveUtil::GetDirectorySaveDataPath(save_dir_path, sizeof(save_dir_path), application_id, space_id, attribute));
|
||||
|
||||
/* 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(&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::EnsureDirectoryExistsRecursively(sd_ifs.get(), save_dir_path));
|
||||
|
||||
/* Create directory savedata filesystem. */
|
||||
std::unique_ptr<fs::fsa::IFileSystem> subdir_fs = std::make_unique<fssystem::SubDirectoryFileSystem>(sd_ifs, 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());
|
||||
|
||||
/* If it's the first time we're making the save, copy existing savedata over. */
|
||||
if (is_new_save) {
|
||||
/* TODO: Check error? */
|
||||
dirsave_ifs->CopySaveFromFileSystem(save_ifs.get());
|
||||
}
|
||||
|
||||
/* Set output. */
|
||||
out.SetValue(std::make_shared<IFileSystemInterface>(std::move(dirsave_ifs), false), target_object_id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FsMitmService::OpenBisStorage(sf::Out<std::shared_ptr<IStorageInterface>> out, u32 _bis_partition_id) {
|
||||
const ::FsBisPartitionId bis_partition_id = static_cast<::FsBisPartitionId>(_bis_partition_id);
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ namespace ams::mitm::fs {
|
|||
/* Result OpenFileSystemWithPatch(Out<std::shared_ptr<IFileSystemInterface>> out, u64 program_id, u32 filesystem_type); */
|
||||
/* Result OpenFileSystemWithId(Out<std::shared_ptr<IFileSystemInterface>> out, InPointer<char> path, u64 program_id, u32 filesystem_type); */
|
||||
Result OpenSdCardFileSystem(sf::Out<std::shared_ptr<IFileSystemInterface>> out);
|
||||
/* Result OpenSaveDataFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out, u8 space_id, FsSave save_struct); */
|
||||
Result OpenSaveDataFileSystem(sf::Out<std::shared_ptr<IFileSystemInterface>> out, u8 space_id, const FsSaveDataAttribute &attribute);
|
||||
Result OpenBisStorage(sf::Out<std::shared_ptr<IStorageInterface>> out, u32 bis_partition_id);
|
||||
Result OpenDataStorageByCurrentProcess(sf::Out<std::shared_ptr<IStorageInterface>> out);
|
||||
Result OpenDataStorageByDataId(sf::Out<std::shared_ptr<IStorageInterface>> out, ncm::ProgramId /* TODO: ncm::DataId */ data_id, u8 storage_id);
|
||||
|
@ -72,7 +72,7 @@ namespace ams::mitm::fs {
|
|||
/* MAKE_SERVICE_COMMAND_META(OpenFileSystemWithPatch, hos::Version_200), */
|
||||
/* MAKE_SERVICE_COMMAND_META(OpenFileSystemWithId, hos::Version_200), */
|
||||
MAKE_SERVICE_COMMAND_META(OpenSdCardFileSystem),
|
||||
/* MAKE_SERVICE_COMMAND_META(OpenSaveDataFileSystem), */
|
||||
MAKE_SERVICE_COMMAND_META(OpenSaveDataFileSystem),
|
||||
MAKE_SERVICE_COMMAND_META(OpenBisStorage),
|
||||
MAKE_SERVICE_COMMAND_META(OpenDataStorageByCurrentProcess),
|
||||
MAKE_SERVICE_COMMAND_META(OpenDataStorageByDataId),
|
||||
|
|
|
@ -50,3 +50,16 @@ Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, Ncm
|
|||
.out_objects = &out->s,
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partitio
|
|||
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out);
|
||||
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, NcmStorageId storage_id);
|
||||
|
||||
Result fsOpenSaveDataFileSystemFwd(Service* s, FsFileSystem* out, FsSaveDataSpaceId save_data_space_id, const FsSaveDataAttribute *attr);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
114
stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.cpp
Normal file
114
stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.cpp
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 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 "fsmitm_save_utils.hpp"
|
||||
|
||||
namespace ams::mitm::fs {
|
||||
|
||||
using namespace ams::fs;
|
||||
|
||||
namespace {
|
||||
|
||||
Result GetSaveDataSpaceIdString(const char **out_str, u8 space_id) {
|
||||
switch (space_id) {
|
||||
case FsSaveDataSpaceId_System:
|
||||
case FsSaveDataSpaceId_ProperSystem:
|
||||
*out_str = "sys";
|
||||
break;
|
||||
case FsSaveDataSpaceId_User:
|
||||
*out_str = "user";
|
||||
break;
|
||||
case FsSaveDataSpaceId_SdSystem:
|
||||
*out_str = "sd_sys";
|
||||
break;
|
||||
case FsSaveDataSpaceId_Temporary:
|
||||
*out_str = "temp";
|
||||
break;
|
||||
case FsSaveDataSpaceId_SdUser:
|
||||
*out_str = "sd_user";
|
||||
break;
|
||||
case FsSaveDataSpaceId_SafeMode:
|
||||
*out_str = "safe";
|
||||
break;
|
||||
default:
|
||||
return fs::ResultInvalidSaveDataSpaceId();
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result GetSaveDataTypeString(const char **out_str, u8 save_data_type) {
|
||||
switch (save_data_type) {
|
||||
case FsSaveDataType_System:
|
||||
*out_str = "system";
|
||||
break;
|
||||
case FsSaveDataType_Account:
|
||||
*out_str = "account";
|
||||
break;
|
||||
case FsSaveDataType_Bcat:
|
||||
*out_str = "bcat";
|
||||
break;
|
||||
case FsSaveDataType_Device:
|
||||
*out_str = "device";
|
||||
break;
|
||||
case FsSaveDataType_Temporary:
|
||||
*out_str = "temp";
|
||||
break;
|
||||
case FsSaveDataType_Cache:
|
||||
*out_str = "cache";
|
||||
break;
|
||||
case FsSaveDataType_SystemBcat:
|
||||
*out_str = "system_bcat";
|
||||
break;
|
||||
default:
|
||||
/* TODO: Better result? */
|
||||
return fs::ResultInvalidArgument();
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
constexpr inline bool IsEmptyAccountId(const AccountUid &uid) {
|
||||
constexpr AccountUid empty_uid = {};
|
||||
return std::memcmp(&uid, &empty_uid, sizeof(uid)) == 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Result SaveUtil::GetDirectorySaveDataPath(char *dst, size_t dst_size, ncm::ProgramId program_id, u8 space_id, const FsSaveDataAttribute &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.save_data_type));
|
||||
|
||||
/* Initialize the path. */
|
||||
const bool is_system = attribute.system_save_data_id != 0 && IsEmptyAccountId(attribute.uid);
|
||||
size_t out_path_len;
|
||||
if (is_system) {
|
||||
out_path_len = static_cast<size_t>(std::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>(std::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.uid.uid[1], attribute.uid.uid[0]));
|
||||
}
|
||||
|
||||
R_UNLESS(out_path_len < dst_size, fs::ResultTooLongPath());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
26
stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.hpp
Normal file
26
stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 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 FsSaveDataAttribute &attribute);
|
||||
};
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
#pragma once
|
||||
#include "cfg_types.hpp"
|
||||
#include "cfg_locale_types.hpp"
|
||||
#include "../sm/sm_types.hpp"
|
||||
|
||||
namespace ams::cfg {
|
||||
|
||||
|
@ -36,7 +37,7 @@ namespace ams::cfg {
|
|||
OverrideLocale GetOverrideLocale(ncm::ProgramId program_id);
|
||||
|
||||
/* Flag utilities. */
|
||||
bool HasFlag(ncm::ProgramId program_id, const char *flag);
|
||||
bool HasFlag(const sm::MitmProcessInfo &process_info, const char *flag);
|
||||
bool HasContentSpecificFlag(ncm::ProgramId program_id, const char *flag);
|
||||
bool HasGlobalFlag(const char *flag);
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace ams::fs {
|
|||
|
||||
class PathTool {
|
||||
public:
|
||||
static constexpr fssrv::sf::Path RootPath = fssrv::sf::FspPath::Encode("/");
|
||||
static constexpr const char RootPath[] = "/";
|
||||
public:
|
||||
static constexpr inline bool IsSeparator(char c) {
|
||||
return c == StringTraits::DirectorySeparator;
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace ams::fs {
|
|||
|
||||
virtual ~RemoteFile() { fsFileClose(this->base_file.get()); }
|
||||
public:
|
||||
virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const ReadOption &option) override final {
|
||||
virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override final {
|
||||
return fsFileRead(this->base_file.get(), offset, buffer, size, option.value, out);
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ namespace ams::fs {
|
|||
return fsFileFlush(this->base_file.get());
|
||||
}
|
||||
|
||||
virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const WriteOption &option) override final {
|
||||
virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override final {
|
||||
return fsFileWrite(this->base_file.get(), offset, buffer, size, option.value);
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ namespace ams::fs {
|
|||
return fsFileSetSize(this->base_file.get(), size);
|
||||
}
|
||||
|
||||
virtual Result OperateRangeImpl(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override final {
|
||||
virtual Result OperateRangeImpl(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override final {
|
||||
/* TODO: How should this be handled? */
|
||||
return fs::ResultNotImplemented();
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace ams::fs::fsa {
|
|||
public:
|
||||
virtual ~IFile() { /* ... */ }
|
||||
|
||||
Result Read(size_t *out, s64 offset, void *buffer, size_t size, const ReadOption &option) {
|
||||
Result Read(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) {
|
||||
R_UNLESS(out != nullptr, fs::ResultNullptrArgument());
|
||||
if (size == 0) {
|
||||
*out = 0;
|
||||
|
@ -51,7 +51,7 @@ namespace ams::fs::fsa {
|
|||
return this->FlushImpl();
|
||||
}
|
||||
|
||||
Result Write(s64 offset, const void *buffer, size_t size, const WriteOption &option) {
|
||||
Result Write(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) {
|
||||
if (size == 0) {
|
||||
if (option.HasFlushFlag()) {
|
||||
R_TRY(this->Flush());
|
||||
|
@ -71,11 +71,11 @@ namespace ams::fs::fsa {
|
|||
return this->SetSizeImpl(size);
|
||||
}
|
||||
|
||||
Result OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
|
||||
Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
|
||||
return this->OperateRangeImpl(dst, dst_size, op_id, offset, size, src, src_size);
|
||||
}
|
||||
|
||||
Result OperateRange(OperationId op_id, s64 offset, s64 size) {
|
||||
Result OperateRange(fs::OperationId op_id, s64 offset, s64 size) {
|
||||
return this->OperateRangeImpl(nullptr, 0, op_id, offset, size, nullptr, 0);
|
||||
}
|
||||
public:
|
||||
|
@ -84,12 +84,12 @@ namespace ams::fs::fsa {
|
|||
protected:
|
||||
/* ...? */
|
||||
private:
|
||||
virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const ReadOption &option) = 0;
|
||||
virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) = 0;
|
||||
virtual Result GetSizeImpl(s64 *out) = 0;
|
||||
virtual Result FlushImpl() = 0;
|
||||
virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const WriteOption &option) = 0;
|
||||
virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) = 0;
|
||||
virtual Result SetSizeImpl(s64 size) = 0;
|
||||
virtual Result OperateRangeImpl(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) = 0;
|
||||
virtual Result OperateRangeImpl(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -19,3 +19,4 @@
|
|||
#include "fssystem/fssystem_path_tool.hpp"
|
||||
#include "fssystem/fssystem_subdirectory_filesystem.hpp"
|
||||
#include "fssystem/fssystem_directory_redirection_filesystem.hpp"
|
||||
#include "fssystem/fssystem_directory_savedata_filesystem.hpp"
|
||||
|
|
|
@ -14,29 +14,33 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "fssystem_path_resolution_filesystem.hpp"
|
||||
#include "impl/fssystem_path_resolution_filesystem.hpp"
|
||||
|
||||
namespace ams::fssystem {
|
||||
|
||||
class DirectoryRedirectionFileSystem : public IPathResolutionFileSystem<DirectoryRedirectionFileSystem> {
|
||||
class DirectoryRedirectionFileSystem : public impl::IPathResolutionFileSystem<DirectoryRedirectionFileSystem> {
|
||||
NON_COPYABLE(DirectoryRedirectionFileSystem);
|
||||
private:
|
||||
using PathResolutionFileSystem = IPathResolutionFileSystem<DirectoryRedirectionFileSystem>;
|
||||
using PathResolutionFileSystem = impl::IPathResolutionFileSystem<DirectoryRedirectionFileSystem>;
|
||||
friend class impl::IPathResolutionFileSystem<DirectoryRedirectionFileSystem>;
|
||||
private:
|
||||
char *before_dir;
|
||||
size_t before_dir_len;
|
||||
char *after_dir;
|
||||
size_t after_dir_len;
|
||||
bool unc_preserved;
|
||||
public:
|
||||
DirectoryRedirectionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *before, const char *after);
|
||||
DirectoryRedirectionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *before, const char *after, bool unc);
|
||||
DirectoryRedirectionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *before, const char *after, bool unc = false);
|
||||
DirectoryRedirectionFileSystem(std::unique_ptr<fs::fsa::IFileSystem> &&fs, const char *before, const char *after, bool unc = false);
|
||||
|
||||
virtual ~DirectoryRedirectionFileSystem();
|
||||
protected:
|
||||
inline std::optional<std::scoped_lock<os::Mutex>> GetAccessorLock() const {
|
||||
/* No accessor lock is needed. */
|
||||
return std::nullopt;
|
||||
}
|
||||
private:
|
||||
Result GetNormalizedDirectoryPath(char **out, size_t *out_size, const char *dir);
|
||||
Result Initialize(const char *before, const char *after);
|
||||
public:
|
||||
Result ResolveFullPath(char *out, size_t out_size, const char *relative_path);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 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 "impl/fssystem_path_resolution_filesystem.hpp"
|
||||
|
||||
namespace ams::fssystem {
|
||||
|
||||
class DirectorySaveDataFileSystem : public impl::IPathResolutionFileSystem<DirectorySaveDataFileSystem> {
|
||||
NON_COPYABLE(DirectorySaveDataFileSystem);
|
||||
private:
|
||||
using PathResolutionFileSystem = impl::IPathResolutionFileSystem<DirectorySaveDataFileSystem>;
|
||||
friend class impl::IPathResolutionFileSystem<DirectorySaveDataFileSystem>;
|
||||
private:
|
||||
os::Mutex accessor_mutex;
|
||||
s32 open_writable_files;
|
||||
public:
|
||||
DirectorySaveDataFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs);
|
||||
DirectorySaveDataFileSystem(std::unique_ptr<fs::fsa::IFileSystem> fs);
|
||||
Result Initialize();
|
||||
|
||||
virtual ~DirectorySaveDataFileSystem();
|
||||
protected:
|
||||
inline std::optional<std::scoped_lock<os::Mutex>> GetAccessorLock() {
|
||||
/* We have a real accessor lock that we want to use. */
|
||||
return std::make_optional<std::scoped_lock<os::Mutex>>(this->accessor_mutex);
|
||||
}
|
||||
private:
|
||||
Result AllocateWorkBuffer(std::unique_ptr<u8[]> *out, size_t *out_size, size_t ideal_size);
|
||||
Result SynchronizeDirectory(const char *dst, const char *src);
|
||||
Result ResolveFullPath(char *out, size_t out_size, const char *relative_path);
|
||||
public:
|
||||
void OnWritableFileClose();
|
||||
Result CopySaveFromFileSystem(fs::fsa::IFileSystem *save_fs);
|
||||
public:
|
||||
/* Overridden from IPathResolutionFileSystem */
|
||||
virtual Result OpenFileImpl(std::unique_ptr<fs::fsa::IFile> *out_file, const char *path, fs::OpenMode mode) override;
|
||||
virtual Result CommitImpl() override;
|
||||
|
||||
/* Overridden from IPathResolutionFileSystem but not commands. */
|
||||
virtual Result CommitProvisionallyImpl(s64 counter) override;
|
||||
virtual Result RollbackImpl() override;
|
||||
|
||||
/* Explicitly overridden to be not implemented. */
|
||||
virtual Result GetFreeSpaceSizeImpl(s64 *out, const char *path) override;
|
||||
virtual Result GetTotalSpaceSizeImpl(s64 *out, const char *path) override;
|
||||
virtual Result GetFileTimeStampRawImpl(fs::FileTimeStampRaw *out, const char *path) override;
|
||||
virtual Result QueryEntryImpl(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const char *path) override;
|
||||
virtual Result FlushImpl() override;
|
||||
};
|
||||
|
||||
}
|
|
@ -14,26 +14,30 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "fssystem_path_resolution_filesystem.hpp"
|
||||
#include "impl/fssystem_path_resolution_filesystem.hpp"
|
||||
|
||||
namespace ams::fssystem {
|
||||
|
||||
class SubDirectoryFileSystem : public IPathResolutionFileSystem<SubDirectoryFileSystem> {
|
||||
class SubDirectoryFileSystem : public impl::IPathResolutionFileSystem<SubDirectoryFileSystem> {
|
||||
NON_COPYABLE(SubDirectoryFileSystem);
|
||||
private:
|
||||
using PathResolutionFileSystem = IPathResolutionFileSystem<SubDirectoryFileSystem>;
|
||||
using PathResolutionFileSystem = impl::IPathResolutionFileSystem<SubDirectoryFileSystem>;
|
||||
friend class impl::IPathResolutionFileSystem<SubDirectoryFileSystem>;
|
||||
private:
|
||||
char *base_path;
|
||||
size_t base_path_len;
|
||||
bool unc_preserved;
|
||||
public:
|
||||
SubDirectoryFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *bp);
|
||||
SubDirectoryFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *bp, bool unc);
|
||||
SubDirectoryFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *bp, bool unc = false);
|
||||
SubDirectoryFileSystem(std::unique_ptr<fs::fsa::IFileSystem> &&fs, const char *bp, bool unc = false);
|
||||
|
||||
virtual ~SubDirectoryFileSystem();
|
||||
protected:
|
||||
inline std::optional<std::scoped_lock<os::Mutex>> GetAccessorLock() const {
|
||||
/* No accessor lock is needed. */
|
||||
return std::nullopt;
|
||||
}
|
||||
private:
|
||||
Result Initialize(const char *bp);
|
||||
public:
|
||||
Result ResolveFullPath(char *out, size_t out_size, const char *relative_path);
|
||||
};
|
||||
|
||||
|
|
|
@ -15,9 +15,119 @@
|
|||
*/
|
||||
#pragma once
|
||||
#include "../fs/fs_common.hpp"
|
||||
#include "../fs/fs_file.hpp"
|
||||
#include "../fs/fs_directory.hpp"
|
||||
#include "../fs/fs_filesystem.hpp"
|
||||
#include "fssystem_path_tool.hpp"
|
||||
|
||||
namespace ams::fssystem {
|
||||
|
||||
namespace impl {
|
||||
|
||||
/* Iteration. */
|
||||
template<typename OnEnterDir, typename OnExitDir, typename OnFile>
|
||||
Result IterateDirectoryRecursivelyImpl(fs::fsa::IFileSystem *fs, char *work_path, size_t work_path_size, fs::DirectoryEntry *dir_ent, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) {
|
||||
/* Open the directory. */
|
||||
std::unique_ptr<fs::fsa::IDirectory> dir;
|
||||
R_TRY(fs->OpenDirectory(&dir, work_path, fs::OpenDirectoryMode_All));
|
||||
|
||||
const size_t parent_len = strnlen(work_path, work_path_size - 1);
|
||||
|
||||
/* Read and handle entries. */
|
||||
while (true) {
|
||||
/* Read a single entry. */
|
||||
s64 read_count = 0;
|
||||
R_TRY(dir->Read(&read_count, dir_ent, 1));
|
||||
|
||||
/* If we're out of entries, we're done. */
|
||||
if (read_count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Validate child path size. */
|
||||
const size_t child_name_len = strnlen(dir_ent->name, sizeof(dir_ent->name) - 1);
|
||||
const bool is_dir = dir_ent->type == fs::DirectoryEntryType_Directory;
|
||||
const size_t separator_size = is_dir ? 1 : 0;
|
||||
R_UNLESS(parent_len + child_name_len + separator_size < work_path_size, fs::ResultTooLongPath());
|
||||
|
||||
/* Set child path. */
|
||||
std::strncat(work_path, dir_ent->name, work_path_size - parent_len - 1);
|
||||
{
|
||||
if (is_dir) {
|
||||
/* Enter directory. */
|
||||
R_TRY(on_enter_dir(work_path, *dir_ent));
|
||||
|
||||
/* Append separator, recurse. */
|
||||
std::strncat(work_path, "/", work_path_size - (parent_len + child_name_len) - 1);
|
||||
R_TRY(IterateDirectoryRecursivelyImpl(fs, work_path, work_path_size, dir_ent, on_enter_dir, on_exit_dir, on_file));
|
||||
|
||||
/* Exit directory. */
|
||||
R_TRY(on_exit_dir(work_path, *dir_ent));
|
||||
} else {
|
||||
/* Call file handler. */
|
||||
R_TRY(on_file(work_path, *dir_ent));
|
||||
}
|
||||
}
|
||||
|
||||
/* Restore parent path. */
|
||||
work_path[parent_len] = StringTraits::NullTerminator;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* TODO: Cleanup. */
|
||||
|
||||
}
|
||||
|
||||
/* Iteration API */
|
||||
template<typename OnEnterDir, typename OnExitDir, typename OnFile>
|
||||
Result IterateDirectoryRecursively(fs::fsa::IFileSystem *fs, const char *root_path, char *work_path, size_t work_path_size, fs::DirectoryEntry *dir_ent_buf, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) {
|
||||
AMS_ASSERT(work_path_size >= fs::EntryNameLengthMax + 1);
|
||||
|
||||
/* Get size of the root path. */
|
||||
size_t root_path_len = strnlen(root_path, fs::EntryNameLengthMax + 1);
|
||||
R_UNLESS(root_path_len <= fs::EntryNameLengthMax, fs::ResultTooLongPath());
|
||||
|
||||
/* Copy root path in, add a / if necessary. */
|
||||
std::memcpy(work_path, root_path, root_path_len);
|
||||
if (!PathTool::IsSeparator(work_path[root_path_len - 1])) {
|
||||
work_path[root_path_len++] = StringTraits::DirectorySeparator;
|
||||
}
|
||||
|
||||
/* Make sure the result path is still valid. */
|
||||
R_UNLESS(root_path_len <= fs::EntryNameLengthMax, fs::ResultTooLongPath());
|
||||
work_path[root_path_len] = StringTraits::NullTerminator;
|
||||
|
||||
return impl::IterateDirectoryRecursivelyImpl(fs, work_path, work_path_size, dir_ent_buf, on_enter_dir, on_exit_dir, on_file);
|
||||
}
|
||||
|
||||
template<typename OnEnterDir, typename OnExitDir, typename OnFile>
|
||||
Result IterateDirectoryRecursively(fs::fsa::IFileSystem *fs, const char *root_path, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) {
|
||||
fs::DirectoryEntry dir_entry = {};
|
||||
char work_path[fs::EntryNameLengthMax + 1] = {};
|
||||
return IterateDirectoryRecursively(fs, root_path, work_path, sizeof(work_path), &dir_entry, on_enter_dir, on_exit_dir, on_file);
|
||||
}
|
||||
|
||||
template<typename OnEnterDir, typename OnExitDir, typename OnFile>
|
||||
Result IterateDirectoryRecursively(fs::fsa::IFileSystem *fs, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) {
|
||||
return IterateDirectoryRecursively(fs, PathTool::RootPath, on_enter_dir, on_exit_dir, on_file);
|
||||
}
|
||||
|
||||
/* TODO: Cleanup API */
|
||||
|
||||
/* Copy API. */
|
||||
Result CopyFile(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const char *dst_parent_path, const char *src_path, const fs::DirectoryEntry *dir_ent, void *work_buf, size_t work_buf_size);
|
||||
NX_INLINE Result CopyFile(fs::fsa::IFileSystem *fs, const char *dst_parent_path, const char *src_path, const fs::DirectoryEntry *dir_ent, void *work_buf, size_t work_buf_size) {
|
||||
return CopyFile(fs, fs, dst_parent_path, src_path, dir_ent, work_buf, work_buf_size);
|
||||
}
|
||||
|
||||
Result CopyDirectoryRecursively(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const char *dst_path, const char *src_path, void *work_buf, size_t work_buf_size);
|
||||
NX_INLINE Result CopyDirectoryRecursively(fs::fsa::IFileSystem *fs, const char *dst_path, const char *src_path, void *work_buf, size_t work_buf_size) {
|
||||
return CopyDirectoryRecursively(fs, fs, dst_path, src_path, work_buf, work_buf_size);
|
||||
}
|
||||
|
||||
/* Semaphore adapter class. */
|
||||
class SemaphoreAdapter : public os::Semaphore {
|
||||
public:
|
||||
SemaphoreAdapter(int c, int mc) : os::Semaphore(c, mc) { /* ... */ }
|
||||
|
@ -31,4 +141,29 @@ namespace ams::fssystem {
|
|||
}
|
||||
};
|
||||
|
||||
/* Other utility. */
|
||||
Result EnsureDirectoryExistsRecursively(fs::fsa::IFileSystem *fs, const char *path);
|
||||
|
||||
template<typename F>
|
||||
NX_INLINE Result RetryFinitelyForTargetLocked(F f) {
|
||||
/* Retry up to 10 times, 100ms between retries. */
|
||||
constexpr s32 MaxRetryCount = 10;
|
||||
constexpr u64 RetryWaitTime = 100'000'000ul;
|
||||
|
||||
s32 remaining_retries = MaxRetryCount;
|
||||
while (true) {
|
||||
R_TRY_CATCH(f()) {
|
||||
R_CATCH(fs::ResultTargetLocked) {
|
||||
R_UNLESS(remaining_retries > 0, fs::ResultTargetLocked());
|
||||
|
||||
remaining_retries--;
|
||||
svcSleepThread(RetryWaitTime);
|
||||
continue;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,27 +14,29 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "../fs/fs_common.hpp"
|
||||
#include "../fs/fsa/fs_ifile.hpp"
|
||||
#include "../fs/fsa/fs_idirectory.hpp"
|
||||
#include "../fs/fsa/fs_ifilesystem.hpp"
|
||||
#include "../../fs/fs_common.hpp"
|
||||
#include "../../fs/fsa/fs_ifile.hpp"
|
||||
#include "../../fs/fsa/fs_idirectory.hpp"
|
||||
#include "../../fs/fsa/fs_ifilesystem.hpp"
|
||||
|
||||
namespace ams::fssystem {
|
||||
namespace ams::fssystem::impl {
|
||||
|
||||
template<typename Impl>
|
||||
class IPathResolutionFileSystem : public fs::fsa::IFileSystem {
|
||||
NON_COPYABLE(IPathResolutionFileSystem);
|
||||
private:
|
||||
std::shared_ptr<fs::fsa::IFileSystem> shared_fs;
|
||||
fs::fsa::IFileSystem *base_fs;
|
||||
std::unique_ptr<fs::fsa::IFileSystem> unique_fs;
|
||||
bool unc_preserved;
|
||||
protected:
|
||||
fs::fsa::IFileSystem * const base_fs;
|
||||
public:
|
||||
IPathResolutionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs) : shared_fs(std::move(fs)), unc_preserved(false) {
|
||||
this->base_fs = this->shared_fs.get();
|
||||
IPathResolutionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, bool unc = false) : shared_fs(std::move(fs)), unc_preserved(unc), base_fs(shared_fs.get()) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
IPathResolutionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, bool unc) : shared_fs(std::move(fs)), unc_preserved(unc) {
|
||||
this->base_fs = this->shared_fs.get();
|
||||
IPathResolutionFileSystem(std::unique_ptr<fs::fsa::IFileSystem> &&fs, bool unc = false) : unique_fs(std::move(fs)), unc_preserved(unc), base_fs(unique_fs.get()) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
virtual ~IPathResolutionFileSystem() { /* ... */ }
|
||||
|
@ -47,6 +49,7 @@ namespace ams::fssystem {
|
|||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->CreateFile(full_path, size, option);
|
||||
}
|
||||
|
||||
|
@ -54,6 +57,7 @@ namespace ams::fssystem {
|
|||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->DeleteFile(full_path);
|
||||
}
|
||||
|
||||
|
@ -61,6 +65,7 @@ namespace ams::fssystem {
|
|||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->CreateDirectory(full_path);
|
||||
}
|
||||
|
||||
|
@ -68,6 +73,7 @@ namespace ams::fssystem {
|
|||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->DeleteDirectory(full_path);
|
||||
}
|
||||
|
||||
|
@ -75,6 +81,7 @@ namespace ams::fssystem {
|
|||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->DeleteDirectoryRecursively(full_path);
|
||||
}
|
||||
|
||||
|
@ -84,6 +91,7 @@ namespace ams::fssystem {
|
|||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(old_full_path, sizeof(old_full_path), old_path));
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(new_full_path, sizeof(new_full_path), new_path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->RenameFile(old_path, new_path);
|
||||
}
|
||||
|
||||
|
@ -93,6 +101,7 @@ namespace ams::fssystem {
|
|||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(old_full_path, sizeof(old_full_path), old_path));
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(new_full_path, sizeof(new_full_path), new_path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->RenameDirectory(old_path, new_path);
|
||||
}
|
||||
|
||||
|
@ -100,6 +109,7 @@ namespace ams::fssystem {
|
|||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->GetEntryType(out, full_path);
|
||||
}
|
||||
|
||||
|
@ -107,6 +117,7 @@ namespace ams::fssystem {
|
|||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->OpenFile(out_file, full_path, mode);
|
||||
}
|
||||
|
||||
|
@ -114,17 +125,20 @@ namespace ams::fssystem {
|
|||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->OpenDirectory(out_dir, full_path, mode);
|
||||
}
|
||||
|
||||
virtual Result CommitImpl() override {
|
||||
return this->base_fs->Rollback();
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->Commit();
|
||||
}
|
||||
|
||||
virtual Result GetFreeSpaceSizeImpl(s64 *out, const char *path) override {
|
||||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->GetFreeSpaceSize(out, full_path);
|
||||
}
|
||||
|
||||
|
@ -132,6 +146,7 @@ namespace ams::fssystem {
|
|||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->GetTotalSpaceSize(out, full_path);
|
||||
}
|
||||
|
||||
|
@ -139,6 +154,7 @@ namespace ams::fssystem {
|
|||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->CleanDirectoryRecursively(full_path);
|
||||
}
|
||||
|
||||
|
@ -146,6 +162,7 @@ namespace ams::fssystem {
|
|||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->GetFileTimeStampRaw(out, full_path);
|
||||
}
|
||||
|
||||
|
@ -153,19 +170,23 @@ namespace ams::fssystem {
|
|||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->QueryEntry(dst, dst_size, src, src_size, query, full_path);
|
||||
}
|
||||
|
||||
/* These aren't accessible as commands. */
|
||||
virtual Result CommitProvisionallyImpl(s64 counter) override {
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->CommitProvisionally(counter);
|
||||
}
|
||||
|
||||
virtual Result RollbackImpl() override {
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->Rollback();
|
||||
}
|
||||
|
||||
virtual Result FlushImpl() override {
|
||||
std::optional optional_lock = static_cast<Impl*>(this)->GetAccessorLock();
|
||||
return this->base_fs->Flush();
|
||||
}
|
||||
};
|
|
@ -56,6 +56,10 @@ namespace ams::sf::cmif {
|
|||
return this->impl_metadata.GetOutObjectCount();
|
||||
}
|
||||
|
||||
constexpr size_t GetImplOutHeadersSize() const {
|
||||
return this->impl_metadata.GetOutHeadersSize();
|
||||
}
|
||||
|
||||
constexpr size_t GetImplOutDataTotalSize() const {
|
||||
return this->impl_metadata.GetOutDataSize() + this->impl_metadata.GetOutHeadersSize();
|
||||
}
|
||||
|
|
|
@ -47,8 +47,8 @@ namespace ams::cfg {
|
|||
}
|
||||
|
||||
/* Flag utilities. */
|
||||
bool HasFlag(ncm::ProgramId program_id, const char *flag) {
|
||||
return HasContentSpecificFlag(program_id, flag) || (IsHblProgramId(program_id) && HasHblFlag(flag));
|
||||
bool HasFlag(const sm::MitmProcessInfo &process_info, const char *flag) {
|
||||
return HasContentSpecificFlag(process_info.program_id, flag) || (process_info.override_status.IsHbl() && HasHblFlag(flag));
|
||||
}
|
||||
|
||||
bool HasContentSpecificFlag(ncm::ProgramId program_id, const char *flag) {
|
||||
|
|
|
@ -17,13 +17,17 @@
|
|||
|
||||
namespace ams::fssystem {
|
||||
|
||||
DirectoryRedirectionFileSystem::DirectoryRedirectionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *before, const char *after) : PathResolutionFileSystem(fs) {
|
||||
DirectoryRedirectionFileSystem::DirectoryRedirectionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *before, const char *after, bool unc)
|
||||
: PathResolutionFileSystem(fs, unc)
|
||||
{
|
||||
this->before_dir = nullptr;
|
||||
this->after_dir = nullptr;
|
||||
R_ASSERT(this->Initialize(before, after));
|
||||
}
|
||||
|
||||
DirectoryRedirectionFileSystem::DirectoryRedirectionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *before, const char *after, bool unc) : PathResolutionFileSystem(fs, unc) {
|
||||
DirectoryRedirectionFileSystem::DirectoryRedirectionFileSystem(std::unique_ptr<fs::fsa::IFileSystem> &&fs, const char *before, const char *after, bool unc)
|
||||
: PathResolutionFileSystem(std::forward<std::unique_ptr<fs::fsa::IFileSystem>>(fs), unc)
|
||||
{
|
||||
this->before_dir = nullptr;
|
||||
this->after_dir = nullptr;
|
||||
R_ASSERT(this->Initialize(before, after));
|
||||
|
|
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 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>
|
||||
|
||||
namespace ams::fssystem {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t IdealWorkBufferSize = 0x100000; /* 1 MiB */
|
||||
|
||||
constexpr const char CommittedDirectoryPath[] = "/0/";
|
||||
constexpr const char WorkingDirectoryPath[] = "/1/";
|
||||
constexpr const char SynchronizingDirectoryPath[] = "/_/";
|
||||
|
||||
class DirectorySaveDataFile : public fs::fsa::IFile {
|
||||
private:
|
||||
std::unique_ptr<fs::fsa::IFile> base_file;
|
||||
DirectorySaveDataFileSystem *parent_fs;
|
||||
fs::OpenMode open_mode;
|
||||
public:
|
||||
DirectorySaveDataFile(std::unique_ptr<fs::fsa::IFile> f, DirectorySaveDataFileSystem *p, fs::OpenMode m) : base_file(std::move(f)), parent_fs(p), open_mode(m) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
virtual ~DirectorySaveDataFile() {
|
||||
/* Observe closing of writable file. */
|
||||
if (this->open_mode & fs::OpenMode_Write) {
|
||||
this->parent_fs->OnWritableFileClose();
|
||||
}
|
||||
}
|
||||
public:
|
||||
virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override {
|
||||
return this->base_file->Read(out, offset, buffer, size, option);
|
||||
}
|
||||
|
||||
virtual Result GetSizeImpl(s64 *out) override {
|
||||
return this->base_file->GetSize(out);
|
||||
}
|
||||
|
||||
virtual Result FlushImpl() override {
|
||||
return this->base_file->Flush();
|
||||
}
|
||||
|
||||
virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override {
|
||||
return this->base_file->Write(offset, buffer, size, option);
|
||||
}
|
||||
|
||||
virtual Result SetSizeImpl(s64 size) override {
|
||||
return this->base_file->SetSize(size);
|
||||
}
|
||||
|
||||
virtual Result OperateRangeImpl(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
|
||||
return this->base_file->OperateRange(dst, dst_size, op_id, offset, size, src, src_size);
|
||||
}
|
||||
public:
|
||||
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
|
||||
return this->base_file->GetDomainObjectId();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
DirectorySaveDataFileSystem::DirectorySaveDataFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs)
|
||||
: PathResolutionFileSystem(fs)
|
||||
{
|
||||
/* ... */
|
||||
}
|
||||
|
||||
DirectorySaveDataFileSystem::DirectorySaveDataFileSystem(std::unique_ptr<fs::fsa::IFileSystem> fs)
|
||||
: PathResolutionFileSystem(std::forward<std::unique_ptr<fs::fsa::IFileSystem>>(fs))
|
||||
{
|
||||
/* ... */
|
||||
}
|
||||
|
||||
DirectorySaveDataFileSystem::~DirectorySaveDataFileSystem() {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::Initialize() {
|
||||
/* Nintendo does not acquire the lock here, but I think we probably should. */
|
||||
std::scoped_lock lk(this->accessor_mutex);
|
||||
|
||||
fs::DirectoryEntryType type;
|
||||
|
||||
/* Check that the working directory exists. */
|
||||
R_TRY_CATCH(this->base_fs->GetEntryType(&type, WorkingDirectoryPath)) {
|
||||
/* If path isn't found, create working directory and committed directory. */
|
||||
R_CATCH(fs::ResultPathNotFound) {
|
||||
R_TRY(this->base_fs->CreateDirectory(WorkingDirectoryPath));
|
||||
R_TRY(this->base_fs->CreateDirectory(CommittedDirectoryPath));
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* Now check for the committed directory. */
|
||||
R_TRY_CATCH(this->base_fs->GetEntryType(&type, CommittedDirectoryPath)) {
|
||||
/* Committed doesn't exist, so synchronize and rename. */
|
||||
R_CATCH(fs::ResultPathNotFound) {
|
||||
R_TRY(this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath));
|
||||
R_TRY(this->base_fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath));
|
||||
return ResultSuccess();
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* The committed directory exists, so synchronize it to the working directory. */
|
||||
return this->SynchronizeDirectory(WorkingDirectoryPath, CommittedDirectoryPath);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::AllocateWorkBuffer(std::unique_ptr<u8[]> *out, size_t *out_size, size_t size) {
|
||||
/* Repeatedly try to allocate until success. */
|
||||
while (size > 0x200) {
|
||||
/* Allocate the buffer. */
|
||||
if (auto mem = new (std::nothrow) u8[size]; mem != nullptr) {
|
||||
out->reset(mem);
|
||||
*out_size = size;
|
||||
return ResultSuccess();
|
||||
} else {
|
||||
/* Divide size by two. */
|
||||
size >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Return a result here? Nintendo does not, but they have other allocation failed results. */
|
||||
/* Consider returning ResultFsAllocationFailureInDirectorySaveDataFileSystem? */
|
||||
AMS_ASSERT(false);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::SynchronizeDirectory(const char *dst, const char *src) {
|
||||
/* Delete destination dir and recreate it. */
|
||||
R_TRY_CATCH(this->base_fs->DeleteDirectoryRecursively(dst)) {
|
||||
R_CATCH(fs::ResultPathNotFound) { /* Nintendo returns error unconditionally, but I think that's a bug in their code. */}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
R_TRY(this->base_fs->CreateDirectory(dst));
|
||||
|
||||
/* Get a work buffer to work with. */
|
||||
std::unique_ptr<u8[]> work_buf;
|
||||
size_t work_buf_size;
|
||||
R_TRY(this->AllocateWorkBuffer(&work_buf, &work_buf_size, IdealWorkBufferSize));
|
||||
|
||||
/* Copy the directory recursively. */
|
||||
return fssystem::CopyDirectoryRecursively(this->base_fs, dst, src, work_buf.get(), work_buf_size);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::ResolveFullPath(char *out, size_t out_size, const char *relative_path) {
|
||||
R_UNLESS(strnlen(relative_path, fs::EntryNameLengthMax + 1) < fs::EntryNameLengthMax + 1, fs::ResultTooLongPath());
|
||||
R_UNLESS(PathTool::IsSeparator(relative_path[0]), fs::ResultInvalidPath());
|
||||
|
||||
/* Copy working directory path. */
|
||||
std::strncpy(out, WorkingDirectoryPath, out_size);
|
||||
out[out_size - 1] = StringTraits::NullTerminator;
|
||||
|
||||
/* Normalize it. */
|
||||
constexpr size_t WorkingDirectoryPathLength = sizeof(WorkingDirectoryPath) - 1;
|
||||
size_t normalized_length;
|
||||
return PathTool::Normalize(out + WorkingDirectoryPathLength - 1, &normalized_length, relative_path, out_size - (WorkingDirectoryPathLength - 1));
|
||||
}
|
||||
|
||||
void DirectorySaveDataFileSystem::OnWritableFileClose() {
|
||||
std::scoped_lock lk(this->accessor_mutex);
|
||||
this->open_writable_files--;
|
||||
|
||||
/* Nintendo does not check this, but I think it's sensible to do so. */
|
||||
AMS_ASSERT(this->open_writable_files >= 0);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::CopySaveFromFileSystem(fs::fsa::IFileSystem *save_fs) {
|
||||
/* If the input save is null, there's nothing to copy. */
|
||||
R_UNLESS(save_fs != nullptr, ResultSuccess());
|
||||
|
||||
/* Get a work buffer to work with. */
|
||||
std::unique_ptr<u8[]> work_buf;
|
||||
size_t work_buf_size;
|
||||
R_TRY(this->AllocateWorkBuffer(&work_buf, &work_buf_size, IdealWorkBufferSize));
|
||||
|
||||
/* Copy the directory recursively. */
|
||||
R_TRY(fssystem::CopyDirectoryRecursively(this->base_fs, save_fs, PathTool::RootPath, PathTool::RootPath, work_buf.get(), work_buf_size));
|
||||
|
||||
return this->Commit();
|
||||
}
|
||||
|
||||
/* Overridden from IPathResolutionFileSystem */
|
||||
Result DirectorySaveDataFileSystem::OpenFileImpl(std::unique_ptr<fs::fsa::IFile> *out_file, const char *path, fs::OpenMode mode) {
|
||||
char full_path[fs::EntryNameLengthMax + 1];
|
||||
R_TRY(this->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||
|
||||
std::scoped_lock lk(this->accessor_mutex);
|
||||
std::unique_ptr<fs::fsa::IFile> base_file;
|
||||
R_TRY(this->base_fs->OpenFile(&base_file, full_path, mode));
|
||||
|
||||
std::unique_ptr<DirectorySaveDataFile> file(new (std::nothrow) DirectorySaveDataFile(std::move(base_file), this, mode));
|
||||
R_UNLESS(file != nullptr, fs::ResultAllocationFailureInDirectorySaveDataFileSystem());
|
||||
|
||||
if (mode & fs::OpenMode_Write) {
|
||||
this->open_writable_files++;
|
||||
}
|
||||
|
||||
*out_file = std::move(file);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::CommitImpl() {
|
||||
/* Here, Nintendo does the following (with retries): */
|
||||
/* - Rename Committed -> Synchronizing. */
|
||||
/* - Synchronize Working -> Synchronizing (deleting Synchronizing). */
|
||||
/* - Rename Synchronizing -> Committed. */
|
||||
std::scoped_lock lk(this->accessor_mutex);
|
||||
|
||||
R_UNLESS(this->open_writable_files == 0, fs::ResultPreconditionViolation());
|
||||
|
||||
const auto RenameCommitedDir = [&]() { return this->base_fs->RenameDirectory(CommittedDirectoryPath, SynchronizingDirectoryPath); };
|
||||
const auto SynchronizeWorkingDir = [&]() { return this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath); };
|
||||
const auto RenameSynchronizingDir = [&]() { return this->base_fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath); };
|
||||
|
||||
/* Rename Committed -> Synchronizing. */
|
||||
R_TRY(fssystem::RetryFinitelyForTargetLocked(std::move(RenameCommitedDir)));
|
||||
|
||||
/* - Synchronize Working -> Synchronizing (deleting Synchronizing). */
|
||||
R_TRY(fssystem::RetryFinitelyForTargetLocked(std::move(SynchronizeWorkingDir)));
|
||||
|
||||
/* - Rename Synchronizing -> Committed. */
|
||||
R_TRY(fssystem::RetryFinitelyForTargetLocked(std::move(RenameSynchronizingDir)));
|
||||
|
||||
/* TODO: Should I call this->base_fs->Commit()? Nintendo does not. */
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Overridden from IPathResolutionFileSystem but not commands. */
|
||||
Result DirectorySaveDataFileSystem::CommitProvisionallyImpl(s64 counter) {
|
||||
/* Nintendo does nothing here. */
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::RollbackImpl() {
|
||||
/* Initialize overwrites the working directory with the committed directory. */
|
||||
return this->Initialize();
|
||||
}
|
||||
|
||||
/* Explicitly overridden to be not implemented. */
|
||||
Result DirectorySaveDataFileSystem::GetFreeSpaceSizeImpl(s64 *out, const char *path) {
|
||||
return fs::ResultNotImplemented();
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::GetTotalSpaceSizeImpl(s64 *out, const char *path) {
|
||||
return fs::ResultNotImplemented();
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::GetFileTimeStampRawImpl(fs::FileTimeStampRaw *out, const char *path) {
|
||||
return fs::ResultNotImplemented();
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::QueryEntryImpl(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const char *path) {
|
||||
return fs::ResultNotImplemented();
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::FlushImpl() {
|
||||
return fs::ResultNotImplemented();
|
||||
}
|
||||
|
||||
}
|
|
@ -17,12 +17,16 @@
|
|||
|
||||
namespace ams::fssystem {
|
||||
|
||||
SubDirectoryFileSystem::SubDirectoryFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *bp) : PathResolutionFileSystem(fs) {
|
||||
SubDirectoryFileSystem::SubDirectoryFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *bp, bool unc)
|
||||
: PathResolutionFileSystem(fs, unc)
|
||||
{
|
||||
this->base_path = nullptr;
|
||||
R_ASSERT(this->Initialize(bp));
|
||||
}
|
||||
|
||||
SubDirectoryFileSystem::SubDirectoryFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *bp, bool unc) : PathResolutionFileSystem(fs, unc) {
|
||||
SubDirectoryFileSystem::SubDirectoryFileSystem(std::unique_ptr<fs::fsa::IFileSystem> &&fs, const char *bp, bool unc)
|
||||
: PathResolutionFileSystem(std::forward<std::unique_ptr<fs::fsa::IFileSystem>>(fs), unc)
|
||||
{
|
||||
this->base_path = nullptr;
|
||||
R_ASSERT(this->Initialize(bp));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 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>
|
||||
|
||||
namespace ams::fssystem {
|
||||
|
||||
namespace {
|
||||
|
||||
inline Result EnsureDirectoryExists(fs::fsa::IFileSystem *fs, const char *path) {
|
||||
R_TRY_CATCH(fs->CreateDirectory(path)) {
|
||||
R_CATCH(fs::ResultPathAlreadyExists) { /* If path already exists, there's no problem. */ }
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result CopyFile(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const char *dst_parent_path, const char *src_path, const fs::DirectoryEntry *entry, void *work_buf, size_t work_buf_size) {
|
||||
/* Open source file. */
|
||||
std::unique_ptr<fs::fsa::IFile> src_file;
|
||||
R_TRY(src_fs->OpenFile(&src_file, src_path, fs::OpenMode_Read));
|
||||
|
||||
/* Open dst file. */
|
||||
std::unique_ptr<fs::fsa::IFile> dst_file;
|
||||
{
|
||||
char dst_path[fs::EntryNameLengthMax + 1];
|
||||
const size_t original_size = static_cast<size_t>(std::snprintf(dst_path, sizeof(dst_path), "%s%s", dst_parent_path, entry->name));
|
||||
/* TODO: Error code? N aborts here. */
|
||||
AMS_ASSERT(original_size < sizeof(dst_path));
|
||||
|
||||
R_TRY(dst_fs->CreateFile(dst_path, entry->file_size));
|
||||
R_TRY(dst_fs->OpenFile(&dst_file, dst_path, fs::OpenMode_Write));
|
||||
}
|
||||
|
||||
/* Read/Write file in work buffer sized chunks. */
|
||||
s64 remaining = entry->file_size;
|
||||
s64 offset = 0;
|
||||
while (remaining > 0) {
|
||||
size_t read_size;
|
||||
R_TRY(src_file->Read(&read_size, offset, work_buf, work_buf_size, fs::ReadOption()));
|
||||
R_TRY(dst_file->Write(offset, work_buf, read_size, fs::WriteOption()));
|
||||
|
||||
remaining -= read_size;
|
||||
offset += read_size;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result CopyDirectoryRecursively(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const char *dst_path, const char *src_path, void *work_buf, size_t work_buf_size) {
|
||||
char dst_path_buf[fs::EntryNameLengthMax + 1];
|
||||
const size_t original_size = static_cast<size_t>(std::snprintf(dst_path_buf, sizeof(dst_path_buf), "%s", dst_path));
|
||||
AMS_ASSERT(original_size < sizeof(dst_path_buf));
|
||||
|
||||
return IterateDirectoryRecursively(src_fs, src_path,
|
||||
[&](const char *path, const fs::DirectoryEntry &entry) -> Result { /* On Enter Directory */
|
||||
/* Update path, create new dir. */
|
||||
std::strncat(dst_path_buf, entry.name, sizeof(dst_path_buf) - strnlen(dst_path_buf, sizeof(dst_path_buf) - 1) - 1);
|
||||
std::strncat(dst_path_buf, "/", sizeof(dst_path_buf) - strnlen(dst_path_buf, sizeof(dst_path_buf) - 1) - 1);
|
||||
return dst_fs->CreateDirectory(dst_path_buf);
|
||||
},
|
||||
[&](const char *path, const fs::DirectoryEntry &entry) -> Result { /* On Exit Directory */
|
||||
/* Check we have a parent directory. */
|
||||
const size_t len = strnlen(dst_path_buf, sizeof(dst_path_buf));
|
||||
R_UNLESS(len >= 2, fs::ResultInvalidPathFormat());
|
||||
|
||||
/* Find previous separator, add null terminator */
|
||||
char *cur = &dst_path_buf[len - 2];
|
||||
while (!PathTool::IsSeparator(*cur) && cur > dst_path_buf) {
|
||||
cur--;
|
||||
}
|
||||
cur[1] = StringTraits::NullTerminator;
|
||||
|
||||
return ResultSuccess();
|
||||
},
|
||||
[&](const char *path, const fs::DirectoryEntry &entry) -> Result { /* On File */
|
||||
return CopyFile(dst_fs, src_fs, dst_path_buf, path, &entry, work_buf, work_buf_size);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Result EnsureDirectoryExistsRecursively(fs::fsa::IFileSystem *fs, const char *path) {
|
||||
/* Normalize the path. */
|
||||
char normalized_path[fs::EntryNameLengthMax + 1];
|
||||
size_t normalized_path_len;
|
||||
R_TRY(PathTool::Normalize(normalized_path, &normalized_path_len, path, sizeof(normalized_path)));
|
||||
|
||||
/* Repeatedly call CreateDirectory on each directory leading to the target. */
|
||||
for (size_t i = 1; i < normalized_path_len; i++) {
|
||||
/* If we detect a separator, create the directory. */
|
||||
if (PathTool::IsSeparator(normalized_path[i])) {
|
||||
normalized_path[i] = StringTraits::NullTerminator;
|
||||
R_TRY(EnsureDirectoryExists(fs, normalized_path));
|
||||
normalized_path[i] = StringTraits::DirectorySeparator;
|
||||
}
|
||||
}
|
||||
|
||||
/* Call CreateDirectory on the final path. */
|
||||
R_TRY(EnsureDirectoryExists(fs, normalized_path));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -149,8 +149,8 @@ namespace ams::sf::cmif {
|
|||
|
||||
/* Write out header. */
|
||||
constexpr size_t out_header_size = sizeof(CmifDomainOutHeader);
|
||||
const size_t impl_out_data_total_size = this->GetImplOutDataTotalSize();
|
||||
AMS_ASSERT(out_header_size + impl_out_data_total_size <= raw_data.GetSize());
|
||||
const size_t impl_out_headers_size = this->GetImplOutHeadersSize();
|
||||
AMS_ASSERT(out_header_size + impl_out_headers_size <= raw_data.GetSize());
|
||||
*reinterpret_cast<CmifDomainOutHeader *>(raw_data.GetPointer()) = CmifDomainOutHeader{ .num_out_objects = 0, };
|
||||
|
||||
/* Set output raw data. */
|
||||
|
|
Loading…
Reference in a new issue