diff options
58 files changed, 1709 insertions, 151 deletions
diff --git a/.travis.yml b/.travis.yml index 4d363cbc9..b0fbe3c5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ matrix: - os: osx env: NAME="macos build" sudo: false - osx_image: xcode9.3 + osx_image: xcode10 install: "./.travis/macos/deps.sh" script: "./.travis/macos/build.sh" after_success: "./.travis/macos/upload.sh" diff --git a/CMakeLists.txt b/CMakeLists.txt index 25c5cb112..cd990188e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,8 +123,6 @@ else() # Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors. add_definitions(/DWIN32_LEAN_AND_MEAN) - # set up output paths for executable binaries (.exe-files, and .dll-files on DLL-capable platforms) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE) # Tweak optimization settings diff --git a/src/common/common_paths.h b/src/common/common_paths.h index df2ce80b1..4f88de768 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -33,6 +33,8 @@ #define NAND_DIR "nand" #define SYSDATA_DIR "sysdata" #define KEYS_DIR "keys" +#define LOAD_DIR "load" +#define DUMP_DIR "dump" #define LOG_DIR "log" // Filenames diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 21a0b9738..548463787 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -705,6 +705,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { #endif paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); + paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP); + paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP); paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP); // TODO: Put the logs in a better location for each OS diff --git a/src/common/file_util.h b/src/common/file_util.h index 24c1e413c..3d8fe6264 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -29,6 +29,8 @@ enum class UserPath { NANDDir, RootDir, SDMCDir, + LoadDir, + DumpDir, SysDataDir, UserDir, }; diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index efd776db6..9f5918851 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -183,6 +183,7 @@ void FileBackend::Write(const Entry& entry) { SUB(Service, FS) \ SUB(Service, GRC) \ SUB(Service, HID) \ + SUB(Service, IRS) \ SUB(Service, LBL) \ SUB(Service, LDN) \ SUB(Service, LDR) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 4d577524f..abbd056ee 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -70,6 +70,7 @@ enum class Class : ClassType { Service_FS, ///< The FS (Filesystem) service Service_GRC, ///< The game recording service Service_HID, ///< The HID (Human interface device) service + Service_IRS, ///< The IRS service Service_LBL, ///< The LBL (LCD backlight) service Service_LDN, ///< The LDN (Local domain network) service Service_LDR, ///< The loader service diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 26f727d96..23fd6e920 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -32,6 +32,8 @@ add_library(core STATIC file_sys/control_metadata.h file_sys/directory.h file_sys/errors.h + file_sys/fsmitm_romfsbuild.cpp + file_sys/fsmitm_romfsbuild.h file_sys/mode.h file_sys/nca_metadata.cpp file_sys/nca_metadata.h @@ -59,10 +61,13 @@ add_library(core STATIC file_sys/vfs.h file_sys/vfs_concat.cpp file_sys/vfs_concat.h + file_sys/vfs_layered.cpp + file_sys/vfs_layered.h file_sys/vfs_offset.cpp file_sys/vfs_offset.h file_sys/vfs_real.cpp file_sys/vfs_real.h + file_sys/vfs_static.h file_sys/vfs_vector.cpp file_sys/vfs_vector.h file_sys/xts_archive.cpp diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 7be5a38de..8760d17a8 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -174,7 +174,7 @@ ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, std::size_t core_index) : cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), core_index{core_index}, exclusive_monitor{std::dynamic_pointer_cast<DynarmicExclusiveMonitor>(exclusive_monitor)} { - ThreadContext ctx; + ThreadContext ctx{}; inner_unicorn.SaveContext(ctx); PageTableChanged(); LoadContext(ctx); diff --git a/src/core/core.cpp b/src/core/core.cpp index 50f0a42fb..7666354dc 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -64,7 +64,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, if (concat.empty()) return nullptr; - return FileSys::ConcatenateFiles(concat, dir->GetName()); + return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName()); } return vfs->OpenFile(path, FileSys::Mode::Read); diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp index 21568ad50..265f8ed9c 100644 --- a/src/core/core_cpu.cpp +++ b/src/core/core_cpu.cpp @@ -55,16 +55,16 @@ Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, if (Settings::values.use_cpu_jit) { #ifdef ARCHITECTURE_x86_64 - arm_interface = std::make_shared<ARM_Dynarmic>(exclusive_monitor, core_index); + arm_interface = std::make_unique<ARM_Dynarmic>(exclusive_monitor, core_index); #else - arm_interface = std::make_shared<ARM_Unicorn>(); + arm_interface = std::make_unique<ARM_Unicorn>(); LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); #endif } else { - arm_interface = std::make_shared<ARM_Unicorn>(); + arm_interface = std::make_unique<ARM_Unicorn>(); } - scheduler = std::make_shared<Kernel::Scheduler>(arm_interface.get()); + scheduler = std::make_shared<Kernel::Scheduler>(*arm_interface); } Cpu::~Cpu() = default; diff --git a/src/core/core_cpu.h b/src/core/core_cpu.h index 685532965..ee7e04abc 100644 --- a/src/core/core_cpu.h +++ b/src/core/core_cpu.h @@ -76,7 +76,7 @@ public: private: void Reschedule(); - std::shared_ptr<ARM_Interface> arm_interface; + std::unique_ptr<ARM_Interface> arm_interface; std::shared_ptr<CpuBarrier> cpu_barrier; std::shared_ptr<Kernel::Scheduler> scheduler; diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index 205492897..6102ef476 100644 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp @@ -2,13 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <fmt/format.h> #include "core/file_sys/bis_factory.h" #include "core/file_sys/registered_cache.h" namespace FileSys { -BISFactory::BISFactory(VirtualDir nand_root_) - : nand_root(std::move(nand_root_)), +BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_) + : nand_root(std::move(nand_root_)), load_root(std::move(load_root_)), sysnand_cache(std::make_shared<RegisteredCache>( GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))), usrnand_cache(std::make_shared<RegisteredCache>( @@ -24,4 +25,11 @@ std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const { return usrnand_cache; } +VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const { + // LayeredFS doesn't work on updates and title id-less homebrew + if (title_id == 0 || (title_id & 0x800) > 0) + return nullptr; + return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id)); +} + } // namespace FileSys diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h index 9523dd864..c352e0925 100644 --- a/src/core/file_sys/bis_factory.h +++ b/src/core/file_sys/bis_factory.h @@ -17,14 +17,17 @@ class RegisteredCache; /// registered caches. class BISFactory { public: - explicit BISFactory(VirtualDir nand_root); + explicit BISFactory(VirtualDir nand_root, VirtualDir load_root); ~BISFactory(); std::shared_ptr<RegisteredCache> GetSystemNANDContents() const; std::shared_ptr<RegisteredCache> GetUserNANDContents() const; + VirtualDir GetModificationLoadRoot(u64 title_id) const; + private: VirtualDir nand_root; + VirtualDir load_root; std::shared_ptr<RegisteredCache> sysnand_cache; std::shared_ptr<RegisteredCache> usrnand_cache; diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp new file mode 100644 index 000000000..2a913ce82 --- /dev/null +++ b/src/core/file_sys/fsmitm_romfsbuild.cpp @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Adapted by DarkLordZach for use/interaction with yuzu + * + * Modifications Copyright 2018 yuzu emulator team + * Licensed under GPLv2 or any later version + * Refer to the license.txt file included. + */ + +#include <cstring> +#include "common/alignment.h" +#include "common/assert.h" +#include "core/file_sys/fsmitm_romfsbuild.h" +#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_vector.h" + +namespace FileSys { + +constexpr u64 FS_MAX_PATH = 0x301; + +constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF; +constexpr u32 ROMFS_FILEPARTITION_OFS = 0x200; + +// Types for building a RomFS. +struct RomFSHeader { + u64 header_size; + u64 dir_hash_table_ofs; + u64 dir_hash_table_size; + u64 dir_table_ofs; + u64 dir_table_size; + u64 file_hash_table_ofs; + u64 file_hash_table_size; + u64 file_table_ofs; + u64 file_table_size; + u64 file_partition_ofs; +}; +static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size."); + +struct RomFSDirectoryEntry { + u32 parent; + u32 sibling; + u32 child; + u32 file; + u32 hash; + u32 name_size; +}; +static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "RomFSDirectoryEntry has incorrect size."); + +struct RomFSFileEntry { + u32 parent; + u32 sibling; + u64 offset; + u64 size; + u32 hash; + u32 name_size; +}; +static_assert(sizeof(RomFSFileEntry) == 0x20, "RomFSFileEntry has incorrect size."); + +struct RomFSBuildFileContext; + +struct RomFSBuildDirectoryContext { + std::string path; + u32 cur_path_ofs = 0; + u32 path_len = 0; + u32 entry_offset = 0; + std::shared_ptr<RomFSBuildDirectoryContext> parent; + std::shared_ptr<RomFSBuildDirectoryContext> child; + std::shared_ptr<RomFSBuildDirectoryContext> sibling; + std::shared_ptr<RomFSBuildFileContext> file; +}; + +struct RomFSBuildFileContext { + std::string path; + u32 cur_path_ofs = 0; + u32 path_len = 0; + u32 entry_offset = 0; + u64 offset = 0; + u64 size = 0; + std::shared_ptr<RomFSBuildDirectoryContext> parent; + std::shared_ptr<RomFSBuildFileContext> sibling; + VirtualFile source; +}; + +static u32 romfs_calc_path_hash(u32 parent, std::string path, u32 start, std::size_t path_len) { + u32 hash = parent ^ 123456789; + for (u32 i = 0; i < path_len; i++) { + hash = (hash >> 5) | (hash << 27); + hash ^= path[start + i]; + } + + return hash; +} + +static u64 romfs_get_hash_table_count(u64 num_entries) { + if (num_entries < 3) { + return 3; + } + + if (num_entries < 19) { + return num_entries | 1; + } + + u64 count = num_entries; + while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 || + count % 11 == 0 || count % 13 == 0 || count % 17 == 0) { + count++; + } + return count; +} + +void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, + std::shared_ptr<RomFSBuildDirectoryContext> parent) { + std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs; + + VirtualDir dir; + + if (parent->path_len == 0) + dir = root_romfs; + else + dir = root_romfs->GetDirectoryRelative(parent->path); + + const auto entries = dir->GetEntries(); + + for (const auto& kv : entries) { + if (kv.second == VfsEntryType::Directory) { + const auto child = std::make_shared<RomFSBuildDirectoryContext>(); + // Set child's path. + child->cur_path_ofs = parent->path_len + 1; + child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size()); + child->path = parent->path + "/" + kv.first; + + // Sanity check on path_len + ASSERT(child->path_len < FS_MAX_PATH); + + if (AddDirectory(parent, child)) { + child_dirs.push_back(child); + } + } else { + const auto child = std::make_shared<RomFSBuildFileContext>(); + // Set child's path. + child->cur_path_ofs = parent->path_len + 1; + child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size()); + child->path = parent->path + "/" + kv.first; + + // Sanity check on path_len + ASSERT(child->path_len < FS_MAX_PATH); + + child->source = root_romfs->GetFileRelative(child->path); + + child->size = child->source->GetSize(); + + AddFile(parent, child); + } + } + + for (auto& child : child_dirs) { + this->VisitDirectory(root_romfs, child); + } +} + +bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx, + std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx) { + // Check whether it's already in the known directories. + const auto existing = directories.find(dir_ctx->path); + if (existing != directories.end()) + return false; + + // Add a new directory. + num_dirs++; + dir_table_size += + sizeof(RomFSDirectoryEntry) + Common::AlignUp(dir_ctx->path_len - dir_ctx->cur_path_ofs, 4); + dir_ctx->parent = parent_dir_ctx; + directories.emplace(dir_ctx->path, dir_ctx); + + return true; +} + +bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx, + std::shared_ptr<RomFSBuildFileContext> file_ctx) { + // Check whether it's already in the known files. + const auto existing = files.find(file_ctx->path); + if (existing != files.end()) { + return false; + } + + // Add a new file. + num_files++; + file_table_size += + sizeof(RomFSFileEntry) + Common::AlignUp(file_ctx->path_len - file_ctx->cur_path_ofs, 4); + file_ctx->parent = parent_dir_ctx; + files.emplace(file_ctx->path, file_ctx); + + return true; +} + +RomFSBuildContext::RomFSBuildContext(VirtualDir base_) : base(std::move(base_)) { + root = std::make_shared<RomFSBuildDirectoryContext>(); + root->path = "\0"; + directories.emplace(root->path, root); + num_dirs = 1; + dir_table_size = 0x18; + + VisitDirectory(base, root); +} + +RomFSBuildContext::~RomFSBuildContext() = default; + +std::map<u64, VirtualFile> RomFSBuildContext::Build() { + const u64 dir_hash_table_entry_count = romfs_get_hash_table_count(num_dirs); + const u64 file_hash_table_entry_count = romfs_get_hash_table_count(num_files); + dir_hash_table_size = 4 * dir_hash_table_entry_count; + file_hash_table_size = 4 * file_hash_table_entry_count; + + // Assign metadata pointers + RomFSHeader header{}; + + std::vector<u32> dir_hash_table(dir_hash_table_entry_count, ROMFS_ENTRY_EMPTY); + std::vector<u32> file_hash_table(file_hash_table_entry_count, ROMFS_ENTRY_EMPTY); + + std::vector<u8> dir_table(dir_table_size); + std::vector<u8> file_table(file_table_size); + + std::shared_ptr<RomFSBuildFileContext> cur_file; + + // Determine file offsets. + u32 entry_offset = 0; + std::shared_ptr<RomFSBuildFileContext> prev_file = nullptr; + for (const auto& it : files) { + cur_file = it.second; + file_partition_size = Common::AlignUp(file_partition_size, 16); + cur_file->offset = file_partition_size; + file_partition_size += cur_file->size; + cur_file->entry_offset = entry_offset; + entry_offset += sizeof(RomFSFileEntry) + + Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4); + prev_file = cur_file; + } + // Assign deferred parent/sibling ownership. + for (auto it = files.rbegin(); it != files.rend(); ++it) { + cur_file = it->second; + cur_file->sibling = cur_file->parent->file; + cur_file->parent->file = cur_file; + } + + std::shared_ptr<RomFSBuildDirectoryContext> cur_dir; + + // Determine directory offsets. + entry_offset = 0; + for (const auto& it : directories) { + cur_dir = it.second; + cur_dir->entry_offset = entry_offset; + entry_offset += sizeof(RomFSDirectoryEntry) + + Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4); + } + // Assign deferred parent/sibling ownership. + for (auto it = directories.rbegin(); it->second != root; ++it) { + cur_dir = it->second; + cur_dir->sibling = cur_dir->parent->child; + cur_dir->parent->child = cur_dir; + } + + std::map<u64, VirtualFile> out; + + // Populate file tables. + for (const auto& it : files) { + cur_file = it.second; + RomFSFileEntry cur_entry{}; + + cur_entry.parent = cur_file->parent->entry_offset; + cur_entry.sibling = + cur_file->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_file->sibling->entry_offset; + cur_entry.offset = cur_file->offset; + cur_entry.size = cur_file->size; + + const auto name_size = cur_file->path_len - cur_file->cur_path_ofs; + const auto hash = romfs_calc_path_hash(cur_file->parent->entry_offset, cur_file->path, + cur_file->cur_path_ofs, name_size); + cur_entry.hash = file_hash_table[hash % file_hash_table_entry_count]; + file_hash_table[hash % file_hash_table_entry_count] = cur_file->entry_offset; + + cur_entry.name_size = name_size; + + out.emplace(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->source); + std::memcpy(file_table.data() + cur_file->entry_offset, &cur_entry, sizeof(RomFSFileEntry)); + std::memset(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), 0, + Common::AlignUp(cur_entry.name_size, 4)); + std::memcpy(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), + cur_file->path.data() + cur_file->cur_path_ofs, name_size); + } + + // Populate dir tables. + for (const auto& it : directories) { + cur_dir = it.second; + RomFSDirectoryEntry cur_entry{}; + + cur_entry.parent = cur_dir == root ? 0 : cur_dir->parent->entry_offset; + cur_entry.sibling = + cur_dir->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->sibling->entry_offset; + cur_entry.child = + cur_dir->child == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->child->entry_offset; + cur_entry.file = cur_dir->file == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->file->entry_offset; + + const auto name_size = cur_dir->path_len - cur_dir->cur_path_ofs; + const auto hash = romfs_calc_path_hash(cur_dir == root ? 0 : cur_dir->parent->entry_offset, + cur_dir->path, cur_dir->cur_path_ofs, name_size); + cur_entry.hash = dir_hash_table[hash % dir_hash_table_entry_count]; + dir_hash_table[hash % dir_hash_table_entry_count] = cur_dir->entry_offset; + + cur_entry.name_size = name_size; + + std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry, + sizeof(RomFSDirectoryEntry)); + std::memset(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry), 0, + Common::AlignUp(cur_entry.name_size, 4)); + std::memcpy(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry), + cur_dir->path.data() + cur_dir->cur_path_ofs, name_size); + } + + // Set header fields. + header.header_size = sizeof(RomFSHeader); + header.file_hash_table_size = file_hash_table_size; + header.file_table_size = file_table_size; + header.dir_hash_table_size = dir_hash_table_size; + header.dir_table_size = dir_table_size; + header.file_partition_ofs = ROMFS_FILEPARTITION_OFS; + header.dir_hash_table_ofs = Common::AlignUp(header.file_partition_ofs + file_partition_size, 4); + header.dir_table_ofs = header.dir_hash_table_ofs + header.dir_hash_table_size; + header.file_hash_table_ofs = header.dir_table_ofs + header.dir_table_size; + header.file_table_ofs = header.file_hash_table_ofs + header.file_hash_table_size; + + std::vector<u8> header_data(sizeof(RomFSHeader)); + std::memcpy(header_data.data(), &header, header_data.size()); + out.emplace(0, std::make_shared<VectorVfsFile>(std::move(header_data))); + + std::vector<u8> metadata(file_hash_table_size + file_table_size + dir_hash_table_size + + dir_table_size); + std::size_t index = 0; + std::memcpy(metadata.data(), dir_hash_table.data(), dir_hash_table.size() * sizeof(u32)); + index += dir_hash_table.size() * sizeof(u32); + std::memcpy(metadata.data() + index, dir_table.data(), dir_table.size()); + index += dir_table.size(); + std::memcpy(metadata.data() + index, file_hash_table.data(), + file_hash_table.size() * sizeof(u32)); + index += file_hash_table.size() * sizeof(u32); + std::memcpy(metadata.data() + index, file_table.data(), file_table.size()); + out.emplace(header.dir_hash_table_ofs, std::make_shared<VectorVfsFile>(std::move(metadata))); + + return out; +} + +} // namespace FileSys diff --git a/src/core/file_sys/fsmitm_romfsbuild.h b/src/core/file_sys/fsmitm_romfsbuild.h new file mode 100644 index 000000000..b0c3c123b --- /dev/null +++ b/src/core/file_sys/fsmitm_romfsbuild.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Adapted by DarkLordZach for use/interaction with yuzu + * + * Modifications Copyright 2018 yuzu emulator team + * Licensed under GPLv2 or any later version + * Refer to the license.txt file included. + */ + +#pragma once + +#include <map> +#include <memory> +#include <string> +#include <boost/detail/container_fwd.hpp> +#include "common/common_types.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { + +struct RomFSBuildDirectoryContext; +struct RomFSBuildFileContext; +struct RomFSDirectoryEntry; +struct RomFSFileEntry; + +class RomFSBuildContext { +public: + explicit RomFSBuildContext(VirtualDir base); + ~RomFSBuildContext(); + + // This finalizes the context. + std::map<u64, VirtualFile> Build(); + +private: + VirtualDir base; + std::shared_ptr<RomFSBuildDirectoryContext> root; + std::map<std::string, std::shared_ptr<RomFSBuildDirectoryContext>, std::less<>> directories; + std::map<std::string, std::shared_ptr<RomFSBuildFileContext>, std::less<>> files; + u64 num_dirs = 0; + u64 num_files = 0; + u64 dir_table_size = 0; + u64 file_table_size = 0; + u64 dir_hash_table_size = 0; + u64 file_hash_table_size = 0; + u64 file_partition_size = 0; + + void VisitDirectory(VirtualDir filesys, std::shared_ptr<RomFSBuildDirectoryContext> parent); + + bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx, + std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx); + bool AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx, + std::shared_ptr<RomFSBuildFileContext> file_ctx); +}; + +} // namespace FileSys diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index aebc69d52..4b3b5e665 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -11,6 +11,7 @@ #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs.h" +#include "core/file_sys/vfs_layered.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" @@ -31,8 +32,9 @@ std::string FormatTitleVersion(u32 version, TitleVersionFormat format) { return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]); } -constexpr std::array<const char*, 1> PATCH_TYPE_NAMES{ +constexpr std::array<const char*, 2> PATCH_TYPE_NAMES{ "Update", + "LayeredFS", }; std::string FormatPatchTypeName(PatchType type) { @@ -66,6 +68,44 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { return exefs; } +static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { + const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) { + return; + } + + auto extracted = ExtractRomFS(romfs); + if (extracted == nullptr) { + return; + } + + auto patch_dirs = load_dir->GetSubdirectories(); + std::sort(patch_dirs.begin(), patch_dirs.end(), + [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); + + std::vector<VirtualDir> layers; + layers.reserve(patch_dirs.size() + 1); + for (const auto& subdir : patch_dirs) { + auto romfs_dir = subdir->GetSubdirectory("romfs"); + if (romfs_dir != nullptr) + layers.push_back(std::move(romfs_dir)); + } + layers.push_back(std::move(extracted)); + + auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); + if (layered == nullptr) { + return; + } + + auto packed = CreateRomFS(std::move(layered)); + if (packed == nullptr) { + return; + } + + LOG_INFO(Loader, " RomFS: LayeredFS patches applied successfully"); + romfs = std::move(packed); +} + VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type) const { LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id, @@ -89,6 +129,9 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, } } + // LayeredFS + ApplyLayeredFS(romfs, title_id, type); + return romfs; } @@ -114,6 +157,10 @@ std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const { } } + const auto lfs_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + if (lfs_dir != nullptr && lfs_dir->GetSize() > 0) + out.insert_or_assign(PatchType::LayeredFS, ""); + return out; } diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 209cab1dc..464f17515 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -26,6 +26,7 @@ std::string FormatTitleVersion(u32 version, enum class PatchType { Update, + LayeredFS, }; std::string FormatPatchTypeName(PatchType type); @@ -42,6 +43,7 @@ public: // Currently tracked RomFS patches: // - Game Updates + // - LayeredFS VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, ContentRecordType type = ContentRecordType::Program) const; diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index dad7ae10b..e9b040689 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -18,6 +18,10 @@ #include "core/loader/loader.h" namespace FileSys { + +// The size of blocks to use when vfs raw copying into nand. +constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000; + std::string RegisteredCacheEntry::DebugInfo() const { return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type)); } @@ -121,7 +125,7 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, if (concat.empty()) return nullptr; - file = FileSys::ConcatenateFiles(concat); + file = ConcatenatedVfsFile::MakeConcatenatedFile(concat, concat.front()->GetName()); } return file; @@ -480,7 +484,8 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs auto out = dir->CreateFileRelative(path); if (out == nullptr) return InstallResult::ErrorCopyFailed; - return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed; + return copy(in, out, VFS_RC_LARGE_COPY_BLOCK) ? InstallResult::Success + : InstallResult::ErrorCopyFailed; } bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index f487b0cf0..c0cd59fc5 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -27,7 +27,7 @@ struct ContentRecord; using NcaID = std::array<u8, 0x10>; using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; -using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>; +using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>; enum class InstallResult { Success, diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp index 9f6e41cdf..5910f7046 100644 --- a/src/core/file_sys/romfs.cpp +++ b/src/core/file_sys/romfs.cpp @@ -4,8 +4,10 @@ #include "common/common_types.h" #include "common/swap.h" +#include "core/file_sys/fsmitm_romfsbuild.h" #include "core/file_sys/romfs.h" #include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_concat.h" #include "core/file_sys/vfs_offset.h" #include "core/file_sys/vfs_vector.h" @@ -98,7 +100,7 @@ void ProcessDirectory(VirtualFile file, std::size_t dir_offset, std::size_t file } } -VirtualDir ExtractRomFS(VirtualFile file) { +VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) { RomFSHeader header{}; if (file->ReadObject(&header) != sizeof(RomFSHeader)) return nullptr; @@ -117,9 +119,22 @@ VirtualDir ExtractRomFS(VirtualFile file) { VirtualDir out = std::move(root); - while (out->GetSubdirectory("") != nullptr) - out = out->GetSubdirectory(""); + while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) { + if (out->GetSubdirectories().front()->GetName() == "data" && + type == RomFSExtractionType::Truncated) + break; + out = out->GetSubdirectories().front(); + } return out; } + +VirtualFile CreateRomFS(VirtualDir dir) { + if (dir == nullptr) + return nullptr; + + RomFSBuildContext ctx{dir}; + return ConcatenatedVfsFile::MakeConcatenatedFile(0, ctx.Build(), dir->GetName()); +} + } // namespace FileSys diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h index e54a7d7a9..ecd1eb725 100644 --- a/src/core/file_sys/romfs.h +++ b/src/core/file_sys/romfs.h @@ -5,6 +5,7 @@ #pragma once #include <array> +#include <map> #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" @@ -12,6 +13,8 @@ namespace FileSys { +struct RomFSHeader; + struct IVFCLevel { u64_le offset; u64_le size; @@ -29,8 +32,18 @@ struct IVFCHeader { }; static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); +enum class RomFSExtractionType { + Full, // Includes data directory + Truncated, // Traverses into data directory +}; + // Converts a RomFS binary blob to VFS Filesystem // Returns nullptr on failure -VirtualDir ExtractRomFS(VirtualFile file); +VirtualDir ExtractRomFS(VirtualFile file, + RomFSExtractionType type = RomFSExtractionType::Truncated); + +// Converts a VFS filesystem into a RomFS binary +// Returns nullptr on failure +VirtualFile CreateRomFS(VirtualDir dir); } // namespace FileSys diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index d7b52abfd..bfe50da73 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -399,6 +399,15 @@ bool VfsDirectory::Copy(std::string_view src, std::string_view dest) { return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize(); } +std::map<std::string, VfsEntryType, std::less<>> VfsDirectory::GetEntries() const { + std::map<std::string, VfsEntryType, std::less<>> out; + for (const auto& dir : GetSubdirectories()) + out.emplace(dir->GetName(), VfsEntryType::Directory); + for (const auto& file : GetFiles()) + out.emplace(file->GetName(), VfsEntryType::File); + return out; +} + std::string VfsDirectory::GetFullPath() const { if (IsRoot()) return GetName(); @@ -454,13 +463,41 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t return true; } -bool VfsRawCopy(VirtualFile src, VirtualFile dest) { - if (src == nullptr || dest == nullptr) +bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size) { + if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) return false; if (!dest->Resize(src->GetSize())) return false; - std::vector<u8> data = src->ReadAllBytes(); - return dest->WriteBytes(data, 0) == data.size(); + + std::vector<u8> temp(std::min(block_size, src->GetSize())); + for (std::size_t i = 0; i < src->GetSize(); i += block_size) { + const auto read = std::min(block_size, src->GetSize() - i); + const auto block = src->Read(temp.data(), read, i); + + if (dest->Write(temp.data(), read, i) != read) + return false; + } + + return true; +} + +bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size) { + if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) + return false; + + for (const auto& file : src->GetFiles()) { + const auto out = dest->CreateFile(file->GetName()); + if (!VfsRawCopy(file, out, block_size)) + return false; + } + + for (const auto& dir : src->GetSubdirectories()) { + const auto out = dest->CreateSubdirectory(dir->GetName()); + if (!VfsRawCopyD(dir, out, block_size)) + return false; + } + + return true; } VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) { diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index 74489b452..270291631 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -4,6 +4,7 @@ #pragma once +#include <map> #include <memory> #include <string> #include <string_view> @@ -265,6 +266,10 @@ public: // dest. virtual bool Copy(std::string_view src, std::string_view dest); + // Gets all of the entries directly in the directory (files and dirs), returning a map between + // item name -> type. + virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const; + // 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 @@ -310,13 +315,19 @@ public: bool Rename(std::string_view name) override; }; -// Compare the two files, byte-for-byte, in increments specificed by block_size -bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size = 0x200); +// Compare the two files, byte-for-byte, in increments specified by block_size +bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, + std::size_t block_size = 0x1000); // A method that copies the raw data between two different implementations of VirtualFile. If you // are using the same implementation, it is probably better to use the Copy method in the parent // directory of src/dest. -bool VfsRawCopy(VirtualFile src, VirtualFile dest); +bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size = 0x1000); + +// A method that performs a similar function to VfsRawCopy above, but instead copies entire +// directories. It suffers the same performance penalties as above and an implementation-specific +// Copy should always be preferred. +bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size = 0x1000); // Checks if the directory at path relative to rel exists. If it does, returns that. If it does not // it attempts to create it and returns the new dir or nullptr on failure. diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp index dc7a279a9..16d801c0c 100644 --- a/src/core/file_sys/vfs_concat.cpp +++ b/src/core/file_sys/vfs_concat.cpp @@ -5,17 +5,22 @@ #include <algorithm> #include <utility> +#include "common/assert.h" #include "core/file_sys/vfs_concat.h" +#include "core/file_sys/vfs_static.h" namespace FileSys { -VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) { - if (files.empty()) - return nullptr; - if (files.size() == 1) - return files[0]; +static bool VerifyConcatenationMapContinuity(const std::map<u64, VirtualFile>& map) { + const auto last_valid = --map.end(); + for (auto iter = map.begin(); iter != last_valid;) { + const auto old = iter++; + if (old->first + old->second->GetSize() != iter->first) { + return false; + } + } - return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name))); + return map.begin()->first == 0; } ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name) @@ -27,8 +32,48 @@ ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::s } } +ConcatenatedVfsFile::ConcatenatedVfsFile(std::map<u64, VirtualFile> files_, std::string name) + : files(std::move(files_)), name(std::move(name)) { + ASSERT(VerifyConcatenationMapContinuity(files)); +} + ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; +VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::vector<VirtualFile> files, + std::string name) { + if (files.empty()) + return nullptr; + if (files.size() == 1) + return files[0]; + + return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name))); +} + +VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte, + std::map<u64, VirtualFile> files, + std::string name) { + if (files.empty()) + return nullptr; + if (files.size() == 1) + return files.begin()->second; + + const auto last_valid = --files.end(); + for (auto iter = files.begin(); iter != last_valid;) { + const auto old = iter++; + if (old->first + old->second->GetSize() != iter->first) { + files.emplace(old->first + old->second->GetSize(), + std::make_shared<StaticVfsFile>(filler_byte, iter->first - old->first - + old->second->GetSize())); + } + } + + // Ensure the map starts at offset 0 (start of file), otherwise pad to fill. + if (files.begin()->first != 0) + files.emplace(0, std::make_shared<StaticVfsFile>(filler_byte, files.begin()->first)); + + return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name))); +} + std::string ConcatenatedVfsFile::GetName() const { if (files.empty()) return ""; @@ -62,7 +107,7 @@ bool ConcatenatedVfsFile::IsReadable() const { } std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { - auto entry = files.end(); + auto entry = --files.end(); for (auto iter = files.begin(); iter != files.end(); ++iter) { if (iter->first > offset) { entry = --iter; @@ -70,20 +115,17 @@ std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t } } - // Check if the entry should be the last one. The loop above will make it end(). - if (entry == files.end() && offset < files.rbegin()->first + files.rbegin()->second->GetSize()) - --entry; - - if (entry == files.end()) + if (entry->first + entry->second->GetSize() <= offset) return 0; - const auto remaining = entry->second->GetSize() + offset - entry->first; - if (length > remaining) { - return entry->second->Read(data, remaining, offset - entry->first) + - Read(data + remaining, length - remaining, offset + remaining); + const auto read_in = + std::min<u64>(entry->first + entry->second->GetSize() - offset, entry->second->GetSize()); + if (length > read_in) { + return entry->second->Read(data, read_in, offset - entry->first) + + Read(data + read_in, length - read_in, offset + read_in); } - return entry->second->Read(data, length, offset - entry->first); + return entry->second->Read(data, std::min<u64>(read_in, length), offset - entry->first); } std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { @@ -93,4 +135,5 @@ std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std:: bool ConcatenatedVfsFile::Rename(std::string_view name) { return false; } + } // namespace FileSys diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h index 717d04bdc..c90f9d5d1 100644 --- a/src/core/file_sys/vfs_concat.h +++ b/src/core/file_sys/vfs_concat.h @@ -4,26 +4,30 @@ #pragma once +#include <map> #include <memory> #include <string_view> -#include <boost/container/flat_map.hpp> #include "core/file_sys/vfs.h" namespace FileSys { -// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases. -VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name = ""); - // Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently // read-only. class ConcatenatedVfsFile : public VfsFile { - friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name); - ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name); + ConcatenatedVfsFile(std::map<u64, VirtualFile> files, std::string name); public: ~ConcatenatedVfsFile() override; + /// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases. + static VirtualFile MakeConcatenatedFile(std::vector<VirtualFile> files, std::string name); + + /// Convenience function that turns a map of offsets to files into a concatenated file, filling + /// gaps with a given filler byte. + static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::map<u64, VirtualFile> files, + std::string name); + std::string GetName() const override; std::size_t GetSize() const override; bool Resize(std::size_t new_size) override; @@ -36,7 +40,7 @@ public: private: // Maps starting offset to file -- more efficient. - boost::container::flat_map<u64, VirtualFile> files; + std::map<u64, VirtualFile> files; std::string name; }; diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp new file mode 100644 index 000000000..bfee01725 --- /dev/null +++ b/src/core/file_sys/vfs_layered.cpp @@ -0,0 +1,132 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <utility> +#include "core/file_sys/vfs_layered.h" + +namespace FileSys { + +LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name) + : dirs(std::move(dirs)), name(std::move(name)) {} + +LayeredVfsDirectory::~LayeredVfsDirectory() = default; + +VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dirs, + std::string name) { + if (dirs.empty()) + return nullptr; + if (dirs.size() == 1) + return dirs[0]; + + return std::shared_ptr<VfsDirectory>(new LayeredVfsDirectory(std::move(dirs), std::move(name))); +} + +std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view path) const { + for (const auto& layer : dirs) { + const auto file = layer->GetFileRelative(path); + if (file != nullptr) + return file; + } + + return nullptr; +} + +std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetDirectoryRelative( + std::string_view path) const { + std::vector<VirtualDir> out; + for (const auto& layer : dirs) { + auto dir = layer->GetDirectoryRelative(path); + if (dir != nullptr) + out.push_back(std::move(dir)); + } + + return MakeLayeredDirectory(std::move(out)); +} + +std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFile(std::string_view name) const { + return GetFileRelative(name); +} + +std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const { + return GetDirectoryRelative(name); +} + +std::string LayeredVfsDirectory::GetFullPath() const { + return dirs[0]->GetFullPath(); +} + +std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const { + std::vector<VirtualFile> out; + for (const auto& layer : dirs) { + for (const auto& file : layer->GetFiles()) { + if (std::find_if(out.begin(), out.end(), [&file](const VirtualFile& comp) { + return comp->GetName() == file->GetName(); + }) == out.end()) { + out.push_back(file); + } + } + } + + return out; +} + +std::vector<std::shared_ptr<VfsDirectory>> LayeredVfsDirectory::GetSubdirectories() const { + std::vector<std::string> names; + for (const auto& layer : dirs) { + for (const auto& sd : layer->GetSubdirectories()) { + if (std::find(names.begin(), names.end(), sd->GetName()) == names.end()) + names.push_back(sd->GetName()); + } + } + + std::vector<VirtualDir> out; + out.reserve(names.size()); + for (const auto& subdir : names) + out.push_back(GetSubdirectory(subdir)); + + return out; +} + +bool LayeredVfsDirectory::IsWritable() const { + return false; +} + +bool LayeredVfsDirectory::IsReadable() const { + return true; +} + +std::string LayeredVfsDirectory::GetName() const { + return name.empty() ? dirs[0]->GetName() : name; +} + +std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const { + return dirs[0]->GetParentDirectory(); +} + +std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) { + return nullptr; +} + +std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) { + return nullptr; +} + +bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view name) { + return false; +} + +bool LayeredVfsDirectory::DeleteFile(std::string_view name) { + return false; +} + +bool LayeredVfsDirectory::Rename(std::string_view name_) { + name = name_; + return true; +} + +bool LayeredVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { + return false; +} +} // namespace FileSys diff --git a/src/core/file_sys/vfs_layered.h b/src/core/file_sys/vfs_layered.h new file mode 100644 index 000000000..d85310f57 --- /dev/null +++ b/src/core/file_sys/vfs_layered.h @@ -0,0 +1,50 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/file_sys/vfs.h" + +namespace FileSys { + +// Class that stacks multiple VfsDirectories on top of each other, attempting to read from the first +// one and falling back to the one after. The highest priority directory (overwrites all others) +// should be element 0 in the dirs vector. +class LayeredVfsDirectory : public VfsDirectory { + LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name); + +public: + ~LayeredVfsDirectory() override; + + /// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases. + static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = ""); + + std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override; + std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override; + std::shared_ptr<VfsFile> GetFile(std::string_view name) const override; + std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override; + std::string GetFullPath() const override; + + 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(std::string_view name) override; + std::shared_ptr<VfsFile> CreateFile(std::string_view name) override; + bool DeleteSubdirectory(std::string_view name) override; + bool DeleteFile(std::string_view name) override; + bool Rename(std::string_view name) override; + +protected: + bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; + +private: + std::vector<VirtualDir> dirs; + std::string name; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 5e242e20f..9defad04c 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -413,6 +413,23 @@ std::string RealVfsDirectory::GetFullPath() const { return out; } +std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const { + if (perms == Mode::Append) + return {}; + + std::map<std::string, VfsEntryType, std::less<>> out; + FileUtil::ForeachDirectoryEntry( + nullptr, path, + [&out](u64* entries_out, const std::string& directory, const std::string& filename) { + const std::string full_path = directory + DIR_SEP + filename; + out.emplace(filename, FileUtil::IsDirectory(full_path) ? VfsEntryType::Directory + : VfsEntryType::File); + return true; + }); + + return out; +} + bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { return false; } diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index 681c43e82..5b61db90d 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -98,6 +98,7 @@ public: bool DeleteFile(std::string_view name) override; bool Rename(std::string_view name) override; std::string GetFullPath() const override; + std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override; protected: bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h new file mode 100644 index 000000000..44fab51d1 --- /dev/null +++ b/src/core/file_sys/vfs_static.h @@ -0,0 +1,79 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <memory> +#include <string_view> + +#include "core/file_sys/vfs.h" + +namespace FileSys { + +class StaticVfsFile : public VfsFile { +public: + explicit StaticVfsFile(u8 value, std::size_t size = 0, std::string name = "", + VirtualDir parent = nullptr) + : value{value}, size{size}, name{std::move(name)}, parent{std::move(parent)} {} + + std::string GetName() const override { + return name; + } + + std::size_t GetSize() const override { + return size; + } + + bool Resize(std::size_t new_size) override { + size = new_size; + return true; + } + + std::shared_ptr<VfsDirectory> GetContainingDirectory() const override { + return parent; + } + + bool IsWritable() const override { + return false; + } + + bool IsReadable() const override { + return true; + } + + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override { + const auto read = std::min(length, size - offset); + std::fill(data, data + read, value); + return read; + } + + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override { + return 0; + } + + boost::optional<u8> ReadByte(std::size_t offset) const override { + if (offset < size) + return value; + return boost::none; + } + + std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override { + const auto read = std::min(length, size - offset); + return std::vector<u8>(read, value); + } + + bool Rename(std::string_view new_name) override { + name = new_name; + return true; + } + +private: + u8 value; + std::size_t size; + std::string name; + VirtualDir parent; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp index ec7f735b5..389c7e003 100644 --- a/src/core/file_sys/vfs_vector.cpp +++ b/src/core/file_sys/vfs_vector.cpp @@ -3,10 +3,64 @@ // Refer to the license.txt file included. #include <algorithm> +#include <cstring> #include <utility> #include "core/file_sys/vfs_vector.h" namespace FileSys { +VectorVfsFile::VectorVfsFile(std::vector<u8> initial_data, std::string name, VirtualDir parent) + : data(std::move(initial_data)), parent(std::move(parent)), name(std::move(name)) {} + +VectorVfsFile::~VectorVfsFile() = default; + +std::string VectorVfsFile::GetName() const { + return name; +} + +size_t VectorVfsFile::GetSize() const { + return data.size(); +} + +bool VectorVfsFile::Resize(size_t new_size) { + data.resize(new_size); + return true; +} + +std::shared_ptr<VfsDirectory> VectorVfsFile::GetContainingDirectory() const { + return parent; +} + +bool VectorVfsFile::IsWritable() const { + return true; +} + +bool VectorVfsFile::IsReadable() const { + return true; +} + +std::size_t VectorVfsFile::Read(u8* data_, std::size_t length, std::size_t offset) const { + const auto read = std::min(length, data.size() - offset); + std::memcpy(data_, data.data() + offset, read); + return read; +} + +std::size_t VectorVfsFile::Write(const u8* data_, std::size_t length, std::size_t offset) { + if (offset + length > data.size()) + data.resize(offset + length); + const auto write = std::min(length, data.size() - offset); + std::memcpy(data.data(), data_, write); + return write; +} + +bool VectorVfsFile::Rename(std::string_view name_) { + name = name_; + return true; +} + +void VectorVfsFile::Assign(std::vector<u8> new_data) { + data = std::move(new_data); +} + VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_, std::vector<VirtualDir> dirs_, std::string name_, VirtualDir parent_) diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h index cba44a7a6..48a414c98 100644 --- a/src/core/file_sys/vfs_vector.h +++ b/src/core/file_sys/vfs_vector.h @@ -8,6 +8,31 @@ namespace FileSys { +// An implementation of VfsFile that is backed by a vector optionally supplied upon construction +class VectorVfsFile : public VfsFile { +public: + explicit VectorVfsFile(std::vector<u8> initial_data = {}, std::string name = "", + VirtualDir parent = nullptr); + ~VectorVfsFile() override; + + std::string GetName() const override; + std::size_t GetSize() const override; + bool Resize(std::size_t new_size) override; + std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; + bool IsWritable() const override; + bool IsReadable() const override; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; + bool Rename(std::string_view name) override; + + virtual void Assign(std::vector<u8> new_data); + +private: + std::vector<u8> data; + VirtualDir parent; + std::string name; +}; + // An implementation of VfsDirectory that maintains two vectors for subdirectories and files. // Vector data is supplied upon construction. class VectorVfsDirectory : public VfsDirectory { diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp index 69c812f16..9faf903cf 100644 --- a/src/core/hle/kernel/scheduler.cpp +++ b/src/core/hle/kernel/scheduler.cpp @@ -17,7 +17,7 @@ namespace Kernel { std::mutex Scheduler::scheduler_mutex; -Scheduler::Scheduler(Core::ARM_Interface* cpu_core) : cpu_core(cpu_core) {} +Scheduler::Scheduler(Core::ARM_Interface& cpu_core) : cpu_core(cpu_core) {} Scheduler::~Scheduler() { for (auto& thread : thread_list) { @@ -59,9 +59,9 @@ void Scheduler::SwitchContext(Thread* new_thread) { // Save context for previous thread if (previous_thread) { previous_thread->last_running_ticks = CoreTiming::GetTicks(); - cpu_core->SaveContext(previous_thread->context); + cpu_core.SaveContext(previous_thread->context); // Save the TPIDR_EL0 system register in case it was modified. - previous_thread->tpidr_el0 = cpu_core->GetTPIDR_EL0(); + previous_thread->tpidr_el0 = cpu_core.GetTPIDR_EL0(); if (previous_thread->status == ThreadStatus::Running) { // This is only the case when a reschedule is triggered without the current thread @@ -91,10 +91,10 @@ void Scheduler::SwitchContext(Thread* new_thread) { SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table); } - cpu_core->LoadContext(new_thread->context); - cpu_core->SetTlsAddress(new_thread->GetTLSAddress()); - cpu_core->SetTPIDR_EL0(new_thread->GetTPIDR_EL0()); - cpu_core->ClearExclusiveState(); + cpu_core.LoadContext(new_thread->context); + cpu_core.SetTlsAddress(new_thread->GetTLSAddress()); + cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0()); + cpu_core.ClearExclusiveState(); } else { current_thread = nullptr; // Note: We do not reset the current process and current page table when idling because diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h index 744990c9b..2c94641ec 100644 --- a/src/core/hle/kernel/scheduler.h +++ b/src/core/hle/kernel/scheduler.h @@ -19,7 +19,7 @@ namespace Kernel { class Scheduler final { public: - explicit Scheduler(Core::ARM_Interface* cpu_core); + explicit Scheduler(Core::ARM_Interface& cpu_core); ~Scheduler(); /// Returns whether there are any threads that are ready to run. @@ -72,7 +72,7 @@ private: SharedPtr<Thread> current_thread = nullptr; - Core::ARM_Interface* cpu_core; + Core::ARM_Interface& cpu_core; static std::mutex scheduler_mutex; }; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 0bc407098..c9d212a4c 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -1017,7 +1017,7 @@ static const FunctionDef SVC_Table[] = { {0x2B, nullptr, "FlushDataCache"}, {0x2C, nullptr, "MapPhysicalMemory"}, {0x2D, nullptr, "UnmapPhysicalMemory"}, - {0x2E, nullptr, "GetNextThreadInfo"}, + {0x2E, nullptr, "GetFutureThreadInfo"}, {0x2F, nullptr, "GetLastThreadInfo"}, {0x30, nullptr, "GetResourceLimitLimitValue"}, {0x31, nullptr, "GetResourceLimitCurrentValue"}, @@ -1043,11 +1043,11 @@ static const FunctionDef SVC_Table[] = { {0x45, nullptr, "CreateEvent"}, {0x46, nullptr, "Unknown"}, {0x47, nullptr, "Unknown"}, - {0x48, nullptr, "AllocateUnsafeMemory"}, - {0x49, nullptr, "FreeUnsafeMemory"}, - {0x4A, nullptr, "SetUnsafeAllocationLimit"}, - {0x4B, nullptr, "CreateJitMemory"}, - {0x4C, nullptr, "MapJitMemory"}, + {0x48, nullptr, "MapPhysicalMemoryUnsafe"}, + {0x49, nullptr, "UnmapPhysicalMemoryUnsafe"}, + {0x4A, nullptr, "SetUnsafeLimit"}, + {0x4B, nullptr, "CreateCodeMemory"}, + {0x4C, nullptr, "ControlCodeMemory"}, {0x4D, nullptr, "SleepSystem"}, {0x4E, nullptr, "ReadWriteRegister"}, {0x4F, nullptr, "SetProcessActivity"}, @@ -1082,7 +1082,7 @@ static const FunctionDef SVC_Table[] = { {0x6C, nullptr, "SetHardwareBreakPoint"}, {0x6D, nullptr, "GetDebugThreadParam"}, {0x6E, nullptr, "Unknown"}, - {0x6F, nullptr, "GetMemoryInfo"}, + {0x6F, nullptr, "GetSystemInfo"}, {0x70, nullptr, "CreatePort"}, {0x71, nullptr, "ManageNamedPort"}, {0x72, nullptr, "ConnectToPort"}, diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 1258de510..6073f4ecd 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -10,6 +10,7 @@ #include "common/alignment.h" #include "common/common_funcs.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index b436ce4e6..2212b2cdd 100644 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp @@ -2,8 +2,17 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> +#include <cstring> +#include <ctime> +#include <fmt/time.h> +#include "common/file_util.h" #include "common/logging/log.h" +#include "common/scm_rev.h" +#include "common/swap.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/process.h" #include "core/hle/service/fatal/fatal.h" #include "core/hle/service/fatal/fatal_p.h" #include "core/hle/service/fatal/fatal_u.h" @@ -15,16 +24,142 @@ Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) Module::Interface::~Interface() = default; +struct FatalInfo { + std::array<u64_le, 31> registers{}; // TODO(ogniK): See if this actually is registers or + // not(find a game which has non zero valeus) + u64_le unk0{}; + u64_le unk1{}; + u64_le unk2{}; + u64_le unk3{}; + u64_le unk4{}; + u64_le unk5{}; + u64_le unk6{}; + + std::array<u64_le, 32> backtrace{}; + u64_le unk7{}; + u64_le unk8{}; + u32_le backtrace_size{}; + u32_le unk9{}; + u32_le unk10{}; // TODO(ogniK): Is this even used or is it just padding? +}; +static_assert(sizeof(FatalInfo) == 0x250, "FatalInfo is an invalid size"); + +enum class FatalType : u32 { + ErrorReportAndScreen = 0, + ErrorReport = 1, + ErrorScreen = 2, +}; + +static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) { + const auto title_id = Core::CurrentProcess()->program_id; + std::string crash_report = + fmt::format("Yuzu {}-{} crash report\n" + "Title ID: {:016x}\n" + "Result: 0x{:X} ({:04}-{:04d})\n" + "\n", + Common::g_scm_branch, Common::g_scm_desc, title_id, error_code.raw, + 2000 + static_cast<u32>(error_code.module.Value()), + static_cast<u32>(error_code.description.Value()), info.unk8, info.unk7); + if (info.backtrace_size != 0x0) { + crash_report += "Registers:\n"; + // TODO(ogniK): This is just a guess, find a game which actually has non zero values + for (size_t i = 0; i < info.registers.size(); i++) { + crash_report += + fmt::format(" X[{:02d}]: {:016x}\n", i, info.registers[i]); + } + crash_report += fmt::format(" Unknown 0: {:016x}\n", info.unk0); + crash_report += fmt::format(" Unknown 1: {:016x}\n", info.unk1); + crash_report += fmt::format(" Unknown 2: {:016x}\n", info.unk2); + crash_report += fmt::format(" Unknown 3: {:016x}\n", info.unk3); + crash_report += fmt::format(" Unknown 4: {:016x}\n", info.unk4); + crash_report += fmt::format(" Unknown 5: {:016x}\n", info.unk5); + crash_report += fmt::format(" Unknown 6: {:016x}\n", info.unk6); + crash_report += "\nBacktrace:\n"; + for (size_t i = 0; i < info.backtrace_size; i++) { + crash_report += + fmt::format(" Backtrace[{:02d}]: {:016x}\n", i, info.backtrace[i]); + } + crash_report += fmt::format("\nUnknown 7: 0x{:016x}\n", info.unk7); + crash_report += fmt::format("Unknown 8: 0x{:016x}\n", info.unk8); + crash_report += fmt::format("Unknown 9: 0x{:016x}\n", info.unk9); + crash_report += fmt::format("Unknown 10: 0x{:016x}\n", info.unk10); + } + + LOG_ERROR(Service_Fatal, "{}", crash_report); + + const std::string crashreport_dir = + FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + "crash_logs"; + + if (!FileUtil::CreateFullPath(crashreport_dir)) { + LOG_ERROR( + Service_Fatal, + "Unable to create crash report directory. Possible log directory permissions issue."); + return; + } + + const std::time_t t = std::time(nullptr); + const std::string crashreport_filename = + fmt::format("{}/{:016x}-{:%F-%H%M%S}.log", crashreport_dir, title_id, *std::localtime(&t)); + + auto file = FileUtil::IOFile(crashreport_filename, "wb"); + if (file.IsOpen()) { + file.WriteString(crash_report); + LOG_ERROR(Service_Fatal, "Saving error report to {}", crashreport_filename); + } else { + LOG_ERROR(Service_Fatal, "Failed to save error report to {}", crashreport_filename); + } +} + +static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const FatalInfo& info) { + LOG_ERROR(Service_Fatal, "Threw fatal error type {}", static_cast<u32>(fatal_type)); + switch (fatal_type) { + case FatalType::ErrorReportAndScreen: + GenerateErrorReport(error_code, info); + [[fallthrough]]; + case FatalType::ErrorScreen: + // Since we have no fatal:u error screen. We should just kill execution instead + ASSERT(false); + break; + // Should not throw a fatal screen but should generate an error report + case FatalType::ErrorReport: + GenerateErrorReport(error_code, info); + break; + }; +} + +void Module::Interface::ThrowFatal(Kernel::HLERequestContext& ctx) { + LOG_ERROR(Service_Fatal, "called"); + IPC::RequestParser rp{ctx}; + auto error_code = rp.Pop<ResultCode>(); + + ThrowFatalError(error_code, FatalType::ErrorScreen, {}); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) { + LOG_ERROR(Service_Fatal, "called"); IPC::RequestParser rp(ctx); - u32 error_code = rp.Pop<u32>(); - LOG_WARNING(Service_Fatal, "(STUBBED) called, error_code=0x{:X}", error_code); + auto error_code = rp.Pop<ResultCode>(); + auto fatal_type = rp.PopEnum<FatalType>(); + + ThrowFatalError(error_code, fatal_type, {}); // No info is passed with ThrowFatalWithPolicy IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Fatal, "(STUBBED) called"); + LOG_ERROR(Service_Fatal, "called"); + IPC::RequestParser rp(ctx); + auto error_code = rp.Pop<ResultCode>(); + auto fatal_type = rp.PopEnum<FatalType>(); + auto fatal_info = ctx.ReadBuffer(); + FatalInfo info{}; + + ASSERT_MSG(fatal_info.size() == sizeof(FatalInfo), "Invalid fatal info buffer size!"); + std::memcpy(&info, fatal_info.data(), sizeof(FatalInfo)); + + ThrowFatalError(error_code, fatal_type, info); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h index 4d9a5be52..09371ff7f 100644 --- a/src/core/hle/service/fatal/fatal.h +++ b/src/core/hle/service/fatal/fatal.h @@ -15,6 +15,7 @@ public: explicit Interface(std::shared_ptr<Module> module, const char* name); ~Interface() override; + void ThrowFatal(Kernel::HLERequestContext& ctx); void ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx); void ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp index befc307cf..1572a2051 100644 --- a/src/core/hle/service/fatal/fatal_u.cpp +++ b/src/core/hle/service/fatal/fatal_u.cpp @@ -8,7 +8,7 @@ namespace Service::Fatal { Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") { static const FunctionInfo functions[] = { - {0, nullptr, "ThrowFatal"}, + {0, &Fatal_U::ThrowFatal, "ThrowFatal"}, {1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"}, {2, &Fatal_U::ThrowFatalWithCpuContext, "ThrowFatalWithCpuContext"}, }; diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index d349ee686..aed2abb71 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -343,6 +343,15 @@ std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() { return sdmc_factory->GetSDMCContents(); } +FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) { + LOG_TRACE(Service_FS, "Opening mod load root for tid={:016X}", title_id); + + if (bis_factory == nullptr) + return nullptr; + + return bis_factory->GetModificationLoadRoot(title_id); +} + void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) { if (overwrite) { bis_factory = nullptr; @@ -354,9 +363,11 @@ void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) { FileSys::Mode::ReadWrite); auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), FileSys::Mode::ReadWrite); + auto load_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), + FileSys::Mode::ReadWrite); if (bis_factory == nullptr) - bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory); + bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory, load_directory); if (save_data_factory == nullptr) save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); if (sdmc_factory == nullptr) diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index aab65a2b8..7039a2247 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -52,6 +52,8 @@ std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents(); std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents(); +FileSys::VirtualDir GetModificationLoadRoot(u64 title_id); + // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function // above is called. void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true); diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index e587ad0d8..872e3c344 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -2,6 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/swap.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/hid/irs.h" namespace Service::HID { @@ -9,28 +14,145 @@ namespace Service::HID { IRS::IRS() : ServiceFramework{"irs"} { // clang-format off static const FunctionInfo functions[] = { - {302, nullptr, "ActivateIrsensor"}, - {303, nullptr, "DeactivateIrsensor"}, - {304, nullptr, "GetIrsensorSharedMemoryHandle"}, - {305, nullptr, "StopImageProcessor"}, - {306, nullptr, "RunMomentProcessor"}, - {307, nullptr, "RunClusteringProcessor"}, - {308, nullptr, "RunImageTransferProcessor"}, - {309, nullptr, "GetImageTransferProcessorState"}, - {310, nullptr, "RunTeraPluginProcessor"}, - {311, nullptr, "GetNpadIrCameraHandle"}, - {312, nullptr, "RunPointingProcessor"}, - {313, nullptr, "SuspendImageProcessor"}, - {314, nullptr, "CheckFirmwareVersion"}, - {315, nullptr, "SetFunctionLevel"}, - {316, nullptr, "RunImageTransferExProcessor"}, - {317, nullptr, "RunIrLedProcessor"}, - {318, nullptr, "StopImageProcessorAsync"}, - {319, nullptr, "ActivateIrsensorWithFunctionLevel"}, + {302, &IRS::ActivateIrsensor, "ActivateIrsensor"}, + {303, &IRS::DeactivateIrsensor, "DeactivateIrsensor"}, + {304, &IRS::GetIrsensorSharedMemoryHandle, "GetIrsensorSharedMemoryHandle"}, + {305, &IRS::StopImageProcessor, "StopImageProcessor"}, + {306, &IRS::RunMomentProcessor, "RunMomentProcessor"}, + {307, &IRS::RunClusteringProcessor, "RunClusteringProcessor"}, + {308, &IRS::RunImageTransferProcessor, "RunImageTransferProcessor"}, + {309, &IRS::GetImageTransferProcessorState, "GetImageTransferProcessorState"}, + {310, &IRS::RunTeraPluginProcessor, "RunTeraPluginProcessor"}, + {311, &IRS::GetNpadIrCameraHandle, "GetNpadIrCameraHandle"}, + {312, &IRS::RunPointingProcessor, "RunPointingProcessor"}, + {313, &IRS::SuspendImageProcessor, "SuspendImageProcessor"}, + {314, &IRS::CheckFirmwareVersion, "CheckFirmwareVersion"}, + {315, &IRS::SetFunctionLevel, "SetFunctionLevel"}, + {316, &IRS::RunImageTransferExProcessor, "RunImageTransferExProcessor"}, + {317, &IRS::RunIrLedProcessor, "RunIrLedProcessor"}, + {318, &IRS::StopImageProcessorAsync, "StopImageProcessorAsync"}, + {319, &IRS::ActivateIrsensorWithFunctionLevel, "ActivateIrsensorWithFunctionLevel"}, }; // clang-format on RegisterHandlers(functions); + + auto& kernel = Core::System::GetInstance().Kernel(); + shared_mem = Kernel::SharedMemory::Create( + kernel, nullptr, 0x8000, Kernel::MemoryPermission::ReadWrite, + Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "IRS:SharedMemory"); +} + +void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(shared_mem); + LOG_DEBUG(Service_IRS, "called"); +} + +void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<u64>(CoreTiming::GetTicks()); + rb.PushRaw<u32>(0); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<u32>(device_handle); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::SetFunctionLevel(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); +} + +void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_IRS, "(STUBBED) called"); } IRS::~IRS() = default; diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h index 6fb16b45d..12de6bfb3 100644 --- a/src/core/hle/service/hid/irs.h +++ b/src/core/hle/service/hid/irs.h @@ -4,14 +4,41 @@ #pragma once +#include "core/hle/kernel/object.h" #include "core/hle/service/service.h" +namespace Kernel { +class SharedMemory; +} + namespace Service::HID { class IRS final : public ServiceFramework<IRS> { public: explicit IRS(); ~IRS() override; + +private: + void ActivateIrsensor(Kernel::HLERequestContext& ctx); + void DeactivateIrsensor(Kernel::HLERequestContext& ctx); + void GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx); + void StopImageProcessor(Kernel::HLERequestContext& ctx); + void RunMomentProcessor(Kernel::HLERequestContext& ctx); + void RunClusteringProcessor(Kernel::HLERequestContext& ctx); + void RunImageTransferProcessor(Kernel::HLERequestContext& ctx); + void GetImageTransferProcessorState(Kernel::HLERequestContext& ctx); + void RunTeraPluginProcessor(Kernel::HLERequestContext& ctx); + void GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx); + void RunPointingProcessor(Kernel::HLERequestContext& ctx); + void SuspendImageProcessor(Kernel::HLERequestContext& ctx); + void CheckFirmwareVersion(Kernel::HLERequestContext& ctx); + void SetFunctionLevel(Kernel::HLERequestContext& ctx); + void RunImageTransferExProcessor(Kernel::HLERequestContext& ctx); + void RunIrLedProcessor(Kernel::HLERequestContext& ctx); + void StopImageProcessorAsync(Kernel::HLERequestContext& ctx); + void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx); + Kernel::SharedPtr<Kernel::SharedMemory> shared_mem; + const u32 device_handle{0xABCD}; }; class IRS_SYS final : public ServiceFramework<IRS_SYS> { diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index f8d2127d9..8c07a05c2 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/service/hid/hid.h" diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp index c1737defa..261ad539c 100644 --- a/src/core/hle/service/nim/nim.cpp +++ b/src/core/hle/service/nim/nim.cpp @@ -4,6 +4,7 @@ #include <chrono> #include <ctime> +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/service/nim/nim.h" diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp index cdf328a26..98f6e4111 100644 --- a/src/core/hle/service/sm/controller.cpp +++ b/src/core/hle/service/sm/controller.cpp @@ -2,8 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/assert.h" #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/server_session.h" #include "core/hle/kernel/session.h" #include "core/hle/service/sm/controller.h" diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index fe0a318ee..bc4f7a437 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -103,6 +103,7 @@ public: } private: + u32 ssl_version{}; void CreateContext(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_SSL, "(STUBBED) called"); @@ -112,10 +113,9 @@ private: } void SetInterfaceVersion(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_SSL, "(STUBBED) called"); + LOG_DEBUG(Service_SSL, "called"); IPC::RequestParser rp{ctx}; - u32 unk1 = rp.Pop<u32>(); // Probably minor/major? - u32 unk2 = rp.Pop<u32>(); // TODO(ogniK): Figure out what this does + ssl_version = rp.Pop<u32>(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index b81b0723d..16cdfc7e2 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -461,7 +461,11 @@ public: u32 entry; } macros; - INSERT_PADDING_WORDS(0x1B8); + INSERT_PADDING_WORDS(0x189); + + u32 tfb_enabled; + + INSERT_PADDING_WORDS(0x2E); RenderTargetConfig rt[NumRenderTargets]; @@ -594,7 +598,9 @@ public: u32 depth_write_enabled; - INSERT_PADDING_WORDS(0x7); + u32 alpha_test_enabled; + + INSERT_PADDING_WORDS(0x6); u32 d3d_cull_mode; @@ -977,6 +983,7 @@ private: "Field " #field_name " has invalid position") ASSERT_REG_POSITION(macros, 0x45); +ASSERT_REG_POSITION(tfb_enabled, 0x1D1); ASSERT_REG_POSITION(rt, 0x200); ASSERT_REG_POSITION(viewport_transform[0], 0x280); ASSERT_REG_POSITION(viewport, 0x300); @@ -996,6 +1003,7 @@ ASSERT_REG_POSITION(zeta_height, 0x48b); ASSERT_REG_POSITION(depth_test_enable, 0x4B3); ASSERT_REG_POSITION(independent_blend_enable, 0x4B9); ASSERT_REG_POSITION(depth_write_enabled, 0x4BA); +ASSERT_REG_POSITION(alpha_test_enabled, 0x4BB); ASSERT_REG_POSITION(d3d_cull_mode, 0x4C2); ASSERT_REG_POSITION(depth_test_func, 0x4C3); ASSERT_REG_POSITION(blend, 0x4CF); diff --git a/src/video_core/engines/maxwell_compute.cpp b/src/video_core/engines/maxwell_compute.cpp index e4e5f9e5e..59e28b22d 100644 --- a/src/video_core/engines/maxwell_compute.cpp +++ b/src/video_core/engines/maxwell_compute.cpp @@ -2,12 +2,29 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.h" +#include "core/core.h" #include "video_core/engines/maxwell_compute.h" namespace Tegra { namespace Engines { -void MaxwellCompute::WriteReg(u32 method, u32 value) {} +void MaxwellCompute::WriteReg(u32 method, u32 value) { + ASSERT_MSG(method < Regs::NUM_REGS, + "Invalid MaxwellCompute register, increase the size of the Regs structure"); + + regs.reg_array[method] = value; + + switch (method) { + case MAXWELL_COMPUTE_REG_INDEX(compute): { + LOG_CRITICAL(HW_GPU, "Compute shaders are not implemented"); + UNREACHABLE(); + break; + } + default: + break; + } +} } // namespace Engines } // namespace Tegra diff --git a/src/video_core/engines/maxwell_compute.h b/src/video_core/engines/maxwell_compute.h index 2b3e4ced6..6ea934fb9 100644 --- a/src/video_core/engines/maxwell_compute.h +++ b/src/video_core/engines/maxwell_compute.h @@ -4,17 +4,53 @@ #pragma once +#include <array> +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" #include "common/common_types.h" namespace Tegra::Engines { +#define MAXWELL_COMPUTE_REG_INDEX(field_name) \ + (offsetof(Tegra::Engines::MaxwellCompute::Regs, field_name) / sizeof(u32)) + class MaxwellCompute final { public: MaxwellCompute() = default; ~MaxwellCompute() = default; + struct Regs { + static constexpr std::size_t NUM_REGS = 0xCF8; + + union { + struct { + INSERT_PADDING_WORDS(0x281); + + union { + u32 compute_end; + BitField<0, 1, u32> unknown; + } compute; + + INSERT_PADDING_WORDS(0xA76); + }; + std::array<u32, NUM_REGS> reg_array; + }; + } regs{}; + + static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32), + "MaxwellCompute Regs has wrong size"); + /// Write the value to the register identified by method. void WriteReg(u32 method, u32 value); }; +#define ASSERT_REG_POSITION(field_name, position) \ + static_assert(offsetof(MaxwellCompute::Regs, field_name) == position * 4, \ + "Field " #field_name " has invalid position") + +ASSERT_REG_POSITION(compute, 0x281); + +#undef ASSERT_REG_POSITION + } // namespace Tegra::Engines diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 70fb54507..44850d193 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -450,6 +450,8 @@ void RasterizerOpenGL::DrawArrays() { SyncBlendState(); SyncLogicOpState(); SyncCullMode(); + SyncAlphaTest(); + SyncTransformFeedback(); // TODO(bunnei): Sync framebuffer_scale uniform here // TODO(bunnei): Sync scissorbox uniform(s) here @@ -883,4 +885,24 @@ void RasterizerOpenGL::SyncLogicOpState() { state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation); } +void RasterizerOpenGL::SyncAlphaTest() { + const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + + // TODO(Rodrigo): Alpha testing is a legacy OpenGL feature, but it can be + // implemented with a test+discard in fragment shaders. + if (regs.alpha_test_enabled != 0) { + LOG_CRITICAL(Render_OpenGL, "Alpha testing is not implemented"); + UNREACHABLE(); + } +} + +void RasterizerOpenGL::SyncTransformFeedback() { + const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + + if (regs.tfb_enabled != 0) { + LOG_CRITICAL(Render_OpenGL, "Transform feedbacks are not implemented"); + UNREACHABLE(); + } +} + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index bf9560bdc..c3f1e14bf 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -158,6 +158,12 @@ private: /// Syncs the LogicOp state to match the guest state void SyncLogicOpState(); + /// Syncs the alpha test state to match the guest state + void SyncAlphaTest(); + + /// Syncs the transform feedback state to match the guest state + void SyncTransformFeedback(); + bool has_ARB_direct_state_access = false; bool has_ARB_multi_bind = false; bool has_ARB_separate_shader_objects = false; diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 20ba6d4f6..3d5476e5d 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -13,47 +13,20 @@ namespace Tegra::Texture { /** + * This table represents the internal swizzle of a gob, + * in format 16 bytes x 2 sector packing. * Calculates the offset of an (x, y) position within a swizzled texture. - * Taken from the Tegra X1 TRM. + * Taken from the Tegra X1 Technical Reference Manual. pages 1187-1188 */ -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 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; - x *= bytes_per_pixel; - u32 address = GOB_address + ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 + - (y % 2) * 16 + (x % 16); - - return address; -} - -void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, - u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height) { - u8* data_ptrs[2]; - for (unsigned y = 0; y < height; ++y) { - for (unsigned x = 0; x < width; ++x) { - u32 swizzle_offset = GetSwizzleOffset(x, y, width, bytes_per_pixel, block_height); - u32 pixel_index = (x + y * width) * out_bytes_per_pixel; - - data_ptrs[unswizzle] = swizzled_data + swizzle_offset; - data_ptrs[!unswizzle] = &unswizzled_data[pixel_index]; - - std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); - } - } -} - -template <std::size_t N, std::size_t M> +template <std::size_t N, std::size_t M, u32 Align> struct alignas(64) SwizzleTable { + static_assert(M * Align == 64, "Swizzle Table does not align to GOB"); constexpr SwizzleTable() { for (u32 y = 0; y < N; ++y) { for (u32 x = 0; x < M; ++x) { - const u32 x2 = x * 16; + const u32 x2 = x * Align; values[y][x] = static_cast<u16>(((x2 % 64) / 32) * 256 + ((y % 8) / 2) * 64 + - ((x2 % 32) / 16) * 32 + (y % 2) * 16); + ((x2 % 32) / 16) * 32 + (y % 2) * 16 + (x2 % 16)); } } } @@ -63,24 +36,60 @@ struct alignas(64) SwizzleTable { std::array<std::array<u16, M>, N> values{}; }; -constexpr auto swizzle_table = SwizzleTable<8, 4>(); +constexpr auto legacy_swizzle_table = SwizzleTable<8, 64, 1>(); +constexpr auto fast_swizzle_table = SwizzleTable<8, 4, 16>(); -void FastSwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u8* swizzled_data, - u8* unswizzled_data, bool unswizzle, u32 block_height) { +static void LegacySwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, + u8* swizzled_data, u8* unswizzled_data, bool unswizzle, + u32 block_height) { + std::array<u8*, 2> data_ptrs; + const std::size_t stride = width * bytes_per_pixel; + const std::size_t gobs_in_x = 64; + const std::size_t gobs_in_y = 8; + const std::size_t gobs_size = gobs_in_x * gobs_in_y; + const std::size_t image_width_in_gobs{(stride + gobs_in_x - 1) / gobs_in_x}; + for (std::size_t y = 0; y < height; ++y) { + const std::size_t gob_y_address = + (y / (gobs_in_y * block_height)) * gobs_size * block_height * image_width_in_gobs + + (y % (gobs_in_y * block_height) / gobs_in_y) * gobs_size; + const auto& table = legacy_swizzle_table[y % gobs_in_y]; + for (std::size_t x = 0; x < width; ++x) { + const std::size_t gob_address = + gob_y_address + (x * bytes_per_pixel / gobs_in_x) * gobs_size * block_height; + const std::size_t x2 = x * bytes_per_pixel; + const std::size_t swizzle_offset = gob_address + table[x2 % gobs_in_x]; + const std::size_t pixel_index = (x + y * width) * out_bytes_per_pixel; + + data_ptrs[unswizzle] = swizzled_data + swizzle_offset; + data_ptrs[!unswizzle] = unswizzled_data + pixel_index; + + std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); + } + } +} + +static void FastSwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, + u8* swizzled_data, u8* unswizzled_data, bool unswizzle, + u32 block_height) { std::array<u8*, 2> data_ptrs; const std::size_t stride{width * bytes_per_pixel}; - const std::size_t image_width_in_gobs{(stride + 63) / 64}; + const std::size_t gobs_in_x = 64; + const std::size_t gobs_in_y = 8; + const std::size_t gobs_size = gobs_in_x * gobs_in_y; + const std::size_t image_width_in_gobs{(stride + gobs_in_x - 1) / gobs_in_x}; const std::size_t copy_size{16}; for (std::size_t y = 0; y < height; ++y) { const std::size_t initial_gob = - (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs + - (y % (8 * block_height) / 8) * 512; - const std::size_t pixel_base{y * width * bytes_per_pixel}; - const auto& table = swizzle_table[y % 8]; + (y / (gobs_in_y * block_height)) * gobs_size * block_height * image_width_in_gobs + + (y % (gobs_in_y * block_height) / gobs_in_y) * gobs_size; + const std::size_t pixel_base{y * width * out_bytes_per_pixel}; + const auto& table = fast_swizzle_table[y % gobs_in_y]; for (std::size_t xb = 0; xb < stride; xb += copy_size) { - const std::size_t gob_address{initial_gob + (xb / 64) * 512 * block_height}; + const std::size_t gob_address{initial_gob + + (xb / gobs_in_x) * gobs_size * block_height}; const std::size_t swizzle_offset{gob_address + table[(xb / 16) % 4]}; - const std::size_t pixel_index{xb + pixel_base}; + const std::size_t out_x = xb * out_bytes_per_pixel / bytes_per_pixel; + const std::size_t pixel_index{out_x + pixel_base}; data_ptrs[unswizzle] = swizzled_data + swizzle_offset; data_ptrs[!unswizzle] = unswizzled_data + pixel_index; std::memcpy(data_ptrs[0], data_ptrs[1], copy_size); @@ -88,6 +97,17 @@ void FastSwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u8* swizzled_da } } +void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, + u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height) { + if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % 16 == 0) { + FastSwizzleData(width, height, bytes_per_pixel, out_bytes_per_pixel, swizzled_data, + unswizzled_data, unswizzle, block_height); + } else { + LegacySwizzleData(width, height, bytes_per_pixel, out_bytes_per_pixel, swizzled_data, + unswizzled_data, unswizzle, block_height); + } +} + u32 BytesPerPixel(TextureFormat format) { switch (format) { case TextureFormat::DXT1: @@ -134,13 +154,8 @@ u32 BytesPerPixel(TextureFormat format) { std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width, u32 height, u32 block_height) { std::vector<u8> unswizzled_data(width * height * bytes_per_pixel); - if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % 16 == 0) { - FastSwizzleData(width / tile_size, height / tile_size, bytes_per_pixel, - Memory::GetPointer(address), unswizzled_data.data(), true, block_height); - } else { - CopySwizzledData(width / tile_size, height / tile_size, bytes_per_pixel, bytes_per_pixel, - Memory::GetPointer(address), unswizzled_data.data(), true, block_height); - } + CopySwizzledData(width / tile_size, height / tile_size, bytes_per_pixel, bytes_per_pixel, + Memory::GetPointer(address), unswizzled_data.data(), true, block_height); return unswizzled_data; } diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index e8b2f720a..991ae10cd 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -318,9 +318,14 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { int row = item_model->itemFromIndex(item)->row(); QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); + std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString(); QMenu context_menu; QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); + QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); + context_menu.addSeparator(); + QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); + QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); open_save_location->setEnabled(program_id != 0); @@ -329,6 +334,10 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { connect(open_save_location, &QAction::triggered, [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); + connect(open_lfs_location, &QAction::triggered, + [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); }); + connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); }); + connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); }); connect(navigate_to_gamedb_entry, &QAction::triggered, [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 2713e7b54..3bf51870e 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -28,7 +28,10 @@ namespace FileSys { class VfsFilesystem; } -enum class GameListOpenTarget { SaveData }; +enum class GameListOpenTarget { + SaveData, + ModData, +}; class GameList : public QWidget { Q_OBJECT @@ -89,6 +92,8 @@ signals: void GameChosen(QString game_path); void ShouldCancelWorker(); void OpenFolderRequested(u64 program_id, GameListOpenTarget target); + void DumpRomFSRequested(u64 program_id, const std::string& game_path); + void CopyTIDRequested(u64 program_id); void NavigateToGamedbEntryRequested(u64 program_id, const CompatibilityList& compatibility_list); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 45bb1d1d1..d74489935 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -7,6 +7,22 @@ #include <memory> #include <thread> +// VFS includes must be before glad as they will conflict with Windows file api, which uses defines. +#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_real.h" + +// These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows +// defines. +static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper( + const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::Mode mode) { + return vfs->CreateDirectory(path, mode); +} + +static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::VirtualDir& dir, + const std::string& path) { + return dir->CreateFile(path); +} + #include <fmt/ostream.h> #include <glad/glad.h> @@ -30,16 +46,18 @@ #include "common/telemetry.h" #include "core/core.h" #include "core/crypto/key_manager.h" +#include "core/file_sys/bis_factory.h" #include "core/file_sys/card_image.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" +#include "core/file_sys/romfs.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/submission_package.h" -#include "core/file_sys/vfs_real.h" #include "core/hle/kernel/process.h" #include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/filesystem/fsp_ldr.h" #include "core/loader/loader.h" #include "core/perf_stats.h" #include "core/settings.h" @@ -362,6 +380,8 @@ void GMainWindow::RestoreUIState() { void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); + connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); + connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, &GMainWindow::OnGameListNavigateToGamedbEntry); @@ -713,6 +733,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target program_id, user_id, 0); break; } + case GameListOpenTarget::ModData: { + open_target = "Mod Data"; + const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir); + path = fmt::format("{}{:016X}", load_dir, program_id); + break; + } default: UNIMPLEMENTED(); } @@ -730,6 +756,120 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); } +static std::size_t CalculateRomFSEntrySize(const FileSys::VirtualDir& dir, bool full) { + std::size_t out = 0; + + for (const auto& subdir : dir->GetSubdirectories()) { + out += 1 + CalculateRomFSEntrySize(subdir, full); + } + + return out + (full ? dir->GetFiles().size() : 0); +} + +static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src, + const FileSys::VirtualDir& dest, std::size_t block_size, bool full) { + if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) + return false; + if (dialog.wasCanceled()) + return false; + + if (full) { + for (const auto& file : src->GetFiles()) { + const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName()); + if (!FileSys::VfsRawCopy(file, out, block_size)) + return false; + dialog.setValue(dialog.value() + 1); + if (dialog.wasCanceled()) + return false; + } + } + + for (const auto& dir : src->GetSubdirectories()) { + const auto out = dest->CreateSubdirectory(dir->GetName()); + if (!RomFSRawCopy(dialog, dir, out, block_size, full)) + return false; + dialog.setValue(dialog.value() + 1); + if (dialog.wasCanceled()) + return false; + } + + return true; +} + +void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) { + const auto path = fmt::format("{}{:016X}/romfs", + FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id); + + const auto failed = [this, &path] { + QMessageBox::warning(this, tr("RomFS Extraction Failed!"), + tr("There was an error copying the RomFS files or the user " + "cancelled the operation.")); + vfs->DeleteDirectory(path); + }; + + const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read)); + if (loader == nullptr) { + failed(); + return; + } + + FileSys::VirtualFile file; + if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) { + failed(); + return; + } + + const auto romfs = + loader->IsRomFSUpdatable() + ? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset()) + : file; + + const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); + if (extracted == nullptr) { + failed(); + return; + } + + const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite); + + if (out == nullptr) { + failed(); + return; + } + + bool ok; + const auto res = QInputDialog::getItem( + this, tr("Select RomFS Dump Mode"), + tr("Please select the how you would like the RomFS dumped.<br>Full will copy all of the " + "files into the new directory while <br>skeleton will only create the directory " + "structure."), + {"Full", "Skeleton"}, 0, false, &ok); + if (!ok) + failed(); + + const auto full = res == "Full"; + const auto entry_size = CalculateRomFSEntrySize(extracted, full); + + QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, entry_size, this); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(100); + + if (RomFSRawCopy(progress, extracted, out, 0x400000, full)) { + progress.close(); + QMessageBox::information(this, tr("RomFS Extraction Succeeded!"), + tr("The operation completed successfully.")); + QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(path))); + } else { + progress.close(); + failed(); + } +} + +void GMainWindow::OnGameListCopyTID(u64 program_id) { + QClipboard* clipboard = QGuiApplication::clipboard(); + clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); +} + void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, const CompatibilityList& compatibility_list) { const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); @@ -790,7 +930,8 @@ void GMainWindow::OnMenuInstallToNAND() { return; } - const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) { + const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, + const FileSys::VirtualFile& dest, std::size_t block_size) { if (src == nullptr || dest == nullptr) return false; if (!dest->Resize(src->GetSize())) diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 552e3e61c..8ee9242b1 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -138,6 +138,8 @@ private slots: /// Called whenever a user selects a game in the game list widget. void OnGameListLoadFile(QString game_path); void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); + void OnGameListDumpRomFS(u64 program_id, const std::string& game_path); + void OnGameListCopyTID(u64 program_id); void OnGameListNavigateToGamedbEntry(u64 program_id, const CompatibilityList& compatibility_list); void OnMenuLoadFile(); |