diff options
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/core/file_sys/partition_filesystem.cpp | 18 | ||||
-rw-r--r-- | src/core/file_sys/partition_filesystem.h | 2 | ||||
-rw-r--r-- | src/core/hle/kernel/hle_ipc.cpp | 5 | ||||
-rw-r--r-- | src/core/hle/service/audio/audren_u.cpp | 103 | ||||
-rw-r--r-- | src/core/hle/service/audio/audren_u.h | 6 | ||||
-rw-r--r-- | src/core/loader/loader.cpp | 10 | ||||
-rw-r--r-- | src/core/loader/loader.h | 1 | ||||
-rw-r--r-- | src/core/loader/nca.cpp | 303 | ||||
-rw-r--r-- | src/core/loader/nca.h | 49 | ||||
-rw-r--r-- | src/core/loader/nso.cpp | 79 | ||||
-rw-r--r-- | src/core/loader/nso.h | 3 |
12 files changed, 517 insertions, 64 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 051eda98d..51e4088d2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -259,6 +259,8 @@ add_library(core STATIC loader/linker.h loader/loader.cpp loader/loader.h + loader/nca.cpp + loader/nca.h loader/nro.cpp loader/nro.h loader/nso.cpp diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp index 86a01a5eb..874b9e23b 100644 --- a/src/core/file_sys/partition_filesystem.cpp +++ b/src/core/file_sys/partition_filesystem.cpp @@ -19,13 +19,20 @@ Loader::ResultStatus PartitionFilesystem::Load(const std::string& file_path, siz if (file.GetSize() < sizeof(Header)) return Loader::ResultStatus::Error; + file.Seek(offset, SEEK_SET); // For cartridges, HFSs can get very large, so we need to calculate the size up to // the actual content itself instead of just blindly reading in the entire file. Header pfs_header; if (!file.ReadBytes(&pfs_header, sizeof(Header))) return Loader::ResultStatus::Error; - bool is_hfs = (memcmp(pfs_header.magic.data(), "HFS", 3) == 0); + if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') && + pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) { + return Loader::ResultStatus::ErrorInvalidFormat; + } + + bool is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0'); + size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry); size_t metadata_size = sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size; @@ -50,7 +57,12 @@ Loader::ResultStatus PartitionFilesystem::Load(const std::vector<u8>& file_data, return Loader::ResultStatus::Error; memcpy(&pfs_header, &file_data[offset], sizeof(Header)); - is_hfs = (memcmp(pfs_header.magic.data(), "HFS", 3) == 0); + if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') && + pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) { + return Loader::ResultStatus::ErrorInvalidFormat; + } + + is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0'); size_t entries_offset = offset + sizeof(Header); size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry); @@ -113,7 +125,7 @@ u64 PartitionFilesystem::GetFileSize(const std::string& name) const { } void PartitionFilesystem::Print() const { - NGLOG_DEBUG(Service_FS, "Magic: {:.4}", pfs_header.magic.data()); + NGLOG_DEBUG(Service_FS, "Magic: {}", pfs_header.magic); NGLOG_DEBUG(Service_FS, "Files: {}", pfs_header.num_entries); for (u32 i = 0; i < pfs_header.num_entries; i++) { NGLOG_DEBUG(Service_FS, " > File {}: {} (0x{:X} bytes, at 0x{:X})", i, diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h index 65cf572f4..9c5810cf1 100644 --- a/src/core/file_sys/partition_filesystem.h +++ b/src/core/file_sys/partition_filesystem.h @@ -37,7 +37,7 @@ public: private: struct Header { - std::array<char, 4> magic; + u32_le magic; u32_le num_entries; u32_le strtab_size; INSERT_PADDING_BYTES(0x4); diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 01904467e..b0d83f401 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -271,6 +271,11 @@ std::vector<u8> HLERequestContext::ReadBuffer(int buffer_index) const { } size_t HLERequestContext::WriteBuffer(const void* buffer, size_t size, int buffer_index) const { + if (size == 0) { + NGLOG_WARNING(Core, "skip empty buffer write"); + return 0; + } + const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[buffer_index].Size()}; const size_t buffer_size{GetWriteBufferSize(buffer_index)}; if (size > buffer_size) { diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index c7653f654..44b7ef216 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -57,27 +57,26 @@ private: } void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) { - NGLOG_DEBUG(Service_Audio, "{}", ctx.Description()); - AudioRendererResponseData response_data{}; - - response_data.section_0_size = - static_cast<u32>(response_data.state_entries.size() * sizeof(AudioRendererStateEntry)); - response_data.section_1_size = static_cast<u32>(response_data.section_1.size()); - response_data.section_2_size = static_cast<u32>(response_data.section_2.size()); - response_data.section_3_size = static_cast<u32>(response_data.section_3.size()); - response_data.section_4_size = static_cast<u32>(response_data.section_4.size()); - response_data.section_5_size = static_cast<u32>(response_data.section_5.size()); - response_data.total_size = sizeof(AudioRendererResponseData); - - for (unsigned i = 0; i < response_data.state_entries.size(); i++) { - // 4 = Busy and 5 = Ready? - response_data.state_entries[i].state = 5; + AudioRendererConfig config; + auto buf = ctx.ReadBuffer(); + std::memcpy(&config, buf.data(), sizeof(AudioRendererConfig)); + + AudioRendererResponse response_data{config}; + + ASSERT(ctx.GetWriteBufferSize() == response_data.total_size); + + std::vector<u8> output(response_data.total_size); + std::memcpy(output.data(), &response_data, sizeof(AudioRendererResponse)); + std::vector<MemoryPoolEntry> memory_pool(config.memory_pools_size / 0x20); + for (auto& entry : memory_pool) { + entry.state = 5; } + std::memcpy(output.data() + sizeof(AudioRendererResponse), memory_pool.data(), + response_data.memory_pools_size); - ctx.WriteBuffer(&response_data, response_data.total_size); + ctx.WriteBuffer(output); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); NGLOG_WARNING(Service_Audio, "(STUBBED) called"); @@ -109,43 +108,55 @@ private: NGLOG_WARNING(Service_Audio, "(STUBBED) called"); } - struct AudioRendererStateEntry { + struct MemoryPoolEntry { u32_le state; u32_le unknown_4; u32_le unknown_8; u32_le unknown_c; }; - static_assert(sizeof(AudioRendererStateEntry) == 0x10, - "AudioRendererStateEntry has wrong size"); - - struct AudioRendererResponseData { - u32_le unknown_0; - u32_le section_5_size; - u32_le section_0_size; - u32_le section_1_size; + static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size"); + + struct AudioRendererConfig { + u32 revision; + u32 behavior_size; + u32 memory_pools_size; + u32 voices_size; + u32 voice_resource_size; + u32 effects_size; + u32 mixes_size; + u32 sinks_size; + u32 performance_buffer_size; + INSERT_PADDING_WORDS(6); + u32 total_size; + }; + static_assert(sizeof(AudioRendererConfig) == 0x40, "AudioRendererConfig has wrong size"); + + struct AudioRendererResponse { + AudioRendererResponse(const AudioRendererConfig& config) { + revision = config.revision; + error_info_size = 0xb0; + memory_pools_size = (config.memory_pools_size / 0x20) * 0x10; + voices_size = (config.voices_size / 0x170) * 0x10; + effects_size = (config.effects_size / 0xC0) * 0x10; + sinks_size = (config.sinks_size / 0x140) * 0x20; + performance_manager_size = 0x10; + total_size = sizeof(AudioRendererResponse) + error_info_size + memory_pools_size + + voices_size + effects_size + sinks_size + performance_manager_size; + } + + u32_le revision; + u32_le error_info_size; + u32_le memory_pools_size; + u32_le voices_size; u32_le unknown_10; - u32_le section_2_size; + u32_le effects_size; u32_le unknown_18; - u32_le section_3_size; - u32_le section_4_size; - u32_le unknown_24; - u32_le unknown_28; - u32_le unknown_2c; - u32_le unknown_30; - u32_le unknown_34; - u32_le unknown_38; + u32_le sinks_size; + u32_le performance_manager_size; + INSERT_PADDING_WORDS(6); u32_le total_size; - - std::array<AudioRendererStateEntry, 0x18e> state_entries; - - std::array<u8, 0x600> section_1; - std::array<u8, 0xe0> section_2; - std::array<u8, 0x20> section_3; - std::array<u8, 0x10> section_4; - std::array<u8, 0xb0> section_5; }; - static_assert(sizeof(AudioRendererResponseData) == 0x20e0, - "AudioRendererResponseData has wrong size"); + static_assert(sizeof(AudioRendererResponse) == 0x40, "AudioRendererResponse has wrong size"); /// This is used to trigger the audio event callback. CoreTiming::EventType* audio_event; @@ -258,7 +269,7 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - auto params = rp.PopRaw<WorkerBufferParameters>(); + auto params = rp.PopRaw<AudioRendererParameters>(); u64 buffer_sz = Common::AlignUp(4 * params.unknown8, 0x40); buffer_sz += params.unknownC * 1024; diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index fe53de4ce..7dbd9b74d 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h @@ -22,7 +22,7 @@ private: void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx); void GetAudioDevice(Kernel::HLERequestContext& ctx); - struct WorkerBufferParameters { + struct AudioRendererParameters { u32_le sample_rate; u32_le sample_count; u32_le unknown8; @@ -38,8 +38,8 @@ private: u8 padding2[4]; u32_le magic; }; - static_assert(sizeof(WorkerBufferParameters) == 52, - "WorkerBufferParameters is an invalid size"); + static_assert(sizeof(AudioRendererParameters) == 52, + "AudioRendererParameters is an invalid size"); enum class AudioFeatures : u32 { Splitter, diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 6a4fd38cb..20cc0bac0 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -9,6 +9,7 @@ #include "core/hle/kernel/process.h" #include "core/loader/deconstructed_rom_directory.h" #include "core/loader/elf.h" +#include "core/loader/nca.h" #include "core/loader/nro.h" #include "core/loader/nso.h" @@ -32,6 +33,7 @@ FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath) { CHECK_TYPE(ELF) CHECK_TYPE(NSO) CHECK_TYPE(NRO) + CHECK_TYPE(NCA) #undef CHECK_TYPE @@ -57,6 +59,8 @@ FileType GuessFromExtension(const std::string& extension_) { return FileType::NRO; else if (extension == ".nso") return FileType::NSO; + else if (extension == ".nca") + return FileType::NCA; return FileType::Unknown; } @@ -69,6 +73,8 @@ const char* GetFileTypeString(FileType type) { return "NRO"; case FileType::NSO: return "NSO"; + case FileType::NCA: + return "NCA"; case FileType::DeconstructedRomDirectory: return "Directory"; case FileType::Error: @@ -104,6 +110,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileTyp case FileType::NRO: return std::make_unique<AppLoader_NRO>(std::move(file), filepath); + // NX NCA file format. + case FileType::NCA: + return std::make_unique<AppLoader_NCA>(std::move(file), filepath); + // NX deconstructed ROM directory. case FileType::DeconstructedRomDirectory: return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file), filepath); diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index b1aabb1cb..b76f7b13d 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -29,6 +29,7 @@ enum class FileType { ELF, NSO, NRO, + NCA, DeconstructedRomDirectory, }; diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp new file mode 100644 index 000000000..067945d46 --- /dev/null +++ b/src/core/loader/nca.cpp @@ -0,0 +1,303 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <vector> + +#include "common/common_funcs.h" +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/swap.h" +#include "core/core.h" +#include "core/file_sys/program_metadata.h" +#include "core/file_sys/romfs_factory.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/resource_limit.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/nca.h" +#include "core/loader/nso.h" +#include "core/memory.h" + +namespace Loader { + +// Media offsets in headers are stored divided by 512. Mult. by this to get real offset. +constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200; + +constexpr u64 SECTION_HEADER_SIZE = 0x200; +constexpr u64 SECTION_HEADER_OFFSET = 0x400; + +enum class NcaContentType : u8 { Program = 0, Meta = 1, Control = 2, Manual = 3, Data = 4 }; + +enum class NcaSectionFilesystemType : u8 { PFS0 = 0x2, ROMFS = 0x3 }; + +struct NcaSectionTableEntry { + u32_le media_offset; + u32_le media_end_offset; + INSERT_PADDING_BYTES(0x8); +}; +static_assert(sizeof(NcaSectionTableEntry) == 0x10, "NcaSectionTableEntry has incorrect size."); + +struct NcaHeader { + std::array<u8, 0x100> rsa_signature_1; + std::array<u8, 0x100> rsa_signature_2; + u32_le magic; + u8 is_system; + NcaContentType content_type; + u8 crypto_type; + u8 key_index; + u64_le size; + u64_le title_id; + INSERT_PADDING_BYTES(0x4); + u32_le sdk_version; + u8 crypto_type_2; + INSERT_PADDING_BYTES(15); + std::array<u8, 0x10> rights_id; + std::array<NcaSectionTableEntry, 0x4> section_tables; + std::array<std::array<u8, 0x20>, 0x4> hash_tables; + std::array<std::array<u8, 0x10>, 0x4> key_area; + INSERT_PADDING_BYTES(0xC0); +}; +static_assert(sizeof(NcaHeader) == 0x400, "NcaHeader has incorrect size."); + +struct NcaSectionHeaderBlock { + INSERT_PADDING_BYTES(3); + NcaSectionFilesystemType filesystem_type; + u8 crypto_type; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(NcaSectionHeaderBlock) == 0x8, "NcaSectionHeaderBlock has incorrect size."); + +struct Pfs0Superblock { + NcaSectionHeaderBlock header_block; + std::array<u8, 0x20> hash; + u32_le size; + INSERT_PADDING_BYTES(4); + u64_le hash_table_offset; + u64_le hash_table_size; + u64_le pfs0_header_offset; + u64_le pfs0_size; + INSERT_PADDING_BYTES(432); +}; +static_assert(sizeof(Pfs0Superblock) == 0x200, "Pfs0Superblock has incorrect size."); + +static bool IsValidNca(const NcaHeader& header) { + return header.magic == Common::MakeMagic('N', 'C', 'A', '2') || + header.magic == Common::MakeMagic('N', 'C', 'A', '3'); +} + +// TODO(DarkLordZach): Add support for encrypted. +class Nca final { + std::vector<FileSys::PartitionFilesystem> pfs; + std::vector<u64> pfs_offset; + + u64 romfs_offset = 0; + u64 romfs_size = 0; + + boost::optional<u8> exefs_id = boost::none; + + FileUtil::IOFile file; + std::string path; + + u64 GetExeFsFileOffset(const std::string& file_name) const; + u64 GetExeFsFileSize(const std::string& file_name) const; + +public: + ResultStatus Load(FileUtil::IOFile&& file, std::string path); + + FileSys::PartitionFilesystem GetPfs(u8 id) const; + + u64 GetRomFsOffset() const; + u64 GetRomFsSize() const; + + std::vector<u8> GetExeFsFile(const std::string& file_name); +}; + +static bool IsPfsExeFs(const FileSys::PartitionFilesystem& pfs) { + // According to switchbrew, an exefs must only contain these two files: + return pfs.GetFileSize("main") > 0 && pfs.GetFileSize("main.npdm") > 0; +} + +ResultStatus Nca::Load(FileUtil::IOFile&& in_file, std::string in_path) { + file = std::move(in_file); + path = in_path; + file.Seek(0, SEEK_SET); + std::array<u8, sizeof(NcaHeader)> header_array{}; + if (sizeof(NcaHeader) != file.ReadBytes(header_array.data(), sizeof(NcaHeader))) + NGLOG_CRITICAL(Loader, "File reader errored out during header read."); + + NcaHeader header{}; + std::memcpy(&header, header_array.data(), sizeof(NcaHeader)); + if (!IsValidNca(header)) + return ResultStatus::ErrorInvalidFormat; + + int number_sections = + std::count_if(std::begin(header.section_tables), std::end(header.section_tables), + [](NcaSectionTableEntry entry) { return entry.media_offset > 0; }); + + for (int i = 0; i < number_sections; ++i) { + // Seek to beginning of this section. + file.Seek(SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE, SEEK_SET); + std::array<u8, sizeof(NcaSectionHeaderBlock)> array{}; + if (sizeof(NcaSectionHeaderBlock) != + file.ReadBytes(array.data(), sizeof(NcaSectionHeaderBlock))) + NGLOG_CRITICAL(Loader, "File reader errored out during header read."); + + NcaSectionHeaderBlock block{}; + std::memcpy(&block, array.data(), sizeof(NcaSectionHeaderBlock)); + + if (block.filesystem_type == NcaSectionFilesystemType::ROMFS) { + romfs_offset = header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER; + romfs_size = + header.section_tables[i].media_end_offset * MEDIA_OFFSET_MULTIPLIER - romfs_offset; + } else if (block.filesystem_type == NcaSectionFilesystemType::PFS0) { + Pfs0Superblock sb{}; + // Seek back to beginning of this section. + file.Seek(SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE, SEEK_SET); + if (sizeof(Pfs0Superblock) != file.ReadBytes(&sb, sizeof(Pfs0Superblock))) + NGLOG_CRITICAL(Loader, "File reader errored out during header read."); + + u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * + MEDIA_OFFSET_MULTIPLIER) + + sb.pfs0_header_offset; + FileSys::PartitionFilesystem npfs{}; + ResultStatus status = npfs.Load(path, offset); + + if (status == ResultStatus::Success) { + pfs.emplace_back(std::move(npfs)); + pfs_offset.emplace_back(offset); + } + } + } + + for (size_t i = 0; i < pfs.size(); ++i) { + if (IsPfsExeFs(pfs[i])) + exefs_id = i; + } + + return ResultStatus::Success; +} + +FileSys::PartitionFilesystem Nca::GetPfs(u8 id) const { + return pfs[id]; +} + +u64 Nca::GetExeFsFileOffset(const std::string& file_name) const { + if (exefs_id == boost::none) + return 0; + return pfs[*exefs_id].GetFileOffset(file_name) + pfs_offset[*exefs_id]; +} + +u64 Nca::GetExeFsFileSize(const std::string& file_name) const { + if (exefs_id == boost::none) + return 0; + return pfs[*exefs_id].GetFileSize(file_name); +} + +u64 Nca::GetRomFsOffset() const { + return romfs_offset; +} + +u64 Nca::GetRomFsSize() const { + return romfs_size; +} + +std::vector<u8> Nca::GetExeFsFile(const std::string& file_name) { + std::vector<u8> out(GetExeFsFileSize(file_name)); + file.Seek(GetExeFsFileOffset(file_name), SEEK_SET); + file.ReadBytes(out.data(), GetExeFsFileSize(file_name)); + return out; +} + +AppLoader_NCA::AppLoader_NCA(FileUtil::IOFile&& file, std::string filepath) + : AppLoader(std::move(file)), filepath(std::move(filepath)) {} + +FileType AppLoader_NCA::IdentifyType(FileUtil::IOFile& file, const std::string&) { + file.Seek(0, SEEK_SET); + std::array<u8, 0x400> header_enc_array{}; + if (0x400 != file.ReadBytes(header_enc_array.data(), 0x400)) + return FileType::Error; + + // TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support. + NcaHeader header{}; + std::memcpy(&header, header_enc_array.data(), sizeof(NcaHeader)); + + if (IsValidNca(header) && header.content_type == NcaContentType::Program) + return FileType::NCA; + + return FileType::Error; +} + +ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) { + if (is_loaded) { + return ResultStatus::ErrorAlreadyLoaded; + } + if (!file.IsOpen()) { + return ResultStatus::Error; + } + + nca = std::make_unique<Nca>(); + ResultStatus result = nca->Load(std::move(file), filepath); + if (result != ResultStatus::Success) { + return result; + } + + result = metadata.Load(nca->GetExeFsFile("main.npdm")); + if (result != ResultStatus::Success) { + return result; + } + metadata.Print(); + + const FileSys::ProgramAddressSpaceType arch_bits{metadata.GetAddressSpaceType()}; + if (arch_bits == FileSys::ProgramAddressSpaceType::Is32Bit) { + return ResultStatus::ErrorUnsupportedArch; + } + + VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR}; + for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", + "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) { + const VAddr load_addr = next_load_addr; + next_load_addr = AppLoader_NSO::LoadModule(module, nca->GetExeFsFile(module), load_addr); + if (next_load_addr) { + NGLOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); + } else { + next_load_addr = load_addr; + } + } + + process->program_id = metadata.GetTitleID(); + process->svc_access_mask.set(); + process->address_mappings = default_address_mappings; + process->resource_limit = + Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); + process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(), + metadata.GetMainThreadStackSize()); + + if (nca->GetRomFsSize() > 0) + Service::FileSystem::RegisterFileSystem(std::make_unique<FileSys::RomFS_Factory>(*this), + Service::FileSystem::Type::RomFS); + + is_loaded = true; + return ResultStatus::Success; +} + +ResultStatus AppLoader_NCA::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, + u64& size) { + if (nca->GetRomFsSize() == 0) { + NGLOG_DEBUG(Loader, "No RomFS available"); + return ResultStatus::ErrorNotUsed; + } + + romfs_file = std::make_shared<FileUtil::IOFile>(filepath, "rb"); + + offset = nca->GetRomFsOffset(); + size = nca->GetRomFsSize(); + + NGLOG_DEBUG(Loader, "RomFS offset: 0x{:016X}", offset); + NGLOG_DEBUG(Loader, "RomFS size: 0x{:016X}", size); + + return ResultStatus::Success; +} + +AppLoader_NCA::~AppLoader_NCA() = default; + +} // namespace Loader diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h new file mode 100644 index 000000000..3b6c451d0 --- /dev/null +++ b/src/core/loader/nca.h @@ -0,0 +1,49 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include "common/common_types.h" +#include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/program_metadata.h" +#include "core/hle/kernel/kernel.h" +#include "core/loader/loader.h" + +namespace Loader { + +class Nca; + +/// Loads an NCA file +class AppLoader_NCA final : public AppLoader { +public: + AppLoader_NCA(FileUtil::IOFile&& file, std::string filepath); + + /** + * Returns the type of the file + * @param file FileUtil::IOFile open file + * @param filepath Path of the file that we are opening. + * @return FileType found, or FileType::Error if this loader doesn't know it + */ + static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath); + + FileType GetFileType() override { + return IdentifyType(file, filepath); + } + + ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; + + ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, + u64& size) override; + + ~AppLoader_NCA(); + +private: + std::string filepath; + FileSys::ProgramMetadata metadata; + + std::unique_ptr<Nca> nca; +}; + +} // namespace Loader diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 01be9e217..845ed7e90 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -66,8 +66,22 @@ FileType AppLoader_NSO::IdentifyType(FileUtil::IOFile& file, const std::string&) return FileType::Error; } +static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, + const NsoSegmentHeader& header) { + std::vector<u8> uncompressed_data; + uncompressed_data.resize(header.size); + const int bytes_uncompressed = LZ4_decompress_safe( + reinterpret_cast<const char*>(compressed_data.data()), + reinterpret_cast<char*>(uncompressed_data.data()), compressed_data.size(), header.size); + + ASSERT_MSG(bytes_uncompressed == header.size && bytes_uncompressed == uncompressed_data.size(), + "{} != {} != {}", bytes_uncompressed, header.size, uncompressed_data.size()); + + return uncompressed_data; +} + static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NsoSegmentHeader& header, - int compressed_size) { + size_t compressed_size) { std::vector<u8> compressed_data; compressed_data.resize(compressed_size); @@ -77,22 +91,65 @@ static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NsoSegmentHeade return {}; } - std::vector<u8> uncompressed_data; - uncompressed_data.resize(header.size); - const int bytes_uncompressed = LZ4_decompress_safe( - reinterpret_cast<const char*>(compressed_data.data()), - reinterpret_cast<char*>(uncompressed_data.data()), compressed_size, header.size); - - ASSERT_MSG(bytes_uncompressed == header.size && bytes_uncompressed == uncompressed_data.size(), - "{} != {} != {}", bytes_uncompressed, header.size, uncompressed_data.size()); - - return uncompressed_data; + return DecompressSegment(compressed_data, header); } static constexpr u32 PageAlignSize(u32 size) { return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; } +VAddr AppLoader_NSO::LoadModule(const std::string& name, const std::vector<u8>& file_data, + VAddr load_base) { + if (file_data.size() < sizeof(NsoHeader)) + return {}; + + NsoHeader nso_header; + std::memcpy(&nso_header, file_data.data(), sizeof(NsoHeader)); + + if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) + return {}; + + // Build program image + Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(""); + std::vector<u8> program_image; + for (int i = 0; i < nso_header.segments.size(); ++i) { + std::vector<u8> compressed_data(nso_header.segments_compressed_size[i]); + for (int j = 0; j < nso_header.segments_compressed_size[i]; ++j) + compressed_data[j] = file_data[nso_header.segments[i].offset + j]; + std::vector<u8> data = DecompressSegment(compressed_data, nso_header.segments[i]); + program_image.resize(nso_header.segments[i].location); + program_image.insert(program_image.end(), data.begin(), data.end()); + codeset->segments[i].addr = nso_header.segments[i].location; + codeset->segments[i].offset = nso_header.segments[i].location; + codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size())); + } + + // MOD header pointer is at .text offset + 4 + u32 module_offset; + std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32)); + + // Read MOD header + ModHeader mod_header{}; + // Default .bss to size in segment header if MOD0 section doesn't exist + u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)}; + std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(ModHeader)); + const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')}; + if (has_mod_header) { + // Resize program image to include .bss section and page align each section + bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset); + } + codeset->data.size += bss_size; + const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)}; + program_image.resize(image_size); + + // Load codeset for current process + codeset->name = name; + codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); + Core::CurrentProcess()->LoadModule(codeset, load_base); + + return load_base + image_size; +} + VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base) { FileUtil::IOFile file(path, "rb"); if (!file.IsOpen()) { diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 1ae30a824..386f4d39a 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -29,6 +29,9 @@ public: return IdentifyType(file, filepath); } + static VAddr LoadModule(const std::string& name, const std::vector<u8>& file_data, + VAddr load_base); + static VAddr LoadModule(const std::string& path, VAddr load_base); ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; |