ams_mitm: begin skeleton refactor
This commit is contained in:
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* 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 <cstring>
|
||||
#include <cstdlib>
|
||||
#include <switch.h>
|
||||
|
||||
#include "fs_dir_utils.hpp"
|
||||
#include "fs_ifile.hpp"
|
||||
|
||||
Result FsDirUtils::CopyFile(IFileSystem *dst_fs, IFileSystem *src_fs, const FsPath &dst_parent_path, const FsPath &src_path, const FsDirectoryEntry *dir_ent, void *work_buf, size_t work_buf_size) {
|
||||
std::unique_ptr<IFile> src_file;
|
||||
std::unique_ptr<IFile> dst_file;
|
||||
const u64 file_size = dir_ent->fileSize;
|
||||
|
||||
/* Open source file for reading. */
|
||||
R_TRY(src_fs->OpenFile(src_file, src_path, OpenMode_Read));
|
||||
|
||||
/* Create and open destination file. */
|
||||
{
|
||||
FsPath dst_path;
|
||||
/* TODO: Error code? N aborts here. */
|
||||
AMS_ASSERT(static_cast<size_t>(snprintf(dst_path.str, sizeof(dst_path.str), "%s%s", dst_parent_path.str, dir_ent->name)) < sizeof(dst_path));
|
||||
|
||||
R_TRY(dst_fs->CreateFile(dst_path, file_size));
|
||||
R_TRY(dst_fs->OpenFile(dst_file, dst_path, OpenMode_Write));
|
||||
}
|
||||
|
||||
/* Read/Write work_buf_size chunks. */
|
||||
u64 offset = 0;
|
||||
while (offset < file_size) {
|
||||
u64 read_size;
|
||||
R_TRY(src_file->Read(&read_size, offset, work_buf, work_buf_size));
|
||||
R_TRY(dst_file->Write(offset, work_buf, read_size));
|
||||
|
||||
offset += read_size;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FsDirUtils::CopyDirectoryRecursively(IFileSystem *dst_fs, IFileSystem *src_fs, const FsPath &dst_path, const FsPath &src_path, void *work_buf, size_t work_buf_size) {
|
||||
FsPath work_path = dst_path;
|
||||
|
||||
return IterateDirectoryRecursively(src_fs, src_path,
|
||||
[&](const FsPath &path, const FsDirectoryEntry *dir_ent) -> Result { /* On Enter Directory */
|
||||
/* Update path, create new dir. */
|
||||
strncat(work_path.str, dir_ent->name, sizeof(work_path) - strnlen(work_path.str, sizeof(work_path) - 1) - 1);
|
||||
strncat(work_path.str, "/", sizeof(work_path) - strnlen(work_path.str, sizeof(work_path) - 1) - 1);
|
||||
return dst_fs->CreateDirectory(work_path);
|
||||
},
|
||||
[&](const FsPath &path, const FsDirectoryEntry *dir_ent) -> Result { /* On Exit Directory */
|
||||
/* Check we have a parent directory. */
|
||||
const size_t work_path_len = strnlen(work_path.str, sizeof(work_path));
|
||||
if (work_path_len < 2) {
|
||||
return ResultFsInvalidPathFormat;
|
||||
}
|
||||
|
||||
/* Find previous separator, add NULL terminator */
|
||||
char *p = &work_path.str[work_path_len - 2];
|
||||
while (*p != '/' && p > work_path.str) {
|
||||
p--;
|
||||
}
|
||||
p[1] = 0;
|
||||
|
||||
return ResultSuccess();
|
||||
},
|
||||
[&](const FsPath &path, const FsDirectoryEntry *dir_ent) -> Result { /* On File */
|
||||
/* Just copy the file to the new fs. */
|
||||
return CopyFile(dst_fs, src_fs, work_path, path, dir_ent, work_buf, work_buf_size);
|
||||
});
|
||||
}
|
||||
|
||||
Result FsDirUtils::EnsureDirectoryExists(IFileSystem *fs, const FsPath &path) {
|
||||
FsPath normal_path;
|
||||
size_t normal_path_len;
|
||||
|
||||
/* Normalize the path. */
|
||||
R_TRY(FsPathUtils::Normalize(normal_path.str, sizeof(normal_path.str) - 1, path.str, &normal_path_len));
|
||||
|
||||
/* Repeatedly call CreateDirectory on each directory leading to the target. */
|
||||
for (size_t i = 1; i < normal_path_len; i++) {
|
||||
/* If we detect a separator, we're done. */
|
||||
if (normal_path.str[i] == '/') {
|
||||
normal_path.str[i] = 0;
|
||||
{
|
||||
R_TRY_CATCH(fs->CreateDirectory(normal_path)) {
|
||||
R_CATCH(ResultFsPathAlreadyExists) {
|
||||
/* If path already exists, there's no problem. */
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
}
|
||||
normal_path.str[i] = '/';
|
||||
}
|
||||
}
|
||||
|
||||
/* Call CreateDirectory on the final path. */
|
||||
R_TRY_CATCH(fs->CreateDirectory(normal_path)) {
|
||||
R_CATCH(ResultFsPathAlreadyExists) {
|
||||
/* If path already exists, there's no problem. */
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
|
||||
#include "fs_path_utils.hpp"
|
||||
#include "fs_ifilesystem.hpp"
|
||||
|
||||
class FsDirUtils {
|
||||
private:
|
||||
template<typename OnEnterDir, typename OnExitDir, typename OnFile>
|
||||
static Result IterateDirectoryRecursivelyInternal(IFileSystem *fs, FsPath &work_path, FsDirectoryEntry *ent_buf, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) {
|
||||
std::unique_ptr<IDirectory> dir;
|
||||
|
||||
/* Open the directory. */
|
||||
R_TRY(fs->OpenDirectory(dir, work_path, DirectoryOpenMode_All));
|
||||
|
||||
const size_t parent_len = strnlen(work_path.str, sizeof(work_path.str) - 1);
|
||||
|
||||
/* Read and handle entries. */
|
||||
while (true) {
|
||||
/* Read a single entry. */
|
||||
u64 read_count;
|
||||
R_TRY(dir->Read(&read_count, ent_buf, 1));
|
||||
|
||||
/* If we're out of entries, we're done. */
|
||||
if (read_count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
const size_t child_name_len = strnlen(ent_buf->name, sizeof(ent_buf->name) - 1);
|
||||
const bool is_dir = ent_buf->type == ENTRYTYPE_DIR;
|
||||
const size_t child_path_len = parent_len + child_name_len + (is_dir ? 1 : 0);
|
||||
|
||||
/* Validate child path size. */
|
||||
if (child_path_len >= sizeof(work_path.str)) {
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
strncat(work_path.str, ent_buf->name, sizeof(work_path.str) - 1 - parent_len);
|
||||
if (is_dir) {
|
||||
/* Enter directory. */
|
||||
R_TRY(on_enter_dir(work_path, ent_buf));
|
||||
|
||||
/* Append separator, recurse. */
|
||||
strncat(work_path.str, "/", sizeof(work_path.str) - 1 - parent_len - child_name_len);
|
||||
R_TRY(IterateDirectoryRecursivelyInternal(fs, work_path, ent_buf, on_enter_dir, on_exit_dir, on_file));
|
||||
|
||||
/* Exit directory. */
|
||||
R_TRY(on_exit_dir(work_path, ent_buf));
|
||||
} else {
|
||||
/* Call file handler. */
|
||||
R_TRY(on_file(work_path, ent_buf));
|
||||
}
|
||||
|
||||
/* Restore parent path. */
|
||||
work_path.str[parent_len] = 0;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
public:
|
||||
template<typename OnEnterDir, typename OnExitDir, typename OnFile>
|
||||
static Result IterateDirectoryRecursively(IFileSystem *fs, const FsPath &root_path, FsPath &work_path, FsDirectoryEntry *ent_buf, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) {
|
||||
/* Ensure valid root path. */
|
||||
size_t root_path_len = strnlen(root_path.str, sizeof(root_path.str));
|
||||
if (root_path_len > FS_MAX_PATH - 1 || ((root_path_len == FS_MAX_PATH - 1) && root_path.str[root_path_len-1] != '/')) {
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
/* Copy path, ensure terminating separator. */
|
||||
memcpy(work_path.str, root_path.str, root_path_len);
|
||||
if (work_path.str[root_path_len-1] != '/') {
|
||||
root_path_len++;
|
||||
work_path.str[root_path_len-1] = '/';
|
||||
}
|
||||
work_path.str[root_path_len] = 0;
|
||||
|
||||
/* Actually iterate. */
|
||||
return IterateDirectoryRecursivelyInternal(fs, work_path, ent_buf, on_enter_dir, on_exit_dir, on_file);
|
||||
}
|
||||
|
||||
/* Helper for not specifying work path/entry buffer. */
|
||||
template<typename OnEnterDir, typename OnExitDir, typename OnFile>
|
||||
static Result IterateDirectoryRecursively(IFileSystem *fs, const FsPath &root_path, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) {
|
||||
FsDirectoryEntry dir_ent = {0};
|
||||
FsPath work_path = {0};
|
||||
return IterateDirectoryRecursively(fs, root_path, work_path, &dir_ent, on_enter_dir, on_exit_dir, on_file);
|
||||
}
|
||||
|
||||
/* Helper for iterating over the filesystem root. */
|
||||
template<typename OnEnterDir, typename OnExitDir, typename OnFile>
|
||||
static Result IterateDirectoryRecursively(IFileSystem *fs, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) {
|
||||
return IterateDirectoryRecursively(fs, FsPathUtils::RootPath, on_enter_dir, on_exit_dir, on_file);
|
||||
}
|
||||
|
||||
/* Copy API. */
|
||||
static Result CopyFile(IFileSystem *dst_fs, IFileSystem *src_fs, const FsPath &dst_parent_path, const FsPath &src_path, const FsDirectoryEntry *dir_ent, void *work_buf, size_t work_buf_size);
|
||||
static Result CopyFile(IFileSystem *fs, const FsPath &dst_parent_path, const FsPath &src_path, const FsDirectoryEntry *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);
|
||||
}
|
||||
|
||||
static Result CopyDirectoryRecursively(IFileSystem *dst_fs, IFileSystem *src_fs, const FsPath &dst_path, const FsPath &src_path, void *work_buf, size_t work_buf_size);
|
||||
static Result CopyDirectoryRecursively(IFileSystem *fs, const FsPath &dst_path, const FsPath &src_path, void *work_buf, size_t work_buf_size) {
|
||||
return CopyDirectoryRecursively(fs, fs, dst_path, src_path, work_buf, work_buf_size);
|
||||
}
|
||||
|
||||
/* Ensure directory existence. */
|
||||
static Result EnsureDirectoryExists(IFileSystem *fs, const FsPath &path);
|
||||
|
||||
/* Other Utility. */
|
||||
template<typename F>
|
||||
static Result RetryUntilTargetNotLocked(F f) {
|
||||
const size_t MaxRetries = 10;
|
||||
|
||||
for (size_t i = 0; i < MaxRetries; i++) {
|
||||
R_TRY_CATCH(f()) {
|
||||
R_CATCH(ResultFsTargetLocked) {
|
||||
/* If target is locked, wait 100ms and try again. */
|
||||
svcSleepThread(100'000'000ul);
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
};
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* 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 <cstring>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../utils.hpp"
|
||||
#include "fs_directory_redirection_filesystem.hpp"
|
||||
#include "fs_path_utils.hpp"
|
||||
|
||||
static char *GetNormalizedDirectory(const char *dir_prefix) {
|
||||
/* Normalize the path. */
|
||||
char normal_path[FS_MAX_PATH + 1];
|
||||
size_t normal_path_len;
|
||||
R_ASSERT(FsPathUtils::Normalize(normal_path, sizeof(normal_path), dir_prefix, &normal_path_len));
|
||||
|
||||
/* Ensure terminating '/' */
|
||||
if (normal_path[normal_path_len-1] != '/') {
|
||||
AMS_ASSERT(normal_path_len + 2 <= sizeof(normal_path));
|
||||
strncat(normal_path, "/", 2);
|
||||
normal_path[sizeof(normal_path)-1] = 0;
|
||||
normal_path_len++;
|
||||
}
|
||||
|
||||
char *output = static_cast<char *>(std::malloc(normal_path_len + 1));
|
||||
AMS_ASSERT(output != nullptr);
|
||||
|
||||
std::strncpy(output, normal_path, normal_path_len + 1);
|
||||
output[normal_path_len] = 0;
|
||||
return output;
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::Initialize(const char *before, const char *after) {
|
||||
if (strnlen(before, FS_MAX_PATH) >= FS_MAX_PATH || strnlen(after, FS_MAX_PATH) >= FS_MAX_PATH) {
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
this->before_dir = GetNormalizedDirectory(before);
|
||||
this->after_dir = GetNormalizedDirectory(after);
|
||||
this->before_dir_len = strlen(this->before_dir);
|
||||
this->after_dir_len = strlen(this->after_dir);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::GetFullPath(char *out, size_t out_size, const char *relative_path) {
|
||||
FsPath tmp_rel_path;
|
||||
R_TRY(FsPathUtils::Normalize(tmp_rel_path.str, sizeof(tmp_rel_path), relative_path, nullptr));
|
||||
|
||||
if (std::strncmp(tmp_rel_path.str, this->before_dir, this->before_dir_len) == 0) {
|
||||
if (this->after_dir_len + strnlen(tmp_rel_path.str, FS_MAX_PATH) - this->before_dir_len > out_size) {
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
/* Copy after path. */
|
||||
std::strncpy(out, this->after_dir, out_size);
|
||||
out[out_size - 1] = 0;
|
||||
|
||||
/* Normalize it. */
|
||||
return FsPathUtils::Normalize(out + this->after_dir_len - 1, out_size - (this->after_dir_len - 1), relative_path + this->before_dir_len - 1, nullptr);
|
||||
} else if (std::memcmp(tmp_rel_path.str, this->before_dir, this->before_dir_len - 1) == 0) {
|
||||
/* Handling raw directory. */
|
||||
if (this->after_dir_len + 1 > out_size) {
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
std::strncpy(out, this->after_dir, out_size);
|
||||
out[out_size - 1] = 0;
|
||||
return ResultSuccess();
|
||||
} else {
|
||||
return FsPathUtils::Normalize(out, out_size, relative_path, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::CreateFileImpl(const FsPath &path, uint64_t size, int flags) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
return this->base_fs->CreateFile(full_path, size, flags);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::DeleteFileImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
return this->base_fs->DeleteFile(full_path);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::CreateDirectoryImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
return this->base_fs->CreateDirectory(full_path);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::DeleteDirectoryImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
return this->base_fs->DeleteDirectory(full_path);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::DeleteDirectoryRecursivelyImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
return this->base_fs->DeleteDirectoryRecursively(full_path);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::RenameFileImpl(const FsPath &old_path, const FsPath &new_path) {
|
||||
FsPath full_old_path, full_new_path;
|
||||
R_TRY(GetFullPath(full_old_path, old_path));
|
||||
R_TRY(GetFullPath(full_new_path, new_path));
|
||||
return this->base_fs->RenameFile(full_old_path, full_new_path);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) {
|
||||
FsPath full_old_path, full_new_path;
|
||||
R_TRY(GetFullPath(full_old_path, old_path));
|
||||
R_TRY(GetFullPath(full_new_path, new_path));
|
||||
return this->base_fs->RenameDirectory(full_old_path, full_new_path);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
return this->base_fs->GetEntryType(out, full_path);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
return this->base_fs->OpenFile(out_file, full_path, mode);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
return this->base_fs->OpenDirectory(out_dir, full_path, mode);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::CommitImpl() {
|
||||
return this->base_fs->Commit();
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
return this->base_fs->GetFreeSpaceSize(out, full_path);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
return this->base_fs->GetTotalSpaceSize(out, full_path);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::CleanDirectoryRecursivelyImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
return this->base_fs->CleanDirectoryRecursively(full_path);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
return this->base_fs->GetFileTimeStampRaw(out, full_path);
|
||||
}
|
||||
|
||||
Result DirectoryRedirectionFileSystem::QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
return this->base_fs->QueryEntry(out, out_size, in, in_size, query, full_path);
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fs_ifilesystem.hpp"
|
||||
#include "fs_path_utils.hpp"
|
||||
|
||||
class DirectoryRedirectionFileSystem : public IFileSystem {
|
||||
private:
|
||||
std::shared_ptr<IFileSystem> base_fs;
|
||||
char *before_dir = nullptr;
|
||||
size_t before_dir_len = 0;
|
||||
char *after_dir = nullptr;
|
||||
size_t after_dir_len = 0;
|
||||
|
||||
public:
|
||||
DirectoryRedirectionFileSystem(IFileSystem *fs, const char *before, const char *after) : base_fs(fs) {
|
||||
R_ASSERT(this->Initialize(before, after));
|
||||
}
|
||||
|
||||
DirectoryRedirectionFileSystem(std::shared_ptr<IFileSystem> fs, const char *before, const char *after) : base_fs(fs) {
|
||||
R_ASSERT(this->Initialize(before, after));
|
||||
}
|
||||
|
||||
|
||||
virtual ~DirectoryRedirectionFileSystem() {
|
||||
if (this->before_dir != nullptr) {
|
||||
free(this->before_dir);
|
||||
}
|
||||
if (this->after_dir != nullptr) {
|
||||
free(this->after_dir);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Result Initialize(const char *before, const char *after);
|
||||
protected:
|
||||
Result GetFullPath(char *out, size_t out_size, const char *relative_path);
|
||||
Result GetFullPath(FsPath &full_path, const FsPath &relative_path) {
|
||||
return GetFullPath(full_path.str, sizeof(full_path.str), relative_path.str);
|
||||
}
|
||||
|
||||
public:
|
||||
virtual Result CreateFileImpl(const FsPath &path, uint64_t size, int flags) override;
|
||||
virtual Result DeleteFileImpl(const FsPath &path) override;
|
||||
virtual Result CreateDirectoryImpl(const FsPath &path) override;
|
||||
virtual Result DeleteDirectoryImpl(const FsPath &path) override;
|
||||
virtual Result DeleteDirectoryRecursivelyImpl(const FsPath &path) override;
|
||||
virtual Result RenameFileImpl(const FsPath &old_path, const FsPath &new_path) override;
|
||||
virtual Result RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) override;
|
||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) override;
|
||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) override;
|
||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) override;
|
||||
virtual Result CommitImpl() override;
|
||||
virtual Result GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) override;
|
||||
virtual Result GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) override;
|
||||
virtual Result CleanDirectoryRecursivelyImpl(const FsPath &path) override;
|
||||
virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) override;
|
||||
virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) override;
|
||||
};
|
||||
@@ -1,335 +0,0 @@
|
||||
/*
|
||||
* 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 <cstring>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../utils.hpp"
|
||||
#include "fs_directory_savedata_filesystem.hpp"
|
||||
#include "fs_dir_utils.hpp"
|
||||
|
||||
class DirectorySaveDataFile : public IFile {
|
||||
private:
|
||||
std::unique_ptr<IFile> base_file;
|
||||
DirectorySaveDataFileSystem *parent_fs; /* TODO: shared_ptr + enabled_shared_from_this? */
|
||||
int open_mode;
|
||||
public:
|
||||
DirectorySaveDataFile(std::unique_ptr<IFile> f, DirectorySaveDataFileSystem *p, int m) : base_file(std::move(f)), parent_fs(p), open_mode(m) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
virtual ~DirectorySaveDataFile() {
|
||||
/* Observe closing of writable file. */
|
||||
if (this->open_mode & OpenMode_Write) {
|
||||
this->parent_fs->OnWritableFileClose();
|
||||
}
|
||||
}
|
||||
public:
|
||||
virtual Result ReadImpl(u64 *out, u64 offset, void *buffer, u64 size) override {
|
||||
return this->base_file->Read(out, offset, buffer, size);
|
||||
}
|
||||
virtual Result GetSizeImpl(u64 *out) override {
|
||||
return this->base_file->GetSize(out);
|
||||
}
|
||||
virtual Result FlushImpl() override {
|
||||
return this->base_file->Flush();
|
||||
}
|
||||
virtual Result WriteImpl(u64 offset, void *buffer, u64 size, u32 option) override {
|
||||
return this->base_file->Write(offset, buffer, size, option);
|
||||
}
|
||||
virtual Result SetSizeImpl(u64 size) override {
|
||||
return this->base_file->SetSize(size);
|
||||
}
|
||||
virtual Result OperateRangeImpl(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
|
||||
return this->base_file->OperateRange(operation_type, offset, size, out_range_info);
|
||||
}
|
||||
};
|
||||
|
||||
/* ================================================================================================ */
|
||||
|
||||
Result DirectorySaveDataFileSystem::Initialize() {
|
||||
DirectoryEntryType ent_type;
|
||||
|
||||
/* Check that the working directory exists. */
|
||||
R_TRY_CATCH(this->fs->GetEntryType(&ent_type, WorkingDirectoryPath)) {
|
||||
/* If path isn't found, create working directory and committed directory. */
|
||||
R_CATCH(ResultFsPathNotFound) {
|
||||
R_TRY(this->fs->CreateDirectory(WorkingDirectoryPath));
|
||||
R_TRY(this->fs->CreateDirectory(CommittedDirectoryPath));
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* Now check for the committed directory. */
|
||||
R_TRY_CATCH(this->fs->GetEntryType(&ent_type, CommittedDirectoryPath)) {
|
||||
/* Committed doesn't exist, so synchronize and rename. */
|
||||
R_CATCH(ResultFsPathNotFound) {
|
||||
R_TRY(this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath));
|
||||
return this->fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath);
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* If committed exists, synchronize it to the working directory. */
|
||||
return this->SynchronizeDirectory(WorkingDirectoryPath, CommittedDirectoryPath);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::SynchronizeDirectory(const FsPath &dst_dir, const FsPath &src_dir) {
|
||||
/* Delete destination dir and recreate it. */
|
||||
R_TRY_CATCH(this->fs->DeleteDirectoryRecursively(dst_dir)) {
|
||||
R_CATCH(ResultFsPathNotFound) {
|
||||
/* Nintendo returns error unconditionally, but I think that's a bug in their code. */
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
R_TRY(this->fs->CreateDirectory(dst_dir));
|
||||
|
||||
/* Get a buffer to work with. */
|
||||
void *work_buf = nullptr;
|
||||
size_t work_buf_size = 0;
|
||||
R_TRY(this->AllocateWorkBuffer(&work_buf, &work_buf_size, IdealWorkBuffersize));
|
||||
ON_SCOPE_EXIT { free(work_buf); };
|
||||
|
||||
return FsDirUtils::CopyDirectoryRecursively(this->fs, dst_dir, src_dir, work_buf, work_buf_size);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::AllocateWorkBuffer(void **out_buf, size_t *out_size, const size_t ideal_size) {
|
||||
size_t try_size = ideal_size;
|
||||
|
||||
/* Repeatedly try to allocate until success. */
|
||||
while (true) {
|
||||
void *buf = malloc(try_size);
|
||||
if (buf != nullptr) {
|
||||
*out_buf = buf;
|
||||
*out_size = try_size;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Divide size by two. */
|
||||
try_size >>= 1;
|
||||
AMS_ASSERT(try_size > 0x200);
|
||||
}
|
||||
|
||||
/* TODO: Return a result here? Nintendo does not, but they have other allocation failed results. */
|
||||
/* Consider returning ResultFsAllocationFailureInDirectorySaveDataFileSystem? */
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::GetFullPath(char *out, size_t out_size, const char *relative_path) {
|
||||
/* Validate path. */
|
||||
if (1 + strnlen(relative_path, FS_MAX_PATH) >= out_size) {
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
if (relative_path[0] != '/') {
|
||||
return ResultFsInvalidPath;
|
||||
}
|
||||
|
||||
/* Copy working directory path. */
|
||||
std::strncpy(out, WorkingDirectoryPath.str, out_size);
|
||||
out[out_size-1] = 0;
|
||||
|
||||
/* Normalize it. */
|
||||
constexpr size_t working_len = WorkingDirectoryPathLen - 1;
|
||||
return FsPathUtils::Normalize(out + working_len, out_size - working_len, relative_path, nullptr);
|
||||
}
|
||||
|
||||
void DirectorySaveDataFileSystem::OnWritableFileClose() {
|
||||
std::scoped_lock lk(this->lock);
|
||||
this->open_writable_files--;
|
||||
|
||||
/* TODO: Abort if < 0? N does not. */
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::CopySaveFromProxy() {
|
||||
if (this->proxy_save_fs != nullptr) {
|
||||
/* Get a buffer to work with. */
|
||||
void *work_buf = nullptr;
|
||||
size_t work_buf_size = 0;
|
||||
R_TRY(this->AllocateWorkBuffer(&work_buf, &work_buf_size, IdealWorkBuffersize));
|
||||
ON_SCOPE_EXIT { free(work_buf); };
|
||||
|
||||
R_TRY(FsDirUtils::CopyDirectoryRecursively(this, this->proxy_save_fs.get(), FsPathUtils::RootPath, FsPathUtils::RootPath, work_buf, work_buf_size));
|
||||
return this->Commit();
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* ================================================================================================ */
|
||||
|
||||
Result DirectorySaveDataFileSystem::CreateFileImpl(const FsPath &path, uint64_t size, int flags) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
std::scoped_lock lk(this->lock);
|
||||
return this->fs->CreateFile(full_path, size, flags);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::DeleteFileImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
std::scoped_lock lk(this->lock);
|
||||
return this->fs->DeleteFile(full_path);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::CreateDirectoryImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
std::scoped_lock lk(this->lock);
|
||||
return this->fs->CreateDirectory(full_path);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::DeleteDirectoryImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
std::scoped_lock lk(this->lock);
|
||||
return this->fs->DeleteDirectory(full_path);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::DeleteDirectoryRecursivelyImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
std::scoped_lock lk(this->lock);
|
||||
return this->fs->DeleteDirectoryRecursively(full_path);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::RenameFileImpl(const FsPath &old_path, const FsPath &new_path) {
|
||||
FsPath full_old_path, full_new_path;
|
||||
R_TRY(GetFullPath(full_old_path, old_path));
|
||||
R_TRY(GetFullPath(full_new_path, new_path));
|
||||
|
||||
std::scoped_lock lk(this->lock);
|
||||
return this->fs->RenameFile(full_old_path, full_new_path);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) {
|
||||
FsPath full_old_path, full_new_path;
|
||||
R_TRY(GetFullPath(full_old_path, old_path));
|
||||
R_TRY(GetFullPath(full_new_path, new_path));
|
||||
|
||||
std::scoped_lock lk(this->lock);
|
||||
return this->fs->RenameDirectory(full_old_path, full_new_path);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
std::scoped_lock lk(this->lock);
|
||||
return this->fs->GetEntryType(out, full_path);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
std::scoped_lock lk(this->lock);
|
||||
|
||||
{
|
||||
/* Open the raw file. */
|
||||
std::unique_ptr<IFile> file;
|
||||
R_TRY(this->fs->OpenFile(file, full_path, mode));
|
||||
|
||||
/* Create DirectorySaveDataFile wrapper. */
|
||||
out_file = std::make_unique<DirectorySaveDataFile>(std::move(file), this, mode);
|
||||
}
|
||||
|
||||
/* Check for allocation failure. */
|
||||
if (out_file == nullptr) {
|
||||
return ResultFsAllocationFailureInDirectorySaveDataFileSystem;
|
||||
}
|
||||
|
||||
/* Increment open writable files, if needed. */
|
||||
if (mode & OpenMode_Write) {
|
||||
this->open_writable_files++;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
std::scoped_lock lk(this->lock);
|
||||
return this->fs->OpenDirectory(out_dir, full_path, mode);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::CommitImpl() {
|
||||
/* Here, Nintendo does the following (with retries): */
|
||||
/* - Rename Committed -> Synchronizing. */
|
||||
/* - Synchronize Working -> Synchronizing (deleting Synchronizing). */
|
||||
/* - Rename Synchronizing -> Committed. */
|
||||
/* I think this is not the optimal order to do things, as the previous committed directory */
|
||||
/* will be deleted if there is an error during synchronization. */
|
||||
/* Instead, we will synchronize first, then delete committed, then rename. */
|
||||
|
||||
std::scoped_lock lk(this->lock);
|
||||
|
||||
/* Ensure we don't have any open writable files. */
|
||||
if (this->open_writable_files != 0) {
|
||||
return ResultFsPreconditionViolation;
|
||||
}
|
||||
|
||||
const auto SynchronizeWorkingDir = [&]() { return this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath); };
|
||||
const auto DeleteCommittedDir = [&]() { return this->fs->DeleteDirectoryRecursively(CommittedDirectoryPath); };
|
||||
const auto RenameSynchDir = [&]() { return this->fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath); };
|
||||
|
||||
/* Synchronize working directory. */
|
||||
R_TRY(FsDirUtils::RetryUntilTargetNotLocked(std::move(SynchronizeWorkingDir)));
|
||||
|
||||
/* Delete committed directory. */
|
||||
R_TRY_CATCH(FsDirUtils::RetryUntilTargetNotLocked(std::move(DeleteCommittedDir))) {
|
||||
R_CATCH(ResultFsPathNotFound) {
|
||||
/* It is okay for us to not have a committed directory here. */
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* Rename synchronizing directory to committed directory. */
|
||||
R_TRY(FsDirUtils::RetryUntilTargetNotLocked(std::move(RenameSynchDir)));
|
||||
|
||||
/* TODO: Should I call this->fs->Commit()? Nintendo does not. */
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
||||
/* TODO: How should this work? N returns ResultFsNotImplemented. */
|
||||
return ResultFsNotImplemented;
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
||||
/* TODO: How should this work? N returns ResultFsNotImplemented. */
|
||||
return ResultFsNotImplemented;
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::CleanDirectoryRecursivelyImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
std::scoped_lock lk(this->lock);
|
||||
return this->fs->CleanDirectoryRecursively(full_path);
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) {
|
||||
/* TODO: How should this work? N returns ResultFsNotImplemented. */
|
||||
return ResultFsNotImplemented;
|
||||
}
|
||||
|
||||
Result DirectorySaveDataFileSystem::QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) {
|
||||
/* TODO: How should this work? N returns ResultFsNotImplemented. */
|
||||
return ResultFsNotImplemented;
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fs_ifilesystem.hpp"
|
||||
#include "fs_path_utils.hpp"
|
||||
|
||||
class DirectorySaveDataFileSystem : public IFileSystem {
|
||||
private:
|
||||
static constexpr FsPath CommittedDirectoryPath = EncodeConstantFsPath("/0/");
|
||||
static constexpr FsPath WorkingDirectoryPath = EncodeConstantFsPath("/1/");
|
||||
static constexpr FsPath SynchronizingDirectoryPath = EncodeConstantFsPath("/_/");
|
||||
|
||||
static constexpr size_t CommittedDirectoryPathLen = GetConstantFsPathLen(CommittedDirectoryPath);
|
||||
static constexpr size_t WorkingDirectoryPathLen = GetConstantFsPathLen(WorkingDirectoryPath);
|
||||
static constexpr size_t SynchronizingDirectoryPathLen = GetConstantFsPathLen(SynchronizingDirectoryPath);
|
||||
|
||||
static constexpr size_t IdealWorkBuffersize = 0x100000; /* 1 MB */
|
||||
private:
|
||||
std::shared_ptr<IFileSystem> shared_fs;
|
||||
std::unique_ptr<IFileSystem> unique_fs;
|
||||
std::unique_ptr<IFileSystem> proxy_save_fs;
|
||||
IFileSystem *fs;
|
||||
ams::os::Mutex lock;
|
||||
size_t open_writable_files = 0;
|
||||
|
||||
public:
|
||||
DirectorySaveDataFileSystem(IFileSystem *fs, std::unique_ptr<IFileSystem> pfs) : unique_fs(fs), proxy_save_fs(std::move(pfs)) {
|
||||
this->fs = this->unique_fs.get();
|
||||
R_ASSERT(this->Initialize());
|
||||
}
|
||||
|
||||
DirectorySaveDataFileSystem(std::unique_ptr<IFileSystem> fs, std::unique_ptr<IFileSystem> pfs) : unique_fs(std::move(fs)), proxy_save_fs(std::move(pfs)) {
|
||||
this->fs = this->unique_fs.get();
|
||||
R_ASSERT(this->Initialize());
|
||||
}
|
||||
|
||||
DirectorySaveDataFileSystem(std::shared_ptr<IFileSystem> fs, std::unique_ptr<IFileSystem> pfs) : shared_fs(fs), proxy_save_fs(std::move(pfs)) {
|
||||
this->fs = this->shared_fs.get();
|
||||
R_ASSERT(this->Initialize());
|
||||
}
|
||||
|
||||
virtual ~DirectorySaveDataFileSystem() { }
|
||||
|
||||
private:
|
||||
Result Initialize();
|
||||
protected:
|
||||
Result SynchronizeDirectory(const FsPath &dst_dir, const FsPath &src_dir);
|
||||
Result AllocateWorkBuffer(void **out_buf, size_t *out_size, const size_t ideal_size);
|
||||
|
||||
Result GetFullPath(char *out, size_t out_size, const char *relative_path);
|
||||
Result GetFullPath(FsPath &full_path, const FsPath &relative_path) {
|
||||
return GetFullPath(full_path.str, sizeof(full_path.str), relative_path.str);
|
||||
}
|
||||
public:
|
||||
void OnWritableFileClose();
|
||||
Result CopySaveFromProxy();
|
||||
public:
|
||||
virtual Result CreateFileImpl(const FsPath &path, uint64_t size, int flags) override;
|
||||
virtual Result DeleteFileImpl(const FsPath &path) override;
|
||||
virtual Result CreateDirectoryImpl(const FsPath &path) override;
|
||||
virtual Result DeleteDirectoryImpl(const FsPath &path) override;
|
||||
virtual Result DeleteDirectoryRecursivelyImpl(const FsPath &path) override;
|
||||
virtual Result RenameFileImpl(const FsPath &old_path, const FsPath &new_path) override;
|
||||
virtual Result RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) override;
|
||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) override;
|
||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) override;
|
||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) override;
|
||||
virtual Result CommitImpl() override;
|
||||
virtual Result GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) override;
|
||||
virtual Result GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) override;
|
||||
virtual Result CleanDirectoryRecursivelyImpl(const FsPath &path) override;
|
||||
virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) override;
|
||||
virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) override;
|
||||
};
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* 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 <cstring>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../utils.hpp"
|
||||
#include "fs_file_storage.hpp"
|
||||
|
||||
Result FileStorage::UpdateSize() {
|
||||
if (this->size == InvalidSize) {
|
||||
return this->file->GetSize(&this->size);
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FileStorage::Read(void *buffer, size_t size, u64 offset) {
|
||||
u64 read_size;
|
||||
|
||||
if (size == 0) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
if (buffer == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
R_TRY(this->UpdateSize());
|
||||
if (!IStorage::IsRangeValid(offset, size, this->size)) {
|
||||
return ResultFsOutOfRange;
|
||||
}
|
||||
|
||||
/* Nintendo doesn't do check output read size, but we will for safety. */
|
||||
R_TRY(this->file->Read(&read_size, offset, buffer, size));
|
||||
if (read_size != size && read_size) {
|
||||
return this->Read(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(buffer) + read_size), size - read_size, offset + read_size);
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FileStorage::Write(void *buffer, size_t size, u64 offset) {
|
||||
if (size == 0) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
if (buffer == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
R_TRY(this->UpdateSize());
|
||||
if (!IStorage::IsRangeValid(offset, size, this->size)) {
|
||||
return ResultFsOutOfRange;
|
||||
}
|
||||
|
||||
return this->file->Write(offset, buffer, size);
|
||||
}
|
||||
|
||||
Result FileStorage::Flush() {
|
||||
return this->file->Flush();
|
||||
}
|
||||
|
||||
Result FileStorage::GetSize(u64 *out_size) {
|
||||
R_TRY(this->UpdateSize());
|
||||
*out_size = this->size;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FileStorage::SetSize(u64 size) {
|
||||
this->size = InvalidSize;
|
||||
return this->file->SetSize(size);
|
||||
}
|
||||
|
||||
Result FileStorage::OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) {
|
||||
switch (operation_type) {
|
||||
case FsOperationId_InvalidateCache:
|
||||
case FsOperationId_QueryRange:
|
||||
if (size == 0) {
|
||||
if (operation_type == FsOperationId_QueryRange) {
|
||||
if (out_range_info == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
/* N checks for size == sizeof(*out_range_info) here, but that's because their wrapper api is bad. */
|
||||
std::memset(out_range_info, 0, sizeof(*out_range_info));
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
R_TRY(this->UpdateSize());
|
||||
/* N checks for positivity + signed overflow on offset/size here, but we're using unsigned types... */
|
||||
return this->file->OperateRange(operation_type, offset, size, out_range_info);
|
||||
default:
|
||||
return ResultFsUnsupportedOperation;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_shim.h"
|
||||
|
||||
#include "../debug.hpp"
|
||||
#include "fs_istorage.hpp"
|
||||
#include "fs_ifile.hpp"
|
||||
|
||||
class FileStorage : public IStorage {
|
||||
public:
|
||||
static constexpr u64 InvalidSize = UINT64_MAX;
|
||||
private:
|
||||
std::shared_ptr<IFile> base_file;
|
||||
IFile *file;
|
||||
u64 size;
|
||||
public:
|
||||
FileStorage(IFile *f) : base_file(f) {
|
||||
this->file = this->base_file.get();
|
||||
this->size = InvalidSize;
|
||||
};
|
||||
FileStorage(std::shared_ptr<IFile> f) : base_file(f) {
|
||||
this->file = this->base_file.get();
|
||||
this->size = InvalidSize;
|
||||
};
|
||||
virtual ~FileStorage() {
|
||||
/* ... */
|
||||
};
|
||||
protected:
|
||||
Result UpdateSize();
|
||||
public:
|
||||
virtual Result Read(void *buffer, size_t size, u64 offset) override;
|
||||
virtual Result Write(void *buffer, size_t size, u64 offset) override;
|
||||
virtual Result Flush() override;
|
||||
virtual Result GetSize(u64 *out_size) override;
|
||||
virtual Result SetSize(u64 size) override;
|
||||
virtual Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override;
|
||||
};
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
class IDirectory {
|
||||
public:
|
||||
virtual ~IDirectory() {}
|
||||
|
||||
Result Read(uint64_t *out_count, FsDirectoryEntry *out_entries, uint64_t max_entries) {
|
||||
if (out_count == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
if (max_entries == 0) {
|
||||
*out_count = 0;
|
||||
return ResultSuccess();
|
||||
}
|
||||
if (out_entries == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return ReadImpl(out_count, out_entries, max_entries);
|
||||
}
|
||||
|
||||
Result GetEntryCount(uint64_t *count) {
|
||||
if (count == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return GetEntryCountImpl(count);
|
||||
}
|
||||
|
||||
protected:
|
||||
/* ...? */
|
||||
private:
|
||||
virtual Result ReadImpl(uint64_t *out_count, FsDirectoryEntry *out_entries, uint64_t max_entries) = 0;
|
||||
virtual Result GetEntryCountImpl(uint64_t *count) = 0;
|
||||
};
|
||||
|
||||
class IDirectoryInterface : public IServiceObject {
|
||||
private:
|
||||
enum class CommandId {
|
||||
Read = 0,
|
||||
GetEntryCount = 1,
|
||||
};
|
||||
private:
|
||||
std::unique_ptr<IDirectory> base_dir;
|
||||
public:
|
||||
IDirectoryInterface(IDirectory *d) : base_dir(d) {
|
||||
/* ... */
|
||||
};
|
||||
IDirectoryInterface(std::unique_ptr<IDirectory> d) : base_dir(std::move(d)) {
|
||||
/* ... */
|
||||
};
|
||||
|
||||
private:
|
||||
/* Actual command API. */
|
||||
virtual Result Read(OutBuffer<FsDirectoryEntry> buffer, Out<u64> out_count) final {
|
||||
return this->base_dir->Read(out_count.GetPointer(), buffer.buffer, buffer.num_elements);
|
||||
};
|
||||
virtual Result GetEntryCount(Out<u64> out_count) final {
|
||||
return this->base_dir->GetEntryCount(out_count.GetPointer());
|
||||
};
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
/* 1.0.0- */
|
||||
MAKE_SERVICE_COMMAND_META(IDirectoryInterface, Read),
|
||||
MAKE_SERVICE_COMMAND_META(IDirectoryInterface, GetEntryCount),
|
||||
};
|
||||
};
|
||||
|
||||
class ProxyDirectory : public IDirectory {
|
||||
private:
|
||||
std::unique_ptr<FsDir> base_dir;
|
||||
public:
|
||||
ProxyDirectory(FsDir *d) : base_dir(d) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ProxyDirectory(std::unique_ptr<FsDir> d) : base_dir(std::move(d)) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ProxyDirectory(FsDir d) {
|
||||
this->base_dir = std::make_unique<FsDir>(d);
|
||||
}
|
||||
|
||||
virtual ~ProxyDirectory() {
|
||||
fsDirClose(this->base_dir.get());
|
||||
}
|
||||
public:
|
||||
virtual Result ReadImpl(uint64_t *out_count, FsDirectoryEntry *out_entries, uint64_t max_entries) {
|
||||
size_t count;
|
||||
R_TRY(fsDirRead(this->base_dir.get(), 0, &count, max_entries, out_entries));
|
||||
|
||||
*out_count = count;
|
||||
return ResultSuccess();
|
||||
}
|
||||
virtual Result GetEntryCountImpl(uint64_t *count) {
|
||||
return fsDirGetEntryCount(this->base_dir.get(), count);
|
||||
}
|
||||
};
|
||||
@@ -1,195 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fs_shim.h"
|
||||
|
||||
class IFile {
|
||||
public:
|
||||
virtual ~IFile() {}
|
||||
|
||||
Result Read(uint64_t *out, uint64_t offset, void *buffer, uint64_t size, uint32_t flags) {
|
||||
(void)(flags);
|
||||
if (out == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
if (size == 0) {
|
||||
*out = 0;
|
||||
return ResultSuccess();
|
||||
}
|
||||
if (buffer == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return ReadImpl(out, offset, buffer, size);
|
||||
}
|
||||
|
||||
Result Read(uint64_t *out, uint64_t offset, void *buffer, uint64_t size) {
|
||||
return Read(out, offset, buffer, size, 0);
|
||||
}
|
||||
|
||||
Result GetSize(uint64_t *out) {
|
||||
if (out == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return GetSizeImpl(out);
|
||||
}
|
||||
|
||||
Result Flush() {
|
||||
return FlushImpl();
|
||||
}
|
||||
|
||||
Result Write(uint64_t offset, void *buffer, uint64_t size, uint32_t flags) {
|
||||
if (size == 0) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
if (buffer == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return WriteImpl(offset, buffer, size, flags);
|
||||
}
|
||||
|
||||
Result Write(uint64_t offset, void *buffer, uint64_t size, bool flush = false) {
|
||||
if (size == 0) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
if (buffer == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return WriteImpl(offset, buffer, size, flush);
|
||||
}
|
||||
|
||||
Result SetSize(uint64_t size) {
|
||||
return SetSizeImpl(size);
|
||||
}
|
||||
|
||||
Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) {
|
||||
if (operation_type == FsOperationId_QueryRange) {
|
||||
return OperateRangeImpl(operation_type, offset, size, out_range_info);
|
||||
}
|
||||
return ResultFsUnsupportedOperation;
|
||||
}
|
||||
|
||||
protected:
|
||||
/* ...? */
|
||||
private:
|
||||
virtual Result ReadImpl(u64 *out, u64 offset, void *buffer, u64 size) = 0;
|
||||
virtual Result GetSizeImpl(u64 *out) = 0;
|
||||
virtual Result FlushImpl() = 0;
|
||||
virtual Result WriteImpl(u64 offset, void *buffer, u64 size, u32 option) = 0;
|
||||
virtual Result SetSizeImpl(u64 size) = 0;
|
||||
virtual Result OperateRangeImpl(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0;
|
||||
};
|
||||
|
||||
class IFileInterface : public IServiceObject {
|
||||
private:
|
||||
enum class CommandId {
|
||||
Read = 0,
|
||||
Write = 1,
|
||||
Flush = 2,
|
||||
SetSize = 3,
|
||||
GetSize = 4,
|
||||
OperateRange = 5,
|
||||
};
|
||||
private:
|
||||
std::unique_ptr<IFile> base_file;
|
||||
public:
|
||||
IFileInterface(IFile *f) : base_file(f) {
|
||||
/* ... */
|
||||
};
|
||||
IFileInterface(std::unique_ptr<IFile> f) : base_file(std::move(f)) {
|
||||
/* ... */
|
||||
};
|
||||
|
||||
private:
|
||||
/* Actual command API. */
|
||||
virtual Result Read(OutBuffer<u8, BufferType_Type1> buffer, Out<u64> out_read, u64 offset, u64 size, u32 read_flags) final {
|
||||
return this->base_file->Read(out_read.GetPointer(), offset, buffer.buffer, std::min(buffer.num_elements, size), read_flags);
|
||||
};
|
||||
virtual Result Write(InBuffer<u8, BufferType_Type1> buffer, u64 offset, u64 size, u32 write_flags) final {
|
||||
return this->base_file->Write(offset, buffer.buffer, std::min(buffer.num_elements, size), write_flags);
|
||||
};
|
||||
virtual Result Flush() final {
|
||||
return this->base_file->Flush();
|
||||
};
|
||||
virtual Result SetSize(u64 size) final {
|
||||
return this->base_file->SetSize(size);
|
||||
};
|
||||
virtual Result GetSize(Out<u64> size) final {
|
||||
return this->base_file->GetSize(size.GetPointer());
|
||||
};
|
||||
virtual Result OperateRange(Out<FsRangeInfo> range_info, u32 operation_type, u64 offset, u64 size) final {
|
||||
return this->base_file->OperateRange(static_cast<FsOperationId>(operation_type), offset, size, range_info.GetPointer());
|
||||
};
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
/* 1.0.0- */
|
||||
MAKE_SERVICE_COMMAND_META(IFileInterface, Read),
|
||||
MAKE_SERVICE_COMMAND_META(IFileInterface, Write),
|
||||
MAKE_SERVICE_COMMAND_META(IFileInterface, Flush),
|
||||
MAKE_SERVICE_COMMAND_META(IFileInterface, SetSize),
|
||||
MAKE_SERVICE_COMMAND_META(IFileInterface, GetSize),
|
||||
|
||||
/* 4.0.0- */
|
||||
MAKE_SERVICE_COMMAND_META(IFileInterface, OperateRange, FirmwareVersion_400),
|
||||
};
|
||||
};
|
||||
|
||||
class ProxyFile : public IFile {
|
||||
private:
|
||||
std::unique_ptr<FsFile> base_file;
|
||||
public:
|
||||
ProxyFile(FsFile *f) : base_file(f) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ProxyFile(std::unique_ptr<FsFile> f) : base_file(std::move(f)) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ProxyFile(FsFile f) {
|
||||
this->base_file = std::make_unique<FsFile>(f);
|
||||
}
|
||||
|
||||
virtual ~ProxyFile() {
|
||||
fsFileClose(this->base_file.get());
|
||||
}
|
||||
public:
|
||||
virtual Result ReadImpl(u64 *out, u64 offset, void *buffer, u64 size) override {
|
||||
size_t out_sz;
|
||||
R_TRY(fsFileRead(this->base_file.get(), offset, buffer, size, FS_READOPTION_NONE, &out_sz));
|
||||
|
||||
*out = out_sz;
|
||||
return ResultSuccess();
|
||||
}
|
||||
virtual Result GetSizeImpl(u64 *out) override {
|
||||
return fsFileGetSize(this->base_file.get(), out);
|
||||
}
|
||||
virtual Result FlushImpl() override {
|
||||
return fsFileFlush(this->base_file.get());
|
||||
}
|
||||
virtual Result WriteImpl(u64 offset, void *buffer, u64 size, u32 option) override {
|
||||
return fsFileWrite(this->base_file.get(), offset, buffer, size, option);
|
||||
}
|
||||
virtual Result SetSizeImpl(u64 size) override {
|
||||
return fsFileSetSize(this->base_file.get(), size);
|
||||
}
|
||||
virtual Result OperateRangeImpl(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
|
||||
return fsFileOperateRange(this->base_file.get(), operation_type, offset, size, out_range_info);
|
||||
}
|
||||
};
|
||||
@@ -1,463 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../utils.hpp"
|
||||
|
||||
#include "fs_filesystem_types.hpp"
|
||||
#include "fs_path_utils.hpp"
|
||||
|
||||
#include "fs_ifile.hpp"
|
||||
#include "fs_idirectory.hpp"
|
||||
|
||||
class IFile;
|
||||
class IDirectory;
|
||||
|
||||
class IFileSystem {
|
||||
public:
|
||||
virtual ~IFileSystem() {}
|
||||
|
||||
Result CreateFile(const FsPath &path, uint64_t size, int flags) {
|
||||
return CreateFileImpl(path, size, flags);
|
||||
}
|
||||
|
||||
Result CreateFile(const FsPath &path, uint64_t size) {
|
||||
return CreateFileImpl(path, size, 0);
|
||||
}
|
||||
|
||||
Result DeleteFile(const FsPath &path) {
|
||||
return DeleteFileImpl(path);
|
||||
}
|
||||
|
||||
Result CreateDirectory(const FsPath &path) {
|
||||
return CreateDirectoryImpl(path);
|
||||
}
|
||||
|
||||
Result DeleteDirectory(const FsPath &path) {
|
||||
return DeleteDirectoryImpl(path);
|
||||
}
|
||||
|
||||
Result DeleteDirectoryRecursively(const FsPath &path) {
|
||||
return DeleteDirectoryRecursivelyImpl(path);
|
||||
}
|
||||
|
||||
Result RenameFile(const FsPath &old_path, const FsPath &new_path) {
|
||||
return RenameFileImpl(old_path, new_path);
|
||||
}
|
||||
|
||||
Result RenameDirectory(const FsPath &old_path, const FsPath &new_path) {
|
||||
return RenameDirectoryImpl(old_path, new_path);
|
||||
}
|
||||
|
||||
Result GetEntryType(DirectoryEntryType *out, const FsPath &path) {
|
||||
if (out == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return GetEntryTypeImpl(out, path);
|
||||
}
|
||||
|
||||
Result OpenFile(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) {
|
||||
if (!(mode & OpenMode_ReadWrite)) {
|
||||
return ResultFsInvalidArgument;
|
||||
}
|
||||
if (mode & ~OpenMode_All) {
|
||||
return ResultFsInvalidArgument;
|
||||
}
|
||||
return OpenFileImpl(out_file, path, mode);
|
||||
}
|
||||
|
||||
Result OpenDirectory(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) {
|
||||
if (!(mode & DirectoryOpenMode_All)) {
|
||||
return ResultFsInvalidArgument;
|
||||
}
|
||||
if (mode & ~DirectoryOpenMode_All) {
|
||||
return ResultFsInvalidArgument;
|
||||
}
|
||||
return OpenDirectoryImpl(out_dir, path, mode);
|
||||
}
|
||||
|
||||
Result Commit() {
|
||||
return CommitImpl();
|
||||
}
|
||||
|
||||
Result GetFreeSpaceSize(uint64_t *out, const FsPath &path) {
|
||||
if (out == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return GetFreeSpaceSizeImpl(out, path);
|
||||
}
|
||||
|
||||
Result GetTotalSpaceSize(uint64_t *out, const FsPath &path) {
|
||||
if (out == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return GetTotalSpaceSizeImpl(out, path);
|
||||
}
|
||||
|
||||
Result CleanDirectoryRecursively(const FsPath &path) {
|
||||
return CleanDirectoryRecursivelyImpl(path);
|
||||
}
|
||||
|
||||
Result GetFileTimeStampRaw(FsTimeStampRaw *out, const FsPath &path) {
|
||||
if (out == nullptr) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
return GetFileTimeStampRawImpl(out, path);
|
||||
}
|
||||
|
||||
Result QueryEntry(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) {
|
||||
return QueryEntryImpl(out, out_size, in, in_size, query, path);
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
/* ...? */
|
||||
private:
|
||||
virtual Result CreateFileImpl(const FsPath &path, uint64_t size, int flags) = 0;
|
||||
virtual Result DeleteFileImpl(const FsPath &path) = 0;
|
||||
virtual Result CreateDirectoryImpl(const FsPath &path) = 0;
|
||||
virtual Result DeleteDirectoryImpl(const FsPath &path) = 0;
|
||||
virtual Result DeleteDirectoryRecursivelyImpl(const FsPath &path) = 0;
|
||||
virtual Result RenameFileImpl(const FsPath &old_path, const FsPath &new_path) = 0;
|
||||
virtual Result RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) = 0;
|
||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) = 0;
|
||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) = 0;
|
||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) = 0;
|
||||
virtual Result CommitImpl() = 0;
|
||||
|
||||
virtual Result GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
||||
(void)(out);
|
||||
(void)(path);
|
||||
return ResultFsNotImplemented;
|
||||
}
|
||||
|
||||
virtual Result GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
||||
(void)(out);
|
||||
(void)(path);
|
||||
return ResultFsNotImplemented;
|
||||
}
|
||||
|
||||
virtual Result CleanDirectoryRecursivelyImpl(const FsPath &path) = 0;
|
||||
|
||||
virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) {
|
||||
(void)(out);
|
||||
(void)(path);
|
||||
return ResultFsNotImplemented;
|
||||
}
|
||||
|
||||
virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) {
|
||||
(void)(out);
|
||||
(void)(out_size);
|
||||
(void)(in);
|
||||
(void)(in_size);
|
||||
(void)(query);
|
||||
(void)(path);
|
||||
return ResultFsNotImplemented;
|
||||
}
|
||||
};
|
||||
|
||||
class IFileSystemInterface : public IServiceObject {
|
||||
private:
|
||||
enum class CommandId {
|
||||
/* 1.0.0+ */
|
||||
CreateFile = 0,
|
||||
DeleteFile = 1,
|
||||
CreateDirectory = 2,
|
||||
DeleteDirectory = 3,
|
||||
DeleteDirectoryRecursively = 4,
|
||||
RenameFile = 5,
|
||||
RenameDirectory = 6,
|
||||
GetEntryType = 7,
|
||||
OpenFile = 8,
|
||||
OpenDirectory = 9,
|
||||
Commit = 10,
|
||||
GetFreeSpaceSize = 11,
|
||||
GetTotalSpaceSize = 12,
|
||||
|
||||
/* 3.0.0+ */
|
||||
CleanDirectoryRecursively = 13,
|
||||
GetFileTimeStampRaw = 14,
|
||||
|
||||
/* 4.0.0+ */
|
||||
QueryEntry = 15,
|
||||
};
|
||||
private:
|
||||
std::unique_ptr<IFileSystem> unique_fs;
|
||||
std::shared_ptr<IFileSystem> shared_fs;
|
||||
IFileSystem *base_fs;
|
||||
public:
|
||||
IFileSystemInterface(IFileSystem *fs) : unique_fs(fs) {
|
||||
this->base_fs = this->unique_fs.get();
|
||||
};
|
||||
IFileSystemInterface(std::unique_ptr<IFileSystem> fs) : unique_fs(std::move(fs)) {
|
||||
this->base_fs = this->unique_fs.get();
|
||||
};
|
||||
IFileSystemInterface(std::shared_ptr<IFileSystem> fs) : shared_fs(fs) {
|
||||
this->base_fs = this->shared_fs.get();
|
||||
};
|
||||
|
||||
private:
|
||||
/* Actual command API. */
|
||||
virtual Result CreateFile(InPointer<char> in_path, uint64_t size, int flags) final {
|
||||
FsPath path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
||||
|
||||
return this->base_fs->CreateFile(path, size, flags);
|
||||
}
|
||||
|
||||
virtual Result DeleteFile(InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
||||
|
||||
return this->base_fs->DeleteFile(path);
|
||||
}
|
||||
|
||||
virtual Result CreateDirectory(InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
||||
|
||||
return this->base_fs->CreateDirectory(path);
|
||||
}
|
||||
|
||||
virtual Result DeleteDirectory(InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
||||
|
||||
return this->base_fs->DeleteDirectory(path);
|
||||
}
|
||||
|
||||
virtual Result DeleteDirectoryRecursively(InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
||||
|
||||
return this->base_fs->DeleteDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
virtual Result RenameFile(InPointer<char> in_old_path, InPointer<char> in_new_path) final {
|
||||
FsPath old_path;
|
||||
FsPath new_path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&old_path, in_old_path.pointer));
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&new_path, in_new_path.pointer));
|
||||
|
||||
return this->base_fs->RenameFile(old_path, new_path);
|
||||
}
|
||||
|
||||
virtual Result RenameDirectory(InPointer<char> in_old_path, InPointer<char> in_new_path) final {
|
||||
FsPath old_path;
|
||||
FsPath new_path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&old_path, in_old_path.pointer));
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&new_path, in_new_path.pointer));
|
||||
|
||||
return this->base_fs->RenameDirectory(old_path, new_path);
|
||||
}
|
||||
|
||||
|
||||
virtual Result GetEntryType(Out<u32> out_type, InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
||||
|
||||
DirectoryEntryType type;
|
||||
R_TRY(this->base_fs->GetEntryType(&type, path));
|
||||
|
||||
out_type.SetValue(type);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result OpenFile(Out<std::shared_ptr<IFileInterface>> out_intf, InPointer<char> in_path, uint32_t mode) final {
|
||||
FsPath path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
||||
|
||||
std::unique_ptr<IFile> out_file;
|
||||
R_TRY(this->base_fs->OpenFile(out_file, path, static_cast<OpenMode>(mode)));
|
||||
|
||||
out_intf.SetValue(std::make_shared<IFileInterface>(std::move(out_file)));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result OpenDirectory(Out<std::shared_ptr<IDirectoryInterface>> out_intf, InPointer<char> in_path, uint32_t mode) final {
|
||||
FsPath path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
||||
|
||||
std::unique_ptr<IDirectory> out_dir;
|
||||
R_TRY(this->base_fs->OpenDirectory(out_dir, path, static_cast<DirectoryOpenMode>(mode)));
|
||||
|
||||
out_intf.SetValue(std::make_shared<IDirectoryInterface>(std::move(out_dir)));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result Commit() final {
|
||||
return this->base_fs->Commit();
|
||||
}
|
||||
|
||||
virtual Result GetFreeSpaceSize(Out<uint64_t> out_size, InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
||||
|
||||
return this->base_fs->GetFreeSpaceSize(out_size.GetPointer(), path);
|
||||
}
|
||||
|
||||
virtual Result GetTotalSpaceSize(Out<uint64_t> out_size, InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
||||
|
||||
return this->base_fs->GetTotalSpaceSize(out_size.GetPointer(), path);
|
||||
}
|
||||
|
||||
virtual Result CleanDirectoryRecursively(InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
||||
|
||||
return this->base_fs->CleanDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
virtual Result GetFileTimeStampRaw(Out<FsTimeStampRaw> out_timestamp, InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
||||
|
||||
return this->base_fs->GetFileTimeStampRaw(out_timestamp.GetPointer(), path);
|
||||
}
|
||||
|
||||
virtual Result QueryEntry(OutBuffer<char, BufferType_Type1> out_buffer, InBuffer<char, BufferType_Type1> in_buffer, int query, InPointer<char> in_path) final {
|
||||
FsPath path;
|
||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
||||
|
||||
return this->base_fs->QueryEntry(out_buffer.buffer, out_buffer.num_elements, in_buffer.buffer, in_buffer.num_elements, query, path);
|
||||
}
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
/* 1.0.0- */
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, CreateFile),
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, DeleteFile),
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, CreateDirectory),
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, DeleteDirectory),
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, DeleteDirectoryRecursively),
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, RenameFile),
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, RenameDirectory),
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, GetEntryType),
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, OpenFile),
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, OpenDirectory),
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, Commit),
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, GetFreeSpaceSize),
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, GetTotalSpaceSize),
|
||||
|
||||
/* 3.0.0- */
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, CleanDirectoryRecursively, FirmwareVersion_300),
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, GetFileTimeStampRaw, FirmwareVersion_300),
|
||||
|
||||
/* 4.0.0- */
|
||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, QueryEntry, FirmwareVersion_400),
|
||||
};
|
||||
};
|
||||
|
||||
class ProxyFileSystem : public IFileSystem {
|
||||
private:
|
||||
std::unique_ptr<FsFileSystem> base_fs;
|
||||
public:
|
||||
ProxyFileSystem(FsFileSystem *fs) : base_fs(fs) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ProxyFileSystem(std::unique_ptr<FsFileSystem> fs) : base_fs(std::move(fs)) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ProxyFileSystem(FsFileSystem fs) {
|
||||
this->base_fs = std::make_unique<FsFileSystem>(fs);
|
||||
}
|
||||
|
||||
virtual ~ProxyFileSystem() {
|
||||
fsFsClose(this->base_fs.get());
|
||||
}
|
||||
|
||||
public:
|
||||
virtual Result CreateFileImpl(const FsPath &path, uint64_t size, int flags) {
|
||||
return fsFsCreateFile(this->base_fs.get(), path.str, size, flags);
|
||||
}
|
||||
|
||||
virtual Result DeleteFileImpl(const FsPath &path) {
|
||||
return fsFsDeleteFile(this->base_fs.get(), path.str);
|
||||
}
|
||||
|
||||
virtual Result CreateDirectoryImpl(const FsPath &path) {
|
||||
return fsFsCreateDirectory(this->base_fs.get(), path.str);
|
||||
}
|
||||
|
||||
virtual Result DeleteDirectoryImpl(const FsPath &path) {
|
||||
return fsFsDeleteDirectory(this->base_fs.get(), path.str);
|
||||
}
|
||||
|
||||
virtual Result DeleteDirectoryRecursivelyImpl(const FsPath &path) {
|
||||
return fsFsDeleteDirectoryRecursively(this->base_fs.get(), path.str);
|
||||
}
|
||||
|
||||
virtual Result RenameFileImpl(const FsPath &old_path, const FsPath &new_path) {
|
||||
return fsFsRenameFile(this->base_fs.get(), old_path.str, new_path.str);
|
||||
}
|
||||
|
||||
virtual Result RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) {
|
||||
return fsFsRenameDirectory(this->base_fs.get(), old_path.str, new_path.str);
|
||||
}
|
||||
|
||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) {
|
||||
FsEntryType type;
|
||||
R_TRY(fsFsGetEntryType(this->base_fs.get(), path.str, &type));
|
||||
|
||||
*out = static_cast<DirectoryEntryType>(static_cast<u32>(type));
|
||||
return ResultSuccess();
|
||||
}
|
||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) {
|
||||
FsFile f;
|
||||
R_TRY(fsFsOpenFile(this->base_fs.get(), path.str, static_cast<int>(mode), &f));
|
||||
|
||||
out_file = std::make_unique<ProxyFile>(f);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) {
|
||||
FsDir d;
|
||||
R_TRY(fsFsOpenDirectory(this->base_fs.get(), path.str, static_cast<int>(mode), &d));
|
||||
|
||||
out_dir = std::make_unique<ProxyDirectory>(d);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result CommitImpl() {
|
||||
return fsFsCommit(this->base_fs.get());
|
||||
}
|
||||
|
||||
virtual Result GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
||||
return fsFsGetFreeSpace(this->base_fs.get(), path.str, out);
|
||||
}
|
||||
|
||||
virtual Result GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
||||
return fsFsGetTotalSpace(this->base_fs.get(), path.str, out);
|
||||
}
|
||||
|
||||
virtual Result CleanDirectoryRecursivelyImpl(const FsPath &path) {
|
||||
return fsFsCleanDirectoryRecursively(this->base_fs.get(), path.str);
|
||||
}
|
||||
|
||||
virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) {
|
||||
return fsFsGetFileTimeStampRaw(this->base_fs.get(), path.str, out);
|
||||
}
|
||||
|
||||
virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) {
|
||||
return fsFsQueryEntry(this->base_fs.get(), out, out_size, in, in_size, path.str,static_cast<FsFileSystemQueryType>(query));
|
||||
}
|
||||
};
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_shim.h"
|
||||
|
||||
#include "../debug.hpp"
|
||||
|
||||
class IStorage {
|
||||
public:
|
||||
virtual ~IStorage();
|
||||
|
||||
virtual Result Read(void *buffer, size_t size, u64 offset) = 0;
|
||||
virtual Result Write(void *buffer, size_t size, u64 offset) = 0;
|
||||
virtual Result Flush() = 0;
|
||||
virtual Result SetSize(u64 size) = 0;
|
||||
virtual Result GetSize(u64 *out_size) = 0;
|
||||
virtual Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0;
|
||||
|
||||
static inline bool IsRangeValid(uint64_t offset, uint64_t size, uint64_t total_size) {
|
||||
return size <= total_size && offset <= total_size - size;
|
||||
}
|
||||
};
|
||||
|
||||
class IStorageInterface : public IServiceObject {
|
||||
private:
|
||||
enum class CommandId {
|
||||
Read = 0,
|
||||
Write = 1,
|
||||
Flush = 2,
|
||||
SetSize = 3,
|
||||
GetSize = 4,
|
||||
OperateRange = 5,
|
||||
};
|
||||
private:
|
||||
IStorage *base_storage;
|
||||
public:
|
||||
IStorageInterface(IStorage *s) : base_storage(s) {
|
||||
/* ... */
|
||||
};
|
||||
|
||||
~IStorageInterface() {
|
||||
delete base_storage;
|
||||
};
|
||||
|
||||
private:
|
||||
/* Actual command API. */
|
||||
virtual Result Read(OutBuffer<u8, BufferType_Type1> buffer, u64 offset, u64 size) final {
|
||||
return this->base_storage->Read(buffer.buffer, std::min(buffer.num_elements, size), offset);
|
||||
};
|
||||
virtual Result Write(InBuffer<u8, BufferType_Type1> buffer, u64 offset, u64 size) final {
|
||||
return this->base_storage->Write(buffer.buffer, std::min(buffer.num_elements, size), offset);
|
||||
};
|
||||
virtual Result Flush() final {
|
||||
return this->base_storage->Flush();
|
||||
};
|
||||
virtual Result SetSize(u64 size) final {
|
||||
return this->base_storage->SetSize(size);
|
||||
};
|
||||
virtual Result GetSize(Out<u64> size) final {
|
||||
return this->base_storage->GetSize(size.GetPointer());
|
||||
};
|
||||
virtual Result OperateRange(Out<FsRangeInfo> range_info, u32 operation_type, u64 offset, u64 size) final {
|
||||
return this->base_storage->OperateRange(static_cast<FsOperationId>(operation_type), offset, size, range_info.GetPointer());
|
||||
};
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
/* 1.0.0- */
|
||||
MAKE_SERVICE_COMMAND_META(IStorageInterface, Read),
|
||||
MAKE_SERVICE_COMMAND_META(IStorageInterface, Write),
|
||||
MAKE_SERVICE_COMMAND_META(IStorageInterface, Flush),
|
||||
MAKE_SERVICE_COMMAND_META(IStorageInterface, SetSize),
|
||||
MAKE_SERVICE_COMMAND_META(IStorageInterface, GetSize),
|
||||
|
||||
/* 4.0.0- */
|
||||
MAKE_SERVICE_COMMAND_META(IStorageInterface, OperateRange, FirmwareVersion_400),
|
||||
};
|
||||
};
|
||||
|
||||
class IROStorage : public IStorage {
|
||||
public:
|
||||
virtual Result Read(void *buffer, size_t size, u64 offset) = 0;
|
||||
virtual Result Write(void *buffer, size_t size, u64 offset) final {
|
||||
(void)(buffer);
|
||||
(void)(offset);
|
||||
(void)(size);
|
||||
return ResultFsUnsupportedOperation;
|
||||
};
|
||||
virtual Result Flush() final {
|
||||
return ResultSuccess();
|
||||
};
|
||||
virtual Result SetSize(u64 size) final {
|
||||
(void)(size);
|
||||
return ResultFsUnsupportedOperation;
|
||||
};
|
||||
virtual Result GetSize(u64 *out_size) = 0;
|
||||
virtual Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0;
|
||||
};
|
||||
|
||||
|
||||
class ProxyStorage : public IStorage {
|
||||
private:
|
||||
FsStorage *base_storage;
|
||||
public:
|
||||
ProxyStorage(FsStorage *s) : base_storage(s) {
|
||||
/* ... */
|
||||
};
|
||||
ProxyStorage(FsStorage s) {
|
||||
this->base_storage = new FsStorage(s);
|
||||
};
|
||||
virtual ~ProxyStorage() {
|
||||
fsStorageClose(base_storage);
|
||||
delete base_storage;
|
||||
};
|
||||
public:
|
||||
virtual Result Read(void *buffer, size_t size, u64 offset) override {
|
||||
return fsStorageRead(this->base_storage, offset, buffer, size);
|
||||
};
|
||||
virtual Result Write(void *buffer, size_t size, u64 offset) override {
|
||||
return fsStorageWrite(this->base_storage, offset, buffer, size);
|
||||
};
|
||||
virtual Result Flush() override {
|
||||
return fsStorageFlush(this->base_storage);
|
||||
};
|
||||
virtual Result GetSize(u64 *out_size) override {
|
||||
return fsStorageGetSize(this->base_storage, out_size);
|
||||
};
|
||||
virtual Result SetSize(u64 size) override {
|
||||
return fsStorageSetSize(this->base_storage, size);
|
||||
};
|
||||
virtual Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
|
||||
return fsStorageOperateRange(this->base_storage, operation_type, offset, size, out_range_info);
|
||||
};
|
||||
};
|
||||
|
||||
class ReadOnlyStorageAdapter : public IROStorage {
|
||||
private:
|
||||
std::shared_ptr<IStorage> base_storage;
|
||||
IStorage *storage;
|
||||
public:
|
||||
ReadOnlyStorageAdapter(IStorage *s) : base_storage(s) {
|
||||
this->storage = this->base_storage.get();
|
||||
}
|
||||
ReadOnlyStorageAdapter(std::shared_ptr<IStorage> s) : base_storage(s) {
|
||||
this->storage = this->base_storage.get();
|
||||
}
|
||||
virtual ~ReadOnlyStorageAdapter() {
|
||||
/* ... */
|
||||
}
|
||||
public:
|
||||
virtual Result Read(void *buffer, size_t size, u64 offset) override {
|
||||
return this->base_storage->Read(buffer, size, offset);
|
||||
};
|
||||
virtual Result GetSize(u64 *out_size) override {
|
||||
return this->base_storage->GetSize(out_size);
|
||||
};
|
||||
virtual Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
|
||||
return this->base_storage->OperateRange(operation_type, offset, size, out_range_info);
|
||||
};
|
||||
};
|
||||
@@ -15,24 +15,28 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
enum OpenMode {
|
||||
OpenMode_Read = (1 << 0),
|
||||
OpenMode_Write = (1 << 1),
|
||||
OpenMode_Append = (1 << 2),
|
||||
namespace ams::mitm::fs {
|
||||
|
||||
OpenMode_ReadWrite = OpenMode_Read | OpenMode_Write,
|
||||
OpenMode_All = OpenMode_ReadWrite | OpenMode_Append,
|
||||
};
|
||||
class FsMitmService : public sf::IMitmServiceObject {
|
||||
private:
|
||||
enum class CommandId {
|
||||
/* TODO */
|
||||
};
|
||||
public:
|
||||
static bool ShouldMitm(os::ProcessId process_id, ncm::ProgramId program_id) {
|
||||
/* TODO */
|
||||
return false;
|
||||
}
|
||||
public:
|
||||
SF_MITM_SERVICE_OBJECT_CTOR(FsMitmService) { /* ... */ }
|
||||
protected:
|
||||
/* TODO */
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
/* TODO */
|
||||
};
|
||||
};
|
||||
|
||||
enum DirectoryOpenMode {
|
||||
DirectoryOpenMode_Directories = (1 << 0),
|
||||
DirectoryOpenMode_Files = (1 << 1),
|
||||
|
||||
DirectoryOpenMode_All = (DirectoryOpenMode_Directories | DirectoryOpenMode_Files),
|
||||
};
|
||||
|
||||
enum DirectoryEntryType {
|
||||
DirectoryEntryType_Directory,
|
||||
DirectoryEntryType_File,
|
||||
};
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
/*
|
||||
* 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 <cstring>
|
||||
#include <cstdlib>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fs_path_utils.hpp"
|
||||
|
||||
Result FsPathUtils::VerifyPath(const char *path, size_t max_path_len, size_t max_name_len) {
|
||||
const char *cur = path;
|
||||
size_t name_len = 0;
|
||||
|
||||
for (size_t path_len = 0; path_len <= max_path_len && name_len <= max_name_len; path_len++) {
|
||||
const char c = *(cur++);
|
||||
/* If terminated, we're done. */
|
||||
if (c == 0) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* TODO: Nintendo converts the path from utf-8 to utf-32, one character at a time. */
|
||||
/* We should do this. */
|
||||
|
||||
/* Banned characters: :*?<>| */
|
||||
if (c == ':' || c == '*' || c == '?' || c == '<' || c == '>' || c == '|') {
|
||||
return ResultFsInvalidCharacter;
|
||||
}
|
||||
|
||||
name_len++;
|
||||
/* Check for separator. */
|
||||
if (c == '/' || c == '\\') {
|
||||
name_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
Result FsPathUtils::ConvertPathForServiceObject(FsPath *out, const char *path) {
|
||||
/* Check for nullptr. */
|
||||
if (out == nullptr || path == nullptr) {
|
||||
return ResultFsInvalidPath;
|
||||
}
|
||||
|
||||
/* Copy string, NULL terminate. */
|
||||
/* NOTE: Nintendo adds an extra char at 0x301 for NULL terminator */
|
||||
/* But then forces 0x300 to NULL anyway... */
|
||||
std::strncpy(out->str, path, sizeof(out->str) - 1);
|
||||
out->str[sizeof(out->str)-1] = 0;
|
||||
|
||||
/* Replace any instances of \ with / */
|
||||
for (size_t i = 0; i < sizeof(out->str); i++) {
|
||||
if (out->str[i] == '\\') {
|
||||
out->str[i] = '/';
|
||||
}
|
||||
}
|
||||
|
||||
/* Nintendo allows some liberties if the path is a windows path. Who knows why... */
|
||||
const auto prefix_len = FsPathUtils::IsWindowsAbsolutePath(path) ? 2 : 0;
|
||||
const size_t max_len = (FS_MAX_PATH-1) - prefix_len;
|
||||
return FsPathUtils::VerifyPath(out->str + prefix_len, max_len, max_len);
|
||||
}
|
||||
|
||||
Result FsPathUtils::IsNormalized(bool *out, const char *path) {
|
||||
/* Nintendo uses a state machine here. */
|
||||
enum class PathState {
|
||||
Start,
|
||||
Normal,
|
||||
FirstSeparator,
|
||||
Separator,
|
||||
CurrentDir,
|
||||
ParentDir,
|
||||
WindowsDriveLetter,
|
||||
};
|
||||
|
||||
PathState state = PathState::Start;
|
||||
|
||||
for (const char *cur = path; *cur != 0; cur++) {
|
||||
const char c = *cur;
|
||||
switch (state) {
|
||||
case PathState::Start:
|
||||
if (IsWindowsDriveLetter(c)) {
|
||||
state = PathState::WindowsDriveLetter;
|
||||
} else if (c == '/') {
|
||||
state = PathState::FirstSeparator;
|
||||
} else {
|
||||
return ResultFsInvalidPathFormat;
|
||||
}
|
||||
break;
|
||||
case PathState::Normal:
|
||||
if (c == '/') {
|
||||
state = PathState::Separator;
|
||||
}
|
||||
break;
|
||||
case PathState::FirstSeparator:
|
||||
case PathState::Separator:
|
||||
/* It is unclear why first separator and separator are separate states... */
|
||||
if (c == '/') {
|
||||
*out = false;
|
||||
return ResultSuccess();
|
||||
} else if (c == '.') {
|
||||
state = PathState::CurrentDir;
|
||||
} else {
|
||||
state = PathState::Normal;
|
||||
}
|
||||
break;
|
||||
case PathState::CurrentDir:
|
||||
if (c == '/') {
|
||||
*out = false;
|
||||
return ResultSuccess();
|
||||
} else if (c == '.') {
|
||||
state = PathState::ParentDir;
|
||||
} else {
|
||||
state = PathState::Normal;
|
||||
}
|
||||
break;
|
||||
case PathState::ParentDir:
|
||||
if (c == '/') {
|
||||
*out = false;
|
||||
return ResultSuccess();
|
||||
} else {
|
||||
state = PathState::Normal;
|
||||
}
|
||||
break;
|
||||
case PathState::WindowsDriveLetter:
|
||||
if (c == ':') {
|
||||
*out = true;
|
||||
return ResultSuccess();
|
||||
} else {
|
||||
return ResultFsInvalidPathFormat;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case PathState::Start:
|
||||
case PathState::WindowsDriveLetter:
|
||||
return ResultFsInvalidPathFormat;
|
||||
case PathState::FirstSeparator:
|
||||
case PathState::Normal:
|
||||
*out = true;
|
||||
break;
|
||||
case PathState::CurrentDir:
|
||||
case PathState::ParentDir:
|
||||
case PathState::Separator:
|
||||
*out = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FsPathUtils::Normalize(char *out, size_t max_out_size, const char *src, size_t *out_len) {
|
||||
/* Paths must start with / */
|
||||
if (src[0] != '/') {
|
||||
return ResultFsInvalidPathFormat;
|
||||
}
|
||||
|
||||
bool skip_next_sep = false;
|
||||
size_t i = 0;
|
||||
size_t len = 0;
|
||||
|
||||
while (src[i] != 0) {
|
||||
if (src[i] == '/') {
|
||||
/* Swallow separators. */
|
||||
while (src[++i] == '/') { }
|
||||
if (src[i] == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Handle skip if needed */
|
||||
if (!skip_next_sep) {
|
||||
if (len + 1 == max_out_size) {
|
||||
out[len] = 0;
|
||||
if (out_len != nullptr) {
|
||||
*out_len = len;
|
||||
}
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
out[len++] = '/';
|
||||
|
||||
/* TODO: N has some weird windows support stuff here under a bool. */
|
||||
/* Boolean is normally false though? */
|
||||
}
|
||||
skip_next_sep = false;
|
||||
}
|
||||
|
||||
/* See length of current dir. */
|
||||
size_t dir_len = 0;
|
||||
while (src[i+dir_len] != '/' && src[i+dir_len] != 0) {
|
||||
dir_len++;
|
||||
}
|
||||
|
||||
if (FsPathUtils::IsCurrentDirectory(&src[i])) {
|
||||
skip_next_sep = true;
|
||||
} else if (FsPathUtils::IsParentDirectory(&src[i])) {
|
||||
if (len == 1) {
|
||||
return ResultFsDirectoryUnobtainable;
|
||||
}
|
||||
|
||||
/* Walk up a directory. */
|
||||
len -= 2;
|
||||
while (out[len] != '/') {
|
||||
len--;
|
||||
}
|
||||
} else {
|
||||
/* Copy, possibly truncating. */
|
||||
if (len + dir_len + 1 <= max_out_size) {
|
||||
for (size_t j = 0; j < dir_len; j++) {
|
||||
out[len++] = src[i+j];
|
||||
}
|
||||
} else {
|
||||
const size_t copy_len = max_out_size - 1 - len;
|
||||
for (size_t j = 0; j < copy_len; j++) {
|
||||
out[len++] = src[i+j];
|
||||
}
|
||||
out[len] = 0;
|
||||
if (out_len != nullptr) {
|
||||
*out_len = len;
|
||||
}
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
}
|
||||
|
||||
i += dir_len;
|
||||
}
|
||||
|
||||
if (skip_next_sep) {
|
||||
len--;
|
||||
}
|
||||
|
||||
if (len == 0 && max_out_size) {
|
||||
out[len++] = '/';
|
||||
}
|
||||
|
||||
if (max_out_size < len - 1) {
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
/* NULL terminate. */
|
||||
out[len] = 0;
|
||||
if (out_len != nullptr) {
|
||||
*out_len = len;
|
||||
}
|
||||
|
||||
/* Assert normalized. */
|
||||
bool normalized = false;
|
||||
R_ASSERT(FsPathUtils::IsNormalized(&normalized, out));
|
||||
AMS_ASSERT(normalized);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
|
||||
struct FsPath {
|
||||
char str[FS_MAX_PATH];
|
||||
};
|
||||
|
||||
constexpr FsPath EncodeConstantFsPath(const char *p) {
|
||||
FsPath path = {0};
|
||||
for (size_t i = 0; i < sizeof(path) - 1; i++) {
|
||||
path.str[i] = p[i];
|
||||
if (p[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
constexpr size_t GetConstantFsPathLen(FsPath path) {
|
||||
size_t len = 0;
|
||||
for (size_t i = 0; i < sizeof(path) - 1; i++) {
|
||||
if (path.str[i] == 0) {
|
||||
break;
|
||||
}
|
||||
len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
class FsPathUtils {
|
||||
public:
|
||||
static constexpr FsPath RootPath = EncodeConstantFsPath("/");
|
||||
public:
|
||||
static Result VerifyPath(const char *path, size_t max_path_len, size_t max_name_len);
|
||||
static Result ConvertPathForServiceObject(FsPath *out, const char *path);
|
||||
|
||||
static Result IsNormalized(bool *out, const char *path);
|
||||
static Result Normalize(char *out, size_t max_out_size, const char *src, size_t *out_size);
|
||||
|
||||
static bool IsWindowsDriveLetter(const char c) {
|
||||
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
|
||||
}
|
||||
|
||||
static bool IsCurrentDirectory(const char *path) {
|
||||
return path[0] == '.' && (path[1] == 0 || path[1] == '/');
|
||||
}
|
||||
|
||||
static bool IsParentDirectory(const char *path) {
|
||||
return path[0] == '.' && path[1] == '.' && (path[2] == 0 || path[2] == '/');
|
||||
}
|
||||
|
||||
static bool IsWindowsAbsolutePath(const char *path) {
|
||||
/* Nintendo uses this in path comparisons... */
|
||||
return IsWindowsDriveLetter(path[0]) && path[1] == ':';
|
||||
}
|
||||
};
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* 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 <cstring>
|
||||
#include <cstdlib>
|
||||
#include <switch.h>
|
||||
|
||||
#include "fs_save_utils.hpp"
|
||||
|
||||
Result FsSaveUtils::GetSaveDataSpaceIdString(const char **out_str, u8 space_id) {
|
||||
const char *str = nullptr;
|
||||
|
||||
switch (space_id) {
|
||||
case FsSaveDataSpaceId_NandSystem:
|
||||
case 100: /* ProperSystem */
|
||||
case 101: /* SafeMode */
|
||||
str = "sys";
|
||||
break;
|
||||
case FsSaveDataSpaceId_NandUser:
|
||||
str = "user";
|
||||
break;
|
||||
case FsSaveDataSpaceId_SdCard:
|
||||
str = "sd_sys";
|
||||
break;
|
||||
case FsSaveDataSpaceId_TemporaryStorage:
|
||||
str = "temp";
|
||||
break;
|
||||
case 4: /* SdUser */
|
||||
str = "sd_user";
|
||||
break;
|
||||
default: /* Unexpected */
|
||||
str = nullptr;
|
||||
break;
|
||||
}
|
||||
|
||||
if (str == nullptr) {
|
||||
return ResultFsInvalidSaveDataSpaceId;
|
||||
}
|
||||
|
||||
if (out_str) {
|
||||
*out_str = str;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FsSaveUtils::GetSaveDataTypeString(const char **out_str, u8 save_data_type) {
|
||||
const char *str = nullptr;
|
||||
|
||||
switch (save_data_type) {
|
||||
case FsSaveDataType_SystemSaveData:
|
||||
str = "system";
|
||||
break;
|
||||
case FsSaveDataType_SaveData:
|
||||
str = "account";
|
||||
break;
|
||||
case FsSaveDataType_BcatDeliveryCacheStorage:
|
||||
str = "bcat";
|
||||
break;
|
||||
case FsSaveDataType_DeviceSaveData:
|
||||
str = "device";
|
||||
break;
|
||||
case FsSaveDataType_TemporaryStorage:
|
||||
str = "temp";
|
||||
break;
|
||||
case FsSaveDataType_CacheStorage:
|
||||
str = "cache";
|
||||
break;
|
||||
case 6: /* System Bcat Save */
|
||||
str = "bcat_sys";
|
||||
break;
|
||||
default: /* Unexpected */
|
||||
str = nullptr;
|
||||
break;
|
||||
}
|
||||
|
||||
if (str == nullptr) {
|
||||
/* TODO: Better result? */
|
||||
return ResultFsInvalidArgument;
|
||||
}
|
||||
|
||||
if (out_str) {
|
||||
*out_str = str;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FsSaveUtils::GetSaveDataDirectoryPath(FsPath &out_path, u8 space_id, u8 save_data_type, u64 title_id, u128 user_id, u64 save_id) {
|
||||
const char *space_id_str, *save_type_str;
|
||||
|
||||
/* Get space_id, save_data_type strings. */
|
||||
R_TRY(GetSaveDataSpaceIdString(&space_id_str, space_id));
|
||||
R_TRY(GetSaveDataTypeString(&save_type_str, save_data_type));
|
||||
|
||||
/* Clear and initialize the path. */
|
||||
std::memset(&out_path, 0, sizeof(out_path));
|
||||
const bool is_system = (save_id != 0 && user_id == 0);
|
||||
size_t out_path_len;
|
||||
if (is_system) {
|
||||
out_path_len = static_cast<size_t>(snprintf(out_path.str, sizeof(out_path.str), "/atmosphere/saves/sysnand/%s/%s/%016lx",
|
||||
space_id_str, save_type_str, save_id));
|
||||
} else {
|
||||
out_path_len = static_cast<size_t>(snprintf(out_path.str, sizeof(out_path.str), "/atmosphere/saves/sysnand/%s/%s/%016lx/%016lx%016lx",
|
||||
space_id_str, save_type_str, title_id, static_cast<u64>(user_id >> 64ul), static_cast<u64>(user_id)));
|
||||
}
|
||||
if (out_path_len >= sizeof(out_path)) {
|
||||
/* TODO: Should we abort here? */
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
|
||||
#include "fs_path_utils.hpp"
|
||||
#include "fs_ifilesystem.hpp"
|
||||
|
||||
class FsSaveUtils {
|
||||
private:
|
||||
static Result GetSaveDataSpaceIdString(const char **out_str, u8 space_id);
|
||||
static Result GetSaveDataTypeString(const char **out_str, u8 save_data_type);
|
||||
public:
|
||||
static Result GetSaveDataDirectoryPath(FsPath &out_path, u8 space_id, u8 save_data_type, u64 title_id, u128 user_id, u64 save_id);
|
||||
};
|
||||
@@ -1,266 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <string.h>
|
||||
#include "fs_shim.h"
|
||||
|
||||
/* Missing fsp-srv commands. */
|
||||
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisStorageId PartitionId) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u32 PartitionId;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 12;
|
||||
raw->PartitionId = PartitionId;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 200;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorageId storage_id, u64 data_id, FsStorage* out) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
FsStorageId storage_id;
|
||||
u64 data_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 202;
|
||||
raw->storage_id = storage_id;
|
||||
raw->data_id = data_id;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result fsOpenFileSystemWithPatchFwd(Service* s, FsFileSystem* out, u64 titleId, FsFileSystemType fsType) {
|
||||
if (hosversionBefore(2, 0, 0)) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
||||
}
|
||||
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u32 fsType;
|
||||
u64 titleId;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 7;
|
||||
raw->fsType = fsType;
|
||||
raw->titleId = titleId;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result fsOpenFileSystemWithIdFwd(Service* s, FsFileSystem* out, u64 titleId, FsFileSystemType fsType, const char* contentPath) {
|
||||
if (hosversionBefore(2, 0, 0)) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
||||
}
|
||||
|
||||
char sendStr[FS_MAX_PATH] = {0};
|
||||
strncpy(sendStr, contentPath, sizeof(sendStr)-1);
|
||||
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
ipcAddSendStatic(&c, sendStr, sizeof(sendStr), 0);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u32 fsType;
|
||||
u64 titleId;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 8;
|
||||
raw->fsType = fsType;
|
||||
raw->titleId = titleId;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result fsOpenSaveDataFileSystemFwd(Service *s, FsFileSystem* out, u8 inval, FsSave *save) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 inval;//Actually u8.
|
||||
FsSave save;
|
||||
} PACKED *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 51;
|
||||
raw->inval = (u64)inval;
|
||||
memcpy(&raw->save, save, sizeof(FsSave));
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* @file fs_shim.h
|
||||
* @brief Filesystem Services (fs) IPC wrapper. To be merged into libnx, eventually.
|
||||
* @author SciresM
|
||||
* @copyright libnx Authors
|
||||
*/
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Missing fsp-srv commands. */
|
||||
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisStorageId PartitionId);
|
||||
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out);
|
||||
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorageId storage_id, u64 data_id, FsStorage* out);
|
||||
Result fsOpenFileSystemWithPatchFwd(Service* s, FsFileSystem* out, u64 titleId, FsFileSystemType fsType);
|
||||
Result fsOpenFileSystemWithIdFwd(Service* s, FsFileSystem* out, u64 titleId, FsFileSystemType fsType, const char* contentPath);
|
||||
Result fsOpenSaveDataFileSystemFwd(Service* s, FsFileSystem* out, u8 inval, FsSave *save);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
* 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 <cstring>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../utils.hpp"
|
||||
#include "fs_subdirectory_filesystem.hpp"
|
||||
#include "fs_path_utils.hpp"
|
||||
|
||||
Result SubDirectoryFileSystem::Initialize(const char *bp) {
|
||||
if (strnlen(bp, FS_MAX_PATH) >= FS_MAX_PATH) {
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
/* Normalize the path. */
|
||||
char normal_path[FS_MAX_PATH + 1];
|
||||
size_t normal_path_len;
|
||||
R_ASSERT(FsPathUtils::Normalize(normal_path, sizeof(normal_path), bp, &normal_path_len));
|
||||
|
||||
/* Ensure terminating '/' */
|
||||
if (normal_path[normal_path_len-1] != '/') {
|
||||
AMS_ASSERT(normal_path_len + 2 <= sizeof(normal_path));
|
||||
|
||||
strncat(normal_path, "/", 2);
|
||||
normal_path[sizeof(normal_path)-1] = 0;
|
||||
normal_path_len++;
|
||||
}
|
||||
|
||||
this->base_path_len = normal_path_len + 1;
|
||||
this->base_path = reinterpret_cast<char *>(malloc(this->base_path_len));
|
||||
if (this->base_path == nullptr) {
|
||||
return ResultFsAllocationFailureInSubDirectoryFileSystem;
|
||||
}
|
||||
|
||||
std::strncpy(this->base_path, normal_path, this->base_path_len);
|
||||
this->base_path[this->base_path_len-1] = 0;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::GetFullPath(char *out, size_t out_size, const char *relative_path) {
|
||||
if (this->base_path_len + strnlen(relative_path, FS_MAX_PATH) > out_size) {
|
||||
return ResultFsTooLongPath;
|
||||
}
|
||||
|
||||
/* Copy base path. */
|
||||
std::strncpy(out, this->base_path, out_size);
|
||||
out[out_size-1] = 0;
|
||||
|
||||
/* Normalize it. */
|
||||
return FsPathUtils::Normalize(out + this->base_path_len - 2, out_size - (this->base_path_len - 2), relative_path, nullptr);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::CreateFileImpl(const FsPath &path, uint64_t size, int flags) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
return this->base_fs->CreateFile(full_path, size, flags);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::DeleteFileImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
return this->base_fs->DeleteFile(full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::CreateDirectoryImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
return this->base_fs->CreateDirectory(full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::DeleteDirectoryImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
return this->base_fs->DeleteDirectory(full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::DeleteDirectoryRecursivelyImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
return this->base_fs->DeleteDirectoryRecursively(full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::RenameFileImpl(const FsPath &old_path, const FsPath &new_path) {
|
||||
FsPath full_old_path, full_new_path;
|
||||
R_TRY(GetFullPath(full_old_path, old_path));
|
||||
R_TRY(GetFullPath(full_new_path, new_path));
|
||||
|
||||
return this->base_fs->RenameFile(full_old_path, full_new_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) {
|
||||
FsPath full_old_path, full_new_path;
|
||||
R_TRY(GetFullPath(full_old_path, old_path));
|
||||
R_TRY(GetFullPath(full_new_path, new_path));
|
||||
|
||||
return this->base_fs->RenameDirectory(full_old_path, full_new_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
return this->base_fs->GetEntryType(out, full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
return this->base_fs->OpenFile(out_file, full_path, mode);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
return this->base_fs->OpenDirectory(out_dir, full_path, mode);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::CommitImpl() {
|
||||
return this->base_fs->Commit();
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
return this->base_fs->GetFreeSpaceSize(out, full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
return this->base_fs->GetTotalSpaceSize(out, full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::CleanDirectoryRecursivelyImpl(const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
return this->base_fs->CleanDirectoryRecursively(full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
return this->base_fs->GetFileTimeStampRaw(out, full_path);
|
||||
}
|
||||
|
||||
Result SubDirectoryFileSystem::QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) {
|
||||
FsPath full_path;
|
||||
R_TRY(GetFullPath(full_path, path));
|
||||
|
||||
return this->base_fs->QueryEntry(out, out_size, in, in_size, query, full_path);
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fs_ifilesystem.hpp"
|
||||
#include "fs_path_utils.hpp"
|
||||
|
||||
class SubDirectoryFileSystem : public IFileSystem {
|
||||
private:
|
||||
std::shared_ptr<IFileSystem> base_fs;
|
||||
char *base_path = nullptr;
|
||||
size_t base_path_len = 0;
|
||||
|
||||
public:
|
||||
SubDirectoryFileSystem(IFileSystem *fs, const char *bp) : base_fs(fs) {
|
||||
R_ASSERT(this->Initialize(bp));
|
||||
}
|
||||
|
||||
SubDirectoryFileSystem(std::shared_ptr<IFileSystem> fs, const char *bp) : base_fs(fs) {
|
||||
R_ASSERT(this->Initialize(bp));
|
||||
}
|
||||
|
||||
|
||||
virtual ~SubDirectoryFileSystem() {
|
||||
if (this->base_path != nullptr) {
|
||||
free(this->base_path);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Result Initialize(const char *bp);
|
||||
protected:
|
||||
Result GetFullPath(char *out, size_t out_size, const char *relative_path);
|
||||
Result GetFullPath(FsPath &full_path, const FsPath &relative_path) {
|
||||
return GetFullPath(full_path.str, sizeof(full_path.str), relative_path.str);
|
||||
}
|
||||
|
||||
public:
|
||||
virtual Result CreateFileImpl(const FsPath &path, uint64_t size, int flags) override;
|
||||
virtual Result DeleteFileImpl(const FsPath &path) override;
|
||||
virtual Result CreateDirectoryImpl(const FsPath &path) override;
|
||||
virtual Result DeleteDirectoryImpl(const FsPath &path) override;
|
||||
virtual Result DeleteDirectoryRecursivelyImpl(const FsPath &path) override;
|
||||
virtual Result RenameFileImpl(const FsPath &old_path, const FsPath &new_path) override;
|
||||
virtual Result RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) override;
|
||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) override;
|
||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) override;
|
||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) override;
|
||||
virtual Result CommitImpl() override;
|
||||
virtual Result GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) override;
|
||||
virtual Result GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) override;
|
||||
virtual Result CleanDirectoryRecursivelyImpl(const FsPath &path) override;
|
||||
virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) override;
|
||||
virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) override;
|
||||
};
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <cstring>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fsmitm_boot0storage.hpp"
|
||||
|
||||
static ams::os::Mutex g_boot0_mutex;
|
||||
static u8 g_boot0_bct_buffer[Boot0Storage::BctEndOffset];
|
||||
|
||||
bool Boot0Storage::CanModifyBctPubks() {
|
||||
if (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 this->title_id == ams::ncm::TitleId::Ns;
|
||||
} else {
|
||||
/* RCM bug unpatched. */
|
||||
/* Allow homebrew but not NS to update the BCT pubks. */
|
||||
return this->title_id != ams::ncm::TitleId::Ns;
|
||||
}
|
||||
}
|
||||
|
||||
Result Boot0Storage::Read(void *_buffer, size_t size, u64 offset) {
|
||||
std::scoped_lock lk{g_boot0_mutex};
|
||||
|
||||
return Base::Read(_buffer, size, offset);
|
||||
}
|
||||
|
||||
Result Boot0Storage::Write(void *_buffer, size_t size, u64 offset) {
|
||||
std::scoped_lock lk{g_boot0_mutex};
|
||||
|
||||
u8 *buffer = static_cast<u8 *>(_buffer);
|
||||
|
||||
/* Protect the keyblob region from writes. */
|
||||
if (offset <= EksStart) {
|
||||
if (offset + size < EksStart) {
|
||||
/* Fall through, no need to do anything here. */
|
||||
} else {
|
||||
if (offset + size < EksEnd) {
|
||||
/* Adjust size to avoid writing end of data. */
|
||||
size = EksStart - offset;
|
||||
} else {
|
||||
/* Perform portion of write falling past end of keyblobs. */
|
||||
const u64 diff = EksEnd - offset;
|
||||
R_TRY(Base::Write(buffer + diff, size - diff, EksEnd));
|
||||
/* Adjust size to avoid writing end of data. */
|
||||
size = EksStart - offset;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (offset < EksEnd) {
|
||||
if (offset + size < EksEnd) {
|
||||
/* Ignore writes falling strictly within the region. */
|
||||
return ResultSuccess();
|
||||
} else {
|
||||
/* Only write past the end of the keyblob region. */
|
||||
buffer = buffer + (EksEnd - offset);
|
||||
size -= (EksEnd - offset);
|
||||
offset = EksEnd;
|
||||
}
|
||||
} else {
|
||||
/* Fall through, no need to do anything here. */
|
||||
}
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* We care about protecting autorcm from NS. */
|
||||
if (CanModifyBctPubks() || offset >= BctEndOffset || (offset + BctSize >= BctEndOffset && offset % BctSize >= BctPubkEnd)) {
|
||||
return Base::Write(buffer, size, offset);
|
||||
}
|
||||
|
||||
/* First, let's deal with the data past the end. */
|
||||
if (offset + size >= BctEndOffset) {
|
||||
const u64 diff = BctEndOffset - offset;
|
||||
R_TRY(ProxyStorage::Write(buffer + diff, size - diff, BctEndOffset));
|
||||
size = diff;
|
||||
}
|
||||
|
||||
/* Read in the current BCT region. */
|
||||
R_TRY(ProxyStorage::Read(g_boot0_bct_buffer, BctEndOffset, 0));
|
||||
|
||||
/* Update the bct buffer. */
|
||||
for (u64 cur_ofs = offset; cur_ofs < BctEndOffset && cur_ofs < offset + size; cur_ofs++) {
|
||||
const u64 cur_bct_rel_ofs = cur_ofs % BctSize;
|
||||
if (cur_bct_rel_ofs < BctPubkStart || BctPubkEnd <= cur_bct_rel_ofs) {
|
||||
g_boot0_bct_buffer[cur_ofs] = buffer[cur_ofs - offset];
|
||||
}
|
||||
}
|
||||
|
||||
return ProxyStorage::Write(g_boot0_bct_buffer, BctEndOffset, 0);
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <cstring>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fs_istorage.hpp"
|
||||
|
||||
/* Represents a sectored storage. */
|
||||
template<u64 SectorSize>
|
||||
class SectoredProxyStorage : public ProxyStorage {
|
||||
private:
|
||||
u64 cur_seek = 0;
|
||||
u64 cur_sector = 0;
|
||||
u64 cur_sector_ofs = 0;
|
||||
u8 sector_buf[SectorSize];
|
||||
private:
|
||||
void Seek(u64 offset) {
|
||||
this->cur_sector_ofs = offset % SectorSize;
|
||||
this->cur_seek = offset - this->cur_sector_ofs;
|
||||
}
|
||||
public:
|
||||
SectoredProxyStorage(FsStorage *s) : ProxyStorage(s) { }
|
||||
SectoredProxyStorage(FsStorage s) : ProxyStorage(s) { }
|
||||
public:
|
||||
virtual Result Read(void *_buffer, size_t size, u64 offset) override {
|
||||
u8 *buffer = static_cast<u8 *>(_buffer);
|
||||
this->Seek(offset);
|
||||
|
||||
if (this->cur_sector_ofs == 0 && size % SectorSize == 0) {
|
||||
/* Fast case. */
|
||||
return ProxyStorage::Read(buffer, size, offset);
|
||||
}
|
||||
|
||||
R_TRY(ProxyStorage::Read(this->sector_buf, SectorSize, this->cur_seek));
|
||||
|
||||
if (size + this->cur_sector_ofs <= SectorSize) {
|
||||
memcpy(buffer, sector_buf + this->cur_sector_ofs, size);
|
||||
} else {
|
||||
/* Leaving the sector... */
|
||||
size_t ofs = SectorSize - this->cur_sector_ofs;
|
||||
memcpy(buffer, sector_buf + this->cur_sector_ofs, ofs);
|
||||
size -= ofs;
|
||||
|
||||
/* We're guaranteed alignment, here. */
|
||||
const size_t aligned_remaining_size = size - (size % SectorSize);
|
||||
if (aligned_remaining_size) {
|
||||
R_TRY(ProxyStorage::Read(buffer + ofs, aligned_remaining_size, offset + ofs));
|
||||
ofs += aligned_remaining_size;
|
||||
size -= aligned_remaining_size;
|
||||
}
|
||||
|
||||
/* Read any leftover data. */
|
||||
if (size) {
|
||||
R_TRY(ProxyStorage::Read(this->sector_buf, SectorSize, offset + ofs));
|
||||
memcpy(buffer + ofs, sector_buf, size);
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result Write(void *_buffer, size_t size, u64 offset) override {
|
||||
u8 *buffer = static_cast<u8 *>(_buffer);
|
||||
this->Seek(offset);
|
||||
|
||||
if (this->cur_sector_ofs == 0 && size % SectorSize == 0) {
|
||||
/* Fast case. */
|
||||
return ProxyStorage::Write(buffer, size, offset);
|
||||
}
|
||||
|
||||
R_TRY(ProxyStorage::Read(this->sector_buf, SectorSize, this->cur_seek));
|
||||
|
||||
if (size + this->cur_sector_ofs <= SectorSize) {
|
||||
memcpy(this->sector_buf + this->cur_sector_ofs, buffer, size);
|
||||
R_TRY(ProxyStorage::Write(this->sector_buf, SectorSize, this->cur_seek));
|
||||
} else {
|
||||
/* Leaving the sector... */
|
||||
size_t ofs = SectorSize - this->cur_sector_ofs;
|
||||
memcpy(this->sector_buf + this->cur_sector_ofs, buffer, ofs);
|
||||
R_TRY(ProxyStorage::Write(this->sector_buf, ofs, this->cur_seek));
|
||||
size -= ofs;
|
||||
|
||||
/* We're guaranteed alignment, here. */
|
||||
const size_t aligned_remaining_size = size - (size % SectorSize);
|
||||
if (aligned_remaining_size) {
|
||||
R_TRY(ProxyStorage::Write(buffer + ofs, aligned_remaining_size, offset + ofs));
|
||||
ofs += aligned_remaining_size;
|
||||
size -= aligned_remaining_size;
|
||||
}
|
||||
|
||||
/* Write any leftover data. */
|
||||
if (size) {
|
||||
R_TRY(ProxyStorage::Read(this->sector_buf, SectorSize, offset + ofs));
|
||||
memcpy(this->sector_buf, buffer + ofs, size);
|
||||
R_TRY(ProxyStorage::Write(this->sector_buf, SectorSize, this->cur_seek));
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
/* Represents an RCM-preserving BOOT0 partition. */
|
||||
class Boot0Storage : public SectoredProxyStorage<0x200> {
|
||||
using Base = SectoredProxyStorage<0x200>;
|
||||
|
||||
public:
|
||||
static constexpr u64 BctEndOffset = 0xFC000;
|
||||
static constexpr u64 BctSize = 0x4000;
|
||||
static constexpr u64 BctPubkStart = 0x210;
|
||||
static constexpr u64 BctPubkSize = 0x100;
|
||||
static constexpr u64 BctPubkEnd = BctPubkStart + BctPubkSize;
|
||||
|
||||
static constexpr u64 EksStart = 0x180000;
|
||||
static constexpr u64 EksSize = 0x4000;
|
||||
static constexpr u64 EksEnd = EksStart + EksSize;
|
||||
private:
|
||||
ams::ncm::TitleId title_id;
|
||||
private:
|
||||
bool CanModifyBctPubks();
|
||||
public:
|
||||
Boot0Storage(FsStorage *s, ams::ncm::TitleId t) : Base(s), title_id(t) { }
|
||||
Boot0Storage(FsStorage s, ams::ncm::TitleId t) : Base(s), title_id(t) { }
|
||||
public:
|
||||
virtual Result Read(void *_buffer, size_t size, u64 offset) override;
|
||||
virtual Result Write(void *_buffer, size_t size, u64 offset) override;
|
||||
};
|
||||
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fsmitm_layeredrom.hpp"
|
||||
#include "../utils.hpp"
|
||||
#include "../debug.hpp"
|
||||
|
||||
IStorage::~IStorage() = default;
|
||||
|
||||
LayeredRomFS::LayeredRomFS(std::shared_ptr<IROStorage> s_r, std::shared_ptr<IROStorage> f_r, u64 tid) : storage_romfs(s_r), file_romfs(f_r), title_id(tid) {
|
||||
/* Start building the new virtual romfs. */
|
||||
RomFSBuildContext build_ctx(this->title_id);
|
||||
this->p_source_infos = std::shared_ptr<std::vector<RomFSSourceInfo>>(new std::vector<RomFSSourceInfo>(), [](std::vector<RomFSSourceInfo> *to_delete) {
|
||||
for (unsigned int i = 0; i < to_delete->size(); i++) {
|
||||
(*to_delete)[i].Cleanup();
|
||||
}
|
||||
delete to_delete;
|
||||
});
|
||||
if (Utils::IsSdInitialized()) {
|
||||
build_ctx.MergeSdFiles();
|
||||
}
|
||||
if (this->file_romfs) {
|
||||
build_ctx.MergeRomStorage(this->file_romfs.get(), RomFSDataSource::FileRomFS);
|
||||
}
|
||||
if (this->storage_romfs) {
|
||||
build_ctx.MergeRomStorage(this->storage_romfs.get(), RomFSDataSource::BaseRomFS);
|
||||
}
|
||||
build_ctx.Build(this->p_source_infos.get());
|
||||
}
|
||||
|
||||
|
||||
Result LayeredRomFS::Read(void *buffer, size_t size, u64 offset) {
|
||||
/* Size zero reads should always succeed. */
|
||||
if (size == 0) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Validate size. */
|
||||
u64 virt_size = (*this->p_source_infos)[this->p_source_infos->size() - 1].virtual_offset + (*this->p_source_infos)[this->p_source_infos->size() - 1].size;
|
||||
if (offset >= virt_size) {
|
||||
return ResultFsInvalidOffset;
|
||||
}
|
||||
if (virt_size - offset < size) {
|
||||
size = virt_size - offset;
|
||||
}
|
||||
/* Find first source info via binary search. */
|
||||
u32 cur_source_ind = 0;
|
||||
u32 low = 0, high = this->p_source_infos->size() - 1;
|
||||
while (low <= high) {
|
||||
u32 mid = (low + high) / 2;
|
||||
if ((*this->p_source_infos)[mid].virtual_offset > offset) {
|
||||
/* Too high. */
|
||||
high = mid - 1;
|
||||
} else {
|
||||
/* sources[mid].virtual_offset <= offset, invariant */
|
||||
if (mid == this->p_source_infos->size() - 1 || (*this->p_source_infos)[mid + 1].virtual_offset > offset) {
|
||||
/* Success */
|
||||
cur_source_ind = mid;
|
||||
break;
|
||||
}
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t read_so_far = 0;
|
||||
while (read_so_far < size) {
|
||||
RomFSSourceInfo *cur_source = &((*this->p_source_infos)[cur_source_ind]);
|
||||
if (cur_source->virtual_offset + cur_source->size > offset) {
|
||||
u64 cur_read_size = size - read_so_far;
|
||||
if (cur_read_size > cur_source->size - (offset - cur_source->virtual_offset)) {
|
||||
cur_read_size = cur_source->size - (offset - cur_source->virtual_offset);
|
||||
}
|
||||
switch (cur_source->type) {
|
||||
case RomFSDataSource::MetaData:
|
||||
{
|
||||
FsFile file;
|
||||
R_ASSERT(Utils::OpenSdFileForAtmosphere(this->title_id, ROMFS_METADATA_FILE_PATH, FS_OPEN_READ, &file));
|
||||
size_t out_read;
|
||||
R_ASSERT(fsFileRead(&file, (offset - cur_source->virtual_offset), (void *)((uintptr_t)buffer + read_so_far), cur_read_size, FS_READOPTION_NONE, &out_read));
|
||||
AMS_ASSERT(out_read == cur_read_size);
|
||||
fsFileClose(&file);
|
||||
}
|
||||
break;
|
||||
case RomFSDataSource::LooseFile:
|
||||
{
|
||||
FsFile file;
|
||||
R_ASSERT(Utils::OpenRomFSSdFile(this->title_id, cur_source->loose_source_info.path, FS_OPEN_READ, &file));
|
||||
size_t out_read;
|
||||
R_ASSERT(fsFileRead(&file, (offset - cur_source->virtual_offset), (void *)((uintptr_t)buffer + read_so_far), cur_read_size, FS_READOPTION_NONE, &out_read));
|
||||
AMS_ASSERT(out_read == cur_read_size);
|
||||
fsFileClose(&file);
|
||||
}
|
||||
break;
|
||||
case RomFSDataSource::Memory:
|
||||
{
|
||||
memcpy((void *)((uintptr_t)buffer + read_so_far), cur_source->memory_source_info.data + (offset - cur_source->virtual_offset), cur_read_size);
|
||||
}
|
||||
break;
|
||||
case RomFSDataSource::BaseRomFS:
|
||||
{
|
||||
R_ASSERT(this->storage_romfs->Read((void *)((uintptr_t)buffer + read_so_far), cur_read_size, cur_source->base_source_info.offset + (offset - cur_source->virtual_offset)));
|
||||
}
|
||||
break;
|
||||
case RomFSDataSource::FileRomFS:
|
||||
{
|
||||
R_ASSERT(this->file_romfs->Read((void *)((uintptr_t)buffer + read_so_far), cur_read_size, cur_source->base_source_info.offset + (offset - cur_source->virtual_offset)));
|
||||
}
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
read_so_far += cur_read_size;
|
||||
offset += cur_read_size;
|
||||
} else {
|
||||
/* Handle padding explicitly. */
|
||||
cur_source_ind++;
|
||||
/* Zero out the padding we skip, here. */
|
||||
memset((void *)((uintptr_t)buffer + read_so_far), 0, ((*this->p_source_infos)[cur_source_ind]).virtual_offset - offset);
|
||||
read_so_far += ((*this->p_source_infos)[cur_source_ind]).virtual_offset - offset;
|
||||
offset = ((*this->p_source_infos)[cur_source_ind]).virtual_offset;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
Result LayeredRomFS::GetSize(u64 *out_size) {
|
||||
*out_size = (*this->p_source_infos)[this->p_source_infos->size() - 1].virtual_offset + (*this->p_source_infos)[this->p_source_infos->size() - 1].size;
|
||||
return ResultSuccess();
|
||||
}
|
||||
Result LayeredRomFS::OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) {
|
||||
/* TODO: How should I implement this for a virtual romfs? */
|
||||
if (operation_type == FsOperationId_QueryRange) {
|
||||
*out_range_info = {0};
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fs_istorage.hpp"
|
||||
#include "fsmitm_romfsbuild.hpp"
|
||||
#include "../utils.hpp"
|
||||
|
||||
|
||||
/* Represents a merged RomFS. */
|
||||
class LayeredRomFS : public IROStorage {
|
||||
private:
|
||||
/* Data Sources. */
|
||||
std::shared_ptr<IROStorage> storage_romfs;
|
||||
std::shared_ptr<IROStorage> file_romfs;
|
||||
/* Information about the merged RomFS. */
|
||||
u64 title_id;
|
||||
std::shared_ptr<std::vector<RomFSSourceInfo>> p_source_infos;
|
||||
|
||||
public:
|
||||
LayeredRomFS(std::shared_ptr<IROStorage> s_r, std::shared_ptr<IROStorage> f_r, u64 tid);
|
||||
virtual ~LayeredRomFS() = default;
|
||||
|
||||
virtual Result Read(void *buffer, size_t size, u64 offset) override;
|
||||
virtual Result GetSize(u64 *out_size) override;
|
||||
virtual Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override;
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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 <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <malloc.h>
|
||||
|
||||
#include <switch.h>
|
||||
#include <atmosphere.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fsmitm_main.hpp"
|
||||
#include "fsmitm_service.hpp"
|
||||
|
||||
#include "../utils.hpp"
|
||||
|
||||
struct FsMitmManagerOptions {
|
||||
static const size_t PointerBufferSize = 0x800;
|
||||
static const size_t MaxDomains = 0x40;
|
||||
static const size_t MaxDomainObjects = 0x4000;
|
||||
};
|
||||
using FsMitmManager = WaitableManager<FsMitmManagerOptions>;
|
||||
|
||||
void FsMitmMain(void *arg) {
|
||||
/* Create server manager. */
|
||||
static auto s_server_manager = FsMitmManager(5);
|
||||
|
||||
/* Create fsp-srv mitm. */
|
||||
AddMitmServerToManager<FsMitmService>(&s_server_manager, "fsp-srv", 61);
|
||||
|
||||
/* Loop forever, servicing our services. */
|
||||
s_server_manager.Process();
|
||||
}
|
||||
|
||||
45
stratosphere/ams_mitm/source/fs_mitm/fsmitm_module.cpp
Normal file
45
stratosphere/ams_mitm/source/fs_mitm/fsmitm_module.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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_module.hpp"
|
||||
#include "fs_mitm_service.hpp"
|
||||
|
||||
namespace ams::mitm::fs {
|
||||
|
||||
namespace {
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
constexpr size_t MaxServers = 1;
|
||||
constexpr size_t MaxSessions = 61;
|
||||
sf::hipc::ServerManager<MaxServers, ServerOptions, MaxSessions> g_server_manager;
|
||||
|
||||
}
|
||||
|
||||
void MitmModule::ThreadFunction(void *arg) {
|
||||
/* Create fs mitm. */
|
||||
R_ASSERT(g_server_manager.RegisterMitmServer<FsMitmService>(MitmServiceName));
|
||||
|
||||
/* Loop forever, servicing our services. */
|
||||
g_server_manager.LoopProcess();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,17 +13,12 @@
|
||||
* 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 <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <malloc.h>
|
||||
|
||||
#include <switch.h>
|
||||
#include <atmosphere.h>
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "../amsmitm_module.hpp"
|
||||
|
||||
constexpr u32 FsMitmPriority = 43;
|
||||
constexpr u32 FsMitmStackSize = 0x8000;
|
||||
namespace ams::mitm::fs {
|
||||
|
||||
void FsMitmMain(void *arg);
|
||||
DEFINE_MITM_MODULE_CLASS(0x8000, 43);
|
||||
|
||||
}
|
||||
@@ -1,408 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <string.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "../utils.hpp"
|
||||
#include "fsmitm_romfsbuild.hpp"
|
||||
|
||||
#include "../debug.hpp"
|
||||
|
||||
void RomFSBuildContext::VisitDirectory(FsFileSystem *filesys, RomFSBuildDirectoryContext *parent) {
|
||||
FsDir dir;
|
||||
|
||||
std::vector<RomFSBuildDirectoryContext *> child_dirs;
|
||||
|
||||
/* Open the current parent directory. */
|
||||
R_ASSERT(Utils::OpenRomFSDir(filesys, this->title_id, parent->path, &dir));
|
||||
{
|
||||
ON_SCOPE_EXIT { fsDirClose(&dir); };
|
||||
|
||||
u64 read_entries;
|
||||
while (true) {
|
||||
R_ASSERT(fsDirRead(&dir, 0, &read_entries, 1, &this->dir_entry));
|
||||
if (read_entries != 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
AMS_ASSERT(this->dir_entry.type == ENTRYTYPE_DIR || this->dir_entry.type == ENTRYTYPE_FILE);
|
||||
if (this->dir_entry.type == ENTRYTYPE_DIR) {
|
||||
RomFSBuildDirectoryContext *child = new RomFSBuildDirectoryContext({0});
|
||||
/* Set child's path. */
|
||||
child->cur_path_ofs = parent->path_len + 1;
|
||||
child->path_len = child->cur_path_ofs + strlen(this->dir_entry.name);
|
||||
child->path = new char[child->path_len + 1];
|
||||
strcpy(child->path, parent->path);
|
||||
if (child->path_len > FS_MAX_PATH - 1) {
|
||||
fatalSimple(ResultFsTooLongPath);
|
||||
}
|
||||
strcat(child->path + parent->path_len, "/");
|
||||
strcat(child->path + parent->path_len, this->dir_entry.name);
|
||||
|
||||
if (!this->AddDirectory(parent, child, NULL)) {
|
||||
delete[] child->path;
|
||||
delete child;
|
||||
} else {
|
||||
child_dirs.push_back(child);
|
||||
}
|
||||
} else /* if (this->dir_entry.type == ENTRYTYPE_FILE) */ {
|
||||
RomFSBuildFileContext *child = new RomFSBuildFileContext({0});
|
||||
/* Set child's path. */
|
||||
child->cur_path_ofs = parent->path_len + 1;
|
||||
child->path_len = child->cur_path_ofs + strlen(this->dir_entry.name);
|
||||
child->path = new char[child->path_len + 1];
|
||||
strcpy(child->path, parent->path);
|
||||
if (child->path_len > FS_MAX_PATH - 1) {
|
||||
fatalSimple(ResultFsTooLongPath);
|
||||
}
|
||||
strcat(child->path + parent->path_len, "/");
|
||||
strcat(child->path + parent->path_len, this->dir_entry.name);
|
||||
|
||||
child->source = this->cur_source_type;
|
||||
|
||||
child->size = this->dir_entry.fileSize;
|
||||
|
||||
if (!this->AddFile(parent, child)) {
|
||||
delete[] child->path;
|
||||
delete child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &child : child_dirs) {
|
||||
this->VisitDirectory(filesys, child);
|
||||
}
|
||||
}
|
||||
|
||||
void RomFSBuildContext::MergeSdFiles() {
|
||||
FsFileSystem sd_filesystem;
|
||||
FsDir dir;
|
||||
if (!Utils::IsSdInitialized()) {
|
||||
return;
|
||||
}
|
||||
if (R_FAILED((Utils::OpenSdDirForAtmosphere(this->title_id, "/romfs", &dir)))) {
|
||||
return;
|
||||
}
|
||||
fsDirClose(&dir);
|
||||
if (R_FAILED(fsMountSdcard(&sd_filesystem))) {
|
||||
return;
|
||||
}
|
||||
this->cur_source_type = RomFSDataSource::LooseFile;
|
||||
this->VisitDirectory(&sd_filesystem, this->root);
|
||||
fsFsClose(&sd_filesystem);
|
||||
}
|
||||
|
||||
void RomFSBuildContext::VisitDirectory(RomFSBuildDirectoryContext *parent, u32 parent_offset, void *dir_table, size_t dir_table_size, void *file_table, size_t file_table_size) {
|
||||
RomFSDirectoryEntry *parent_entry = romfs_get_direntry(dir_table, parent_offset);
|
||||
if (parent_entry->file != ROMFS_ENTRY_EMPTY) {
|
||||
RomFSFileEntry *cur_file = romfs_get_fentry(file_table, parent_entry->file);
|
||||
while (cur_file != NULL) {
|
||||
RomFSBuildFileContext *child = new RomFSBuildFileContext({0});
|
||||
/* Set child's path. */
|
||||
child->cur_path_ofs = parent->path_len + 1;
|
||||
child->path_len = child->cur_path_ofs + cur_file->name_size;
|
||||
child->path = new char[child->path_len + 1];
|
||||
strcpy(child->path, parent->path);
|
||||
if (child->path_len > FS_MAX_PATH - 1) {
|
||||
fatalSimple(ResultFsTooLongPath);
|
||||
}
|
||||
strcat(child->path + parent->path_len, "/");
|
||||
strncat(child->path + parent->path_len, cur_file->name, cur_file->name_size);
|
||||
child->size = cur_file->size;
|
||||
|
||||
child->source = this->cur_source_type;
|
||||
child->orig_offset = cur_file->offset;
|
||||
if (!this->AddFile(parent, child)) {
|
||||
delete[] child->path;
|
||||
delete child;
|
||||
}
|
||||
if (cur_file->sibling == ROMFS_ENTRY_EMPTY) {
|
||||
cur_file = NULL;
|
||||
} else {
|
||||
cur_file = romfs_get_fentry(file_table, cur_file->sibling);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parent_entry->child != ROMFS_ENTRY_EMPTY) {
|
||||
RomFSDirectoryEntry *cur_child = romfs_get_direntry(dir_table, parent_entry->child);
|
||||
u32 cur_child_offset = parent_entry->child;
|
||||
while (cur_child != NULL) {
|
||||
RomFSBuildDirectoryContext *child = new RomFSBuildDirectoryContext({0});
|
||||
/* Set child's path. */
|
||||
child->cur_path_ofs = parent->path_len + 1;
|
||||
child->path_len = child->cur_path_ofs + cur_child->name_size;
|
||||
child->path = new char[child->path_len + 1];
|
||||
strcpy(child->path, parent->path);
|
||||
if (child->path_len > FS_MAX_PATH - 1) {
|
||||
fatalSimple(ResultFsTooLongPath);
|
||||
}
|
||||
strcat(child->path + parent->path_len, "/");
|
||||
strncat(child->path + parent->path_len, cur_child->name, cur_child->name_size);
|
||||
|
||||
RomFSBuildDirectoryContext *real = NULL;
|
||||
if (!this->AddDirectory(parent, child, &real)) {
|
||||
delete[] child->path;
|
||||
delete child;
|
||||
}
|
||||
if (real == NULL) {
|
||||
/* TODO: Better error. */
|
||||
fatalSimple(ResultKernelConnectionClosed);
|
||||
}
|
||||
|
||||
this->VisitDirectory(real, cur_child_offset, dir_table, dir_table_size, file_table, file_table_size);
|
||||
|
||||
if (cur_child->sibling == ROMFS_ENTRY_EMPTY) {
|
||||
cur_child = NULL;
|
||||
} else {
|
||||
cur_child_offset = cur_child->sibling;
|
||||
cur_child = romfs_get_direntry(dir_table, cur_child->sibling);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RomFSBuildContext::MergeRomStorage(IROStorage *storage, RomFSDataSource source) {
|
||||
RomFSHeader header;
|
||||
R_ASSERT(storage->Read(&header, sizeof(header), 0));
|
||||
AMS_ASSERT(header.header_size == sizeof(header));
|
||||
|
||||
/* Read tables. */
|
||||
auto dir_table = std::make_unique<u8[]>(header.dir_table_size);
|
||||
auto file_table = std::make_unique<u8[]>(header.file_table_size);
|
||||
R_ASSERT(storage->Read(dir_table.get(), header.dir_table_size, header.dir_table_ofs));
|
||||
R_ASSERT(storage->Read(file_table.get(), header.file_table_size, header.file_table_ofs));
|
||||
|
||||
this->cur_source_type = source;
|
||||
this->VisitDirectory(this->root, 0x0, dir_table.get(), (size_t)header.dir_table_size, file_table.get(), (size_t)header.file_table_size);
|
||||
}
|
||||
|
||||
bool RomFSBuildContext::AddDirectory(RomFSBuildDirectoryContext *parent_dir_ctx, RomFSBuildDirectoryContext *dir_ctx, RomFSBuildDirectoryContext **out_dir_ctx) {
|
||||
/* Check whether it's already in the known directories. */
|
||||
auto existing = this->directories.find(dir_ctx->path);
|
||||
if (existing != this->directories.end()) {
|
||||
if (out_dir_ctx) {
|
||||
*out_dir_ctx = existing->second;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Add a new directory. */
|
||||
this->num_dirs++;
|
||||
this->dir_table_size += sizeof(RomFSDirectoryEntry) + ((dir_ctx->path_len - dir_ctx->cur_path_ofs + 3) & ~3);
|
||||
dir_ctx->parent = parent_dir_ctx;
|
||||
this->directories.insert({dir_ctx->path, dir_ctx});
|
||||
|
||||
if (out_dir_ctx) {
|
||||
*out_dir_ctx = dir_ctx;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RomFSBuildContext::AddFile(RomFSBuildDirectoryContext *parent_dir_ctx, RomFSBuildFileContext *file_ctx) {
|
||||
/* Check whether it's already in the known files. */
|
||||
auto existing = this->files.find(file_ctx->path);
|
||||
if (existing != this->files.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Add a new file. */
|
||||
this->num_files++;
|
||||
this->file_table_size += sizeof(RomFSFileEntry) + ((file_ctx->path_len - file_ctx->cur_path_ofs + 3) & ~3);
|
||||
file_ctx->parent = parent_dir_ctx;
|
||||
this->files.insert({file_ctx->path, file_ctx});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RomFSBuildContext::Build(std::vector<RomFSSourceInfo> *out_infos) {
|
||||
RomFSBuildFileContext *cur_file;
|
||||
RomFSBuildDirectoryContext *cur_dir;
|
||||
u32 entry_offset;
|
||||
|
||||
u32 dir_hash_table_entry_count = romfs_get_hash_table_count(this->num_dirs);
|
||||
u32 file_hash_table_entry_count = romfs_get_hash_table_count(this->num_files);
|
||||
this->dir_hash_table_size = 4 * dir_hash_table_entry_count;
|
||||
this->file_hash_table_size = 4 * file_hash_table_entry_count;
|
||||
|
||||
/* Assign metadata pointers */
|
||||
auto *header = reinterpret_cast<RomFSHeader*>(std::malloc(sizeof(RomFSHeader)));
|
||||
*header = {};
|
||||
const size_t metadata_size = this->dir_hash_table_size + this->dir_table_size + this->file_hash_table_size + this->file_table_size;
|
||||
u8 *metadata = reinterpret_cast<u8*>(std::malloc(metadata_size));
|
||||
u32 *dir_hash_table = (u32 *)((uintptr_t)metadata);
|
||||
RomFSDirectoryEntry *dir_table = (RomFSDirectoryEntry *)((uintptr_t)dir_hash_table + this->dir_hash_table_size);
|
||||
u32 *file_hash_table = (u32 *)((uintptr_t)dir_table + this->dir_table_size);
|
||||
RomFSFileEntry *file_table = (RomFSFileEntry *)((uintptr_t)file_hash_table + this->file_hash_table_size);
|
||||
|
||||
/* Clear out hash tables. */
|
||||
for (u32 i = 0; i < dir_hash_table_entry_count; i++) {
|
||||
dir_hash_table[i] = ROMFS_ENTRY_EMPTY;
|
||||
}
|
||||
for (u32 i = 0; i < file_hash_table_entry_count; i++) {
|
||||
file_hash_table[i] = ROMFS_ENTRY_EMPTY;
|
||||
}
|
||||
|
||||
out_infos->clear();
|
||||
out_infos->emplace_back(0, sizeof(*header), header, RomFSDataSource::Memory);
|
||||
|
||||
/* Determine file offsets. */
|
||||
entry_offset = 0;
|
||||
RomFSBuildFileContext *prev_file = NULL;
|
||||
for (const auto &it : this->files) {
|
||||
cur_file = it.second;
|
||||
this->file_partition_size = (this->file_partition_size + 0xFULL) & ~0xFULL;
|
||||
/* Check for extra padding in the original romfs source and preserve it, to help ourselves later. */
|
||||
if (prev_file && prev_file->source == cur_file->source && (prev_file->source == RomFSDataSource::BaseRomFS || prev_file->source == RomFSDataSource::FileRomFS)) {
|
||||
u64 expected = (this->file_partition_size - prev_file->offset + prev_file->orig_offset);
|
||||
if (expected != cur_file->orig_offset) {
|
||||
if (expected > cur_file->orig_offset) {
|
||||
/* This case should NEVER happen. */
|
||||
/* TODO: Better error. */
|
||||
fatalSimple(ResultKernelConnectionClosed);
|
||||
}
|
||||
this->file_partition_size += cur_file->orig_offset - expected;
|
||||
}
|
||||
}
|
||||
cur_file->offset = this->file_partition_size;
|
||||
this->file_partition_size += cur_file->size;
|
||||
cur_file->entry_offset = entry_offset;
|
||||
entry_offset += sizeof(RomFSFileEntry) + ((cur_file->path_len - cur_file->cur_path_ofs + 3) & ~3);
|
||||
prev_file = cur_file;
|
||||
}
|
||||
/* Assign deferred parent/sibling ownership. */
|
||||
for (auto it = this->files.rbegin(); it != this->files.rend(); it++) {
|
||||
cur_file = it->second;
|
||||
cur_file->sibling = cur_file->parent->file;
|
||||
cur_file->parent->file = cur_file;
|
||||
}
|
||||
|
||||
/* Determine directory offsets. */
|
||||
entry_offset = 0;
|
||||
for (const auto &it : this->directories) {
|
||||
cur_dir = it.second;
|
||||
cur_dir->entry_offset = entry_offset;
|
||||
entry_offset += sizeof(RomFSDirectoryEntry) + ((cur_dir->path_len - cur_dir->cur_path_ofs + 3) & ~3);
|
||||
}
|
||||
/* Assign deferred parent/sibling ownership. */
|
||||
for (auto it = this->directories.rbegin(); it->second != this->root; it++) {
|
||||
cur_dir = it->second;
|
||||
cur_dir->sibling = cur_dir->parent->child;
|
||||
cur_dir->parent->child = cur_dir;
|
||||
}
|
||||
|
||||
|
||||
/* Populate file tables. */
|
||||
for (const auto &it : this->files) {
|
||||
cur_file = it.second;
|
||||
RomFSFileEntry *cur_entry = romfs_get_fentry(file_table, cur_file->entry_offset);
|
||||
|
||||
cur_entry->parent = cur_file->parent->entry_offset;
|
||||
cur_entry->sibling = (cur_file->sibling == NULL) ? ROMFS_ENTRY_EMPTY : cur_file->sibling->entry_offset;
|
||||
cur_entry->offset = cur_file->offset;
|
||||
cur_entry->size = cur_file->size;
|
||||
|
||||
u32 name_size = cur_file->path_len - cur_file->cur_path_ofs;
|
||||
u32 hash = romfs_calc_path_hash(cur_file->parent->entry_offset, (unsigned char *)cur_file->path + cur_file->cur_path_ofs, 0, name_size);
|
||||
cur_entry->hash = file_hash_table[hash % file_hash_table_entry_count];
|
||||
file_hash_table[hash % file_hash_table_entry_count] = cur_file->entry_offset;
|
||||
|
||||
cur_entry->name_size = name_size;
|
||||
memset(cur_entry->name, 0, (cur_entry->name_size + 3) & ~3);
|
||||
memcpy(cur_entry->name, cur_file->path + cur_file->cur_path_ofs, name_size);
|
||||
|
||||
switch (cur_file->source) {
|
||||
case RomFSDataSource::BaseRomFS:
|
||||
case RomFSDataSource::FileRomFS:
|
||||
/* Try to compact, if possible. */
|
||||
if (out_infos->back().type == cur_file->source) {
|
||||
out_infos->back().size = cur_file->offset + ROMFS_FILEPARTITION_OFS + cur_file->size - out_infos->back().virtual_offset;
|
||||
} else {
|
||||
out_infos->emplace_back(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->size, cur_file->orig_offset + ROMFS_FILEPARTITION_OFS, cur_file->source);
|
||||
}
|
||||
break;
|
||||
case RomFSDataSource::LooseFile:
|
||||
{
|
||||
char *path = new char[cur_file->path_len + 1];
|
||||
strcpy(path, cur_file->path);
|
||||
out_infos->emplace_back(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->size, path, cur_file->source);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* TODO: Better error. */
|
||||
fatalSimple(ResultKernelConnectionClosed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Populate dir tables. */
|
||||
for (const auto &it : this->directories) {
|
||||
cur_dir = it.second;
|
||||
RomFSDirectoryEntry *cur_entry = romfs_get_direntry(dir_table, cur_dir->entry_offset);
|
||||
cur_entry->parent = cur_dir == this->root ? 0 : cur_dir->parent->entry_offset;
|
||||
cur_entry->sibling = (cur_dir->sibling == NULL) ? ROMFS_ENTRY_EMPTY : cur_dir->sibling->entry_offset;
|
||||
cur_entry->child = (cur_dir->child == NULL) ? ROMFS_ENTRY_EMPTY : cur_dir->child->entry_offset;
|
||||
cur_entry->file = (cur_dir->file == NULL) ? ROMFS_ENTRY_EMPTY : cur_dir->file->entry_offset;
|
||||
|
||||
u32 name_size = cur_dir->path_len - cur_dir->cur_path_ofs;
|
||||
u32 hash = romfs_calc_path_hash(cur_dir == this->root ? 0 : cur_dir->parent->entry_offset, (unsigned char *)cur_dir->path + cur_dir->cur_path_ofs, 0, name_size);
|
||||
cur_entry->hash = dir_hash_table[hash % dir_hash_table_entry_count];
|
||||
dir_hash_table[hash % dir_hash_table_entry_count] = cur_dir->entry_offset;
|
||||
|
||||
cur_entry->name_size = name_size;
|
||||
memset(cur_entry->name, 0, (cur_entry->name_size + 3) & ~3);
|
||||
memcpy(cur_entry->name, cur_dir->path + cur_dir->cur_path_ofs, name_size);
|
||||
}
|
||||
|
||||
/* Delete directories. */
|
||||
for (const auto &it : this->directories) {
|
||||
cur_dir = it.second;
|
||||
delete[] cur_dir->path;
|
||||
delete cur_dir;
|
||||
}
|
||||
this->root = NULL;
|
||||
this->directories.clear();
|
||||
|
||||
/* Delete files. */
|
||||
for (const auto &it : this->files) {
|
||||
cur_file = it.second;
|
||||
delete[] cur_file->path;
|
||||
delete cur_file;
|
||||
}
|
||||
this->files.clear();
|
||||
|
||||
/* Set header fields. */
|
||||
header->header_size = sizeof(*header);
|
||||
header->file_hash_table_size = this->file_hash_table_size;
|
||||
header->file_table_size = this->file_table_size;
|
||||
header->dir_hash_table_size = this->dir_hash_table_size;
|
||||
header->dir_table_size = this->dir_table_size;
|
||||
header->file_partition_ofs = ROMFS_FILEPARTITION_OFS;
|
||||
header->dir_hash_table_ofs = (header->file_partition_ofs + this->file_partition_size + 3ULL) & ~3ULL;
|
||||
header->dir_table_ofs = header->dir_hash_table_ofs + header->dir_hash_table_size;
|
||||
header->file_hash_table_ofs = header->dir_table_ofs + header->dir_table_size;
|
||||
header->file_table_ofs = header->file_hash_table_ofs + header->file_hash_table_size;
|
||||
|
||||
/* Try to save metadata to the SD card, to save on memory space. */
|
||||
if (R_SUCCEEDED(Utils::SaveSdFileForAtmosphere(this->title_id, ROMFS_METADATA_FILE_PATH, metadata, metadata_size))) {
|
||||
out_infos->emplace_back(header->dir_hash_table_ofs, metadata_size, RomFSDataSource::MetaData);
|
||||
std::free(metadata);
|
||||
} else {
|
||||
out_infos->emplace_back(header->dir_hash_table_ofs, metadata_size, metadata, RomFSDataSource::Memory);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
/*
|
||||
* 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 <cstdlib>
|
||||
#include <switch.h>
|
||||
#include <map>
|
||||
|
||||
#include "../debug.hpp"
|
||||
#include "fs_istorage.hpp"
|
||||
|
||||
#define ROMFS_ENTRY_EMPTY 0xFFFFFFFF
|
||||
#define ROMFS_FILEPARTITION_OFS 0x200
|
||||
|
||||
#define ROMFS_METADATA_FILE_PATH "romfs_metadata.bin"
|
||||
|
||||
/* Types for RomFS Meta construction. */
|
||||
enum class RomFSDataSource {
|
||||
BaseRomFS,
|
||||
FileRomFS,
|
||||
LooseFile,
|
||||
MetaData,
|
||||
Memory,
|
||||
};
|
||||
|
||||
struct RomFSBaseSourceInfo {
|
||||
u64 offset;
|
||||
};
|
||||
|
||||
struct RomFSFileSourceInfo {
|
||||
u64 offset;
|
||||
};
|
||||
|
||||
struct RomFSLooseSourceInfo {
|
||||
const char *path;
|
||||
};
|
||||
|
||||
struct RomFSMemorySourceInfo {
|
||||
const u8 *data;
|
||||
};
|
||||
|
||||
struct RomFSMetaDataSourceInfo {
|
||||
|
||||
};
|
||||
|
||||
struct RomFSSourceInfo {
|
||||
u64 virtual_offset;
|
||||
u64 size;
|
||||
union {
|
||||
RomFSBaseSourceInfo base_source_info;
|
||||
RomFSFileSourceInfo file_source_info;
|
||||
RomFSLooseSourceInfo loose_source_info;
|
||||
RomFSMemorySourceInfo memory_source_info;
|
||||
RomFSMetaDataSourceInfo metadata_source_info;
|
||||
};
|
||||
RomFSDataSource type;
|
||||
|
||||
RomFSSourceInfo(u64 v_o, u64 s, u64 offset, RomFSDataSource t) : virtual_offset(v_o), size(s), type(t) {
|
||||
switch (this->type) {
|
||||
case RomFSDataSource::BaseRomFS:
|
||||
this->base_source_info.offset = offset;
|
||||
break;
|
||||
case RomFSDataSource::FileRomFS:
|
||||
this->file_source_info.offset = offset;
|
||||
break;
|
||||
case RomFSDataSource::LooseFile:
|
||||
case RomFSDataSource::MetaData:
|
||||
case RomFSDataSource::Memory:
|
||||
default:
|
||||
/* TODO: Better error. */
|
||||
fatalSimple(ResultKernelConnectionClosed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RomFSSourceInfo(u64 v_o, u64 s, const void *arg, RomFSDataSource t) : virtual_offset(v_o), size(s), type(t) {
|
||||
switch (this->type) {
|
||||
case RomFSDataSource::LooseFile:
|
||||
this->loose_source_info.path = (decltype(this->loose_source_info.path))arg;
|
||||
break;
|
||||
case RomFSDataSource::Memory:
|
||||
this->memory_source_info.data = (decltype(this->memory_source_info.data))arg;
|
||||
break;
|
||||
case RomFSDataSource::MetaData:
|
||||
case RomFSDataSource::BaseRomFS:
|
||||
case RomFSDataSource::FileRomFS:
|
||||
default:
|
||||
/* TODO: Better error. */
|
||||
fatalSimple(ResultKernelConnectionClosed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RomFSSourceInfo(u64 v_o, u64 s, RomFSDataSource t) : virtual_offset(v_o), size(s), type(t) {
|
||||
switch (this->type) {
|
||||
case RomFSDataSource::MetaData:
|
||||
break;
|
||||
case RomFSDataSource::LooseFile:
|
||||
case RomFSDataSource::Memory:
|
||||
case RomFSDataSource::BaseRomFS:
|
||||
case RomFSDataSource::FileRomFS:
|
||||
default:
|
||||
/* TODO: Better error. */
|
||||
fatalSimple(ResultKernelConnectionClosed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Cleanup() {
|
||||
switch (this->type) {
|
||||
case RomFSDataSource::BaseRomFS:
|
||||
case RomFSDataSource::FileRomFS:
|
||||
case RomFSDataSource::MetaData:
|
||||
break;
|
||||
case RomFSDataSource::LooseFile:
|
||||
delete[] this->loose_source_info.path;
|
||||
break;
|
||||
case RomFSDataSource::Memory:
|
||||
std::free((void*)this->memory_source_info.data);
|
||||
break;
|
||||
default:
|
||||
/* TODO: Better error. */
|
||||
fatalSimple(ResultKernelConnectionClosed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool Compare(RomFSSourceInfo *a, RomFSSourceInfo *b) {
|
||||
return (a->virtual_offset < b->virtual_offset);
|
||||
}
|
||||
};
|
||||
|
||||
/* Types for building a RomFS. */
|
||||
struct RomFSHeader {
|
||||
u64 header_size;
|
||||
u64 dir_hash_table_ofs;
|
||||
u64 dir_hash_table_size;
|
||||
u64 dir_table_ofs;
|
||||
u64 dir_table_size;
|
||||
u64 file_hash_table_ofs;
|
||||
u64 file_hash_table_size;
|
||||
u64 file_table_ofs;
|
||||
u64 file_table_size;
|
||||
u64 file_partition_ofs;
|
||||
};
|
||||
|
||||
static_assert(sizeof(RomFSHeader) == 0x50, "Incorrect RomFS Header definition!");
|
||||
|
||||
struct RomFSDirectoryEntry {
|
||||
u32 parent;
|
||||
u32 sibling;
|
||||
u32 child;
|
||||
u32 file;
|
||||
u32 hash;
|
||||
u32 name_size;
|
||||
char name[];
|
||||
};
|
||||
|
||||
static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "Incorrect RomFSDirectoryEntry definition!");
|
||||
|
||||
struct RomFSFileEntry {
|
||||
u32 parent;
|
||||
u32 sibling;
|
||||
u64 offset;
|
||||
u64 size;
|
||||
u32 hash;
|
||||
u32 name_size;
|
||||
char name[];
|
||||
};
|
||||
|
||||
static_assert(sizeof(RomFSFileEntry) == 0x20, "Incorrect RomFSFileEntry definition!");
|
||||
|
||||
struct RomFSBuildFileContext;
|
||||
|
||||
/* Used as comparator for std::map<char *, RomFSBuild*Context> */
|
||||
struct build_ctx_cmp {
|
||||
bool operator()(const char *a, const char *b) const {
|
||||
return strcmp(a, b) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct RomFSBuildDirectoryContext {
|
||||
char *path;
|
||||
u32 cur_path_ofs;
|
||||
u32 path_len;
|
||||
u32 entry_offset = 0;
|
||||
RomFSBuildDirectoryContext *parent = NULL;
|
||||
RomFSBuildDirectoryContext *child = NULL;
|
||||
RomFSBuildDirectoryContext *sibling = NULL;
|
||||
RomFSBuildFileContext *file = NULL;
|
||||
};
|
||||
|
||||
struct RomFSBuildFileContext {
|
||||
char *path;
|
||||
u32 cur_path_ofs;
|
||||
u32 path_len;
|
||||
u32 entry_offset = 0;
|
||||
u64 offset = 0;
|
||||
u64 size = 0;
|
||||
RomFSBuildDirectoryContext *parent = NULL;
|
||||
RomFSBuildFileContext *sibling = NULL;
|
||||
RomFSDataSource source{0};
|
||||
u64 orig_offset = 0;
|
||||
};
|
||||
|
||||
class RomFSBuildContext {
|
||||
private:
|
||||
u64 title_id;
|
||||
RomFSBuildDirectoryContext *root;
|
||||
std::map<char *, RomFSBuildDirectoryContext *, build_ctx_cmp> directories;
|
||||
std::map<char *, RomFSBuildFileContext *, build_ctx_cmp> files;
|
||||
u64 num_dirs = 0;
|
||||
u64 num_files = 0;
|
||||
u64 dir_table_size = 0;
|
||||
u64 file_table_size = 0;
|
||||
u64 dir_hash_table_size = 0;
|
||||
u64 file_hash_table_size = 0;
|
||||
u64 file_partition_size = 0;
|
||||
|
||||
FsDirectoryEntry dir_entry;
|
||||
RomFSDataSource cur_source_type;
|
||||
|
||||
void VisitDirectory(FsFileSystem *filesys, RomFSBuildDirectoryContext *parent);
|
||||
void VisitDirectory(RomFSBuildDirectoryContext *parent, u32 parent_offset, void *dir_table, size_t dir_table_size, void *file_table, size_t file_table_size);
|
||||
|
||||
bool AddDirectory(RomFSBuildDirectoryContext *parent_dir_ctx, RomFSBuildDirectoryContext *dir_ctx, RomFSBuildDirectoryContext **out_dir_ctx);
|
||||
bool AddFile(RomFSBuildDirectoryContext *parent_dir_ctx, RomFSBuildFileContext *file_ctx);
|
||||
public:
|
||||
RomFSBuildContext(u64 tid) : title_id(tid) {
|
||||
this->root = new RomFSBuildDirectoryContext({0});
|
||||
this->root->path = new char[1];
|
||||
this->root->path[0] = '\x00';
|
||||
this->directories.insert({this->root->path, this->root});
|
||||
this->num_dirs = 1;
|
||||
this->dir_table_size = 0x18;
|
||||
}
|
||||
|
||||
void MergeSdFiles();
|
||||
void MergeRomStorage(IROStorage *storage, RomFSDataSource source);
|
||||
|
||||
/* This finalizes the context. */
|
||||
void Build(std::vector<RomFSSourceInfo> *out_infos);
|
||||
};
|
||||
|
||||
|
||||
static inline RomFSDirectoryEntry *romfs_get_direntry(void *directories, uint32_t offset) {
|
||||
return (RomFSDirectoryEntry *)((uintptr_t)directories + offset);
|
||||
}
|
||||
|
||||
static inline RomFSFileEntry *romfs_get_fentry(void *files, uint32_t offset) {
|
||||
return (RomFSFileEntry *)((uintptr_t)files + offset);
|
||||
}
|
||||
|
||||
static inline uint32_t romfs_calc_path_hash(uint32_t parent, const unsigned char *path, uint32_t start, size_t path_len) {
|
||||
uint32_t hash = parent ^ 123456789;
|
||||
for (uint32_t i = 0; i < path_len; i++) {
|
||||
hash = (hash >> 5) | (hash << 27);
|
||||
hash ^= path[start + i];
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static inline uint32_t romfs_get_hash_table_count(uint32_t num_entries) {
|
||||
if (num_entries < 3) {
|
||||
return 3;
|
||||
} else if (num_entries < 19) {
|
||||
return num_entries | 1;
|
||||
}
|
||||
uint32_t count = num_entries;
|
||||
while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 || count % 11 == 0 || count % 13 == 0 || count % 17 == 0) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "fs_istorage.hpp"
|
||||
|
||||
/* Represents a RomFS stored in some file. */
|
||||
class RomFileStorage : public IROStorage {
|
||||
private:
|
||||
FsFile *base_file;
|
||||
public:
|
||||
RomFileStorage(FsFile *f) : base_file(f) {
|
||||
/* ... */
|
||||
};
|
||||
RomFileStorage(FsFile f) {
|
||||
this->base_file = new FsFile(f);
|
||||
};
|
||||
virtual ~RomFileStorage() {
|
||||
fsFileClose(base_file);
|
||||
delete base_file;
|
||||
};
|
||||
public:
|
||||
Result Read(void *buffer, size_t size, u64 offset) override {
|
||||
size_t out_sz = 0;
|
||||
R_TRY(fsFileRead(this->base_file, offset, buffer, size, FS_READOPTION_NONE, &out_sz));
|
||||
if (out_sz != size && out_sz) {
|
||||
R_TRY(this->Read((void *)((uintptr_t)buffer + out_sz), size - out_sz, offset + out_sz));
|
||||
}
|
||||
return ResultSuccess();
|
||||
};
|
||||
Result GetSize(u64 *out_size) override {
|
||||
return fsFileGetSize(this->base_file, out_size);
|
||||
};
|
||||
Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
|
||||
/* TODO: Merge into libnx? */
|
||||
return fsFileOperateRange(this->base_file, operation_type, offset, size, out_range_info);
|
||||
};
|
||||
};
|
||||
|
||||
/* Represents a RomFS accessed via some IStorage. */
|
||||
using RomInterfaceStorage = ROProxyStorage;
|
||||
@@ -1,386 +0,0 @@
|
||||
/*
|
||||
* 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 <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "fsmitm_service.hpp"
|
||||
#include "fs_shim.h"
|
||||
|
||||
#include "../utils.hpp"
|
||||
#include "fsmitm_boot0storage.hpp"
|
||||
#include "fsmitm_layeredrom.hpp"
|
||||
|
||||
#include "fs_dir_utils.hpp"
|
||||
#include "fs_save_utils.hpp"
|
||||
#include "fs_file_storage.hpp"
|
||||
#include "fs_subdirectory_filesystem.hpp"
|
||||
#include "fs_directory_redirection_filesystem.hpp"
|
||||
#include "fs_directory_savedata_filesystem.hpp"
|
||||
|
||||
#include "../debug.hpp"
|
||||
|
||||
static ams::os::Mutex g_StorageCacheLock;
|
||||
static std::unordered_map<u64, std::weak_ptr<IStorageInterface>> g_StorageCache;
|
||||
|
||||
static bool StorageCacheGetEntry(ams::ncm::TitleId title_id, std::shared_ptr<IStorageInterface> *out) {
|
||||
std::scoped_lock lock(g_StorageCacheLock);
|
||||
if (g_StorageCache.find(static_cast<u64>(title_id)) == g_StorageCache.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto intf = g_StorageCache[static_cast<u64>(title_id)].lock();
|
||||
if (intf != nullptr) {
|
||||
*out = intf;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void StorageCacheSetEntry(ams::ncm::TitleId title_id, std::shared_ptr<IStorageInterface> *ptr) {
|
||||
std::scoped_lock lock(g_StorageCacheLock);
|
||||
|
||||
/* Ensure we always use the cached copy if present. */
|
||||
if (g_StorageCache.find(static_cast<u64>(title_id)) != g_StorageCache.end()) {
|
||||
auto intf = g_StorageCache[static_cast<u64>(title_id)].lock();
|
||||
if (intf != nullptr) {
|
||||
*ptr = intf;
|
||||
}
|
||||
}
|
||||
|
||||
g_StorageCache[static_cast<u64>(title_id)] = *ptr;
|
||||
}
|
||||
|
||||
void FsMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) {
|
||||
auto this_ptr = static_cast<FsMitmService *>(obj);
|
||||
switch (static_cast<CommandId>(ctx->cmd_id)) {
|
||||
case CommandId::SetCurrentProcess:
|
||||
if (R_SUCCEEDED(ctx->rc)) {
|
||||
this_ptr->has_initialized = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Result FsMitmService::OpenHblWebContentFileSystem(Out<std::shared_ptr<IFileSystemInterface>> &out_fs) {
|
||||
/* Mount the SD card using fs.mitm's session. */
|
||||
FsFileSystem sd_fs;
|
||||
R_TRY(fsMountSdcard(&sd_fs));
|
||||
|
||||
/* Set output filesystem. */
|
||||
std::unique_ptr<IFileSystem> web_ifs = std::make_unique<SubDirectoryFileSystem>(std::make_shared<ProxyFileSystem>(sd_fs), AtmosphereHblWebContentDir);
|
||||
out_fs.SetValue(std::make_shared<IFileSystemInterface>(std::move(web_ifs)));
|
||||
if (out_fs.IsDomain()) {
|
||||
out_fs.ChangeObjectId(sd_fs.s.object_id);
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FsMitmService::OpenFileSystemWithPatch(Out<std::shared_ptr<IFileSystemInterface>> out_fs, u64 title_id, u32 filesystem_type) {
|
||||
/* Check for eligibility. */
|
||||
{
|
||||
FsDir d;
|
||||
if (!Utils::IsWebAppletTid(static_cast<u64>(this->title_id)) || filesystem_type != FsFileSystemType_ContentManual || !Utils::IsHblTid(title_id) ||
|
||||
R_FAILED(Utils::OpenSdDir(AtmosphereHblWebContentDir, &d))) {
|
||||
return ResultAtmosphereMitmShouldForwardToSession;
|
||||
}
|
||||
fsDirClose(&d);
|
||||
}
|
||||
|
||||
/* If there's an existing filesystem, don't override. */
|
||||
/* TODO: Multiplex, overriding existing content with HBL content. */
|
||||
{
|
||||
FsFileSystem fs;
|
||||
if (R_SUCCEEDED(fsOpenFileSystemWithPatchFwd(this->forward_service.get(), &fs, title_id, static_cast<FsFileSystemType>(filesystem_type)))) {
|
||||
fsFsClose(&fs);
|
||||
return ResultAtmosphereMitmShouldForwardToSession;
|
||||
}
|
||||
}
|
||||
|
||||
return this->OpenHblWebContentFileSystem(out_fs);
|
||||
}
|
||||
|
||||
Result FsMitmService::OpenFileSystemWithId(Out<std::shared_ptr<IFileSystemInterface>> out_fs, InPointer<char> path, u64 title_id, u32 filesystem_type) {
|
||||
/* Check for eligibility. */
|
||||
{
|
||||
FsDir d;
|
||||
if (!Utils::IsWebAppletTid(static_cast<u64>(this->title_id)) || filesystem_type != FsFileSystemType_ContentManual || !Utils::IsHblTid(title_id) ||
|
||||
R_FAILED(Utils::OpenSdDir(AtmosphereHblWebContentDir, &d))) {
|
||||
return ResultAtmosphereMitmShouldForwardToSession;
|
||||
}
|
||||
fsDirClose(&d);
|
||||
}
|
||||
|
||||
/* If there's an existing filesystem, don't override. */
|
||||
/* TODO: Multiplex, overriding existing content with HBL content. */
|
||||
{
|
||||
FsFileSystem fs;
|
||||
if (R_SUCCEEDED(fsOpenFileSystemWithIdFwd(this->forward_service.get(), &fs, title_id, static_cast<FsFileSystemType>(filesystem_type), path.pointer))) {
|
||||
fsFsClose(&fs);
|
||||
return ResultAtmosphereMitmShouldForwardToSession;
|
||||
}
|
||||
}
|
||||
|
||||
return this->OpenHblWebContentFileSystem(out_fs);
|
||||
}
|
||||
|
||||
Result FsMitmService::OpenSdCardFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out_fs) {
|
||||
/* We only care about redirecting this for NS/Emummc. */
|
||||
if (this->title_id != ams::ncm::TitleId::Ns) {
|
||||
return ResultAtmosphereMitmShouldForwardToSession;
|
||||
}
|
||||
if (!IsEmummc()) {
|
||||
return ResultAtmosphereMitmShouldForwardToSession;
|
||||
}
|
||||
|
||||
/* Mount the SD card. */
|
||||
FsFileSystem sd_fs;
|
||||
R_TRY(fsMountSdcard(&sd_fs));
|
||||
|
||||
/* Set output filesystem. */
|
||||
std::unique_ptr<IFileSystem> redir_fs = std::make_unique<DirectoryRedirectionFileSystem>(new ProxyFileSystem(sd_fs), "/Nintendo", GetEmummcNintendoDirPath());
|
||||
out_fs.SetValue(std::make_shared<IFileSystemInterface>(std::move(redir_fs)));
|
||||
if (out_fs.IsDomain()) {
|
||||
out_fs.ChangeObjectId(sd_fs.s.object_id);
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FsMitmService::OpenSaveDataFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out_fs, u8 space_id, FsSave save_struct) {
|
||||
bool should_redirect_saves = false;
|
||||
const bool has_redirect_save_flags = Utils::HasFlag(static_cast<u64>(this->title_id), "redirect_save");
|
||||
if (R_FAILED(Utils::GetSettingsItemBooleanValue("atmosphere", "fsmitm_redirect_saves_to_sd", &should_redirect_saves))) {
|
||||
return ResultAtmosphereMitmShouldForwardToSession;
|
||||
}
|
||||
|
||||
/* For now, until we're sure this is robust, only intercept normal savedata , check if flag exist*/
|
||||
if (!has_redirect_save_flags || !should_redirect_saves || save_struct.saveDataType != FsSaveDataType_SaveData) {
|
||||
return ResultAtmosphereMitmShouldForwardToSession;
|
||||
}
|
||||
|
||||
/* Verify we can open the save. */
|
||||
FsFileSystem save_fs;
|
||||
if (R_FAILED(fsOpenSaveDataFileSystemFwd(this->forward_service.get(), &save_fs, space_id, &save_struct))) {
|
||||
return ResultAtmosphereMitmShouldForwardToSession;
|
||||
}
|
||||
std::unique_ptr<IFileSystem> save_ifs = std::make_unique<ProxyFileSystem>(save_fs);
|
||||
|
||||
{
|
||||
/* Mount the SD card using fs.mitm's session. */
|
||||
FsFileSystem sd_fs;
|
||||
R_TRY(fsMountSdcard(&sd_fs));
|
||||
std::shared_ptr<IFileSystem> sd_ifs = std::make_shared<ProxyFileSystem>(sd_fs);
|
||||
|
||||
|
||||
/* Verify that we can open the save directory, and that it exists. */
|
||||
const u64 target_tid = save_struct.titleID == 0 ? static_cast<u64>(this->title_id) : save_struct.titleID;
|
||||
FsPath save_dir_path;
|
||||
R_TRY(FsSaveUtils::GetSaveDataDirectoryPath(save_dir_path, space_id, save_struct.saveDataType, target_tid, save_struct.userID, save_struct.saveID));
|
||||
|
||||
/* Check if this is the first time we're making the save. */
|
||||
bool is_new_save = false;
|
||||
{
|
||||
DirectoryEntryType ent;
|
||||
if (sd_ifs->GetEntryType(&ent, save_dir_path) == ResultFsPathNotFound) {
|
||||
is_new_save = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure the directory exists. */
|
||||
R_TRY(FsDirUtils::EnsureDirectoryExists(sd_ifs.get(), save_dir_path));
|
||||
|
||||
std::shared_ptr<DirectorySaveDataFileSystem> dirsave_ifs = std::make_shared<DirectorySaveDataFileSystem>(new SubDirectoryFileSystem(sd_ifs, save_dir_path.str), std::move(save_ifs));
|
||||
|
||||
/* If it's the first time we're making the save, copy existing savedata over. */
|
||||
if (is_new_save) {
|
||||
/* TODO: Check error? */
|
||||
dirsave_ifs->CopySaveFromProxy();
|
||||
}
|
||||
|
||||
out_fs.SetValue(std::make_shared<IFileSystemInterface>(static_cast<std::shared_ptr<IFileSystem>>(dirsave_ifs)));
|
||||
if (out_fs.IsDomain()) {
|
||||
out_fs.ChangeObjectId(sd_fs.s.object_id);
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
/* Gate access to the BIS partitions. */
|
||||
Result FsMitmService::OpenBisStorage(Out<std::shared_ptr<IStorageInterface>> out_storage, u32 _bis_partition_id) {
|
||||
const FsBisStorageId bis_partition_id = static_cast<FsBisStorageId>(_bis_partition_id);
|
||||
|
||||
/* Try to open a storage for the partition. */
|
||||
FsStorage bis_storage;
|
||||
R_TRY(fsOpenBisStorageFwd(this->forward_service.get(), &bis_storage, bis_partition_id));
|
||||
|
||||
const bool is_sysmodule = ams::ncm::IsSystemTitleId(this->title_id);
|
||||
const bool has_bis_write_flag = Utils::HasFlag(static_cast<u64>(this->title_id), "bis_write");
|
||||
const bool has_cal0_read_flag = Utils::HasFlag(static_cast<u64>(this->title_id), "cal_read");
|
||||
|
||||
/* Set output storage. */
|
||||
if (bis_partition_id == FsBisStorageId_Boot0) {
|
||||
out_storage.SetValue(std::make_shared<IStorageInterface>(new Boot0Storage(bis_storage, this->title_id)));
|
||||
} else if (bis_partition_id == FsBisStorageId_CalibrationBinary) {
|
||||
/* PRODINFO should *never* be writable. */
|
||||
if (is_sysmodule || has_cal0_read_flag) {
|
||||
out_storage.SetValue(std::make_shared<IStorageInterface>(new ReadOnlyStorageAdapter(new ProxyStorage(bis_storage))));
|
||||
} else {
|
||||
/* Do not allow non-sysmodules to read *or* write CAL0. */
|
||||
fsStorageClose(&bis_storage);
|
||||
return ResultFsPermissionDenied;
|
||||
}
|
||||
} else {
|
||||
if (is_sysmodule || has_bis_write_flag) {
|
||||
/* Sysmodules should still be allowed to read and write. */
|
||||
out_storage.SetValue(std::make_shared<IStorageInterface>(new ProxyStorage(bis_storage)));
|
||||
} else if (Utils::IsHblTid(static_cast<u64>(this->title_id)) &&
|
||||
((FsBisStorageId_BootConfigAndPackage2NormalMain <= bis_partition_id && bis_partition_id <= FsBisStorageId_BootConfigAndPackage2RepairSub) ||
|
||||
bis_partition_id == FsBisStorageId_Boot1)) {
|
||||
/* 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 :/ */
|
||||
out_storage.SetValue(std::make_shared<IStorageInterface>(new ProxyStorage(bis_storage)));
|
||||
} else {
|
||||
/* Non-sysmodules should be allowed to read. */
|
||||
out_storage.SetValue(std::make_shared<IStorageInterface>(new ReadOnlyStorageAdapter(new ProxyStorage(bis_storage))));
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy domain id. */
|
||||
if (out_storage.IsDomain()) {
|
||||
out_storage.ChangeObjectId(bis_storage.s.object_id);
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Add redirection for RomFS to the SD card. */
|
||||
Result FsMitmService::OpenDataStorageByCurrentProcess(Out<std::shared_ptr<IStorageInterface>> out_storage) {
|
||||
if (!this->should_override_contents) {
|
||||
return ResultAtmosphereMitmShouldForwardToSession;
|
||||
}
|
||||
|
||||
/* If we don't have anything to modify, there's no sense in maintaining a copy of the metadata tables. */
|
||||
if (!Utils::HasSdRomfsContent(static_cast<u64>(this->title_id))) {
|
||||
return ResultAtmosphereMitmShouldForwardToSession;
|
||||
}
|
||||
|
||||
/* Try to get from the cache. */
|
||||
{
|
||||
std::shared_ptr<IStorageInterface> cached_storage = nullptr;
|
||||
bool has_cache = StorageCacheGetEntry(this->title_id, &cached_storage);
|
||||
|
||||
if (has_cache) {
|
||||
if (out_storage.IsDomain()) {
|
||||
/* TODO: Don't leak object id? */
|
||||
FsStorage s = {0};
|
||||
R_TRY(fsOpenDataStorageByCurrentProcessFwd(this->forward_service.get(), &s));
|
||||
out_storage.ChangeObjectId(s.s.object_id);
|
||||
}
|
||||
out_storage.SetValue(std::move(cached_storage));
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to open process romfs. */
|
||||
FsStorage data_storage;
|
||||
R_TRY(fsOpenDataStorageByCurrentProcessFwd(this->forward_service.get(), &data_storage));
|
||||
|
||||
/* Make new layered romfs, cacheing to storage. */
|
||||
{
|
||||
std::shared_ptr<IStorageInterface> storage_to_cache = nullptr;
|
||||
/* TODO: Is there a sensible path that ends in ".romfs" we can use?" */
|
||||
FsFile data_file;
|
||||
if (R_SUCCEEDED(Utils::OpenSdFileForAtmosphere(static_cast<u64>(this->title_id), "romfs.bin", FS_OPEN_READ, &data_file))) {
|
||||
storage_to_cache = std::make_shared<IStorageInterface>(new LayeredRomFS(std::make_shared<ReadOnlyStorageAdapter>(new ProxyStorage(data_storage)), std::make_shared<ReadOnlyStorageAdapter>(new FileStorage(new ProxyFile(data_file))), static_cast<u64>(this->title_id)));
|
||||
} else {
|
||||
storage_to_cache = std::make_shared<IStorageInterface>(new LayeredRomFS(std::make_shared<ReadOnlyStorageAdapter>(new ProxyStorage(data_storage)), nullptr, static_cast<u64>(this->title_id)));
|
||||
}
|
||||
|
||||
StorageCacheSetEntry(this->title_id, &storage_to_cache);
|
||||
|
||||
out_storage.SetValue(std::move(storage_to_cache));
|
||||
if (out_storage.IsDomain()) {
|
||||
out_storage.ChangeObjectId(data_storage.s.object_id);
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Add redirection for System Data Archives to the SD card. */
|
||||
Result FsMitmService::OpenDataStorageByDataId(Out<std::shared_ptr<IStorageInterface>> out_storage, u64 data_id, u8 sid) {
|
||||
if (!this->should_override_contents) {
|
||||
return ResultAtmosphereMitmShouldForwardToSession;
|
||||
}
|
||||
|
||||
/* If we don't have anything to modify, there's no sense in maintaining a copy of the metadata tables. */
|
||||
if (!Utils::HasSdRomfsContent(data_id)) {
|
||||
return ResultAtmosphereMitmShouldForwardToSession;
|
||||
}
|
||||
|
||||
FsStorageId storage_id = static_cast<FsStorageId>(sid);
|
||||
|
||||
/* Try to get from the cache. */
|
||||
{
|
||||
std::shared_ptr<IStorageInterface> cached_storage = nullptr;
|
||||
bool has_cache = StorageCacheGetEntry(ams::ncm::TitleId{data_id}, &cached_storage);
|
||||
|
||||
if (has_cache) {
|
||||
if (out_storage.IsDomain()) {
|
||||
/* TODO: Don't leak object id? */
|
||||
FsStorage s = {0};
|
||||
R_TRY(fsOpenDataStorageByDataIdFwd(this->forward_service.get(), storage_id, data_id, &s));
|
||||
out_storage.ChangeObjectId(s.s.object_id);
|
||||
}
|
||||
out_storage.SetValue(std::move(cached_storage));
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to open data storage. */
|
||||
FsStorage data_storage;
|
||||
R_TRY(fsOpenDataStorageByDataIdFwd(this->forward_service.get(), storage_id, data_id, &data_storage));
|
||||
|
||||
/* Make new layered romfs, cacheing to storage. */
|
||||
{
|
||||
std::shared_ptr<IStorageInterface> storage_to_cache = nullptr;
|
||||
/* TODO: Is there a sensible path that ends in ".romfs" we can use?" */
|
||||
FsFile data_file;
|
||||
if (R_SUCCEEDED(Utils::OpenSdFileForAtmosphere(data_id, "romfs.bin", FS_OPEN_READ, &data_file))) {
|
||||
storage_to_cache = std::make_shared<IStorageInterface>(new LayeredRomFS(std::make_shared<ReadOnlyStorageAdapter>(new ProxyStorage(data_storage)), std::make_shared<ReadOnlyStorageAdapter>(new FileStorage(new ProxyFile(data_file))), data_id));
|
||||
} else {
|
||||
storage_to_cache = std::make_shared<IStorageInterface>(new LayeredRomFS(std::make_shared<ReadOnlyStorageAdapter>(new ProxyStorage(data_storage)), nullptr, data_id));
|
||||
}
|
||||
|
||||
StorageCacheSetEntry(ams::ncm::TitleId{data_id}, &storage_to_cache);
|
||||
|
||||
out_storage.SetValue(std::move(storage_to_cache));
|
||||
if (out_storage.IsDomain()) {
|
||||
out_storage.ChangeObjectId(data_storage.s.object_id);
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_istorage.hpp"
|
||||
#include "fs_ifilesystem.hpp"
|
||||
#include "../utils.hpp"
|
||||
|
||||
class FsMitmService : public IMitmServiceObject {
|
||||
private:
|
||||
enum class CommandId {
|
||||
OpenFileSystemDeprecated = 0,
|
||||
|
||||
SetCurrentProcess = 1,
|
||||
OpenFileSystemWithPatch = 7,
|
||||
OpenFileSystemWithId = 8,
|
||||
|
||||
OpenSdCardFileSystem = 18,
|
||||
|
||||
OpenSaveDataFileSystem = 51,
|
||||
|
||||
OpenBisStorage = 12,
|
||||
OpenDataStorageByCurrentProcess = 200,
|
||||
OpenDataStorageByDataId = 202,
|
||||
};
|
||||
private:
|
||||
static constexpr const char *AtmosphereHblWebContentDir = "/atmosphere/hbl_html";
|
||||
private:
|
||||
bool has_initialized = false;
|
||||
bool should_override_contents;
|
||||
public:
|
||||
FsMitmService(std::shared_ptr<Service> s, u64 pid, ams::ncm::TitleId tid) : IMitmServiceObject(s, pid, tid) {
|
||||
if (Utils::HasSdDisableMitMFlag(static_cast<u64>(this->title_id))) {
|
||||
this->should_override_contents = false;
|
||||
} else {
|
||||
this->should_override_contents = (this->title_id >= ams::ncm::TitleId::ApplicationStart || Utils::HasSdMitMFlag(static_cast<u64>(this->title_id))) && Utils::HasOverrideButton(static_cast<u64>(this->title_id));
|
||||
}
|
||||
}
|
||||
|
||||
static bool ShouldMitm(u64 pid, ams::ncm::TitleId tid) {
|
||||
/* Don't Mitm KIPs */
|
||||
if (pid < 0x50) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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. */
|
||||
if (tid == ams::ncm::TitleId::AppletQlaunch || tid == ams::ncm::TitleId::AppletMaintenanceMenu) {
|
||||
has_launched_qlaunch = true;
|
||||
}
|
||||
|
||||
return has_launched_qlaunch || tid == ams::ncm::TitleId::Ns || tid >= ams::ncm::TitleId::ApplicationStart || Utils::HasSdMitMFlag(static_cast<u64>(tid));
|
||||
}
|
||||
|
||||
static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
|
||||
private:
|
||||
Result OpenHblWebContentFileSystem(Out<std::shared_ptr<IFileSystemInterface>> &out);
|
||||
protected:
|
||||
/* Overridden commands. */
|
||||
Result OpenFileSystemWithPatch(Out<std::shared_ptr<IFileSystemInterface>> out, u64 title_id, u32 filesystem_type);
|
||||
Result OpenFileSystemWithId(Out<std::shared_ptr<IFileSystemInterface>> out, InPointer<char> path, u64 title_id, u32 filesystem_type);
|
||||
Result OpenSdCardFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out);
|
||||
Result OpenSaveDataFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out, u8 space_id, FsSave save_struct);
|
||||
Result OpenBisStorage(Out<std::shared_ptr<IStorageInterface>> out, u32 bis_partition_id);
|
||||
Result OpenDataStorageByCurrentProcess(Out<std::shared_ptr<IStorageInterface>> out);
|
||||
Result OpenDataStorageByDataId(Out<std::shared_ptr<IStorageInterface>> out, u64 data_id, u8 storage_id);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
/* TODO MAKE_SERVICE_COMMAND_META(FsMitmService, OpenFileSystemDeprecated), */
|
||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenFileSystemWithPatch, FirmwareVersion_200),
|
||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenFileSystemWithId, FirmwareVersion_200),
|
||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenSdCardFileSystem),
|
||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenSaveDataFileSystem),
|
||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenBisStorage),
|
||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenDataStorageByCurrentProcess),
|
||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenDataStorageByDataId),
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user