diff options
Diffstat (limited to '')
-rw-r--r-- | src/core/file_sys/patch_manager.cpp | 49 | ||||
-rw-r--r-- | src/core/gdbstub/gdbstub.cpp | 123 | ||||
-rw-r--r-- | src/core/hle/service/am/am.cpp | 12 | ||||
-rw-r--r-- | src/core/hle/service/filesystem/filesystem.cpp | 5 | ||||
-rw-r--r-- | src/core/hle/service/filesystem/filesystem.h | 1 | ||||
-rw-r--r-- | src/core/hle/service/sm/sm.cpp | 54 | ||||
-rw-r--r-- | src/core/hle/service/sm/sm.h | 3 | ||||
-rw-r--r-- | src/core/hle/service/vi/vi.cpp | 32 | ||||
-rw-r--r-- | src/core/settings.h | 1 |
9 files changed, 226 insertions, 54 deletions
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 8d062eb3e..e8df08724 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -26,6 +26,11 @@ namespace FileSys { constexpr u64 SINGLE_BYTE_MODULUS = 0x100; constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; +constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{ + "main", "main.npdm", "rtld", "sdk", "subsdk0", "subsdk1", "subsdk2", + "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9", +}; + struct NSOBuildHeader { u32_le magic; INSERT_PADDING_BYTES(0x3C); @@ -57,6 +62,15 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { if (exefs == nullptr) return exefs; + if (Settings::values.dump_exefs) { + LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id); + const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id); + if (dump_dir != nullptr) { + const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs"); + VfsRawCopyD(exefs, exefs_dir); + } + } + const auto installed = Service::FileSystem::GetUnionContents(); // Game Updates @@ -70,6 +84,30 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { exefs = update->GetExeFS(); } + // LayeredExeFS + const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + if (load_dir != nullptr && load_dir->GetSize() > 0) { + auto patch_dirs = load_dir->GetSubdirectories(); + std::sort( + patch_dirs.begin(), patch_dirs.end(), + [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); + + std::vector<VirtualDir> layers; + layers.reserve(patch_dirs.size() + 1); + for (const auto& subdir : patch_dirs) { + auto exefs_dir = subdir->GetSubdirectory("exefs"); + if (exefs_dir != nullptr) + layers.push_back(std::move(exefs_dir)); + } + layers.push_back(exefs); + + auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); + if (layered != nullptr) { + LOG_INFO(Loader, " ExeFS: LayeredExeFS patches applied successfully"); + exefs = std::move(layered); + } + } + return exefs; } @@ -314,18 +352,25 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam if (IsDirValidAndNonEmpty(exefs_dir)) { bool ips = false; bool ipswitch = false; + bool layeredfs = false; for (const auto& file : exefs_dir->GetFiles()) { - if (file->GetExtension() == "ips") + if (file->GetExtension() == "ips") { ips = true; - else if (file->GetExtension() == "pchtxt") + } else if (file->GetExtension() == "pchtxt") { ipswitch = true; + } else if (std::find(EXEFS_FILE_NAMES.begin(), EXEFS_FILE_NAMES.end(), + file->GetName()) != EXEFS_FILE_NAMES.end()) { + layeredfs = true; + } } if (ips) AppendCommaIfNotEmpty(types, "IPS"); if (ipswitch) AppendCommaIfNotEmpty(types, "IPSwitch"); + if (layeredfs) + AppendCommaIfNotEmpty(types, "LayeredExeFS"); } if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs"))) AppendCommaIfNotEmpty(types, "LayeredFS"); diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index bdcc889e0..687dea409 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -71,10 +71,6 @@ constexpr u32 PSTATE_REGISTER = 33; constexpr u32 UC_ARM64_REG_Q0 = 34; constexpr u32 FPCR_REGISTER = 66; -// TODO/WiP - Used while working on support for FPU -constexpr u32 TODO_DUMMY_REG_997 = 997; -constexpr u32 TODO_DUMMY_REG_998 = 998; - // For sample XML files see the GDB source /gdb/features // GDB also wants the l character at the start // This XML defines what the registers are for this specific ARM device @@ -260,6 +256,36 @@ static void RegWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr) } } +static u128 FpuRead(std::size_t id, Kernel::Thread* thread = nullptr) { + if (!thread) { + return u128{0}; + } + + auto& thread_context = thread->GetContext(); + + if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { + return thread_context.vector_registers[id - UC_ARM64_REG_Q0]; + } else if (id == FPCR_REGISTER) { + return u128{thread_context.fpcr, 0}; + } else { + return u128{0}; + } +} + +static void FpuWrite(std::size_t id, u128 val, Kernel::Thread* thread = nullptr) { + if (!thread) { + return; + } + + auto& thread_context = thread->GetContext(); + + if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { + thread_context.vector_registers[id - UC_ARM64_REG_Q0] = val; + } else if (id == FPCR_REGISTER) { + thread_context.fpcr = val[0]; + } +} + /** * Turns hex string character into the equivalent byte. * @@ -409,6 +435,27 @@ static u64 GdbHexToLong(const u8* src) { return output; } +/** + * Convert a gdb-formatted hex string into a u128. + * + * @param src Pointer to hex string. + */ +static u128 GdbHexToU128(const u8* src) { + u128 output; + + for (int i = 0; i < 16; i += 2) { + output[0] = (output[0] << 4) | HexCharToValue(src[15 - i - 1]); + output[0] = (output[0] << 4) | HexCharToValue(src[15 - i]); + } + + for (int i = 0; i < 16; i += 2) { + output[1] = (output[1] << 4) | HexCharToValue(src[16 + 15 - i - 1]); + output[1] = (output[1] << 4) | HexCharToValue(src[16 + 15 - i]); + } + + return output; +} + /// Read a byte from the gdb client. static u8 ReadByte() { u8 c; @@ -599,8 +646,7 @@ static void HandleQuery() { for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) { const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList(); for (const auto& thread : threads) { - val += fmt::format("{:x}", thread->GetThreadID()); - val += ","; + val += fmt::format("{:x},", thread->GetThreadID()); } } val.pop_back(); @@ -791,11 +837,15 @@ static void ReadRegister() { } else if (id == PSTATE_REGISTER) { IntToGdbHex(reply, static_cast<u32>(RegRead(id, current_thread))); } else if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { - LongToGdbHex(reply, RegRead(id, current_thread)); + u128 r = FpuRead(id, current_thread); + LongToGdbHex(reply, r[0]); + LongToGdbHex(reply + 16, r[1]); } else if (id == FPCR_REGISTER) { - LongToGdbHex(reply, RegRead(TODO_DUMMY_REG_998, current_thread)); - } else { - LongToGdbHex(reply, RegRead(TODO_DUMMY_REG_997, current_thread)); + u128 r = FpuRead(id, current_thread); + IntToGdbHex(reply, static_cast<u32>(r[0])); + } else if (id == FPCR_REGISTER + 1) { + u128 r = FpuRead(id, current_thread); + IntToGdbHex(reply, static_cast<u32>(r[0] >> 32)); } SendReply(reinterpret_cast<char*>(reply)); @@ -822,13 +872,18 @@ static void ReadRegisters() { bufptr += 8; - for (u32 reg = UC_ARM64_REG_Q0; reg <= UC_ARM64_REG_Q0 + 31; reg++) { - LongToGdbHex(bufptr + reg * 16, RegRead(reg, current_thread)); + u128 r; + + for (u32 reg = UC_ARM64_REG_Q0; reg < FPCR_REGISTER; reg++) { + r = FpuRead(reg, current_thread); + LongToGdbHex(bufptr + reg * 32, r[0]); + LongToGdbHex(bufptr + reg * 32 + 16, r[1]); } bufptr += 32 * 32; - LongToGdbHex(bufptr, RegRead(TODO_DUMMY_REG_998, current_thread)); + r = FpuRead(FPCR_REGISTER, current_thread); + IntToGdbHex(bufptr, static_cast<u32>(r[0])); bufptr += 8; @@ -853,14 +908,12 @@ static void WriteRegister() { } else if (id == PSTATE_REGISTER) { RegWrite(id, GdbHexToInt(buffer_ptr), current_thread); } else if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { - RegWrite(id, GdbHexToLong(buffer_ptr), current_thread); + FpuWrite(id, GdbHexToU128(buffer_ptr), current_thread); } else if (id == FPCR_REGISTER) { - RegWrite(TODO_DUMMY_REG_998, GdbHexToLong(buffer_ptr), current_thread); - } else { - RegWrite(TODO_DUMMY_REG_997, GdbHexToLong(buffer_ptr), current_thread); + } else if (id == FPCR_REGISTER + 1) { } - // Update Unicorn context skipping scheduler, no running threads at this point + // Update ARM context, skipping scheduler - no running threads at this point Core::System::GetInstance() .ArmInterface(current_core) .LoadContext(current_thread->GetContext()); @@ -885,13 +938,13 @@ static void WriteRegisters() { } else if (reg >= UC_ARM64_REG_Q0 && reg < FPCR_REGISTER) { RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread); } else if (reg == FPCR_REGISTER) { - RegWrite(TODO_DUMMY_REG_998, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } else { - UNIMPLEMENTED(); + RegWrite(FPCR_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread); + } else if (reg == FPCR_REGISTER + 1) { + RegWrite(FPCR_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread); } } - // Update Unicorn context skipping scheduler, no running threads at this point + // Update ARM context, skipping scheduler - no running threads at this point Core::System::GetInstance() .ArmInterface(current_core) .LoadContext(current_thread->GetContext()); @@ -917,12 +970,6 @@ static void ReadMemory() { SendReply("E01"); } - const auto& vm_manager = Core::CurrentProcess()->VMManager(); - if (addr < vm_manager.GetCodeRegionBaseAddress() || - addr >= vm_manager.GetMapRegionEndAddress()) { - return SendReply("E00"); - } - if (!Memory::IsValidVirtualAddress(addr)) { return SendReply("E00"); } @@ -967,7 +1014,7 @@ void Break(bool is_memory_break) { static void Step() { if (command_length > 1) { RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread); - // Update Unicorn context skipping scheduler, no running threads at this point + // Update ARM context, skipping scheduler - no running threads at this point Core::System::GetInstance() .ArmInterface(current_core) .LoadContext(current_thread->GetContext()); @@ -1010,7 +1057,7 @@ static bool CommitBreakpoint(BreakpointType type, VAddr addr, u64 len) { breakpoint.addr = addr; breakpoint.len = len; Memory::ReadBlock(addr, breakpoint.inst.data(), breakpoint.inst.size()); - static constexpr std::array<u8, 4> btrap{{0x00, 0x7d, 0x20, 0xd4}}; + static constexpr std::array<u8, 4> btrap{0x00, 0x7d, 0x20, 0xd4}; Memory::WriteBlock(addr, btrap.data(), btrap.size()); Core::System::GetInstance().InvalidateCpuInstructionCaches(); p.insert({addr, breakpoint}); @@ -1321,13 +1368,15 @@ void SetCpuStepFlag(bool is_step) { } void SendTrap(Kernel::Thread* thread, int trap) { - if (send_trap) { - if (!halt_loop || current_thread == thread) { - current_thread = thread; - SendSignal(thread, trap); - } - halt_loop = true; - send_trap = false; + if (!send_trap) { + return; } + + if (!halt_loop || current_thread == thread) { + current_thread = thread; + SendSignal(thread, trap); + } + halt_loop = true; + send_trap = false; } }; // namespace GDBStub diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index f4127701f..4f17b52f9 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -483,11 +483,15 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext& rb.Push(RESULT_SUCCESS); if (Settings::values.use_docked_mode) { - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) * + static_cast<u32>(Settings::values.resolution_factor)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) * + static_cast<u32>(Settings::values.resolution_factor)); } else { - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth)); - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth) * + static_cast<u32>(Settings::values.resolution_factor)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight) * + static_cast<u32>(Settings::values.resolution_factor)); } LOG_DEBUG(Service_AM, "called"); diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 5d6294016..2aa77f68d 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -341,6 +341,10 @@ std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents() { return registered_cache_union; } +void ClearUnionContents() { + registered_cache_union = nullptr; +} + FileSys::RegisteredCache* GetSystemNANDContents() { LOG_TRACE(Service_FS, "Opening System NAND Contents"); @@ -391,6 +395,7 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) { bis_factory = nullptr; save_data_factory = nullptr; sdmc_factory = nullptr; + ClearUnionContents(); } auto nand_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index ff9182e84..0a6cb6635 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -49,6 +49,7 @@ ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) ResultVal<FileSys::VirtualDir> OpenSDMC(); std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents(); +void ClearUnionContents(); FileSys::RegisteredCache* GetSystemNANDContents(); FileSys::RegisteredCache* GetUserNANDContents(); diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 464e79d01..c1b2f33b9 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -63,6 +63,17 @@ ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService return MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port)); } +ResultCode ServiceManager::UnregisterService(std::string name) { + CASCADE_CODE(ValidateServiceName(name)); + + const auto iter = registered_services.find(name); + if (iter == registered_services.end()) + return ERR_SERVICE_NOT_REGISTERED; + + registered_services.erase(iter); + return RESULT_SUCCESS; +} + ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> ServiceManager::GetServicePort( const std::string& name) { @@ -127,13 +138,52 @@ void SM::GetService(Kernel::HLERequestContext& ctx) { } } +void SM::RegisterService(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto name_buf = rp.PopRaw<std::array<char, 8>>(); + const auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); + + const std::string name(name_buf.begin(), end); + + const auto unk_bool = static_cast<bool>(rp.PopRaw<u32>()); + const auto session_count = rp.PopRaw<u32>(); + + LOG_DEBUG(Service_SM, "called with unk_bool={}", unk_bool); + + auto handle = service_manager->RegisterService(name, session_count); + if (handle.Failed()) { + LOG_ERROR(Service_SM, "failed to register service with error_code={:08X}", + handle.Code().raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(handle.Code()); + return; + } + + IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; + rb.Push(handle.Code()); + rb.PushMoveObjects(std::move(handle).Unwrap()); +} + +void SM::UnregisterService(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto name_buf = rp.PopRaw<std::array<char, 8>>(); + const auto end = std::find(name_buf.begin(), name_buf.end(), '\0'); + + const std::string name(name_buf.begin(), end); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(service_manager->UnregisterService(name)); +} + SM::SM(std::shared_ptr<ServiceManager> service_manager) : ServiceFramework("sm:", 4), service_manager(std::move(service_manager)) { static const FunctionInfo functions[] = { {0x00000000, &SM::Initialize, "Initialize"}, {0x00000001, &SM::GetService, "GetService"}, - {0x00000002, nullptr, "RegisterService"}, - {0x00000003, nullptr, "UnregisterService"}, + {0x00000002, &SM::RegisterService, "RegisterService"}, + {0x00000003, &SM::UnregisterService, "UnregisterService"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 4f8145dda..c4714b3e3 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -35,6 +35,8 @@ public: private: void Initialize(Kernel::HLERequestContext& ctx); void GetService(Kernel::HLERequestContext& ctx); + void RegisterService(Kernel::HLERequestContext& ctx); + void UnregisterService(Kernel::HLERequestContext& ctx); std::shared_ptr<ServiceManager> service_manager; }; @@ -48,6 +50,7 @@ public: ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> RegisterService(std::string name, unsigned int max_sessions); + ResultCode UnregisterService(std::string name); ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name); ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ConnectToService(const std::string& name); diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index d25fdb1fe..a72416084 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -510,7 +510,11 @@ private: if (transaction == TransactionId::Connect) { IGBPConnectRequestParcel request{ctx.ReadBuffer()}; - IGBPConnectResponseParcel response{1280, 720}; + IGBPConnectResponseParcel response{ + static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedWidth) * + Settings::values.resolution_factor), + static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedHeight) * + Settings::values.resolution_factor)}; ctx.WriteBuffer(response.Serialize()); } else if (transaction == TransactionId::SetPreallocatedBuffer) { IGBPSetPreallocatedBufferRequestParcel request{ctx.ReadBuffer()}; @@ -692,11 +696,15 @@ private: rb.Push(RESULT_SUCCESS); if (Settings::values.use_docked_mode) { - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) * + static_cast<u32>(Settings::values.resolution_factor)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) * + static_cast<u32>(Settings::values.resolution_factor)); } else { - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth)); - rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth) * + static_cast<u32>(Settings::values.resolution_factor)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight) * + static_cast<u32>(Settings::values.resolution_factor)); } rb.PushRaw<float>(60.0f); @@ -901,11 +909,15 @@ private: rb.Push(RESULT_SUCCESS); if (Settings::values.use_docked_mode) { - rb.Push(static_cast<u64>(DisplayResolution::DockedWidth)); - rb.Push(static_cast<u64>(DisplayResolution::DockedHeight)); + rb.Push(static_cast<u64>(DisplayResolution::DockedWidth) * + static_cast<u32>(Settings::values.resolution_factor)); + rb.Push(static_cast<u64>(DisplayResolution::DockedHeight) * + static_cast<u32>(Settings::values.resolution_factor)); } else { - rb.Push(static_cast<u64>(DisplayResolution::UndockedWidth)); - rb.Push(static_cast<u64>(DisplayResolution::UndockedHeight)); + rb.Push(static_cast<u64>(DisplayResolution::UndockedWidth) * + static_cast<u32>(Settings::values.resolution_factor)); + rb.Push(static_cast<u64>(DisplayResolution::UndockedHeight) * + static_cast<u32>(Settings::values.resolution_factor)); } } @@ -922,6 +934,8 @@ private: void ListDisplays(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; DisplayInfo display_info; + display_info.width *= static_cast<u64>(Settings::values.resolution_factor); + display_info.height *= static_cast<u64>(Settings::values.resolution_factor); ctx.WriteBuffer(&display_info, sizeof(DisplayInfo)); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/settings.h b/src/core/settings.h index e63134f80..a0c5fd447 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -403,6 +403,7 @@ struct Values { bool use_gdbstub; u16 gdbstub_port; std::string program_args; + bool dump_exefs; bool dump_nso; // WebService |