Compare commits

..

1 Commits

Author SHA1 Message Date
Lioncash
1cd5ccfe89 program_metadata: Take std::vector by const reference in Load()
This is only ever read from in the function itself, it's not moved or
copied anywhere else, so this is kind of wasteful. Given it has a const
qualifier, it was likely intended to be a reference anyways, however the
ampersand was forgotten.
2018-07-18 18:48:22 -04:00
56 changed files with 1968 additions and 1958 deletions

View File

@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <sstream>
#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/common_paths.h"
@@ -387,7 +386,7 @@ u64 GetSize(FILE* f) {
bool CreateEmptyFile(const std::string& filename) {
LOG_TRACE(Common_Filesystem, "{}", filename);
if (!FileUtil::IOFile(filename, "wb").IsOpen()) {
if (!FileUtil::IOFile(filename, "wb")) {
LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
return false;
}
@@ -751,7 +750,7 @@ size_t WriteStringToFile(bool text_file, const std::string& str, const char* fil
size_t ReadFileToString(bool text_file, const char* filename, std::string& str) {
IOFile file(filename, text_file ? "r" : "rb");
if (!file.IsOpen())
if (!file)
return false;
str.resize(static_cast<u32>(file.GetSize()));
@@ -800,71 +799,6 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
}
}
std::vector<std::string> SplitPathComponents(const std::string& filename) {
auto copy(filename);
std::replace(copy.begin(), copy.end(), '\\', '/');
std::vector<std::string> out;
std::stringstream stream(filename);
std::string item;
while (std::getline(stream, item, '/'))
out.push_back(std::move(item));
return out;
}
std::string GetParentPath(const std::string& path) {
auto out = path;
const auto name_bck_index = out.find_last_of('\\');
const auto name_fwd_index = out.find_last_of('/');
size_t name_index;
if (name_bck_index == std::string::npos || name_fwd_index == std::string::npos)
name_index = std::min<size_t>(name_bck_index, name_fwd_index);
else
name_index = std::max<size_t>(name_bck_index, name_fwd_index);
return out.erase(name_index);
}
std::string GetPathWithoutTop(std::string path) {
if (path.empty())
return "";
while (path[0] == '\\' || path[0] == '/') {
path = path.substr(1);
if (path.empty())
return "";
}
const auto name_bck_index = path.find_first_of('\\');
const auto name_fwd_index = path.find_first_of('/');
return path.substr(std::min<size_t>(name_bck_index, name_fwd_index) + 1);
return path.substr(std::min<size_t>(name_bck_index, name_fwd_index) + 1);
}
std::string GetFilename(std::string path) {
std::replace(path.begin(), path.end(), '\\', '/');
auto name_index = path.find_last_of('/');
if (name_index == std::string::npos)
return "";
return path.substr(name_index + 1);
}
std::string GetExtensionFromFilename(const std::string& name) {
size_t index = name.find_last_of('.');
if (index == std::string::npos)
return "";
return name.substr(index + 1);
}
std::string RemoveTrailingSlash(const std::string& path) {
if (path.empty())
return path;
if (path.back() == '\\' || path.back() == '/')
return path.substr(0, path.size() - 1);
return path;
}
IOFile::IOFile() {}
IOFile::IOFile(const std::string& filename, const char openmode[], int flags) {
@@ -886,6 +820,7 @@ IOFile& IOFile::operator=(IOFile&& other) noexcept {
void IOFile::Swap(IOFile& other) noexcept {
std::swap(m_file, other.m_file);
std::swap(m_good, other.m_good);
}
bool IOFile::Open(const std::string& filename, const char openmode[], int flags) {
@@ -902,15 +837,16 @@ bool IOFile::Open(const std::string& filename, const char openmode[], int flags)
m_file = fopen(filename.c_str(), openmode);
#endif
return IsOpen();
m_good = IsOpen();
return m_good;
}
bool IOFile::Close() {
if (!IsOpen() || 0 != std::fclose(m_file))
return false;
m_good = false;
m_file = nullptr;
return true;
return m_good;
}
u64 IOFile::GetSize() const {
@@ -920,8 +856,11 @@ u64 IOFile::GetSize() const {
return 0;
}
bool IOFile::Seek(s64 off, int origin) const {
return IsOpen() && 0 == fseeko(m_file, off, origin);
bool IOFile::Seek(s64 off, int origin) {
if (!IsOpen() || 0 != fseeko(m_file, off, origin))
m_good = false;
return m_good;
}
u64 IOFile::Tell() const {
@@ -932,20 +871,26 @@ u64 IOFile::Tell() const {
}
bool IOFile::Flush() {
return IsOpen() && 0 == std::fflush(m_file);
if (!IsOpen() || 0 != std::fflush(m_file))
m_good = false;
return m_good;
}
bool IOFile::Resize(u64 size) {
return IsOpen() && 0 ==
if (!IsOpen() || 0 !=
#ifdef _WIN32
// ector: _chsize sucks, not 64-bit safe
// F|RES: changed to _chsize_s. i think it is 64-bit safe
_chsize_s(_fileno(m_file), size)
// ector: _chsize sucks, not 64-bit safe
// F|RES: changed to _chsize_s. i think it is 64-bit safe
_chsize_s(_fileno(m_file), size)
#else
// TODO: handle 64bit and growing
ftruncate(fileno(m_file), size)
// TODO: handle 64bit and growing
ftruncate(fileno(m_file), size)
#endif
;
)
m_good = false;
return m_good;
}
} // namespace FileUtil

View File

@@ -150,34 +150,6 @@ size_t ReadFileToString(bool text_file, const char* filename, std::string& str);
void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
std::array<char, 4>& extension);
// Splits the path on '/' or '\' and put the components into a vector
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
std::vector<std::string> SplitPathComponents(const std::string& filename);
// Gets all of the text up to the last '/' or '\' in the path.
std::string GetParentPath(const std::string& path);
// Gets all of the text after the first '/' or '\' in the path.
std::string GetPathWithoutTop(std::string path);
// Gets the filename of the path
std::string GetFilename(std::string path);
// Gets the extension of the filename
std::string GetExtensionFromFilename(const std::string& name);
// Removes the final '/' or '\' if one exists
std::string RemoveTrailingSlash(const std::string& path);
// Creates a new vector containing indices [first, last) from the original.
template <typename T>
std::vector<T> SliceVector(const std::vector<T>& vector, size_t first, size_t last) {
if (first >= last)
return {};
last = std::min<size_t>(last, vector.size());
return std::vector<T>(vector.begin() + first, vector.begin() + first + last);
}
// simple wrapper for cstdlib file functions to
// hopefully will make error checking easier
// and make forgetting an fclose() harder
@@ -200,27 +172,41 @@ public:
bool Close();
template <typename T>
size_t ReadArray(T* data, size_t length) const {
size_t ReadArray(T* data, size_t length) {
static_assert(std::is_trivially_copyable<T>(),
"Given array does not consist of trivially copyable objects");
if (!IsOpen())
if (!IsOpen()) {
m_good = false;
return -1;
}
return std::fread(data, sizeof(T), length, m_file);
size_t items_read = std::fread(data, sizeof(T), length, m_file);
if (items_read != length)
m_good = false;
return items_read;
}
template <typename T>
size_t WriteArray(const T* data, size_t length) {
static_assert(std::is_trivially_copyable<T>(),
"Given array does not consist of trivially copyable objects");
if (!IsOpen())
if (!IsOpen()) {
m_good = false;
return -1;
return std::fwrite(data, sizeof(T), length, m_file);
}
size_t items_written = std::fwrite(data, sizeof(T), length, m_file);
if (items_written != length)
m_good = false;
return items_written;
}
template <typename T>
size_t ReadBytes(T* data, size_t length) const {
size_t ReadBytes(T* data, size_t length) {
static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable");
return ReadArray(reinterpret_cast<char*>(data), length);
}
@@ -245,7 +231,15 @@ public:
return nullptr != m_file;
}
bool Seek(s64 off, int origin) const;
// m_good is set to false when a read, write or other function fails
bool IsGood() const {
return m_good;
}
explicit operator bool() const {
return IsGood();
}
bool Seek(s64 off, int origin);
u64 Tell() const;
u64 GetSize() const;
bool Resize(u64 size);
@@ -253,11 +247,13 @@ public:
// clear error state
void Clear() {
m_good = true;
std::clearerr(m_file);
}
private:
std::FILE* m_file = nullptr;
bool m_good = true;
};
} // namespace FileUtil

View File

@@ -8,27 +8,27 @@ add_library(core STATIC
core_cpu.h
core_timing.cpp
core_timing.h
file_sys/content_archive.cpp
file_sys/content_archive.h
file_sys/directory.h
file_sys/disk_filesystem.cpp
file_sys/disk_filesystem.h
file_sys/errors.h
file_sys/mode.h
file_sys/filesystem.cpp
file_sys/filesystem.h
file_sys/partition_filesystem.cpp
file_sys/partition_filesystem.h
file_sys/path_parser.cpp
file_sys/path_parser.h
file_sys/program_metadata.cpp
file_sys/program_metadata.h
file_sys/romfs_factory.cpp
file_sys/romfs_factory.h
file_sys/romfs_filesystem.cpp
file_sys/romfs_filesystem.h
file_sys/savedata_factory.cpp
file_sys/savedata_factory.h
file_sys/sdmc_factory.cpp
file_sys/sdmc_factory.h
file_sys/vfs.cpp
file_sys/vfs.h
file_sys/vfs_offset.cpp
file_sys/vfs_offset.h
file_sys/vfs_real.cpp
file_sys/vfs_real.h
file_sys/storage.h
frontend/emu_window.cpp
frontend/emu_window.h
frontend/framebuffer_layout.cpp

View File

@@ -19,7 +19,6 @@
#include "core/loader/loader.h"
#include "core/memory_setup.h"
#include "core/settings.h"
#include "file_sys/vfs_real.h"
#include "video_core/video_core.h"
namespace Core {
@@ -85,7 +84,7 @@ System::ResultStatus System::SingleStep() {
}
System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& filepath) {
app_loader = Loader::GetLoader(std::make_shared<FileSys::RealVfsFile>(filepath));
app_loader = Loader::GetLoader(filepath);
if (!app_loader) {
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);

View File

@@ -1,167 +0,0 @@
// 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/file_sys/content_archive.h"
#include "core/file_sys/vfs_offset.h"
#include "core/loader/loader.h"
namespace FileSys {
// Media offsets in headers are stored divided by 512. Mult. by this to get real offset.
constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200;
constexpr u64 SECTION_HEADER_SIZE = 0x200;
constexpr u64 SECTION_HEADER_OFFSET = 0x400;
constexpr u32 IVFC_MAX_LEVEL = 6;
enum class NCASectionFilesystemType : u8 {
PFS0 = 0x2,
ROMFS = 0x3,
};
struct NCASectionHeaderBlock {
INSERT_PADDING_BYTES(3);
NCASectionFilesystemType filesystem_type;
u8 crypto_type;
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
struct PFS0Superblock {
NCASectionHeaderBlock header_block;
std::array<u8, 0x20> hash;
u32_le size;
INSERT_PADDING_BYTES(4);
u64_le hash_table_offset;
u64_le hash_table_size;
u64_le pfs0_header_offset;
u64_le pfs0_size;
INSERT_PADDING_BYTES(432);
};
static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
struct IVFCLevel {
u64_le offset;
u64_le size;
u32_le block_size;
u32_le reserved;
};
static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size.");
struct RomFSSuperblock {
NCASectionHeaderBlock header_block;
u32_le magic;
u32_le magic_number;
INSERT_PADDING_BYTES(8);
std::array<IVFCLevel, 6> levels;
INSERT_PADDING_BYTES(64);
};
static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size.");
NCA::NCA(VirtualFile file_) : file(file_) {
if (sizeof(NCAHeader) != file->ReadObject(&header))
LOG_CRITICAL(Loader, "File reader errored out during header read.");
if (!IsValidNCA(header)) {
status = Loader::ResultStatus::ErrorInvalidFormat;
return;
}
std::ptrdiff_t number_sections =
std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
[](NCASectionTableEntry entry) { return entry.media_offset > 0; });
for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
// Seek to beginning of this section.
NCASectionHeaderBlock block{};
if (sizeof(NCASectionHeaderBlock) !=
file->ReadObject(&block, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
LOG_CRITICAL(Loader, "File reader errored out during header read.");
if (block.filesystem_type == NCASectionFilesystemType::ROMFS) {
RomFSSuperblock sb{};
if (sizeof(RomFSSuperblock) !=
file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
LOG_CRITICAL(Loader, "File reader errored out during header read.");
const size_t romfs_offset =
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER +
sb.levels[IVFC_MAX_LEVEL - 1].offset;
const size_t romfs_size = sb.levels[IVFC_MAX_LEVEL - 1].size;
files.emplace_back(std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset));
romfs = files.back();
} else if (block.filesystem_type == NCASectionFilesystemType::PFS0) {
PFS0Superblock sb{};
// Seek back to beginning of this section.
if (sizeof(PFS0Superblock) !=
file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
LOG_CRITICAL(Loader, "File reader errored out during header read.");
u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
MEDIA_OFFSET_MULTIPLIER) +
sb.pfs0_header_offset;
u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
header.section_tables[i].media_offset);
auto npfs = std::make_shared<PartitionFilesystem>(
std::make_shared<OffsetVfsFile>(file, size, offset));
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
dirs.emplace_back(npfs);
if (IsDirectoryExeFS(dirs.back()))
exefs = dirs.back();
}
}
}
status = Loader::ResultStatus::Success;
}
Loader::ResultStatus NCA::GetStatus() const {
return status;
}
std::vector<std::shared_ptr<VfsFile>> NCA::GetFiles() const {
if (status != Loader::ResultStatus::Success)
return {};
return files;
}
std::vector<std::shared_ptr<VfsDirectory>> NCA::GetSubdirectories() const {
if (status != Loader::ResultStatus::Success)
return {};
return dirs;
}
std::string NCA::GetName() const {
return file->GetName();
}
std::shared_ptr<VfsDirectory> NCA::GetParentDirectory() const {
return file->GetContainingDirectory();
}
NCAContentType NCA::GetType() const {
return header.content_type;
}
u64 NCA::GetTitleId() const {
if (status != Loader::ResultStatus::Success)
return {};
return header.title_id;
}
VirtualFile NCA::GetRomFS() const {
return romfs;
}
VirtualDir NCA::GetExeFS() const {
return exefs;
}
bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
return false;
}
} // namespace FileSys

View File

@@ -1,95 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/partition_filesystem.h"
namespace FileSys {
enum class NCAContentType : u8 {
Program = 0,
Meta = 1,
Control = 2,
Manual = 3,
Data = 4,
};
struct NCASectionTableEntry {
u32_le media_offset;
u32_le media_end_offset;
INSERT_PADDING_BYTES(0x8);
};
static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size.");
struct NCAHeader {
std::array<u8, 0x100> rsa_signature_1;
std::array<u8, 0x100> rsa_signature_2;
u32_le magic;
u8 is_system;
NCAContentType content_type;
u8 crypto_type;
u8 key_index;
u64_le size;
u64_le title_id;
INSERT_PADDING_BYTES(0x4);
u32_le sdk_version;
u8 crypto_type_2;
INSERT_PADDING_BYTES(15);
std::array<u8, 0x10> rights_id;
std::array<NCASectionTableEntry, 0x4> section_tables;
std::array<std::array<u8, 0x20>, 0x4> hash_tables;
std::array<std::array<u8, 0x10>, 0x4> key_area;
INSERT_PADDING_BYTES(0xC0);
};
static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
inline bool IsDirectoryExeFS(std::shared_ptr<FileSys::VfsDirectory> pfs) {
// According to switchbrew, an exefs must only contain these two files:
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
}
inline bool IsValidNCA(const NCAHeader& header) {
return header.magic == Common::MakeMagic('N', 'C', 'A', '2') ||
header.magic == Common::MakeMagic('N', 'C', 'A', '3');
}
// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
// After construction, use GetStatus to determine if the file is valid and ready to be used.
class NCA : public ReadOnlyVfsDirectory {
public:
explicit NCA(VirtualFile file);
Loader::ResultStatus GetStatus() const;
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
std::string GetName() const override;
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
NCAContentType GetType() const;
u64 GetTitleId() const;
VirtualFile GetRomFS() const;
VirtualDir GetExeFS() const;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
std::vector<VirtualDir> dirs;
std::vector<VirtualFile> files;
VirtualFile romfs = nullptr;
VirtualDir exefs = nullptr;
VirtualFile file;
NCAHeader header{};
Loader::ResultStatus status{};
};
} // namespace FileSys

View File

@@ -8,17 +8,13 @@
#include <cstddef>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/file_sys/filesystem.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// FileSys namespace
namespace FileSys {
enum EntryType : u8 {
Directory = 0,
File = 1,
};
// Structure of a directory entry, from
// http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry
const size_t FILENAME_LENGTH = 0x300;

View File

@@ -0,0 +1,239 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include <memory>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/file_sys/disk_filesystem.h"
#include "core/file_sys/errors.h"
namespace FileSys {
static std::string ModeFlagsToString(Mode mode) {
std::string mode_str;
u32 mode_flags = static_cast<u32>(mode);
// Calculate the correct open mode for the file.
if ((mode_flags & static_cast<u32>(Mode::Read)) &&
(mode_flags & static_cast<u32>(Mode::Write))) {
if (mode_flags & static_cast<u32>(Mode::Append))
mode_str = "a+";
else
mode_str = "r+";
} else {
if (mode_flags & static_cast<u32>(Mode::Read))
mode_str = "r";
else if (mode_flags & static_cast<u32>(Mode::Append))
mode_str = "a";
else if (mode_flags & static_cast<u32>(Mode::Write))
mode_str = "w";
}
mode_str += "b";
return mode_str;
}
std::string Disk_FileSystem::GetName() const {
return "Disk";
}
ResultVal<std::unique_ptr<StorageBackend>> Disk_FileSystem::OpenFile(const std::string& path,
Mode mode) const {
// Calculate the correct open mode for the file.
std::string mode_str = ModeFlagsToString(mode);
std::string full_path = base_directory + path;
auto file = std::make_shared<FileUtil::IOFile>(full_path, mode_str.c_str());
if (!file->IsOpen()) {
return ERROR_PATH_NOT_FOUND;
}
return MakeResult<std::unique_ptr<StorageBackend>>(
std::make_unique<Disk_Storage>(std::move(file)));
}
ResultCode Disk_FileSystem::DeleteFile(const std::string& path) const {
std::string full_path = base_directory + path;
if (!FileUtil::Exists(full_path)) {
return ERROR_PATH_NOT_FOUND;
}
FileUtil::Delete(full_path);
return RESULT_SUCCESS;
}
ResultCode Disk_FileSystem::RenameFile(const std::string& src_path,
const std::string& dest_path) const {
const std::string full_src_path = base_directory + src_path;
const std::string full_dest_path = base_directory + dest_path;
if (!FileUtil::Exists(full_src_path)) {
return ERROR_PATH_NOT_FOUND;
}
// TODO(wwylele): Use correct error code
return FileUtil::Rename(full_src_path, full_dest_path) ? RESULT_SUCCESS : ResultCode(-1);
}
ResultCode Disk_FileSystem::DeleteDirectory(const Path& path) const {
LOG_WARNING(Service_FS, "(STUBBED) called");
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultCode Disk_FileSystem::DeleteDirectoryRecursively(const Path& path) const {
LOG_WARNING(Service_FS, "(STUBBED) called");
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultCode Disk_FileSystem::CreateFile(const std::string& path, u64 size) const {
LOG_WARNING(Service_FS, "(STUBBED) called");
std::string full_path = base_directory + path;
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(Subv): Find out the correct error code
return ResultCode(-1);
}
ResultCode Disk_FileSystem::CreateDirectory(const std::string& path) const {
// TODO(Subv): Perform path validation to prevent escaping the emulator sandbox.
std::string full_path = base_directory + path;
if (FileUtil::CreateDir(full_path)) {
return RESULT_SUCCESS;
}
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating {}", full_path);
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultCode Disk_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const {
LOG_WARNING(Service_FS, "(STUBBED) called");
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultVal<std::unique_ptr<DirectoryBackend>> Disk_FileSystem::OpenDirectory(
const std::string& path) const {
std::string full_path = base_directory + path;
if (!FileUtil::IsDirectory(full_path)) {
// TODO(Subv): Find the correct error code for this.
return ResultCode(-1);
}
auto directory = std::make_unique<Disk_Directory>(full_path);
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
}
u64 Disk_FileSystem::GetFreeSpaceSize() const {
LOG_WARNING(Service_FS, "(STUBBED) called");
return 0;
}
ResultVal<FileSys::EntryType> Disk_FileSystem::GetEntryType(const std::string& path) const {
std::string full_path = base_directory + path;
if (!FileUtil::Exists(full_path)) {
return ERROR_PATH_NOT_FOUND;
}
if (FileUtil::IsDirectory(full_path))
return MakeResult(EntryType::Directory);
return MakeResult(EntryType::File);
}
ResultVal<size_t> Disk_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length);
file->Seek(offset, SEEK_SET);
return MakeResult<size_t>(file->ReadBytes(buffer, length));
}
ResultVal<size_t> Disk_Storage::Write(const u64 offset, const size_t length, const bool flush,
const u8* buffer) const {
LOG_WARNING(Service_FS, "(STUBBED) called");
file->Seek(offset, SEEK_SET);
size_t written = file->WriteBytes(buffer, length);
if (flush) {
file->Flush();
}
return MakeResult<size_t>(written);
}
u64 Disk_Storage::GetSize() const {
return file->GetSize();
}
bool Disk_Storage::SetSize(const u64 size) const {
file->Resize(size);
file->Flush();
return true;
}
Disk_Directory::Disk_Directory(const std::string& path) {
unsigned size = FileUtil::ScanDirectoryTree(path, directory);
directory.size = size;
directory.isDirectory = true;
children_iterator = directory.children.begin();
}
u64 Disk_Directory::Read(const u64 count, Entry* entries) {
u64 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 {}: size={} dir={}", filename, 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;
}
if (file.isDirectory) {
entry.file_size = 0;
entry.type = EntryType::Directory;
} else {
entry.file_size = file.size;
entry.type = EntryType::File;
}
++entries_read;
++children_iterator;
}
return entries_read;
}
u64 Disk_Directory::GetEntryCount() const {
// We convert the children iterator into a const_iterator to allow template argument deduction
// in std::distance.
std::vector<FileUtil::FSTEntry>::const_iterator current = children_iterator;
return std::distance(current, directory.children.end());
}
} // namespace FileSys

View File

@@ -0,0 +1,84 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <cstddef>
#include <memory>
#include <string>
#include "common/common_types.h"
#include "common/file_util.h"
#include "core/file_sys/directory.h"
#include "core/file_sys/filesystem.h"
#include "core/file_sys/storage.h"
#include "core/hle/result.h"
namespace FileSys {
class Disk_FileSystem : public FileSystemBackend {
public:
explicit Disk_FileSystem(std::string base_directory)
: base_directory(std::move(base_directory)) {}
std::string GetName() const override;
ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
Mode mode) const override;
ResultCode DeleteFile(const std::string& path) const override;
ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override;
ResultCode DeleteDirectory(const Path& path) const override;
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
ResultCode CreateFile(const std::string& path, u64 size) const override;
ResultCode CreateDirectory(const std::string& path) const override;
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
const std::string& path) const override;
u64 GetFreeSpaceSize() const override;
ResultVal<EntryType> GetEntryType(const std::string& path) const override;
protected:
std::string base_directory;
};
class Disk_Storage : public StorageBackend {
public:
explicit Disk_Storage(std::shared_ptr<FileUtil::IOFile> file) : file(std::move(file)) {}
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 {
return false;
}
void Flush() const override {}
private:
std::shared_ptr<FileUtil::IOFile> file;
};
class Disk_Directory : public DirectoryBackend {
public:
explicit Disk_Directory(const std::string& path);
~Disk_Directory() override {
Close();
}
u64 Read(const u64 count, Entry* entries) override;
u64 GetEntryCount() const override;
bool Close() const override {
return true;
}
protected:
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

View File

@@ -11,7 +11,7 @@ namespace FileSys {
namespace ErrCodes {
enum {
NotFound = 1,
TitleNotFound = 1002,
SaveDataNotFound = 1002,
SdCardNotFound = 2001,
RomFSNotFound = 2520,
};

View File

@@ -0,0 +1,122 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstddef>
#include <iomanip>
#include <sstream>
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/file_sys/filesystem.h"
#include "core/memory.h"
namespace FileSys {
Path::Path(LowPathType type, u32 size, u32 pointer) : type(type) {
switch (type) {
case Binary: {
binary.resize(size);
Memory::ReadBlock(pointer, binary.data(), binary.size());
break;
}
case Char: {
string.resize(size - 1); // Data is always null-terminated.
Memory::ReadBlock(pointer, &string[0], string.size());
break;
}
case Wchar: {
u16str.resize(size / 2 - 1); // Data is always null-terminated.
Memory::ReadBlock(pointer, &u16str[0], u16str.size() * sizeof(char16_t));
break;
}
default:
break;
}
}
std::string Path::DebugStr() const {
switch (GetType()) {
case Invalid:
default:
return "[Invalid]";
case Empty:
return "[Empty]";
case Binary: {
std::stringstream res;
res << "[Binary: ";
for (unsigned byte : binary)
res << std::hex << std::setw(2) << std::setfill('0') << byte;
res << ']';
return res.str();
}
case Char:
return "[Char: " + AsString() + ']';
case Wchar:
return "[Wchar: " + AsString() + ']';
}
}
std::string Path::AsString() const {
switch (GetType()) {
case Char:
return string;
case Wchar:
return Common::UTF16ToUTF8(u16str);
case Empty:
return {};
case Invalid:
case Binary:
default:
// TODO(yuriks): Add assert
LOG_ERROR(Service_FS, "LowPathType cannot be converted to string!");
return {};
}
}
std::u16string Path::AsU16Str() const {
switch (GetType()) {
case Char:
return Common::UTF8ToUTF16(string);
case Wchar:
return u16str;
case Empty:
return {};
case Invalid:
case Binary:
// TODO(yuriks): Add assert
LOG_ERROR(Service_FS, "LowPathType cannot be converted to u16string!");
return {};
}
UNREACHABLE();
}
std::vector<u8> Path::AsBinary() const {
switch (GetType()) {
case Binary:
return binary;
case Char:
return std::vector<u8>(string.begin(), string.end());
case Wchar: {
// use two u8 for each character of u16str
std::vector<u8> to_return(u16str.size() * 2);
for (size_t i = 0; i < u16str.size(); ++i) {
u16 tmp_char = u16str.at(i);
to_return[i * 2] = (tmp_char & 0xFF00) >> 8;
to_return[i * 2 + 1] = (tmp_char & 0x00FF);
}
return to_return;
}
case Empty:
return {};
case Invalid:
default:
// TODO(yuriks): Add assert
LOG_ERROR(Service_FS, "LowPathType cannot be converted to binary!");
return {};
}
}
} // namespace FileSys

View File

@@ -0,0 +1,170 @@
// 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 <utility>
#include <vector>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/result.h"
namespace FileSys {
class StorageBackend;
class DirectoryBackend;
// Path string type
enum LowPathType : u32 {
Invalid = 0,
Empty = 1,
Binary = 2,
Char = 3,
Wchar = 4,
};
enum EntryType : u8 {
Directory = 0,
File = 1,
};
enum class Mode : u32 {
Read = 1,
Write = 2,
Append = 4,
};
class Path {
public:
Path() : type(Invalid) {}
Path(const char* path) : type(Char), string(path) {}
Path(std::vector<u8> binary_data) : type(Binary), binary(std::move(binary_data)) {}
Path(LowPathType type, u32 size, u32 pointer);
LowPathType GetType() const {
return type;
}
/**
* Gets the string representation of the path for debugging
* @return String representation of the path for debugging
*/
std::string DebugStr() const;
std::string AsString() const;
std::u16string AsU16Str() const;
std::vector<u8> AsBinary() const;
private:
LowPathType type;
std::vector<u8> binary;
std::string string;
std::u16string u16str;
};
/// Parameters of the archive, as specified in the Create or Format call.
struct ArchiveFormatInfo {
u32_le total_size; ///< The pre-defined size of the archive.
u32_le number_directories; ///< The pre-defined number of directories in the archive.
u32_le number_files; ///< The pre-defined number of files in the archive.
u8 duplicate_data; ///< Whether the archive should duplicate the data.
};
static_assert(std::is_pod<ArchiveFormatInfo>::value, "ArchiveFormatInfo is not POD");
class FileSystemBackend : NonCopyable {
public:
virtual ~FileSystemBackend() {}
/**
* Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.)
*/
virtual std::string GetName() const = 0;
/**
* Create a file specified by its path
* @param path Path relative to the Archive
* @param size The size of the new file, filled with zeroes
* @return Result of the operation
*/
virtual ResultCode CreateFile(const std::string& path, u64 size) const = 0;
/**
* Delete a file specified by its path
* @param path Path relative to the archive
* @return Result of the operation
*/
virtual ResultCode DeleteFile(const std::string& path) const = 0;
/**
* Create a directory specified by its path
* @param path Path relative to the archive
* @return Result of the operation
*/
virtual ResultCode CreateDirectory(const std::string& path) const = 0;
/**
* Delete a directory specified by its path
* @param path Path relative to the archive
* @return Result of the operation
*/
virtual ResultCode DeleteDirectory(const Path& path) const = 0;
/**
* Delete a directory specified by its path and anything under it
* @param path Path relative to the archive
* @return Result of the operation
*/
virtual ResultCode DeleteDirectoryRecursively(const Path& path) const = 0;
/**
* Rename a File specified by its path
* @param src_path Source path relative to the archive
* @param dest_path Destination path relative to the archive
* @return Result of the operation
*/
virtual ResultCode RenameFile(const std::string& src_path,
const std::string& dest_path) const = 0;
/**
* Rename a Directory specified by its path
* @param src_path Source path relative to the archive
* @param dest_path Destination path relative to the archive
* @return Result of the operation
*/
virtual ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const = 0;
/**
* Open a file specified by its path, using the specified mode
* @param path Path relative to the archive
* @param mode Mode to open the file with
* @return Opened file, or error code
*/
virtual ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
Mode mode) const = 0;
/**
* Open a directory specified by its path
* @param path Path relative to the archive
* @return Opened directory, or error code
*/
virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
const std::string& path) const = 0;
/**
* Get the free space
* @return The number of free bytes in the archive
*/
virtual u64 GetFreeSpaceSize() const = 0;
/**
* Get the type of the specified path
* @return The type of the specified path or error code
*/
virtual ResultVal<EntryType> GetEntryType(const std::string& path) const = 0;
};
} // namespace FileSys

View File

@@ -1,17 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_types.h"
namespace FileSys {
enum class Mode : u32 {
Read = 1,
Write = 2,
Append = 4,
};
} // namespace FileSys

View File

@@ -6,30 +6,29 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/file_sys/partition_filesystem.h"
#include "core/file_sys/vfs_offset.h"
#include "core/loader/loader.h"
namespace FileSys {
PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) {
// At least be as large as the header
if (file->GetSize() < sizeof(Header)) {
status = Loader::ResultStatus::Error;
return;
}
Loader::ResultStatus PartitionFilesystem::Load(const std::string& file_path, size_t offset) {
FileUtil::IOFile file(file_path, "rb");
if (!file.IsOpen())
return Loader::ResultStatus::Error;
// At least be as large as the header
if (file.GetSize() < sizeof(Header))
return Loader::ResultStatus::Error;
file.Seek(offset, SEEK_SET);
// For cartridges, HFSs can get very large, so we need to calculate the size up to
// the actual content itself instead of just blindly reading in the entire file.
Header pfs_header;
if (sizeof(Header) != file->ReadObject(&pfs_header)) {
status = Loader::ResultStatus::Error;
return;
}
if (!file.ReadBytes(&pfs_header, sizeof(Header)))
return Loader::ResultStatus::Error;
if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') &&
pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) {
status = Loader::ResultStatus::ErrorInvalidFormat;
return;
return Loader::ResultStatus::ErrorInvalidFormat;
}
bool is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0');
@@ -39,86 +38,99 @@ PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) {
sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size;
// Actually read in now...
std::vector<u8> file_data = file->ReadBytes(metadata_size);
file.Seek(offset, SEEK_SET);
std::vector<u8> file_data(metadata_size);
if (file_data.size() != metadata_size) {
status = Loader::ResultStatus::Error;
return;
}
if (!file.ReadBytes(file_data.data(), metadata_size))
return Loader::ResultStatus::Error;
size_t total_size = file_data.size();
if (total_size < sizeof(Header)) {
status = Loader::ResultStatus::Error;
return;
}
Loader::ResultStatus result = Load(file_data);
if (result != Loader::ResultStatus::Success)
LOG_ERROR(Service_FS, "Failed to load PFS from file {}!", file_path);
memcpy(&pfs_header, file_data.data(), sizeof(Header));
return result;
}
Loader::ResultStatus PartitionFilesystem::Load(const std::vector<u8>& file_data, size_t offset) {
size_t total_size = file_data.size() - offset;
if (total_size < sizeof(Header))
return Loader::ResultStatus::Error;
memcpy(&pfs_header, &file_data[offset], sizeof(Header));
if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') &&
pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) {
status = Loader::ResultStatus::ErrorInvalidFormat;
return;
return Loader::ResultStatus::ErrorInvalidFormat;
}
is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0');
size_t entries_offset = sizeof(Header);
size_t entries_offset = offset + sizeof(Header);
size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry);
size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size);
content_offset = strtab_offset + pfs_header.strtab_size;
for (u16 i = 0; i < pfs_header.num_entries; i++) {
FSEntry entry;
FileEntry entry;
memcpy(&entry, &file_data[entries_offset + (i * entry_size)], sizeof(FSEntry));
std::string name(
reinterpret_cast<const char*>(&file_data[strtab_offset + entry.strtab_offset]));
pfs_files.emplace_back(
std::make_shared<OffsetVfsFile>(file, entry.size, content_offset + entry.offset, name));
memcpy(&entry.fs_entry, &file_data[entries_offset + (i * entry_size)], sizeof(FSEntry));
entry.name = std::string(reinterpret_cast<const char*>(
&file_data[strtab_offset + entry.fs_entry.strtab_offset]));
pfs_entries.push_back(std::move(entry));
}
status = Loader::ResultStatus::Success;
content_offset = strtab_offset + pfs_header.strtab_size;
return Loader::ResultStatus::Success;
}
Loader::ResultStatus PartitionFilesystem::GetStatus() const {
return status;
u32 PartitionFilesystem::GetNumEntries() const {
return pfs_header.num_entries;
}
std::vector<std::shared_ptr<VfsFile>> PartitionFilesystem::GetFiles() const {
return pfs_files;
u64 PartitionFilesystem::GetEntryOffset(u32 index) const {
if (index > GetNumEntries())
return 0;
return content_offset + pfs_entries[index].fs_entry.offset;
}
std::vector<std::shared_ptr<VfsDirectory>> PartitionFilesystem::GetSubdirectories() const {
return {};
u64 PartitionFilesystem::GetEntrySize(u32 index) const {
if (index > GetNumEntries())
return 0;
return pfs_entries[index].fs_entry.size;
}
std::string PartitionFilesystem::GetName() const {
return is_hfs ? "HFS0" : "PFS0";
std::string PartitionFilesystem::GetEntryName(u32 index) const {
if (index > GetNumEntries())
return "";
return pfs_entries[index].name;
}
std::shared_ptr<VfsDirectory> PartitionFilesystem::GetParentDirectory() const {
// TODO(DarkLordZach): Add support for nested containers.
return nullptr;
u64 PartitionFilesystem::GetFileOffset(const std::string& name) const {
for (u32 i = 0; i < pfs_header.num_entries; i++) {
if (pfs_entries[i].name == name)
return content_offset + pfs_entries[i].fs_entry.offset;
}
return 0;
}
void PartitionFilesystem::PrintDebugInfo() const {
LOG_DEBUG(Service_FS, "Magic: {:.4}", pfs_header.magic);
u64 PartitionFilesystem::GetFileSize(const std::string& name) const {
for (u32 i = 0; i < pfs_header.num_entries; i++) {
if (pfs_entries[i].name == name)
return pfs_entries[i].fs_entry.size;
}
return 0;
}
void PartitionFilesystem::Print() const {
LOG_DEBUG(Service_FS, "Magic: {}", pfs_header.magic);
LOG_DEBUG(Service_FS, "Files: {}", pfs_header.num_entries);
for (u32 i = 0; i < pfs_header.num_entries; i++) {
LOG_DEBUG(Service_FS, " > File {}: {} (0x{:X} bytes, at 0x{:X})", i,
pfs_files[i]->GetName(), pfs_files[i]->GetSize(),
dynamic_cast<OffsetVfsFile*>(pfs_files[i].get())->GetOffset());
pfs_entries[i].name.c_str(), pfs_entries[i].fs_entry.size,
GetFileOffset(pfs_entries[i].name));
}
}
bool PartitionFilesystem::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
auto iter = std::find(pfs_files.begin(), pfs_files.end(), file);
if (iter == pfs_files.end())
return false;
pfs_files[iter - pfs_files.begin()] = pfs_files.back();
pfs_files.pop_back();
pfs_dirs.emplace_back(dir);
return true;
}
} // namespace FileSys

View File

@@ -10,7 +10,6 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/vfs.h"
namespace Loader {
enum class ResultStatus;
@@ -22,19 +21,19 @@ namespace FileSys {
* Helper which implements an interface to parse PFS/HFS filesystems.
* Data can either be loaded from a file path or data with an offset into it.
*/
class PartitionFilesystem : public ReadOnlyVfsDirectory {
class PartitionFilesystem {
public:
explicit PartitionFilesystem(std::shared_ptr<VfsFile> file);
Loader::ResultStatus GetStatus() const;
Loader::ResultStatus Load(const std::string& file_path, size_t offset = 0);
Loader::ResultStatus Load(const std::vector<u8>& file_data, size_t offset = 0);
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
std::string GetName() const override;
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
void PrintDebugInfo() const;
u32 GetNumEntries() const;
u64 GetEntryOffset(u32 index) const;
u64 GetEntrySize(u32 index) const;
std::string GetEntryName(u32 index) const;
u64 GetFileOffset(const std::string& name) const;
u64 GetFileSize(const std::string& name) const;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
void Print() const;
private:
struct Header {
@@ -73,14 +72,16 @@ private:
#pragma pack(pop)
Loader::ResultStatus status;
struct FileEntry {
FSEntry fs_entry;
std::string name;
};
Header pfs_header;
bool is_hfs;
size_t content_offset;
std::vector<VirtualFile> pfs_files;
std::vector<VirtualDir> pfs_dirs;
std::vector<FileEntry> pfs_entries;
};
} // namespace FileSys

View File

@@ -0,0 +1,98 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <set>
#include "common/file_util.h"
#include "common/string_util.h"
#include "core/file_sys/path_parser.h"
namespace FileSys {
PathParser::PathParser(const Path& path) {
if (path.GetType() != LowPathType::Char && path.GetType() != LowPathType::Wchar) {
is_valid = false;
return;
}
auto path_string = path.AsString();
if (path_string.size() == 0 || path_string[0] != '/') {
is_valid = false;
return;
}
// Filter out invalid characters for the host system.
// Although some of these characters are valid on 3DS, they are unlikely to be used by games.
if (std::find_if(path_string.begin(), path_string.end(), [](char c) {
static const std::set<char> invalid_chars{'<', '>', '\\', '|', ':', '\"', '*', '?'};
return invalid_chars.find(c) != invalid_chars.end();
}) != path_string.end()) {
is_valid = false;
return;
}
Common::SplitString(path_string, '/', path_sequence);
auto begin = path_sequence.begin();
auto end = path_sequence.end();
end = std::remove_if(begin, end, [](std::string& str) { return str == "" || str == "."; });
path_sequence = std::vector<std::string>(begin, end);
// checks if the path is out of bounds.
int level = 0;
for (auto& node : path_sequence) {
if (node == "..") {
--level;
if (level < 0) {
is_valid = false;
return;
}
} else {
++level;
}
}
is_valid = true;
is_root = level == 0;
}
PathParser::HostStatus PathParser::GetHostStatus(const std::string& mount_point) const {
auto path = mount_point;
if (!FileUtil::IsDirectory(path))
return InvalidMountPoint;
if (path_sequence.empty()) {
return DirectoryFound;
}
for (auto iter = path_sequence.begin(); iter != path_sequence.end() - 1; iter++) {
if (path.back() != '/')
path += '/';
path += *iter;
if (!FileUtil::Exists(path))
return PathNotFound;
if (FileUtil::IsDirectory(path))
continue;
return FileInPath;
}
path += "/" + path_sequence.back();
if (!FileUtil::Exists(path))
return NotFound;
if (FileUtil::IsDirectory(path))
return DirectoryFound;
return FileFound;
}
std::string PathParser::BuildHostPath(const std::string& mount_point) const {
std::string path = mount_point;
for (auto& node : path_sequence) {
if (path.back() != '/')
path += '/';
path += node;
}
return path;
}
} // namespace FileSys

View File

@@ -0,0 +1,61 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
#include <vector>
#include "core/file_sys/filesystem.h"
namespace FileSys {
/**
* A helper class parsing and verifying a string-type Path.
* Every archives with a sub file system should use this class to parse the path argument and check
* the status of the file / directory in question on the host file system.
*/
class PathParser {
public:
explicit PathParser(const Path& path);
/**
* Checks if the Path is valid.
* This function should be called once a PathParser is constructed.
* A Path is valid if:
* - it is a string path (with type LowPathType::Char or LowPathType::Wchar),
* - it starts with "/" (this seems a hard requirement in real 3DS),
* - it doesn't contain invalid characters, and
* - it doesn't go out of the root directory using "..".
*/
bool IsValid() const {
return is_valid;
}
/// Checks if the Path represents the root directory.
bool IsRootDirectory() const {
return is_root;
}
enum HostStatus {
InvalidMountPoint,
PathNotFound, // "/a/b/c" when "a" doesn't exist
FileInPath, // "/a/b/c" when "a" is a file
FileFound, // "/a/b/c" when "c" is a file
DirectoryFound, // "/a/b/c" when "c" is a directory
NotFound // "/a/b/c" when "a/b/" exists but "c" doesn't exist
};
/// Checks the status of the specified file / directory by the Path on the host file system.
HostStatus GetHostStatus(const std::string& mount_point) const;
/// Builds a full path on the host file system.
std::string BuildHostPath(const std::string& mount_point) const;
private:
std::vector<std::string> path_sequence;
bool is_valid{};
bool is_root{};
};
} // namespace FileSys

View File

@@ -9,29 +9,40 @@
namespace FileSys {
Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
size_t total_size = static_cast<size_t>(file->GetSize());
Loader::ResultStatus ProgramMetadata::Load(const std::string& file_path) {
FileUtil::IOFile file(file_path, "rb");
if (!file.IsOpen())
return Loader::ResultStatus::Error;
std::vector<u8> file_data(file.GetSize());
if (!file.ReadBytes(file_data.data(), file_data.size()))
return Loader::ResultStatus::Error;
Loader::ResultStatus result = Load(file_data);
if (result != Loader::ResultStatus::Success)
LOG_ERROR(Service_FS, "Failed to load NPDM from file {}!", file_path);
return result;
}
Loader::ResultStatus ProgramMetadata::Load(const std::vector<u8>& file_data, size_t offset) {
size_t total_size = static_cast<size_t>(file_data.size() - offset);
if (total_size < sizeof(Header))
return Loader::ResultStatus::Error;
// TODO(DarkLordZach): Use ReadObject when Header/AcidHeader becomes trivially copyable.
std::vector<u8> npdm_header_data = file->ReadBytes(sizeof(Header));
if (sizeof(Header) != npdm_header_data.size())
return Loader::ResultStatus::Error;
std::memcpy(&npdm_header, npdm_header_data.data(), sizeof(Header));
size_t header_offset = offset;
memcpy(&npdm_header, &file_data[offset], sizeof(Header));
std::vector<u8> acid_header_data = file->ReadBytes(sizeof(AcidHeader), npdm_header.acid_offset);
if (sizeof(AcidHeader) != acid_header_data.size())
return Loader::ResultStatus::Error;
std::memcpy(&acid_header, acid_header_data.data(), sizeof(AcidHeader));
size_t aci_offset = header_offset + npdm_header.aci_offset;
size_t acid_offset = header_offset + npdm_header.acid_offset;
memcpy(&aci_header, &file_data[aci_offset], sizeof(AciHeader));
memcpy(&acid_header, &file_data[acid_offset], sizeof(AcidHeader));
if (sizeof(AciHeader) != file->ReadObject(&aci_header, npdm_header.aci_offset))
return Loader::ResultStatus::Error;
if (sizeof(FileAccessControl) != file->ReadObject(&acid_file_access, acid_header.fac_offset))
return Loader::ResultStatus::Error;
if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset))
return Loader::ResultStatus::Error;
size_t fac_offset = acid_offset + acid_header.fac_offset;
size_t fah_offset = aci_offset + aci_header.fah_offset;
memcpy(&acid_file_access, &file_data[fac_offset], sizeof(FileAccessControl));
memcpy(&aci_file_access, &file_data[fah_offset], sizeof(FileAccessHeader));
return Loader::ResultStatus::Success;
}

View File

@@ -10,7 +10,6 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "partition_filesystem.h"
namespace Loader {
enum class ResultStatus;
@@ -38,7 +37,8 @@ enum class ProgramFilePermission : u64 {
*/
class ProgramMetadata {
public:
Loader::ResultStatus Load(VirtualFile file);
Loader::ResultStatus Load(const std::string& file_path);
Loader::ResultStatus Load(const std::vector<u8>& file_data, size_t offset = 0);
bool Is64BitProgram() const;
ProgramAddressSpaceType GetAddressSpaceType() const;
@@ -51,7 +51,6 @@ public:
void Print() const;
private:
// TODO(DarkLordZach): BitField is not trivially copyable.
struct Header {
std::array<char, 4> magic;
std::array<u8, 8> reserved;
@@ -78,7 +77,6 @@ private:
static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong");
// TODO(DarkLordZach): BitField is not trivially copyable.
struct AcidHeader {
std::array<u8, 0x100> signature;
std::array<u8, 0x100> nca_modulus;

View File

@@ -7,19 +7,21 @@
#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 {
RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
// Load the RomFS from the app
if (Loader::ResultStatus::Success != app_loader.ReadRomFS(file)) {
if (Loader::ResultStatus::Success != app_loader.ReadRomFS(romfs_file, data_offset, data_size)) {
LOG_ERROR(Service_FS, "Unable to read RomFS!");
}
}
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id) {
ResultVal<std::unique_ptr<FileSystemBackend>> RomFSFactory::Open(u64 title_id) {
// TODO(DarkLordZach): Use title id.
return MakeResult<VirtualFile>(file);
auto archive = std::make_unique<RomFS_FileSystem>(romfs_file, data_offset, data_size);
return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
}
} // namespace FileSys

View File

@@ -5,7 +5,10 @@
#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"
@@ -16,10 +19,12 @@ class RomFSFactory {
public:
explicit RomFSFactory(Loader::AppLoader& app_loader);
ResultVal<VirtualFile> Open(u64 title_id);
ResultVal<std::unique_ptr<FileSystemBackend>> Open(u64 title_id);
private:
VirtualFile file;
std::shared_ptr<FileUtil::IOFile> romfs_file;
u64 data_offset;
u64 data_size;
};
} // namespace FileSys

View File

@@ -0,0 +1,110 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include <memory>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/file_sys/romfs_filesystem.h"
namespace FileSys {
std::string RomFS_FileSystem::GetName() const {
return "RomFS";
}
ResultVal<std::unique_ptr<StorageBackend>> RomFS_FileSystem::OpenFile(const std::string& path,
Mode mode) const {
return MakeResult<std::unique_ptr<StorageBackend>>(
std::make_unique<RomFS_Storage>(romfs_file, data_offset, data_size));
}
ResultCode RomFS_FileSystem::DeleteFile(const std::string& path) const {
LOG_CRITICAL(Service_FS, "Attempted to delete a file from an ROMFS archive ({}).", GetName());
// TODO(bunnei): Use correct error code
return ResultCode(-1);
}
ResultCode RomFS_FileSystem::RenameFile(const std::string& src_path,
const std::string& dest_path) const {
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", GetName());
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultCode RomFS_FileSystem::DeleteDirectory(const Path& path) const {
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).",
GetName());
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultCode RomFS_FileSystem::DeleteDirectoryRecursively(const Path& path) const {
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).",
GetName());
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultCode RomFS_FileSystem::CreateFile(const std::string& path, u64 size) const {
LOG_CRITICAL(Service_FS, "Attempted to create a file in an ROMFS archive ({}).", GetName());
// TODO(bunnei): Use correct error code
return ResultCode(-1);
}
ResultCode RomFS_FileSystem::CreateDirectory(const std::string& path) const {
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive ({}).",
GetName());
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultCode RomFS_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const {
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", GetName());
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultVal<std::unique_ptr<DirectoryBackend>> RomFS_FileSystem::OpenDirectory(
const std::string& path) const {
LOG_WARNING(Service_FS, "Opening Directory in a ROMFS archive");
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<ROMFSDirectory>());
}
u64 RomFS_FileSystem::GetFreeSpaceSize() const {
LOG_WARNING(Service_FS, "Attempted to get the free space in an ROMFS archive");
return 0;
}
ResultVal<FileSys::EntryType> RomFS_FileSystem::GetEntryType(const std::string& path) const {
LOG_CRITICAL(Service_FS, "Called within an ROMFS archive (path {}).", path);
// TODO(wwylele): Use correct error code
return ResultCode(-1);
}
ResultVal<size_t> RomFS_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length);
romfs_file->Seek(data_offset + offset, SEEK_SET);
size_t read_length = (size_t)std::min((u64)length, data_size - offset);
return MakeResult<size_t>(romfs_file->ReadBytes(buffer, read_length));
}
ResultVal<size_t> RomFS_Storage::Write(const u64 offset, const size_t length, const bool flush,
const u8* buffer) const {
LOG_ERROR(Service_FS, "Attempted to write to ROMFS file");
// TODO(Subv): Find error code
return MakeResult<size_t>(0);
}
u64 RomFS_Storage::GetSize() const {
return data_size;
}
bool RomFS_Storage::SetSize(const u64 size) const {
LOG_ERROR(Service_FS, "Attempted to set the size of an ROMFS file");
return false;
}
} // namespace FileSys

View File

@@ -0,0 +1,85 @@
// Copyright 2018 yuzu emulator team
// 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/directory.h"
#include "core/file_sys/filesystem.h"
#include "core/file_sys/storage.h"
#include "core/hle/result.h"
namespace FileSys {
/**
* Helper which implements an interface to deal with Switch .istorage ROMFS images used in some
* archives This should be subclassed by concrete archive types, which will provide the input data
* (load the raw ROMFS archive) and override any required methods
*/
class RomFS_FileSystem : public FileSystemBackend {
public:
RomFS_FileSystem(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
: romfs_file(file), data_offset(offset), data_size(size) {}
std::string GetName() const override;
ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
Mode mode) const override;
ResultCode DeleteFile(const std::string& path) const override;
ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override;
ResultCode DeleteDirectory(const Path& path) const override;
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
ResultCode CreateFile(const std::string& path, u64 size) const override;
ResultCode CreateDirectory(const std::string& path) const override;
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
const std::string& path) const override;
u64 GetFreeSpaceSize() const override;
ResultVal<EntryType> GetEntryType(const std::string& path) const override;
protected:
std::shared_ptr<FileUtil::IOFile> romfs_file;
u64 data_offset;
u64 data_size;
};
class RomFS_Storage : public StorageBackend {
public:
RomFS_Storage(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
: romfs_file(file), data_offset(offset), data_size(size) {}
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 {
return false;
}
void Flush() const override {}
private:
std::shared_ptr<FileUtil::IOFile> romfs_file;
u64 data_offset;
u64 data_size;
};
class ROMFSDirectory : public DirectoryBackend {
public:
u64 Read(const u64 count, Entry* entries) override {
return 0;
}
u64 GetEntryCount() const override {
return 0;
}
bool Close() const override {
return false;
}
};
} // namespace FileSys

View File

@@ -6,6 +6,7 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/file_sys/disk_filesystem.h"
#include "core/file_sys/savedata_factory.h"
#include "core/hle/kernel/process.h"
@@ -16,9 +17,11 @@ std::string SaveDataDescriptor::DebugInfo() {
static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id);
}
SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {}
SaveDataFactory::SaveDataFactory(std::string nand_directory)
: nand_directory(std::move(nand_directory)) {}
ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescriptor meta) {
ResultVal<std::unique_ptr<FileSystemBackend>> SaveDataFactory::Open(SaveDataSpaceId space,
SaveDataDescriptor meta) {
if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) {
if (meta.zero_1 != 0) {
LOG_WARNING(Service_FS,
@@ -53,23 +56,28 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescr
// TODO(DarkLordZach): Try to not create when opening, there are dedicated create save methods.
// But, user_ids don't match so this works for now.
auto out = dir->GetDirectoryRelative(save_directory);
if (out == nullptr) {
if (!FileUtil::Exists(save_directory)) {
// TODO(bunnei): This is a work-around to always create a save data directory if it does not
// already exist. This is a hack, as we do not understand yet how this works on hardware.
// Without a save data directory, many games will assert on boot. This should not have any
// bad side-effects.
out = dir->CreateDirectoryRelative(save_directory);
FileUtil::CreateFullPath(save_directory);
}
// TODO(DarkLordZach): For some reason, CreateFullPath doesn't create the last bit. Should be
// fixed with VFS.
if (!FileUtil::IsDirectory(save_directory)) {
FileUtil::CreateDir(save_directory);
}
// Return an error if the save data doesn't actually exist.
if (out == nullptr) {
if (!FileUtil::IsDirectory(save_directory)) {
// TODO(Subv): Find out correct error code.
return ResultCode(-1);
}
return MakeResult<VirtualDir>(std::move(out));
auto archive = std::make_unique<Disk_FileSystem>(save_directory);
return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
}
std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
@@ -79,14 +87,14 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
if (type == SaveDataType::SaveData && title_id == 0)
title_id = Core::CurrentProcess()->program_id;
std::string out;
std::string prefix;
switch (space) {
case SaveDataSpaceId::NandSystem:
out = "/system/save/";
prefix = nand_directory + "system/save/";
break;
case SaveDataSpaceId::NandUser:
out = "/user/save/";
prefix = nand_directory + "user/save/";
break;
default:
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
@@ -94,9 +102,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
switch (type) {
case SaveDataType::SystemSaveData:
return fmt::format("{}{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]);
return fmt::format("{}{:016X}/{:016X}{:016X}", prefix, save_id, user_id[1], user_id[0]);
case SaveDataType::SaveData:
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", prefix, 0, user_id[1], user_id[0],
title_id);
default:
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));

View File

@@ -7,6 +7,7 @@
#include <memory>
#include <string>
#include "common/common_types.h"
#include "core/file_sys/filesystem.h"
#include "core/hle/result.h"
namespace FileSys {
@@ -44,12 +45,14 @@ static_assert(sizeof(SaveDataDescriptor) == 0x40, "SaveDataDescriptor has incorr
/// File system interface to the SaveData archive
class SaveDataFactory {
public:
explicit SaveDataFactory(VirtualDir dir);
explicit SaveDataFactory(std::string nand_directory);
ResultVal<VirtualDir> Open(SaveDataSpaceId space, SaveDataDescriptor meta);
ResultVal<std::unique_ptr<FileSystemBackend>> Open(SaveDataSpaceId space,
SaveDataDescriptor meta);
private:
VirtualDir dir;
std::string nand_directory;
std::string sd_directory;
std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id,
u64 save_id) const;

View File

@@ -3,15 +3,25 @@
// Refer to the license.txt file included.
#include <memory>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/disk_filesystem.h"
#include "core/file_sys/sdmc_factory.h"
namespace FileSys {
SDMCFactory::SDMCFactory(VirtualDir dir) : dir(std::move(dir)) {}
SDMCFactory::SDMCFactory(std::string sd_directory) : sd_directory(std::move(sd_directory)) {}
ResultVal<VirtualDir> SDMCFactory::Open() {
return MakeResult<VirtualDir>(dir);
ResultVal<std::unique_ptr<FileSystemBackend>> SDMCFactory::Open() {
// Create the SD Card directory if it doesn't already exist.
if (!FileUtil::IsDirectory(sd_directory)) {
FileUtil::CreateFullPath(sd_directory);
}
auto archive = std::make_unique<Disk_FileSystem>(sd_directory);
return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
}
} // namespace FileSys

View File

@@ -4,6 +4,10 @@
#pragma once
#include <memory>
#include <string>
#include "common/common_types.h"
#include "core/file_sys/filesystem.h"
#include "core/hle/result.h"
namespace FileSys {
@@ -11,12 +15,12 @@ namespace FileSys {
/// File system interface to the SDCard archive
class SDMCFactory {
public:
explicit SDMCFactory(VirtualDir dir);
explicit SDMCFactory(std::string sd_directory);
ResultVal<VirtualDir> Open();
ResultVal<std::unique_ptr<FileSystemBackend>> Open();
private:
VirtualDir dir;
std::string sd_directory;
};
} // namespace FileSys

View File

@@ -0,0 +1,63 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <cstddef>
#include "common/common_types.h"
#include "core/hle/result.h"
namespace FileSys {
class StorageBackend : NonCopyable {
public:
StorageBackend() {}
virtual ~StorageBackend() {}
/**
* Read data from the file
* @param offset Offset in bytes to start reading data from
* @param length Length in bytes of data to read from file
* @param buffer Buffer to read data into
* @return Number of bytes read, or error code
*/
virtual ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const = 0;
/**
* Write data to the file
* @param offset Offset in bytes to start writing data to
* @param length Length in bytes of data to write to file
* @param flush The flush parameters (0 == do not flush)
* @param buffer Buffer to read data from
* @return Number of bytes written, or error code
*/
virtual ResultVal<size_t> Write(u64 offset, size_t length, bool flush,
const u8* buffer) const = 0;
/**
* Flushes the file
*/
virtual void Flush() const = 0;
/**
* Set the size of the file in bytes
* @param size New size of the file
* @return true if successful
*/
virtual bool SetSize(u64 size) const = 0;
/**
* Get the size of the file in bytes
* @return Size of the file in bytes
*/
virtual u64 GetSize() const = 0;
/**
* Close the file
* @return true if the file closed correctly
*/
virtual bool Close() const = 0;
};
} // namespace FileSys

View File

@@ -1,238 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <numeric>
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
VfsFile::~VfsFile() = default;
std::string VfsFile::GetExtension() const {
return FileUtil::GetExtensionFromFilename(GetName());
}
VfsDirectory::~VfsDirectory() = default;
boost::optional<u8> VfsFile::ReadByte(size_t offset) const {
u8 out{};
size_t size = Read(&out, 1, offset);
if (size == 1)
return out;
return boost::none;
}
std::vector<u8> VfsFile::ReadBytes(size_t size, size_t offset) const {
std::vector<u8> out(size);
size_t read_size = Read(out.data(), size, offset);
out.resize(read_size);
return out;
}
std::vector<u8> VfsFile::ReadAllBytes() const {
return ReadBytes(GetSize());
}
bool VfsFile::WriteByte(u8 data, size_t offset) {
return Write(&data, 1, offset) == 1;
}
size_t VfsFile::WriteBytes(std::vector<u8> data, size_t offset) {
return Write(data.data(), data.size(), offset);
}
std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(const std::string& path) const {
auto vec = FileUtil::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
if (vec.empty())
return nullptr;
if (vec.size() == 1)
return GetFile(vec[0]);
auto dir = GetSubdirectory(vec[0]);
for (size_t component = 1; component < vec.size() - 1; ++component) {
if (dir == nullptr)
return nullptr;
dir = dir->GetSubdirectory(vec[component]);
}
if (dir == nullptr)
return nullptr;
return dir->GetFile(vec.back());
}
std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(const std::string& path) const {
if (IsRoot())
return GetFileRelative(path);
return GetParentDirectory()->GetFileAbsolute(path);
}
std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(const std::string& path) const {
auto vec = FileUtil::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
if (vec.empty())
// TODO(DarkLordZach): Return this directory if path is '/' or similar. Can't currently
// because of const-ness
return nullptr;
auto dir = GetSubdirectory(vec[0]);
for (size_t component = 1; component < vec.size(); ++component) {
if (dir == nullptr)
return nullptr;
dir = dir->GetSubdirectory(vec[component]);
}
return dir;
}
std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryAbsolute(const std::string& path) const {
if (IsRoot())
return GetDirectoryRelative(path);
return GetParentDirectory()->GetDirectoryAbsolute(path);
}
std::shared_ptr<VfsFile> VfsDirectory::GetFile(const std::string& name) const {
const auto& files = GetFiles();
const auto iter = std::find_if(files.begin(), files.end(),
[&name](const auto& file1) { return name == file1->GetName(); });
return iter == files.end() ? nullptr : *iter;
}
std::shared_ptr<VfsDirectory> VfsDirectory::GetSubdirectory(const std::string& name) const {
const auto& subs = GetSubdirectories();
const auto iter = std::find_if(subs.begin(), subs.end(),
[&name](const auto& file1) { return name == file1->GetName(); });
return iter == subs.end() ? nullptr : *iter;
}
bool VfsDirectory::IsRoot() const {
return GetParentDirectory() == nullptr;
}
size_t VfsDirectory::GetSize() const {
const auto& files = GetFiles();
const auto file_total =
std::accumulate(files.begin(), files.end(), 0ull,
[](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); });
const auto& sub_dir = GetSubdirectories();
const auto subdir_total =
std::accumulate(sub_dir.begin(), sub_dir.end(), 0ull,
[](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); });
return file_total + subdir_total;
}
std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(const std::string& path) {
auto vec = FileUtil::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
if (vec.empty())
return nullptr;
if (vec.size() == 1)
return CreateFile(vec[0]);
auto dir = GetSubdirectory(vec[0]);
if (dir == nullptr) {
dir = CreateSubdirectory(vec[0]);
if (dir == nullptr)
return nullptr;
}
return dir->CreateFileRelative(FileUtil::GetPathWithoutTop(path));
}
std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(const std::string& path) {
if (IsRoot())
return CreateFileRelative(path);
return GetParentDirectory()->CreateFileAbsolute(path);
}
std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(const std::string& path) {
auto vec = FileUtil::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
if (vec.empty())
return nullptr;
if (vec.size() == 1)
return CreateSubdirectory(vec[0]);
auto dir = GetSubdirectory(vec[0]);
if (dir == nullptr) {
dir = CreateSubdirectory(vec[0]);
if (dir == nullptr)
return nullptr;
}
return dir->CreateDirectoryRelative(FileUtil::GetPathWithoutTop(path));
}
std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryAbsolute(const std::string& path) {
if (IsRoot())
return CreateDirectoryRelative(path);
return GetParentDirectory()->CreateDirectoryAbsolute(path);
}
bool VfsDirectory::DeleteSubdirectoryRecursive(const std::string& name) {
auto dir = GetSubdirectory(name);
if (dir == nullptr)
return false;
bool success = true;
for (const auto& file : dir->GetFiles()) {
if (!DeleteFile(file->GetName()))
success = false;
}
for (const auto& sdir : dir->GetSubdirectories()) {
if (!dir->DeleteSubdirectoryRecursive(sdir->GetName()))
success = false;
}
return success;
}
bool VfsDirectory::Copy(const std::string& src, const std::string& dest) {
const auto f1 = GetFile(src);
auto f2 = CreateFile(dest);
if (f1 == nullptr || f2 == nullptr)
return false;
if (!f2->Resize(f1->GetSize())) {
DeleteFile(dest);
return false;
}
return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize();
}
bool ReadOnlyVfsDirectory::IsWritable() const {
return false;
}
bool ReadOnlyVfsDirectory::IsReadable() const {
return true;
}
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateSubdirectory(const std::string& name) {
return nullptr;
}
std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFile(const std::string& name) {
return nullptr;
}
bool ReadOnlyVfsDirectory::DeleteSubdirectory(const std::string& name) {
return false;
}
bool ReadOnlyVfsDirectory::DeleteFile(const std::string& name) {
return false;
}
bool ReadOnlyVfsDirectory::Rename(const std::string& name) {
return false;
}
} // namespace FileSys

View File

@@ -1,237 +0,0 @@
// 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 <type_traits>
#include <vector>
#include "boost/optional.hpp"
#include "common/common_types.h"
#include "common/file_util.h"
namespace FileSys {
struct VfsFile;
struct VfsDirectory;
// Convenience typedefs to use VfsDirectory and VfsFile
using VirtualDir = std::shared_ptr<FileSys::VfsDirectory>;
using VirtualFile = std::shared_ptr<FileSys::VfsFile>;
// A class representing a file in an abstract filesystem.
struct VfsFile : NonCopyable {
virtual ~VfsFile();
// Retrieves the file name.
virtual std::string GetName() const = 0;
// Retrieves the extension of the file name.
virtual std::string GetExtension() const;
// Retrieves the size of the file.
virtual size_t GetSize() const = 0;
// Resizes the file to new_size. Returns whether or not the operation was successful.
virtual bool Resize(size_t new_size) = 0;
// Gets a pointer to the directory containing this file, returning nullptr if there is none.
virtual std::shared_ptr<VfsDirectory> GetContainingDirectory() const = 0;
// Returns whether or not the file can be written to.
virtual bool IsWritable() const = 0;
// Returns whether or not the file can be read from.
virtual bool IsReadable() const = 0;
// The primary method of reading from the file. Reads length bytes into data starting at offset
// into file. Returns number of bytes successfully read.
virtual size_t Read(u8* data, size_t length, size_t offset = 0) const = 0;
// The primary method of writing to the file. Writes length bytes from data starting at offset
// into file. Returns number of bytes successfully written.
virtual size_t Write(const u8* data, size_t length, size_t offset = 0) = 0;
// Reads exactly one byte at the offset provided, returning boost::none on error.
virtual boost::optional<u8> ReadByte(size_t offset = 0) const;
// Reads size bytes starting at offset in file into a vector.
virtual std::vector<u8> ReadBytes(size_t size, size_t offset = 0) const;
// Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(),
// 0)'
virtual std::vector<u8> ReadAllBytes() const;
// Reads an array of type T, size number_elements starting at offset.
// Returns the number of bytes (sizeof(T)*number_elements) read successfully.
template <typename T>
size_t ReadArray(T* data, size_t number_elements, size_t offset = 0) const {
static_assert(std::is_trivially_copyable<T>::value,
"Data type must be trivially copyable.");
return Read(reinterpret_cast<u8*>(data), number_elements * sizeof(T), offset);
}
// Reads size bytes into the memory starting at data starting at offset into the file.
// Returns the number of bytes read successfully.
template <typename T>
size_t ReadBytes(T* data, size_t size, size_t offset = 0) const {
static_assert(std::is_trivially_copyable<T>::value,
"Data type must be trivially copyable.");
return Read(reinterpret_cast<u8*>(data), size, offset);
}
// Reads one object of type T starting at offset in file.
// Returns the number of bytes read successfully (sizeof(T)).
template <typename T>
size_t ReadObject(T* data, size_t offset = 0) const {
static_assert(std::is_trivially_copyable<T>::value,
"Data type must be trivially copyable.");
return Read(reinterpret_cast<u8*>(data), sizeof(T), offset);
}
// Writes exactly one byte to offset in file and retuns whether or not the byte was written
// successfully.
virtual bool WriteByte(u8 data, size_t offset = 0);
// Writes a vector of bytes to offset in file and returns the number of bytes successfully
// written.
virtual size_t WriteBytes(std::vector<u8> data, size_t offset = 0);
// Writes an array of type T, size number_elements to offset in file.
// Returns the number of bytes (sizeof(T)*number_elements) written successfully.
template <typename T>
size_t WriteArray(T* data, size_t number_elements, size_t offset = 0) {
static_assert(std::is_trivially_copyable<T>::value,
"Data type must be trivially copyable.");
return Write(data, number_elements * sizeof(T), offset);
}
// Writes size bytes starting at memory location data to offset in file.
// Returns the number of bytes written successfully.
template <typename T>
size_t WriteBytes(T* data, size_t size, size_t offset = 0) {
static_assert(std::is_trivially_copyable<T>::value,
"Data type must be trivially copyable.");
return Write(reinterpret_cast<u8*>(data), size, offset);
}
// Writes one object of type T to offset in file.
// Returns the number of bytes written successfully (sizeof(T)).
template <typename T>
size_t WriteObject(const T& data, size_t offset = 0) {
static_assert(std::is_trivially_copyable<T>::value,
"Data type must be trivially copyable.");
return Write(&data, sizeof(T), offset);
}
// Renames the file to name. Returns whether or not the operation was successsful.
virtual bool Rename(const std::string& name) = 0;
};
// A class representing a directory in an abstract filesystem.
struct VfsDirectory : NonCopyable {
virtual ~VfsDirectory();
// Retrives the file located at path as if the current directory was root. Returns nullptr if
// not found.
virtual std::shared_ptr<VfsFile> GetFileRelative(const std::string& path) const;
// Calls GetFileRelative(path) on the root of the current directory.
virtual std::shared_ptr<VfsFile> GetFileAbsolute(const std::string& path) const;
// Retrives the directory located at path as if the current directory was root. Returns nullptr
// if not found.
virtual std::shared_ptr<VfsDirectory> GetDirectoryRelative(const std::string& path) const;
// Calls GetDirectoryRelative(path) on the root of the current directory.
virtual std::shared_ptr<VfsDirectory> GetDirectoryAbsolute(const std::string& path) const;
// Returns a vector containing all of the files in this directory.
virtual std::vector<std::shared_ptr<VfsFile>> GetFiles() const = 0;
// Returns the file with filename matching name. Returns nullptr if directory dosen't have a
// file with name.
virtual std::shared_ptr<VfsFile> GetFile(const std::string& name) const;
// Returns a vector containing all of the subdirectories in this directory.
virtual std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const = 0;
// Returns the directory with name matching name. Returns nullptr if directory dosen't have a
// directory with name.
virtual std::shared_ptr<VfsDirectory> GetSubdirectory(const std::string& name) const;
// Returns whether or not the directory can be written to.
virtual bool IsWritable() const = 0;
// Returns whether of not the directory can be read from.
virtual bool IsReadable() const = 0;
// Returns whether or not the directory is the root of the current file tree.
virtual bool IsRoot() const;
// Returns the name of the directory.
virtual std::string GetName() const = 0;
// Returns the total size of all files and subdirectories in this directory.
virtual size_t GetSize() const;
// Returns the parent directory of this directory. Returns nullptr if this directory is root or
// has no parent.
virtual std::shared_ptr<VfsDirectory> GetParentDirectory() const = 0;
// Creates a new subdirectory with name name. Returns a pointer to the new directory or nullptr
// if the operation failed.
virtual std::shared_ptr<VfsDirectory> CreateSubdirectory(const std::string& name) = 0;
// Creates a new file with name name. Returns a pointer to the new file or nullptr if the
// operation failed.
virtual std::shared_ptr<VfsFile> CreateFile(const std::string& name) = 0;
// Creates a new file at the path relative to this directory. Also creates directories if
// they do not exist and is supported by this implementation. Returns nullptr on any failure.
virtual std::shared_ptr<VfsFile> CreateFileRelative(const std::string& path);
// Creates a new file at the path relative to root of this directory. Also creates directories
// if they do not exist and is supported by this implementation. Returns nullptr on any failure.
virtual std::shared_ptr<VfsFile> CreateFileAbsolute(const std::string& path);
// Creates a new directory at the path relative to this directory. Also creates directories if
// they do not exist and is supported by this implementation. Returns nullptr on any failure.
virtual std::shared_ptr<VfsDirectory> CreateDirectoryRelative(const std::string& path);
// Creates a new directory at the path relative to root of this directory. Also creates
// directories if they do not exist and is supported by this implementation. Returns nullptr on
// any failure.
virtual std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(const std::string& path);
// Deletes the subdirectory with name and returns true on success.
virtual bool DeleteSubdirectory(const std::string& name) = 0;
// Deletes all subdirectories and files of subdirectory with name recirsively and then deletes
// the subdirectory. Returns true on success.
virtual bool DeleteSubdirectoryRecursive(const std::string& name);
// Returnes whether or not the file with name name was deleted successfully.
virtual bool DeleteFile(const std::string& name) = 0;
// Returns whether or not this directory was renamed to name.
virtual bool Rename(const std::string& name) = 0;
// Returns whether or not the file with name src was successfully copied to a new file with name
// dest.
virtual bool Copy(const std::string& src, const std::string& dest);
// Interprets the file with name file instead as a directory of type directory.
// The directory must have a constructor that takes a single argument of type
// std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a
// subdirectory in one call.
template <typename Directory>
bool InterpretAsDirectory(const std::string& file) {
auto file_p = GetFile(file);
if (file_p == nullptr)
return false;
return ReplaceFileWithSubdirectory(file, std::make_shared<Directory>(file_p));
}
protected:
// Backend for InterpretAsDirectory.
// Removes all references to file and adds a reference to dir in the directory's implementation.
virtual bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) = 0;
};
// A convenience partial-implementation of VfsDirectory that stubs out methods that should only work
// if writable. This is to avoid redundant empty methods everywhere.
struct ReadOnlyVfsDirectory : public VfsDirectory {
bool IsWritable() const override;
bool IsReadable() const override;
std::shared_ptr<VfsDirectory> CreateSubdirectory(const std::string& name) override;
std::shared_ptr<VfsFile> CreateFile(const std::string& name) override;
bool DeleteSubdirectory(const std::string& name) override;
bool DeleteFile(const std::string& name) override;
bool Rename(const std::string& name) override;
};
} // namespace FileSys

View File

@@ -1,92 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/file_sys/vfs_offset.h"
namespace FileSys {
OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, size_t size_, size_t offset_,
const std::string& name_)
: file(file_), offset(offset_), size(size_), name(name_) {}
std::string OffsetVfsFile::GetName() const {
return name.empty() ? file->GetName() : name;
}
size_t OffsetVfsFile::GetSize() const {
return size;
}
bool OffsetVfsFile::Resize(size_t new_size) {
if (offset + new_size < file->GetSize()) {
size = new_size;
} else {
auto res = file->Resize(offset + new_size);
if (!res)
return false;
size = new_size;
}
return true;
}
std::shared_ptr<VfsDirectory> OffsetVfsFile::GetContainingDirectory() const {
return file->GetContainingDirectory();
}
bool OffsetVfsFile::IsWritable() const {
return file->IsWritable();
}
bool OffsetVfsFile::IsReadable() const {
return file->IsReadable();
}
size_t OffsetVfsFile::Read(u8* data, size_t length, size_t r_offset) const {
return file->Read(data, TrimToFit(length, r_offset), offset + r_offset);
}
size_t OffsetVfsFile::Write(const u8* data, size_t length, size_t r_offset) {
return file->Write(data, TrimToFit(length, r_offset), offset + r_offset);
}
boost::optional<u8> OffsetVfsFile::ReadByte(size_t r_offset) const {
if (r_offset < size)
return file->ReadByte(offset + r_offset);
return boost::none;
}
std::vector<u8> OffsetVfsFile::ReadBytes(size_t r_size, size_t r_offset) const {
return file->ReadBytes(TrimToFit(r_size, r_offset), offset + r_offset);
}
std::vector<u8> OffsetVfsFile::ReadAllBytes() const {
return file->ReadBytes(size, offset);
}
bool OffsetVfsFile::WriteByte(u8 data, size_t r_offset) {
if (r_offset < size)
return file->WriteByte(data, offset + r_offset);
return false;
}
size_t OffsetVfsFile::WriteBytes(std::vector<u8> data, size_t r_offset) {
return file->Write(data.data(), TrimToFit(data.size(), r_offset), offset + r_offset);
}
bool OffsetVfsFile::Rename(const std::string& name) {
return file->Rename(name);
}
size_t OffsetVfsFile::GetOffset() const {
return offset;
}
size_t OffsetVfsFile::TrimToFit(size_t r_size, size_t r_offset) const {
return std::max<size_t>(std::min<size_t>(size - r_offset, r_size), 0);
}
} // namespace FileSys

View File

@@ -1,46 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/file_sys/vfs.h"
namespace FileSys {
// An implementation of VfsFile that wraps around another VfsFile at a certain offset.
// Similar to seeking to an offset.
// If the file is writable, operations that would write past the end of the offset file will expand
// the size of this wrapper.
struct OffsetVfsFile : public VfsFile {
OffsetVfsFile(std::shared_ptr<VfsFile> file, size_t size, size_t offset = 0,
const std::string& new_name = "");
std::string GetName() const override;
size_t GetSize() const override;
bool Resize(size_t new_size) override;
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
bool IsWritable() const override;
bool IsReadable() const override;
size_t Read(u8* data, size_t length, size_t offset) const override;
size_t Write(const u8* data, size_t length, size_t offset) override;
boost::optional<u8> ReadByte(size_t offset) const override;
std::vector<u8> ReadBytes(size_t size, size_t offset) const override;
std::vector<u8> ReadAllBytes() const override;
bool WriteByte(u8 data, size_t offset) override;
size_t WriteBytes(std::vector<u8> data, size_t offset) override;
bool Rename(const std::string& name) override;
size_t GetOffset() const;
private:
size_t TrimToFit(size_t r_size, size_t r_offset) const;
std::shared_ptr<VfsFile> file;
size_t offset;
size_t size;
std::string name;
};
} // namespace FileSys

View File

@@ -1,177 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/common_paths.h"
#include "common/logging/log.h"
#include "core/file_sys/vfs_real.h"
namespace FileSys {
static std::string PermissionsToCharArray(Mode perms) {
std::string out;
switch (perms) {
case Mode::Read:
out += "r";
break;
case Mode::Write:
out += "r+";
break;
case Mode::Append:
out += "a";
break;
}
return out + "b";
}
RealVfsFile::RealVfsFile(const std::string& path_, Mode perms_)
: backing(path_, PermissionsToCharArray(perms_).c_str()), path(path_),
parent_path(FileUtil::GetParentPath(path_)),
path_components(FileUtil::SplitPathComponents(path_)),
parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
perms(perms_) {}
std::string RealVfsFile::GetName() const {
return path_components.back();
}
size_t RealVfsFile::GetSize() const {
return backing.GetSize();
}
bool RealVfsFile::Resize(size_t new_size) {
return backing.Resize(new_size);
}
std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const {
return std::make_shared<RealVfsDirectory>(parent_path, perms);
}
bool RealVfsFile::IsWritable() const {
return perms == Mode::Append || perms == Mode::Write;
}
bool RealVfsFile::IsReadable() const {
return perms == Mode::Read || perms == Mode::Write;
}
size_t RealVfsFile::Read(u8* data, size_t length, size_t offset) const {
if (!backing.Seek(offset, SEEK_SET))
return 0;
return backing.ReadBytes(data, length);
}
size_t RealVfsFile::Write(const u8* data, size_t length, size_t offset) {
if (!backing.Seek(offset, SEEK_SET))
return 0;
return backing.WriteBytes(data, length);
}
bool RealVfsFile::Rename(const std::string& name) {
const auto out = FileUtil::Rename(GetName(), name);
path = parent_path + DIR_SEP + name;
path_components = parent_components;
path_components.push_back(name);
backing = FileUtil::IOFile(path, PermissionsToCharArray(perms).c_str());
return out;
}
bool RealVfsFile::Close() {
return backing.Close();
}
RealVfsDirectory::RealVfsDirectory(const std::string& path_, Mode perms_)
: path(FileUtil::RemoveTrailingSlash(path_)), parent_path(FileUtil::GetParentPath(path)),
path_components(FileUtil::SplitPathComponents(path)),
parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
perms(perms_) {
if (!FileUtil::Exists(path) && (perms == Mode::Write || perms == Mode::Append))
FileUtil::CreateDir(path);
unsigned size;
if (perms == Mode::Append)
return;
FileUtil::ForeachDirectoryEntry(
&size, path,
[this](unsigned* entries_out, const std::string& directory, const std::string& filename) {
std::string full_path = directory + DIR_SEP + filename;
if (FileUtil::IsDirectory(full_path))
subdirectories.emplace_back(std::make_shared<RealVfsDirectory>(full_path, perms));
else
files.emplace_back(std::make_shared<RealVfsFile>(full_path, perms));
return true;
});
}
std::vector<std::shared_ptr<VfsFile>> RealVfsDirectory::GetFiles() const {
return std::vector<std::shared_ptr<VfsFile>>(files);
}
std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories() const {
return std::vector<std::shared_ptr<VfsDirectory>>(subdirectories);
}
bool RealVfsDirectory::IsWritable() const {
return perms == Mode::Write || perms == Mode::Append;
}
bool RealVfsDirectory::IsReadable() const {
return perms == Mode::Read || perms == Mode::Write;
}
std::string RealVfsDirectory::GetName() const {
return path_components.back();
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
if (path_components.size() <= 1)
return nullptr;
return std::make_shared<RealVfsDirectory>(parent_path, perms);
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateSubdirectory(const std::string& name) {
if (!FileUtil::CreateDir(path + DIR_SEP + name))
return nullptr;
subdirectories.emplace_back(std::make_shared<RealVfsDirectory>(path + DIR_SEP + name, perms));
return subdirectories.back();
}
std::shared_ptr<VfsFile> RealVfsDirectory::CreateFile(const std::string& name) {
if (!FileUtil::CreateEmptyFile(path + DIR_SEP + name))
return nullptr;
files.emplace_back(std::make_shared<RealVfsFile>(path + DIR_SEP + name, perms));
return files.back();
}
bool RealVfsDirectory::DeleteSubdirectory(const std::string& name) {
return FileUtil::DeleteDirRecursively(path + DIR_SEP + name);
}
bool RealVfsDirectory::DeleteFile(const std::string& name) {
auto file = GetFile(name);
if (file == nullptr)
return false;
files.erase(std::find(files.begin(), files.end(), file));
auto real_file = std::static_pointer_cast<RealVfsFile>(file);
real_file->Close();
return FileUtil::Delete(path + DIR_SEP + name);
}
bool RealVfsDirectory::Rename(const std::string& name) {
return FileUtil::Rename(path, parent_path + DIR_SEP + name);
}
bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
auto iter = std::find(files.begin(), files.end(), file);
if (iter == files.end())
return false;
files[iter - files.begin()] = files.back();
files.pop_back();
subdirectories.emplace_back(dir);
return true;
}
} // namespace FileSys

View File

@@ -1,69 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/file_util.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
// An implmentation of VfsFile that represents a file on the user's computer.
struct RealVfsFile : public VfsFile {
friend struct RealVfsDirectory;
RealVfsFile(const std::string& name, Mode perms = Mode::Read);
std::string GetName() const override;
size_t GetSize() const override;
bool Resize(size_t new_size) override;
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
bool IsWritable() const override;
bool IsReadable() const override;
size_t Read(u8* data, size_t length, size_t offset) const override;
size_t Write(const u8* data, size_t length, size_t offset) override;
bool Rename(const std::string& name) override;
private:
bool Close();
FileUtil::IOFile backing;
std::string path;
std::string parent_path;
std::vector<std::string> path_components;
std::vector<std::string> parent_components;
Mode perms;
};
// An implementation of VfsDirectory that represents a directory on the user's computer.
struct RealVfsDirectory : public VfsDirectory {
RealVfsDirectory(const std::string& path, Mode perms);
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::string GetName() const override;
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
std::shared_ptr<VfsDirectory> CreateSubdirectory(const std::string& name) override;
std::shared_ptr<VfsFile> CreateFile(const std::string& name) override;
bool DeleteSubdirectory(const std::string& name) override;
bool DeleteFile(const std::string& name) override;
bool Rename(const std::string& name) override;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
std::string path;
std::string parent_path;
std::vector<std::string> path_components;
std::vector<std::string> parent_components;
Mode perms;
std::vector<std::shared_ptr<VfsFile>> files;
std::vector<std::shared_ptr<VfsDirectory>> subdirectories;
};
} // namespace FileSys

View File

@@ -5,6 +5,7 @@
#include <cinttypes>
#include <stack>
#include "core/core.h"
#include "core/file_sys/filesystem.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/process.h"

View File

@@ -2,204 +2,17 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma optimize("", off)
#include "common/assert.h"
#include <boost/container/flat_map.hpp>
#include "common/file_util.h"
#include "core/core.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/filesystem.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/sdmc_factory.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_offset.h"
#include "core/file_sys/vfs_real.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/fsp_srv.h"
namespace Service::FileSystem {
// Size of emulated sd card free space, reported in bytes.
// Just using 32GB because thats reasonable
// TODO(DarkLordZach): Eventually make this configurable in settings.
constexpr u64 EMULATED_SD_REPORTED_SIZE = 32000000000;
static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base,
const std::string& dir_name) {
if (dir_name == "." || dir_name == "" || dir_name == "/" || dir_name == "\\")
return base;
return base->GetDirectoryRelative(dir_name);
}
VfsDirectoryServiceWrapper::VfsDirectoryServiceWrapper(FileSys::VirtualDir backing_)
: backing(backing_) {}
std::string VfsDirectoryServiceWrapper::GetName() const {
return backing->GetName();
}
ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path, u64 size) const {
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
auto file = dir->CreateFile(FileUtil::GetFilename(path));
if (file == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
return ResultCode(-1);
}
if (!file->Resize(size)) {
// TODO(DarkLordZach): Find a better error code for this
return ResultCode(-1);
}
return RESULT_SUCCESS;
}
ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path) const {
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
if (path == "/" || path == "\\") {
// TODO(DarkLordZach): Why do games call this and what should it do? Works as is but...
return RESULT_SUCCESS;
}
if (dir->GetFile(FileUtil::GetFilename(path)) == nullptr)
return FileSys::ERROR_PATH_NOT_FOUND;
if (!backing->DeleteFile(FileUtil::GetFilename(path))) {
// TODO(DarkLordZach): Find a better error code for this
return ResultCode(-1);
}
return RESULT_SUCCESS;
}
ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path) const {
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
if (dir == nullptr && FileUtil::GetFilename(FileUtil::GetParentPath(path)).empty())
dir = backing;
auto new_dir = dir->CreateSubdirectory(FileUtil::GetFilename(path));
if (new_dir == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
return ResultCode(-1);
}
return RESULT_SUCCESS;
}
ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path) const {
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
if (!dir->DeleteSubdirectory(FileUtil::GetFilename(path))) {
// TODO(DarkLordZach): Find a better error code for this
return ResultCode(-1);
}
return RESULT_SUCCESS;
}
ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path) const {
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
if (!dir->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path))) {
// TODO(DarkLordZach): Find a better error code for this
return ResultCode(-1);
}
return RESULT_SUCCESS;
}
ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path,
const std::string& dest_path) const {
auto src = backing->GetFileRelative(src_path);
if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) {
// Use more-optimized vfs implementation rename.
if (src == nullptr)
return FileSys::ERROR_PATH_NOT_FOUND;
if (!src->Rename(FileUtil::GetFilename(dest_path))) {
// TODO(DarkLordZach): Find a better error code for this
return ResultCode(-1);
}
return RESULT_SUCCESS;
}
// Move by hand -- TODO(DarkLordZach): Optimize
auto c_res = CreateFile(dest_path, src->GetSize());
if (c_res != RESULT_SUCCESS)
return c_res;
auto dest = backing->GetFileRelative(dest_path);
ASSERT_MSG(dest != nullptr, "Newly created file with success cannot be found.");
ASSERT_MSG(dest->WriteBytes(src->ReadAllBytes()) == src->GetSize(),
"Could not write all of the bytes but everything else has succeded.");
if (!src->GetContainingDirectory()->DeleteFile(FileUtil::GetFilename(src_path))) {
// TODO(DarkLordZach): Find a better error code for this
return ResultCode(-1);
}
return RESULT_SUCCESS;
}
ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path,
const std::string& dest_path) const {
auto src = GetDirectoryRelativeWrapped(backing, src_path);
if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) {
// Use more-optimized vfs implementation rename.
if (src == nullptr)
return FileSys::ERROR_PATH_NOT_FOUND;
if (!src->Rename(FileUtil::GetFilename(dest_path))) {
// TODO(DarkLordZach): Find a better error code for this
return ResultCode(-1);
}
return RESULT_SUCCESS;
}
// TODO(DarkLordZach): Implement renaming across the tree (move).
ASSERT_MSG(false,
"Could not rename directory with path \"{}\" to new path \"{}\" because parent dirs "
"don't match -- UNIMPLEMENTED",
src_path, dest_path);
// TODO(DarkLordZach): Find a better error code for this
return ResultCode(-1);
}
ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::string& path,
FileSys::Mode mode) const {
auto npath = path;
while (npath.size() > 0 && (npath[0] == '/' || npath[0] == '\\'))
npath = npath.substr(1);
auto file = backing->GetFileRelative(npath);
if (file == nullptr)
return FileSys::ERROR_PATH_NOT_FOUND;
if (mode == FileSys::Mode::Append) {
return MakeResult<FileSys::VirtualFile>(
std::make_shared<FileSys::OffsetVfsFile>(file, 0, file->GetSize()));
}
return MakeResult<FileSys::VirtualFile>(file);
}
ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const std::string& path) {
auto dir = GetDirectoryRelativeWrapped(backing, path);
if (dir == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
return ResultCode(-1);
}
return MakeResult(dir);
}
u64 VfsDirectoryServiceWrapper::GetFreeSpaceSize() const {
if (backing->IsWritable())
return EMULATED_SD_REPORTED_SIZE;
return 0;
}
ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
const std::string& path) const {
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
if (dir == nullptr)
return FileSys::ERROR_PATH_NOT_FOUND;
auto filename = FileUtil::GetFilename(path);
if (dir->GetFile(filename) != nullptr)
return MakeResult(FileSys::EntryType::File);
if (dir->GetSubdirectory(filename) != nullptr)
return MakeResult(FileSys::EntryType::Directory);
return FileSys::ERROR_PATH_NOT_FOUND;
}
/**
* Map of registered file systems, identified by type. Once an file system is registered here, it
* is never removed until UnregisterFileSystems is called.
@@ -229,7 +42,7 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
return RESULT_SUCCESS;
}
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id) {
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenRomFS(u64 title_id) {
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id);
if (romfs_factory == nullptr) {
@@ -240,19 +53,19 @@ ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id) {
return romfs_factory->Open(title_id);
}
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
FileSys::SaveDataDescriptor save_struct) {
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenSaveData(
FileSys::SaveDataSpaceId space, FileSys::SaveDataDescriptor save_struct) {
LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}",
static_cast<u8>(space), save_struct.DebugInfo());
static_cast<u8>(space), SaveStructDebugInfo(save_struct));
if (save_data_factory == nullptr) {
return ResultCode(ErrorModule::FS, FileSys::ErrCodes::TitleNotFound);
return ResultCode(ErrorModule::FS, FileSys::ErrCodes::SaveDataNotFound);
}
return save_data_factory->Open(space, save_struct);
}
ResultVal<FileSys::VirtualDir> OpenSDMC() {
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenSDMC() {
LOG_TRACE(Service_FS, "Opening SDMC");
if (sdmc_factory == nullptr) {
@@ -267,10 +80,8 @@ void RegisterFileSystems() {
save_data_factory = nullptr;
sdmc_factory = nullptr;
auto nand_directory = std::make_shared<FileSys::RealVfsDirectory>(
FileUtil::GetUserPath(D_NAND_IDX), FileSys::Mode::Write);
auto sd_directory = std::make_shared<FileSys::RealVfsDirectory>(
FileUtil::GetUserPath(D_SDMC_IDX), FileSys::Mode::Write);
std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX);
std::string sd_directory = FileUtil::GetUserPath(D_SDMC_IDX);
auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
save_data_factory = std::move(savedata);

View File

@@ -6,13 +6,15 @@
#include <memory>
#include "common/common_types.h"
#include "core/file_sys/directory.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/romfs_factory.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/sdmc_factory.h"
#include "core/hle/result.h"
namespace FileSys {
class FileSystemBackend;
} // namespace FileSys
namespace Service {
namespace SM {
@@ -27,10 +29,11 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
// TODO(DarkLordZach): BIS Filesystem
// ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id);
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
FileSys::SaveDataDescriptor save_struct);
ResultVal<FileSys::VirtualDir> OpenSDMC();
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenRomFS(u64 title_id);
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenSaveData(
FileSys::SaveDataSpaceId space, FileSys::SaveDataDescriptor save_struct);
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenSDMC();
// TODO(DarkLordZach): BIS Filesystem
// ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS();
@@ -38,100 +41,5 @@ ResultVal<FileSys::VirtualDir> OpenSDMC();
/// Registers all Filesystem services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
// A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of
// pointers and booleans. This makes using a VfsDirectory with switch services much easier and
// avoids repetitive code.
class VfsDirectoryServiceWrapper {
public:
explicit VfsDirectoryServiceWrapper(FileSys::VirtualDir backing);
/**
* Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.)
*/
std::string GetName() const;
/**
* Create a file specified by its path
* @param path Path relative to the Archive
* @param size The size of the new file, filled with zeroes
* @return Result of the operation
*/
ResultCode CreateFile(const std::string& path, u64 size) const;
/**
* Delete a file specified by its path
* @param path Path relative to the archive
* @return Result of the operation
*/
ResultCode DeleteFile(const std::string& path) const;
/**
* Create a directory specified by its path
* @param path Path relative to the archive
* @return Result of the operation
*/
ResultCode CreateDirectory(const std::string& path) const;
/**
* Delete a directory specified by its path
* @param path Path relative to the archive
* @return Result of the operation
*/
ResultCode DeleteDirectory(const std::string& path) const;
/**
* Delete a directory specified by its path and anything under it
* @param path Path relative to the archive
* @return Result of the operation
*/
ResultCode DeleteDirectoryRecursively(const std::string& path) const;
/**
* Rename a File specified by its path
* @param src_path Source path relative to the archive
* @param dest_path Destination path relative to the archive
* @return Result of the operation
*/
ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const;
/**
* Rename a Directory specified by its path
* @param src_path Source path relative to the archive
* @param dest_path Destination path relative to the archive
* @return Result of the operation
*/
ResultCode RenameDirectory(const std::string& src_path, const std::string& dest_path) const;
/**
* Open a file specified by its path, using the specified mode
* @param path Path relative to the archive
* @param mode Mode to open the file with
* @return Opened file, or error code
*/
ResultVal<FileSys::VirtualFile> OpenFile(const std::string& path, FileSys::Mode mode) const;
/**
* Open a directory specified by its path
* @param path Path relative to the archive
* @return Opened directory, or error code
*/
ResultVal<FileSys::VirtualDir> OpenDirectory(const std::string& path);
/**
* Get the free space
* @return The number of free bytes in the archive
*/
u64 GetFreeSpaceSize() const;
/**
* Get the type of the specified path
* @return The type of the specified path or error code
*/
ResultVal<FileSys::EntryType> GetEntryType(const std::string& path) const;
private:
FileSys::VirtualDir backing;
};
} // namespace FileSystem
} // namespace Service

View File

@@ -8,7 +8,11 @@
#include "core/core.h"
#include "core/file_sys/directory.h"
#include "core/file_sys/errors.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/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/fsp_srv.h"
@@ -21,13 +25,13 @@ enum class StorageId : u8 {
GameCard = 2,
NandSystem = 3,
NandUser = 4,
SdCard = 5,
SdCard = 5
};
class IStorage final : public ServiceFramework<IStorage> {
public:
IStorage(FileSys::VirtualFile backend_)
: ServiceFramework("IStorage"), backend(std::move(backend_)) {
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"}, {5, nullptr, "OperateRange"},
@@ -36,7 +40,7 @@ public:
}
private:
FileSys::VirtualFile backend;
std::unique_ptr<FileSys::StorageBackend> backend;
void Read(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
@@ -58,7 +62,14 @@ private:
}
// Read the data from the Storage backend
std::vector<u8> output = backend->ReadBytes(length, offset);
std::vector<u8> output(length);
ResultVal<size_t> res = backend->Read(offset, length, output.data());
if (res.Failed()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(res.Code());
return;
}
// Write the data to memory
ctx.WriteBuffer(output);
@@ -69,8 +80,8 @@ private:
class IFile final : public ServiceFramework<IFile> {
public:
explicit IFile(FileSys::VirtualFile backend_)
: ServiceFramework("IFile"), backend(std::move(backend_)) {
explicit IFile(std::unique_ptr<FileSys::StorageBackend>&& backend)
: ServiceFramework("IFile"), backend(std::move(backend)) {
static const FunctionInfo functions[] = {
{0, &IFile::Read, "Read"}, {1, &IFile::Write, "Write"},
{2, &IFile::Flush, "Flush"}, {3, &IFile::SetSize, "SetSize"},
@@ -80,7 +91,7 @@ public:
}
private:
FileSys::VirtualFile backend;
std::unique_ptr<FileSys::StorageBackend> backend;
void Read(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
@@ -103,14 +114,20 @@ private:
}
// Read the data from the Storage backend
std::vector<u8> output = backend->ReadBytes(length, offset);
std::vector<u8> output(length);
ResultVal<size_t> res = backend->Read(offset, length, output.data());
if (res.Failed()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(res.Code());
return;
}
// Write the data to memory
ctx.WriteBuffer(output);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push(static_cast<u64>(output.size()));
rb.Push(static_cast<u64>(*res));
}
void Write(Kernel::HLERequestContext& ctx) {
@@ -133,21 +150,14 @@ private:
return;
}
std::vector<u8> data = ctx.ReadBuffer();
std::vector<u8> actual_data(length);
ASSERT_MSG(
data.size() <= length,
"Attempting to write more data than requested (requested={:016X}, actual={:016X}).",
length, data.size());
std::copy(data.begin(), data.end(), actual_data.begin());
// Write the data to the Storage backend
auto written = backend->WriteBytes(data, offset);
ASSERT_MSG(written == length,
"Could not write all bytes to file (requested={:016X}, actual={:016X}).", length,
written);
std::vector<u8> data = ctx.ReadBuffer();
ResultVal<size_t> res = backend->Write(offset, length, true, data.data());
if (res.Failed()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(res.Code());
return;
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -155,8 +165,7 @@ private:
void Flush(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called");
// Exists for SDK compatibiltity -- No need to flush file.
backend->Flush();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -165,7 +174,7 @@ private:
void SetSize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 size = rp.Pop<u64>();
backend->Resize(size);
backend->SetSize(size);
LOG_DEBUG(Service_FS, "called, size={}", size);
IPC::ResponseBuilder rb{ctx, 2};
@@ -182,39 +191,19 @@ private:
}
};
template <typename T>
static void BuildEntryIndex(std::vector<FileSys::Entry>& entries, const std::vector<T>& new_data,
FileSys::EntryType type) {
for (const auto& new_entry : new_data) {
FileSys::Entry entry;
entry.filename[0] = '\0';
std::strncat(entry.filename, new_entry->GetName().c_str(), FileSys::FILENAME_LENGTH - 1);
entry.type = type;
entry.file_size = new_entry->GetSize();
entries.emplace_back(std::move(entry));
}
}
class IDirectory final : public ServiceFramework<IDirectory> {
public:
explicit IDirectory(FileSys::VirtualDir backend_)
: ServiceFramework("IDirectory"), backend(std::move(backend_)) {
explicit IDirectory(std::unique_ptr<FileSys::DirectoryBackend>&& backend)
: ServiceFramework("IDirectory"), backend(std::move(backend)) {
static const FunctionInfo functions[] = {
{0, &IDirectory::Read, "Read"},
{1, &IDirectory::GetEntryCount, "GetEntryCount"},
};
RegisterHandlers(functions);
// TODO(DarkLordZach): Verify that this is the correct behavior.
// Build entry index now to save time later.
BuildEntryIndex(entries, backend->GetFiles(), FileSys::File);
BuildEntryIndex(entries, backend->GetSubdirectories(), FileSys::Directory);
}
private:
FileSys::VirtualDir backend;
std::vector<FileSys::Entry> entries;
u64 next_entry_index = 0;
std::unique_ptr<FileSys::DirectoryBackend> backend;
void Read(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
@@ -225,31 +214,26 @@ private:
// Calculate how many entries we can fit in the output buffer
u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry);
// Cap at total number of entries.
u64 actual_entries = std::min(count_entries, entries.size() - next_entry_index);
// Read the data from the Directory backend
std::vector<FileSys::Entry> entry_data(entries.begin() + next_entry_index,
entries.begin() + next_entry_index + actual_entries);
next_entry_index += actual_entries;
std::vector<FileSys::Entry> entries(count_entries);
u64 read_entries = backend->Read(count_entries, entries.data());
// Convert the data into a byte array
std::vector<u8> output(entry_data.size() * sizeof(FileSys::Entry));
std::memcpy(output.data(), entry_data.data(), output.size());
std::vector<u8> output(entries.size() * sizeof(FileSys::Entry));
std::memcpy(output.data(), entries.data(), output.size());
// Write the data to memory
ctx.WriteBuffer(output);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push(actual_entries);
rb.Push(read_entries);
}
void GetEntryCount(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called");
u64 count = entries.size() - next_entry_index;
u64 count = backend->GetEntryCount();
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
@@ -259,7 +243,7 @@ private:
class IFileSystem final : public ServiceFramework<IFileSystem> {
public:
explicit IFileSystem(FileSys::VirtualDir backend)
explicit IFileSystem(std::unique_ptr<FileSys::FileSystemBackend>&& backend)
: ServiceFramework("IFileSystem"), backend(std::move(backend)) {
static const FunctionInfo functions[] = {
{0, &IFileSystem::CreateFile, "CreateFile"},
@@ -294,7 +278,7 @@ public:
LOG_DEBUG(Service_FS, "called file {} mode 0x{:X} size 0x{:08X}", name, mode, size);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(backend.CreateFile(name, size));
rb.Push(backend->CreateFile(name, size));
}
void DeleteFile(Kernel::HLERequestContext& ctx) {
@@ -306,7 +290,7 @@ public:
LOG_DEBUG(Service_FS, "called file {}", name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(backend.DeleteFile(name));
rb.Push(backend->DeleteFile(name));
}
void CreateDirectory(Kernel::HLERequestContext& ctx) {
@@ -318,7 +302,7 @@ public:
LOG_DEBUG(Service_FS, "called directory {}", name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(backend.CreateDirectory(name));
rb.Push(backend->CreateDirectory(name));
}
void RenameFile(Kernel::HLERequestContext& ctx) {
@@ -336,7 +320,7 @@ public:
LOG_DEBUG(Service_FS, "called file '{}' to file '{}'", src_name, dst_name);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(backend.RenameFile(src_name, dst_name));
rb.Push(backend->RenameFile(src_name, dst_name));
}
void OpenFile(Kernel::HLERequestContext& ctx) {
@@ -349,14 +333,14 @@ public:
LOG_DEBUG(Service_FS, "called file {} mode {}", name, static_cast<u32>(mode));
auto result = backend.OpenFile(name, mode);
auto result = backend->OpenFile(name, mode);
if (result.Failed()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result.Code());
return;
}
IFile file(result.Unwrap());
auto file = std::move(result.Unwrap());
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
@@ -374,14 +358,14 @@ public:
LOG_DEBUG(Service_FS, "called directory {} filter {}", name, filter_flags);
auto result = backend.OpenDirectory(name);
auto result = backend->OpenDirectory(name);
if (result.Failed()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result.Code());
return;
}
IDirectory directory(result.Unwrap());
auto directory = std::move(result.Unwrap());
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
@@ -396,7 +380,7 @@ public:
LOG_DEBUG(Service_FS, "called file {}", name);
auto result = backend.GetEntryType(name);
auto result = backend->GetEntryType(name);
if (result.Failed()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result.Code());
@@ -416,7 +400,7 @@ public:
}
private:
VfsDirectoryServiceWrapper backend;
std::unique_ptr<FileSys::FileSystemBackend> backend;
};
FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
@@ -552,19 +536,17 @@ void FSP_SRV::MountSaveData(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_FS, "called with unknown={:08X}", unk);
auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>();
auto dir = OpenSaveData(space_id, save_struct);
auto filesystem = OpenSaveData(space_id, save_struct);
if (dir.Failed()) {
if (filesystem.Failed()) {
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
rb.Push(ResultCode(ErrorModule::FS, FileSys::ErrCodes::TitleNotFound));
rb.Push(ResultCode(ErrorModule::FS, FileSys::ErrCodes::SaveDataNotFound));
return;
}
IFileSystem filesystem(std::move(dir.Unwrap()));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
rb.PushIpcInterface<IFileSystem>(std::move(filesystem.Unwrap()));
}
void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
@@ -587,11 +569,18 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
return;
}
IStorage storage(std::move(romfs.Unwrap()));
auto storage = romfs.Unwrap()->OpenFile({}, {});
if (storage.Failed()) {
LOG_CRITICAL(Service_FS, "no storage interface available!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(storage.Code());
return;
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IStorage>(std::move(storage));
rb.PushIpcInterface<IStorage>(std::move(storage.Unwrap()));
}
void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) {
@@ -602,9 +591,33 @@ void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}",
static_cast<u8>(storage_id), title_id);
if (title_id != Core::System::GetInstance().CurrentProcess()->program_id) {
LOG_CRITICAL(
Service_FS,
"Attempting to access RomFS of another title id (current={:016X}, requested={:016X}).",
Core::System::GetInstance().CurrentProcess()->program_id, title_id);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultCode(ErrorModule::FS, FileSys::ErrCodes::TitleNotFound));
auto romfs = OpenRomFS(title_id);
if (romfs.Failed()) {
LOG_CRITICAL(Service_FS, "no file system interface available!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultCode(ErrorModule::FS, FileSys::ErrCodes::RomFSNotFound));
return;
}
auto storage = romfs.Unwrap()->OpenFile({}, {});
if (storage.Failed()) {
LOG_CRITICAL(Service_FS, "no storage interface available!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(storage.Code());
return;
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IStorage>(std::move(storage.Unwrap()));
}
} // namespace Service::FileSystem

View File

@@ -27,7 +27,7 @@ private:
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
void OpenRomStorage(Kernel::HLERequestContext& ctx);
FileSys::VirtualFile romfs;
std::unique_ptr<FileSys::FileSystemBackend> romfs;
};
} // namespace Service::FileSystem

View File

@@ -5,7 +5,6 @@
#include <atomic>
#include "common/logging/log.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "core/frontend/input.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
@@ -64,8 +63,7 @@ private:
std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
sticks.begin(), Input::CreateDevice<Input::AnalogDevice>);
touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
// TODO(shinyquagsire23): gyro, mouse, keyboard
// TODO(shinyquagsire23): gyro, touch, mouse, keyboard
}
void UpdatePadCallback(u64 userdata, int cycles_late) {
@@ -153,6 +151,8 @@ private:
}
}
// TODO(bunnei): Properly implement the touch screen, the below will just write empty data
TouchScreen& touchscreen = mem.touchscreen;
const u64 last_entry = touchscreen.header.latest_entry;
const u64 curr_entry = (last_entry + 1) % touchscreen.entries.size();
@@ -164,26 +164,7 @@ private:
touchscreen.header.max_entry_index = touchscreen.entries.size();
touchscreen.header.timestamp = timestamp;
touchscreen.entries[curr_entry].header.timestamp = sample_counter;
TouchScreenEntryTouch touch_entry{};
auto [x, y, pressed] = touch_device->GetStatus();
touch_entry.timestamp = timestamp;
touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
touch_entry.touch_index = 0;
// TODO(DarkLordZach): Maybe try to derive these from EmuWindow?
touch_entry.diameter_x = 15;
touch_entry.diameter_y = 15;
touch_entry.angle = 0;
// TODO(DarkLordZach): Implement multi-touch support
if (pressed) {
touchscreen.entries[curr_entry].header.num_touches = 1;
touchscreen.entries[curr_entry].touches[0] = touch_entry;
} else {
touchscreen.entries[curr_entry].header.num_touches = 0;
}
touchscreen.entries[curr_entry].header.num_touches = 0;
// TODO(shinyquagsire23): Properly implement mouse
Mouse& mouse = mem.mouse;
@@ -269,7 +250,6 @@ private:
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
buttons;
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks;
std::unique_ptr<Input::TouchDevice> touch_device;
};
class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {

View File

@@ -4,10 +4,11 @@
#include <cinttypes>
#include "common/common_funcs.h"
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/romfs_factory.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
@@ -46,11 +47,55 @@ static std::string FindRomFS(const std::string& directory) {
return filepath_romfs;
}
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file)
: AppLoader(std::move(file)) {}
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileUtil::IOFile&& file,
std::string filepath)
: AppLoader(std::move(file)), filepath(std::move(filepath)) {}
FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& file) {
if (FileSys::IsDirectoryExeFS(file->GetContainingDirectory())) {
FileType AppLoader_DeconstructedRomDirectory::IdentifyType(FileUtil::IOFile& file,
const std::string& filepath) {
bool is_main_found{};
bool is_npdm_found{};
bool is_rtld_found{};
bool is_sdk_found{};
const auto callback = [&](unsigned* num_entries_out, const std::string& directory,
const std::string& virtual_name) -> bool {
// Skip directories
std::string physical_name = directory + virtual_name;
if (FileUtil::IsDirectory(physical_name)) {
return true;
}
// Verify filename
if (Common::ToLower(virtual_name) == "main") {
is_main_found = true;
} else if (Common::ToLower(virtual_name) == "main.npdm") {
is_npdm_found = true;
return true;
} else if (Common::ToLower(virtual_name) == "rtld") {
is_rtld_found = true;
} else if (Common::ToLower(virtual_name) == "sdk") {
is_sdk_found = true;
} else {
// Continue searching
return true;
}
// Verify file is an NSO
FileUtil::IOFile file(physical_name, "rb");
if (AppLoader_NSO::IdentifyType(file, physical_name) != FileType::NSO) {
return false;
}
// We are done if we've found and verified all required NSOs
return !(is_main_found && is_npdm_found && is_rtld_found && is_sdk_found);
};
// Search the directory recursively, looking for the required modules
const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP;
FileUtil::ForeachDirectoryEntry(nullptr, directory, callback);
if (is_main_found && is_npdm_found && is_rtld_found && is_sdk_found) {
return FileType::DeconstructedRomDirectory;
}
@@ -62,13 +107,14 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
if (is_loaded) {
return ResultStatus::ErrorAlreadyLoaded;
}
if (!file.IsOpen()) {
return ResultStatus::Error;
}
const FileSys::VirtualDir dir = file->GetContainingDirectory();
const FileSys::VirtualFile npdm = dir->GetFile("main.npdm");
if (npdm == nullptr)
return ResultStatus::ErrorInvalidFormat;
const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP;
const std::string npdm_path = directory + DIR_SEP + "main.npdm";
ResultStatus result = metadata.Load(npdm);
ResultStatus result = metadata.Load(npdm_path);
if (result != ResultStatus::Success) {
return result;
}
@@ -83,10 +129,9 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR};
for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
"subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) {
const std::string path = directory + DIR_SEP + module;
const VAddr load_addr = next_load_addr;
const FileSys::VirtualFile module_file = dir->GetFile(module);
if (module_file != nullptr)
next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr);
next_load_addr = AppLoader_NSO::LoadModule(path, load_addr);
if (next_load_addr) {
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
// Register module with GDBStub
@@ -105,15 +150,10 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
metadata.GetMainThreadStackSize());
// Find the RomFS by searching for a ".romfs" file in this directory
const auto& files = dir->GetFiles();
const auto romfs_iter =
std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) {
return file->GetName().find(".romfs") != std::string::npos;
});
filepath_romfs = FindRomFS(directory);
// Register the RomFS if a ".romfs" file was found
if (romfs_iter != files.end() && *romfs_iter != nullptr) {
romfs = *romfs_iter;
if (!filepath_romfs.empty()) {
Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this));
}
@@ -121,10 +161,29 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
return ResultStatus::Success;
}
ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(FileSys::VirtualFile& dir) {
if (romfs == nullptr)
ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(
std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, u64& size) {
if (filepath_romfs.empty()) {
LOG_DEBUG(Loader, "No RomFS available");
return ResultStatus::ErrorNotUsed;
dir = romfs;
}
// We reopen the file, to allow its position to be independent
romfs_file = std::make_shared<FileUtil::IOFile>(filepath_romfs, "rb");
if (!romfs_file->IsOpen()) {
return ResultStatus::Error;
}
offset = 0;
size = romfs_file->GetSize();
LOG_DEBUG(Loader, "RomFS offset: 0x{:016X}", offset);
LOG_DEBUG(Loader, "RomFS size: 0x{:016X}", size);
// Reset read pointer
file.Seek(0, SEEK_SET);
return ResultStatus::Success;
}

View File

@@ -20,26 +20,29 @@ namespace Loader {
*/
class AppLoader_DeconstructedRomDirectory final : public AppLoader {
public:
explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile main_file);
AppLoader_DeconstructedRomDirectory(FileUtil::IOFile&& file, std::string filepath);
/**
* Returns the type of the file
* @param file std::shared_ptr<VfsFile> open file
* @param file FileUtil::IOFile open file
* @param filepath Path of the file that we are opening.
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
FileType GetFileType() override {
return IdentifyType(file);
return IdentifyType(file, filepath);
}
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
u64& size) override;
private:
std::string filepath_romfs;
std::string filepath;
FileSys::ProgramMetadata metadata;
FileSys::VirtualFile romfs;
};
} // namespace Loader

View File

@@ -365,17 +365,20 @@ SectionID ElfReader::GetSectionByName(const char* name, int firstSection) const
namespace Loader {
AppLoader_ELF::AppLoader_ELF(FileSys::VirtualFile file) : AppLoader(std::move(file)) {}
AppLoader_ELF::AppLoader_ELF(FileUtil::IOFile&& file, std::string filename)
: AppLoader(std::move(file)), filename(std::move(filename)) {}
FileType AppLoader_ELF::IdentifyType(const FileSys::VirtualFile& file) {
FileType AppLoader_ELF::IdentifyType(FileUtil::IOFile& file, const std::string&) {
static constexpr u16 ELF_MACHINE_ARM{0x28};
u32 magic = 0;
if (4 != file->ReadObject(&magic))
file.Seek(0, SEEK_SET);
if (1 != file.ReadArray<u32>(&magic, 1))
return FileType::Error;
u16 machine = 0;
if (2 != file->ReadObject(&machine, 18))
file.Seek(18, SEEK_SET);
if (1 != file.ReadArray<u16>(&machine, 1))
return FileType::Error;
if (Common::MakeMagic('\x7f', 'E', 'L', 'F') == magic && ELF_MACHINE_ARM == machine)
@@ -388,13 +391,20 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) {
if (is_loaded)
return ResultStatus::ErrorAlreadyLoaded;
std::vector<u8> buffer = file->ReadAllBytes();
if (buffer.size() != file->GetSize())
if (!file.IsOpen())
return ResultStatus::Error;
// Reset read pointer in case this file has been read before.
file.Seek(0, SEEK_SET);
size_t size = file.GetSize();
std::unique_ptr<u8[]> buffer(new u8[size]);
if (file.ReadBytes(&buffer[0], size) != size)
return ResultStatus::Error;
ElfReader elf_reader(&buffer[0]);
SharedPtr<CodeSet> codeset = elf_reader.LoadInto(Memory::PROCESS_IMAGE_VADDR);
codeset->name = file->GetName();
codeset->name = filename;
process->LoadModule(codeset, codeset->entrypoint);
process->svc_access_mask.set();

View File

@@ -16,20 +16,24 @@ namespace Loader {
/// Loads an ELF/AXF file
class AppLoader_ELF final : public AppLoader {
public:
explicit AppLoader_ELF(FileSys::VirtualFile file);
AppLoader_ELF(FileUtil::IOFile&& file, std::string filename);
/**
* Returns the type of the file
* @param file std::shared_ptr<VfsFile> open file
* @param file FileUtil::IOFile open file
* @param filepath Path of the file that we are opening.
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
FileType GetFileType() override {
return IdentifyType(file);
return IdentifyType(file, filename);
}
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
private:
std::string filename;
};
} // namespace Loader

View File

@@ -6,7 +6,6 @@
#include <string>
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/file_sys/vfs_real.h"
#include "core/hle/kernel/process.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/elf.h"
@@ -22,11 +21,11 @@ const std::initializer_list<Kernel::AddressMapping> default_address_mappings = {
{0x1F000000, 0x600000, false}, // entire VRAM
};
FileType IdentifyFile(FileSys::VirtualFile file) {
FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath) {
FileType type;
#define CHECK_TYPE(loader) \
type = AppLoader_##loader::IdentifyType(file); \
type = AppLoader_##loader::IdentifyType(file, filepath); \
if (FileType::Error != type) \
return type;
@@ -42,22 +41,25 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
}
FileType IdentifyFile(const std::string& file_name) {
return IdentifyFile(FileSys::VirtualFile(std::make_shared<FileSys::RealVfsFile>(file_name)));
FileUtil::IOFile file(file_name, "rb");
if (!file.IsOpen()) {
LOG_ERROR(Loader, "Failed to load file {}", file_name);
return FileType::Unknown;
}
return IdentifyFile(file, file_name);
}
FileType GuessFromFilename(const std::string& name) {
if (name == "main")
return FileType::DeconstructedRomDirectory;
FileType GuessFromExtension(const std::string& extension_) {
std::string extension = Common::ToLower(extension_);
const std::string extension = Common::ToLower(FileUtil::GetExtensionFromFilename(name));
if (extension == "elf")
if (extension == ".elf")
return FileType::ELF;
if (extension == "nro")
else if (extension == ".nro")
return FileType::NRO;
if (extension == "nso")
else if (extension == ".nso")
return FileType::NSO;
if (extension == "nca")
else if (extension == ".nca")
return FileType::NCA;
return FileType::Unknown;
@@ -91,47 +93,58 @@ const char* GetFileTypeString(FileType type) {
* @param filepath the file full path (with name)
* @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type
*/
static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileType type) {
static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileType type,
const std::string& filename,
const std::string& filepath) {
switch (type) {
// Standard ELF file format.
case FileType::ELF:
return std::make_unique<AppLoader_ELF>(std::move(file));
return std::make_unique<AppLoader_ELF>(std::move(file), filename);
// NX NSO file format.
case FileType::NSO:
return std::make_unique<AppLoader_NSO>(std::move(file));
return std::make_unique<AppLoader_NSO>(std::move(file), filepath);
// NX NRO file format.
case FileType::NRO:
return std::make_unique<AppLoader_NRO>(std::move(file));
return std::make_unique<AppLoader_NRO>(std::move(file), filepath);
// NX NCA file format.
case FileType::NCA:
return std::make_unique<AppLoader_NCA>(std::move(file));
return std::make_unique<AppLoader_NCA>(std::move(file), filepath);
// NX deconstructed ROM directory.
case FileType::DeconstructedRomDirectory:
return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file));
return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file), filepath);
default:
return nullptr;
}
}
std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file) {
FileType type = IdentifyFile(file);
FileType filename_type = GuessFromFilename(file->GetName());
std::unique_ptr<AppLoader> GetLoader(const std::string& filename) {
FileUtil::IOFile file(filename, "rb");
if (!file.IsOpen()) {
LOG_ERROR(Loader, "Failed to load file {}", filename);
return nullptr;
}
std::string filename_filename, filename_extension;
Common::SplitPath(filename, nullptr, &filename_filename, &filename_extension);
FileType type = IdentifyFile(file, filename);
FileType filename_type = GuessFromExtension(filename_extension);
if (type != filename_type) {
LOG_WARNING(Loader, "File {} has a different type than its extension.", file->GetName());
LOG_WARNING(Loader, "File {} has a different type than its extension.", filename);
if (FileType::Unknown == type)
type = filename_type;
}
LOG_DEBUG(Loader, "Loading file {} as {}...", file->GetName(), GetFileTypeString(type));
LOG_DEBUG(Loader, "Loading file {} as {}...", filename, GetFileTypeString(type));
return GetFileLoader(std::move(file), type);
return GetFileLoader(std::move(file), type, filename_filename, filename);
}
} // namespace Loader

