summaryrefslogtreecommitdiffstats
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/CMakeLists.txt36
-rw-r--r--src/core/arm/arm_interface.cpp201
-rw-r--r--src/core/arm/arm_interface.h18
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp9
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.h3
-rw-r--r--src/core/arm/unicorn/arm_unicorn.cpp18
-rw-r--r--src/core/arm/unicorn/arm_unicorn.h3
-rw-r--r--src/core/core.cpp107
-rw-r--r--src/core/core.h34
-rw-r--r--src/core/core_cpu.cpp19
-rw-r--r--src/core/core_timing.cpp34
-rw-r--r--src/core/core_timing.h23
-rw-r--r--src/core/crypto/key_manager.cpp245
-rw-r--r--src/core/crypto/key_manager.h116
-rw-r--r--src/core/crypto/partition_data_manager.cpp131
-rw-r--r--src/core/file_sys/card_image.cpp46
-rw-r--r--src/core/file_sys/content_archive.cpp4
-rw-r--r--src/core/file_sys/control_metadata.cpp4
-rw-r--r--src/core/file_sys/control_metadata.h4
-rw-r--r--src/core/file_sys/ips_layer.cpp3
-rw-r--r--src/core/file_sys/kernel_executable.cpp228
-rw-r--r--src/core/file_sys/kernel_executable.h99
-rw-r--r--src/core/file_sys/nca_metadata.h15
-rw-r--r--src/core/file_sys/patch_manager.cpp18
-rw-r--r--src/core/file_sys/patch_manager.h9
-rw-r--r--src/core/file_sys/program_metadata.cpp19
-rw-r--r--src/core/file_sys/program_metadata.h9
-rw-r--r--src/core/file_sys/registered_cache.cpp37
-rw-r--r--src/core/file_sys/registered_cache.h3
-rw-r--r--src/core/file_sys/submission_package.cpp24
-rw-r--r--src/core/file_sys/system_archive/mii_model.cpp46
-rw-r--r--src/core/file_sys/system_archive/mii_model.h13
-rw-r--r--src/core/file_sys/system_archive/system_archive.cpp3
-rw-r--r--src/core/file_sys/xts_archive.cpp2
-rw-r--r--src/core/frontend/applets/general_frontend.cpp99
-rw-r--r--src/core/frontend/applets/general_frontend.h84
-rw-r--r--src/core/frontend/applets/web_browser.cpp6
-rw-r--r--src/core/frontend/applets/web_browser.h8
-rw-r--r--src/core/hardware_interrupt_manager.cpp30
-rw-r--r--src/core/hardware_interrupt_manager.h31
-rw-r--r--src/core/hle/kernel/code_set.h3
-rw-r--r--src/core/hle/kernel/kernel.cpp14
-rw-r--r--src/core/hle/kernel/kernel.h5
-rw-r--r--src/core/hle/kernel/physical_memory.h19
-rw-r--r--src/core/hle/kernel/process.cpp184
-rw-r--r--src/core/hle/kernel/process.h65
-rw-r--r--src/core/hle/kernel/shared_memory.cpp6
-rw-r--r--src/core/hle/kernel/shared_memory.h13
-rw-r--r--src/core/hle/kernel/svc.cpp166
-rw-r--r--src/core/hle/kernel/svc_wrap.h5
-rw-r--r--src/core/hle/kernel/thread.cpp10
-rw-r--r--src/core/hle/kernel/thread.h16
-rw-r--r--src/core/hle/kernel/transfer_memory.cpp2
-rw-r--r--src/core/hle/kernel/transfer_memory.h3
-rw-r--r--src/core/hle/kernel/vm_manager.cpp401
-rw-r--r--src/core/hle/kernel/vm_manager.h108
-rw-r--r--src/core/hle/service/acc/acc.cpp250
-rw-r--r--src/core/hle/service/acc/acc.h30
-rw-r--r--src/core/hle/service/acc/acc_aa.cpp5
-rw-r--r--src/core/hle/service/acc/acc_aa.h4
-rw-r--r--src/core/hle/service/acc/acc_su.cpp7
-rw-r--r--src/core/hle/service/acc/acc_su.h4
-rw-r--r--src/core/hle/service/acc/acc_u0.cpp9
-rw-r--r--src/core/hle/service/acc/acc_u0.h4
-rw-r--r--src/core/hle/service/acc/acc_u1.cpp5
-rw-r--r--src/core/hle/service/acc/acc_u1.h4
-rw-r--r--src/core/hle/service/acc/errors.h14
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp11
-rw-r--r--src/core/hle/service/acc/profile_manager.h2
-rw-r--r--src/core/hle/service/am/am.cpp155
-rw-r--r--src/core/hle/service/am/am.h45
-rw-r--r--src/core/hle/service/am/applet_ae.cpp40
-rw-r--r--src/core/hle/service/am/applet_ae.h3
-rw-r--r--src/core/hle/service/am/applet_oe.cpp21
-rw-r--r--src/core/hle/service/am/applet_oe.h3
-rw-r--r--src/core/hle/service/am/applets/applets.cpp90
-rw-r--r--src/core/hle/service/am/applets/applets.h43
-rw-r--r--src/core/hle/service/am/applets/error.cpp22
-rw-r--r--src/core/hle/service/am/applets/error.h7
-rw-r--r--src/core/hle/service/am/applets/general_backend.cpp145
-rw-r--r--src/core/hle/service/am/applets/general_backend.h43
-rw-r--r--src/core/hle/service/am/applets/profile_select.cpp5
-rw-r--r--src/core/hle/service/am/applets/profile_select.h7
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.cpp5
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.h7
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp499
-rw-r--r--src/core/hle/service/am/applets/web_browser.h41
-rw-r--r--src/core/hle/service/apm/apm.cpp13
-rw-r--r--src/core/hle/service/apm/apm.h7
-rw-r--r--src/core/hle/service/apm/controller.cpp68
-rw-r--r--src/core/hle/service/apm/controller.h70
-rw-r--r--src/core/hle/service/apm/interface.cpp82
-rw-r--r--src/core/hle/service/apm/interface.h14
-rw-r--r--src/core/hle/service/arp/arp.cpp75
-rw-r--r--src/core/hle/service/arp/arp.h16
-rw-r--r--src/core/hle/service/audio/audio.cpp6
-rw-r--r--src/core/hle/service/audio/audio.h6
-rw-r--r--src/core/hle/service/audio/audout_u.cpp59
-rw-r--r--src/core/hle/service/audio/audout_u.h12
-rw-r--r--src/core/hle/service/audio/audren_u.cpp257
-rw-r--r--src/core/hle/service/audio/audren_u.h24
-rw-r--r--src/core/hle/service/es/es.cpp230
-rw-r--r--src/core/hle/service/fatal/fatal.cpp28
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp10
-rw-r--r--src/core/hle/service/filesystem/filesystem.h2
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp54
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h26
-rw-r--r--src/core/hle/service/friend/errors.h12
-rw-r--r--src/core/hle/service/friend/friend.cpp150
-rw-r--r--src/core/hle/service/friend/friend.h1
-rw-r--r--src/core/hle/service/friend/interface.cpp2
-rw-r--r--src/core/hle/service/glue/arp.cpp297
-rw-r--r--src/core/hle/service/glue/arp.h43
-rw-r--r--src/core/hle/service/glue/bgtc.cpp50
-rw-r--r--src/core/hle/service/glue/bgtc.h23
-rw-r--r--src/core/hle/service/glue/errors.h16
-rw-r--r--src/core/hle/service/glue/glue.cpp25
-rw-r--r--src/core/hle/service/glue/glue.h16
-rw-r--r--src/core/hle/service/glue/manager.cpp78
-rw-r--r--src/core/hle/service/glue/manager.h63
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp37
-rw-r--r--src/core/hle/service/hid/controllers/npad.h6
-rw-r--r--src/core/hle/service/hid/errors.h13
-rw-r--r--src/core/hle/service/hid/hid.cpp75
-rw-r--r--src/core/hle/service/hid/hid.h5
-rw-r--r--src/core/hle/service/ldr/ldr.cpp34
-rw-r--r--src/core/hle/service/mii/mii.cpp16
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp4
-rw-r--r--src/core/hle/service/ns/pl_u.cpp12
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdevice.h13
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp11
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp15
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp152
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.h15
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp7
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp48
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h41
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.h5
-rw-r--r--src/core/hle/service/nvdrv/interface.cpp48
-rw-r--r--src/core/hle/service/nvdrv/interface.h4
-rw-r--r--src/core/hle/service/nvdrv/nvdata.h48
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.cpp59
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.h88
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.cpp23
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.h11
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp23
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h4
-rw-r--r--src/core/hle/service/pm/pm.cpp124
-rw-r--r--src/core/hle/service/pm/pm.h6
-rw-r--r--src/core/hle/service/prepo/prepo.cpp25
-rw-r--r--src/core/hle/service/service.cpp26
-rw-r--r--src/core/hle/service/service.h3
-rw-r--r--src/core/hle/service/set/set.cpp10
-rw-r--r--src/core/hle/service/set/set.h1
-rw-r--r--src/core/hle/service/time/interface.cpp11
-rw-r--r--src/core/hle/service/time/interface.h5
-rw-r--r--src/core/hle/service/time/time.cpp115
-rw-r--r--src/core/hle/service/time/time.h11
-rw-r--r--src/core/hle/service/time/time_sharedmemory.cpp68
-rw-r--r--src/core/hle/service/time/time_sharedmemory.h74
-rw-r--r--src/core/hle/service/vi/vi.cpp48
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp11
-rw-r--r--src/core/loader/deconstructed_rom_directory.h4
-rw-r--r--src/core/loader/elf.cpp2
-rw-r--r--src/core/loader/kip.cpp102
-rw-r--r--src/core/loader/kip.h35
-rw-r--r--src/core/loader/loader.cpp16
-rw-r--r--src/core/loader/loader.h11
-rw-r--r--src/core/loader/nax.cpp4
-rw-r--r--src/core/loader/nax.h2
-rw-r--r--src/core/loader/nca.cpp9
-rw-r--r--src/core/loader/nca.h2
-rw-r--r--src/core/loader/nro.cpp11
-rw-r--r--src/core/loader/nro.h1
-rw-r--r--src/core/loader/nso.cpp15
-rw-r--r--src/core/loader/nso.h5
-rw-r--r--src/core/loader/nsp.cpp7
-rw-r--r--src/core/loader/nsp.h2
-rw-r--r--src/core/loader/xci.cpp6
-rw-r--r--src/core/loader/xci.h2
-rw-r--r--src/core/memory.cpp2
-rw-r--r--src/core/memory.h4
-rw-r--r--src/core/reporter.cpp387
-rw-r--r--src/core/reporter.h67
-rw-r--r--src/core/settings.cpp1
-rw-r--r--src/core/settings.h3
-rw-r--r--src/core/telemetry_session.cpp1
-rw-r--r--src/core/tools/freezer.cpp188
-rw-r--r--src/core/tools/freezer.h82
-rw-r--r--src/core/tracer/citrace.h100
-rw-r--r--src/core/tracer/recorder.cpp208
-rw-r--r--src/core/tracer/recorder.h87
202 files changed, 7208 insertions, 1789 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index a2e2e976e..877a9e353 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -45,6 +45,8 @@ add_library(core STATIC
file_sys/fsmitm_romfsbuild.h
file_sys/ips_layer.cpp
file_sys/ips_layer.h
+ file_sys/kernel_executable.cpp
+ file_sys/kernel_executable.h
file_sys/mode.h
file_sys/nca_metadata.cpp
file_sys/nca_metadata.h
@@ -68,6 +70,8 @@ add_library(core STATIC
file_sys/sdmc_factory.h
file_sys/submission_package.cpp
file_sys/submission_package.h
+ file_sys/system_archive/mii_model.cpp
+ file_sys/system_archive/mii_model.h
file_sys/system_archive/ng_word.cpp
file_sys/system_archive/ng_word.h
file_sys/system_archive/system_archive.cpp
@@ -109,6 +113,8 @@ add_library(core STATIC
frontend/scope_acquire_window_context.h
gdbstub/gdbstub.cpp
gdbstub/gdbstub.h
+ hardware_interrupt_manager.cpp
+ hardware_interrupt_manager.h
hle/ipc.h
hle/ipc_helpers.h
hle/kernel/address_arbiter.cpp
@@ -173,6 +179,7 @@ add_library(core STATIC
hle/service/acc/acc_u0.h
hle/service/acc/acc_u1.cpp
hle/service/acc/acc_u1.h
+ hle/service/acc/errors.h
hle/service/acc/profile_manager.cpp
hle/service/acc/profile_manager.h
hle/service/am/am.cpp
@@ -205,10 +212,10 @@ add_library(core STATIC
hle/service/aoc/aoc_u.h
hle/service/apm/apm.cpp
hle/service/apm/apm.h
+ hle/service/apm/controller.cpp
+ hle/service/apm/controller.h
hle/service/apm/interface.cpp
hle/service/apm/interface.h
- hle/service/arp/arp.cpp
- hle/service/arp/arp.h
hle/service/audio/audctl.cpp
hle/service/audio/audctl.h
hle/service/audio/auddbg.cpp
@@ -270,10 +277,20 @@ add_library(core STATIC
hle/service/filesystem/fsp_srv.h
hle/service/fgm/fgm.cpp
hle/service/fgm/fgm.h
+ hle/service/friend/errors.h
hle/service/friend/friend.cpp
hle/service/friend/friend.h
hle/service/friend/interface.cpp
hle/service/friend/interface.h
+ hle/service/glue/arp.cpp
+ hle/service/glue/arp.h
+ hle/service/glue/bgtc.cpp
+ hle/service/glue/bgtc.h
+ hle/service/glue/errors.h
+ hle/service/glue/glue.cpp
+ hle/service/glue/glue.h
+ hle/service/glue/manager.cpp
+ hle/service/glue/manager.h
hle/service/grc/grc.cpp
hle/service/grc/grc.h
hle/service/hid/hid.cpp
@@ -282,6 +299,7 @@ add_library(core STATIC
hle/service/hid/irs.h
hle/service/hid/xcd.cpp
hle/service/hid/xcd.h
+ hle/service/hid/errors.h
hle/service/hid/controllers/controller_base.cpp
hle/service/hid/controllers/controller_base.h
hle/service/hid/controllers/debug_pad.cpp
@@ -358,6 +376,7 @@ add_library(core STATIC
hle/service/nvdrv/devices/nvmap.h
hle/service/nvdrv/interface.cpp
hle/service/nvdrv/interface.h
+ hle/service/nvdrv/nvdata.h
hle/service/nvdrv/nvdrv.cpp
hle/service/nvdrv/nvdrv.h
hle/service/nvdrv/nvmemp.cpp
@@ -420,6 +439,8 @@ add_library(core STATIC
hle/service/time/interface.h
hle/service/time/time.cpp
hle/service/time/time.h
+ hle/service/time/time_sharedmemory.cpp
+ hle/service/time/time_sharedmemory.h
hle/service/usb/usb.cpp
hle/service/usb/usb.h
hle/service/vi/display/vi_display.cpp
@@ -440,6 +461,8 @@ add_library(core STATIC
loader/deconstructed_rom_directory.h
loader/elf.cpp
loader/elf.h
+ loader/kip.cpp
+ loader/kip.h
loader/loader.cpp
loader/loader.h
loader/nax.cpp
@@ -459,19 +482,20 @@ add_library(core STATIC
memory_setup.h
perf_stats.cpp
perf_stats.h
+ reporter.cpp
+ reporter.h
settings.cpp
settings.h
telemetry_session.cpp
telemetry_session.h
- tracer/citrace.h
- tracer/recorder.cpp
- tracer/recorder.h
+ tools/freezer.cpp
+ tools/freezer.h
)
create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
-target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt mbedtls opus unicorn open_source_archives)
+target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives)
if (ENABLE_WEB_SERVICE)
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
target_link_libraries(core PRIVATE web_service)
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index 2223cbeed..372612c9b 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -2,26 +2,213 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <map>
+#include <optional>
+#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
+#include "core/core.h"
+#include "core/loader/loader.h"
#include "core/memory.h"
namespace Core {
-void ARM_Interface::LogBacktrace() const {
- VAddr fp = GetReg(29);
- VAddr lr = GetReg(30);
- const VAddr sp = GetReg(13);
- const VAddr pc = GetPC();
- LOG_ERROR(Core_ARM, "Backtrace, sp={:016X}, pc={:016X}", sp, pc);
+namespace {
+
+constexpr u64 ELF_DYNAMIC_TAG_NULL = 0;
+constexpr u64 ELF_DYNAMIC_TAG_STRTAB = 5;
+constexpr u64 ELF_DYNAMIC_TAG_SYMTAB = 6;
+constexpr u64 ELF_DYNAMIC_TAG_SYMENT = 11;
+
+enum class ELFSymbolType : u8 {
+ None = 0,
+ Object = 1,
+ Function = 2,
+ Section = 3,
+ File = 4,
+ Common = 5,
+ TLS = 6,
+};
+
+enum class ELFSymbolBinding : u8 {
+ Local = 0,
+ Global = 1,
+ Weak = 2,
+};
+
+enum class ELFSymbolVisibility : u8 {
+ Default = 0,
+ Internal = 1,
+ Hidden = 2,
+ Protected = 3,
+};
+
+struct ELFSymbol {
+ u32 name_index;
+ union {
+ u8 info;
+
+ BitField<0, 4, ELFSymbolType> type;
+ BitField<4, 4, ELFSymbolBinding> binding;
+ };
+ ELFSymbolVisibility visibility;
+ u16 sh_index;
+ u64 value;
+ u64 size;
+};
+static_assert(sizeof(ELFSymbol) == 0x18, "ELFSymbol has incorrect size.");
+
+using Symbols = std::vector<std::pair<ELFSymbol, std::string>>;
+
+Symbols GetSymbols(VAddr text_offset) {
+ const auto mod_offset = text_offset + Memory::Read32(text_offset + 4);
+
+ if (mod_offset < text_offset || (mod_offset & 0b11) != 0 ||
+ Memory::Read32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) {
+ return {};
+ }
+
+ const auto dynamic_offset = Memory::Read32(mod_offset + 0x4) + mod_offset;
+
+ VAddr string_table_offset{};
+ VAddr symbol_table_offset{};
+ u64 symbol_entry_size{};
+
+ VAddr dynamic_index = dynamic_offset;
+ while (true) {
+ const auto tag = Memory::Read64(dynamic_index);
+ const auto value = Memory::Read64(dynamic_index + 0x8);
+ dynamic_index += 0x10;
+
+ if (tag == ELF_DYNAMIC_TAG_NULL) {
+ break;
+ }
+
+ if (tag == ELF_DYNAMIC_TAG_STRTAB) {
+ string_table_offset = value;
+ } else if (tag == ELF_DYNAMIC_TAG_SYMTAB) {
+ symbol_table_offset = value;
+ } else if (tag == ELF_DYNAMIC_TAG_SYMENT) {
+ symbol_entry_size = value;
+ }
+ }
+
+ if (string_table_offset == 0 || symbol_table_offset == 0 || symbol_entry_size == 0) {
+ return {};
+ }
+
+ const auto string_table_address = text_offset + string_table_offset;
+ const auto symbol_table_address = text_offset + symbol_table_offset;
+
+ Symbols out;
+
+ VAddr symbol_index = symbol_table_address;
+ while (symbol_index < string_table_address) {
+ ELFSymbol symbol{};
+ Memory::ReadBlock(symbol_index, &symbol, sizeof(ELFSymbol));
+
+ VAddr string_offset = string_table_address + symbol.name_index;
+ std::string name;
+ for (u8 c = Memory::Read8(string_offset); c != 0; c = Memory::Read8(++string_offset)) {
+ name += static_cast<char>(c);
+ }
+
+ symbol_index += symbol_entry_size;
+ out.push_back({symbol, name});
+ }
+
+ return out;
+}
+
+std::optional<std::string> GetSymbolName(const Symbols& symbols, VAddr func_address) {
+ const auto iter =
+ std::find_if(symbols.begin(), symbols.end(), [func_address](const auto& pair) {
+ const auto& [symbol, name] = pair;
+ const auto end_address = symbol.value + symbol.size;
+ return func_address >= symbol.value && func_address < end_address;
+ });
+
+ if (iter == symbols.end()) {
+ return std::nullopt;
+ }
+
+ return iter->second;
+}
+
+} // Anonymous namespace
+
+constexpr u64 SEGMENT_BASE = 0x7100000000ull;
+
+std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktrace() const {
+ std::vector<BacktraceEntry> out;
+
+ auto fp = GetReg(29);
+ auto lr = GetReg(30);
+
while (true) {
- LOG_ERROR(Core_ARM, "{:016X}", lr);
+ out.push_back({"", 0, lr, 0});
if (!fp) {
break;
}
lr = Memory::Read64(fp + 8) - 4;
fp = Memory::Read64(fp);
}
+
+ std::map<VAddr, std::string> modules;
+ auto& loader{System::GetInstance().GetAppLoader()};
+ if (loader.ReadNSOModules(modules) != Loader::ResultStatus::Success) {
+ return {};
+ }
+
+ std::map<std::string, Symbols> symbols;
+ for (const auto& module : modules) {
+ symbols.insert_or_assign(module.second, GetSymbols(module.first));
+ }
+
+ for (auto& entry : out) {
+ VAddr base = 0;
+ for (auto iter = modules.rbegin(); iter != modules.rend(); ++iter) {
+ const auto& module{*iter};
+ if (entry.original_address >= module.first) {
+ entry.module = module.second;
+ base = module.first;
+ break;
+ }
+ }
+
+ entry.offset = entry.original_address - base;
+ entry.address = SEGMENT_BASE + entry.offset;
+
+ if (entry.module.empty())
+ entry.module = "unknown";
+
+ const auto symbol_set = symbols.find(entry.module);
+ if (symbol_set != symbols.end()) {
+ const auto symbol = GetSymbolName(symbol_set->second, entry.offset);
+ if (symbol.has_value()) {
+ // TODO(DarkLordZach): Add demangling of symbol names.
+ entry.name = *symbol;
+ }
+ }
+ }
+
+ return out;
+}
+
+void ARM_Interface::LogBacktrace() const {
+ const VAddr sp = GetReg(13);
+ const VAddr pc = GetPC();
+ LOG_ERROR(Core_ARM, "Backtrace, sp={:016X}, pc={:016X}", sp, pc);
+ LOG_ERROR(Core_ARM, "{:20}{:20}{:20}{:20}{}", "Module Name", "Address", "Original Address",
+ "Offset", "Symbol");
+ LOG_ERROR(Core_ARM, "");
+
+ const auto backtrace = GetBacktrace();
+ for (const auto& entry : backtrace) {
+ LOG_ERROR(Core_ARM, "{:20}{:016X} {:016X} {:016X} {}", entry.module, entry.address,
+ entry.original_address, entry.offset, entry.name);
+ }
}
+
} // namespace Core
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 978b1518f..45e94e625 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -5,6 +5,7 @@
#pragma once
#include <array>
+#include <vector>
#include "common/common_types.h"
namespace Common {
@@ -43,13 +44,6 @@ public:
/// Step CPU by one instruction
virtual void Step() = 0;
- /// Maps a backing memory region for the CPU
- virtual void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
- Kernel::VMAPermission perms) = 0;
-
- /// Unmaps a region of memory that was previously mapped using MapBackingMemory
- virtual void UnmapMemory(VAddr address, std::size_t size) = 0;
-
/// Clear all instruction cache
virtual void ClearInstructionCache() = 0;
@@ -152,6 +146,16 @@ public:
/// Prepare core for thread reschedule (if needed to correctly handle state)
virtual void PrepareReschedule() = 0;
+ struct BacktraceEntry {
+ std::string module;
+ u64 address;
+ u64 original_address;
+ u64 offset;
+ std::string name;
+ };
+
+ std::vector<BacktraceEntry> GetBacktrace() const;
+
/// fp (= r29) points to the last frame record.
/// Note that this is the frame record for the *previous* frame, not the current one.
/// Note we need to subtract 4 from our last read to get the proper address
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 44307fa19..f1506b372 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -177,15 +177,6 @@ ARM_Dynarmic::ARM_Dynarmic(System& system, ExclusiveMonitor& exclusive_monitor,
ARM_Dynarmic::~ARM_Dynarmic() = default;
-void ARM_Dynarmic::MapBackingMemory(u64 address, std::size_t size, u8* memory,
- Kernel::VMAPermission perms) {
- inner_unicorn.MapBackingMemory(address, size, memory, perms);
-}
-
-void ARM_Dynarmic::UnmapMemory(u64 address, std::size_t size) {
- inner_unicorn.UnmapMemory(address, size);
-}
-
void ARM_Dynarmic::SetPC(u64 pc) {
jit->SetPC(pc);
}
diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h
index b701e97a3..504d46c68 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.h
+++ b/src/core/arm/dynarmic/arm_dynarmic.h
@@ -23,9 +23,6 @@ public:
ARM_Dynarmic(System& system, ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
~ARM_Dynarmic() override;
- void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
- Kernel::VMAPermission perms) override;
- void UnmapMemory(u64 address, std::size_t size) override;
void SetPC(u64 pc) override;
u64 GetPC() const override;
u64 GetReg(int index) const override;
diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp
index 4e07fe8b5..97d5c2a8a 100644
--- a/src/core/arm/unicorn/arm_unicorn.cpp
+++ b/src/core/arm/unicorn/arm_unicorn.cpp
@@ -50,11 +50,14 @@ static void CodeHook(uc_engine* uc, uint64_t address, uint32_t size, void* user_
static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value,
void* user_data) {
+ auto* const system = static_cast<System*>(user_data);
+
ARM_Interface::ThreadContext ctx{};
- Core::CurrentArmInterface().SaveContext(ctx);
+ system->CurrentArmInterface().SaveContext(ctx);
ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x{:X}, pc=0x{:X}, lr=0x{:X}", addr,
ctx.pc, ctx.cpu_registers[30]);
- return {};
+
+ return false;
}
ARM_Unicorn::ARM_Unicorn(System& system) : system{system} {
@@ -65,7 +68,7 @@ ARM_Unicorn::ARM_Unicorn(System& system) : system{system} {
uc_hook hook{};
CHECKED(uc_hook_add(uc, &hook, UC_HOOK_INTR, (void*)InterruptHook, this, 0, -1));
- CHECKED(uc_hook_add(uc, &hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, this, 0, -1));
+ CHECKED(uc_hook_add(uc, &hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, &system, 0, -1));
if (GDBStub::IsServerEnabled()) {
CHECKED(uc_hook_add(uc, &hook, UC_HOOK_CODE, (void*)CodeHook, this, 0, -1));
last_bkpt_hit = false;
@@ -76,15 +79,6 @@ ARM_Unicorn::~ARM_Unicorn() {
CHECKED(uc_close(uc));
}
-void ARM_Unicorn::MapBackingMemory(VAddr address, std::size_t size, u8* memory,
- Kernel::VMAPermission perms) {
- CHECKED(uc_mem_map_ptr(uc, address, size, static_cast<u32>(perms), memory));
-}
-
-void ARM_Unicorn::UnmapMemory(VAddr address, std::size_t size) {
- CHECKED(uc_mem_unmap(uc, address, size));
-}
-
void ARM_Unicorn::SetPC(u64 pc) {
CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &pc));
}
diff --git a/src/core/arm/unicorn/arm_unicorn.h b/src/core/arm/unicorn/arm_unicorn.h
index 34e974b4d..fe2ffd70c 100644
--- a/src/core/arm/unicorn/arm_unicorn.h
+++ b/src/core/arm/unicorn/arm_unicorn.h
@@ -18,9 +18,6 @@ public:
explicit ARM_Unicorn(System& system);
~ARM_Unicorn() override;
- void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
- Kernel::VMAPermission perms) override;
- void UnmapMemory(VAddr address, std::size_t size) override;
void SetPC(u64 pc) override;
u64 GetPC() const override;
u64 GetReg(int index) const override;
diff --git a/src/core/core.cpp b/src/core/core.cpp
index ff0721079..3d0978cbf 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -19,25 +19,55 @@
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_real.h"
#include "core/gdbstub/gdbstub.h"
+#include "core/hardware_interrupt_manager.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/apm/controller.h"
+#include "core/hle/service/glue/manager.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h"
+#include "core/reporter.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
+#include "core/tools/freezer.h"
#include "file_sys/cheat_engine.h"
+#include "file_sys/patch_manager.h"
#include "video_core/debug_utils/debug_utils.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
namespace Core {
+namespace {
+
+FileSys::StorageId GetStorageIdForFrontendSlot(
+ std::optional<FileSys::ContentProviderUnionSlot> slot) {
+ if (!slot.has_value()) {
+ return FileSys::StorageId::None;
+ }
+
+ switch (*slot) {
+ case FileSys::ContentProviderUnionSlot::UserNAND:
+ return FileSys::StorageId::NandUser;
+ case FileSys::ContentProviderUnionSlot::SysNAND:
+ return FileSys::StorageId::NandSystem;
+ case FileSys::ContentProviderUnionSlot::SDMC:
+ return FileSys::StorageId::SdCard;
+ case FileSys::ContentProviderUnionSlot::FrontendManual:
+ return FileSys::StorageId::Host;
+ default:
+ return FileSys::StorageId::None;
+ }
+}
+
+} // Anonymous namespace
+
/*static*/ System System::s_instance;
FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
@@ -74,7 +104,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
return vfs->OpenFile(path, FileSys::Mode::Read);
}
struct System::Impl {
- explicit Impl(System& system) : kernel{system}, cpu_core_manager{system} {}
+ explicit Impl(System& system)
+ : kernel{system}, cpu_core_manager{system}, applet_manager{system}, reporter{system} {}
Cpu& CurrentCpuCore() {
return cpu_core_manager.GetCurrentCore();
@@ -109,17 +140,20 @@ struct System::Impl {
/// Create default implementations of applets if one is not provided.
applet_manager.SetDefaultAppletsIfMissing();
+ /// Reset all glue registrations
+ arp_manager.ResetAll();
+
telemetry_session = std::make_unique<Core::TelemetrySession>();
service_manager = std::make_shared<Service::SM::ServiceManager>();
- Service::Init(service_manager, system, *virtual_filesystem);
+ Service::Init(service_manager, system);
GDBStub::Init();
renderer = VideoCore::CreateRenderer(emu_window, system);
if (!renderer->Init()) {
return ResultStatus::ErrorVideoCore;
}
-
+ interrupt_manager = std::make_unique<Core::Hardware::InterruptManager>(system);
gpu_core = VideoCore::CreateGPU(system);
is_powered_on = true;
@@ -150,7 +184,8 @@ struct System::Impl {
}
telemetry_session->AddInitialInfo(*app_loader);
- auto main_process = Kernel::Process::Create(system, "main");
+ auto main_process =
+ Kernel::Process::Create(system, "main", Kernel::Process::ProcessType::Userland);
const auto [load_result, load_parameters] = app_loader->Load(*main_process);
if (load_result != Loader::ResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
@@ -159,6 +194,7 @@ struct System::Impl {
return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) +
static_cast<u32>(load_result));
}
+ AddGlueRegistrationForProcess(*app_loader, *main_process);
kernel.MakeCurrentProcess(main_process.get());
// Main process has been loaded and been made current.
@@ -217,6 +253,31 @@ struct System::Impl {
return app_loader->ReadTitle(out);
}
+ void AddGlueRegistrationForProcess(Loader::AppLoader& loader, Kernel::Process& process) {
+ std::vector<u8> nacp_data;
+ FileSys::NACP nacp;
+ if (loader.ReadControlData(nacp) == Loader::ResultStatus::Success) {
+ nacp_data = nacp.GetRawBytes();
+ } else {
+ nacp_data.resize(sizeof(FileSys::RawNACP));
+ }
+
+ Service::Glue::ApplicationLaunchProperty launch{};
+ launch.title_id = process.GetTitleID();
+
+ FileSys::PatchManager pm{launch.title_id};
+ launch.version = pm.GetGameVersion().value_or(0);
+
+ // TODO(DarkLordZach): When FSController/Game Card Support is added, if
+ // current_process_game_card use correct StorageId
+ launch.base_game_storage_id = GetStorageIdForFrontendSlot(content_provider->GetSlotForEntry(
+ launch.title_id, FileSys::ContentRecordType::Program));
+ launch.update_storage_id = GetStorageIdForFrontendSlot(content_provider->GetSlotForEntry(
+ FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program));
+
+ arp_manager.Register(launch.title_id, launch, std::move(nacp_data));
+ }
+
void SetStatus(ResultStatus new_status, const char* details = nullptr) {
status = new_status;
if (details) {
@@ -239,20 +300,30 @@ struct System::Impl {
std::unique_ptr<VideoCore::RendererBase> renderer;
std::unique_ptr<Tegra::GPU> gpu_core;
std::shared_ptr<Tegra::DebugContext> debug_context;
+ std::unique_ptr<Core::Hardware::InterruptManager> interrupt_manager;
CpuCoreManager cpu_core_manager;
bool is_powered_on = false;
std::unique_ptr<FileSys::CheatEngine> cheat_engine;
+ std::unique_ptr<Tools::Freezer> memory_freezer;
/// Frontend applets
Service::AM::Applets::AppletManager applet_manager;
+ /// APM (Performance) services
+ Service::APM::Controller apm_controller{core_timing};
+
+ /// Glue services
+ Service::Glue::ARPManager arp_manager;
+
/// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager;
/// Telemetry session for this emulation session
std::unique_ptr<Core::TelemetrySession> telemetry_session;
+ Reporter reporter;
+
ResultStatus status = ResultStatus::Success;
std::string status_details = "";
@@ -376,6 +447,14 @@ const Tegra::GPU& System::GPU() const {
return *impl->gpu_core;
}
+Core::Hardware::InterruptManager& System::InterruptManager() {
+ return *impl->interrupt_manager;
+}
+
+const Core::Hardware::InterruptManager& System::InterruptManager() const {
+ return *impl->interrupt_manager;
+}
+
VideoCore::RendererBase& System::Renderer() {
return *impl->renderer;
}
@@ -492,6 +571,26 @@ void System::ClearContentProvider(FileSys::ContentProviderUnionSlot slot) {
impl->content_provider->ClearSlot(slot);
}
+const Reporter& System::GetReporter() const {
+ return impl->reporter;
+}
+
+Service::Glue::ARPManager& System::GetARPManager() {
+ return impl->arp_manager;
+}
+
+const Service::Glue::ARPManager& System::GetARPManager() const {
+ return impl->arp_manager;
+}
+
+Service::APM::Controller& System::GetAPMController() {
+ return impl->apm_controller;
+}
+
+const Service::APM::Controller& System::GetAPMController() const {
+ return impl->apm_controller;
+}
+
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
return impl->Init(*this, emu_window);
}
diff --git a/src/core/core.h b/src/core/core.h
index 20959de54..0138d93b0 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -8,6 +8,7 @@
#include <memory>
#include <string>
+#include <map>
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
#include "core/hle/kernel/object.h"
@@ -42,6 +43,14 @@ struct AppletFrontendSet;
class AppletManager;
} // namespace AM::Applets
+namespace APM {
+class Controller;
+}
+
+namespace Glue {
+class ARPManager;
+}
+
namespace SM {
class ServiceManager;
} // namespace SM
@@ -61,6 +70,10 @@ namespace Core::Timing {
class CoreTiming;
}
+namespace Core::Hardware {
+class InterruptManager;
+}
+
namespace Core {
class ARM_Interface;
@@ -68,6 +81,7 @@ class Cpu;
class ExclusiveMonitor;
class FrameLimiter;
class PerfStats;
+class Reporter;
class TelemetrySession;
struct PerfStatsResults;
@@ -224,6 +238,12 @@ public:
/// Provides a constant reference to the core timing instance.
const Timing::CoreTiming& CoreTiming() const;
+ /// Provides a reference to the interrupt manager instance.
+ Core::Hardware::InterruptManager& InterruptManager();
+
+ /// Provides a constant reference to the interrupt manager instance.
+ const Core::Hardware::InterruptManager& InterruptManager() const;
+
/// Provides a reference to the kernel instance.
Kernel::KernelCore& Kernel();
@@ -284,6 +304,16 @@ public:
void ClearContentProvider(FileSys::ContentProviderUnionSlot slot);
+ const Reporter& GetReporter() const;
+
+ Service::Glue::ARPManager& GetARPManager();
+
+ const Service::Glue::ARPManager& GetARPManager() const;
+
+ Service::APM::Controller& GetAPMController();
+
+ const Service::APM::Controller& GetAPMController() const;
+
private:
System();
@@ -307,10 +337,6 @@ private:
static System s_instance;
};
-inline ARM_Interface& CurrentArmInterface() {
- return System::GetInstance().CurrentArmInterface();
-}
-
inline Kernel::Process* CurrentProcess() {
return System::GetInstance().CurrentProcess();
}
diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp
index ba63c3e61..21c410e34 100644
--- a/src/core/core_cpu.cpp
+++ b/src/core/core_cpu.cpp
@@ -53,16 +53,12 @@ bool CpuBarrier::Rendezvous() {
Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
std::size_t core_index)
: cpu_barrier{cpu_barrier}, core_timing{system.CoreTiming()}, core_index{core_index} {
- if (Settings::values.use_cpu_jit) {
#ifdef ARCHITECTURE_x86_64
- arm_interface = std::make_unique<ARM_Dynarmic>(system, exclusive_monitor, core_index);
+ arm_interface = std::make_unique<ARM_Dynarmic>(system, exclusive_monitor, core_index);
#else
- arm_interface = std::make_unique<ARM_Unicorn>(system);
- LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
+ arm_interface = std::make_unique<ARM_Unicorn>(system);
+ LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
#endif
- } else {
- arm_interface = std::make_unique<ARM_Unicorn>(system);
- }
scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface);
}
@@ -70,15 +66,12 @@ Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_ba
Cpu::~Cpu() = default;
std::unique_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) {
- if (Settings::values.use_cpu_jit) {
#ifdef ARCHITECTURE_x86_64
- return std::make_unique<DynarmicExclusiveMonitor>(num_cores);
+ return std::make_unique<DynarmicExclusiveMonitor>(num_cores);
#else
- return nullptr; // TODO(merry): Passthrough exclusive monitor
+ // TODO(merry): Passthrough exclusive monitor
+ return nullptr;
#endif
- } else {
- return nullptr; // TODO(merry): Passthrough exclusive monitor
- }
}
void Cpu::RunLoop(bool tight_loop) {
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 41adb2302..a58f7b131 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -56,12 +56,12 @@ void CoreTiming::Initialize() {
}
void CoreTiming::Shutdown() {
- MoveEvents();
ClearPendingEvents();
UnregisterAllEvents();
}
EventType* CoreTiming::RegisterEvent(const std::string& name, TimedCallback callback) {
+ std::lock_guard guard{inner_mutex};
// check for existing type with same name.
// we want event type names to remain unique so that we can use them for serialization.
ASSERT_MSG(event_types.find(name) == event_types.end(),
@@ -82,6 +82,7 @@ void CoreTiming::UnregisterAllEvents() {
void CoreTiming::ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata) {
ASSERT(event_type != nullptr);
+ std::lock_guard guard{inner_mutex};
const s64 timeout = GetTicks() + cycles_into_future;
// If this event needs to be scheduled before the next advance(), force one early
@@ -93,12 +94,8 @@ void CoreTiming::ScheduleEvent(s64 cycles_into_future, const EventType* event_ty
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
-void CoreTiming::ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type,
- u64 userdata) {
- ts_queue.Push(Event{global_timer + cycles_into_future, 0, userdata, event_type});
-}
-
void CoreTiming::UnscheduleEvent(const EventType* event_type, u64 userdata) {
+ std::lock_guard guard{inner_mutex};
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
return e.type == event_type && e.userdata == userdata;
});
@@ -110,10 +107,6 @@ void CoreTiming::UnscheduleEvent(const EventType* event_type, u64 userdata) {
}
}
-void CoreTiming::UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata) {
- unschedule_queue.Push(std::make_pair(event_type, userdata));
-}
-
u64 CoreTiming::GetTicks() const {
u64 ticks = static_cast<u64>(global_timer);
if (!is_global_timer_sane) {
@@ -135,6 +128,7 @@ void CoreTiming::ClearPendingEvents() {
}
void CoreTiming::RemoveEvent(const EventType* event_type) {
+ std::lock_guard guard{inner_mutex};
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(),
[&](const Event& e) { return e.type == event_type; });
@@ -145,11 +139,6 @@ void CoreTiming::RemoveEvent(const EventType* event_type) {
}
}
-void CoreTiming::RemoveNormalAndThreadsafeEvent(const EventType* event_type) {
- MoveEvents();
- RemoveEvent(event_type);
-}
-
void CoreTiming::ForceExceptionCheck(s64 cycles) {
cycles = std::max<s64>(0, cycles);
if (downcount <= cycles) {
@@ -162,19 +151,8 @@ void CoreTiming::ForceExceptionCheck(s64 cycles) {
downcount = static_cast<int>(cycles);
}
-void CoreTiming::MoveEvents() {
- for (Event ev; ts_queue.Pop(ev);) {
- ev.fifo_order = event_fifo_id++;
- event_queue.emplace_back(std::move(ev));
- std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
- }
-}
-
void CoreTiming::Advance() {
- MoveEvents();
- for (std::pair<const EventType*, u64> ev; unschedule_queue.Pop(ev);) {
- UnscheduleEvent(ev.first, ev.second);
- }
+ std::unique_lock<std::mutex> guard(inner_mutex);
const int cycles_executed = slice_length - downcount;
global_timer += cycles_executed;
@@ -186,7 +164,9 @@ void CoreTiming::Advance() {
Event evt = std::move(event_queue.front());
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
event_queue.pop_back();
+ inner_mutex.unlock();
evt.type->callback(evt.userdata, global_timer - evt.time);
+ inner_mutex.lock();
}
is_global_timer_sane = false;
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 9d2efde37..161c7007d 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -6,6 +6,7 @@
#include <chrono>
#include <functional>
+#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
@@ -67,7 +68,7 @@ public:
///
EventType* RegisterEvent(const std::string& name, TimedCallback callback);
- /// Unregisters all registered events thus far.
+ /// Unregisters all registered events thus far. Note: not thread unsafe
void UnregisterAllEvents();
/// After the first Advance, the slice lengths and the downcount will be reduced whenever an
@@ -76,20 +77,10 @@ public:
/// Scheduling from a callback will not update the downcount until the Advance() completes.
void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata = 0);
- /// This is to be called when outside of hle threads, such as the graphics thread, wants to
- /// schedule things to be executed on the main thread.
- ///
- /// @note This doesn't change slice_length and thus events scheduled by this might be
- /// called with a delay of up to MAX_SLICE_LENGTH
- void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type,
- u64 userdata = 0);
-
void UnscheduleEvent(const EventType* event_type, u64 userdata);
- void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata);
/// We only permit one event of each type in the queue at a time.
void RemoveEvent(const EventType* event_type);
- void RemoveNormalAndThreadsafeEvent(const EventType* event_type);
void ForceExceptionCheck(s64 cycles);
@@ -120,7 +111,6 @@ private:
/// Clear all pending events. This should ONLY be done on exit.
void ClearPendingEvents();
- void MoveEvents();
s64 global_timer = 0;
s64 idled_cycles = 0;
@@ -143,14 +133,9 @@ private:
// remain stable regardless of rehashes/resizing.
std::unordered_map<std::string, EventType> event_types;
- // The queue for storing the events from other threads threadsafe until they will be added
- // to the event_queue by the emu thread
- Common::MPSCQueue<Event> ts_queue;
-
- // The queue for unscheduling the events from other threads threadsafe
- Common::MPSCQueue<std::pair<const EventType*, u64>> unschedule_queue;
-
EventType* ev_lost = nullptr;
+
+ std::mutex inner_mutex;
};
} // namespace Core::Timing
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index dc006e2bb..46aceec3d 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -37,6 +37,7 @@
namespace Core::Crypto {
constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
+constexpr u64 FULL_TICKET_SIZE = 0x400;
using namespace Common;
@@ -55,6 +56,99 @@ const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
{{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
};
+namespace {
+template <std::size_t Size>
+bool IsAllZeroArray(const std::array<u8, Size>& array) {
+ return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; });
+}
+} // namespace
+
+u64 GetSignatureTypeDataSize(SignatureType type) {
+ switch (type) {
+ case SignatureType::RSA_4096_SHA1:
+ case SignatureType::RSA_4096_SHA256:
+ return 0x200;
+ case SignatureType::RSA_2048_SHA1:
+ case SignatureType::RSA_2048_SHA256:
+ return 0x100;
+ case SignatureType::ECDSA_SHA1:
+ case SignatureType::ECDSA_SHA256:
+ return 0x3C;
+ }
+ UNREACHABLE();
+}
+
+u64 GetSignatureTypePaddingSize(SignatureType type) {
+ switch (type) {
+ case SignatureType::RSA_4096_SHA1:
+ case SignatureType::RSA_4096_SHA256:
+ case SignatureType::RSA_2048_SHA1:
+ case SignatureType::RSA_2048_SHA256:
+ return 0x3C;
+ case SignatureType::ECDSA_SHA1:
+ case SignatureType::ECDSA_SHA256:
+ return 0x40;
+ }
+ UNREACHABLE();
+}
+
+SignatureType Ticket::GetSignatureType() const {
+ if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
+ return ticket->sig_type;
+ }
+ if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+ return ticket->sig_type;
+ }
+ if (auto ticket = std::get_if<ECDSATicket>(&data)) {
+ return ticket->sig_type;
+ }
+
+ UNREACHABLE();
+}
+
+TicketData& Ticket::GetData() {
+ if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
+ return ticket->data;
+ }
+ if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+ return ticket->data;
+ }
+ if (auto ticket = std::get_if<ECDSATicket>(&data)) {
+ return ticket->data;
+ }
+
+ UNREACHABLE();
+}
+
+const TicketData& Ticket::GetData() const {
+ if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
+ return ticket->data;
+ }
+ if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+ return ticket->data;
+ }
+ if (auto ticket = std::get_if<ECDSATicket>(&data)) {
+ return ticket->data;
+ }
+
+ UNREACHABLE();
+}
+
+u64 Ticket::GetSize() const {
+ const auto sig_type = GetSignatureType();
+
+ return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) +
+ GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData);
+}
+
+Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& rights_id) {
+ RSA2048Ticket out{};
+ out.sig_type = SignatureType::RSA_2048_SHA256;
+ out.data.rights_id = rights_id;
+ out.data.title_key_common = title_key;
+ return Ticket{out};
+}
+
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
Key128 out{};
@@ -135,6 +229,27 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
}
}
+RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
+ if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek))
+ return {};
+
+ const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek);
+
+ std::vector<u8> extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10);
+ std::array<u8, 0x230> extended_dec{};
+ AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
+ rsa_1.SetIV(extended_iv);
+ rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
+ extended_dec.data(), Op::Decrypt);
+
+ RSAKeyPair<2048> rsa_key{};
+ std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
+ std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
+ std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
+
+ return rsa_key;
+}
+
Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB);
Key128 mac_key{};
@@ -237,7 +352,7 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
return Loader::ResultStatus::Success;
}
-std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
+std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) {
if (!ticket_save.IsOpen())
return {};
@@ -246,14 +361,14 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
return {};
}
- std::vector<TicketRaw> out;
+ std::vector<Ticket> out;
for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
buffer[offset + 3] == 0x0) {
out.emplace_back();
auto& next = out.back();
- std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw));
- offset += next.size();
+ std::memcpy(&next, buffer.data() + offset, sizeof(Ticket));
+ offset += FULL_TICKET_SIZE;
}
}
@@ -305,29 +420,23 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
return offset;
}
-std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
+std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
const RSAKeyPair<2048>& key) {
- u32 cert_authority;
- std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority));
- if (cert_authority == 0)
+ const auto issuer = ticket.GetData().issuer;
+ if (issuer == std::array<u8, 0x40>{})
return {};
- if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) {
- LOG_INFO(Crypto,
- "Attempting to parse ticket with non-standard certificate authority {:08X}.",
- cert_authority);
+ if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
+ LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority.");
}
- Key128 rights_id;
- std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128));
+ Key128 rights_id = ticket.GetData().rights_id;
if (rights_id == Key128{})
return {};
- Key128 key_temp{};
-
- if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) {
- std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size());
- return std::make_pair(rights_id, key_temp);
+ if (!std::any_of(ticket.GetData().title_key_common_pad.begin(),
+ ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) {
+ return std::make_pair(rights_id, ticket.GetData().title_key_common);
}
mbedtls_mpi D; // RSA Private Exponent
@@ -342,7 +451,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size());
mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size());
- mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100);
+ mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100);
mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
@@ -366,6 +475,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
return {};
ASSERT(*offset > 0);
+ Key128 key_temp{};
std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size());
return std::make_pair(rights_id, key_temp);
@@ -450,6 +560,8 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
+ } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
+ eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
} else {
for (const auto& kv : KEYS_VARIABLE_LENGTH) {
if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2))
@@ -572,7 +684,7 @@ void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
<< "# If you are experiencing issues involving keys, it may help to delete this file\n";
}
- file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key));
+ file << fmt::format("\n{} = {}", keyname, Common::HexToString(key));
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title);
}
@@ -583,7 +695,7 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
Key128 rights_id;
std::memcpy(rights_id.data(), &field2, sizeof(u64));
std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64));
- WriteKeyToFile(KeyCategory::Title, Common::HexArrayToString(rights_id), key);
+ WriteKeyToFile(KeyCategory::Title, Common::HexToString(rights_id), key);
}
auto category = KeyCategory::Standard;
@@ -862,20 +974,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
// Titlekeys
data.DecryptProdInfo(GetBISKey(0));
- const auto eticket_extended_kek = data.GetETicketExtendedKek();
+ eticket_extended_kek = data.GetETicketExtendedKek();
+ WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek);
+ PopulateTickets();
+}
- std::vector<u8> extended_iv(0x10);
- std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size());
- std::array<u8, 0x230> extended_dec{};
- AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
- rsa_1.SetIV(extended_iv);
- rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
- extended_dec.data(), Op::Decrypt);
+void KeyManager::PopulateTickets() {
+ const auto rsa_key = GetETicketRSAKey();
- RSAKeyPair<2048> rsa_key{};
- std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
- std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
- std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
+ if (rsa_key == RSAKeyPair<2048>{})
+ return;
+
+ if (!common_tickets.empty() && !personal_tickets.empty())
+ return;
const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/80000000000000e1",
@@ -886,19 +997,41 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
const auto blob2 = GetTicketblob(save2);
auto res = GetTicketblob(save1);
+ const auto idx = res.size();
res.insert(res.end(), blob2.begin(), blob2.end());
- for (const auto& raw : res) {
- const auto pair = ParseTicket(raw, rsa_key);
+ for (std::size_t i = 0; i < res.size(); ++i) {
+ const auto common = i < idx;
+ const auto pair = ParseTicket(res[i], rsa_key);
if (!pair)
continue;
const auto& [rid, key] = *pair;
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
+
+ if (common) {
+ common_tickets[rights_id] = res[i];
+ } else {
+ personal_tickets[rights_id] = res[i];
+ }
+
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
}
}
+void KeyManager::SynthesizeTickets() {
+ for (const auto& key : s128_keys) {
+ if (key.first.type != S128KeyType::Titlekey) {
+ continue;
+ }
+ u128 rights_id{key.first.field1, key.first.field2};
+ Key128 rights_id_2;
+ std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size());
+ const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2);
+ common_tickets.insert_or_assign(rights_id, ticket);
+ }
+}
+
void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
if (key == Key128{})
return;
@@ -997,6 +1130,46 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
DeriveBase();
}
+const std::map<u128, Ticket>& KeyManager::GetCommonTickets() const {
+ return common_tickets;
+}
+
+const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const {
+ return personal_tickets;
+}
+
+bool KeyManager::AddTicketCommon(Ticket raw) {
+ const auto rsa_key = GetETicketRSAKey();
+ if (rsa_key == RSAKeyPair<2048>{})
+ return false;
+
+ const auto pair = ParseTicket(raw, rsa_key);
+ if (!pair)
+ return false;
+ const auto& [rid, key] = *pair;
+ u128 rights_id;
+ std::memcpy(rights_id.data(), rid.data(), rid.size());
+ common_tickets[rights_id] = raw;
+ SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
+ return true;
+}
+
+bool KeyManager::AddTicketPersonalized(Ticket raw) {
+ const auto rsa_key = GetETicketRSAKey();
+ if (rsa_key == RSAKeyPair<2048>{})
+ return false;
+
+ const auto pair = ParseTicket(raw, rsa_key);
+ if (!pair)
+ return false;
+ const auto& [rid, key] = *pair;
+ u128 rights_id;
+ std::memcpy(rights_id.data(), rid.data(), rid.size());
+ common_tickets[rights_id] = raw;
+ SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
+ return true;
+}
+
const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
{"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
{"eticket_rsa_kek_source",
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 22f268c65..7265c4171 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -9,8 +9,10 @@
#include <optional>
#include <string>
+#include <variant>
#include <boost/container/flat_map.hpp>
#include <fmt/format.h>
+#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/crypto/partition_data_manager.h"
#include "core/file_sys/vfs_types.h"
@@ -30,7 +32,79 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
using Key128 = std::array<u8, 0x10>;
using Key256 = std::array<u8, 0x20>;
using SHA256Hash = std::array<u8, 0x20>;
-using TicketRaw = std::array<u8, 0x400>;
+
+enum class SignatureType {
+ RSA_4096_SHA1 = 0x10000,
+ RSA_2048_SHA1 = 0x10001,
+ ECDSA_SHA1 = 0x10002,
+ RSA_4096_SHA256 = 0x10003,
+ RSA_2048_SHA256 = 0x10004,
+ ECDSA_SHA256 = 0x10005,
+};
+
+u64 GetSignatureTypeDataSize(SignatureType type);
+u64 GetSignatureTypePaddingSize(SignatureType type);
+
+enum class TitleKeyType : u8 {
+ Common = 0,
+ Personalized = 1,
+};
+
+struct TicketData {
+ std::array<u8, 0x40> issuer;
+ union {
+ std::array<u8, 0x100> title_key_block;
+
+ struct {
+ Key128 title_key_common;
+ std::array<u8, 0xF0> title_key_common_pad;
+ };
+ };
+
+ INSERT_PADDING_BYTES(0x1);
+ TitleKeyType type;
+ INSERT_PADDING_BYTES(0x3);
+ u8 revision;
+ INSERT_PADDING_BYTES(0xA);
+ u64 ticket_id;
+ u64 device_id;
+ std::array<u8, 0x10> rights_id;
+ u32 account_id;
+ INSERT_PADDING_BYTES(0x14C);
+};
+static_assert(sizeof(TicketData) == 0x2C0, "TicketData has incorrect size.");
+
+struct RSA4096Ticket {
+ SignatureType sig_type;
+ std::array<u8, 0x200> sig_data;
+ INSERT_PADDING_BYTES(0x3C);
+ TicketData data;
+};
+
+struct RSA2048Ticket {
+ SignatureType sig_type;
+ std::array<u8, 0x100> sig_data;
+ INSERT_PADDING_BYTES(0x3C);
+ TicketData data;
+};
+
+struct ECDSATicket {
+ SignatureType sig_type;
+ std::array<u8, 0x3C> sig_data;
+ INSERT_PADDING_BYTES(0x40);
+ TicketData data;
+};
+
+struct Ticket {
+ std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data;
+
+ SignatureType GetSignatureType() const;
+ TicketData& GetData();
+ const TicketData& GetData() const;
+ u64 GetSize() const;
+
+ static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id);
+};
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big.");
@@ -43,6 +117,19 @@ struct RSAKeyPair {
std::array<u8, 4> exponent;
};
+template <size_t bit_size, size_t byte_size>
+bool operator==(const RSAKeyPair<bit_size, byte_size>& lhs,
+ const RSAKeyPair<bit_size, byte_size>& rhs) {
+ return std::tie(lhs.encryption_key, lhs.decryption_key, lhs.modulus, lhs.exponent) ==
+ std::tie(rhs.encryption_key, rhs.decryption_key, rhs.modulus, rhs.exponent);
+}
+
+template <size_t bit_size, size_t byte_size>
+bool operator!=(const RSAKeyPair<bit_size, byte_size>& lhs,
+ const RSAKeyPair<bit_size, byte_size>& rhs) {
+ return !(lhs == rhs);
+}
+
enum class KeyCategory : u8 {
Standard,
Title,
@@ -151,22 +238,35 @@ public:
static bool KeyFileExists(bool title);
- // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save
- // 8*43 and the private file to exist.
+ // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system
+ // save 8*43 and the private file to exist.
void DeriveSDSeedLazy();
bool BaseDeriveNecessary() const;
void DeriveBase();
void DeriveETicket(PartitionDataManager& data);
+ void PopulateTickets();
+ void SynthesizeTickets();
void PopulateFromPartitionData(PartitionDataManager& data);
+ const std::map<u128, Ticket>& GetCommonTickets() const;
+ const std::map<u128, Ticket>& GetPersonalizedTickets() const;
+
+ bool AddTicketCommon(Ticket raw);
+ bool AddTicketPersonalized(Ticket raw);
+
private:
std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
+ // Map from rights ID to ticket
+ std::map<u128, Ticket> common_tickets;
+ std::map<u128, Ticket> personal_tickets;
+
std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
+ std::array<u8, 576> eticket_extended_kek{};
bool dev_mode;
void LoadFromFile(const std::string& filename, bool is_title_keys);
@@ -178,6 +278,8 @@ private:
void DeriveGeneralPurposeKeys(std::size_t crypto_revision);
+ RSAKeyPair<2048> GetETicketRSAKey() const;
+
void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
@@ -195,11 +297,11 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo
std::optional<Key128> DeriveSDSeed();
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
-std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save);
+std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save);
-// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset
-// 0x140-0x144 is zero)
-std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
+// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority
+// (offset 0x140-0x144 is zero)
+std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
const RSAKeyPair<2048>& eticket_extended_key);
} // namespace Core::Crypto
diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp
index ed0775444..01a969be9 100644
--- a/src/core/crypto/partition_data_manager.cpp
+++ b/src/core/crypto/partition_data_manager.cpp
@@ -22,8 +22,10 @@
#include "core/crypto/key_manager.h"
#include "core/crypto/partition_data_manager.h"
#include "core/crypto/xts_encryption_layer.h"
+#include "core/file_sys/kernel_executable.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs_vector.h"
using namespace Common;
@@ -45,36 +47,6 @@ struct Package2Header {
};
static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size.");
-struct INIHeader {
- u32_le magic;
- u32_le size;
- u32_le process_count;
- INSERT_PADDING_BYTES(4);
-};
-static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size.");
-
-struct SectionHeader {
- u32_le offset;
- u32_le size_decompressed;
- u32_le size_compressed;
- u32_le attribute;
-};
-static_assert(sizeof(SectionHeader) == 0x10, "SectionHeader has incorrect size.");
-
-struct KIPHeader {
- u32_le magic;
- std::array<char, 12> name;
- u64_le title_id;
- u32_le category;
- u8 priority;
- u8 core;
- INSERT_PADDING_BYTES(1);
- u8 flags;
- std::array<SectionHeader, 6> sections;
- std::array<u32, 0x20> capabilities;
-};
-static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size.");
-
const std::array<SHA256Hash, 0x10> source_hashes{
"B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source
"7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source
@@ -170,65 +142,6 @@ const std::array<SHA256Hash, 0x20> master_key_hashes{
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F
};
-static std::vector<u8> DecompressBLZ(const std::vector<u8>& in) {
- const auto data_size = in.size() - 0xC;
-
- u32 compressed_size{};
- u32 init_index{};
- u32 additional_size{};
- std::memcpy(&compressed_size, in.data() + data_size, sizeof(u32));
- std::memcpy(&init_index, in.data() + data_size + 0x4, sizeof(u32));
- std::memcpy(&additional_size, in.data() + data_size + 0x8, sizeof(u32));
-
- std::vector<u8> out(in.size() + additional_size);
-
- if (compressed_size == in.size())
- std::memcpy(out.data(), in.data() + in.size() - compressed_size, compressed_size);
- else
- std::memcpy(out.data(), in.data(), compressed_size);
-
- auto index = in.size() - init_index;
- auto out_index = out.size();
-
- while (out_index > 0) {
- --index;
- auto control = in[index];
- for (size_t i = 0; i < 8; ++i) {
- if ((control & 0x80) > 0) {
- ASSERT(index >= 2);
- index -= 2;
- u64 segment_offset = in[index] | in[index + 1] << 8;
- u64 segment_size = ((segment_offset >> 12) & 0xF) + 3;
- segment_offset &= 0xFFF;
- segment_offset += 3;
-
- if (out_index < segment_size)
- segment_size = out_index;
-
- ASSERT(out_index >= segment_size);
-
- out_index -= segment_size;
-
- for (size_t j = 0; j < segment_size; ++j) {
- ASSERT(out_index + j + segment_offset < out.size());
- out[out_index + j] = out[out_index + j + segment_offset];
- }
- } else {
- ASSERT(out_index >= 1);
- --out_index;
- --index;
- out[out_index] = in[index];
- }
-
- control <<= 1;
- if (out_index == 0)
- return out;
- }
- }
-
- return out;
-}
-
static u8 CalculateMaxKeyblobSourceHash() {
for (s8 i = 0x1F; i >= 0; --i) {
if (keyblob_source_hashes[i] != SHA256Hash{})
@@ -478,37 +391,22 @@ void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& packa
cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()});
cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt);
- INIHeader ini;
- std::memcpy(&ini, c.data(), sizeof(INIHeader));
- if (ini.magic != Common::MakeMagic('I', 'N', 'I', '1'))
+ const auto ini_file = std::make_shared<FileSys::VectorVfsFile>(c);
+ const FileSys::INI ini{ini_file};
+ if (ini.GetStatus() != Loader::ResultStatus::Success)
return;
- u64 offset = sizeof(INIHeader);
- for (size_t i = 0; i < ini.process_count; ++i) {
- KIPHeader kip;
- std::memcpy(&kip, c.data() + offset, sizeof(KIPHeader));
- if (kip.magic != Common::MakeMagic('K', 'I', 'P', '1'))
+ for (const auto& kip : ini.GetKIPs()) {
+ if (kip.GetStatus() != Loader::ResultStatus::Success)
return;
- const auto name =
- Common::StringFromFixedZeroTerminatedBuffer(kip.name.data(), kip.name.size());
-
- if (name != "FS" && name != "spl") {
- offset += sizeof(KIPHeader) + kip.sections[0].size_compressed +
- kip.sections[1].size_compressed + kip.sections[2].size_compressed;
+ if (kip.GetName() != "FS" && kip.GetName() != "spl") {
continue;
}
- const u64 initial_offset = sizeof(KIPHeader) + offset;
- const auto text_begin = c.cbegin() + initial_offset;
- const auto text_end = text_begin + kip.sections[0].size_compressed;
- const std::vector<u8> text = DecompressBLZ({text_begin, text_end});
-
- const auto rodata_end = text_end + kip.sections[1].size_compressed;
- const std::vector<u8> rodata = DecompressBLZ({text_end, rodata_end});
-
- const auto data_end = rodata_end + kip.sections[2].size_compressed;
- const std::vector<u8> data = DecompressBLZ({rodata_end, data_end});
+ const auto& text = kip.GetTextSection();
+ const auto& rodata = kip.GetRODataSection();
+ const auto& data = kip.GetDataSection();
std::vector<u8> out;
out.reserve(text.size() + rodata.size() + data.size());
@@ -516,12 +414,9 @@ void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& packa
out.insert(out.end(), rodata.begin(), rodata.end());
out.insert(out.end(), data.begin(), data.end());
- offset += sizeof(KIPHeader) + kip.sections[0].size_compressed +
- kip.sections[1].size_compressed + kip.sections[2].size_compressed;
-
- if (name == "FS")
+ if (kip.GetName() == "FS")
package2_fs[static_cast<size_t>(type)] = std::move(out);
- else if (name == "spl")
+ else if (kip.GetName() == "spl")
package2_spl[static_cast<size_t>(type)] = std::move(out);
}
}
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 2c145bd09..626ed0042 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -18,11 +18,16 @@
namespace FileSys {
-constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", "logo"};
+constexpr std::array partition_names{
+ "update",
+ "normal",
+ "secure",
+ "logo",
+};
XCI::XCI(VirtualFile file_)
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
- partitions(0x4) {
+ partitions(partition_names.size()) {
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
status = Loader::ResultStatus::ErrorBadXCIHeader;
return;
@@ -43,23 +48,24 @@ XCI::XCI(VirtualFile file_)
for (XCIPartition partition :
{XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
- auto raw = main_hfs.GetFile(partition_names[static_cast<std::size_t>(partition)]);
- if (raw != nullptr)
- partitions[static_cast<std::size_t>(partition)] =
- std::make_shared<PartitionFilesystem>(raw);
+ const auto partition_idx = static_cast<std::size_t>(partition);
+ auto raw = main_hfs.GetFile(partition_names[partition_idx]);
+
+ if (raw != nullptr) {
+ partitions[partition_idx] = std::make_shared<PartitionFilesystem>(std::move(raw));
+ }
}
secure_partition = std::make_shared<NSP>(
main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)]));
- const auto secure_ncas = secure_partition->GetNCAsCollapsed();
- std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas));
-
+ ncas = secure_partition->GetNCAsCollapsed();
program =
secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program);
program_nca_status = secure_partition->GetProgramStatus(secure_partition->GetProgramTitleID());
- if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA)
+ if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA) {
program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
+ }
auto result = AddNCAFromPartition(XCIPartition::Update);
if (result != Loader::ResultStatus::Success) {
@@ -147,8 +153,9 @@ std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
auto nca = GetNCAByType(type);
- if (nca != nullptr)
+ if (nca != nullptr) {
return nca->GetBaseFile();
+ }
return nullptr;
}
@@ -169,17 +176,22 @@ VirtualDir XCI::GetParentDirectory() const {
}
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
- if (partitions[static_cast<std::size_t>(part)] == nullptr) {
+ const auto partition_index = static_cast<std::size_t>(part);
+ const auto& partition = partitions[partition_index];
+
+ if (partition == nullptr) {
return Loader::ResultStatus::ErrorXCIMissingPartition;
}
- for (const VirtualFile& file : partitions[static_cast<std::size_t>(part)]->GetFiles()) {
- if (file->GetExtension() != "nca")
+ for (const VirtualFile& file : partition->GetFiles()) {
+ if (file->GetExtension() != "nca") {
continue;
+ }
+
auto nca = std::make_shared<NCA>(file, nullptr, 0, keys);
- // TODO(DarkLordZach): Add proper Rev1+ Support
- if (nca->IsUpdate())
+ if (nca->IsUpdate()) {
continue;
+ }
if (nca->GetType() == NCAContentType::Program) {
program_nca_status = nca->GetStatus();
}
@@ -188,7 +200,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
} else {
const u16 error_id = static_cast<u16>(nca->GetStatus());
LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})",
- partition_names[static_cast<std::size_t>(part)], nca->GetName(), error_id,
+ partition_names[partition_index], nca->GetName(), error_id,
nca->GetStatus());
}
}
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 5aa3b600b..ce5c69b41 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -452,13 +452,13 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s
switch (s_header.raw.header.crypto_type) {
case NCASectionCryptoType::NONE:
- LOG_DEBUG(Crypto, "called with mode=NONE");
+ LOG_TRACE(Crypto, "called with mode=NONE");
return in;
case NCASectionCryptoType::CTR:
// During normal BKTR decryption, this entire function is skipped. This is for the metadata,
// which uses the same CTR as usual.
case NCASectionCryptoType::BKTR:
- LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
+ LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
{
std::optional<Core::Crypto::Key128> key = {};
if (has_rights_id) {
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index 04da30825..f155a1341 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -87,6 +87,10 @@ u64 NACP::GetDefaultJournalSaveSize() const {
return raw.user_account_save_data_journal_size;
}
+bool NACP::GetUserAccountSwitchLock() const {
+ return raw.user_account_switch_lock != 0;
+}
+
u32 NACP::GetSupportedLanguages() const {
return raw.supported_languages;
}
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 1be34ed55..2d8c251ac 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -30,7 +30,8 @@ struct RawNACP {
std::array<LanguageEntry, 16> language_entries;
std::array<u8, 0x25> isbn;
u8 startup_user_account;
- INSERT_PADDING_BYTES(2);
+ u8 user_account_switch_lock;
+ u8 addon_content_registration_type;
u32_le application_attribute;
u32_le supported_languages;
u32_le parental_control;
@@ -111,6 +112,7 @@ public:
u64 GetDefaultJournalSaveSize() const;
u32 GetSupportedLanguages() const;
std::vector<u8> GetRawBytes() const;
+ bool GetUserAccountSwitchLock() const;
private:
RawNACP raw{};
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index 485c4913a..a08a70efd 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -287,7 +287,6 @@ void IPSwitchCompiler::Parse() {
} else {
// hex replacement
const auto value = patch_line.substr(9);
- replace.reserve(value.size() / 2);
replace = Common::HexStringToVector(value, is_little_endian);
}
@@ -295,7 +294,7 @@ void IPSwitchCompiler::Parse() {
LOG_INFO(Loader,
"[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} "
"with byte string '{}'",
- patch_text->GetName(), offset, Common::HexVectorToString(replace));
+ patch_text->GetName(), offset, Common::HexToString(replace));
}
patch.records.insert_or_assign(offset, std::move(replace));
diff --git a/src/core/file_sys/kernel_executable.cpp b/src/core/file_sys/kernel_executable.cpp
new file mode 100644
index 000000000..371300684
--- /dev/null
+++ b/src/core/file_sys/kernel_executable.cpp
@@ -0,0 +1,228 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/string_util.h"
+#include "core/file_sys/kernel_executable.h"
+#include "core/file_sys/vfs_offset.h"
+
+namespace FileSys {
+
+constexpr u32 INI_MAX_KIPS = 0x50;
+
+namespace {
+bool DecompressBLZ(std::vector<u8>& data) {
+ if (data.size() < 0xC)
+ return {};
+
+ const auto data_size = data.size() - 0xC;
+
+ u32 compressed_size{};
+ u32 init_index{};
+ u32 additional_size{};
+ std::memcpy(&compressed_size, data.data() + data_size, sizeof(u32));
+ std::memcpy(&init_index, data.data() + data_size + 0x4, sizeof(u32));
+ std::memcpy(&additional_size, data.data() + data_size + 0x8, sizeof(u32));
+
+ const auto start_offset = data.size() - compressed_size;
+ data.resize(compressed_size + additional_size + start_offset);
+
+ std::size_t index = compressed_size - init_index;
+ std::size_t out_index = compressed_size + additional_size;
+
+ while (out_index > 0) {
+ --index;
+ auto control = data[index + start_offset];
+ for (size_t i = 0; i < 8; ++i) {
+ if (((control << i) & 0x80) > 0) {
+ if (index < 2) {
+ return false;
+ }
+ index -= 2;
+ std::size_t segment_offset =
+ data[index + start_offset] | data[index + start_offset + 1] << 8;
+ std::size_t segment_size = ((segment_offset >> 12) & 0xF) + 3;
+ segment_offset &= 0xFFF;
+ segment_offset += 3;
+
+ if (out_index < segment_size)
+ segment_size = out_index;
+
+ if (out_index < segment_size) {
+ return false;
+ }
+
+ out_index -= segment_size;
+
+ for (size_t j = 0; j < segment_size; ++j) {
+ if (out_index + j + segment_offset + start_offset >= data.size()) {
+ return false;
+ }
+ data[out_index + j + start_offset] =
+ data[out_index + j + segment_offset + start_offset];
+ }
+ } else {
+ if (out_index < 1) {
+ return false;
+ }
+ --out_index;
+ --index;
+ data[out_index + start_offset] = data[index + start_offset];
+ }
+
+ if (out_index == 0)
+ break;
+ }
+ }
+
+ return true;
+}
+} // Anonymous namespace
+
+KIP::KIP(const VirtualFile& file) : status(Loader::ResultStatus::Success) {
+ if (file == nullptr) {
+ status = Loader::ResultStatus::ErrorNullFile;
+ return;
+ }
+
+ if (file->GetSize() < sizeof(KIPHeader) || file->ReadObject(&header) != sizeof(KIPHeader)) {
+ status = Loader::ResultStatus::ErrorBadKIPHeader;
+ return;
+ }
+
+ if (header.magic != Common::MakeMagic('K', 'I', 'P', '1')) {
+ status = Loader::ResultStatus::ErrorBadKIPHeader;
+ return;
+ }
+
+ u64 offset = sizeof(KIPHeader);
+ for (std::size_t i = 0; i < header.sections.size(); ++i) {
+ auto compressed = file->ReadBytes(header.sections[i].compressed_size, offset);
+ offset += header.sections[i].compressed_size;
+
+ if (header.sections[i].compressed_size == 0 && header.sections[i].decompressed_size != 0) {
+ decompressed_sections[i] = std::vector<u8>(header.sections[i].decompressed_size);
+ } else if (header.sections[i].compressed_size == header.sections[i].decompressed_size) {
+ decompressed_sections[i] = std::move(compressed);
+ } else {
+ decompressed_sections[i] = compressed;
+ if (!DecompressBLZ(decompressed_sections[i])) {
+ status = Loader::ResultStatus::ErrorBLZDecompressionFailed;
+ return;
+ }
+ }
+ }
+}
+
+Loader::ResultStatus KIP::GetStatus() const {
+ return status;
+}
+
+std::string KIP::GetName() const {
+ return Common::StringFromFixedZeroTerminatedBuffer(header.name.data(), header.name.size());
+}
+
+u64 KIP::GetTitleID() const {
+ return header.title_id;
+}
+
+std::vector<u8> KIP::GetSectionDecompressed(u8 index) const {
+ return decompressed_sections[index];
+}
+
+bool KIP::Is64Bit() const {
+ return (header.flags & 0x8) != 0;
+}
+
+bool KIP::Is39BitAddressSpace() const {
+ return (header.flags & 0x10) != 0;
+}
+
+bool KIP::IsService() const {
+ return (header.flags & 0x20) != 0;
+}
+
+std::vector<u32> KIP::GetKernelCapabilities() const {
+ return std::vector<u32>(header.capabilities.begin(), header.capabilities.end());
+}
+
+s32 KIP::GetMainThreadPriority() const {
+ return header.main_thread_priority;
+}
+
+u32 KIP::GetMainThreadStackSize() const {
+ return header.sections[1].attribute;
+}
+
+u32 KIP::GetMainThreadCpuCore() const {
+ return header.default_core;
+}
+
+const std::vector<u8>& KIP::GetTextSection() const {
+ return decompressed_sections[0];
+}
+
+const std::vector<u8>& KIP::GetRODataSection() const {
+ return decompressed_sections[1];
+}
+
+const std::vector<u8>& KIP::GetDataSection() const {
+ return decompressed_sections[2];
+}
+
+u32 KIP::GetTextOffset() const {
+ return header.sections[0].offset;
+}
+
+u32 KIP::GetRODataOffset() const {
+ return header.sections[1].offset;
+}
+
+u32 KIP::GetDataOffset() const {
+ return header.sections[2].offset;
+}
+
+u32 KIP::GetBSSSize() const {
+ return header.sections[3].decompressed_size;
+}
+
+u32 KIP::GetBSSOffset() const {
+ return header.sections[3].offset;
+}
+
+INI::INI(const VirtualFile& file) : status(Loader::ResultStatus::Success) {
+ if (file->GetSize() < sizeof(INIHeader) || file->ReadObject(&header) != sizeof(INIHeader)) {
+ status = Loader::ResultStatus::ErrorBadINIHeader;
+ return;
+ }
+
+ if (header.magic != Common::MakeMagic('I', 'N', 'I', '1')) {
+ status = Loader::ResultStatus::ErrorBadINIHeader;
+ return;
+ }
+
+ if (header.kip_count > INI_MAX_KIPS) {
+ status = Loader::ResultStatus::ErrorINITooManyKIPs;
+ return;
+ }
+
+ u64 offset = sizeof(INIHeader);
+ for (std::size_t i = 0; i < header.kip_count; ++i) {
+ const auto kip_file =
+ std::make_shared<OffsetVfsFile>(file, file->GetSize() - offset, offset);
+ KIP kip(kip_file);
+ if (kip.GetStatus() == Loader::ResultStatus::Success) {
+ kips.push_back(std::move(kip));
+ }
+ }
+}
+
+Loader::ResultStatus INI::GetStatus() const {
+ return status;
+}
+
+const std::vector<KIP>& INI::GetKIPs() const {
+ return kips;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h
new file mode 100644
index 000000000..324a57384
--- /dev/null
+++ b/src/core/file_sys/kernel_executable.h
@@ -0,0 +1,99 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/swap.h"
+#include "core/file_sys/vfs_types.h"
+#include "core/loader/loader.h"
+
+namespace FileSys {
+
+struct KIPSectionHeader {
+ u32_le offset;
+ u32_le decompressed_size;
+ u32_le compressed_size;
+ u32_le attribute;
+};
+static_assert(sizeof(KIPSectionHeader) == 0x10, "KIPSectionHeader has incorrect size.");
+
+struct KIPHeader {
+ u32_le magic;
+ std::array<char, 0xC> name;
+ u64_le title_id;
+ u32_le process_category;
+ u8 main_thread_priority;
+ u8 default_core;
+ INSERT_PADDING_BYTES(1);
+ u8 flags;
+ std::array<KIPSectionHeader, 6> sections;
+ std::array<u32, 0x20> capabilities;
+};
+static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size.");
+
+struct INIHeader {
+ u32_le magic;
+ u32_le size;
+ u32_le kip_count;
+ INSERT_PADDING_BYTES(0x4);
+};
+static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size.");
+
+// Kernel Internal Process
+class KIP {
+public:
+ explicit KIP(const VirtualFile& file);
+
+ Loader::ResultStatus GetStatus() const;
+
+ std::string GetName() const;
+ u64 GetTitleID() const;
+ std::vector<u8> GetSectionDecompressed(u8 index) const;
+
+ // Executable Flags
+ bool Is64Bit() const;
+ bool Is39BitAddressSpace() const;
+ bool IsService() const;
+
+ std::vector<u32> GetKernelCapabilities() const;
+
+ s32 GetMainThreadPriority() const;
+ u32 GetMainThreadStackSize() const;
+ u32 GetMainThreadCpuCore() const;
+
+ const std::vector<u8>& GetTextSection() const;
+ const std::vector<u8>& GetRODataSection() const;
+ const std::vector<u8>& GetDataSection() const;
+
+ u32 GetTextOffset() const;
+ u32 GetRODataOffset() const;
+ u32 GetDataOffset() const;
+
+ u32 GetBSSSize() const;
+ u32 GetBSSOffset() const;
+
+private:
+ Loader::ResultStatus status;
+
+ KIPHeader header{};
+ std::array<std::vector<u8>, 6> decompressed_sections;
+};
+
+class INI {
+public:
+ explicit INI(const VirtualFile& file);
+
+ Loader::ResultStatus GetStatus() const;
+
+ const std::vector<KIP>& GetKIPs() const;
+
+private:
+ Loader::ResultStatus status;
+
+ INIHeader header{};
+ std::vector<KIP> kips;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h
index 50bf38471..1f82fff0a 100644
--- a/src/core/file_sys/nca_metadata.h
+++ b/src/core/file_sys/nca_metadata.h
@@ -4,6 +4,7 @@
#pragma once
+#include <array>
#include <memory>
#include <vector>
#include "common/common_funcs.h"
@@ -34,9 +35,9 @@ enum class ContentRecordType : u8 {
Program = 1,
Data = 2,
Control = 3,
- Manual = 4,
- Legal = 5,
- Patch = 6,
+ HtmlDocument = 4,
+ LegalInformation = 5,
+ DeltaFragment = 6,
};
struct ContentRecord {
@@ -69,11 +70,15 @@ struct CNMTHeader {
u64_le title_id;
u32_le title_version;
TitleType type;
- INSERT_PADDING_BYTES(1);
+ u8 reserved;
u16_le table_offset;
u16_le number_content_entries;
u16_le number_meta_entries;
- INSERT_PADDING_BYTES(12);
+ u8 attributes;
+ std::array<u8, 2> reserved2;
+ u8 is_committed;
+ u32_le required_download_system_version;
+ std::array<u8, 4> reserved3;
};
static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size.");
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 78dbadee3..a8f80e2c6 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -142,7 +142,7 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
if (!compiler.IsValid())
continue;
- auto this_build_id = Common::HexArrayToString(compiler.GetBuildID());
+ auto this_build_id = Common::HexToString(compiler.GetBuildID());
this_build_id =
this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1);
@@ -168,7 +168,7 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
return nso;
}
- const auto build_id_raw = Common::HexArrayToString(header.build_id);
+ const auto build_id_raw = Common::HexToString(header.build_id);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
if (Settings::values.dump_nso) {
@@ -219,7 +219,7 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
}
bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
- const auto build_id_raw = Common::HexArrayToString(build_id_);
+ const auto build_id_raw = Common::HexToString(build_id_);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
@@ -235,7 +235,7 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id,
const std::array<u8, 0x20>& build_id_,
const VirtualDir& base_path, bool upper) {
- const auto build_id_raw = Common::HexArrayToString(build_id_, upper);
+ const auto build_id_raw = Common::HexToString(build_id_, upper);
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
@@ -493,6 +493,16 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
return out;
}
+std::optional<u32> PatchManager::GetGameVersion() const {
+ const auto& installed = Core::System::GetInstance().GetContentProvider();
+ const auto update_tid = GetUpdateTitleID(title_id);
+ if (installed.HasEntry(update_tid, ContentRecordType::Program)) {
+ return installed.GetEntryVersion(update_tid);
+ }
+
+ return installed.GetEntryVersion(title_id);
+}
+
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
const auto& installed = Core::System::GetInstance().GetContentProvider();
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 769f8c6f0..a363c6577 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -66,8 +66,13 @@ public:
std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
VirtualFile update_raw = nullptr) const;
- // Given title_id of the program, attempts to get the control data of the update and parse it,
- // falling back to the base control data.
+ // If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
+ // it will fallback to the Meta-type NCA of the base game. If that fails, the result will be
+ // std::nullopt
+ std::optional<u32> GetGameVersion() const;
+
+ // Given title_id of the program, attempts to get the control data of the update and parse
+ // it, falling back to the base control data.
std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const;
// Version of GetControlMetadata that takes an arbitrary NCA
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index d863253f8..7310b3602 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -51,6 +51,21 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
return Loader::ResultStatus::Success;
}
+void ProgramMetadata::LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space,
+ u8 main_thread_prio, u8 main_thread_core,
+ u32 main_thread_stack_size, u64 title_id,
+ u64 filesystem_permissions,
+ KernelCapabilityDescriptors capabilities) {
+ npdm_header.has_64_bit_instructions.Assign(is_64_bit);
+ npdm_header.address_space_type.Assign(address_space);
+ npdm_header.main_thread_priority = main_thread_prio;
+ npdm_header.main_thread_cpu = main_thread_core;
+ npdm_header.main_stack_size = main_thread_stack_size;
+ aci_header.title_id = title_id;
+ aci_file_access.permissions = filesystem_permissions;
+ aci_kernel_capabilities = std ::move(capabilities);
+}
+
bool ProgramMetadata::Is64BitProgram() const {
return npdm_header.has_64_bit_instructions;
}
@@ -79,6 +94,10 @@ u64 ProgramMetadata::GetFilesystemPermissions() const {
return aci_file_access.permissions;
}
+u32 ProgramMetadata::GetSystemResourceSize() const {
+ return npdm_header.system_resource_size;
+}
+
const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCapabilities() const {
return aci_kernel_capabilities;
}
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 7de5b9cf9..88ec97d85 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -46,6 +46,11 @@ public:
Loader::ResultStatus Load(VirtualFile file);
+ // Load from parameters instead of NPDM file, used for KIP
+ void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, u8 main_thread_prio,
+ u8 main_thread_core, u32 main_thread_stack_size, u64 title_id,
+ u64 filesystem_permissions, KernelCapabilityDescriptors capabilities);
+
bool Is64BitProgram() const;
ProgramAddressSpaceType GetAddressSpaceType() const;
u8 GetMainThreadPriority() const;
@@ -53,6 +58,7 @@ public:
u32 GetMainThreadStackSize() const;
u64 GetTitleID() const;
u64 GetFilesystemPermissions() const;
+ u32 GetSystemResourceSize() const;
const KernelCapabilityDescriptors& GetKernelCapabilities() const;
void Print() const;
@@ -71,7 +77,8 @@ private:
u8 reserved_3;
u8 main_thread_priority;
u8 main_thread_cpu;
- std::array<u8, 8> reserved_4;
+ std::array<u8, 4> reserved_4;
+ u32_le system_resource_size;
u32_le process_category;
u32_le main_stack_size;
std::array<u8, 0x10> application_name;
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 3946ff871..3725b10f7 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -53,13 +53,14 @@ static bool FollowsNcaIdFormat(std::string_view name) {
static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,
bool within_two_digit) {
- if (!within_two_digit)
- return fmt::format("/{}.nca", Common::HexArrayToString(nca_id, second_hex_upper));
+ if (!within_two_digit) {
+ return fmt::format("/{}.nca", Common::HexToString(nca_id, second_hex_upper));
+ }
Core::Crypto::SHA256Hash hash{};
mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
return fmt::format("/000000{:02X}/{}.nca", hash[0],
- Common::HexArrayToString(nca_id, second_hex_upper));
+ Common::HexToString(nca_id, second_hex_upper));
}
static std::string GetCNMTName(TitleType type, u64 title_id) {
@@ -98,7 +99,7 @@ ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
return ContentRecordType::Data;
case NCAContentType::Manual:
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
- return ContentRecordType::Manual;
+ return ContentRecordType::HtmlDocument;
default:
UNREACHABLE_MSG("Invalid NCAContentType={:02X}", static_cast<u8>(type));
}
@@ -376,10 +377,11 @@ std::vector<ContentProviderEntry> RegisteredCache::ListEntriesFilter(
}
static std::shared_ptr<NCA> GetNCAFromNSPForID(const NSP& nsp, const NcaID& id) {
- const auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false)));
- if (file == nullptr)
+ auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexToString(id, false)));
+ if (file == nullptr) {
return nullptr;
- return std::make_shared<NCA>(file);
+ }
+ return std::make_shared<NCA>(std::move(file));
}
InstallResult RegisteredCache::InstallEntry(const XCI& xci, bool overwrite_if_exists,
@@ -395,8 +397,8 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
});
if (meta_iter == ncas.end()) {
- LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and "
- "is therefore malformed. Double check your encryption keys.");
+ LOG_ERROR(Loader, "The file you are attempting to install does not have a metadata NCA and "
+ "is therefore malformed. Check your encryption keys.");
return InstallResult::ErrorMetaFailed;
}
@@ -413,6 +415,9 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
const auto cnmt_file = section0->GetFiles()[0];
const CNMT cnmt(cnmt_file);
for (const auto& record : cnmt.GetContentRecords()) {
+ // Ignore DeltaFragments, they are not useful to us
+ if (record.type == ContentRecordType::DeltaFragment)
+ continue;
const auto nca = GetNCAFromNSPForID(nsp, record.nca_id);
if (nca == nullptr)
return InstallResult::ErrorCopyFailed;
@@ -643,6 +648,20 @@ ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnion
return out;
}
+std::optional<ContentProviderUnionSlot> ContentProviderUnion::GetSlotForEntry(
+ u64 title_id, ContentRecordType type) const {
+ const auto iter =
+ std::find_if(providers.begin(), providers.end(), [title_id, type](const auto& provider) {
+ return provider.second != nullptr && provider.second->HasEntry(title_id, type);
+ });
+
+ if (iter == providers.end()) {
+ return std::nullopt;
+ }
+
+ return iter->first;
+}
+
ManualContentProvider::~ManualContentProvider() = default;
void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type,
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index ec9052653..4398d63e1 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -199,6 +199,9 @@ public:
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
std::optional<u64> title_id = {}) const;
+ std::optional<ContentProviderUnionSlot> GetSlotForEntry(u64 title_id,
+ ContentRecordType type) const;
+
private:
std::map<ContentProviderUnionSlot, ContentProvider*> providers;
};
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index 691ca90c8..730221fd6 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -261,27 +261,33 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
const auto section0 = nca->GetSubdirectories()[0];
for (const auto& inner_file : section0->GetFiles()) {
- if (inner_file->GetExtension() != "cnmt")
+ if (inner_file->GetExtension() != "cnmt") {
continue;
+ }
const CNMT cnmt(inner_file);
auto& ncas_title = ncas[cnmt.GetTitleID()];
ncas_title[{cnmt.GetType(), ContentRecordType::Meta}] = nca;
for (const auto& rec : cnmt.GetContentRecords()) {
- const auto id_string = Common::HexArrayToString(rec.nca_id, false);
- const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
+ const auto id_string = Common::HexToString(rec.nca_id, false);
+ auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
+
if (next_file == nullptr) {
- LOG_WARNING(Service_FS,
- "NCA with ID {}.nca is listed in content metadata, but cannot "
- "be found in PFS. NSP appears to be corrupted.",
- id_string);
+ if (rec.type != ContentRecordType::DeltaFragment) {
+ LOG_WARNING(Service_FS,
+ "NCA with ID {}.nca is listed in content metadata, but cannot "
+ "be found in PFS. NSP appears to be corrupted.",
+ id_string);
+ }
+
continue;
}
- auto next_nca = std::make_shared<NCA>(next_file, nullptr, 0, keys);
- if (next_nca->GetType() == NCAContentType::Program)
+ auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0, keys);
+ if (next_nca->GetType() == NCAContentType::Program) {
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
+ }
if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
(next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
(cnmt.GetTitleID() & 0x800) != 0)) {
diff --git a/src/core/file_sys/system_archive/mii_model.cpp b/src/core/file_sys/system_archive/mii_model.cpp
new file mode 100644
index 000000000..6a9add87c
--- /dev/null
+++ b/src/core/file_sys/system_archive/mii_model.cpp
@@ -0,0 +1,46 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/file_sys/system_archive/mii_model.h"
+#include "core/file_sys/vfs_vector.h"
+
+namespace FileSys::SystemArchive {
+
+namespace MiiModelData {
+
+constexpr std::array<u8, 0x10> NFTR_STANDARD{'N', 'F', 'T', 'R', 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+constexpr std::array<u8, 0x10> NFSR_STANDARD{'N', 'F', 'S', 'R', 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+constexpr auto TEXTURE_LOW_LINEAR = NFTR_STANDARD;
+constexpr auto TEXTURE_LOW_SRGB = NFTR_STANDARD;
+constexpr auto TEXTURE_MID_LINEAR = NFTR_STANDARD;
+constexpr auto TEXTURE_MID_SRGB = NFTR_STANDARD;
+constexpr auto SHAPE_HIGH = NFSR_STANDARD;
+constexpr auto SHAPE_MID = NFSR_STANDARD;
+
+} // namespace MiiModelData
+
+VirtualDir MiiModel() {
+ auto out = std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{},
+ std::vector<VirtualDir>{}, "data");
+
+ out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_LINEAR.size()>>(
+ MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat"));
+ out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_SRGB.size()>>(
+ MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat"));
+ out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_LINEAR.size()>>(
+ MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat"));
+ out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_SRGB.size()>>(
+ MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat"));
+ out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_HIGH.size()>>(
+ MiiModelData::SHAPE_HIGH, "ShapeHigh.dat"));
+ out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_MID.size()>>(
+ MiiModelData::SHAPE_MID, "ShapeMid.dat"));
+
+ return std::move(out);
+}
+
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/mii_model.h b/src/core/file_sys/system_archive/mii_model.h
new file mode 100644
index 000000000..6c2d9398b
--- /dev/null
+++ b/src/core/file_sys/system_archive/mii_model.h
@@ -0,0 +1,13 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/file_sys/vfs_types.h"
+
+namespace FileSys::SystemArchive {
+
+VirtualDir MiiModel();
+
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp
index c9722ed77..6d8445383 100644
--- a/src/core/file_sys/system_archive/system_archive.cpp
+++ b/src/core/file_sys/system_archive/system_archive.cpp
@@ -4,6 +4,7 @@
#include "common/logging/log.h"
#include "core/file_sys/romfs.h"
+#include "core/file_sys/system_archive/mii_model.h"
#include "core/file_sys/system_archive/ng_word.h"
#include "core/file_sys/system_archive/system_archive.h"
#include "core/file_sys/system_archive/system_version.h"
@@ -24,7 +25,7 @@ struct SystemArchiveDescriptor {
constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{
{0x0100000000000800, "CertStore", nullptr},
{0x0100000000000801, "ErrorMessage", nullptr},
- {0x0100000000000802, "MiiModel", nullptr},
+ {0x0100000000000802, "MiiModel", &MiiModel},
{0x0100000000000803, "BrowserDll", nullptr},
{0x0100000000000804, "Help", nullptr},
{0x0100000000000805, "SharedFont", nullptr},
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp
index eec51c64e..4bc5cb2ee 100644
--- a/src/core/file_sys/xts_archive.cpp
+++ b/src/core/file_sys/xts_archive.cpp
@@ -66,7 +66,7 @@ NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id)
Core::Crypto::SHA256Hash hash{};
mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0],
- Common::HexArrayToString(nca_id, false)));
+ Common::HexToString(nca_id, false)));
}
NAX::~NAX() = default;
diff --git a/src/core/frontend/applets/general_frontend.cpp b/src/core/frontend/applets/general_frontend.cpp
index b974f2289..c30b36de7 100644
--- a/src/core/frontend/applets/general_frontend.cpp
+++ b/src/core/frontend/applets/general_frontend.cpp
@@ -7,9 +7,38 @@
namespace Core::Frontend {
+ParentalControlsApplet::~ParentalControlsApplet() = default;
+
+DefaultParentalControlsApplet::~DefaultParentalControlsApplet() = default;
+
+void DefaultParentalControlsApplet::VerifyPIN(std::function<void(bool)> finished,
+ bool suspend_future_verification_temporarily) {
+ LOG_INFO(Service_AM,
+ "Application requested frontend to verify PIN (normal), "
+ "suspend_future_verification_temporarily={}, verifying as correct.",
+ suspend_future_verification_temporarily);
+ finished(true);
+}
+
+void DefaultParentalControlsApplet::VerifyPINForSettings(std::function<void(bool)> finished) {
+ LOG_INFO(Service_AM,
+ "Application requested frontend to verify PIN (settings), verifying as correct.");
+ finished(true);
+}
+
+void DefaultParentalControlsApplet::RegisterPIN(std::function<void()> finished) {
+ LOG_INFO(Service_AM, "Application requested frontend to register new PIN");
+ finished();
+}
+
+void DefaultParentalControlsApplet::ChangePIN(std::function<void()> finished) {
+ LOG_INFO(Service_AM, "Application requested frontend to change PIN to new value");
+ finished();
+}
+
PhotoViewerApplet::~PhotoViewerApplet() = default;
-DefaultPhotoViewerApplet::~DefaultPhotoViewerApplet() {}
+DefaultPhotoViewerApplet::~DefaultPhotoViewerApplet() = default;
void DefaultPhotoViewerApplet::ShowPhotosForApplication(u64 title_id,
std::function<void()> finished) const {
@@ -24,4 +53,72 @@ void DefaultPhotoViewerApplet::ShowAllPhotos(std::function<void()> finished) con
finished();
}
+ECommerceApplet::~ECommerceApplet() = default;
+
+DefaultECommerceApplet::~DefaultECommerceApplet() = default;
+
+void DefaultECommerceApplet::ShowApplicationInformation(
+ std::function<void()> finished, u64 title_id, std::optional<u128> user_id,
+ std::optional<bool> full_display, std::optional<std::string> extra_parameter) {
+ const auto value = user_id.value_or(u128{});
+ LOG_INFO(Service_AM,
+ "Application requested frontend show application information for EShop, "
+ "title_id={:016X}, user_id={:016X}{:016X}, full_display={}, extra_parameter={}",
+ title_id, value[1], value[0],
+ full_display.has_value() ? fmt::format("{}", *full_display) : "null",
+ extra_parameter.value_or("null"));
+ finished();
+}
+
+void DefaultECommerceApplet::ShowAddOnContentList(std::function<void()> finished, u64 title_id,
+ std::optional<u128> user_id,
+ std::optional<bool> full_display) {
+ const auto value = user_id.value_or(u128{});
+ LOG_INFO(Service_AM,
+ "Application requested frontend show add on content list for EShop, "
+ "title_id={:016X}, user_id={:016X}{:016X}, full_display={}",
+ title_id, value[1], value[0],
+ full_display.has_value() ? fmt::format("{}", *full_display) : "null");
+ finished();
+}
+
+void DefaultECommerceApplet::ShowSubscriptionList(std::function<void()> finished, u64 title_id,
+ std::optional<u128> user_id) {
+ const auto value = user_id.value_or(u128{});
+ LOG_INFO(Service_AM,
+ "Application requested frontend show subscription list for EShop, title_id={:016X}, "
+ "user_id={:016X}{:016X}",
+ title_id, value[1], value[0]);
+ finished();
+}
+
+void DefaultECommerceApplet::ShowConsumableItemList(std::function<void()> finished, u64 title_id,
+ std::optional<u128> user_id) {
+ const auto value = user_id.value_or(u128{});
+ LOG_INFO(
+ Service_AM,
+ "Application requested frontend show consumable item list for EShop, title_id={:016X}, "
+ "user_id={:016X}{:016X}",
+ title_id, value[1], value[0]);
+ finished();
+}
+
+void DefaultECommerceApplet::ShowShopHome(std::function<void()> finished, u128 user_id,
+ bool full_display) {
+ LOG_INFO(Service_AM,
+ "Application requested frontend show home menu for EShop, user_id={:016X}{:016X}, "
+ "full_display={}",
+ user_id[1], user_id[0], full_display);
+ finished();
+}
+
+void DefaultECommerceApplet::ShowSettings(std::function<void()> finished, u128 user_id,
+ bool full_display) {
+ LOG_INFO(Service_AM,
+ "Application requested frontend show settings menu for EShop, user_id={:016X}{:016X}, "
+ "full_display={}",
+ user_id[1], user_id[0], full_display);
+ finished();
+}
+
} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/general_frontend.h b/src/core/frontend/applets/general_frontend.h
index d4506c999..4b63f828e 100644
--- a/src/core/frontend/applets/general_frontend.h
+++ b/src/core/frontend/applets/general_frontend.h
@@ -5,10 +5,43 @@
#pragma once
#include <functional>
+#include <optional>
#include "common/common_types.h"
namespace Core::Frontend {
+class ParentalControlsApplet {
+public:
+ virtual ~ParentalControlsApplet();
+
+ // Prompts the user to enter a PIN and calls the callback with whether or not it matches the
+ // correct PIN. If the bool is passed, and the PIN was recently entered correctly, the frontend
+ // should not prompt and simply return true.
+ virtual void VerifyPIN(std::function<void(bool)> finished,
+ bool suspend_future_verification_temporarily) = 0;
+
+ // Prompts the user to enter a PIN and calls the callback for correctness. Frontends can
+ // optionally alert the user that this is to change parental controls settings.
+ virtual void VerifyPINForSettings(std::function<void(bool)> finished) = 0;
+
+ // Prompts the user to create a new PIN for pctl and stores it with the service.
+ virtual void RegisterPIN(std::function<void()> finished) = 0;
+
+ // Prompts the user to verify the current PIN and then store a new one into pctl.
+ virtual void ChangePIN(std::function<void()> finished) = 0;
+};
+
+class DefaultParentalControlsApplet final : public ParentalControlsApplet {
+public:
+ ~DefaultParentalControlsApplet() override;
+
+ void VerifyPIN(std::function<void(bool)> finished,
+ bool suspend_future_verification_temporarily) override;
+ void VerifyPINForSettings(std::function<void(bool)> finished) override;
+ void RegisterPIN(std::function<void()> finished) override;
+ void ChangePIN(std::function<void()> finished) override;
+};
+
class PhotoViewerApplet {
public:
virtual ~PhotoViewerApplet();
@@ -25,4 +58,55 @@ public:
void ShowAllPhotos(std::function<void()> finished) const override;
};
+class ECommerceApplet {
+public:
+ virtual ~ECommerceApplet();
+
+ // Shows a page with application icons, description, name, and price.
+ virtual void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
+ std::optional<u128> user_id = {},
+ std::optional<bool> full_display = {},
+ std::optional<std::string> extra_parameter = {}) = 0;
+
+ // Shows a page with all of the add on content available for a game, with name, description, and
+ // price.
+ virtual void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
+ std::optional<u128> user_id = {},
+ std::optional<bool> full_display = {}) = 0;
+
+ // Shows a page with all of the subscriptions (recurring payments) for a game, with name,
+ // description, price, and renewal period.
+ virtual void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
+ std::optional<u128> user_id = {}) = 0;
+
+ // Shows a page with a list of any additional game related purchasable items (DLC,
+ // subscriptions, etc) for a particular game, with name, description, type, and price.
+ virtual void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
+ std::optional<u128> user_id = {}) = 0;
+
+ // Shows the home page of the shop.
+ virtual void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) = 0;
+
+ // Shows the user settings page of the shop.
+ virtual void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) = 0;
+};
+
+class DefaultECommerceApplet : public ECommerceApplet {
+public:
+ ~DefaultECommerceApplet() override;
+
+ void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
+ std::optional<u128> user_id, std::optional<bool> full_display,
+ std::optional<std::string> extra_parameter) override;
+ void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
+ std::optional<u128> user_id,
+ std::optional<bool> full_display) override;
+ void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
+ std::optional<u128> user_id) override;
+ void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
+ std::optional<u128> user_id) override;
+ void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) override;
+ void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) override;
+};
+
} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/web_browser.cpp b/src/core/frontend/applets/web_browser.cpp
index 3a3d3d0bf..528295ffc 100644
--- a/src/core/frontend/applets/web_browser.cpp
+++ b/src/core/frontend/applets/web_browser.cpp
@@ -11,9 +11,9 @@ WebBrowserApplet::~WebBrowserApplet() = default;
DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default;
-void DefaultWebBrowserApplet::OpenPage(std::string_view filename,
- std::function<void()> unpack_romfs_callback,
- std::function<void()> finished_callback) {
+void DefaultWebBrowserApplet::OpenPageLocal(std::string_view filename,
+ std::function<void()> unpack_romfs_callback,
+ std::function<void()> finished_callback) {
LOG_INFO(Service_AM,
"(STUBBED) called - No suitable web browser implementation found to open website page "
"at '{}'!",
diff --git a/src/core/frontend/applets/web_browser.h b/src/core/frontend/applets/web_browser.h
index f952856af..110e33bc4 100644
--- a/src/core/frontend/applets/web_browser.h
+++ b/src/core/frontend/applets/web_browser.h
@@ -13,16 +13,16 @@ class WebBrowserApplet {
public:
virtual ~WebBrowserApplet();
- virtual void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
- std::function<void()> finished_callback) = 0;
+ virtual void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
+ std::function<void()> finished_callback) = 0;
};
class DefaultWebBrowserApplet final : public WebBrowserApplet {
public:
~DefaultWebBrowserApplet() override;
- void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
- std::function<void()> finished_callback) override;
+ void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
+ std::function<void()> finished_callback) override;
};
} // namespace Core::Frontend
diff --git a/src/core/hardware_interrupt_manager.cpp b/src/core/hardware_interrupt_manager.cpp
new file mode 100644
index 000000000..c2115db2d
--- /dev/null
+++ b/src/core/hardware_interrupt_manager.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hardware_interrupt_manager.h"
+#include "core/hle/service/nvdrv/interface.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Core::Hardware {
+
+InterruptManager::InterruptManager(Core::System& system_in) : system(system_in) {
+ gpu_interrupt_event =
+ system.CoreTiming().RegisterEvent("GPUInterrupt", [this](u64 message, s64) {
+ auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv");
+ const u32 syncpt = static_cast<u32>(message >> 32);
+ const u32 value = static_cast<u32>(message);
+ nvdrv->SignalGPUInterruptSyncpt(syncpt, value);
+ });
+}
+
+InterruptManager::~InterruptManager() = default;
+
+void InterruptManager::GPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
+ const u64 msg = (static_cast<u64>(syncpoint_id) << 32ULL) | value;
+ system.CoreTiming().ScheduleEvent(10, gpu_interrupt_event, msg);
+}
+
+} // namespace Core::Hardware
diff --git a/src/core/hardware_interrupt_manager.h b/src/core/hardware_interrupt_manager.h
new file mode 100644
index 000000000..494db883a
--- /dev/null
+++ b/src/core/hardware_interrupt_manager.h
@@ -0,0 +1,31 @@
+// Copyright 2019 Yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Core {
+class System;
+}
+
+namespace Core::Timing {
+struct EventType;
+}
+
+namespace Core::Hardware {
+
+class InterruptManager {
+public:
+ explicit InterruptManager(Core::System& system);
+ ~InterruptManager();
+
+ void GPUInterruptSyncpt(u32 syncpoint_id, u32 value);
+
+private:
+ Core::System& system;
+ Core::Timing::EventType* gpu_interrupt_event{};
+};
+
+} // namespace Core::Hardware
diff --git a/src/core/hle/kernel/code_set.h b/src/core/hle/kernel/code_set.h
index 879957dcb..d8ad54030 100644
--- a/src/core/hle/kernel/code_set.h
+++ b/src/core/hle/kernel/code_set.h
@@ -8,6 +8,7 @@
#include <vector>
#include "common/common_types.h"
+#include "core/hle/kernel/physical_memory.h"
namespace Kernel {
@@ -77,7 +78,7 @@ struct CodeSet final {
}
/// The overall data that backs this code set.
- std::vector<u8> memory;
+ Kernel::PhysicalMemory memory;
/// The segments that comprise this code set.
std::array<Segment, 3> segments;
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 757e5f21f..799e5e0d8 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -99,7 +99,8 @@ struct KernelCore::Impl {
void Shutdown() {
next_object_id = 0;
- next_process_id = Process::ProcessIDMin;
+ next_kernel_process_id = Process::InitialKIPIDMin;
+ next_user_process_id = Process::ProcessIDMin;
next_thread_id = 1;
process_list.clear();
@@ -132,7 +133,8 @@ struct KernelCore::Impl {
}
std::atomic<u32> next_object_id{0};
- std::atomic<u64> next_process_id{Process::ProcessIDMin};
+ std::atomic<u64> next_kernel_process_id{Process::InitialKIPIDMin};
+ std::atomic<u64> next_user_process_id{Process::ProcessIDMin};
std::atomic<u64> next_thread_id{1};
// Lists all processes that exist in the current session.
@@ -226,8 +228,12 @@ u64 KernelCore::CreateNewThreadID() {
return impl->next_thread_id++;
}
-u64 KernelCore::CreateNewProcessID() {
- return impl->next_process_id++;
+u64 KernelCore::CreateNewKernelProcessID() {
+ return impl->next_kernel_process_id++;
+}
+
+u64 KernelCore::CreateNewUserProcessID() {
+ return impl->next_user_process_id++;
}
Core::Timing::EventType* KernelCore::ThreadWakeupCallbackEventType() const {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 6b8738599..0cc44ee76 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -96,7 +96,10 @@ private:
u32 CreateNewObjectID();
/// Creates a new process ID, incrementing the internal process ID counter;
- u64 CreateNewProcessID();
+ u64 CreateNewKernelProcessID();
+
+ /// Creates a new process ID, incrementing the internal process ID counter;
+ u64 CreateNewUserProcessID();
/// Creates a new thread ID, incrementing the internal thread ID counter.
u64 CreateNewThreadID();
diff --git a/src/core/hle/kernel/physical_memory.h b/src/core/hle/kernel/physical_memory.h
new file mode 100644
index 000000000..090565310
--- /dev/null
+++ b/src/core/hle/kernel/physical_memory.h
@@ -0,0 +1,19 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/alignment.h"
+
+namespace Kernel {
+
+// This encapsulation serves 2 purposes:
+// - First, to encapsulate host physical memory under a single type and set an
+// standard for managing it.
+// - Second to ensure all host backing memory used is aligned to 256 bytes due
+// to strict alignment restrictions on GPU memory.
+
+using PhysicalMemory = std::vector<u8, Common::AlignmentAllocator<u8, 256>>;
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 2b81a8d4f..e80a12ac3 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <bitset>
#include <memory>
#include <random>
#include "common/alignment.h"
@@ -48,7 +49,58 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, u32 priority) {
}
} // Anonymous namespace
-SharedPtr<Process> Process::Create(Core::System& system, std::string name) {
+// Represents a page used for thread-local storage.
+//
+// Each TLS page contains slots that may be used by processes and threads.
+// Every process and thread is created with a slot in some arbitrary page
+// (whichever page happens to have an available slot).
+class TLSPage {
+public:
+ static constexpr std::size_t num_slot_entries = Memory::PAGE_SIZE / Memory::TLS_ENTRY_SIZE;
+
+ explicit TLSPage(VAddr address) : base_address{address} {}
+
+ bool HasAvailableSlots() const {
+ return !is_slot_used.all();
+ }
+
+ VAddr GetBaseAddress() const {
+ return base_address;
+ }
+
+ std::optional<VAddr> ReserveSlot() {
+ for (std::size_t i = 0; i < is_slot_used.size(); i++) {
+ if (is_slot_used[i]) {
+ continue;
+ }
+
+ is_slot_used[i] = true;
+ return base_address + (i * Memory::TLS_ENTRY_SIZE);
+ }
+
+ return std::nullopt;
+ }
+
+ void ReleaseSlot(VAddr address) {
+ // Ensure that all given addresses are consistent with how TLS pages
+ // are intended to be used when releasing slots.
+ ASSERT(IsWithinPage(address));
+ ASSERT((address % Memory::TLS_ENTRY_SIZE) == 0);
+
+ const std::size_t index = (address - base_address) / Memory::TLS_ENTRY_SIZE;
+ is_slot_used[index] = false;
+ }
+
+private:
+ bool IsWithinPage(VAddr address) const {
+ return base_address <= address && address < base_address + Memory::PAGE_SIZE;
+ }
+
+ VAddr base_address;
+ std::bitset<num_slot_entries> is_slot_used;
+};
+
+SharedPtr<Process> Process::Create(Core::System& system, std::string name, ProcessType type) {
auto& kernel = system.Kernel();
SharedPtr<Process> process(new Process(system));
@@ -56,7 +108,8 @@ SharedPtr<Process> Process::Create(Core::System& system, std::string name) {
process->resource_limit = kernel.GetSystemResourceLimit();
process->status = ProcessStatus::Created;
process->program_id = 0;
- process->process_id = kernel.CreateNewProcessID();
+ process->process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID()
+ : kernel.CreateNewUserProcessID();
process->capabilities.InitializeForMetadatalessProcess();
std::mt19937 rng(Settings::values.rng_seed.value_or(0));
@@ -76,20 +129,17 @@ u64 Process::GetTotalPhysicalMemoryAvailable() const {
return vm_manager.GetTotalPhysicalMemoryAvailable();
}
-u64 Process::GetTotalPhysicalMemoryAvailableWithoutMmHeap() const {
- // TODO: Subtract the personal heap size from this when the
- // personal heap is implemented.
- return GetTotalPhysicalMemoryAvailable();
+u64 Process::GetTotalPhysicalMemoryAvailableWithoutSystemResource() const {
+ return GetTotalPhysicalMemoryAvailable() - GetSystemResourceSize();
}
u64 Process::GetTotalPhysicalMemoryUsed() const {
- return vm_manager.GetCurrentHeapSize() + main_thread_stack_size + code_memory_size;
+ return vm_manager.GetCurrentHeapSize() + main_thread_stack_size + code_memory_size +
+ GetSystemResourceUsage();
}
-u64 Process::GetTotalPhysicalMemoryUsedWithoutMmHeap() const {
- // TODO: Subtract the personal heap size from this when the
- // personal heap is implemented.
- return GetTotalPhysicalMemoryUsed();
+u64 Process::GetTotalPhysicalMemoryUsedWithoutSystemResource() const {
+ return GetTotalPhysicalMemoryUsed() - GetSystemResourceUsage();
}
void Process::RegisterThread(const Thread* thread) {
@@ -119,6 +169,7 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
program_id = metadata.GetTitleID();
ideal_core = metadata.GetMainThreadCore();
is_64bit_process = metadata.Is64BitProgram();
+ system_resource_size = metadata.GetSystemResourceSize();
vm_manager.Reset(metadata.GetAddressSpaceType());
@@ -133,19 +184,11 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
}
void Process::Run(s32 main_thread_priority, u64 stack_size) {
- // The kernel always ensures that the given stack size is page aligned.
- main_thread_stack_size = Common::AlignUp(stack_size, Memory::PAGE_SIZE);
-
- // Allocate and map the main thread stack
- // TODO(bunnei): This is heap area that should be allocated by the kernel and not mapped as part
- // of the user address space.
- const VAddr mapping_address = vm_manager.GetTLSIORegionEndAddress() - main_thread_stack_size;
- vm_manager
- .MapMemoryBlock(mapping_address, std::make_shared<std::vector<u8>>(main_thread_stack_size),
- 0, main_thread_stack_size, MemoryState::Stack)
- .Unwrap();
+ AllocateMainThreadStack(stack_size);
+ tls_region_address = CreateTLSRegion();
vm_manager.LogLayout();
+
ChangeStatus(ProcessStatus::Running);
SetupMainThread(*this, kernel, main_thread_priority);
@@ -175,69 +218,66 @@ void Process::PrepareForTermination() {
stop_threads(system.Scheduler(2).GetThreadList());
stop_threads(system.Scheduler(3).GetThreadList());
+ FreeTLSRegion(tls_region_address);
+ tls_region_address = 0;
+
ChangeStatus(ProcessStatus::Exited);
}
/**
- * Finds a free location for the TLS section of a thread.
- * @param tls_slots The TLS page array of the thread's owner process.
- * Returns a tuple of (page, slot, alloc_needed) where:
- * page: The index of the first allocated TLS page that has free slots.
- * slot: The index of the first free slot in the indicated page.
- * alloc_needed: Whether there's a need to allocate a new TLS page (All pages are full).
+ * Attempts to find a TLS page that contains a free slot for
+ * use by a thread.
+ *
+ * @returns If a page with an available slot is found, then an iterator
+ * pointing to the page is returned. Otherwise the end iterator
+ * is returned instead.
*/
-static std::tuple<std::size_t, std::size_t, bool> FindFreeThreadLocalSlot(
- const std::vector<std::bitset<8>>& tls_slots) {
- // Iterate over all the allocated pages, and try to find one where not all slots are used.
- for (std::size_t page = 0; page < tls_slots.size(); ++page) {
- const auto& page_tls_slots = tls_slots[page];
- if (!page_tls_slots.all()) {
- // We found a page with at least one free slot, find which slot it is
- for (std::size_t slot = 0; slot < page_tls_slots.size(); ++slot) {
- if (!page_tls_slots.test(slot)) {
- return std::make_tuple(page, slot, false);
- }
- }
- }
- }
-
- return std::make_tuple(0, 0, true);
+static auto FindTLSPageWithAvailableSlots(std::vector<TLSPage>& tls_pages) {
+ return std::find_if(tls_pages.begin(), tls_pages.end(),
+ [](const auto& page) { return page.HasAvailableSlots(); });
}
-VAddr Process::MarkNextAvailableTLSSlotAsUsed(Thread& thread) {
- auto [available_page, available_slot, needs_allocation] = FindFreeThreadLocalSlot(tls_slots);
- const VAddr tls_begin = vm_manager.GetTLSIORegionBaseAddress();
+VAddr Process::CreateTLSRegion() {
+ auto tls_page_iter = FindTLSPageWithAvailableSlots(tls_pages);
- if (needs_allocation) {
- tls_slots.emplace_back(0); // The page is completely available at the start
- available_page = tls_slots.size() - 1;
- available_slot = 0; // Use the first slot in the new page
+ if (tls_page_iter == tls_pages.cend()) {
+ const auto region_address =
+ vm_manager.FindFreeRegion(vm_manager.GetTLSIORegionBaseAddress(),
+ vm_manager.GetTLSIORegionEndAddress(), Memory::PAGE_SIZE);
+ ASSERT(region_address.Succeeded());
- // Allocate some memory from the end of the linear heap for this region.
- auto& tls_memory = thread.GetTLSMemory();
- tls_memory->insert(tls_memory->end(), Memory::PAGE_SIZE, 0);
+ const auto map_result = vm_manager.MapMemoryBlock(
+ *region_address, std::make_shared<PhysicalMemory>(Memory::PAGE_SIZE), 0,
+ Memory::PAGE_SIZE, MemoryState::ThreadLocal);
+ ASSERT(map_result.Succeeded());
- vm_manager.RefreshMemoryBlockMappings(tls_memory.get());
+ tls_pages.emplace_back(*region_address);
- vm_manager.MapMemoryBlock(tls_begin + available_page * Memory::PAGE_SIZE, tls_memory, 0,
- Memory::PAGE_SIZE, MemoryState::ThreadLocal);
- }
+ const auto reserve_result = tls_pages.back().ReserveSlot();
+ ASSERT(reserve_result.has_value());
- tls_slots[available_page].set(available_slot);
+ return *reserve_result;
+ }
- return tls_begin + available_page * Memory::PAGE_SIZE + available_slot * Memory::TLS_ENTRY_SIZE;
+ return *tls_page_iter->ReserveSlot();
}
-void Process::FreeTLSSlot(VAddr tls_address) {
- const VAddr tls_base = tls_address - vm_manager.GetTLSIORegionBaseAddress();
- const VAddr tls_page = tls_base / Memory::PAGE_SIZE;
- const VAddr tls_slot = (tls_base % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE;
+void Process::FreeTLSRegion(VAddr tls_address) {
+ const VAddr aligned_address = Common::AlignDown(tls_address, Memory::PAGE_SIZE);
+ auto iter =
+ std::find_if(tls_pages.begin(), tls_pages.end(), [aligned_address](const auto& page) {
+ return page.GetBaseAddress() == aligned_address;
+ });
- tls_slots[tls_page].reset(tls_slot);
+ // Something has gone very wrong if we're freeing a region
+ // with no actual page available.
+ ASSERT(iter != tls_pages.cend());
+
+ iter->ReleaseSlot(tls_address);
}
void Process::LoadModule(CodeSet module_, VAddr base_addr) {
- const auto memory = std::make_shared<std::vector<u8>>(std::move(module_.memory));
+ const auto memory = std::make_shared<PhysicalMemory>(std::move(module_.memory));
const auto MapSegment = [&](const CodeSet::Segment& segment, VMAPermission permissions,
MemoryState memory_state) {
@@ -280,4 +320,16 @@ void Process::ChangeStatus(ProcessStatus new_status) {
WakeupAllWaitingThreads();
}
+void Process::AllocateMainThreadStack(u64 stack_size) {
+ // The kernel always ensures that the given stack size is page aligned.
+ main_thread_stack_size = Common::AlignUp(stack_size, Memory::PAGE_SIZE);
+
+ // Allocate and map the main thread stack
+ const VAddr mapping_address = vm_manager.GetTLSIORegionEndAddress() - main_thread_stack_size;
+ vm_manager
+ .MapMemoryBlock(mapping_address, std::make_shared<PhysicalMemory>(main_thread_stack_size),
+ 0, main_thread_stack_size, MemoryState::Stack)
+ .Unwrap();
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index 29e016983..c2df451f3 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -5,7 +5,6 @@
#pragma once
#include <array>
-#include <bitset>
#include <cstddef>
#include <list>
#include <string>
@@ -32,6 +31,7 @@ namespace Kernel {
class KernelCore;
class ResourceLimit;
class Thread;
+class TLSPage;
struct CodeSet;
@@ -73,9 +73,15 @@ public:
ProcessIDMax = 0xFFFFFFFFFFFFFFFF,
};
+ // Used to determine how process IDs are assigned.
+ enum class ProcessType {
+ KernelInternal,
+ Userland,
+ };
+
static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4;
- static SharedPtr<Process> Create(Core::System& system, std::string name);
+ static SharedPtr<Process> Create(Core::System& system, std::string name, ProcessType type);
std::string GetTypeName() const override {
return "Process";
@@ -129,6 +135,11 @@ public:
return mutex;
}
+ /// Gets the address to the process' dedicated TLS region.
+ VAddr GetTLSRegionAddress() const {
+ return tls_region_address;
+ }
+
/// Gets the current status of the process
ProcessStatus GetStatus() const {
return status;
@@ -162,8 +173,24 @@ public:
return capabilities.GetPriorityMask();
}
- u32 IsVirtualMemoryEnabled() const {
- return is_virtual_address_memory_enabled;
+ /// Gets the amount of secure memory to allocate for memory management.
+ u32 GetSystemResourceSize() const {
+ return system_resource_size;
+ }
+
+ /// Gets the amount of secure memory currently in use for memory management.
+ u32 GetSystemResourceUsage() const {
+ // On hardware, this returns the amount of system resource memory that has
+ // been used by the kernel. This is problematic for Yuzu to emulate, because
+ // system resource memory is used for page tables -- and yuzu doesn't really
+ // have a way to calculate how much memory is required for page tables for
+ // the current process at any given time.
+ // TODO: Is this even worth implementing? Games may retrieve this value via
+ // an SDK function that gets used + available system resource size for debug
+ // or diagnostic purposes. However, it seems unlikely that a game would make
+ // decisions based on how much system memory is dedicated to its page tables.
+ // Is returning a value other than zero wise?
+ return 0;
}
/// Whether this process is an AArch64 or AArch32 process.
@@ -190,15 +217,15 @@ public:
u64 GetTotalPhysicalMemoryAvailable() const;
/// Retrieves the total physical memory available to this process in bytes,
- /// without the size of the personal heap added to it.
- u64 GetTotalPhysicalMemoryAvailableWithoutMmHeap() const;
+ /// without the size of the personal system resource heap added to it.
+ u64 GetTotalPhysicalMemoryAvailableWithoutSystemResource() const;
/// Retrieves the total physical memory used by this process in bytes.
u64 GetTotalPhysicalMemoryUsed() const;
/// Retrieves the total physical memory used by this process in bytes,
- /// without the size of the personal heap added to it.
- u64 GetTotalPhysicalMemoryUsedWithoutMmHeap() const;
+ /// without the size of the personal system resource heap added to it.
+ u64 GetTotalPhysicalMemoryUsedWithoutSystemResource() const;
/// Gets the list of all threads created with this process as their owner.
const std::list<const Thread*>& GetThreadList() const {
@@ -254,10 +281,10 @@ public:
// Thread-local storage management
// Marks the next available region as used and returns the address of the slot.
- VAddr MarkNextAvailableTLSSlotAsUsed(Thread& thread);
+ [[nodiscard]] VAddr CreateTLSRegion();
// Frees a used TLS slot identified by the given address
- void FreeTLSSlot(VAddr tls_address);
+ void FreeTLSRegion(VAddr tls_address);
private:
explicit Process(Core::System& system);
@@ -274,6 +301,9 @@ private:
/// a process signal.
void ChangeStatus(ProcessStatus new_status);
+ /// Allocates the main thread stack for the process, given the stack size in bytes.
+ void AllocateMainThreadStack(u64 stack_size);
+
/// Memory manager for this process.
Kernel::VMManager vm_manager;
@@ -284,7 +314,7 @@ private:
u64 code_memory_size = 0;
/// Current status of the process
- ProcessStatus status;
+ ProcessStatus status{};
/// The ID of this process
u64 process_id = 0;
@@ -292,19 +322,23 @@ private:
/// Title ID corresponding to the process
u64 program_id = 0;
+ /// Specifies additional memory to be reserved for the process's memory management by the
+ /// system. When this is non-zero, secure memory is allocated and used for page table allocation
+ /// instead of using the normal global page tables/memory block management.
+ u32 system_resource_size = 0;
+
/// Resource limit descriptor for this process
SharedPtr<ResourceLimit> resource_limit;
/// The ideal CPU core for this process, threads are scheduled on this core by default.
u8 ideal_core = 0;
- u32 is_virtual_address_memory_enabled = 0;
/// The Thread Local Storage area is allocated as processes create threads,
/// each TLS area is 0x200 bytes, so one page (0x1000) is split up in 8 parts, and each part
/// holds the TLS for a specific thread. This vector contains which parts are in use for each
/// page as a bitmask.
/// This vector will grow as more pages are allocated for new threads.
- std::vector<std::bitset<8>> tls_slots;
+ std::vector<TLSPage> tls_pages;
/// Contains the parsed process capability descriptors.
ProcessCapabilities capabilities;
@@ -332,8 +366,11 @@ private:
/// variable related facilities.
Mutex mutex;
+ /// Address indicating the location of the process' dedicated TLS region.
+ VAddr tls_region_address = 0;
+
/// Random values for svcGetInfo RandomEntropy
- std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy;
+ std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy{};
/// List of threads that are running with this process as their owner.
std::list<const Thread*> thread_list;
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index f15c5ee36..a815c4eea 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -28,7 +28,7 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_
shared_memory->other_permissions = other_permissions;
if (address == 0) {
- shared_memory->backing_block = std::make_shared<std::vector<u8>>(size);
+ shared_memory->backing_block = std::make_shared<Kernel::PhysicalMemory>(size);
shared_memory->backing_block_offset = 0;
// Refresh the address mappings for the current process.
@@ -59,8 +59,8 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_
}
SharedPtr<SharedMemory> SharedMemory::CreateForApplet(
- KernelCore& kernel, std::shared_ptr<std::vector<u8>> heap_block, std::size_t offset, u64 size,
- MemoryPermission permissions, MemoryPermission other_permissions, std::string name) {
+ KernelCore& kernel, std::shared_ptr<Kernel::PhysicalMemory> heap_block, std::size_t offset,
+ u64 size, MemoryPermission permissions, MemoryPermission other_permissions, std::string name) {
SharedPtr<SharedMemory> shared_memory(new SharedMemory(kernel));
shared_memory->owner_process = nullptr;
diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h
index c2b6155e1..01ca6dcd2 100644
--- a/src/core/hle/kernel/shared_memory.h
+++ b/src/core/hle/kernel/shared_memory.h
@@ -10,6 +10,7 @@
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
+#include "core/hle/kernel/physical_memory.h"
#include "core/hle/kernel/process.h"
#include "core/hle/result.h"
@@ -62,12 +63,10 @@ public:
* block.
* @param name Optional object name, used for debugging purposes.
*/
- static SharedPtr<SharedMemory> CreateForApplet(KernelCore& kernel,
- std::shared_ptr<std::vector<u8>> heap_block,
- std::size_t offset, u64 size,
- MemoryPermission permissions,
- MemoryPermission other_permissions,
- std::string name = "Unknown Applet");
+ static SharedPtr<SharedMemory> CreateForApplet(
+ KernelCore& kernel, std::shared_ptr<Kernel::PhysicalMemory> heap_block, std::size_t offset,
+ u64 size, MemoryPermission permissions, MemoryPermission other_permissions,
+ std::string name = "Unknown Applet");
std::string GetTypeName() const override {
return "SharedMemory";
@@ -135,7 +134,7 @@ private:
~SharedMemory() override;
/// Backing memory for this shared memory block.
- std::shared_ptr<std::vector<u8>> backing_block;
+ std::shared_ptr<PhysicalMemory> backing_block;
/// Offset into the backing block for this shared memory.
std::size_t backing_block_offset = 0;
/// Size of the memory block. Page-aligned.
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index f9c606bc5..1fd1a732a 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -38,6 +38,7 @@
#include "core/hle/result.h"
#include "core/hle/service/service.h"
#include "core/memory.h"
+#include "core/reporter.h"
namespace Kernel {
namespace {
@@ -97,9 +98,9 @@ ResultCode MapUnmapMemorySanityChecks(const VMManager& vm_manager, VAddr dst_add
return ERR_INVALID_ADDRESS_STATE;
}
- if (!vm_manager.IsWithinNewMapRegion(dst_addr, size)) {
+ if (!vm_manager.IsWithinStackRegion(dst_addr, size)) {
LOG_ERROR(Kernel_SVC,
- "Destination is not within the new map region, addr=0x{:016X}, size=0x{:016X}",
+ "Destination is not within the stack region, addr=0x{:016X}, size=0x{:016X}",
dst_addr, size);
return ERR_INVALID_MEMORY_RANGE;
}
@@ -317,7 +318,14 @@ static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_ad
return result;
}
- return vm_manager.UnmapRange(dst_addr, size);
+ const auto unmap_res = vm_manager.UnmapRange(dst_addr, size);
+
+ // Reprotect the source mapping on success
+ if (unmap_res.IsSuccess()) {
+ ASSERT(vm_manager.ReprotectRange(src_addr, size, VMAPermission::ReadWrite).IsSuccess());
+ }
+
+ return unmap_res;
}
/// Connect to an OS service given the port name, returns the handle to the port to out
@@ -594,6 +602,7 @@ struct BreakReason {
static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
BreakReason break_reason{reason};
bool has_dumped_buffer{};
+ std::vector<u8> debug_buffer;
const auto handle_debug_buffer = [&](VAddr addr, u64 sz) {
if (sz == 0 || addr == 0 || has_dumped_buffer) {
@@ -605,7 +614,7 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
LOG_CRITICAL(Debug_Emulated, "debug_buffer_err_code={:X}", Memory::Read32(addr));
} else {
// We don't know what's in here so we'll hexdump it
- std::vector<u8> debug_buffer(sz);
+ debug_buffer.resize(sz);
Memory::ReadBlock(addr, debug_buffer.data(), sz);
std::string hexdump;
for (std::size_t i = 0; i < debug_buffer.size(); i++) {
@@ -664,6 +673,10 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
break;
}
+ system.GetReporter().SaveSvcBreakReport(
+ static_cast<u32>(break_reason.break_type.Value()), break_reason.signal_debugger, info1,
+ info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt);
+
if (!break_reason.signal_debugger) {
LOG_CRITICAL(
Debug_Emulated,
@@ -720,19 +733,19 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
// 2.0.0+
ASLRRegionBaseAddr = 12,
ASLRRegionSize = 13,
- NewMapRegionBaseAddr = 14,
- NewMapRegionSize = 15,
+ StackRegionBaseAddr = 14,
+ StackRegionSize = 15,
// 3.0.0+
- IsVirtualAddressMemoryEnabled = 16,
- PersonalMmHeapUsage = 17,
+ SystemResourceSize = 16,
+ SystemResourceUsage = 17,
TitleId = 18,
// 4.0.0+
PrivilegedProcessId = 19,
// 5.0.0+
UserExceptionContextAddr = 20,
// 6.0.0+
- TotalPhysicalMemoryAvailableWithoutMmHeap = 21,
- TotalPhysicalMemoryUsedWithoutMmHeap = 22,
+ TotalPhysicalMemoryAvailableWithoutSystemResource = 21,
+ TotalPhysicalMemoryUsedWithoutSystemResource = 22,
};
const auto info_id_type = static_cast<GetInfoType>(info_id);
@@ -746,16 +759,16 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
case GetInfoType::HeapRegionSize:
case GetInfoType::ASLRRegionBaseAddr:
case GetInfoType::ASLRRegionSize:
- case GetInfoType::NewMapRegionBaseAddr:
- case GetInfoType::NewMapRegionSize:
+ case GetInfoType::StackRegionBaseAddr:
+ case GetInfoType::StackRegionSize:
case GetInfoType::TotalPhysicalMemoryAvailable:
case GetInfoType::TotalPhysicalMemoryUsed:
- case GetInfoType::IsVirtualAddressMemoryEnabled:
- case GetInfoType::PersonalMmHeapUsage:
+ case GetInfoType::SystemResourceSize:
+ case GetInfoType::SystemResourceUsage:
case GetInfoType::TitleId:
case GetInfoType::UserExceptionContextAddr:
- case GetInfoType::TotalPhysicalMemoryAvailableWithoutMmHeap:
- case GetInfoType::TotalPhysicalMemoryUsedWithoutMmHeap: {
+ case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource:
+ case GetInfoType::TotalPhysicalMemoryUsedWithoutSystemResource: {
if (info_sub_id != 0) {
return ERR_INVALID_ENUM_VALUE;
}
@@ -800,12 +813,12 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
*result = process->VMManager().GetASLRRegionSize();
return RESULT_SUCCESS;
- case GetInfoType::NewMapRegionBaseAddr:
- *result = process->VMManager().GetNewMapRegionBaseAddress();
+ case GetInfoType::StackRegionBaseAddr:
+ *result = process->VMManager().GetStackRegionBaseAddress();
return RESULT_SUCCESS;
- case GetInfoType::NewMapRegionSize:
- *result = process->VMManager().GetNewMapRegionSize();
+ case GetInfoType::StackRegionSize:
+ *result = process->VMManager().GetStackRegionSize();
return RESULT_SUCCESS;
case GetInfoType::TotalPhysicalMemoryAvailable:
@@ -816,8 +829,13 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
*result = process->GetTotalPhysicalMemoryUsed();
return RESULT_SUCCESS;
- case GetInfoType::IsVirtualAddressMemoryEnabled:
- *result = process->IsVirtualMemoryEnabled();
+ case GetInfoType::SystemResourceSize:
+ *result = process->GetSystemResourceSize();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::SystemResourceUsage:
+ LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage");
+ *result = process->GetSystemResourceUsage();
return RESULT_SUCCESS;
case GetInfoType::TitleId:
@@ -825,17 +843,15 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
return RESULT_SUCCESS;
case GetInfoType::UserExceptionContextAddr:
- LOG_WARNING(Kernel_SVC,
- "(STUBBED) Attempted to query user exception context address, returned 0");
- *result = 0;
+ *result = process->GetTLSRegionAddress();
return RESULT_SUCCESS;
- case GetInfoType::TotalPhysicalMemoryAvailableWithoutMmHeap:
- *result = process->GetTotalPhysicalMemoryAvailable();
+ case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource:
+ *result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource();
return RESULT_SUCCESS;
- case GetInfoType::TotalPhysicalMemoryUsedWithoutMmHeap:
- *result = process->GetTotalPhysicalMemoryUsedWithoutMmHeap();
+ case GetInfoType::TotalPhysicalMemoryUsedWithoutSystemResource:
+ *result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource();
return RESULT_SUCCESS;
default:
@@ -940,6 +956,86 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
}
}
+/// Maps memory at a desired address
+static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
+ LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size);
+
+ if (!Common::Is4KBAligned(addr)) {
+ LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr);
+ return ERR_INVALID_ADDRESS;
+ }
+
+ if (!Common::Is4KBAligned(size)) {
+ LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size);
+ return ERR_INVALID_SIZE;
+ }
+
+ if (size == 0) {
+ LOG_ERROR(Kernel_SVC, "Size is zero");
+ return ERR_INVALID_SIZE;
+ }
+
+ if (!(addr < addr + size)) {
+ LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address");
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ Process* const current_process = system.Kernel().CurrentProcess();
+ auto& vm_manager = current_process->VMManager();
+
+ if (current_process->GetSystemResourceSize() == 0) {
+ LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
+ return ERR_INVALID_STATE;
+ }
+
+ if (!vm_manager.IsWithinMapRegion(addr, size)) {
+ LOG_ERROR(Kernel_SVC, "Range not within map region");
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ return vm_manager.MapPhysicalMemory(addr, size);
+}
+
+/// Unmaps memory previously mapped via MapPhysicalMemory
+static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
+ LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size);
+
+ if (!Common::Is4KBAligned(addr)) {
+ LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr);
+ return ERR_INVALID_ADDRESS;
+ }
+
+ if (!Common::Is4KBAligned(size)) {
+ LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size);
+ return ERR_INVALID_SIZE;
+ }
+
+ if (size == 0) {
+ LOG_ERROR(Kernel_SVC, "Size is zero");
+ return ERR_INVALID_SIZE;
+ }
+
+ if (!(addr < addr + size)) {
+ LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address");
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ Process* const current_process = system.Kernel().CurrentProcess();
+ auto& vm_manager = current_process->VMManager();
+
+ if (current_process->GetSystemResourceSize() == 0) {
+ LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
+ return ERR_INVALID_STATE;
+ }
+
+ if (!vm_manager.IsWithinMapRegion(addr, size)) {
+ LOG_ERROR(Kernel_SVC, "Range not within map region");
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ return vm_manager.UnmapPhysicalMemory(addr, size);
+}
+
/// Sets the thread activity
static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 activity) {
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", handle, activity);
@@ -1641,8 +1737,8 @@ static ResultCode SignalProcessWideKey(Core::System& system, VAddr condition_var
// Wait for an address (via Address Arbiter)
static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type, s32 value,
s64 timeout) {
- LOG_WARNING(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, timeout={}",
- address, type, value, timeout);
+ LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, timeout={}", address,
+ type, value, timeout);
// If the passed address is a kernel virtual address, return invalid memory state.
if (Memory::IsKernelVirtualAddress(address)) {
@@ -1664,8 +1760,8 @@ static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type,
// Signals to an address (via Address Arbiter)
static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type, s32 value,
s32 num_to_wake) {
- LOG_WARNING(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, num_to_wake=0x{:X}",
- address, type, value, num_to_wake);
+ LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, num_to_wake=0x{:X}",
+ address, type, value, num_to_wake);
// If the passed address is a kernel virtual address, return invalid memory state.
if (Memory::IsKernelVirtualAddress(address)) {
@@ -2297,8 +2393,8 @@ static const FunctionDef SVC_Table[] = {
{0x29, SvcWrap<GetInfo>, "GetInfo"},
{0x2A, nullptr, "FlushEntireDataCache"},
{0x2B, nullptr, "FlushDataCache"},
- {0x2C, nullptr, "MapPhysicalMemory"},
- {0x2D, nullptr, "UnmapPhysicalMemory"},
+ {0x2C, SvcWrap<MapPhysicalMemory>, "MapPhysicalMemory"},
+ {0x2D, SvcWrap<UnmapPhysicalMemory>, "UnmapPhysicalMemory"},
{0x2E, nullptr, "GetFutureThreadInfo"},
{0x2F, nullptr, "GetLastThreadInfo"},
{0x30, SvcWrap<GetResourceLimitLimitValue>, "GetResourceLimitLimitValue"},
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index 865473c6f..c2d8d0dc3 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -32,6 +32,11 @@ void SvcWrap(Core::System& system) {
FuncReturn(system, func(system, Param(system, 0)).raw);
}
+template <ResultCode func(Core::System&, u64, u64)>
+void SvcWrap(Core::System& system) {
+ FuncReturn(system, func(system, Param(system, 0), Param(system, 1)).raw);
+}
+
template <ResultCode func(Core::System&, u32)>
void SvcWrap(Core::System& system) {
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw);
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index c73a40977..ec529e7f2 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -65,7 +65,7 @@ void Thread::Stop() {
owner_process->UnregisterThread(this);
// Mark the TLS slot in the thread's page as free.
- owner_process->FreeTLSSlot(tls_address);
+ owner_process->FreeTLSRegion(tls_address);
}
void Thread::WakeAfterDelay(s64 nanoseconds) {
@@ -76,13 +76,13 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
// This function might be called from any thread so we have to be cautious and use the
// thread-safe version of ScheduleEvent.
const s64 cycles = Core::Timing::nsToCycles(std::chrono::nanoseconds{nanoseconds});
- Core::System::GetInstance().CoreTiming().ScheduleEventThreadsafe(
+ Core::System::GetInstance().CoreTiming().ScheduleEvent(
cycles, kernel.ThreadWakeupCallbackEventType(), callback_handle);
}
void Thread::CancelWakeupTimer() {
- Core::System::GetInstance().CoreTiming().UnscheduleEventThreadsafe(
- kernel.ThreadWakeupCallbackEventType(), callback_handle);
+ Core::System::GetInstance().CoreTiming().UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(),
+ callback_handle);
}
static std::optional<s32> GetNextProcessorId(u64 mask) {
@@ -205,9 +205,9 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
thread->name = std::move(name);
thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
thread->owner_process = &owner_process;
+ thread->tls_address = thread->owner_process->CreateTLSRegion();
thread->scheduler = &system.Scheduler(processor_id);
thread->scheduler->AddThread(thread);
- thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
thread->owner_process->RegisterThread(thread.get());
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index b4b9cda7c..07e989637 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -5,7 +5,6 @@
#pragma once
#include <functional>
-#include <memory>
#include <string>
#include <vector>
@@ -78,9 +77,6 @@ enum class ThreadActivity : u32 {
class Thread final : public WaitObject {
public:
- using TLSMemory = std::vector<u8>;
- using TLSMemoryPtr = std::shared_ptr<TLSMemory>;
-
using MutexWaitingThreads = std::vector<SharedPtr<Thread>>;
using ThreadContext = Core::ARM_Interface::ThreadContext;
@@ -169,14 +165,6 @@ public:
return thread_id;
}
- TLSMemoryPtr& GetTLSMemory() {
- return tls_memory;
- }
-
- const TLSMemoryPtr& GetTLSMemory() const {
- return tls_memory;
- }
-
/// Resumes a thread from waiting
void ResumeFromWait();
@@ -463,11 +451,9 @@ private:
u32 ideal_core{0xFFFFFFFF};
u64 affinity_mask{0x1};
- TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>();
+ ThreadActivity activity = ThreadActivity::Normal;
std::string name;
-
- ThreadActivity activity = ThreadActivity::Normal;
};
/**
diff --git a/src/core/hle/kernel/transfer_memory.cpp b/src/core/hle/kernel/transfer_memory.cpp
index 26c4e5e67..1113c815e 100644
--- a/src/core/hle/kernel/transfer_memory.cpp
+++ b/src/core/hle/kernel/transfer_memory.cpp
@@ -47,7 +47,7 @@ ResultCode TransferMemory::MapMemory(VAddr address, u64 size, MemoryPermission p
return ERR_INVALID_STATE;
}
- backing_block = std::make_shared<std::vector<u8>>(size);
+ backing_block = std::make_shared<PhysicalMemory>(size);
const auto map_state = owner_permissions == MemoryPermission::None
? MemoryState::TransferMemoryIsolated
diff --git a/src/core/hle/kernel/transfer_memory.h b/src/core/hle/kernel/transfer_memory.h
index a140b1e2b..6be9dc094 100644
--- a/src/core/hle/kernel/transfer_memory.h
+++ b/src/core/hle/kernel/transfer_memory.h
@@ -8,6 +8,7 @@
#include <vector>
#include "core/hle/kernel/object.h"
+#include "core/hle/kernel/physical_memory.h"
union ResultCode;
@@ -82,7 +83,7 @@ private:
~TransferMemory() override;
/// Memory block backing this instance.
- std::shared_ptr<std::vector<u8>> backing_block;
+ std::shared_ptr<PhysicalMemory> backing_block;
/// The base address for the memory managed by this instance.
VAddr base_address = 0;
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index 6d6980aba..c7af87073 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -5,13 +5,15 @@
#include <algorithm>
#include <iterator>
#include <utility>
+#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/memory_hook.h"
-#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/file_sys/program_metadata.h"
#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/memory.h"
#include "core/memory_setup.h"
@@ -49,10 +51,14 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
type != next.type) {
return false;
}
- if (type == VMAType::AllocatedMemoryBlock &&
- (backing_block != next.backing_block || offset + size != next.offset)) {
+ if ((attribute & MemoryAttribute::DeviceMapped) == MemoryAttribute::DeviceMapped) {
+ // TODO: Can device mapped memory be merged sanely?
+ // Not merging it may cause inaccuracies versus hardware when memory layout is queried.
return false;
}
+ if (type == VMAType::AllocatedMemoryBlock) {
+ return true;
+ }
if (type == VMAType::BackingMemory && backing_memory + size != next.backing_memory) {
return false;
}
@@ -68,9 +74,7 @@ VMManager::VMManager(Core::System& system) : system{system} {
Reset(FileSys::ProgramAddressSpaceType::Is39Bit);
}
-VMManager::~VMManager() {
- Reset(FileSys::ProgramAddressSpaceType::Is39Bit);
-}
+VMManager::~VMManager() = default;
void VMManager::Reset(FileSys::ProgramAddressSpaceType type) {
Clear();
@@ -100,9 +104,9 @@ bool VMManager::IsValidHandle(VMAHandle handle) const {
}
ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
- std::shared_ptr<std::vector<u8>> block,
+ std::shared_ptr<PhysicalMemory> block,
std::size_t offset, u64 size,
- MemoryState state) {
+ MemoryState state, VMAPermission perm) {
ASSERT(block != nullptr);
ASSERT(offset + size <= block->size());
@@ -111,17 +115,8 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
VirtualMemoryArea& final_vma = vma_handle->second;
ASSERT(final_vma.size == size);
- system.ArmInterface(0).MapBackingMemory(target, size, block->data() + offset,
- VMAPermission::ReadWriteExecute);
- system.ArmInterface(1).MapBackingMemory(target, size, block->data() + offset,
- VMAPermission::ReadWriteExecute);
- system.ArmInterface(2).MapBackingMemory(target, size, block->data() + offset,
- VMAPermission::ReadWriteExecute);
- system.ArmInterface(3).MapBackingMemory(target, size, block->data() + offset,
- VMAPermission::ReadWriteExecute);
-
final_vma.type = VMAType::AllocatedMemoryBlock;
- final_vma.permissions = VMAPermission::ReadWrite;
+ final_vma.permissions = perm;
final_vma.state = state;
final_vma.backing_block = std::move(block);
final_vma.offset = offset;
@@ -139,11 +134,6 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
VirtualMemoryArea& final_vma = vma_handle->second;
ASSERT(final_vma.size == size);
- system.ArmInterface(0).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
- system.ArmInterface(1).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
- system.ArmInterface(2).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
- system.ArmInterface(3).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
-
final_vma.type = VMAType::BackingMemory;
final_vma.permissions = VMAPermission::ReadWrite;
final_vma.state = state;
@@ -154,22 +144,33 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
}
ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const {
- // Find the first Free VMA.
- const VAddr base = GetASLRRegionBaseAddress();
- const VMAHandle vma_handle = std::find_if(vma_map.begin(), vma_map.end(), [&](const auto& vma) {
- if (vma.second.type != VMAType::Free)
- return false;
+ return FindFreeRegion(GetASLRRegionBaseAddress(), GetASLRRegionEndAddress(), size);
+}
- const VAddr vma_end = vma.second.base + vma.second.size;
- return vma_end > base && vma_end >= base + size;
- });
+ResultVal<VAddr> VMManager::FindFreeRegion(VAddr begin, VAddr end, u64 size) const {
+ ASSERT(begin < end);
+ ASSERT(size <= end - begin);
- if (vma_handle == vma_map.end()) {
+ const VMAHandle vma_handle =
+ std::find_if(vma_map.begin(), vma_map.end(), [begin, end, size](const auto& vma) {
+ if (vma.second.type != VMAType::Free) {
+ return false;
+ }
+ const VAddr vma_base = vma.second.base;
+ const VAddr vma_end = vma_base + vma.second.size;
+ const VAddr assumed_base = (begin < vma_base) ? vma_base : begin;
+ const VAddr used_range = assumed_base + size;
+
+ return vma_base <= assumed_base && assumed_base < used_range && used_range < end &&
+ used_range <= vma_end;
+ });
+
+ if (vma_handle == vma_map.cend()) {
// TODO(Subv): Find the correct error code here.
return ResultCode(-1);
}
- const VAddr target = std::max(base, vma_handle->second.base);
+ const VAddr target = std::max(begin, vma_handle->second.base);
return MakeResult<VAddr>(target);
}
@@ -221,11 +222,6 @@ ResultCode VMManager::UnmapRange(VAddr target, u64 size) {
ASSERT(FindVMA(target)->second.size >= size);
- system.ArmInterface(0).UnmapMemory(target, size);
- system.ArmInterface(1).UnmapMemory(target, size);
- system.ArmInterface(2).UnmapMemory(target, size);
- system.ArmInterface(3).UnmapMemory(target, size);
-
return RESULT_SUCCESS;
}
@@ -265,7 +261,7 @@ ResultVal<VAddr> VMManager::SetHeapSize(u64 size) {
if (heap_memory == nullptr) {
// Initialize heap
- heap_memory = std::make_shared<std::vector<u8>>(size);
+ heap_memory = std::make_shared<PhysicalMemory>(size);
heap_end = heap_region_base + size;
} else {
UnmapRange(heap_region_base, GetCurrentHeapSize());
@@ -299,6 +295,162 @@ ResultVal<VAddr> VMManager::SetHeapSize(u64 size) {
return MakeResult<VAddr>(heap_region_base);
}
+ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
+ // Check how much memory we've already mapped.
+ const auto mapped_size_result = SizeOfAllocatedVMAsInRange(target, size);
+ if (mapped_size_result.Failed()) {
+ return mapped_size_result.Code();
+ }
+
+ // If we've already mapped the desired amount, return early.
+ const std::size_t mapped_size = *mapped_size_result;
+ if (mapped_size == size) {
+ return RESULT_SUCCESS;
+ }
+
+ // Check that we can map the memory we want.
+ const auto res_limit = system.CurrentProcess()->GetResourceLimit();
+ const u64 physmem_remaining = res_limit->GetMaxResourceValue(ResourceType::PhysicalMemory) -
+ res_limit->GetCurrentResourceValue(ResourceType::PhysicalMemory);
+ if (physmem_remaining < (size - mapped_size)) {
+ return ERR_RESOURCE_LIMIT_EXCEEDED;
+ }
+
+ // Keep track of the memory regions we unmap.
+ std::vector<std::pair<u64, u64>> mapped_regions;
+ ResultCode result = RESULT_SUCCESS;
+
+ // Iterate, trying to map memory.
+ {
+ const auto end_addr = target + size;
+ const auto last_addr = end_addr - 1;
+ VAddr cur_addr = target;
+
+ auto iter = FindVMA(target);
+ ASSERT(iter != vma_map.end());
+
+ while (true) {
+ const auto& vma = iter->second;
+ const auto vma_start = vma.base;
+ const auto vma_end = vma_start + vma.size;
+ const auto vma_last = vma_end - 1;
+
+ // Map the memory block
+ const auto map_size = std::min(end_addr - cur_addr, vma_end - cur_addr);
+ if (vma.state == MemoryState::Unmapped) {
+ const auto map_res =
+ MapMemoryBlock(cur_addr, std::make_shared<PhysicalMemory>(map_size), 0,
+ map_size, MemoryState::Heap, VMAPermission::ReadWrite);
+ result = map_res.Code();
+ if (result.IsError()) {
+ break;
+ }
+
+ mapped_regions.emplace_back(cur_addr, map_size);
+ }
+
+ // Break once we hit the end of the range.
+ if (last_addr <= vma_last) {
+ break;
+ }
+
+ // Advance to the next block.
+ cur_addr = vma_end;
+ iter = FindVMA(cur_addr);
+ ASSERT(iter != vma_map.end());
+ }
+ }
+
+ // If we failed, unmap memory.
+ if (result.IsError()) {
+ for (const auto [unmap_address, unmap_size] : mapped_regions) {
+ ASSERT_MSG(UnmapRange(unmap_address, unmap_size).IsSuccess(),
+ "Failed to unmap memory range.");
+ }
+
+ return result;
+ }
+
+ // Update amount of mapped physical memory.
+ physical_memory_mapped += size - mapped_size;
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) {
+ // Check how much memory is currently mapped.
+ const auto mapped_size_result = SizeOfUnmappablePhysicalMemoryInRange(target, size);
+ if (mapped_size_result.Failed()) {
+ return mapped_size_result.Code();
+ }
+
+ // If we've already unmapped all the memory, return early.
+ const std::size_t mapped_size = *mapped_size_result;
+ if (mapped_size == 0) {
+ return RESULT_SUCCESS;
+ }
+
+ // Keep track of the memory regions we unmap.
+ std::vector<std::pair<u64, u64>> unmapped_regions;
+ ResultCode result = RESULT_SUCCESS;
+
+ // Try to unmap regions.
+ {
+ const auto end_addr = target + size;
+ const auto last_addr = end_addr - 1;
+ VAddr cur_addr = target;
+
+ auto iter = FindVMA(target);
+ ASSERT(iter != vma_map.end());
+
+ while (true) {
+ const auto& vma = iter->second;
+ const auto vma_start = vma.base;
+ const auto vma_end = vma_start + vma.size;
+ const auto vma_last = vma_end - 1;
+
+ // Unmap the memory block
+ const auto unmap_size = std::min(end_addr - cur_addr, vma_end - cur_addr);
+ if (vma.state == MemoryState::Heap) {
+ result = UnmapRange(cur_addr, unmap_size);
+ if (result.IsError()) {
+ break;
+ }
+
+ unmapped_regions.emplace_back(cur_addr, unmap_size);
+ }
+
+ // Break once we hit the end of the range.
+ if (last_addr <= vma_last) {
+ break;
+ }
+
+ // Advance to the next block.
+ cur_addr = vma_end;
+ iter = FindVMA(cur_addr);
+ ASSERT(iter != vma_map.end());
+ }
+ }
+
+ // If we failed, re-map regions.
+ // TODO: Preserve memory contents?
+ if (result.IsError()) {
+ for (const auto [map_address, map_size] : unmapped_regions) {
+ const auto remap_res =
+ MapMemoryBlock(map_address, std::make_shared<PhysicalMemory>(map_size), 0, map_size,
+ MemoryState::Heap, VMAPermission::None);
+ ASSERT_MSG(remap_res.Succeeded(), "Failed to remap a memory block.");
+ }
+
+ return result;
+ }
+
+ // Update mapped amount
+ physical_memory_mapped -= mapped_size;
+
+ return RESULT_SUCCESS;
+}
+
ResultCode VMManager::MapCodeMemory(VAddr dst_address, VAddr src_address, u64 size) {
constexpr auto ignore_attribute = MemoryAttribute::LockedForIPC | MemoryAttribute::DeviceMapped;
const auto src_check_result = CheckRangeState(
@@ -438,7 +590,7 @@ ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, Mem
ASSERT_MSG(vma_offset + size <= vma->second.size,
"Shared memory exceeds bounds of mapped block");
- const std::shared_ptr<std::vector<u8>>& backing_block = vma->second.backing_block;
+ const std::shared_ptr<PhysicalMemory>& backing_block = vma->second.backing_block;
const std::size_t backing_block_offset = vma->second.offset + vma_offset;
CASCADE_RESULT(auto new_vma,
@@ -446,12 +598,12 @@ ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, Mem
// Protect mirror with permissions from old region
Reprotect(new_vma, vma->second.permissions);
// Remove permissions from old region
- Reprotect(vma, VMAPermission::None);
+ ReprotectRange(src_addr, size, VMAPermission::None);
return RESULT_SUCCESS;
}
-void VMManager::RefreshMemoryBlockMappings(const std::vector<u8>* block) {
+void VMManager::RefreshMemoryBlockMappings(const PhysicalMemory* block) {
// If this ever proves to have a noticeable performance impact, allow users of the function to
// specify a specific range of addresses to limit the scan to.
for (const auto& p : vma_map) {
@@ -579,14 +731,14 @@ VMManager::VMAIter VMManager::SplitVMA(VMAIter vma_handle, u64 offset_in_vma) {
VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) {
const VMAIter next_vma = std::next(iter);
if (next_vma != vma_map.end() && iter->second.CanBeMergedWith(next_vma->second)) {
- iter->second.size += next_vma->second.size;
+ MergeAdjacentVMA(iter->second, next_vma->second);
vma_map.erase(next_vma);
}
if (iter != vma_map.begin()) {
VMAIter prev_vma = std::prev(iter);
if (prev_vma->second.CanBeMergedWith(iter->second)) {
- prev_vma->second.size += iter->second.size;
+ MergeAdjacentVMA(prev_vma->second, iter->second);
vma_map.erase(iter);
iter = prev_vma;
}
@@ -595,6 +747,44 @@ VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) {
return iter;
}
+void VMManager::MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryArea& right) {
+ ASSERT(left.CanBeMergedWith(right));
+
+ // Always merge allocated memory blocks, even when they don't share the same backing block.
+ if (left.type == VMAType::AllocatedMemoryBlock &&
+ (left.backing_block != right.backing_block || left.offset + left.size != right.offset)) {
+ const auto right_begin = right.backing_block->begin() + right.offset;
+ const auto right_end = right_begin + right.size;
+
+ // Check if we can save work.
+ if (left.offset == 0 && left.size == left.backing_block->size()) {
+ // Fast case: left is an entire backing block.
+ left.backing_block->insert(left.backing_block->end(), right_begin, right_end);
+ } else {
+ // Slow case: make a new memory block for left and right.
+ const auto left_begin = left.backing_block->begin() + left.offset;
+ const auto left_end = left_begin + left.size;
+ const auto left_size = static_cast<std::size_t>(std::distance(left_begin, left_end));
+ const auto right_size = static_cast<std::size_t>(std::distance(right_begin, right_end));
+
+ auto new_memory = std::make_shared<PhysicalMemory>();
+ new_memory->reserve(left_size + right_size);
+ new_memory->insert(new_memory->end(), left_begin, left_end);
+ new_memory->insert(new_memory->end(), right_begin, right_end);
+
+ left.backing_block = std::move(new_memory);
+ left.offset = 0;
+ }
+
+ // Page table update is needed, because backing memory changed.
+ left.size += right.size;
+ UpdatePageTableForVMA(left);
+ } else {
+ // Just update the size.
+ left.size += right.size;
+ }
+}
+
void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
switch (vma.type) {
case VMAType::Free:
@@ -616,9 +806,11 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType type) {
u64 map_region_size = 0;
u64 heap_region_size = 0;
- u64 new_map_region_size = 0;
+ u64 stack_region_size = 0;
u64 tls_io_region_size = 0;
+ u64 stack_and_tls_io_end = 0;
+
switch (type) {
case FileSys::ProgramAddressSpaceType::Is32Bit:
case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
@@ -634,6 +826,7 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
map_region_size = 0;
heap_region_size = 0x80000000;
}
+ stack_and_tls_io_end = 0x40000000;
break;
case FileSys::ProgramAddressSpaceType::Is36Bit:
address_space_width = 36;
@@ -643,6 +836,7 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
aslr_region_end = aslr_region_base + 0xFF8000000;
map_region_size = 0x180000000;
heap_region_size = 0x180000000;
+ stack_and_tls_io_end = 0x80000000;
break;
case FileSys::ProgramAddressSpaceType::Is39Bit:
address_space_width = 39;
@@ -652,7 +846,7 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
aslr_region_end = aslr_region_base + 0x7FF8000000;
map_region_size = 0x1000000000;
heap_region_size = 0x180000000;
- new_map_region_size = 0x80000000;
+ stack_region_size = 0x80000000;
tls_io_region_size = 0x1000000000;
break;
default:
@@ -660,6 +854,8 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
return;
}
+ const u64 stack_and_tls_io_begin = aslr_region_base;
+
address_space_base = 0;
address_space_end = 1ULL << address_space_width;
@@ -670,15 +866,20 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
heap_region_end = heap_region_base + heap_region_size;
heap_end = heap_region_base;
- new_map_region_base = heap_region_end;
- new_map_region_end = new_map_region_base + new_map_region_size;
+ stack_region_base = heap_region_end;
+ stack_region_end = stack_region_base + stack_region_size;
- tls_io_region_base = new_map_region_end;
+ tls_io_region_base = stack_region_end;
tls_io_region_end = tls_io_region_base + tls_io_region_size;
- if (new_map_region_size == 0) {
- new_map_region_base = address_space_base;
- new_map_region_end = address_space_end;
+ if (stack_region_size == 0) {
+ stack_region_base = stack_and_tls_io_begin;
+ stack_region_end = stack_and_tls_io_end;
+ }
+
+ if (tls_io_region_size == 0) {
+ tls_io_region_base = stack_and_tls_io_begin;
+ tls_io_region_end = stack_and_tls_io_end;
}
}
@@ -758,6 +959,84 @@ VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, Memo
std::make_tuple(initial_state, initial_permissions, initial_attributes & ~ignore_mask));
}
+ResultVal<std::size_t> VMManager::SizeOfAllocatedVMAsInRange(VAddr address,
+ std::size_t size) const {
+ const VAddr end_addr = address + size;
+ const VAddr last_addr = end_addr - 1;
+ std::size_t mapped_size = 0;
+
+ VAddr cur_addr = address;
+ auto iter = FindVMA(cur_addr);
+ ASSERT(iter != vma_map.end());
+
+ while (true) {
+ const auto& vma = iter->second;
+ const VAddr vma_start = vma.base;
+ const VAddr vma_end = vma_start + vma.size;
+ const VAddr vma_last = vma_end - 1;
+
+ // Add size if relevant.
+ if (vma.state != MemoryState::Unmapped) {
+ mapped_size += std::min(end_addr - cur_addr, vma_end - cur_addr);
+ }
+
+ // Break once we hit the end of the range.
+ if (last_addr <= vma_last) {
+ break;
+ }
+
+ // Advance to the next block.
+ cur_addr = vma_end;
+ iter = std::next(iter);
+ ASSERT(iter != vma_map.end());
+ }
+
+ return MakeResult(mapped_size);
+}
+
+ResultVal<std::size_t> VMManager::SizeOfUnmappablePhysicalMemoryInRange(VAddr address,
+ std::size_t size) const {
+ const VAddr end_addr = address + size;
+ const VAddr last_addr = end_addr - 1;
+ std::size_t mapped_size = 0;
+
+ VAddr cur_addr = address;
+ auto iter = FindVMA(cur_addr);
+ ASSERT(iter != vma_map.end());
+
+ while (true) {
+ const auto& vma = iter->second;
+ const auto vma_start = vma.base;
+ const auto vma_end = vma_start + vma.size;
+ const auto vma_last = vma_end - 1;
+ const auto state = vma.state;
+ const auto attr = vma.attribute;
+
+ // Memory within region must be free or mapped heap.
+ if (!((state == MemoryState::Heap && attr == MemoryAttribute::None) ||
+ (state == MemoryState::Unmapped))) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ // Add size if relevant.
+ if (state != MemoryState::Unmapped) {
+ mapped_size += std::min(end_addr - cur_addr, vma_end - cur_addr);
+ }
+
+ // Break once we hit the end of the range.
+ if (last_addr <= vma_last) {
+ break;
+ }
+
+ // Advance to the next block.
+ cur_addr = vma_end;
+ iter = std::next(iter);
+ ASSERT(iter != vma_map.end());
+ }
+
+ return MakeResult(mapped_size);
+}
+
u64 VMManager::GetTotalPhysicalMemoryAvailable() const {
LOG_WARNING(Kernel, "(STUBBED) called");
return 0xF8000000;
@@ -870,21 +1149,21 @@ bool VMManager::IsWithinMapRegion(VAddr address, u64 size) const {
return IsInsideAddressRange(address, size, GetMapRegionBaseAddress(), GetMapRegionEndAddress());
}
-VAddr VMManager::GetNewMapRegionBaseAddress() const {
- return new_map_region_base;
+VAddr VMManager::GetStackRegionBaseAddress() const {
+ return stack_region_base;
}
-VAddr VMManager::GetNewMapRegionEndAddress() const {
- return new_map_region_end;
+VAddr VMManager::GetStackRegionEndAddress() const {
+ return stack_region_end;
}
-u64 VMManager::GetNewMapRegionSize() const {
- return new_map_region_end - new_map_region_base;
+u64 VMManager::GetStackRegionSize() const {
+ return stack_region_end - stack_region_base;
}
-bool VMManager::IsWithinNewMapRegion(VAddr address, u64 size) const {
- return IsInsideAddressRange(address, size, GetNewMapRegionBaseAddress(),
- GetNewMapRegionEndAddress());
+bool VMManager::IsWithinStackRegion(VAddr address, u64 size) const {
+ return IsInsideAddressRange(address, size, GetStackRegionBaseAddress(),
+ GetStackRegionEndAddress());
}
VAddr VMManager::GetTLSIORegionBaseAddress() const {
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index dfbf7a894..850a7ebc3 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -11,6 +11,7 @@
#include "common/common_types.h"
#include "common/memory_hook.h"
#include "common/page_table.h"
+#include "core/hle/kernel/physical_memory.h"
#include "core/hle/result.h"
#include "core/memory.h"
@@ -290,7 +291,7 @@ struct VirtualMemoryArea {
// Settings for type = AllocatedMemoryBlock
/// Memory block backing this VMA.
- std::shared_ptr<std::vector<u8>> backing_block = nullptr;
+ std::shared_ptr<PhysicalMemory> backing_block = nullptr;
/// Offset into the backing_memory the mapping starts from.
std::size_t offset = 0;
@@ -348,8 +349,9 @@ public:
* @param size Size of the mapping.
* @param state MemoryState tag to attach to the VMA.
*/
- ResultVal<VMAHandle> MapMemoryBlock(VAddr target, std::shared_ptr<std::vector<u8>> block,
- std::size_t offset, u64 size, MemoryState state);
+ ResultVal<VMAHandle> MapMemoryBlock(VAddr target, std::shared_ptr<PhysicalMemory> block,
+ std::size_t offset, u64 size, MemoryState state,
+ VMAPermission perm = VMAPermission::ReadWrite);
/**
* Maps an unmanaged host memory pointer at a given address.
@@ -362,14 +364,39 @@ public:
ResultVal<VMAHandle> MapBackingMemory(VAddr target, u8* memory, u64 size, MemoryState state);
/**
- * Finds the first free address that can hold a region of the desired size.
+ * Finds the first free memory region of the given size within
+ * the user-addressable ASLR memory region.
*
- * @param size Size of the desired region.
- * @return The found free address.
+ * @param size The size of the desired region in bytes.
+ *
+ * @returns If successful, the base address of the free region with
+ * the given size.
*/
ResultVal<VAddr> FindFreeRegion(u64 size) const;
/**
+ * Finds the first free address range that can hold a region of the desired size
+ *
+ * @param begin The starting address of the range.
+ * This is treated as an inclusive beginning address.
+ *
+ * @param end The ending address of the range.
+ * This is treated as an exclusive ending address.
+ *
+ * @param size The size of the free region to attempt to locate,
+ * in bytes.
+ *
+ * @returns If successful, the base address of the free region with
+ * the given size.
+ *
+ * @returns If unsuccessful, a result containing an error code.
+ *
+ * @pre The starting address must be less than the ending address.
+ * @pre The size must not exceed the address range itself.
+ */
+ ResultVal<VAddr> FindFreeRegion(VAddr begin, VAddr end, u64 size) const;
+
+ /**
* Maps a memory-mapped IO region at a given address.
*
* @param target The guest address to start the mapping at.
@@ -425,6 +452,34 @@ public:
///
ResultVal<VAddr> SetHeapSize(u64 size);
+ /// Maps memory at a given address.
+ ///
+ /// @param target The virtual address to map memory at.
+ /// @param size The amount of memory to map.
+ ///
+ /// @note The destination address must lie within the Map region.
+ ///
+ /// @note This function requires that SystemResourceSize be non-zero,
+ /// however, this is just because if it were not then the
+ /// resulting page tables could be exploited on hardware by
+ /// a malicious program. SystemResource usage does not need
+ /// to be explicitly checked or updated here.
+ ResultCode MapPhysicalMemory(VAddr target, u64 size);
+
+ /// Unmaps memory at a given address.
+ ///
+ /// @param target The virtual address to unmap memory at.
+ /// @param size The amount of memory to unmap.
+ ///
+ /// @note The destination address must lie within the Map region.
+ ///
+ /// @note This function requires that SystemResourceSize be non-zero,
+ /// however, this is just because if it were not then the
+ /// resulting page tables could be exploited on hardware by
+ /// a malicious program. SystemResource usage does not need
+ /// to be explicitly checked or updated here.
+ ResultCode UnmapPhysicalMemory(VAddr target, u64 size);
+
/// Maps a region of memory as code memory.
///
/// @param dst_address The base address of the region to create the aliasing memory region.
@@ -493,7 +548,7 @@ public:
* Scans all VMAs and updates the page table range of any that use the given vector as backing
* memory. This should be called after any operation that causes reallocation of the vector.
*/
- void RefreshMemoryBlockMappings(const std::vector<u8>* block);
+ void RefreshMemoryBlockMappings(const PhysicalMemory* block);
/// Dumps the address space layout to the log, for debugging
void LogLayout() const;
@@ -571,17 +626,17 @@ public:
/// Determines whether or not the specified range is within the map region.
bool IsWithinMapRegion(VAddr address, u64 size) const;
- /// Gets the base address of the new map region.
- VAddr GetNewMapRegionBaseAddress() const;
+ /// Gets the base address of the stack region.
+ VAddr GetStackRegionBaseAddress() const;
- /// Gets the end address of the new map region.
- VAddr GetNewMapRegionEndAddress() const;
+ /// Gets the end address of the stack region.
+ VAddr GetStackRegionEndAddress() const;
- /// Gets the total size of the new map region in bytes.
- u64 GetNewMapRegionSize() const;
+ /// Gets the total size of the stack region in bytes.
+ u64 GetStackRegionSize() const;
- /// Determines whether or not the given address range is within the new map region
- bool IsWithinNewMapRegion(VAddr address, u64 size) const;
+ /// Determines whether or not the given address range is within the stack region
+ bool IsWithinStackRegion(VAddr address, u64 size) const;
/// Gets the base address of the TLS IO region.
VAddr GetTLSIORegionBaseAddress() const;
@@ -632,6 +687,11 @@ private:
*/
VMAIter MergeAdjacent(VMAIter vma);
+ /**
+ * Merges two adjacent VMAs.
+ */
+ void MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryArea& right);
+
/// Updates the pages corresponding to this VMA so they match the VMA's attributes.
void UpdatePageTableForVMA(const VirtualMemoryArea& vma);
@@ -676,6 +736,13 @@ private:
MemoryAttribute attribute_mask, MemoryAttribute attribute,
MemoryAttribute ignore_mask) const;
+ /// Gets the amount of memory currently mapped (state != Unmapped) in a range.
+ ResultVal<std::size_t> SizeOfAllocatedVMAsInRange(VAddr address, std::size_t size) const;
+
+ /// Gets the amount of memory unmappable by UnmapPhysicalMemory in a range.
+ ResultVal<std::size_t> SizeOfUnmappablePhysicalMemoryInRange(VAddr address,
+ std::size_t size) const;
+
/**
* A map covering the entirety of the managed address space, keyed by the `base` field of each
* VMA. It must always be modified by splitting or merging VMAs, so that the invariant
@@ -701,8 +768,8 @@ private:
VAddr map_region_base = 0;
VAddr map_region_end = 0;
- VAddr new_map_region_base = 0;
- VAddr new_map_region_end = 0;
+ VAddr stack_region_base = 0;
+ VAddr stack_region_end = 0;
VAddr tls_io_region_base = 0;
VAddr tls_io_region_end = 0;
@@ -711,12 +778,17 @@ private:
// the entire virtual address space extents that bound the allocations, including any holes.
// This makes deallocation and reallocation of holes fast and keeps process memory contiguous
// in the emulator address space, allowing Memory::GetPointer to be reasonably safe.
- std::shared_ptr<std::vector<u8>> heap_memory;
+ std::shared_ptr<PhysicalMemory> heap_memory;
// The end of the currently allocated heap. This is not an inclusive
// end of the range. This is essentially 'base_address + current_size'.
VAddr heap_end = 0;
+ // The current amount of memory mapped via MapPhysicalMemory.
+ // This is used here (and in Nintendo's kernel) only for debugging, and does not impact
+ // any behavior.
+ u64 physical_memory_mapped = 0;
+
Core::System& system;
};
} // namespace Kernel
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index cb66e344b..a7c55e116 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -12,16 +12,28 @@
#include "common/swap.h"
#include "core/constants.h"
#include "core/core_timing.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/patch_manager.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/service/acc/acc.h"
#include "core/hle/service/acc/acc_aa.h"
#include "core/hle/service/acc/acc_su.h"
#include "core/hle/service/acc/acc_u0.h"
#include "core/hle/service/acc/acc_u1.h"
+#include "core/hle/service/acc/errors.h"
#include "core/hle/service/acc/profile_manager.h"
+#include "core/hle/service/glue/arp.h"
+#include "core/hle/service/glue/manager.h"
+#include "core/hle/service/sm/sm.h"
+#include "core/loader/loader.h"
namespace Service::Account {
+constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 30};
+constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100};
+
static std::string GetImagePath(Common::UUID uuid) {
return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
@@ -32,20 +44,31 @@ static constexpr u32 SanitizeJPEGSize(std::size_t size) {
return static_cast<u32>(std::min(size, max_jpeg_image_size));
}
-class IProfile final : public ServiceFramework<IProfile> {
+class IProfileCommon : public ServiceFramework<IProfileCommon> {
public:
- explicit IProfile(Common::UUID user_id, ProfileManager& profile_manager)
- : ServiceFramework("IProfile"), profile_manager(profile_manager), user_id(user_id) {
+ explicit IProfileCommon(const char* name, bool editor_commands, Common::UUID user_id,
+ ProfileManager& profile_manager)
+ : ServiceFramework(name), profile_manager(profile_manager), user_id(user_id) {
static const FunctionInfo functions[] = {
- {0, &IProfile::Get, "Get"},
- {1, &IProfile::GetBase, "GetBase"},
- {10, &IProfile::GetImageSize, "GetImageSize"},
- {11, &IProfile::LoadImage, "LoadImage"},
+ {0, &IProfileCommon::Get, "Get"},
+ {1, &IProfileCommon::GetBase, "GetBase"},
+ {10, &IProfileCommon::GetImageSize, "GetImageSize"},
+ {11, &IProfileCommon::LoadImage, "LoadImage"},
};
+
RegisterHandlers(functions);
+
+ if (editor_commands) {
+ static const FunctionInfo editor_functions[] = {
+ {100, &IProfileCommon::Store, "Store"},
+ {101, &IProfileCommon::StoreWithImage, "StoreWithImage"},
+ };
+
+ RegisterHandlers(editor_functions);
+ }
}
-private:
+protected:
void Get(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_ACC, "called user_id={}", user_id.Format());
ProfileBase profile_base{};
@@ -90,7 +113,7 @@ private:
LOG_WARNING(Service_ACC,
"Failed to load user provided image! Falling back to built-in backup...");
ctx.WriteBuffer(Core::Constants::ACCOUNT_BACKUP_JPEG);
- rb.Push<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size());
+ rb.Push(SanitizeJPEGSize(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
return;
}
@@ -112,16 +135,97 @@ private:
if (!image.IsOpen()) {
LOG_WARNING(Service_ACC,
"Failed to load user provided image! Falling back to built-in backup...");
- rb.Push<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size());
+ rb.Push(SanitizeJPEGSize(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
} else {
- rb.Push<u32>(SanitizeJPEGSize(image.GetSize()));
+ rb.Push(SanitizeJPEGSize(image.GetSize()));
}
}
- const ProfileManager& profile_manager;
+ void Store(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto base = rp.PopRaw<ProfileBase>();
+
+ const auto user_data = ctx.ReadBuffer();
+
+ LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid={}",
+ Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(base.username.data()), base.username.size()),
+ base.timestamp, base.user_uuid.Format());
+
+ if (user_data.size() < sizeof(ProfileData)) {
+ LOG_ERROR(Service_ACC, "ProfileData buffer too small!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_INVALID_BUFFER_SIZE);
+ return;
+ }
+
+ ProfileData data;
+ std::memcpy(&data, user_data.data(), sizeof(ProfileData));
+
+ if (!profile_manager.SetProfileBaseAndData(user_id, base, data)) {
+ LOG_ERROR(Service_ACC, "Failed to update profile data and base!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_FAILED_SAVE_DATA);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void StoreWithImage(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto base = rp.PopRaw<ProfileBase>();
+
+ const auto user_data = ctx.ReadBuffer();
+ const auto image_data = ctx.ReadBuffer(1);
+
+ LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid={}",
+ Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(base.username.data()), base.username.size()),
+ base.timestamp, base.user_uuid.Format());
+
+ if (user_data.size() < sizeof(ProfileData)) {
+ LOG_ERROR(Service_ACC, "ProfileData buffer too small!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_INVALID_BUFFER_SIZE);
+ return;
+ }
+
+ ProfileData data;
+ std::memcpy(&data, user_data.data(), sizeof(ProfileData));
+
+ FileUtil::IOFile image(GetImagePath(user_id), "wb");
+
+ if (!image.IsOpen() || !image.Resize(image_data.size()) ||
+ image.WriteBytes(image_data.data(), image_data.size()) != image_data.size() ||
+ !profile_manager.SetProfileBaseAndData(user_id, base, data)) {
+ LOG_ERROR(Service_ACC, "Failed to update profile data, base, and image!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_FAILED_SAVE_DATA);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ ProfileManager& profile_manager;
Common::UUID user_id; ///< The user id this profile refers to.
};
+class IProfile final : public IProfileCommon {
+public:
+ IProfile(Common::UUID user_id, ProfileManager& profile_manager)
+ : IProfileCommon("IProfile", false, user_id, profile_manager) {}
+};
+
+class IProfileEditor final : public IProfileCommon {
+public:
+ IProfileEditor(Common::UUID user_id, ProfileManager& profile_manager)
+ : IProfileCommon("IProfileEditor", true, user_id, profile_manager) {}
+};
+
class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
public:
IManagerForApplication() : ServiceFramework("IManagerForApplication") {
@@ -214,9 +318,71 @@ void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestCon
}
void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ auto pid = rp.Pop<u64>();
+
+ LOG_DEBUG(Service_ACC, "called, process_id={}", pid);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
+ rb.Push(InitializeApplicationInfoBase(pid));
+}
+
+void Module::Interface::InitializeApplicationInfoRestricted(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto pid = rp.Pop<u64>();
+
+ LOG_WARNING(Service_ACC, "(Partial implementation) called, process_id={}", pid);
+
+ // TODO(ogniK): We require checking if the user actually owns the title and what not. As of
+ // currently, we assume the user owns the title. InitializeApplicationInfoBase SHOULD be called
+ // first then we do extra checks if the game is a digital copy.
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InitializeApplicationInfoBase(pid));
+}
+
+ResultCode Module::Interface::InitializeApplicationInfoBase(u64 process_id) {
+ if (application_info) {
+ LOG_ERROR(Service_ACC, "Application already initialized");
+ return ERR_ACCOUNTINFO_ALREADY_INITIALIZED;
+ }
+
+ const auto& list = system.Kernel().GetProcessList();
+ const auto iter = std::find_if(list.begin(), list.end(), [&process_id](const auto& process) {
+ return process->GetProcessID() == process_id;
+ });
+
+ if (iter == list.end()) {
+ LOG_ERROR(Service_ACC, "Failed to find process ID");
+ application_info.application_type = ApplicationType::Unknown;
+
+ return ERR_ACCOUNTINFO_BAD_APPLICATION;
+ }
+
+ const auto launch_property = system.GetARPManager().GetLaunchProperty((*iter)->GetTitleID());
+
+ if (launch_property.Failed()) {
+ LOG_ERROR(Service_ACC, "Failed to get launch property");
+ return ERR_ACCOUNTINFO_BAD_APPLICATION;
+ }
+
+ switch (launch_property->base_game_storage_id) {
+ case FileSys::StorageId::GameCard:
+ application_info.application_type = ApplicationType::GameCard;
+ break;
+ case FileSys::StorageId::Host:
+ case FileSys::StorageId::NandUser:
+ case FileSys::StorageId::SdCard:
+ application_info.application_type = ApplicationType::Digital;
+ break;
+ default:
+ LOG_ERROR(Service_ACC, "Invalid game storage ID");
+ return ERR_ACCOUNTINFO_BAD_APPLICATION;
+ }
+
+ LOG_WARNING(Service_ACC, "ApplicationInfo init required");
+ // TODO(ogniK): Actual initalization here
+
+ return RESULT_SUCCESS;
}
void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx) {
@@ -226,6 +392,42 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo
rb.PushIpcInterface<IManagerForApplication>();
}
+void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_ACC, "called");
+ FileSys::NACP nacp;
+ const auto res = system.GetAppLoader().ReadControlData(nacp);
+
+ bool is_locked = false;
+
+ if (res != Loader::ResultStatus::Success) {
+ FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
+ auto nacp_unique = pm.GetControlMetadata().first;
+
+ if (nacp_unique != nullptr) {
+ is_locked = nacp_unique->GetUserAccountSwitchLock();
+ } else {
+ LOG_ERROR(Service_ACC, "nacp_unique is null!");
+ }
+ } else {
+ is_locked = nacp.GetUserAccountSwitchLock();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(is_locked);
+}
+
+void Module::Interface::GetProfileEditor(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ Common::UUID user_id = rp.PopRaw<Common::UUID>();
+
+ LOG_DEBUG(Service_ACC, "called, user_id={}", user_id.Format());
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IProfileEditor>(user_id, *profile_manager);
+}
+
void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
// A u8 is passed into this function which we can safely ignore. It's to determine if we have
@@ -251,19 +453,25 @@ void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContex
}
Module::Interface::Interface(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager, const char* name)
+ std::shared_ptr<ProfileManager> profile_manager, Core::System& system,
+ const char* name)
: ServiceFramework(name), module(std::move(module)),
- profile_manager(std::move(profile_manager)) {}
+ profile_manager(std::move(profile_manager)), system(system) {}
Module::Interface::~Interface() = default;
-void InstallInterfaces(SM::ServiceManager& service_manager) {
+void InstallInterfaces(Core::System& system) {
auto module = std::make_shared<Module>();
auto profile_manager = std::make_shared<ProfileManager>();
- std::make_shared<ACC_AA>(module, profile_manager)->InstallAsService(service_manager);
- std::make_shared<ACC_SU>(module, profile_manager)->InstallAsService(service_manager);
- std::make_shared<ACC_U0>(module, profile_manager)->InstallAsService(service_manager);
- std::make_shared<ACC_U1>(module, profile_manager)->InstallAsService(service_manager);
+
+ std::make_shared<ACC_AA>(module, profile_manager, system)
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<ACC_SU>(module, profile_manager, system)
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<ACC_U0>(module, profile_manager, system)
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<ACC_U1>(module, profile_manager, system)
+ ->InstallAsService(system.ServiceManager());
}
} // namespace Service::Account
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index 89b2104fa..7a7dc9ec6 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.h
@@ -4,6 +4,7 @@
#pragma once
+#include "core/hle/service/glue/manager.h"
#include "core/hle/service/service.h"
namespace Service::Account {
@@ -15,7 +16,8 @@ public:
class Interface : public ServiceFramework<Interface> {
public:
explicit Interface(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager, const char* name);
+ std::shared_ptr<ProfileManager> profile_manager, Core::System& system,
+ const char* name);
~Interface() override;
void GetUserCount(Kernel::HLERequestContext& ctx);
@@ -25,17 +27,41 @@ public:
void GetLastOpenedUser(Kernel::HLERequestContext& ctx);
void GetProfile(Kernel::HLERequestContext& ctx);
void InitializeApplicationInfo(Kernel::HLERequestContext& ctx);
+ void InitializeApplicationInfoRestricted(Kernel::HLERequestContext& ctx);
void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx);
void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx);
void TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx);
+ void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx);
+ void GetProfileEditor(Kernel::HLERequestContext& ctx);
+
+ private:
+ ResultCode InitializeApplicationInfoBase(u64 process_id);
+
+ enum class ApplicationType : u32_le {
+ GameCard = 0,
+ Digital = 1,
+ Unknown = 3,
+ };
+
+ struct ApplicationInfo {
+ Service::Glue::ApplicationLaunchProperty launch_property;
+ ApplicationType application_type;
+
+ constexpr explicit operator bool() const {
+ return launch_property.title_id != 0x0;
+ }
+ };
+
+ ApplicationInfo application_info{};
protected:
std::shared_ptr<Module> module;
std::shared_ptr<ProfileManager> profile_manager;
+ Core::System& system;
};
};
/// Registers all ACC services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(Core::System& system);
} // namespace Service::Account
diff --git a/src/core/hle/service/acc/acc_aa.cpp b/src/core/hle/service/acc/acc_aa.cpp
index e84d9f7cf..3bac6bcd1 100644
--- a/src/core/hle/service/acc/acc_aa.cpp
+++ b/src/core/hle/service/acc/acc_aa.cpp
@@ -6,8 +6,9 @@
namespace Service::Account {
-ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
- : Module::Interface(std::move(module), std::move(profile_manager), "acc:aa") {
+ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system)
+ : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:aa") {
static const FunctionInfo functions[] = {
{0, nullptr, "EnsureCacheAsync"},
{1, nullptr, "LoadCache"},
diff --git a/src/core/hle/service/acc/acc_aa.h b/src/core/hle/service/acc/acc_aa.h
index 9edb0421b..932c04890 100644
--- a/src/core/hle/service/acc/acc_aa.h
+++ b/src/core/hle/service/acc/acc_aa.h
@@ -10,8 +10,8 @@ namespace Service::Account {
class ACC_AA final : public Module::Interface {
public:
- explicit ACC_AA(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager);
+ explicit ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system);
~ACC_AA() override;
};
diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp
index d66233cad..0d1663657 100644
--- a/src/core/hle/service/acc/acc_su.cpp
+++ b/src/core/hle/service/acc/acc_su.cpp
@@ -6,8 +6,9 @@
namespace Service::Account {
-ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
- : Module::Interface(std::move(module), std::move(profile_manager), "acc:su") {
+ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system)
+ : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:su") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ACC_SU::GetUserCount, "GetUserCount"},
@@ -40,7 +41,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{202, nullptr, "CancelUserRegistration"},
{203, nullptr, "DeleteUser"},
{204, nullptr, "SetUserPosition"},
- {205, nullptr, "GetProfileEditor"},
+ {205, &ACC_SU::GetProfileEditor, "GetProfileEditor"},
{206, nullptr, "CompleteUserRegistrationForcibly"},
{210, nullptr, "CreateFloatingRegistrationRequest"},
{230, nullptr, "AuthenticateServiceAsync"},
diff --git a/src/core/hle/service/acc/acc_su.h b/src/core/hle/service/acc/acc_su.h
index fcced063a..0a700d9bf 100644
--- a/src/core/hle/service/acc/acc_su.h
+++ b/src/core/hle/service/acc/acc_su.h
@@ -10,8 +10,8 @@ namespace Service::Account {
class ACC_SU final : public Module::Interface {
public:
- explicit ACC_SU(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager);
+ explicit ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system);
~ACC_SU() override;
};
diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp
index 182f7c7e5..0ac19f4ff 100644
--- a/src/core/hle/service/acc/acc_u0.cpp
+++ b/src/core/hle/service/acc/acc_u0.cpp
@@ -6,8 +6,9 @@
namespace Service::Account {
-ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
- : Module::Interface(std::move(module), std::move(profile_manager), "acc:u0") {
+ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system)
+ : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:u0") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ACC_U0::GetUserCount, "GetUserCount"},
@@ -30,9 +31,9 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{120, nullptr, "CreateGuestLoginRequest"},
{130, nullptr, "LoadOpenContext"},
{131, nullptr, "ListOpenContextStoredUsers"},
- {140, nullptr, "InitializeApplicationInfo"},
+ {140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"},
{141, nullptr, "ListQualifiedUsers"},
- {150, nullptr, "IsUserAccountSwitchLocked"},
+ {150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"},
};
// clang-format on
diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h
index a1290e0bd..3bd9c3164 100644
--- a/src/core/hle/service/acc/acc_u0.h
+++ b/src/core/hle/service/acc/acc_u0.h
@@ -10,8 +10,8 @@ namespace Service::Account {
class ACC_U0 final : public Module::Interface {
public:
- explicit ACC_U0(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager);
+ explicit ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system);
~ACC_U0() override;
};
diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp
index 2dd17d935..6520b3968 100644
--- a/src/core/hle/service/acc/acc_u1.cpp
+++ b/src/core/hle/service/acc/acc_u1.cpp
@@ -6,8 +6,9 @@
namespace Service::Account {
-ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager)
- : Module::Interface(std::move(module), std::move(profile_manager), "acc:u1") {
+ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system)
+ : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:u1") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ACC_U1::GetUserCount, "GetUserCount"},
diff --git a/src/core/hle/service/acc/acc_u1.h b/src/core/hle/service/acc/acc_u1.h
index 9e79daee3..829f8a744 100644
--- a/src/core/hle/service/acc/acc_u1.h
+++ b/src/core/hle/service/acc/acc_u1.h
@@ -10,8 +10,8 @@ namespace Service::Account {
class ACC_U1 final : public Module::Interface {
public:
- explicit ACC_U1(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager);
+ explicit ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager,
+ Core::System& system);
~ACC_U1() override;
};
diff --git a/src/core/hle/service/acc/errors.h b/src/core/hle/service/acc/errors.h
new file mode 100644
index 000000000..1f0577239
--- /dev/null
+++ b/src/core/hle/service/acc/errors.h
@@ -0,0 +1,14 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::Account {
+
+constexpr ResultCode ERR_ACCOUNTINFO_BAD_APPLICATION{ErrorModule::Account, 22};
+constexpr ResultCode ERR_ACCOUNTINFO_ALREADY_INITIALIZED{ErrorModule::Account, 41};
+
+} // namespace Service::Account
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index 49aa5908b..8f9986326 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -305,6 +305,17 @@ bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
return true;
}
+bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new,
+ const ProfileData& data_new) {
+ const auto index = GetUserIndex(uuid);
+ if (index.has_value() && SetProfileBase(uuid, profile_new)) {
+ profiles[*index].data = data_new;
+ return true;
+ }
+
+ return false;
+}
+
void ProfileManager::ParseUserSaveFile() {
FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat",
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index fd7abb541..5a6d28925 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -91,6 +91,8 @@ public:
bool RemoveUser(Common::UUID uuid);
bool SetProfileBase(Common::UUID uuid, const ProfileBase& profile_new);
+ bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new,
+ const ProfileData& data_new);
private:
void ParseUserSaveFile();
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 3f201c821..aa2c83937 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -29,7 +29,8 @@
#include "core/hle/service/am/omm.h"
#include "core/hle/service/am/spsm.h"
#include "core/hle/service/am/tcap.h"
-#include "core/hle/service/apm/apm.h"
+#include "core/hle/service/apm/controller.h"
+#include "core/hle/service/apm/interface.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nvflinger/nvflinger.h"
@@ -55,7 +56,8 @@ struct LaunchParameters {
};
static_assert(sizeof(LaunchParameters) == 0x88);
-IWindowController::IWindowController() : ServiceFramework("IWindowController") {
+IWindowController::IWindowController(Core::System& system_)
+ : ServiceFramework("IWindowController"), system{system_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CreateWindow"},
@@ -74,7 +76,7 @@ IWindowController::IWindowController() : ServiceFramework("IWindowController") {
IWindowController::~IWindowController() = default;
void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) {
- const u64 process_id = Core::System::GetInstance().Kernel().CurrentProcess()->GetProcessID();
+ const u64 process_id = system.CurrentProcess()->GetProcessID();
LOG_DEBUG(Service_AM, "called. Process ID=0x{:016X}", process_id);
@@ -230,8 +232,9 @@ IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} {
IDebugFunctions::~IDebugFunctions() = default;
-ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
- : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger)) {
+ISelfController::ISelfController(Core::System& system_,
+ std::shared_ptr<NVFlinger::NVFlinger> nvflinger_)
+ : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger_)) {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Exit"},
@@ -265,13 +268,13 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
{65, nullptr, "ReportUserIsActive"},
{66, nullptr, "GetCurrentIlluminance"},
{67, nullptr, "IsIlluminanceAvailable"},
- {68, nullptr, "SetAutoSleepDisabled"},
- {69, nullptr, "IsAutoSleepDisabled"},
+ {68, &ISelfController::SetAutoSleepDisabled, "SetAutoSleepDisabled"},
+ {69, &ISelfController::IsAutoSleepDisabled, "IsAutoSleepDisabled"},
{70, nullptr, "ReportMultimediaError"},
{71, nullptr, "GetCurrentIlluminanceEx"},
{80, nullptr, "SetWirelessPriorityMode"},
- {90, nullptr, "GetAccumulatedSuspendedTickValue"},
- {91, nullptr, "GetAccumulatedSuspendedTickChangedEvent"},
+ {90, &ISelfController::GetAccumulatedSuspendedTickValue, "GetAccumulatedSuspendedTickValue"},
+ {91, &ISelfController::GetAccumulatedSuspendedTickChangedEvent, "GetAccumulatedSuspendedTickChangedEvent"},
{100, nullptr, "SetAlbumImageTakenNotificationEnabled"},
{1000, nullptr, "GetDebugStorageChannel"},
};
@@ -279,9 +282,18 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
RegisterHandlers(functions);
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system_.Kernel();
launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
"ISelfController:LaunchableEvent");
+
+ // This event is created by AM on the first time GetAccumulatedSuspendedTickChangedEvent() is
+ // called. Yuzu can just create it unconditionally, since it doesn't need to support multiple
+ // ISelfControllers. The event is signaled on creation, and on transition from suspended -> not
+ // suspended if the event has previously been created by a call to
+ // GetAccumulatedSuspendedTickChangedEvent.
+ accumulated_suspended_tick_changed_event = Kernel::WritableEvent::CreateEventPair(
+ kernel, Kernel::ResetType::Manual, "ISelfController:AccumulatedSuspendedTickChangedEvent");
+ accumulated_suspended_tick_changed_event.writable->Signal();
}
ISelfController::~ISelfController() = default;
@@ -444,8 +456,54 @@ void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& c
rb.Push<u32>(idle_time_detection_extension);
}
-AppletMessageQueue::AppletMessageQueue() {
- auto& kernel = Core::System::GetInstance().Kernel();
+void ISelfController::SetAutoSleepDisabled(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ is_auto_sleep_disabled = rp.Pop<bool>();
+
+ // On the system itself, if the previous state of is_auto_sleep_disabled
+ // differed from the current value passed in, it'd signify the internal
+ // window manager to update (and also increment some statistics like update counts)
+ //
+ // It'd also indicate this change to an idle handling context.
+ //
+ // However, given we're emulating this behavior, most of this can be ignored
+ // and it's sufficient to simply set the member variable for querying via
+ // IsAutoSleepDisabled().
+
+ LOG_DEBUG(Service_AM, "called. is_auto_sleep_disabled={}", is_auto_sleep_disabled);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void ISelfController::IsAutoSleepDisabled(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called.");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(is_auto_sleep_disabled);
+}
+
+void ISelfController::GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called.");
+
+ // This command returns the total number of system ticks since ISelfController creation
+ // where the game was suspended. Since Yuzu doesn't implement game suspension, this command
+ // can just always return 0 ticks.
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(0);
+}
+
+void ISelfController::GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called.");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(accumulated_suspended_tick_changed_event.readable);
+}
+
+AppletMessageQueue::AppletMessageQueue(Kernel::KernelCore& kernel) {
on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
"AMMessageQueue:OnMessageRecieved");
on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair(
@@ -492,8 +550,9 @@ void AppletMessageQueue::OperationModeChanged() {
on_operation_mode_changed.writable->Signal();
}
-ICommonStateGetter::ICommonStateGetter(std::shared_ptr<AppletMessageQueue> msg_queue)
- : ServiceFramework("ICommonStateGetter"), msg_queue(std::move(msg_queue)) {
+ICommonStateGetter::ICommonStateGetter(Core::System& system,
+ std::shared_ptr<AppletMessageQueue> msg_queue)
+ : ServiceFramework("ICommonStateGetter"), system(system), msg_queue(std::move(msg_queue)) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
@@ -526,7 +585,7 @@ ICommonStateGetter::ICommonStateGetter(std::shared_ptr<AppletMessageQueue> msg_q
{63, nullptr, "GetHdcpAuthenticationStateChangeEvent"},
{64, nullptr, "SetTvPowerStateMatchingMode"},
{65, nullptr, "GetApplicationIdByContentActionName"},
- {66, nullptr, "SetCpuBoostMode"},
+ {66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"},
{80, nullptr, "PerformSystemButtonPressingIfInFocus"},
{90, nullptr, "SetPerformanceConfigurationChangedNotification"},
{91, nullptr, "GetCurrentPerformanceConfiguration"},
@@ -607,6 +666,16 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext&
}
}
+void ICommonStateGetter::SetCpuBoostMode(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called, forwarding to APM:SYS");
+
+ const auto& sm = system.ServiceManager();
+ const auto apm_sys = sm.GetService<APM::APM_Sys>("apm:sys");
+ ASSERT(apm_sys != nullptr);
+
+ apm_sys->SetCpuBoostMode(ctx);
+}
+
IStorage::IStorage(std::vector<u8> buffer)
: ServiceFramework("IStorage"), buffer(std::move(buffer)) {
// clang-format off
@@ -635,13 +704,11 @@ void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) {
}
void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) {
- const bool use_docked_mode{Settings::values.use_docked_mode};
- LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode);
+ LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(static_cast<u32>(use_docked_mode ? APM::PerformanceMode::Docked
- : APM::PerformanceMode::Handheld));
+ rb.PushEnum(system.GetAPMController().GetCurrentPerformanceMode());
}
class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> {
@@ -871,7 +938,8 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
}
-ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") {
+ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_)
+ : ServiceFramework("ILibraryAppletCreator"), system{system_} {
static const FunctionInfo functions[] = {
{0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
{1, nullptr, "TerminateAllLibraryApplets"},
@@ -893,7 +961,7 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx)
LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}",
static_cast<u32>(applet_id), applet_mode);
- const auto& applet_manager{Core::System::GetInstance().GetAppletManager()};
+ const auto& applet_manager{system.GetAppletManager()};
const auto applet = applet_manager.GetApplet(applet_id);
if (applet == nullptr) {
@@ -931,8 +999,7 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex
const auto handle{rp.Pop<Kernel::Handle>()};
const auto transfer_mem =
- Core::System::GetInstance().CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>(
- handle);
+ system.CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>(handle);
if (transfer_mem == nullptr) {
LOG_ERROR(Service_AM, "shared_mem is a nullpr for handle={:08X}", handle);
@@ -950,7 +1017,8 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex
rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory)));
}
-IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") {
+IApplicationFunctions::IApplicationFunctions(Core::System& system_)
+ : ServiceFramework("IApplicationFunctions"), system{system_} {
// clang-format off
static const FunctionInfo functions[] = {
{1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"},
@@ -989,6 +1057,7 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
{120, nullptr, "ExecuteProgram"},
{121, nullptr, "ClearUserChannel"},
{122, nullptr, "UnpopToUserChannel"},
+ {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"},
{500, nullptr, "StartContinuousRecordingFlushForDebug"},
{1000, nullptr, "CreateMovieMaker"},
{1001, nullptr, "PrepareForJit"},
@@ -996,6 +1065,10 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
// clang-format on
RegisterHandlers(functions);
+
+ auto& kernel = Core::System::GetInstance().Kernel();
+ gpu_error_detected_event = Kernel::WritableEvent::CreateEventPair(
+ kernel, Kernel::ResetType::Manual, "IApplicationFunctions:GpuErrorDetectedSystemEvent");
}
IApplicationFunctions::~IApplicationFunctions() = default;
@@ -1107,7 +1180,7 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
// Get supported languages from NACP, if possible
// Default to 0 (all languages supported)
u32 supported_languages = 0;
- FileSys::PatchManager pm{Core::System::GetInstance().CurrentProcess()->GetTitleID()};
+ FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
const auto res = pm.GetControlMetadata();
if (res.first != nullptr) {
@@ -1115,8 +1188,8 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
}
// Call IApplicationManagerInterface implementation.
- auto& service_manager = Core::System::GetInstance().ServiceManager();
- auto ns_am2 = service_manager.GetService<Service::NS::NS>("ns:am2");
+ auto& service_manager = system.ServiceManager();
+ auto ns_am2 = service_manager.GetService<NS::NS>("ns:am2");
auto app_man = ns_am2->GetApplicationManagerInterface();
// Get desired application language
@@ -1188,8 +1261,8 @@ void IApplicationFunctions::ExtendSaveData(Kernel::HLERequestContext& ctx) {
"new_journal={:016X}",
static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size);
- FileSystem::WriteSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id,
- {new_normal_size, new_journal_size});
+ const auto title_id = system.CurrentProcess()->GetTitleID();
+ FileSystem::WriteSaveDataSize(type, title_id, user_id, {new_normal_size, new_journal_size});
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
@@ -1208,8 +1281,8 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type),
user_id[1], user_id[0]);
- const auto size =
- FileSystem::ReadSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id);
+ const auto title_id = system.CurrentProcess()->GetTitleID();
+ const auto size = FileSystem::ReadSaveDataSize(type, title_id, user_id);
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
@@ -1217,14 +1290,22 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) {
rb.Push(size.journal);
}
+void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(gpu_error_detected_event.readable);
+}
+
void InstallInterfaces(SM::ServiceManager& service_manager,
- std::shared_ptr<NVFlinger::NVFlinger> nvflinger) {
- auto message_queue = std::make_shared<AppletMessageQueue>();
- message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); // Needed on
- // game boot
+ std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) {
+ auto message_queue = std::make_shared<AppletMessageQueue>(system.Kernel());
+ // Needed on game boot
+ message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
- std::make_shared<AppletAE>(nvflinger, message_queue)->InstallAsService(service_manager);
- std::make_shared<AppletOE>(nvflinger, message_queue)->InstallAsService(service_manager);
+ std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
+ std::make_shared<AppletOE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
std::make_shared<IdleSys>()->InstallAsService(service_manager);
std::make_shared<OMM>()->InstallAsService(service_manager);
std::make_shared<SPSM>()->InstallAsService(service_manager);
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 991b7d47c..28f870302 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -10,12 +10,15 @@
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/service.h"
-namespace Service {
-namespace NVFlinger {
+namespace Kernel {
+class KernelCore;
+}
+
+namespace Service::NVFlinger {
class NVFlinger;
}
-namespace AM {
+namespace Service::AM {
enum SystemLanguage {
Japanese = 0,
@@ -47,7 +50,7 @@ public:
PerformanceModeChanged = 31,
};
- AppletMessageQueue();
+ explicit AppletMessageQueue(Kernel::KernelCore& kernel);
~AppletMessageQueue();
const Kernel::SharedPtr<Kernel::ReadableEvent>& GetMesssageRecieveEvent() const;
@@ -65,12 +68,14 @@ private:
class IWindowController final : public ServiceFramework<IWindowController> {
public:
- IWindowController();
+ explicit IWindowController(Core::System& system_);
~IWindowController() override;
private:
void GetAppletResourceUserId(Kernel::HLERequestContext& ctx);
void AcquireForegroundRights(Kernel::HLERequestContext& ctx);
+
+ Core::System& system;
};
class IAudioController final : public ServiceFramework<IAudioController> {
@@ -113,7 +118,8 @@ public:
class ISelfController final : public ServiceFramework<ISelfController> {
public:
- explicit ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger);
+ explicit ISelfController(Core::System& system_,
+ std::shared_ptr<NVFlinger::NVFlinger> nvflinger_);
~ISelfController() override;
private:
@@ -133,16 +139,24 @@ private:
void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx);
void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
+ void SetAutoSleepDisabled(Kernel::HLERequestContext& ctx);
+ void IsAutoSleepDisabled(Kernel::HLERequestContext& ctx);
+ void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx);
+ void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx);
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
Kernel::EventPair launchable_event;
+ Kernel::EventPair accumulated_suspended_tick_changed_event;
+
u32 idle_time_detection_extension = 0;
u64 num_fatal_sections_entered = 0;
+ bool is_auto_sleep_disabled = false;
};
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
public:
- explicit ICommonStateGetter(std::shared_ptr<AppletMessageQueue> msg_queue);
+ explicit ICommonStateGetter(Core::System& system,
+ std::shared_ptr<AppletMessageQueue> msg_queue);
~ICommonStateGetter() override;
private:
@@ -164,7 +178,9 @@ private:
void GetPerformanceMode(Kernel::HLERequestContext& ctx);
void GetBootMode(Kernel::HLERequestContext& ctx);
void GetDefaultDisplayResolution(Kernel::HLERequestContext& ctx);
+ void SetCpuBoostMode(Kernel::HLERequestContext& ctx);
+ Core::System& system;
std::shared_ptr<AppletMessageQueue> msg_queue;
};
@@ -198,18 +214,20 @@ private:
class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> {
public:
- ILibraryAppletCreator();
+ explicit ILibraryAppletCreator(Core::System& system_);
~ILibraryAppletCreator() override;
private:
void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
void CreateStorage(Kernel::HLERequestContext& ctx);
void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx);
+
+ Core::System& system;
};
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
public:
- IApplicationFunctions();
+ explicit IApplicationFunctions(Core::System& system_);
~IApplicationFunctions() override;
private:
@@ -230,6 +248,10 @@ private:
void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx);
void EndBlockingHomeButton(Kernel::HLERequestContext& ctx);
void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx);
+ void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
+
+ Kernel::EventPair gpu_error_detected_event;
+ Core::System& system;
};
class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> {
@@ -261,7 +283,6 @@ public:
/// Registers all AM services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager,
- std::shared_ptr<NVFlinger::NVFlinger> nvflinger);
+ std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system);
-} // namespace AM
-} // namespace Service
+} // namespace Service::AM
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index 488add8e7..e454b77d8 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -4,6 +4,7 @@
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/nvflinger/nvflinger.h"
@@ -13,9 +14,10 @@ namespace Service::AM {
class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> {
public:
explicit ILibraryAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue)
+ std::shared_ptr<AppletMessageQueue> msg_queue,
+ Core::System& system)
: ServiceFramework("ILibraryAppletProxy"), nvflinger(std::move(nvflinger)),
- msg_queue(std::move(msg_queue)) {
+ msg_queue(std::move(msg_queue)), system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ILibraryAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
@@ -40,7 +42,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ICommonStateGetter>(msg_queue);
+ rb.PushIpcInterface<ICommonStateGetter>(system, msg_queue);
}
void GetSelfController(Kernel::HLERequestContext& ctx) {
@@ -48,7 +50,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISelfController>(nvflinger);
+ rb.PushIpcInterface<ISelfController>(system, nvflinger);
}
void GetWindowController(Kernel::HLERequestContext& ctx) {
@@ -56,7 +58,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IWindowController>();
+ rb.PushIpcInterface<IWindowController>(system);
}
void GetAudioController(Kernel::HLERequestContext& ctx) {
@@ -96,7 +98,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ILibraryAppletCreator>();
+ rb.PushIpcInterface<ILibraryAppletCreator>(system);
}
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
@@ -104,19 +106,20 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IApplicationFunctions>();
+ rb.PushIpcInterface<IApplicationFunctions>(system);
}
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
+ Core::System& system;
};
class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> {
public:
explicit ISystemAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue)
+ std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
: ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)),
- msg_queue(std::move(msg_queue)) {
+ msg_queue(std::move(msg_queue)), system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
@@ -143,7 +146,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ICommonStateGetter>(msg_queue);
+ rb.PushIpcInterface<ICommonStateGetter>(system, msg_queue);
}
void GetSelfController(Kernel::HLERequestContext& ctx) {
@@ -151,7 +154,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISelfController>(nvflinger);
+ rb.PushIpcInterface<ISelfController>(system, nvflinger);
}
void GetWindowController(Kernel::HLERequestContext& ctx) {
@@ -159,7 +162,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IWindowController>();
+ rb.PushIpcInterface<IWindowController>(system);
}
void GetAudioController(Kernel::HLERequestContext& ctx) {
@@ -191,7 +194,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ILibraryAppletCreator>();
+ rb.PushIpcInterface<ILibraryAppletCreator>(system);
}
void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) {
@@ -219,6 +222,7 @@ private:
}
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
+ Core::System& system;
};
void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) {
@@ -226,7 +230,7 @@ void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemAppletProxy>(nvflinger, msg_queue);
+ rb.PushIpcInterface<ISystemAppletProxy>(nvflinger, msg_queue, system);
}
void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) {
@@ -234,7 +238,7 @@ void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue);
+ rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system);
}
void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
@@ -242,13 +246,13 @@ void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue);
+ rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system);
}
AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue)
+ std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
: ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)),
- msg_queue(std::move(msg_queue)) {
+ msg_queue(std::move(msg_queue)), system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"},
diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h
index 902db2665..9e006cd9d 100644
--- a/src/core/hle/service/am/applet_ae.h
+++ b/src/core/hle/service/am/applet_ae.h
@@ -18,7 +18,7 @@ namespace AM {
class AppletAE final : public ServiceFramework<AppletAE> {
public:
explicit AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue);
+ std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system);
~AppletAE() override;
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
@@ -30,6 +30,7 @@ private:
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
+ Core::System& system;
};
} // namespace AM
diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp
index d3a0a1568..a2ffaa440 100644
--- a/src/core/hle/service/am/applet_oe.cpp
+++ b/src/core/hle/service/am/applet_oe.cpp
@@ -13,9 +13,9 @@ namespace Service::AM {
class IApplicationProxy final : public ServiceFramework<IApplicationProxy> {
public:
explicit IApplicationProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue)
+ std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
: ServiceFramework("IApplicationProxy"), nvflinger(std::move(nvflinger)),
- msg_queue(std::move(msg_queue)) {
+ msg_queue(std::move(msg_queue)), system(system) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"},
@@ -63,7 +63,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IWindowController>();
+ rb.PushIpcInterface<IWindowController>(system);
}
void GetSelfController(Kernel::HLERequestContext& ctx) {
@@ -71,7 +71,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISelfController>(nvflinger);
+ rb.PushIpcInterface<ISelfController>(system, nvflinger);
}
void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
@@ -79,7 +79,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ICommonStateGetter>(msg_queue);
+ rb.PushIpcInterface<ICommonStateGetter>(system, msg_queue);
}
void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) {
@@ -87,7 +87,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ILibraryAppletCreator>();
+ rb.PushIpcInterface<ILibraryAppletCreator>(system);
}
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
@@ -95,11 +95,12 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IApplicationFunctions>();
+ rb.PushIpcInterface<IApplicationFunctions>(system);
}
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
+ Core::System& system;
};
void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
@@ -107,13 +108,13 @@ void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IApplicationProxy>(nvflinger, msg_queue);
+ rb.PushIpcInterface<IApplicationProxy>(nvflinger, msg_queue, system);
}
AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue)
+ std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
: ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)),
- msg_queue(std::move(msg_queue)) {
+ msg_queue(std::move(msg_queue)), system(system) {
static const FunctionInfo functions[] = {
{0, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"},
};
diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h
index bbd0108ef..22c05419d 100644
--- a/src/core/hle/service/am/applet_oe.h
+++ b/src/core/hle/service/am/applet_oe.h
@@ -18,7 +18,7 @@ namespace AM {
class AppletOE final : public ServiceFramework<AppletOE> {
public:
explicit AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue);
+ std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system);
~AppletOE() override;
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
@@ -28,6 +28,7 @@ private:
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
+ Core::System& system;
};
} // namespace AM
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index 14fa92318..d2e35362f 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -23,8 +23,7 @@
namespace Service::AM::Applets {
-AppletDataBroker::AppletDataBroker() {
- auto& kernel = Core::System::GetInstance().Kernel();
+AppletDataBroker::AppletDataBroker(Kernel::KernelCore& kernel) {
state_changed_event = Kernel::WritableEvent::CreateEventPair(
kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:StateChangedEvent");
pop_out_data_event = Kernel::WritableEvent::CreateEventPair(
@@ -35,12 +34,28 @@ AppletDataBroker::AppletDataBroker() {
AppletDataBroker::~AppletDataBroker() = default;
+AppletDataBroker::RawChannelData AppletDataBroker::PeekDataToAppletForDebug() const {
+ std::vector<std::vector<u8>> out_normal;
+
+ for (const auto& storage : in_channel) {
+ out_normal.push_back(storage->GetData());
+ }
+
+ std::vector<std::vector<u8>> out_interactive;
+
+ for (const auto& storage : in_interactive_channel) {
+ out_interactive.push_back(storage->GetData());
+ }
+
+ return {std::move(out_normal), std::move(out_interactive)};
+}
+
std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() {
if (out_channel.empty())
return nullptr;
auto out = std::move(out_channel.front());
- out_channel.pop();
+ out_channel.pop_front();
return out;
}
@@ -49,7 +64,7 @@ std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() {
return nullptr;
auto out = std::move(in_channel.front());
- in_channel.pop();
+ in_channel.pop_front();
return out;
}
@@ -58,7 +73,7 @@ std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() {
return nullptr;
auto out = std::move(out_interactive_channel.front());
- out_interactive_channel.pop();
+ out_interactive_channel.pop_front();
return out;
}
@@ -67,25 +82,25 @@ std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() {
return nullptr;
auto out = std::move(in_interactive_channel.front());
- in_interactive_channel.pop();
+ in_interactive_channel.pop_front();
return out;
}
void AppletDataBroker::PushNormalDataFromGame(IStorage storage) {
- in_channel.push(std::make_unique<IStorage>(storage));
+ in_channel.push_back(std::make_unique<IStorage>(storage));
}
void AppletDataBroker::PushNormalDataFromApplet(IStorage storage) {
- out_channel.push(std::make_unique<IStorage>(storage));
+ out_channel.push_back(std::make_unique<IStorage>(storage));
pop_out_data_event.writable->Signal();
}
void AppletDataBroker::PushInteractiveDataFromGame(IStorage storage) {
- in_interactive_channel.push(std::make_unique<IStorage>(storage));
+ in_interactive_channel.push_back(std::make_unique<IStorage>(storage));
}
void AppletDataBroker::PushInteractiveDataFromApplet(IStorage storage) {
- out_interactive_channel.push(std::make_unique<IStorage>(storage));
+ out_interactive_channel.push_back(std::make_unique<IStorage>(storage));
pop_interactive_out_data_event.writable->Signal();
}
@@ -105,7 +120,7 @@ Kernel::SharedPtr<Kernel::ReadableEvent> AppletDataBroker::GetStateChangedEvent(
return state_changed_event.readable;
}
-Applet::Applet() = default;
+Applet::Applet(Kernel::KernelCore& kernel_) : broker{kernel_} {}
Applet::~Applet() = default;
@@ -123,12 +138,14 @@ void Applet::Initialize() {
AppletFrontendSet::AppletFrontendSet() = default;
-AppletFrontendSet::AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer,
- ProfileSelect profile_select,
- SoftwareKeyboard software_keyboard, WebBrowser web_browser)
- : error{std::move(error)}, photo_viewer{std::move(photo_viewer)}, profile_select{std::move(
- profile_select)},
- software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)} {}
+AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
+ PhotoViewer photo_viewer, ProfileSelect profile_select,
+ SoftwareKeyboard software_keyboard, WebBrowser web_browser,
+ ECommerceApplet e_commerce)
+ : parental_controls{std::move(parental_controls)}, error{std::move(error)},
+ photo_viewer{std::move(photo_viewer)}, profile_select{std::move(profile_select)},
+ software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)},
+ e_commerce{std::move(e_commerce)} {}
AppletFrontendSet::~AppletFrontendSet() = default;
@@ -136,11 +153,13 @@ AppletFrontendSet::AppletFrontendSet(AppletFrontendSet&&) noexcept = default;
AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default;
-AppletManager::AppletManager() = default;
+AppletManager::AppletManager(Core::System& system_) : system{system_} {}
AppletManager::~AppletManager() = default;
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
+ if (set.parental_controls != nullptr)
+ frontend.parental_controls = std::move(set.parental_controls);
if (set.error != nullptr)
frontend.error = std::move(set.error);
if (set.photo_viewer != nullptr)
@@ -151,17 +170,21 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
frontend.software_keyboard = std::move(set.software_keyboard);
if (set.web_browser != nullptr)
frontend.web_browser = std::move(set.web_browser);
+ if (set.e_commerce != nullptr)
+ frontend.e_commerce = std::move(set.e_commerce);
}
void AppletManager::SetDefaultAppletFrontendSet() {
- frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
- frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>();
- frontend.profile_select = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
- frontend.software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
- frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
+ ClearAll();
+ SetDefaultAppletsIfMissing();
}
void AppletManager::SetDefaultAppletsIfMissing() {
+ if (frontend.parental_controls == nullptr) {
+ frontend.parental_controls =
+ std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
+ }
+
if (frontend.error == nullptr) {
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
}
@@ -182,6 +205,10 @@ void AppletManager::SetDefaultAppletsIfMissing() {
if (frontend.web_browser == nullptr) {
frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
}
+
+ if (frontend.e_commerce == nullptr) {
+ frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>();
+ }
}
void AppletManager::ClearAll() {
@@ -190,21 +217,26 @@ void AppletManager::ClearAll() {
std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
switch (id) {
+ case AppletId::Auth:
+ return std::make_shared<Auth>(system, *frontend.parental_controls);
case AppletId::Error:
- return std::make_shared<Error>(*frontend.error);
+ return std::make_shared<Error>(system, *frontend.error);
case AppletId::ProfileSelect:
- return std::make_shared<ProfileSelect>(*frontend.profile_select);
+ return std::make_shared<ProfileSelect>(system, *frontend.profile_select);
case AppletId::SoftwareKeyboard:
- return std::make_shared<SoftwareKeyboard>(*frontend.software_keyboard);
+ return std::make_shared<SoftwareKeyboard>(system, *frontend.software_keyboard);
case AppletId::PhotoViewer:
- return std::make_shared<PhotoViewer>(*frontend.photo_viewer);
+ return std::make_shared<PhotoViewer>(system, *frontend.photo_viewer);
+ case AppletId::LibAppletShop:
+ return std::make_shared<WebBrowser>(system, *frontend.web_browser,
+ frontend.e_commerce.get());
case AppletId::LibAppletOff:
- return std::make_shared<WebBrowser>(*frontend.web_browser);
+ return std::make_shared<WebBrowser>(system, *frontend.web_browser);
default:
UNIMPLEMENTED_MSG(
"No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
static_cast<u8>(id));
- return std::make_shared<StubApplet>();
+ return std::make_shared<StubApplet>(system, id);
}
}
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index b46e10a4a..764c3418c 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -12,14 +12,24 @@
union ResultCode;
+namespace Core {
+class System;
+}
+
namespace Core::Frontend {
+class ECommerceApplet;
class ErrorApplet;
+class ParentalControlsApplet;
class PhotoViewerApplet;
class ProfileSelectApplet;
class SoftwareKeyboardApplet;
class WebBrowserApplet;
} // namespace Core::Frontend
+namespace Kernel {
+class KernelCore;
+}
+
namespace Service::AM {
class IStorage;
@@ -51,9 +61,17 @@ enum class AppletId : u32 {
class AppletDataBroker final {
public:
- AppletDataBroker();
+ explicit AppletDataBroker(Kernel::KernelCore& kernel_);
~AppletDataBroker();
+ struct RawChannelData {
+ std::vector<std::vector<u8>> normal;
+ std::vector<std::vector<u8>> interactive;
+ };
+
+ // Retrieves but does not pop the data sent to applet.
+ RawChannelData PeekDataToAppletForDebug() const;
+
std::unique_ptr<IStorage> PopNormalDataToGame();
std::unique_ptr<IStorage> PopNormalDataToApplet();
@@ -76,16 +94,16 @@ private:
// Queues are named from applet's perspective
// PopNormalDataToApplet and PushNormalDataFromGame
- std::queue<std::unique_ptr<IStorage>> in_channel;
+ std::deque<std::unique_ptr<IStorage>> in_channel;
// PopNormalDataToGame and PushNormalDataFromApplet
- std::queue<std::unique_ptr<IStorage>> out_channel;
+ std::deque<std::unique_ptr<IStorage>> out_channel;
// PopInteractiveDataToApplet and PushInteractiveDataFromGame
- std::queue<std::unique_ptr<IStorage>> in_interactive_channel;
+ std::deque<std::unique_ptr<IStorage>> in_interactive_channel;
// PopInteractiveDataToGame and PushInteractiveDataFromApplet
- std::queue<std::unique_ptr<IStorage>> out_interactive_channel;
+ std::deque<std::unique_ptr<IStorage>> out_interactive_channel;
Kernel::EventPair state_changed_event;
@@ -98,7 +116,7 @@ private:
class Applet {
public:
- Applet();
+ explicit Applet(Kernel::KernelCore& kernel_);
virtual ~Applet();
virtual void Initialize();
@@ -137,15 +155,19 @@ protected:
};
struct AppletFrontendSet {
+ using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>;
using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>;
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
+ using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>;
AppletFrontendSet();
- AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer, ProfileSelect profile_select,
- SoftwareKeyboard software_keyboard, WebBrowser web_browser);
+ AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
+ PhotoViewer photo_viewer, ProfileSelect profile_select,
+ SoftwareKeyboard software_keyboard, WebBrowser web_browser,
+ ECommerceApplet e_commerce);
~AppletFrontendSet();
AppletFrontendSet(const AppletFrontendSet&) = delete;
@@ -154,16 +176,18 @@ struct AppletFrontendSet {
AppletFrontendSet(AppletFrontendSet&&) noexcept;
AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
+ ParentalControlsApplet parental_controls;
ErrorApplet error;
PhotoViewer photo_viewer;
ProfileSelect profile_select;
SoftwareKeyboard software_keyboard;
WebBrowser web_browser;
+ ECommerceApplet e_commerce;
};
class AppletManager {
public:
- AppletManager();
+ explicit AppletManager(Core::System& system_);
~AppletManager();
void SetAppletFrontendSet(AppletFrontendSet set);
@@ -175,6 +199,7 @@ public:
private:
AppletFrontendSet frontend;
+ Core::System& system;
};
} // namespace Applets
diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp
index 04774bedc..a7db26725 100644
--- a/src/core/hle/service/am/applets/error.cpp
+++ b/src/core/hle/service/am/applets/error.cpp
@@ -9,8 +9,10 @@
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/error.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/error.h"
+#include "core/reporter.h"
namespace Service::AM::Applets {
@@ -83,7 +85,8 @@ ResultCode Decode64BitError(u64 error) {
} // Anonymous namespace
-Error::Error(const Core::Frontend::ErrorApplet& frontend) : frontend(frontend) {}
+Error::Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_)
+ : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
Error::~Error() = default;
@@ -143,9 +146,12 @@ void Error::Execute() {
}
const auto callback = [this] { DisplayCompleted(); };
+ const auto title_id = system.CurrentProcess()->GetTitleID();
+ const auto& reporter{system.GetReporter()};
switch (mode) {
case ErrorAppletMode::ShowError:
+ reporter.SaveErrorReport(title_id, error_code);
frontend.ShowError(error_code, callback);
break;
case ErrorAppletMode::ShowSystemError:
@@ -156,14 +162,18 @@ void Error::Execute() {
const auto& detail_text =
system ? args->system_error.detail_text : args->application_error.detail_text;
- frontend.ShowCustomErrorText(
- error_code,
- Common::StringFromFixedZeroTerminatedBuffer(main_text.data(), main_text.size()),
- Common::StringFromFixedZeroTerminatedBuffer(detail_text.data(), detail_text.size()),
- callback);
+ const auto main_text_string =
+ Common::StringFromFixedZeroTerminatedBuffer(main_text.data(), main_text.size());
+ const auto detail_text_string =
+ Common::StringFromFixedZeroTerminatedBuffer(detail_text.data(), detail_text.size());
+
+ reporter.SaveErrorReport(title_id, error_code, main_text_string, detail_text_string);
+ frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback);
break;
}
case ErrorAppletMode::ShowErrorRecord:
+ reporter.SaveErrorReport(title_id, error_code,
+ fmt::format("{:016X}", args->error_record.posix_time));
frontend.ShowErrorWithTimestamp(
error_code, std::chrono::seconds{args->error_record.posix_time}, callback);
break;
diff --git a/src/core/hle/service/am/applets/error.h b/src/core/hle/service/am/applets/error.h
index a3590d181..a105cdb0c 100644
--- a/src/core/hle/service/am/applets/error.h
+++ b/src/core/hle/service/am/applets/error.h
@@ -7,6 +7,10 @@
#include "core/hle/result.h"
#include "core/hle/service/am/applets/applets.h"
+namespace Core {
+class System;
+}
+
namespace Service::AM::Applets {
enum class ErrorAppletMode : u8 {
@@ -21,7 +25,7 @@ enum class ErrorAppletMode : u8 {
class Error final : public Applet {
public:
- explicit Error(const Core::Frontend::ErrorApplet& frontend);
+ explicit Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_);
~Error() override;
void Initialize() override;
@@ -42,6 +46,7 @@ private:
std::unique_ptr<ErrorArguments> args;
bool complete = false;
+ Core::System& system;
};
} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/general_backend.cpp b/src/core/hle/service/am/applets/general_backend.cpp
index c591b9ac2..328438a1d 100644
--- a/src/core/hle/service/am/applets/general_backend.cpp
+++ b/src/core/hle/service/am/applets/general_backend.cpp
@@ -2,7 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <string>
+#include <string_view>
#include "common/assert.h"
#include "common/hex_util.h"
@@ -13,28 +13,147 @@
#include "core/hle/result.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/general_backend.h"
+#include "core/reporter.h"
namespace Service::AM::Applets {
-static void LogCurrentStorage(AppletDataBroker& broker, std::string prefix) {
+constexpr ResultCode ERROR_INVALID_PIN{ErrorModule::PCTL, 221};
+
+static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) {
std::unique_ptr<IStorage> storage = broker.PopNormalDataToApplet();
for (; storage != nullptr; storage = broker.PopNormalDataToApplet()) {
const auto data = storage->GetData();
LOG_INFO(Service_AM,
- "called (STUBBED), during {} recieved normal data with size={:08X}, data={}",
- prefix, data.size(), Common::HexVectorToString(data));
+ "called (STUBBED), during {} received normal data with size={:08X}, data={}",
+ prefix, data.size(), Common::HexToString(data));
}
storage = broker.PopInteractiveDataToApplet();
for (; storage != nullptr; storage = broker.PopInteractiveDataToApplet()) {
const auto data = storage->GetData();
LOG_INFO(Service_AM,
- "called (STUBBED), during {} recieved interactive data with size={:08X}, data={}",
- prefix, data.size(), Common::HexVectorToString(data));
+ "called (STUBBED), during {} received interactive data with size={:08X}, data={}",
+ prefix, data.size(), Common::HexToString(data));
}
}
-PhotoViewer::PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend) : frontend(frontend) {}
+Auth::Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_)
+ : Applet{system_.Kernel()}, frontend(frontend_) {}
+
+Auth::~Auth() = default;
+
+void Auth::Initialize() {
+ Applet::Initialize();
+ complete = false;
+
+ const auto storage = broker.PopNormalDataToApplet();
+ ASSERT(storage != nullptr);
+ const auto data = storage->GetData();
+ ASSERT(data.size() >= 0xC);
+
+ struct Arg {
+ INSERT_PADDING_BYTES(4);
+ AuthAppletType type;
+ u8 arg0;
+ u8 arg1;
+ u8 arg2;
+ INSERT_PADDING_BYTES(1);
+ };
+ static_assert(sizeof(Arg) == 0xC, "Arg (AuthApplet) has incorrect size.");
+
+ Arg arg{};
+ std::memcpy(&arg, data.data(), sizeof(Arg));
+
+ type = arg.type;
+ arg0 = arg.arg0;
+ arg1 = arg.arg1;
+ arg2 = arg.arg2;
+}
+
+bool Auth::TransactionComplete() const {
+ return complete;
+}
+
+ResultCode Auth::GetStatus() const {
+ return successful ? RESULT_SUCCESS : ERROR_INVALID_PIN;
+}
+
+void Auth::ExecuteInteractive() {
+ UNREACHABLE_MSG("Unexpected interactive applet data.");
+}
+
+void Auth::Execute() {
+ if (complete) {
+ return;
+ }
+
+ const auto unimplemented_log = [this] {
+ UNIMPLEMENTED_MSG("Unimplemented Auth applet type for type={:08X}, arg0={:02X}, "
+ "arg1={:02X}, arg2={:02X}",
+ static_cast<u32>(type), arg0, arg1, arg2);
+ };
+
+ switch (type) {
+ case AuthAppletType::ShowParentalAuthentication: {
+ const auto callback = [this](bool successful) { AuthFinished(successful); };
+
+ if (arg0 == 1 && arg1 == 0 && arg2 == 1) {
+ // ShowAuthenticatorForConfiguration
+ frontend.VerifyPINForSettings(callback);
+ } else if (arg1 == 0 && arg2 == 0) {
+ // ShowParentalAuthentication(bool)
+ frontend.VerifyPIN(callback, static_cast<bool>(arg0));
+ } else {
+ unimplemented_log();
+ }
+ break;
+ }
+ case AuthAppletType::RegisterParentalPasscode: {
+ const auto callback = [this] { AuthFinished(true); };
+
+ if (arg0 == 0 && arg1 == 0 && arg2 == 0) {
+ // RegisterParentalPasscode
+ frontend.RegisterPIN(callback);
+ } else {
+ unimplemented_log();
+ }
+ break;
+ }
+ case AuthAppletType::ChangeParentalPasscode: {
+ const auto callback = [this] { AuthFinished(true); };
+
+ if (arg0 == 0 && arg1 == 0 && arg2 == 0) {
+ // ChangeParentalPasscode
+ frontend.ChangePIN(callback);
+ } else {
+ unimplemented_log();
+ }
+ break;
+ }
+ default:
+ unimplemented_log();
+ }
+}
+
+void Auth::AuthFinished(bool successful) {
+ this->successful = successful;
+
+ struct Return {
+ ResultCode result_code;
+ };
+ static_assert(sizeof(Return) == 0x4, "Return (AuthApplet) has incorrect size.");
+
+ Return return_{GetStatus()};
+
+ std::vector<u8> out(sizeof(Return));
+ std::memcpy(out.data(), &return_, sizeof(Return));
+
+ broker.PushNormalDataFromApplet(IStorage{out});
+ broker.SignalStateChanged();
+}
+
+PhotoViewer::PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_)
+ : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
PhotoViewer::~PhotoViewer() = default;
@@ -68,7 +187,7 @@ void PhotoViewer::Execute() {
const auto callback = [this] { ViewFinished(); };
switch (mode) {
case PhotoViewerAppletMode::CurrentApp:
- frontend.ShowPhotosForApplication(Core::CurrentProcess()->GetTitleID(), callback);
+ frontend.ShowPhotosForApplication(system.CurrentProcess()->GetTitleID(), callback);
break;
case PhotoViewerAppletMode::AllApps:
frontend.ShowAllPhotos(callback);
@@ -83,13 +202,21 @@ void PhotoViewer::ViewFinished() {
broker.SignalStateChanged();
}
-StubApplet::StubApplet() = default;
+StubApplet::StubApplet(Core::System& system_, AppletId id_)
+ : Applet{system_.Kernel()}, id(id_), system{system_} {}
StubApplet::~StubApplet() = default;
void StubApplet::Initialize() {
LOG_WARNING(Service_AM, "called (STUBBED)");
Applet::Initialize();
+
+ const auto data = broker.PeekDataToAppletForDebug();
+ system.GetReporter().SaveUnimplementedAppletReport(
+ static_cast<u32>(id), common_args.arguments_version, common_args.library_version,
+ common_args.theme_color, common_args.play_startup_sound, common_args.system_tick,
+ data.normal, data.interactive);
+
LogCurrentStorage(broker, "Initialize");
}
diff --git a/src/core/hle/service/am/applets/general_backend.h b/src/core/hle/service/am/applets/general_backend.h
index 2dd255d7c..cfa2df369 100644
--- a/src/core/hle/service/am/applets/general_backend.h
+++ b/src/core/hle/service/am/applets/general_backend.h
@@ -6,8 +6,42 @@
#include "core/hle/service/am/applets/applets.h"
+namespace Core {
+class System;
+}
+
namespace Service::AM::Applets {
+enum class AuthAppletType : u32 {
+ ShowParentalAuthentication,
+ RegisterParentalPasscode,
+ ChangeParentalPasscode,
+};
+
+class Auth final : public Applet {
+public:
+ explicit Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_);
+ ~Auth() override;
+
+ void Initialize() override;
+ bool TransactionComplete() const override;
+ ResultCode GetStatus() const override;
+ void ExecuteInteractive() override;
+ void Execute() override;
+
+ void AuthFinished(bool successful = true);
+
+private:
+ Core::Frontend::ParentalControlsApplet& frontend;
+ bool complete = false;
+ bool successful = false;
+
+ AuthAppletType type = AuthAppletType::ShowParentalAuthentication;
+ u8 arg0 = 0;
+ u8 arg1 = 0;
+ u8 arg2 = 0;
+};
+
enum class PhotoViewerAppletMode : u8 {
CurrentApp = 0,
AllApps = 1,
@@ -15,7 +49,7 @@ enum class PhotoViewerAppletMode : u8 {
class PhotoViewer final : public Applet {
public:
- explicit PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend);
+ explicit PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_);
~PhotoViewer() override;
void Initialize() override;
@@ -30,11 +64,12 @@ private:
const Core::Frontend::PhotoViewerApplet& frontend;
bool complete = false;
PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp;
+ Core::System& system;
};
class StubApplet final : public Applet {
public:
- StubApplet();
+ explicit StubApplet(Core::System& system_, AppletId id_);
~StubApplet() override;
void Initialize() override;
@@ -43,6 +78,10 @@ public:
ResultCode GetStatus() const override;
void ExecuteInteractive() override;
void Execute() override;
+
+private:
+ AppletId id;
+ Core::System& system;
};
} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp
index 57b5419e8..3eba696ca 100644
--- a/src/core/hle/service/am/applets/profile_select.cpp
+++ b/src/core/hle/service/am/applets/profile_select.cpp
@@ -15,8 +15,9 @@ namespace Service::AM::Applets {
constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1};
-ProfileSelect::ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend)
- : frontend(frontend) {}
+ProfileSelect::ProfileSelect(Core::System& system_,
+ const Core::Frontend::ProfileSelectApplet& frontend_)
+ : Applet{system_.Kernel()}, frontend(frontend_) {}
ProfileSelect::~ProfileSelect() = default;
diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h
index 563cd744a..16364ead7 100644
--- a/src/core/hle/service/am/applets/profile_select.h
+++ b/src/core/hle/service/am/applets/profile_select.h
@@ -11,6 +11,10 @@
#include "core/hle/result.h"
#include "core/hle/service/am/applets/applets.h"
+namespace Core {
+class System;
+}
+
namespace Service::AM::Applets {
struct UserSelectionConfig {
@@ -29,7 +33,8 @@ static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has inco
class ProfileSelect final : public Applet {
public:
- explicit ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend);
+ explicit ProfileSelect(Core::System& system_,
+ const Core::Frontend::ProfileSelectApplet& frontend_);
~ProfileSelect() override;
void Initialize() override;
diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp
index e197990f7..748559cd0 100644
--- a/src/core/hle/service/am/applets/software_keyboard.cpp
+++ b/src/core/hle/service/am/applets/software_keyboard.cpp
@@ -39,8 +39,9 @@ static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters(
return params;
}
-SoftwareKeyboard::SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend)
- : frontend(frontend) {}
+SoftwareKeyboard::SoftwareKeyboard(Core::System& system_,
+ const Core::Frontend::SoftwareKeyboardApplet& frontend_)
+ : Applet{system_.Kernel()}, frontend(frontend_) {}
SoftwareKeyboard::~SoftwareKeyboard() = default;
diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h
index 0fbc43e51..ef4801fc6 100644
--- a/src/core/hle/service/am/applets/software_keyboard.h
+++ b/src/core/hle/service/am/applets/software_keyboard.h
@@ -16,6 +16,10 @@
union ResultCode;
+namespace Core {
+class System;
+}
+
namespace Service::AM::Applets {
enum class KeysetDisable : u32 {
@@ -55,7 +59,8 @@ static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect siz
class SoftwareKeyboard final : public Applet {
public:
- explicit SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend);
+ explicit SoftwareKeyboard(Core::System& system_,
+ const Core::Frontend::SoftwareKeyboardApplet& frontend_);
~SoftwareKeyboard() override;
void Initialize() override;
diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp
index 7878f5136..32283e819 100644
--- a/src/core/hle/service/am/applets/web_browser.cpp
+++ b/src/core/hle/service/am/applets/web_browser.cpp
@@ -19,7 +19,9 @@
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
+#include "core/file_sys/system_archive/system_archive.h"
#include "core/file_sys/vfs_types.h"
+#include "core/frontend/applets/general_frontend.h"
#include "core/frontend/applets/web_browser.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/am/applets/web_browser.h"
@@ -28,74 +30,188 @@
namespace Service::AM::Applets {
-// TODO(DarkLordZach): There are other arguments in the WebBuffer structure that are currently not
-// parsed, for example footer mode and left stick mode. Some of these are not particularly relevant,
-// but some may be worth an implementation.
-constexpr u16 WEB_ARGUMENT_URL_TYPE = 0x6;
+enum class WebArgTLVType : u16 {
+ InitialURL = 0x1,
+ ShopArgumentsURL = 0x2, ///< TODO(DarkLordZach): This is not the official name.
+ CallbackURL = 0x3,
+ CallbackableURL = 0x4,
+ ApplicationID = 0x5,
+ DocumentPath = 0x6,
+ DocumentKind = 0x7,
+ SystemDataID = 0x8,
+ ShareStartPage = 0x9,
+ Whitelist = 0xA,
+ News = 0xB,
+ UserID = 0xE,
+ AlbumEntry0 = 0xF,
+ ScreenShotEnabled = 0x10,
+ EcClientCertEnabled = 0x11,
+ Unk12 = 0x12,
+ PlayReportEnabled = 0x13,
+ Unk14 = 0x14,
+ Unk15 = 0x15,
+ BootDisplayKind = 0x17,
+ BackgroundKind = 0x18,
+ FooterEnabled = 0x19,
+ PointerEnabled = 0x1A,
+ LeftStickMode = 0x1B,
+ KeyRepeatFrame1 = 0x1C,
+ KeyRepeatFrame2 = 0x1D,
+ BootAsMediaPlayerInv = 0x1E,
+ DisplayUrlKind = 0x1F,
+ BootAsMediaPlayer = 0x21,
+ ShopJumpEnabled = 0x22,
+ MediaAutoPlayEnabled = 0x23,
+ LobbyParameter = 0x24,
+ ApplicationAlbumEntry = 0x26,
+ JsExtensionEnabled = 0x27,
+ AdditionalCommentText = 0x28,
+ TouchEnabledOnContents = 0x29,
+ UserAgentAdditionalString = 0x2A,
+ AdditionalMediaData0 = 0x2B,
+ MediaPlayerAutoCloseEnabled = 0x2C,
+ PageCacheEnabled = 0x2D,
+ WebAudioEnabled = 0x2E,
+ Unk2F = 0x2F,
+ YouTubeVideoWhitelist = 0x31,
+ FooterFixedKind = 0x32,
+ PageFadeEnabled = 0x33,
+ MediaCreatorApplicationRatingAge = 0x34,
+ BootLoadingIconEnabled = 0x35,
+ PageScrollIndicationEnabled = 0x36,
+ MediaPlayerSpeedControlEnabled = 0x37,
+ AlbumEntry1 = 0x38,
+ AlbumEntry2 = 0x39,
+ AlbumEntry3 = 0x3A,
+ AdditionalMediaData1 = 0x3B,
+ AdditionalMediaData2 = 0x3C,
+ AdditionalMediaData3 = 0x3D,
+ BootFooterButton = 0x3E,
+ OverrideWebAudioVolume = 0x3F,
+ OverrideMediaAudioVolume = 0x40,
+ BootMode = 0x41,
+ WebSessionEnabled = 0x42,
+};
+
+enum class ShimKind : u32 {
+ Shop = 1,
+ Login = 2,
+ Offline = 3,
+ Share = 4,
+ Web = 5,
+ Wifi = 6,
+ Lobby = 7,
+};
+
+enum class ShopWebTarget {
+ ApplicationInfo,
+ AddOnContentList,
+ SubscriptionList,
+ ConsumableItemList,
+ Home,
+ Settings,
+};
+
+namespace {
-struct WebBufferHeader {
+constexpr std::size_t SHIM_KIND_COUNT = 0x8;
+
+struct WebArgHeader {
u16 count;
- INSERT_PADDING_BYTES(6);
+ INSERT_PADDING_BYTES(2);
+ ShimKind kind;
};
-static_assert(sizeof(WebBufferHeader) == 0x8, "WebBufferHeader has incorrect size.");
+static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size.");
-struct WebArgumentHeader {
- u16 type;
+struct WebArgTLV {
+ WebArgTLVType type;
u16 size;
u32 offset;
};
-static_assert(sizeof(WebArgumentHeader) == 0x8, "WebArgumentHeader has incorrect size.");
+static_assert(sizeof(WebArgTLV) == 0x8, "WebArgTLV has incorrect size.");
-struct WebArgumentResult {
+struct WebCommonReturnValue {
u32 result_code;
+ INSERT_PADDING_BYTES(0x4);
std::array<char, 0x1000> last_url;
u64 last_url_size;
};
-static_assert(sizeof(WebArgumentResult) == 0x1010, "WebArgumentResult has incorrect size.");
-
-static std::vector<u8> GetArgumentDataForTagType(const std::vector<u8>& data, u16 type) {
- WebBufferHeader header;
- ASSERT(sizeof(WebBufferHeader) <= data.size());
- std::memcpy(&header, data.data(), sizeof(WebBufferHeader));
-
- u64 offset = sizeof(WebBufferHeader);
- for (u16 i = 0; i < header.count; ++i) {
- WebArgumentHeader arg;
- ASSERT(offset + sizeof(WebArgumentHeader) <= data.size());
- std::memcpy(&arg, data.data() + offset, sizeof(WebArgumentHeader));
- offset += sizeof(WebArgumentHeader);
-
- if (arg.type == type) {
- std::vector<u8> out(arg.size);
- offset += arg.offset;
- ASSERT(offset + arg.size <= data.size());
- std::memcpy(out.data(), data.data() + offset, out.size());
+static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
+
+struct WebWifiPageArg {
+ INSERT_PADDING_BYTES(4);
+ std::array<char, 0x100> connection_test_url;
+ std::array<char, 0x400> initial_url;
+ std::array<u8, 0x10> nifm_network_uuid;
+ u32 nifm_requirement;
+};
+static_assert(sizeof(WebWifiPageArg) == 0x518, "WebWifiPageArg has incorrect size.");
+
+struct WebWifiReturnValue {
+ INSERT_PADDING_BYTES(4);
+ u32 result;
+};
+static_assert(sizeof(WebWifiReturnValue) == 0x8, "WebWifiReturnValue has incorrect size.");
+
+enum class OfflineWebSource : u32 {
+ OfflineHtmlPage = 0x1,
+ ApplicationLegalInformation = 0x2,
+ SystemDataPage = 0x3,
+};
+
+std::map<WebArgTLVType, std::vector<u8>> GetWebArguments(const std::vector<u8>& arg) {
+ if (arg.size() < sizeof(WebArgHeader))
+ return {};
+
+ WebArgHeader header{};
+ std::memcpy(&header, arg.data(), sizeof(WebArgHeader));
+
+ std::map<WebArgTLVType, std::vector<u8>> out;
+ u64 offset = sizeof(WebArgHeader);
+ for (std::size_t i = 0; i < header.count; ++i) {
+ if (arg.size() < (offset + sizeof(WebArgTLV)))
+ return out;
+
+ WebArgTLV tlv{};
+ std::memcpy(&tlv, arg.data() + offset, sizeof(WebArgTLV));
+ offset += sizeof(WebArgTLV);
+
+ offset += tlv.offset;
+ if (arg.size() < (offset + tlv.size))
return out;
- }
- offset += arg.offset + arg.size;
+ std::vector<u8> data(tlv.size);
+ std::memcpy(data.data(), arg.data() + offset, tlv.size);
+ offset += tlv.size;
+
+ out.insert_or_assign(tlv.type, data);
}
- return {};
+ return out;
}
-static FileSys::VirtualFile GetManualRomFS() {
- auto& loader{Core::System::GetInstance().GetAppLoader()};
+FileSys::VirtualFile GetApplicationRomFS(const Core::System& system, u64 title_id,
+ FileSys::ContentRecordType type) {
+ const auto& installed{system.GetContentProvider()};
+ const auto res = installed.GetEntry(title_id, type);
- FileSys::VirtualFile out;
- if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success)
- return out;
+ if (res != nullptr) {
+ return res->GetRomFS();
+ }
- const auto& installed{Core::System::GetInstance().GetContentProvider()};
- const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(),
- FileSys::ContentRecordType::Manual);
+ if (type == FileSys::ContentRecordType::Data) {
+ return FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
+ }
- if (res != nullptr)
- return res->GetRomFS();
return nullptr;
}
-WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend) : frontend(frontend) {}
+} // Anonymous namespace
+
+WebBrowser::WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_,
+ Core::Frontend::ECommerceApplet* frontend_e_commerce_)
+ : Applet{system_.Kernel()}, frontend(frontend_),
+ frontend_e_commerce(frontend_e_commerce_), system{system_} {}
WebBrowser::~WebBrowser() = default;
@@ -111,24 +227,12 @@ void WebBrowser::Initialize() {
ASSERT(web_arg_storage != nullptr);
const auto& web_arg = web_arg_storage->GetData();
- const auto url_data = GetArgumentDataForTagType(web_arg, WEB_ARGUMENT_URL_TYPE);
- filename = Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(url_data.data()), url_data.size());
+ ASSERT(web_arg.size() >= 0x8);
+ std::memcpy(&kind, web_arg.data() + 0x4, sizeof(ShimKind));
- temporary_dir = FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
- "web_applet_manual",
- FileUtil::DirectorySeparator::PlatformDefault);
- FileUtil::DeleteDirRecursively(temporary_dir);
-
- manual_romfs = GetManualRomFS();
- if (manual_romfs == nullptr) {
- status = ResultCode(-1);
- LOG_ERROR(Service_AM, "Failed to find manual for current process!");
- }
+ args = GetWebArguments(web_arg);
- filename =
- FileUtil::SanitizePath(temporary_dir + DIR_SEP + "html-document" + DIR_SEP + filename,
- FileUtil::DirectorySeparator::PlatformDefault);
+ InitializeInternal();
}
bool WebBrowser::TransactionComplete() const {
@@ -144,25 +248,26 @@ void WebBrowser::ExecuteInteractive() {
}
void WebBrowser::Execute() {
- if (complete)
+ if (complete) {
return;
+ }
if (status != RESULT_SUCCESS) {
complete = true;
return;
}
- frontend.OpenPage(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
+ ExecuteInternal();
}
void WebBrowser::UnpackRomFS() {
if (unpacked)
return;
- ASSERT(manual_romfs != nullptr);
+ ASSERT(offline_romfs != nullptr);
const auto dir =
- FileSys::ExtractRomFS(manual_romfs, FileSys::RomFSExtractionType::SingleDiscard);
- const auto& vfs{Core::System::GetInstance().GetFilesystem()};
+ FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
+ const auto& vfs{system.GetFilesystem()};
const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
FileSys::VfsRawCopyD(dir, temp_dir);
@@ -172,17 +277,275 @@ void WebBrowser::UnpackRomFS() {
void WebBrowser::Finalize() {
complete = true;
- WebArgumentResult out{};
+ WebCommonReturnValue out{};
out.result_code = 0;
out.last_url_size = 0;
- std::vector<u8> data(sizeof(WebArgumentResult));
- std::memcpy(data.data(), &out, sizeof(WebArgumentResult));
+ std::vector<u8> data(sizeof(WebCommonReturnValue));
+ std::memcpy(data.data(), &out, sizeof(WebCommonReturnValue));
broker.PushNormalDataFromApplet(IStorage{data});
broker.SignalStateChanged();
+ if (!temporary_dir.empty() && FileUtil::IsDirectory(temporary_dir)) {
+ FileUtil::DeleteDirRecursively(temporary_dir);
+ }
+}
+
+void WebBrowser::InitializeInternal() {
+ using WebAppletInitializer = void (WebBrowser::*)();
+
+ constexpr std::array<WebAppletInitializer, SHIM_KIND_COUNT> functions{
+ nullptr, &WebBrowser::InitializeShop,
+ nullptr, &WebBrowser::InitializeOffline,
+ nullptr, nullptr,
+ nullptr, nullptr,
+ };
+
+ const auto index = static_cast<u32>(kind);
+
+ if (index > functions.size() || functions[index] == nullptr) {
+ LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
+ return;
+ }
+
+ const auto function = functions[index];
+ (this->*function)();
+}
+
+void WebBrowser::ExecuteInternal() {
+ using WebAppletExecutor = void (WebBrowser::*)();
+
+ constexpr std::array<WebAppletExecutor, SHIM_KIND_COUNT> functions{
+ nullptr, &WebBrowser::ExecuteShop,
+ nullptr, &WebBrowser::ExecuteOffline,
+ nullptr, nullptr,
+ nullptr, nullptr,
+ };
+
+ const auto index = static_cast<u32>(kind);
+
+ if (index > functions.size() || functions[index] == nullptr) {
+ LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
+ return;
+ }
+
+ const auto function = functions[index];
+ (this->*function)();
+}
+
+void WebBrowser::InitializeShop() {
+ if (frontend_e_commerce == nullptr) {
+ LOG_ERROR(Service_AM, "Missing ECommerce Applet frontend!");
+ status = ResultCode(-1);
+ return;
+ }
+
+ const auto user_id_data = args.find(WebArgTLVType::UserID);
+
+ user_id = std::nullopt;
+ if (user_id_data != args.end()) {
+ user_id = u128{};
+ std::memcpy(user_id->data(), user_id_data->second.data(), sizeof(u128));
+ }
+
+ const auto url = args.find(WebArgTLVType::ShopArgumentsURL);
+
+ if (url == args.end()) {
+ LOG_ERROR(Service_AM, "Missing EShop Arguments URL for initialization!");
+ status = ResultCode(-1);
+ return;
+ }
+
+ std::vector<std::string> split_query;
+ Common::SplitString(Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(url->second.data()), url->second.size()),
+ '?', split_query);
+
+ // 2 -> Main URL '?' Query Parameters
+ // Less is missing info, More is malformed
+ if (split_query.size() != 2) {
+ LOG_ERROR(Service_AM, "EShop Arguments has more than one question mark, malformed");
+ status = ResultCode(-1);
+ return;
+ }
+
+ std::vector<std::string> queries;
+ Common::SplitString(split_query[1], '&', queries);
+
+ const auto split_single_query =
+ [](const std::string& in) -> std::pair<std::string, std::string> {
+ const auto index = in.find('=');
+ if (index == std::string::npos || index == in.size() - 1) {
+ return {in, ""};
+ }
+
+ return {in.substr(0, index), in.substr(index + 1)};
+ };
+
+ std::transform(queries.begin(), queries.end(),
+ std::inserter(shop_query, std::next(shop_query.begin())), split_single_query);
+
+ const auto scene = shop_query.find("scene");
+
+ if (scene == shop_query.end()) {
+ LOG_ERROR(Service_AM, "No scene parameter was passed via shop query!");
+ status = ResultCode(-1);
+ return;
+ }
+
+ const std::map<std::string, ShopWebTarget, std::less<>> target_map{
+ {"product_detail", ShopWebTarget::ApplicationInfo},
+ {"aocs", ShopWebTarget::AddOnContentList},
+ {"subscriptions", ShopWebTarget::SubscriptionList},
+ {"consumption", ShopWebTarget::ConsumableItemList},
+ {"settings", ShopWebTarget::Settings},
+ {"top", ShopWebTarget::Home},
+ };
+
+ const auto target = target_map.find(scene->second);
+ if (target == target_map.end()) {
+ LOG_ERROR(Service_AM, "Scene for shop query is invalid! (scene={})", scene->second);
+ status = ResultCode(-1);
+ return;
+ }
+
+ shop_web_target = target->second;
+
+ const auto title_id_data = shop_query.find("dst_app_id");
+ if (title_id_data != shop_query.end()) {
+ title_id = std::stoull(title_id_data->second, nullptr, 0x10);
+ }
+
+ const auto mode_data = shop_query.find("mode");
+ if (mode_data != shop_query.end()) {
+ shop_full_display = mode_data->second == "full";
+ }
+}
+
+void WebBrowser::InitializeOffline() {
+ if (args.find(WebArgTLVType::DocumentPath) == args.end() ||
+ args.find(WebArgTLVType::DocumentKind) == args.end() ||
+ args.find(WebArgTLVType::ApplicationID) == args.end()) {
+ status = ResultCode(-1);
+ LOG_ERROR(Service_AM, "Missing necessary parameters for initialization!");
+ }
+
+ const auto url_data = args[WebArgTLVType::DocumentPath];
+ filename = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(url_data.data()), url_data.size());
+
+ OfflineWebSource source;
+ ASSERT(args[WebArgTLVType::DocumentKind].size() >= 4);
+ std::memcpy(&source, args[WebArgTLVType::DocumentKind].data(), sizeof(OfflineWebSource));
+
+ constexpr std::array<const char*, 3> WEB_SOURCE_NAMES{
+ "manual",
+ "legal",
+ "system",
+ };
+
+ temporary_dir =
+ FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + "web_applet_" +
+ WEB_SOURCE_NAMES[static_cast<u32>(source) - 1],
+ FileUtil::DirectorySeparator::PlatformDefault);
FileUtil::DeleteDirRecursively(temporary_dir);
+
+ u64 title_id = 0; // 0 corresponds to current process
+ ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8);
+ std::memcpy(&title_id, args[WebArgTLVType::ApplicationID].data(), sizeof(u64));
+ FileSys::ContentRecordType type = FileSys::ContentRecordType::Data;
+
+ switch (source) {
+ case OfflineWebSource::OfflineHtmlPage:
+ // While there is an AppID TLV field, in official SW this is always ignored.
+ title_id = 0;
+ type = FileSys::ContentRecordType::HtmlDocument;
+ break;
+ case OfflineWebSource::ApplicationLegalInformation:
+ type = FileSys::ContentRecordType::LegalInformation;
+ break;
+ case OfflineWebSource::SystemDataPage:
+ type = FileSys::ContentRecordType::Data;
+ break;
+ }
+
+ if (title_id == 0) {
+ title_id = system.CurrentProcess()->GetTitleID();
+ }
+
+ offline_romfs = GetApplicationRomFS(system, title_id, type);
+ if (offline_romfs == nullptr) {
+ status = ResultCode(-1);
+ LOG_ERROR(Service_AM, "Failed to find offline data for request!");
+ }
+
+ std::string path_additional_directory;
+ if (source == OfflineWebSource::OfflineHtmlPage) {
+ path_additional_directory = std::string(DIR_SEP).append("html-document");
+ }
+
+ filename =
+ FileUtil::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename,
+ FileUtil::DirectorySeparator::PlatformDefault);
+}
+
+void WebBrowser::ExecuteShop() {
+ const auto callback = [this]() { Finalize(); };
+
+ const auto check_optional_parameter = [this](const auto& p) {
+ if (!p.has_value()) {
+ LOG_ERROR(Service_AM, "Missing one or more necessary parameters for execution!");
+ status = ResultCode(-1);
+ return false;
+ }
+
+ return true;
+ };
+
+ switch (shop_web_target) {
+ case ShopWebTarget::ApplicationInfo:
+ if (!check_optional_parameter(title_id))
+ return;
+ frontend_e_commerce->ShowApplicationInformation(callback, *title_id, user_id,
+ shop_full_display, shop_extra_parameter);
+ break;
+ case ShopWebTarget::AddOnContentList:
+ if (!check_optional_parameter(title_id))
+ return;
+ frontend_e_commerce->ShowAddOnContentList(callback, *title_id, user_id, shop_full_display);
+ break;
+ case ShopWebTarget::ConsumableItemList:
+ if (!check_optional_parameter(title_id))
+ return;
+ frontend_e_commerce->ShowConsumableItemList(callback, *title_id, user_id);
+ break;
+ case ShopWebTarget::Home:
+ if (!check_optional_parameter(user_id))
+ return;
+ if (!check_optional_parameter(shop_full_display))
+ return;
+ frontend_e_commerce->ShowShopHome(callback, *user_id, *shop_full_display);
+ break;
+ case ShopWebTarget::Settings:
+ if (!check_optional_parameter(user_id))
+ return;
+ if (!check_optional_parameter(shop_full_display))
+ return;
+ frontend_e_commerce->ShowSettings(callback, *user_id, *shop_full_display);
+ break;
+ case ShopWebTarget::SubscriptionList:
+ if (!check_optional_parameter(title_id))
+ return;
+ frontend_e_commerce->ShowSubscriptionList(callback, *title_id, user_id);
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+void WebBrowser::ExecuteOffline() {
+ frontend.OpenPageLocal(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
}
} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h
index 7e0f34c7d..8d4027411 100644
--- a/src/core/hle/service/am/applets/web_browser.h
+++ b/src/core/hle/service/am/applets/web_browser.h
@@ -4,15 +4,26 @@
#pragma once
+#include <map>
#include "core/file_sys/vfs_types.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applets.h"
+namespace Core {
+class System;
+}
+
namespace Service::AM::Applets {
+enum class ShimKind : u32;
+enum class ShopWebTarget;
+enum class WebArgTLVType : u16;
+
class WebBrowser final : public Applet {
public:
- WebBrowser(Core::Frontend::WebBrowserApplet& frontend);
+ WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_,
+ Core::Frontend::ECommerceApplet* frontend_e_commerce_ = nullptr);
+
~WebBrowser() override;
void Initialize() override;
@@ -32,15 +43,41 @@ public:
void Finalize();
private:
+ void InitializeInternal();
+ void ExecuteInternal();
+
+ // Specific initializers for the types of web applets
+ void InitializeShop();
+ void InitializeOffline();
+
+ // Specific executors for the types of web applets
+ void ExecuteShop();
+ void ExecuteOffline();
+
Core::Frontend::WebBrowserApplet& frontend;
+ // Extra frontends for specialized functions
+ Core::Frontend::ECommerceApplet* frontend_e_commerce;
+
bool complete = false;
bool unpacked = false;
ResultCode status = RESULT_SUCCESS;
- FileSys::VirtualFile manual_romfs;
+ ShimKind kind;
+ std::map<WebArgTLVType, std::vector<u8>> args;
+
+ FileSys::VirtualFile offline_romfs;
std::string temporary_dir;
std::string filename;
+
+ ShopWebTarget shop_web_target;
+ std::map<std::string, std::string, std::less<>> shop_query;
+ std::optional<u64> title_id = 0;
+ std::optional<u128> user_id;
+ std::optional<bool> shop_full_display;
+ std::string shop_extra_parameter;
+
+ Core::System& system;
};
} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp
index f3c09bbb1..85bbf5988 100644
--- a/src/core/hle/service/apm/apm.cpp
+++ b/src/core/hle/service/apm/apm.cpp
@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/apm/interface.h"
@@ -12,11 +11,15 @@ namespace Service::APM {
Module::Module() = default;
Module::~Module() = default;
-void InstallInterfaces(SM::ServiceManager& service_manager) {
+void InstallInterfaces(Core::System& system) {
auto module_ = std::make_shared<Module>();
- std::make_shared<APM>(module_, "apm")->InstallAsService(service_manager);
- std::make_shared<APM>(module_, "apm:p")->InstallAsService(service_manager);
- std::make_shared<APM_Sys>()->InstallAsService(service_manager);
+ std::make_shared<APM>(module_, system.GetAPMController(), "apm")
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<APM>(module_, system.GetAPMController(), "apm:p")
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<APM>(module_, system.GetAPMController(), "apm:am")
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<APM_Sys>(system.GetAPMController())->InstallAsService(system.ServiceManager());
}
} // namespace Service::APM
diff --git a/src/core/hle/service/apm/apm.h b/src/core/hle/service/apm/apm.h
index 4d7d5bb7c..cf4c2bb11 100644
--- a/src/core/hle/service/apm/apm.h
+++ b/src/core/hle/service/apm/apm.h
@@ -8,11 +8,6 @@
namespace Service::APM {
-enum class PerformanceMode : u8 {
- Handheld = 0,
- Docked = 1,
-};
-
class Module final {
public:
Module();
@@ -20,6 +15,6 @@ public:
};
/// Registers all AM services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(Core::System& system);
} // namespace Service::APM
diff --git a/src/core/hle/service/apm/controller.cpp b/src/core/hle/service/apm/controller.cpp
new file mode 100644
index 000000000..4376612eb
--- /dev/null
+++ b/src/core/hle/service/apm/controller.cpp
@@ -0,0 +1,68 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/core_timing.h"
+#include "core/hle/service/apm/controller.h"
+#include "core/settings.h"
+
+namespace Service::APM {
+
+constexpr PerformanceConfiguration DEFAULT_PERFORMANCE_CONFIGURATION =
+ PerformanceConfiguration::Config7;
+
+Controller::Controller(Core::Timing::CoreTiming& core_timing)
+ : core_timing(core_timing), configs{
+ {PerformanceMode::Handheld, DEFAULT_PERFORMANCE_CONFIGURATION},
+ {PerformanceMode::Docked, DEFAULT_PERFORMANCE_CONFIGURATION},
+ } {}
+
+Controller::~Controller() = default;
+
+void Controller::SetPerformanceConfiguration(PerformanceMode mode,
+ PerformanceConfiguration config) {
+ static const std::map<PerformanceConfiguration, u32> PCONFIG_TO_SPEED_MAP{
+ {PerformanceConfiguration::Config1, 1020}, {PerformanceConfiguration::Config2, 1020},
+ {PerformanceConfiguration::Config3, 1224}, {PerformanceConfiguration::Config4, 1020},
+ {PerformanceConfiguration::Config5, 1020}, {PerformanceConfiguration::Config6, 1224},
+ {PerformanceConfiguration::Config7, 1020}, {PerformanceConfiguration::Config8, 1020},
+ {PerformanceConfiguration::Config9, 1020}, {PerformanceConfiguration::Config10, 1020},
+ {PerformanceConfiguration::Config11, 1020}, {PerformanceConfiguration::Config12, 1020},
+ {PerformanceConfiguration::Config13, 1785}, {PerformanceConfiguration::Config14, 1785},
+ {PerformanceConfiguration::Config15, 1020}, {PerformanceConfiguration::Config16, 1020},
+ };
+
+ SetClockSpeed(PCONFIG_TO_SPEED_MAP.find(config)->second);
+ configs.insert_or_assign(mode, config);
+}
+
+void Controller::SetFromCpuBoostMode(CpuBoostMode mode) {
+ constexpr std::array<PerformanceConfiguration, 3> BOOST_MODE_TO_CONFIG_MAP{{
+ PerformanceConfiguration::Config7,
+ PerformanceConfiguration::Config13,
+ PerformanceConfiguration::Config15,
+ }};
+
+ SetPerformanceConfiguration(PerformanceMode::Docked,
+ BOOST_MODE_TO_CONFIG_MAP.at(static_cast<u32>(mode)));
+}
+
+PerformanceMode Controller::GetCurrentPerformanceMode() {
+ return Settings::values.use_docked_mode ? PerformanceMode::Docked : PerformanceMode::Handheld;
+}
+
+PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) {
+ if (configs.find(mode) == configs.end()) {
+ configs.insert_or_assign(mode, DEFAULT_PERFORMANCE_CONFIGURATION);
+ }
+
+ return configs[mode];
+}
+
+void Controller::SetClockSpeed(u32 mhz) {
+ LOG_INFO(Service_APM, "called, mhz={:08X}", mhz);
+ // TODO(DarkLordZach): Actually signal core_timing to change clock speed.
+}
+
+} // namespace Service::APM
diff --git a/src/core/hle/service/apm/controller.h b/src/core/hle/service/apm/controller.h
new file mode 100644
index 000000000..8ac80eaea
--- /dev/null
+++ b/src/core/hle/service/apm/controller.h
@@ -0,0 +1,70 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <map>
+#include "common/common_types.h"
+
+namespace Core::Timing {
+class CoreTiming;
+}
+
+namespace Service::APM {
+
+enum class PerformanceConfiguration : u32 {
+ Config1 = 0x00010000,
+ Config2 = 0x00010001,
+ Config3 = 0x00010002,
+ Config4 = 0x00020000,
+ Config5 = 0x00020001,
+ Config6 = 0x00020002,
+ Config7 = 0x00020003,
+ Config8 = 0x00020004,
+ Config9 = 0x00020005,
+ Config10 = 0x00020006,
+ Config11 = 0x92220007,
+ Config12 = 0x92220008,
+ Config13 = 0x92220009,
+ Config14 = 0x9222000A,
+ Config15 = 0x9222000B,
+ Config16 = 0x9222000C,
+};
+
+enum class CpuBoostMode : u32 {
+ Disabled = 0,
+ Full = 1, // CPU + GPU -> Config 13, 14, 15, or 16
+ Partial = 2, // GPU Only -> Config 15 or 16
+};
+
+enum class PerformanceMode : u8 {
+ Handheld = 0,
+ Docked = 1,
+};
+
+// Class to manage the state and change of the emulated system performance.
+// Specifically, this deals with PerformanceMode, which corresponds to the system being docked or
+// undocked, and PerformanceConfig which specifies the exact CPU, GPU, and Memory clocks to operate
+// at. Additionally, this manages 'Boost Mode', which allows games to temporarily overclock the
+// system during times of high load -- this simply maps to different PerformanceConfigs to use.
+class Controller {
+public:
+ Controller(Core::Timing::CoreTiming& core_timing);
+ ~Controller();
+
+ void SetPerformanceConfiguration(PerformanceMode mode, PerformanceConfiguration config);
+ void SetFromCpuBoostMode(CpuBoostMode mode);
+
+ PerformanceMode GetCurrentPerformanceMode();
+ PerformanceConfiguration GetCurrentPerformanceConfiguration(PerformanceMode mode);
+
+private:
+ void SetClockSpeed(u32 mhz);
+
+ std::map<PerformanceMode, PerformanceConfiguration> configs;
+
+ Core::Timing::CoreTiming& core_timing;
+};
+
+} // namespace Service::APM
diff --git a/src/core/hle/service/apm/interface.cpp b/src/core/hle/service/apm/interface.cpp
index d058c0245..06f0f8edd 100644
--- a/src/core/hle/service/apm/interface.cpp
+++ b/src/core/hle/service/apm/interface.cpp
@@ -5,43 +5,32 @@
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/apm/apm.h"
+#include "core/hle/service/apm/controller.h"
#include "core/hle/service/apm/interface.h"
namespace Service::APM {
class ISession final : public ServiceFramework<ISession> {
public:
- ISession() : ServiceFramework("ISession") {
+ ISession(Controller& controller) : ServiceFramework("ISession"), controller(controller) {
static const FunctionInfo functions[] = {
{0, &ISession::SetPerformanceConfiguration, "SetPerformanceConfiguration"},
{1, &ISession::GetPerformanceConfiguration, "GetPerformanceConfiguration"},
+ {2, nullptr, "SetCpuOverclockEnabled"},
};
RegisterHandlers(functions);
}
private:
- enum class PerformanceConfiguration : u32 {
- Config1 = 0x00010000,
- Config2 = 0x00010001,
- Config3 = 0x00010002,
- Config4 = 0x00020000,
- Config5 = 0x00020001,
- Config6 = 0x00020002,
- Config7 = 0x00020003,
- Config8 = 0x00020004,
- Config9 = 0x00020005,
- Config10 = 0x00020006,
- Config11 = 0x92220007,
- Config12 = 0x92220008,
- };
-
void SetPerformanceConfiguration(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto mode = static_cast<PerformanceMode>(rp.Pop<u32>());
- u32 config = rp.Pop<u32>();
- LOG_WARNING(Service_APM, "(STUBBED) called mode={} config={}", static_cast<u32>(mode),
- config);
+ const auto mode = rp.PopEnum<PerformanceMode>();
+ const auto config = rp.PopEnum<PerformanceConfiguration>();
+ LOG_DEBUG(Service_APM, "called mode={} config={}", static_cast<u32>(mode),
+ static_cast<u32>(config));
+
+ controller.SetPerformanceConfiguration(mode, config);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -50,20 +39,23 @@ private:
void GetPerformanceConfiguration(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto mode = static_cast<PerformanceMode>(rp.Pop<u32>());
- LOG_WARNING(Service_APM, "(STUBBED) called mode={}", static_cast<u32>(mode));
+ const auto mode = rp.PopEnum<PerformanceMode>();
+ LOG_DEBUG(Service_APM, "called mode={}", static_cast<u32>(mode));
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(static_cast<u32>(PerformanceConfiguration::Config1));
+ rb.PushEnum(controller.GetCurrentPerformanceConfiguration(mode));
}
+
+ Controller& controller;
};
-APM::APM(std::shared_ptr<Module> apm, const char* name)
- : ServiceFramework(name), apm(std::move(apm)) {
+APM::APM(std::shared_ptr<Module> apm, Controller& controller, const char* name)
+ : ServiceFramework(name), apm(std::move(apm)), controller(controller) {
static const FunctionInfo functions[] = {
{0, &APM::OpenSession, "OpenSession"},
- {1, nullptr, "GetPerformanceMode"},
+ {1, &APM::GetPerformanceMode, "GetPerformanceMode"},
+ {6, nullptr, "IsCpuOverclockEnabled"},
};
RegisterHandlers(functions);
}
@@ -75,10 +67,17 @@ void APM::OpenSession(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISession>();
+ rb.PushIpcInterface<ISession>(controller);
+}
+
+void APM::GetPerformanceMode(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_APM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.PushEnum(controller.GetCurrentPerformanceMode());
}
-APM_Sys::APM_Sys() : ServiceFramework{"apm:sys"} {
+APM_Sys::APM_Sys(Controller& controller) : ServiceFramework{"apm:sys"}, controller(controller) {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestPerformanceMode"},
@@ -87,8 +86,8 @@ APM_Sys::APM_Sys() : ServiceFramework{"apm:sys"} {
{3, nullptr, "GetLastThrottlingState"},
{4, nullptr, "ClearLastThrottlingState"},
{5, nullptr, "LoadAndApplySettings"},
- {6, nullptr, "SetCpuBoostMode"},
- {7, nullptr, "GetCurrentPerformanceConfiguration"},
+ {6, &APM_Sys::SetCpuBoostMode, "SetCpuBoostMode"},
+ {7, &APM_Sys::GetCurrentPerformanceConfiguration, "GetCurrentPerformanceConfiguration"},
};
// clang-format on
@@ -102,7 +101,28 @@ void APM_Sys::GetPerformanceEvent(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISession>();
+ rb.PushIpcInterface<ISession>(controller);
+}
+
+void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto mode = rp.PopEnum<CpuBoostMode>();
+
+ LOG_DEBUG(Service_APM, "called, mode={:08X}", static_cast<u32>(mode));
+
+ controller.SetFromCpuBoostMode(mode);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void APM_Sys::GetCurrentPerformanceConfiguration(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_APM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(
+ controller.GetCurrentPerformanceConfiguration(controller.GetCurrentPerformanceMode()));
}
} // namespace Service::APM
diff --git a/src/core/hle/service/apm/interface.h b/src/core/hle/service/apm/interface.h
index 773541aa4..de1b89437 100644
--- a/src/core/hle/service/apm/interface.h
+++ b/src/core/hle/service/apm/interface.h
@@ -8,24 +8,34 @@
namespace Service::APM {
+class Controller;
+class Module;
+
class APM final : public ServiceFramework<APM> {
public:
- explicit APM(std::shared_ptr<Module> apm, const char* name);
+ explicit APM(std::shared_ptr<Module> apm, Controller& controller, const char* name);
~APM() override;
private:
void OpenSession(Kernel::HLERequestContext& ctx);
+ void GetPerformanceMode(Kernel::HLERequestContext& ctx);
std::shared_ptr<Module> apm;
+ Controller& controller;
};
class APM_Sys final : public ServiceFramework<APM_Sys> {
public:
- explicit APM_Sys();
+ explicit APM_Sys(Controller& controller);
~APM_Sys() override;
+ void SetCpuBoostMode(Kernel::HLERequestContext& ctx);
+
private:
void GetPerformanceEvent(Kernel::HLERequestContext& ctx);
+ void GetCurrentPerformanceConfiguration(Kernel::HLERequestContext& ctx);
+
+ Controller& controller;
};
} // namespace Service::APM
diff --git a/src/core/hle/service/arp/arp.cpp b/src/core/hle/service/arp/arp.cpp
deleted file mode 100644
index e675b0188..000000000
--- a/src/core/hle/service/arp/arp.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <memory>
-
-#include "common/logging/log.h"
-#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/hle_ipc.h"
-#include "core/hle/service/arp/arp.h"
-#include "core/hle/service/service.h"
-#include "core/hle/service/sm/sm.h"
-
-namespace Service::ARP {
-
-class ARP_R final : public ServiceFramework<ARP_R> {
-public:
- explicit ARP_R() : ServiceFramework{"arp:r"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "GetApplicationLaunchProperty"},
- {1, nullptr, "GetApplicationLaunchPropertyWithApplicationId"},
- {2, nullptr, "GetApplicationControlProperty"},
- {3, nullptr, "GetApplicationControlPropertyWithApplicationId"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
-class IRegistrar final : public ServiceFramework<IRegistrar> {
-public:
- explicit IRegistrar() : ServiceFramework{"IRegistrar"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "Issue"},
- {1, nullptr, "SetApplicationLaunchProperty"},
- {2, nullptr, "SetApplicationControlProperty"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
-class ARP_W final : public ServiceFramework<ARP_W> {
-public:
- explicit ARP_W() : ServiceFramework{"arp:w"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"},
- {1, nullptr, "DeleteProperties"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
-private:
- void AcquireRegistrar(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_ARP, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IRegistrar>();
- }
-};
-
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<ARP_R>()->InstallAsService(sm);
- std::make_shared<ARP_W>()->InstallAsService(sm);
-}
-
-} // namespace Service::ARP
diff --git a/src/core/hle/service/arp/arp.h b/src/core/hle/service/arp/arp.h
deleted file mode 100644
index 9d100187c..000000000
--- a/src/core/hle/service/arp/arp.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-namespace Service::SM {
-class ServiceManager;
-}
-
-namespace Service::ARP {
-
-/// Registers all ARP services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& sm);
-
-} // namespace Service::ARP
diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp
index 128df7db5..1781bec83 100644
--- a/src/core/hle/service/audio/audio.cpp
+++ b/src/core/hle/service/audio/audio.cpp
@@ -19,16 +19,16 @@
namespace Service::Audio {
-void InstallInterfaces(SM::ServiceManager& service_manager) {
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
std::make_shared<AudCtl>()->InstallAsService(service_manager);
std::make_shared<AudOutA>()->InstallAsService(service_manager);
- std::make_shared<AudOutU>()->InstallAsService(service_manager);
+ std::make_shared<AudOutU>(system)->InstallAsService(service_manager);
std::make_shared<AudInA>()->InstallAsService(service_manager);
std::make_shared<AudInU>()->InstallAsService(service_manager);
std::make_shared<AudRecA>()->InstallAsService(service_manager);
std::make_shared<AudRecU>()->InstallAsService(service_manager);
std::make_shared<AudRenA>()->InstallAsService(service_manager);
- std::make_shared<AudRenU>()->InstallAsService(service_manager);
+ std::make_shared<AudRenU>(system)->InstallAsService(service_manager);
std::make_shared<CodecCtl>()->InstallAsService(service_manager);
std::make_shared<HwOpus>()->InstallAsService(service_manager);
diff --git a/src/core/hle/service/audio/audio.h b/src/core/hle/service/audio/audio.h
index f5bd3bf5f..b6d13912e 100644
--- a/src/core/hle/service/audio/audio.h
+++ b/src/core/hle/service/audio/audio.h
@@ -4,6 +4,10 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
@@ -11,6 +15,6 @@ class ServiceManager;
namespace Service::Audio {
/// Registers all Audio services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index 6ba41b20a..fb84a8f13 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -40,8 +40,8 @@ enum class AudioState : u32 {
class IAudioOut final : public ServiceFramework<IAudioOut> {
public:
- IAudioOut(AudoutParams audio_params, AudioCore::AudioOut& audio_core, std::string&& device_name,
- std::string&& unique_name)
+ IAudioOut(Core::System& system, AudoutParams audio_params, AudioCore::AudioOut& audio_core,
+ std::string&& device_name, std::string&& unique_name)
: ServiceFramework("IAudioOut"), audio_core(audio_core),
device_name(std::move(device_name)), audio_params(audio_params) {
// clang-format off
@@ -58,14 +58,13 @@ public:
{9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"},
{10, nullptr, "GetAudioOutPlayedSampleCount"},
{11, nullptr, "FlushAudioOutBuffers"},
- {12, nullptr, "SetAudioOutVolume"},
- {13, nullptr, "GetAudioOutVolume"},
+ {12, &IAudioOut::SetAudioOutVolume, "SetAudioOutVolume"},
+ {13, &IAudioOut::GetAudioOutVolume, "GetAudioOutVolume"},
};
// clang-format on
RegisterHandlers(functions);
// This is the event handle used to check if the audio buffer was released
- auto& system = Core::System::GetInstance();
buffer_event = Kernel::WritableEvent::CreateEventPair(
system.Kernel(), Kernel::ResetType::Manual, "IAudioOutBufferReleased");
@@ -183,6 +182,25 @@ private:
rb.Push(static_cast<u32>(stream->GetQueueSize()));
}
+ void SetAudioOutVolume(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const float volume = rp.Pop<float>();
+ LOG_DEBUG(Service_Audio, "called, volume={}", volume);
+
+ stream->SetVolume(volume);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetAudioOutVolume(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(stream->GetVolume());
+ }
+
AudioCore::AudioOut& audio_core;
AudioCore::StreamPtr stream;
std::string device_name;
@@ -193,6 +211,22 @@ private:
Kernel::EventPair buffer_event;
};
+AudOutU::AudOutU(Core::System& system_) : ServiceFramework("audout:u"), system{system_} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"},
+ {1, &AudOutU::OpenAudioOutImpl, "OpenAudioOut"},
+ {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"},
+ {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ audio_core = std::make_unique<AudioCore::AudioOut>();
+}
+
+AudOutU::~AudOutU() = default;
+
void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
@@ -229,7 +263,7 @@ void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) {
std::string unique_name{fmt::format("{}-{}", device_name, audio_out_interfaces.size())};
auto audio_out_interface = std::make_shared<IAudioOut>(
- params, *audio_core, std::move(device_name), std::move(unique_name));
+ system, params, *audio_core, std::move(device_name), std::move(unique_name));
IPC::ResponseBuilder rb{ctx, 6, 0, 1};
rb.Push(RESULT_SUCCESS);
@@ -237,20 +271,9 @@ void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(params.channel_count);
rb.Push<u32>(static_cast<u32>(AudioCore::Codec::PcmFormat::Int16));
rb.Push<u32>(static_cast<u32>(AudioState::Stopped));
- rb.PushIpcInterface<Audio::IAudioOut>(audio_out_interface);
+ rb.PushIpcInterface<IAudioOut>(audio_out_interface);
audio_out_interfaces.push_back(std::move(audio_out_interface));
}
-AudOutU::AudOutU() : ServiceFramework("audout:u") {
- static const FunctionInfo functions[] = {{0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"},
- {1, &AudOutU::OpenAudioOutImpl, "OpenAudioOut"},
- {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"},
- {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}};
- RegisterHandlers(functions);
- audio_core = std::make_unique<AudioCore::AudioOut>();
-}
-
-AudOutU::~AudOutU() = default;
-
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h
index aed4c43b2..c9f532ccd 100644
--- a/src/core/hle/service/audio/audout_u.h
+++ b/src/core/hle/service/audio/audout_u.h
@@ -11,6 +11,10 @@ namespace AudioCore {
class AudioOut;
}
+namespace Core {
+class System;
+}
+
namespace Kernel {
class HLERequestContext;
}
@@ -21,15 +25,17 @@ class IAudioOut;
class AudOutU final : public ServiceFramework<AudOutU> {
public:
- AudOutU();
+ explicit AudOutU(Core::System& system_);
~AudOutU() override;
private:
+ void ListAudioOutsImpl(Kernel::HLERequestContext& ctx);
+ void OpenAudioOutImpl(Kernel::HLERequestContext& ctx);
+
std::vector<std::shared_ptr<IAudioOut>> audio_out_interfaces;
std::unique_ptr<AudioCore::AudioOut> audio_core;
- void ListAudioOutsImpl(Kernel::HLERequestContext& ctx);
- void OpenAudioOutImpl(Kernel::HLERequestContext& ctx);
+ Core::System& system;
};
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 75db0c2dc..f162249ed 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -5,6 +5,7 @@
#include <algorithm>
#include <array>
#include <memory>
+#include <string_view>
#include "audio_core/audio_renderer.h"
#include "common/alignment.h"
@@ -25,7 +26,8 @@ namespace Service::Audio {
class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
public:
- explicit IAudioRenderer(AudioCore::AudioRendererParameter audren_params)
+ explicit IAudioRenderer(Core::System& system, AudioCore::AudioRendererParameter audren_params,
+ const std::size_t instance_number)
: ServiceFramework("IAudioRenderer") {
// clang-format off
static const FunctionInfo functions[] = {
@@ -45,11 +47,10 @@ public:
// clang-format on
RegisterHandlers(functions);
- auto& system = Core::System::GetInstance();
system_event = Kernel::WritableEvent::CreateEventPair(
system.Kernel(), Kernel::ResetType::Manual, "IAudioRenderer:SystemEvent");
- renderer = std::make_unique<AudioCore::AudioRenderer>(system.CoreTiming(), audren_params,
- system_event.writable);
+ renderer = std::make_unique<AudioCore::AudioRenderer>(
+ system.CoreTiming(), audren_params, system_event.writable, instance_number);
}
private:
@@ -159,40 +160,81 @@ private:
class IAudioDevice final : public ServiceFramework<IAudioDevice> {
public:
- IAudioDevice() : ServiceFramework("IAudioDevice") {
+ explicit IAudioDevice(Core::System& system, u32_le revision_num)
+ : ServiceFramework("IAudioDevice"), revision{revision_num} {
static const FunctionInfo functions[] = {
{0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"},
{1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"},
- {2, nullptr, "GetAudioDeviceOutputVolume"},
+ {2, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolume"},
{3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"},
{4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"},
{5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"},
- {6, &IAudioDevice::ListAudioDeviceName,
- "ListAudioDeviceNameAuto"}, // TODO(ogniK): Confirm if autos are identical to non auto
+ {6, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceNameAuto"},
{7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"},
- {8, nullptr, "GetAudioDeviceOutputVolumeAuto"},
+ {8, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolumeAuto"},
{10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"},
- {11, nullptr, "QueryAudioDeviceInputEvent"},
- {12, nullptr, "QueryAudioDeviceOutputEvent"},
+ {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"},
+ {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"},
{13, nullptr, "GetAudioSystemMasterVolumeSetting"},
};
RegisterHandlers(functions);
- auto& kernel = Core::System::GetInstance().Kernel();
+ auto& kernel = system.Kernel();
buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
"IAudioOutBufferReleasedEvent");
+
+ // Should be similar to audio_output_device_switch_event
+ audio_input_device_switch_event = Kernel::WritableEvent::CreateEventPair(
+ kernel, Kernel::ResetType::Automatic, "IAudioDevice:AudioInputDeviceSwitchedEvent");
+
+ // Should only be signalled when an audio output device has been changed, example: speaker
+ // to headset
+ audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair(
+ kernel, Kernel::ResetType::Automatic, "IAudioDevice:AudioOutputDeviceSwitchedEvent");
}
private:
+ using AudioDeviceName = std::array<char, 256>;
+ static constexpr std::array<std::string_view, 4> audio_device_names{{
+ "AudioStereoJackOutput",
+ "AudioBuiltInSpeakerOutput",
+ "AudioTvOutput",
+ "AudioUsbDeviceOutput",
+ }};
+ enum class DeviceType {
+ AHUBHeadphones,
+ AHUBSpeakers,
+ HDA,
+ USBOutput,
+ };
+
void ListAudioDeviceName(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ LOG_DEBUG(Service_Audio, "called");
+
+ const bool usb_output_supported =
+ IsFeatureSupported(AudioFeatures::AudioUSBDeviceOutput, revision);
+ const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioDeviceName);
- constexpr std::array<char, 15> audio_interface{{"AudioInterface"}};
- ctx.WriteBuffer(audio_interface);
+ std::vector<AudioDeviceName> name_buffer;
+ name_buffer.reserve(audio_device_names.size());
+
+ for (std::size_t i = 0; i < count && i < audio_device_names.size(); i++) {
+ const auto type = static_cast<DeviceType>(i);
+
+ if (!usb_output_supported && type == DeviceType::USBOutput) {
+ continue;
+ }
+
+ const auto& device_name = audio_device_names[i];
+ auto& entry = name_buffer.emplace_back();
+ device_name.copy(entry.data(), device_name.size());
+ }
+
+ ctx.WriteBuffer(name_buffer);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(1);
+ rb.Push(static_cast<u32>(name_buffer.size()));
}
void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
@@ -208,15 +250,32 @@ private:
rb.Push(RESULT_SUCCESS);
}
+ void GetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const auto device_name_buffer = ctx.ReadBuffer();
+ const std::string name = Common::StringFromBuffer(device_name_buffer);
+
+ LOG_WARNING(Service_Audio, "(STUBBED) called. name={}", name);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(1.0f);
+ }
+
void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
- constexpr std::array<char, 12> audio_interface{{"AudioDevice"}};
- ctx.WriteBuffer(audio_interface);
+ // Currently set to always be TV audio output.
+ const auto& device_name = audio_device_names[2];
- IPC::ResponseBuilder rb{ctx, 3};
+ AudioDeviceName out_device_name{};
+ device_name.copy(out_device_name.data(), device_name.size());
+
+ ctx.WriteBuffer(out_device_name);
+
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(1);
}
void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) {
@@ -237,11 +296,31 @@ private:
rb.Push<u32>(1);
}
+ // Should be similar to QueryAudioDeviceOutputEvent
+ void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(audio_input_device_switch_event.readable);
+ }
+
+ void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(audio_output_device_switch_event.readable);
+ }
+
+ u32_le revision = 0;
Kernel::EventPair buffer_event;
+ Kernel::EventPair audio_input_device_switch_event;
+ Kernel::EventPair audio_output_device_switch_event;
}; // namespace Audio
-AudRenU::AudRenU() : ServiceFramework("audren:u") {
+AudRenU::AudRenU(Core::System& system_) : ServiceFramework("audren:u"), system{system_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"},
@@ -314,7 +393,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the portion of the size related to the mix data (and the sorting thereof).
- const auto calculate_mix_info_size = [this](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_mix_info_size = [](const AudioCore::AudioRendererParameter& params) {
// The size of the mixing info data structure.
constexpr u64 mix_info_size = 0x940;
@@ -386,7 +465,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
// Calculates the part of the size related to the splitter context.
const auto calculate_splitter_context_size =
- [this](const AudioCore::AudioRendererParameter& params) -> u64 {
+ [](const AudioCore::AudioRendererParameter& params) -> u64 {
if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
return 0;
}
@@ -433,7 +512,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size related to performance statistics.
- const auto calculate_perf_size = [this](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_perf_size = [](const AudioCore::AudioRendererParameter& params) {
// Extra size value appended to the end of the calculation.
constexpr u64 appended = 128;
@@ -460,78 +539,76 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size that relates to the audio command buffer.
- const auto calculate_command_buffer_size =
- [this](const AudioCore::AudioRendererParameter& params) {
- constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
+ const auto calculate_command_buffer_size = [](const AudioCore::AudioRendererParameter& params) {
+ constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
- if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
- constexpr u64 command_buffer_size = 0x18000;
+ if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
+ constexpr u64 command_buffer_size = 0x18000;
- return command_buffer_size + alignment;
- }
+ return command_buffer_size + alignment;
+ }
- // When the variadic command buffer is supported, this means
- // the command generator for the audio renderer can issue commands
- // that are (as one would expect), variable in size. So what we need to do
- // is determine the maximum possible size for a few command data structures
- // then multiply them by the amount of present commands indicated by the given
- // respective audio parameters.
+ // When the variadic command buffer is supported, this means
+ // the command generator for the audio renderer can issue commands
+ // that are (as one would expect), variable in size. So what we need to do
+ // is determine the maximum possible size for a few command data structures
+ // then multiply them by the amount of present commands indicated by the given
+ // respective audio parameters.
- constexpr u64 max_biquad_filters = 2;
- constexpr u64 max_mix_buffers = 24;
+ constexpr u64 max_biquad_filters = 2;
+ constexpr u64 max_mix_buffers = 24;
- constexpr u64 biquad_filter_command_size = 0x2C;
+ constexpr u64 biquad_filter_command_size = 0x2C;
- constexpr u64 depop_mix_command_size = 0x24;
- constexpr u64 depop_setup_command_size = 0x50;
+ constexpr u64 depop_mix_command_size = 0x24;
+ constexpr u64 depop_setup_command_size = 0x50;
- constexpr u64 effect_command_max_size = 0x540;
+ constexpr u64 effect_command_max_size = 0x540;
- constexpr u64 mix_command_size = 0x1C;
- constexpr u64 mix_ramp_command_size = 0x24;
- constexpr u64 mix_ramp_grouped_command_size = 0x13C;
+ constexpr u64 mix_command_size = 0x1C;
+ constexpr u64 mix_ramp_command_size = 0x24;
+ constexpr u64 mix_ramp_grouped_command_size = 0x13C;
- constexpr u64 perf_command_size = 0x28;
+ constexpr u64 perf_command_size = 0x28;
- constexpr u64 sink_command_size = 0x130;
+ constexpr u64 sink_command_size = 0x130;
- constexpr u64 submix_command_max_size =
- depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
+ constexpr u64 submix_command_max_size =
+ depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
- constexpr u64 volume_command_size = 0x1C;
- constexpr u64 volume_ramp_command_size = 0x20;
+ constexpr u64 volume_command_size = 0x1C;
+ constexpr u64 volume_ramp_command_size = 0x20;
- constexpr u64 voice_biquad_filter_command_size =
- biquad_filter_command_size * max_biquad_filters;
- constexpr u64 voice_data_command_size = 0x9C;
- const u64 voice_command_max_size =
- (params.splitter_count * depop_setup_command_size) +
- (voice_data_command_size + voice_biquad_filter_command_size +
- volume_ramp_command_size + mix_ramp_grouped_command_size);
+ constexpr u64 voice_biquad_filter_command_size =
+ biquad_filter_command_size * max_biquad_filters;
+ constexpr u64 voice_data_command_size = 0x9C;
+ const u64 voice_command_max_size =
+ (params.splitter_count * depop_setup_command_size) +
+ (voice_data_command_size + voice_biquad_filter_command_size + volume_ramp_command_size +
+ mix_ramp_grouped_command_size);
- // Now calculate the individual elements that comprise the size and add them together.
- const u64 effect_commands_size = params.effect_count * effect_command_max_size;
+ // Now calculate the individual elements that comprise the size and add them together.
+ const u64 effect_commands_size = params.effect_count * effect_command_max_size;
- const u64 final_mix_commands_size =
- depop_mix_command_size + volume_command_size * max_mix_buffers;
+ const u64 final_mix_commands_size =
+ depop_mix_command_size + volume_command_size * max_mix_buffers;
- const u64 perf_commands_size =
- perf_command_size *
- (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
+ const u64 perf_commands_size =
+ perf_command_size * (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
- const u64 sink_commands_size = params.sink_count * sink_command_size;
+ const u64 sink_commands_size = params.sink_count * sink_command_size;
- const u64 splitter_commands_size =
- params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
+ const u64 splitter_commands_size =
+ params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
- const u64 submix_commands_size = params.submix_count * submix_command_max_size;
+ const u64 submix_commands_size = params.submix_count * submix_command_max_size;
- const u64 voice_commands_size = params.voice_count * voice_command_max_size;
+ const u64 voice_commands_size = params.voice_count * voice_command_max_size;
- return effect_commands_size + final_mix_commands_size + perf_commands_size +
- sink_commands_size + splitter_commands_size + submix_commands_size +
- voice_commands_size + alignment;
- };
+ return effect_commands_size + final_mix_commands_size + perf_commands_size +
+ sink_commands_size + splitter_commands_size + submix_commands_size +
+ voice_commands_size + alignment;
+ };
IPC::RequestParser rp{ctx};
const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
@@ -564,12 +641,16 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
}
void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Audio, "called");
+ IPC::RequestParser rp{ctx};
+ const u64 aruid = rp.Pop<u64>();
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ LOG_DEBUG(Service_Audio, "called. aruid={:016X}", aruid);
+ // Revisionless variant of GetAudioDeviceServiceWithRevisionInfo that
+ // always assumes the initial release revision (REV1).
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<Audio::IAudioDevice>();
+ rb.PushIpcInterface<IAudioDevice>(system, Common::MakeMagic('R', 'E', 'V', '1'));
}
void AudRenU::OpenAudioRendererAuto(Kernel::HLERequestContext& ctx) {
@@ -579,13 +660,19 @@ void AudRenU::OpenAudioRendererAuto(Kernel::HLERequestContext& ctx) {
}
void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Audio, "(STUBBED) called");
+ struct Parameters {
+ u32 revision;
+ u64 aruid;
+ };
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ IPC::RequestParser rp{ctx};
+ const auto [revision, aruid] = rp.PopRaw<Parameters>();
+
+ LOG_DEBUG(Service_Audio, "called. revision={:08X}, aruid={:016X}", revision, aruid);
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<Audio::IAudioDevice>(); // TODO(ogniK): Figure out what is different
- // based on the current revision
+ rb.PushIpcInterface<IAudioDevice>(system, revision);
}
void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
@@ -594,14 +681,16 @@ void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IAudioRenderer>(params);
+ rb.PushIpcInterface<IAudioRenderer>(system, params, audren_instance_count++);
}
-bool AudRenU::IsFeatureSupported(AudioFeatures feature, u32_le revision) const {
+bool IsFeatureSupported(AudioFeatures feature, u32_le revision) {
// Byte swap
const u32_be version_num = revision - Common::MakeMagic('R', 'E', 'V', '0');
switch (feature) {
+ case AudioFeatures::AudioUSBDeviceOutput:
+ return version_num >= 4U;
case AudioFeatures::Splitter:
return version_num >= 2U;
case AudioFeatures::PerformanceMetricsVersion2:
diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h
index 1d3c8df61..4e0ccc792 100644
--- a/src/core/hle/service/audio/audren_u.h
+++ b/src/core/hle/service/audio/audren_u.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class HLERequestContext;
}
@@ -14,7 +18,7 @@ namespace Service::Audio {
class AudRenU final : public ServiceFramework<AudRenU> {
public:
- explicit AudRenU();
+ explicit AudRenU(Core::System& system_);
~AudRenU() override;
private:
@@ -26,13 +30,19 @@ private:
void OpenAudioRendererImpl(Kernel::HLERequestContext& ctx);
- enum class AudioFeatures : u32 {
- Splitter,
- PerformanceMetricsVersion2,
- VariadicCommandBuffer,
- };
+ std::size_t audren_instance_count = 0;
+ Core::System& system;
+};
- bool IsFeatureSupported(AudioFeatures feature, u32_le revision) const;
+// Describes a particular audio feature that may be supported in a particular revision.
+enum class AudioFeatures : u32 {
+ AudioUSBDeviceOutput,
+ Splitter,
+ PerformanceMetricsVersion2,
+ VariadicCommandBuffer,
};
+// Tests if a particular audio feature is supported with a given audio revision.
+bool IsFeatureSupported(AudioFeatures feature, u32_le revision);
+
} // namespace Service::Audio
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp
index 6701cb913..af70d174d 100644
--- a/src/core/hle/service/es/es.cpp
+++ b/src/core/hle/service/es/es.cpp
@@ -2,32 +2,37 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/crypto/key_manager.h"
+#include "core/hle/ipc_helpers.h"
#include "core/hle/service/service.h"
namespace Service::ES {
+constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::ETicket, 2};
+constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3};
+
class ETicket final : public ServiceFramework<ETicket> {
public:
explicit ETicket() : ServiceFramework{"es"} {
// clang-format off
static const FunctionInfo functions[] = {
- {1, nullptr, "ImportTicket"},
+ {1, &ETicket::ImportTicket, "ImportTicket"},
{2, nullptr, "ImportTicketCertificateSet"},
{3, nullptr, "DeleteTicket"},
{4, nullptr, "DeletePersonalizedTicket"},
{5, nullptr, "DeleteAllCommonTicket"},
{6, nullptr, "DeleteAllPersonalizedTicket"},
{7, nullptr, "DeleteAllPersonalizedTicketEx"},
- {8, nullptr, "GetTitleKey"},
- {9, nullptr, "CountCommonTicket"},
- {10, nullptr, "CountPersonalizedTicket"},
- {11, nullptr, "ListCommonTicket"},
- {12, nullptr, "ListPersonalizedTicket"},
+ {8, &ETicket::GetTitleKey, "GetTitleKey"},
+ {9, &ETicket::CountCommonTicket, "CountCommonTicket"},
+ {10, &ETicket::CountPersonalizedTicket, "CountPersonalizedTicket"},
+ {11, &ETicket::ListCommonTicket, "ListCommonTicket"},
+ {12, &ETicket::ListPersonalizedTicket, "ListPersonalizedTicket"},
{13, nullptr, "ListMissingPersonalizedTicket"},
- {14, nullptr, "GetCommonTicketSize"},
- {15, nullptr, "GetPersonalizedTicketSize"},
- {16, nullptr, "GetCommonTicketData"},
- {17, nullptr, "GetPersonalizedTicketData"},
+ {14, &ETicket::GetCommonTicketSize, "GetCommonTicketSize"},
+ {15, &ETicket::GetPersonalizedTicketSize, "GetPersonalizedTicketSize"},
+ {16, &ETicket::GetCommonTicketData, "GetCommonTicketData"},
+ {17, &ETicket::GetPersonalizedTicketData, "GetPersonalizedTicketData"},
{18, nullptr, "OwnTicket"},
{19, nullptr, "GetTicketInfo"},
{20, nullptr, "ListLightTicketInfo"},
@@ -51,7 +56,212 @@ public:
};
// clang-format on
RegisterHandlers(functions);
+
+ keys.PopulateTickets();
+ keys.SynthesizeTickets();
+ }
+
+private:
+ bool CheckRightsId(Kernel::HLERequestContext& ctx, const u128& rights_id) {
+ if (rights_id == u128{}) {
+ LOG_ERROR(Service_ETicket, "The rights ID was invalid!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_RIGHTS_ID);
+ return false;
+ }
+
+ return true;
+ }
+
+ void ImportTicket(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto ticket = ctx.ReadBuffer();
+ const auto cert = ctx.ReadBuffer(1);
+
+ if (ticket.size() < sizeof(Core::Crypto::Ticket)) {
+ LOG_ERROR(Service_ETicket, "The input buffer is not large enough!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ return;
+ }
+
+ Core::Crypto::Ticket raw{};
+ std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket));
+
+ if (!keys.AddTicketPersonalized(raw)) {
+ LOG_ERROR(Service_ETicket, "The ticket could not be imported!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetTitleKey(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto rights_id = rp.PopRaw<u128>();
+
+ LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
+
+ if (!CheckRightsId(ctx, rights_id))
+ return;
+
+ const auto key =
+ keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
+
+ if (key == Core::Crypto::Key128{}) {
+ LOG_ERROR(Service_ETicket,
+ "The titlekey doesn't exist in the KeyManager or the rights ID was invalid!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_RIGHTS_ID);
+ return;
+ }
+
+ ctx.WriteBuffer(key.data(), key.size());
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void CountCommonTicket(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_ETicket, "called");
+
+ const auto count = keys.GetCommonTickets().size();
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(count);
+ }
+
+ void CountPersonalizedTicket(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_ETicket, "called");
+
+ const auto count = keys.GetPersonalizedTickets().size();
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(count);
+ }
+
+ void ListCommonTicket(Kernel::HLERequestContext& ctx) {
+ u32 out_entries;
+ if (keys.GetCommonTickets().empty())
+ out_entries = 0;
+ else
+ out_entries = ctx.GetWriteBufferSize() / sizeof(u128);
+
+ LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
+
+ keys.PopulateTickets();
+ const auto tickets = keys.GetCommonTickets();
+ std::vector<u128> ids;
+ std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
+ [](const auto& pair) { return pair.first; });
+
+ out_entries = std::min<u32>(ids.size(), out_entries);
+ ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(out_entries);
}
+
+ void ListPersonalizedTicket(Kernel::HLERequestContext& ctx) {
+ u32 out_entries;
+ if (keys.GetPersonalizedTickets().empty())
+ out_entries = 0;
+ else
+ out_entries = ctx.GetWriteBufferSize() / sizeof(u128);
+
+ LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
+
+ keys.PopulateTickets();
+ const auto tickets = keys.GetPersonalizedTickets();
+ std::vector<u128> ids;
+ std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
+ [](const auto& pair) { return pair.first; });
+
+ out_entries = std::min<u32>(ids.size(), out_entries);
+ ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(out_entries);
+ }
+
+ void GetCommonTicketSize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto rights_id = rp.PopRaw<u128>();
+
+ LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
+
+ if (!CheckRightsId(ctx, rights_id))
+ return;
+
+ const auto ticket = keys.GetCommonTickets().at(rights_id);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(ticket.GetSize());
+ }
+
+ void GetPersonalizedTicketSize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto rights_id = rp.PopRaw<u128>();
+
+ LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
+
+ if (!CheckRightsId(ctx, rights_id))
+ return;
+
+ const auto ticket = keys.GetPersonalizedTickets().at(rights_id);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(ticket.GetSize());
+ }
+
+ void GetCommonTicketData(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto rights_id = rp.PopRaw<u128>();
+
+ LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
+
+ if (!CheckRightsId(ctx, rights_id))
+ return;
+
+ const auto ticket = keys.GetCommonTickets().at(rights_id);
+
+ const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize());
+ ctx.WriteBuffer(&ticket, write_size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(write_size);
+ }
+
+ void GetPersonalizedTicketData(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto rights_id = rp.PopRaw<u128>();
+
+ LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
+
+ if (!CheckRightsId(ctx, rights_id))
+ return;
+
+ const auto ticket = keys.GetPersonalizedTickets().at(rights_id);
+
+ const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize());
+ ctx.WriteBuffer(&ticket, write_size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(write_size);
+ }
+
+ Core::Crypto::KeyManager keys;
};
void InstallInterfaces(SM::ServiceManager& service_manager) {
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
index 2c229bcad..01fa06ad3 100644
--- a/src/core/hle/service/fatal/fatal.cpp
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -5,7 +5,7 @@
#include <array>
#include <cstring>
#include <ctime>
-#include <fmt/time.h>
+#include <fmt/chrono.h>
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/scm_rev.h"
@@ -16,6 +16,7 @@
#include "core/hle/service/fatal/fatal.h"
#include "core/hle/service/fatal/fatal_p.h"
#include "core/hle/service/fatal/fatal_u.h"
+#include "core/reporter.h"
namespace Service::Fatal {
@@ -100,27 +101,10 @@ static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) {
LOG_ERROR(Service_Fatal, "{}", crash_report);
- const std::string crashreport_dir =
- FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + "crash_logs";
-
- if (!FileUtil::CreateFullPath(crashreport_dir)) {
- LOG_ERROR(
- Service_Fatal,
- "Unable to create crash report directory. Possible log directory permissions issue.");
- return;
- }
-
- const std::time_t t = std::time(nullptr);
- const std::string crashreport_filename =
- fmt::format("{}/{:016x}-{:%F-%H%M%S}.log", crashreport_dir, title_id, *std::localtime(&t));
-
- auto file = FileUtil::IOFile(crashreport_filename, "wb");
- if (file.IsOpen()) {
- file.WriteString(crash_report);
- LOG_ERROR(Service_Fatal, "Saving error report to {}", crashreport_filename);
- } else {
- LOG_ERROR(Service_Fatal, "Failed to save error report to {}", crashreport_filename);
- }
+ Core::System::GetInstance().GetReporter().SaveCrashReport(
+ title_id, error_code, info.set_flags, info.program_entry_point, info.sp, info.pc,
+ info.pstate, info.afsr0, info.afsr1, info.esr, info.far, info.registers, info.backtrace,
+ info.backtrace_size, info.ArchAsString(), info.unk10);
}
static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const FatalInfo& info) {
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 1ebfeb4bf..8ce110dd1 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -472,12 +472,12 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
}
}
-void InstallInterfaces(SM::ServiceManager& service_manager, FileSys::VfsFilesystem& vfs) {
+void InstallInterfaces(Core::System& system) {
romfs_factory = nullptr;
- CreateFactories(vfs, false);
- std::make_shared<FSP_LDR>()->InstallAsService(service_manager);
- std::make_shared<FSP_PR>()->InstallAsService(service_manager);
- std::make_shared<FSP_SRV>()->InstallAsService(service_manager);
+ CreateFactories(*system.GetFilesystem(), false);
+ std::make_shared<FSP_LDR>()->InstallAsService(system.ServiceManager());
+ std::make_shared<FSP_PR>()->InstallAsService(system.ServiceManager());
+ std::make_shared<FSP_SRV>(system.GetReporter())->InstallAsService(system.ServiceManager());
}
} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 6481f237c..3849dd89e 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -65,7 +65,7 @@ FileSys::VirtualDir GetModificationDumpRoot(u64 title_id);
// above is called.
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
-void InstallInterfaces(SM::ServiceManager& service_manager, FileSys::VfsFilesystem& vfs);
+void InstallInterfaces(Core::System& system);
// A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of
// pointers and booleans. This makes using a VfsDirectory with switch services much easier and
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index e7df8fd98..d3cd46a9b 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -26,6 +26,7 @@
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/fsp_srv.h"
+#include "core/reporter.h"
namespace Service::FileSystem {
@@ -613,7 +614,7 @@ private:
u64 next_entry_index = 0;
};
-FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
+FSP_SRV::FSP_SRV(const Core::Reporter& reporter) : ServiceFramework("fsp-srv"), reporter(reporter) {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "OpenFileSystem"},
@@ -710,14 +711,14 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
{1001, nullptr, "SetSaveDataSize"},
{1002, nullptr, "SetSaveDataRootPath"},
{1003, nullptr, "DisableAutoSaveDataCreation"},
- {1004, nullptr, "SetGlobalAccessLogMode"},
+ {1004, &FSP_SRV::SetGlobalAccessLogMode, "SetGlobalAccessLogMode"},
{1005, &FSP_SRV::GetGlobalAccessLogMode, "GetGlobalAccessLogMode"},
- {1006, nullptr, "OutputAccessLogToSdCard"},
+ {1006, &FSP_SRV::OutputAccessLogToSdCard, "OutputAccessLogToSdCard"},
{1007, nullptr, "RegisterUpdatePartition"},
{1008, nullptr, "OpenRegisteredUpdatePartition"},
{1009, nullptr, "GetAndClearMemoryReportInfo"},
{1010, nullptr, "SetDataStorageRedirectTarget"},
- {1011, nullptr, "OutputAccessLogToSdCard2"},
+ {1011, &FSP_SRV::GetAccessLogVersionInfo, "GetAccessLogVersionInfo"},
{1100, nullptr, "OverrideSaveDataTransferTokenSignVerificationKey"},
{1110, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId2"},
{1200, nullptr, "OpenMultiCommitManager"},
@@ -814,21 +815,22 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext&
rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space));
}
-void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_FS, "(STUBBED) called");
+void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ log_mode = rp.PopEnum<LogMode>();
- enum class LogMode : u32 {
- Off,
- Log,
- RedirectToSdCard,
- LogToSdCard = Log | RedirectToSdCard,
- };
+ LOG_DEBUG(Service_FS, "called, log_mode={:08X}", static_cast<u32>(log_mode));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_FS, "called");
- // Given we always want to receive logging information,
- // we always specify logging as enabled.
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.PushEnum(LogMode::Log);
+ rb.PushEnum(log_mode);
}
void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
@@ -902,4 +904,26 @@ void FSP_SRV::OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ct
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
}
+void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) {
+ const auto raw = ctx.ReadBuffer();
+ auto log = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(raw.data()), raw.size());
+
+ LOG_DEBUG(Service_FS, "called, log='{}'", log);
+
+ reporter.SaveFilesystemAccessReport(log_mode, std::move(log));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void FSP_SRV::GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_FS, "called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(AccessLogVersion::Latest);
+ rb.Push(access_log_program_index);
+}
+
} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index d7572ba7a..b5486a193 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -7,15 +7,32 @@
#include <memory>
#include "core/hle/service/service.h"
+namespace Core {
+class Reporter;
+}
+
namespace FileSys {
class FileSystemBackend;
}
namespace Service::FileSystem {
+enum class AccessLogVersion : u32 {
+ V7_0_0 = 2,
+
+ Latest = V7_0_0,
+};
+
+enum class LogMode : u32 {
+ Off,
+ Log,
+ RedirectToSdCard,
+ LogToSdCard = Log | RedirectToSdCard,
+};
+
class FSP_SRV final : public ServiceFramework<FSP_SRV> {
public:
- explicit FSP_SRV();
+ explicit FSP_SRV(const Core::Reporter& reporter);
~FSP_SRV() override;
private:
@@ -26,13 +43,20 @@ private:
void OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx);
void OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx);
void OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& ctx);
+ void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx);
void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
+ void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx);
+ void GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx);
FileSys::VirtualFile romfs;
u64 current_process_id = 0;
+ u32 access_log_program_index = 0;
+ LogMode log_mode = LogMode::LogToSdCard;
+
+ const Core::Reporter& reporter;
};
} // namespace Service::FileSystem
diff --git a/src/core/hle/service/friend/errors.h b/src/core/hle/service/friend/errors.h
new file mode 100644
index 000000000..b3996e275
--- /dev/null
+++ b/src/core/hle/service/friend/errors.h
@@ -0,0 +1,12 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::Friend {
+
+constexpr ResultCode ERR_NO_NOTIFICATIONS{ErrorModule::Account, 15};
+}
diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp
index 5100e376c..d1ec12ef9 100644
--- a/src/core/hle/service/friend/friend.cpp
+++ b/src/core/hle/service/friend/friend.cpp
@@ -2,8 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <queue>
#include "common/logging/log.h"
+#include "common/uuid.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/readable_event.h"
+#include "core/hle/kernel/writable_event.h"
+#include "core/hle/service/friend/errors.h"
#include "core/hle/service/friend/friend.h"
#include "core/hle/service/friend/interface.h"
@@ -17,7 +22,7 @@ public:
{0, nullptr, "GetCompletionEvent"},
{1, nullptr, "Cancel"},
{10100, nullptr, "GetFriendListIds"},
- {10101, nullptr, "GetFriendList"},
+ {10101, &IFriendService::GetFriendList, "GetFriendList"},
{10102, nullptr, "UpdateFriendInfo"},
{10110, nullptr, "GetFriendProfileImage"},
{10200, nullptr, "SendFriendRequestForApplication"},
@@ -94,6 +99,23 @@ public:
}
private:
+ enum class PresenceFilter : u32 {
+ None = 0,
+ Online = 1,
+ OnlinePlay = 2,
+ OnlineOrOnlinePlay = 3,
+ };
+
+ struct SizedFriendFilter {
+ PresenceFilter presence;
+ u8 is_favorite;
+ u8 same_app;
+ u8 same_app_played;
+ u8 arbitary_app_played;
+ u64 group_id;
+ };
+ static_assert(sizeof(SizedFriendFilter) == 0x10, "SizedFriendFilter is an invalid size");
+
void DeclareCloseOnlinePlaySession(Kernel::HLERequestContext& ctx) {
// Stub used by Splatoon 2
LOG_WARNING(Service_ACC, "(STUBBED) called");
@@ -107,6 +129,121 @@ private:
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+
+ void GetFriendList(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto friend_offset = rp.Pop<u32>();
+ const auto uuid = rp.PopRaw<Common::UUID>();
+ [[maybe_unused]] const auto filter = rp.PopRaw<SizedFriendFilter>();
+ const auto pid = rp.Pop<u64>();
+ LOG_WARNING(Service_ACC, "(STUBBED) called, offset={}, uuid={}, pid={}", friend_offset,
+ uuid.Format(), pid);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+
+ rb.Push<u32>(0); // Friend count
+ // TODO(ogniK): Return a buffer of u64s which are the "NetworkServiceAccountId"
+ }
+};
+
+class INotificationService final : public ServiceFramework<INotificationService> {
+public:
+ INotificationService(Common::UUID uuid) : ServiceFramework("INotificationService"), uuid(uuid) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &INotificationService::GetEvent, "GetEvent"},
+ {1, &INotificationService::Clear, "Clear"},
+ {2, &INotificationService::Pop, "Pop"}
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void GetEvent(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_ACC, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+
+ if (!is_event_created) {
+ auto& kernel = Core::System::GetInstance().Kernel();
+ notification_event = Kernel::WritableEvent::CreateEventPair(
+ kernel, Kernel::ResetType::Manual, "INotificationService:NotifyEvent");
+ is_event_created = true;
+ }
+ rb.PushCopyObjects(notification_event.readable);
+ }
+
+ void Clear(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_ACC, "called");
+ while (!notifications.empty()) {
+ notifications.pop();
+ }
+ std::memset(&states, 0, sizeof(States));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void Pop(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_ACC, "called");
+
+ if (notifications.empty()) {
+ LOG_ERROR(Service_ACC, "No notifications in queue!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_NO_NOTIFICATIONS);
+ return;
+ }
+
+ const auto notification = notifications.front();
+ notifications.pop();
+
+ switch (notification.notification_type) {
+ case NotificationTypes::HasUpdatedFriendsList:
+ states.has_updated_friends = false;
+ break;
+ case NotificationTypes::HasReceivedFriendRequest:
+ states.has_received_friend_request = false;
+ break;
+ default:
+ // HOS seems not have an error case for an unknown notification
+ LOG_WARNING(Service_ACC, "Unknown notification {:08X}",
+ static_cast<u32>(notification.notification_type));
+ break;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<SizedNotificationInfo>(notification);
+ }
+
+ enum class NotificationTypes : u32 {
+ HasUpdatedFriendsList = 0x65,
+ HasReceivedFriendRequest = 0x1
+ };
+
+ struct SizedNotificationInfo {
+ NotificationTypes notification_type;
+ INSERT_PADDING_WORDS(
+ 1); // TODO(ogniK): This doesn't seem to be used within any IPC returns as of now
+ u64_le account_id;
+ };
+ static_assert(sizeof(SizedNotificationInfo) == 0x10,
+ "SizedNotificationInfo is an incorrect size");
+
+ struct States {
+ bool has_updated_friends;
+ bool has_received_friend_request;
+ };
+
+ Common::UUID uuid;
+ bool is_event_created = false;
+ Kernel::EventPair notification_event;
+ std::queue<SizedNotificationInfo> notifications;
+ States states{};
};
void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) {
@@ -116,6 +253,17 @@ void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
}
+void Module::Interface::CreateNotificationService(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto uuid = rp.PopRaw<Common::UUID>();
+
+ LOG_DEBUG(Service_ACC, "called, uuid={}", uuid.Format());
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<INotificationService>(uuid);
+}
+
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
: ServiceFramework(name), module(std::move(module)) {}
diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h
index e762840cb..38d05fa8e 100644
--- a/src/core/hle/service/friend/friend.h
+++ b/src/core/hle/service/friend/friend.h
@@ -16,6 +16,7 @@ public:
~Interface() override;
void CreateFriendService(Kernel::HLERequestContext& ctx);
+ void CreateNotificationService(Kernel::HLERequestContext& ctx);
protected:
std::shared_ptr<Module> module;
diff --git a/src/core/hle/service/friend/interface.cpp b/src/core/hle/service/friend/interface.cpp
index 5a6840af5..5b384f733 100644
--- a/src/core/hle/service/friend/interface.cpp
+++ b/src/core/hle/service/friend/interface.cpp
@@ -10,7 +10,7 @@ Friend::Friend(std::shared_ptr<Module> module, const char* name)
: Interface(std::move(module), name) {
static const FunctionInfo functions[] = {
{0, &Friend::CreateFriendService, "CreateFriendService"},
- {1, nullptr, "CreateNotificationService"},
+ {1, &Friend::CreateNotificationService, "CreateNotificationService"},
{2, nullptr, "CreateDaemonSuspendSessionService"},
};
RegisterHandlers(functions);
diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp
new file mode 100644
index 000000000..b591ce31b
--- /dev/null
+++ b/src/core/hle/service/glue/arp.cpp
@@ -0,0 +1,297 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+
+#include "common/logging/log.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/service/glue/arp.h"
+#include "core/hle/service/glue/errors.h"
+#include "core/hle/service/glue/manager.h"
+#include "core/hle/service/service.h"
+
+namespace Service::Glue {
+
+namespace {
+std::optional<u64> GetTitleIDForProcessID(const Core::System& system, u64 process_id) {
+ const auto& list = system.Kernel().GetProcessList();
+ const auto iter = std::find_if(list.begin(), list.end(), [&process_id](const auto& process) {
+ return process->GetProcessID() == process_id;
+ });
+
+ if (iter == list.end()) {
+ return std::nullopt;
+ }
+
+ return (*iter)->GetTitleID();
+}
+} // Anonymous namespace
+
+ARP_R::ARP_R(const Core::System& system, const ARPManager& manager)
+ : ServiceFramework{"arp:r"}, system(system), manager(manager) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &ARP_R::GetApplicationLaunchProperty, "GetApplicationLaunchProperty"},
+ {1, &ARP_R::GetApplicationLaunchPropertyWithApplicationId, "GetApplicationLaunchPropertyWithApplicationId"},
+ {2, &ARP_R::GetApplicationControlProperty, "GetApplicationControlProperty"},
+ {3, &ARP_R::GetApplicationControlPropertyWithApplicationId, "GetApplicationControlPropertyWithApplicationId"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+ARP_R::~ARP_R() = default;
+
+void ARP_R::GetApplicationLaunchProperty(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto process_id = rp.PopRaw<u64>();
+
+ LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);
+
+ const auto title_id = GetTitleIDForProcessID(system, process_id);
+ if (!title_id.has_value()) {
+ LOG_ERROR(Service_ARP, "Failed to get title ID for process ID!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_NOT_REGISTERED);
+ return;
+ }
+
+ const auto res = manager.GetLaunchProperty(*title_id);
+
+ if (res.Failed()) {
+ LOG_ERROR(Service_ARP, "Failed to get launch property!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res.Code());
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(*res);
+}
+
+void ARP_R::GetApplicationLaunchPropertyWithApplicationId(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto title_id = rp.PopRaw<u64>();
+
+ LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id);
+
+ const auto res = manager.GetLaunchProperty(title_id);
+
+ if (res.Failed()) {
+ LOG_ERROR(Service_ARP, "Failed to get launch property!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res.Code());
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(*res);
+}
+
+void ARP_R::GetApplicationControlProperty(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto process_id = rp.PopRaw<u64>();
+
+ LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);
+
+ const auto title_id = GetTitleIDForProcessID(system, process_id);
+ if (!title_id.has_value()) {
+ LOG_ERROR(Service_ARP, "Failed to get title ID for process ID!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_NOT_REGISTERED);
+ return;
+ }
+
+ const auto res = manager.GetControlProperty(*title_id);
+
+ if (res.Failed()) {
+ LOG_ERROR(Service_ARP, "Failed to get control property!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res.Code());
+ return;
+ }
+
+ ctx.WriteBuffer(*res);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void ARP_R::GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto title_id = rp.PopRaw<u64>();
+
+ LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id);
+
+ const auto res = manager.GetControlProperty(title_id);
+
+ if (res.Failed()) {
+ LOG_ERROR(Service_ARP, "Failed to get control property!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res.Code());
+ return;
+ }
+
+ ctx.WriteBuffer(*res);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+class IRegistrar final : public ServiceFramework<IRegistrar> {
+ friend class ARP_W;
+
+public:
+ explicit IRegistrar(
+ std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issuer)
+ : ServiceFramework{"IRegistrar"}, issue_process_id(std::move(issuer)) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IRegistrar::Issue, "Issue"},
+ {1, &IRegistrar::SetApplicationLaunchProperty, "SetApplicationLaunchProperty"},
+ {2, &IRegistrar::SetApplicationControlProperty, "SetApplicationControlProperty"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void Issue(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto process_id = rp.PopRaw<u64>();
+
+ LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);
+
+ if (process_id == 0) {
+ LOG_ERROR(Service_ARP, "Must have non-zero process ID!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_INVALID_PROCESS_ID);
+ return;
+ }
+
+ if (issued) {
+ LOG_ERROR(Service_ARP,
+ "Attempted to issue registrar, but registrar is already issued!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_INVALID_ACCESS);
+ return;
+ }
+
+ issue_process_id(process_id, launch, std::move(control));
+ issued = true;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void SetApplicationLaunchProperty(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_ARP, "called");
+
+ if (issued) {
+ LOG_ERROR(
+ Service_ARP,
+ "Attempted to set application launch property, but registrar is already issued!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_INVALID_ACCESS);
+ return;
+ }
+
+ IPC::RequestParser rp{ctx};
+ launch = rp.PopRaw<ApplicationLaunchProperty>();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void SetApplicationControlProperty(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_ARP, "called");
+
+ if (issued) {
+ LOG_ERROR(
+ Service_ARP,
+ "Attempted to set application control property, but registrar is already issued!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_INVALID_ACCESS);
+ return;
+ }
+
+ control = ctx.ReadBuffer();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issue_process_id;
+ bool issued = false;
+ ApplicationLaunchProperty launch;
+ std::vector<u8> control;
+};
+
+ARP_W::ARP_W(const Core::System& system, ARPManager& manager)
+ : ServiceFramework{"arp:w"}, system(system), manager(manager) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"},
+ {1, &ARP_W::DeleteProperties, "DeleteProperties"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+ARP_W::~ARP_W() = default;
+
+void ARP_W::AcquireRegistrar(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_ARP, "called");
+
+ registrar = std::make_shared<IRegistrar>(
+ [this](u64 process_id, ApplicationLaunchProperty launch, std::vector<u8> control) {
+ const auto res = GetTitleIDForProcessID(system, process_id);
+ if (!res.has_value()) {
+ return ERR_NOT_REGISTERED;
+ }
+
+ return manager.Register(*res, launch, std::move(control));
+ });
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface(registrar);
+}
+
+void ARP_W::DeleteProperties(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto process_id = rp.PopRaw<u64>();
+
+ LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);
+
+ if (process_id == 0) {
+ LOG_ERROR(Service_ARP, "Must have non-zero process ID!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_INVALID_PROCESS_ID);
+ return;
+ }
+
+ const auto title_id = GetTitleIDForProcessID(system, process_id);
+
+ if (!title_id.has_value()) {
+ LOG_ERROR(Service_ARP, "No title ID for process ID!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_NOT_REGISTERED);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(manager.Unregister(*title_id));
+}
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/arp.h b/src/core/hle/service/glue/arp.h
new file mode 100644
index 000000000..d5f8a7e7a
--- /dev/null
+++ b/src/core/hle/service/glue/arp.h
@@ -0,0 +1,43 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service::Glue {
+
+class ARPManager;
+class IRegistrar;
+
+class ARP_R final : public ServiceFramework<ARP_R> {
+public:
+ explicit ARP_R(const Core::System& system, const ARPManager& manager);
+ ~ARP_R() override;
+
+private:
+ void GetApplicationLaunchProperty(Kernel::HLERequestContext& ctx);
+ void GetApplicationLaunchPropertyWithApplicationId(Kernel::HLERequestContext& ctx);
+ void GetApplicationControlProperty(Kernel::HLERequestContext& ctx);
+ void GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx);
+
+ const Core::System& system;
+ const ARPManager& manager;
+};
+
+class ARP_W final : public ServiceFramework<ARP_W> {
+public:
+ explicit ARP_W(const Core::System& system, ARPManager& manager);
+ ~ARP_W() override;
+
+private:
+ void AcquireRegistrar(Kernel::HLERequestContext& ctx);
+ void DeleteProperties(Kernel::HLERequestContext& ctx);
+
+ const Core::System& system;
+ ARPManager& manager;
+ std::shared_ptr<IRegistrar> registrar;
+};
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/bgtc.cpp b/src/core/hle/service/glue/bgtc.cpp
new file mode 100644
index 000000000..cd89d088f
--- /dev/null
+++ b/src/core/hle/service/glue/bgtc.cpp
@@ -0,0 +1,50 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/glue/bgtc.h"
+
+namespace Service::Glue {
+
+BGTC_T::BGTC_T() : ServiceFramework{"bgtc:t"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {1, nullptr, "NotifyTaskStarting"},
+ {2, nullptr, "NotifyTaskFinished"},
+ {3, nullptr, "GetTriggerEvent"},
+ {4, nullptr, "IsInHalfAwake"},
+ {5, nullptr, "NotifyClientName"},
+ {6, nullptr, "IsInFullAwake"},
+ {11, nullptr, "ScheduleTask"},
+ {12, nullptr, "GetScheduledTaskInterval"},
+ {13, nullptr, "UnscheduleTask"},
+ {14, nullptr, "GetScheduleEvent"},
+ {15, nullptr, "SchedulePeriodicTask"},
+ {101, nullptr, "GetOperationMode"},
+ {102, nullptr, "WillDisconnectNetworkWhenEnteringSleep"},
+ {103, nullptr, "WillStayHalfAwakeInsteadSleep"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+BGTC_T::~BGTC_T() = default;
+
+BGTC_SC::BGTC_SC() : ServiceFramework{"bgtc:sc"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {1, nullptr, "GetState"},
+ {2, nullptr, "GetStateChangedEvent"},
+ {3, nullptr, "NotifyEnteringHalfAwake"},
+ {4, nullptr, "NotifyLeavingHalfAwake"},
+ {5, nullptr, "SetIsUsingSleepUnsupportedDevices"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+BGTC_SC::~BGTC_SC() = default;
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/bgtc.h b/src/core/hle/service/glue/bgtc.h
new file mode 100644
index 000000000..81844f03e
--- /dev/null
+++ b/src/core/hle/service/glue/bgtc.h
@@ -0,0 +1,23 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service::Glue {
+
+class BGTC_T final : public ServiceFramework<BGTC_T> {
+public:
+ BGTC_T();
+ ~BGTC_T() override;
+};
+
+class BGTC_SC final : public ServiceFramework<BGTC_SC> {
+public:
+ BGTC_SC();
+ ~BGTC_SC() override;
+};
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/errors.h b/src/core/hle/service/glue/errors.h
new file mode 100644
index 000000000..c2874c585
--- /dev/null
+++ b/src/core/hle/service/glue/errors.h
@@ -0,0 +1,16 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::Glue {
+
+constexpr ResultCode ERR_INVALID_RESOURCE{ErrorModule::ARP, 0x1E};
+constexpr ResultCode ERR_INVALID_PROCESS_ID{ErrorModule::ARP, 0x1F};
+constexpr ResultCode ERR_INVALID_ACCESS{ErrorModule::ARP, 0x2A};
+constexpr ResultCode ERR_NOT_REGISTERED{ErrorModule::ARP, 0x66};
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/glue.cpp b/src/core/hle/service/glue/glue.cpp
new file mode 100644
index 000000000..c728e815c
--- /dev/null
+++ b/src/core/hle/service/glue/glue.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include "core/core.h"
+#include "core/hle/service/glue/arp.h"
+#include "core/hle/service/glue/bgtc.h"
+#include "core/hle/service/glue/glue.h"
+
+namespace Service::Glue {
+
+void InstallInterfaces(Core::System& system) {
+ // ARP
+ std::make_shared<ARP_R>(system, system.GetARPManager())
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<ARP_W>(system, system.GetARPManager())
+ ->InstallAsService(system.ServiceManager());
+
+ // BackGround Task Controller
+ std::make_shared<BGTC_T>()->InstallAsService(system.ServiceManager());
+ std::make_shared<BGTC_SC>()->InstallAsService(system.ServiceManager());
+}
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/glue.h b/src/core/hle/service/glue/glue.h
new file mode 100644
index 000000000..112cd238b
--- /dev/null
+++ b/src/core/hle/service/glue/glue.h
@@ -0,0 +1,16 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace Service::Glue {
+
+/// Registers all Glue services with the specified service manager.
+void InstallInterfaces(Core::System& system);
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/manager.cpp b/src/core/hle/service/glue/manager.cpp
new file mode 100644
index 000000000..6da52d2d6
--- /dev/null
+++ b/src/core/hle/service/glue/manager.cpp
@@ -0,0 +1,78 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/glue/errors.h"
+#include "core/hle/service/glue/manager.h"
+
+namespace Service::Glue {
+
+struct ARPManager::MapEntry {
+ ApplicationLaunchProperty launch;
+ std::vector<u8> control;
+};
+
+ARPManager::ARPManager() = default;
+
+ARPManager::~ARPManager() = default;
+
+ResultVal<ApplicationLaunchProperty> ARPManager::GetLaunchProperty(u64 title_id) const {
+ if (title_id == 0) {
+ return ERR_INVALID_PROCESS_ID;
+ }
+
+ const auto iter = entries.find(title_id);
+ if (iter == entries.end()) {
+ return ERR_NOT_REGISTERED;
+ }
+
+ return MakeResult<ApplicationLaunchProperty>(iter->second.launch);
+}
+
+ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const {
+ if (title_id == 0) {
+ return ERR_INVALID_PROCESS_ID;
+ }
+
+ const auto iter = entries.find(title_id);
+ if (iter == entries.end()) {
+ return ERR_NOT_REGISTERED;
+ }
+
+ return MakeResult<std::vector<u8>>(iter->second.control);
+}
+
+ResultCode ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch,
+ std::vector<u8> control) {
+ if (title_id == 0) {
+ return ERR_INVALID_PROCESS_ID;
+ }
+
+ const auto iter = entries.find(title_id);
+ if (iter != entries.end()) {
+ return ERR_INVALID_ACCESS;
+ }
+
+ entries.insert_or_assign(title_id, MapEntry{launch, std::move(control)});
+ return RESULT_SUCCESS;
+}
+
+ResultCode ARPManager::Unregister(u64 title_id) {
+ if (title_id == 0) {
+ return ERR_INVALID_PROCESS_ID;
+ }
+
+ const auto iter = entries.find(title_id);
+ if (iter == entries.end()) {
+ return ERR_NOT_REGISTERED;
+ }
+
+ entries.erase(iter);
+ return RESULT_SUCCESS;
+}
+
+void ARPManager::ResetAll() {
+ entries.clear();
+}
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/manager.h b/src/core/hle/service/glue/manager.h
new file mode 100644
index 000000000..a7f5ce3ee
--- /dev/null
+++ b/src/core/hle/service/glue/manager.h
@@ -0,0 +1,63 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <map>
+#include <vector>
+#include "common/common_types.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/romfs_factory.h"
+#include "core/hle/result.h"
+
+namespace Service::Glue {
+
+struct ApplicationLaunchProperty {
+ u64 title_id;
+ u32 version;
+ FileSys::StorageId base_game_storage_id;
+ FileSys::StorageId update_storage_id;
+ u8 program_index;
+ u8 reserved;
+};
+static_assert(sizeof(ApplicationLaunchProperty) == 0x10,
+ "ApplicationLaunchProperty has incorrect size.");
+
+// A class to manage state related to the arp:w and arp:r services, specifically the registration
+// and unregistration of launch and control properties.
+class ARPManager {
+public:
+ ARPManager();
+ ~ARPManager();
+
+ // Returns the ApplicationLaunchProperty corresponding to the provided title ID if it was
+ // previously registered, otherwise ERR_NOT_REGISTERED if it was never registered or
+ // ERR_INVALID_PROCESS_ID if the title ID is 0.
+ ResultVal<ApplicationLaunchProperty> GetLaunchProperty(u64 title_id) const;
+
+ // Returns a vector of the raw bytes of NACP data (necessarily 0x4000 in size) corresponding to
+ // the provided title ID if it was previously registered, otherwise ERR_NOT_REGISTERED if it was
+ // never registered or ERR_INVALID_PROCESS_ID if the title ID is 0.
+ ResultVal<std::vector<u8>> GetControlProperty(u64 title_id) const;
+
+ // Adds a new entry to the internal database with the provided parameters, returning
+ // ERR_INVALID_ACCESS if attempting to re-register a title ID without an intermediate Unregister
+ // step, and ERR_INVALID_PROCESS_ID if the title ID is 0.
+ ResultCode Register(u64 title_id, ApplicationLaunchProperty launch, std::vector<u8> control);
+
+ // Removes the registration for the provided title ID from the database, returning
+ // ERR_NOT_REGISTERED if it doesn't exist in the database and ERR_INVALID_PROCESS_ID if the
+ // title ID is 0.
+ ResultCode Unregister(u64 title_id);
+
+ // Removes all entries from the database, always succeeds. Should only be used when resetting
+ // system state.
+ void ResetAll();
+
+private:
+ struct MapEntry;
+ std::map<u64, MapEntry> entries;
+};
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index fdd6d79a2..e47fe8188 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -548,6 +548,37 @@ void Controller_NPad::DisconnectNPad(u32 npad_id) {
connected_controllers[NPadIdToIndex(npad_id)].is_connected = false;
}
+void Controller_NPad::StartLRAssignmentMode() {
+ // Nothing internally is used for lr assignment mode. Since we have the ability to set the
+ // controller types from boot, it doesn't really matter about showing a selection screen
+ is_in_lr_assignment_mode = true;
+}
+
+void Controller_NPad::StopLRAssignmentMode() {
+ is_in_lr_assignment_mode = false;
+}
+
+bool Controller_NPad::SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2) {
+ if (npad_id_1 == NPAD_HANDHELD || npad_id_2 == NPAD_HANDHELD || npad_id_1 == NPAD_UNKNOWN ||
+ npad_id_2 == NPAD_UNKNOWN) {
+ return true;
+ }
+ const auto npad_index_1 = NPadIdToIndex(npad_id_1);
+ const auto npad_index_2 = NPadIdToIndex(npad_id_2);
+
+ if (!IsControllerSupported(connected_controllers[npad_index_1].type) ||
+ !IsControllerSupported(connected_controllers[npad_index_2].type)) {
+ return false;
+ }
+
+ std::swap(connected_controllers[npad_index_1].type, connected_controllers[npad_index_2].type);
+
+ InitNewlyAddedControler(npad_index_1);
+ InitNewlyAddedControler(npad_index_2);
+
+ return true;
+}
+
bool Controller_NPad::IsControllerSupported(NPadControllerType controller) {
if (controller == NPadControllerType::Handheld) {
// Handheld is not even a supported type, lets stop here
@@ -605,10 +636,15 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
return LedPattern{0, 0, 0, 0};
};
}
+
void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
can_controllers_vibrate = can_vibrate;
}
+bool Controller_NPad::IsVibrationEnabled() const {
+ return can_controllers_vibrate;
+}
+
void Controller_NPad::ClearAllConnectedControllers() {
for (auto& controller : connected_controllers) {
if (controller.is_connected && controller.type != NPadControllerType::None) {
@@ -617,6 +653,7 @@ void Controller_NPad::ClearAllConnectedControllers() {
}
}
}
+
void Controller_NPad::DisconnectAllConnectedControllers() {
std::for_each(connected_controllers.begin(), connected_controllers.end(),
[](ControllerHolder& controller) { controller.is_connected = false; });
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 4ff50b3cd..f28b36806 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -119,11 +119,16 @@ public:
void DisconnectNPad(u32 npad_id);
LedPattern GetLedPattern(u32 npad_id);
void SetVibrationEnabled(bool can_vibrate);
+ bool IsVibrationEnabled() const;
void ClearAllConnectedControllers();
void DisconnectAllConnectedControllers();
void ConnectAllDisconnectedControllers();
void ClearAllControllers();
+ void StartLRAssignmentMode();
+ void StopLRAssignmentMode();
+ bool SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2);
+
// Logical OR for all buttons presses on all controllers
// Specifically for cheat engine and other features.
u32 GetAndResetPressState();
@@ -321,5 +326,6 @@ private:
void RequestPadStateUpdate(u32 npad_id);
std::array<ControllerPad, 10> npad_pad_states{};
bool IsControllerSupported(NPadControllerType controller);
+ bool is_in_lr_assignment_mode{false};
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h
new file mode 100644
index 000000000..3583642e7
--- /dev/null
+++ b/src/core/hle/service/hid/errors.h
@@ -0,0 +1,13 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::HID {
+
+constexpr ResultCode ERR_NPAD_NOT_CONNECTED{ErrorModule::HID, 710};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index a4ad95d96..f8b1ca816 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -16,6 +16,7 @@
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/writable_event.h"
+#include "core/hle/service/hid/errors.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/hid/irs.h"
#include "core/hle/service/hid/xcd.h"
@@ -202,11 +203,11 @@ Hid::Hid() : ServiceFramework("hid") {
{123, nullptr, "SetNpadJoyAssignmentModeSingleByDefault"},
{124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"},
{125, &Hid::MergeSingleJoyAsDualJoy, "MergeSingleJoyAsDualJoy"},
- {126, nullptr, "StartLrAssignmentMode"},
- {127, nullptr, "StopLrAssignmentMode"},
+ {126, &Hid::StartLrAssignmentMode, "StartLrAssignmentMode"},
+ {127, &Hid::StopLrAssignmentMode, "StopLrAssignmentMode"},
{128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"},
{129, nullptr, "GetNpadHandheldActivationMode"},
- {130, nullptr, "SwapNpadAssignment"},
+ {130, &Hid::SwapNpadAssignment, "SwapNpadAssignment"},
{131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"},
{132, nullptr, "EnableUnintendedHomeButtonInputProtection"},
{133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"},
@@ -215,8 +216,8 @@ Hid::Hid() : ServiceFramework("hid") {
{201, &Hid::SendVibrationValue, "SendVibrationValue"},
{202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"},
{203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"},
- {204, nullptr, "PermitVibration"},
- {205, nullptr, "IsVibrationPermitted"},
+ {204, &Hid::PermitVibration, "PermitVibration"},
+ {205, &Hid::IsVibrationPermitted, "IsVibrationPermitted"},
{206, &Hid::SendVibrationValues, "SendVibrationValues"},
{207, nullptr, "SendVibrationGcErmCommand"},
{208, nullptr, "GetActualVibrationGcErmCommand"},
@@ -678,6 +679,27 @@ void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
rb.PushIpcInterface<IActiveVibrationDeviceList>();
}
+void Hid::PermitVibration(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto can_vibrate{rp.Pop<bool>()};
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetVibrationEnabled(can_vibrate);
+
+ LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).IsVibrationEnabled());
+}
+
void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
@@ -733,6 +755,49 @@ void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
}
+void Hid::StartLrAssignmentMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+ auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ controller.StartLRAssignmentMode();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+ auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ controller.StopLRAssignmentMode();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_1{rp.Pop<u32>()};
+ const auto npad_2{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, npad_1={}, npad_2={}",
+ applet_resource_user_id, npad_1, npad_2);
+
+ auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ IPC::ResponseBuilder rb{ctx, 2};
+ if (controller.SwapNpadAssignment(npad_1, npad_2)) {
+ rb.Push(RESULT_SUCCESS);
+ } else {
+ LOG_ERROR(Service_HID, "Npads are not connected!");
+ rb.Push(ERR_NPAD_NOT_CONNECTED);
+ }
+}
+
class HidDbg final : public ServiceFramework<HidDbg> {
public:
explicit HidDbg() : ServiceFramework{"hid:dbg"} {
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index d3660cad2..2fd6d9fc7 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -114,11 +114,16 @@ private:
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx);
void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx);
+ void PermitVibration(Kernel::HLERequestContext& ctx);
+ void IsVibrationPermitted(Kernel::HLERequestContext& ctx);
void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
void StopSixAxisSensor(Kernel::HLERequestContext& ctx);
void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
+ void StartLrAssignmentMode(Kernel::HLERequestContext& ctx);
+ void StopLrAssignmentMode(Kernel::HLERequestContext& ctx);
+ void SwapNpadAssignment(Kernel::HLERequestContext& ctx);
std::shared_ptr<IAppletResource> applet_resource;
};
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index 5af925515..8ddad8682 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -310,7 +310,7 @@ public:
if (!IsValidNROHash(hash)) {
LOG_ERROR(Service_LDR,
"NRO hash is not present in any currently loaded NRRs (hash={})!",
- Common::HexArrayToString(hash));
+ Common::HexToString(hash));
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_MISSING_NRR_HASH);
return;
@@ -345,14 +345,16 @@ public:
vm_manager
.MirrorMemory(*map_address, nro_address, nro_size, Kernel::MemoryState::ModuleCode)
.IsSuccess());
- ASSERT(vm_manager.UnmapRange(nro_address, nro_size).IsSuccess());
+ ASSERT(vm_manager.ReprotectRange(nro_address, nro_size, Kernel::VMAPermission::None)
+ .IsSuccess());
if (bss_size > 0) {
ASSERT(vm_manager
.MirrorMemory(*map_address + nro_size, bss_address, bss_size,
Kernel::MemoryState::ModuleCode)
.IsSuccess());
- ASSERT(vm_manager.UnmapRange(bss_address, bss_size).IsSuccess());
+ ASSERT(vm_manager.ReprotectRange(bss_address, bss_size, Kernel::VMAPermission::None)
+ .IsSuccess());
}
vm_manager.ReprotectRange(*map_address, header.text_size,
@@ -364,7 +366,8 @@ public:
Core::System::GetInstance().InvalidateCpuInstructionCaches();
- nro.insert_or_assign(*map_address, NROInfo{hash, nro_size + bss_size});
+ nro.insert_or_assign(*map_address,
+ NROInfo{hash, nro_address, nro_size, bss_address, bss_size});
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
@@ -409,9 +412,23 @@ public:
}
auto& vm_manager = Core::CurrentProcess()->VMManager();
- const auto& nro_size = iter->second.size;
+ const auto& nro_info = iter->second;
- ASSERT(vm_manager.UnmapRange(nro_address, nro_size).IsSuccess());
+ // Unmap the mirrored memory
+ ASSERT(
+ vm_manager.UnmapRange(nro_address, nro_info.nro_size + nro_info.bss_size).IsSuccess());
+
+ // Reprotect the source memory
+ ASSERT(vm_manager
+ .ReprotectRange(nro_info.nro_address, nro_info.nro_size,
+ Kernel::VMAPermission::ReadWrite)
+ .IsSuccess());
+ if (nro_info.bss_size > 0) {
+ ASSERT(vm_manager
+ .ReprotectRange(nro_info.bss_address, nro_info.bss_size,
+ Kernel::VMAPermission::ReadWrite)
+ .IsSuccess());
+ }
Core::System::GetInstance().InvalidateCpuInstructionCaches();
@@ -473,7 +490,10 @@ private:
struct NROInfo {
SHA256Hash hash;
- u64 size;
+ VAddr nro_address;
+ u64 nro_size;
+ VAddr bss_address;
+ u64 bss_size;
};
bool initialized = false;
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp
index ce84e25ed..0b3923ad9 100644
--- a/src/core/hle/service/mii/mii.cpp
+++ b/src/core/hle/service/mii/mii.cpp
@@ -48,7 +48,7 @@ public:
{19, nullptr, "Export"},
{20, nullptr, "IsBrokenDatabaseWithClearFlag"},
{21, &IDatabaseService::GetIndex, "GetIndex"},
- {22, nullptr, "SetInterfaceVersion"},
+ {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"},
{23, nullptr, "Convert"},
};
// clang-format on
@@ -350,8 +350,22 @@ private:
rb.Push(index);
}
+ void SetInterfaceVersion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ current_interface_version = rp.PopRaw<u32>();
+
+ LOG_DEBUG(Service_Mii, "called, interface_version={:08X}", current_interface_version);
+
+ UNIMPLEMENTED_IF(current_interface_version != 1);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
MiiManager db;
+ u32 current_interface_version = 0;
+
// Last read offsets of Get functions
std::array<u32, 4> offsets{};
};
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index 131b01d62..8d0353075 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -175,6 +175,10 @@ MiiStoreData ConvertInfoToStoreData(const MiiInfo& info) {
} // namespace
std::ostream& operator<<(std::ostream& os, Source source) {
+ if (static_cast<std::size_t>(source) >= SOURCE_NAMES.size()) {
+ return os << "[UNKNOWN SOURCE]";
+ }
+
os << SOURCE_NAMES.at(static_cast<std::size_t>(source));
return os;
}
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
index ad176f89d..2a522136d 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -77,7 +77,7 @@ enum class LoadState : u32 {
Done = 1,
};
-static void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output,
+static void DecryptSharedFont(const std::vector<u32>& input, Kernel::PhysicalMemory& output,
std::size_t& offset) {
ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,
"Shared fonts exceeds 17mb!");
@@ -94,7 +94,7 @@ static void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& ou
offset += transformed_font.size() * sizeof(u32);
}
-static void EncryptSharedFont(const std::vector<u8>& input, std::vector<u8>& output,
+static void EncryptSharedFont(const std::vector<u8>& input, Kernel::PhysicalMemory& output,
std::size_t& offset) {
ASSERT_MSG(offset + input.size() + 8 < SHARED_FONT_MEM_SIZE, "Shared fonts exceeds 17mb!");
const u32 KEY = EXPECTED_MAGIC ^ EXPECTED_RESULT;
@@ -121,7 +121,7 @@ struct PL_U::Impl {
return shared_font_regions.at(index);
}
- void BuildSharedFontsRawRegions(const std::vector<u8>& input) {
+ void BuildSharedFontsRawRegions(const Kernel::PhysicalMemory& input) {
// As we can derive the xor key we can just populate the offsets
// based on the shared memory dump
unsigned cur_offset = 0;
@@ -144,7 +144,7 @@ struct PL_U::Impl {
Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem;
/// Backing memory for the shared font data
- std::shared_ptr<std::vector<u8>> shared_font;
+ std::shared_ptr<Kernel::PhysicalMemory> shared_font;
// Automatically populated based on shared_fonts dump or system archives.
std::vector<FontRegion> shared_font_regions;
@@ -166,7 +166,7 @@ PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} {
// Rebuild shared fonts from data ncas
if (nand->HasEntry(static_cast<u64>(FontArchives::Standard),
FileSys::ContentRecordType::Data)) {
- impl->shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE);
+ impl->shared_font = std::make_shared<Kernel::PhysicalMemory>(SHARED_FONT_MEM_SIZE);
for (auto font : SHARED_FONTS) {
const auto nca =
nand->GetEntry(static_cast<u64>(font.first), FileSys::ContentRecordType::Data);
@@ -207,7 +207,7 @@ PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} {
}
} else {
- impl->shared_font = std::make_shared<std::vector<u8>>(
+ impl->shared_font = std::make_shared<Kernel::PhysicalMemory>(
SHARED_FONT_MEM_SIZE); // Shared memory needs to always be allocated and a fixed size
const std::string user_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir);
diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h
index 4f6042b00..5b8248433 100644
--- a/src/core/hle/service/nvdrv/devices/nvdevice.h
+++ b/src/core/hle/service/nvdrv/devices/nvdevice.h
@@ -8,6 +8,11 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/swap.h"
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Core {
+class System;
+}
namespace Service::Nvidia::Devices {
@@ -15,7 +20,7 @@ namespace Service::Nvidia::Devices {
/// implement the ioctl interface.
class nvdevice {
public:
- nvdevice() = default;
+ explicit nvdevice(Core::System& system) : system{system} {};
virtual ~nvdevice() = default;
union Ioctl {
u32_le raw;
@@ -33,7 +38,11 @@ public:
* @param output A buffer where the output data will be written to.
* @returns The result code of the ioctl.
*/
- virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) = 0;
+ virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) = 0;
+
+protected:
+ Core::System& system;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index 20c7c39aa..926a1285d 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -13,10 +13,12 @@
namespace Service::Nvidia::Devices {
-nvdisp_disp0::nvdisp_disp0(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {}
+nvdisp_disp0::nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
+ : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvdisp_disp0 ::~nvdisp_disp0() = default;
-u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) {
UNIMPLEMENTED_MSG("Unimplemented ioctl");
return 0;
}
@@ -34,9 +36,8 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u3
addr, offset, width, height, stride, static_cast<PixelFormat>(format),
transform, crop_rect};
- auto& instance = Core::System::GetInstance();
- instance.GetPerfStats().EndGameFrame();
- instance.GPU().SwapBuffers(framebuffer);
+ system.GetPerfStats().EndGameFrame();
+ system.GPU().SwapBuffers(&framebuffer);
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
index 12f3ef825..e79e490ff 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -17,10 +17,11 @@ class nvmap;
class nvdisp_disp0 final : public nvdevice {
public:
- explicit nvdisp_disp0(std::shared_ptr<nvmap> nvmap_dev);
+ explicit nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvdisp_disp0() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) override;
/// Performs a screen flip, drawing the buffer pointed to by the handle.
void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride,
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
index af62d33d2..24ab3f2e9 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -22,10 +22,12 @@ enum {
};
}
-nvhost_as_gpu::nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {}
+nvhost_as_gpu::nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
+ : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvhost_as_gpu::~nvhost_as_gpu() = default;
-u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
@@ -65,7 +67,7 @@ u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>&
LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages,
params.page_size, params.flags);
- auto& gpu = Core::System::GetInstance().GPU();
+ auto& gpu = system.GPU();
const u64 size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
if (params.flags & 1) {
params.offset = gpu.MemoryManager().AllocateSpace(params.offset, size, 1);
@@ -85,7 +87,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
std::vector<IoctlRemapEntry> entries(num_entries);
std::memcpy(entries.data(), input.data(), input.size());
- auto& gpu = Core::System::GetInstance().GPU();
+ auto& gpu = system.GPU();
for (const auto& entry : entries) {
LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
entry.offset, entry.nvmap_handle, entry.pages);
@@ -136,7 +138,7 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou
// case to prevent unexpected behavior.
ASSERT(object->id == params.nvmap_handle);
- auto& gpu = Core::System::GetInstance().GPU();
+ auto& gpu = system.GPU();
if (params.flags & 1) {
params.offset = gpu.MemoryManager().MapBufferEx(object->addr, params.offset, object->size);
@@ -173,8 +175,7 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
return 0;
}
- params.offset = Core::System::GetInstance().GPU().MemoryManager().UnmapBuffer(params.offset,
- itr->second.size);
+ params.offset = system.GPU().MemoryManager().UnmapBuffer(params.offset, itr->second.size);
buffer_mappings.erase(itr->second.offset);
std::memcpy(output.data(), &params, output.size());
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
index eb14b1da8..30ca5f4c3 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
@@ -17,10 +17,11 @@ class nvmap;
class nvhost_as_gpu final : public nvdevice {
public:
- explicit nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev);
+ explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvhost_as_gpu() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
index b39fb9ef9..9a66a5f88 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -7,14 +7,20 @@
#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hle/kernel/readable_event.h"
+#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
+#include "video_core/gpu.h"
namespace Service::Nvidia::Devices {
-nvhost_ctrl::nvhost_ctrl() = default;
+nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface)
+ : nvdevice(system), events_interface{events_interface} {}
nvhost_ctrl::~nvhost_ctrl() = default;
-u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
@@ -22,11 +28,15 @@ u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<
case IoctlCommand::IocGetConfigCommand:
return NvOsGetConfigU32(input, output);
case IoctlCommand::IocCtrlEventWaitCommand:
- return IocCtrlEventWait(input, output, false);
+ return IocCtrlEventWait(input, output, false, ctrl);
case IoctlCommand::IocCtrlEventWaitAsyncCommand:
- return IocCtrlEventWait(input, output, true);
+ return IocCtrlEventWait(input, output, true, ctrl);
case IoctlCommand::IocCtrlEventRegisterCommand:
return IocCtrlEventRegister(input, output);
+ case IoctlCommand::IocCtrlEventUnregisterCommand:
+ return IocCtrlEventUnregister(input, output);
+ case IoctlCommand::IocCtrlEventSignalCommand:
+ return IocCtrlEventSignal(input, output);
}
UNIMPLEMENTED_MSG("Unimplemented ioctl");
return 0;
@@ -41,23 +51,137 @@ u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>&
}
u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
- bool is_async) {
+ bool is_async, IoctlCtrl& ctrl) {
IocCtrlEventWaitParams params{};
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service_NVDRV,
- "(STUBBED) called, syncpt_id={}, threshold={}, timeout={}, is_async={}",
- params.syncpt_id, params.threshold, params.timeout, is_async);
+ LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_async={}",
+ params.syncpt_id, params.threshold, params.timeout, is_async);
- // TODO(Subv): Implement actual syncpt waiting.
- params.value = 0;
+ if (params.syncpt_id >= MaxSyncPoints) {
+ return NvResult::BadParameter;
+ }
+
+ auto& gpu = system.GPU();
+ // This is mostly to take into account unimplemented features. As synced
+ // gpu is always synced.
+ if (!gpu.IsAsync()) {
+ return NvResult::Success;
+ }
+ auto lock = gpu.LockSync();
+ const u32 current_syncpoint_value = gpu.GetSyncpointValue(params.syncpt_id);
+ const s32 diff = current_syncpoint_value - params.threshold;
+ if (diff >= 0) {
+ params.value = current_syncpoint_value;
+ std::memcpy(output.data(), &params, sizeof(params));
+ return NvResult::Success;
+ }
+ const u32 target_value = current_syncpoint_value - diff;
+
+ if (!is_async) {
+ params.value = 0;
+ }
+
+ if (params.timeout == 0) {
+ std::memcpy(output.data(), &params, sizeof(params));
+ return NvResult::Timeout;
+ }
+
+ u32 event_id;
+ if (is_async) {
+ event_id = params.value & 0x00FF;
+ if (event_id >= MaxNvEvents) {
+ std::memcpy(output.data(), &params, sizeof(params));
+ return NvResult::BadParameter;
+ }
+ } else {
+ if (ctrl.fresh_call) {
+ const auto result = events_interface.GetFreeEvent();
+ if (result) {
+ event_id = *result;
+ } else {
+ LOG_CRITICAL(Service_NVDRV, "No Free Events available!");
+ event_id = params.value & 0x00FF;
+ }
+ } else {
+ event_id = ctrl.event_id;
+ }
+ }
+
+ EventState status = events_interface.status[event_id];
+ if (event_id < MaxNvEvents || status == EventState::Free || status == EventState::Registered) {
+ events_interface.SetEventStatus(event_id, EventState::Waiting);
+ events_interface.assigned_syncpt[event_id] = params.syncpt_id;
+ events_interface.assigned_value[event_id] = target_value;
+ if (is_async) {
+ params.value = params.syncpt_id << 4;
+ } else {
+ params.value = ((params.syncpt_id & 0xfff) << 16) | 0x10000000;
+ }
+ params.value |= event_id;
+ events_interface.events[event_id].writable->Clear();
+ gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value);
+ if (!is_async && ctrl.fresh_call) {
+ ctrl.must_delay = true;
+ ctrl.timeout = params.timeout;
+ ctrl.event_id = event_id;
+ return NvResult::Timeout;
+ }
+ std::memcpy(output.data(), &params, sizeof(params));
+ return NvResult::Timeout;
+ }
std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ return NvResult::BadParameter;
}
u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
- LOG_WARNING(Service_NVDRV, "(STUBBED) called");
- // TODO(bunnei): Implement this.
- return 0;
+ IocCtrlEventRegisterParams params{};
+ std::memcpy(&params, input.data(), sizeof(params));
+ const u32 event_id = params.user_event_id & 0x00FF;
+ LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
+ if (event_id >= MaxNvEvents) {
+ return NvResult::BadParameter;
+ }
+ if (events_interface.registered[event_id]) {
+ return NvResult::BadParameter;
+ }
+ events_interface.RegisterEvent(event_id);
+ return NvResult::Success;
+}
+
+u32 nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output) {
+ IocCtrlEventUnregisterParams params{};
+ std::memcpy(&params, input.data(), sizeof(params));
+ const u32 event_id = params.user_event_id & 0x00FF;
+ LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
+ if (event_id >= MaxNvEvents) {
+ return NvResult::BadParameter;
+ }
+ if (!events_interface.registered[event_id]) {
+ return NvResult::BadParameter;
+ }
+ events_interface.UnregisterEvent(event_id);
+ return NvResult::Success;
+}
+
+u32 nvhost_ctrl::IocCtrlEventSignal(const std::vector<u8>& input, std::vector<u8>& output) {
+ IocCtrlEventSignalParams params{};
+ std::memcpy(&params, input.data(), sizeof(params));
+ // TODO(Blinkhawk): This is normally called when an NvEvents timeout on WaitSynchronization
+ // It is believed from RE to cancel the GPU Event. However, better research is required
+ u32 event_id = params.user_event_id & 0x00FF;
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called, user_event_id: {:X}", event_id);
+ if (event_id >= MaxNvEvents) {
+ return NvResult::BadParameter;
+ }
+ if (events_interface.status[event_id] == EventState::Waiting) {
+ auto& gpu = system.GPU();
+ if (gpu.CancelSyncptInterrupt(events_interface.assigned_syncpt[event_id],
+ events_interface.assigned_value[event_id])) {
+ events_interface.LiberateEvent(event_id);
+ events_interface.events[event_id].writable->Signal();
+ }
+ }
+ return NvResult::Success;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
index 6d0de2212..14e6e7e57 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
@@ -8,15 +8,17 @@
#include <vector>
#include "common/common_types.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
namespace Service::Nvidia::Devices {
class nvhost_ctrl final : public nvdevice {
public:
- nvhost_ctrl();
+ explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface);
~nvhost_ctrl() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {
@@ -132,9 +134,16 @@ private:
u32 NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
- u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async);
+ u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async,
+ IoctlCtrl& ctrl);
u32 IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
+
+ u32 IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output);
+
+ u32 IocCtrlEventSignal(const std::vector<u8>& input, std::vector<u8>& output);
+
+ EventInterface& events_interface;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
index 0e28755bd..988effd90 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
@@ -12,10 +12,11 @@
namespace Service::Nvidia::Devices {
-nvhost_ctrl_gpu::nvhost_ctrl_gpu() = default;
+nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system) : nvdevice(system) {}
nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default;
-u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
@@ -185,7 +186,7 @@ u32 nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& o
IoctlGetGpuTime params{};
std::memcpy(&params, input.data(), input.size());
- const auto ns = Core::Timing::CyclesToNs(Core::System::GetInstance().CoreTiming().GetTicks());
+ const auto ns = Core::Timing::CyclesToNs(system.CoreTiming().GetTicks());
params.gpu_time = static_cast<u64_le>(ns.count());
std::memcpy(output.data(), &params, output.size());
return 0;
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
index 240435eea..2b035ae3f 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
@@ -13,10 +13,11 @@ namespace Service::Nvidia::Devices {
class nvhost_ctrl_gpu final : public nvdevice {
public:
- nvhost_ctrl_gpu();
+ explicit nvhost_ctrl_gpu(Core::System& system);
~nvhost_ctrl_gpu() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index 8ce7bc7a5..b4ee2a255 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -13,10 +13,12 @@
namespace Service::Nvidia::Devices {
-nvhost_gpu::nvhost_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {}
+nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
+ : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvhost_gpu::~nvhost_gpu() = default;
-u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
@@ -119,8 +121,10 @@ u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& ou
params.num_entries, params.flags, params.unk0, params.unk1, params.unk2,
params.unk3);
- params.fence_out.id = 0;
- params.fence_out.value = 0;
+ auto& gpu = system.GPU();
+ params.fence_out.id = assigned_syncpoints;
+ params.fence_out.value = gpu.GetSyncpointValue(assigned_syncpoints);
+ assigned_syncpoints++;
std::memcpy(output.data(), &params, output.size());
return 0;
}
@@ -142,8 +146,8 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp
}
IoctlSubmitGpfifo params{};
std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}",
- params.address, params.num_entries, params.flags);
+ LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address,
+ params.num_entries, params.flags.raw);
ASSERT_MSG(input.size() == sizeof(IoctlSubmitGpfifo) +
params.num_entries * sizeof(Tegra::CommandListHeader),
@@ -153,10 +157,18 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp
std::memcpy(entries.data(), &input[sizeof(IoctlSubmitGpfifo)],
params.num_entries * sizeof(Tegra::CommandListHeader));
- Core::System::GetInstance().GPU().PushGPUEntries(std::move(entries));
+ UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0);
+ UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0);
+
+ auto& gpu = system.GPU();
+ u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id);
+ if (params.flags.increment.Value()) {
+ params.fence_out.value += current_syncpoint_value;
+ } else {
+ params.fence_out.value = current_syncpoint_value;
+ }
+ gpu.PushGPUEntries(std::move(entries));
- params.fence_out.id = 0;
- params.fence_out.value = 0;
std::memcpy(output.data(), &params, sizeof(IoctlSubmitGpfifo));
return 0;
}
@@ -167,17 +179,25 @@ u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output)
}
IoctlSubmitGpfifo params{};
std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}",
- params.address, params.num_entries, params.flags);
+ LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address,
+ params.num_entries, params.flags.raw);
Tegra::CommandList entries(params.num_entries);
Memory::ReadBlock(params.address, entries.data(),
params.num_entries * sizeof(Tegra::CommandListHeader));
- Core::System::GetInstance().GPU().PushGPUEntries(std::move(entries));
+ UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0);
+ UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0);
+
+ auto& gpu = system.GPU();
+ u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id);
+ if (params.flags.increment.Value()) {
+ params.fence_out.value += current_syncpoint_value;
+ } else {
+ params.fence_out.value = current_syncpoint_value;
+ }
+ gpu.PushGPUEntries(std::move(entries));
- params.fence_out.id = 0;
- params.fence_out.value = 0;
std::memcpy(output.data(), &params, output.size());
return 0;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index 62beb5c0c..d2e8fbae9 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -10,6 +10,7 @@
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
+#include "core/hle/service/nvdrv/nvdata.h"
namespace Service::Nvidia::Devices {
@@ -20,10 +21,11 @@ constexpr u32 NVGPU_IOCTL_CHANNEL_KICKOFF_PB(0x1b);
class nvhost_gpu final : public nvdevice {
public:
- explicit nvhost_gpu(std::shared_ptr<nvmap> nvmap_dev);
+ explicit nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvhost_gpu() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {
@@ -113,11 +115,7 @@ private:
static_assert(sizeof(IoctlGetErrorNotification) == 16,
"IoctlGetErrorNotification is incorrect size");
- struct IoctlFence {
- u32_le id;
- u32_le value;
- };
- static_assert(sizeof(IoctlFence) == 8, "IoctlFence is incorrect size");
+ static_assert(sizeof(Fence) == 8, "Fence is incorrect size");
struct IoctlAllocGpfifoEx {
u32_le num_entries;
@@ -132,13 +130,13 @@ private:
static_assert(sizeof(IoctlAllocGpfifoEx) == 32, "IoctlAllocGpfifoEx is incorrect size");
struct IoctlAllocGpfifoEx2 {
- u32_le num_entries; // in
- u32_le flags; // in
- u32_le unk0; // in (1 works)
- IoctlFence fence_out; // out
- u32_le unk1; // in
- u32_le unk2; // in
- u32_le unk3; // in
+ u32_le num_entries; // in
+ u32_le flags; // in
+ u32_le unk0; // in (1 works)
+ Fence fence_out; // out
+ u32_le unk1; // in
+ u32_le unk2; // in
+ u32_le unk3; // in
};
static_assert(sizeof(IoctlAllocGpfifoEx2) == 32, "IoctlAllocGpfifoEx2 is incorrect size");
@@ -153,10 +151,16 @@ private:
struct IoctlSubmitGpfifo {
u64_le address; // pointer to gpfifo entry structs
u32_le num_entries; // number of fence objects being submitted
- u32_le flags;
- IoctlFence fence_out; // returned new fence object for others to wait on
- };
- static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(IoctlFence),
+ union {
+ u32_le raw;
+ BitField<0, 1, u32_le> add_wait; // append a wait sync_point to the list
+ BitField<1, 1, u32_le> add_increment; // append an increment to the list
+ BitField<2, 1, u32_le> new_hw_format; // Mostly ignored
+ BitField<8, 1, u32_le> increment; // increment the returned fence
+ } flags;
+ Fence fence_out; // returned new fence object for others to wait on
+ };
+ static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(Fence),
"IoctlSubmitGpfifo is incorrect size");
struct IoctlGetWaitbase {
@@ -184,6 +188,7 @@ private:
u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
std::shared_ptr<nvmap> nvmap_dev;
+ u32 assigned_syncpoints{};
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index f5e8ea7c3..f572ad30f 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -10,10 +10,11 @@
namespace Service::Nvidia::Devices {
-nvhost_nvdec::nvhost_nvdec() = default;
+nvhost_nvdec::nvhost_nvdec(Core::System& system) : nvdevice(system) {}
nvhost_nvdec::~nvhost_nvdec() = default;
-u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
index 0e7b284f8..2710f0511 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
@@ -13,10 +13,11 @@ namespace Service::Nvidia::Devices {
class nvhost_nvdec final : public nvdevice {
public:
- nvhost_nvdec();
+ explicit nvhost_nvdec(Core::System& system);
~nvhost_nvdec() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
index 3e0951ab0..38282956f 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
@@ -10,10 +10,11 @@
namespace Service::Nvidia::Devices {
-nvhost_nvjpg::nvhost_nvjpg() = default;
+nvhost_nvjpg::nvhost_nvjpg(Core::System& system) : nvdevice(system) {}
nvhost_nvjpg::~nvhost_nvjpg() = default;
-u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
index 89fd5e95e..379766693 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
@@ -13,10 +13,11 @@ namespace Service::Nvidia::Devices {
class nvhost_nvjpg final : public nvdevice {
public:
- nvhost_nvjpg();
+ explicit nvhost_nvjpg(Core::System& system);
~nvhost_nvjpg() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
index d544f0f31..70e8091db 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
@@ -10,10 +10,11 @@
namespace Service::Nvidia::Devices {
-nvhost_vic::nvhost_vic() = default;
+nvhost_vic::nvhost_vic(Core::System& system) : nvdevice(system) {}
nvhost_vic::~nvhost_vic() = default;
-u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) {
LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
command.raw, input.size(), output.size());
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h
index fc24c3f9c..7d111977e 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h
@@ -13,10 +13,11 @@ namespace Service::Nvidia::Devices {
class nvhost_vic final : public nvdevice {
public:
- nvhost_vic();
+ explicit nvhost_vic(Core::System& system);
~nvhost_vic() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) override;
private:
enum class IoctlCommand : u32_le {
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index 1ec796fc6..223b496b7 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -18,7 +18,7 @@ enum {
};
}
-nvmap::nvmap() = default;
+nvmap::nvmap(Core::System& system) : nvdevice(system) {}
nvmap::~nvmap() = default;
VAddr nvmap::GetObjectAddress(u32 handle) const {
@@ -28,7 +28,8 @@ VAddr nvmap::GetObjectAddress(u32 handle) const {
return object->addr;
}
-u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) {
switch (static_cast<IoctlCommand>(command.raw)) {
case IoctlCommand::Create:
return IocCreate(input, output);
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index 396230c19..bf4a101c2 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -16,13 +16,14 @@ namespace Service::Nvidia::Devices {
class nvmap final : public nvdevice {
public:
- nvmap();
+ explicit nvmap(Core::System& system);
~nvmap() override;
/// Returns the allocated address of an nvmap object given its handle.
VAddr GetObjectAddress(u32 handle) const;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) override;
/// Represents an nvmap object.
struct Object {
diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp
index b60fc748b..d5be64ed2 100644
--- a/src/core/hle/service/nvdrv/interface.cpp
+++ b/src/core/hle/service/nvdrv/interface.cpp
@@ -8,12 +8,18 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/readable_event.h"
+#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/interface.h"
+#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvdrv/nvdrv.h"
namespace Service::Nvidia {
+void NVDRV::SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
+ nvdrv->SignalSyncpt(syncpoint_id, value);
+}
+
void NVDRV::Open(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NVDRV, "called");
@@ -36,11 +42,31 @@ void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) {
std::vector<u8> output(ctx.GetWriteBufferSize());
+ IoctlCtrl ctrl{};
+
+ u32 result = nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output, ctrl);
+
+ if (ctrl.must_delay) {
+ ctrl.fresh_call = false;
+ ctx.SleepClientThread(
+ "NVServices::DelayedResponse", ctrl.timeout,
+ [=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
+ Kernel::ThreadWakeupReason reason) {
+ IoctlCtrl ctrl2{ctrl};
+ std::vector<u8> output2 = output;
+ u32 result = nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output2, ctrl2);
+ ctx.WriteBuffer(output2);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(result);
+ },
+ nvdrv->GetEventWriteable(ctrl.event_id));
+ } else {
+ ctx.WriteBuffer(output);
+ }
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output));
-
- ctx.WriteBuffer(output);
+ rb.Push(result);
}
void NVDRV::Close(Kernel::HLERequestContext& ctx) {
@@ -66,13 +92,19 @@ void NVDRV::Initialize(Kernel::HLERequestContext& ctx) {
void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u32 fd = rp.Pop<u32>();
- u32 event_id = rp.Pop<u32>();
+ // TODO(Blinkhawk): Figure the meaning of the flag at bit 16
+ u32 event_id = rp.Pop<u32>() & 0x000000FF;
LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id);
IPC::ResponseBuilder rb{ctx, 3, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(query_event.readable);
- rb.Push<u32>(0);
+ if (event_id < MaxNvEvents) {
+ rb.PushCopyObjects(nvdrv->GetEvent(event_id));
+ rb.Push<u32>(NvResult::Success);
+ } else {
+ rb.Push<u32>(0);
+ rb.Push<u32>(NvResult::BadParameter);
+ }
}
void NVDRV::SetClientPID(Kernel::HLERequestContext& ctx) {
@@ -127,10 +159,6 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name)
{13, &NVDRV::FinishInitialize, "FinishInitialize"},
};
RegisterHandlers(functions);
-
- auto& kernel = Core::System::GetInstance().Kernel();
- query_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
- "NVDRV::query_event");
}
NVDRV::~NVDRV() = default;
diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h
index 5b4889910..10a0ecd52 100644
--- a/src/core/hle/service/nvdrv/interface.h
+++ b/src/core/hle/service/nvdrv/interface.h
@@ -19,6 +19,8 @@ public:
NVDRV(std::shared_ptr<Module> nvdrv, const char* name);
~NVDRV() override;
+ void SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value);
+
private:
void Open(Kernel::HLERequestContext& ctx);
void Ioctl(Kernel::HLERequestContext& ctx);
@@ -33,8 +35,6 @@ private:
std::shared_ptr<Module> nvdrv;
u64 pid{};
-
- Kernel::EventPair query_event;
};
} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdata.h b/src/core/hle/service/nvdrv/nvdata.h
new file mode 100644
index 000000000..ac03cbc23
--- /dev/null
+++ b/src/core/hle/service/nvdrv/nvdata.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <array>
+#include "common/common_types.h"
+
+namespace Service::Nvidia {
+
+constexpr u32 MaxSyncPoints = 192;
+constexpr u32 MaxNvEvents = 64;
+
+struct Fence {
+ s32 id;
+ u32 value;
+};
+
+static_assert(sizeof(Fence) == 8, "Fence has wrong size");
+
+struct MultiFence {
+ u32 num_fences;
+ std::array<Fence, 4> fences;
+};
+
+enum NvResult : u32 {
+ Success = 0,
+ BadParameter = 4,
+ Timeout = 5,
+ ResourceError = 15,
+};
+
+enum class EventState {
+ Free = 0,
+ Registered = 1,
+ Waiting = 2,
+ Busy = 3,
+};
+
+struct IoctlCtrl {
+ // First call done to the servioce for services that call itself again after a call.
+ bool fresh_call{true};
+ // Tells the Ioctl Wrapper that it must delay the IPC response and send the thread to sleep
+ bool must_delay{};
+ // Timeout for the delay
+ s64 timeout{};
+ // NV Event Id
+ s32 event_id{-1};
+};
+
+} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp
index 6e4b8f2c6..2011a226a 100644
--- a/src/core/hle/service/nvdrv/nvdrv.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv.cpp
@@ -4,7 +4,10 @@
#include <utility>
+#include <fmt/format.h>
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/readable_event.h"
+#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
@@ -22,8 +25,9 @@
namespace Service::Nvidia {
-void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger) {
- auto module_ = std::make_shared<Module>();
+void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
+ Core::System& system) {
+ auto module_ = std::make_shared<Module>(system);
std::make_shared<NVDRV>(module_, "nvdrv")->InstallAsService(service_manager);
std::make_shared<NVDRV>(module_, "nvdrv:a")->InstallAsService(service_manager);
std::make_shared<NVDRV>(module_, "nvdrv:s")->InstallAsService(service_manager);
@@ -32,17 +36,25 @@ void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger
nvflinger.SetNVDrvInstance(module_);
}
-Module::Module() {
- auto nvmap_dev = std::make_shared<Devices::nvmap>();
- devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(nvmap_dev);
- devices["/dev/nvhost-gpu"] = std::make_shared<Devices::nvhost_gpu>(nvmap_dev);
- devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>();
+Module::Module(Core::System& system) {
+ auto& kernel = system.Kernel();
+ for (u32 i = 0; i < MaxNvEvents; i++) {
+ std::string event_label = fmt::format("NVDRV::NvEvent_{}", i);
+ events_interface.events[i] = Kernel::WritableEvent::CreateEventPair(
+ kernel, Kernel::ResetType::Automatic, event_label);
+ events_interface.status[i] = EventState::Free;
+ events_interface.registered[i] = false;
+ }
+ auto nvmap_dev = std::make_shared<Devices::nvmap>(system);
+ devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(system, nvmap_dev);
+ devices["/dev/nvhost-gpu"] = std::make_shared<Devices::nvhost_gpu>(system, nvmap_dev);
+ devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>(system);
devices["/dev/nvmap"] = nvmap_dev;
- devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(nvmap_dev);
- devices["/dev/nvhost-ctrl"] = std::make_shared<Devices::nvhost_ctrl>();
- devices["/dev/nvhost-nvdec"] = std::make_shared<Devices::nvhost_nvdec>();
- devices["/dev/nvhost-nvjpg"] = std::make_shared<Devices::nvhost_nvjpg>();
- devices["/dev/nvhost-vic"] = std::make_shared<Devices::nvhost_vic>();
+ devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(system, nvmap_dev);
+ devices["/dev/nvhost-ctrl"] = std::make_shared<Devices::nvhost_ctrl>(system, events_interface);
+ devices["/dev/nvhost-nvdec"] = std::make_shared<Devices::nvhost_nvdec>(system);
+ devices["/dev/nvhost-nvjpg"] = std::make_shared<Devices::nvhost_nvjpg>(system);
+ devices["/dev/nvhost-vic"] = std::make_shared<Devices::nvhost_vic>(system);
}
Module::~Module() = default;
@@ -59,12 +71,13 @@ u32 Module::Open(const std::string& device_name) {
return fd;
}
-u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output) {
+u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl) {
auto itr = open_files.find(fd);
ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device");
auto& device = itr->second;
- return device->ioctl({command}, input, output);
+ return device->ioctl({command}, input, output, ctrl);
}
ResultCode Module::Close(u32 fd) {
@@ -77,4 +90,22 @@ ResultCode Module::Close(u32 fd) {
return RESULT_SUCCESS;
}
+void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) {
+ for (u32 i = 0; i < MaxNvEvents; i++) {
+ if (events_interface.assigned_syncpt[i] == syncpoint_id &&
+ events_interface.assigned_value[i] == value) {
+ events_interface.LiberateEvent(i);
+ events_interface.events[i].writable->Signal();
+ }
+ }
+}
+
+Kernel::SharedPtr<Kernel::ReadableEvent> Module::GetEvent(const u32 event_id) const {
+ return events_interface.events[event_id].readable;
+}
+
+Kernel::SharedPtr<Kernel::WritableEvent> Module::GetEventWriteable(const u32 event_id) const {
+ return events_interface.events[event_id].writable;
+}
+
} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h
index 53564f696..a339ab672 100644
--- a/src/core/hle/service/nvdrv/nvdrv.h
+++ b/src/core/hle/service/nvdrv/nvdrv.h
@@ -8,8 +8,14 @@
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
+#include "core/hle/kernel/writable_event.h"
+#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::NVFlinger {
class NVFlinger;
}
@@ -20,16 +26,72 @@ namespace Devices {
class nvdevice;
}
-struct IoctlFence {
- u32 id;
- u32 value;
+struct EventInterface {
+ // Mask representing currently busy events
+ u64 events_mask{};
+ // Each kernel event associated to an NV event
+ std::array<Kernel::EventPair, MaxNvEvents> events;
+ // The status of the current NVEvent
+ std::array<EventState, MaxNvEvents> status{};
+ // Tells if an NVEvent is registered or not
+ std::array<bool, MaxNvEvents> registered{};
+ // When an NVEvent is waiting on GPU interrupt, this is the sync_point
+ // associated with it.
+ std::array<u32, MaxNvEvents> assigned_syncpt{};
+ // This is the value of the GPU interrupt for which the NVEvent is waiting
+ // for.
+ std::array<u32, MaxNvEvents> assigned_value{};
+ // Constant to denote an unasigned syncpoint.
+ static constexpr u32 unassigned_syncpt = 0xFFFFFFFF;
+ std::optional<u32> GetFreeEvent() const {
+ u64 mask = events_mask;
+ for (u32 i = 0; i < MaxNvEvents; i++) {
+ const bool is_free = (mask & 0x1) == 0;
+ if (is_free) {
+ if (status[i] == EventState::Registered || status[i] == EventState::Free) {
+ return {i};
+ }
+ }
+ mask = mask >> 1;
+ }
+ return {};
+ }
+ void SetEventStatus(const u32 event_id, EventState new_status) {
+ EventState old_status = status[event_id];
+ if (old_status == new_status) {
+ return;
+ }
+ status[event_id] = new_status;
+ if (new_status == EventState::Registered) {
+ registered[event_id] = true;
+ }
+ if (new_status == EventState::Waiting || new_status == EventState::Busy) {
+ events_mask |= (1ULL << event_id);
+ }
+ }
+ void RegisterEvent(const u32 event_id) {
+ registered[event_id] = true;
+ if (status[event_id] == EventState::Free) {
+ status[event_id] = EventState::Registered;
+ }
+ }
+ void UnregisterEvent(const u32 event_id) {
+ registered[event_id] = false;
+ if (status[event_id] == EventState::Registered) {
+ status[event_id] = EventState::Free;
+ }
+ }
+ void LiberateEvent(const u32 event_id) {
+ status[event_id] = registered[event_id] ? EventState::Registered : EventState::Free;
+ events_mask &= ~(1ULL << event_id);
+ assigned_syncpt[event_id] = unassigned_syncpt;
+ assigned_value[event_id] = 0;
+ }
};
-static_assert(sizeof(IoctlFence) == 8, "IoctlFence has wrong size");
-
class Module final {
public:
- Module();
+ Module(Core::System& system);
~Module();
/// Returns a pointer to one of the available devices, identified by its name.
@@ -44,10 +106,17 @@ public:
/// Opens a device node and returns a file descriptor to it.
u32 Open(const std::string& device_name);
/// Sends an ioctl command to the specified file descriptor.
- u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output);
+ u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output,
+ IoctlCtrl& ctrl);
/// Closes a device file descriptor and returns operation success.
ResultCode Close(u32 fd);
+ void SignalSyncpt(const u32 syncpoint_id, const u32 value);
+
+ Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent(u32 event_id) const;
+
+ Kernel::SharedPtr<Kernel::WritableEvent> GetEventWriteable(u32 event_id) const;
+
private:
/// Id to use for the next open file descriptor.
u32 next_fd = 1;
@@ -57,9 +126,12 @@ private:
/// Mapping of device node names to their implementation.
std::unordered_map<std::string, std::shared_ptr<Devices::nvdevice>> devices;
+
+ EventInterface events_interface;
};
/// Registers all NVDRV services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger);
+void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
+ Core::System& system);
} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp
index 5731e815f..e1a07d3ee 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue.cpp
@@ -34,7 +34,8 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer)
buffer_wait_event.writable->Signal();
}
-std::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
+std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width,
+ u32 height) {
auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) {
// Only consider free buffers. Buffers become free once again after they've been Acquired
// and Released by the compositor, see the NVFlinger::Compose method.
@@ -51,7 +52,7 @@ std::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
}
itr->status = Buffer::Status::Dequeued;
- return itr->slot;
+ return {{itr->slot, &itr->multi_fence}};
}
const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const {
@@ -63,7 +64,8 @@ const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const {
}
void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform,
- const Common::Rectangle<int>& crop_rect) {
+ const Common::Rectangle<int>& crop_rect, u32 swap_interval,
+ Service::Nvidia::MultiFence& multi_fence) {
auto itr = std::find_if(queue.begin(), queue.end(),
[&](const Buffer& buffer) { return buffer.slot == slot; });
ASSERT(itr != queue.end());
@@ -71,12 +73,21 @@ void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform,
itr->status = Buffer::Status::Queued;
itr->transform = transform;
itr->crop_rect = crop_rect;
+ itr->swap_interval = swap_interval;
+ itr->multi_fence = multi_fence;
+ queue_sequence.push_back(slot);
}
std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() {
- auto itr = std::find_if(queue.begin(), queue.end(), [](const Buffer& buffer) {
- return buffer.status == Buffer::Status::Queued;
- });
+ auto itr = queue.end();
+ // Iterate to find a queued buffer matching the requested slot.
+ while (itr == queue.end() && !queue_sequence.empty()) {
+ u32 slot = queue_sequence.front();
+ itr = std::find_if(queue.begin(), queue.end(), [&slot](const Buffer& buffer) {
+ return buffer.status == Buffer::Status::Queued && buffer.slot == slot;
+ });
+ queue_sequence.pop_front();
+ }
if (itr == queue.end())
return {};
itr->status = Buffer::Status::Acquired;
diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h
index e1ccb6171..356bedb81 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.h
+++ b/src/core/hle/service/nvflinger/buffer_queue.h
@@ -4,6 +4,7 @@
#pragma once
+#include <list>
#include <optional>
#include <vector>
@@ -12,6 +13,7 @@
#include "common/swap.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/writable_event.h"
+#include "core/hle/service/nvdrv/nvdata.h"
namespace Service::NVFlinger {
@@ -68,13 +70,17 @@ public:
IGBPBuffer igbp_buffer;
BufferTransformFlags transform;
Common::Rectangle<int> crop_rect;
+ u32 swap_interval;
+ Service::Nvidia::MultiFence multi_fence;
};
void SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer);
- std::optional<u32> DequeueBuffer(u32 width, u32 height);
+ std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> DequeueBuffer(u32 width,
+ u32 height);
const IGBPBuffer& RequestBuffer(u32 slot) const;
void QueueBuffer(u32 slot, BufferTransformFlags transform,
- const Common::Rectangle<int>& crop_rect);
+ const Common::Rectangle<int>& crop_rect, u32 swap_interval,
+ Service::Nvidia::MultiFence& multi_fence);
std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer();
void ReleaseBuffer(u32 slot);
u32 Query(QueryType type);
@@ -92,6 +98,7 @@ private:
u64 layer_id;
std::vector<Buffer> queue;
+ std::list<u32> queue_sequence;
Kernel::EventPair buffer_wait_event;
};
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index 3c5c53e24..f9db79370 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -37,15 +37,14 @@ NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_t
displays.emplace_back(4, "Null");
// Schedule the screen composition events
- const auto ticks = Settings::values.force_30fps_mode ? frame_ticks_30fps : frame_ticks;
-
- composition_event = core_timing.RegisterEvent(
- "ScreenComposition", [this, ticks](u64 userdata, s64 cycles_late) {
- Compose();
- this->core_timing.ScheduleEvent(ticks - cycles_late, composition_event);
- });
-
- core_timing.ScheduleEvent(ticks, composition_event);
+ composition_event = core_timing.RegisterEvent("ScreenComposition", [this](u64 userdata,
+ s64 cycles_late) {
+ Compose();
+ const auto ticks = Settings::values.force_30fps_mode ? frame_ticks_30fps : GetNextTicks();
+ this->core_timing.ScheduleEvent(std::max<s64>(0LL, ticks - cycles_late), composition_event);
+ });
+
+ core_timing.ScheduleEvent(frame_ticks, composition_event);
}
NVFlinger::~NVFlinger() {
@@ -206,8 +205,14 @@ void NVFlinger::Compose() {
igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride,
buffer->get().transform, buffer->get().crop_rect);
+ swap_interval = buffer->get().swap_interval;
buffer_queue.ReleaseBuffer(buffer->get().slot);
}
}
+s64 NVFlinger::GetNextTicks() const {
+ constexpr s64 max_hertz = 120LL;
+ return (Core::Timing::BASE_CLOCK_RATE * (1LL << swap_interval)) / max_hertz;
+}
+
} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index c0a83fffb..988be8726 100644
--- a/src/core/hle/service/nvflinger/nvflinger.h
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -74,6 +74,8 @@ public:
/// finished.
void Compose();
+ s64 GetNextTicks() const;
+
private:
/// Finds the display identified by the specified ID.
VI::Display* FindDisplay(u64 display_id);
@@ -98,6 +100,8 @@ private:
/// layers.
u32 next_buffer_queue_id = 1;
+ u32 swap_interval = 1;
+
/// Event that handles screen composition.
Core::Timing::EventType* composition_event;
diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp
index ebcc41a43..fe6b5f798 100644
--- a/src/core/hle/service/pm/pm.cpp
+++ b/src/core/hle/service/pm/pm.cpp
@@ -3,11 +3,44 @@
// Refer to the license.txt file included.
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/service/pm/pm.h"
#include "core/hle/service/service.h"
namespace Service::PM {
+namespace {
+
+constexpr ResultCode ERROR_PROCESS_NOT_FOUND{ErrorModule::PM, 1};
+
+constexpr u64 NO_PROCESS_FOUND_PID{0};
+
+std::optional<Kernel::SharedPtr<Kernel::Process>> SearchProcessList(
+ const std::vector<Kernel::SharedPtr<Kernel::Process>>& process_list,
+ std::function<bool(const Kernel::SharedPtr<Kernel::Process>&)> predicate) {
+ const auto iter = std::find_if(process_list.begin(), process_list.end(), predicate);
+
+ if (iter == process_list.end()) {
+ return std::nullopt;
+ }
+
+ return *iter;
+}
+
+void GetApplicationPidGeneric(Kernel::HLERequestContext& ctx,
+ const std::vector<Kernel::SharedPtr<Kernel::Process>>& process_list) {
+ const auto process = SearchProcessList(process_list, [](const auto& process) {
+ return process->GetProcessID() == Kernel::Process::ProcessIDMin;
+ });
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(process.has_value() ? (*process)->GetProcessID() : NO_PROCESS_FOUND_PID);
+}
+
+} // Anonymous namespace
+
class BootMode final : public ServiceFramework<BootMode> {
public:
explicit BootMode() : ServiceFramework{"pm:bm"} {
@@ -41,14 +74,15 @@ private:
class DebugMonitor final : public ServiceFramework<DebugMonitor> {
public:
- explicit DebugMonitor() : ServiceFramework{"pm:dmnt"} {
+ explicit DebugMonitor(const Kernel::KernelCore& kernel)
+ : ServiceFramework{"pm:dmnt"}, kernel(kernel) {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetDebugProcesses"},
{1, nullptr, "StartDebugProcess"},
- {2, nullptr, "GetTitlePid"},
+ {2, &DebugMonitor::GetTitlePid, "GetTitlePid"},
{3, nullptr, "EnableDebugForTitleId"},
- {4, nullptr, "GetApplicationPid"},
+ {4, &DebugMonitor::GetApplicationPid, "GetApplicationPid"},
{5, nullptr, "EnableDebugForApplication"},
{6, nullptr, "DisableDebug"},
};
@@ -56,21 +90,77 @@ public:
RegisterHandlers(functions);
}
+
+private:
+ void GetTitlePid(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto title_id = rp.PopRaw<u64>();
+
+ LOG_DEBUG(Service_PM, "called, title_id={:016X}", title_id);
+
+ const auto process =
+ SearchProcessList(kernel.GetProcessList(), [title_id](const auto& process) {
+ return process->GetTitleID() == title_id;
+ });
+
+ if (!process.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_PROCESS_NOT_FOUND);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push((*process)->GetProcessID());
+ }
+
+ void GetApplicationPid(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_PM, "called");
+ GetApplicationPidGeneric(ctx, kernel.GetProcessList());
+ }
+
+ const Kernel::KernelCore& kernel;
};
class Info final : public ServiceFramework<Info> {
public:
- explicit Info() : ServiceFramework{"pm:info"} {
+ explicit Info(const std::vector<Kernel::SharedPtr<Kernel::Process>>& process_list)
+ : ServiceFramework{"pm:info"}, process_list(process_list) {
static const FunctionInfo functions[] = {
- {0, nullptr, "GetTitleId"},
+ {0, &Info::GetTitleId, "GetTitleId"},
};
RegisterHandlers(functions);
}
+
+private:
+ void GetTitleId(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto process_id = rp.PopRaw<u64>();
+
+ LOG_DEBUG(Service_PM, "called, process_id={:016X}", process_id);
+
+ const auto process = SearchProcessList(process_list, [process_id](const auto& process) {
+ return process->GetProcessID() == process_id;
+ });
+
+ if (!process.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_PROCESS_NOT_FOUND);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push((*process)->GetTitleID());
+ }
+
+ const std::vector<Kernel::SharedPtr<Kernel::Process>>& process_list;
};
class Shell final : public ServiceFramework<Shell> {
public:
- explicit Shell() : ServiceFramework{"pm:shell"} {
+ explicit Shell(const Kernel::KernelCore& kernel)
+ : ServiceFramework{"pm:shell"}, kernel(kernel) {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "LaunchProcess"},
@@ -79,21 +169,31 @@ public:
{3, nullptr, "GetProcessEventWaiter"},
{4, nullptr, "GetProcessEventType"},
{5, nullptr, "NotifyBootFinished"},
- {6, nullptr, "GetApplicationPid"},
+ {6, &Shell::GetApplicationPid, "GetApplicationPid"},
{7, nullptr, "BoostSystemMemoryResourceLimit"},
{8, nullptr, "EnableAdditionalSystemThreads"},
+ {9, nullptr, "GetUnimplementedEventHandle"},
};
// clang-format on
RegisterHandlers(functions);
}
+
+private:
+ void GetApplicationPid(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_PM, "called");
+ GetApplicationPidGeneric(ctx, kernel.GetProcessList());
+ }
+
+ const Kernel::KernelCore& kernel;
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<BootMode>()->InstallAsService(sm);
- std::make_shared<DebugMonitor>()->InstallAsService(sm);
- std::make_shared<Info>()->InstallAsService(sm);
- std::make_shared<Shell>()->InstallAsService(sm);
+void InstallInterfaces(Core::System& system) {
+ std::make_shared<BootMode>()->InstallAsService(system.ServiceManager());
+ std::make_shared<DebugMonitor>(system.Kernel())->InstallAsService(system.ServiceManager());
+ std::make_shared<Info>(system.Kernel().GetProcessList())
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<Shell>(system.Kernel())->InstallAsService(system.ServiceManager());
}
} // namespace Service::PM
diff --git a/src/core/hle/service/pm/pm.h b/src/core/hle/service/pm/pm.h
index cc8d3f215..852e7050c 100644
--- a/src/core/hle/service/pm/pm.h
+++ b/src/core/hle/service/pm/pm.h
@@ -4,8 +4,8 @@
#pragma once
-namespace Service::SM {
-class ServiceManager;
+namespace Core {
+class System;
}
namespace Service::PM {
@@ -16,6 +16,6 @@ enum class SystemBootMode {
};
/// Registers all PM services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(Core::System& system);
} // namespace Service::PM
diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp
index e4fcee9f8..7e134f5c1 100644
--- a/src/core/hle/service/prepo/prepo.cpp
+++ b/src/core/hle/service/prepo/prepo.cpp
@@ -2,10 +2,18 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <json.hpp>
+#include "common/file_util.h"
+#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "common/scm_rev.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/prepo/prepo.h"
#include "core/hle/service/service.h"
+#include "core/reporter.h"
+#include "core/settings.h"
namespace Service::PlayReport {
@@ -40,8 +48,21 @@ public:
private:
void SaveReportWithUserOld(Kernel::HLERequestContext& ctx) {
- // TODO(ogniK): Do we want to add play report?
- LOG_WARNING(Service_PREPO, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto user_id = rp.PopRaw<u128>();
+ const auto process_id = rp.PopRaw<u64>();
+
+ const auto data1 = ctx.ReadBuffer(0);
+ const auto data2 = ctx.ReadBuffer(1);
+
+ LOG_DEBUG(
+ Service_PREPO,
+ "called, user_id={:016X}{:016X}, unk1={:016X}, data1_size={:016X}, data2_size={:016X}",
+ user_id[1], user_id[0], process_id, data1.size(), data2.size());
+
+ const auto& reporter{Core::System::GetInstance().GetReporter()};
+ reporter.SavePlayReport(Core::CurrentProcess()->GetTitleID(), process_id, {data1, data2},
+ user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 00806b0ed..3a0f8c3f6 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -19,7 +19,6 @@
#include "core/hle/service/am/am.h"
#include "core/hle/service/aoc/aoc_u.h"
#include "core/hle/service/apm/apm.h"
-#include "core/hle/service/arp/arp.h"
#include "core/hle/service/audio/audio.h"
#include "core/hle/service/bcat/module.h"
#include "core/hle/service/bpc/bpc.h"
@@ -33,6 +32,7 @@
#include "core/hle/service/fgm/fgm.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/friend/friend.h"
+#include "core/hle/service/glue/glue.h"
#include "core/hle/service/grc/grc.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/lbl/lbl.h"
@@ -68,6 +68,7 @@
#include "core/hle/service/usb/usb.h"
#include "core/hle/service/vi/vi.h"
#include "core/hle/service/wlan/wlan.h"
+#include "core/reporter.h"
namespace Service {
@@ -148,6 +149,8 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext
}
buf.push_back('}');
+ Core::System::GetInstance().GetReporter().SaveUnimplementedFunctionReport(
+ ctx, ctx.GetCommand(), function_name, service_name);
UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf));
}
@@ -192,20 +195,18 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
// Module interface
/// Initialize ServiceManager
-void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
- FileSys::VfsFilesystem& vfs) {
+void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
// NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
// here and pass it into the respective InstallInterfaces functions.
auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(system.CoreTiming());
SM::ServiceManager::InstallInterfaces(sm);
- Account::InstallInterfaces(*sm);
- AM::InstallInterfaces(*sm, nv_flinger);
+ Account::InstallInterfaces(system);
+ AM::InstallInterfaces(*sm, nv_flinger, system);
AOC::InstallInterfaces(*sm);
- APM::InstallInterfaces(*sm);
- ARP::InstallInterfaces(*sm);
- Audio::InstallInterfaces(*sm);
+ APM::InstallInterfaces(system);
+ Audio::InstallInterfaces(*sm, system);
BCAT::InstallInterfaces(*sm);
BPC::InstallInterfaces(*sm);
BtDrv::InstallInterfaces(*sm);
@@ -216,8 +217,9 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
EUPLD::InstallInterfaces(*sm);
Fatal::InstallInterfaces(*sm);
FGM::InstallInterfaces(*sm);
- FileSystem::InstallInterfaces(*sm, vfs);
+ FileSystem::InstallInterfaces(system);
Friend::InstallInterfaces(*sm);
+ Glue::InstallInterfaces(system);
GRC::InstallInterfaces(*sm);
HID::InstallInterfaces(*sm);
LBL::InstallInterfaces(*sm);
@@ -234,19 +236,19 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
NIM::InstallInterfaces(*sm);
NPNS::InstallInterfaces(*sm);
NS::InstallInterfaces(*sm);
- Nvidia::InstallInterfaces(*sm, *nv_flinger);
+ Nvidia::InstallInterfaces(*sm, *nv_flinger, system);
PCIe::InstallInterfaces(*sm);
PCTL::InstallInterfaces(*sm);
PCV::InstallInterfaces(*sm);
PlayReport::InstallInterfaces(*sm);
- PM::InstallInterfaces(*sm);
+ PM::InstallInterfaces(system);
PSC::InstallInterfaces(*sm);
PSM::InstallInterfaces(*sm);
Set::InstallInterfaces(*sm);
Sockets::InstallInterfaces(*sm);
SPL::InstallInterfaces(*sm);
SSL::InstallInterfaces(*sm);
- Time::InstallInterfaces(*sm);
+ Time::InstallInterfaces(system);
USB::InstallInterfaces(*sm);
VI::InstallInterfaces(*sm, nv_flinger);
WLAN::InstallInterfaces(*sm);
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index abbfe5524..c6c4bdae5 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -182,8 +182,7 @@ private:
};
/// Initialize ServiceManager
-void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
- FileSys::VfsFilesystem& vfs);
+void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system);
/// Shutdown ServiceManager
void Shutdown();
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index 298d85011..b54214421 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -95,6 +95,14 @@ void SET::GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx) {
PushResponseLanguageCode(ctx, post4_0_0_max_entries);
}
+void SET::GetQuestFlag(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_SET, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u32>(Settings::values.quest_flag));
+}
+
void SET::GetLanguageCode(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called {}", Settings::values.language_index);
@@ -114,7 +122,7 @@ SET::SET() : ServiceFramework("set") {
{5, &SET::GetAvailableLanguageCodes2, "GetAvailableLanguageCodes2"},
{6, &SET::GetAvailableLanguageCodeCount2, "GetAvailableLanguageCodeCount2"},
{7, nullptr, "GetKeyCodeMap"},
- {8, nullptr, "GetQuestFlag"},
+ {8, &SET::GetQuestFlag, "GetQuestFlag"},
{9, nullptr, "GetKeyCodeMap2"},
};
// clang-format on
diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h
index 31f9cb296..b154e08aa 100644
--- a/src/core/hle/service/set/set.h
+++ b/src/core/hle/service/set/set.h
@@ -42,6 +42,7 @@ private:
void GetAvailableLanguageCodes2(Kernel::HLERequestContext& ctx);
void GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx);
void GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx);
+ void GetQuestFlag(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Set
diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp
index 8d122ae33..1030185e0 100644
--- a/src/core/hle/service/time/interface.cpp
+++ b/src/core/hle/service/time/interface.cpp
@@ -6,8 +6,9 @@
namespace Service::Time {
-Time::Time(std::shared_ptr<Module> time, const char* name)
- : Module::Interface(std::move(time), name) {
+Time::Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory,
+ const char* name)
+ : Module::Interface(std::move(time), std::move(shared_memory), name) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &Time::GetStandardUserSystemClock, "GetStandardUserSystemClock"},
@@ -16,12 +17,12 @@ Time::Time(std::shared_ptr<Module> time, const char* name)
{3, &Time::GetTimeZoneService, "GetTimeZoneService"},
{4, &Time::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"},
{5, nullptr, "GetEphemeralNetworkSystemClock"},
- {20, nullptr, "GetSharedMemoryNativeHandle"},
+ {20, &Time::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"},
{30, nullptr, "GetStandardNetworkClockOperationEventReadableHandle"},
{31, nullptr, "GetEphemeralNetworkClockOperationEventReadableHandle"},
{50, nullptr, "SetStandardSteadyClockInternalOffset"},
- {100, nullptr, "IsStandardUserSystemClockAutomaticCorrectionEnabled"},
- {101, nullptr, "SetStandardUserSystemClockAutomaticCorrectionEnabled"},
+ {100, &Time::IsStandardUserSystemClockAutomaticCorrectionEnabled, "IsStandardUserSystemClockAutomaticCorrectionEnabled"},
+ {101, &Time::SetStandardUserSystemClockAutomaticCorrectionEnabled, "SetStandardUserSystemClockAutomaticCorrectionEnabled"},
{102, nullptr, "GetStandardUserSystemClockInitialYear"},
{200, nullptr, "IsStandardNetworkSystemClockAccuracySufficient"},
{201, nullptr, "GetStandardUserSystemClockAutomaticCorrectionUpdatedTime"},
diff --git a/src/core/hle/service/time/interface.h b/src/core/hle/service/time/interface.h
index cd6b44dec..bdf0883e2 100644
--- a/src/core/hle/service/time/interface.h
+++ b/src/core/hle/service/time/interface.h
@@ -8,9 +8,12 @@
namespace Service::Time {
+class SharedMemory;
+
class Time final : public Module::Interface {
public:
- explicit Time(std::shared_ptr<Module> time, const char* name);
+ explicit Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory,
+ const char* name);
~Time() override;
};
diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp
index 346bad80d..ae6446204 100644
--- a/src/core/hle/service/time/time.cpp
+++ b/src/core/hle/service/time/time.cpp
@@ -13,6 +13,7 @@
#include "core/hle/kernel/client_session.h"
#include "core/hle/service/time/interface.h"
#include "core/hle/service/time/time.h"
+#include "core/hle/service/time/time_sharedmemory.h"
#include "core/settings.h"
namespace Service::Time {
@@ -61,9 +62,18 @@ static u64 CalendarToPosix(const CalendarTime& calendar_time,
return static_cast<u64>(epoch_time);
}
+enum class ClockContextType {
+ StandardSteady,
+ StandardUserSystem,
+ StandardNetworkSystem,
+ StandardLocalSystem,
+};
+
class ISystemClock final : public ServiceFramework<ISystemClock> {
public:
- ISystemClock() : ServiceFramework("ISystemClock") {
+ ISystemClock(std::shared_ptr<Service::Time::SharedMemory> shared_memory,
+ ClockContextType clock_type)
+ : ServiceFramework("ISystemClock"), shared_memory(shared_memory), clock_type(clock_type) {
static const FunctionInfo functions[] = {
{0, &ISystemClock::GetCurrentTime, "GetCurrentTime"},
{1, nullptr, "SetCurrentTime"},
@@ -72,6 +82,8 @@ public:
};
RegisterHandlers(functions);
+
+ UpdateSharedMemoryContext(system_clock_context);
}
private:
@@ -87,34 +99,63 @@ private:
void GetSystemClockContext(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Time, "(STUBBED) called");
- SystemClockContext system_clock_ontext{};
+ // TODO(ogniK): This should be updated periodically however since we have it stubbed we'll
+ // only update when we get a new context
+ UpdateSharedMemoryContext(system_clock_context);
+
IPC::ResponseBuilder rb{ctx, (sizeof(SystemClockContext) / 4) + 2};
rb.Push(RESULT_SUCCESS);
- rb.PushRaw(system_clock_ontext);
+ rb.PushRaw(system_clock_context);
}
+
+ void UpdateSharedMemoryContext(const SystemClockContext& clock_context) {
+ switch (clock_type) {
+ case ClockContextType::StandardLocalSystem:
+ shared_memory->SetStandardLocalSystemClockContext(clock_context);
+ break;
+ case ClockContextType::StandardNetworkSystem:
+ shared_memory->SetStandardNetworkSystemClockContext(clock_context);
+ break;
+ }
+ }
+
+ SystemClockContext system_clock_context{};
+ std::shared_ptr<Service::Time::SharedMemory> shared_memory;
+ ClockContextType clock_type;
};
class ISteadyClock final : public ServiceFramework<ISteadyClock> {
public:
- ISteadyClock() : ServiceFramework("ISteadyClock") {
+ ISteadyClock(std::shared_ptr<SharedMemory> shared_memory)
+ : ServiceFramework("ISteadyClock"), shared_memory(shared_memory) {
static const FunctionInfo functions[] = {
{0, &ISteadyClock::GetCurrentTimePoint, "GetCurrentTimePoint"},
};
RegisterHandlers(functions);
+
+ shared_memory->SetStandardSteadyClockTimepoint(GetCurrentTimePoint());
}
private:
void GetCurrentTimePoint(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
- const auto& core_timing = Core::System::GetInstance().CoreTiming();
- const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks());
- const SteadyClockTimePoint steady_clock_time_point{static_cast<u64_le>(ms.count() / 1000),
- {}};
+ const auto time_point = GetCurrentTimePoint();
+ // TODO(ogniK): This should be updated periodically
+ shared_memory->SetStandardSteadyClockTimepoint(time_point);
+
IPC::ResponseBuilder rb{ctx, (sizeof(SteadyClockTimePoint) / 4) + 2};
rb.Push(RESULT_SUCCESS);
- rb.PushRaw(steady_clock_time_point);
+ rb.PushRaw(time_point);
}
+
+ SteadyClockTimePoint GetCurrentTimePoint() const {
+ const auto& core_timing = Core::System::GetInstance().CoreTiming();
+ const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks());
+ return {static_cast<u64_le>(ms.count() / 1000), {}};
+ }
+
+ std::shared_ptr<SharedMemory> shared_memory;
};
class ITimeZoneService final : public ServiceFramework<ITimeZoneService> {
@@ -233,7 +274,7 @@ void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ct
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemClock>();
+ rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardUserSystem);
}
void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx) {
@@ -241,7 +282,7 @@ void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext&
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemClock>();
+ rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardNetworkSystem);
}
void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) {
@@ -249,7 +290,7 @@ void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISteadyClock>();
+ rb.PushIpcInterface<ISteadyClock>(shared_memory);
}
void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) {
@@ -265,7 +306,7 @@ void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& c
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemClock>();
+ rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardLocalSystem);
}
void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
@@ -333,16 +374,52 @@ void Module::Interface::CalculateStandardUserSystemClockDifferenceByUser(
rb.PushRaw<u64>(difference);
}
-Module::Interface::Interface(std::shared_ptr<Module> time, const char* name)
- : ServiceFramework(name), time(std::move(time)) {}
+void Module::Interface::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called");
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(shared_memory->GetSharedMemoryHolder());
+}
+
+void Module::Interface::IsStandardUserSystemClockAutomaticCorrectionEnabled(
+ Kernel::HLERequestContext& ctx) {
+ // ogniK(TODO): When clock contexts are implemented, the value should be read from the context
+ // instead of our shared memory holder
+ LOG_DEBUG(Service_Time, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u8>(shared_memory->GetStandardUserSystemClockAutomaticCorrectionEnabled());
+}
+
+void Module::Interface::SetStandardUserSystemClockAutomaticCorrectionEnabled(
+ Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto enabled = rp.Pop<u8>();
+
+ LOG_WARNING(Service_Time, "(PARTIAL IMPLEMENTATION) called");
+
+ // TODO(ogniK): Update clock contexts and correct timespans
+
+ shared_memory->SetStandardUserSystemClockAutomaticCorrectionEnabled(enabled > 0);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+Module::Interface::Interface(std::shared_ptr<Module> time,
+ std::shared_ptr<SharedMemory> shared_memory, const char* name)
+ : ServiceFramework(name), time(std::move(time)), shared_memory(std::move(shared_memory)) {}
Module::Interface::~Interface() = default;
-void InstallInterfaces(SM::ServiceManager& service_manager) {
+void InstallInterfaces(Core::System& system) {
auto time = std::make_shared<Module>();
- std::make_shared<Time>(time, "time:a")->InstallAsService(service_manager);
- std::make_shared<Time>(time, "time:s")->InstallAsService(service_manager);
- std::make_shared<Time>(time, "time:u")->InstallAsService(service_manager);
+ auto shared_mem = std::make_shared<SharedMemory>(system);
+
+ std::make_shared<Time>(time, shared_mem, "time:a")->InstallAsService(system.ServiceManager());
+ std::make_shared<Time>(time, shared_mem, "time:s")->InstallAsService(system.ServiceManager());
+ std::make_shared<Time>(std::move(time), shared_mem, "time:u")
+ ->InstallAsService(system.ServiceManager());
}
} // namespace Service::Time
diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h
index f11affe95..e0708f856 100644
--- a/src/core/hle/service/time/time.h
+++ b/src/core/hle/service/time/time.h
@@ -10,6 +10,8 @@
namespace Service::Time {
+class SharedMemory;
+
struct LocationName {
std::array<u8, 0x24> name;
};
@@ -77,7 +79,8 @@ class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> time, const char* name);
+ explicit Interface(std::shared_ptr<Module> time,
+ std::shared_ptr<SharedMemory> shared_memory, const char* name);
~Interface() override;
void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx);
@@ -87,13 +90,17 @@ public:
void GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx);
void GetClockSnapshot(Kernel::HLERequestContext& ctx);
void CalculateStandardUserSystemClockDifferenceByUser(Kernel::HLERequestContext& ctx);
+ void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx);
+ void IsStandardUserSystemClockAutomaticCorrectionEnabled(Kernel::HLERequestContext& ctx);
+ void SetStandardUserSystemClockAutomaticCorrectionEnabled(Kernel::HLERequestContext& ctx);
protected:
std::shared_ptr<Module> time;
+ std::shared_ptr<SharedMemory> shared_memory;
};
};
/// Registers all Time services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(Core::System& system);
} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_sharedmemory.cpp b/src/core/hle/service/time/time_sharedmemory.cpp
new file mode 100644
index 000000000..bfc81b83c
--- /dev/null
+++ b/src/core/hle/service/time/time_sharedmemory.cpp
@@ -0,0 +1,68 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/core.h"
+#include "core/hle/service/time/time_sharedmemory.h"
+
+namespace Service::Time {
+const std::size_t SHARED_MEMORY_SIZE = 0x1000;
+
+SharedMemory::SharedMemory(Core::System& system) : system(system) {
+ shared_memory_holder = Kernel::SharedMemory::Create(
+ system.Kernel(), nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite,
+ Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "Time:SharedMemory");
+
+ // Seems static from 1.0.0 -> 8.1.0. Specific games seem to check this value and crash
+ // if it's set to anything else
+ shared_memory_format.format_version = 14;
+ std::memcpy(shared_memory_holder->GetPointer(), &shared_memory_format, sizeof(Format));
+}
+
+SharedMemory::~SharedMemory() = default;
+
+Kernel::SharedPtr<Kernel::SharedMemory> SharedMemory::GetSharedMemoryHolder() const {
+ return shared_memory_holder;
+}
+
+void SharedMemory::SetStandardSteadyClockTimepoint(const SteadyClockTimePoint& timepoint) {
+ shared_memory_format.standard_steady_clock_timepoint.StoreData(
+ shared_memory_holder->GetPointer(), timepoint);
+}
+
+void SharedMemory::SetStandardLocalSystemClockContext(const SystemClockContext& context) {
+ shared_memory_format.standard_local_system_clock_context.StoreData(
+ shared_memory_holder->GetPointer(), context);
+}
+
+void SharedMemory::SetStandardNetworkSystemClockContext(const SystemClockContext& context) {
+ shared_memory_format.standard_network_system_clock_context.StoreData(
+ shared_memory_holder->GetPointer(), context);
+}
+
+void SharedMemory::SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled) {
+ shared_memory_format.standard_user_system_clock_automatic_correction.StoreData(
+ shared_memory_holder->GetPointer(), enabled);
+}
+
+SteadyClockTimePoint SharedMemory::GetStandardSteadyClockTimepoint() {
+ return shared_memory_format.standard_steady_clock_timepoint.ReadData(
+ shared_memory_holder->GetPointer());
+}
+
+SystemClockContext SharedMemory::GetStandardLocalSystemClockContext() {
+ return shared_memory_format.standard_local_system_clock_context.ReadData(
+ shared_memory_holder->GetPointer());
+}
+
+SystemClockContext SharedMemory::GetStandardNetworkSystemClockContext() {
+ return shared_memory_format.standard_network_system_clock_context.ReadData(
+ shared_memory_holder->GetPointer());
+}
+
+bool SharedMemory::GetStandardUserSystemClockAutomaticCorrectionEnabled() {
+ return shared_memory_format.standard_user_system_clock_automatic_correction.ReadData(
+ shared_memory_holder->GetPointer());
+}
+
+} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_sharedmemory.h b/src/core/hle/service/time/time_sharedmemory.h
new file mode 100644
index 000000000..cb8253541
--- /dev/null
+++ b/src/core/hle/service/time/time_sharedmemory.h
@@ -0,0 +1,74 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/kernel/shared_memory.h"
+#include "core/hle/service/time/time.h"
+
+namespace Service::Time {
+class SharedMemory {
+public:
+ explicit SharedMemory(Core::System& system);
+ ~SharedMemory();
+
+ // Return the shared memory handle
+ Kernel::SharedPtr<Kernel::SharedMemory> GetSharedMemoryHolder() const;
+
+ // Set memory barriers in shared memory and update them
+ void SetStandardSteadyClockTimepoint(const SteadyClockTimePoint& timepoint);
+ void SetStandardLocalSystemClockContext(const SystemClockContext& context);
+ void SetStandardNetworkSystemClockContext(const SystemClockContext& context);
+ void SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled);
+
+ // Pull from memory barriers in the shared memory
+ SteadyClockTimePoint GetStandardSteadyClockTimepoint();
+ SystemClockContext GetStandardLocalSystemClockContext();
+ SystemClockContext GetStandardNetworkSystemClockContext();
+ bool GetStandardUserSystemClockAutomaticCorrectionEnabled();
+
+ // TODO(ogniK): We have to properly simulate memory barriers, how are we going to do this?
+ template <typename T, std::size_t Offset>
+ struct MemoryBarrier {
+ static_assert(std::is_trivially_constructible_v<T>, "T must be trivially constructable");
+ u32_le read_attempt{};
+ std::array<T, 2> data{};
+
+ // These are not actually memory barriers at the moment as we don't have multicore and all
+ // HLE is mutexed. This will need to properly be implemented when we start updating the time
+ // points on threads. As of right now, we'll be updated both values synchronously and just
+ // incrementing the read_attempt to indicate that we waited.
+ void StoreData(u8* shared_memory, T data_to_store) {
+ std::memcpy(this, shared_memory + Offset, sizeof(*this));
+ read_attempt++;
+ data[read_attempt & 1] = data_to_store;
+ std::memcpy(shared_memory + Offset, this, sizeof(*this));
+ }
+
+ // For reading we're just going to read the last stored value. If there was no value stored
+ // it will just end up reading an empty value as intended.
+ T ReadData(u8* shared_memory) {
+ std::memcpy(this, shared_memory + Offset, sizeof(*this));
+ return data[(read_attempt - 1) & 1];
+ }
+ };
+
+ // Shared memory format
+ struct Format {
+ MemoryBarrier<SteadyClockTimePoint, 0x0> standard_steady_clock_timepoint;
+ MemoryBarrier<SystemClockContext, 0x38> standard_local_system_clock_context;
+ MemoryBarrier<SystemClockContext, 0x80> standard_network_system_clock_context;
+ MemoryBarrier<bool, 0xc8> standard_user_system_clock_automatic_correction;
+ u32_le format_version;
+ };
+ static_assert(sizeof(Format) == 0xd8, "Format is an invalid size");
+
+private:
+ Kernel::SharedPtr<Kernel::SharedMemory> shared_memory_holder{};
+ Core::System& system;
+ Format shared_memory_format{};
+};
+
+} // namespace Service::Time
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index f1fa6ccd1..199b30635 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -21,6 +21,7 @@
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/writable_event.h"
+#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
#include "core/hle/service/nvflinger/nvflinger.h"
@@ -328,32 +329,22 @@ public:
Data data;
};
-struct BufferProducerFence {
- u32 is_valid;
- std::array<Nvidia::IoctlFence, 4> fences;
-};
-static_assert(sizeof(BufferProducerFence) == 36, "BufferProducerFence has wrong size");
-
class IGBPDequeueBufferResponseParcel : public Parcel {
public:
- explicit IGBPDequeueBufferResponseParcel(u32 slot) : slot(slot) {}
+ explicit IGBPDequeueBufferResponseParcel(u32 slot, Service::Nvidia::MultiFence& multi_fence)
+ : slot(slot), multi_fence(multi_fence) {}
~IGBPDequeueBufferResponseParcel() override = default;
protected:
void SerializeData() override {
- // TODO(Subv): Find out how this Fence is used.
- BufferProducerFence fence = {};
- fence.is_valid = 1;
- for (auto& fence_ : fence.fences)
- fence_.id = -1;
-
Write(slot);
Write<u32_le>(1);
- WriteObject(fence);
+ WriteObject(multi_fence);
Write<u32_le>(0);
}
u32_le slot;
+ Service::Nvidia::MultiFence multi_fence;
};
class IGBPRequestBufferRequestParcel : public Parcel {
@@ -400,12 +391,6 @@ public:
data = Read<Data>();
}
- struct Fence {
- u32_le id;
- u32_le value;
- };
- static_assert(sizeof(Fence) == 8, "Fence has wrong size");
-
struct Data {
u32_le slot;
INSERT_PADDING_WORDS(3);
@@ -418,15 +403,15 @@ public:
s32_le scaling_mode;
NVFlinger::BufferQueue::BufferTransformFlags transform;
u32_le sticky_transform;
- INSERT_PADDING_WORDS(2);
- u32_le fence_is_valid;
- std::array<Fence, 2> fences;
+ INSERT_PADDING_WORDS(1);
+ u32_le swap_interval;
+ Service::Nvidia::MultiFence multi_fence;
Common::Rectangle<int> GetCropRect() const {
return {crop_left, crop_top, crop_right, crop_bottom};
}
};
- static_assert(sizeof(Data) == 80, "ParcelData has wrong size");
+ static_assert(sizeof(Data) == 96, "ParcelData has wrong size");
Data data;
};
@@ -547,11 +532,11 @@ private:
IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()};
const u32 width{request.data.width};
const u32 height{request.data.height};
- std::optional<u32> slot = buffer_queue.DequeueBuffer(width, height);
+ auto result = buffer_queue.DequeueBuffer(width, height);
- if (slot) {
+ if (result) {
// Buffer is available
- IGBPDequeueBufferResponseParcel response{*slot};
+ IGBPDequeueBufferResponseParcel response{result->first, *result->second};
ctx.WriteBuffer(response.Serialize());
} else {
// Wait the current thread until a buffer becomes available
@@ -561,10 +546,10 @@ private:
Kernel::ThreadWakeupReason reason) {
// Repeat TransactParcel DequeueBuffer when a buffer is available
auto& buffer_queue = nv_flinger->FindBufferQueue(id);
- std::optional<u32> slot = buffer_queue.DequeueBuffer(width, height);
- ASSERT_MSG(slot != std::nullopt, "Could not dequeue buffer.");
+ auto result = buffer_queue.DequeueBuffer(width, height);
+ ASSERT_MSG(result != std::nullopt, "Could not dequeue buffer.");
- IGBPDequeueBufferResponseParcel response{*slot};
+ IGBPDequeueBufferResponseParcel response{result->first, *result->second};
ctx.WriteBuffer(response.Serialize());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -582,7 +567,8 @@ private:
IGBPQueueBufferRequestParcel request{ctx.ReadBuffer()};
buffer_queue.QueueBuffer(request.data.slot, request.data.transform,
- request.data.GetCropRect());
+ request.data.GetCropRect(), request.data.swap_interval,
+ request.data.multi_fence);
IGBPQueueBufferResponseParcel response{1280, 720};
ctx.WriteBuffer(response.Serialize());
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 10b13fb1d..f9e88be2b 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -141,6 +141,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
const FileSys::PatchManager pm(metadata.GetTitleID());
// Load NSO modules
+ modules.clear();
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
VAddr next_load_addr = base_address;
for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
@@ -159,6 +160,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
}
next_load_addr = *tentative_next_load_addr;
+ modules.insert_or_assign(load_addr, module);
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
// Register module with GDBStub
GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
@@ -212,4 +214,13 @@ bool AppLoader_DeconstructedRomDirectory::IsRomFSUpdatable() const {
return false;
}
+ResultStatus AppLoader_DeconstructedRomDirectory::ReadNSOModules(Modules& modules) {
+ if (!is_loaded) {
+ return ResultStatus::ErrorNotInitialized;
+ }
+
+ modules = this->modules;
+ return ResultStatus::Success;
+}
+
} // namespace Loader
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h
index 1a65c16a4..1c0a354a4 100644
--- a/src/core/loader/deconstructed_rom_directory.h
+++ b/src/core/loader/deconstructed_rom_directory.h
@@ -45,6 +45,8 @@ public:
ResultStatus ReadTitle(std::string& title) override;
bool IsRomFSUpdatable() const override;
+ ResultStatus ReadNSOModules(Modules& modules) override;
+
private:
FileSys::ProgramMetadata metadata;
FileSys::VirtualFile romfs;
@@ -54,6 +56,8 @@ private:
std::string name;
u64 title_id{};
bool override_update;
+
+ Modules modules;
};
} // namespace Loader
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index 6d4b02375..f1795fdd6 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -295,7 +295,7 @@ Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) {
}
}
- std::vector<u8> program_image(total_image_size);
+ Kernel::PhysicalMemory program_image(total_image_size);
std::size_t current_image_position = 0;
Kernel::CodeSet codeset;
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
new file mode 100644
index 000000000..474b55cb1
--- /dev/null
+++ b/src/core/loader/kip.cpp
@@ -0,0 +1,102 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/file_sys/kernel_executable.h"
+#include "core/file_sys/program_metadata.h"
+#include "core/gdbstub/gdbstub.h"
+#include "core/hle/kernel/code_set.h"
+#include "core/hle/kernel/process.h"
+#include "core/loader/kip.h"
+
+namespace Loader {
+
+namespace {
+constexpr u32 PageAlignSize(u32 size) {
+ return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
+}
+} // Anonymous namespace
+
+AppLoader_KIP::AppLoader_KIP(FileSys::VirtualFile file_)
+ : AppLoader(std::move(file_)), kip(std::make_unique<FileSys::KIP>(file)) {}
+
+AppLoader_KIP::~AppLoader_KIP() = default;
+
+FileType AppLoader_KIP::IdentifyType(const FileSys::VirtualFile& file) {
+ u32_le magic{};
+ if (file->GetSize() < sizeof(u32) || file->ReadObject(&magic) != sizeof(u32)) {
+ return FileType::Error;
+ }
+
+ if (magic == Common::MakeMagic('K', 'I', 'P', '1')) {
+ return FileType::KIP;
+ }
+
+ return FileType::Error;
+}
+
+FileType AppLoader_KIP::GetFileType() const {
+ return (kip != nullptr && kip->GetStatus() == ResultStatus::Success) ? FileType::KIP
+ : FileType::Error;
+}
+
+AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) {
+ if (is_loaded) {
+ return {ResultStatus::ErrorAlreadyLoaded, {}};
+ }
+
+ if (kip == nullptr) {
+ return {ResultStatus::ErrorNullFile, {}};
+ }
+
+ if (kip->GetStatus() != ResultStatus::Success) {
+ return {kip->GetStatus(), {}};
+ }
+
+ const auto get_kip_address_space_type = [](const auto& kip) {
+ return kip.Is64Bit()
+ ? (kip.Is39BitAddressSpace() ? FileSys::ProgramAddressSpaceType::Is39Bit
+ : FileSys::ProgramAddressSpaceType::Is36Bit)
+ : FileSys::ProgramAddressSpaceType::Is32Bit;
+ };
+
+ const auto address_space = get_kip_address_space_type(*kip);
+
+ FileSys::ProgramMetadata metadata;
+ metadata.LoadManual(kip->Is64Bit(), address_space, kip->GetMainThreadPriority(),
+ kip->GetMainThreadCpuCore(), kip->GetMainThreadStackSize(),
+ kip->GetTitleID(), 0xFFFFFFFFFFFFFFFF, kip->GetKernelCapabilities());
+
+ const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
+ Kernel::CodeSet codeset;
+ Kernel::PhysicalMemory program_image;
+
+ const auto load_segment = [&program_image](Kernel::CodeSet::Segment& segment,
+ const std::vector<u8>& data, u32 offset) {
+ segment.addr = offset;
+ segment.offset = offset;
+ segment.size = PageAlignSize(static_cast<u32>(data.size()));
+ program_image.resize(offset);
+ program_image.insert(program_image.end(), data.begin(), data.end());
+ };
+
+ load_segment(codeset.CodeSegment(), kip->GetTextSection(), kip->GetTextOffset());
+ load_segment(codeset.RODataSegment(), kip->GetRODataSection(), kip->GetRODataOffset());
+ load_segment(codeset.DataSegment(), kip->GetDataSection(), kip->GetDataOffset());
+
+ program_image.resize(PageAlignSize(kip->GetBSSOffset()) + kip->GetBSSSize());
+ codeset.DataSegment().size += kip->GetBSSSize();
+
+ GDBStub::RegisterModule(kip->GetName(), base_address, base_address + program_image.size());
+
+ codeset.memory = std::move(program_image);
+ process.LoadModule(std::move(codeset), base_address);
+
+ LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", kip->GetName(), base_address);
+
+ is_loaded = true;
+ return {ResultStatus::Success,
+ LoadParameters{kip->GetMainThreadPriority(), kip->GetMainThreadStackSize()}};
+}
+
+} // namespace Loader
diff --git a/src/core/loader/kip.h b/src/core/loader/kip.h
new file mode 100644
index 000000000..12ca40269
--- /dev/null
+++ b/src/core/loader/kip.h
@@ -0,0 +1,35 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/loader/loader.h"
+
+namespace FileSys {
+class KIP;
+}
+
+namespace Loader {
+
+class AppLoader_KIP final : public AppLoader {
+public:
+ explicit AppLoader_KIP(FileSys::VirtualFile file);
+ ~AppLoader_KIP() override;
+
+ /**
+ * Returns the type of the file
+ * @param file std::shared_ptr<VfsFile> open file
+ * @return FileType found, or FileType::Error if this loader doesn't know it
+ */
+ static FileType IdentifyType(const FileSys::VirtualFile& file);
+
+ FileType GetFileType() const override;
+
+ LoadResult Load(Kernel::Process& process) override;
+
+private:
+ std::unique_ptr<FileSys::KIP> kip;
+};
+
+} // namespace Loader
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index d8cc30959..59ca7091a 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -11,6 +11,7 @@
#include "core/hle/kernel/process.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/elf.h"
+#include "core/loader/kip.h"
#include "core/loader/nax.h"
#include "core/loader/nca.h"
#include "core/loader/nro.h"
@@ -36,6 +37,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
CHECK_TYPE(XCI)
CHECK_TYPE(NAX)
CHECK_TYPE(NSP)
+ CHECK_TYPE(KIP)
#undef CHECK_TYPE
@@ -63,6 +65,8 @@ FileType GuessFromFilename(const std::string& name) {
return FileType::XCI;
if (extension == "nsp")
return FileType::NSP;
+ if (extension == "kip")
+ return FileType::KIP;
return FileType::Unknown;
}
@@ -83,6 +87,8 @@ std::string GetFileTypeString(FileType type) {
return "NAX";
case FileType::NSP:
return "NSP";
+ case FileType::KIP:
+ return "KIP";
case FileType::DeconstructedRomDirectory:
return "Directory";
case FileType::Error:
@@ -93,7 +99,7 @@ std::string GetFileTypeString(FileType type) {
return "unknown";
}
-constexpr std::array<const char*, 62> RESULT_MESSAGES{
+constexpr std::array<const char*, 66> RESULT_MESSAGES{
"The operation completed successfully.",
"The loader requested to load is already loaded.",
"The operation is not implemented.",
@@ -156,6 +162,10 @@ constexpr std::array<const char*, 62> RESULT_MESSAGES{
"The BKTR-type NCA has a bad Subsection bucket.",
"The BKTR-type NCA is missing the base RomFS.",
"The NSP or XCI does not contain an update in addition to the base game.",
+ "The KIP file has a bad header.",
+ "The KIP BLZ decompression of the section failed unexpectedly.",
+ "The INI file has a bad header.",
+ "The INI file contains more than the maximum allowable number of KIP files.",
};
std::ostream& operator<<(std::ostream& os, ResultStatus status) {
@@ -205,6 +215,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
case FileType::NSP:
return std::make_unique<AppLoader_NSP>(std::move(file));
+ // NX KIP (Kernel Internal Process) file format
+ case FileType::KIP:
+ return std::make_unique<AppLoader_KIP>(std::move(file));
+
// NX deconstructed ROM directory.
case FileType::DeconstructedRomDirectory:
return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file));
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 869406b75..227ecc704 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -37,6 +37,7 @@ enum class FileType {
NSP,
XCI,
NAX,
+ KIP,
DeconstructedRomDirectory,
};
@@ -124,6 +125,10 @@ enum class ResultStatus : u16 {
ErrorBadSubsectionBuckets,
ErrorMissingBKTRBaseRomFS,
ErrorNoPackedUpdate,
+ ErrorBadKIPHeader,
+ ErrorBLZDecompressionFailed,
+ ErrorBadINIHeader,
+ ErrorINITooManyKIPs,
};
std::ostream& operator<<(std::ostream& os, ResultStatus status);
@@ -267,6 +272,12 @@ public:
return ResultStatus::ErrorNotImplemented;
}
+ using Modules = std::map<VAddr, std::string>;
+
+ virtual ResultStatus ReadNSOModules(Modules& modules) {
+ return ResultStatus::ErrorNotImplemented;
+ }
+
protected:
FileSys::VirtualFile file;
bool is_loaded = false;
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp
index 34efef09a..a152981a0 100644
--- a/src/core/loader/nax.cpp
+++ b/src/core/loader/nax.cpp
@@ -94,4 +94,8 @@ ResultStatus AppLoader_NAX::ReadLogo(std::vector<u8>& buffer) {
return nca_loader->ReadLogo(buffer);
}
+ResultStatus AppLoader_NAX::ReadNSOModules(Modules& modules) {
+ return nca_loader->ReadNSOModules(modules);
+}
+
} // namespace Loader
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h
index 00f1659c1..eaec9bf58 100644
--- a/src/core/loader/nax.h
+++ b/src/core/loader/nax.h
@@ -42,6 +42,8 @@ public:
ResultStatus ReadBanner(std::vector<u8>& buffer) override;
ResultStatus ReadLogo(std::vector<u8>& buffer) override;
+ ResultStatus ReadNSOModules(Modules& modules) override;
+
private:
std::unique_ptr<FileSys::NAX> nax;
std::unique_ptr<AppLoader_NCA> nca_loader;
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index b3f8f1083..0f65fb637 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -105,4 +105,13 @@ ResultStatus AppLoader_NCA::ReadLogo(std::vector<u8>& buffer) {
buffer = logo->GetFile("NintendoLogo.png")->ReadAllBytes();
return ResultStatus::Success;
}
+
+ResultStatus AppLoader_NCA::ReadNSOModules(Modules& modules) {
+ if (directory_loader == nullptr) {
+ return ResultStatus::ErrorNotInitialized;
+ }
+
+ return directory_loader->ReadNSOModules(modules);
+}
+
} // namespace Loader
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index 94f0ed677..e47dc0e47 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.h
@@ -42,6 +42,8 @@ public:
ResultStatus ReadBanner(std::vector<u8>& buffer) override;
ResultStatus ReadLogo(std::vector<u8>& buffer) override;
+ ResultStatus ReadNSOModules(Modules& modules) override;
+
private:
std::unique_ptr<FileSys::NCA> nca;
std::unique_ptr<AppLoader_DeconstructedRomDirectory> directory_loader;
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 6a0ca389b..3a5361fdd 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -143,7 +143,7 @@ static bool LoadNroImpl(Kernel::Process& process, const std::vector<u8>& data,
}
// Build program image
- std::vector<u8> program_image(PageAlignSize(nro_header.file_size));
+ Kernel::PhysicalMemory program_image(PageAlignSize(nro_header.file_size));
std::memcpy(program_image.data(), data.data(), program_image.size());
if (program_image.size() != PageAlignSize(nro_header.file_size)) {
return {};
@@ -258,6 +258,15 @@ ResultStatus AppLoader_NRO::ReadTitle(std::string& title) {
return ResultStatus::Success;
}
+ResultStatus AppLoader_NRO::ReadControlData(FileSys::NACP& control) {
+ if (nacp == nullptr) {
+ return ResultStatus::ErrorNoControl;
+ }
+
+ control = *nacp;
+ return ResultStatus::Success;
+}
+
bool AppLoader_NRO::IsRomFSUpdatable() const {
return false;
}
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index 1ffdae805..71811bc29 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -43,6 +43,7 @@ public:
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
ResultStatus ReadTitle(std::string& title) override;
+ ResultStatus ReadControlData(FileSys::NACP& control) override;
bool IsRomFSUpdatable() const override;
private:
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 62c090353..70c90109f 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -89,7 +89,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
// Build program image
Kernel::CodeSet codeset;
- std::vector<u8> program_image;
+ Kernel::PhysicalMemory program_image;
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
std::vector<u8> data =
file.ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
@@ -152,8 +152,8 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
auto& system = Core::System::GetInstance();
const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
if (!cheats.empty()) {
- system.RegisterCheatList(cheats, Common::HexArrayToString(nso_header.build_id),
- load_base, load_base + program_image.size());
+ system.RegisterCheatList(cheats, Common::HexToString(nso_header.build_id), load_base,
+ load_base + program_image.size());
}
}
@@ -172,11 +172,15 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
+ modules.clear();
+
// Load module
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
if (!LoadModule(process, *file, base_address, true)) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
+
+ modules.insert_or_assign(base_address, file->GetName());
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address);
is_loaded = true;
@@ -184,4 +188,9 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
LoadParameters{Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE}};
}
+ResultStatus AppLoader_NSO::ReadNSOModules(Modules& modules) {
+ modules = this->modules;
+ return ResultStatus::Success;
+}
+
} // namespace Loader
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index fdce9191c..58cbe162d 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -85,6 +85,11 @@ public:
std::optional<FileSys::PatchManager> pm = {});
LoadResult Load(Kernel::Process& process) override;
+
+ ResultStatus ReadNSOModules(Modules& modules) override;
+
+private:
+ Modules modules;
};
} // namespace Loader
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index 9035dc3f9..35c82c99d 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -167,7 +167,8 @@ ResultStatus AppLoader_NSP::ReadControlData(FileSys::NACP& nacp) {
}
ResultStatus AppLoader_NSP::ReadManualRomFS(FileSys::VirtualFile& file) {
- const auto nca = nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Manual);
+ const auto nca =
+ nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::HtmlDocument);
if (nsp->GetStatus() != ResultStatus::Success || nca == nullptr)
return ResultStatus::ErrorNoRomFS;
file = nca->GetRomFS();
@@ -182,4 +183,8 @@ ResultStatus AppLoader_NSP::ReadLogo(std::vector<u8>& buffer) {
return secondary_loader->ReadLogo(buffer);
}
+ResultStatus AppLoader_NSP::ReadNSOModules(Modules& modules) {
+ return secondary_loader->ReadNSOModules(modules);
+}
+
} // namespace Loader
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index 85e870bdf..868b028d3 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -49,6 +49,8 @@ public:
ResultStatus ReadBanner(std::vector<u8>& buffer) override;
ResultStatus ReadLogo(std::vector<u8>& buffer) override;
+ ResultStatus ReadNSOModules(Modules& modules) override;
+
private:
std::unique_ptr<FileSys::NSP> nsp;
std::unique_ptr<AppLoader> secondary_loader;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 1e285a053..5e8553db9 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -134,7 +134,7 @@ ResultStatus AppLoader_XCI::ReadControlData(FileSys::NACP& control) {
ResultStatus AppLoader_XCI::ReadManualRomFS(FileSys::VirtualFile& file) {
const auto nca = xci->GetSecurePartitionNSP()->GetNCA(xci->GetProgramTitleID(),
- FileSys::ContentRecordType::Manual);
+ FileSys::ContentRecordType::HtmlDocument);
if (xci->GetStatus() != ResultStatus::Success || nca == nullptr)
return ResultStatus::ErrorXCIMissingPartition;
file = nca->GetRomFS();
@@ -149,4 +149,8 @@ ResultStatus AppLoader_XCI::ReadLogo(std::vector<u8>& buffer) {
return nca_loader->ReadLogo(buffer);
}
+ResultStatus AppLoader_XCI::ReadNSOModules(Modules& modules) {
+ return nca_loader->ReadNSOModules(modules);
+}
+
} // namespace Loader
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index ae7145b14..618ae2f47 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -49,6 +49,8 @@ public:
ResultStatus ReadBanner(std::vector<u8>& buffer) override;
ResultStatus ReadLogo(std::vector<u8>& buffer) override;
+ ResultStatus ReadNSOModules(Modules& modules) override;
+
private:
std::unique_ptr<FileSys::XCI> xci;
std::unique_ptr<AppLoader_NCA> nca_loader;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index f18f6226b..8555691c0 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -16,11 +16,9 @@
#include "core/core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
-#include "core/hle/lock.h"
#include "core/memory.h"
#include "core/memory_setup.h"
#include "video_core/gpu.h"
-#include "video_core/renderer_base.h"
namespace Memory {
diff --git a/src/core/memory.h b/src/core/memory.h
index 04e2c5f1d..09008e1dd 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -8,10 +8,6 @@
#include <string>
#include "common/common_types.h"
-namespace Common {
-struct PageTable;
-}
-
namespace Kernel {
class Process;
}
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
new file mode 100644
index 000000000..cfe0771e2
--- /dev/null
+++ b/src/core/reporter.cpp
@@ -0,0 +1,387 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <ctime>
+#include <fstream>
+
+#include <fmt/chrono.h>
+#include <fmt/format.h>
+#include <json.hpp>
+
+#include "common/file_util.h"
+#include "common/hex_util.h"
+#include "common/scm_rev.h"
+#include "core/arm/arm_interface.h"
+#include "core/core.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/result.h"
+#include "core/reporter.h"
+#include "core/settings.h"
+
+namespace {
+
+std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) {
+ return fmt::format("{}{}/{:016X}_{}.json", FileUtil::GetUserPath(FileUtil::UserPath::LogDir),
+ type, title_id, timestamp);
+}
+
+std::string GetTimestamp() {
+ const auto time = std::time(nullptr);
+ return fmt::format("{:%FT%H-%M-%S}", *std::localtime(&time));
+}
+
+using namespace nlohmann;
+
+void SaveToFile(json json, const std::string& filename) {
+ if (!FileUtil::CreateFullPath(filename)) {
+ LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename);
+ return;
+ }
+
+ std::ofstream file(
+ FileUtil::SanitizePath(filename, FileUtil::DirectorySeparator::PlatformDefault));
+ file << std::setw(4) << json << std::endl;
+}
+
+json GetYuzuVersionData() {
+ return {
+ {"scm_rev", std::string(Common::g_scm_rev)},
+ {"scm_branch", std::string(Common::g_scm_branch)},
+ {"scm_desc", std::string(Common::g_scm_desc)},
+ {"build_name", std::string(Common::g_build_name)},
+ {"build_date", std::string(Common::g_build_date)},
+ {"build_fullname", std::string(Common::g_build_fullname)},
+ {"build_version", std::string(Common::g_build_version)},
+ {"shader_cache_version", std::string(Common::g_shader_cache_version)},
+ };
+}
+
+json GetReportCommonData(u64 title_id, ResultCode result, const std::string& timestamp,
+ std::optional<u128> user_id = {}) {
+ auto out = json{
+ {"title_id", fmt::format("{:016X}", title_id)},
+ {"result_raw", fmt::format("{:08X}", result.raw)},
+ {"result_module", fmt::format("{:08X}", static_cast<u32>(result.module.Value()))},
+ {"result_description", fmt::format("{:08X}", result.description.Value())},
+ {"timestamp", timestamp},
+ };
+
+ if (user_id.has_value()) {
+ out["user_id"] = fmt::format("{:016X}{:016X}", (*user_id)[1], (*user_id)[0]);
+ }
+
+ return out;
+}
+
+json GetProcessorStateData(const std::string& architecture, u64 entry_point, u64 sp, u64 pc,
+ u64 pstate, std::array<u64, 31> registers,
+ std::optional<std::array<u64, 32>> backtrace = {}) {
+ auto out = json{
+ {"entry_point", fmt::format("{:016X}", entry_point)},
+ {"sp", fmt::format("{:016X}", sp)},
+ {"pc", fmt::format("{:016X}", pc)},
+ {"pstate", fmt::format("{:016X}", pstate)},
+ {"architecture", architecture},
+ };
+
+ auto registers_out = json::object();
+ for (std::size_t i = 0; i < registers.size(); ++i) {
+ registers_out[fmt::format("X{:02d}", i)] = fmt::format("{:016X}", registers[i]);
+ }
+
+ out["registers"] = std::move(registers_out);
+
+ if (backtrace.has_value()) {
+ auto backtrace_out = json::array();
+ for (const auto& entry : *backtrace) {
+ backtrace_out.push_back(fmt::format("{:016X}", entry));
+ }
+ out["backtrace"] = std::move(backtrace_out);
+ }
+
+ return out;
+}
+
+json GetProcessorStateDataAuto(Core::System& system) {
+ const auto* process{system.CurrentProcess()};
+ const auto& vm_manager{process->VMManager()};
+ auto& arm{system.CurrentArmInterface()};
+
+ Core::ARM_Interface::ThreadContext context{};
+ arm.SaveContext(context);
+
+ return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32",
+ vm_manager.GetCodeRegionBaseAddress(), context.sp, context.pc,
+ context.pstate, context.cpu_registers);
+}
+
+json GetBacktraceData(Core::System& system) {
+ auto out = json::array();
+ const auto& backtrace{system.CurrentArmInterface().GetBacktrace()};
+ for (const auto& entry : backtrace) {
+ out.push_back({
+ {"module", entry.module},
+ {"address", fmt::format("{:016X}", entry.address)},
+ {"original_address", fmt::format("{:016X}", entry.original_address)},
+ {"offset", fmt::format("{:016X}", entry.offset)},
+ {"symbol_name", entry.name},
+ });
+ }
+
+ return out;
+}
+
+json GetFullDataAuto(const std::string& timestamp, u64 title_id, Core::System& system) {
+ json out;
+
+ out["yuzu_version"] = GetYuzuVersionData();
+ out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp);
+ out["processor_state"] = GetProcessorStateDataAuto(system);
+ out["backtrace"] = GetBacktraceData(system);
+
+ return out;
+}
+
+template <bool read_value, typename DescriptorType>
+json GetHLEBufferDescriptorData(const std::vector<DescriptorType>& buffer) {
+ auto buffer_out = json::array();
+ for (const auto& desc : buffer) {
+ auto entry = json{
+ {"address", fmt::format("{:016X}", desc.Address())},
+ {"size", fmt::format("{:016X}", desc.Size())},
+ };
+
+ if constexpr (read_value) {
+ std::vector<u8> data(desc.Size());
+ Memory::ReadBlock(desc.Address(), data.data(), desc.Size());
+ entry["data"] = Common::HexToString(data);
+ }
+
+ buffer_out.push_back(std::move(entry));
+ }
+
+ return buffer_out;
+}
+
+json GetHLERequestContextData(Kernel::HLERequestContext& ctx) {
+ json out;
+
+ auto cmd_buf = json::array();
+ for (std::size_t i = 0; i < IPC::COMMAND_BUFFER_LENGTH; ++i) {
+ cmd_buf.push_back(fmt::format("{:08X}", ctx.CommandBuffer()[i]));
+ }
+
+ out["command_buffer"] = std::move(cmd_buf);
+
+ out["buffer_descriptor_a"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorA());
+ out["buffer_descriptor_b"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorB());
+ out["buffer_descriptor_c"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorC());
+ out["buffer_descriptor_x"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorX());
+
+ return out;
+}
+
+} // Anonymous namespace
+
+namespace Core {
+
+Reporter::Reporter(System& system) : system(system) {}
+
+Reporter::~Reporter() = default;
+
+void Reporter::SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point,
+ u64 sp, u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
+ const std::array<u64, 31>& registers,
+ const std::array<u64, 32>& backtrace, u32 backtrace_size,
+ const std::string& arch, u32 unk10) const {
+ if (!IsReportingEnabled()) {
+ return;
+ }
+
+ const auto timestamp = GetTimestamp();
+ json out;
+
+ out["yuzu_version"] = GetYuzuVersionData();
+ out["report_common"] = GetReportCommonData(title_id, result, timestamp);
+
+ auto proc_out = GetProcessorStateData(arch, entry_point, sp, pc, pstate, registers, backtrace);
+ proc_out["set_flags"] = fmt::format("{:016X}", set_flags);
+ proc_out["afsr0"] = fmt::format("{:016X}", afsr0);
+ proc_out["afsr1"] = fmt::format("{:016X}", afsr1);
+ proc_out["esr"] = fmt::format("{:016X}", esr);
+ proc_out["far"] = fmt::format("{:016X}", far);
+ proc_out["backtrace_size"] = fmt::format("{:08X}", backtrace_size);
+ proc_out["unknown_10"] = fmt::format("{:08X}", unk10);
+
+ out["processor_state"] = std::move(proc_out);
+
+ SaveToFile(std::move(out), GetPath("crash_report", title_id, timestamp));
+}
+
+void Reporter::SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2,
+ std::optional<std::vector<u8>> resolved_buffer) const {
+ if (!IsReportingEnabled()) {
+ return;
+ }
+
+ const auto timestamp = GetTimestamp();
+ const auto title_id = system.CurrentProcess()->GetTitleID();
+ auto out = GetFullDataAuto(timestamp, title_id, system);
+
+ auto break_out = json{
+ {"type", fmt::format("{:08X}", type)},
+ {"signal_debugger", fmt::format("{}", signal_debugger)},
+ {"info1", fmt::format("{:016X}", info1)},
+ {"info2", fmt::format("{:016X}", info2)},
+ };
+
+ if (resolved_buffer.has_value()) {
+ break_out["debug_buffer"] = Common::HexToString(*resolved_buffer);
+ }
+
+ out["svc_break"] = std::move(break_out);
+
+ SaveToFile(std::move(out), GetPath("svc_break_report", title_id, timestamp));
+}
+
+void Reporter::SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id,
+ const std::string& name,
+ const std::string& service_name) const {
+ if (!IsReportingEnabled()) {
+ return;
+ }
+
+ const auto timestamp = GetTimestamp();
+ const auto title_id = system.CurrentProcess()->GetTitleID();
+ auto out = GetFullDataAuto(timestamp, title_id, system);
+
+ auto function_out = GetHLERequestContextData(ctx);
+ function_out["command_id"] = command_id;
+ function_out["function_name"] = name;
+ function_out["service_name"] = service_name;
+
+ out["function"] = std::move(function_out);
+
+ SaveToFile(std::move(out), GetPath("unimpl_func_report", title_id, timestamp));
+}
+
+void Reporter::SaveUnimplementedAppletReport(
+ u32 applet_id, u32 common_args_version, u32 library_version, u32 theme_color,
+ bool startup_sound, u64 system_tick, std::vector<std::vector<u8>> normal_channel,
+ std::vector<std::vector<u8>> interactive_channel) const {
+ if (!IsReportingEnabled()) {
+ return;
+ }
+
+ const auto timestamp = GetTimestamp();
+ const auto title_id = system.CurrentProcess()->GetTitleID();
+ auto out = GetFullDataAuto(timestamp, title_id, system);
+
+ out["applet_common_args"] = {
+ {"applet_id", fmt::format("{:02X}", applet_id)},
+ {"common_args_version", fmt::format("{:08X}", common_args_version)},
+ {"library_version", fmt::format("{:08X}", library_version)},
+ {"theme_color", fmt::format("{:08X}", theme_color)},
+ {"startup_sound", fmt::format("{}", startup_sound)},
+ {"system_tick", fmt::format("{:016X}", system_tick)},
+ };
+
+ auto normal_out = json::array();
+ for (const auto& data : normal_channel) {
+ normal_out.push_back(Common::HexToString(data));
+ }
+
+ auto interactive_out = json::array();
+ for (const auto& data : interactive_channel) {
+ interactive_out.push_back(Common::HexToString(data));
+ }
+
+ out["applet_normal_data"] = std::move(normal_out);
+ out["applet_interactive_data"] = std::move(interactive_out);
+
+ SaveToFile(std::move(out), GetPath("unimpl_applet_report", title_id, timestamp));
+}
+
+void Reporter::SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vector<u8>> data,
+ std::optional<u128> user_id) const {
+ if (!IsReportingEnabled()) {
+ return;
+ }
+
+ const auto timestamp = GetTimestamp();
+ json out;
+
+ out["yuzu_version"] = GetYuzuVersionData();
+ out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp, user_id);
+
+ auto data_out = json::array();
+ for (const auto& d : data) {
+ data_out.push_back(Common::HexToString(d));
+ }
+
+ out["play_report_process_id"] = fmt::format("{:016X}", process_id);
+ out["play_report_data"] = std::move(data_out);
+
+ SaveToFile(std::move(out), GetPath("play_report", title_id, timestamp));
+}
+
+void Reporter::SaveErrorReport(u64 title_id, ResultCode result,
+ std::optional<std::string> custom_text_main,
+ std::optional<std::string> custom_text_detail) const {
+ if (!IsReportingEnabled()) {
+ return;
+ }
+
+ const auto timestamp = GetTimestamp();
+ json out;
+
+ out["yuzu_version"] = GetYuzuVersionData();
+ out["report_common"] = GetReportCommonData(title_id, result, timestamp);
+ out["processor_state"] = GetProcessorStateDataAuto(system);
+ out["backtrace"] = GetBacktraceData(system);
+
+ out["error_custom_text"] = {
+ {"main", *custom_text_main},
+ {"detail", *custom_text_detail},
+ };
+
+ SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp));
+}
+
+void Reporter::SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode,
+ std::string log_message) const {
+ if (!IsReportingEnabled())
+ return;
+
+ const auto timestamp = GetTimestamp();
+ const auto title_id = system.CurrentProcess()->GetTitleID();
+ json out;
+
+ out["yuzu_version"] = GetYuzuVersionData();
+ out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp);
+
+ out["log_mode"] = fmt::format("{:08X}", static_cast<u32>(log_mode));
+ out["log_message"] = std::move(log_message);
+
+ SaveToFile(std::move(out), GetPath("filesystem_access_report", title_id, timestamp));
+}
+
+void Reporter::SaveUserReport() const {
+ if (!IsReportingEnabled()) {
+ return;
+ }
+
+ const auto timestamp = GetTimestamp();
+ const auto title_id = system.CurrentProcess()->GetTitleID();
+
+ SaveToFile(GetFullDataAuto(timestamp, title_id, system),
+ GetPath("user_report", title_id, timestamp));
+}
+
+bool Reporter::IsReportingEnabled() const {
+ return Settings::values.reporting_services;
+}
+
+} // namespace Core
diff --git a/src/core/reporter.h b/src/core/reporter.h
new file mode 100644
index 000000000..44256de50
--- /dev/null
+++ b/src/core/reporter.h
@@ -0,0 +1,67 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <optional>
+#include <string>
+#include <vector>
+#include "common/common_types.h"
+
+union ResultCode;
+
+namespace Kernel {
+class HLERequestContext;
+} // namespace Kernel
+
+namespace Service::FileSystem {
+enum class LogMode : u32;
+}
+
+namespace Core {
+
+class System;
+
+class Reporter {
+public:
+ explicit Reporter(System& system);
+ ~Reporter();
+
+ void SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point, u64 sp,
+ u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
+ const std::array<u64, 31>& registers, const std::array<u64, 32>& backtrace,
+ u32 backtrace_size, const std::string& arch, u32 unk10) const;
+
+ void SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2,
+ std::optional<std::vector<u8>> resolved_buffer = {}) const;
+
+ void SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id,
+ const std::string& name,
+ const std::string& service_name) const;
+
+ void SaveUnimplementedAppletReport(u32 applet_id, u32 common_args_version, u32 library_version,
+ u32 theme_color, bool startup_sound, u64 system_tick,
+ std::vector<std::vector<u8>> normal_channel,
+ std::vector<std::vector<u8>> interactive_channel) const;
+
+ void SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vector<u8>> data,
+ std::optional<u128> user_id = {}) const;
+
+ void SaveErrorReport(u64 title_id, ResultCode result,
+ std::optional<std::string> custom_text_main = {},
+ std::optional<std::string> custom_text_detail = {}) const;
+
+ void SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode,
+ std::string log_message) const;
+
+ void SaveUserReport() const;
+
+private:
+ bool IsReportingEnabled() const;
+
+ System& system;
+};
+
+} // namespace Core
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 6d32ebea3..0dd1632ac 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -85,7 +85,6 @@ void LogSettings() {
LogSetting("System_RngSeed", Settings::values.rng_seed.value_or(0));
LogSetting("System_CurrentUser", Settings::values.current_user);
LogSetting("System_LanguageIndex", Settings::values.language_index);
- LogSetting("Core_UseCpuJit", Settings::values.use_cpu_jit);
LogSetting("Core_UseMultiCore", Settings::values.use_multi_core);
LogSetting("Renderer_UseResolutionFactor", Settings::values.resolution_factor);
LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit);
diff --git a/src/core/settings.h b/src/core/settings.h
index b84390745..6638ce8f9 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -378,7 +378,6 @@ struct Values {
std::atomic_bool is_device_reload_pending{true};
// Core
- bool use_cpu_jit;
bool use_multi_core;
// Data Storage
@@ -415,6 +414,8 @@ struct Values {
std::string program_args;
bool dump_exefs;
bool dump_nso;
+ bool reporting_services;
+ bool quest_flag;
// WebService
bool enable_telemetry;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 90d06830f..793d102d3 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -168,7 +168,6 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
AddField(Telemetry::FieldType::UserConfig, "Audio_SinkId", Settings::values.sink_id);
AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching",
Settings::values.enable_audio_stretching);
- AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit);
AddField(Telemetry::FieldType::UserConfig, "Core_UseMultiCore",
Settings::values.use_multi_core);
AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor",
diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp
new file mode 100644
index 000000000..17f050068
--- /dev/null
+++ b/src/core/tools/freezer.cpp
@@ -0,0 +1,188 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/memory.h"
+#include "core/tools/freezer.h"
+
+namespace Tools {
+
+namespace {
+
+constexpr s64 MEMORY_FREEZER_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
+
+u64 MemoryReadWidth(u32 width, VAddr addr) {
+ switch (width) {
+ case 1:
+ return Memory::Read8(addr);
+ case 2:
+ return Memory::Read16(addr);
+ case 4:
+ return Memory::Read32(addr);
+ case 8:
+ return Memory::Read64(addr);
+ default:
+ UNREACHABLE();
+ return 0;
+ }
+}
+
+void MemoryWriteWidth(u32 width, VAddr addr, u64 value) {
+ switch (width) {
+ case 1:
+ Memory::Write8(addr, static_cast<u8>(value));
+ break;
+ case 2:
+ Memory::Write16(addr, static_cast<u16>(value));
+ break;
+ case 4:
+ Memory::Write32(addr, static_cast<u32>(value));
+ break;
+ case 8:
+ Memory::Write64(addr, value);
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+} // Anonymous namespace
+
+Freezer::Freezer(Core::Timing::CoreTiming& core_timing) : core_timing(core_timing) {
+ event = core_timing.RegisterEvent(
+ "MemoryFreezer::FrameCallback",
+ [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
+ core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event);
+}
+
+Freezer::~Freezer() {
+ core_timing.UnscheduleEvent(event, 0);
+}
+
+void Freezer::SetActive(bool active) {
+ if (!this->active.exchange(active)) {
+ FillEntryReads();
+ core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event);
+ LOG_DEBUG(Common_Memory, "Memory freezer activated!");
+ } else {
+ LOG_DEBUG(Common_Memory, "Memory freezer deactivated!");
+ }
+}
+
+bool Freezer::IsActive() const {
+ return active.load(std::memory_order_relaxed);
+}
+
+void Freezer::Clear() {
+ std::lock_guard lock{entries_mutex};
+
+ LOG_DEBUG(Common_Memory, "Clearing all frozen memory values.");
+
+ entries.clear();
+}
+
+u64 Freezer::Freeze(VAddr address, u32 width) {
+ std::lock_guard lock{entries_mutex};
+
+ const auto current_value = MemoryReadWidth(width, address);
+ entries.push_back({address, width, current_value});
+
+ LOG_DEBUG(Common_Memory,
+ "Freezing memory for address={:016X}, width={:02X}, current_value={:016X}", address,
+ width, current_value);
+
+ return current_value;
+}
+
+void Freezer::Unfreeze(VAddr address) {
+ std::lock_guard lock{entries_mutex};
+
+ LOG_DEBUG(Common_Memory, "Unfreezing memory for address={:016X}", address);
+
+ entries.erase(
+ std::remove_if(entries.begin(), entries.end(),
+ [&address](const Entry& entry) { return entry.address == address; }),
+ entries.end());
+}
+
+bool Freezer::IsFrozen(VAddr address) const {
+ std::lock_guard lock{entries_mutex};
+
+ return std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
+ return entry.address == address;
+ }) != entries.end();
+}
+
+void Freezer::SetFrozenValue(VAddr address, u64 value) {
+ std::lock_guard lock{entries_mutex};
+
+ const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
+ return entry.address == address;
+ });
+
+ if (iter == entries.end()) {
+ LOG_ERROR(Common_Memory,
+ "Tried to set freeze value for address={:016X} that is not frozen!", address);
+ return;
+ }
+
+ LOG_DEBUG(Common_Memory,
+ "Manually overridden freeze value for address={:016X}, width={:02X} to value={:016X}",
+ iter->address, iter->width, value);
+ iter->value = value;
+}
+
+std::optional<Freezer::Entry> Freezer::GetEntry(VAddr address) const {
+ std::lock_guard lock{entries_mutex};
+
+ const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
+ return entry.address == address;
+ });
+
+ if (iter == entries.end()) {
+ return std::nullopt;
+ }
+
+ return *iter;
+}
+
+std::vector<Freezer::Entry> Freezer::GetEntries() const {
+ std::lock_guard lock{entries_mutex};
+
+ return entries;
+}
+
+void Freezer::FrameCallback(u64 userdata, s64 cycles_late) {
+ if (!IsActive()) {
+ LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events.");
+ return;
+ }
+
+ std::lock_guard lock{entries_mutex};
+
+ for (const auto& entry : entries) {
+ LOG_DEBUG(Common_Memory,
+ "Enforcing memory freeze at address={:016X}, value={:016X}, width={:02X}",
+ entry.address, entry.value, entry.width);
+ MemoryWriteWidth(entry.width, entry.address, entry.value);
+ }
+
+ core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS - cycles_late, event);
+}
+
+void Freezer::FillEntryReads() {
+ std::lock_guard lock{entries_mutex};
+
+ LOG_DEBUG(Common_Memory, "Updating memory freeze entries to current values.");
+
+ for (auto& entry : entries) {
+ entry.value = MemoryReadWidth(entry.width, entry.address);
+ }
+}
+
+} // namespace Tools
diff --git a/src/core/tools/freezer.h b/src/core/tools/freezer.h
new file mode 100644
index 000000000..b58de5472
--- /dev/null
+++ b/src/core/tools/freezer.h
@@ -0,0 +1,82 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <mutex>
+#include <optional>
+#include <vector>
+#include "common/common_types.h"
+
+namespace Core::Timing {
+class CoreTiming;
+struct EventType;
+} // namespace Core::Timing
+
+namespace Tools {
+
+/**
+ * This class allows the user to prevent an application from writing new values to certain memory
+ * locations. This has a variety of uses when attempting to reverse a game.
+ *
+ * One example could be a cheat to prevent Mario from taking damage in SMO. One could freeze the
+ * memory address that the game uses to store Mario's health so when he takes damage (and the game
+ * tries to write the new health value to memory), the value won't change.
+ */
+class Freezer {
+public:
+ struct Entry {
+ VAddr address;
+ u32 width;
+ u64 value;
+ };
+
+ explicit Freezer(Core::Timing::CoreTiming& core_timing);
+ ~Freezer();
+
+ // Enables or disables the entire memory freezer.
+ void SetActive(bool active);
+
+ // Returns whether or not the freezer is active.
+ bool IsActive() const;
+
+ // Removes all entries from the freezer.
+ void Clear();
+
+ // Freezes a value to its current memory address. The value the memory is kept at will be the
+ // value that is read during this function. Width can be 1, 2, 4, or 8 (in bytes).
+ u64 Freeze(VAddr address, u32 width);
+
+ // Unfreezes the memory value at address. If the address isn't frozen, this is a no-op.
+ void Unfreeze(VAddr address);
+
+ // Returns whether or not the address is frozen.
+ bool IsFrozen(VAddr address) const;
+
+ // Sets the value that address should be frozen to. This doesn't change the width set by using
+ // Freeze(). If the value isn't frozen, this will not freeze it and is thus a no-op.
+ void SetFrozenValue(VAddr address, u64 value);
+
+ // Returns the entry corresponding to the address if the address is frozen, otherwise
+ // std::nullopt.
+ std::optional<Entry> GetEntry(VAddr address) const;
+
+ // Returns all the entries in the freezer, an empty vector means nothing is frozen.
+ std::vector<Entry> GetEntries() const;
+
+private:
+ void FrameCallback(u64 userdata, s64 cycles_late);
+ void FillEntryReads();
+
+ std::atomic_bool active{false};
+
+ mutable std::mutex entries_mutex;
+ std::vector<Entry> entries;
+
+ Core::Timing::EventType* event;
+ Core::Timing::CoreTiming& core_timing;
+};
+
+} // namespace Tools
diff --git a/src/core/tracer/citrace.h b/src/core/tracer/citrace.h
deleted file mode 100644
index 21fdc127a..000000000
--- a/src/core/tracer/citrace.h
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/common_types.h"
-
-namespace CiTrace {
-
-// NOTE: Things are stored in little-endian
-
-#pragma pack(1)
-
-struct CTHeader {
- static const char* ExpectedMagicWord() {
- return "CiTr";
- }
-
- static u32 ExpectedVersion() {
- return 1;
- }
-
- char magic[4];
- u32 version;
- u32 header_size;
-
- struct {
- // NOTE: Register range sizes are technically hardware-constants, but the actual limits
- // aren't known. Hence we store the presumed limits along the offsets.
- // Sizes are given in u32 units.
- u32 gpu_registers;
- u32 gpu_registers_size;
- u32 lcd_registers;
- u32 lcd_registers_size;
- u32 pica_registers;
- u32 pica_registers_size;
- u32 default_attributes;
- u32 default_attributes_size;
- u32 vs_program_binary;
- u32 vs_program_binary_size;
- u32 vs_swizzle_data;
- u32 vs_swizzle_data_size;
- u32 vs_float_uniforms;
- u32 vs_float_uniforms_size;
- u32 gs_program_binary;
- u32 gs_program_binary_size;
- u32 gs_swizzle_data;
- u32 gs_swizzle_data_size;
- u32 gs_float_uniforms;
- u32 gs_float_uniforms_size;
-
- // Other things we might want to store here:
- // - Initial framebuffer data, maybe even a full copy of FCRAM/VRAM
- // - Lookup tables for fragment lighting
- // - Lookup tables for procedural textures
- } initial_state_offsets;
-
- u32 stream_offset;
- u32 stream_size;
-};
-
-enum CTStreamElementType : u32 {
- FrameMarker = 0xE1,
- MemoryLoad = 0xE2,
- RegisterWrite = 0xE3,
-};
-
-struct CTMemoryLoad {
- u32 file_offset;
- u32 size;
- u32 physical_address;
- u32 pad;
-};
-
-struct CTRegisterWrite {
- u32 physical_address;
-
- enum : u32 {
- SIZE_8 = 0xD1,
- SIZE_16 = 0xD2,
- SIZE_32 = 0xD3,
- SIZE_64 = 0xD4,
- } size;
-
- // TODO: Make it clearer which bits of this member are used for sizes other than 32 bits
- u64 value;
-};
-
-struct CTStreamElement {
- CTStreamElementType type;
-
- union {
- CTMemoryLoad memory_load;
- CTRegisterWrite register_write;
- };
-};
-
-#pragma pack()
-} // namespace CiTrace
diff --git a/src/core/tracer/recorder.cpp b/src/core/tracer/recorder.cpp
deleted file mode 100644
index 73cacb47f..000000000
--- a/src/core/tracer/recorder.cpp
+++ /dev/null
@@ -1,208 +0,0 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <cstring>
-#include "common/assert.h"
-#include "common/file_util.h"
-#include "common/logging/log.h"
-#include "core/tracer/recorder.h"
-
-namespace CiTrace {
-
-Recorder::Recorder(const InitialState& initial_state) : initial_state(initial_state) {}
-
-void Recorder::Finish(const std::string& filename) {
- // Setup CiTrace header
- CTHeader header;
- std::memcpy(header.magic, CTHeader::ExpectedMagicWord(), 4);
- header.version = CTHeader::ExpectedVersion();
- header.header_size = sizeof(CTHeader);
-
- // Calculate file offsets
- auto& initial = header.initial_state_offsets;
-
- initial.gpu_registers_size = static_cast<u32>(initial_state.gpu_registers.size());
- initial.lcd_registers_size = static_cast<u32>(initial_state.lcd_registers.size());
- initial.pica_registers_size = static_cast<u32>(initial_state.pica_registers.size());
- initial.default_attributes_size = static_cast<u32>(initial_state.default_attributes.size());
- initial.vs_program_binary_size = static_cast<u32>(initial_state.vs_program_binary.size());
- initial.vs_swizzle_data_size = static_cast<u32>(initial_state.vs_swizzle_data.size());
- initial.vs_float_uniforms_size = static_cast<u32>(initial_state.vs_float_uniforms.size());
- initial.gs_program_binary_size = static_cast<u32>(initial_state.gs_program_binary.size());
- initial.gs_swizzle_data_size = static_cast<u32>(initial_state.gs_swizzle_data.size());
- initial.gs_float_uniforms_size = static_cast<u32>(initial_state.gs_float_uniforms.size());
- header.stream_size = static_cast<u32>(stream.size());
-
- initial.gpu_registers = sizeof(header);
- initial.lcd_registers = initial.gpu_registers + initial.gpu_registers_size * sizeof(u32);
- initial.pica_registers = initial.lcd_registers + initial.lcd_registers_size * sizeof(u32);
- ;
- initial.default_attributes = initial.pica_registers + initial.pica_registers_size * sizeof(u32);
- initial.vs_program_binary =
- initial.default_attributes + initial.default_attributes_size * sizeof(u32);
- initial.vs_swizzle_data =
- initial.vs_program_binary + initial.vs_program_binary_size * sizeof(u32);
- initial.vs_float_uniforms =
- initial.vs_swizzle_data + initial.vs_swizzle_data_size * sizeof(u32);
- initial.gs_program_binary =
- initial.vs_float_uniforms + initial.vs_float_uniforms_size * sizeof(u32);
- initial.gs_swizzle_data =
- initial.gs_program_binary + initial.gs_program_binary_size * sizeof(u32);
- initial.gs_float_uniforms =
- initial.gs_swizzle_data + initial.gs_swizzle_data_size * sizeof(u32);
- header.stream_offset = initial.gs_float_uniforms + initial.gs_float_uniforms_size * sizeof(u32);
-
- // Iterate through stream elements, update relevant stream element data
- for (auto& stream_element : stream) {
- switch (stream_element.data.type) {
- case MemoryLoad: {
- auto& file_offset = memory_regions[stream_element.hash];
- if (!stream_element.uses_existing_data) {
- file_offset = header.stream_offset;
- }
- stream_element.data.memory_load.file_offset = file_offset;
- break;
- }
-
- default:
- // Other commands don't use any extra data
- DEBUG_ASSERT(stream_element.extra_data.size() == 0);
- break;
- }
- header.stream_offset += static_cast<u32>(stream_element.extra_data.size());
- }
-
- try {
- // Open file and write header
- FileUtil::IOFile file(filename, "wb");
- std::size_t written = file.WriteObject(header);
- if (written != 1 || file.Tell() != initial.gpu_registers)
- throw "Failed to write header";
-
- // Write initial state
- written =
- file.WriteArray(initial_state.gpu_registers.data(), initial_state.gpu_registers.size());
- if (written != initial_state.gpu_registers.size() || file.Tell() != initial.lcd_registers)
- throw "Failed to write GPU registers";
-
- written =
- file.WriteArray(initial_state.lcd_registers.data(), initial_state.lcd_registers.size());
- if (written != initial_state.lcd_registers.size() || file.Tell() != initial.pica_registers)
- throw "Failed to write LCD registers";
-
- written = file.WriteArray(initial_state.pica_registers.data(),
- initial_state.pica_registers.size());
- if (written != initial_state.pica_registers.size() ||
- file.Tell() != initial.default_attributes)
- throw "Failed to write Pica registers";
-
- written = file.WriteArray(initial_state.default_attributes.data(),
- initial_state.default_attributes.size());
- if (written != initial_state.default_attributes.size() ||
- file.Tell() != initial.vs_program_binary)
- throw "Failed to write default vertex attributes";
-
- written = file.WriteArray(initial_state.vs_program_binary.data(),
- initial_state.vs_program_binary.size());
- if (written != initial_state.vs_program_binary.size() ||
- file.Tell() != initial.vs_swizzle_data)
- throw "Failed to write vertex shader program binary";
-
- written = file.WriteArray(initial_state.vs_swizzle_data.data(),
- initial_state.vs_swizzle_data.size());
- if (written != initial_state.vs_swizzle_data.size() ||
- file.Tell() != initial.vs_float_uniforms)
- throw "Failed to write vertex shader swizzle data";
-
- written = file.WriteArray(initial_state.vs_float_uniforms.data(),
- initial_state.vs_float_uniforms.size());
- if (written != initial_state.vs_float_uniforms.size() ||
- file.Tell() != initial.gs_program_binary)
- throw "Failed to write vertex shader float uniforms";
-
- written = file.WriteArray(initial_state.gs_program_binary.data(),
- initial_state.gs_program_binary.size());
- if (written != initial_state.gs_program_binary.size() ||
- file.Tell() != initial.gs_swizzle_data)
- throw "Failed to write geomtry shader program binary";
-
- written = file.WriteArray(initial_state.gs_swizzle_data.data(),
- initial_state.gs_swizzle_data.size());
- if (written != initial_state.gs_swizzle_data.size() ||
- file.Tell() != initial.gs_float_uniforms)
- throw "Failed to write geometry shader swizzle data";
-
- written = file.WriteArray(initial_state.gs_float_uniforms.data(),
- initial_state.gs_float_uniforms.size());
- if (written != initial_state.gs_float_uniforms.size() ||
- file.Tell() != initial.gs_float_uniforms + sizeof(u32) * initial.gs_float_uniforms_size)
- throw "Failed to write geometry shader float uniforms";
-
- // Iterate through stream elements, write "extra data"
- for (const auto& stream_element : stream) {
- if (stream_element.extra_data.size() == 0)
- continue;
-
- written =
- file.WriteBytes(stream_element.extra_data.data(), stream_element.extra_data.size());
- if (written != stream_element.extra_data.size())
- throw "Failed to write extra data";
- }
-
- if (file.Tell() != header.stream_offset)
- throw "Unexpected end of extra data";
-
- // Write actual stream elements
- for (const auto& stream_element : stream) {
- if (1 != file.WriteObject(stream_element.data))
- throw "Failed to write stream element";
- }
- } catch (const char* str) {
- LOG_ERROR(HW_GPU, "Writing CiTrace file failed: {}", str);
- }
-}
-
-void Recorder::FrameFinished() {
- stream.push_back({{FrameMarker}});
-}
-
-void Recorder::MemoryAccessed(const u8* data, u32 size, u32 physical_address) {
- StreamElement element = {{MemoryLoad}};
- element.data.memory_load.size = size;
- element.data.memory_load.physical_address = physical_address;
-
- // Compute hash over given memory region to check if the contents are already stored internally
- boost::crc_32_type result;
- result.process_bytes(data, size);
- element.hash = result.checksum();
-
- element.uses_existing_data = (memory_regions.find(element.hash) != memory_regions.end());
- if (!element.uses_existing_data) {
- element.extra_data.resize(size);
- memcpy(element.extra_data.data(), data, size);
- memory_regions.insert({element.hash, 0}); // file offset will be initialized in Finish()
- }
-
- stream.push_back(element);
-}
-
-template <typename T>
-void Recorder::RegisterWritten(u32 physical_address, T value) {
- StreamElement element = {{RegisterWrite}};
- element.data.register_write.size =
- (sizeof(T) == 1) ? CTRegisterWrite::SIZE_8
- : (sizeof(T) == 2) ? CTRegisterWrite::SIZE_16
- : (sizeof(T) == 4) ? CTRegisterWrite::SIZE_32
- : CTRegisterWrite::SIZE_64;
- element.data.register_write.physical_address = physical_address;
- element.data.register_write.value = value;
-
- stream.push_back(element);
-}
-
-template void Recorder::RegisterWritten(u32, u8);
-template void Recorder::RegisterWritten(u32, u16);
-template void Recorder::RegisterWritten(u32, u32);
-template void Recorder::RegisterWritten(u32, u64);
-} // namespace CiTrace
diff --git a/src/core/tracer/recorder.h b/src/core/tracer/recorder.h
deleted file mode 100644
index e1cefd5fe..000000000
--- a/src/core/tracer/recorder.h
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <string>
-#include <unordered_map>
-#include <vector>
-#include <boost/crc.hpp>
-#include "common/common_types.h"
-#include "core/tracer/citrace.h"
-
-namespace CiTrace {
-
-class Recorder {
-public:
- struct InitialState {
- std::vector<u32> gpu_registers;
- std::vector<u32> lcd_registers;
- std::vector<u32> pica_registers;
- std::vector<u32> default_attributes;
- std::vector<u32> vs_program_binary;
- std::vector<u32> vs_swizzle_data;
- std::vector<u32> vs_float_uniforms;
- std::vector<u32> gs_program_binary;
- std::vector<u32> gs_swizzle_data;
- std::vector<u32> gs_float_uniforms;
- };
-
- /**
- * Recorder constructor
- * @param initial_state Initial recorder state
- */
- explicit Recorder(const InitialState& initial_state);
-
- /// Finish recording of this Citrace and save it using the given filename.
- void Finish(const std::string& filename);
-
- /// Mark end of a frame
- void FrameFinished();
-
- /**
- * Store a copy of the given memory range in the recording.
- * @note Use this whenever the GPU is about to access a particular memory region.
- * @note The implementation will make sure to minimize redundant memory updates.
- */
- void MemoryAccessed(const u8* data, u32 size, u32 physical_address);
-
- /**
- * Record a register write.
- * @note Use this whenever a GPU-related MMIO register has been written to.
- */
- template <typename T>
- void RegisterWritten(u32 physical_address, T value);
-
-private:
- // Initial state of recording start
- InitialState initial_state;
-
- // Command stream
- struct StreamElement {
- CTStreamElement data;
-
- /**
- * Extra data to store along "core" data.
- * This is e.g. used for data used in MemoryUpdates.
- */
- std::vector<u8> extra_data;
-
- /// Optional CRC hash (e.g. for hashing memory regions)
- boost::crc_32_type::value_type hash;
-
- /// If true, refer to data already written to the output file instead of extra_data
- bool uses_existing_data;
- };
-
- std::vector<StreamElement> stream;
-
- /**
- * Internal cache which maps hashes of memory contents to file offsets at which those memory
- * contents are stored.
- */
- std::unordered_map<boost::crc_32_type::value_type /*hash*/, u32 /*file_offset*/> memory_regions;
-};
-
-} // namespace CiTrace