file_sys: Remove disk_archive, savedata_archive, and title_metadata.
This commit is contained in:
		
							parent
							
								
									7988f02489
								
							
						
					
					
						commit
						1c06c918af
					
				@ -9,18 +9,12 @@ add_library(core STATIC
 | 
			
		||||
    file_sys/archive_backend.cpp
 | 
			
		||||
    file_sys/archive_backend.h
 | 
			
		||||
    file_sys/directory_backend.h
 | 
			
		||||
    file_sys/disk_archive.cpp
 | 
			
		||||
    file_sys/disk_archive.h
 | 
			
		||||
    file_sys/errors.h
 | 
			
		||||
    file_sys/file_backend.h
 | 
			
		||||
    file_sys/path_parser.cpp
 | 
			
		||||
    file_sys/path_parser.h
 | 
			
		||||
    file_sys/romfs_archive.cpp
 | 
			
		||||
    file_sys/romfs_archive.h
 | 
			
		||||
    file_sys/savedata_archive.cpp
 | 
			
		||||
    file_sys/savedata_archive.h
 | 
			
		||||
    file_sys/title_metadata.cpp
 | 
			
		||||
    file_sys/title_metadata.h
 | 
			
		||||
    frontend/emu_window.cpp
 | 
			
		||||
    frontend/emu_window.h
 | 
			
		||||
    frontend/framebuffer_layout.cpp
 | 
			
		||||
 | 
			
		||||
@ -1,99 +0,0 @@
 | 
			
		||||
// Copyright 2014 Citra Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/file_util.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "core/file_sys/disk_archive.h"
 | 
			
		||||
#include "core/file_sys/errors.h"
 | 
			
		||||
 | 
			
		||||
////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// FileSys namespace
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const {
 | 
			
		||||
    if (!mode.read_flag)
 | 
			
		||||
        return ERROR_INVALID_OPEN_FLAGS;
 | 
			
		||||
 | 
			
		||||
    file->Seek(offset, SEEK_SET);
 | 
			
		||||
    return MakeResult<size_t>(file->ReadBytes(buffer, length));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultVal<size_t> DiskFile::Write(const u64 offset, const size_t length, const bool flush,
 | 
			
		||||
                                  const u8* buffer) const {
 | 
			
		||||
    if (!mode.write_flag)
 | 
			
		||||
        return ERROR_INVALID_OPEN_FLAGS;
 | 
			
		||||
 | 
			
		||||
    file->Seek(offset, SEEK_SET);
 | 
			
		||||
    size_t written = file->WriteBytes(buffer, length);
 | 
			
		||||
    if (flush)
 | 
			
		||||
        file->Flush();
 | 
			
		||||
    return MakeResult<size_t>(written);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u64 DiskFile::GetSize() const {
 | 
			
		||||
    return file->GetSize();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DiskFile::SetSize(const u64 size) const {
 | 
			
		||||
    file->Resize(size);
 | 
			
		||||
    file->Flush();
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DiskFile::Close() const {
 | 
			
		||||
    return file->Close();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
DiskDirectory::DiskDirectory(const std::string& path) : directory() {
 | 
			
		||||
    unsigned size = FileUtil::ScanDirectoryTree(path, directory);
 | 
			
		||||
    directory.size = size;
 | 
			
		||||
    directory.isDirectory = true;
 | 
			
		||||
    children_iterator = directory.children.begin();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32 DiskDirectory::Read(const u32 count, Entry* entries) {
 | 
			
		||||
    u32 entries_read = 0;
 | 
			
		||||
 | 
			
		||||
    while (entries_read < count && children_iterator != directory.children.cend()) {
 | 
			
		||||
        const FileUtil::FSTEntry& file = *children_iterator;
 | 
			
		||||
        const std::string& filename = file.virtualName;
 | 
			
		||||
        Entry& entry = entries[entries_read];
 | 
			
		||||
 | 
			
		||||
        LOG_TRACE(Service_FS, "File %s: size=%llu dir=%d", filename.c_str(), file.size,
 | 
			
		||||
                  file.isDirectory);
 | 
			
		||||
 | 
			
		||||
        // TODO(Link Mauve): use a proper conversion to UTF-16.
 | 
			
		||||
        for (size_t j = 0; j < FILENAME_LENGTH; ++j) {
 | 
			
		||||
            entry.filename[j] = filename[j];
 | 
			
		||||
            if (!filename[j])
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        FileUtil::SplitFilename83(filename, entry.short_name, entry.extension);
 | 
			
		||||
 | 
			
		||||
        entry.is_directory = file.isDirectory;
 | 
			
		||||
        entry.is_hidden = (filename[0] == '.');
 | 
			
		||||
        entry.is_read_only = 0;
 | 
			
		||||
        entry.file_size = file.size;
 | 
			
		||||
 | 
			
		||||
        // We emulate a SD card where the archive bit has never been cleared, as it would be on
 | 
			
		||||
        // most user SD cards.
 | 
			
		||||
        // Some homebrews (blargSNES for instance) are known to mistakenly use the archive bit as a
 | 
			
		||||
        // file bit.
 | 
			
		||||
        entry.is_archive = !file.isDirectory;
 | 
			
		||||
 | 
			
		||||
        ++entries_read;
 | 
			
		||||
        ++children_iterator;
 | 
			
		||||
    }
 | 
			
		||||
    return entries_read;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
@ -1,68 +0,0 @@
 | 
			
		||||
// Copyright 2014 Citra Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/file_util.h"
 | 
			
		||||
#include "core/file_sys/archive_backend.h"
 | 
			
		||||
#include "core/file_sys/directory_backend.h"
 | 
			
		||||
#include "core/file_sys/file_backend.h"
 | 
			
		||||
#include "core/hle/result.h"
 | 
			
		||||
 | 
			
		||||
////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// FileSys namespace
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
class DiskFile : public FileBackend {
 | 
			
		||||
public:
 | 
			
		||||
    DiskFile(FileUtil::IOFile&& file_, const Mode& mode_)
 | 
			
		||||
        : file(new FileUtil::IOFile(std::move(file_))) {
 | 
			
		||||
        mode.hex = mode_.hex;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
 | 
			
		||||
    ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
 | 
			
		||||
    u64 GetSize() const override;
 | 
			
		||||
    bool SetSize(u64 size) const override;
 | 
			
		||||
    bool Close() const override;
 | 
			
		||||
 | 
			
		||||
    void Flush() const override {
 | 
			
		||||
        file->Flush();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    Mode mode;
 | 
			
		||||
    std::unique_ptr<FileUtil::IOFile> file;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DiskDirectory : public DirectoryBackend {
 | 
			
		||||
public:
 | 
			
		||||
    DiskDirectory(const std::string& path);
 | 
			
		||||
 | 
			
		||||
    ~DiskDirectory() override {
 | 
			
		||||
        Close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    u32 Read(const u32 count, Entry* entries) override;
 | 
			
		||||
 | 
			
		||||
    bool Close() const override {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    u32 total_entries_in_directory;
 | 
			
		||||
    FileUtil::FSTEntry directory;
 | 
			
		||||
 | 
			
		||||
    // We need to remember the last entry we returned, so a subsequent call to Read will continue
 | 
			
		||||
    // from the next one.  This iterator will always point to the next unread entry.
 | 
			
		||||
    std::vector<FileUtil::FSTEntry>::iterator children_iterator;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
@ -1,330 +0,0 @@
 | 
			
		||||
// Copyright 2016 Citra Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include "common/file_util.h"
 | 
			
		||||
#include "core/file_sys/disk_archive.h"
 | 
			
		||||
#include "core/file_sys/errors.h"
 | 
			
		||||
#include "core/file_sys/path_parser.h"
 | 
			
		||||
#include "core/file_sys/savedata_archive.h"
 | 
			
		||||
 | 
			
		||||
////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// FileSys namespace
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path,
 | 
			
		||||
                                                                  const Mode& mode) const {
 | 
			
		||||
    LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
 | 
			
		||||
 | 
			
		||||
    const PathParser path_parser(path);
 | 
			
		||||
 | 
			
		||||
    if (!path_parser.IsValid()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
 | 
			
		||||
        return ERROR_INVALID_PATH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (mode.hex == 0) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Empty open mode");
 | 
			
		||||
        return ERROR_UNSUPPORTED_OPEN_FLAGS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (mode.create_flag && !mode.write_flag) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Create flag set but write flag not set");
 | 
			
		||||
        return ERROR_UNSUPPORTED_OPEN_FLAGS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto full_path = path_parser.BuildHostPath(mount_point);
 | 
			
		||||
 | 
			
		||||
    switch (path_parser.GetHostStatus(mount_point)) {
 | 
			
		||||
    case PathParser::InvalidMountPoint:
 | 
			
		||||
        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
 | 
			
		||||
        return ERROR_FILE_NOT_FOUND;
 | 
			
		||||
    case PathParser::PathNotFound:
 | 
			
		||||
        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
 | 
			
		||||
        return ERROR_PATH_NOT_FOUND;
 | 
			
		||||
    case PathParser::FileInPath:
 | 
			
		||||
    case PathParser::DirectoryFound:
 | 
			
		||||
        LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str());
 | 
			
		||||
        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
 | 
			
		||||
    case PathParser::NotFound:
 | 
			
		||||
        if (!mode.create_flag) {
 | 
			
		||||
            LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
 | 
			
		||||
                      full_path.c_str());
 | 
			
		||||
            return ERROR_FILE_NOT_FOUND;
 | 
			
		||||
        } else {
 | 
			
		||||
            // Create the file
 | 
			
		||||
            FileUtil::CreateEmptyFile(full_path);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    case PathParser::FileFound:
 | 
			
		||||
        break; // Expected 'success' case
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
 | 
			
		||||
    if (!file.IsOpen()) {
 | 
			
		||||
        LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
 | 
			
		||||
        return ERROR_FILE_NOT_FOUND;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto disk_file = std::make_unique<DiskFile>(std::move(file), mode);
 | 
			
		||||
    return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultCode SaveDataArchive::DeleteFile(const Path& path) const {
 | 
			
		||||
    const PathParser path_parser(path);
 | 
			
		||||
 | 
			
		||||
    if (!path_parser.IsValid()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
 | 
			
		||||
        return ERROR_INVALID_PATH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto full_path = path_parser.BuildHostPath(mount_point);
 | 
			
		||||
 | 
			
		||||
    switch (path_parser.GetHostStatus(mount_point)) {
 | 
			
		||||
    case PathParser::InvalidMountPoint:
 | 
			
		||||
        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
 | 
			
		||||
        return ERROR_FILE_NOT_FOUND;
 | 
			
		||||
    case PathParser::PathNotFound:
 | 
			
		||||
        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
 | 
			
		||||
        return ERROR_PATH_NOT_FOUND;
 | 
			
		||||
    case PathParser::FileInPath:
 | 
			
		||||
    case PathParser::DirectoryFound:
 | 
			
		||||
    case PathParser::NotFound:
 | 
			
		||||
        LOG_ERROR(Service_FS, "File not found %s", full_path.c_str());
 | 
			
		||||
        return ERROR_FILE_NOT_FOUND;
 | 
			
		||||
    case PathParser::FileFound:
 | 
			
		||||
        break; // Expected 'success' case
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (FileUtil::Delete(full_path)) {
 | 
			
		||||
        return RESULT_SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str());
 | 
			
		||||
    return ERROR_FILE_NOT_FOUND;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
 | 
			
		||||
    const PathParser path_parser_src(src_path);
 | 
			
		||||
 | 
			
		||||
    // TODO: Verify these return codes with HW
 | 
			
		||||
    if (!path_parser_src.IsValid()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str());
 | 
			
		||||
        return ERROR_INVALID_PATH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const PathParser path_parser_dest(dest_path);
 | 
			
		||||
 | 
			
		||||
    if (!path_parser_dest.IsValid()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str());
 | 
			
		||||
        return ERROR_INVALID_PATH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
 | 
			
		||||
    const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
 | 
			
		||||
 | 
			
		||||
    if (FileUtil::Rename(src_path_full, dest_path_full)) {
 | 
			
		||||
        return RESULT_SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO(bunnei): Use correct error code
 | 
			
		||||
    return ResultCode(-1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point,
 | 
			
		||||
                                        T deleter) {
 | 
			
		||||
    const PathParser path_parser(path);
 | 
			
		||||
 | 
			
		||||
    if (!path_parser.IsValid()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
 | 
			
		||||
        return ERROR_INVALID_PATH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (path_parser.IsRootDirectory())
 | 
			
		||||
        return ERROR_DIRECTORY_NOT_EMPTY;
 | 
			
		||||
 | 
			
		||||
    const auto full_path = path_parser.BuildHostPath(mount_point);
 | 
			
		||||
 | 
			
		||||
    switch (path_parser.GetHostStatus(mount_point)) {
 | 
			
		||||
    case PathParser::InvalidMountPoint:
 | 
			
		||||
        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
 | 
			
		||||
        return ERROR_PATH_NOT_FOUND;
 | 
			
		||||
    case PathParser::PathNotFound:
 | 
			
		||||
    case PathParser::NotFound:
 | 
			
		||||
        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
 | 
			
		||||
        return ERROR_PATH_NOT_FOUND;
 | 
			
		||||
    case PathParser::FileInPath:
 | 
			
		||||
    case PathParser::FileFound:
 | 
			
		||||
        LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str());
 | 
			
		||||
        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
 | 
			
		||||
    case PathParser::DirectoryFound:
 | 
			
		||||
        break; // Expected 'success' case
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (deleter(full_path)) {
 | 
			
		||||
        return RESULT_SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str());
 | 
			
		||||
    return ERROR_DIRECTORY_NOT_EMPTY;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultCode SaveDataArchive::DeleteDirectory(const Path& path) const {
 | 
			
		||||
    return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultCode SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const {
 | 
			
		||||
    return DeleteDirectoryHelper(
 | 
			
		||||
        path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const {
 | 
			
		||||
    const PathParser path_parser(path);
 | 
			
		||||
 | 
			
		||||
    if (!path_parser.IsValid()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
 | 
			
		||||
        return ERROR_INVALID_PATH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto full_path = path_parser.BuildHostPath(mount_point);
 | 
			
		||||
 | 
			
		||||
    switch (path_parser.GetHostStatus(mount_point)) {
 | 
			
		||||
    case PathParser::InvalidMountPoint:
 | 
			
		||||
        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
 | 
			
		||||
        return ERROR_FILE_NOT_FOUND;
 | 
			
		||||
    case PathParser::PathNotFound:
 | 
			
		||||
        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
 | 
			
		||||
        return ERROR_PATH_NOT_FOUND;
 | 
			
		||||
    case PathParser::FileInPath:
 | 
			
		||||
        LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
 | 
			
		||||
        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
 | 
			
		||||
    case PathParser::DirectoryFound:
 | 
			
		||||
    case PathParser::FileFound:
 | 
			
		||||
        LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
 | 
			
		||||
        return ERROR_FILE_ALREADY_EXISTS;
 | 
			
		||||
    case PathParser::NotFound:
 | 
			
		||||
        break; // Expected 'success' case
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (size == 0) {
 | 
			
		||||
        FileUtil::CreateEmptyFile(full_path);
 | 
			
		||||
        return RESULT_SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    FileUtil::IOFile file(full_path, "wb");
 | 
			
		||||
    // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
 | 
			
		||||
    // We do this by seeking to the right size, then writing a single null byte.
 | 
			
		||||
    if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
 | 
			
		||||
        return RESULT_SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LOG_ERROR(Service_FS, "Too large file");
 | 
			
		||||
 | 
			
		||||
    // TODO(bunnei): Use correct error code
 | 
			
		||||
    return ResultCode(-1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultCode SaveDataArchive::CreateDirectory(const Path& path) const {
 | 
			
		||||
    const PathParser path_parser(path);
 | 
			
		||||
 | 
			
		||||
    if (!path_parser.IsValid()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
 | 
			
		||||
        return ERROR_INVALID_PATH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto full_path = path_parser.BuildHostPath(mount_point);
 | 
			
		||||
 | 
			
		||||
    switch (path_parser.GetHostStatus(mount_point)) {
 | 
			
		||||
    case PathParser::InvalidMountPoint:
 | 
			
		||||
        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
 | 
			
		||||
        return ERROR_FILE_NOT_FOUND;
 | 
			
		||||
    case PathParser::PathNotFound:
 | 
			
		||||
        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
 | 
			
		||||
        return ERROR_PATH_NOT_FOUND;
 | 
			
		||||
    case PathParser::FileInPath:
 | 
			
		||||
        LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
 | 
			
		||||
        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
 | 
			
		||||
    case PathParser::DirectoryFound:
 | 
			
		||||
    case PathParser::FileFound:
 | 
			
		||||
        LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
 | 
			
		||||
        return ERROR_DIRECTORY_ALREADY_EXISTS;
 | 
			
		||||
    case PathParser::NotFound:
 | 
			
		||||
        break; // Expected 'success' case
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (FileUtil::CreateDir(mount_point + path.AsString())) {
 | 
			
		||||
        return RESULT_SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str());
 | 
			
		||||
 | 
			
		||||
    // TODO(bunnei): Use correct error code
 | 
			
		||||
    return ResultCode(-1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
 | 
			
		||||
    const PathParser path_parser_src(src_path);
 | 
			
		||||
 | 
			
		||||
    // TODO: Verify these return codes with HW
 | 
			
		||||
    if (!path_parser_src.IsValid()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str());
 | 
			
		||||
        return ERROR_INVALID_PATH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const PathParser path_parser_dest(dest_path);
 | 
			
		||||
 | 
			
		||||
    if (!path_parser_dest.IsValid()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str());
 | 
			
		||||
        return ERROR_INVALID_PATH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
 | 
			
		||||
    const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
 | 
			
		||||
 | 
			
		||||
    if (FileUtil::Rename(src_path_full, dest_path_full)) {
 | 
			
		||||
        return RESULT_SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO(bunnei): Use correct error code
 | 
			
		||||
    return ResultCode(-1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory(
 | 
			
		||||
    const Path& path) const {
 | 
			
		||||
    const PathParser path_parser(path);
 | 
			
		||||
 | 
			
		||||
    if (!path_parser.IsValid()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
 | 
			
		||||
        return ERROR_INVALID_PATH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto full_path = path_parser.BuildHostPath(mount_point);
 | 
			
		||||
 | 
			
		||||
    switch (path_parser.GetHostStatus(mount_point)) {
 | 
			
		||||
    case PathParser::InvalidMountPoint:
 | 
			
		||||
        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
 | 
			
		||||
        return ERROR_FILE_NOT_FOUND;
 | 
			
		||||
    case PathParser::PathNotFound:
 | 
			
		||||
    case PathParser::NotFound:
 | 
			
		||||
        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
 | 
			
		||||
        return ERROR_PATH_NOT_FOUND;
 | 
			
		||||
    case PathParser::FileInPath:
 | 
			
		||||
    case PathParser::FileFound:
 | 
			
		||||
        LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
 | 
			
		||||
        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
 | 
			
		||||
    case PathParser::DirectoryFound:
 | 
			
		||||
        break; // Expected 'success' case
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto directory = std::make_unique<DiskDirectory>(full_path);
 | 
			
		||||
    return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u64 SaveDataArchive::GetFreeSpaceSize() const {
 | 
			
		||||
    // TODO: Stubbed to return 1GiB
 | 
			
		||||
    return 1024 * 1024 * 1024;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
@ -1,43 +0,0 @@
 | 
			
		||||
// Copyright 2016 Citra Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include "core/file_sys/archive_backend.h"
 | 
			
		||||
#include "core/file_sys/directory_backend.h"
 | 
			
		||||
#include "core/file_sys/file_backend.h"
 | 
			
		||||
#include "core/hle/result.h"
 | 
			
		||||
 | 
			
		||||
////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// FileSys namespace
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
/// Archive backend for general save data archive type (SaveData and SystemSaveData)
 | 
			
		||||
class SaveDataArchive : public ArchiveBackend {
 | 
			
		||||
public:
 | 
			
		||||
    explicit SaveDataArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
 | 
			
		||||
 | 
			
		||||
    std::string GetName() const override {
 | 
			
		||||
        return "SaveDataArchive: " + mount_point;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
 | 
			
		||||
                                                     const Mode& mode) const override;
 | 
			
		||||
    ResultCode DeleteFile(const Path& path) const override;
 | 
			
		||||
    ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
 | 
			
		||||
    ResultCode DeleteDirectory(const Path& path) const override;
 | 
			
		||||
    ResultCode DeleteDirectoryRecursively(const Path& path) const override;
 | 
			
		||||
    ResultCode CreateFile(const Path& path, u64 size) const override;
 | 
			
		||||
    ResultCode CreateDirectory(const Path& path) const override;
 | 
			
		||||
    ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
 | 
			
		||||
    ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
 | 
			
		||||
    u64 GetFreeSpaceSize() const override;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    std::string mount_point;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
@ -1,163 +0,0 @@
 | 
			
		||||
// Copyright 2017 Citra Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include "common/alignment.h"
 | 
			
		||||
#include "common/file_util.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "core/file_sys/title_metadata.h"
 | 
			
		||||
#include "core/loader/loader.h"
 | 
			
		||||
 | 
			
		||||
////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// FileSys namespace
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
static u32 GetSignatureSize(u32 signature_type) {
 | 
			
		||||
    switch (signature_type) {
 | 
			
		||||
    case Rsa4096Sha1:
 | 
			
		||||
    case Rsa4096Sha256:
 | 
			
		||||
        return 0x200;
 | 
			
		||||
 | 
			
		||||
    case Rsa2048Sha1:
 | 
			
		||||
    case Rsa2048Sha256:
 | 
			
		||||
        return 0x100;
 | 
			
		||||
 | 
			
		||||
    case EllipticSha1:
 | 
			
		||||
    case EcdsaSha256:
 | 
			
		||||
        return 0x3C;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Loader::ResultStatus TitleMetadata::Load() {
 | 
			
		||||
    FileUtil::IOFile file(filepath, "rb");
 | 
			
		||||
    if (!file.IsOpen())
 | 
			
		||||
        return Loader::ResultStatus::Error;
 | 
			
		||||
 | 
			
		||||
    if (!file.ReadBytes(&signature_type, sizeof(u32_be)))
 | 
			
		||||
        return Loader::ResultStatus::Error;
 | 
			
		||||
 | 
			
		||||
    // Signature lengths are variable, and the body follows the signature
 | 
			
		||||
    u32 signature_size = GetSignatureSize(signature_type);
 | 
			
		||||
 | 
			
		||||
    tmd_signature.resize(signature_size);
 | 
			
		||||
    if (!file.ReadBytes(&tmd_signature[0], signature_size))
 | 
			
		||||
        return Loader::ResultStatus::Error;
 | 
			
		||||
 | 
			
		||||
    // The TMD body start position is rounded to the nearest 0x40 after the signature
 | 
			
		||||
    size_t body_start = Common::AlignUp(signature_size + sizeof(u32), 0x40);
 | 
			
		||||
    file.Seek(body_start, SEEK_SET);
 | 
			
		||||
 | 
			
		||||
    // Read our TMD body, then load the amount of ContentChunks specified
 | 
			
		||||
    if (file.ReadBytes(&tmd_body, sizeof(TitleMetadata::Body)) != sizeof(TitleMetadata::Body))
 | 
			
		||||
        return Loader::ResultStatus::Error;
 | 
			
		||||
 | 
			
		||||
    for (u16 i = 0; i < tmd_body.content_count; i++) {
 | 
			
		||||
        ContentChunk chunk;
 | 
			
		||||
        if (file.ReadBytes(&chunk, sizeof(ContentChunk)) == sizeof(ContentChunk)) {
 | 
			
		||||
            tmd_chunks.push_back(chunk);
 | 
			
		||||
        } else {
 | 
			
		||||
            LOG_ERROR(Service_FS, "Malformed TMD %s, failed to load content chunk index %u!",
 | 
			
		||||
                      filepath.c_str(), i);
 | 
			
		||||
            return Loader::ResultStatus::ErrorInvalidFormat;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Loader::ResultStatus::Success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Loader::ResultStatus TitleMetadata::Save() {
 | 
			
		||||
    UNIMPLEMENTED();
 | 
			
		||||
    return Loader::ResultStatus::Success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u64 TitleMetadata::GetTitleID() const {
 | 
			
		||||
    return tmd_body.title_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32 TitleMetadata::GetTitleType() const {
 | 
			
		||||
    return tmd_body.title_type;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u16 TitleMetadata::GetTitleVersion() const {
 | 
			
		||||
    return tmd_body.title_version;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u64 TitleMetadata::GetSystemVersion() const {
 | 
			
		||||
    return tmd_body.system_version;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t TitleMetadata::GetContentCount() const {
 | 
			
		||||
    return tmd_chunks.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32 TitleMetadata::GetBootContentID() const {
 | 
			
		||||
    return tmd_chunks[TMDContentIndex::Main].id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32 TitleMetadata::GetManualContentID() const {
 | 
			
		||||
    return tmd_chunks[TMDContentIndex::Manual].id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32 TitleMetadata::GetDLPContentID() const {
 | 
			
		||||
    return tmd_chunks[TMDContentIndex::DLP].id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TitleMetadata::SetTitleID(u64 title_id) {
 | 
			
		||||
    tmd_body.title_id = title_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TitleMetadata::SetTitleType(u32 type) {
 | 
			
		||||
    tmd_body.title_type = type;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TitleMetadata::SetTitleVersion(u16 version) {
 | 
			
		||||
    tmd_body.title_version = version;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TitleMetadata::SetSystemVersion(u64 version) {
 | 
			
		||||
    tmd_body.system_version = version;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TitleMetadata::AddContentChunk(const ContentChunk& chunk) {
 | 
			
		||||
    tmd_chunks.push_back(chunk);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TitleMetadata::Print() const {
 | 
			
		||||
    LOG_DEBUG(Service_FS, "%s - %u chunks", filepath.c_str(),
 | 
			
		||||
              static_cast<u32>(tmd_body.content_count));
 | 
			
		||||
 | 
			
		||||
    // Content info describes ranges of content chunks
 | 
			
		||||
    LOG_DEBUG(Service_FS, "Content info:");
 | 
			
		||||
    for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) {
 | 
			
		||||
        if (tmd_body.contentinfo[i].command_count == 0)
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        LOG_DEBUG(Service_FS, "    Index %04X, Command Count %04X",
 | 
			
		||||
                  static_cast<u32>(tmd_body.contentinfo[i].index),
 | 
			
		||||
                  static_cast<u32>(tmd_body.contentinfo[i].command_count));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // For each content info, print their content chunk range
 | 
			
		||||
    for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) {
 | 
			
		||||
        u16 index = static_cast<u16>(tmd_body.contentinfo[i].index);
 | 
			
		||||
        u16 count = static_cast<u16>(tmd_body.contentinfo[i].command_count);
 | 
			
		||||
 | 
			
		||||
        if (count == 0)
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        LOG_DEBUG(Service_FS, "Content chunks for content info index %zu:", i);
 | 
			
		||||
        for (u16 j = index; j < index + count; j++) {
 | 
			
		||||
            // Don't attempt to print content we don't have
 | 
			
		||||
            if (j > tmd_body.content_count)
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            const ContentChunk& chunk = tmd_chunks[j];
 | 
			
		||||
            LOG_DEBUG(Service_FS, "    ID %08X, Index %04X, Type %04x, Size %016" PRIX64,
 | 
			
		||||
                      static_cast<u32>(chunk.id), static_cast<u32>(chunk.index),
 | 
			
		||||
                      static_cast<u32>(chunk.type), static_cast<u64>(chunk.size));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
@ -1,126 +0,0 @@
 | 
			
		||||
// Copyright 2017 Citra Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
 | 
			
		||||
namespace Loader {
 | 
			
		||||
enum class ResultStatus;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// FileSys namespace
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
enum TMDSignatureType : u32 {
 | 
			
		||||
    Rsa4096Sha1 = 0x10000,
 | 
			
		||||
    Rsa2048Sha1 = 0x10001,
 | 
			
		||||
    EllipticSha1 = 0x10002,
 | 
			
		||||
    Rsa4096Sha256 = 0x10003,
 | 
			
		||||
    Rsa2048Sha256 = 0x10004,
 | 
			
		||||
    EcdsaSha256 = 0x10005
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum TMDContentTypeFlag : u16 {
 | 
			
		||||
    Encrypted = 1 << 1,
 | 
			
		||||
    Disc = 1 << 2,
 | 
			
		||||
    CFM = 1 << 3,
 | 
			
		||||
    Optional = 1 << 14,
 | 
			
		||||
    Shared = 1 << 15
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper which implements an interface to read and write Title Metadata (TMD) files.
 | 
			
		||||
 * If a file path is provided and the file exists, it can be parsed and used, otherwise
 | 
			
		||||
 * it must be created. The TMD file can then be interpreted, modified and/or saved.
 | 
			
		||||
 */
 | 
			
		||||
class TitleMetadata {
 | 
			
		||||
public:
 | 
			
		||||
    struct ContentChunk {
 | 
			
		||||
        u32_be id;
 | 
			
		||||
        u16_be index;
 | 
			
		||||
        u16_be type;
 | 
			
		||||
        u64_be size;
 | 
			
		||||
        std::array<u8, 0x20> hash;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    static_assert(sizeof(ContentChunk) == 0x30, "TMD ContentChunk structure size is wrong");
 | 
			
		||||
 | 
			
		||||
    struct ContentInfo {
 | 
			
		||||
        u16_be index;
 | 
			
		||||
        u16_be command_count;
 | 
			
		||||
        std::array<u8, 0x20> hash;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    static_assert(sizeof(ContentInfo) == 0x24, "TMD ContentInfo structure size is wrong");
 | 
			
		||||
 | 
			
		||||
#pragma pack(push, 1)
 | 
			
		||||
 | 
			
		||||
    struct Body {
 | 
			
		||||
        std::array<u8, 0x40> issuer;
 | 
			
		||||
        u8 version;
 | 
			
		||||
        u8 ca_crl_version;
 | 
			
		||||
        u8 signer_crl_version;
 | 
			
		||||
        u8 reserved;
 | 
			
		||||
        u64_be system_version;
 | 
			
		||||
        u64_be title_id;
 | 
			
		||||
        u32_be title_type;
 | 
			
		||||
        u16_be group_id;
 | 
			
		||||
        u32_be savedata_size;
 | 
			
		||||
        u32_be srl_private_savedata_size;
 | 
			
		||||
        std::array<u8, 4> reserved_2;
 | 
			
		||||
        u8 srl_flag;
 | 
			
		||||
        std::array<u8, 0x31> reserved_3;
 | 
			
		||||
        u32_be access_rights;
 | 
			
		||||
        u16_be title_version;
 | 
			
		||||
        u16_be content_count;
 | 
			
		||||
        u16_be boot_content;
 | 
			
		||||
        std::array<u8, 2> reserved_4;
 | 
			
		||||
        std::array<u8, 0x20> contentinfo_hash;
 | 
			
		||||
        std::array<ContentInfo, 64> contentinfo;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    static_assert(sizeof(Body) == 0x9C4, "TMD body structure size is wrong");
 | 
			
		||||
 | 
			
		||||
#pragma pack(pop)
 | 
			
		||||
 | 
			
		||||
    explicit TitleMetadata(std::string& path) : filepath(std::move(path)) {}
 | 
			
		||||
    Loader::ResultStatus Load();
 | 
			
		||||
    Loader::ResultStatus Save();
 | 
			
		||||
 | 
			
		||||
    u64 GetTitleID() const;
 | 
			
		||||
    u32 GetTitleType() const;
 | 
			
		||||
    u16 GetTitleVersion() const;
 | 
			
		||||
    u64 GetSystemVersion() const;
 | 
			
		||||
    size_t GetContentCount() const;
 | 
			
		||||
    u32 GetBootContentID() const;
 | 
			
		||||
    u32 GetManualContentID() const;
 | 
			
		||||
    u32 GetDLPContentID() const;
 | 
			
		||||
 | 
			
		||||
    void SetTitleID(u64 title_id);
 | 
			
		||||
    void SetTitleType(u32 type);
 | 
			
		||||
    void SetTitleVersion(u16 version);
 | 
			
		||||
    void SetSystemVersion(u64 version);
 | 
			
		||||
    void AddContentChunk(const ContentChunk& chunk);
 | 
			
		||||
 | 
			
		||||
    void Print() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    enum TMDContentIndex { Main = 0, Manual = 1, DLP = 2 };
 | 
			
		||||
 | 
			
		||||
    Body tmd_body;
 | 
			
		||||
    u32_be signature_type;
 | 
			
		||||
    std::vector<u8> tmd_signature;
 | 
			
		||||
    std::vector<ContentChunk> tmd_chunks;
 | 
			
		||||
 | 
			
		||||
    std::string filepath;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user