diff options
Diffstat (limited to 'src/core')
61 files changed, 1767 insertions, 262 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0b0ae5ccc..31a7bf6fd 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -20,6 +20,8 @@ add_library(core STATIC crypto/key_manager.h crypto/ctr_encryption_layer.cpp crypto/ctr_encryption_layer.h + file_sys/bis_factory.cpp + file_sys/bis_factory.h file_sys/card_image.cpp file_sys/card_image.h file_sys/content_archive.cpp @@ -29,10 +31,14 @@ add_library(core STATIC file_sys/directory.h file_sys/errors.h file_sys/mode.h + file_sys/nca_metadata.cpp + file_sys/nca_metadata.h file_sys/partition_filesystem.cpp file_sys/partition_filesystem.h file_sys/program_metadata.cpp file_sys/program_metadata.h + file_sys/registered_cache.cpp + file_sys/registered_cache.h file_sys/romfs.cpp file_sys/romfs.h file_sys/romfs_factory.cpp @@ -43,6 +49,8 @@ add_library(core STATIC file_sys/sdmc_factory.h file_sys/vfs.cpp file_sys/vfs.h + file_sys/vfs_concat.cpp + file_sys/vfs_concat.h file_sys/vfs_offset.cpp file_sys/vfs_offset.h file_sys/vfs_real.cpp @@ -114,6 +122,8 @@ add_library(core STATIC hle/service/acc/acc_u0.h hle/service/acc/acc_u1.cpp hle/service/acc/acc_u1.h + hle/service/acc/profile_manager.cpp + hle/service/acc/profile_manager.h hle/service/am/am.cpp hle/service/am/am.h hle/service/am/applet_ae.cpp diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 20e5200a8..2c817d7d1 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -134,6 +134,9 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const { config.dczid_el0 = 4; config.ctr_el0 = 0x8444c004; + // Unpredictable instructions + config.define_unpredictable_behaviour = true; + return std::make_unique<Dynarmic::A64::Jit>(config); } diff --git a/src/core/core.cpp b/src/core/core.cpp index 83d4d742b..28038ff6f 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -5,6 +5,7 @@ #include <memory> #include <utility> #include "common/logging/log.h" +#include "common/string_util.h" #include "core/core.h" #include "core/core_timing.h" #include "core/gdbstub/gdbstub.h" @@ -17,6 +18,7 @@ #include "core/hle/service/sm/sm.h" #include "core/loader/loader.h" #include "core/settings.h" +#include "file_sys/vfs_concat.h" #include "file_sys/vfs_real.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" @@ -88,8 +90,39 @@ System::ResultStatus System::SingleStep() { return RunLoop(false); } +static FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, + const std::string& path) { + // To account for split 00+01+etc files. + std::string dir_name; + std::string filename; + Common::SplitPath(path, &dir_name, &filename, nullptr); + if (filename == "00") { + const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read); + std::vector<FileSys::VirtualFile> concat; + for (u8 i = 0; i < 0x10; ++i) { + auto next = dir->GetFile(fmt::format("{:02X}", i)); + if (next != nullptr) + concat.push_back(std::move(next)); + else { + next = dir->GetFile(fmt::format("{:02x}", i)); + if (next != nullptr) + concat.push_back(std::move(next)); + else + break; + } + } + + if (concat.empty()) + return nullptr; + + return FileSys::ConcatenateFiles(concat, dir->GetName()); + } + + return vfs->OpenFile(path, FileSys::Mode::Read); +} + System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { - app_loader = Loader::GetLoader(virtual_filesystem->OpenFile(filepath, FileSys::Mode::Read)); + app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath)); if (!app_loader) { LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); diff --git a/src/core/core.h b/src/core/core.h index d98b15a71..790e23cae 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -40,6 +40,12 @@ namespace Core { class System { public: + System(const System&) = delete; + System& operator=(const System&) = delete; + + System(System&&) = delete; + System& operator=(System&&) = delete; + ~System(); /** diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index f977d1b32..7953c8720 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -56,6 +56,9 @@ static u64 event_fifo_id; // to the event_queue by the emu thread static Common::MPSCQueue<Event, false> ts_queue; +// the queue for unscheduling the events from other threads threadsafe +static Common::MPSCQueue<std::pair<const EventType*, u64>, false> unschedule_queue; + constexpr int MAX_SLICE_LENGTH = 20000; static s64 idled_cycles; @@ -158,6 +161,10 @@ void UnscheduleEvent(const EventType* event_type, u64 userdata) { } } +void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata) { + unschedule_queue.Push(std::make_pair(event_type, userdata)); +} + void RemoveEvent(const EventType* event_type) { auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) { return e.type == event_type; }); @@ -194,6 +201,9 @@ void MoveEvents() { void Advance() { MoveEvents(); + for (std::pair<const EventType*, u64> ev; unschedule_queue.Pop(ev);) { + UnscheduleEvent(ev.first, ev.second); + } int cycles_executed = slice_length - downcount; global_timer += cycles_executed; diff --git a/src/core/core_timing.h b/src/core/core_timing.h index dfa161c0d..9ed757bd7 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -65,6 +65,7 @@ void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 user void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, u64 userdata); void UnscheduleEvent(const EventType* event_type, u64 userdata); +void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata); /// We only permit one event of each type in the queue at a time. void RemoveEvent(const EventType* event_type); diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index fc45e7ab5..db8b22c85 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -10,44 +10,13 @@ #include <string_view> #include "common/common_paths.h" #include "common/file_util.h" +#include "common/hex_util.h" +#include "common/logging/log.h" #include "core/crypto/key_manager.h" #include "core/settings.h" namespace Core::Crypto { -static u8 ToHexNibble(char c1) { - if (c1 >= 65 && c1 <= 70) - return c1 - 55; - if (c1 >= 97 && c1 <= 102) - return c1 - 87; - if (c1 >= 48 && c1 <= 57) - return c1 - 48; - throw std::logic_error("Invalid hex digit"); -} - -template <size_t Size> -static std::array<u8, Size> HexStringToArray(std::string_view str) { - std::array<u8, Size> out{}; - for (size_t i = 0; i < 2 * Size; i += 2) { - auto d1 = str[i]; - auto d2 = str[i + 1]; - out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2); - } - return out; -} - -std::array<u8, 16> operator""_array16(const char* str, size_t len) { - if (len != 32) - throw std::logic_error("Not of correct size."); - return HexStringToArray<16>(str); -} - -std::array<u8, 32> operator""_array32(const char* str, size_t len) { - if (len != 64) - throw std::logic_error("Not of correct size."); - return HexStringToArray<32>(str); -} - KeyManager::KeyManager() { // Initialize keys const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); @@ -83,20 +52,20 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end()); if (is_title_keys) { - auto rights_id_raw = HexStringToArray<16>(out[0]); + auto rights_id_raw = Common::HexStringToArray<16>(out[0]); u128 rights_id{}; std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size()); - Key128 key = HexStringToArray<16>(out[1]); + Key128 key = Common::HexStringToArray<16>(out[1]); SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); } else { std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower); if (s128_file_id.find(out[0]) != s128_file_id.end()) { const auto index = s128_file_id.at(out[0]); - Key128 key = HexStringToArray<16>(out[1]); + Key128 key = Common::HexStringToArray<16>(out[1]); SetKey(index.type, key, index.field1, index.field2); } else if (s256_file_id.find(out[0]) != s256_file_id.end()) { const auto index = s256_file_id.at(out[0]); - Key256 key = HexStringToArray<32>(out[1]); + Key256 key = Common::HexStringToArray<32>(out[1]); SetKey(index.type, key, index.field1, index.field2); } } diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index c4c53cefc..0c62d4421 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -87,9 +87,6 @@ struct hash<Core::Crypto::KeyIndex<KeyType>> { namespace Core::Crypto { -std::array<u8, 0x10> operator"" _array16(const char* str, size_t len); -std::array<u8, 0x20> operator"" _array32(const char* str, size_t len); - class KeyManager { public: KeyManager(); diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp new file mode 100644 index 000000000..ae4e33800 --- /dev/null +++ b/src/core/file_sys/bis_factory.cpp @@ -0,0 +1,31 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/file_sys/bis_factory.h" + +namespace FileSys { + +static VirtualDir GetOrCreateDirectory(const VirtualDir& dir, std::string_view path) { + const auto res = dir->GetDirectoryRelative(path); + if (res == nullptr) + return dir->CreateDirectoryRelative(path); + return res; +} + +BISFactory::BISFactory(VirtualDir nand_root_) + : nand_root(std::move(nand_root_)), + sysnand_cache(std::make_shared<RegisteredCache>( + GetOrCreateDirectory(nand_root, "/system/Contents/registered"))), + usrnand_cache(std::make_shared<RegisteredCache>( + GetOrCreateDirectory(nand_root, "/user/Contents/registered"))) {} + +std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const { + return sysnand_cache; +} + +std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const { + return usrnand_cache; +} + +} // namespace FileSys diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h new file mode 100644 index 000000000..a970a5e2e --- /dev/null +++ b/src/core/file_sys/bis_factory.h @@ -0,0 +1,30 @@ +// 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/loader/loader.h" +#include "registered_cache.h" + +namespace FileSys { + +/// File system interface to the Built-In Storage +/// This is currently missing accessors to BIS partitions, but seemed like a good place for the NAND +/// registered caches. +class BISFactory { +public: + explicit BISFactory(VirtualDir nand_root); + + std::shared_ptr<RegisteredCache> GetSystemNANDContents() const; + std::shared_ptr<RegisteredCache> GetUserNANDContents() const; + +private: + VirtualDir nand_root; + + std::shared_ptr<RegisteredCache> sysnand_cache; + std::shared_ptr<RegisteredCache> usrnand_cache; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 8e05b9d0e..1d7c7fb10 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -4,11 +4,14 @@ #include <array> #include <string> -#include <core/loader/loader.h> + +#include <fmt/ostream.h> + #include "common/logging/log.h" #include "core/file_sys/card_image.h" #include "core/file_sys/partition_filesystem.h" #include "core/file_sys/vfs_offset.h" +#include "core/loader/loader.h" namespace FileSys { @@ -93,6 +96,10 @@ VirtualDir XCI::GetLogoPartition() const { return GetPartition(XCIPartition::Logo); } +const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const { + return ncas; +} + std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const { const auto iter = std::find_if(ncas.begin(), ncas.end(), @@ -142,7 +149,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { const u16 error_id = static_cast<u16>(nca->GetStatus()); LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})", partition_names[static_cast<size_t>(part)], nca->GetName(), error_id, - Loader::GetMessageForResultStatus(nca->GetStatus())); + nca->GetStatus()); } } diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index 4618d9c00..a03d5264e 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -68,6 +68,7 @@ public: VirtualDir GetUpdatePartition() const; VirtualDir GetLogoPartition() const; + const std::vector<std::shared_ptr<NCA>>& GetNCAs() const; std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const; VirtualFile GetNCAFileByType(NCAContentType type) const; diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index 3ddc9f162..ae21ad5b9 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -16,7 +16,7 @@ std::string LanguageEntry::GetDeveloperName() const { return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100); } -NACP::NACP(VirtualFile file_) : file(std::move(file_)), raw(std::make_unique<RawNACP>()) { +NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) { file->ReadObject(raw.get()); } diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 6582cc240..8c2cc1a2a 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -81,7 +81,6 @@ public: std::string GetVersionString() const; private: - VirtualFile file; std::unique_ptr<RawNACP> raw; }; diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp new file mode 100644 index 000000000..449244444 --- /dev/null +++ b/src/core/file_sys/nca_metadata.cpp @@ -0,0 +1,131 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/common_funcs.h" +#include "common/logging/log.h" +#include "common/swap.h" +#include "content_archive.h" +#include "core/file_sys/nca_metadata.h" + +namespace FileSys { + +bool operator>=(TitleType lhs, TitleType rhs) { + return static_cast<size_t>(lhs) >= static_cast<size_t>(rhs); +} + +bool operator<=(TitleType lhs, TitleType rhs) { + return static_cast<size_t>(lhs) <= static_cast<size_t>(rhs); +} + +CNMT::CNMT(VirtualFile file) { + if (file->ReadObject(&header) != sizeof(CNMTHeader)) + return; + + // If type is {Application, Update, AOC} has opt-header. + if (header.type >= TitleType::Application && header.type <= TitleType::AOC) { + if (file->ReadObject(&opt_header, sizeof(CNMTHeader)) != sizeof(OptionalHeader)) { + LOG_WARNING(Loader, "Failed to read optional header."); + } + } + + for (u16 i = 0; i < header.number_content_entries; ++i) { + auto& next = content_records.emplace_back(ContentRecord{}); + if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(ContentRecord) + + header.table_offset) != sizeof(ContentRecord)) { + content_records.erase(content_records.end() - 1); + } + } + + for (u16 i = 0; i < header.number_meta_entries; ++i) { + auto& next = meta_records.emplace_back(MetaRecord{}); + if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(MetaRecord) + + header.table_offset) != sizeof(MetaRecord)) { + meta_records.erase(meta_records.end() - 1); + } + } +} + +CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records, + std::vector<MetaRecord> meta_records) + : header(std::move(header)), opt_header(std::move(opt_header)), + content_records(std::move(content_records)), meta_records(std::move(meta_records)) {} + +u64 CNMT::GetTitleID() const { + return header.title_id; +} + +u32 CNMT::GetTitleVersion() const { + return header.title_version; +} + +TitleType CNMT::GetType() const { + return header.type; +} + +const std::vector<ContentRecord>& CNMT::GetContentRecords() const { + return content_records; +} + +const std::vector<MetaRecord>& CNMT::GetMetaRecords() const { + return meta_records; +} + +bool CNMT::UnionRecords(const CNMT& other) { + bool change = false; + for (const auto& rec : other.content_records) { + const auto iter = std::find_if(content_records.begin(), content_records.end(), + [&rec](const ContentRecord& r) { + return r.nca_id == rec.nca_id && r.type == rec.type; + }); + if (iter == content_records.end()) { + content_records.emplace_back(rec); + ++header.number_content_entries; + change = true; + } + } + for (const auto& rec : other.meta_records) { + const auto iter = + std::find_if(meta_records.begin(), meta_records.end(), [&rec](const MetaRecord& r) { + return r.title_id == rec.title_id && r.title_version == rec.title_version && + r.type == rec.type; + }); + if (iter == meta_records.end()) { + meta_records.emplace_back(rec); + ++header.number_meta_entries; + change = true; + } + } + return change; +} + +std::vector<u8> CNMT::Serialize() const { + const bool has_opt_header = + header.type >= TitleType::Application && header.type <= TitleType::AOC; + const auto dead_zone = header.table_offset + sizeof(CNMTHeader); + std::vector<u8> out( + std::max(sizeof(CNMTHeader) + (has_opt_header ? sizeof(OptionalHeader) : 0), dead_zone) + + content_records.size() * sizeof(ContentRecord) + meta_records.size() * sizeof(MetaRecord)); + memcpy(out.data(), &header, sizeof(CNMTHeader)); + + // Optional Header + if (has_opt_header) { + memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader)); + } + + auto offset = header.table_offset; + + for (const auto& rec : content_records) { + memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord)); + offset += sizeof(ContentRecord); + } + + for (const auto& rec : meta_records) { + memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(MetaRecord)); + offset += sizeof(MetaRecord); + } + + return out; +} +} // namespace FileSys diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h new file mode 100644 index 000000000..88e66d4da --- /dev/null +++ b/src/core/file_sys/nca_metadata.h @@ -0,0 +1,111 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstring> +#include <memory> +#include <vector> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { +class CNMT; + +struct CNMTHeader; +struct OptionalHeader; + +enum class TitleType : u8 { + SystemProgram = 0x01, + SystemDataArchive = 0x02, + SystemUpdate = 0x03, + FirmwarePackageA = 0x04, + FirmwarePackageB = 0x05, + Application = 0x80, + Update = 0x81, + AOC = 0x82, + DeltaTitle = 0x83, +}; + +bool operator>=(TitleType lhs, TitleType rhs); +bool operator<=(TitleType lhs, TitleType rhs); + +enum class ContentRecordType : u8 { + Meta = 0, + Program = 1, + Data = 2, + Control = 3, + Manual = 4, + Legal = 5, + Patch = 6, +}; + +struct ContentRecord { + std::array<u8, 0x20> hash; + std::array<u8, 0x10> nca_id; + std::array<u8, 0x6> size; + ContentRecordType type; + INSERT_PADDING_BYTES(1); +}; +static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size."); + +constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}}; + +struct MetaRecord { + u64_le title_id; + u32_le title_version; + TitleType type; + u8 install_byte; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(MetaRecord) == 0x10, "MetaRecord has incorrect size."); + +struct OptionalHeader { + u64_le title_id; + u64_le minimum_version; +}; +static_assert(sizeof(OptionalHeader) == 0x10, "OptionalHeader has incorrect size."); + +struct CNMTHeader { + u64_le title_id; + u32_le title_version; + TitleType type; + INSERT_PADDING_BYTES(1); + u16_le table_offset; + u16_le number_content_entries; + u16_le number_meta_entries; + INSERT_PADDING_BYTES(12); +}; +static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size."); + +// A class representing the format used by NCA metadata files, typically named {}.cnmt.nca or +// meta0.ncd. These describe which NCA's belong with which titles in the registered cache. +class CNMT { +public: + explicit CNMT(VirtualFile file); + CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records, + std::vector<MetaRecord> meta_records); + + u64 GetTitleID() const; + u32 GetTitleVersion() const; + TitleType GetType() const; + + const std::vector<ContentRecord>& GetContentRecords() const; + const std::vector<MetaRecord>& GetMetaRecords() const; + + bool UnionRecords(const CNMT& other); + std::vector<u8> Serialize() const; + +private: + CNMTHeader header; + OptionalHeader opt_header; + std::vector<ContentRecord> content_records; + std::vector<MetaRecord> meta_records; + + // TODO(DarkLordZach): According to switchbrew, for Patch-type there is additional data + // after the table. This is not documented, unfortunately. +}; + +} // namespace FileSys diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp new file mode 100644 index 000000000..d25eeee34 --- /dev/null +++ b/src/core/file_sys/registered_cache.cpp @@ -0,0 +1,478 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <regex> +#include <mbedtls/sha256.h> +#include "common/assert.h" +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "core/crypto/encryption_layer.h" +#include "core/file_sys/card_image.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/registered_cache.h" +#include "core/file_sys/vfs_concat.h" + +namespace FileSys { +std::string RegisteredCacheEntry::DebugInfo() const { + return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type)); +} + +bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) { + return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type); +} + +static bool FollowsTwoDigitDirFormat(std::string_view name) { + static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript | + std::regex_constants::icase); + return std::regex_match(name.begin(), name.end(), two_digit_regex); +} + +static bool FollowsNcaIdFormat(std::string_view name) { + static const std::regex nca_id_regex("[0-9A-F]{32}\\.nca", std::regex_constants::ECMAScript | + std::regex_constants::icase); + return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex); +} + +static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper, + bool within_two_digit) { + if (!within_two_digit) + return fmt::format("/{}.nca", Common::HexArrayToString(nca_id, second_hex_upper)); + + Core::Crypto::SHA256Hash hash{}; + mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0); + return fmt::format("/000000{:02X}/{}.nca", hash[0], + Common::HexArrayToString(nca_id, second_hex_upper)); +} + +static std::string GetCNMTName(TitleType type, u64 title_id) { + constexpr std::array<const char*, 9> TITLE_TYPE_NAMES{ + "SystemProgram", + "SystemData", + "SystemUpdate", + "BootImagePackage", + "BootImagePackageSafe", + "Application", + "Patch", + "AddOnContent", + "" ///< Currently unknown 'DeltaTitle' + }; + + auto index = static_cast<size_t>(type); + // If the index is after the jump in TitleType, subtract it out. + if (index >= static_cast<size_t>(TitleType::Application)) { + index -= static_cast<size_t>(TitleType::Application) - + static_cast<size_t>(TitleType::FirmwarePackageB); + } + return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); +} + +static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { + switch (type) { + case NCAContentType::Program: + // TODO(DarkLordZach): Differentiate between Program and Patch + return ContentRecordType::Program; + case NCAContentType::Meta: + return ContentRecordType::Meta; + case NCAContentType::Control: + return ContentRecordType::Control; + case NCAContentType::Data: + return ContentRecordType::Data; + case NCAContentType::Manual: + // TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal. + return ContentRecordType::Manual; + default: + UNREACHABLE(); + } +} + +VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, + std::string_view path) const { + if (dir->GetFileRelative(path) != nullptr) + return dir->GetFileRelative(path); + if (dir->GetDirectoryRelative(path) != nullptr) { + const auto nca_dir = dir->GetDirectoryRelative(path); + VirtualFile file = nullptr; + + const auto files = nca_dir->GetFiles(); + if (files.size() == 1 && files[0]->GetName() == "00") { + file = files[0]; + } else { + std::vector<VirtualFile> concat; + // Since the files are a two-digit hex number, max is FF. + for (size_t i = 0; i < 0x100; ++i) { + auto next = nca_dir->GetFile(fmt::format("{:02X}", i)); + if (next != nullptr) { + concat.push_back(std::move(next)); + } else { + next = nca_dir->GetFile(fmt::format("{:02x}", i)); + if (next != nullptr) + concat.push_back(std::move(next)); + else + break; + } + } + + if (concat.empty()) + return nullptr; + + file = FileSys::ConcatenateFiles(concat); + } + + return file; + } + return nullptr; +} + +VirtualFile RegisteredCache::GetFileAtID(NcaID id) const { + VirtualFile file; + // Try all four modes of file storage: + // (bit 1 = uppercase/lower, bit 0 = within a two-digit dir) + // 00: /000000**/{:032X}.nca + // 01: /{:032X}.nca + // 10: /000000**/{:032x}.nca + // 11: /{:032x}.nca + for (u8 i = 0; i < 4; ++i) { + const auto path = GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0); + file = OpenFileOrDirectoryConcat(dir, path); + if (file != nullptr) + return file; + } + return file; +} + +static boost::optional<NcaID> CheckMapForContentRecord( + const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) { + if (map.find(title_id) == map.end()) + return boost::none; + + const auto& cnmt = map.at(title_id); + + const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(), + [type](const ContentRecord& rec) { return rec.type == type; }); + if (iter == cnmt.GetContentRecords().end()) + return boost::none; + + return boost::make_optional(iter->nca_id); +} + +boost::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id, + ContentRecordType type) const { + if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end()) + return meta_id.at(title_id); + + const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type); + if (res1 != boost::none) + return res1; + return CheckMapForContentRecord(meta, title_id, type); +} + +std::vector<NcaID> RegisteredCache::AccumulateFiles() const { + std::vector<NcaID> ids; + for (const auto& d2_dir : dir->GetSubdirectories()) { + if (FollowsNcaIdFormat(d2_dir->GetName())) { + ids.push_back(Common::HexStringToArray<0x10, true>(d2_dir->GetName().substr(0, 0x20))); + continue; + } + + if (!FollowsTwoDigitDirFormat(d2_dir->GetName())) + continue; + + for (const auto& nca_dir : d2_dir->GetSubdirectories()) { + if (!FollowsNcaIdFormat(nca_dir->GetName())) + continue; + + ids.push_back(Common::HexStringToArray<0x10, true>(nca_dir->GetName().substr(0, 0x20))); + } + + for (const auto& nca_file : d2_dir->GetFiles()) { + if (!FollowsNcaIdFormat(nca_file->GetName())) + continue; + + ids.push_back( + Common::HexStringToArray<0x10, true>(nca_file->GetName().substr(0, 0x20))); + } + } + + for (const auto& d2_file : dir->GetFiles()) { + if (FollowsNcaIdFormat(d2_file->GetName())) + ids.push_back(Common::HexStringToArray<0x10, true>(d2_file->GetName().substr(0, 0x20))); + } + return ids; +} + +void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) { + for (const auto& id : ids) { + const auto file = GetFileAtID(id); + + if (file == nullptr) + continue; + const auto nca = std::make_shared<NCA>(parser(file, id)); + if (nca->GetStatus() != Loader::ResultStatus::Success || + nca->GetType() != NCAContentType::Meta) { + continue; + } + + const auto section0 = nca->GetSubdirectories()[0]; + + for (const auto& file : section0->GetFiles()) { + if (file->GetExtension() != "cnmt") + continue; + + meta.insert_or_assign(nca->GetTitleId(), CNMT(file)); + meta_id.insert_or_assign(nca->GetTitleId(), id); + break; + } + } +} + +void RegisteredCache::AccumulateYuzuMeta() { + const auto dir = this->dir->GetSubdirectory("yuzu_meta"); + if (dir == nullptr) + return; + + for (const auto& file : dir->GetFiles()) { + if (file->GetExtension() != "cnmt") + continue; + + CNMT cnmt(file); + yuzu_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt)); + } +} + +void RegisteredCache::Refresh() { + if (dir == nullptr) + return; + const auto ids = AccumulateFiles(); + ProcessFiles(ids); + AccumulateYuzuMeta(); +} + +RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function) + : dir(std::move(dir_)), parser(std::move(parsing_function)) { + Refresh(); +} + +bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const { + return GetEntryRaw(title_id, type) != nullptr; +} + +bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const { + return GetEntryRaw(entry) != nullptr; +} + +VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const { + const auto id = GetNcaIDFromMetadata(title_id, type); + if (id == boost::none) + return nullptr; + + return parser(GetFileAtID(id.get()), id.get()); +} + +VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const { + return GetEntryRaw(entry.title_id, entry.type); +} + +std::shared_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const { + const auto raw = GetEntryRaw(title_id, type); + if (raw == nullptr) + return nullptr; + return std::make_shared<NCA>(raw); +} + +std::shared_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const { + return GetEntry(entry.title_id, entry.type); +} + +template <typename T> +void RegisteredCache::IterateAllMetadata( + std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc, + std::function<bool(const CNMT&, const ContentRecord&)> filter) const { + for (const auto& kv : meta) { + const auto& cnmt = kv.second; + if (filter(cnmt, EMPTY_META_CONTENT_RECORD)) + out.push_back(proc(cnmt, EMPTY_META_CONTENT_RECORD)); + for (const auto& rec : cnmt.GetContentRecords()) { + if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) { + out.push_back(proc(cnmt, rec)); + } + } + } + for (const auto& kv : yuzu_meta) { + const auto& cnmt = kv.second; + for (const auto& rec : cnmt.GetContentRecords()) { + if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) { + out.push_back(proc(cnmt, rec)); + } + } + } +} + +std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const { + std::vector<RegisteredCacheEntry> out; + IterateAllMetadata<RegisteredCacheEntry>( + out, + [](const CNMT& c, const ContentRecord& r) { + return RegisteredCacheEntry{c.GetTitleID(), r.type}; + }, + [](const CNMT& c, const ContentRecord& r) { return true; }); + return out; +} + +std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter( + boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type, + boost::optional<u64> title_id) const { + std::vector<RegisteredCacheEntry> out; + IterateAllMetadata<RegisteredCacheEntry>( + out, + [](const CNMT& c, const ContentRecord& r) { + return RegisteredCacheEntry{c.GetTitleID(), r.type}; + }, + [&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) { + if (title_type != boost::none && title_type.get() != c.GetType()) + return false; + if (record_type != boost::none && record_type.get() != r.type) + return false; + if (title_id != boost::none && title_id.get() != c.GetTitleID()) + return false; + return true; + }); + return out; +} + +static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) { + const auto filename = fmt::format("{}.nca", Common::HexArrayToString(id, false)); + const auto iter = + std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(), + [&filename](std::shared_ptr<NCA> nca) { return nca->GetName() == filename; }); + return iter == xci->GetNCAs().end() ? nullptr : *iter; +} + +InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists, + const VfsCopyFunction& copy) { + const auto& ncas = xci->GetNCAs(); + const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) { + return nca->GetType() == NCAContentType::Meta; + }); + + if (meta_iter == ncas.end()) { + LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and " + "is therefore malformed. Double check your encryption keys."); + return InstallResult::ErrorMetaFailed; + } + + // Install Metadata File + const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32); + const auto meta_id = Common::HexStringToArray<16>(meta_id_raw); + + const auto res = RawInstallNCA(*meta_iter, copy, overwrite_if_exists, meta_id); + if (res != InstallResult::Success) + return res; + + // Install all the other NCAs + const auto section0 = (*meta_iter)->GetSubdirectories()[0]; + const auto cnmt_file = section0->GetFiles()[0]; + const CNMT cnmt(cnmt_file); + for (const auto& record : cnmt.GetContentRecords()) { + const auto nca = GetNCAFromXCIForID(xci, record.nca_id); + if (nca == nullptr) + return InstallResult::ErrorCopyFailed; + const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id); + if (res2 != InstallResult::Success) + return res2; + } + + Refresh(); + return InstallResult::Success; +} + +InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType type, + bool overwrite_if_exists, const VfsCopyFunction& copy) { + CNMTHeader header{ + nca->GetTitleId(), ///< Title ID + 0, ///< Ignore/Default title version + type, ///< Type + {}, ///< Padding + 0x10, ///< Default table offset + 1, ///< 1 Content Entry + 0, ///< No Meta Entries + {}, ///< Padding + }; + OptionalHeader opt_header{0, 0}; + ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca->GetType()), {}}; + const auto& data = nca->GetBaseFile()->ReadBytes(0x100000); + mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0); + memcpy(&c_rec.nca_id, &c_rec.hash, 16); + const CNMT new_cnmt(header, opt_header, {c_rec}, {}); + if (!RawInstallYuzuMeta(new_cnmt)) + return InstallResult::ErrorMetaFailed; + return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); +} + +InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy, + bool overwrite_if_exists, + boost::optional<NcaID> override_id) { + const auto in = nca->GetBaseFile(); + Core::Crypto::SHA256Hash hash{}; + + // Calculate NcaID + // NOTE: Because computing the SHA256 of an entire NCA is quite expensive (especially if the + // game is massive), we're going to cheat and only hash the first MB of the NCA. + // Also, for XCIs the NcaID matters, so if the override id isn't none, use that. + NcaID id{}; + if (override_id == boost::none) { + const auto& data = in->ReadBytes(0x100000); + mbedtls_sha256(data.data(), data.size(), hash.data(), 0); + memcpy(id.data(), hash.data(), 16); + } else { + id = override_id.get(); + } + + std::string path = GetRelativePathFromNcaID(id, false, true); + + if (GetFileAtID(id) != nullptr && !overwrite_if_exists) { + LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping..."); + return InstallResult::ErrorAlreadyExists; + } + + if (GetFileAtID(id) != nullptr) { + LOG_WARNING(Loader, "Overwriting existing NCA..."); + VirtualDir c_dir; + { c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); } + c_dir->DeleteFile(FileUtil::GetFilename(path)); + } + + auto out = dir->CreateFileRelative(path); + if (out == nullptr) + return InstallResult::ErrorCopyFailed; + return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed; +} + +bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { + // Reasoning behind this method can be found in the comment for InstallEntry, NCA overload. + const auto dir = this->dir->CreateDirectoryRelative("yuzu_meta"); + const auto filename = GetCNMTName(cnmt.GetType(), cnmt.GetTitleID()); + if (dir->GetFile(filename) == nullptr) { + auto out = dir->CreateFile(filename); + const auto buffer = cnmt.Serialize(); + out->Resize(buffer.size()); + out->WriteBytes(buffer); + } else { + auto out = dir->GetFile(filename); + CNMT old_cnmt(out); + // Returns true on change + if (old_cnmt.UnionRecords(cnmt)) { + out->Resize(0); + const auto buffer = old_cnmt.Serialize(); + out->Resize(buffer.size()); + out->WriteBytes(buffer); + } + } + Refresh(); + return std::find_if(yuzu_meta.begin(), yuzu_meta.end(), + [&cnmt](const std::pair<u64, CNMT>& kv) { + return kv.second.GetType() == cnmt.GetType() && + kv.second.GetTitleID() == cnmt.GetTitleID(); + }) != yuzu_meta.end(); +} +} // namespace FileSys diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h new file mode 100644 index 000000000..a7c51a59c --- /dev/null +++ b/src/core/file_sys/registered_cache.h @@ -0,0 +1,124 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <functional> +#include <map> +#include <memory> +#include <string> +#include <vector> +#include <boost/container/flat_map.hpp> +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "content_archive.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { +class XCI; +class CNMT; + +using NcaID = std::array<u8, 0x10>; +using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; +using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>; + +enum class InstallResult { + Success, + ErrorAlreadyExists, + ErrorCopyFailed, + ErrorMetaFailed, +}; + +struct RegisteredCacheEntry { + u64 title_id; + ContentRecordType type; + + std::string DebugInfo() const; +}; + +// boost flat_map requires operator< for O(log(n)) lookups. +bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); + +/* + * A class that catalogues NCAs in the registered directory structure. + * Nintendo's registered format follows this structure: + * + * Root + * | 000000XX <- XX is the ____ two digits of the NcaID + * | <hash>.nca <- hash is the NcaID (first half of SHA256 over entire file) (folder) + * | 00 + * | 01 <- Actual content split along 4GB boundaries. (optional) + * + * (This impl also supports substituting the nca dir for an nca file, as that's more convenient when + * 4GB splitting can be ignored.) + */ +class RegisteredCache { +public: + // Parsing function defines the conversion from raw file to NCA. If there are other steps + // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom + // parsing function. + explicit RegisteredCache(VirtualDir dir, + RegisteredCacheParsingFunction parsing_function = + [](const VirtualFile& file, const NcaID& id) { return file; }); + + void Refresh(); + + bool HasEntry(u64 title_id, ContentRecordType type) const; + bool HasEntry(RegisteredCacheEntry entry) const; + + VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const; + VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const; + + std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const; + std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const; + + std::vector<RegisteredCacheEntry> ListEntries() const; + // If a parameter is not boost::none, it will be filtered for from all entries. + std::vector<RegisteredCacheEntry> ListEntriesFilter( + boost::optional<TitleType> title_type = boost::none, + boost::optional<ContentRecordType> record_type = boost::none, + boost::optional<u64> title_id = boost::none) const; + + // Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there + // is a meta NCA and all of them are accessible. + InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false, + const VfsCopyFunction& copy = &VfsRawCopy); + + // Due to the fact that we must use Meta-type NCAs to determine the existance of files, this + // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a + // dir inside the NAND called 'yuzu_meta' and store the raw CNMT there. + // TODO(DarkLordZach): Author real meta-type NCAs and install those. + InstallResult InstallEntry(std::shared_ptr<NCA> nca, TitleType type, + bool overwrite_if_exists = false, + const VfsCopyFunction& copy = &VfsRawCopy); + +private: + template <typename T> + void IterateAllMetadata(std::vector<T>& out, + std::function<T(const CNMT&, const ContentRecord&)> proc, + std::function<bool(const CNMT&, const ContentRecord&)> filter) const; + std::vector<NcaID> AccumulateFiles() const; + void ProcessFiles(const std::vector<NcaID>& ids); + void AccumulateYuzuMeta(); + boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const; + VirtualFile GetFileAtID(NcaID id) const; + VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const; + InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy, + bool overwrite_if_exists, + boost::optional<NcaID> override_id = boost::none); + bool RawInstallYuzuMeta(const CNMT& cnmt); + + VirtualDir dir; + RegisteredCacheParsingFunction parser; + // maps tid -> NcaID of meta + boost::container::flat_map<u64, NcaID> meta_id; + // maps tid -> meta + boost::container::flat_map<u64, CNMT> meta; + // maps tid -> meta for CNMT in yuzu_meta + boost::container::flat_map<u64, CNMT> yuzu_meta; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp index ff3ddb29c..e490c8ace 100644 --- a/src/core/file_sys/romfs.cpp +++ b/src/core/file_sys/romfs.cpp @@ -65,7 +65,7 @@ void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 t auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset); parent->AddFile(std::make_shared<OffsetVfsFile>( - file, entry.first.size, entry.first.offset + data_offset, entry.second, parent)); + file, entry.first.size, entry.first.offset + data_offset, entry.second)); if (entry.first.sibling == ROMFS_ENTRY_EMPTY) break; @@ -79,7 +79,7 @@ void ProcessDirectory(VirtualFile file, size_t dir_offset, size_t file_offset, s while (true) { auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset); auto current = std::make_shared<VectorVfsDirectory>( - std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parent, entry.second); + std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, entry.second); if (entry.first.child_file != ROMFS_ENTRY_EMPTY) { ProcessFile(file, file_offset, data_offset, entry.first.child_file, current); @@ -108,9 +108,9 @@ VirtualDir ExtractRomFS(VirtualFile file) { const u64 file_offset = header.file_meta.offset; const u64 dir_offset = header.directory_meta.offset + 4; - const auto root = + auto root = std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, - file->GetContainingDirectory(), file->GetName()); + file->GetName(), file->GetContainingDirectory()); ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root); diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp new file mode 100644 index 000000000..e6bf586a3 --- /dev/null +++ b/src/core/file_sys/vfs_concat.cpp @@ -0,0 +1,94 @@ +// 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_concat.h" + +namespace FileSys { + +VirtualFile ConcatenateFiles(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))); +} + +ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name) + : name(std::move(name)) { + size_t next_offset = 0; + for (const auto& file : files_) { + files[next_offset] = file; + next_offset += file->GetSize(); + } +} + +std::string ConcatenatedVfsFile::GetName() const { + if (files.empty()) + return ""; + if (!name.empty()) + return name; + return files.begin()->second->GetName(); +} + +size_t ConcatenatedVfsFile::GetSize() const { + if (files.empty()) + return 0; + return files.rbegin()->first + files.rbegin()->second->GetSize(); +} + +bool ConcatenatedVfsFile::Resize(size_t new_size) { + return false; +} + +std::shared_ptr<VfsDirectory> ConcatenatedVfsFile::GetContainingDirectory() const { + if (files.empty()) + return nullptr; + return files.begin()->second->GetContainingDirectory(); +} + +bool ConcatenatedVfsFile::IsWritable() const { + return false; +} + +bool ConcatenatedVfsFile::IsReadable() const { + return true; +} + +size_t ConcatenatedVfsFile::Read(u8* data, size_t length, size_t offset) const { + auto entry = files.end(); + for (auto iter = files.begin(); iter != files.end(); ++iter) { + if (iter->first > offset) { + entry = --iter; + break; + } + } + + // 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()) + 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); + } + + return entry->second->Read(data, length, offset - entry->first); +} + +size_t ConcatenatedVfsFile::Write(const u8* data, size_t length, size_t offset) { + return 0; +} + +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 new file mode 100644 index 000000000..686d32515 --- /dev/null +++ b/src/core/file_sys/vfs_concat.h @@ -0,0 +1,41 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string_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); + +public: + std::string GetName() const override; + size_t GetSize() const override; + bool Resize(size_t new_size) override; + std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; + bool IsWritable() const override; + bool IsReadable() const override; + size_t Read(u8* data, size_t length, size_t offset) const override; + size_t Write(const u8* data, size_t length, size_t offset) override; + bool Rename(std::string_view name) override; + +private: + // Maps starting offset to file -- more efficient. + boost::container::flat_map<u64, VirtualFile> files; + std::string name; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 1b5919737..0afe515f0 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -83,8 +83,12 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); - if (!FileUtil::Exists(path) && !FileUtil::CreateEmptyFile(path)) - return nullptr; + const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash); + if (!FileUtil::Exists(path)) { + FileUtil::CreateFullPath(path_fwd); + if (!FileUtil::CreateEmptyFile(path)) + return nullptr; + } return OpenFile(path, perms); } @@ -140,8 +144,12 @@ VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) { const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); - if (!FileUtil::Exists(path) && !FileUtil::CreateDir(path)) - return nullptr; + const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash); + if (!FileUtil::Exists(path)) { + FileUtil::CreateFullPath(path_fwd); + if (!FileUtil::CreateDir(path)) + return nullptr; + } // Cannot use make_shared as RealVfsDirectory constructor is private return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); } @@ -306,14 +314,14 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const { const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); - if (!FileUtil::Exists(full_path)) + if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path)) return nullptr; return base.OpenFile(full_path, perms); } std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const { const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); - if (!FileUtil::Exists(full_path)) + if (!FileUtil::Exists(full_path) || !FileUtil::IsDirectory(full_path)) return nullptr; return base.OpenDirectory(full_path, perms); } diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index 8a1e79ef6..989803d43 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -5,7 +5,6 @@ #pragma once #include <string_view> - #include <boost/container/flat_map.hpp> #include "common/file_util.h" #include "core/file_sys/mode.h" diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp index fda603960..98e7c4598 100644 --- a/src/core/file_sys/vfs_vector.cpp +++ b/src/core/file_sys/vfs_vector.cpp @@ -8,8 +8,8 @@ namespace FileSys { VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_, - std::vector<VirtualDir> dirs_, VirtualDir parent_, - std::string name_) + std::vector<VirtualDir> dirs_, std::string name_, + VirtualDir parent_) : files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)), name(std::move(name_)) {} diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h index b3b468233..179f62e4b 100644 --- a/src/core/file_sys/vfs_vector.h +++ b/src/core/file_sys/vfs_vector.h @@ -13,8 +13,8 @@ namespace FileSys { class VectorVfsDirectory : public VfsDirectory { public: explicit VectorVfsDirectory(std::vector<VirtualFile> files = {}, - std::vector<VirtualDir> dirs = {}, VirtualDir parent = nullptr, - std::string name = ""); + std::vector<VirtualDir> dirs = {}, std::string name = "", + VirtualDir parent = nullptr); std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 384dc7822..7006a37b3 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -34,9 +34,9 @@ class EmuWindow { public: /// Data structure to store emuwindow configuration struct WindowConfig { - bool fullscreen; - int res_width; - int res_height; + bool fullscreen = false; + int res_width = 0; + int res_height = 0; std::pair<unsigned, unsigned> min_client_area_size; }; diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index d09ca5992..51a1ec160 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -152,7 +152,7 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) { // Handle scenario when ConvertToDomain command was issued, as we must do the conversion at the // end of the command such that only commands following this one are handled as domains if (convert_to_domain) { - ASSERT_MSG(domain_request_handlers.empty(), "already a domain"); + ASSERT_MSG(IsSession(), "ServerSession is already a domain instance."); domain_request_handlers = {hle_handler}; convert_to_domain = false; } diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index 2bce54fee..1a88e66b9 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -97,7 +97,12 @@ public: /// Returns true if the session has been converted to a domain, otherwise False bool IsDomain() const { - return !domain_request_handlers.empty(); + return !IsSession(); + } + + /// Returns true if this session has not been converted to a domain, otherwise false. + bool IsSession() const { + return domain_request_handlers.empty(); } /// Converts the session to a domain at the end of the current command diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index b24f409b3..6be5c474e 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -250,8 +250,11 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) { } /// Break program execution -static void Break(u64 unk_0, u64 unk_1, u64 unk_2) { - LOG_CRITICAL(Debug_Emulated, "Emulated program broke execution!"); +static void Break(u64 reason, u64 info1, u64 info2) { + LOG_CRITICAL( + Debug_Emulated, + "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", + reason, info1, info2); ASSERT(false); } diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index a1a7867ce..cf4f94822 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -167,7 +167,7 @@ void Thread::WakeAfterDelay(s64 nanoseconds) { } void Thread::CancelWakeupTimer() { - CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle); + CoreTiming::UnscheduleEventThreadsafe(ThreadWakeupEventType, callback_handle); } static boost::optional<s32> GetNextProcessorId(u64 mask) { diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index f3c5b1b9c..979f2f892 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -3,7 +3,10 @@ // Refer to the license.txt file included. #include <array> +#include "common/common_types.h" #include "common/logging/log.h" +#include "common/swap.h" +#include "core/core_timing.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/acc/acc.h" #include "core/hle/service/acc/acc_aa.h" @@ -13,7 +16,6 @@ #include "core/settings.h" namespace Service::Account { - // TODO: RE this structure struct UserData { INSERT_PADDING_WORDS(1); @@ -25,19 +27,13 @@ struct UserData { }; static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size"); -struct ProfileBase { - u128 user_id; - u64 timestamp; - std::array<u8, 0x20> username; -}; -static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase structure has incorrect size"); - // TODO(ogniK): Generate a real user id based on username, md5(username) maybe? -static constexpr u128 DEFAULT_USER_ID{1ull, 0ull}; +static UUID DEFAULT_USER_ID{1ull, 0ull}; class IProfile final : public ServiceFramework<IProfile> { public: - explicit IProfile(u128 user_id) : ServiceFramework("IProfile"), user_id(user_id) { + explicit IProfile(UUID user_id, ProfileManager& profile_manager) + : ServiceFramework("IProfile"), user_id(user_id), profile_manager(profile_manager) { static const FunctionInfo functions[] = { {0, &IProfile::Get, "Get"}, {1, &IProfile::GetBase, "GetBase"}, @@ -49,38 +45,34 @@ public: private: void Get(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); + LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); ProfileBase profile_base{}; - profile_base.user_id = user_id; - if (Settings::values.username.size() > profile_base.username.size()) { - std::copy_n(Settings::values.username.begin(), profile_base.username.size(), - profile_base.username.begin()); + std::array<u8, MAX_DATA> data{}; + if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) { + ctx.WriteBuffer(data); + IPC::ResponseBuilder rb{ctx, 16}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(profile_base); } else { - std::copy(Settings::values.username.begin(), Settings::values.username.end(), - profile_base.username.begin()); + LOG_ERROR(Service_ACC, "Failed to get profile base and data for user={}", + user_id.Format()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); // TODO(ogniK): Get actual error code } - - IPC::ResponseBuilder rb{ctx, 16}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(profile_base); } void GetBase(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - - // TODO(Subv): Retrieve this information from somewhere. + LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); ProfileBase profile_base{}; - profile_base.user_id = user_id; - if (Settings::values.username.size() > profile_base.username.size()) { - std::copy_n(Settings::values.username.begin(), profile_base.username.size(), - profile_base.username.begin()); + if (profile_manager.GetProfileBase(user_id, profile_base)) { + IPC::ResponseBuilder rb{ctx, 16}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(profile_base); } else { - std::copy(Settings::values.username.begin(), Settings::values.username.end(), - profile_base.username.begin()); + LOG_ERROR(Service_ACC, "Failed to get profile base for user={}", user_id.Format()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); // TODO(ogniK): Get actual error code } - IPC::ResponseBuilder rb{ctx, 16}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(profile_base); } void LoadImage(Kernel::HLERequestContext& ctx) { @@ -104,7 +96,8 @@ private: rb.Push<u32>(jpeg_size); } - u128 user_id; ///< The user id this profile refers to. + const ProfileManager& profile_manager; + UUID user_id; ///< The user id this profile refers to. }; class IManagerForApplication final : public ServiceFramework<IManagerForApplication> { @@ -141,44 +134,57 @@ private: }; void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); + LOG_INFO(Service_ACC, "called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(1); + rb.Push<u32>(static_cast<u32>(profile_manager->GetUserCount())); } void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + UUID user_id = rp.PopRaw<UUID>(); + LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(true); // TODO: Check when this is supposed to return true and when not + rb.Push(profile_manager->UserExists(user_id)); } void Module::Interface::ListAllUsers(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - // TODO(Subv): There is only one user for now. - const std::vector<u128> user_ids = {DEFAULT_USER_ID}; - ctx.WriteBuffer(user_ids); + LOG_INFO(Service_ACC, "called"); + ctx.WriteBuffer(profile_manager->GetAllUsers()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void Module::Interface::ListOpenUsers(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - // TODO(Subv): There is only one user for now. - const std::vector<u128> user_ids = {DEFAULT_USER_ID}; - ctx.WriteBuffer(user_ids); + LOG_INFO(Service_ACC, "called"); + ctx.WriteBuffer(profile_manager->GetOpenUsers()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } +void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_ACC, "called"); + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<UUID>(profile_manager->GetLastOpenedUser()); +} + void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - u128 user_id = rp.PopRaw<u128>(); + UUID user_id = rp.PopRaw<UUID>(); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IProfile>(user_id); - LOG_DEBUG(Service_ACC, "called user_id=0x{:016X}{:016X}", user_id[1], user_id[0]); + rb.PushIpcInterface<IProfile>(user_id, *profile_manager); + LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); +} + +void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(profile_manager->CanSystemRegisterUser()); } void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) { @@ -194,22 +200,18 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo LOG_DEBUG(Service_ACC, "called"); } -void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 6}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(DEFAULT_USER_ID); -} - -Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) - : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::Interface(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager, const char* name) + : ServiceFramework(name), module(std::move(module)), + profile_manager(std::move(profile_manager)) {} void InstallInterfaces(SM::ServiceManager& service_manager) { auto module = std::make_shared<Module>(); - std::make_shared<ACC_AA>(module)->InstallAsService(service_manager); - std::make_shared<ACC_SU>(module)->InstallAsService(service_manager); - std::make_shared<ACC_U0>(module)->InstallAsService(service_manager); - std::make_shared<ACC_U1>(module)->InstallAsService(service_manager); + auto profile_manager = std::make_shared<ProfileManager>(); + std::make_shared<ACC_AA>(module, profile_manager)->InstallAsService(service_manager); + std::make_shared<ACC_SU>(module, profile_manager)->InstallAsService(service_manager); + std::make_shared<ACC_U0>(module, profile_manager)->InstallAsService(service_manager); + std::make_shared<ACC_U1>(module, profile_manager)->InstallAsService(service_manager); } } // namespace Service::Account diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index 88cabaa01..d7c6d2415 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.h @@ -4,6 +4,7 @@ #pragma once +#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/service.h" namespace Service::Account { @@ -12,7 +13,8 @@ class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, const char* name); + explicit Interface(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager, const char* name); void GetUserCount(Kernel::HLERequestContext& ctx); void GetUserExistence(Kernel::HLERequestContext& ctx); @@ -22,9 +24,11 @@ public: void GetProfile(Kernel::HLERequestContext& ctx); void InitializeApplicationInfo(Kernel::HLERequestContext& ctx); void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx); + void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx); protected: std::shared_ptr<Module> module; + std::shared_ptr<ProfileManager> profile_manager; }; }; diff --git a/src/core/hle/service/acc/acc_aa.cpp b/src/core/hle/service/acc/acc_aa.cpp index 280b3e464..9bd595a37 100644 --- a/src/core/hle/service/acc/acc_aa.cpp +++ b/src/core/hle/service/acc/acc_aa.cpp @@ -6,7 +6,8 @@ namespace Service::Account { -ACC_AA::ACC_AA(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:aa") { +ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) + : Module::Interface(std::move(module), std::move(profile_manager), "acc:aa") { static const FunctionInfo functions[] = { {0, nullptr, "EnsureCacheAsync"}, {1, nullptr, "LoadCache"}, diff --git a/src/core/hle/service/acc/acc_aa.h b/src/core/hle/service/acc/acc_aa.h index 796f7ef85..2e08c781a 100644 --- a/src/core/hle/service/acc/acc_aa.h +++ b/src/core/hle/service/acc/acc_aa.h @@ -10,7 +10,8 @@ namespace Service::Account { class ACC_AA final : public Module::Interface { public: - explicit ACC_AA(std::shared_ptr<Module> module); + explicit ACC_AA(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager); }; } // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp index 8b2a71f37..0218ee859 100644 --- a/src/core/hle/service/acc/acc_su.cpp +++ b/src/core/hle/service/acc/acc_su.cpp @@ -6,7 +6,8 @@ namespace Service::Account { -ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:su") { +ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) + : Module::Interface(std::move(module), std::move(profile_manager), "acc:su") { static const FunctionInfo functions[] = { {0, &ACC_SU::GetUserCount, "GetUserCount"}, {1, &ACC_SU::GetUserExistence, "GetUserExistence"}, @@ -15,7 +16,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(mod {4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"}, {5, &ACC_SU::GetProfile, "GetProfile"}, {6, nullptr, "GetProfileDigest"}, - {50, nullptr, "IsUserRegistrationRequestPermitted"}, + {50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {51, nullptr, "TrySelectUserWithoutInteraction"}, {60, nullptr, "ListOpenContextStoredUsers"}, {100, nullptr, "GetUserRegistrationNotifier"}, diff --git a/src/core/hle/service/acc/acc_su.h b/src/core/hle/service/acc/acc_su.h index 3894a6991..79a47d88d 100644 --- a/src/core/hle/service/acc/acc_su.h +++ b/src/core/hle/service/acc/acc_su.h @@ -11,7 +11,8 @@ namespace Account { class ACC_SU final : public Module::Interface { public: - explicit ACC_SU(std::shared_ptr<Module> module); + explicit ACC_SU(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager); }; } // namespace Account diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp index d84c8b2e1..84a4d05b8 100644 --- a/src/core/hle/service/acc/acc_u0.cpp +++ b/src/core/hle/service/acc/acc_u0.cpp @@ -6,7 +6,8 @@ namespace Service::Account { -ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u0") { +ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) + : Module::Interface(std::move(module), std::move(profile_manager), "acc:u0") { static const FunctionInfo functions[] = { {0, &ACC_U0::GetUserCount, "GetUserCount"}, {1, &ACC_U0::GetUserExistence, "GetUserExistence"}, @@ -15,7 +16,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(mod {4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"}, {5, &ACC_U0::GetProfile, "GetProfile"}, {6, nullptr, "GetProfileDigest"}, - {50, nullptr, "IsUserRegistrationRequestPermitted"}, + {50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {51, nullptr, "TrySelectUserWithoutInteraction"}, {60, nullptr, "ListOpenContextStoredUsers"}, {100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"}, diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h index 6ded596b3..e8a114f99 100644 --- a/src/core/hle/service/acc/acc_u0.h +++ b/src/core/hle/service/acc/acc_u0.h @@ -10,7 +10,8 @@ namespace Service::Account { class ACC_U0 final : public Module::Interface { public: - explicit ACC_U0(std::shared_ptr<Module> module); + explicit ACC_U0(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager); }; } // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp index 0ceaf06b5..495693949 100644 --- a/src/core/hle/service/acc/acc_u1.cpp +++ b/src/core/hle/service/acc/acc_u1.cpp @@ -6,7 +6,8 @@ namespace Service::Account { -ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u1") { +ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) + : Module::Interface(std::move(module), std::move(profile_manager), "acc:u1") { static const FunctionInfo functions[] = { {0, &ACC_U1::GetUserCount, "GetUserCount"}, {1, &ACC_U1::GetUserExistence, "GetUserExistence"}, @@ -15,7 +16,7 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(mod {4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"}, {5, &ACC_U1::GetProfile, "GetProfile"}, {6, nullptr, "GetProfileDigest"}, - {50, nullptr, "IsUserRegistrationRequestPermitted"}, + {50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {51, nullptr, "TrySelectUserWithoutInteraction"}, {60, nullptr, "ListOpenContextStoredUsers"}, {100, nullptr, "GetUserRegistrationNotifier"}, diff --git a/src/core/hle/service/acc/acc_u1.h b/src/core/hle/service/acc/acc_u1.h index 5e3e7659b..a77520e6f 100644 --- a/src/core/hle/service/acc/acc_u1.h +++ b/src/core/hle/service/acc/acc_u1.h @@ -10,7 +10,8 @@ namespace Service::Account { class ACC_U1 final : public Module::Interface { public: - explicit ACC_U1(std::shared_ptr<Module> module); + explicit ACC_U1(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager); }; } // namespace Service::Account diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp new file mode 100644 index 000000000..62c2121fa --- /dev/null +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -0,0 +1,226 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <boost/optional.hpp> +#include "core/hle/service/acc/profile_manager.h" +#include "core/settings.h" + +namespace Service::Account { +// TODO(ogniK): Get actual error codes +constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, -1); +constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, -2); +constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20); + +ProfileManager::ProfileManager() { + // TODO(ogniK): Create the default user we have for now until loading/saving users is added + auto user_uuid = UUID{1, 0}; + CreateNewUser(user_uuid, Settings::values.username); + OpenUser(user_uuid); +} + +/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the +/// internal management of the users profiles +boost::optional<size_t> ProfileManager::AddToProfiles(const ProfileInfo& user) { + if (user_count >= MAX_USERS) { + return boost::none; + } + profiles[user_count] = std::move(user); + return user_count++; +} + +/// Deletes a specific profile based on it's profile index +bool ProfileManager::RemoveProfileAtIndex(size_t index) { + if (index >= MAX_USERS || index >= user_count) { + return false; + } + if (index < user_count - 1) { + std::rotate(profiles.begin() + index, profiles.begin() + index + 1, profiles.end()); + } + profiles.back() = {}; + user_count--; + return true; +} + +/// Helper function to register a user to the system +ResultCode ProfileManager::AddUser(ProfileInfo user) { + if (AddToProfiles(user) == boost::none) { + return ERROR_TOO_MANY_USERS; + } + return RESULT_SUCCESS; +} + +/// Create a new user on the system. If the uuid of the user already exists, the user is not +/// created. +ResultCode ProfileManager::CreateNewUser(UUID uuid, std::array<u8, 0x20>& username) { + if (user_count == MAX_USERS) { + return ERROR_TOO_MANY_USERS; + } + if (!uuid) { + return ERROR_ARGUMENT_IS_NULL; + } + if (username[0] == 0x0) { + return ERROR_ARGUMENT_IS_NULL; + } + if (std::any_of(profiles.begin(), profiles.end(), + [&uuid](const ProfileInfo& profile) { return uuid == profile.user_uuid; })) { + return ERROR_USER_ALREADY_EXISTS; + } + ProfileInfo profile; + profile.user_uuid = std::move(uuid); + profile.username = username; + profile.data = {}; + profile.creation_time = 0x0; + profile.is_open = false; + return AddUser(profile); +} + +/// Creates a new user on the system. This function allows a much simpler method of registration +/// specifically by allowing an std::string for the username. This is required specifically since +/// we're loading a string straight from the config +ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username) { + std::array<u8, 0x20> username_output; + if (username.size() > username_output.size()) { + std::copy_n(username.begin(), username_output.size(), username_output.begin()); + } else { + std::copy(username.begin(), username.end(), username_output.begin()); + } + return CreateNewUser(uuid, username_output); +} + +/// Returns a users profile index based on their user id. +boost::optional<size_t> ProfileManager::GetUserIndex(const UUID& uuid) const { + if (!uuid) { + return boost::none; + } + auto iter = std::find_if(profiles.begin(), profiles.end(), + [&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; }); + if (iter == profiles.end()) { + return boost::none; + } + return static_cast<size_t>(std::distance(profiles.begin(), iter)); +} + +/// Returns a users profile index based on their profile +boost::optional<size_t> ProfileManager::GetUserIndex(ProfileInfo user) const { + return GetUserIndex(user.user_uuid); +} + +/// Returns the data structure used by the switch when GetProfileBase is called on acc:* +bool ProfileManager::GetProfileBase(boost::optional<size_t> index, ProfileBase& profile) const { + if (index == boost::none || index >= MAX_USERS) { + return false; + } + const auto& prof_info = profiles[index.get()]; + profile.user_uuid = prof_info.user_uuid; + profile.username = prof_info.username; + profile.timestamp = prof_info.creation_time; + return true; +} + +/// Returns the data structure used by the switch when GetProfileBase is called on acc:* +bool ProfileManager::GetProfileBase(UUID uuid, ProfileBase& profile) const { + auto idx = GetUserIndex(uuid); + return GetProfileBase(idx, profile); +} + +/// Returns the data structure used by the switch when GetProfileBase is called on acc:* +bool ProfileManager::GetProfileBase(ProfileInfo user, ProfileBase& profile) const { + return GetProfileBase(user.user_uuid, profile); +} + +/// Returns the current user count on the system. We keep a variable which tracks the count so we +/// don't have to loop the internal profile array every call. +size_t ProfileManager::GetUserCount() const { + return user_count; +} + +/// Lists the current "opened" users on the system. Users are typically not open until they sign +/// into something or pick a profile. As of right now users should all be open until qlaunch is +/// booting +size_t ProfileManager::GetOpenUserCount() const { + return std::count_if(profiles.begin(), profiles.end(), + [](const ProfileInfo& p) { return p.is_open; }); +} + +/// Checks if a user id exists in our profile manager +bool ProfileManager::UserExists(UUID uuid) const { + return (GetUserIndex(uuid) != boost::none); +} + +/// Opens a specific user +void ProfileManager::OpenUser(UUID uuid) { + auto idx = GetUserIndex(uuid); + if (idx == boost::none) { + return; + } + profiles[idx.get()].is_open = true; + last_opened_user = uuid; +} + +/// Closes a specific user +void ProfileManager::CloseUser(UUID uuid) { + auto idx = GetUserIndex(uuid); + if (idx == boost::none) { + return; + } + profiles[idx.get()].is_open = false; +} + +/// Gets all valid user ids on the system +std::array<UUID, MAX_USERS> ProfileManager::GetAllUsers() const { + std::array<UUID, MAX_USERS> output; + std::transform(profiles.begin(), profiles.end(), output.begin(), + [](const ProfileInfo& p) { return p.user_uuid; }); + return output; +} + +/// Get all the open users on the system and zero out the rest of the data. This is specifically +/// needed for GetOpenUsers and we need to ensure the rest of the output buffer is zero'd out +std::array<UUID, MAX_USERS> ProfileManager::GetOpenUsers() const { + std::array<UUID, MAX_USERS> output; + std::transform(profiles.begin(), profiles.end(), output.begin(), [](const ProfileInfo& p) { + if (p.is_open) + return p.user_uuid; + return UUID{}; + }); + std::stable_partition(output.begin(), output.end(), [](const UUID& uuid) { return uuid; }); + return output; +} + +/// Returns the last user which was opened +UUID ProfileManager::GetLastOpenedUser() const { + return last_opened_user; +} + +/// Return the users profile base and the unknown arbitary data. +bool ProfileManager::GetProfileBaseAndData(boost::optional<size_t> index, ProfileBase& profile, + std::array<u8, MAX_DATA>& data) const { + if (GetProfileBase(index, profile)) { + std::memcpy(data.data(), profiles[index.get()].data.data(), MAX_DATA); + return true; + } + return false; +} + +/// Return the users profile base and the unknown arbitary data. +bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile, + std::array<u8, MAX_DATA>& data) const { + auto idx = GetUserIndex(uuid); + return GetProfileBaseAndData(idx, profile, data); +} + +/// Return the users profile base and the unknown arbitary data. +bool ProfileManager::GetProfileBaseAndData(ProfileInfo user, ProfileBase& profile, + std::array<u8, MAX_DATA>& data) const { + return GetProfileBaseAndData(user.user_uuid, profile, data); +} + +/// Returns if the system is allowing user registrations or not +bool ProfileManager::CanSystemRegisterUser() const { + return false; // TODO(ogniK): Games shouldn't have + // access to user registration, when we + // emulate qlaunch. Update this to dynamically change. +} + +}; // namespace Service::Account diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h new file mode 100644 index 000000000..314bccbf9 --- /dev/null +++ b/src/core/hle/service/acc/profile_manager.h @@ -0,0 +1,124 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <random> +#include "boost/optional.hpp" +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/result.h" + +namespace Service::Account { +constexpr size_t MAX_USERS = 8; +constexpr size_t MAX_DATA = 128; +static const u128 INVALID_UUID = {0, 0}; + +struct UUID { + // UUIDs which are 0 are considered invalid! + u128 uuid = INVALID_UUID; + UUID() = default; + explicit UUID(const u128& id) : uuid{id} {} + explicit UUID(const u64 lo, const u64 hi) { + uuid[0] = lo; + uuid[1] = hi; + }; + explicit operator bool() const { + return uuid[0] != INVALID_UUID[0] || uuid[1] != INVALID_UUID[1]; + } + + bool operator==(const UUID& rhs) const { + return std::tie(uuid[0], uuid[1]) == std::tie(rhs.uuid[0], rhs.uuid[1]); + } + + bool operator!=(const UUID& rhs) const { + return !operator==(rhs); + } + + // TODO(ogniK): Properly generate uuids based on RFC-4122 + const UUID& Generate() { + std::random_device device; + std::mt19937 gen(device()); + std::uniform_int_distribution<uint64_t> distribution(1, + std::numeric_limits<uint64_t>::max()); + uuid[0] = distribution(gen); + uuid[1] = distribution(gen); + return *this; + } + + // Set the UUID to {0,0} to be considered an invalid user + void Invalidate() { + uuid = INVALID_UUID; + } + std::string Format() const { + return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]); + } +}; +static_assert(sizeof(UUID) == 16, "UUID is an invalid size!"); + +/// This holds general information about a users profile. This is where we store all the information +/// based on a specific user +struct ProfileInfo { + UUID user_uuid; + std::array<u8, 0x20> username; + u64 creation_time; + std::array<u8, MAX_DATA> data; // TODO(ognik): Work out what this is + bool is_open; +}; + +struct ProfileBase { + UUID user_uuid; + u64_le timestamp; + std::array<u8, 0x20> username; + + // Zero out all the fields to make the profile slot considered "Empty" + void Invalidate() { + user_uuid.Invalidate(); + timestamp = 0; + username.fill(0); + } +}; +static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase is an invalid size"); + +/// The profile manager is used for handling multiple user profiles at once. It keeps track of open +/// users, all the accounts registered on the "system" as well as fetching individual "ProfileInfo" +/// objects +class ProfileManager { +public: + ProfileManager(); // TODO(ogniK): Load from system save + ResultCode AddUser(ProfileInfo user); + ResultCode CreateNewUser(UUID uuid, std::array<u8, 0x20>& username); + ResultCode CreateNewUser(UUID uuid, const std::string& username); + boost::optional<size_t> GetUserIndex(const UUID& uuid) const; + boost::optional<size_t> GetUserIndex(ProfileInfo user) const; + bool GetProfileBase(boost::optional<size_t> index, ProfileBase& profile) const; + bool GetProfileBase(UUID uuid, ProfileBase& profile) const; + bool GetProfileBase(ProfileInfo user, ProfileBase& profile) const; + bool GetProfileBaseAndData(boost::optional<size_t> index, ProfileBase& profile, + std::array<u8, MAX_DATA>& data) const; + bool GetProfileBaseAndData(UUID uuid, ProfileBase& profile, + std::array<u8, MAX_DATA>& data) const; + bool GetProfileBaseAndData(ProfileInfo user, ProfileBase& profile, + std::array<u8, MAX_DATA>& data) const; + size_t GetUserCount() const; + size_t GetOpenUserCount() const; + bool UserExists(UUID uuid) const; + void OpenUser(UUID uuid); + void CloseUser(UUID uuid); + std::array<UUID, MAX_USERS> GetOpenUsers() const; + std::array<UUID, MAX_USERS> GetAllUsers() const; + UUID GetLastOpenedUser() const; + + bool CanSystemRegisterUser() const; + +private: + std::array<ProfileInfo, MAX_USERS> profiles{}; + size_t user_count = 0; + boost::optional<size_t> AddToProfiles(const ProfileInfo& profile); + bool RemoveProfileAtIndex(size_t index); + UUID last_opened_user{0, 0}; +}; + +}; // namespace Service::Account diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 762763463..c524e7a48 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -145,8 +145,8 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger {51, nullptr, "ApproveToDisplay"}, {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"}, {61, nullptr, "SetMediaPlaybackState"}, - {62, nullptr, "SetIdleTimeDetectionExtension"}, - {63, nullptr, "GetIdleTimeDetectionExtension"}, + {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"}, + {63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"}, {64, nullptr, "SetInputDetectionSourceSet"}, {65, nullptr, "ReportUserIsActive"}, {66, nullptr, "GetCurrentIlluminance"}, @@ -281,6 +281,23 @@ void ISelfController::SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx) LOG_WARNING(Service_AM, "(STUBBED) called"); } +void ISelfController::SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + idle_time_detection_extension = rp.Pop<u32>(); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(idle_time_detection_extension); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter") { static const FunctionInfo functions[] = { {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"}, @@ -306,7 +323,8 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter" {52, nullptr, "SwitchLcdBacklight"}, {55, nullptr, "IsInControllerFirmwareUpdateSection"}, {60, nullptr, "GetDefaultDisplayResolution"}, - {61, nullptr, "GetDefaultDisplayResolutionChangeEvent"}, + {61, &ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent, + "GetDefaultDisplayResolutionChangeEvent"}, {62, nullptr, "GetHdcpAuthenticationState"}, {63, nullptr, "GetHdcpAuthenticationStateChangeEvent"}, }; @@ -341,6 +359,16 @@ void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_AM, "(STUBBED) called"); } +void ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent(Kernel::HLERequestContext& ctx) { + event->Signal(); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(event); + + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) { const bool use_docked_mode{Settings::values.use_docked_mode}; IPC::ResponseBuilder rb{ctx, 3}; diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 862f338ac..b763aff6f 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -87,9 +87,12 @@ private: void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx); void SetScreenShotPermission(Kernel::HLERequestContext& ctx); void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx); + void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); + void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); std::shared_ptr<NVFlinger::NVFlinger> nvflinger; Kernel::SharedPtr<Kernel::Event> launchable_event; + u32 idle_time_detection_extension = 0; }; class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { @@ -110,6 +113,7 @@ private: void GetEventHandle(Kernel::HLERequestContext& ctx); void ReceiveMessage(Kernel::HLERequestContext& ctx); void GetCurrentFocusState(Kernel::HLERequestContext& ctx); + void GetDefaultDisplayResolutionChangeEvent(Kernel::HLERequestContext& ctx); void GetOperationMode(Kernel::HLERequestContext& ctx); void GetPerformanceMode(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 5e416cde2..da658cbe6 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -226,6 +226,7 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType( static std::unique_ptr<FileSys::RomFSFactory> romfs_factory; static std::unique_ptr<FileSys::SaveDataFactory> save_data_factory; static std::unique_ptr<FileSys::SDMCFactory> sdmc_factory; +static std::unique_ptr<FileSys::BISFactory> bis_factory; ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) { ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second RomFS"); @@ -248,6 +249,13 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) { return RESULT_SUCCESS; } +ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) { + ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS"); + bis_factory = std::move(factory); + LOG_DEBUG(Service_FS, "Registred BIS"); + return RESULT_SUCCESS; +} + ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id) { LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id); @@ -281,6 +289,14 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() { return sdmc_factory->Open(); } +std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() { + return bis_factory->GetSystemNANDContents(); +} + +std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() { + return bis_factory->GetUserNANDContents(); +} + void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) { romfs_factory = nullptr; save_data_factory = nullptr; @@ -291,6 +307,9 @@ void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) { auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), FileSys::Mode::ReadWrite); + if (bis_factory == nullptr) + bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory); + auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); save_data_factory = std::move(savedata); diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 462c13f20..1d6f922dd 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -6,6 +6,7 @@ #include <memory> #include "common/common_types.h" +#include "core/file_sys/bis_factory.h" #include "core/file_sys/directory.h" #include "core/file_sys/mode.h" #include "core/file_sys/romfs_factory.h" @@ -24,16 +25,15 @@ namespace FileSystem { ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory); ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory); ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory); +ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory); -// TODO(DarkLordZach): BIS Filesystem -// ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory); ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id); ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, FileSys::SaveDataDescriptor save_struct); ResultVal<FileSys::VirtualDir> OpenSDMC(); -// TODO(DarkLordZach): BIS Filesystem -// ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS(); +std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents(); +std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); /// Registers all Filesystem services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs); diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index 2e99ddf51..098da2a41 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -92,7 +92,11 @@ private: // Parse out log metadata u32 line{}; - std::string message, filename, function; + std::string module; + std::string message; + std::string filename; + std::string function; + std::string thread; while (addr < end_addr) { const Field field{static_cast<Field>(Memory::Read8(addr++))}; const size_t length{Memory::Read8(addr++)}; @@ -102,6 +106,8 @@ private: } switch (field) { + case Field::Skip: + break; case Field::Message: message = Memory::ReadCString(addr, length); break; @@ -114,6 +120,12 @@ private: case Field::Function: function = Memory::ReadCString(addr, length); break; + case Field::Module: + module = Memory::ReadCString(addr, length); + break; + case Field::Thread: + thread = Memory::ReadCString(addr, length); + break; } addr += length; @@ -128,12 +140,18 @@ private: if (!filename.empty()) { log_stream << filename << ':'; } + if (!module.empty()) { + log_stream << module << ':'; + } if (!function.empty()) { log_stream << function << ':'; } if (line) { log_stream << std::to_string(line) << ':'; } + if (!thread.empty()) { + log_stream << thread << ':'; + } if (log_stream.str().length() > 0 && log_stream.str().back() == ':') { log_stream << ' '; } @@ -142,7 +160,7 @@ private: if (header.IsTailLog()) { switch (header.severity) { case MessageHeader::Severity::Trace: - LOG_TRACE(Debug_Emulated, "{}", log_stream.str()); + LOG_DEBUG(Debug_Emulated, "{}", log_stream.str()); break; case MessageHeader::Severity::Info: LOG_INFO(Debug_Emulated, "{}", log_stream.str()); diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp index 08f45b78a..7b91bb258 100644 --- a/src/core/hle/service/mm/mm_u.cpp +++ b/src/core/hle/service/mm/mm_u.cpp @@ -9,42 +9,63 @@ namespace Service::MM { -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<MM_U>()->InstallAsService(service_manager); -} +class MM_U final : public ServiceFramework<MM_U> { +public: + explicit MM_U() : ServiceFramework{"mm:u"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &MM_U::Initialize, "InitializeOld"}, + {1, &MM_U::Finalize, "FinalizeOld"}, + {2, &MM_U::SetAndWait, "SetAndWaitOld"}, + {3, &MM_U::Get, "GetOld"}, + {4, &MM_U::Initialize, "Initialize"}, + {5, &MM_U::Finalize, "Finalize"}, + {6, &MM_U::SetAndWait, "SetAndWait"}, + {7, &MM_U::Get, "Get"}, + }; + // clang-format on -void MM_U::Initialize(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_MM, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} + RegisterHandlers(functions); + } -void MM_U::SetAndWait(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - min = rp.Pop<u32>(); - max = rp.Pop<u32>(); - current = min; +private: + void Initialize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } - LOG_WARNING(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} + void Finalize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } -void MM_U::Get(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_MM, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push(current); -} + void SetAndWait(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + min = rp.Pop<u32>(); + max = rp.Pop<u32>(); + current = min; -MM_U::MM_U() : ServiceFramework("mm:u") { - static const FunctionInfo functions[] = { - {0, nullptr, "InitializeOld"}, {1, nullptr, "FinalizeOld"}, - {2, nullptr, "SetAndWaitOld"}, {3, nullptr, "GetOld"}, - {4, &MM_U::Initialize, "Initialize"}, {5, nullptr, "Finalize"}, - {6, &MM_U::SetAndWait, "SetAndWait"}, {7, &MM_U::Get, "Get"}, - }; - RegisterHandlers(functions); + LOG_WARNING(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void Get(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(current); + } + + u32 min{0}; + u32 max{0}; + u32 current{0}; +}; + +void InstallInterfaces(SM::ServiceManager& service_manager) { + std::make_shared<MM_U>()->InstallAsService(service_manager); } } // namespace Service::MM diff --git a/src/core/hle/service/mm/mm_u.h b/src/core/hle/service/mm/mm_u.h index 79eeedf9c..5439fa653 100644 --- a/src/core/hle/service/mm/mm_u.h +++ b/src/core/hle/service/mm/mm_u.h @@ -8,21 +8,6 @@ namespace Service::MM { -class MM_U final : public ServiceFramework<MM_U> { -public: - MM_U(); - ~MM_U() = default; - -private: - void Initialize(Kernel::HLERequestContext& ctx); - void SetAndWait(Kernel::HLERequestContext& ctx); - void Get(Kernel::HLERequestContext& ctx); - - u32 min{0}; - u32 max{0}; - u32 current{0}; -}; - /// Registers all MM services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); diff --git a/src/core/hle/service/pctl/module.cpp b/src/core/hle/service/pctl/module.cpp index fcf1f3da3..6cc3b1992 100644 --- a/src/core/hle/service/pctl/module.cpp +++ b/src/core/hle/service/pctl/module.cpp @@ -14,7 +14,8 @@ public: IParentalControlService() : ServiceFramework("IParentalControlService") { static const FunctionInfo functions[] = { {1, &IParentalControlService::Initialize, "Initialize"}, - {1001, nullptr, "CheckFreeCommunicationPermission"}, + {1001, &IParentalControlService::CheckFreeCommunicationPermission, + "CheckFreeCommunicationPermission"}, {1002, nullptr, "ConfirmLaunchApplicationPermission"}, {1003, nullptr, "ConfirmResumeApplicationPermission"}, {1004, nullptr, "ConfirmSnsPostPermission"}, @@ -116,6 +117,12 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 0}; rb.Push(RESULT_SUCCESS); } + + void CheckFreeCommunicationPermission(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_PCTL, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } }; void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp index 518a0cc46..1cef73216 100644 --- a/src/core/hle/service/sm/controller.cpp +++ b/src/core/hle/service/sm/controller.cpp @@ -10,7 +10,7 @@ namespace Service::SM { void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) { - ASSERT_MSG(!ctx.Session()->IsDomain(), "session is alread a domain"); + ASSERT_MSG(ctx.Session()->IsSession(), "Session is already a domain"); ctx.Session()->ConvertToDomain(); IPC::ResponseBuilder rb{ctx, 3}; @@ -41,7 +41,7 @@ void Controller::DuplicateSessionEx(Kernel::HLERequestContext& ctx) { void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0x500); + rb.Push<u16>(0x500); LOG_WARNING(Service, "(STUBBED) called"); } diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index de05f21d8..d575a9bea 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -118,7 +118,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load( process->program_id = metadata.GetTitleID(); process->svc_access_mask.set(); - process->address_mappings = default_address_mappings; process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(), diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 401cad3ab..6420a7f11 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -398,7 +398,6 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) { process->LoadModule(codeset, codeset->entrypoint); process->svc_access_mask.set(); - process->address_mappings = default_address_mappings; // Attach the default resource limit (APPLICATION) to the process process->resource_limit = diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 1f2f31535..70ef5d240 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <memory> +#include <ostream> #include <string> #include "common/logging/log.h" #include "common/string_util.h" @@ -17,12 +18,6 @@ namespace Loader { -const std::initializer_list<Kernel::AddressMapping> default_address_mappings = { - {0x1FF50000, 0x8000, true}, // part of DSP RAM - {0x1FF70000, 0x8000, true}, // part of DSP RAM - {0x1F000000, 0x600000, false}, // entire VRAM -}; - FileType IdentifyFile(FileSys::VirtualFile file) { FileType type; @@ -46,6 +41,8 @@ FileType IdentifyFile(FileSys::VirtualFile file) { FileType GuessFromFilename(const std::string& name) { if (name == "main") return FileType::DeconstructedRomDirectory; + if (name == "00") + return FileType::NCA; const std::string extension = Common::ToLower(std::string(FileUtil::GetExtensionFromFilename(name))); @@ -125,14 +122,9 @@ constexpr std::array<const char*, 36> RESULT_MESSAGES{ "There is no control data available.", }; -std::string GetMessageForResultStatus(ResultStatus status) { - return GetMessageForResultStatus(static_cast<u16>(status)); -} - -std::string GetMessageForResultStatus(u16 status) { - if (status >= 36) - return ""; - return RESULT_MESSAGES[status]; +std::ostream& operator<<(std::ostream& os, ResultStatus status) { + os << RESULT_MESSAGES.at(static_cast<size_t>(status)); + return os; } /** diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 285363549..b74cfbf8a 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -5,7 +5,7 @@ #pragma once #include <algorithm> -#include <initializer_list> +#include <iosfwd> #include <memory> #include <string> #include <utility> @@ -95,8 +95,7 @@ enum class ResultStatus : u16 { ErrorNoControl, }; -std::string GetMessageForResultStatus(ResultStatus status); -std::string GetMessageForResultStatus(u16 status); +std::ostream& operator<<(std::ostream& os, ResultStatus status); /// Interface for loading an application class AppLoader : NonCopyable { @@ -208,12 +207,6 @@ protected: }; /** - * Common address mappings found in most games, used for binary formats that don't have this - * information. - */ -extern const std::initializer_list<Kernel::AddressMapping> default_address_mappings; - -/** * Identifies a bootable file and return a suitable loader * @param file The bootable file * @return the best loader for this file diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 8498cc94b..9d50c7d42 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -3,28 +3,22 @@ // Refer to the license.txt file included. #include <utility> -#include <vector> #include "common/file_util.h" #include "common/logging/log.h" -#include "common/string_util.h" -#include "common/swap.h" -#include "core/core.h" #include "core/file_sys/content_archive.h" -#include "core/file_sys/program_metadata.h" -#include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/resource_limit.h" #include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/deconstructed_rom_directory.h" #include "core/loader/nca.h" -#include "core/loader/nso.h" -#include "core/memory.h" namespace Loader { AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file_) : AppLoader(std::move(file_)), nca(std::make_unique<FileSys::NCA>(file)) {} +AppLoader_NCA::~AppLoader_NCA() = default; + FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) { FileSys::NCA nca(file); @@ -83,6 +77,4 @@ ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { return ResultStatus::Success; } -AppLoader_NCA::~AppLoader_NCA() = default; - } // namespace Loader diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index 7f7d8ea0b..326f84857 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -4,20 +4,24 @@ #pragma once -#include <string> #include "common/common_types.h" -#include "core/file_sys/content_archive.h" -#include "core/file_sys/program_metadata.h" +#include "core/file_sys/vfs.h" #include "core/hle/kernel/object.h" #include "core/loader/loader.h" -#include "deconstructed_rom_directory.h" + +namespace FileSys { +class NCA; +} namespace Loader { +class AppLoader_DeconstructedRomDirectory; + /// Loads an NCA file class AppLoader_NCA final : public AppLoader { public: explicit AppLoader_NCA(FileSys::VirtualFile file); + ~AppLoader_NCA() override; /** * Returns the type of the file @@ -35,12 +39,7 @@ public: ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; ResultStatus ReadProgramId(u64& out_program_id) override; - ~AppLoader_NCA(); - private: - FileSys::ProgramMetadata metadata; - - FileSys::NCAHeader header; std::unique_ptr<FileSys::NCA> nca; std::unique_ptr<AppLoader_DeconstructedRomDirectory> directory_loader; }; diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 908d91eab..2179cf2ea 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -186,7 +186,6 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) { } process->svc_access_mask.set(); - process->address_mappings = default_address_mappings; process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); process->Run(base_addr, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index fee7d58c6..a94558ac5 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -152,7 +152,6 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) { LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), Memory::PROCESS_IMAGE_VADDR); process->svc_access_mask.set(); - process->address_mappings = default_address_mappings; process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); process->Run(Memory::PROCESS_IMAGE_VADDR, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 5d67fb186..4c4979545 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -4,22 +4,14 @@ #include <vector> -#include "common/file_util.h" -#include "common/logging/log.h" -#include "common/string_util.h" -#include "common/swap.h" -#include "core/core.h" +#include "common/common_types.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/program_metadata.h" #include "core/file_sys/romfs.h" -#include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/resource_limit.h" -#include "core/hle/service/filesystem/filesystem.h" -#include "core/loader/nso.h" +#include "core/loader/nca.h" #include "core/loader/xci.h" -#include "core/memory.h" namespace Loader { diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 973833050..cc4287e17 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -6,12 +6,18 @@ #include <memory> #include "common/common_types.h" -#include "core/file_sys/card_image.h" +#include "core/file_sys/vfs.h" #include "core/loader/loader.h" -#include "core/loader/nca.h" + +namespace FileSys { +class NACP; +class XCI; +} // namespace FileSys namespace Loader { +class AppLoader_NCA; + /// Loads an XCI file class AppLoader_XCI final : public AppLoader { public: @@ -37,8 +43,6 @@ public: ResultStatus ReadTitle(std::string& title) override; private: - FileSys::ProgramMetadata metadata; - std::unique_ptr<FileSys::XCI> xci; std::unique_ptr<AppLoader_NCA> nca_loader; |