View File

@@ -13,7 +13,6 @@
#include <boost/optional.hpp>
#include "common/common_types.h"
#include "common/file_util.h"
#include "core/file_sys/vfs.h"
#include "core/hle/kernel/kernel.h"
namespace Kernel {
@@ -37,9 +36,10 @@ enum class FileType {
/**
* Identifies the type of a bootable file based on the magic value in its header.
* @param file open file
* @param filepath Path of the file that we are opening.
* @return FileType of file
*/
FileType IdentifyFile(FileSys::VirtualFile file);
FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath);
/**
* Identifies the type of a bootable file based on the magic value in its header.
@@ -50,12 +50,12 @@ FileType IdentifyFile(FileSys::VirtualFile file);
FileType IdentifyFile(const std::string& file_name);
/**
* Guess the type of a bootable file from its name
* @param name String name of bootable file
* Guess the type of a bootable file from its extension
* @param extension String extension of bootable file
* @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine
* a filetype, and will never return FileType::Error.
*/
FileType GuessFromFilename(const std::string& name);
FileType GuessFromExtension(const std::string& extension);
/**
* Convert a FileType into a string which can be displayed to the user.
@@ -79,7 +79,7 @@ enum class ResultStatus {
/// Interface for loading an application
class AppLoader : NonCopyable {
public:
AppLoader(FileSys::VirtualFile file) : file(std::move(file)) {}
AppLoader(FileUtil::IOFile&& file) : file(std::move(file)) {}
virtual ~AppLoader() {}
/**
@@ -154,20 +154,26 @@ public:
/**
* Get the RomFS of the application
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer
* @param file The file containing the RomFS
* @param romfs_file The file containing the RomFS
* @param offset The offset the romfs begins on
* @param size The size of the romfs
* @return ResultStatus result of function
*/
virtual ResultStatus ReadRomFS(FileSys::VirtualFile& dir) {
virtual ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
u64& size) {
return ResultStatus::ErrorNotImplemented;
}
/**
* Get the update RomFS of the application
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer
* @param file The file containing the RomFS
* @param romfs_file The file containing the RomFS
* @param offset The offset the romfs begins on
* @param size The size of the romfs
* @return ResultStatus result of function
*/
virtual ResultStatus ReadUpdateRomFS(FileSys::VirtualFile& file) {
virtual ResultStatus ReadUpdateRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
u64& size) {
return ResultStatus::ErrorNotImplemented;
}
@@ -181,7 +187,7 @@ public:
}
protected:
FileSys::VirtualFile file;
FileUtil::IOFile file;
bool is_loaded = false;
};
@@ -196,6 +202,6 @@ extern const std::initializer_list<Kernel::AddressMapping> default_address_mappi
* @param filename String filename of bootable file
* @return best loader for this file
*/
std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file);
std::unique_ptr<AppLoader> GetLoader(const std::string& filename);
} // namespace Loader

