Browse Source
Merge pull request #123 from bunnei/fs
Merge pull request #123 from bunnei/fs
Initial implementation of RomFS filesystem and fsp-srvnce_cpp
committed by
GitHub
25 changed files with 535 additions and 947 deletions
-
24src/core/CMakeLists.txt
-
2src/core/file_sys/directory.h
-
99src/core/file_sys/disk_archive.cpp
-
68src/core/file_sys/disk_archive.h
-
4src/core/file_sys/filesystem.cpp
-
58src/core/file_sys/filesystem.h
-
2src/core/file_sys/path_parser.h
-
38src/core/file_sys/romfs_factory.cpp
-
35src/core/file_sys/romfs_factory.h
-
68src/core/file_sys/romfs_filesystem.cpp
-
31src/core/file_sys/romfs_filesystem.h
-
330src/core/file_sys/savedata_archive.cpp
-
43src/core/file_sys/savedata_archive.h
-
27src/core/file_sys/storage.h
-
163src/core/file_sys/title_metadata.cpp
-
126src/core/file_sys/title_metadata.h
-
5src/core/hle/ipc_helpers.h
-
2src/core/hle/result.h
-
54src/core/hle/service/filesystem/filesystem.cpp
-
50src/core/hle/service/filesystem/filesystem.h
-
138src/core/hle/service/filesystem/fsp_srv.cpp
-
34src/core/hle/service/filesystem/fsp_srv.h
-
2src/core/hle/service/service.cpp
-
69src/core/loader/deconstructed_rom_directory.cpp
-
4src/core/loader/deconstructed_rom_directory.h
@ -1,4 +1,4 @@ |
|||
// Copyright 2014 Citra Emulator Project |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
@ -1,99 +0,0 @@ |
|||
// Copyright 2014 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
#include <cstdio>
|
|||
#include <memory>
|
|||
#include "common/common_types.h"
|
|||
#include "common/file_util.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "core/file_sys/disk_archive.h"
|
|||
#include "core/file_sys/errors.h"
|
|||
|
|||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|||
// FileSys namespace
|
|||
|
|||
namespace FileSys { |
|||
|
|||
ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const { |
|||
if (!mode.read_flag) |
|||
return ERROR_INVALID_OPEN_FLAGS; |
|||
|
|||
file->Seek(offset, SEEK_SET); |
|||
return MakeResult<size_t>(file->ReadBytes(buffer, length)); |
|||
} |
|||
|
|||
ResultVal<size_t> DiskFile::Write(const u64 offset, const size_t length, const bool flush, |
|||
const u8* buffer) const { |
|||
if (!mode.write_flag) |
|||
return ERROR_INVALID_OPEN_FLAGS; |
|||
|
|||
file->Seek(offset, SEEK_SET); |
|||
size_t written = file->WriteBytes(buffer, length); |
|||
if (flush) |
|||
file->Flush(); |
|||
return MakeResult<size_t>(written); |
|||
} |
|||
|
|||
u64 DiskFile::GetSize() const { |
|||
return file->GetSize(); |
|||
} |
|||
|
|||
bool DiskFile::SetSize(const u64 size) const { |
|||
file->Resize(size); |
|||
file->Flush(); |
|||
return true; |
|||
} |
|||
|
|||
bool DiskFile::Close() const { |
|||
return file->Close(); |
|||
} |
|||
|
|||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
DiskDirectory::DiskDirectory(const std::string& path) : directory() { |
|||
unsigned size = FileUtil::ScanDirectoryTree(path, directory); |
|||
directory.size = size; |
|||
directory.isDirectory = true; |
|||
children_iterator = directory.children.begin(); |
|||
} |
|||
|
|||
u32 DiskDirectory::Read(const u32 count, Entry* entries) { |
|||
u32 entries_read = 0; |
|||
|
|||
while (entries_read < count && children_iterator != directory.children.cend()) { |
|||
const FileUtil::FSTEntry& file = *children_iterator; |
|||
const std::string& filename = file.virtualName; |
|||
Entry& entry = entries[entries_read]; |
|||
|
|||
LOG_TRACE(Service_FS, "File %s: size=%llu dir=%d", filename.c_str(), file.size, |
|||
file.isDirectory); |
|||
|
|||
// TODO(Link Mauve): use a proper conversion to UTF-16.
|
|||
for (size_t j = 0; j < FILENAME_LENGTH; ++j) { |
|||
entry.filename[j] = filename[j]; |
|||
if (!filename[j]) |
|||
break; |
|||
} |
|||
|
|||
FileUtil::SplitFilename83(filename, entry.short_name, entry.extension); |
|||
|
|||
entry.is_directory = file.isDirectory; |
|||
entry.is_hidden = (filename[0] == '.'); |
|||
entry.is_read_only = 0; |
|||
entry.file_size = file.size; |
|||
|
|||
// We emulate a SD card where the archive bit has never been cleared, as it would be on
|
|||
// most user SD cards.
|
|||
// Some homebrews (blargSNES for instance) are known to mistakenly use the archive bit as a
|
|||
// file bit.
|
|||
entry.is_archive = !file.isDirectory; |
|||
|
|||
++entries_read; |
|||
++children_iterator; |
|||
} |
|||
return entries_read; |
|||
} |
|||
|
|||
} // namespace FileSys
|
|||
@ -1,68 +0,0 @@ |
|||
// Copyright 2014 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <cstddef> |
|||
#include <memory> |
|||
#include <string> |
|||
#include <vector> |
|||
#include "common/common_types.h" |
|||
#include "common/file_util.h" |
|||
#include "core/file_sys/archive_backend.h" |
|||
#include "core/file_sys/directory_backend.h" |
|||
#include "core/file_sys/file_backend.h" |
|||
#include "core/hle/result.h" |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
// FileSys namespace |
|||
|
|||
namespace FileSys { |
|||
|
|||
class DiskFile : public FileBackend { |
|||
public: |
|||
DiskFile(FileUtil::IOFile&& file_, const Mode& mode_) |
|||
: file(new FileUtil::IOFile(std::move(file_))) { |
|||
mode.hex = mode_.hex; |
|||
} |
|||
|
|||
ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; |
|||
ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; |
|||
u64 GetSize() const override; |
|||
bool SetSize(u64 size) const override; |
|||
bool Close() const override; |
|||
|
|||
void Flush() const override { |
|||
file->Flush(); |
|||
} |
|||
|
|||
protected: |
|||
Mode mode; |
|||
std::unique_ptr<FileUtil::IOFile> file; |
|||
}; |
|||
|
|||
class DiskDirectory : public DirectoryBackend { |
|||
public: |
|||
DiskDirectory(const std::string& path); |
|||
|
|||
~DiskDirectory() override { |
|||
Close(); |
|||
} |
|||
|
|||
u32 Read(const u32 count, Entry* entries) override; |
|||
|
|||
bool Close() const override { |
|||
return true; |
|||
} |
|||
|
|||
protected: |
|||
u32 total_entries_in_directory; |
|||
FileUtil::FSTEntry directory; |
|||
|
|||
// We need to remember the last entry we returned, so a subsequent call to Read will continue |
|||
// from the next one. This iterator will always point to the next unread entry. |
|||
std::vector<FileUtil::FSTEntry>::iterator children_iterator; |
|||
}; |
|||
|
|||
} // namespace FileSys |
|||
@ -0,0 +1,38 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
#include <memory>
|
|||
#include "common/common_types.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "core/file_sys/romfs_factory.h"
|
|||
#include "core/file_sys/romfs_filesystem.h"
|
|||
|
|||
namespace FileSys { |
|||
|
|||
RomFS_Factory::RomFS_Factory(Loader::AppLoader& app_loader) { |
|||
// Load the RomFS from the app
|
|||
if (Loader::ResultStatus::Success != app_loader.ReadRomFS(romfs_file, data_offset, data_size)) { |
|||
LOG_ERROR(Service_FS, "Unable to read RomFS!"); |
|||
} |
|||
} |
|||
|
|||
ResultVal<std::unique_ptr<FileSystemBackend>> RomFS_Factory::Open(const Path& path) { |
|||
auto archive = std::make_unique<RomFS_FileSystem>(romfs_file, data_offset, data_size); |
|||
return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive)); |
|||
} |
|||
|
|||
ResultCode RomFS_Factory::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) { |
|||
LOG_ERROR(Service_FS, "Unimplemented Format archive %s", GetName().c_str()); |
|||
// TODO(bunnei): Find the right error code for this
|
|||
return ResultCode(-1); |
|||
} |
|||
|
|||
ResultVal<ArchiveFormatInfo> RomFS_Factory::GetFormatInfo(const Path& path) const { |
|||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); |
|||
// TODO(bunnei): Find the right error code for this
|
|||
return ResultCode(-1); |
|||
} |
|||
|
|||
} // namespace FileSys
|
|||
@ -0,0 +1,35 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <string> |
|||
#include <vector> |
|||
#include "common/common_types.h" |
|||
#include "core/file_sys/filesystem.h" |
|||
#include "core/hle/result.h" |
|||
#include "core/loader/loader.h" |
|||
|
|||
namespace FileSys { |
|||
|
|||
/// File system interface to the RomFS archive |
|||
class RomFS_Factory final : public FileSystemFactory { |
|||
public: |
|||
explicit RomFS_Factory(Loader::AppLoader& app_loader); |
|||
|
|||
std::string GetName() const override { |
|||
return "ArchiveFactory_RomFS"; |
|||
} |
|||
ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override; |
|||
ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override; |
|||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; |
|||
|
|||
private: |
|||
std::shared_ptr<FileUtil::IOFile> romfs_file; |
|||
u64 data_offset; |
|||
u64 data_size; |
|||
}; |
|||
|
|||
} // namespace FileSys |
|||
@ -1,330 +0,0 @@ |
|||
// Copyright 2016 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include "common/file_util.h"
|
|||
#include "core/file_sys/disk_archive.h"
|
|||
#include "core/file_sys/errors.h"
|
|||
#include "core/file_sys/path_parser.h"
|
|||
#include "core/file_sys/savedata_archive.h"
|
|||
|
|||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|||
// FileSys namespace
|
|||
|
|||
namespace FileSys { |
|||
|
|||
ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path, |
|||
const Mode& mode) const { |
|||
LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); |
|||
|
|||
const PathParser path_parser(path); |
|||
|
|||
if (!path_parser.IsValid()) { |
|||
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); |
|||
return ERROR_INVALID_PATH; |
|||
} |
|||
|
|||
if (mode.hex == 0) { |
|||
LOG_ERROR(Service_FS, "Empty open mode"); |
|||
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
|||
} |
|||
|
|||
if (mode.create_flag && !mode.write_flag) { |
|||
LOG_ERROR(Service_FS, "Create flag set but write flag not set"); |
|||
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
|||
} |
|||
|
|||
const auto full_path = path_parser.BuildHostPath(mount_point); |
|||
|
|||
switch (path_parser.GetHostStatus(mount_point)) { |
|||
case PathParser::InvalidMountPoint: |
|||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); |
|||
return ERROR_FILE_NOT_FOUND; |
|||
case PathParser::PathNotFound: |
|||
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); |
|||
return ERROR_PATH_NOT_FOUND; |
|||
case PathParser::FileInPath: |
|||
case PathParser::DirectoryFound: |
|||
LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str()); |
|||
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; |
|||
case PathParser::NotFound: |
|||
if (!mode.create_flag) { |
|||
LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.", |
|||
full_path.c_str()); |
|||
return ERROR_FILE_NOT_FOUND; |
|||
} else { |
|||
// Create the file
|
|||
FileUtil::CreateEmptyFile(full_path); |
|||
} |
|||
break; |
|||
case PathParser::FileFound: |
|||
break; // Expected 'success' case
|
|||
} |
|||
|
|||
FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb"); |
|||
if (!file.IsOpen()) { |
|||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str()); |
|||
return ERROR_FILE_NOT_FOUND; |
|||
} |
|||
|
|||
auto disk_file = std::make_unique<DiskFile>(std::move(file), mode); |
|||
return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file)); |
|||
} |
|||
|
|||
ResultCode SaveDataArchive::DeleteFile(const Path& path) const { |
|||
const PathParser path_parser(path); |
|||
|
|||
if (!path_parser.IsValid()) { |
|||
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); |
|||
return ERROR_INVALID_PATH; |
|||
} |
|||
|
|||
const auto full_path = path_parser.BuildHostPath(mount_point); |
|||
|
|||
switch (path_parser.GetHostStatus(mount_point)) { |
|||
case PathParser::InvalidMountPoint: |
|||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); |
|||
return ERROR_FILE_NOT_FOUND; |
|||
case PathParser::PathNotFound: |
|||
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); |
|||
return ERROR_PATH_NOT_FOUND; |
|||
case PathParser::FileInPath: |
|||
case PathParser::DirectoryFound: |
|||
case PathParser::NotFound: |
|||
LOG_ERROR(Service_FS, "File not found %s", full_path.c_str()); |
|||
return ERROR_FILE_NOT_FOUND; |
|||
case PathParser::FileFound: |
|||
break; // Expected 'success' case
|
|||
} |
|||
|
|||
if (FileUtil::Delete(full_path)) { |
|||
return RESULT_SUCCESS; |
|||
} |
|||
|
|||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str()); |
|||
return ERROR_FILE_NOT_FOUND; |
|||
} |
|||
|
|||
ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const { |
|||
const PathParser path_parser_src(src_path); |
|||
|
|||
// TODO: Verify these return codes with HW
|
|||
if (!path_parser_src.IsValid()) { |
|||
LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str()); |
|||
return ERROR_INVALID_PATH; |
|||
} |
|||
|
|||
const PathParser path_parser_dest(dest_path); |
|||
|
|||
if (!path_parser_dest.IsValid()) { |
|||
LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str()); |
|||
return ERROR_INVALID_PATH; |
|||
} |
|||
|
|||
const auto src_path_full = path_parser_src.BuildHostPath(mount_point); |
|||
const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point); |
|||
|
|||
if (FileUtil::Rename(src_path_full, dest_path_full)) { |
|||
return RESULT_SUCCESS; |
|||
} |
|||
|
|||
// TODO(bunnei): Use correct error code
|
|||
return ResultCode(-1); |
|||
} |
|||
|
|||
template <typename T> |
|||
static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point, |
|||
T deleter) { |
|||
const PathParser path_parser(path); |
|||
|
|||
if (!path_parser.IsValid()) { |
|||
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); |
|||
return ERROR_INVALID_PATH; |
|||
} |
|||
|
|||
if (path_parser.IsRootDirectory()) |
|||
return ERROR_DIRECTORY_NOT_EMPTY; |
|||
|
|||
const auto full_path = path_parser.BuildHostPath(mount_point); |
|||
|
|||
switch (path_parser.GetHostStatus(mount_point)) { |
|||
case PathParser::InvalidMountPoint: |
|||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); |
|||
return ERROR_PATH_NOT_FOUND; |
|||
case PathParser::PathNotFound: |
|||
case PathParser::NotFound: |
|||
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); |
|||
return ERROR_PATH_NOT_FOUND; |
|||
case PathParser::FileInPath: |
|||
case PathParser::FileFound: |
|||
LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str()); |
|||
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; |
|||
case PathParser::DirectoryFound: |
|||
break; // Expected 'success' case
|
|||
} |
|||
|
|||
if (deleter(full_path)) { |
|||
return RESULT_SUCCESS; |
|||
} |
|||
|
|||
LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str()); |
|||
return ERROR_DIRECTORY_NOT_EMPTY; |
|||
} |
|||
|
|||
ResultCode SaveDataArchive::DeleteDirectory(const Path& path) const { |
|||
return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir); |
|||
} |
|||
|
|||
ResultCode SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const { |
|||
return DeleteDirectoryHelper( |
|||
path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); }); |
|||
} |
|||
|
|||
ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const { |
|||
const PathParser path_parser(path); |
|||
|
|||
if (!path_parser.IsValid()) { |
|||
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); |
|||
return ERROR_INVALID_PATH; |
|||
} |
|||
|
|||
const auto full_path = path_parser.BuildHostPath(mount_point); |
|||
|
|||
switch (path_parser.GetHostStatus(mount_point)) { |
|||
case PathParser::InvalidMountPoint: |
|||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); |
|||
return ERROR_FILE_NOT_FOUND; |
|||
case PathParser::PathNotFound: |
|||
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); |
|||
return ERROR_PATH_NOT_FOUND; |
|||
case PathParser::FileInPath: |
|||
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); |
|||
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; |
|||
case PathParser::DirectoryFound: |
|||
case PathParser::FileFound: |
|||
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); |
|||
return ERROR_FILE_ALREADY_EXISTS; |
|||
case PathParser::NotFound: |
|||
break; // Expected 'success' case
|
|||
} |
|||
|
|||
if (size == 0) { |
|||
FileUtil::CreateEmptyFile(full_path); |
|||
return RESULT_SUCCESS; |
|||
} |
|||
|
|||
FileUtil::IOFile file(full_path, "wb"); |
|||
// Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
|
|||
// We do this by seeking to the right size, then writing a single null byte.
|
|||
if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) { |
|||
return RESULT_SUCCESS; |
|||
} |
|||
|
|||
LOG_ERROR(Service_FS, "Too large file"); |
|||
|
|||
// TODO(bunnei): Use correct error code
|
|||
return ResultCode(-1); |
|||
} |
|||
|
|||
ResultCode SaveDataArchive::CreateDirectory(const Path& path) const { |
|||
const PathParser path_parser(path); |
|||
|
|||
if (!path_parser.IsValid()) { |
|||
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); |
|||
return ERROR_INVALID_PATH; |
|||
} |
|||
|
|||
const auto full_path = path_parser.BuildHostPath(mount_point); |
|||
|
|||
switch (path_parser.GetHostStatus(mount_point)) { |
|||
case PathParser::InvalidMountPoint: |
|||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); |
|||
return ERROR_FILE_NOT_FOUND; |
|||
case PathParser::PathNotFound: |
|||
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); |
|||
return ERROR_PATH_NOT_FOUND; |
|||
case PathParser::FileInPath: |
|||
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); |
|||
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; |
|||
case PathParser::DirectoryFound: |
|||
case PathParser::FileFound: |
|||
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); |
|||
return ERROR_DIRECTORY_ALREADY_EXISTS; |
|||
case PathParser::NotFound: |
|||
break; // Expected 'success' case
|
|||
} |
|||
|
|||
if (FileUtil::CreateDir(mount_point + path.AsString())) { |
|||
return RESULT_SUCCESS; |
|||
} |
|||
|
|||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str()); |
|||
|
|||
// TODO(bunnei): Use correct error code
|
|||
return ResultCode(-1); |
|||
} |
|||
|
|||
ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { |
|||
const PathParser path_parser_src(src_path); |
|||
|
|||
// TODO: Verify these return codes with HW
|
|||
if (!path_parser_src.IsValid()) { |
|||
LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str()); |
|||
return ERROR_INVALID_PATH; |
|||
} |
|||
|
|||
const PathParser path_parser_dest(dest_path); |
|||
|
|||
if (!path_parser_dest.IsValid()) { |
|||
LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str()); |
|||
return ERROR_INVALID_PATH; |
|||
} |
|||
|
|||
const auto src_path_full = path_parser_src.BuildHostPath(mount_point); |
|||
const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point); |
|||
|
|||
if (FileUtil::Rename(src_path_full, dest_path_full)) { |
|||
return RESULT_SUCCESS; |
|||
} |
|||
|
|||
// TODO(bunnei): Use correct error code
|
|||
return ResultCode(-1); |
|||
} |
|||
|
|||
ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory( |
|||
const Path& path) const { |
|||
const PathParser path_parser(path); |
|||
|
|||
if (!path_parser.IsValid()) { |
|||
LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); |
|||
return ERROR_INVALID_PATH; |
|||
} |
|||
|
|||
const auto full_path = path_parser.BuildHostPath(mount_point); |
|||
|
|||
switch (path_parser.GetHostStatus(mount_point)) { |
|||
case PathParser::InvalidMountPoint: |
|||
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); |
|||
return ERROR_FILE_NOT_FOUND; |
|||
case PathParser::PathNotFound: |
|||
case PathParser::NotFound: |
|||
LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); |
|||
return ERROR_PATH_NOT_FOUND; |
|||
case PathParser::FileInPath: |
|||
case PathParser::FileFound: |
|||
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); |
|||
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; |
|||
case PathParser::DirectoryFound: |
|||
break; // Expected 'success' case
|
|||
} |
|||
|
|||
auto directory = std::make_unique<DiskDirectory>(full_path); |
|||
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory)); |
|||
} |
|||
|
|||
u64 SaveDataArchive::GetFreeBytes() const { |
|||
// TODO: Stubbed to return 1GiB
|
|||
return 1024 * 1024 * 1024; |
|||
} |
|||
|
|||
} // namespace FileSys
|
|||
@ -1,43 +0,0 @@ |
|||
// Copyright 2016 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
#include "core/file_sys/archive_backend.h" |
|||
#include "core/file_sys/directory_backend.h" |
|||
#include "core/file_sys/file_backend.h" |
|||
#include "core/hle/result.h" |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
// FileSys namespace |
|||
|
|||
namespace FileSys { |
|||
|
|||
/// Archive backend for general save data archive type (SaveData and SystemSaveData) |
|||
class SaveDataArchive : public ArchiveBackend { |
|||
public: |
|||
explicit SaveDataArchive(const std::string& mount_point_) : mount_point(mount_point_) {} |
|||
|
|||
std::string GetName() const override { |
|||
return "SaveDataArchive: " + mount_point; |
|||
} |
|||
|
|||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, |
|||
const Mode& mode) const override; |
|||
ResultCode DeleteFile(const Path& path) const override; |
|||
ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; |
|||
ResultCode DeleteDirectory(const Path& path) const override; |
|||
ResultCode DeleteDirectoryRecursively(const Path& path) const override; |
|||
ResultCode CreateFile(const Path& path, u64 size) const override; |
|||
ResultCode CreateDirectory(const Path& path) const override; |
|||
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; |
|||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; |
|||
u64 GetFreeBytes() const override; |
|||
|
|||
protected: |
|||
std::string mount_point; |
|||
}; |
|||
|
|||
} // namespace FileSys |
|||
@ -1,163 +0,0 @@ |
|||
// Copyright 2017 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <cinttypes>
|
|||
#include "common/alignment.h"
|
|||
#include "common/file_util.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "core/file_sys/title_metadata.h"
|
|||
#include "core/loader/loader.h"
|
|||
|
|||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|||
// FileSys namespace
|
|||
|
|||
namespace FileSys { |
|||
|
|||
static u32 GetSignatureSize(u32 signature_type) { |
|||
switch (signature_type) { |
|||
case Rsa4096Sha1: |
|||
case Rsa4096Sha256: |
|||
return 0x200; |
|||
|
|||
case Rsa2048Sha1: |
|||
case Rsa2048Sha256: |
|||
return 0x100; |
|||
|
|||
case EllipticSha1: |
|||
case EcdsaSha256: |
|||
return 0x3C; |
|||
} |
|||
} |
|||
|
|||
Loader::ResultStatus TitleMetadata::Load() { |
|||
FileUtil::IOFile file(filepath, "rb"); |
|||
if (!file.IsOpen()) |
|||
return Loader::ResultStatus::Error; |
|||
|
|||
if (!file.ReadBytes(&signature_type, sizeof(u32_be))) |
|||
return Loader::ResultStatus::Error; |
|||
|
|||
// Signature lengths are variable, and the body follows the signature
|
|||
u32 signature_size = GetSignatureSize(signature_type); |
|||
|
|||
tmd_signature.resize(signature_size); |
|||
if (!file.ReadBytes(&tmd_signature[0], signature_size)) |
|||
return Loader::ResultStatus::Error; |
|||
|
|||
// The TMD body start position is rounded to the nearest 0x40 after the signature
|
|||
size_t body_start = Common::AlignUp(signature_size + sizeof(u32), 0x40); |
|||
file.Seek(body_start, SEEK_SET); |
|||
|
|||
// Read our TMD body, then load the amount of ContentChunks specified
|
|||
if (file.ReadBytes(&tmd_body, sizeof(TitleMetadata::Body)) != sizeof(TitleMetadata::Body)) |
|||
return Loader::ResultStatus::Error; |
|||
|
|||
for (u16 i = 0; i < tmd_body.content_count; i++) { |
|||
ContentChunk chunk; |
|||
if (file.ReadBytes(&chunk, sizeof(ContentChunk)) == sizeof(ContentChunk)) { |
|||
tmd_chunks.push_back(chunk); |
|||
} else { |
|||
LOG_ERROR(Service_FS, "Malformed TMD %s, failed to load content chunk index %u!", |
|||
filepath.c_str(), i); |
|||
return Loader::ResultStatus::ErrorInvalidFormat; |
|||
} |
|||
} |
|||
|
|||
return Loader::ResultStatus::Success; |
|||
} |
|||
|
|||
Loader::ResultStatus TitleMetadata::Save() { |
|||
UNIMPLEMENTED(); |
|||
return Loader::ResultStatus::Success; |
|||
} |
|||
|
|||
u64 TitleMetadata::GetTitleID() const { |
|||
return tmd_body.title_id; |
|||
} |
|||
|
|||
u32 TitleMetadata::GetTitleType() const { |
|||
return tmd_body.title_type; |
|||
} |
|||
|
|||
u16 TitleMetadata::GetTitleVersion() const { |
|||
return tmd_body.title_version; |
|||
} |
|||
|
|||
u64 TitleMetadata::GetSystemVersion() const { |
|||
return tmd_body.system_version; |
|||
} |
|||
|
|||
size_t TitleMetadata::GetContentCount() const { |
|||
return tmd_chunks.size(); |
|||
} |
|||
|
|||
u32 TitleMetadata::GetBootContentID() const { |
|||
return tmd_chunks[TMDContentIndex::Main].id; |
|||
} |
|||
|
|||
u32 TitleMetadata::GetManualContentID() const { |
|||
return tmd_chunks[TMDContentIndex::Manual].id; |
|||
} |
|||
|
|||
u32 TitleMetadata::GetDLPContentID() const { |
|||
return tmd_chunks[TMDContentIndex::DLP].id; |
|||
} |
|||
|
|||
void TitleMetadata::SetTitleID(u64 title_id) { |
|||
tmd_body.title_id = title_id; |
|||
} |
|||
|
|||
void TitleMetadata::SetTitleType(u32 type) { |
|||
tmd_body.title_type = type; |
|||
} |
|||
|
|||
void TitleMetadata::SetTitleVersion(u16 version) { |
|||
tmd_body.title_version = version; |
|||
} |
|||
|
|||
void TitleMetadata::SetSystemVersion(u64 version) { |
|||
tmd_body.system_version = version; |
|||
} |
|||
|
|||
void TitleMetadata::AddContentChunk(const ContentChunk& chunk) { |
|||
tmd_chunks.push_back(chunk); |
|||
} |
|||
|
|||
void TitleMetadata::Print() const { |
|||
LOG_DEBUG(Service_FS, "%s - %u chunks", filepath.c_str(), |
|||
static_cast<u32>(tmd_body.content_count)); |
|||
|
|||
// Content info describes ranges of content chunks
|
|||
LOG_DEBUG(Service_FS, "Content info:"); |
|||
for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) { |
|||
if (tmd_body.contentinfo[i].command_count == 0) |
|||
break; |
|||
|
|||
LOG_DEBUG(Service_FS, " Index %04X, Command Count %04X", |
|||
static_cast<u32>(tmd_body.contentinfo[i].index), |
|||
static_cast<u32>(tmd_body.contentinfo[i].command_count)); |
|||
} |
|||
|
|||
// For each content info, print their content chunk range
|
|||
for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) { |
|||
u16 index = static_cast<u16>(tmd_body.contentinfo[i].index); |
|||
u16 count = static_cast<u16>(tmd_body.contentinfo[i].command_count); |
|||
|
|||
if (count == 0) |
|||
continue; |
|||
|
|||
LOG_DEBUG(Service_FS, "Content chunks for content info index %zu:", i); |
|||
for (u16 j = index; j < index + count; j++) { |
|||
// Don't attempt to print content we don't have
|
|||
if (j > tmd_body.content_count) |
|||
break; |
|||
|
|||
const ContentChunk& chunk = tmd_chunks[j]; |
|||
LOG_DEBUG(Service_FS, " ID %08X, Index %04X, Type %04x, Size %016" PRIX64, |
|||
static_cast<u32>(chunk.id), static_cast<u32>(chunk.index), |
|||
static_cast<u32>(chunk.type), static_cast<u64>(chunk.size)); |
|||
} |
|||
} |
|||
} |
|||
} // namespace FileSys
|
|||
@ -1,126 +0,0 @@ |
|||
// Copyright 2017 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
#include <string> |
|||
#include <vector> |
|||
#include "common/common_types.h" |
|||
#include "common/swap.h" |
|||
|
|||
namespace Loader { |
|||
enum class ResultStatus; |
|||
} |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
// FileSys namespace |
|||
|
|||
namespace FileSys { |
|||
|
|||
enum TMDSignatureType : u32 { |
|||
Rsa4096Sha1 = 0x10000, |
|||
Rsa2048Sha1 = 0x10001, |
|||
EllipticSha1 = 0x10002, |
|||
Rsa4096Sha256 = 0x10003, |
|||
Rsa2048Sha256 = 0x10004, |
|||
EcdsaSha256 = 0x10005 |
|||
}; |
|||
|
|||
enum TMDContentTypeFlag : u16 { |
|||
Encrypted = 1 << 1, |
|||
Disc = 1 << 2, |
|||
CFM = 1 << 3, |
|||
Optional = 1 << 14, |
|||
Shared = 1 << 15 |
|||
}; |
|||
|
|||
/** |
|||
* Helper which implements an interface to read and write Title Metadata (TMD) files. |
|||
* If a file path is provided and the file exists, it can be parsed and used, otherwise |
|||
* it must be created. The TMD file can then be interpreted, modified and/or saved. |
|||
*/ |
|||
class TitleMetadata { |
|||
public: |
|||
struct ContentChunk { |
|||
u32_be id; |
|||
u16_be index; |
|||
u16_be type; |
|||
u64_be size; |
|||
std::array<u8, 0x20> hash; |
|||
}; |
|||
|
|||
static_assert(sizeof(ContentChunk) == 0x30, "TMD ContentChunk structure size is wrong"); |
|||
|
|||
struct ContentInfo { |
|||
u16_be index; |
|||
u16_be command_count; |
|||
std::array<u8, 0x20> hash; |
|||
}; |
|||
|
|||
static_assert(sizeof(ContentInfo) == 0x24, "TMD ContentInfo structure size is wrong"); |
|||
|
|||
#pragma pack(push, 1) |
|||
|
|||
struct Body { |
|||
std::array<u8, 0x40> issuer; |
|||
u8 version; |
|||
u8 ca_crl_version; |
|||
u8 signer_crl_version; |
|||
u8 reserved; |
|||
u64_be system_version; |
|||
u64_be title_id; |
|||
u32_be title_type; |
|||
u16_be group_id; |
|||
u32_be savedata_size; |
|||
u32_be srl_private_savedata_size; |
|||
std::array<u8, 4> reserved_2; |
|||
u8 srl_flag; |
|||
std::array<u8, 0x31> reserved_3; |
|||
u32_be access_rights; |
|||
u16_be title_version; |
|||
u16_be content_count; |
|||
u16_be boot_content; |
|||
std::array<u8, 2> reserved_4; |
|||
std::array<u8, 0x20> contentinfo_hash; |
|||
std::array<ContentInfo, 64> contentinfo; |
|||
}; |
|||
|
|||
static_assert(sizeof(Body) == 0x9C4, "TMD body structure size is wrong"); |
|||
|
|||
#pragma pack(pop) |
|||
|
|||
explicit TitleMetadata(std::string& path) : filepath(std::move(path)) {} |
|||
Loader::ResultStatus Load(); |
|||
Loader::ResultStatus Save(); |
|||
|
|||
u64 GetTitleID() const; |
|||
u32 GetTitleType() const; |
|||
u16 GetTitleVersion() const; |
|||
u64 GetSystemVersion() const; |
|||
size_t GetContentCount() const; |
|||
u32 GetBootContentID() const; |
|||
u32 GetManualContentID() const; |
|||
u32 GetDLPContentID() const; |
|||
|
|||
void SetTitleID(u64 title_id); |
|||
void SetTitleType(u32 type); |
|||
void SetTitleVersion(u16 version); |
|||
void SetSystemVersion(u64 version); |
|||
void AddContentChunk(const ContentChunk& chunk); |
|||
|
|||
void Print() const; |
|||
|
|||
private: |
|||
enum TMDContentIndex { Main = 0, Manual = 1, DLP = 2 }; |
|||
|
|||
Body tmd_body; |
|||
u32_be signature_type; |
|||
std::vector<u8> tmd_signature; |
|||
std::vector<ContentChunk> tmd_chunks; |
|||
|
|||
std::string filepath; |
|||
}; |
|||
|
|||
} // namespace FileSys |
|||
@ -0,0 +1,54 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <boost/container/flat_map.hpp>
|
|||
#include "core/file_sys/filesystem.h"
|
|||
#include "core/hle/service/filesystem/filesystem.h"
|
|||
#include "core/hle/service/filesystem/fsp_srv.h"
|
|||
|
|||
namespace Service { |
|||
namespace FileSystem { |
|||
|
|||
/**
|
|||
* Map of registered file systems, identified by type. Once an file system is registered here, it |
|||
* is never removed until UnregisterFileSystems is called. |
|||
*/ |
|||
static boost::container::flat_map<Type, std::unique_ptr<FileSys::FileSystemFactory>> filesystem_map; |
|||
|
|||
ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& factory, Type type) { |
|||
auto result = filesystem_map.emplace(type, std::move(factory)); |
|||
|
|||
bool inserted = result.second; |
|||
ASSERT_MSG(inserted, "Tried to register more than one system with same id code"); |
|||
|
|||
auto& filesystem = result.first->second; |
|||
LOG_DEBUG(Service_FS, "Registered file system %s with id code 0x%08X", |
|||
filesystem->GetName().c_str(), static_cast<u32>(type)); |
|||
return RESULT_SUCCESS; |
|||
} |
|||
|
|||
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type, |
|||
FileSys::Path& path) { |
|||
LOG_TRACE(Service_FS, "Opening FileSystem with type=%d", type); |
|||
|
|||
auto itr = filesystem_map.find(type); |
|||
if (itr == filesystem_map.end()) { |
|||
// TODO(bunnei): Find a better error code for this
|
|||
return ResultCode(-1); |
|||
} |
|||
|
|||
return itr->second->Open(path); |
|||
} |
|||
|
|||
void UnregisterFileSystems() { |
|||
filesystem_map.clear(); |
|||
} |
|||
|
|||
void InstallInterfaces(SM::ServiceManager& service_manager) { |
|||
UnregisterFileSystems(); |
|||
std::make_shared<FSP_SRV>()->InstallAsService(service_manager); |
|||
} |
|||
|
|||
} // namespace FileSystem
|
|||
} // namespace Service
|
|||
@ -0,0 +1,50 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include "common/common_types.h" |
|||
#include "core/hle/result.h" |
|||
|
|||
namespace FileSys { |
|||
class FileSystemBackend; |
|||
class FileSystemFactory; |
|||
class Path; |
|||
} // namespace FileSys |
|||
|
|||
namespace Service { |
|||
|
|||
namespace SM { |
|||
class ServiceManager; |
|||
} // namespace SM |
|||
|
|||
namespace FileSystem { |
|||
|
|||
/// Supported FileSystem types |
|||
enum class Type { |
|||
RomFS = 1, |
|||
}; |
|||
|
|||
/** |
|||
* Registers a FileSystem, instances of which can later be opened using its IdCode. |
|||
* @param factory FileSystem backend interface to use |
|||
* @param type Type used to access this type of FileSystem |
|||
*/ |
|||
ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& factory, Type type); |
|||
|
|||
/** |
|||
* Opens a file system |
|||
* @param type Type of the file system to open |
|||
* @param path Path to the file system, used with Binary paths |
|||
* @return FileSys::FileSystemBackend interface to the file system |
|||
*/ |
|||
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type, |
|||
FileSys::Path& path); |
|||
|
|||
/// Registers all Filesystem services with the specified service manager. |
|||
void InstallInterfaces(SM::ServiceManager& service_manager); |
|||
|
|||
} // namespace FileSystem |
|||
} // namespace Service |
|||
@ -0,0 +1,138 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include "common/logging/log.h"
|
|||
#include "core/core.h"
|
|||
#include "core/file_sys/filesystem.h"
|
|||
#include "core/file_sys/storage.h"
|
|||
#include "core/hle/ipc_helpers.h"
|
|||
#include "core/hle/kernel/client_port.h"
|
|||
#include "core/hle/kernel/client_session.h"
|
|||
#include "core/hle/service/filesystem/filesystem.h"
|
|||
#include "core/hle/service/filesystem/fsp_srv.h"
|
|||
|
|||
namespace Service { |
|||
namespace FileSystem { |
|||
|
|||
class IStorage final : public ServiceFramework<IStorage> { |
|||
public: |
|||
IStorage(std::unique_ptr<FileSys::StorageBackend>&& backend) |
|||
: ServiceFramework("IStorage"), backend(std::move(backend)) { |
|||
static const FunctionInfo functions[] = { |
|||
{0, &IStorage::Read, "Read"}, {1, nullptr, "Write"}, {2, nullptr, "Flush"}, |
|||
{3, nullptr, "SetSize"}, {4, nullptr, "GetSize"}, |
|||
}; |
|||
RegisterHandlers(functions); |
|||
} |
|||
|
|||
private: |
|||
std::unique_ptr<FileSys::StorageBackend> backend; |
|||
|
|||
void Read(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const s64 offset = rp.Pop<s64>(); |
|||
const s64 length = rp.Pop<s64>(); |
|||
const auto& descriptor = ctx.BufferDescriptorB()[0]; |
|||
|
|||
LOG_DEBUG(Service_FS, "called, offset=0x%llx, length=0x%llx", offset, length); |
|||
|
|||
// Error checking
|
|||
ASSERT_MSG(length == descriptor.Size(), "unexpected size difference"); |
|||
if (length < 0) { |
|||
IPC::RequestBuilder rb{ctx, 2}; |
|||
rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidLength)); |
|||
return; |
|||
} |
|||
if (offset < 0) { |
|||
IPC::RequestBuilder rb{ctx, 2}; |
|||
rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidOffset)); |
|||
return; |
|||
} |
|||
|
|||
// Read the data from the Storage backend
|
|||
std::vector<u8> output(length); |
|||
ResultVal<size_t> res = backend->Read(offset, length, output.data()); |
|||
if (res.Failed()) { |
|||
IPC::RequestBuilder rb{ctx, 2}; |
|||
rb.Push(res.Code()); |
|||
return; |
|||
} |
|||
|
|||
// Write the data to memory
|
|||
Memory::WriteBlock(descriptor.Address(), output.data(), descriptor.Size()); |
|||
|
|||
IPC::RequestBuilder rb{ctx, 2}; |
|||
rb.Push(RESULT_SUCCESS); |
|||
} |
|||
}; |
|||
|
|||
FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { |
|||
static const FunctionInfo functions[] = { |
|||
{1, &FSP_SRV::Initalize, "Initalize"}, |
|||
{200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"}, |
|||
{203, &FSP_SRV::OpenRomStorage, "OpenRomStorage"}, |
|||
{1005, &FSP_SRV::GetGlobalAccessLogMode, "GetGlobalAccessLogMode"}, |
|||
}; |
|||
RegisterHandlers(functions); |
|||
} |
|||
|
|||
void FSP_SRV::TryLoadRomFS() { |
|||
if (romfs) { |
|||
return; |
|||
} |
|||
FileSys::Path unused; |
|||
auto res = OpenFileSystem(Type::RomFS, unused); |
|||
if (res.Succeeded()) { |
|||
romfs = std::move(res.Unwrap()); |
|||
} |
|||
} |
|||
|
|||
void FSP_SRV::Initalize(Kernel::HLERequestContext& ctx) { |
|||
LOG_WARNING(Service_FS, "(STUBBED) called"); |
|||
|
|||
IPC::RequestBuilder rb{ctx, 2}; |
|||
rb.Push(RESULT_SUCCESS); |
|||
} |
|||
|
|||
void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { |
|||
LOG_WARNING(Service_FS, "(STUBBED) called"); |
|||
|
|||
IPC::RequestBuilder rb{ctx, 4}; |
|||
rb.Push(RESULT_SUCCESS); |
|||
rb.Push<u32>(5); |
|||
} |
|||
|
|||
void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { |
|||
LOG_DEBUG(Service_FS, "called"); |
|||
|
|||
TryLoadRomFS(); |
|||
if (!romfs) { |
|||
// TODO (bunnei): Find the right error code to use here
|
|||
LOG_CRITICAL(Service_FS, "no file system interface available!"); |
|||
IPC::RequestBuilder rb{ctx, 2}; |
|||
rb.Push(ResultCode(-1)); |
|||
return; |
|||
} |
|||
|
|||
// Attempt to open a StorageBackend interface to the RomFS
|
|||
auto storage = romfs->OpenFile({}, {}); |
|||
if (storage.Failed()) { |
|||
LOG_CRITICAL(Service_FS, "no storage interface available!"); |
|||
IPC::RequestBuilder rb{ctx, 2}; |
|||
rb.Push(storage.Code()); |
|||
return; |
|||
} |
|||
|
|||
IPC::RequestBuilder rb{ctx, 2, 0, 0, 1}; |
|||
rb.Push(RESULT_SUCCESS); |
|||
rb.PushIpcInterface<IStorage>(std::move(storage.Unwrap())); |
|||
} |
|||
|
|||
void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) { |
|||
LOG_WARNING(Service_FS, "(STUBBED) called, using OpenDataStorageByCurrentProcess"); |
|||
OpenDataStorageByCurrentProcess(ctx); |
|||
} |
|||
|
|||
} // namespace FileSystem
|
|||
} // namespace Service
|
|||
@ -0,0 +1,34 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include "core/hle/service/service.h" |
|||
|
|||
namespace FileSys { |
|||
class FileSystemBackend; |
|||
} |
|||
|
|||
namespace Service { |
|||
namespace FileSystem { |
|||
|
|||
class FSP_SRV final : public ServiceFramework<FSP_SRV> { |
|||
public: |
|||
explicit FSP_SRV(); |
|||
~FSP_SRV() = default; |
|||
|
|||
private: |
|||
void TryLoadRomFS(); |
|||
|
|||
void Initalize(Kernel::HLERequestContext& ctx); |
|||
void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); |
|||
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); |
|||
void OpenRomStorage(Kernel::HLERequestContext& ctx); |
|||
|
|||
std::unique_ptr<FileSys::FileSystemBackend> romfs; |
|||
}; |
|||
|
|||
} // namespace FileSystem |
|||
} // namespace Service |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue