From ad0b57f4071fb7ec9da764b3905e0bb5e4c5eef2 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Thu, 7 Sep 2017 22:05:42 -0600 Subject: [PATCH 01/11] GPU: Add draw for immediate and batch modes PR #1461 introduced a regression where some games would change configuration even while in the poorly named "drawing" mode, which broke the heuristic citra was using to determine when to draw the batch. This change adds back in a draw call for batching, and also adds in a draw call in immediate mode each time it adds a triangle. --- src/video_core/command_processor.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index fb65a3a0a..fff159058 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -243,6 +243,15 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { ASSERT(!g_state.geometry_pipeline.NeedIndexInput()); g_state.geometry_pipeline.Setup(shader_engine); g_state.geometry_pipeline.SubmitVertex(output); + + // TODO: If drawing after every immediate mode triangle kills performance, + // change it to flush triangles whenever a draing config register changes + // See: https://github.com/citra-emu/citra/pull/2866#issuecomment-327011550 + VideoCore::g_renderer->Rasterizer()->DrawTriangles(); + if (g_debug_context) { + g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, + nullptr); + } } } } @@ -398,6 +407,12 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { range.second, range.first); } + MICROPROFILE_SCOPE(GPU_Drawing); + VideoCore::g_renderer->Rasterizer()->DrawTriangles(); + if (g_debug_context) { + g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); + } + break; } @@ -632,6 +647,6 @@ void ProcessCommandList(const u32* list, u32 size) { } } -} // namespace +} // namespace CommandProcessor -} // namespace +} // namespace Pica From 19d41dcc6e6892125f1123b34db3dc284f04b744 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Sat, 23 Sep 2017 09:28:20 -0600 Subject: [PATCH 02/11] Remove pipeline.gpu_mode and fix minor issues --- src/video_core/command_processor.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index fff159058..3ab4af374 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -245,7 +245,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { g_state.geometry_pipeline.SubmitVertex(output); // TODO: If drawing after every immediate mode triangle kills performance, - // change it to flush triangles whenever a draing config register changes + // change it to flush triangles whenever a drawing config register changes // See: https://github.com/citra-emu/citra/pull/2866#issuecomment-327011550 VideoCore::g_renderer->Rasterizer()->DrawTriangles(); if (g_debug_context) { @@ -259,16 +259,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { } case PICA_REG_INDEX(pipeline.gpu_mode): - if (regs.pipeline.gpu_mode == PipelineRegs::GPUMode::Configuring) { - MICROPROFILE_SCOPE(GPU_Drawing); - - // Draw immediate mode triangles when GPU Mode is set to GPUMode::Configuring - VideoCore::g_renderer->Rasterizer()->DrawTriangles(); - - if (g_debug_context) { - g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); - } - } + // This register likely just enables vertex processing and doesn't need any special handling break; case PICA_REG_INDEX_WORKAROUND(pipeline.command_buffer.trigger[0], 0x23c): @@ -407,7 +398,6 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { range.second, range.first); } - MICROPROFILE_SCOPE(GPU_Drawing); VideoCore::g_renderer->Rasterizer()->DrawTriangles(); if (g_debug_context) { g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); From 0b33e36292ca44151da32c7866e4c4394add564b Mon Sep 17 00:00:00 2001 From: Subv Date: Sun, 24 Sep 2017 00:12:58 -0500 Subject: [PATCH 03/11] HLE/SRV: Implemented RegisterService. Now system modules can do more than just crash immediately on startup. --- src/core/hle/service/sm/sm.cpp | 4 ++++ src/core/hle/service/sm/sm.h | 3 +++ src/core/hle/service/sm/srv.cpp | 26 +++++++++++++++++++++++++- src/core/hle/service/sm/srv.h | 1 + 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 5e7fc68f9..854ab9a05 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -36,6 +36,10 @@ ResultVal> ServiceManager::RegisterService std::string name, unsigned int max_sessions) { CASCADE_CODE(ValidateServiceName(name)); + + if (registered_services.find(name) != registered_services.end()) + return ERR_ALREADY_REGISTERED; + Kernel::SharedPtr server_port; Kernel::SharedPtr client_port; std::tie(server_port, client_port) = Kernel::ServerPort::CreatePortPair(max_sessions, name); diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 8f0dbf2db..9f60a7965 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -32,6 +32,9 @@ constexpr ResultCode ERR_ACCESS_DENIED(6, ErrorModule::SRV, ErrorSummary::Invali ErrorLevel::Permanent); // 0xD8E06406 constexpr ResultCode ERR_NAME_CONTAINS_NUL(7, ErrorModule::SRV, ErrorSummary::WrongArgument, ErrorLevel::Permanent); // 0xD9006407 +constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorDescription::AlreadyExists, ErrorModule::OS, + ErrorSummary::WrongArgument, + ErrorLevel::Permanent); // 0xD9001BFC class ServiceManager { public: diff --git a/src/core/hle/service/sm/srv.cpp b/src/core/hle/service/sm/srv.cpp index 352941e69..5c955cf54 100644 --- a/src/core/hle/service/sm/srv.cpp +++ b/src/core/hle/service/sm/srv.cpp @@ -13,6 +13,7 @@ #include "core/hle/kernel/errors.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/semaphore.h" +#include "core/hle/kernel/server_port.h" #include "core/hle/kernel/server_session.h" #include "core/hle/service/sm/sm.h" #include "core/hle/service/sm/srv.h" @@ -184,12 +185,35 @@ void SRV::PublishToSubscriber(Kernel::HLERequestContext& ctx) { flags); } +void SRV::RegisterService(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x3, 4, 0); + + auto name_buf = rp.PopRaw>(); + size_t name_len = rp.Pop(); + u32 max_sessions = rp.Pop(); + + std::string name(name_buf.data(), std::min(name_len, name_buf.size())); + + auto port = service_manager->RegisterService(name, max_sessions); + + if (port.Failed()) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(port.Code()); + LOG_ERROR(Service_SRV, "called service=%s -> error 0x%08X", name.c_str(), port.Code().raw); + return; + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushObjects(port.Unwrap()); +} + SRV::SRV(std::shared_ptr service_manager) : ServiceFramework("srv:", 4), service_manager(std::move(service_manager)) { static const FunctionInfo functions[] = { {0x00010002, &SRV::RegisterClient, "RegisterClient"}, {0x00020000, &SRV::EnableNotification, "EnableNotification"}, - {0x00030100, nullptr, "RegisterService"}, + {0x00030100, &SRV::RegisterService, "RegisterService"}, {0x000400C0, nullptr, "UnregisterService"}, {0x00050100, &SRV::GetServiceHandle, "GetServiceHandle"}, {0x000600C2, nullptr, "RegisterPort"}, diff --git a/src/core/hle/service/sm/srv.h b/src/core/hle/service/sm/srv.h index 75cca5184..aad839563 100644 --- a/src/core/hle/service/sm/srv.h +++ b/src/core/hle/service/sm/srv.h @@ -28,6 +28,7 @@ private: void Subscribe(Kernel::HLERequestContext& ctx); void Unsubscribe(Kernel::HLERequestContext& ctx); void PublishToSubscriber(Kernel::HLERequestContext& ctx); + void RegisterService(Kernel::HLERequestContext& ctx); std::shared_ptr service_manager; Kernel::SharedPtr notification_semaphore; From 7117fcc02452c264f39d270188e6d2123a6e26ac Mon Sep 17 00:00:00 2001 From: wwylele Date: Sun, 24 Sep 2017 17:52:11 +0300 Subject: [PATCH 04/11] citra-qt: fix some untranslated strings --- .../configuration/configure_graphics.ui | 22 +++++++++---------- .../configuration/configure_system.cpp | 3 ++- src/citra_qt/configuration/configure_web.cpp | 18 +++++++-------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/citra_qt/configuration/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui index b340149d5..5667b14b6 100644 --- a/src/citra_qt/configuration/configure_graphics.ui +++ b/src/citra_qt/configuration/configure_graphics.ui @@ -63,57 +63,57 @@ - Auto (Window Size) + Auto (Window Size) - Native (400x240) + Native (400x240) - 2x Native (800x480) + 2x Native (800x480) - 3x Native (1200x720) + 3x Native (1200x720) - 4x Native (1600x960) + 4x Native (1600x960) - 5x Native (2000x1200) + 5x Native (2000x1200) - 6x Native (2400x1440) + 6x Native (2400x1440) - 7x Native (2800x1680) + 7x Native (2800x1680) - 8x Native (3200x1920) + 8x Native (3200x1920) - 9x Native (3600x2160) + 9x Native (3600x2160) - 10x Native (4000x2400) + 10x Native (4000x2400) diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index 9b1e6711d..88a067c12 100644 --- a/src/citra_qt/configuration/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp @@ -78,7 +78,8 @@ void ConfigureSystem::ReadSystemSettings() { // set the console id u64 console_id = Service::CFG::GetConsoleUniqueId(); - ui->label_console_id->setText("Console ID: 0x" + QString::number(console_id, 16).toUpper()); + ui->label_console_id->setText( + tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); } void ConfigureSystem::applyConfiguration() { diff --git a/src/citra_qt/configuration/configure_web.cpp b/src/citra_qt/configuration/configure_web.cpp index 38ce19c0f..bf8c21ac7 100644 --- a/src/citra_qt/configuration/configure_web.cpp +++ b/src/citra_qt/configuration/configure_web.cpp @@ -24,15 +24,15 @@ ConfigureWeb::~ConfigureWeb() {} void ConfigureWeb::setConfiguration() { ui->web_credentials_disclaimer->setWordWrap(true); ui->telemetry_learn_more->setOpenExternalLinks(true); - ui->telemetry_learn_more->setText("Learn more"); + ui->telemetry_learn_more->setText(tr("Learn more")); ui->web_signup_link->setOpenExternalLinks(true); - ui->web_signup_link->setText("Sign up"); + ui->web_signup_link->setText(tr("Sign up")); ui->web_token_info_link->setOpenExternalLinks(true); ui->web_token_info_link->setText( - "What is my token?"); + tr("What is my token?")); ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry); ui->edit_username->setText(QString::fromStdString(Settings::values.citra_username)); @@ -40,8 +40,8 @@ void ConfigureWeb::setConfiguration() { // Connect after setting the values, to avoid calling OnLoginChanged now connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); - ui->label_telemetry_id->setText("Telemetry ID: 0x" + - QString::number(Core::GetTelemetryId(), 16).toUpper()); + ui->label_telemetry_id->setText( + tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper())); user_verified = true; } @@ -60,8 +60,8 @@ void ConfigureWeb::applyConfiguration() { void ConfigureWeb::RefreshTelemetryID() { const u64 new_telemetry_id{Core::RegenerateTelemetryId()}; - ui->label_telemetry_id->setText("Telemetry ID: 0x" + - QString::number(new_telemetry_id, 16).toUpper()); + ui->label_telemetry_id->setText( + tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper())); } void ConfigureWeb::OnLoginChanged() { From 876aa82c29d2e17f8b5a4f74155971cba78c00b6 Mon Sep 17 00:00:00 2001 From: Huw Pascoe Date: Sun, 24 Sep 2017 22:24:45 +0100 Subject: [PATCH 05/11] Optimized Morton --- src/video_core/utils.h | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/video_core/utils.h b/src/video_core/utils.h index 7ce83a055..d8567f314 100644 --- a/src/video_core/utils.h +++ b/src/video_core/utils.h @@ -8,17 +8,11 @@ namespace VideoCore { -/** - * Interleave the lower 3 bits of each coordinate to get the intra-block offsets, which are - * arranged in a Z-order curve. More details on the bit manipulation at: - * https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/ - */ +// 8x8 Z-Order coordinate from 2D coordinates static inline u32 MortonInterleave(u32 x, u32 y) { - u32 i = (x & 7) | ((y & 7) << 8); // ---- -210 - i = (i ^ (i << 2)) & 0x1313; // ---2 --10 - i = (i ^ (i << 1)) & 0x1515; // ---2 -1-0 - i = (i | (i >> 7)) & 0x3F; - return i; + static const u32 xlut[] = {0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15}; + static const u32 ylut[] = {0x00, 0x02, 0x08, 0x0a, 0x20, 0x22, 0x28, 0x2a}; + return xlut[x % 8] + ylut[y % 8]; } /** From c02bbb7030efd072511bd0051a44d9e503016f74 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Sun, 24 Sep 2017 22:42:42 +0100 Subject: [PATCH 06/11] memory: Add GetCurrentPageTable/SetCurrentPageTable Don't expose Memory::current_page_table as a global. --- src/core/hle/kernel/thread.cpp | 11 ++++------- src/core/loader/3dsx.cpp | 2 +- src/core/loader/elf.cpp | 2 +- src/core/loader/ncch.cpp | 2 +- src/core/memory.cpp | 10 +++++++++- src/core/memory.h | 3 ++- src/tests/core/arm/arm_test_common.cpp | 2 +- 7 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 324415a36..61378211f 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -178,16 +178,13 @@ static void SwitchContext(Thread* new_thread) { ready_queue.remove(new_thread->current_priority, new_thread); new_thread->status = THREADSTATUS_RUNNING; - Core::CPU().LoadContext(new_thread->context); - Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); - if (previous_process != current_thread->owner_process) { Kernel::g_current_process = current_thread->owner_process; - Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; - // We have switched processes and thus, page tables, clear the instruction cache so we - // don't keep stale data from the previous process. - Core::CPU().ClearInstructionCache(); + SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); } + + Core::CPU().LoadContext(new_thread->context); + Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); } else { current_thread = nullptr; // Note: We do not reset the current process and current page table when idling because diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 69cdc0867..a03515e6e 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -270,7 +270,7 @@ ResultStatus AppLoader_THREEDSX::Load() { Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); Kernel::g_current_process->svc_access_mask.set(); Kernel::g_current_process->address_mappings = default_address_mappings; - Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; + Memory::SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); // Attach the default resource limit (APPLICATION) to the process Kernel::g_current_process->resource_limit = diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 2f27606a1..2de1f4e81 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -397,7 +397,7 @@ ResultStatus AppLoader_ELF::Load() { Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); Kernel::g_current_process->svc_access_mask.set(); Kernel::g_current_process->address_mappings = default_address_mappings; - Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; + Memory::SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); // Attach the default resource limit (APPLICATION) to the process Kernel::g_current_process->resource_limit = diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 79ea50147..ca282f935 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -172,7 +172,7 @@ ResultStatus AppLoader_NCCH::LoadExec() { codeset->memory = std::make_shared>(std::move(code)); Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); - Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; + Memory::SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); // Attach a resource limit to the process based on the resource limit category Kernel::g_current_process->resource_limit = diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 68a6b1ac2..17fa10b49 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -22,12 +22,20 @@ namespace Memory { static std::array vram; static std::array n3ds_extra_ram; -PageTable* current_page_table = nullptr; +static PageTable* current_page_table = nullptr; std::array* GetCurrentPageTablePointers() { return ¤t_page_table->pointers; } +void SetCurrentPageTable(PageTable* page_table) { + current_page_table = page_table; +} + +PageTable* GetCurrentPageTable() { + return current_page_table; +} + static void MapPages(PageTable& page_table, u32 base, u32 size, u8* memory, PageType type) { LOG_DEBUG(HW_Memory, "Mapping %p onto %08X-%08X", memory, base * PAGE_SIZE, (base + size) * PAGE_SIZE); diff --git a/src/core/memory.h b/src/core/memory.h index b228a48c2..db5a704d0 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -182,7 +182,8 @@ enum : VAddr { }; /// Currently active page table -extern PageTable* current_page_table; +void SetCurrentPageTable(PageTable* page_table); +PageTable* GetCurrentPageTable(); bool IsValidVirtualAddress(const VAddr addr); bool IsValidPhysicalAddress(const PAddr addr); diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp index 8384ce744..cfe0d503a 100644 --- a/src/tests/core/arm/arm_test_common.cpp +++ b/src/tests/core/arm/arm_test_common.cpp @@ -21,7 +21,7 @@ TestEnvironment::TestEnvironment(bool mutable_memory_) Memory::MapIoRegion(page_table, 0x00000000, 0x80000000, test_memory); Memory::MapIoRegion(page_table, 0x80000000, 0x80000000, test_memory); - Memory::current_page_table = &page_table; + Memory::SetCurrentPageTable(&page_table); } TestEnvironment::~TestEnvironment() { From 4e5eb2044acc304fc2068b53eb03e3a626832996 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Sun, 24 Sep 2017 22:43:28 +0100 Subject: [PATCH 07/11] memory: Remove GetCurrentPageTablePointers --- src/core/memory.cpp | 4 ---- src/core/memory.h | 6 ------ 2 files changed, 10 deletions(-) diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 17fa10b49..67ba732ad 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -24,10 +24,6 @@ static std::array n3ds_extra_ram; static PageTable* current_page_table = nullptr; -std::array* GetCurrentPageTablePointers() { - return ¤t_page_table->pointers; -} - void SetCurrentPageTable(PageTable* page_table) { current_page_table = page_table; } diff --git a/src/core/memory.h b/src/core/memory.h index db5a704d0..1865bfea0 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -260,10 +260,4 @@ enum class FlushMode { */ void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode); -/** - * Dynarmic has an optimization to memory accesses when the pointer to the page exists that - * can be used by setting up the current page table as a callback. This function is used to - * retrieve the current page table for that purpose. - */ -std::array* GetCurrentPageTablePointers(); } // namespace Memory From 67a70bd9e1655dfd705550c1d561f3ba444360c8 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Sun, 24 Sep 2017 22:44:13 +0100 Subject: [PATCH 08/11] ARM_Interface: Implement PageTableChanged --- src/core/arm/arm_interface.h | 3 +++ src/core/arm/dynarmic/arm_dynarmic.cpp | 22 +++++++++++++++++----- src/core/arm/dynarmic/arm_dynarmic.h | 10 +++++++++- src/core/arm/dyncom/arm_dyncom.cpp | 4 ++++ src/core/arm/dyncom/arm_dyncom.h | 1 + src/core/memory.cpp | 5 +++++ 6 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index ccd43f431..2aa017a54 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -41,6 +41,9 @@ public: /// Clear all instruction cache virtual void ClearInstructionCache() = 0; + /// Notify CPU emulation that page tables have changed + virtual void PageTableChanged() = 0; + /** * Set the Program Counter to an address * @param addr Address to set PC to diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 34c5aa381..42ae93ae8 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -41,7 +41,7 @@ static bool IsReadOnlyMemory(u32 vaddr) { } static Dynarmic::UserCallbacks GetUserCallbacks( - const std::shared_ptr& interpeter_state) { + const std::shared_ptr& interpeter_state, Memory::PageTable* current_page_table) { Dynarmic::UserCallbacks user_callbacks{}; user_callbacks.InterpreterFallback = &InterpreterFallback; user_callbacks.user_arg = static_cast(interpeter_state.get()); @@ -56,16 +56,14 @@ static Dynarmic::UserCallbacks GetUserCallbacks( user_callbacks.memory.Write16 = &Memory::Write16; user_callbacks.memory.Write32 = &Memory::Write32; user_callbacks.memory.Write64 = &Memory::Write64; - // TODO(Subv): Re-add the page table pointers once dynarmic supports switching page tables at - // runtime. - user_callbacks.page_table = nullptr; + user_callbacks.page_table = ¤t_page_table->pointers; user_callbacks.coprocessors[15] = std::make_shared(interpeter_state); return user_callbacks; } ARM_Dynarmic::ARM_Dynarmic(PrivilegeMode initial_mode) { interpreter_state = std::make_shared(initial_mode); - jit = std::make_unique(GetUserCallbacks(interpreter_state)); + PageTableChanged(); } void ARM_Dynarmic::SetPC(u32 pc) { @@ -136,6 +134,7 @@ void ARM_Dynarmic::AddTicks(u64 ticks) { MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64)); void ARM_Dynarmic::ExecuteInstructions(int num_instructions) { + ASSERT(Memory::GetCurrentPageTable() == current_page_table); MICROPROFILE_SCOPE(ARM_Jit); std::size_t ticks_executed = jit->Run(static_cast(num_instructions)); @@ -178,3 +177,16 @@ void ARM_Dynarmic::PrepareReschedule() { void ARM_Dynarmic::ClearInstructionCache() { jit->ClearCache(); } + +void ARM_Dynarmic::PageTableChanged() { + current_page_table = Memory::GetCurrentPageTable(); + + auto iter = jits.find(current_page_table); + if (iter != jits.end()) { + jit = iter->second.get(); + return; + } + + jit = new Dynarmic::Jit(GetUserCallbacks(interpreter_state, current_page_table)); + jits.emplace(current_page_table, std::unique_ptr(jit)); +} diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index 834dc989e..96148a1a5 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -4,12 +4,17 @@ #pragma once +#include #include #include #include "common/common_types.h" #include "core/arm/arm_interface.h" #include "core/arm/skyeye_common/armstate.h" +namespace Memory { +struct PageTable; +} // namespace Memory + class ARM_Dynarmic final : public ARM_Interface { public: ARM_Dynarmic(PrivilegeMode initial_mode); @@ -36,8 +41,11 @@ public: void ExecuteInstructions(int num_instructions) override; void ClearInstructionCache() override; + void PageTableChanged() override; private: - std::unique_ptr jit; + Dynarmic::Jit* jit = nullptr; + Memory::PageTable* current_page_table = nullptr; + std::map> jits; std::shared_ptr interpreter_state; }; diff --git a/src/core/arm/dyncom/arm_dyncom.cpp b/src/core/arm/dyncom/arm_dyncom.cpp index 81f9bf99e..da955c9b9 100644 --- a/src/core/arm/dyncom/arm_dyncom.cpp +++ b/src/core/arm/dyncom/arm_dyncom.cpp @@ -25,6 +25,10 @@ void ARM_DynCom::ClearInstructionCache() { trans_cache_buf_top = 0; } +void ARM_DynCom::PageTableChanged() { + ClearInstructionCache(); +} + void ARM_DynCom::SetPC(u32 pc) { state->Reg[15] = pc; } diff --git a/src/core/arm/dyncom/arm_dyncom.h b/src/core/arm/dyncom/arm_dyncom.h index 62c174f3c..0ae535671 100644 --- a/src/core/arm/dyncom/arm_dyncom.h +++ b/src/core/arm/dyncom/arm_dyncom.h @@ -16,6 +16,7 @@ public: ~ARM_DynCom(); void ClearInstructionCache() override; + void PageTableChanged() override; void SetPC(u32 pc) override; u32 GetPC() const override; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 67ba732ad..a6b5f6c99 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -9,6 +9,8 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "common/swap.h" +#include "core/arm/arm_interface.h" +#include "core/core.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/process.h" #include "core/hle/lock.h" @@ -26,6 +28,9 @@ static PageTable* current_page_table = nullptr; void SetCurrentPageTable(PageTable* page_table) { current_page_table = page_table; + if (Core::System::GetInstance().IsPoweredOn()) { + Core::CPU().PageTableChanged(); + } } PageTable* GetCurrentPageTable() { From 903906da3b9b274836510adcabf8adf8e2c15954 Mon Sep 17 00:00:00 2001 From: Huw Pascoe Date: Fri, 22 Sep 2017 15:37:42 +0100 Subject: [PATCH 09/11] Optimized Float multiplication Before: ucomiss xmm1, xmm1 jp .L9 pxor xmm2, xmm2 mov edx, 1 ucomiss xmm0, xmm2 setp al cmovne eax, edx test al, al jne .L9 .L3: movaps xmm0, xmm2 ret .L9: ucomiss xmm0, xmm0 jp .L10 pxor xmm2, xmm2 mov edx, 1 ucomiss xmm1, xmm2 setp al cmovne eax, edx test al, al je .L3 After: movaps xmm2, xmm1 mulss xmm2, xmm0 ucomiss xmm2, xmm2 jnp .L3 ucomiss xmm1, xmm0 jnp .L11 .L3: movaps xmm0, xmm2 ret .L11: pxor xmm2, xmm2 jmp .L3 --- src/video_core/pica_types.h | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/video_core/pica_types.h b/src/video_core/pica_types.h index 5d7e10066..2eafa7e9e 100644 --- a/src/video_core/pica_types.h +++ b/src/video_core/pica_types.h @@ -58,11 +58,12 @@ public: } Float operator*(const Float& flt) const { - if ((this->value == 0.f && !std::isnan(flt.value)) || - (flt.value == 0.f && !std::isnan(this->value))) - // PICA gives 0 instead of NaN when multiplying by inf - return Zero(); - return Float::FromFloat32(ToFloat32() * flt.ToFloat32()); + float result = value * flt.ToFloat32(); + // PICA gives 0 instead of NaN when multiplying by inf + if (!std::isnan(value) && !std::isnan(flt.ToFloat32())) + if (std::isnan(result)) + result = 0.f; + return Float::FromFloat32(result); } Float operator/(const Float& flt) const { @@ -78,12 +79,7 @@ public: } Float& operator*=(const Float& flt) { - if ((this->value == 0.f && !std::isnan(flt.value)) || - (flt.value == 0.f && !std::isnan(this->value))) - // PICA gives 0 instead of NaN when multiplying by inf - *this = Zero(); - else - value *= flt.ToFloat32(); + value = operator*(flt).value; return *this; } From d673d508dd1ca463dc72ff68b5582ee56d62f142 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Mon, 25 Sep 2017 08:16:27 +0200 Subject: [PATCH 10/11] Services/UDS: Added a function to send EAPoL-Start packets (#2920) * Services/UDS: Added a function to generate the EAPoL-Start packet body. * Services/UDS: Added filter for beacons. * Services/UDS: Lock a mutex when accessing connection_status from both the emulation and network thread. * Services/UDS: Handle the Association Response frame and respond with the EAPoL-Start frame. * fixup: make use of current_node, changed received_beacons into a list, mutex and assert corrections * fixup: fix damn clang-format --- src/core/hle/service/nwm/nwm_uds.cpp | 261 ++++++++++++++------ src/core/hle/service/nwm/uds_connection.cpp | 9 + src/core/hle/service/nwm/uds_connection.h | 5 + src/core/hle/service/nwm/uds_data.cpp | 21 ++ src/core/hle/service/nwm/uds_data.h | 28 +++ 5 files changed, 243 insertions(+), 81 deletions(-) diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index 893bbb1e7..4e2af9ae6 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -2,8 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include +#include #include #include #include @@ -37,9 +39,12 @@ static ConnectionStatus connection_status{}; /* Node information about the current network. * The amount of elements in this vector is always the maximum number * of nodes specified in the network configuration. - * The first node is always the host, so this always contains at least 1 entry. + * The first node is always the host. */ -static NodeList node_info(1); +static NodeList node_info; + +// Node information about our own system. +static NodeInfo current_node; // Mapping of bind node ids to their respective events. static std::unordered_map> bind_node_events; @@ -54,6 +59,10 @@ static NetworkInfo network_info; // Event that will generate and send the 802.11 beacon frames. static int beacon_broadcast_event; +// Mutex to synchronize access to the connection status between the emulation thread and the +// network thread. +static std::mutex connection_status_mutex; + // Mutex to synchronize access to the list of received beacons between the emulation thread and the // network thread. static std::mutex beacon_mutex; @@ -63,14 +72,26 @@ static std::mutex beacon_mutex; constexpr size_t MaxBeaconFrames = 15; // List of the last beacons received from the network. -static std::deque received_beacons; +static std::list received_beacons; /** * Returns a list of received 802.11 beacon frames from the specified sender since the last call. */ -std::deque GetReceivedBeacons(const MacAddress& sender) { +std::list GetReceivedBeacons(const MacAddress& sender) { std::lock_guard lock(beacon_mutex); - // TODO(Subv): Filter by sender. + if (sender != Network::BroadcastMac) { + std::list filtered_list; + const auto beacon = std::find_if(received_beacons.begin(), received_beacons.end(), + [&sender](const Network::WifiPacket& packet) { + return packet.transmitter_address == sender; + }); + if (beacon != received_beacons.end()) { + filtered_list.push_back(*beacon); + // TODO(B3N30): Check if the complete deque is cleared or just the fetched entries + received_beacons.erase(beacon); + } + return filtered_list; + } return std::move(received_beacons); } @@ -83,6 +104,15 @@ void SendPacket(Network::WifiPacket& packet) { // limit is exceeded. void HandleBeaconFrame(const Network::WifiPacket& packet) { std::lock_guard lock(beacon_mutex); + const auto unique_beacon = + std::find_if(received_beacons.begin(), received_beacons.end(), + [&packet](const Network::WifiPacket& new_packet) { + return new_packet.transmitter_address == packet.transmitter_address; + }); + if (unique_beacon != received_beacons.end()) { + // We already have a beacon from the same mac in the deque, remove the old one; + received_beacons.erase(unique_beacon); + } received_beacons.emplace_back(packet); @@ -91,14 +121,33 @@ void HandleBeaconFrame(const Network::WifiPacket& packet) { received_beacons.pop_front(); } +void HandleAssociationResponseFrame(const Network::WifiPacket& packet) { + auto assoc_result = GetAssociationResult(packet.data); + + ASSERT_MSG(std::get(assoc_result) == AssocStatus::Successful, + "Could not join network"); + { + std::lock_guard lock(connection_status_mutex); + ASSERT(connection_status.status == static_cast(NetworkStatus::Connecting)); + } + + // Send the EAPoL-Start packet to the server. + using Network::WifiPacket; + WifiPacket eapol_start; + eapol_start.channel = network_channel; + eapol_start.data = GenerateEAPoLStartFrame(std::get(assoc_result), current_node); + // TODO(B3N30): Encrypt the packet. + eapol_start.destination_address = packet.transmitter_address; + eapol_start.type = WifiPacket::PacketType::Data; + + SendPacket(eapol_start); +} + /* * Returns an available index in the nodes array for the * currently-hosted UDS network. */ static u16 GetNextAvailableNodeId() { - ASSERT_MSG(connection_status.status == static_cast(NetworkStatus::ConnectedAsHost), - "Can not accept clients if we're not hosting a network"); - for (u16 index = 0; index < connection_status.max_nodes; ++index) { if ((connection_status.node_bitmask & (1 << index)) == 0) return index; @@ -113,35 +162,46 @@ static u16 GetNextAvailableNodeId() { * authentication frame with SEQ1. */ void StartConnectionSequence(const MacAddress& server) { - ASSERT(connection_status.status == static_cast(NetworkStatus::NotConnected)); - - // TODO(Subv): Handle timeout. - - // Send an authentication frame with SEQ1 using Network::WifiPacket; WifiPacket auth_request; - auth_request.channel = network_channel; - auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ1); - auth_request.destination_address = server; - auth_request.type = WifiPacket::PacketType::Authentication; + { + std::lock_guard lock(connection_status_mutex); + ASSERT(connection_status.status == static_cast(NetworkStatus::NotConnected)); + + // TODO(Subv): Handle timeout. + + // Send an authentication frame with SEQ1 + auth_request.channel = network_channel; + auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ1); + auth_request.destination_address = server; + auth_request.type = WifiPacket::PacketType::Authentication; + } SendPacket(auth_request); } /// Sends an Association Response frame to the specified mac address void SendAssociationResponseFrame(const MacAddress& address) { - ASSERT_MSG(connection_status.status == static_cast(NetworkStatus::ConnectedAsHost)); - using Network::WifiPacket; WifiPacket assoc_response; - assoc_response.channel = network_channel; - // TODO(Subv): This will cause multiple clients to end up with the same association id, but - // we're not using that for anything. - u16 association_id = 1; - assoc_response.data = GenerateAssocResponseFrame(AssocStatus::Successful, association_id, - network_info.network_id); - assoc_response.destination_address = address; - assoc_response.type = WifiPacket::PacketType::AssociationResponse; + + { + std::lock_guard lock(connection_status_mutex); + if (connection_status.status != static_cast(NetworkStatus::ConnectedAsHost)) { + LOG_ERROR(Service_NWM, "Connection sequence aborted, because connection status is %u", + connection_status.status); + return; + } + + assoc_response.channel = network_channel; + // TODO(Subv): This will cause multiple clients to end up with the same association id, but + // we're not using that for anything. + u16 association_id = 1; + assoc_response.data = GenerateAssocResponseFrame(AssocStatus::Successful, association_id, + network_info.network_id); + assoc_response.destination_address = address; + assoc_response.type = WifiPacket::PacketType::AssociationResponse; + } SendPacket(assoc_response); } @@ -155,16 +215,23 @@ void SendAssociationResponseFrame(const MacAddress& address) { void HandleAuthenticationFrame(const Network::WifiPacket& packet) { // Only the SEQ1 auth frame is handled here, the SEQ2 frame doesn't need any special behavior if (GetAuthenticationSeqNumber(packet.data) == AuthenticationSeq::SEQ1) { - ASSERT_MSG(connection_status.status == static_cast(NetworkStatus::ConnectedAsHost)); - - // Respond with an authentication response frame with SEQ2 using Network::WifiPacket; WifiPacket auth_request; - auth_request.channel = network_channel; - auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2); - auth_request.destination_address = packet.transmitter_address; - auth_request.type = WifiPacket::PacketType::Authentication; + { + std::lock_guard lock(connection_status_mutex); + if (connection_status.status != static_cast(NetworkStatus::ConnectedAsHost)) { + LOG_ERROR(Service_NWM, + "Connection sequence aborted, because connection status is %u", + connection_status.status); + return; + } + // Respond with an authentication response frame with SEQ2 + auth_request.channel = network_channel; + auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2); + auth_request.destination_address = packet.transmitter_address; + auth_request.type = WifiPacket::PacketType::Authentication; + } SendPacket(auth_request); SendAssociationResponseFrame(packet.transmitter_address); @@ -180,6 +247,9 @@ void OnWifiPacketReceived(const Network::WifiPacket& packet) { case Network::WifiPacket::PacketType::Authentication: HandleAuthenticationFrame(packet); break; + case Network::WifiPacket::PacketType::AssociationResponse: + HandleAssociationResponseFrame(packet); + break; } } @@ -305,7 +375,7 @@ static void InitializeWithVersion(Interface* self) { u32 sharedmem_size = rp.Pop(); // Update the node information with the data the game gave us. - rp.PopRaw(node_info[0]); + rp.PopRaw(current_node); u16 version = rp.Pop(); @@ -315,10 +385,14 @@ static void InitializeWithVersion(Interface* self) { ASSERT_MSG(recv_buffer_memory->size == sharedmem_size, "Invalid shared memory size."); - // Reset the connection status, it contains all zeros after initialization, - // except for the actual status value. - connection_status = {}; - connection_status.status = static_cast(NetworkStatus::NotConnected); + { + std::lock_guard lock(connection_status_mutex); + + // Reset the connection status, it contains all zeros after initialization, + // except for the actual status value. + connection_status = {}; + connection_status.status = static_cast(NetworkStatus::NotConnected); + } IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); @@ -348,12 +422,16 @@ static void GetConnectionStatus(Interface* self) { IPC::RequestBuilder rb = rp.MakeBuilder(13, 0); rb.Push(RESULT_SUCCESS); - rb.PushRaw(connection_status); + { + std::lock_guard lock(connection_status_mutex); + rb.PushRaw(connection_status); - // Reset the bitmask of changed nodes after each call to this - // function to prevent falsely informing games of outstanding - // changes in subsequent calls. - connection_status.changed_nodes = 0; + // Reset the bitmask of changed nodes after each call to this + // function to prevent falsely informing games of outstanding + // changes in subsequent calls. + // TODO(Subv): Find exactly where the NWM module resets this value. + connection_status.changed_nodes = 0; + } LOG_DEBUG(Service_NWM, "called"); } @@ -434,31 +512,36 @@ static void BeginHostingNetwork(Interface* self) { // The real UDS module throws a fatal error if this assert fails. ASSERT_MSG(network_info.max_nodes > 1, "Trying to host a network of only one member."); - connection_status.status = static_cast(NetworkStatus::ConnectedAsHost); + { + std::lock_guard lock(connection_status_mutex); + connection_status.status = static_cast(NetworkStatus::ConnectedAsHost); - // Ensure the application data size is less than the maximum value. - ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, "Data size is too big."); + // Ensure the application data size is less than the maximum value. + ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, + "Data size is too big."); - // Set up basic information for this network. - network_info.oui_value = NintendoOUI; - network_info.oui_type = static_cast(NintendoTagId::NetworkInfo); + // Set up basic information for this network. + network_info.oui_value = NintendoOUI; + network_info.oui_type = static_cast(NintendoTagId::NetworkInfo); - connection_status.max_nodes = network_info.max_nodes; + connection_status.max_nodes = network_info.max_nodes; - // Resize the nodes list to hold max_nodes. - node_info.resize(network_info.max_nodes); + // Resize the nodes list to hold max_nodes. + node_info.resize(network_info.max_nodes); - // There's currently only one node in the network (the host). - connection_status.total_nodes = 1; - network_info.total_nodes = 1; - // The host is always the first node - connection_status.network_node_id = 1; - node_info[0].network_node_id = 1; - connection_status.nodes[0] = connection_status.network_node_id; - // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken. - connection_status.node_bitmask |= 1; - // Notify the application that the first node was set. - connection_status.changed_nodes |= 1; + // There's currently only one node in the network (the host). + connection_status.total_nodes = 1; + network_info.total_nodes = 1; + // The host is always the first node + connection_status.network_node_id = 1; + current_node.network_node_id = 1; + connection_status.nodes[0] = connection_status.network_node_id; + // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken. + connection_status.node_bitmask |= 1; + // Notify the application that the first node was set. + connection_status.changed_nodes |= 1; + node_info[0] = current_node; + } // If the game has a preferred channel, use that instead. if (network_info.channel != 0) @@ -495,9 +578,13 @@ static void DestroyNetwork(Interface* self) { // Unschedule the beacon broadcast event. CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0); - // TODO(Subv): Check if connection_status is indeed reset after this call. - connection_status = {}; - connection_status.status = static_cast(NetworkStatus::NotConnected); + { + std::lock_guard lock(connection_status_mutex); + + // TODO(Subv): Check if connection_status is indeed reset after this call. + connection_status = {}; + connection_status.status = static_cast(NetworkStatus::NotConnected); + } connection_status_event->Signal(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); @@ -540,17 +627,24 @@ static void SendTo(Interface* self) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - if (connection_status.status != static_cast(NetworkStatus::ConnectedAsClient) && - connection_status.status != static_cast(NetworkStatus::ConnectedAsHost)) { - rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::UDS, - ErrorSummary::InvalidState, ErrorLevel::Status)); - return; - } + u16 network_node_id; - if (dest_node_id == connection_status.network_node_id) { - rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS, - ErrorSummary::WrongArgument, ErrorLevel::Status)); - return; + { + std::lock_guard lock(connection_status_mutex); + if (connection_status.status != static_cast(NetworkStatus::ConnectedAsClient) && + connection_status.status != static_cast(NetworkStatus::ConnectedAsHost)) { + rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::UDS, + ErrorSummary::InvalidState, ErrorLevel::Status)); + return; + } + + if (dest_node_id == connection_status.network_node_id) { + rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS, + ErrorSummary::WrongArgument, ErrorLevel::Status)); + return; + } + + network_node_id = connection_status.network_node_id; } // TODO(Subv): Do something with the flags. @@ -567,8 +661,8 @@ static void SendTo(Interface* self) { // TODO(Subv): Increment the sequence number after each sent packet. u16 sequence_number = 0; - std::vector data_payload = GenerateDataPayload( - data, data_channel, dest_node_id, connection_status.network_node_id, sequence_number); + std::vector data_payload = + GenerateDataPayload(data, data_channel, dest_node_id, network_node_id, sequence_number); // TODO(Subv): Retrieve the MAC address of the dest_node_id and our own to encrypt // and encapsulate the payload. @@ -595,6 +689,7 @@ static void GetChannel(Interface* self) { IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 0, 0); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + std::lock_guard lock(connection_status_mutex); bool is_connected = connection_status.status != static_cast(NetworkStatus::NotConnected); u8 channel = is_connected ? network_channel : 0; @@ -766,6 +861,7 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) { * @param network_node_id Network Node Id of the connecting client. */ void OnClientConnected(u16 network_node_id) { + std::lock_guard lock(connection_status_mutex); ASSERT_MSG(connection_status.status == static_cast(NetworkStatus::ConnectedAsHost), "Can not accept clients if we're not hosting a network"); ASSERT_MSG(connection_status.total_nodes < connection_status.max_nodes, @@ -827,8 +923,11 @@ NWM_UDS::~NWM_UDS() { connection_status_event = nullptr; recv_buffer_memory = nullptr; - connection_status = {}; - connection_status.status = static_cast(NetworkStatus::NotConnected); + { + std::lock_guard lock(connection_status_mutex); + connection_status = {}; + connection_status.status = static_cast(NetworkStatus::NotConnected); + } CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0); } diff --git a/src/core/hle/service/nwm/uds_connection.cpp b/src/core/hle/service/nwm/uds_connection.cpp index c8a76ec2a..c74f51253 100644 --- a/src/core/hle/service/nwm/uds_connection.cpp +++ b/src/core/hle/service/nwm/uds_connection.cpp @@ -75,5 +75,14 @@ std::vector GenerateAssocResponseFrame(AssocStatus status, u16 association_i return data; } +std::tuple GetAssociationResult(const std::vector& body) { + AssociationResponseFrame frame; + memcpy(&frame, body.data(), sizeof(frame)); + + constexpr u16 AssociationIdMask = 0x3FFF; + return std::make_tuple(static_cast(frame.status_code), + frame.assoc_id & AssociationIdMask); +} + } // namespace NWM } // namespace Service diff --git a/src/core/hle/service/nwm/uds_connection.h b/src/core/hle/service/nwm/uds_connection.h index 73f55a4fd..a664f8471 100644 --- a/src/core/hle/service/nwm/uds_connection.h +++ b/src/core/hle/service/nwm/uds_connection.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include "common/common_types.h" #include "common/swap.h" @@ -47,5 +48,9 @@ AuthenticationSeq GetAuthenticationSeqNumber(const std::vector& body); /// network id, starting at the frame body. std::vector GenerateAssocResponseFrame(AssocStatus status, u16 association_id, u32 network_id); +/// Returns a tuple of (association status, association id) from the body of an AssociationResponse +/// frame. +std::tuple GetAssociationResult(const std::vector& body); + } // namespace NWM } // namespace Service diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp index 8c6742dba..0fd9b8b8c 100644 --- a/src/core/hle/service/nwm/uds_data.cpp +++ b/src/core/hle/service/nwm/uds_data.cpp @@ -274,5 +274,26 @@ std::vector GenerateDataPayload(const std::vector& data, u8 channel, u16 return buffer; } +std::vector GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info) { + EAPoLStartPacket eapol_start{}; + eapol_start.association_id = association_id; + eapol_start.friend_code_seed = node_info.friend_code_seed; + + for (int i = 0; i < node_info.username.size(); ++i) + eapol_start.username[i] = node_info.username[i]; + + // Note: The network_node_id and unknown bytes seem to be uninitialized in the NWM module. + // TODO(B3N30): The last 8 bytes seem to have a fixed value of 07 88 15 00 04 e9 13 00 in + // EAPoL-Start packets from different 3DSs to the same host during a Super Smash Bros. 4 game. + // Find out what that means. + + std::vector eapol_buffer(sizeof(EAPoLStartPacket)); + std::memcpy(eapol_buffer.data(), &eapol_start, sizeof(eapol_start)); + + std::vector buffer = GenerateLLCHeader(EtherType::EAPoL); + buffer.insert(buffer.end(), eapol_buffer.begin(), eapol_buffer.end()); + return buffer; +} + } // namespace NWM } // namespace Service diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h index a23520a41..76e8f546b 100644 --- a/src/core/hle/service/nwm/uds_data.h +++ b/src/core/hle/service/nwm/uds_data.h @@ -67,6 +67,27 @@ struct DataFrameCryptoCTR { static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wrong size"); +constexpr u16 EAPoLStartMagic = 0x201; + +/* + * Nintendo EAPoLStartPacket, is used to initaliaze a connection between client and host + */ +struct EAPoLStartPacket { + u16_be magic = EAPoLStartMagic; + u16_be association_id; + // This value is hardcoded to 1 in the NWM module. + u16_be unknown = 1; + INSERT_PADDING_BYTES(2); + + u64_be friend_code_seed; + std::array username; + INSERT_PADDING_BYTES(4); + u16_be network_node_id; + INSERT_PADDING_BYTES(6); +}; + +static_assert(sizeof(EAPoLStartPacket) == 0x30, "EAPoLStartPacket has the wrong size"); + /** * Generates an unencrypted 802.11 data payload. * @returns The generated frame payload. @@ -74,5 +95,12 @@ static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wron std::vector GenerateDataPayload(const std::vector& data, u8 channel, u16 dest_node, u16 src_node, u16 sequence_number); +/* + * Generates an unencrypted 802.11 data frame body with the EAPoL-Start format for UDS + * communication. + * @returns The generated frame body. + */ +std::vector GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info); + } // namespace NWM } // namespace Service From c91ccbd0ba4118554d7377bbc3bd4c64f9bccf84 Mon Sep 17 00:00:00 2001 From: Max Thomas Date: Mon, 25 Sep 2017 00:17:38 -0600 Subject: [PATCH 11/11] Loader/NCCH: Add support for loading application updates (#2927) * loader/ncch: split NCCH parsing into its own file * loader/ncch: add support for loading update NCCHs from the SD card * loader/ncch: fix formatting * file_sys/ncch_container: Return a value for OpenFile * loader/ncch: cleanup, always instantiate overlay_ncch to base_ncch * file_sys/ncch_container: better encryption checks, allow non-app NCCHs to load properly and for the existence of NCCH structures to be checked * file_sys/ncch_container: pass filepath as a const reference --- src/core/CMakeLists.txt | 1 + src/core/file_sys/archive_selfncch.cpp | 28 ++- src/core/file_sys/archive_selfncch.h | 4 + src/core/file_sys/ncch_container.cpp | 316 ++++++++++++++++++++++++ src/core/file_sys/ncch_container.h | 244 +++++++++++++++++++ src/core/loader/loader.h | 13 + src/core/loader/ncch.cpp | 319 +++++-------------------- src/core/loader/ncch.h | 184 +------------- 8 files changed, 670 insertions(+), 439 deletions(-) create mode 100644 src/core/file_sys/ncch_container.cpp create mode 100644 src/core/file_sys/ncch_container.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index cd1a8de2d..3ed619991 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -26,6 +26,7 @@ set(SRCS file_sys/archive_systemsavedata.cpp file_sys/disk_archive.cpp file_sys/ivfc_archive.cpp + file_sys/ncch_container.cpp file_sys/path_parser.cpp file_sys/savedata_archive.cpp frontend/camera/blank_camera.cpp diff --git a/src/core/file_sys/archive_selfncch.cpp b/src/core/file_sys/archive_selfncch.cpp index 298a37a44..7dc91a405 100644 --- a/src/core/file_sys/archive_selfncch.cpp +++ b/src/core/file_sys/archive_selfncch.cpp @@ -102,8 +102,7 @@ public: switch (static_cast(file_path.type)) { case SelfNCCHFilePathType::UpdateRomFS: - LOG_WARNING(Service_FS, "(STUBBED) open update RomFS"); - return OpenRomFS(); + return OpenUpdateRomFS(); case SelfNCCHFilePathType::RomFS: return OpenRomFS(); @@ -179,6 +178,17 @@ private: } } + ResultVal> OpenUpdateRomFS() const { + if (ncch_data.update_romfs_file) { + return MakeResult>(std::make_unique( + ncch_data.update_romfs_file, ncch_data.update_romfs_offset, + ncch_data.update_romfs_size)); + } else { + LOG_INFO(Service_FS, "Unable to read update RomFS"); + return ERROR_ROMFS_NOT_FOUND; + } + } + ResultVal> OpenExeFS(const std::string& filename) const { if (filename == "icon") { if (ncch_data.icon) { @@ -218,11 +228,19 @@ private: }; ArchiveFactory_SelfNCCH::ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader) { - std::shared_ptr romfs_file_; + std::shared_ptr romfs_file; if (Loader::ResultStatus::Success == - app_loader.ReadRomFS(romfs_file_, ncch_data.romfs_offset, ncch_data.romfs_size)) { + app_loader.ReadRomFS(romfs_file, ncch_data.romfs_offset, ncch_data.romfs_size)) { - ncch_data.romfs_file = std::move(romfs_file_); + ncch_data.romfs_file = std::move(romfs_file); + } + + std::shared_ptr update_romfs_file; + if (Loader::ResultStatus::Success == + app_loader.ReadUpdateRomFS(update_romfs_file, ncch_data.update_romfs_offset, + ncch_data.update_romfs_size)) { + + ncch_data.update_romfs_file = std::move(update_romfs_file); } std::vector buffer; diff --git a/src/core/file_sys/archive_selfncch.h b/src/core/file_sys/archive_selfncch.h index f1b971296..f1c659948 100644 --- a/src/core/file_sys/archive_selfncch.h +++ b/src/core/file_sys/archive_selfncch.h @@ -24,6 +24,10 @@ struct NCCHData { std::shared_ptr romfs_file; u64 romfs_offset = 0; u64 romfs_size = 0; + + std::shared_ptr update_romfs_file; + u64 update_romfs_offset = 0; + u64 update_romfs_size = 0; }; /// File system interface to the SelfNCCH archive diff --git a/src/core/file_sys/ncch_container.cpp b/src/core/file_sys/ncch_container.cpp new file mode 100644 index 000000000..59c72f3e9 --- /dev/null +++ b/src/core/file_sys/ncch_container.cpp @@ -0,0 +1,316 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/file_sys/ncch_container.h" +#include "core/loader/loader.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +static const int kMaxSections = 8; ///< Maximum number of sections (files) in an ExeFs +static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes) + +/** + * Get the decompressed size of an LZSS compressed ExeFS file + * @param buffer Buffer of compressed file + * @param size Size of compressed buffer + * @return Size of decompressed buffer + */ +static u32 LZSS_GetDecompressedSize(const u8* buffer, u32 size) { + u32 offset_size = *(u32*)(buffer + size - 4); + return offset_size + size; +} + +/** + * Decompress ExeFS file (compressed with LZSS) + * @param compressed Compressed buffer + * @param compressed_size Size of compressed buffer + * @param decompressed Decompressed buffer + * @param decompressed_size Size of decompressed buffer + * @return True on success, otherwise false + */ +static bool LZSS_Decompress(const u8* compressed, u32 compressed_size, u8* decompressed, + u32 decompressed_size) { + const u8* footer = compressed + compressed_size - 8; + u32 buffer_top_and_bottom = *reinterpret_cast(footer); + u32 out = decompressed_size; + u32 index = compressed_size - ((buffer_top_and_bottom >> 24) & 0xFF); + u32 stop_index = compressed_size - (buffer_top_and_bottom & 0xFFFFFF); + + memset(decompressed, 0, decompressed_size); + memcpy(decompressed, compressed, compressed_size); + + while (index > stop_index) { + u8 control = compressed[--index]; + + for (unsigned i = 0; i < 8; i++) { + if (index <= stop_index) + break; + if (index <= 0) + break; + if (out <= 0) + break; + + if (control & 0x80) { + // Check if compression is out of bounds + if (index < 2) + return false; + index -= 2; + + u32 segment_offset = compressed[index] | (compressed[index + 1] << 8); + u32 segment_size = ((segment_offset >> 12) & 15) + 3; + segment_offset &= 0x0FFF; + segment_offset += 2; + + // Check if compression is out of bounds + if (out < segment_size) + return false; + + for (unsigned j = 0; j < segment_size; j++) { + // Check if compression is out of bounds + if (out + segment_offset >= decompressed_size) + return false; + + u8 data = decompressed[out + segment_offset]; + decompressed[--out] = data; + } + } else { + // Check if compression is out of bounds + if (out < 1) + return false; + decompressed[--out] = compressed[--index]; + } + control <<= 1; + } + } + return true; +} + +NCCHContainer::NCCHContainer(const std::string& filepath) : filepath(filepath) { + file = FileUtil::IOFile(filepath, "rb"); +} + +Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath) { + this->filepath = filepath; + file = FileUtil::IOFile(filepath, "rb"); + + if (!file.IsOpen()) { + LOG_WARNING(Service_FS, "Failed to open %s", filepath.c_str()); + return Loader::ResultStatus::Error; + } + + LOG_DEBUG(Service_FS, "Opened %s", filepath.c_str()); + return Loader::ResultStatus::Success; +} + +Loader::ResultStatus NCCHContainer::Load() { + if (is_loaded) + return Loader::ResultStatus::Success; + + // Reset read pointer in case this file has been read before. + file.Seek(0, SEEK_SET); + + if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header)) + return Loader::ResultStatus::Error; + + // 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; + file.Seek(ncch_offset, SEEK_SET); + file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); + } + + // Verify we are loading the correct file type... + if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic) + return Loader::ResultStatus::ErrorInvalidFormat; + + // System archives and DLC don't have an extended header but have RomFS + if (ncch_header.extended_header_size) { + if (file.ReadBytes(&exheader_header, sizeof(ExHeader_Header)) != sizeof(ExHeader_Header)) + return Loader::ResultStatus::Error; + + is_compressed = (exheader_header.codeset_info.flags.flag & 1) == 1; + u32 entry_point = exheader_header.codeset_info.text.address; + u32 code_size = exheader_header.codeset_info.text.code_size; + u32 stack_size = exheader_header.codeset_info.stack_size; + u32 bss_size = exheader_header.codeset_info.bss_size; + u32 core_version = exheader_header.arm11_system_local_caps.core_version; + u8 priority = exheader_header.arm11_system_local_caps.priority; + u8 resource_limit_category = + exheader_header.arm11_system_local_caps.resource_limit_category; + + LOG_DEBUG(Service_FS, "Name: %s", exheader_header.codeset_info.name); + LOG_DEBUG(Service_FS, "Program ID: %016" PRIX64, ncch_header.program_id); + LOG_DEBUG(Service_FS, "Code compressed: %s", is_compressed ? "yes" : "no"); + LOG_DEBUG(Service_FS, "Entry point: 0x%08X", entry_point); + LOG_DEBUG(Service_FS, "Code size: 0x%08X", code_size); + LOG_DEBUG(Service_FS, "Stack size: 0x%08X", stack_size); + LOG_DEBUG(Service_FS, "Bss size: 0x%08X", bss_size); + LOG_DEBUG(Service_FS, "Core version: %d", core_version); + LOG_DEBUG(Service_FS, "Thread priority: 0x%X", priority); + LOG_DEBUG(Service_FS, "Resource limit category: %d", resource_limit_category); + LOG_DEBUG(Service_FS, "System Mode: %d", + static_cast(exheader_header.arm11_system_local_caps.system_mode)); + + if (exheader_header.system_info.jump_id != ncch_header.program_id) { + LOG_ERROR(Service_FS, "ExHeader Program ID mismatch: the ROM is probably encrypted."); + return Loader::ResultStatus::ErrorEncrypted; + } + + has_exheader = true; + } + + // DLC can have an ExeFS and a RomFS but no extended header + if (ncch_header.exefs_size) { + exefs_offset = ncch_header.exefs_offset * kBlockSize; + u32 exefs_size = ncch_header.exefs_size * kBlockSize; + + LOG_DEBUG(Service_FS, "ExeFS offset: 0x%08X", exefs_offset); + LOG_DEBUG(Service_FS, "ExeFS size: 0x%08X", exefs_size); + + file.Seek(exefs_offset + ncch_offset, SEEK_SET); + if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header)) + return Loader::ResultStatus::Error; + + has_exefs = true; + } + + if (ncch_header.romfs_offset != 0 && ncch_header.romfs_size != 0) + has_romfs = true; + + is_loaded = true; + return Loader::ResultStatus::Success; +} + +Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vector& buffer) { + if (!file.IsOpen()) + return Loader::ResultStatus::Error; + + Loader::ResultStatus result = Load(); + if (result != Loader::ResultStatus::Success) + return result; + + if (!has_exefs) + return Loader::ResultStatus::ErrorNotUsed; + + LOG_DEBUG(Service_FS, "%d sections:", kMaxSections); + // Iterate through the ExeFs archive until we find a section with the specified name... + for (unsigned section_number = 0; section_number < kMaxSections; section_number++) { + const auto& section = exefs_header.section[section_number]; + + // Load the specified section... + if (strcmp(section.name, name) == 0) { + LOG_DEBUG(Service_FS, "%d - offset: 0x%08X, size: 0x%08X, name: %s", section_number, + section.offset, section.size, section.name); + + s64 section_offset = + (section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset); + file.Seek(section_offset, SEEK_SET); + + if (strcmp(section.name, ".code") == 0 && is_compressed) { + // Section is compressed, read compressed .code section... + std::unique_ptr temp_buffer; + try { + temp_buffer.reset(new u8[section.size]); + } catch (std::bad_alloc&) { + return Loader::ResultStatus::ErrorMemoryAllocationFailed; + } + + if (file.ReadBytes(&temp_buffer[0], section.size) != section.size) + return Loader::ResultStatus::Error; + + // Decompress .code section... + u32 decompressed_size = LZSS_GetDecompressedSize(&temp_buffer[0], section.size); + buffer.resize(decompressed_size); + if (!LZSS_Decompress(&temp_buffer[0], section.size, &buffer[0], decompressed_size)) + return Loader::ResultStatus::ErrorInvalidFormat; + } else { + // Section is uncompressed... + buffer.resize(section.size); + if (file.ReadBytes(&buffer[0], section.size) != section.size) + return Loader::ResultStatus::Error; + } + return Loader::ResultStatus::Success; + } + } + return Loader::ResultStatus::ErrorNotUsed; +} + +Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr& romfs_file, + u64& offset, u64& size) { + if (!file.IsOpen()) + return Loader::ResultStatus::Error; + + Loader::ResultStatus result = Load(); + if (result != Loader::ResultStatus::Success) + return result; + + if (!has_romfs) { + LOG_DEBUG(Service_FS, "RomFS requested from NCCH which has no RomFS"); + return Loader::ResultStatus::ErrorNotUsed; + } + + u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * kBlockSize) + 0x1000; + u32 romfs_size = (ncch_header.romfs_size * kBlockSize) - 0x1000; + + LOG_DEBUG(Service_FS, "RomFS offset: 0x%08X", romfs_offset); + LOG_DEBUG(Service_FS, "RomFS size: 0x%08X", romfs_size); + + if (file.GetSize() < romfs_offset + romfs_size) + return Loader::ResultStatus::Error; + + // We reopen the file, to allow its position to be independent from file's + romfs_file = std::make_shared(filepath, "rb"); + if (!romfs_file->IsOpen()) + return Loader::ResultStatus::Error; + + offset = romfs_offset; + size = romfs_size; + + return Loader::ResultStatus::Success; +} + +Loader::ResultStatus NCCHContainer::ReadProgramId(u64_le& program_id) { + Loader::ResultStatus result = Load(); + if (result != Loader::ResultStatus::Success) + return result; + + program_id = ncch_header.program_id; + return Loader::ResultStatus::Success; +} + +bool NCCHContainer::HasExeFS() { + Loader::ResultStatus result = Load(); + if (result != Loader::ResultStatus::Success) + return false; + + return has_exefs; +} + +bool NCCHContainer::HasRomFS() { + Loader::ResultStatus result = Load(); + if (result != Loader::ResultStatus::Success) + return false; + + return has_romfs; +} + +bool NCCHContainer::HasExHeader() { + Loader::ResultStatus result = Load(); + if (result != Loader::ResultStatus::Success) + return false; + + return has_exheader; +} + +} // namespace FileSys diff --git a/src/core/file_sys/ncch_container.h b/src/core/file_sys/ncch_container.h new file mode 100644 index 000000000..8af9032b4 --- /dev/null +++ b/src/core/file_sys/ncch_container.h @@ -0,0 +1,244 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/file_util.h" +#include "common/swap.h" +#include "core/core.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// NCCH header (Note: "NCCH" appears to be a publicly unknown acronym) + +struct NCCH_Header { + u8 signature[0x100]; + u32_le magic; + u32_le content_size; + u8 partition_id[8]; + u16_le maker_code; + u16_le version; + u8 reserved_0[4]; + u64_le program_id; + u8 reserved_1[0x10]; + u8 logo_region_hash[0x20]; + u8 product_code[0x10]; + u8 extended_header_hash[0x20]; + u32_le extended_header_size; + u8 reserved_2[4]; + u8 flags[8]; + u32_le plain_region_offset; + u32_le plain_region_size; + u32_le logo_region_offset; + u32_le logo_region_size; + u32_le exefs_offset; + u32_le exefs_size; + u32_le exefs_hash_region_size; + u8 reserved_3[4]; + u32_le romfs_offset; + u32_le romfs_size; + u32_le romfs_hash_region_size; + u8 reserved_4[4]; + u8 exefs_super_block_hash[0x20]; + u8 romfs_super_block_hash[0x20]; +}; + +static_assert(sizeof(NCCH_Header) == 0x200, "NCCH header structure size is wrong"); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ExeFS (executable file system) headers + +struct ExeFs_SectionHeader { + char name[8]; + u32 offset; + u32 size; +}; + +struct ExeFs_Header { + ExeFs_SectionHeader section[8]; + u8 reserved[0x80]; + u8 hashes[8][0x20]; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ExHeader (executable file system header) headers + +struct ExHeader_SystemInfoFlags { + u8 reserved[5]; + u8 flag; + u8 remaster_version[2]; +}; + +struct ExHeader_CodeSegmentInfo { + u32 address; + u32 num_max_pages; + u32 code_size; +}; + +struct ExHeader_CodeSetInfo { + u8 name[8]; + ExHeader_SystemInfoFlags flags; + ExHeader_CodeSegmentInfo text; + u32 stack_size; + ExHeader_CodeSegmentInfo ro; + u8 reserved[4]; + ExHeader_CodeSegmentInfo data; + u32 bss_size; +}; + +struct ExHeader_DependencyList { + u8 program_id[0x30][8]; +}; + +struct ExHeader_SystemInfo { + u64 save_data_size; + u64_le jump_id; + u8 reserved_2[0x30]; +}; + +struct ExHeader_StorageInfo { + u8 ext_save_data_id[8]; + u8 system_save_data_id[8]; + u8 reserved[8]; + u8 access_info[7]; + u8 other_attributes; +}; + +struct ExHeader_ARM11_SystemLocalCaps { + u64_le program_id; + u32_le core_version; + u8 reserved_flags[2]; + union { + u8 flags0; + BitField<0, 2, u8> ideal_processor; + BitField<2, 2, u8> affinity_mask; + BitField<4, 4, u8> system_mode; + }; + u8 priority; + u8 resource_limit_descriptor[0x10][2]; + ExHeader_StorageInfo storage_info; + u8 service_access_control[0x20][8]; + u8 ex_service_access_control[0x2][8]; + u8 reserved[0xf]; + u8 resource_limit_category; +}; + +struct ExHeader_ARM11_KernelCaps { + u32_le descriptors[28]; + u8 reserved[0x10]; +}; + +struct ExHeader_ARM9_AccessControl { + u8 descriptors[15]; + u8 descversion; +}; + +struct ExHeader_Header { + ExHeader_CodeSetInfo codeset_info; + ExHeader_DependencyList dependency_list; + ExHeader_SystemInfo system_info; + ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; + ExHeader_ARM11_KernelCaps arm11_kernel_caps; + ExHeader_ARM9_AccessControl arm9_access_control; + struct { + u8 signature[0x100]; + u8 ncch_public_key_modulus[0x100]; + ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; + ExHeader_ARM11_KernelCaps arm11_kernel_caps; + ExHeader_ARM9_AccessControl arm9_access_control; + } access_desc; +}; + +static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wrong"); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +/** + * Helper which implements an interface to deal with NCCH containers which can + * contain ExeFS archives or RomFS archives for games or other applications. + */ +class NCCHContainer { +public: + NCCHContainer(const std::string& filepath); + NCCHContainer() {} + + Loader::ResultStatus OpenFile(const std::string& filepath); + + /** + * Ensure ExeFS and exheader is loaded and ready for reading sections + * @return ResultStatus result of function + */ + Loader::ResultStatus Load(); + + /** + * Reads an application ExeFS section of an NCCH file (e.g. .code, .logo, etc.) + * @param name Name of section to read out of NCCH file + * @param buffer Vector to read data into + * @return ResultStatus result of function + */ + Loader::ResultStatus LoadSectionExeFS(const char* name, std::vector& buffer); + + /** + * Get the RomFS of the NCCH container + * Since the RomFS can be huge, we return a file reference instead of copying to a buffer + * @param romfs_file The file containing the RomFS + * @param offset The offset the romfs begins on + * @param size The size of the romfs + * @return ResultStatus result of function + */ + Loader::ResultStatus ReadRomFS(std::shared_ptr& romfs_file, u64& offset, + u64& size); + + /** + * Get the Program ID of the NCCH container + * @return ResultStatus result of function + */ + Loader::ResultStatus ReadProgramId(u64_le& program_id); + + /** + * Checks whether the NCCH container contains an ExeFS + * @return bool check result + */ + bool HasExeFS(); + + /** + * Checks whether the NCCH container contains a RomFS + * @return bool check result + */ + bool HasRomFS(); + + /** + * Checks whether the NCCH container contains an ExHeader + * @return bool check result + */ + bool HasExHeader(); + + NCCH_Header ncch_header; + ExeFs_Header exefs_header; + ExHeader_Header exheader_header; + +private: + bool has_exheader = false; + bool has_exefs = false; + bool has_romfs = false; + + bool is_loaded = false; + bool is_compressed = false; + + u32 ncch_offset = 0; // Offset to NCCH header, can be 0 or after NCSD header + u32 exefs_offset = 0; + + std::string filepath; + FileUtil::IOFile file; +}; + +} // namespace FileSys diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index e731888a2..3160fd2fd 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -166,6 +166,19 @@ public: return ResultStatus::ErrorNotImplemented; } + /** + * Get the update RomFS of the application + * Since the RomFS can be huge, we return a file reference instead of copying to a buffer + * @param romfs_file The file containing the RomFS + * @param offset The offset the romfs begins on + * @param size The size of the romfs + * @return ResultStatus result of function + */ + virtual ResultStatus ReadUpdateRomFS(std::shared_ptr& romfs_file, u64& offset, + u64& size) { + return ResultStatus::ErrorNotImplemented; + } + /** * Get the title of the application * @param title Reference to store the application title into diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 79ea50147..bef7fa567 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -13,6 +13,7 @@ #include "common/swap.h" #include "core/core.h" #include "core/file_sys/archive_selfncch.h" +#include "core/file_sys/ncch_container.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/service/cfg/cfg.h" @@ -27,87 +28,7 @@ namespace Loader { -static const int kMaxSections = 8; ///< Maximum number of sections (files) in an ExeFs -static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes) - -/** - * Get the decompressed size of an LZSS compressed ExeFS file - * @param buffer Buffer of compressed file - * @param size Size of compressed buffer - * @return Size of decompressed buffer - */ -static u32 LZSS_GetDecompressedSize(const u8* buffer, u32 size) { - u32 offset_size = *(u32*)(buffer + size - 4); - return offset_size + size; -} - -/** - * Decompress ExeFS file (compressed with LZSS) - * @param compressed Compressed buffer - * @param compressed_size Size of compressed buffer - * @param decompressed Decompressed buffer - * @param decompressed_size Size of decompressed buffer - * @return True on success, otherwise false - */ -static bool LZSS_Decompress(const u8* compressed, u32 compressed_size, u8* decompressed, - u32 decompressed_size) { - const u8* footer = compressed + compressed_size - 8; - u32 buffer_top_and_bottom = *reinterpret_cast(footer); - u32 out = decompressed_size; - u32 index = compressed_size - ((buffer_top_and_bottom >> 24) & 0xFF); - u32 stop_index = compressed_size - (buffer_top_and_bottom & 0xFFFFFF); - - memset(decompressed, 0, decompressed_size); - memcpy(decompressed, compressed, compressed_size); - - while (index > stop_index) { - u8 control = compressed[--index]; - - for (unsigned i = 0; i < 8; i++) { - if (index <= stop_index) - break; - if (index <= 0) - break; - if (out <= 0) - break; - - if (control & 0x80) { - // Check if compression is out of bounds - if (index < 2) - return false; - index -= 2; - - u32 segment_offset = compressed[index] | (compressed[index + 1] << 8); - u32 segment_size = ((segment_offset >> 12) & 15) + 3; - segment_offset &= 0x0FFF; - segment_offset += 2; - - // Check if compression is out of bounds - if (out < segment_size) - return false; - - for (unsigned j = 0; j < segment_size; j++) { - // Check if compression is out of bounds - if (out + segment_offset >= decompressed_size) - return false; - - u8 data = decompressed[out + segment_offset]; - decompressed[--out] = data; - } - } else { - // Check if compression is out of bounds - if (out < 1) - return false; - decompressed[--out] = compressed[--index]; - } - control <<= 1; - } - } - return true; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// AppLoader_NCCH class +static const u64 UPDATE_MASK = 0x0000000e00000000; FileType AppLoader_NCCH::IdentifyType(FileUtil::IOFile& file) { u32 magic; @@ -124,15 +45,25 @@ FileType AppLoader_NCCH::IdentifyType(FileUtil::IOFile& file) { return FileType::Error; } +static std::string GetUpdateNCCHPath(u64_le program_id) { + u32 high = static_cast((program_id | UPDATE_MASK) >> 32); + u32 low = static_cast((program_id | UPDATE_MASK) & 0xFFFFFFFF); + + return Common::StringFromFormat("%sNintendo 3DS/%s/%s/title/%08x/%08x/content/00000000.app", + FileUtil::GetUserPath(D_SDMC_IDX).c_str(), SYSTEM_ID, SDCARD_ID, + high, low); +} + std::pair, ResultStatus> AppLoader_NCCH::LoadKernelSystemMode() { if (!is_loaded) { - ResultStatus res = LoadExeFS(); + ResultStatus res = base_ncch.Load(); if (res != ResultStatus::Success) { return std::make_pair(boost::none, res); } } + // Set the system mode as the one from the exheader. - return std::make_pair(exheader_header.arm11_system_local_caps.system_mode.Value(), + return std::make_pair(overlay_ncch->exheader_header.arm11_system_local_caps.system_mode.Value(), ResultStatus::Success); } @@ -144,29 +75,34 @@ ResultStatus AppLoader_NCCH::LoadExec() { return ResultStatus::ErrorNotLoaded; std::vector code; - if (ResultStatus::Success == ReadCode(code)) { + u64_le program_id; + if (ResultStatus::Success == ReadCode(code) && + ResultStatus::Success == ReadProgramId(program_id)) { std::string process_name = Common::StringFromFixedZeroTerminatedBuffer( - (const char*)exheader_header.codeset_info.name, 8); + (const char*)overlay_ncch->exheader_header.codeset_info.name, 8); - SharedPtr codeset = CodeSet::Create(process_name, ncch_header.program_id); + SharedPtr codeset = CodeSet::Create(process_name, program_id); codeset->code.offset = 0; - codeset->code.addr = exheader_header.codeset_info.text.address; - codeset->code.size = exheader_header.codeset_info.text.num_max_pages * Memory::PAGE_SIZE; + codeset->code.addr = overlay_ncch->exheader_header.codeset_info.text.address; + codeset->code.size = + overlay_ncch->exheader_header.codeset_info.text.num_max_pages * Memory::PAGE_SIZE; codeset->rodata.offset = codeset->code.offset + codeset->code.size; - codeset->rodata.addr = exheader_header.codeset_info.ro.address; - codeset->rodata.size = exheader_header.codeset_info.ro.num_max_pages * Memory::PAGE_SIZE; + codeset->rodata.addr = overlay_ncch->exheader_header.codeset_info.ro.address; + codeset->rodata.size = + overlay_ncch->exheader_header.codeset_info.ro.num_max_pages * Memory::PAGE_SIZE; // TODO(yuriks): Not sure if the bss size is added to the page-aligned .data size or just // to the regular size. Playing it safe for now. - u32 bss_page_size = (exheader_header.codeset_info.bss_size + 0xFFF) & ~0xFFF; + u32 bss_page_size = (overlay_ncch->exheader_header.codeset_info.bss_size + 0xFFF) & ~0xFFF; code.resize(code.size() + bss_page_size, 0); codeset->data.offset = codeset->rodata.offset + codeset->rodata.size; - codeset->data.addr = exheader_header.codeset_info.data.address; + codeset->data.addr = overlay_ncch->exheader_header.codeset_info.data.address; codeset->data.size = - exheader_header.codeset_info.data.num_max_pages * Memory::PAGE_SIZE + bss_page_size; + overlay_ncch->exheader_header.codeset_info.data.num_max_pages * Memory::PAGE_SIZE + + bss_page_size; codeset->entrypoint = codeset->code.addr; codeset->memory = std::make_shared>(std::move(code)); @@ -177,150 +113,27 @@ ResultStatus AppLoader_NCCH::LoadExec() { // Attach a resource limit to the process based on the resource limit category Kernel::g_current_process->resource_limit = Kernel::ResourceLimit::GetForCategory(static_cast( - exheader_header.arm11_system_local_caps.resource_limit_category)); + overlay_ncch->exheader_header.arm11_system_local_caps.resource_limit_category)); // Set the default CPU core for this process Kernel::g_current_process->ideal_processor = - exheader_header.arm11_system_local_caps.ideal_processor; + overlay_ncch->exheader_header.arm11_system_local_caps.ideal_processor; // Copy data while converting endianness - std::array kernel_caps; - std::copy_n(exheader_header.arm11_kernel_caps.descriptors, kernel_caps.size(), + std::arrayexheader_header.arm11_kernel_caps.descriptors)> + kernel_caps; + std::copy_n(overlay_ncch->exheader_header.arm11_kernel_caps.descriptors, kernel_caps.size(), begin(kernel_caps)); Kernel::g_current_process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size()); - s32 priority = exheader_header.arm11_system_local_caps.priority; - u32 stack_size = exheader_header.codeset_info.stack_size; + s32 priority = overlay_ncch->exheader_header.arm11_system_local_caps.priority; + u32 stack_size = overlay_ncch->exheader_header.codeset_info.stack_size; Kernel::g_current_process->Run(priority, stack_size); return ResultStatus::Success; } return ResultStatus::Error; } -ResultStatus AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector& buffer) { - if (!file.IsOpen()) - return ResultStatus::Error; - - ResultStatus result = LoadExeFS(); - if (result != ResultStatus::Success) - return result; - - LOG_DEBUG(Loader, "%d sections:", kMaxSections); - // Iterate through the ExeFs archive until we find a section with the specified name... - for (unsigned section_number = 0; section_number < kMaxSections; section_number++) { - const auto& section = exefs_header.section[section_number]; - - // Load the specified section... - if (strcmp(section.name, name) == 0) { - LOG_DEBUG(Loader, "%d - offset: 0x%08X, size: 0x%08X, name: %s", section_number, - section.offset, section.size, section.name); - - s64 section_offset = - (section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset); - file.Seek(section_offset, SEEK_SET); - - if (strcmp(section.name, ".code") == 0 && is_compressed) { - // Section is compressed, read compressed .code section... - std::unique_ptr temp_buffer; - try { - temp_buffer.reset(new u8[section.size]); - } catch (std::bad_alloc&) { - return ResultStatus::ErrorMemoryAllocationFailed; - } - - if (file.ReadBytes(&temp_buffer[0], section.size) != section.size) - return ResultStatus::Error; - - // Decompress .code section... - u32 decompressed_size = LZSS_GetDecompressedSize(&temp_buffer[0], section.size); - buffer.resize(decompressed_size); - if (!LZSS_Decompress(&temp_buffer[0], section.size, &buffer[0], decompressed_size)) - return ResultStatus::ErrorInvalidFormat; - } else { - // Section is uncompressed... - buffer.resize(section.size); - if (file.ReadBytes(&buffer[0], section.size) != section.size) - return ResultStatus::Error; - } - return ResultStatus::Success; - } - } - return ResultStatus::ErrorNotUsed; -} - -ResultStatus AppLoader_NCCH::LoadExeFS() { - if (is_exefs_loaded) - return ResultStatus::Success; - - if (!file.IsOpen()) - return ResultStatus::Error; - - // Reset read pointer in case this file has been read before. - file.Seek(0, SEEK_SET); - - if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header)) - return ResultStatus::Error; - - // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)... - if (MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) { - LOG_DEBUG(Loader, "Only loading the first (bootable) NCCH within the NCSD file!"); - ncch_offset = 0x4000; - file.Seek(ncch_offset, SEEK_SET); - file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); - } - - // Verify we are loading the correct file type... - if (MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic) - return ResultStatus::ErrorInvalidFormat; - - // Read ExHeader... - - if (file.ReadBytes(&exheader_header, sizeof(ExHeader_Header)) != sizeof(ExHeader_Header)) - return ResultStatus::Error; - - is_compressed = (exheader_header.codeset_info.flags.flag & 1) == 1; - entry_point = exheader_header.codeset_info.text.address; - code_size = exheader_header.codeset_info.text.code_size; - stack_size = exheader_header.codeset_info.stack_size; - bss_size = exheader_header.codeset_info.bss_size; - core_version = exheader_header.arm11_system_local_caps.core_version; - priority = exheader_header.arm11_system_local_caps.priority; - resource_limit_category = exheader_header.arm11_system_local_caps.resource_limit_category; - - LOG_DEBUG(Loader, "Name: %s", exheader_header.codeset_info.name); - LOG_DEBUG(Loader, "Program ID: %016" PRIX64, ncch_header.program_id); - LOG_DEBUG(Loader, "Code compressed: %s", is_compressed ? "yes" : "no"); - LOG_DEBUG(Loader, "Entry point: 0x%08X", entry_point); - LOG_DEBUG(Loader, "Code size: 0x%08X", code_size); - LOG_DEBUG(Loader, "Stack size: 0x%08X", stack_size); - LOG_DEBUG(Loader, "Bss size: 0x%08X", bss_size); - LOG_DEBUG(Loader, "Core version: %d", core_version); - LOG_DEBUG(Loader, "Thread priority: 0x%X", priority); - LOG_DEBUG(Loader, "Resource limit category: %d", resource_limit_category); - LOG_DEBUG(Loader, "System Mode: %d", - static_cast(exheader_header.arm11_system_local_caps.system_mode)); - - if (exheader_header.arm11_system_local_caps.program_id != ncch_header.program_id) { - LOG_ERROR(Loader, "ExHeader Program ID mismatch: the ROM is probably encrypted."); - return ResultStatus::ErrorEncrypted; - } - - // Read ExeFS... - - exefs_offset = ncch_header.exefs_offset * kBlockSize; - u32 exefs_size = ncch_header.exefs_size * kBlockSize; - - LOG_DEBUG(Loader, "ExeFS offset: 0x%08X", exefs_offset); - LOG_DEBUG(Loader, "ExeFS size: 0x%08X", exefs_size); - - file.Seek(exefs_offset + ncch_offset, SEEK_SET); - if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header)) - return ResultStatus::Error; - - is_exefs_loaded = true; - return ResultStatus::Success; -} - void AppLoader_NCCH::ParseRegionLockoutInfo() { std::vector smdh_buffer; if (ReadIcon(smdh_buffer) == ResultStatus::Success && smdh_buffer.size() >= sizeof(SMDH)) { @@ -339,23 +152,32 @@ void AppLoader_NCCH::ParseRegionLockoutInfo() { } ResultStatus AppLoader_NCCH::Load() { + u64_le ncch_program_id; + if (is_loaded) return ResultStatus::ErrorAlreadyLoaded; - ResultStatus result = LoadExeFS(); + ResultStatus result = base_ncch.Load(); if (result != ResultStatus::Success) return result; - std::string program_id{Common::StringFromFormat("%016" PRIX64, ncch_header.program_id)}; + ReadProgramId(ncch_program_id); + std::string program_id{Common::StringFromFormat("%016" PRIX64, ncch_program_id)}; LOG_INFO(Loader, "Program ID: %s", program_id.c_str()); + update_ncch.OpenFile(GetUpdateNCCHPath(ncch_program_id)); + result = update_ncch.Load(); + if (result == ResultStatus::Success) { + overlay_ncch = &update_ncch; + } + Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", program_id); if (auto room_member = Network::GetRoomMember().lock()) { Network::GameInfo game_info; ReadTitle(game_info.name); - game_info.id = ncch_header.program_id; + game_info.id = ncch_program_id; room_member->SendGameInfo(game_info); } @@ -374,61 +196,40 @@ ResultStatus AppLoader_NCCH::Load() { } ResultStatus AppLoader_NCCH::ReadCode(std::vector& buffer) { - return LoadSectionExeFS(".code", buffer); + return overlay_ncch->LoadSectionExeFS(".code", buffer); } ResultStatus AppLoader_NCCH::ReadIcon(std::vector& buffer) { - return LoadSectionExeFS("icon", buffer); + return overlay_ncch->LoadSectionExeFS("icon", buffer); } ResultStatus AppLoader_NCCH::ReadBanner(std::vector& buffer) { - return LoadSectionExeFS("banner", buffer); + return overlay_ncch->LoadSectionExeFS("banner", buffer); } ResultStatus AppLoader_NCCH::ReadLogo(std::vector& buffer) { - return LoadSectionExeFS("logo", buffer); + return overlay_ncch->LoadSectionExeFS("logo", buffer); } ResultStatus AppLoader_NCCH::ReadProgramId(u64& out_program_id) { - if (!file.IsOpen()) - return ResultStatus::Error; - - ResultStatus result = LoadExeFS(); + ResultStatus result = base_ncch.ReadProgramId(out_program_id); if (result != ResultStatus::Success) return result; - out_program_id = ncch_header.program_id; return ResultStatus::Success; } ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr& romfs_file, u64& offset, u64& size) { - if (!file.IsOpen()) - return ResultStatus::Error; + return base_ncch.ReadRomFS(romfs_file, offset, size); +} - // Check if the NCCH has a RomFS... - if (ncch_header.romfs_offset != 0 && ncch_header.romfs_size != 0) { - u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * kBlockSize) + 0x1000; - u32 romfs_size = (ncch_header.romfs_size * kBlockSize) - 0x1000; +ResultStatus AppLoader_NCCH::ReadUpdateRomFS(std::shared_ptr& romfs_file, + u64& offset, u64& size) { + ResultStatus result = update_ncch.ReadRomFS(romfs_file, offset, size); - LOG_DEBUG(Loader, "RomFS offset: 0x%08X", romfs_offset); - LOG_DEBUG(Loader, "RomFS size: 0x%08X", romfs_size); - - if (file.GetSize() < romfs_offset + romfs_size) - return ResultStatus::Error; - - // We reopen the file, to allow its position to be independent from file's - romfs_file = std::make_shared(filepath, "rb"); - if (!romfs_file->IsOpen()) - return ResultStatus::Error; - - offset = romfs_offset; - size = romfs_size; - - return ResultStatus::Success; - } - LOG_DEBUG(Loader, "NCCH has no RomFS"); - return ResultStatus::ErrorNotUsed; + if (result != ResultStatus::Success) + return base_ncch.ReadRomFS(romfs_file, offset, size); } ResultStatus AppLoader_NCCH::ReadTitle(std::string& title) { diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index e40cef764..9b56465cb 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -5,154 +5,11 @@ #pragma once #include -#include "common/bit_field.h" #include "common/common_types.h" #include "common/swap.h" +#include "core/file_sys/ncch_container.h" #include "core/loader/loader.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -/// NCCH header (Note: "NCCH" appears to be a publicly unknown acronym) - -struct NCCH_Header { - u8 signature[0x100]; - u32_le magic; - u32_le content_size; - u8 partition_id[8]; - u16_le maker_code; - u16_le version; - u8 reserved_0[4]; - u64_le program_id; - u8 reserved_1[0x10]; - u8 logo_region_hash[0x20]; - u8 product_code[0x10]; - u8 extended_header_hash[0x20]; - u32_le extended_header_size; - u8 reserved_2[4]; - u8 flags[8]; - u32_le plain_region_offset; - u32_le plain_region_size; - u32_le logo_region_offset; - u32_le logo_region_size; - u32_le exefs_offset; - u32_le exefs_size; - u32_le exefs_hash_region_size; - u8 reserved_3[4]; - u32_le romfs_offset; - u32_le romfs_size; - u32_le romfs_hash_region_size; - u8 reserved_4[4]; - u8 exefs_super_block_hash[0x20]; - u8 romfs_super_block_hash[0x20]; -}; - -static_assert(sizeof(NCCH_Header) == 0x200, "NCCH header structure size is wrong"); - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// ExeFS (executable file system) headers - -struct ExeFs_SectionHeader { - char name[8]; - u32 offset; - u32 size; -}; - -struct ExeFs_Header { - ExeFs_SectionHeader section[8]; - u8 reserved[0x80]; - u8 hashes[8][0x20]; -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// ExHeader (executable file system header) headers - -struct ExHeader_SystemInfoFlags { - u8 reserved[5]; - u8 flag; - u8 remaster_version[2]; -}; - -struct ExHeader_CodeSegmentInfo { - u32 address; - u32 num_max_pages; - u32 code_size; -}; - -struct ExHeader_CodeSetInfo { - u8 name[8]; - ExHeader_SystemInfoFlags flags; - ExHeader_CodeSegmentInfo text; - u32 stack_size; - ExHeader_CodeSegmentInfo ro; - u8 reserved[4]; - ExHeader_CodeSegmentInfo data; - u32 bss_size; -}; - -struct ExHeader_DependencyList { - u8 program_id[0x30][8]; -}; - -struct ExHeader_SystemInfo { - u64 save_data_size; - u8 jump_id[8]; - u8 reserved_2[0x30]; -}; - -struct ExHeader_StorageInfo { - u8 ext_save_data_id[8]; - u8 system_save_data_id[8]; - u8 reserved[8]; - u8 access_info[7]; - u8 other_attributes; -}; - -struct ExHeader_ARM11_SystemLocalCaps { - u64_le program_id; - u32_le core_version; - u8 reserved_flags[2]; - union { - u8 flags0; - BitField<0, 2, u8> ideal_processor; - BitField<2, 2, u8> affinity_mask; - BitField<4, 4, u8> system_mode; - }; - u8 priority; - u8 resource_limit_descriptor[0x10][2]; - ExHeader_StorageInfo storage_info; - u8 service_access_control[0x20][8]; - u8 ex_service_access_control[0x2][8]; - u8 reserved[0xf]; - u8 resource_limit_category; -}; - -struct ExHeader_ARM11_KernelCaps { - u32_le descriptors[28]; - u8 reserved[0x10]; -}; - -struct ExHeader_ARM9_AccessControl { - u8 descriptors[15]; - u8 descversion; -}; - -struct ExHeader_Header { - ExHeader_CodeSetInfo codeset_info; - ExHeader_DependencyList dependency_list; - ExHeader_SystemInfo system_info; - ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; - ExHeader_ARM11_KernelCaps arm11_kernel_caps; - ExHeader_ARM9_AccessControl arm9_access_control; - struct { - u8 signature[0x100]; - u8 ncch_public_key_modulus[0x100]; - ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; - ExHeader_ARM11_KernelCaps arm11_kernel_caps; - ExHeader_ARM9_AccessControl arm9_access_control; - } access_desc; -}; - -static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wrong"); - //////////////////////////////////////////////////////////////////////////////////////////////////// // Loader namespace @@ -162,7 +19,8 @@ namespace Loader { class AppLoader_NCCH final : public AppLoader { public: AppLoader_NCCH(FileUtil::IOFile&& file, const std::string& filepath) - : AppLoader(std::move(file)), filepath(filepath) {} + : AppLoader(std::move(file)), filepath(filepath), base_ncch(filepath), + overlay_ncch(&base_ncch) {} /** * Returns the type of the file @@ -196,48 +54,24 @@ public: ResultStatus ReadRomFS(std::shared_ptr& romfs_file, u64& offset, u64& size) override; + ResultStatus ReadUpdateRomFS(std::shared_ptr& romfs_file, u64& offset, + u64& size) override; + ResultStatus ReadTitle(std::string& title) override; private: - /** - * Reads an application ExeFS section of an NCCH file into AppLoader (e.g. .code, .logo, etc.) - * @param name Name of section to read out of NCCH file - * @param buffer Vector to read data into - * @return ResultStatus result of function - */ - ResultStatus LoadSectionExeFS(const char* name, std::vector& buffer); - /** * Loads .code section into memory for booting * @return ResultStatus result of function */ ResultStatus LoadExec(); - /** - * Ensure ExeFS is loaded and ready for reading sections - * @return ResultStatus result of function - */ - ResultStatus LoadExeFS(); - /// Reads the region lockout info in the SMDH and send it to CFG service void ParseRegionLockoutInfo(); - bool is_exefs_loaded = false; - bool is_compressed = false; - - u32 entry_point = 0; - u32 code_size = 0; - u32 stack_size = 0; - u32 bss_size = 0; - u32 core_version = 0; - u8 priority = 0; - u8 resource_limit_category = 0; - u32 ncch_offset = 0; // Offset to NCCH header, can be 0 or after NCSD header - u32 exefs_offset = 0; - - NCCH_Header ncch_header; - ExeFs_Header exefs_header; - ExHeader_Header exheader_header; + FileSys::NCCHContainer base_ncch; + FileSys::NCCHContainer update_ncch; + FileSys::NCCHContainer* overlay_ncch; std::string filepath; };