View File

@@ -4,13 +4,14 @@
#include <vector>
#include "common/common_funcs.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "common/swap.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/program_metadata.h"
#include "core/file_sys/romfs_factory.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
@@ -21,15 +22,208 @@
namespace Loader {
AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file) : AppLoader(file) {}
// Media offsets in headers are stored divided by 512. Mult. by this to get real offset.
constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200;
FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) {
// TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support.
FileSys::NCAHeader header{};
if (sizeof(FileSys::NCAHeader) != file->ReadObject(&header))
constexpr u64 SECTION_HEADER_SIZE = 0x200;
constexpr u64 SECTION_HEADER_OFFSET = 0x400;
enum class NcaContentType : u8 { Program = 0, Meta = 1, Control = 2, Manual = 3, Data = 4 };
enum class NcaSectionFilesystemType : u8 { PFS0 = 0x2, ROMFS = 0x3 };
struct NcaSectionTableEntry {
u32_le media_offset;
u32_le media_end_offset;
INSERT_PADDING_BYTES(0x8);
};
static_assert(sizeof(NcaSectionTableEntry) == 0x10, "NcaSectionTableEntry has incorrect size.");
struct NcaHeader {
std::array<u8, 0x100> rsa_signature_1;
std::array<u8, 0x100> rsa_signature_2;
u32_le magic;
u8 is_system;
NcaContentType content_type;
u8 crypto_type;
u8 key_index;
u64_le size;
u64_le title_id;
INSERT_PADDING_BYTES(0x4);
u32_le sdk_version;
u8 crypto_type_2;
INSERT_PADDING_BYTES(15);
std::array<u8, 0x10> rights_id;
std::array<NcaSectionTableEntry, 0x4> section_tables;
std::array<std::array<u8, 0x20>, 0x4> hash_tables;
std::array<std::array<u8, 0x10>, 0x4> key_area;
INSERT_PADDING_BYTES(0xC0);
};
static_assert(sizeof(NcaHeader) == 0x400, "NcaHeader has incorrect size.");
struct NcaSectionHeaderBlock {
INSERT_PADDING_BYTES(3);
NcaSectionFilesystemType filesystem_type;
u8 crypto_type;
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(NcaSectionHeaderBlock) == 0x8, "NcaSectionHeaderBlock has incorrect size.");
struct Pfs0Superblock {
NcaSectionHeaderBlock header_block;
std::array<u8, 0x20> hash;
u32_le size;
INSERT_PADDING_BYTES(4);
u64_le hash_table_offset;
u64_le hash_table_size;
u64_le pfs0_header_offset;
u64_le pfs0_size;
INSERT_PADDING_BYTES(432);
};
static_assert(sizeof(Pfs0Superblock) == 0x200, "Pfs0Superblock has incorrect size.");
static bool IsValidNca(const NcaHeader& header) {
return header.magic == Common::MakeMagic('N', 'C', 'A', '2') ||
header.magic == Common::MakeMagic('N', 'C', 'A', '3');
}
// TODO(DarkLordZach): Add support for encrypted.
class Nca final {
std::vector<FileSys::PartitionFilesystem> pfs;
std::vector<u64> pfs_offset;
u64 romfs_offset = 0;
u64 romfs_size = 0;
boost::optional<u8> exefs_id = boost::none;
FileUtil::IOFile file;
std::string path;
u64 GetExeFsFileOffset(const std::string& file_name) const;
u64 GetExeFsFileSize(const std::string& file_name) const;
public:
ResultStatus Load(FileUtil::IOFile&& file, std::string path);
FileSys::PartitionFilesystem GetPfs(u8 id) const;
u64 GetRomFsOffset() const;
u64 GetRomFsSize() const;
std::vector<u8> GetExeFsFile(const std::string& file_name);
};
static bool IsPfsExeFs(const FileSys::PartitionFilesystem& pfs) {
// According to switchbrew, an exefs must only contain these two files:
return pfs.GetFileSize("main") > 0 && pfs.GetFileSize("main.npdm") > 0;
}
ResultStatus Nca::Load(FileUtil::IOFile&& in_file, std::string in_path) {
file = std::move(in_file);
path = in_path;
file.Seek(0, SEEK_SET);
std::array<u8, sizeof(NcaHeader)> header_array{};
if (sizeof(NcaHeader) != file.ReadBytes(header_array.data(), sizeof(NcaHeader)))
LOG_CRITICAL(Loader, "File reader errored out during header read.");
NcaHeader header{};
std::memcpy(&header, header_array.data(), sizeof(NcaHeader));
if (!IsValidNca(header))
return ResultStatus::ErrorInvalidFormat;
int number_sections =
std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
[](NcaSectionTableEntry entry) { return entry.media_offset > 0; });
for (int i = 0; i < number_sections; ++i) {
// Seek to beginning of this section.
file.Seek(SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE, SEEK_SET);
std::array<u8, sizeof(NcaSectionHeaderBlock)> array{};
if (sizeof(NcaSectionHeaderBlock) !=
file.ReadBytes(array.data(), sizeof(NcaSectionHeaderBlock)))
LOG_CRITICAL(Loader, "File reader errored out during header read.");
NcaSectionHeaderBlock block{};
std::memcpy(&block, array.data(), sizeof(NcaSectionHeaderBlock));
if (block.filesystem_type == NcaSectionFilesystemType::ROMFS) {
romfs_offset = header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER;
romfs_size =
header.section_tables[i].media_end_offset * MEDIA_OFFSET_MULTIPLIER - romfs_offset;
} else if (block.filesystem_type == NcaSectionFilesystemType::PFS0) {
Pfs0Superblock sb{};
// Seek back to beginning of this section.
file.Seek(SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE, SEEK_SET);
if (sizeof(Pfs0Superblock) != file.ReadBytes(&sb, sizeof(Pfs0Superblock)))
LOG_CRITICAL(Loader, "File reader errored out during header read.");
u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
MEDIA_OFFSET_MULTIPLIER) +
sb.pfs0_header_offset;
FileSys::PartitionFilesystem npfs{};
ResultStatus status = npfs.Load(path, offset);
if (status == ResultStatus::Success) {
pfs.emplace_back(std::move(npfs));
pfs_offset.emplace_back(offset);
}
}
}
for (size_t i = 0; i < pfs.size(); ++i) {
if (IsPfsExeFs(pfs[i]))
exefs_id = i;
}
return ResultStatus::Success;
}
FileSys::PartitionFilesystem Nca::GetPfs(u8 id) const {
return pfs[id];
}
u64 Nca::GetExeFsFileOffset(const std::string& file_name) const {
if (exefs_id == boost::none)
return 0;
return pfs[*exefs_id].GetFileOffset(file_name) + pfs_offset[*exefs_id];
}
u64 Nca::GetExeFsFileSize(const std::string& file_name) const {
if (exefs_id == boost::none)
return 0;
return pfs[*exefs_id].GetFileSize(file_name);
}
u64 Nca::GetRomFsOffset() const {
return romfs_offset;
}
u64 Nca::GetRomFsSize() const {
return romfs_size;
}
std::vector<u8> Nca::GetExeFsFile(const std::string& file_name) {
std::vector<u8> out(GetExeFsFileSize(file_name));
file.Seek(GetExeFsFileOffset(file_name), SEEK_SET);
file.ReadBytes(out.data(), GetExeFsFileSize(file_name));
return out;
}
AppLoader_NCA::AppLoader_NCA(FileUtil::IOFile&& file, std::string filepath)
: AppLoader(std::move(file)), filepath(std::move(filepath)) {}
FileType AppLoader_NCA::IdentifyType(FileUtil::IOFile& file, const std::string&) {
file.Seek(0, SEEK_SET);
std::array<u8, 0x400> header_enc_array{};
if (0x400 != file.ReadBytes(header_enc_array.data(), 0x400))
return FileType::Error;
if (IsValidNCA(header) && header.content_type == FileSys::NCAContentType::Program)
// TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support.
NcaHeader header{};
std::memcpy(&header, header_enc_array.data(), sizeof(NcaHeader));
if (IsValidNca(header) && header.content_type == NcaContentType::Program)
return FileType::NCA;
return FileType::Error;
@@ -39,22 +233,17 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) {
if (is_loaded) {
return ResultStatus::ErrorAlreadyLoaded;
}
if (!file.IsOpen()) {
return ResultStatus::Error;
}
nca = std::make_unique<FileSys::NCA>(file);
ResultStatus result = nca->GetStatus();
nca = std::make_unique<Nca>();
ResultStatus result = nca->Load(std::move(file), filepath);
if (result != ResultStatus::Success) {
return result;
}
if (nca->GetType() != FileSys::NCAContentType::Program)
return ResultStatus::ErrorInvalidFormat;
auto exefs = nca->GetExeFS();
if (exefs == nullptr)
return ResultStatus::ErrorInvalidFormat;
result = metadata.Load(exefs->GetFile("main.npdm"));
result = metadata.Load(nca->GetExeFsFile("main.npdm"));
if (result != ResultStatus::Success) {
return result;
}
@@ -69,8 +258,7 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) {
for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
"subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) {
const VAddr load_addr = next_load_addr;
next_load_addr = AppLoader_NSO::LoadModule(exefs->GetFile(module), load_addr);
next_load_addr = AppLoader_NSO::LoadModule(module, nca->GetExeFsFile(module), load_addr);
if (next_load_addr) {
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
// Register module with GDBStub
@@ -88,18 +276,28 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) {
process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(),
metadata.GetMainThreadStackSize());
if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0)
if (nca->GetRomFsSize() > 0)
Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this));
is_loaded = true;
return ResultStatus::Success;
}
ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
if (nca == nullptr || nca->GetRomFS() == nullptr || nca->GetRomFS()->GetSize() == 0)
ResultStatus AppLoader_NCA::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
u64& size) {
if (nca->GetRomFsSize() == 0) {
LOG_DEBUG(Loader, "No RomFS available");
return ResultStatus::ErrorNotUsed;
dir = nca->GetRomFS();
}
romfs_file = std::make_shared<FileUtil::IOFile>(filepath, "rb");
offset = nca->GetRomFsOffset();
size = nca->GetRomFsSize();
LOG_DEBUG(Loader, "RomFS offset: 0x{:016X}", offset);
LOG_DEBUG(Loader, "RomFS size: 0x{:016X}", size);
return ResultStatus::Success;
}

