From 1722701c07088593f1db605ff97d9d6865899f5e Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 14 Jul 2020 16:14:30 +0200 Subject: [PATCH] [WIP] NCCHContainer: support for partitions if container is NCSD (#5345) * GetProgramLaunchInfo: improve to for 3ds files * NCSD: allow to load other partitions * fix typo * Update src/core/hle/service/fs/fs_user.cpp Co-authored-by: Valentin Vanelslande * Update src/core/hle/service/fs/fs_user.cpp Co-authored-by: Valentin Vanelslande Co-authored-by: Marshall Mohror Co-authored-by: Valentin Vanelslande --- src/core/file_sys/archive_ncch.cpp | 2 +- src/core/file_sys/ncch_container.cpp | 25 +++++-- src/core/file_sys/ncch_container.h | 31 +++++++- src/core/hle/service/am/am.cpp | 34 +++++---- src/core/hle/service/fs/archive.cpp | 10 +++ src/core/hle/service/fs/archive.h | 8 +++ src/core/hle/service/fs/fs_user.cpp | 103 ++++++++++++++++++++++++--- src/core/hle/service/fs/fs_user.h | 33 +++++++++ src/core/loader/3dsx.cpp | 6 ++ src/core/loader/ncch.cpp | 8 +++ 10 files changed, 226 insertions(+), 34 deletions(-) diff --git a/src/core/file_sys/archive_ncch.cpp b/src/core/file_sys/archive_ncch.cpp index ae2e97535..b658e9a29 100644 --- a/src/core/file_sys/archive_ncch.cpp +++ b/src/core/file_sys/archive_ncch.cpp @@ -90,7 +90,7 @@ ResultVal> NCCHArchive::OpenFile(const Path& path, std::string file_path = Service::AM::GetTitleContentPath(media_type, title_id, openfile_path.content_index); - auto ncch_container = NCCHContainer(file_path); + auto ncch_container = NCCHContainer(file_path, 0, openfile_path.content_index); Loader::ResultStatus result; std::unique_ptr file; diff --git a/src/core/file_sys/ncch_container.cpp b/src/core/file_sys/ncch_container.cpp index cb8a2d513..831c930fc 100644 --- a/src/core/file_sys/ncch_container.cpp +++ b/src/core/file_sys/ncch_container.cpp @@ -114,14 +114,16 @@ static bool LZSS_Decompress(const u8* compressed, u32 compressed_size, u8* decom return true; } -NCCHContainer::NCCHContainer(const std::string& filepath, u32 ncch_offset) - : ncch_offset(ncch_offset), filepath(filepath) { +NCCHContainer::NCCHContainer(const std::string& filepath, u32 ncch_offset, u32 partition) + : ncch_offset(ncch_offset), partition(partition), filepath(filepath) { file = FileUtil::IOFile(filepath, "rb"); } -Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath, u32 ncch_offset) { +Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath, u32 ncch_offset, + u32 partition) { this->filepath = filepath; this->ncch_offset = ncch_offset; + this->partition = partition; file = FileUtil::IOFile(filepath, "rb"); if (!file.IsOpen()) { @@ -150,8 +152,13 @@ Loader::ResultStatus NCCHContainer::LoadHeader() { // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)... if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) { - LOG_DEBUG(Service_FS, "Only loading the first (bootable) NCCH within the NCSD file!"); - ncch_offset += 0x4000; + NCSD_Header ncsd_header; + file.Seek(ncch_offset, SEEK_SET); + file.ReadBytes(&ncsd_header, sizeof(NCSD_Header)); + ASSERT(Loader::MakeMagic('N', 'C', 'S', 'D') == ncsd_header.magic); + ASSERT(partition < 8); + ncch_offset = ncsd_header.partitions[partition].offset * kBlockSize; + LOG_ERROR(Service_FS, "{}", ncch_offset); file.Seek(ncch_offset, SEEK_SET); file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); } @@ -178,8 +185,12 @@ Loader::ResultStatus NCCHContainer::Load() { // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)... if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) { - LOG_DEBUG(Service_FS, "Only loading the first (bootable) NCCH within the NCSD file!"); - ncch_offset += 0x4000; + NCSD_Header ncsd_header; + file.Seek(ncch_offset, SEEK_SET); + file.ReadBytes(&ncsd_header, sizeof(NCSD_Header)); + ASSERT(Loader::MakeMagic('N', 'C', 'S', 'D') == ncsd_header.magic); + ASSERT(partition < 8); + ncch_offset = ncsd_header.partitions[partition].offset * kBlockSize; file.Seek(ncch_offset, SEEK_SET); file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); } diff --git a/src/core/file_sys/ncch_container.h b/src/core/file_sys/ncch_container.h index 484218636..9db1d90be 100644 --- a/src/core/file_sys/ncch_container.h +++ b/src/core/file_sys/ncch_container.h @@ -15,6 +15,31 @@ #include "core/core.h" #include "core/file_sys/romfs_reader.h" +enum NCSDContentIndex { Main = 0, Manual = 1, DLP = 2, New3DSUpdate = 6, Update = 7 }; + +struct NCSD_Partitions { + u32 offset; + u32 size; +}; + +struct NCSD_Header { + u8 signature[0x100]; + u32_le magic; + u32_le media_size; + u8 media_id[8]; + u8 partition_fs_type[8]; + u8 partition_crypt_type[8]; + NCSD_Partitions partitions[8]; + u8 extended_header_hash[0x20]; + u32_le additional_header_size; + u32_le sector_zero_offset; + u8 partition_flags[8]; + u8 partition_id_table[0x40]; + u8 reserved[0x30]; +}; + +static_assert(sizeof(NCSD_Header) == 0x200, "NCCH header structure size is wrong"); + //////////////////////////////////////////////////////////////////////////////////////////////////// /// NCCH header (Note: "NCCH" appears to be a publicly unknown acronym) @@ -209,10 +234,11 @@ namespace FileSys { */ class NCCHContainer { public: - NCCHContainer(const std::string& filepath, u32 ncch_offset = 0); + NCCHContainer(const std::string& filepath, u32 ncch_offset = 0, u32 partition = 0); NCCHContainer() {} - Loader::ResultStatus OpenFile(const std::string& filepath, u32 ncch_offset = 0); + Loader::ResultStatus OpenFile(const std::string& filepath, u32 ncch_offset = 0, + u32 partition = 0); /** * Ensure NCCH header is loaded and ready for reading sections @@ -339,6 +365,7 @@ private: u32 ncch_offset = 0; // Offset to NCCH header, can be 0 for NCCHs or non-zero for CIAs/NCSDs u32 exefs_offset = 0; + u32 partition = 0; std::string filepath; FileUtil::IOFile file; diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 295269c27..c51c611f8 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -31,6 +31,7 @@ #include "core/hle/service/am/am_sys.h" #include "core/hle/service/am/am_u.h" #include "core/hle/service/fs/archive.h" +#include "core/hle/service/fs/fs_user.h" #include "core/loader/loader.h" #include "core/loader/smdh.h" @@ -465,16 +466,19 @@ std::string GetTitleMetadataPath(Service::FS::MediaType media_type, u64 tid, boo return content_path + fmt::format("{:08x}.tmd", (update ? update_id : base_id)); } -std::string GetTitleContentPath(FS::MediaType media_type, u64 tid, std::size_t index, bool update) { - std::string content_path = GetTitlePath(media_type, tid) + "content/"; +std::string GetTitleContentPath(Service::FS::MediaType media_type, u64 tid, std::size_t index, + bool update) { - if (media_type == FS::MediaType::GameCard) { - // TODO(shinyquagsire23): get current app file if TID matches? - LOG_ERROR(Service_AM, "Request for gamecard partition {} content path unimplemented!", - static_cast(index)); - return ""; + if (media_type == Service::FS::MediaType::GameCard) { + // TODO(B3N30): check if TID matches + auto fs_user = + Core::System::GetInstance().ServiceManager().GetService( + "fs:USER"); + return fs_user->GetCurrentGamecardPath(); } + std::string content_path = GetTitlePath(media_type, tid) + "content/"; + std::string tmd_path = GetTitleMetadataPath(media_type, tid, update); u32 content_id = 0; @@ -509,9 +513,11 @@ std::string GetTitlePath(Service::FS::MediaType media_type, u64 tid) { return fmt::format("{}{:08x}/{:08x}/", GetMediaTitlePath(media_type), high, low); if (media_type == Service::FS::MediaType::GameCard) { - // TODO(shinyquagsire23): get current app path if TID matches? - LOG_ERROR(Service_AM, "Request for gamecard title path unimplemented!"); - return ""; + // TODO(B3N30): check if TID matches + auto fs_user = + Core::System::GetInstance().ServiceManager().GetService( + "fs:USER"); + return fs_user->GetCurrentGamecardPath(); } return ""; @@ -528,9 +534,11 @@ std::string GetMediaTitlePath(Service::FS::MediaType media_type) { SDCARD_ID); if (media_type == Service::FS::MediaType::GameCard) { - // TODO(shinyquagsire23): get current app parent folder if TID matches? - LOG_ERROR(Service_AM, "Request for gamecard parent path unimplemented!"); - return ""; + // TODO(B3N30): check if TID matchess + auto fs_user = + Core::System::GetInstance().ServiceManager().GetService( + "fs:USER"); + return fs_user->GetCurrentGamecardPath(); } return ""; diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index bca2d7934..28121d7f0 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -31,6 +31,16 @@ namespace Service::FS { +MediaType GetMediaTypeFromPath(std::string_view path) { + if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), 0) == 0) { + return MediaType::NAND; + } + if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), 0) == 0) { + return MediaType::SDMC; + } + return MediaType::GameCard; +} + ArchiveBackend* ArchiveManager::GetArchive(ArchiveHandle handle) { auto itr = handle_map.find(handle); return (itr == handle_map.end()) ? nullptr : itr->second.get(); diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 4ce61a3c1..b11167807 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -48,6 +48,14 @@ enum class ArchiveIdCode : u32 { /// Media types for the archives enum class MediaType : u32 { NAND = 0, SDMC = 1, GameCard = 2 }; +MediaType GetMediaTypeFromPath(std::string_view path); + +enum class SpecialContentType : u8 { + Update = 1, + Manual = 2, + DLPChild = 3, +}; + typedef u64 ArchiveHandle; struct ArchiveResource { diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index 831cecca2..ed8ede529 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -12,6 +12,7 @@ #include "common/string_util.h" #include "core/core.h" #include "core/file_sys/errors.h" +#include "core/file_sys/ncch_container.h" #include "core/file_sys/seed_db.h" #include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" @@ -682,13 +683,11 @@ void FS_USER::GetProgramLaunchInfo(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "process_id={}", process_id); - // TODO(Subv): The real FS service manages its own process list and only checks the processes - // that were registered with the 'fs:REG' service. - auto process = system.Kernel().GetProcessById(process_id); + auto program_info = program_info_map.find(process_id); IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); - if (process == nullptr) { + if (program_info == program_info_map.end()) { // Note: In this case, the rest of the parameters are not changed but the command header // remains the same. rb.Push(ResultCode(FileSys::ErrCodes::ArchiveNotMounted, ErrorModule::FS, @@ -697,13 +696,9 @@ void FS_USER::GetProgramLaunchInfo(Kernel::HLERequestContext& ctx) { return; } - u64 program_id = process->codeset->program_id; - - auto media_type = Service::AM::GetTitleMediaType(program_id); - rb.Push(RESULT_SUCCESS); - rb.Push(program_id); - rb.Push(static_cast(media_type)); + rb.Push(program_info->second.program_id); + rb.Push(static_cast(program_info->second.media_type)); // TODO(Subv): Find out what this value means. rb.Push(0); @@ -752,6 +747,32 @@ void FS_USER::ObsoletedDeleteExtSaveData(Kernel::HLERequestContext& ctx) { static_cast(media_type)); } +void FS_USER::GetSpecialContentIndex(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x83A, 4, 0); + const MediaType media_type = static_cast(rp.Pop()); + const u64 title_id = rp.Pop(); + const auto type = rp.PopEnum(); + + LOG_DEBUG(Service_FS, "called, media_type={:08X} type={:08X}, title_id={:016X}", + static_cast(media_type), static_cast(type), title_id); + + ResultVal index; + if (media_type == MediaType::GameCard) { + index = GetSpecialContentIndexFromGameCard(title_id, type); + } else { + index = GetSpecialContentIndexFromTMD(media_type, title_id, type); + } + + if (index.Succeeded()) { + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(index.Unwrap()); + } else { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(index.Code()); + } +} + void FS_USER::GetNumSeeds(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb{ctx, 0x87D, 2, 0}; rb.Push(RESULT_SUCCESS); @@ -808,6 +829,66 @@ void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { rb.Push(0); // the secure value } +void FS_USER::Register(u32 process_id, u64 program_id, const std::string& filepath) { + const MediaType media_type = GetMediaTypeFromPath(filepath); + program_info_map.insert_or_assign(process_id, ProgramInfo{program_id, media_type}); + if (media_type == MediaType::GameCard) { + current_gamecard_path = filepath; + } +} + +std::string FS_USER::GetCurrentGamecardPath() const { + return current_gamecard_path; +} + +ResultVal FS_USER::GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type) { + // TODO(B3N30) check if on real 3DS NCSD is checked if partition exists + + if (type > SpecialContentType::DLPChild) { + // Maybe type 4 is New 3DS update/partition 6 but this needs more research + // TODO(B3N30): Find correct result code + return ResultCode(-1); + } + + switch (type) { + case SpecialContentType::Update: + return MakeResult(static_cast(NCSDContentIndex::Update)); + case SpecialContentType::Manual: + return MakeResult(static_cast(NCSDContentIndex::Manual)); + case SpecialContentType::DLPChild: + return MakeResult(static_cast(NCSDContentIndex::DLP)); + default: + ASSERT(false); + } +} + +ResultVal FS_USER::GetSpecialContentIndexFromTMD(MediaType media_type, u64 title_id, + SpecialContentType type) { + if (type > SpecialContentType::DLPChild) { + // TODO(B3N30): Find correct result code + return ResultCode(-1); + } + + std::string tmd_path = AM::GetTitleMetadataPath(media_type, title_id); + + FileSys::TitleMetadata tmd; + if (tmd.Load(tmd_path) != Loader::ResultStatus::Success || type == SpecialContentType::Update) { + // TODO(B3N30): Find correct result code + return ResultCode(-1); + } + + // TODO(B3N30): Does real 3DS check if content exists in TMD? + + switch (type) { + case SpecialContentType::Manual: + return MakeResult(static_cast(FileSys::TMDContentIndex::Manual)); + case SpecialContentType::DLPChild: + return MakeResult(static_cast(FileSys::TMDContentIndex::DLP)); + default: + ASSERT(false); + } +} + FS_USER::FS_USER(Core::System& system) : ServiceFramework("fs:USER", 30), system(system), archives(system.ArchiveManager()) { static const FunctionInfo functions[] = { @@ -870,7 +951,7 @@ FS_USER::FS_USER(Core::System& system) {0x08370040, nullptr, "SetCardSpiBaudRate"}, {0x08380040, nullptr, "SetCardSpiBusMode"}, {0x08390000, nullptr, "SendInitializeInfoTo9"}, - {0x083A0100, nullptr, "GetSpecialContentIndex"}, + {0x083A0100, &FS_USER::GetSpecialContentIndex, "GetSpecialContentIndex"}, {0x083B00C2, nullptr, "GetLegacyRomHeader"}, {0x083C00C2, nullptr, "GetLegacyBannerData"}, {0x083D0100, nullptr, "CheckAuthorityToAccessExtSaveData"}, diff --git a/src/core/hle/service/fs/fs_user.h b/src/core/hle/service/fs/fs_user.h index 243b8f858..3962b8d6a 100644 --- a/src/core/hle/service/fs/fs_user.h +++ b/src/core/hle/service/fs/fs_user.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include "common/common_types.h" #include "core/hle/service/service.h" @@ -38,6 +39,12 @@ class FS_USER final : public ServiceFramework { public: explicit FS_USER(Core::System& system); + // On real HW this is part of FS:Reg. But since that module is only used by loader and pm, which + // we HLEed, we can just directly use it here + void Register(u32 process_id, u64 program_id, const std::string& filepath); + + std::string GetCurrentGamecardPath() const; + private: void Initialize(Kernel::HLERequestContext& ctx); @@ -525,6 +532,20 @@ private: */ void ObsoletedDeleteExtSaveData(Kernel::HLERequestContext& ctx); + /** + * FS_User::GetSpecialContentIndex service function. + * Inputs: + * 0 : 0x083A0100 + * 1 : Media type + * 2-3 : Program ID + * 4 : Special content type + * Outputs: + * 0 : 0x083A0080 + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Special content index + */ + void GetSpecialContentIndex(Kernel::HLERequestContext& ctx); + /** * FS_User::GetNumSeeds service function. * Inputs: @@ -577,6 +598,18 @@ private: */ void GetSaveDataSecureValue(Kernel::HLERequestContext& ctx); + static ResultVal GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type); + static ResultVal GetSpecialContentIndexFromTMD(MediaType media_type, u64 title_id, + SpecialContentType type); + + struct ProgramInfo { + u64 program_id; + MediaType media_type; + }; + + std::unordered_map program_info_map; + std::string current_gamecard_path; + u32 priority = -1; ///< For SetPriority and GetPriority service functions Core::System& system; diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 84321011b..40c2bd1fb 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -9,6 +9,7 @@ #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/service/fs/archive.h" +#include "core/hle/service/fs/fs_user.h" #include "core/loader/3dsx.h" #include "core/memory.h" @@ -274,6 +275,11 @@ ResultStatus AppLoader_THREEDSX::Load(std::shared_ptr& process) process->resource_limit = Core::System::GetInstance().Kernel().ResourceLimit().GetForCategory( Kernel::ResourceLimitCategory::APPLICATION); + // On real HW this is done with FS:Reg, but we can be lazy + auto fs_user = + Core::System::GetInstance().ServiceManager().GetService("fs:USER"); + fs_user->Register(process->GetObjectId(), process->codeset->program_id, filepath); + process->Run(48, Kernel::DEFAULT_STACK_SIZE); Core::System::GetInstance().ArchiveManager().RegisterSelfNCCH(*this); diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index b2efe0198..6f32cc2eb 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -21,6 +21,7 @@ #include "core/hle/service/am/am.h" #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/fs/archive.h" +#include "core/hle/service/fs/fs_user.h" #include "core/loader/ncch.h" #include "core/loader/smdh.h" #include "core/memory.h" @@ -142,6 +143,13 @@ ResultStatus AppLoader_NCCH::LoadExec(std::shared_ptr& process) s32 priority = overlay_ncch->exheader_header.arm11_system_local_caps.priority; u32 stack_size = overlay_ncch->exheader_header.codeset_info.stack_size; + + // On real HW this is done with FS:Reg, but we can be lazy + auto fs_user = + Core::System::GetInstance().ServiceManager().GetService( + "fs:USER"); + fs_user->Register(process->process_id, process->codeset->program_id, filepath); + process->Run(priority, stack_size); return ResultStatus::Success; }