diff options
Diffstat (limited to '')
31 files changed, 1081 insertions, 856 deletions
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index e29f70b3a..8f758d6d9 100644 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp @@ -3,8 +3,12 @@ // Refer to the license.txt file included. #include <fmt/format.h> +#include "common/file_util.h" +#include "core/core.h" #include "core/file_sys/bis_factory.h" +#include "core/file_sys/mode.h" #include "core/file_sys/registered_cache.h" +#include "core/settings.h" namespace FileSys { @@ -14,10 +18,22 @@ BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_, VirtualDir sysnand_cache(std::make_unique<RegisteredCache>( GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))), usrnand_cache(std::make_unique<RegisteredCache>( - GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {} + GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))), + sysnand_placeholder(std::make_unique<PlaceholderCache>( + GetOrCreateDirectoryRelative(nand_root, "/system/Contents/placehld"))), + usrnand_placeholder(std::make_unique<PlaceholderCache>( + GetOrCreateDirectoryRelative(nand_root, "/user/Contents/placehld"))) {} BISFactory::~BISFactory() = default; +VirtualDir BISFactory::GetSystemNANDContentDirectory() const { + return GetOrCreateDirectoryRelative(nand_root, "/system/Contents"); +} + +VirtualDir BISFactory::GetUserNANDContentDirectory() const { + return GetOrCreateDirectoryRelative(nand_root, "/user/Contents"); +} + RegisteredCache* BISFactory::GetSystemNANDContents() const { return sysnand_cache.get(); } @@ -26,9 +42,17 @@ RegisteredCache* BISFactory::GetUserNANDContents() const { return usrnand_cache.get(); } +PlaceholderCache* BISFactory::GetSystemNANDPlaceholder() const { + return sysnand_placeholder.get(); +} + +PlaceholderCache* BISFactory::GetUserNANDPlaceholder() const { + return usrnand_placeholder.get(); +} + VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const { // LayeredFS doesn't work on updates and title id-less homebrew - if (title_id == 0 || (title_id & 0x800) > 0) + if (title_id == 0 || (title_id & 0xFFF) == 0x800) return nullptr; return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id)); } @@ -39,4 +63,77 @@ VirtualDir BISFactory::GetModificationDumpRoot(u64 title_id) const { return GetOrCreateDirectoryRelative(dump_root, fmt::format("/{:016X}", title_id)); } +VirtualDir BISFactory::OpenPartition(BisPartitionId id) const { + switch (id) { + case BisPartitionId::CalibrationFile: + return GetOrCreateDirectoryRelative(nand_root, "/prodinfof"); + case BisPartitionId::SafeMode: + return GetOrCreateDirectoryRelative(nand_root, "/safe"); + case BisPartitionId::System: + return GetOrCreateDirectoryRelative(nand_root, "/system"); + case BisPartitionId::User: + return GetOrCreateDirectoryRelative(nand_root, "/user"); + default: + return nullptr; + } +} + +VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const { + Core::Crypto::KeyManager keys; + Core::Crypto::PartitionDataManager pdm{ + Core::System::GetInstance().GetFilesystem()->OpenDirectory( + FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), Mode::Read)}; + keys.PopulateFromPartitionData(pdm); + + switch (id) { + case BisPartitionId::CalibrationBinary: + return pdm.GetDecryptedProdInfo(); + case BisPartitionId::BootConfigAndPackage2Part1: + case BisPartitionId::BootConfigAndPackage2Part2: + case BisPartitionId::BootConfigAndPackage2Part3: + case BisPartitionId::BootConfigAndPackage2Part4: + case BisPartitionId::BootConfigAndPackage2Part5: + case BisPartitionId::BootConfigAndPackage2Part6: { + const auto new_id = static_cast<u8>(id) - + static_cast<u8>(BisPartitionId::BootConfigAndPackage2Part1) + + static_cast<u8>(Core::Crypto::Package2Type::NormalMain); + return pdm.GetPackage2Raw(static_cast<Core::Crypto::Package2Type>(new_id)); + } + default: + return nullptr; + } +} + +VirtualDir BISFactory::GetImageDirectory() const { + return GetOrCreateDirectoryRelative(nand_root, "/user/Album"); +} + +u64 BISFactory::GetSystemNANDFreeSpace() const { + const auto sys_dir = GetOrCreateDirectoryRelative(nand_root, "/system"); + if (sys_dir == nullptr) + return 0; + + return GetSystemNANDTotalSpace() - sys_dir->GetSize(); +} + +u64 BISFactory::GetSystemNANDTotalSpace() const { + return static_cast<u64>(Settings::values.nand_system_size); +} + +u64 BISFactory::GetUserNANDFreeSpace() const { + const auto usr_dir = GetOrCreateDirectoryRelative(nand_root, "/user"); + if (usr_dir == nullptr) + return 0; + + return GetUserNANDTotalSpace() - usr_dir->GetSize(); +} + +u64 BISFactory::GetUserNANDTotalSpace() const { + return static_cast<u64>(Settings::values.nand_user_size); +} + +u64 BISFactory::GetFullNANDTotalSpace() const { + return static_cast<u64>(Settings::values.nand_total_size); +} + } // namespace FileSys diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h index 453c11ad2..bdfe728c9 100644 --- a/src/core/file_sys/bis_factory.h +++ b/src/core/file_sys/bis_factory.h @@ -10,7 +10,25 @@ namespace FileSys { +enum class BisPartitionId : u32 { + UserDataRoot = 20, + CalibrationBinary = 27, + CalibrationFile = 28, + BootConfigAndPackage2Part1 = 21, + BootConfigAndPackage2Part2 = 22, + BootConfigAndPackage2Part3 = 23, + BootConfigAndPackage2Part4 = 24, + BootConfigAndPackage2Part5 = 25, + BootConfigAndPackage2Part6 = 26, + SafeMode = 29, + System = 31, + SystemProperEncryption = 32, + SystemProperPartition = 33, + User = 30, +}; + class RegisteredCache; +class PlaceholderCache; /// 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 @@ -20,12 +38,29 @@ public: explicit BISFactory(VirtualDir nand_root, VirtualDir load_root, VirtualDir dump_root); ~BISFactory(); + VirtualDir GetSystemNANDContentDirectory() const; + VirtualDir GetUserNANDContentDirectory() const; + RegisteredCache* GetSystemNANDContents() const; RegisteredCache* GetUserNANDContents() const; + PlaceholderCache* GetSystemNANDPlaceholder() const; + PlaceholderCache* GetUserNANDPlaceholder() const; + VirtualDir GetModificationLoadRoot(u64 title_id) const; VirtualDir GetModificationDumpRoot(u64 title_id) const; + VirtualDir OpenPartition(BisPartitionId id) const; + VirtualFile OpenPartitionStorage(BisPartitionId id) const; + + VirtualDir GetImageDirectory() const; + + u64 GetSystemNANDFreeSpace() const; + u64 GetSystemNANDTotalSpace() const; + u64 GetUserNANDFreeSpace() const; + u64 GetUserNANDTotalSpace() const; + u64 GetFullNANDTotalSpace() const; + private: VirtualDir nand_root; VirtualDir load_root; @@ -33,6 +68,9 @@ private: std::unique_ptr<RegisteredCache> sysnand_cache; std::unique_ptr<RegisteredCache> usrnand_cache; + + std::unique_ptr<PlaceholderCache> sysnand_placeholder; + std::unique_ptr<PlaceholderCache> usrnand_placeholder; }; } // namespace FileSys diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 2c145bd09..db54113a0 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -12,17 +12,26 @@ #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/romfs.h" #include "core/file_sys/submission_package.h" +#include "core/file_sys/vfs_concat.h" #include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs_vector.h" #include "core/loader/loader.h" namespace FileSys { -constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", "logo"}; +constexpr u64 GAMECARD_CERTIFICATE_OFFSET = 0x7000; +constexpr std::array partition_names{ + "update", + "normal", + "secure", + "logo", +}; XCI::XCI(VirtualFile file_) : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, - partitions(0x4) { + partitions(partition_names.size()) { if (file->ReadObject(&header) != sizeof(GamecardHeader)) { status = Loader::ResultStatus::ErrorBadXCIHeader; return; @@ -43,23 +52,24 @@ XCI::XCI(VirtualFile file_) for (XCIPartition partition : {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) { - auto raw = main_hfs.GetFile(partition_names[static_cast<std::size_t>(partition)]); - if (raw != nullptr) - partitions[static_cast<std::size_t>(partition)] = - std::make_shared<PartitionFilesystem>(raw); + const auto partition_idx = static_cast<std::size_t>(partition); + auto raw = main_hfs.GetFile(partition_names[partition_idx]); + + if (raw != nullptr) { + partitions[partition_idx] = std::make_shared<PartitionFilesystem>(std::move(raw)); + } } secure_partition = std::make_shared<NSP>( main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)])); - const auto secure_ncas = secure_partition->GetNCAsCollapsed(); - std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas)); - + ncas = secure_partition->GetNCAsCollapsed(); program = secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program); program_nca_status = secure_partition->GetProgramStatus(secure_partition->GetProgramTitleID()); - if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA) + if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA) { program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA; + } auto result = AddNCAFromPartition(XCIPartition::Update); if (result != Loader::ResultStatus::Success) { @@ -147,8 +157,9 @@ std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const { VirtualFile XCI::GetNCAFileByType(NCAContentType type) const { auto nca = GetNCAByType(type); - if (nca != nullptr) + if (nca != nullptr) { return nca->GetBaseFile(); + } return nullptr; } @@ -168,18 +179,43 @@ VirtualDir XCI::GetParentDirectory() const { return file->GetContainingDirectory(); } +VirtualDir XCI::ConcatenatedPseudoDirectory() { + const auto out = std::make_shared<VectorVfsDirectory>(); + for (const auto& part_id : {XCIPartition::Normal, XCIPartition::Logo, XCIPartition::Secure}) { + const auto& part = GetPartition(part_id); + if (part == nullptr) + continue; + + for (const auto& file : part->GetFiles()) + out->AddFile(file); + } + + return out; +} + +std::array<u8, 0x200> XCI::GetCertificate() const { + std::array<u8, 0x200> out; + file->Read(out.data(), out.size(), GAMECARD_CERTIFICATE_OFFSET); + return out; +} + Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { - if (partitions[static_cast<std::size_t>(part)] == nullptr) { + const auto partition_index = static_cast<std::size_t>(part); + const auto& partition = partitions[partition_index]; + + if (partition == nullptr) { return Loader::ResultStatus::ErrorXCIMissingPartition; } - for (const VirtualFile& file : partitions[static_cast<std::size_t>(part)]->GetFiles()) { - if (file->GetExtension() != "nca") + for (const VirtualFile& file : partition->GetFiles()) { + if (file->GetExtension() != "nca") { continue; + } + auto nca = std::make_shared<NCA>(file, nullptr, 0, keys); - // TODO(DarkLordZach): Add proper Rev1+ Support - if (nca->IsUpdate()) + if (nca->IsUpdate()) { continue; + } if (nca->GetType() == NCAContentType::Program) { program_nca_status = nca->GetStatus(); } @@ -188,7 +224,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { } else { 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<std::size_t>(part)], nca->GetName(), error_id, + partition_names[partition_index], nca->GetName(), error_id, nca->GetStatus()); } } diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index a350496f7..3e6b92ff3 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -91,6 +91,8 @@ public: VirtualDir GetLogoPartition() const; u64 GetProgramTitleID() const; + u32 GetSystemUpdateVersion(); + u64 GetSystemUpdateTitleID() const; bool HasProgramNCA() const; VirtualFile GetProgramNCAFile() const; @@ -106,6 +108,11 @@ public: VirtualDir GetParentDirectory() const override; + // Creates a directory that contains all the NCAs in the gamecard + VirtualDir ConcatenatedPseudoDirectory(); + + std::array<u8, 0x200> GetCertificate() const; + private: Loader::ResultStatus AddNCAFromPartition(XCIPartition part); @@ -120,6 +127,8 @@ private: std::shared_ptr<NCA> program; std::vector<std::shared_ptr<NCA>> ncas; + u64 update_normal_partition_end; + Core::Crypto::KeyManager keys; }; } // namespace FileSys diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp deleted file mode 100644 index b06c2f20a..000000000 --- a/src/core/file_sys/cheat_engine.cpp +++ /dev/null @@ -1,492 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <locale> -#include "common/hex_util.h" -#include "common/microprofile.h" -#include "common/swap.h" -#include "core/core.h" -#include "core/core_timing.h" -#include "core/core_timing_util.h" -#include "core/file_sys/cheat_engine.h" -#include "core/hle/kernel/process.h" -#include "core/hle/service/hid/controllers/npad.h" -#include "core/hle/service/hid/hid.h" -#include "core/hle/service/sm/sm.h" - -namespace FileSys { - -constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60); -constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; - -u64 Cheat::Address() const { - u64 out; - std::memcpy(&out, raw.data(), sizeof(u64)); - return Common::swap64(out) & 0xFFFFFFFFFF; -} - -u64 Cheat::ValueWidth(u64 offset) const { - return Value(offset, width); -} - -u64 Cheat::Value(u64 offset, u64 width) const { - u64 out; - std::memcpy(&out, raw.data() + offset, sizeof(u64)); - out = Common::swap64(out); - if (width == 8) - return out; - return out & ((1ull << (width * CHAR_BIT)) - 1); -} - -u32 Cheat::KeypadValue() const { - u32 out; - std::memcpy(&out, raw.data(), sizeof(u32)); - return Common::swap32(out) & 0x0FFFFFFF; -} - -void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, - VAddr heap_end, MemoryWriter writer, MemoryReader reader) { - this->main_region_begin = main_begin; - this->main_region_end = main_end; - this->heap_region_begin = heap_begin; - this->heap_region_end = heap_end; - this->writer = writer; - this->reader = reader; -} - -MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); - -void CheatList::Execute() { - MICROPROFILE_SCOPE(Cheat_Engine); - - std::fill(scratch.begin(), scratch.end(), 0); - in_standard = false; - for (std::size_t i = 0; i < master_list.size(); ++i) { - LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first); - current_block = i; - ExecuteBlock(master_list[i].second); - } - - in_standard = true; - for (std::size_t i = 0; i < standard_list.size(); ++i) { - LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first); - current_block = i; - ExecuteBlock(standard_list[i].second); - } -} - -CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard) - : master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {} - -bool CheatList::EvaluateConditional(const Cheat& cheat) const { - using ComparisonFunction = bool (*)(u64, u64); - constexpr std::array<ComparisonFunction, 6> comparison_functions{ - [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; }, - [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; }, - [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; }, - }; - - if (cheat.type == CodeType::ConditionalInput) { - const auto applet_resource = - system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource(); - if (applet_resource == nullptr) { - LOG_WARNING( - Common_Filesystem, - "Attempted to evaluate input conditional, but applet resource is not initialized!"); - return false; - } - - const auto press_state = - applet_resource - ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad) - .GetAndResetPressState(); - return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0; - } - - ASSERT(cheat.type == CodeType::Conditional); - - const auto offset = - cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; - ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6); - auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())]; - const auto addr = cheat.Address() + offset; - - return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8)); -} - -void CheatList::ProcessBlockPairs(const Block& block) { - block_pairs.clear(); - - u64 scope = 0; - std::map<u64, u64> pairs; - - for (std::size_t i = 0; i < block.size(); ++i) { - const auto& cheat = block[i]; - - switch (cheat.type) { - case CodeType::Conditional: - case CodeType::ConditionalInput: - pairs.insert_or_assign(scope, i); - ++scope; - break; - case CodeType::EndConditional: { - --scope; - const auto idx = pairs.at(scope); - block_pairs.insert_or_assign(idx, i); - break; - } - case CodeType::Loop: { - if (cheat.end_of_loop) { - --scope; - const auto idx = pairs.at(scope); - block_pairs.insert_or_assign(idx, i); - } else { - pairs.insert_or_assign(scope, i); - ++scope; - } - break; - } - } - } -} - -void CheatList::WriteImmediate(const Cheat& cheat) { - const auto offset = - cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; - const auto& register_3 = scratch.at(cheat.register_3); - - const auto addr = cheat.Address() + offset + register_3; - LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr, - cheat.Value(8, cheat.width)); - writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8)); -} - -void CheatList::BeginConditional(const Cheat& cheat) { - if (EvaluateConditional(cheat)) { - return; - } - - const auto iter = block_pairs.find(current_index); - ASSERT(iter != block_pairs.end()); - current_index = iter->second - 1; -} - -void CheatList::EndConditional(const Cheat& cheat) { - LOG_DEBUG(Common_Filesystem, "Ending conditional block."); -} - -void CheatList::Loop(const Cheat& cheat) { - if (cheat.end_of_loop.Value()) - ASSERT(!cheat.end_of_loop.Value()); - - auto& register_3 = scratch.at(cheat.register_3); - const auto iter = block_pairs.find(current_index); - ASSERT(iter != block_pairs.end()); - ASSERT(iter->first < iter->second); - - const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32))); - for (s32 i = initial_value; i >= 0; --i) { - register_3 = static_cast<u64>(i); - for (std::size_t c = iter->first + 1; c < iter->second; ++c) { - current_index = c; - ExecuteSingleCheat( - (in_standard ? standard_list : master_list)[current_block].second[c]); - } - } - - current_index = iter->second; -} - -void CheatList::LoadImmediate(const Cheat& cheat) { - auto& register_3 = scratch.at(cheat.register_3); - - LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3, - cheat.Value(4, 8)); - register_3 = cheat.Value(4, 8); -} - -void CheatList::LoadIndexed(const Cheat& cheat) { - const auto offset = - cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; - auto& register_3 = scratch.at(cheat.register_3); - - const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address(); - LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}", - cheat.register_3, addr); - register_3 = reader(cheat.width, SanitizeAddress(addr)); -} - -void CheatList::StoreIndexed(const Cheat& cheat) { - const auto& register_3 = scratch.at(cheat.register_3); - - const auto addr = - register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0); - LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", - cheat.Value(4, cheat.width), addr); - writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4)); -} - -void CheatList::RegisterArithmetic(const Cheat& cheat) { - using ArithmeticFunction = u64 (*)(u64, u64); - constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{ - [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; }, - [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; }, - [](u64 a, u64 b) { return a >> b; }, - }; - - using ArithmeticOverflowCheck = bool (*)(u64, u64); - constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{ - [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b - [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b - [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b - [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b - [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b - }; - - static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks), - "Missing or have extra arithmetic overflow checks compared to functions!"); - - auto& register_3 = scratch.at(cheat.register_3); - - ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5); - auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())]; - auto* overflow_function = - arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())]; - LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}", - cheat.register_3, cheat.ValueWidth(4)); - - if (overflow_function(register_3, cheat.ValueWidth(4))) { - LOG_WARNING(Common_Filesystem, - "overflow will occur when performing arithmetic operation={:02X} with operands " - "a={:016X}, b={:016X}!", - static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4)); - } - - register_3 = function(register_3, cheat.ValueWidth(4)); -} - -void CheatList::BeginConditionalInput(const Cheat& cheat) { - if (EvaluateConditional(cheat)) - return; - - const auto iter = block_pairs.find(current_index); - ASSERT(iter != block_pairs.end()); - current_index = iter->second - 1; -} - -VAddr CheatList::SanitizeAddress(VAddr in) const { - if ((in < main_region_begin || in >= main_region_end) && - (in < heap_region_begin || in >= heap_region_end)) { - LOG_ERROR(Common_Filesystem, - "Cheat attempting to access memory at invalid address={:016X}, if this persists, " - "the cheat may be incorrect. However, this may be normal early in execution if " - "the game has not properly set up yet.", - in); - return 0; ///< Invalid addresses will hard crash - } - - return in; -} - -void CheatList::ExecuteSingleCheat(const Cheat& cheat) { - using CheatOperationFunction = void (CheatList::*)(const Cheat&); - constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{ - &CheatList::WriteImmediate, &CheatList::BeginConditional, - &CheatList::EndConditional, &CheatList::Loop, - &CheatList::LoadImmediate, &CheatList::LoadIndexed, - &CheatList::StoreIndexed, &CheatList::RegisterArithmetic, - &CheatList::BeginConditionalInput, - }; - - const auto index = static_cast<u8>(cheat.type.Value()); - ASSERT(index < sizeof(cheat_operation_functions)); - const auto op = cheat_operation_functions[index]; - (this->*op)(cheat); -} - -void CheatList::ExecuteBlock(const Block& block) { - encountered_loops.clear(); - - ProcessBlockPairs(block); - for (std::size_t i = 0; i < block.size(); ++i) { - current_index = i; - ExecuteSingleCheat(block[i]); - i = current_index; - } -} - -CheatParser::~CheatParser() = default; - -CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master, - CheatList::ProgramSegment standard) const { - return {system, std::move(master), std::move(standard)}; -} - -TextCheatParser::~TextCheatParser() = default; - -CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const { - std::stringstream ss; - ss.write(reinterpret_cast<const char*>(data.data()), data.size()); - - std::vector<std::string> lines; - std::string stream_line; - while (std::getline(ss, stream_line)) { - // Remove a trailing \r - if (!stream_line.empty() && stream_line.back() == '\r') - stream_line.pop_back(); - lines.push_back(std::move(stream_line)); - } - - CheatList::ProgramSegment master_list; - CheatList::ProgramSegment standard_list; - - for (std::size_t i = 0; i < lines.size(); ++i) { - auto line = lines[i]; - - if (!line.empty() && (line[0] == '[' || line[0] == '{')) { - const auto master = line[0] == '{'; - const auto begin = master ? line.find('{') : line.find('['); - const auto end = master ? line.rfind('}') : line.rfind(']'); - - ASSERT(begin != std::string::npos && end != std::string::npos); - - const std::string patch_name{line.begin() + begin + 1, line.begin() + end}; - CheatList::Block block{}; - - while (i < lines.size() - 1) { - line = lines[++i]; - if (!line.empty() && (line[0] == '[' || line[0] == '{')) { - --i; - break; - } - - if (line.size() < 8) - continue; - - Cheat out{}; - out.raw = ParseSingleLineCheat(line); - block.push_back(out); - } - - (master ? master_list : standard_list).emplace_back(patch_name, block); - } - } - - return MakeCheatList(system, master_list, standard_list); -} - -std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const { - std::array<u8, 16> out{}; - - if (line.size() < 8) - return out; - - const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8}); - std::memcpy(out.data(), word1.data(), sizeof(u32)); - - if (line.size() < 17 || line[8] != ' ') - return out; - - const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8}); - std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32)); - - if (line.size() < 26 || line[17] != ' ') { - // Perform shifting in case value is truncated early. - const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); - if (type == CodeType::Loop || type == CodeType::LoadImmediate || - type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) { - std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32)); - std::memset(out.data() + 4, 0, sizeof(u32)); - } - - return out; - } - - const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8}); - std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32)); - - if (line.size() < 35 || line[26] != ' ') { - // Perform shifting in case value is truncated early. - const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); - if (type == CodeType::WriteImmediate || type == CodeType::Conditional) { - std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32)); - std::memset(out.data() + 8, 0, sizeof(u32)); - } - - return out; - } - - const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8}); - std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32)); - - return out; -} - -namespace { -u64 MemoryReadImpl(u32 width, VAddr addr) { - switch (width) { - case 1: - return Memory::Read8(addr); - case 2: - return Memory::Read16(addr); - case 4: - return Memory::Read32(addr); - case 8: - return Memory::Read64(addr); - default: - UNREACHABLE(); - return 0; - } -} - -void MemoryWriteImpl(u32 width, VAddr addr, u64 value) { - switch (width) { - case 1: - Memory::Write8(addr, static_cast<u8>(value)); - break; - case 2: - Memory::Write16(addr, static_cast<u16>(value)); - break; - case 4: - Memory::Write32(addr, static_cast<u32>(value)); - break; - case 8: - Memory::Write64(addr, value); - break; - default: - UNREACHABLE(); - } -} -} // Anonymous namespace - -CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_, - const std::string& build_id, VAddr code_region_start, - VAddr code_region_end) - : cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} { - event = core_timing.RegisterEvent( - "CheatEngine::FrameCallback::" + build_id, - [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); - core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); - - const auto& vm_manager = system.CurrentProcess()->VMManager(); - for (auto& list : this->cheats) { - list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(), - code_region_end, vm_manager.GetHeapRegionEndAddress(), - &MemoryWriteImpl, &MemoryReadImpl); - } -} - -CheatEngine::~CheatEngine() { - core_timing.UnscheduleEvent(event, 0); -} - -void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) { - for (auto& list : cheats) { - list.Execute(); - } - - core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); -} - -} // namespace FileSys diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h deleted file mode 100644 index ac22a82cb..000000000 --- a/src/core/file_sys/cheat_engine.h +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <map> -#include <set> -#include <vector> -#include "common/bit_field.h" -#include "common/common_types.h" - -namespace Core { -class System; -} - -namespace Core::Timing { -class CoreTiming; -struct EventType; -} // namespace Core::Timing - -namespace FileSys { - -enum class CodeType : u32 { - // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY - // Writes a T sized value Y to the address A added to the value of register R in memory domain M - WriteImmediate = 0, - - // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY - // Compares the T sized value Y to the value at address A in memory domain M using the - // conditional function C. If success, continues execution. If failure, jumps to the matching - // EndConditional statement. - Conditional = 1, - - // 20000000 - // Terminates a Conditional or ConditionalInput block. - EndConditional = 2, - - // 300R0000 VVVVVVVV - // Starts looping V times, storing the current count in register R. - // Loop block is terminated with a matching 310R0000. - Loop = 3, - - // 400R0000 VVVVVVVV VVVVVVVV - // Sets the value of register R to the value V. - LoadImmediate = 4, - - // 5TMRI0AA AAAAAAAA - // Sets the value of register R to the value of width T at address A in memory domain M, with - // the current value of R added to the address if I == 1. - LoadIndexed = 5, - - // 6T0RIFG0 VVVVVVVV VVVVVVVV - // Writes the value V of width T to the memory address stored in register R. Adds the value of - // register G to the final calculation if F is nonzero. Increments the value of register R by T - // after operation if I is nonzero. - StoreIndexed = 6, - - // 7T0RA000 VVVVVVVV - // Performs the arithmetic operation A on the value in register R and the value V of width T, - // storing the result in register R. - RegisterArithmetic = 7, - - // 8KKKKKKK - // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are, - // execution continues. If none are, execution skips to the next EndConditional command. - ConditionalInput = 8, -}; - -enum class MemoryType : u32 { - // Addressed relative to start of main NSO - MainNSO = 0, - - // Addressed relative to start of heap - Heap = 1, -}; - -enum class ArithmeticOp : u32 { - Add = 0, - Sub = 1, - Mult = 2, - LShift = 3, - RShift = 4, -}; - -enum class ComparisonOp : u32 { - GreaterThan = 1, - GreaterThanEqual = 2, - LessThan = 3, - LessThanEqual = 4, - Equal = 5, - Inequal = 6, -}; - -union Cheat { - std::array<u8, 16> raw; - - BitField<4, 4, CodeType> type; - BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes. - BitField<0, 4, u32> end_of_loop; - BitField<12, 4, MemoryType> memory_type; - BitField<8, 4, u32> register_3; - BitField<8, 4, ComparisonOp> comparison_op; - BitField<20, 4, u32> load_from_register; - BitField<20, 4, u32> increment_register; - BitField<20, 4, ArithmeticOp> arithmetic_op; - BitField<16, 4, u32> add_additional_register; - BitField<28, 4, u32> register_6; - - u64 Address() const; - u64 ValueWidth(u64 offset) const; - u64 Value(u64 offset, u64 width) const; - u32 KeypadValue() const; -}; - -class CheatParser; - -// Represents a full collection of cheats for a game. The Execute function should be called every -// interval that all cheats should be executed. Clients should not directly instantiate this class -// (hence private constructor), they should instead receive an instance from CheatParser, which -// guarantees the list is always in an acceptable state. -class CheatList { -public: - friend class CheatParser; - - using Block = std::vector<Cheat>; - using ProgramSegment = std::vector<std::pair<std::string, Block>>; - - // (width in bytes, address, value) - using MemoryWriter = void (*)(u32, VAddr, u64); - // (width in bytes, address) -> value - using MemoryReader = u64 (*)(u32, VAddr); - - void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end, - MemoryWriter writer, MemoryReader reader); - - void Execute(); - -private: - CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard); - - void ProcessBlockPairs(const Block& block); - void ExecuteSingleCheat(const Cheat& cheat); - - void ExecuteBlock(const Block& block); - - bool EvaluateConditional(const Cheat& cheat) const; - - // Individual cheat operations - void WriteImmediate(const Cheat& cheat); - void BeginConditional(const Cheat& cheat); - void EndConditional(const Cheat& cheat); - void Loop(const Cheat& cheat); - void LoadImmediate(const Cheat& cheat); - void LoadIndexed(const Cheat& cheat); - void StoreIndexed(const Cheat& cheat); - void RegisterArithmetic(const Cheat& cheat); - void BeginConditionalInput(const Cheat& cheat); - - VAddr SanitizeAddress(VAddr in) const; - - // Master Codes are defined as codes that cannot be disabled and are run prior to all - // others. - ProgramSegment master_list; - // All other codes - ProgramSegment standard_list; - - bool in_standard = false; - - // 16 (0x0-0xF) scratch registers that can be used by cheats - std::array<u64, 16> scratch{}; - - MemoryWriter writer = nullptr; - MemoryReader reader = nullptr; - - u64 main_region_begin{}; - u64 heap_region_begin{}; - u64 main_region_end{}; - u64 heap_region_end{}; - - u64 current_block{}; - // The current index of the cheat within the current Block - u64 current_index{}; - - // The 'stack' of the program. When a conditional or loop statement is encountered, its index is - // pushed onto this queue. When a end block is encountered, the condition is checked. - std::map<u64, u64> block_pairs; - - std::set<u64> encountered_loops; - - const Core::System* system; -}; - -// Intermediary class that parses a text file or other disk format for storing cheats into a -// CheatList object, that can be used for execution. -class CheatParser { -public: - virtual ~CheatParser(); - - virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0; - -protected: - CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master, - CheatList::ProgramSegment standard) const; -}; - -// CheatParser implementation that parses text files -class TextCheatParser final : public CheatParser { -public: - ~TextCheatParser() override; - - CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override; - -private: - std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const; -}; - -// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming -class CheatEngine final { -public: - CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id, - VAddr code_region_start, VAddr code_region_end); - ~CheatEngine(); - -private: - void FrameCallback(u64 userdata, s64 cycles_late); - - std::vector<CheatList> cheats; - - Core::Timing::EventType* event; - Core::Timing::CoreTiming& core_timing; -}; - -} // namespace FileSys diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 5aa3b600b..ea5c92f61 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -452,13 +452,13 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s switch (s_header.raw.header.crypto_type) { case NCASectionCryptoType::NONE: - LOG_DEBUG(Crypto, "called with mode=NONE"); + LOG_TRACE(Crypto, "called with mode=NONE"); return in; case NCASectionCryptoType::CTR: // During normal BKTR decryption, this entire function is skipped. This is for the metadata, // which uses the same CTR as usual. case NCASectionCryptoType::BKTR: - LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); + LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); { std::optional<Core::Crypto::Key128> key = {}; if (has_rights_id) { @@ -528,6 +528,14 @@ u64 NCA::GetTitleId() const { return header.title_id; } +std::array<u8, 16> NCA::GetRightsId() const { + return header.rights_id; +} + +u32 NCA::GetSDKVersion() const { + return header.sdk_version; +} + bool NCA::IsUpdate() const { return is_update; } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 15b9e6624..e249079b5 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -112,6 +112,8 @@ public: NCAContentType GetType() const; u64 GetTitleId() const; + std::array<u8, 0x10> GetRightsId() const; + u32 GetSDKVersion() const; bool IsUpdate() const; VirtualFile GetRomFS() const; diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index 04da30825..f155a1341 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -87,6 +87,10 @@ u64 NACP::GetDefaultJournalSaveSize() const { return raw.user_account_save_data_journal_size; } +bool NACP::GetUserAccountSwitchLock() const { + return raw.user_account_switch_lock != 0; +} + u32 NACP::GetSupportedLanguages() const { return raw.supported_languages; } diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 1be34ed55..2d8c251ac 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -30,7 +30,8 @@ struct RawNACP { std::array<LanguageEntry, 16> language_entries; std::array<u8, 0x25> isbn; u8 startup_user_account; - INSERT_PADDING_BYTES(2); + u8 user_account_switch_lock; + u8 addon_content_registration_type; u32_le application_attribute; u32_le supported_languages; u32_le parental_control; @@ -111,6 +112,7 @@ public: u64 GetDefaultJournalSaveSize() const; u32 GetSupportedLanguages() const; std::vector<u8> GetRawBytes() const; + bool GetUserAccountSwitchLock() const; private: RawNACP raw{}; diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index 485c4913a..a08a70efd 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp @@ -287,7 +287,6 @@ void IPSwitchCompiler::Parse() { } else { // hex replacement const auto value = patch_line.substr(9); - replace.reserve(value.size() / 2); replace = Common::HexStringToVector(value, is_little_endian); } @@ -295,7 +294,7 @@ void IPSwitchCompiler::Parse() { LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} " "with byte string '{}'", - patch_text->GetName(), offset, Common::HexVectorToString(replace)); + patch_text->GetName(), offset, Common::HexToString(replace)); } patch.records.insert_or_assign(offset, std::move(replace)); diff --git a/src/core/file_sys/kernel_executable.cpp b/src/core/file_sys/kernel_executable.cpp new file mode 100644 index 000000000..371300684 --- /dev/null +++ b/src/core/file_sys/kernel_executable.cpp @@ -0,0 +1,228 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/string_util.h" +#include "core/file_sys/kernel_executable.h" +#include "core/file_sys/vfs_offset.h" + +namespace FileSys { + +constexpr u32 INI_MAX_KIPS = 0x50; + +namespace { +bool DecompressBLZ(std::vector<u8>& data) { + if (data.size() < 0xC) + return {}; + + const auto data_size = data.size() - 0xC; + + u32 compressed_size{}; + u32 init_index{}; + u32 additional_size{}; + std::memcpy(&compressed_size, data.data() + data_size, sizeof(u32)); + std::memcpy(&init_index, data.data() + data_size + 0x4, sizeof(u32)); + std::memcpy(&additional_size, data.data() + data_size + 0x8, sizeof(u32)); + + const auto start_offset = data.size() - compressed_size; + data.resize(compressed_size + additional_size + start_offset); + + std::size_t index = compressed_size - init_index; + std::size_t out_index = compressed_size + additional_size; + + while (out_index > 0) { + --index; + auto control = data[index + start_offset]; + for (size_t i = 0; i < 8; ++i) { + if (((control << i) & 0x80) > 0) { + if (index < 2) { + return false; + } + index -= 2; + std::size_t segment_offset = + data[index + start_offset] | data[index + start_offset + 1] << 8; + std::size_t segment_size = ((segment_offset >> 12) & 0xF) + 3; + segment_offset &= 0xFFF; + segment_offset += 3; + + if (out_index < segment_size) + segment_size = out_index; + + if (out_index < segment_size) { + return false; + } + + out_index -= segment_size; + + for (size_t j = 0; j < segment_size; ++j) { + if (out_index + j + segment_offset + start_offset >= data.size()) { + return false; + } + data[out_index + j + start_offset] = + data[out_index + j + segment_offset + start_offset]; + } + } else { + if (out_index < 1) { + return false; + } + --out_index; + --index; + data[out_index + start_offset] = data[index + start_offset]; + } + + if (out_index == 0) + break; + } + } + + return true; +} +} // Anonymous namespace + +KIP::KIP(const VirtualFile& file) : status(Loader::ResultStatus::Success) { + if (file == nullptr) { + status = Loader::ResultStatus::ErrorNullFile; + return; + } + + if (file->GetSize() < sizeof(KIPHeader) || file->ReadObject(&header) != sizeof(KIPHeader)) { + status = Loader::ResultStatus::ErrorBadKIPHeader; + return; + } + + if (header.magic != Common::MakeMagic('K', 'I', 'P', '1')) { + status = Loader::ResultStatus::ErrorBadKIPHeader; + return; + } + + u64 offset = sizeof(KIPHeader); + for (std::size_t i = 0; i < header.sections.size(); ++i) { + auto compressed = file->ReadBytes(header.sections[i].compressed_size, offset); + offset += header.sections[i].compressed_size; + + if (header.sections[i].compressed_size == 0 && header.sections[i].decompressed_size != 0) { + decompressed_sections[i] = std::vector<u8>(header.sections[i].decompressed_size); + } else if (header.sections[i].compressed_size == header.sections[i].decompressed_size) { + decompressed_sections[i] = std::move(compressed); + } else { + decompressed_sections[i] = compressed; + if (!DecompressBLZ(decompressed_sections[i])) { + status = Loader::ResultStatus::ErrorBLZDecompressionFailed; + return; + } + } + } +} + +Loader::ResultStatus KIP::GetStatus() const { + return status; +} + +std::string KIP::GetName() const { + return Common::StringFromFixedZeroTerminatedBuffer(header.name.data(), header.name.size()); +} + +u64 KIP::GetTitleID() const { + return header.title_id; +} + +std::vector<u8> KIP::GetSectionDecompressed(u8 index) const { + return decompressed_sections[index]; +} + +bool KIP::Is64Bit() const { + return (header.flags & 0x8) != 0; +} + +bool KIP::Is39BitAddressSpace() const { + return (header.flags & 0x10) != 0; +} + +bool KIP::IsService() const { + return (header.flags & 0x20) != 0; +} + +std::vector<u32> KIP::GetKernelCapabilities() const { + return std::vector<u32>(header.capabilities.begin(), header.capabilities.end()); +} + +s32 KIP::GetMainThreadPriority() const { + return header.main_thread_priority; +} + +u32 KIP::GetMainThreadStackSize() const { + return header.sections[1].attribute; +} + +u32 KIP::GetMainThreadCpuCore() const { + return header.default_core; +} + +const std::vector<u8>& KIP::GetTextSection() const { + return decompressed_sections[0]; +} + +const std::vector<u8>& KIP::GetRODataSection() const { + return decompressed_sections[1]; +} + +const std::vector<u8>& KIP::GetDataSection() const { + return decompressed_sections[2]; +} + +u32 KIP::GetTextOffset() const { + return header.sections[0].offset; +} + +u32 KIP::GetRODataOffset() const { + return header.sections[1].offset; +} + +u32 KIP::GetDataOffset() const { + return header.sections[2].offset; +} + +u32 KIP::GetBSSSize() const { + return header.sections[3].decompressed_size; +} + +u32 KIP::GetBSSOffset() const { + return header.sections[3].offset; +} + +INI::INI(const VirtualFile& file) : status(Loader::ResultStatus::Success) { + if (file->GetSize() < sizeof(INIHeader) || file->ReadObject(&header) != sizeof(INIHeader)) { + status = Loader::ResultStatus::ErrorBadINIHeader; + return; + } + + if (header.magic != Common::MakeMagic('I', 'N', 'I', '1')) { + status = Loader::ResultStatus::ErrorBadINIHeader; + return; + } + + if (header.kip_count > INI_MAX_KIPS) { + status = Loader::ResultStatus::ErrorINITooManyKIPs; + return; + } + + u64 offset = sizeof(INIHeader); + for (std::size_t i = 0; i < header.kip_count; ++i) { + const auto kip_file = + std::make_shared<OffsetVfsFile>(file, file->GetSize() - offset, offset); + KIP kip(kip_file); + if (kip.GetStatus() == Loader::ResultStatus::Success) { + kips.push_back(std::move(kip)); + } + } +} + +Loader::ResultStatus INI::GetStatus() const { + return status; +} + +const std::vector<KIP>& INI::GetKIPs() const { + return kips; +} + +} // namespace FileSys diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h new file mode 100644 index 000000000..324a57384 --- /dev/null +++ b/src/core/file_sys/kernel_executable.h @@ -0,0 +1,99 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "common/swap.h" +#include "core/file_sys/vfs_types.h" +#include "core/loader/loader.h" + +namespace FileSys { + +struct KIPSectionHeader { + u32_le offset; + u32_le decompressed_size; + u32_le compressed_size; + u32_le attribute; +}; +static_assert(sizeof(KIPSectionHeader) == 0x10, "KIPSectionHeader has incorrect size."); + +struct KIPHeader { + u32_le magic; + std::array<char, 0xC> name; + u64_le title_id; + u32_le process_category; + u8 main_thread_priority; + u8 default_core; + INSERT_PADDING_BYTES(1); + u8 flags; + std::array<KIPSectionHeader, 6> sections; + std::array<u32, 0x20> capabilities; +}; +static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size."); + +struct INIHeader { + u32_le magic; + u32_le size; + u32_le kip_count; + INSERT_PADDING_BYTES(0x4); +}; +static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size."); + +// Kernel Internal Process +class KIP { +public: + explicit KIP(const VirtualFile& file); + + Loader::ResultStatus GetStatus() const; + + std::string GetName() const; + u64 GetTitleID() const; + std::vector<u8> GetSectionDecompressed(u8 index) const; + + // Executable Flags + bool Is64Bit() const; + bool Is39BitAddressSpace() const; + bool IsService() const; + + std::vector<u32> GetKernelCapabilities() const; + + s32 GetMainThreadPriority() const; + u32 GetMainThreadStackSize() const; + u32 GetMainThreadCpuCore() const; + + const std::vector<u8>& GetTextSection() const; + const std::vector<u8>& GetRODataSection() const; + const std::vector<u8>& GetDataSection() const; + + u32 GetTextOffset() const; + u32 GetRODataOffset() const; + u32 GetDataOffset() const; + + u32 GetBSSSize() const; + u32 GetBSSOffset() const; + +private: + Loader::ResultStatus status; + + KIPHeader header{}; + std::array<std::vector<u8>, 6> decompressed_sections; +}; + +class INI { +public: + explicit INI(const VirtualFile& file); + + Loader::ResultStatus GetStatus() const; + + const std::vector<KIP>& GetKIPs() const; + +private: + Loader::ResultStatus status; + + INIHeader header{}; + std::vector<KIP> kips; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h index 50bf38471..1f82fff0a 100644 --- a/src/core/file_sys/nca_metadata.h +++ b/src/core/file_sys/nca_metadata.h @@ -4,6 +4,7 @@ #pragma once +#include <array> #include <memory> #include <vector> #include "common/common_funcs.h" @@ -34,9 +35,9 @@ enum class ContentRecordType : u8 { Program = 1, Data = 2, Control = 3, - Manual = 4, - Legal = 5, - Patch = 6, + HtmlDocument = 4, + LegalInformation = 5, + DeltaFragment = 6, }; struct ContentRecord { @@ -69,11 +70,15 @@ struct CNMTHeader { u64_le title_id; u32_le title_version; TitleType type; - INSERT_PADDING_BYTES(1); + u8 reserved; u16_le table_offset; u16_le number_content_entries; u16_le number_meta_entries; - INSERT_PADDING_BYTES(12); + u8 attributes; + std::array<u8, 2> reserved2; + u8 is_committed; + u32_le required_download_system_version; + std::array<u8, 4> reserved3; }; static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size."); diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 78dbadee3..df0ecb15c 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -22,6 +22,7 @@ #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" #include "core/loader/nso.h" +#include "core/memory/cheat_engine.h" #include "core/settings.h" namespace FileSys { @@ -63,7 +64,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { if (Settings::values.dump_exefs) { LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id); - const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id); + const auto dump_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id); if (dump_dir != nullptr) { const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs"); VfsRawCopyD(exefs, exefs_dir); @@ -88,7 +90,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { } // LayeredExeFS - const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + const auto load_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); if (load_dir != nullptr && load_dir->GetSize() > 0) { auto patch_dirs = load_dir->GetSubdirectories(); std::sort( @@ -142,7 +145,7 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD if (!compiler.IsValid()) continue; - auto this_build_id = Common::HexArrayToString(compiler.GetBuildID()); + auto this_build_id = Common::HexToString(compiler.GetBuildID()); this_build_id = this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1); @@ -168,13 +171,14 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st return nso; } - const auto build_id_raw = Common::HexArrayToString(header.build_id); + const auto build_id_raw = Common::HexToString(header.build_id); const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); if (Settings::values.dump_nso) { LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id, title_id); - const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id); + const auto dump_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id); if (dump_dir != nullptr) { const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso"); const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id)); @@ -186,7 +190,13 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id); - const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + const auto load_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); + if (load_dir == nullptr) { + LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); + return nso; + } + auto patch_dirs = load_dir->GetSubdirectories(); std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); @@ -219,12 +229,18 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st } bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { - const auto build_id_raw = Common::HexArrayToString(build_id_); + const auto build_id_raw = Common::HexToString(build_id_); const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); - const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + const auto load_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); + if (load_dir == nullptr) { + LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); + return false; + } + auto patch_dirs = load_dir->GetSubdirectories(); std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); @@ -232,10 +248,11 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { return !CollectPatches(patch_dirs, build_id).empty(); } -static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id, - const std::array<u8, 0x20>& build_id_, - const VirtualDir& base_path, bool upper) { - const auto build_id_raw = Common::HexArrayToString(build_id_, upper); +namespace { +std::optional<std::vector<Memory::CheatEntry>> ReadCheatFileFromFolder( + const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_, + const VirtualDir& base_path, bool upper) { + const auto build_id_raw = Common::HexToString(build_id_, upper); const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); @@ -252,31 +269,39 @@ static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& syst return std::nullopt; } - TextCheatParser parser; - return parser.Parse(system, data); + Memory::TextCheatParser parser; + return parser.Parse( + system, std::string_view(reinterpret_cast<const char* const>(data.data()), data.size())); } -std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system, - const std::array<u8, 32>& build_id_) const { - const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); +} // Anonymous namespace + +std::vector<Memory::CheatEntry> PatchManager::CreateCheatList( + const Core::System& system, const std::array<u8, 32>& build_id_) const { + const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id); + if (load_dir == nullptr) { + LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); + return {}; + } + auto patch_dirs = load_dir->GetSubdirectories(); std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); - std::vector<CheatList> out; - out.reserve(patch_dirs.size()); + std::vector<Memory::CheatEntry> out; for (const auto& subdir : patch_dirs) { auto cheats_dir = subdir->GetSubdirectory("cheats"); if (cheats_dir != nullptr) { auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true); if (res.has_value()) { - out.push_back(std::move(*res)); + std::copy(res->begin(), res->end(), std::back_inserter(out)); continue; } res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false); - if (res.has_value()) - out.push_back(std::move(*res)); + if (res.has_value()) { + std::copy(res->begin(), res->end(), std::back_inserter(out)); + } } } @@ -284,7 +309,8 @@ std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system, } static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { - const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + const auto load_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || load_dir == nullptr || load_dir->GetSize() <= 0) { return; @@ -393,6 +419,8 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) { std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames( VirtualFile update_raw) const { + if (title_id == 0) + return {}; std::map<std::string, std::string, std::less<>> out; const auto& installed = Core::System::GetInstance().GetContentProvider(); const auto& disabled = Settings::values.disabled_addons[title_id]; @@ -423,7 +451,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam } // General Mods (LayeredFS and IPS) - const auto mod_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + const auto mod_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); if (mod_dir != nullptr && mod_dir->GetSize() > 0) { for (const auto& mod : mod_dir->GetSubdirectories()) { std::string types; @@ -493,6 +522,16 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam return out; } +std::optional<u32> PatchManager::GetGameVersion() const { + const auto& installed = Core::System::GetInstance().GetContentProvider(); + const auto update_tid = GetUpdateTitleID(title_id); + if (installed.HasEntry(update_tid, ContentRecordType::Program)) { + return installed.GetEntryVersion(update_tid); + } + + return installed.GetEntryVersion(title_id); +} + std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { const auto& installed = Core::System::GetInstance().GetContentProvider(); diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 769f8c6f0..e857e6e82 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -8,9 +8,9 @@ #include <memory> #include <string> #include "common/common_types.h" -#include "core/file_sys/cheat_engine.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/vfs.h" +#include "core/memory/dmnt_cheat_types.h" namespace Core { class System; @@ -51,8 +51,8 @@ public: bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const; // Creates a CheatList object with all - std::vector<CheatList> CreateCheatList(const Core::System& system, - const std::array<u8, 0x20>& build_id) const; + std::vector<Memory::CheatEntry> CreateCheatList(const Core::System& system, + const std::array<u8, 0x20>& build_id) const; // Currently tracked RomFS patches: // - Game Updates @@ -66,8 +66,13 @@ public: std::map<std::string, std::string, std::less<>> GetPatchVersionNames( VirtualFile update_raw = nullptr) const; - // Given title_id of the program, attempts to get the control data of the update and parse it, - // falling back to the base control data. + // If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails, + // it will fallback to the Meta-type NCA of the base game. If that fails, the result will be + // std::nullopt + std::optional<u32> GetGameVersion() const; + + // Given title_id of the program, attempts to get the control data of the update and parse + // it, falling back to the base control data. std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const; // Version of GetControlMetadata that takes an arbitrary NCA diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index d863253f8..7310b3602 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -51,6 +51,21 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) { return Loader::ResultStatus::Success; } +void ProgramMetadata::LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, + u8 main_thread_prio, u8 main_thread_core, + u32 main_thread_stack_size, u64 title_id, + u64 filesystem_permissions, + KernelCapabilityDescriptors capabilities) { + npdm_header.has_64_bit_instructions.Assign(is_64_bit); + npdm_header.address_space_type.Assign(address_space); + npdm_header.main_thread_priority = main_thread_prio; + npdm_header.main_thread_cpu = main_thread_core; + npdm_header.main_stack_size = main_thread_stack_size; + aci_header.title_id = title_id; + aci_file_access.permissions = filesystem_permissions; + aci_kernel_capabilities = std ::move(capabilities); +} + bool ProgramMetadata::Is64BitProgram() const { return npdm_header.has_64_bit_instructions; } @@ -79,6 +94,10 @@ u64 ProgramMetadata::GetFilesystemPermissions() const { return aci_file_access.permissions; } +u32 ProgramMetadata::GetSystemResourceSize() const { + return npdm_header.system_resource_size; +} + const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCapabilities() const { return aci_kernel_capabilities; } diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h index 7de5b9cf9..88ec97d85 100644 --- a/src/core/file_sys/program_metadata.h +++ b/src/core/file_sys/program_metadata.h @@ -46,6 +46,11 @@ public: Loader::ResultStatus Load(VirtualFile file); + // Load from parameters instead of NPDM file, used for KIP + void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, u8 main_thread_prio, + u8 main_thread_core, u32 main_thread_stack_size, u64 title_id, + u64 filesystem_permissions, KernelCapabilityDescriptors capabilities); + bool Is64BitProgram() const; ProgramAddressSpaceType GetAddressSpaceType() const; u8 GetMainThreadPriority() const; @@ -53,6 +58,7 @@ public: u32 GetMainThreadStackSize() const; u64 GetTitleID() const; u64 GetFilesystemPermissions() const; + u32 GetSystemResourceSize() const; const KernelCapabilityDescriptors& GetKernelCapabilities() const; void Print() const; @@ -71,7 +77,8 @@ private: u8 reserved_3; u8 main_thread_priority; u8 main_thread_cpu; - std::array<u8, 8> reserved_4; + std::array<u8, 4> reserved_4; + u32_le system_resource_size; u32_le process_category; u32_le main_stack_size; std::array<u8, 0x10> application_name; diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 3946ff871..ac3fbd849 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <random> #include <regex> #include <mbedtls/sha256.h> #include "common/assert.h" @@ -48,18 +49,22 @@ static bool FollowsTwoDigitDirFormat(std::string_view name) { 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 const std::regex nca_id_cnmt_regex( + "[0-9A-F]{32}\\.cnmt.nca", std::regex_constants::ECMAScript | std::regex_constants::icase); + return (name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex)) || + (name.size() == 41 && std::regex_match(name.begin(), name.end(), nca_id_cnmt_regex)); } static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper, - bool within_two_digit) { + bool within_two_digit, bool cnmt_suffix) { if (!within_two_digit) - return fmt::format("/{}.nca", Common::HexArrayToString(nca_id, second_hex_upper)); + return fmt::format(cnmt_suffix ? "{}.cnmt.nca" : "/{}.nca", + Common::HexToString(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)); + return fmt::format(cnmt_suffix ? "/000000{:02X}/{}.cnmt.nca" : "/000000{:02X}/{}.nca", hash[0], + Common::HexToString(nca_id, second_hex_upper)); } static std::string GetCNMTName(TitleType type, u64 title_id) { @@ -98,7 +103,7 @@ ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { return ContentRecordType::Data; case NCAContentType::Manual: // TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal. - return ContentRecordType::Manual; + return ContentRecordType::HtmlDocument; default: UNREACHABLE_MSG("Invalid NCAContentType={:02X}", static_cast<u8>(type)); } @@ -126,6 +131,156 @@ std::vector<ContentProviderEntry> ContentProvider::ListEntries() const { return ListEntriesFilter(std::nullopt, std::nullopt, std::nullopt); } +PlaceholderCache::PlaceholderCache(VirtualDir dir_) : dir(std::move(dir_)) {} + +bool PlaceholderCache::Create(const NcaID& id, u64 size) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + + if (dir->GetFileRelative(path) != nullptr) { + return false; + } + + Core::Crypto::SHA256Hash hash{}; + mbedtls_sha256(id.data(), id.size(), hash.data(), 0); + const auto dirname = fmt::format("000000{:02X}", hash[0]); + + const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname); + + if (dir2 == nullptr) + return false; + + const auto file = dir2->CreateFile(fmt::format("{}.nca", Common::HexToString(id, false))); + + if (file == nullptr) + return false; + + return file->Resize(size); +} + +bool PlaceholderCache::Delete(const NcaID& id) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + + if (dir->GetFileRelative(path) == nullptr) { + return false; + } + + Core::Crypto::SHA256Hash hash{}; + mbedtls_sha256(id.data(), id.size(), hash.data(), 0); + const auto dirname = fmt::format("000000{:02X}", hash[0]); + + const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname); + + const auto res = dir2->DeleteFile(fmt::format("{}.nca", Common::HexToString(id, false))); + + return res; +} + +bool PlaceholderCache::Exists(const NcaID& id) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + + return dir->GetFileRelative(path) != nullptr; +} + +bool PlaceholderCache::Write(const NcaID& id, u64 offset, const std::vector<u8>& data) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + const auto file = dir->GetFileRelative(path); + + if (file == nullptr) + return false; + + return file->WriteBytes(data, offset) == data.size(); +} + +bool PlaceholderCache::Register(RegisteredCache* cache, const NcaID& placeholder, + const NcaID& install) const { + const auto path = GetRelativePathFromNcaID(placeholder, false, true, false); + const auto file = dir->GetFileRelative(path); + + if (file == nullptr) + return false; + + const auto res = cache->RawInstallNCA(NCA{file}, &VfsRawCopy, false, install); + + if (res != InstallResult::Success) + return false; + + return Delete(placeholder); +} + +bool PlaceholderCache::CleanAll() const { + return dir->GetParentDirectory()->CleanSubdirectoryRecursive(dir->GetName()); +} + +std::optional<std::array<u8, 0x10>> PlaceholderCache::GetRightsID(const NcaID& id) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + const auto file = dir->GetFileRelative(path); + + if (file == nullptr) + return std::nullopt; + + NCA nca{file}; + + if (nca.GetStatus() != Loader::ResultStatus::Success && + nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { + return std::nullopt; + } + + const auto rights_id = nca.GetRightsId(); + if (rights_id == NcaID{}) + return std::nullopt; + + return rights_id; +} + +u64 PlaceholderCache::Size(const NcaID& id) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + const auto file = dir->GetFileRelative(path); + + if (file == nullptr) + return 0; + + return file->GetSize(); +} + +bool PlaceholderCache::SetSize(const NcaID& id, u64 new_size) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + const auto file = dir->GetFileRelative(path); + + if (file == nullptr) + return false; + + return file->Resize(new_size); +} + +std::vector<NcaID> PlaceholderCache::List() const { + std::vector<NcaID> out; + for (const auto& sdir : dir->GetSubdirectories()) { + for (const auto& file : sdir->GetFiles()) { + const auto name = file->GetName(); + if (name.length() == 36 && name[32] == '.' && name[33] == 'n' && name[34] == 'c' && + name[35] == 'a') { + out.push_back(Common::HexStringToArray<0x10>(name.substr(0, 32))); + } + } + } + return out; +} + +NcaID PlaceholderCache::Generate() { + std::random_device device; + std::mt19937 gen(device()); + std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max()); + + NcaID out{}; + + const auto v1 = distribution(gen); + const auto v2 = distribution(gen); + std::memcpy(out.data(), &v1, sizeof(u64)); + std::memcpy(out.data() + sizeof(u64), &v2, sizeof(u64)); + + return out; +} + VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const { const auto file = dir->GetFileRelative(path); @@ -168,14 +323,18 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, 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); + // Try all five relevant modes of file storage: + // (bit 2 = uppercase/lower, bit 1 = within a two-digit dir, bit 0 = .cnmt suffix) + // 000: /000000**/{:032X}.nca + // 010: /{:032X}.nca + // 100: /000000**/{:032x}.nca + // 110: /{:032x}.nca + // 111: /{:032x}.cnmt.nca + for (u8 i = 0; i < 8; ++i) { + if ((i % 2) == 1 && i != 7) + continue; + const auto path = + GetRelativePathFromNcaID(id, (i & 0b100) == 0, (i & 0b010) == 0, (i & 0b001) == 0b001); file = OpenFileOrDirectoryConcat(dir, path); if (file != nullptr) return file; @@ -376,10 +535,11 @@ std::vector<ContentProviderEntry> RegisteredCache::ListEntriesFilter( } static std::shared_ptr<NCA> GetNCAFromNSPForID(const NSP& nsp, const NcaID& id) { - const auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false))); - if (file == nullptr) + auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexToString(id, false))); + if (file == nullptr) { return nullptr; - return std::make_shared<NCA>(file); + } + return std::make_shared<NCA>(std::move(file)); } InstallResult RegisteredCache::InstallEntry(const XCI& xci, bool overwrite_if_exists, @@ -395,8 +555,8 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex }); 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."); + LOG_ERROR(Loader, "The file you are attempting to install does not have a metadata NCA and " + "is therefore malformed. Check your encryption keys."); return InstallResult::ErrorMetaFailed; } @@ -413,6 +573,9 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex const auto cnmt_file = section0->GetFiles()[0]; const CNMT cnmt(cnmt_file); for (const auto& record : cnmt.GetContentRecords()) { + // Ignore DeltaFragments, they are not useful to us + if (record.type == ContentRecordType::DeltaFragment) + continue; const auto nca = GetNCAFromNSPForID(nsp, record.nca_id); if (nca == nullptr) return InstallResult::ErrorCopyFailed; @@ -467,7 +630,7 @@ InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFuncti memcpy(id.data(), hash.data(), 16); } - std::string path = GetRelativePathFromNcaID(id, false, true); + std::string path = GetRelativePathFromNcaID(id, false, true, false); if (GetFileAtID(id) != nullptr && !overwrite_if_exists) { LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping..."); @@ -643,6 +806,20 @@ ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnion return out; } +std::optional<ContentProviderUnionSlot> ContentProviderUnion::GetSlotForEntry( + u64 title_id, ContentRecordType type) const { + const auto iter = + std::find_if(providers.begin(), providers.end(), [title_id, type](const auto& provider) { + return provider.second != nullptr && provider.second->HasEntry(title_id, type); + }); + + if (iter == providers.end()) { + return std::nullopt; + } + + return iter->first; +} + ManualContentProvider::~ManualContentProvider() = default; void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type, diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index ec9052653..d1eec240e 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -25,6 +25,8 @@ enum class NCAContentType : u8; enum class TitleType : u8; struct ContentRecord; +struct MetaRecord; +class RegisteredCache; using NcaID = std::array<u8, 0x10>; using ContentProviderParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; @@ -89,6 +91,27 @@ protected: Core::Crypto::KeyManager keys; }; +class PlaceholderCache { +public: + explicit PlaceholderCache(VirtualDir dir); + + bool Create(const NcaID& id, u64 size) const; + bool Delete(const NcaID& id) const; + bool Exists(const NcaID& id) const; + bool Write(const NcaID& id, u64 offset, const std::vector<u8>& data) const; + bool Register(RegisteredCache* cache, const NcaID& placeholder, const NcaID& install) const; + bool CleanAll() const; + std::optional<std::array<u8, 0x10>> GetRightsID(const NcaID& id) const; + u64 Size(const NcaID& id) const; + bool SetSize(const NcaID& id, u64 new_size) const; + std::vector<NcaID> List() const; + + static NcaID Generate(); + +private: + VirtualDir dir; +}; + /* * A class that catalogues NCAs in the registered directory structure. * Nintendo's registered format follows this structure: @@ -103,6 +126,8 @@ protected: * when 4GB splitting can be ignored.) */ class RegisteredCache : public ContentProvider { + friend class PlaceholderCache; + 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 @@ -199,6 +224,9 @@ public: std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {}, std::optional<u64> title_id = {}) const; + std::optional<ContentProviderUnionSlot> GetSlotForEntry(u64 title_id, + ContentRecordType type) const; + private: std::map<ContentProviderUnionSlot, ContentProvider*> providers; }; diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index b2ccb2926..84cd4684c 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -7,6 +7,7 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "core/core.h" +#include "core/file_sys/card_image.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" @@ -34,7 +35,7 @@ void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) { this->update_raw = std::move(update_raw); } -ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() { +ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() const { if (!updatable) return MakeResult<VirtualFile>(file); @@ -43,7 +44,8 @@ ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() { patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw)); } -ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) { +ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, + ContentRecordType type) const { std::shared_ptr<NCA> res; switch (storage) { @@ -51,13 +53,17 @@ ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, Conte res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type); break; case StorageId::NandSystem: - res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type); + res = + Core::System::GetInstance().GetFileSystemController().GetSystemNANDContents()->GetEntry( + title_id, type); break; case StorageId::NandUser: - res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type); + res = Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->GetEntry( + title_id, type); break; case StorageId::SdCard: - res = Service::FileSystem::GetSDMCContents()->GetEntry(title_id, type); + res = Core::System::GetInstance().GetFileSystemController().GetSDMCContents()->GetEntry( + title_id, type); break; default: UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage)); diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index 7724c0b23..da63a313a 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -33,8 +33,8 @@ public: ~RomFSFactory(); void SetPackedUpdate(VirtualFile update_raw); - ResultVal<VirtualFile> OpenCurrentProcess(); - ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type); + ResultVal<VirtualFile> OpenCurrentProcess() const; + ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const; private: VirtualFile file; diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 7974b031d..f77cc02ac 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -15,22 +15,8 @@ namespace FileSys { constexpr char SAVE_DATA_SIZE_FILENAME[] = ".yuzu_save_size"; -std::string SaveDataDescriptor::DebugInfo() const { - return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}, " - "rank={}, index={}]", - static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id, - static_cast<u8>(rank), index); -} - -SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) { - // Delete all temporary storages - // On hardware, it is expected that temporary storage be empty at first use. - dir->DeleteSubdirectoryRecursive("temp"); -} - -SaveDataFactory::~SaveDataFactory() = default; - -ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) { +namespace { +void PrintSaveDataDescriptorWarnings(SaveDataDescriptor meta) { if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) { if (meta.zero_1 != 0) { LOG_WARNING(Service_FS, @@ -65,23 +51,51 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, const SaveDat "non-zero ({:016X}{:016X})", meta.user_id[1], meta.user_id[0]); } +} +} // Anonymous namespace - std::string save_directory = - GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); +std::string SaveDataDescriptor::DebugInfo() const { + return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, " + "save_id={:016X}, " + "rank={}, index={}]", + static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id, + static_cast<u8>(rank), index); +} - // TODO(DarkLordZach): Try to not create when opening, there are dedicated create save methods. - // But, user_ids don't match so this works for now. +SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) { + // Delete all temporary storages + // On hardware, it is expected that temporary storage be empty at first use. + dir->DeleteSubdirectoryRecursive("temp"); +} - auto out = dir->GetDirectoryRelative(save_directory); +SaveDataFactory::~SaveDataFactory() = default; + +ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space, + const SaveDataDescriptor& meta) const { + PrintSaveDataDescriptorWarnings(meta); + + const auto save_directory = + GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); + + auto out = dir->CreateDirectoryRelative(save_directory); + // Return an error if the save data doesn't actually exist. if (out == nullptr) { - // TODO(bunnei): This is a work-around to always create a save data directory if it does not - // already exist. This is a hack, as we do not understand yet how this works on hardware. - // Without a save data directory, many games will assert on boot. This should not have any - // bad side-effects. - out = dir->CreateDirectoryRelative(save_directory); + // TODO(DarkLordZach): Find out correct error code. + return ResultCode(-1); } + return MakeResult<VirtualDir>(std::move(out)); +} + +ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, + const SaveDataDescriptor& meta) const { + + const auto save_directory = + GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); + + auto out = dir->GetDirectoryRelative(save_directory); + // Return an error if the save data doesn't actually exist. if (out == nullptr) { // TODO(Subv): Find out correct error code. @@ -152,7 +166,7 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id, } void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, - SaveDataSize new_value) { + SaveDataSize new_value) const { const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0); const auto dir = GetOrCreateDirectoryRelative(this->dir, path); diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index b73654571..991e57aa1 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -64,7 +64,8 @@ public: explicit SaveDataFactory(VirtualDir dir); ~SaveDataFactory(); - ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta); + ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataDescriptor& meta) const; + ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) const; VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const; @@ -73,7 +74,8 @@ public: u128 user_id, u64 save_id); SaveDataSize ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const; - void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, SaveDataSize new_value); + void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, + SaveDataSize new_value) const; private: VirtualDir dir; diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index bd3a57058..5113a1ca6 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -6,6 +6,7 @@ #include "core/file_sys/registered_cache.h" #include "core/file_sys/sdmc_factory.h" #include "core/file_sys/xts_archive.h" +#include "core/settings.h" namespace FileSys { @@ -14,16 +15,38 @@ SDMCFactory::SDMCFactory(VirtualDir dir_) GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"), [](const VirtualFile& file, const NcaID& id) { return NAX{file, id}.GetDecrypted(); - })) {} + })), + placeholder(std::make_unique<PlaceholderCache>( + GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/placehld"))) {} SDMCFactory::~SDMCFactory() = default; -ResultVal<VirtualDir> SDMCFactory::Open() { +ResultVal<VirtualDir> SDMCFactory::Open() const { return MakeResult<VirtualDir>(dir); } +VirtualDir SDMCFactory::GetSDMCContentDirectory() const { + return GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents"); +} + RegisteredCache* SDMCFactory::GetSDMCContents() const { return contents.get(); } +PlaceholderCache* SDMCFactory::GetSDMCPlaceholder() const { + return placeholder.get(); +} + +VirtualDir SDMCFactory::GetImageDirectory() const { + return GetOrCreateDirectoryRelative(dir, "/Nintendo/Album"); +} + +u64 SDMCFactory::GetSDMCFreeSpace() const { + return GetSDMCTotalSpace() - dir->GetSize(); +} + +u64 SDMCFactory::GetSDMCTotalSpace() const { + return static_cast<u64>(Settings::values.sdmc_size); +} + } // namespace FileSys diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h index 42794ba5b..42dc4e08a 100644 --- a/src/core/file_sys/sdmc_factory.h +++ b/src/core/file_sys/sdmc_factory.h @@ -11,6 +11,7 @@ namespace FileSys { class RegisteredCache; +class PlaceholderCache; /// File system interface to the SDCard archive class SDMCFactory { @@ -18,13 +19,23 @@ public: explicit SDMCFactory(VirtualDir dir); ~SDMCFactory(); - ResultVal<VirtualDir> Open(); + ResultVal<VirtualDir> Open() const; + + VirtualDir GetSDMCContentDirectory() const; + RegisteredCache* GetSDMCContents() const; + PlaceholderCache* GetSDMCPlaceholder() const; + + VirtualDir GetImageDirectory() const; + + u64 GetSDMCFreeSpace() const; + u64 GetSDMCTotalSpace() const; private: VirtualDir dir; std::unique_ptr<RegisteredCache> contents; + std::unique_ptr<PlaceholderCache> placeholder; }; } // namespace FileSys diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index c69caae0f..ef3084681 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -14,6 +14,7 @@ #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/program_metadata.h" #include "core/file_sys/submission_package.h" #include "core/loader/loader.h" @@ -78,6 +79,10 @@ Loader::ResultStatus NSP::GetStatus() const { } Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const { + if (IsExtractedType() && GetExeFS() != nullptr && FileSys::IsDirectoryExeFS(GetExeFS())) { + return Loader::ResultStatus::Success; + } + const auto iter = program_status.find(title_id); if (iter == program_status.end()) return Loader::ResultStatus::ErrorNSPMissingProgramNCA; @@ -85,12 +90,29 @@ Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const { } u64 NSP::GetFirstTitleID() const { + if (IsExtractedType()) { + return GetProgramTitleID(); + } + if (program_status.empty()) return 0; return program_status.begin()->first; } u64 NSP::GetProgramTitleID() const { + if (IsExtractedType()) { + if (GetExeFS() == nullptr || !IsDirectoryExeFS(GetExeFS())) { + return 0; + } + + ProgramMetadata meta; + if (meta.Load(GetExeFS()->GetFile("main.npdm")) == Loader::ResultStatus::Success) { + return meta.GetTitleID(); + } else { + return 0; + } + } + const auto out = GetFirstTitleID(); if ((out & 0x800) == 0) return out; @@ -102,6 +124,10 @@ u64 NSP::GetProgramTitleID() const { } std::vector<u64> NSP::GetTitleIDs() const { + if (IsExtractedType()) { + return {GetProgramTitleID()}; + } + std::vector<u64> out; out.reserve(ncas.size()); for (const auto& kv : ncas) @@ -222,7 +248,8 @@ void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) { void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { for (const auto& outer_file : files) { - if (outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") { + if (outer_file->GetName().size() < 9 || + outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") { continue; } @@ -235,27 +262,33 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { const auto section0 = nca->GetSubdirectories()[0]; for (const auto& inner_file : section0->GetFiles()) { - if (inner_file->GetExtension() != "cnmt") + if (inner_file->GetExtension() != "cnmt") { continue; + } const CNMT cnmt(inner_file); auto& ncas_title = ncas[cnmt.GetTitleID()]; ncas_title[{cnmt.GetType(), ContentRecordType::Meta}] = nca; for (const auto& rec : cnmt.GetContentRecords()) { - const auto id_string = Common::HexArrayToString(rec.nca_id, false); - const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string)); + const auto id_string = Common::HexToString(rec.nca_id, false); + auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string)); + if (next_file == nullptr) { - LOG_WARNING(Service_FS, - "NCA with ID {}.nca is listed in content metadata, but cannot " - "be found in PFS. NSP appears to be corrupted.", - id_string); + if (rec.type != ContentRecordType::DeltaFragment) { + LOG_WARNING(Service_FS, + "NCA with ID {}.nca is listed in content metadata, but cannot " + "be found in PFS. NSP appears to be corrupted.", + id_string); + } + continue; } - auto next_nca = std::make_shared<NCA>(next_file, nullptr, 0, keys); - if (next_nca->GetType() == NCAContentType::Program) + auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0, keys); + if (next_nca->GetType() == NCAContentType::Program) { program_status[cnmt.GetTitleID()] = next_nca->GetStatus(); + } if (next_nca->GetStatus() == Loader::ResultStatus::Success || (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS && (cnmt.GetTitleID() & 0x800) != 0)) { diff --git a/src/core/file_sys/system_archive/mii_model.cpp b/src/core/file_sys/system_archive/mii_model.cpp new file mode 100644 index 000000000..6a9add87c --- /dev/null +++ b/src/core/file_sys/system_archive/mii_model.cpp @@ -0,0 +1,46 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/file_sys/system_archive/mii_model.h" +#include "core/file_sys/vfs_vector.h" + +namespace FileSys::SystemArchive { + +namespace MiiModelData { + +constexpr std::array<u8, 0x10> NFTR_STANDARD{'N', 'F', 'T', 'R', 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +constexpr std::array<u8, 0x10> NFSR_STANDARD{'N', 'F', 'S', 'R', 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +constexpr auto TEXTURE_LOW_LINEAR = NFTR_STANDARD; +constexpr auto TEXTURE_LOW_SRGB = NFTR_STANDARD; +constexpr auto TEXTURE_MID_LINEAR = NFTR_STANDARD; +constexpr auto TEXTURE_MID_SRGB = NFTR_STANDARD; +constexpr auto SHAPE_HIGH = NFSR_STANDARD; +constexpr auto SHAPE_MID = NFSR_STANDARD; + +} // namespace MiiModelData + +VirtualDir MiiModel() { + auto out = std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, + std::vector<VirtualDir>{}, "data"); + + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_LINEAR.size()>>( + MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_SRGB.size()>>( + MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_LINEAR.size()>>( + MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_SRGB.size()>>( + MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_HIGH.size()>>( + MiiModelData::SHAPE_HIGH, "ShapeHigh.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_MID.size()>>( + MiiModelData::SHAPE_MID, "ShapeMid.dat")); + + return std::move(out); +} + +} // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/mii_model.h b/src/core/file_sys/system_archive/mii_model.h new file mode 100644 index 000000000..6c2d9398b --- /dev/null +++ b/src/core/file_sys/system_archive/mii_model.h @@ -0,0 +1,13 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/file_sys/vfs_types.h" + +namespace FileSys::SystemArchive { + +VirtualDir MiiModel(); + +} // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp index c9722ed77..6d8445383 100644 --- a/src/core/file_sys/system_archive/system_archive.cpp +++ b/src/core/file_sys/system_archive/system_archive.cpp @@ -4,6 +4,7 @@ #include "common/logging/log.h" #include "core/file_sys/romfs.h" +#include "core/file_sys/system_archive/mii_model.h" #include "core/file_sys/system_archive/ng_word.h" #include "core/file_sys/system_archive/system_archive.h" #include "core/file_sys/system_archive/system_version.h" @@ -24,7 +25,7 @@ struct SystemArchiveDescriptor { constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{ {0x0100000000000800, "CertStore", nullptr}, {0x0100000000000801, "ErrorMessage", nullptr}, - {0x0100000000000802, "MiiModel", nullptr}, + {0x0100000000000802, "MiiModel", &MiiModel}, {0x0100000000000803, "BrowserDll", nullptr}, {0x0100000000000804, "Help", nullptr}, {0x0100000000000805, "SharedFont", nullptr}, diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp index eec51c64e..4bc5cb2ee 100644 --- a/src/core/file_sys/xts_archive.cpp +++ b/src/core/file_sys/xts_archive.cpp @@ -66,7 +66,7 @@ NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id) Core::Crypto::SHA256Hash hash{}; mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0); status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0], - Common::HexArrayToString(nca_id, false))); + Common::HexToString(nca_id, false))); } NAX::~NAX() = default; |