View File

@@ -6,39 +6,44 @@
#include <string>
#include "common/common_types.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/partition_filesystem.h"
#include "core/file_sys/program_metadata.h"
#include "core/hle/kernel/kernel.h"
#include "core/loader/loader.h"
namespace Loader {
class Nca;
/// Loads an NCA file
class AppLoader_NCA final : public AppLoader {
public:
explicit AppLoader_NCA(FileSys::VirtualFile file);
AppLoader_NCA(FileUtil::IOFile&& file, std::string filepath);
/**
* Returns the type of the file
* @param file std::shared_ptr<VfsFile> open file
* @param file FileUtil::IOFile open file
* @param filepath Path of the file that we are opening.
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
FileType GetFileType() override {
return IdentifyType(file);
return IdentifyType(file, filepath);
}
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
u64& size) override;
~AppLoader_NCA();
private:
std::string filepath;
FileSys::ProgramMetadata metadata;
std::unique_ptr<FileSys::NCA> nca;
std::unique_ptr<Nca> nca;
};
} // namespace Loader

View File

@@ -48,12 +48,14 @@ struct ModHeader {
};
static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size.");
AppLoader_NRO::AppLoader_NRO(FileSys::VirtualFile file) : AppLoader(file) {}
AppLoader_NRO::AppLoader_NRO(FileUtil::IOFile&& file, std::string filepath)
: AppLoader(std::move(file)), filepath(std::move(filepath)) {}
FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& file) {
FileType AppLoader_NRO::IdentifyType(FileUtil::IOFile& file, const std::string&) {
// Read NSO header
NroHeader nro_header{};
if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
file.Seek(0, SEEK_SET);
if (sizeof(NroHeader) != file.ReadBytes(&nro_header, sizeof(NroHeader))) {
return FileType::Error;
}
if (nro_header.magic == Common::MakeMagic('N', 'R', 'O', '0')) {
@@ -66,10 +68,16 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) {
FileUtil::IOFile file(path, "rb");
if (!file.IsOpen()) {
return {};
}
// Read NSO header
NroHeader nro_header{};
if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
file.Seek(0, SEEK_SET);
if (sizeof(NroHeader) != file.ReadBytes(&nro_header, sizeof(NroHeader))) {
return {};
}
if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) {
@@ -78,9 +86,10 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
// Build program image
Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("");
std::vector<u8> program_image = file->ReadBytes(PageAlignSize(nro_header.file_size));
if (program_image.size() != PageAlignSize(nro_header.file_size))
return {};
std::vector<u8> program_image;
program_image.resize(PageAlignSize(nro_header.file_size));
file.Seek(0, SEEK_SET);
file.ReadBytes(program_image.data(), nro_header.file_size);
for (int i = 0; i < nro_header.segments.size(); ++i) {
codeset->segments[i].addr = nro_header.segments[i].offset;
@@ -103,7 +112,7 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
// Load codeset for current process
codeset->name = file->GetName();
codeset->name = path;
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
Core::CurrentProcess()->LoadModule(codeset, load_base);
@@ -117,11 +126,14 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
if (is_loaded) {
return ResultStatus::ErrorAlreadyLoaded;
}
if (!file.IsOpen()) {
return ResultStatus::Error;
}
// Load NRO
static constexpr VAddr base_addr{Memory::PROCESS_IMAGE_VADDR};
if (!LoadNro(file, base_addr)) {
if (!LoadNro(filepath, base_addr)) {
return ResultStatus::ErrorInvalidFormat;
}

View File

@@ -15,23 +15,26 @@ namespace Loader {
/// Loads an NRO file
class AppLoader_NRO final : public AppLoader, Linker {
public:
AppLoader_NRO(FileSys::VirtualFile file);
AppLoader_NRO(FileUtil::IOFile&& file, std::string filepath);
/**
* Returns the type of the file
* @param file std::shared_ptr<VfsFile> open file
* @param file FileUtil::IOFile open file
* @param filepath Path of the file that we are opening.
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
FileType GetFileType() override {
return IdentifyType(file);
return IdentifyType(file, filepath);
}
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
private:
bool LoadNro(FileSys::VirtualFile file, VAddr load_base);
bool LoadNro(const std::string& path, VAddr load_base);
std::string filepath;
};
} // namespace Loader

View File

@@ -38,7 +38,6 @@ struct NsoHeader {
std::array<u32_le, 3> segments_compressed_size;
};
static_assert(sizeof(NsoHeader) == 0x6c, "NsoHeader has incorrect size.");
static_assert(std::is_trivially_copyable_v<NsoHeader>, "NsoHeader isn't trivially copyable.");
struct ModHeader {
u32_le magic;
@@ -51,11 +50,15 @@ struct ModHeader {
};
static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size.");
AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {}
AppLoader_NSO::AppLoader_NSO(FileUtil::IOFile&& file, std::string filepath)
: AppLoader(std::move(file)), filepath(std::move(filepath)) {}
FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) {
FileType AppLoader_NSO::IdentifyType(FileUtil::IOFile& file, const std::string&) {
u32 magic = 0;
file->ReadObject(&magic);
file.Seek(0, SEEK_SET);
if (1 != file.ReadArray<u32>(&magic, 1)) {
return FileType::Error;
}
if (Common::MakeMagic('N', 'S', 'O', '0') == magic) {
return FileType::NSO;
@@ -96,16 +99,13 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
if (file == nullptr)
VAddr AppLoader_NSO::LoadModule(const std::string& name, const std::vector<u8>& file_data,
VAddr load_base) {
if (file_data.size() < sizeof(NsoHeader))
return {};
if (file->GetSize() < sizeof(NsoHeader))
return {};
NsoHeader nso_header{};
if (sizeof(NsoHeader) != file->ReadObject(&nso_header))
return {};
NsoHeader nso_header;
std::memcpy(&nso_header, file_data.data(), sizeof(NsoHeader));
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
return {};
@@ -114,8 +114,9 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("");
std::vector<u8> program_image;
for (int i = 0; i < nso_header.segments.size(); ++i) {
const std::vector<u8> compressed_data =
file->ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
std::vector<u8> compressed_data(nso_header.segments_compressed_size[i]);
for (auto j = 0; j < nso_header.segments_compressed_size[i]; ++j)
compressed_data[j] = file_data[nso_header.segments[i].offset + j];
std::vector<u8> data = DecompressSegment(compressed_data, nso_header.segments[i]);
program_image.resize(nso_header.segments[i].location);
program_image.insert(program_image.end(), data.begin(), data.end());
@@ -143,7 +144,7 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
program_image.resize(image_size);
// Load codeset for current process
codeset->name = file->GetName();
codeset->name = name;
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
Core::CurrentProcess()->LoadModule(codeset, load_base);
@@ -153,14 +154,72 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
return load_base + image_size;
}
VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base) {
FileUtil::IOFile file(path, "rb");
if (!file.IsOpen()) {
return {};
}
// Read NSO header
NsoHeader nso_header{};
file.Seek(0, SEEK_SET);
if (sizeof(NsoHeader) != file.ReadBytes(&nso_header, sizeof(NsoHeader))) {
return {};
}
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
return {};
}
// Build program image
Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("");
std::vector<u8> program_image;
for (int i = 0; i < nso_header.segments.size(); ++i) {
std::vector<u8> data =
ReadSegment(file, nso_header.segments[i], nso_header.segments_compressed_size[i]);
program_image.resize(nso_header.segments[i].location);
program_image.insert(program_image.end(), data.begin(), data.end());
codeset->segments[i].addr = nso_header.segments[i].location;
codeset->segments[i].offset = nso_header.segments[i].location;
codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
}
// MOD header pointer is at .text offset + 4
u32 module_offset;
std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32));
// Read MOD header
ModHeader mod_header{};
// Default .bss to size in segment header if MOD0 section doesn't exist
u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)};
std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(ModHeader));
const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')};
if (has_mod_header) {
// Resize program image to include .bss section and page align each section
bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
}
codeset->data.size += bss_size;
const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)};
program_image.resize(image_size);
// Load codeset for current process
codeset->name = path;
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
Core::CurrentProcess()->LoadModule(codeset, load_base);
return load_base + image_size;
}
ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
if (is_loaded) {
return ResultStatus::ErrorAlreadyLoaded;
}
if (!file.IsOpen()) {
return ResultStatus::Error;
}
// Load module
LoadModule(file, Memory::PROCESS_IMAGE_VADDR);
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), Memory::PROCESS_IMAGE_VADDR);
LoadModule(filepath, Memory::PROCESS_IMAGE_VADDR);
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", filepath, Memory::PROCESS_IMAGE_VADDR);
process->svc_access_mask.set();
process->address_mappings = default_address_mappings;

View File

@@ -15,22 +15,29 @@ namespace Loader {
/// Loads an NSO file
class AppLoader_NSO final : public AppLoader, Linker {
public:
explicit AppLoader_NSO(FileSys::VirtualFile file);
AppLoader_NSO(FileUtil::IOFile&& file, std::string filepath);
/**
* Returns the type of the file
* @param file std::shared_ptr<VfsFile> open file
* @param file FileUtil::IOFile open file
* @param filepath Path of the file that we are opening.
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
FileType GetFileType() override {
return IdentifyType(file);
return IdentifyType(file, filepath);
}
static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base);
static VAddr LoadModule(const std::string& name, const std::vector<u8>& file_data,
VAddr load_base);
static VAddr LoadModule(const std::string& path, VAddr load_base);
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
private:
std::string filepath;
};
} // namespace Loader

View File

@@ -3,6 +3,7 @@ add_executable(tests
core/arm/arm_test_common.cpp
core/arm/arm_test_common.h
core/core_timing.cpp
core/file_sys/path_parser.cpp
core/memory/memory.cpp
glad.cpp
tests.cpp

View File

@@ -0,0 +1,38 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <catch.hpp>
#include "common/file_util.h"
#include "core/file_sys/path_parser.h"
namespace FileSys {
TEST_CASE("PathParser", "[core][file_sys]") {
REQUIRE(!PathParser(Path(std::vector<u8>{})).IsValid());
REQUIRE(!PathParser(Path("a")).IsValid());
REQUIRE(!PathParser(Path("/|")).IsValid());
REQUIRE(PathParser(Path("/a")).IsValid());
REQUIRE(!PathParser(Path("/a/b/../../c/../../d")).IsValid());
REQUIRE(PathParser(Path("/a/b/../c/../../d")).IsValid());
REQUIRE(PathParser(Path("/")).IsRootDirectory());
REQUIRE(!PathParser(Path("/a")).IsRootDirectory());
REQUIRE(PathParser(Path("/a/..")).IsRootDirectory());
}
TEST_CASE("PathParser - Host file system", "[core][file_sys]") {
std::string test_dir = "./test";
FileUtil::CreateDir(test_dir);
FileUtil::CreateDir(test_dir + "/z");
FileUtil::CreateEmptyFile(test_dir + "/a");
REQUIRE(PathParser(Path("/a")).GetHostStatus(test_dir) == PathParser::FileFound);
REQUIRE(PathParser(Path("/b")).GetHostStatus(test_dir) == PathParser::NotFound);
REQUIRE(PathParser(Path("/z")).GetHostStatus(test_dir) == PathParser::DirectoryFound);
REQUIRE(PathParser(Path("/a/c")).GetHostStatus(test_dir) == PathParser::FileInPath);
REQUIRE(PathParser(Path("/b/c")).GetHostStatus(test_dir) == PathParser::PathNotFound);
FileUtil::DeleteDirRecursively(test_dir);
}
} // namespace FileSys

View File

@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cmath>
#include <cstring>
#include "common/assert.h"
#include "core/memory.h"
@@ -18,9 +17,7 @@ namespace Texture {
* Taken from the Tegra X1 TRM.
*/
static u32 GetSwizzleOffset(u32 x, u32 y, u32 image_width, u32 bytes_per_pixel, u32 block_height) {
// Round up to the next gob
const u32 image_width_in_gobs{(image_width * bytes_per_pixel + 63) / 64};
u32 image_width_in_gobs = image_width * bytes_per_pixel / 64;
u32 GOB_address = 0 + (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs +
(x * bytes_per_pixel / 64) * 512 * block_height +
(y % (8 * block_height) / 8) * 512;

View File

@@ -12,7 +12,6 @@
#include "common/common_paths.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/file_sys/vfs_real.h"
#include "core/loader/loader.h"
#include "game_list.h"
#include "game_list_p.h"
@@ -404,8 +403,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
bool is_dir = FileUtil::IsDirectory(physical_name);
if (!is_dir &&
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
std::unique_ptr<Loader::AppLoader> loader =
Loader::GetLoader(std::make_shared<FileSys::RealVfsFile>(physical_name));
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
if (!loader)
return true;