summaryrefslogtreecommitdiffstats
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/CMakeLists.txt30
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp31
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.h2
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.cpp88
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.h32
-rw-r--r--src/core/arm/dyncom/arm_dyncom_interpreter.cpp8
-rw-r--r--src/core/core.cpp19
-rw-r--r--src/core/core.h9
-rw-r--r--src/core/file_sys/archive_extsavedata.cpp4
-rw-r--r--src/core/file_sys/archive_extsavedata.h2
-rw-r--r--src/core/file_sys/archive_romfs.cpp43
-rw-r--r--src/core/file_sys/archive_sdmc.cpp14
-rw-r--r--src/core/file_sys/archive_sdmcwriteonly.cpp2
-rw-r--r--src/core/file_sys/archive_selfncch.cpp257
-rw-r--r--src/core/file_sys/archive_selfncch.h (renamed from src/core/file_sys/archive_romfs.h)23
-rw-r--r--src/core/file_sys/archive_source_sd_savedata.cpp2
-rw-r--r--src/core/file_sys/errors.h10
-rw-r--r--src/core/file_sys/savedata_archive.cpp12
-rw-r--r--src/core/frontend/camera/blank_camera.cpp31
-rw-r--r--src/core/frontend/camera/blank_camera.h28
-rw-r--r--src/core/frontend/camera/factory.cpp32
-rw-r--r--src/core/frontend/camera/factory.h41
-rw-r--r--src/core/frontend/camera/interface.cpp11
-rw-r--r--src/core/frontend/camera/interface.h61
-rw-r--r--src/core/frontend/emu_window.cpp45
-rw-r--r--src/core/frontend/emu_window.h54
-rw-r--r--src/core/frontend/input.h110
-rw-r--r--src/core/frontend/key_map.cpp152
-rw-r--r--src/core/frontend/key_map.h93
-rw-r--r--src/core/gdbstub/gdbstub.cpp1
-rw-r--r--src/core/hle/applets/applet.cpp5
-rw-r--r--src/core/hle/applets/mint.cpp72
-rw-r--r--src/core/hle/applets/mint.h29
-rw-r--r--src/core/hle/config_mem.cpp13
-rw-r--r--src/core/hle/ipc.h83
-rw-r--r--src/core/hle/ipc_helpers.h275
-rw-r--r--src/core/hle/kernel/server_session.h4
-rw-r--r--src/core/hle/kernel/thread.h1
-rw-r--r--src/core/hle/kernel/timer.cpp41
-rw-r--r--src/core/hle/kernel/timer.h8
-rw-r--r--src/core/hle/result.h4
-rw-r--r--src/core/hle/service/apt/apt.cpp103
-rw-r--r--src/core/hle/service/apt/apt.h40
-rw-r--r--src/core/hle/service/apt/apt_a.cpp4
-rw-r--r--src/core/hle/service/apt/apt_s.cpp4
-rw-r--r--src/core/hle/service/apt/apt_u.cpp4
-rw-r--r--src/core/hle/service/cam/cam.cpp1024
-rw-r--r--src/core/hle/service/cam/cam.h358
-rw-r--r--src/core/hle/service/cam/cam_u.cpp32
-rw-r--r--src/core/hle/service/cfg/cfg.cpp105
-rw-r--r--src/core/hle/service/err_f.cpp2
-rw-r--r--src/core/hle/service/fs/archive.h2
-rw-r--r--src/core/hle/service/fs/fs_user.cpp27
-rw-r--r--src/core/hle/service/gsp_gpu.cpp37
-rw-r--r--src/core/hle/service/hid/hid.cpp63
-rw-r--r--src/core/hle/service/hid/hid.h41
-rw-r--r--src/core/hle/service/ir/ir.cpp94
-rw-r--r--src/core/hle/service/ir/ir.h57
-rw-r--r--src/core/hle/service/ir/ir_rst.cpp37
-rw-r--r--src/core/hle/service/ir/ir_rst.h3
-rw-r--r--src/core/hle/service/ir/ir_u.cpp2
-rw-r--r--src/core/hle/service/ir/ir_user.cpp112
-rw-r--r--src/core/hle/service/ir/ir_user.h3
-rw-r--r--src/core/hle/service/ldr_ro/cro_helper.h4
-rw-r--r--src/core/hle/service/ldr_ro/ldr_ro.cpp1
-rw-r--r--src/core/hle/service/nim/nim.cpp2
-rw-r--r--src/core/hle/service/service.h1
-rw-r--r--src/core/hle/service/y2r_u.cpp47
-rw-r--r--src/core/hle/svc.cpp25
-rw-r--r--src/core/hw/aes/arithmetic128.cpp47
-rw-r--r--src/core/hw/aes/arithmetic128.h17
-rw-r--r--src/core/hw/aes/ccm.cpp95
-rw-r--r--src/core/hw/aes/ccm.h40
-rw-r--r--src/core/hw/aes/key.cpp173
-rw-r--r--src/core/hw/aes/key.h35
-rw-r--r--src/core/hw/gpu.cpp41
-rw-r--r--src/core/hw/gpu.h2
-rw-r--r--src/core/hw/hw.cpp2
-rw-r--r--src/core/loader/3dsx.cpp6
-rw-r--r--src/core/loader/loader.cpp2
-rw-r--r--src/core/loader/loader.h2
-rw-r--r--src/core/loader/ncch.cpp15
-rw-r--r--src/core/perf_stats.cpp105
-rw-r--r--src/core/perf_stats.h83
-rw-r--r--src/core/settings.cpp5
-rw-r--r--src/core/settings.h85
86 files changed, 3738 insertions, 1033 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index faad0a561..61a0b1cc3 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -2,6 +2,7 @@ set(SRCS
arm/disassembler/arm_disasm.cpp
arm/disassembler/load_symbol_map.cpp
arm/dynarmic/arm_dynarmic.cpp
+ arm/dynarmic/arm_dynarmic_cp15.cpp
arm/dyncom/arm_dyncom.cpp
arm/dyncom/arm_dyncom_dec.cpp
arm/dyncom/arm_dyncom_interpreter.cpp
@@ -19,24 +20,27 @@ set(SRCS
file_sys/archive_extsavedata.cpp
file_sys/archive_ncch.cpp
file_sys/archive_other_savedata.cpp
- file_sys/archive_romfs.cpp
file_sys/archive_savedata.cpp
file_sys/archive_sdmc.cpp
file_sys/archive_sdmcwriteonly.cpp
+ file_sys/archive_selfncch.cpp
file_sys/archive_source_sd_savedata.cpp
file_sys/archive_systemsavedata.cpp
file_sys/disk_archive.cpp
file_sys/ivfc_archive.cpp
file_sys/path_parser.cpp
file_sys/savedata_archive.cpp
+ frontend/camera/blank_camera.cpp
+ frontend/camera/factory.cpp
+ frontend/camera/interface.cpp
frontend/emu_window.cpp
- frontend/key_map.cpp
frontend/motion_emu.cpp
gdbstub/gdbstub.cpp
hle/config_mem.cpp
hle/applets/applet.cpp
hle/applets/erreula.cpp
hle/applets/mii_selector.cpp
+ hle/applets/mint.cpp
hle/applets/swkbd.cpp
hle/kernel/address_arbiter.cpp
hle/kernel/client_port.cpp
@@ -154,6 +158,9 @@ set(SRCS
hle/service/y2r_u.cpp
hle/shared_page.cpp
hle/svc.cpp
+ hw/aes/arithmetic128.cpp
+ hw/aes/ccm.cpp
+ hw/aes/key.cpp
hw/gpu.cpp
hw/hw.cpp
hw/lcd.cpp
@@ -165,6 +172,7 @@ set(SRCS
loader/smdh.cpp
tracer/recorder.cpp
memory.cpp
+ perf_stats.cpp
settings.cpp
)
@@ -173,6 +181,7 @@ set(HEADERS
arm/disassembler/arm_disasm.h
arm/disassembler/load_symbol_map.h
arm/dynarmic/arm_dynarmic.h
+ arm/dynarmic/arm_dynarmic_cp15.h
arm/dyncom/arm_dyncom.h
arm/dyncom/arm_dyncom_dec.h
arm/dyncom/arm_dyncom_interpreter.h
@@ -191,28 +200,34 @@ set(HEADERS
file_sys/archive_extsavedata.h
file_sys/archive_ncch.h
file_sys/archive_other_savedata.h
- file_sys/archive_romfs.h
file_sys/archive_savedata.h
file_sys/archive_sdmc.h
file_sys/archive_sdmcwriteonly.h
+ file_sys/archive_selfncch.h
file_sys/archive_source_sd_savedata.h
file_sys/archive_systemsavedata.h
file_sys/directory_backend.h
file_sys/disk_archive.h
+ file_sys/errors.h
file_sys/file_backend.h
file_sys/ivfc_archive.h
file_sys/path_parser.h
file_sys/savedata_archive.h
+ frontend/camera/blank_camera.h
+ frontend/camera/factory.h
+ frontend/camera/interface.h
frontend/emu_window.h
- frontend/key_map.h
+ frontend/input.h
frontend/motion_emu.h
gdbstub/gdbstub.h
hle/config_mem.h
hle/function_wrappers.h
hle/ipc.h
+ hle/ipc_helpers.h
hle/applets/applet.h
hle/applets/erreula.h
hle/applets/mii_selector.h
+ hle/applets/mint.h
hle/applets/swkbd.h
hle/kernel/address_arbiter.h
hle/kernel/client_port.h
@@ -331,6 +346,9 @@ set(HEADERS
hle/service/y2r_u.h
hle/shared_page.h
hle/svc.h
+ hw/aes/arithmetic128.h
+ hw/aes/ccm.h
+ hw/aes/key.h
hw/gpu.h
hw/hw.h
hw/lcd.h
@@ -345,13 +363,15 @@ set(HEADERS
memory.h
memory_setup.h
mmio.h
+ perf_stats.h
settings.h
)
include_directories(../../externals/dynarmic/include)
+include_directories(../../externals/cryptopp)
create_directory_groups(${SRCS} ${HEADERS})
add_library(core STATIC ${SRCS} ${HEADERS})
-target_link_libraries(core dynarmic)
+target_link_libraries(core dynarmic cryptopp)
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 9f25e3b00..7d2790b08 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -7,6 +7,7 @@
#include "common/assert.h"
#include "common/microprofile.h"
#include "core/arm/dynarmic/arm_dynarmic.h"
+#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
#include "core/arm/dyncom/arm_dyncom_interpreter.h"
#include "core/core.h"
#include "core/core_timing.h"
@@ -39,28 +40,30 @@ static bool IsReadOnlyMemory(u32 vaddr) {
return false;
}
-static Dynarmic::UserCallbacks GetUserCallbacks(ARMul_State* interpeter_state) {
+static Dynarmic::UserCallbacks GetUserCallbacks(
+ const std::shared_ptr<ARMul_State>& interpeter_state) {
Dynarmic::UserCallbacks user_callbacks{};
user_callbacks.InterpreterFallback = &InterpreterFallback;
- user_callbacks.user_arg = static_cast<void*>(interpeter_state);
+ user_callbacks.user_arg = static_cast<void*>(interpeter_state.get());
user_callbacks.CallSVC = &SVC::CallSVC;
- user_callbacks.IsReadOnlyMemory = &IsReadOnlyMemory;
- user_callbacks.MemoryReadCode = &Memory::Read32;
- user_callbacks.MemoryRead8 = &Memory::Read8;
- user_callbacks.MemoryRead16 = &Memory::Read16;
- user_callbacks.MemoryRead32 = &Memory::Read32;
- user_callbacks.MemoryRead64 = &Memory::Read64;
- user_callbacks.MemoryWrite8 = &Memory::Write8;
- user_callbacks.MemoryWrite16 = &Memory::Write16;
- user_callbacks.MemoryWrite32 = &Memory::Write32;
- user_callbacks.MemoryWrite64 = &Memory::Write64;
+ user_callbacks.memory.IsReadOnlyMemory = &IsReadOnlyMemory;
+ user_callbacks.memory.ReadCode = &Memory::Read32;
+ user_callbacks.memory.Read8 = &Memory::Read8;
+ user_callbacks.memory.Read16 = &Memory::Read16;
+ user_callbacks.memory.Read32 = &Memory::Read32;
+ user_callbacks.memory.Read64 = &Memory::Read64;
+ user_callbacks.memory.Write8 = &Memory::Write8;
+ user_callbacks.memory.Write16 = &Memory::Write16;
+ user_callbacks.memory.Write32 = &Memory::Write32;
+ user_callbacks.memory.Write64 = &Memory::Write64;
user_callbacks.page_table = Memory::GetCurrentPageTablePointers();
+ user_callbacks.coprocessors[15] = std::make_shared<DynarmicCP15>(interpeter_state);
return user_callbacks;
}
ARM_Dynarmic::ARM_Dynarmic(PrivilegeMode initial_mode) {
- interpreter_state = std::make_unique<ARMul_State>(initial_mode);
- jit = std::make_unique<Dynarmic::Jit>(GetUserCallbacks(interpreter_state.get()));
+ interpreter_state = std::make_shared<ARMul_State>(initial_mode);
+ jit = std::make_unique<Dynarmic::Jit>(GetUserCallbacks(interpreter_state));
}
void ARM_Dynarmic::SetPC(u32 pc) {
diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h
index 87ab53d81..834dc989e 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.h
+++ b/src/core/arm/dynarmic/arm_dynarmic.h
@@ -39,5 +39,5 @@ public:
private:
std::unique_ptr<Dynarmic::Jit> jit;
- std::unique_ptr<ARMul_State> interpreter_state;
+ std::shared_ptr<ARMul_State> interpreter_state;
};
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
new file mode 100644
index 000000000..b1fdce096
--- /dev/null
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
@@ -0,0 +1,88 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
+#include "core/arm/skyeye_common/arm_regformat.h"
+#include "core/arm/skyeye_common/armstate.h"
+
+using Callback = Dynarmic::Coprocessor::Callback;
+using CallbackOrAccessOneWord = Dynarmic::Coprocessor::CallbackOrAccessOneWord;
+using CallbackOrAccessTwoWords = Dynarmic::Coprocessor::CallbackOrAccessTwoWords;
+
+DynarmicCP15::DynarmicCP15(const std::shared_ptr<ARMul_State>& state) : interpreter_state(state) {}
+
+DynarmicCP15::~DynarmicCP15() = default;
+
+boost::optional<Callback> DynarmicCP15::CompileInternalOperation(bool two, unsigned opc1,
+ CoprocReg CRd, CoprocReg CRn,
+ CoprocReg CRm, unsigned opc2) {
+ return boost::none;
+}
+
+CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn,
+ CoprocReg CRm, unsigned opc2) {
+ // TODO(merry): Privileged CP15 registers
+
+ if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C5 && opc2 == 4) {
+ // This is a dummy write, we ignore the value written here.
+ return &interpreter_state->CP15[CP15_FLUSH_PREFETCH_BUFFER];
+ }
+
+ if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C10) {
+ switch (opc2) {
+ case 4:
+ // This is a dummy write, we ignore the value written here.
+ return &interpreter_state->CP15[CP15_DATA_SYNC_BARRIER];
+ case 5:
+ // This is a dummy write, we ignore the value written here.
+ return &interpreter_state->CP15[CP15_DATA_MEMORY_BARRIER];
+ default:
+ return boost::blank{};
+ }
+ }
+
+ if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 2) {
+ return &interpreter_state->CP15[CP15_THREAD_UPRW];
+ }
+
+ return boost::blank{};
+}
+
+CallbackOrAccessTwoWords DynarmicCP15::CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) {
+ return boost::blank{};
+}
+
+CallbackOrAccessOneWord DynarmicCP15::CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn,
+ CoprocReg CRm, unsigned opc2) {
+ // TODO(merry): Privileged CP15 registers
+
+ if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0) {
+ switch (opc2) {
+ case 2:
+ return &interpreter_state->CP15[CP15_THREAD_UPRW];
+ case 3:
+ return &interpreter_state->CP15[CP15_THREAD_URO];
+ default:
+ return boost::blank{};
+ }
+ }
+
+ return boost::blank{};
+}
+
+CallbackOrAccessTwoWords DynarmicCP15::CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) {
+ return boost::blank{};
+}
+
+boost::optional<Callback> DynarmicCP15::CompileLoadWords(bool two, bool long_transfer,
+ CoprocReg CRd,
+ boost::optional<u8> option) {
+ return boost::none;
+}
+
+boost::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_transfer,
+ CoprocReg CRd,
+ boost::optional<u8> option) {
+ return boost::none;
+}
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
new file mode 100644
index 000000000..7fa54e14c
--- /dev/null
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
@@ -0,0 +1,32 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include <dynarmic/coprocessor.h>
+#include "common/common_types.h"
+
+struct ARMul_State;
+
+class DynarmicCP15 final : public Dynarmic::Coprocessor {
+public:
+ explicit DynarmicCP15(const std::shared_ptr<ARMul_State>&);
+ ~DynarmicCP15() override;
+
+ boost::optional<Callback> CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd,
+ CoprocReg CRn, CoprocReg CRm,
+ unsigned opc2) override;
+ CallbackOrAccessOneWord CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn,
+ CoprocReg CRm, unsigned opc2) override;
+ CallbackOrAccessTwoWords CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) override;
+ CallbackOrAccessOneWord CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm,
+ unsigned opc2) override;
+ CallbackOrAccessTwoWords CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) override;
+ boost::optional<Callback> CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd,
+ boost::optional<u8> option) override;
+ boost::optional<Callback> CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd,
+ boost::optional<u8> option) override;
+
+private:
+ std::shared_ptr<ARMul_State> interpreter_state;
+};
diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp
index 67c45640a..273bc8167 100644
--- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp
+++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp
@@ -3928,13 +3928,13 @@ SXTB16_INST : {
if (inst_cream->Rn == 15) {
u32 lo = (u32)(s8)rm_val;
u32 hi = (u32)(s8)(rm_val >> 16);
- RD = (lo | (hi << 16));
+ RD = (lo & 0xFFFF) | (hi << 16);
}
// SXTAB16
else {
- u32 lo = (rn_val & 0xFFFF) + (u32)(s8)(rm_val & 0xFF);
- u32 hi = ((rn_val >> 16) & 0xFFFF) + (u32)(s8)((rm_val >> 16) & 0xFF);
- RD = (lo | (hi << 16));
+ u32 lo = rn_val + (u32)(s8)(rm_val & 0xFF);
+ u32 hi = (rn_val >> 16) + (u32)(s8)((rm_val >> 16) & 0xFF);
+ RD = (lo & 0xFFFF) | (hi << 16);
}
}
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 202cd332b..140ff6451 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -67,10 +67,6 @@ System::ResultStatus System::SingleStep() {
}
System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& filepath) {
- if (app_loader) {
- app_loader.reset();
- }
-
app_loader = Loader::GetLoader(filepath);
if (!app_loader) {
@@ -113,6 +109,10 @@ void System::PrepareReschedule() {
reschedule_pending = true;
}
+PerfStats::Results System::GetAndResetPerfStats() {
+ return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs());
+}
+
void System::Reschedule() {
if (!reschedule_pending) {
return;
@@ -123,10 +123,6 @@ void System::Reschedule() {
}
System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
- if (cpu_core) {
- cpu_core.reset();
- }
-
Memory::Init();
if (Settings::values.use_cpu_jit) {
@@ -148,6 +144,10 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
LOG_DEBUG(Core, "Initialized OK");
+ // Reset counters and set time origin to current frame
+ GetAndResetPerfStats();
+ perf_stats.BeginSystemFrame();
+
return ResultStatus::Success;
}
@@ -159,7 +159,8 @@ void System::Shutdown() {
Kernel::Shutdown();
HW::Shutdown();
CoreTiming::Shutdown();
- cpu_core.reset();
+ cpu_core = nullptr;
+ app_loader = nullptr;
LOG_DEBUG(Core, "Shutdown OK");
}
diff --git a/src/core/core.h b/src/core/core.h
index 1015e8847..6c9c936b5 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -6,9 +6,9 @@
#include <memory>
#include <string>
-
#include "common/common_types.h"
#include "core/memory.h"
+#include "core/perf_stats.h"
class EmuWindow;
class ARM_Interface;
@@ -83,6 +83,8 @@ public:
/// Prepare the core emulation for a reschedule
void PrepareReschedule();
+ PerfStats::Results GetAndResetPerfStats();
+
/**
* Gets a reference to the emulated CPU.
* @returns A reference to the emulated CPU.
@@ -91,6 +93,9 @@ public:
return *cpu_core;
}
+ PerfStats perf_stats;
+ FrameLimiter frame_limiter;
+
private:
/**
* Initialize the emulated system.
@@ -115,7 +120,7 @@ private:
static System s_instance;
};
-static ARM_Interface& CPU() {
+inline ARM_Interface& CPU() {
return System::GetInstance().CPU();
}
diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp
index 51ce78435..f454e7840 100644
--- a/src/core/file_sys/archive_extsavedata.cpp
+++ b/src/core/file_sys/archive_extsavedata.cpp
@@ -107,6 +107,8 @@ public:
case PathParser::NotFound:
LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
return ERROR_FILE_NOT_FOUND;
+ case PathParser::FileFound:
+ break; // Expected 'success' case
}
FileUtil::IOFile file(full_path, "r+b");
@@ -171,7 +173,7 @@ Path ConstructExtDataBinaryPath(u32 media_type, u32 high, u32 low) {
ArchiveFactory_ExtSaveData::ArchiveFactory_ExtSaveData(const std::string& mount_location,
bool shared)
: shared(shared), mount_point(GetExtDataContainerPath(mount_location, shared)) {
- LOG_INFO(Service_FS, "Directory %s set as base for ExtSaveData.", mount_point.c_str());
+ LOG_DEBUG(Service_FS, "Directory %s set as base for ExtSaveData.", mount_point.c_str());
}
bool ArchiveFactory_ExtSaveData::Initialize() {
diff --git a/src/core/file_sys/archive_extsavedata.h b/src/core/file_sys/archive_extsavedata.h
index 6a3431e94..f705ade1c 100644
--- a/src/core/file_sys/archive_extsavedata.h
+++ b/src/core/file_sys/archive_extsavedata.h
@@ -52,7 +52,7 @@ private:
/**
* This holds the full directory path for this archive, it is only set after a successful call
- * to Open, this is formed as <base extsavedatapath>/<type>/<high>/<low>.
+ * to Open, this is formed as `<base extsavedatapath>/<type>/<high>/<low>`.
* See GetExtSaveDataPath for the code that extracts this data from an archive path.
*/
std::string mount_point;
diff --git a/src/core/file_sys/archive_romfs.cpp b/src/core/file_sys/archive_romfs.cpp
deleted file mode 100644
index 6c99ca5b4..000000000
--- a/src/core/file_sys/archive_romfs.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <memory>
-#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "core/file_sys/archive_romfs.h"
-#include "core/file_sys/ivfc_archive.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// FileSys namespace
-
-namespace FileSys {
-
-ArchiveFactory_RomFS::ArchiveFactory_RomFS(Loader::AppLoader& app_loader) {
- // Load the RomFS from the app
- if (Loader::ResultStatus::Success != app_loader.ReadRomFS(romfs_file, data_offset, data_size)) {
- LOG_ERROR(Service_FS, "Unable to read RomFS!");
- }
-}
-
-ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_RomFS::Open(const Path& path) {
- auto archive = std::make_unique<IVFCArchive>(romfs_file, data_offset, data_size);
- return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
-}
-
-ResultCode ArchiveFactory_RomFS::Format(const Path& path,
- const FileSys::ArchiveFormatInfo& format_info) {
- LOG_ERROR(Service_FS, "Attempted to format a RomFS archive.");
- // TODO: Verify error code
- return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
- ErrorLevel::Permanent);
-}
-
-ResultVal<ArchiveFormatInfo> ArchiveFactory_RomFS::GetFormatInfo(const Path& path) const {
- // TODO(Subv): Implement
- LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
- return ResultCode(-1);
-}
-
-} // namespace FileSys
diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp
index 333dfb92e..679909d06 100644
--- a/src/core/file_sys/archive_sdmc.cpp
+++ b/src/core/file_sys/archive_sdmc.cpp
@@ -72,6 +72,8 @@ ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFileBase(const Path& pa
FileUtil::CreateEmptyFile(full_path);
}
break;
+ case PathParser::FileFound:
+ break; // Expected 'success' case
}
FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
@@ -106,6 +108,8 @@ ResultCode SDMCArchive::DeleteFile(const Path& path) const {
case PathParser::DirectoryFound:
LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str());
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+ case PathParser::FileFound:
+ break; // Expected 'success' case
}
if (FileUtil::Delete(full_path)) {
@@ -154,6 +158,8 @@ static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mou
case PathParser::FileFound:
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+ case PathParser::DirectoryFound:
+ break; // Expected 'success' case
}
if (deleter(full_path)) {
@@ -197,6 +203,8 @@ ResultCode SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const {
case PathParser::FileFound:
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
return ERROR_ALREADY_EXISTS;
+ case PathParser::NotFound:
+ break; // Expected 'success' case
}
if (size == 0) {
@@ -238,6 +246,8 @@ ResultCode SDMCArchive::CreateDirectory(const Path& path) const {
case PathParser::FileFound:
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
return ERROR_ALREADY_EXISTS;
+ case PathParser::NotFound:
+ break; // Expected 'success' case
}
if (FileUtil::CreateDir(mount_point + path.AsString())) {
@@ -281,6 +291,8 @@ ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Pa
case PathParser::FileInPath:
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+ case PathParser::DirectoryFound:
+ break; // Expected 'success' case
}
auto directory = std::make_unique<DiskDirectory>(full_path);
@@ -294,7 +306,7 @@ u64 SDMCArchive::GetFreeBytes() const {
ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory)
: sdmc_directory(sdmc_directory) {
- LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str());
+ LOG_DEBUG(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str());
}
bool ArchiveFactory_SDMC::Initialize() {
diff --git a/src/core/file_sys/archive_sdmcwriteonly.cpp b/src/core/file_sys/archive_sdmcwriteonly.cpp
index 2aafc9b1d..244aef48a 100644
--- a/src/core/file_sys/archive_sdmcwriteonly.cpp
+++ b/src/core/file_sys/archive_sdmcwriteonly.cpp
@@ -32,7 +32,7 @@ ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory
ArchiveFactory_SDMCWriteOnly::ArchiveFactory_SDMCWriteOnly(const std::string& mount_point)
: sdmc_directory(mount_point) {
- LOG_INFO(Service_FS, "Directory %s set as SDMCWriteOnly.", sdmc_directory.c_str());
+ LOG_DEBUG(Service_FS, "Directory %s set as SDMCWriteOnly.", sdmc_directory.c_str());
}
bool ArchiveFactory_SDMCWriteOnly::Initialize() {
diff --git a/src/core/file_sys/archive_selfncch.cpp b/src/core/file_sys/archive_selfncch.cpp
new file mode 100644
index 000000000..298a37a44
--- /dev/null
+++ b/src/core/file_sys/archive_selfncch.cpp
@@ -0,0 +1,257 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/swap.h"
+#include "core/file_sys/archive_selfncch.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/ivfc_archive.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+enum class SelfNCCHFilePathType : u32 {
+ RomFS = 0,
+ Code = 1, // This is not supported by SelfNCCHArchive but by archive 0x2345678E
+ ExeFS = 2,
+ UpdateRomFS = 5, // This is presumably for accessing the RomFS of the update patch.
+};
+
+struct SelfNCCHFilePath {
+ u32_le type;
+ std::array<char, 8> exefs_filename;
+};
+static_assert(sizeof(SelfNCCHFilePath) == 12, "NCCHFilePath has wrong size!");
+
+// A read-only file created from a block of data. It only allows you to read the entire file at
+// once, in a single read operation.
+class ExeFSSectionFile final : public FileBackend {
+public:
+ explicit ExeFSSectionFile(std::shared_ptr<std::vector<u8>> data_) : data(std::move(data_)) {}
+
+ ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override {
+ if (offset != 0) {
+ LOG_ERROR(Service_FS, "offset must be zero!");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ if (length != data->size()) {
+ LOG_ERROR(Service_FS, "size must match the file size!");
+ return ERROR_INCORRECT_EXEFS_READ_SIZE;
+ }
+
+ std::memcpy(buffer, data->data(), data->size());
+ return MakeResult<size_t>(data->size());
+ }
+
+ ResultVal<size_t> Write(u64 offset, size_t length, bool flush,
+ const u8* buffer) const override {
+ LOG_ERROR(Service_FS, "The file is read-only!");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ u64 GetSize() const override {
+ return data->size();
+ }
+
+ bool SetSize(u64 size) const override {
+ return false;
+ }
+
+ bool Close() const override {
+ return true;
+ }
+
+ void Flush() const override {}
+
+private:
+ std::shared_ptr<std::vector<u8>> data;
+};
+
+// SelfNCCHArchive represents the running application itself. From this archive the application can
+// open RomFS and ExeFS, excluding the .code section.
+class SelfNCCHArchive final : public ArchiveBackend {
+public:
+ explicit SelfNCCHArchive(const NCCHData& ncch_data_) : ncch_data(ncch_data_) {}
+
+ std::string GetName() const override {
+ return "SelfNCCHArchive";
+ }
+
+ ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode&) const override {
+ // Note: SelfNCCHArchive doesn't check the open mode.
+
+ if (path.GetType() != LowPathType::Binary) {
+ LOG_ERROR(Service_FS, "Path need to be Binary");
+ return ERROR_INVALID_PATH;
+ }
+
+ std::vector<u8> binary = path.AsBinary();
+ if (binary.size() != sizeof(SelfNCCHFilePath)) {
+ LOG_ERROR(Service_FS, "Wrong path size %zu", binary.size());
+ return ERROR_INVALID_PATH;
+ }
+
+ SelfNCCHFilePath file_path;
+ std::memcpy(&file_path, binary.data(), sizeof(SelfNCCHFilePath));
+
+ switch (static_cast<SelfNCCHFilePathType>(file_path.type)) {
+ case SelfNCCHFilePathType::UpdateRomFS:
+ LOG_WARNING(Service_FS, "(STUBBED) open update RomFS");
+ return OpenRomFS();
+
+ case SelfNCCHFilePathType::RomFS:
+ return OpenRomFS();
+
+ case SelfNCCHFilePathType::Code:
+ LOG_ERROR(Service_FS, "Reading the code section is not supported!");
+ return ERROR_COMMAND_NOT_ALLOWED;
+
+ case SelfNCCHFilePathType::ExeFS: {
+ const auto& raw = file_path.exefs_filename;
+ auto end = std::find(raw.begin(), raw.end(), '\0');
+ std::string filename(raw.begin(), end);
+ return OpenExeFS(filename);
+ }
+ default:
+ LOG_ERROR(Service_FS, "Unknown file type %u!", static_cast<u32>(file_path.type));
+ return ERROR_INVALID_PATH;
+ }
+ }
+
+ ResultCode DeleteFile(const Path& path) const override {
+ LOG_ERROR(Service_FS, "Unsupported");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override {
+ LOG_ERROR(Service_FS, "Unsupported");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ ResultCode DeleteDirectory(const Path& path) const override {
+ LOG_ERROR(Service_FS, "Unsupported");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ ResultCode DeleteDirectoryRecursively(const Path& path) const override {
+ LOG_ERROR(Service_FS, "Unsupported");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ ResultCode CreateFile(const Path& path, u64 size) const override {
+ LOG_ERROR(Service_FS, "Unsupported");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ ResultCode CreateDirectory(const Path& path) const override {
+ LOG_ERROR(Service_FS, "Unsupported");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override {
+ LOG_ERROR(Service_FS, "Unsupported");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override {
+ LOG_ERROR(Service_FS, "Unsupported");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ u64 GetFreeBytes() const override {
+ return 0;
+ }
+
+private:
+ ResultVal<std::unique_ptr<FileBackend>> OpenRomFS() const {
+ if (ncch_data.romfs_file) {
+ return MakeResult<std::unique_ptr<FileBackend>>(std::make_unique<IVFCFile>(
+ ncch_data.romfs_file, ncch_data.romfs_offset, ncch_data.romfs_size));
+ } else {
+ LOG_INFO(Service_FS, "Unable to read RomFS");
+ return ERROR_ROMFS_NOT_FOUND;
+ }
+ }
+
+ ResultVal<std::unique_ptr<FileBackend>> OpenExeFS(const std::string& filename) const {
+ if (filename == "icon") {
+ if (ncch_data.icon) {
+ return MakeResult<std::unique_ptr<FileBackend>>(
+ std::make_unique<ExeFSSectionFile>(ncch_data.icon));
+ }
+
+ LOG_WARNING(Service_FS, "Unable to read icon");
+ return ERROR_EXEFS_SECTION_NOT_FOUND;
+ }
+
+ if (filename == "logo") {
+ if (ncch_data.logo) {
+ return MakeResult<std::unique_ptr<FileBackend>>(
+ std::make_unique<ExeFSSectionFile>(ncch_data.logo));
+ }
+
+ LOG_WARNING(Service_FS, "Unable to read logo");
+ return ERROR_EXEFS_SECTION_NOT_FOUND;
+ }
+
+ if (filename == "banner") {
+ if (ncch_data.banner) {
+ return MakeResult<std::unique_ptr<FileBackend>>(
+ std::make_unique<ExeFSSectionFile>(ncch_data.banner));
+ }
+
+ LOG_WARNING(Service_FS, "Unable to read banner");
+ return ERROR_EXEFS_SECTION_NOT_FOUND;
+ }
+
+ LOG_ERROR(Service_FS, "Unknown ExeFS section %s!", filename.c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ NCCHData ncch_data;
+};
+
+ArchiveFactory_SelfNCCH::ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader) {
+ std::shared_ptr<FileUtil::IOFile> romfs_file_;
+ if (Loader::ResultStatus::Success ==
+ app_loader.ReadRomFS(romfs_file_, ncch_data.romfs_offset, ncch_data.romfs_size)) {
+
+ ncch_data.romfs_file = std::move(romfs_file_);
+ }
+
+ std::vector<u8> buffer;
+
+ if (Loader::ResultStatus::Success == app_loader.ReadIcon(buffer))
+ ncch_data.icon = std::make_shared<std::vector<u8>>(std::move(buffer));
+
+ buffer.clear();
+ if (Loader::ResultStatus::Success == app_loader.ReadLogo(buffer))
+ ncch_data.logo = std::make_shared<std::vector<u8>>(std::move(buffer));
+
+ buffer.clear();
+ if (Loader::ResultStatus::Success == app_loader.ReadBanner(buffer))
+ ncch_data.banner = std::make_shared<std::vector<u8>>(std::move(buffer));
+}
+
+ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SelfNCCH::Open(const Path& path) {
+ auto archive = std::make_unique<SelfNCCHArchive>(ncch_data);
+ return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
+}
+
+ResultCode ArchiveFactory_SelfNCCH::Format(const Path&, const FileSys::ArchiveFormatInfo&) {
+ LOG_ERROR(Service_FS, "Attempted to format a SelfNCCH archive.");
+ return ERROR_INVALID_PATH;
+}
+
+ResultVal<ArchiveFormatInfo> ArchiveFactory_SelfNCCH::GetFormatInfo(const Path&) const {
+ LOG_ERROR(Service_FS, "Attempted to get format info of a SelfNCCH archive");
+ return ERROR_INVALID_PATH;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/archive_romfs.h b/src/core/file_sys/archive_selfncch.h
index 1eaf99b54..f1b971296 100644
--- a/src/core/file_sys/archive_romfs.h
+++ b/src/core/file_sys/archive_selfncch.h
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -17,22 +17,29 @@
namespace FileSys {
-/// File system interface to the RomFS archive
-class ArchiveFactory_RomFS final : public ArchiveFactory {
+struct NCCHData {
+ std::shared_ptr<std::vector<u8>> icon;
+ std::shared_ptr<std::vector<u8>> logo;
+ std::shared_ptr<std::vector<u8>> banner;
+ std::shared_ptr<FileUtil::IOFile> romfs_file;
+ u64 romfs_offset = 0;
+ u64 romfs_size = 0;
+};
+
+/// File system interface to the SelfNCCH archive
+class ArchiveFactory_SelfNCCH final : public ArchiveFactory {
public:
- explicit ArchiveFactory_RomFS(Loader::AppLoader& app_loader);
+ explicit ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader);
std::string GetName() const override {
- return "RomFS";
+ return "SelfNCCH";
}
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override;
ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override;
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
private:
- std::shared_ptr<FileUtil::IOFile> romfs_file;
- u64 data_offset;
- u64 data_size;
+ NCCHData ncch_data;
};
} // namespace FileSys
diff --git a/src/core/file_sys/archive_source_sd_savedata.cpp b/src/core/file_sys/archive_source_sd_savedata.cpp
index e01357891..f31a68038 100644
--- a/src/core/file_sys/archive_source_sd_savedata.cpp
+++ b/src/core/file_sys/archive_source_sd_savedata.cpp
@@ -39,7 +39,7 @@ std::string GetSaveDataMetadataPath(const std::string& mount_location, u64 progr
ArchiveSource_SDSaveData::ArchiveSource_SDSaveData(const std::string& sdmc_directory)
: mount_point(GetSaveDataContainerPath(sdmc_directory)) {
- LOG_INFO(Service_FS, "Directory %s set as SaveData.", mount_point.c_str());
+ LOG_DEBUG(Service_FS, "Directory %s set as SaveData.", mount_point.c_str());
}
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open(u64 program_id) {
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index 4d5f62b08..9fc8d753b 100644
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -39,5 +39,15 @@ const ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrorDescription::FS_DirectoryNotEmpt
const ResultCode ERROR_GAMECARD_NOT_INSERTED(ErrorDescription::FS_GameCardNotInserted,
ErrorModule::FS, ErrorSummary::NotFound,
ErrorLevel::Status);
+const ResultCode ERROR_INCORRECT_EXEFS_READ_SIZE(ErrorDescription::FS_IncorrectExeFSReadSize,
+ ErrorModule::FS, ErrorSummary::NotSupported,
+ ErrorLevel::Usage);
+const ResultCode ERROR_ROMFS_NOT_FOUND(ErrorDescription::FS_RomFSNotFound, ErrorModule::FS,
+ ErrorSummary::NotFound, ErrorLevel::Status);
+const ResultCode ERROR_COMMAND_NOT_ALLOWED(ErrorDescription::FS_CommandNotAllowed, ErrorModule::FS,
+ ErrorSummary::WrongArgument, ErrorLevel::Permanent);
+const ResultCode ERROR_EXEFS_SECTION_NOT_FOUND(ErrorDescription::FS_ExeFSSectionNotFound,
+ ErrorModule::FS, ErrorSummary::NotFound,
+ ErrorLevel::Status);
} // namespace FileSys
diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp
index f2e6a06bc..f540c4a93 100644
--- a/src/core/file_sys/savedata_archive.cpp
+++ b/src/core/file_sys/savedata_archive.cpp
@@ -57,6 +57,8 @@ ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& pa
FileUtil::CreateEmptyFile(full_path);
}
break;
+ case PathParser::FileFound:
+ break; // Expected 'success' case
}
FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
@@ -91,6 +93,8 @@ ResultCode SaveDataArchive::DeleteFile(const Path& path) const {
case PathParser::NotFound:
LOG_ERROR(Service_FS, "File not found %s", full_path.c_str());
return ERROR_FILE_NOT_FOUND;
+ case PathParser::FileFound:
+ break; // Expected 'success' case
}
if (FileUtil::Delete(full_path)) {
@@ -139,6 +143,8 @@ static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mou
case PathParser::FileFound:
LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str());
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+ case PathParser::DirectoryFound:
+ break; // Expected 'success' case
}
if (deleter(full_path)) {
@@ -182,6 +188,8 @@ ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) cons
case PathParser::FileFound:
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
return ERROR_FILE_ALREADY_EXISTS;
+ case PathParser::NotFound:
+ break; // Expected 'success' case
}
if (size == 0) {
@@ -225,6 +233,8 @@ ResultCode SaveDataArchive::CreateDirectory(const Path& path) const {
case PathParser::FileFound:
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
return ERROR_DIRECTORY_ALREADY_EXISTS;
+ case PathParser::NotFound:
+ break; // Expected 'success' case
}
if (FileUtil::CreateDir(mount_point + path.AsString())) {
@@ -269,6 +279,8 @@ ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory(
case PathParser::FileFound:
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+ case PathParser::DirectoryFound:
+ break; // Expected 'success' case
}
auto directory = std::make_unique<DiskDirectory>(full_path);
diff --git a/src/core/frontend/camera/blank_camera.cpp b/src/core/frontend/camera/blank_camera.cpp
new file mode 100644
index 000000000..7995abcbd
--- /dev/null
+++ b/src/core/frontend/camera/blank_camera.cpp
@@ -0,0 +1,31 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/frontend/camera/blank_camera.h"
+
+namespace Camera {
+
+void BlankCamera::StartCapture() {}
+
+void BlankCamera::StopCapture() {}
+
+void BlankCamera::SetFormat(Service::CAM::OutputFormat output_format) {
+ output_rgb = output_format == Service::CAM::OutputFormat::RGB565;
+}
+
+void BlankCamera::SetResolution(const Service::CAM::Resolution& resolution) {
+ width = resolution.width;
+ height = resolution.height;
+};
+
+void BlankCamera::SetFlip(Service::CAM::Flip) {}
+
+void BlankCamera::SetEffect(Service::CAM::Effect) {}
+
+std::vector<u16> BlankCamera::ReceiveFrame() const {
+ // Note: 0x80008000 stands for two black pixels in YUV422
+ return std::vector<u16>(width * height, output_rgb ? 0 : 0x8000);
+}
+
+} // namespace Camera
diff --git a/src/core/frontend/camera/blank_camera.h b/src/core/frontend/camera/blank_camera.h
new file mode 100644
index 000000000..c6619bd88
--- /dev/null
+++ b/src/core/frontend/camera/blank_camera.h
@@ -0,0 +1,28 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/frontend/camera/factory.h"
+#include "core/frontend/camera/interface.h"
+
+namespace Camera {
+
+class BlankCamera final : public CameraInterface {
+public:
+ void StartCapture() override;
+ void StopCapture() override;
+ void SetResolution(const Service::CAM::Resolution&) override;
+ void SetFlip(Service::CAM::Flip) override;
+ void SetEffect(Service::CAM::Effect) override;
+ void SetFormat(Service::CAM::OutputFormat) override;
+ std::vector<u16> ReceiveFrame() const override;
+
+private:
+ int width = 0;
+ int height = 0;
+ bool output_rgb = false;
+};
+
+} // namespace Camera
diff --git a/src/core/frontend/camera/factory.cpp b/src/core/frontend/camera/factory.cpp
new file mode 100644
index 000000000..4b4da50dd
--- /dev/null
+++ b/src/core/frontend/camera/factory.cpp
@@ -0,0 +1,32 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <unordered_map>
+#include "common/logging/log.h"
+#include "core/frontend/camera/blank_camera.h"
+#include "core/frontend/camera/factory.h"
+
+namespace Camera {
+
+static std::unordered_map<std::string, std::unique_ptr<CameraFactory>> factories;
+
+CameraFactory::~CameraFactory() = default;
+
+void RegisterFactory(const std::string& name, std::unique_ptr<CameraFactory> factory) {
+ factories[name] = std::move(factory);
+}
+
+std::unique_ptr<CameraInterface> CreateCamera(const std::string& name, const std::string& config) {
+ auto pair = factories.find(name);
+ if (pair != factories.end()) {
+ return pair->second->Create(config);
+ }
+
+ if (name != "blank") {
+ LOG_ERROR(Service_CAM, "Unknown camera \"%s\"", name.c_str());
+ }
+ return std::make_unique<BlankCamera>();
+}
+
+} // namespace Camera
diff --git a/src/core/frontend/camera/factory.h b/src/core/frontend/camera/factory.h
new file mode 100644
index 000000000..f46413fa7
--- /dev/null
+++ b/src/core/frontend/camera/factory.h
@@ -0,0 +1,41 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include "core/frontend/camera/interface.h"
+
+namespace Camera {
+
+class CameraFactory {
+public:
+ virtual ~CameraFactory();
+
+ /**
+ * Creates a camera object based on the configuration string.
+ * @param config Configuration string to create the camera. The implementation can decide the
+ * meaning of this string.
+ * @returns a unique_ptr to the created camera object.
+ */
+ virtual std::unique_ptr<CameraInterface> Create(const std::string& config) const = 0;
+};
+
+/**
+ * Registers an external camera factory.
+ * @param name Identifier of the camera factory.
+ * @param factory Camera factory to register.
+ */
+void RegisterFactory(const std::string& name, std::unique_ptr<CameraFactory> factory);
+
+/**
+ * Creates a camera from the factory.
+ * @param name Identifier of the camera factory.
+ * @param config Configuration string to create the camera. The meaning of this string is
+ * defined by the factory.
+ */
+std::unique_ptr<CameraInterface> CreateCamera(const std::string& name, const std::string& config);
+
+} // namespace Camera
diff --git a/src/core/frontend/camera/interface.cpp b/src/core/frontend/camera/interface.cpp
new file mode 100644
index 000000000..9aec9e7f1
--- /dev/null
+++ b/src/core/frontend/camera/interface.cpp
@@ -0,0 +1,11 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/frontend/camera/interface.h"
+
+namespace Camera {
+
+CameraInterface::~CameraInterface() = default;
+
+} // namespace Camera
diff --git a/src/core/frontend/camera/interface.h b/src/core/frontend/camera/interface.h
new file mode 100644
index 000000000..a55a495c9
--- /dev/null
+++ b/src/core/frontend/camera/interface.h
@@ -0,0 +1,61 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_types.h"
+#include "core/hle/service/cam/cam.h"
+
+namespace Camera {
+
+/// An abstract class standing for a camera. All camera implementations should inherit from this.
+class CameraInterface {
+public:
+ virtual ~CameraInterface();
+
+ /// Starts the camera for video capturing.
+ virtual void StartCapture() = 0;
+
+ /// Stops the camera for video capturing.
+ virtual void StopCapture() = 0;
+
+ /**
+ * Sets the video resolution from raw CAM service parameters.
+ * For the meaning of the parameters, please refer to Service::CAM::Resolution. Note that the
+ * actual camera implementation doesn't need to respect all the parameters. However, the width
+ * and the height parameters must be respected and be used to determine the size of output
+ * frames.
+ * @param resolution The resolution parameters to set
+ */
+ virtual void SetResolution(const Service::CAM::Resolution& resolution) = 0;
+
+ /**
+ * Configures how received frames should be flipped by the camera.
+ * @param flip Flip applying to the frame
+ */
+ virtual void SetFlip(Service::CAM::Flip flip) = 0;
+
+ /**
+ * Configures what effect should be applied to received frames by the camera.
+ * @param effect Effect applying to the frame
+ */
+ virtual void SetEffect(Service::CAM::Effect effect) = 0;
+
+ /**
+ * Sets the output format of the all frames received after this function is called.
+ * @param format Output format of the frame
+ */
+ virtual void SetFormat(Service::CAM::OutputFormat format) = 0;
+
+ /**
+ * Receives a frame from the camera.
+ * This function should be only called between a StartCapture call and a StopCapture call.
+ * @returns A std::vector<u16> containing pixels. The total size of the vector is width * height
+ * where width and height are set by a call to SetResolution.
+ */
+ virtual std::vector<u16> ReceiveFrame() const = 0;
+};
+
+} // namespace Camera
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index e5b1147a5..5fdb3a7e8 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -5,35 +5,11 @@
#include <algorithm>
#include <cmath>
#include "common/assert.h"
-#include "common/profiler_reporting.h"
+#include "core/core.h"
#include "core/frontend/emu_window.h"
-#include "core/frontend/key_map.h"
+#include "core/settings.h"
#include "video_core/video_core.h"
-void EmuWindow::ButtonPressed(Service::HID::PadState pad) {
- pad_state.hex |= pad.hex;
-}
-
-void EmuWindow::ButtonReleased(Service::HID::PadState pad) {
- pad_state.hex &= ~pad.hex;
-}
-
-void EmuWindow::CirclePadUpdated(float x, float y) {
- constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position
-
- // Make sure the coordinates are in the unit circle,
- // otherwise normalize it.
- float r = x * x + y * y;
- if (r > 1) {
- r = std::sqrt(r);
- x /= r;
- y /= r;
- }
-
- circle_pad_x = static_cast<s16>(x * MAX_CIRCLEPAD_POS);
- circle_pad_y = static_cast<s16>(y * MAX_CIRCLEPAD_POS);
-}
-
/**
* Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
* @param layout FramebufferLayout object describing the framebuffer size and screen positions
@@ -70,14 +46,12 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
(framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top);
touch_pressed = true;
- pad_state.touch.Assign(1);
}
void EmuWindow::TouchReleased() {
touch_pressed = false;
touch_x = 0;
touch_y = 0;
- pad_state.touch.Assign(0);
}
void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
@@ -98,20 +72,19 @@ void EmuWindow::AccelerometerChanged(float x, float y, float z) {
// TODO(wwylele): do a time stretch as it in GyroscopeChanged
// The time stretch formula should be like
// stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
- accel_x = x * coef;
- accel_y = y * coef;
- accel_z = z * coef;
+ accel_x = static_cast<s16>(x * coef);
+ accel_y = static_cast<s16>(y * coef);
+ accel_z = static_cast<s16>(z * coef);
}
void EmuWindow::GyroscopeChanged(float x, float y, float z) {
constexpr float FULL_FPS = 60;
float coef = GetGyroscopeRawToDpsCoefficient();
- float stretch =
- FULL_FPS / Common::Profiling::GetTimingResultsAggregator()->GetAggregatedResults().fps;
+ float stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale();
std::lock_guard<std::mutex> lock(gyro_mutex);
- gyro_x = x * coef * stretch;
- gyro_y = y * coef * stretch;
- gyro_z = z * coef * stretch;
+ gyro_x = static_cast<s16>(x * coef * stretch);
+ gyro_y = static_cast<s16>(y * coef * stretch);
+ gyro_z = static_cast<s16>(z * coef * stretch);
}
void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 1ba64c92b..36f2667fa 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -10,7 +10,6 @@
#include "common/common_types.h"
#include "common/framebuffer_layout.h"
#include "common/math_util.h"
-#include "core/hle/service/hid/hid.h"
/**
* Abstraction class used to provide an interface between emulation code and the frontend
@@ -52,30 +51,6 @@ public:
/// Releases (dunno if this is the "right" word) the GLFW context from the caller thread
virtual void DoneCurrent() = 0;
- virtual void ReloadSetKeymaps() = 0;
-
- /**
- * Signals a button press action to the HID module.
- * @param pad_state indicates which button to press
- * @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
- */
- void ButtonPressed(Service::HID::PadState pad_state);
-
- /**
- * Signals a button release action to the HID module.
- * @param pad_state indicates which button to press
- * @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
- */
- void ButtonReleased(Service::HID::PadState pad_state);
-
- /**
- * Signals a circle pad change action to the HID module.
- * @param x new x-coordinate of the circle pad, in the range [-1.0, 1.0]
- * @param y new y-coordinate of the circle pad, in the range [-1.0, 1.0]
- * @note the coordinates will be normalized if the radius is larger than 1
- */
- void CirclePadUpdated(float x, float y);
-
/**
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
* @param framebuffer_x Framebuffer x-coordinate that was pressed
@@ -115,27 +90,6 @@ public:
void GyroscopeChanged(float x, float y, float z);
/**
- * Gets the current pad state (which buttons are pressed).
- * @note This should be called by the core emu thread to get a state set by the window thread.
- * @note This doesn't include analog input like circle pad direction
- * @todo Fix this function to be thread-safe.
- * @return PadState object indicating the current pad state
- */
- Service::HID::PadState GetPadState() const {
- return pad_state;
- }
-
- /**
- * Gets the current circle pad state.
- * @note This should be called by the core emu thread to get a state set by the window thread.
- * @todo Fix this function to be thread-safe.
- * @return std::tuple of (x, y), where `x` and `y` are the circle pad coordinates
- */
- std::tuple<s16, s16> GetCirclePadState() const {
- return std::make_tuple(circle_pad_x, circle_pad_y);
- }
-
- /**
* Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).
* @note This should be called by the core emu thread to get a state set by the window thread.
* @todo Fix this function to be thread-safe.
@@ -230,11 +184,8 @@ protected:
// TODO: Find a better place to set this.
config.min_client_area_size = std::make_pair(400u, 480u);
active_config = config;
- pad_state.hex = 0;
touch_x = 0;
touch_y = 0;
- circle_pad_x = 0;
- circle_pad_y = 0;
touch_pressed = false;
accel_x = 0;
accel_y = -512;
@@ -304,9 +255,6 @@ private:
u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
- s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156)
- s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156)
-
std::mutex accel_mutex;
s16 accel_x; ///< Accelerometer X-axis value in native 3DS units
s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units
@@ -321,6 +269,4 @@ private:
* Clip the provided coordinates to be inside the touchscreen area.
*/
std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y);
-
- Service::HID::PadState pad_state;
};
diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h
new file mode 100644
index 000000000..0a5713dc0
--- /dev/null
+++ b/src/core/frontend/input.h
@@ -0,0 +1,110 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+#include <utility>
+#include "common/logging/log.h"
+#include "common/param_package.h"
+
+namespace Input {
+
+/// An abstract class template for an input device (a button, an analog input, etc.).
+template <typename StatusType>
+class InputDevice {
+public:
+ virtual ~InputDevice() = default;
+ virtual StatusType GetStatus() const {
+ return {};
+ }
+};
+
+/// An abstract class template for a factory that can create input devices.
+template <typename InputDeviceType>
+class Factory {
+public:
+ virtual ~Factory() = default;
+ virtual std::unique_ptr<InputDeviceType> Create(const Common::ParamPackage&) = 0;
+};
+
+namespace Impl {
+
+template <typename InputDeviceType>
+using FactoryListType = std::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>;
+
+template <typename InputDeviceType>
+struct FactoryList {
+ static FactoryListType<InputDeviceType> list;
+};
+
+template <typename InputDeviceType>
+FactoryListType<InputDeviceType> FactoryList<InputDeviceType>::list;
+
+} // namespace Impl
+
+/**
+ * Registers an input device factory.
+ * @tparam InputDeviceType the type of input devices the factory can create
+ * @param name the name of the factory. Will be used to match the "engine" parameter when creating
+ * a device
+ * @param factory the factory object to register
+ */
+template <typename InputDeviceType>
+void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) {
+ auto pair = std::make_pair(name, std::move(factory));
+ if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) {
+ LOG_ERROR(Input, "Factory %s already registered", name.c_str());
+ }
+}
+
+/**
+ * Unregisters an input device factory.
+ * @tparam InputDeviceType the type of input devices the factory can create
+ * @param name the name of the factory to unregister
+ */
+template <typename InputDeviceType>
+void UnregisterFactory(const std::string& name) {
+ if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) {
+ LOG_ERROR(Input, "Factory %s not registered", name.c_str());
+ }
+}
+
+/**
+ * Create an input device from given paramters.
+ * @tparam InputDeviceType the type of input devices to create
+ * @param params a serialized ParamPackage string contains all parameters for creating the device
+ */
+template <typename InputDeviceType>
+std::unique_ptr<InputDeviceType> CreateDevice(const std::string& params) {
+ const Common::ParamPackage package(params);
+ const std::string engine = package.Get("engine", "null");
+ const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
+ const auto pair = factory_list.find(engine);
+ if (pair == factory_list.end()) {
+ if (engine != "null") {
+ LOG_ERROR(Input, "Unknown engine name: %s", engine.c_str());
+ }
+ return std::make_unique<InputDeviceType>();
+ }
+ return pair->second->Create(package);
+}
+
+/**
+ * A button device is an input device that returns bool as status.
+ * true for pressed; false for released.
+ */
+using ButtonDevice = InputDevice<bool>;
+
+/**
+ * An analog device is an input device that returns a tuple of x and y coordinates as status. The
+ * coordinates are within the unit circle. x+ is defined as right direction, and y+ is defined as up
+ * direction
+ */
+using AnalogDevice = InputDevice<std::tuple<float, float>>;
+
+} // namespace Input
diff --git a/src/core/frontend/key_map.cpp b/src/core/frontend/key_map.cpp
deleted file mode 100644
index 15f0e079c..000000000
--- a/src/core/frontend/key_map.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <map>
-#include "core/frontend/emu_window.h"
-#include "core/frontend/key_map.h"
-
-namespace KeyMap {
-
-// TODO (wwylele): currently we treat c-stick as four direction buttons
-// and map it directly to EmuWindow::ButtonPressed.
-// It should go the analog input way like circle pad does.
-const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets = {{
- Service::HID::PAD_A,
- Service::HID::PAD_B,
- Service::HID::PAD_X,
- Service::HID::PAD_Y,
- Service::HID::PAD_L,
- Service::HID::PAD_R,
- Service::HID::PAD_ZL,
- Service::HID::PAD_ZR,
- Service::HID::PAD_START,
- Service::HID::PAD_SELECT,
- Service::HID::PAD_NONE,
- Service::HID::PAD_UP,
- Service::HID::PAD_DOWN,
- Service::HID::PAD_LEFT,
- Service::HID::PAD_RIGHT,
- Service::HID::PAD_C_UP,
- Service::HID::PAD_C_DOWN,
- Service::HID::PAD_C_LEFT,
- Service::HID::PAD_C_RIGHT,
-
- IndirectTarget::CirclePadUp,
- IndirectTarget::CirclePadDown,
- IndirectTarget::CirclePadLeft,
- IndirectTarget::CirclePadRight,
- IndirectTarget::CirclePadModifier,
-}};
-
-static std::map<HostDeviceKey, KeyTarget> key_map;
-static int next_device_id = 0;
-
-static bool circle_pad_up = false;
-static bool circle_pad_down = false;
-static bool circle_pad_left = false;
-static bool circle_pad_right = false;
-static bool circle_pad_modifier = false;
-
-static void UpdateCirclePad(EmuWindow& emu_window) {
- constexpr float SQRT_HALF = 0.707106781f;
- int x = 0, y = 0;
-
- if (circle_pad_right)
- ++x;
- if (circle_pad_left)
- --x;
- if (circle_pad_up)
- ++y;
- if (circle_pad_down)
- --y;
-
- float modifier = circle_pad_modifier ? Settings::values.pad_circle_modifier_scale : 1.0f;
- emu_window.CirclePadUpdated(x * modifier * (y == 0 ? 1.0f : SQRT_HALF),
- y * modifier * (x == 0 ? 1.0f : SQRT_HALF));
-}
-
-int NewDeviceId() {
- return next_device_id++;
-}
-
-void SetKeyMapping(HostDeviceKey key, KeyTarget target) {
- key_map[key] = target;
-}
-
-void ClearKeyMapping(int device_id) {
- auto iter = key_map.begin();
- while (iter != key_map.end()) {
- if (iter->first.device_id == device_id)
- key_map.erase(iter++);
- else
- ++iter;
- }
-}
-
-void PressKey(EmuWindow& emu_window, HostDeviceKey key) {
- auto target = key_map.find(key);
- if (target == key_map.end())
- return;
-
- if (target->second.direct) {
- emu_window.ButtonPressed({{target->second.target.direct_target_hex}});
- } else {
- switch (target->second.target.indirect_target) {
- case IndirectTarget::CirclePadUp:
- circle_pad_up = true;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadDown:
- circle_pad_down = true;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadLeft:
- circle_pad_left = true;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadRight:
- circle_pad_right = true;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadModifier:
- circle_pad_modifier = true;
- UpdateCirclePad(emu_window);
- break;
- }
- }
-}
-
-void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key) {
- auto target = key_map.find(key);
- if (target == key_map.end())
- return;
-
- if (target->second.direct) {
- emu_window.ButtonReleased({{target->second.target.direct_target_hex}});
- } else {
- switch (target->second.target.indirect_target) {
- case IndirectTarget::CirclePadUp:
- circle_pad_up = false;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadDown:
- circle_pad_down = false;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadLeft:
- circle_pad_left = false;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadRight:
- circle_pad_right = false;
- UpdateCirclePad(emu_window);
- break;
- case IndirectTarget::CirclePadModifier:
- circle_pad_modifier = false;
- UpdateCirclePad(emu_window);
- break;
- }
- }
-}
-}
diff --git a/src/core/frontend/key_map.h b/src/core/frontend/key_map.h
deleted file mode 100644
index 040794578..000000000
--- a/src/core/frontend/key_map.h
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <tuple>
-#include "core/hle/service/hid/hid.h"
-
-class EmuWindow;
-
-namespace KeyMap {
-
-/**
- * Represents key mapping targets that are not real 3DS buttons.
- * They will be handled by KeyMap and translated to 3DS input.
- */
-enum class IndirectTarget {
- CirclePadUp,
- CirclePadDown,
- CirclePadLeft,
- CirclePadRight,
- CirclePadModifier,
-};
-
-/**
- * Represents a key mapping target. It can be a PadState that represents real 3DS buttons,
- * or an IndirectTarget.
- */
-struct KeyTarget {
- bool direct;
- union {
- u32 direct_target_hex;
- IndirectTarget indirect_target;
- } target;
-
- KeyTarget() : direct(true) {
- target.direct_target_hex = 0;
- }
-
- KeyTarget(Service::HID::PadState pad) : direct(true) {
- target.direct_target_hex = pad.hex;
- }
-
- KeyTarget(IndirectTarget i) : direct(false) {
- target.indirect_target = i;
- }
-};
-
-/**
- * Represents a key for a specific host device.
- */
-struct HostDeviceKey {
- int key_code;
- int device_id; ///< Uniquely identifies a host device
-
- bool operator<(const HostDeviceKey& other) const {
- return std::tie(key_code, device_id) < std::tie(other.key_code, other.device_id);
- }
-
- bool operator==(const HostDeviceKey& other) const {
- return std::tie(key_code, device_id) == std::tie(other.key_code, other.device_id);
- }
-};
-
-extern const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets;
-
-/**
- * Generates a new device id, which uniquely identifies a host device within KeyMap.
- */
-int NewDeviceId();
-
-/**
- * Maps a device-specific key to a target (a PadState or an IndirectTarget).
- */
-void SetKeyMapping(HostDeviceKey key, KeyTarget target);
-
-/**
- * Clears all key mappings belonging to one device.
- */
-void ClearKeyMapping(int device_id);
-
-/**
- * Maps a key press action and call the corresponding function in EmuWindow
- */
-void PressKey(EmuWindow& emu_window, HostDeviceKey key);
-
-/**
- * Maps a key release action and call the corresponding function in EmuWindow
- */
-void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key);
-}
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index 5cf45ada5..123fe7cd4 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -230,6 +230,7 @@ static void GdbHexToMem(u8* dest, const u8* src, size_t len) {
* Convert a u32 into a gdb-formatted hex string.
*
* @param dest Pointer to buffer to store output hex string characters.
+ * @param v Value to convert.
*/
static void IntToGdbHex(u8* dest, u32 v) {
for (int i = 0; i < 8; i += 2) {
diff --git a/src/core/hle/applets/applet.cpp b/src/core/hle/applets/applet.cpp
index 645b2d5fe..9c43ed2fd 100644
--- a/src/core/hle/applets/applet.cpp
+++ b/src/core/hle/applets/applet.cpp
@@ -12,6 +12,7 @@
#include "core/hle/applets/applet.h"
#include "core/hle/applets/erreula.h"
#include "core/hle/applets/mii_selector.h"
+#include "core/hle/applets/mint.h"
#include "core/hle/applets/swkbd.h"
#include "core/hle/result.h"
#include "core/hle/service/apt/apt.h"
@@ -56,6 +57,10 @@ ResultCode Applet::Create(Service::APT::AppletId id) {
case Service::APT::AppletId::Error2:
applets[id] = std::make_shared<ErrEula>(id);
break;
+ case Service::APT::AppletId::Mint:
+ case Service::APT::AppletId::Mint2:
+ applets[id] = std::make_shared<Mint>(id);
+ break;
default:
LOG_ERROR(Service_APT, "Could not create applet %u", id);
// TODO(Subv): Find the right error code
diff --git a/src/core/hle/applets/mint.cpp b/src/core/hle/applets/mint.cpp
new file mode 100644
index 000000000..31a79ea17
--- /dev/null
+++ b/src/core/hle/applets/mint.cpp
@@ -0,0 +1,72 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/string_util.h"
+#include "core/hle/applets/mint.h"
+#include "core/hle/service/apt/apt.h"
+
+namespace HLE {
+namespace Applets {
+
+ResultCode Mint::ReceiveParameter(const Service::APT::MessageParameter& parameter) {
+ if (parameter.signal != static_cast<u32>(Service::APT::SignalType::Request)) {
+ LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal);
+ UNIMPLEMENTED();
+ // TODO(Subv): Find the right error code
+ return ResultCode(-1);
+ }
+
+ // The Request message contains a buffer with the size of the framebuffer shared
+ // memory.
+ // Create the SharedMemory that will hold the framebuffer data
+ Service::APT::CaptureBufferInfo capture_info;
+ ASSERT(sizeof(capture_info) == parameter.buffer.size());
+
+ memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info));
+
+ // TODO: allocated memory never released
+ using Kernel::MemoryPermission;
+ // Allocate a heap block of the required size for this applet.
+ heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
+ // Create a SharedMemory that directly points to this heap block.
+ framebuffer_memory = Kernel::SharedMemory::CreateForApplet(
+ heap_memory, 0, heap_memory->size(), MemoryPermission::ReadWrite,
+ MemoryPermission::ReadWrite, "Mint Memory");
+
+ // Send the response message with the newly created SharedMemory
+ Service::APT::MessageParameter result;
+ result.signal = static_cast<u32>(Service::APT::SignalType::Response);
+ result.buffer.clear();
+ result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
+ result.sender_id = static_cast<u32>(id);
+ result.object = framebuffer_memory;
+
+ Service::APT::SendParameter(result);
+ return RESULT_SUCCESS;
+}
+
+ResultCode Mint::StartImpl(const Service::APT::AppletStartupParameter& parameter) {
+ is_running = true;
+
+ // TODO(Subv): Set the expected fields in the response buffer before resending it to the
+ // application.
+ // TODO(Subv): Reverse the parameter format for the Mint applet
+
+ // Let the application know that we're closing
+ Service::APT::MessageParameter message;
+ message.buffer.resize(parameter.buffer.size());
+ std::fill(message.buffer.begin(), message.buffer.end(), 0);
+ message.signal = static_cast<u32>(Service::APT::SignalType::WakeupByExit);
+ message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
+ message.sender_id = static_cast<u32>(id);
+ Service::APT::SendParameter(message);
+
+ is_running = false;
+ return RESULT_SUCCESS;
+}
+
+void Mint::Update() {}
+
+} // namespace Applets
+} // namespace HLE
diff --git a/src/core/hle/applets/mint.h b/src/core/hle/applets/mint.h
new file mode 100644
index 000000000..d23dc40f9
--- /dev/null
+++ b/src/core/hle/applets/mint.h
@@ -0,0 +1,29 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/applets/applet.h"
+#include "core/hle/kernel/shared_memory.h"
+
+namespace HLE {
+namespace Applets {
+
+class Mint final : public Applet {
+public:
+ explicit Mint(Service::APT::AppletId id) : Applet(id) {}
+
+ ResultCode ReceiveParameter(const Service::APT::MessageParameter& parameter) override;
+ ResultCode StartImpl(const Service::APT::AppletStartupParameter& parameter) override;
+ void Update() override;
+
+private:
+ /// This SharedMemory will be created when we receive the Request message.
+ /// It holds the framebuffer info retrieved by the application with
+ /// GSPGPU::ImportDisplayCaptureInfo
+ Kernel::SharedPtr<Kernel::SharedMemory> framebuffer_memory;
+};
+
+} // namespace Applets
+} // namespace HLE
diff --git a/src/core/hle/config_mem.cpp b/src/core/hle/config_mem.cpp
index ccd73cfcb..e386ccdc6 100644
--- a/src/core/hle/config_mem.cpp
+++ b/src/core/hle/config_mem.cpp
@@ -14,15 +14,18 @@ ConfigMemDef config_mem;
void Init() {
std::memset(&config_mem, 0, sizeof(config_mem));
- config_mem.update_flag = 0; // No update
+ // Values extracted from firmware 11.2.0-35E
+ config_mem.kernel_version_min = 0x34;
+ config_mem.kernel_version_maj = 0x2;
+ config_mem.ns_tid = 0x0004013000008002;
config_mem.sys_core_ver = 0x2;
config_mem.unit_info = 0x1; // Bit 0 set for Retail
- config_mem.prev_firm = 0;
- config_mem.firm_unk = 0;
- config_mem.firm_version_rev = 0;
- config_mem.firm_version_min = 0x40;
+ config_mem.prev_firm = 0x1;
+ config_mem.ctr_sdk_ver = 0x0000F297;
+ config_mem.firm_version_min = 0x34;
config_mem.firm_version_maj = 0x2;
config_mem.firm_sys_core_ver = 0x2;
+ config_mem.firm_ctr_sdk_ver = 0x0000F297;
}
} // namespace
diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h
index 4e094faa7..cd9a5863d 100644
--- a/src/core/hle/ipc.h
+++ b/src/core/hle/ipc.h
@@ -18,12 +18,26 @@ static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of
* the thread's TLS to an intermediate buffer in kernel memory, and then copied again to
* the service handler process' memory.
* @param offset Optional offset into command buffer
+ * @param offset Optional offset into command buffer (in bytes)
* @return Pointer to command buffer
*/
inline u32* GetCommandBuffer(const int offset = 0) {
return (u32*)Memory::GetPointer(GetCurrentThread()->GetTLSAddress() + kCommandHeaderOffset +
offset);
}
+
+static const int kStaticBuffersOffset =
+ 0x100; ///< Offset into static buffers, relative to command buffer header
+
+/**
+ * Returns a pointer to the static buffers area in the current thread's TLS
+ * TODO(Subv): cf. GetCommandBuffer
+ * @param offset Optional offset into static buffers area (in bytes)
+ * @return Pointer to static buffers area
+ */
+inline u32* GetStaticBuffers(const int offset = 0) {
+ return GetCommandBuffer(kStaticBuffersOffset + offset);
+}
}
namespace IPC {
@@ -40,10 +54,17 @@ enum DescriptorType : u32 {
CallingPid = 0x20,
};
+union Header {
+ u32 raw;
+ BitField<0, 6, u32> translate_params_size;
+ BitField<6, 6, u32> normal_params_size;
+ BitField<16, 16, u32> command_id;
+};
+
/**
* @brief Creates a command header to be used for IPC
* @param command_id ID of the command to create a header for.
- * @param normal_params Size of the normal parameters in words. Up to 63.
+ * @param normal_params_size Size of the normal parameters in words. Up to 63.
* @param translate_params_size Size of the translate parameters in words. Up to 63.
* @return The created IPC header.
*
@@ -51,24 +72,16 @@ enum DescriptorType : u32 {
* through modifications and checks by the kernel.
* The translate parameters are described by headers generated with the IPC::*Desc functions.
*
- * @note While #normal_params is equivalent to the number of normal parameters,
- * #translate_params_size includes the size occupied by the translate parameters headers.
+ * @note While @p normal_params_size is equivalent to the number of normal parameters,
+ * @p translate_params_size includes the size occupied by the translate parameters headers.
*/
-constexpr u32 MakeHeader(u16 command_id, unsigned int normal_params,
- unsigned int translate_params_size) {
- return (u32(command_id) << 16) | ((u32(normal_params) & 0x3F) << 6) |
- (u32(translate_params_size) & 0x3F);
-}
-
-union Header {
- u32 raw;
- BitField<0, 6, u32> translate_params_size;
- BitField<6, 6, u32> normal_params;
- BitField<16, 16, u32> command_id;
-};
-
-inline Header ParseHeader(u32 header) {
- return {header};
+inline u32 MakeHeader(u16 command_id, unsigned int normal_params_size,
+ unsigned int translate_params_size) {
+ Header header{};
+ header.command_id.Assign(command_id);
+ header.normal_params_size.Assign(normal_params_size);
+ header.translate_params_size.Assign(translate_params_size);
+ return header.raw;
}
constexpr u32 MoveHandleDesc(u32 num_handles = 1) {
@@ -83,7 +96,7 @@ constexpr u32 CallingPidDesc() {
return CallingPid;
}
-constexpr bool isHandleDescriptor(u32 descriptor) {
+constexpr bool IsHandleDescriptor(u32 descriptor) {
return (descriptor & 0xF) == 0x0;
}
@@ -91,18 +104,19 @@ constexpr u32 HandleNumberFromDesc(u32 handle_descriptor) {
return (handle_descriptor >> 26) + 1;
}
-constexpr u32 StaticBufferDesc(u32 size, u8 buffer_id) {
- return StaticBuffer | (size << 14) | ((buffer_id & 0xF) << 10);
-}
-
union StaticBufferDescInfo {
u32 raw;
+ BitField<0, 4, u32> descriptor_type;
BitField<10, 4, u32> buffer_id;
BitField<14, 18, u32> size;
};
-inline StaticBufferDescInfo ParseStaticBufferDesc(const u32 desc) {
- return {desc};
+inline u32 StaticBufferDesc(u32 size, u8 buffer_id) {
+ StaticBufferDescInfo info{};
+ info.descriptor_type.Assign(StaticBuffer);
+ info.buffer_id.Assign(buffer_id);
+ info.size.Assign(size);
+ return info.raw;
}
/**
@@ -122,29 +136,30 @@ inline u32 PXIBufferDesc(u32 size, unsigned buffer_id, bool is_read_only) {
return type | (size << 8) | ((buffer_id & 0xF) << 4);
}
-enum MappedBufferPermissions {
+enum MappedBufferPermissions : u32 {
R = 1,
W = 2,
RW = R | W,
};
-constexpr u32 MappedBufferDesc(u32 size, MappedBufferPermissions perms) {
- return MappedBuffer | (size << 4) | (u32(perms) << 1);
-}
-
union MappedBufferDescInfo {
u32 raw;
- BitField<4, 28, u32> size;
+ BitField<0, 4, u32> flags;
BitField<1, 2, MappedBufferPermissions> perms;
+ BitField<4, 28, u32> size;
};
-inline MappedBufferDescInfo ParseMappedBufferDesc(const u32 desc) {
- return {desc};
+inline u32 MappedBufferDesc(u32 size, MappedBufferPermissions perms) {
+ MappedBufferDescInfo info{};
+ info.flags.Assign(MappedBuffer);
+ info.perms.Assign(perms);
+ info.size.Assign(size);
+ return info.raw;
}
inline DescriptorType GetDescriptorType(u32 descriptor) {
// Note: Those checks must be done in this order
- if (isHandleDescriptor(descriptor))
+ if (IsHandleDescriptor(descriptor))
return (DescriptorType)(descriptor & 0x30);
// handle the fact that the following descriptors can have rights
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
new file mode 100644
index 000000000..323158bb5
--- /dev/null
+++ b/src/core/hle/ipc_helpers.h
@@ -0,0 +1,275 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+#include "core/hle/ipc.h"
+#include "core/hle/kernel/kernel.h"
+
+namespace IPC {
+
+class RequestHelperBase {
+protected:
+ u32* cmdbuf;
+ ptrdiff_t index = 1;
+ Header header;
+
+public:
+ RequestHelperBase(u32* command_buffer, Header command_header)
+ : cmdbuf(command_buffer), header(command_header) {}
+
+ /// Returns the total size of the request in words
+ size_t TotalSize() const {
+ return 1 /* command header */ + header.normal_params_size + header.translate_params_size;
+ }
+
+ void ValidateHeader() {
+ DEBUG_ASSERT_MSG(index == TotalSize(), "Operations do not match the header (cmd 0x%x)",
+ header.raw);
+ }
+};
+
+class RequestBuilder : public RequestHelperBase {
+public:
+ RequestBuilder(u32* command_buffer, Header command_header)
+ : RequestHelperBase(command_buffer, command_header) {
+ cmdbuf[0] = header.raw;
+ }
+ explicit RequestBuilder(u32* command_buffer, u32 command_header)
+ : RequestBuilder(command_buffer, Header{command_header}) {}
+ RequestBuilder(u32* command_buffer, u16 command_id, unsigned normal_params_size,
+ unsigned translate_params_size)
+ : RequestBuilder(command_buffer,
+ MakeHeader(command_id, normal_params_size, translate_params_size)) {}
+
+ // Validate on destruction, as there shouldn't be any case where we don't want it
+ ~RequestBuilder() {
+ ValidateHeader();
+ }
+
+ template <typename T>
+ void Push(T value);
+
+ void Push(u32 value) {
+ cmdbuf[index++] = value;
+ }
+ template <typename First, typename... Other>
+ void Push(const First& first_value, const Other&... other_values) {
+ Push(first_value);
+ Push(other_values...);
+ }
+
+ /**
+ * @brief Copies the content of the given trivially copyable class to the buffer as a normal
+ * param
+ * @note: The input class must be correctly packed/padded to fit hardware layout.
+ */
+ template <typename T>
+ void PushRaw(const T& value) {
+ static_assert(std::is_trivially_copyable<T>(), "Raw types should be trivially copyable");
+ std::memcpy(cmdbuf + index, &value, sizeof(T));
+ index += (sizeof(T) + 3) / 4; // round up to word length
+ }
+
+ // TODO : ensure that translate params are added after all regular params
+ template <typename... H>
+ void PushCopyHandles(H... handles) {
+ Push(CopyHandleDesc(sizeof...(H)));
+ Push(static_cast<Kernel::Handle>(handles)...);
+ }
+
+ template <typename... H>
+ void PushMoveHandles(H... handles) {
+ Push(MoveHandleDesc(sizeof...(H)));
+ Push(static_cast<Kernel::Handle>(handles)...);
+ }
+
+ void PushCurrentPIDHandle() {
+ Push(CallingPidDesc());
+ Push(u32(0));
+ }
+
+ void PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id) {
+ Push(StaticBufferDesc(size, buffer_id));
+ Push(buffer_vaddr);
+ }
+
+ void PushMappedBuffer(VAddr buffer_vaddr, u32 size, MappedBufferPermissions perms) {
+ Push(MappedBufferDesc(size, perms));
+ Push(buffer_vaddr);
+ }
+};
+
+/// Push ///
+
+template <>
+inline void RequestBuilder::Push<u32>(u32 value) {
+ Push(value);
+}
+
+template <>
+inline void RequestBuilder::Push<u64>(u64 value) {
+ Push(static_cast<u32>(value));
+ Push(static_cast<u32>(value >> 32));
+}
+
+template <>
+inline void RequestBuilder::Push<ResultCode>(ResultCode value) {
+ Push(value.raw);
+}
+
+class RequestParser : public RequestHelperBase {
+public:
+ RequestParser(u32* command_buffer, Header command_header)
+ : RequestHelperBase(command_buffer, command_header) {}
+ explicit RequestParser(u32* command_buffer, u32 command_header)
+ : RequestParser(command_buffer, Header{command_header}) {}
+ RequestParser(u32* command_buffer, u16 command_id, unsigned normal_params_size,
+ unsigned translate_params_size)
+ : RequestParser(command_buffer,
+ MakeHeader(command_id, normal_params_size, translate_params_size)) {}
+
+ RequestBuilder MakeBuilder(u32 normal_params_size, u32 translate_params_size,
+ bool validateHeader = true) {
+ if (validateHeader)
+ ValidateHeader();
+ Header builderHeader{
+ MakeHeader(header.command_id, normal_params_size, translate_params_size)};
+ return {cmdbuf, builderHeader};
+ }
+
+ template <typename T>
+ T Pop();
+
+ template <typename T>
+ void Pop(T& value);
+
+ template <typename First, typename... Other>
+ void Pop(First& first_value, Other&... other_values);
+
+ Kernel::Handle PopHandle();
+
+ template <typename... H>
+ void PopHandles(H&... handles);
+
+ /**
+ * @brief Pops the static buffer vaddr
+ * @return The virtual address of the buffer
+ * @param[out] data_size If non-null, the pointed value will be set to the size of the data
+ * @param[out] useStaticBuffersToGetVaddr Indicates if we should read the vaddr from the static
+ * buffers (which is the correct thing to do, but no service presently implement it) instead of
+ * using the same value as the process who sent the request
+ * given by the source process
+ *
+ * Static buffers must be set up before any IPC request using those is sent.
+ * It is the duty of the process (usually services) to allocate and set up the receiving static
+ * buffer information
+ * Please note that the setup uses virtual addresses.
+ */
+ VAddr PopStaticBuffer(size_t* data_size = nullptr, bool useStaticBuffersToGetVaddr = false);
+
+ /**
+ * @brief Pops the mapped buffer vaddr
+ * @return The virtual address of the buffer
+ * @param[out] data_size If non-null, the pointed value will be set to the size of the data
+ * given by the source process
+ * @param[out] buffer_perms If non-null, the pointed value will be set to the permissions of the
+ * buffer
+ */
+ VAddr PopMappedBuffer(size_t* data_size = nullptr,
+ MappedBufferPermissions* buffer_perms = nullptr);
+
+ /**
+ * @brief Reads the next normal parameters as a struct, by copying it
+ * @note: The output class must be correctly packed/padded to fit hardware layout.
+ */
+ template <typename T>
+ void PopRaw(T& value);
+};
+
+/// Pop ///
+
+template <>
+inline u32 RequestParser::Pop<u32>() {
+ return cmdbuf[index++];
+}
+
+template <>
+inline u64 RequestParser::Pop<u64>() {
+ const u64 lsw = Pop<u32>();
+ const u64 msw = Pop<u32>();
+ return msw << 32 | lsw;
+}
+
+template <>
+inline ResultCode RequestParser::Pop<ResultCode>() {
+ return ResultCode{Pop<u32>()};
+}
+
+template <typename T>
+void RequestParser::Pop(T& value) {
+ value = Pop<T>();
+}
+
+template <typename First, typename... Other>
+void RequestParser::Pop(First& first_value, Other&... other_values) {
+ first_value = Pop<First>();
+ Pop(other_values...);
+}
+
+inline Kernel::Handle RequestParser::PopHandle() {
+ const u32 handle_descriptor = Pop<u32>();
+ DEBUG_ASSERT_MSG(IsHandleDescriptor(handle_descriptor),
+ "Tried to pop handle(s) but the descriptor is not a handle descriptor");
+ DEBUG_ASSERT_MSG(HandleNumberFromDesc(handle_descriptor) == 1,
+ "Descriptor indicates that there isn't exactly one handle");
+ return Pop<Kernel::Handle>();
+}
+
+template <typename... H>
+void RequestParser::PopHandles(H&... handles) {
+ const u32 handle_descriptor = Pop<u32>();
+ const int handles_number = sizeof...(H);
+ DEBUG_ASSERT_MSG(IsHandleDescriptor(handle_descriptor),
+ "Tried to pop handle(s) but the descriptor is not a handle descriptor");
+ DEBUG_ASSERT_MSG(handles_number == HandleNumberFromDesc(handle_descriptor),
+ "Number of handles doesn't match the descriptor");
+ Pop(static_cast<Kernel::Handle&>(handles)...);
+}
+
+inline VAddr RequestParser::PopStaticBuffer(size_t* data_size, bool useStaticBuffersToGetVaddr) {
+ const u32 sbuffer_descriptor = Pop<u32>();
+ StaticBufferDescInfo bufferInfo{sbuffer_descriptor};
+ if (data_size != nullptr)
+ *data_size = bufferInfo.size;
+ if (!useStaticBuffersToGetVaddr)
+ return Pop<VAddr>();
+ else {
+ ASSERT_MSG(0, "remove the assert if multiprocess/IPC translation are implemented.");
+ // The buffer has already been copied to the static buffer by the kernel during
+ // translation
+ Pop<VAddr>(); // Pop the calling process buffer address
+ // and get the vaddr from the static buffers
+ return cmdbuf[(0x100 >> 2) + bufferInfo.buffer_id * 2 + 1];
+ }
+}
+
+inline VAddr RequestParser::PopMappedBuffer(size_t* data_size,
+ MappedBufferPermissions* buffer_perms) {
+ const u32 sbuffer_descriptor = Pop<u32>();
+ MappedBufferDescInfo bufferInfo{sbuffer_descriptor};
+ if (data_size != nullptr)
+ *data_size = bufferInfo.size;
+ if (buffer_perms != nullptr)
+ *buffer_perms = bufferInfo.perms;
+ return Pop<VAddr>();
+}
+
+template <typename T>
+void RequestParser::PopRaw(T& value) {
+ static_assert(std::is_trivially_copyable<T>(), "Raw types should be trivially copyable");
+ std::memcpy(&value, cmdbuf + index, sizeof(T));
+ index += (sizeof(T) + 3) / 4; // round up to word length
+}
+
+} // namespace IPC
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index c088b9a19..761fc4781 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -4,6 +4,7 @@
#pragma once
+#include <memory>
#include <string>
#include "common/assert.h"
#include "common/common_types.h"
@@ -44,7 +45,8 @@ public:
/**
* Creates a pair of ServerSession and an associated ClientSession.
- * @param name Optional name of the ports
+ * @param name Optional name of the ports.
+ * @param hle_handler Optional HLE handler for this server session.
* @return The created session tuple
*/
static SessionPair CreateSessionPair(
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index c557a2279..6ab31c70b 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -11,7 +11,6 @@
#include <boost/container/flat_set.hpp>
#include "common/common_types.h"
#include "core/arm/arm_interface.h"
-#include "core/core.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/result.h"
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index 60537f355..a00c75679 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -52,9 +52,14 @@ void Timer::Set(s64 initial, s64 interval) {
initial_delay = initial;
interval_delay = interval;
- u64 initial_microseconds = initial / 1000;
- CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type,
- callback_handle);
+ if (initial == 0) {
+ // Immediately invoke the callback
+ Signal(0);
+ } else {
+ u64 initial_microseconds = initial / 1000;
+ CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type,
+ callback_handle);
+ }
}
void Timer::Cancel() {
@@ -72,6 +77,22 @@ void Timer::WakeupAllWaitingThreads() {
signaled = false;
}
+void Timer::Signal(int cycles_late) {
+ LOG_TRACE(Kernel, "Timer %u fired", GetObjectId());
+
+ signaled = true;
+
+ // Resume all waiting threads
+ WakeupAllWaitingThreads();
+
+ if (interval_delay != 0) {
+ // Reschedule the timer with the interval delay
+ u64 interval_microseconds = interval_delay / 1000;
+ CoreTiming::ScheduleEvent(usToCycles(interval_microseconds) - cycles_late,
+ timer_callback_event_type, callback_handle);
+ }
+}
+
/// The timer callback event, called when a timer is fired
static void TimerCallback(u64 timer_handle, int cycles_late) {
SharedPtr<Timer> timer =
@@ -82,19 +103,7 @@ static void TimerCallback(u64 timer_handle, int cycles_late) {
return;
}
- LOG_TRACE(Kernel, "Timer %08" PRIx64 " fired", timer_handle);
-
- timer->signaled = true;
-
- // Resume all waiting threads
- timer->WakeupAllWaitingThreads();
-
- if (timer->interval_delay != 0) {
- // Reschedule the timer with the interval delay
- u64 interval_microseconds = timer->interval_delay / 1000;
- CoreTiming::ScheduleEvent(usToCycles(interval_microseconds) - cycles_late,
- timer_callback_event_type, timer_handle);
- }
+ timer->Signal(cycles_late);
}
void TimersInit() {
diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h
index c174f5664..b0f818933 100644
--- a/src/core/hle/kernel/timer.h
+++ b/src/core/hle/kernel/timer.h
@@ -54,6 +54,14 @@ public:
void Cancel();
void Clear();
+ /**
+ * Signals the timer, waking up any waiting threads and rescheduling it
+ * for the next interval.
+ * This method should not be called from outside the timer callback handler,
+ * lest multiple callback events get scheduled.
+ */
+ void Signal(int cycles_late);
+
private:
Timer();
~Timer() override;
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 53864a3a7..cfefbbc64 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -20,6 +20,7 @@ enum class ErrorDescription : u32 {
OS_InvalidBufferDescriptor = 48,
MaxConnectionsReached = 52,
WrongAddress = 53,
+ FS_RomFSNotFound = 100,
FS_ArchiveNotMounted = 101,
FS_FileNotFound = 112,
FS_PathNotFound = 113,
@@ -35,10 +36,13 @@ enum class ErrorDescription : u32 {
OutofRangeOrMisalignedAddress =
513, // TODO(purpasmart): Check if this name fits its actual usage
GPU_FirstInitialization = 519,
+ FS_ExeFSSectionNotFound = 567,
+ FS_CommandNotAllowed = 630,
FS_InvalidReadFlag = 700,
FS_InvalidPath = 702,
FS_WriteBeyondEnd = 705,
FS_UnsupportedOpenFlags = 760,
+ FS_IncorrectExeFSReadSize = 761,
FS_UnexpectedFileOrDirectory = 770,
InvalidSection = 1000,
TooLarge = 1001,
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index 615fe31ea..1517d3a2f 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -18,6 +18,8 @@
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/ptm/ptm.h"
#include "core/hle/service/service.h"
+#include "core/hw/aes/ccm.h"
+#include "core/hw/aes/key.h"
namespace Service {
namespace APT {
@@ -470,6 +472,107 @@ void GetStartupArgument(Service::Interface* self) {
cmd_buff[2] = 0;
}
+void Wrap(Service::Interface* self) {
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x46, 4, 4);
+ const u32 output_size = rp.Pop<u32>();
+ const u32 input_size = rp.Pop<u32>();
+ const u32 nonce_offset = rp.Pop<u32>();
+ u32 nonce_size = rp.Pop<u32>();
+ size_t desc_size;
+ IPC::MappedBufferPermissions desc_permission;
+ const VAddr input = rp.PopMappedBuffer(&desc_size, &desc_permission);
+ ASSERT(desc_size == input_size && desc_permission == IPC::MappedBufferPermissions::R);
+ const VAddr output = rp.PopMappedBuffer(&desc_size, &desc_permission);
+ ASSERT(desc_size == output_size && desc_permission == IPC::MappedBufferPermissions::W);
+
+ // Note: real 3DS still returns SUCCESS when the sizes don't match. It seems that it doesn't
+ // check the buffer size and writes data with potential overflow.
+ ASSERT_MSG(output_size == input_size + HW::AES::CCM_MAC_SIZE,
+ "input_size (%d) doesn't match to output_size (%d)", input_size, output_size);
+
+ LOG_DEBUG(Service_APT, "called, output_size=%u, input_size=%u, nonce_offset=%u, nonce_size=%u",
+ output_size, input_size, nonce_offset, nonce_size);
+
+ // Note: This weird nonce size modification is verified against real 3DS
+ nonce_size = std::min<u32>(nonce_size & ~3, HW::AES::CCM_NONCE_SIZE);
+
+ // Reads nonce and concatenates the rest of the input as plaintext
+ HW::AES::CCMNonce nonce{};
+ Memory::ReadBlock(input + nonce_offset, nonce.data(), nonce_size);
+ u32 pdata_size = input_size - nonce_size;
+ std::vector<u8> pdata(pdata_size);
+ Memory::ReadBlock(input, pdata.data(), nonce_offset);
+ Memory::ReadBlock(input + nonce_offset + nonce_size, pdata.data() + nonce_offset,
+ pdata_size - nonce_offset);
+
+ // Encrypts the plaintext using AES-CCM
+ auto cipher = HW::AES::EncryptSignCCM(pdata, nonce, HW::AES::KeySlotID::APTWrap);
+
+ // Puts the nonce to the beginning of the output, with ciphertext followed
+ Memory::WriteBlock(output, nonce.data(), nonce_size);
+ Memory::WriteBlock(output + nonce_size, cipher.data(), cipher.size());
+
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 4);
+ rb.Push(RESULT_SUCCESS);
+
+ // Unmap buffer
+ rb.PushMappedBuffer(input, input_size, IPC::MappedBufferPermissions::R);
+ rb.PushMappedBuffer(output, output_size, IPC::MappedBufferPermissions::W);
+}
+
+void Unwrap(Service::Interface* self) {
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x47, 4, 4);
+ const u32 output_size = rp.Pop<u32>();
+ const u32 input_size = rp.Pop<u32>();
+ const u32 nonce_offset = rp.Pop<u32>();
+ u32 nonce_size = rp.Pop<u32>();
+ size_t desc_size;
+ IPC::MappedBufferPermissions desc_permission;
+ const VAddr input = rp.PopMappedBuffer(&desc_size, &desc_permission);
+ ASSERT(desc_size == input_size && desc_permission == IPC::MappedBufferPermissions::R);
+ const VAddr output = rp.PopMappedBuffer(&desc_size, &desc_permission);
+ ASSERT(desc_size == output_size && desc_permission == IPC::MappedBufferPermissions::W);
+
+ // Note: real 3DS still returns SUCCESS when the sizes don't match. It seems that it doesn't
+ // check the buffer size and writes data with potential overflow.
+ ASSERT_MSG(output_size == input_size - HW::AES::CCM_MAC_SIZE,
+ "input_size (%d) doesn't match to output_size (%d)", input_size, output_size);
+
+ LOG_DEBUG(Service_APT, "called, output_size=%u, input_size=%u, nonce_offset=%u, nonce_size=%u",
+ output_size, input_size, nonce_offset, nonce_size);
+
+ // Note: This weird nonce size modification is verified against real 3DS
+ nonce_size = std::min<u32>(nonce_size & ~3, HW::AES::CCM_NONCE_SIZE);
+
+ // Reads nonce and cipher text
+ HW::AES::CCMNonce nonce{};
+ Memory::ReadBlock(input, nonce.data(), nonce_size);
+ u32 cipher_size = input_size - nonce_size;
+ std::vector<u8> cipher(cipher_size);
+ Memory::ReadBlock(input + nonce_size, cipher.data(), cipher_size);
+
+ // Decrypts the ciphertext using AES-CCM
+ auto pdata = HW::AES::DecryptVerifyCCM(cipher, nonce, HW::AES::KeySlotID::APTWrap);
+
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 4);
+ if (!pdata.empty()) {
+ // Splits the plaintext and put the nonce in between
+ Memory::WriteBlock(output, pdata.data(), nonce_offset);
+ Memory::WriteBlock(output + nonce_offset, nonce.data(), nonce_size);
+ Memory::WriteBlock(output + nonce_offset + nonce_size, pdata.data() + nonce_offset,
+ pdata.size() - nonce_offset);
+ rb.Push(RESULT_SUCCESS);
+ } else {
+ LOG_ERROR(Service_APT, "Failed to decrypt data");
+ rb.Push(ResultCode(static_cast<ErrorDescription>(1), ErrorModule::PS,
+ ErrorSummary::WrongArgument, ErrorLevel::Status));
+ }
+
+ // Unmap buffer
+ rb.PushMappedBuffer(input, input_size, IPC::MappedBufferPermissions::R);
+ rb.PushMappedBuffer(output, output_size, IPC::MappedBufferPermissions::W);
+}
+
void CheckNew3DSApp(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h
index 80325361f..e63b61450 100644
--- a/src/core/hle/service/apt/apt.h
+++ b/src/core/hle/service/apt/apt.h
@@ -137,6 +137,46 @@ void Initialize(Service::Interface* self);
void GetSharedFont(Service::Interface* self);
/**
+ * APT::Wrap service function
+ * Inputs:
+ * 1 : Output buffer size
+ * 2 : Input buffer size
+ * 3 : Nonce offset to the input buffer
+ * 4 : Nonce size
+ * 5 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xA)
+ * 6 : Input buffer address
+ * 7 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xC)
+ * 8 : Output buffer address
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xA)
+ * 3 : Input buffer address
+ * 4 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xC)
+ * 5 : Output buffer address
+ */
+void Wrap(Service::Interface* self);
+
+/**
+ * APT::Unwrap service function
+ * Inputs:
+ * 1 : Output buffer size
+ * 2 : Input buffer size
+ * 3 : Nonce offset to the output buffer
+ * 4 : Nonce size
+ * 5 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xA)
+ * 6 : Input buffer address
+ * 7 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xC)
+ * 8 : Output buffer address
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xA)
+ * 3 : Input buffer address
+ * 4 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xC)
+ * 5 : Output buffer address
+ */
+void Unwrap(Service::Interface* self);
+
+/**
* APT::NotifyToWait service function
* Inputs:
* 1 : AppID
diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp
index 62dc2d61d..c496cba8d 100644
--- a/src/core/hle/service/apt/apt_a.cpp
+++ b/src/core/hle/service/apt/apt_a.cpp
@@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00430040, NotifyToWait, "NotifyToWait"},
{0x00440000, GetSharedFont, "GetSharedFont"},
{0x00450040, nullptr, "GetWirelessRebootInfo"},
- {0x00460104, nullptr, "Wrap"},
- {0x00470104, nullptr, "Unwrap"},
+ {0x00460104, Wrap, "Wrap"},
+ {0x00470104, Unwrap, "Unwrap"},
{0x00480100, nullptr, "GetProgramInfo"},
{0x00490180, nullptr, "Reboot"},
{0x004A0040, nullptr, "GetCaptureInfo"},
diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp
index effd23dce..ec5668d05 100644
--- a/src/core/hle/service/apt/apt_s.cpp
+++ b/src/core/hle/service/apt/apt_s.cpp
@@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00430040, NotifyToWait, "NotifyToWait"},
{0x00440000, GetSharedFont, "GetSharedFont"},
{0x00450040, nullptr, "GetWirelessRebootInfo"},
- {0x00460104, nullptr, "Wrap"},
- {0x00470104, nullptr, "Unwrap"},
+ {0x00460104, Wrap, "Wrap"},
+ {0x00470104, Unwrap, "Unwrap"},
{0x00480100, nullptr, "GetProgramInfo"},
{0x00490180, nullptr, "Reboot"},
{0x004A0040, nullptr, "GetCaptureInfo"},
diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp
index e06084a1e..9dd002590 100644
--- a/src/core/hle/service/apt/apt_u.cpp
+++ b/src/core/hle/service/apt/apt_u.cpp
@@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00430040, NotifyToWait, "NotifyToWait"},
{0x00440000, GetSharedFont, "GetSharedFont"},
{0x00450040, nullptr, "GetWirelessRebootInfo"},
- {0x00460104, nullptr, "Wrap"},
- {0x00470104, nullptr, "Unwrap"},
+ {0x00460104, Wrap, "Wrap"},
+ {0x00470104, Unwrap, "Unwrap"},
{0x00480100, nullptr, "GetProgramInfo"},
{0x00490180, nullptr, "Reboot"},
{0x004A0040, nullptr, "GetCaptureInfo"},
diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp
index 5594aedab..95665e754 100644
--- a/src/core/hle/service/cam/cam.cpp
+++ b/src/core/hle/service/cam/cam.cpp
@@ -2,7 +2,15 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
+#include <array>
+#include <future>
+#include <memory>
+#include <vector>
+#include "common/bit_set.h"
#include "common/logging/log.h"
+#include "core/core_timing.h"
+#include "core/frontend/camera/factory.h"
#include "core/hle/kernel/event.h"
#include "core/hle/service/cam/cam.h"
#include "core/hle/service/cam/cam_c.h"
@@ -10,206 +18,924 @@
#include "core/hle/service/cam/cam_s.h"
#include "core/hle/service/cam/cam_u.h"
#include "core/hle/service/service.h"
+#include "core/settings.h"
namespace Service {
namespace CAM {
-static const u32 TRANSFER_BYTES = 5 * 1024;
+namespace {
+
+struct ContextConfig {
+ Flip flip;
+ Effect effect;
+ OutputFormat format;
+ Resolution resolution;
+};
+
+struct CameraConfig {
+ std::unique_ptr<Camera::CameraInterface> impl;
+ std::array<ContextConfig, 2> contexts;
+ int current_context;
+ FrameRate frame_rate;
+};
+
+struct PortConfig {
+ int camera_id;
+
+ bool is_active; // set when the port is activated by an Activate call.
+ bool is_pending_receiving; // set if SetReceiving is called when is_busy = false. When
+ // StartCapture is called then, this will trigger a receiving
+ // process and reset itself.
+ bool is_busy; // set when StartCapture is called and reset when StopCapture is called.
+ bool is_receiving; // set when there is an ongoing receiving process.
+
+ bool is_trimming;
+ u16 x0; // x-coordinate of starting position for trimming
+ u16 y0; // y-coordinate of starting position for trimming
+ u16 x1; // x-coordinate of ending position for trimming
+ u16 y1; // y-coordinate of ending position for trimming
+
+ u32 transfer_bytes;
+
+ Kernel::SharedPtr<Kernel::Event> completion_event;
+ Kernel::SharedPtr<Kernel::Event> buffer_error_interrupt_event;
+ Kernel::SharedPtr<Kernel::Event> vsync_interrupt_event;
+
+ std::future<std::vector<u16>> capture_result; // will hold the received frame.
+ VAddr dest; // the destination address of a receiving process
+ u32 dest_size; // the destination size of a receiving process
+
+ void Clear() {
+ completion_event->Clear();
+ buffer_error_interrupt_event->Clear();
+ vsync_interrupt_event->Clear();
+ is_receiving = false;
+ is_active = false;
+ is_pending_receiving = false;
+ is_busy = false;
+ is_trimming = false;
+ x0 = 0;
+ y0 = 0;
+ x1 = 0;
+ y1 = 0;
+ transfer_bytes = 256;
+ }
+};
+
+// built-in resolution parameters
+constexpr std::array<Resolution, 8> PRESET_RESOLUTION{{
+ {640, 480, 0, 0, 639, 479}, // VGA
+ {320, 240, 0, 0, 639, 479}, // QVGA
+ {160, 120, 0, 0, 639, 479}, // QQVGA
+ {352, 288, 26, 0, 613, 479}, // CIF
+ {176, 144, 26, 0, 613, 479}, // QCIF
+ {256, 192, 0, 0, 639, 479}, // DS_LCD
+ {512, 384, 0, 0, 639, 479}, // DS_LCDx4
+ {400, 240, 0, 48, 639, 431}, // CTR_TOP_LCD
+}};
+
+// latency in ms for each frame rate option
+constexpr std::array<int, 13> LATENCY_BY_FRAME_RATE{{
+ 67, // Rate_15
+ 67, // Rate_15_To_5
+ 67, // Rate_15_To_2
+ 100, // Rate_10
+ 118, // Rate_8_5
+ 200, // Rate_5
+ 50, // Rate_20
+ 50, // Rate_20_To_5
+ 33, // Rate_30
+ 33, // Rate_30_To_5
+ 67, // Rate_15_To_10
+ 50, // Rate_20_To_10
+ 33, // Rate_30_To_10
+}};
+
+std::array<CameraConfig, NumCameras> cameras;
+std::array<PortConfig, 2> ports;
+int completion_event_callback;
+
+const ResultCode ERROR_INVALID_ENUM_VALUE(ErrorDescription::InvalidEnumValue, ErrorModule::CAM,
+ ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+const ResultCode ERROR_OUT_OF_RANGE(ErrorDescription::OutOfRange, ErrorModule::CAM,
+ ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+
+void CompletionEventCallBack(u64 port_id, int) {
+ PortConfig& port = ports[port_id];
+ const CameraConfig& camera = cameras[port.camera_id];
+ const auto buffer = port.capture_result.get();
+
+ if (port.is_trimming) {
+ u32 trim_width;
+ u32 trim_height;
+ const int original_width = camera.contexts[camera.current_context].resolution.width;
+ const int original_height = camera.contexts[camera.current_context].resolution.height;
+ if (port.x1 <= port.x0 || port.y1 <= port.y0 || port.x1 > original_width ||
+ port.y1 > original_height) {
+ LOG_ERROR(Service_CAM, "Invalid trimming coordinates x0=%u, y0=%u, x1=%u, y1=%u",
+ port.x0, port.y0, port.x1, port.y1);
+ trim_width = 0;
+ trim_height = 0;
+ } else {
+ trim_width = port.x1 - port.x0;
+ trim_height = port.y1 - port.y0;
+ }
+
+ u32 trim_size = (port.x1 - port.x0) * (port.y1 - port.y0) * 2;
+ if (port.dest_size != trim_size) {
+ LOG_ERROR(Service_CAM, "The destination size (%u) doesn't match the source (%u)!",
+ port.dest_size, trim_size);
+ }
+
+ const u32 src_offset = port.y0 * original_width + port.x0;
+ const u16* src_ptr = buffer.data() + src_offset;
+ // Note: src_size_left is int because it can be negative if the buffer size doesn't match.
+ int src_size_left = static_cast<int>((buffer.size() - src_offset) * sizeof(u16));
+ VAddr dest_ptr = port.dest;
+ // Note: dest_size_left and line_bytes are int to match the type of src_size_left.
+ int dest_size_left = static_cast<int>(port.dest_size);
+ const int line_bytes = static_cast<int>(trim_width * sizeof(u16));
+
+ for (u32 y = 0; y < trim_height; ++y) {
+ int copy_length = std::min({line_bytes, dest_size_left, src_size_left});
+ if (copy_length <= 0) {
+ break;
+ }
+ Memory::WriteBlock(dest_ptr, src_ptr, copy_length);
+ dest_ptr += copy_length;
+ dest_size_left -= copy_length;
+ src_ptr += original_width;
+ src_size_left -= original_width * sizeof(u16);
+ }
+ } else {
+ std::size_t buffer_size = buffer.size() * sizeof(u16);
+ if (port.dest_size != buffer_size) {
+ LOG_ERROR(Service_CAM, "The destination size (%u) doesn't match the source (%zu)!",
+ port.dest_size, buffer_size);
+ }
+ Memory::WriteBlock(port.dest, buffer.data(), std::min<u32>(port.dest_size, buffer_size));
+ }
+
+ port.is_receiving = false;
+ port.completion_event->Signal();
+}
+
+// Starts a receiving process on the specified port. This can only be called when is_busy = true and
+// is_receiving = false.
+void StartReceiving(int port_id) {
+ PortConfig& port = ports[port_id];
+ port.is_receiving = true;
+
+ // launches a capture task asynchronously
+ const CameraConfig& camera = cameras[port.camera_id];
+ port.capture_result =
+ std::async(std::launch::async, &Camera::CameraInterface::ReceiveFrame, camera.impl.get());
+
+ // schedules a completion event according to the frame rate. The event will block on the
+ // capture task if it is not finished within the expected time
+ CoreTiming::ScheduleEvent(
+ msToCycles(LATENCY_BY_FRAME_RATE[static_cast<int>(camera.frame_rate)]),
+ completion_event_callback, port_id);
+}
+
+// Cancels any ongoing receiving processes at the specified port. This is used by functions that
+// stop capturing.
+// TODO: what is the exact behaviour on real 3DS when stopping capture during an ongoing process?
+// Will the completion event still be signaled?
+void CancelReceiving(int port_id) {
+ if (!ports[port_id].is_receiving)
+ return;
+ LOG_WARNING(Service_CAM, "tries to cancel an ongoing receiving process.");
+ CoreTiming::UnscheduleEvent(completion_event_callback, port_id);
+ ports[port_id].capture_result.wait();
+ ports[port_id].is_receiving = false;
+}
+
+// Activates the specified port with the specfied camera.
+static void ActivatePort(int port_id, int camera_id) {
+ if (ports[port_id].is_busy && ports[port_id].camera_id != camera_id) {
+ CancelReceiving(port_id);
+ cameras[ports[port_id].camera_id].impl->StopCapture();
+ ports[port_id].is_busy = false;
+ }
+ ports[port_id].is_active = true;
+ ports[port_id].camera_id = camera_id;
+}
+
+template <int max_index>
+class CommandParamBitSet : public BitSet8 {
+public:
+ explicit CommandParamBitSet(u32 command_param)
+ : BitSet8(static_cast<u8>(command_param & 0xFF)) {}
-static Kernel::SharedPtr<Kernel::Event> completion_event_cam1;
-static Kernel::SharedPtr<Kernel::Event> completion_event_cam2;
-static Kernel::SharedPtr<Kernel::Event> interrupt_error_event;
-static Kernel::SharedPtr<Kernel::Event> vsync_interrupt_error_event;
+ bool IsValid() const {
+ return m_val < (1 << max_index);
+ }
+
+ bool IsSingle() const {
+ return IsValid() && Count() == 1;
+ }
+};
+
+using PortSet = CommandParamBitSet<2>;
+using ContextSet = CommandParamBitSet<2>;
+using CameraSet = CommandParamBitSet<3>;
+
+} // namespace
void StartCapture(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ if (!ports[i].is_busy) {
+ if (!ports[i].is_active) {
+ // This doesn't return an error, but seems to put the camera in an undefined
+ // state
+ LOG_ERROR(Service_CAM, "port %u hasn't been activated", i);
+ } else {
+ cameras[ports[i].camera_id].impl->StartCapture();
+ ports[i].is_busy = true;
+ if (ports[i].is_pending_receiving) {
+ ports[i].is_pending_receiving = false;
+ StartReceiving(i);
+ }
+ }
+ } else {
+ LOG_WARNING(Service_CAM, "port %u already started", i);
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port);
+ LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
}
void StopCapture(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ if (ports[i].is_busy) {
+ CancelReceiving(i);
+ cameras[ports[i].camera_id].impl->StopCapture();
+ ports[i].is_busy = false;
+ } else {
+ LOG_WARNING(Service_CAM, "port %u already stopped", i);
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x2, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
+}
+
+void IsBusy(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsValid()) {
+ bool is_busy = true;
+ // Note: the behaviour on no or both ports selected are verified against real 3DS.
+ for (int i : port_select) {
+ is_busy &= ports[i].is_busy;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = is_busy ? 1 : 0;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x3, 2, 0);
+
+ LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
+}
+
+void ClearBuffer(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const PortSet port_select(cmd_buff[1]);
+
+ cmd_buff[0] = IPC::MakeHeader(0x4, 1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port);
+ LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val);
}
void GetVsyncInterruptEvent(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsSingle()) {
+ int port = *port_select.begin();
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = IPC::CopyHandleDesc();
+ cmd_buff[3] = Kernel::g_handle_table.Create(ports[port].vsync_interrupt_event).MoveFrom();
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ cmd_buff[2] = IPC::CopyHandleDesc();
+ cmd_buff[2] = 0;
+ }
cmd_buff[0] = IPC::MakeHeader(0x5, 1, 2);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[2] = IPC::CopyHandleDesc();
- cmd_buff[3] = Kernel::g_handle_table.Create(vsync_interrupt_error_event).MoveFrom();
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port);
+ LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val);
}
void GetBufferErrorInterruptEvent(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
-
- cmd_buff[0] = IPC::MakeHeader(0x6, 1, 2);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[2] = IPC::CopyHandleDesc();
- cmd_buff[3] = Kernel::g_handle_table.Create(interrupt_error_event).MoveFrom();
-
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port);
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsSingle()) {
+ int port = *port_select.begin();
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = IPC::CopyHandleDesc();
+ cmd_buff[3] =
+ Kernel::g_handle_table.Create(ports[port].buffer_error_interrupt_event).MoveFrom();
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ cmd_buff[2] = IPC::CopyHandleDesc();
+ cmd_buff[2] = 0;
+ }
+
+ LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val);
}
void SetReceiving(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- VAddr dest = cmd_buff[1];
- u8 port = cmd_buff[2] & 0xFF;
- u32 image_size = cmd_buff[3];
- u16 trans_unit = cmd_buff[4] & 0xFFFF;
+ const VAddr dest = cmd_buff[1];
+ const PortSet port_select(cmd_buff[2]);
+ const u32 image_size = cmd_buff[3];
+ const u32 trans_unit = cmd_buff[4] & 0xFFFF;
+
+ if (port_select.IsSingle()) {
+ int port_id = *port_select.begin();
+ PortConfig& port = ports[port_id];
+ CancelReceiving(port_id);
+ port.completion_event->Clear();
+ port.dest = dest;
+ port.dest_size = image_size;
+
+ if (port.is_busy) {
+ StartReceiving(port_id);
+ } else {
+ port.is_pending_receiving = true;
+ }
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = IPC::CopyHandleDesc();
+ cmd_buff[3] = Kernel::g_handle_table.Create(port.completion_event).MoveFrom();
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x7, 1, 2);
- Kernel::Event* completion_event =
- (Port)port == Port::Cam2 ? completion_event_cam2.get() : completion_event_cam1.get();
+ LOG_DEBUG(Service_CAM, "called, addr=0x%X, port_select=%u, image_size=%u, trans_unit=%u", dest,
+ port_select.m_val, image_size, trans_unit);
+}
- completion_event->Signal();
+void IsFinishedReceiving(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
- cmd_buff[0] = IPC::MakeHeader(0x7, 1, 2);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[2] = IPC::CopyHandleDesc();
- cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom();
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsSingle()) {
+ int port = *port_select.begin();
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = (ports[port].is_receiving || ports[port].is_pending_receiving) ? 0 : 1;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
- LOG_WARNING(Service_CAM, "(STUBBED) called, addr=0x%X, port=%d, image_size=%d, trans_unit=%d",
- dest, port, image_size, trans_unit);
+ cmd_buff[0] = IPC::MakeHeader(0x8, 2, 0);
+
+ LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
}
void SetTransferLines(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
- u16 transfer_lines = cmd_buff[2] & 0xFFFF;
- u16 width = cmd_buff[3] & 0xFFFF;
- u16 height = cmd_buff[4] & 0xFFFF;
+ const PortSet port_select(cmd_buff[1]);
+ const u32 transfer_lines = cmd_buff[2] & 0xFFFF;
+ const u32 width = cmd_buff[3] & 0xFFFF;
+ const u32 height = cmd_buff[4] & 0xFFFF;
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ ports[i].transfer_bytes = transfer_lines * width * 2;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x9, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d, lines=%d, width=%d, height=%d", port,
- transfer_lines, width, height);
+ LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u, lines=%u, width=%u, height=%u",
+ port_select.m_val, transfer_lines, width, height);
}
void GetMaxLines(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u16 width = cmd_buff[1] & 0xFFFF;
- u16 height = cmd_buff[2] & 0xFFFF;
+ const u32 width = cmd_buff[1] & 0xFFFF;
+ const u32 height = cmd_buff[2] & 0xFFFF;
+
+ // Note: the result of the algorithm below are hwtested with width < 640 and with height < 480
+ constexpr u32 MIN_TRANSFER_UNIT = 256;
+ constexpr u32 MAX_BUFFER_SIZE = 2560;
+ if (width * height * 2 % MIN_TRANSFER_UNIT != 0) {
+ cmd_buff[1] = ERROR_OUT_OF_RANGE.raw;
+ } else {
+ u32 lines = MAX_BUFFER_SIZE / width;
+ if (lines > height) {
+ lines = height;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ while (height % lines != 0 || (lines * width * 2 % MIN_TRANSFER_UNIT != 0)) {
+ --lines;
+ if (lines == 0) {
+ cmd_buff[1] = ERROR_OUT_OF_RANGE.raw;
+ break;
+ }
+ }
+ cmd_buff[2] = lines;
+ }
cmd_buff[0] = IPC::MakeHeader(0xA, 2, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[2] = TRANSFER_BYTES / (2 * width);
- LOG_WARNING(Service_CAM, "(STUBBED) called, width=%d, height=%d, lines = %d", width, height,
- cmd_buff[2]);
+ LOG_DEBUG(Service_CAM, "called, width=%u, height=%u", width, height);
+}
+
+void SetTransferBytes(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const PortSet port_select(cmd_buff[1]);
+ const u32 transfer_bytes = cmd_buff[2] & 0xFFFF;
+ const u32 width = cmd_buff[3] & 0xFFFF;
+ const u32 height = cmd_buff[4] & 0xFFFF;
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ ports[i].transfer_bytes = transfer_bytes;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0xB, 1, 0);
+
+ LOG_WARNING(Service_CAM, "(STUBBED)called, port_select=%u, bytes=%u, width=%u, height=%u",
+ port_select.m_val, transfer_bytes, width, height);
}
void GetTransferBytes(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsSingle()) {
+ int port = *port_select.begin();
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = ports[port].transfer_bytes;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0xC, 2, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[2] = TRANSFER_BYTES;
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port);
+ LOG_WARNING(Service_CAM, "(STUBBED)called, port_select=%u", port_select.m_val);
+}
+
+void GetMaxBytes(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const u32 width = cmd_buff[1] & 0xFFFF;
+ const u32 height = cmd_buff[2] & 0xFFFF;
+
+ // Note: the result of the algorithm below are hwtested with width < 640 and with height < 480
+ constexpr u32 MIN_TRANSFER_UNIT = 256;
+ constexpr u32 MAX_BUFFER_SIZE = 2560;
+ if (width * height * 2 % MIN_TRANSFER_UNIT != 0) {
+ cmd_buff[1] = ERROR_OUT_OF_RANGE.raw;
+ } else {
+ u32 bytes = MAX_BUFFER_SIZE;
+
+ while (width * height * 2 % bytes != 0) {
+ bytes -= MIN_TRANSFER_UNIT;
+ }
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = bytes;
+ }
+ cmd_buff[0] = IPC::MakeHeader(0xD, 2, 0);
+
+ LOG_DEBUG(Service_CAM, "called, width=%u, height=%u", width, height);
}
void SetTrimming(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
- bool trim = (cmd_buff[2] & 0xFF) != 0;
+ const PortSet port_select(cmd_buff[1]);
+ const bool trim = (cmd_buff[2] & 0xFF) != 0;
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ ports[i].is_trimming = trim;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0xE, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d, trim=%d", port, trim);
+ LOG_DEBUG(Service_CAM, "called, port_select=%u, trim=%d", port_select.m_val, trim);
+}
+
+void IsTrimming(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsSingle()) {
+ int port = *port_select.begin();
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = ports[port].is_trimming;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0);
+
+ LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
+}
+
+void SetTrimmingParams(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const PortSet port_select(cmd_buff[1]);
+ const u16 x0 = static_cast<u16>(cmd_buff[2] & 0xFFFF);
+ const u16 y0 = static_cast<u16>(cmd_buff[3] & 0xFFFF);
+ const u16 x1 = static_cast<u16>(cmd_buff[4] & 0xFFFF);
+ const u16 y1 = static_cast<u16>(cmd_buff[5] & 0xFFFF);
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ ports[i].x0 = x0;
+ ports[i].y0 = y0;
+ ports[i].x1 = x1;
+ ports[i].y1 = y1;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x10, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called, port_select=%u, x0=%u, y0=%u, x1=%u, y1=%u", port_select.m_val,
+ x0, y0, x1, y1);
+}
+
+void GetTrimmingParams(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsSingle()) {
+ int port = *port_select.begin();
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = ports[port].x0;
+ cmd_buff[3] = ports[port].y0;
+ cmd_buff[4] = ports[port].x1;
+ cmd_buff[5] = ports[port].y1;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x11, 5, 0);
+
+ LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
}
void SetTrimmingParamsCenter(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
- s16 trimW = cmd_buff[2] & 0xFFFF;
- s16 trimH = cmd_buff[3] & 0xFFFF;
- s16 camW = cmd_buff[4] & 0xFFFF;
- s16 camH = cmd_buff[5] & 0xFFFF;
+ const PortSet port_select(cmd_buff[1]);
+ const u16 trim_w = static_cast<u16>(cmd_buff[2] & 0xFFFF);
+ const u16 trim_h = static_cast<u16>(cmd_buff[3] & 0xFFFF);
+ const u16 cam_w = static_cast<u16>(cmd_buff[4] & 0xFFFF);
+ const u16 cam_h = static_cast<u16>(cmd_buff[5] & 0xFFFF);
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ ports[i].x0 = (cam_w - trim_w) / 2;
+ ports[i].y0 = (cam_h - trim_h) / 2;
+ ports[i].x1 = ports[i].x0 + trim_w;
+ ports[i].y1 = ports[i].y0 + trim_h;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x12, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d, trimW=%d, trimH=%d, camW=%d, camH=%d",
- port, trimW, trimH, camW, camH);
+ LOG_DEBUG(Service_CAM, "called, port_select=%u, trim_w=%u, trim_h=%u, cam_w=%u, cam_h=%u",
+ port_select.m_val, trim_w, trim_h, cam_w, cam_h);
}
void Activate(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 cam_select = cmd_buff[1] & 0xFF;
+ const CameraSet camera_select(cmd_buff[1]);
+
+ if (camera_select.IsValid()) {
+ if (camera_select.m_val == 0) { // deactive all
+ for (int i = 0; i < 2; ++i) {
+ if (ports[i].is_busy) {
+ CancelReceiving(i);
+ cameras[ports[i].camera_id].impl->StopCapture();
+ ports[i].is_busy = false;
+ }
+ ports[i].is_active = false;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else if (camera_select[0] && camera_select[1]) {
+ LOG_ERROR(Service_CAM, "camera 0 and 1 can't be both activated");
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ } else {
+ if (camera_select[0]) {
+ ActivatePort(0, 0);
+ } else if (camera_select[1]) {
+ ActivatePort(0, 1);
+ }
+
+ if (camera_select[2]) {
+ ActivatePort(1, 2);
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ }
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u", camera_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x13, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d", cam_select);
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u", camera_select.m_val);
+}
+
+void SwitchContext(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const CameraSet camera_select(cmd_buff[1]);
+ const ContextSet context_select(cmd_buff[2]);
+
+ if (camera_select.IsValid() && context_select.IsSingle()) {
+ int context = *context_select.begin();
+ for (int camera : camera_select) {
+ cameras[camera].current_context = context;
+ const ContextConfig& context_config = cameras[camera].contexts[context];
+ cameras[camera].impl->SetFlip(context_config.flip);
+ cameras[camera].impl->SetEffect(context_config.effect);
+ cameras[camera].impl->SetFormat(context_config.format);
+ cameras[camera].impl->SetResolution(context_config.resolution);
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x14, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
}
void FlipImage(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 cam_select = cmd_buff[1] & 0xFF;
- u8 flip = cmd_buff[2] & 0xFF;
- u8 context = cmd_buff[3] & 0xFF;
+ const CameraSet camera_select(cmd_buff[1]);
+ const Flip flip = static_cast<Flip>(cmd_buff[2] & 0xFF);
+ const ContextSet context_select(cmd_buff[3]);
+
+ if (camera_select.IsValid() && context_select.IsValid()) {
+ for (int camera : camera_select) {
+ for (int context : context_select) {
+ cameras[camera].contexts[context].flip = flip;
+ if (cameras[camera].current_context == context) {
+ cameras[camera].impl->SetFlip(flip);
+ }
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x1D, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d, flip=%d, context=%d", cam_select,
- flip, context);
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u, flip=%d, context_select=%u",
+ camera_select.m_val, static_cast<int>(flip), context_select.m_val);
+}
+
+void SetDetailSize(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const CameraSet camera_select(cmd_buff[1]);
+ Resolution resolution;
+ resolution.width = static_cast<u16>(cmd_buff[2] & 0xFFFF);
+ resolution.height = static_cast<u16>(cmd_buff[3] & 0xFFFF);
+ resolution.crop_x0 = static_cast<u16>(cmd_buff[4] & 0xFFFF);
+ resolution.crop_y0 = static_cast<u16>(cmd_buff[5] & 0xFFFF);
+ resolution.crop_x1 = static_cast<u16>(cmd_buff[6] & 0xFFFF);
+ resolution.crop_y1 = static_cast<u16>(cmd_buff[7] & 0xFFFF);
+ const ContextSet context_select(cmd_buff[8]);
+
+ if (camera_select.IsValid() && context_select.IsValid()) {
+ for (int camera : camera_select) {
+ for (int context : context_select) {
+ cameras[camera].contexts[context].resolution = resolution;
+ if (cameras[camera].current_context == context) {
+ cameras[camera].impl->SetResolution(resolution);
+ }
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x1E, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u, width=%u, height=%u, crop_x0=%u, crop_y0=%u, "
+ "crop_x1=%u, crop_y1=%u, context_select=%u",
+ camera_select.m_val, resolution.width, resolution.height, resolution.crop_x0,
+ resolution.crop_y0, resolution.crop_x1, resolution.crop_y1, context_select.m_val);
}
void SetSize(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 cam_select = cmd_buff[1] & 0xFF;
- u8 size = cmd_buff[2] & 0xFF;
- u8 context = cmd_buff[3] & 0xFF;
+ const CameraSet camera_select(cmd_buff[1]);
+ const u32 size = cmd_buff[2] & 0xFF;
+ const ContextSet context_select(cmd_buff[3]);
+
+ if (camera_select.IsValid() && context_select.IsValid()) {
+ for (int camera : camera_select) {
+ for (int context : context_select) {
+ cameras[camera].contexts[context].resolution = PRESET_RESOLUTION[size];
+ if (cameras[camera].current_context == context) {
+ cameras[camera].impl->SetResolution(PRESET_RESOLUTION[size]);
+ }
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x1F, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d, size=%d, context=%d", cam_select,
- size, context);
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u, size=%u, context_select=%u",
+ camera_select.m_val, size, context_select.m_val);
}
void SetFrameRate(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 cam_select = cmd_buff[1] & 0xFF;
- u8 frame_rate = cmd_buff[2] & 0xFF;
+ const CameraSet camera_select(cmd_buff[1]);
+ const FrameRate frame_rate = static_cast<FrameRate>(cmd_buff[2] & 0xFF);
+
+ if (camera_select.IsValid()) {
+ for (int camera : camera_select) {
+ cameras[camera].frame_rate = frame_rate;
+ // TODO(wwylele): consider hinting the actual camera with the expected frame rate
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u", camera_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x20, 1, 0);
+
+ LOG_WARNING(Service_CAM, "(STUBBED) called, camera_select=%u, frame_rate=%d",
+ camera_select.m_val, static_cast<int>(frame_rate));
+}
+
+void SetEffect(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const CameraSet camera_select(cmd_buff[1]);
+ const Effect effect = static_cast<Effect>(cmd_buff[2] & 0xFF);
+ const ContextSet context_select(cmd_buff[3]);
+
+ if (camera_select.IsValid() && context_select.IsValid()) {
+ for (int camera : camera_select) {
+ for (int context : context_select) {
+ cameras[camera].contexts[context].effect = effect;
+ if (cameras[camera].current_context == context) {
+ cameras[camera].impl->SetEffect(effect);
+ }
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x22, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u, effect=%d, context_select=%u",
+ camera_select.m_val, static_cast<int>(effect), context_select.m_val);
+}
+
+void SetOutputFormat(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const CameraSet camera_select(cmd_buff[1]);
+ const OutputFormat format = static_cast<OutputFormat>(cmd_buff[2] & 0xFF);
+ const ContextSet context_select(cmd_buff[3]);
+
+ if (camera_select.IsValid() && context_select.IsValid()) {
+ for (int camera : camera_select) {
+ for (int context : context_select) {
+ cameras[camera].contexts[context].format = format;
+ if (cameras[camera].current_context == context) {
+ cameras[camera].impl->SetFormat(format);
+ }
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x25, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u, format=%d, context_select=%u",
+ camera_select.m_val, static_cast<int>(format), context_select.m_val);
+}
+
+void SynchronizeVsyncTiming(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const u32 camera_select1 = cmd_buff[1] & 0xFF;
+ const u32 camera_select2 = cmd_buff[2] & 0xFF;
+
+ cmd_buff[0] = IPC::MakeHeader(0x29, 1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d, frame_rate=%d", cam_select,
- frame_rate);
+ LOG_WARNING(Service_CAM, "(STUBBED) called, camera_select1=%u, camera_select2=%u",
+ camera_select1, camera_select2);
}
void GetStereoCameraCalibrationData(Service::Interface* self) {
@@ -239,6 +965,67 @@ void GetStereoCameraCalibrationData(Service::Interface* self) {
LOG_TRACE(Service_CAM, "called");
}
+void SetPackageParameterWithoutContext(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ PackageParameterWithoutContext package;
+ std::memcpy(&package, cmd_buff + 1, sizeof(package));
+
+ cmd_buff[0] = IPC::MakeHeader(0x33, 1, 0);
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+
+ LOG_WARNING(Service_CAM, "(STUBBED) called");
+}
+
+template <typename PackageParameterType, int command_id>
+static void SetPackageParameter() {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ PackageParameterType package;
+ std::memcpy(&package, cmd_buff + 1, sizeof(package));
+
+ const CameraSet camera_select(static_cast<u32>(package.camera_select));
+ const ContextSet context_select(static_cast<u32>(package.context_select));
+
+ if (camera_select.IsValid() && context_select.IsValid()) {
+ for (int camera_id : camera_select) {
+ CameraConfig& camera = cameras[camera_id];
+ for (int context_id : context_select) {
+ ContextConfig& context = camera.contexts[context_id];
+ context.effect = package.effect;
+ context.flip = package.flip;
+ context.resolution = package.GetResolution();
+ if (context_id == camera.current_context) {
+ camera.impl->SetEffect(context.effect);
+ camera.impl->SetFlip(context.flip);
+ camera.impl->SetResolution(context.resolution);
+ }
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", package.camera_select,
+ package.context_select);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(command_id, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called");
+}
+
+Resolution PackageParameterWithContext::GetResolution() {
+ return PRESET_RESOLUTION[static_cast<int>(size)];
+}
+
+void SetPackageParameterWithContext(Service::Interface* self) {
+ SetPackageParameter<PackageParameterWithContext, 0x34>();
+}
+
+void SetPackageParameterWithContextDetail(Service::Interface* self) {
+ SetPackageParameter<PackageParameterWithContextDetail, 0x35>();
+}
+
void GetSuitableY2rStandardCoefficient(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
@@ -263,24 +1050,50 @@ void PlayShutterSound(Service::Interface* self) {
void DriverInitialize(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- completion_event_cam1->Clear();
- completion_event_cam2->Clear();
- interrupt_error_event->Clear();
- vsync_interrupt_error_event->Clear();
+ for (int camera_id = 0; camera_id < NumCameras; ++camera_id) {
+ CameraConfig& camera = cameras[camera_id];
+ camera.current_context = 0;
+ for (int context_id = 0; context_id < 2; ++context_id) {
+ // Note: the following default values are verified against real 3DS
+ ContextConfig& context = camera.contexts[context_id];
+ context.flip = camera_id == 1 ? Flip::Horizontal : Flip::None;
+ context.effect = Effect::None;
+ context.format = OutputFormat::YUV422;
+ context.resolution =
+ context_id == 0 ? PRESET_RESOLUTION[5 /*DS_LCD*/] : PRESET_RESOLUTION[0 /*VGA*/];
+ }
+ camera.impl = Camera::CreateCamera(Settings::values.camera_name[camera_id],
+ Settings::values.camera_config[camera_id]);
+ camera.impl->SetFlip(camera.contexts[0].flip);
+ camera.impl->SetEffect(camera.contexts[0].effect);
+ camera.impl->SetFormat(camera.contexts[0].format);
+ camera.impl->SetResolution(camera.contexts[0].resolution);
+ }
+
+ for (PortConfig& port : ports) {
+ port.Clear();
+ }
cmd_buff[0] = IPC::MakeHeader(0x39, 1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called");
+ LOG_DEBUG(Service_CAM, "called");
}
void DriverFinalize(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
+ CancelReceiving(0);
+ CancelReceiving(1);
+
+ for (CameraConfig& camera : cameras) {
+ camera.impl = nullptr;
+ }
+
cmd_buff[0] = IPC::MakeHeader(0x3A, 1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called");
+ LOG_DEBUG(Service_CAM, "called");
}
void Init() {
@@ -291,21 +1104,28 @@ void Init() {
AddService(new CAM_S_Interface);
AddService(new CAM_U_Interface);
- completion_event_cam1 =
- Kernel::Event::Create(ResetType::OneShot, "CAM_U::completion_event_cam1");
- completion_event_cam2 =
- Kernel::Event::Create(ResetType::OneShot, "CAM_U::completion_event_cam2");
- interrupt_error_event =
- Kernel::Event::Create(ResetType::OneShot, "CAM_U::interrupt_error_event");
- vsync_interrupt_error_event =
- Kernel::Event::Create(ResetType::OneShot, "CAM_U::vsync_interrupt_error_event");
+ for (PortConfig& port : ports) {
+ port.completion_event = Event::Create(ResetType::Sticky, "CAM_U::completion_event");
+ port.buffer_error_interrupt_event =
+ Event::Create(ResetType::OneShot, "CAM_U::buffer_error_interrupt_event");
+ port.vsync_interrupt_event =
+ Event::Create(ResetType::OneShot, "CAM_U::vsync_interrupt_event");
+ }
+ completion_event_callback =
+ CoreTiming::RegisterEvent("CAM_U::CompletionEventCallBack", CompletionEventCallBack);
}
void Shutdown() {
- completion_event_cam1 = nullptr;
- completion_event_cam2 = nullptr;
- interrupt_error_event = nullptr;
- vsync_interrupt_error_event = nullptr;
+ CancelReceiving(0);
+ CancelReceiving(1);
+ for (PortConfig& port : ports) {
+ port.completion_event = nullptr;
+ port.buffer_error_interrupt_event = nullptr;
+ port.vsync_interrupt_event = nullptr;
+ }
+ for (CameraConfig& camera : cameras) {
+ camera.impl = nullptr;
+ }
}
} // namespace CAM
diff --git a/src/core/hle/service/cam/cam.h b/src/core/hle/service/cam/cam.h
index c9b6f8acf..34a9c8479 100644
--- a/src/core/hle/service/cam/cam.h
+++ b/src/core/hle/service/cam/cam.h
@@ -13,17 +13,12 @@
namespace Service {
namespace CAM {
-enum class Port : u8 { None = 0, Cam1 = 1, Cam2 = 2, Both = Cam1 | Cam2 };
+enum CameraIndex {
+ OuterRightCamera = 0,
+ InnerCamera = 1,
+ OuterLeftCamera = 2,
-enum class CameraSelect : u8 {
- None = 0,
- Out1 = 1,
- In1 = 2,
- Out2 = 4,
- In1Out1 = Out1 | In1,
- Out1Out2 = Out1 | Out2,
- In1Out2 = In1 | Out2,
- All = Out1 | In1 | Out2,
+ NumCameras = 3,
};
enum class Effect : u8 {
@@ -35,13 +30,6 @@ enum class Effect : u8 {
Sepia01 = 5,
};
-enum class Context : u8 {
- None = 0,
- A = 1,
- B = 2,
- Both = A | B,
-};
-
enum class Flip : u8 {
None = 0,
Horizontal = 1,
@@ -160,8 +148,23 @@ struct StereoCameraCalibrationData {
static_assert(sizeof(StereoCameraCalibrationData) == 64,
"StereoCameraCalibrationData structure size is wrong");
-struct PackageParameterCameraSelect {
- CameraSelect camera;
+/**
+ * Resolution parameters for the camera.
+ * The native resolution of 3DS camera is 640 * 480. The captured image will be cropped in the
+ * region [crop_x0, crop_x1] * [crop_y0, crop_y1], and then scaled to size width * height as the
+ * output image. Note that all cropping coordinates are inclusive.
+ */
+struct Resolution {
+ u16 width;
+ u16 height;
+ u16 crop_x0;
+ u16 crop_y0;
+ u16 crop_x1;
+ u16 crop_y1;
+};
+
+struct PackageParameterWithoutContext {
+ u8 camera_select;
s8 exposure;
WhiteBalance white_balance;
s8 sharpness;
@@ -183,14 +186,43 @@ struct PackageParameterCameraSelect {
s16 auto_white_balance_window_height;
};
-static_assert(sizeof(PackageParameterCameraSelect) == 28,
- "PackageParameterCameraSelect structure size is wrong");
+static_assert(sizeof(PackageParameterWithoutContext) == 28,
+ "PackageParameterCameraWithoutContext structure size is wrong");
+
+struct PackageParameterWithContext {
+ u8 camera_select;
+ u8 context_select;
+ Flip flip;
+ Effect effect;
+ Size size;
+ INSERT_PADDING_BYTES(3);
+
+ Resolution GetResolution();
+};
+
+static_assert(sizeof(PackageParameterWithContext) == 8,
+ "PackageParameterWithContext structure size is wrong");
+
+struct PackageParameterWithContextDetail {
+ u8 camera_select;
+ u8 context_select;
+ Flip flip;
+ Effect effect;
+ Resolution resolution;
+
+ Resolution GetResolution() {
+ return resolution;
+ }
+};
+
+static_assert(sizeof(PackageParameterWithContextDetail) == 16,
+ "PackageParameterWithContextDetail structure size is wrong");
/**
- * Unknown
+ * Starts capturing at the selected port.
* Inputs:
* 0: 0x00010040
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* Outputs:
* 0: 0x00010040
* 1: ResultCode
@@ -198,10 +230,10 @@ static_assert(sizeof(PackageParameterCameraSelect) == 28,
void StartCapture(Service::Interface* self);
/**
- * Unknown
+ * Stops capturing from the selected port.
* Inputs:
* 0: 0x00020040
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* Outputs:
* 0: 0x00020040
* 1: ResultCode
@@ -209,10 +241,33 @@ void StartCapture(Service::Interface* self);
void StopCapture(Service::Interface* self);
/**
+ * Gets whether the selected port is currently capturing.
+ * Inputs:
+ * 0: 0x00030040
+ * 1: u8 selected port
+ * Outputs:
+ * 0: 0x00030080
+ * 1: ResultCode
+ * 2: 0 if not capturing, 1 if capturing
+ */
+void IsBusy(Service::Interface* self);
+
+/**
+ * Clears the buffer of selected ports.
+ * Inputs:
+ * 0: 0x00040040
+ * 1: u8 selected port
+ * Outputs:
+ * 0: 0x00040040
+ * 2: ResultCode
+ */
+void ClearBuffer(Service::Interface* self);
+
+/**
* Unknown
* Inputs:
* 0: 0x00050040
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* Outputs:
* 0: 0x00050042
* 1: ResultCode
@@ -225,7 +280,7 @@ void GetVsyncInterruptEvent(Service::Interface* self);
* Unknown
* Inputs:
* 0: 0x00060040
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* Outputs:
* 0: 0x00060042
* 1: ResultCode
@@ -241,9 +296,9 @@ void GetBufferErrorInterruptEvent(Service::Interface* self);
* Inputs:
* 0: 0x00070102
* 1: Destination address in calling process
- * 2: u8 Camera port (`Port` enum)
- * 3: Image size (in bytes?)
- * 4: u16 Transfer unit size (in bytes?)
+ * 2: u8 selected port
+ * 3: Image size (in bytes)
+ * 4: u16 Transfer unit size (in bytes)
* 5: Descriptor: Handle
* 6: Handle to destination process
* Outputs:
@@ -255,21 +310,34 @@ void GetBufferErrorInterruptEvent(Service::Interface* self);
void SetReceiving(Service::Interface* self);
/**
- * Unknown
+ * Gets whether the selected port finished receiving a frame.
+ * Inputs:
+ * 0: 0x00080040
+ * 1: u8 selected port
+ * Outputs:
+ * 0: 0x00080080
+ * 1: ResultCode
+ * 2: 0 if not finished, 1 if finished
+ */
+void IsFinishedReceiving(Service::Interface* self);
+
+/**
+ * Sets the number of lines the buffer contains.
* Inputs:
* 0: 0x00090100
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* 2: u16 Number of lines to transfer
* 3: u16 Width
* 4: u16 Height
* Outputs:
* 0: 0x00090040
* 1: ResultCode
+ * @todo figure out how the "buffer" actually works.
*/
void SetTransferLines(Service::Interface* self);
/**
- * Unknown
+ * Gets the maximum number of lines that fit in the buffer
* Inputs:
* 0: 0x000A0080
* 1: u16 Width
@@ -277,27 +345,58 @@ void SetTransferLines(Service::Interface* self);
* Outputs:
* 0: 0x000A0080
* 1: ResultCode
- * 2: Maximum number of lines that fit in the buffer(?)
+ * 2: Maximum number of lines that fit in the buffer
+ * @todo figure out how the "buffer" actually works.
*/
void GetMaxLines(Service::Interface* self);
/**
- * Unknown
+ * Sets the number of bytes the buffer contains.
+ * Inputs:
+ * 0: 0x000B0100
+ * 1: u8 selected port
+ * 2: u16 Number of bytes to transfer
+ * 3: u16 Width
+ * 4: u16 Height
+ * Outputs:
+ * 0: 0x000B0040
+ * 1: ResultCode
+ * @todo figure out how the "buffer" actually works.
+ */
+void SetTransferBytes(Service::Interface* self);
+
+/**
+ * Gets the number of bytes to the buffer contains.
* Inputs:
* 0: 0x000C0040
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* Outputs:
* 0: 0x000C0080
* 1: ResultCode
- * 2: Total number of bytes for each frame with current settings(?)
+ * 2: The number of bytes the buffer contains
+ * @todo figure out how the "buffer" actually works.
*/
void GetTransferBytes(Service::Interface* self);
/**
- * Unknown
+ * Gets the maximum number of bytes that fit in the buffer.
+ * Inputs:
+ * 0: 0x000D0080
+ * 1: u16 Width
+ * 2: u16 Height
+ * Outputs:
+ * 0: 0x000D0080
+ * 1: ResultCode
+ * 2: Maximum number of bytes that fit in the buffer
+ * @todo figure out how the "buffer" actually works.
+ */
+void GetMaxBytes(Service::Interface* self);
+
+/**
+ * Enables or disables trimming.
* Inputs:
* 0: 0x000E0080
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* 2: u8 bool Enable trimming if true
* Outputs:
* 0: 0x000E0040
@@ -306,14 +405,58 @@ void GetTransferBytes(Service::Interface* self);
void SetTrimming(Service::Interface* self);
/**
- * Unknown
+ * Gets whether trimming is enabled.
+ * Inputs:
+ * 0: 0x000F0040
+ * 1: u8 selected port
+ * Outputs:
+ * 0: 0x000F0080
+ * 1: ResultCode
+ * 2: u8 bool Enable trimming if true
+ */
+void IsTrimming(Service::Interface* self);
+
+/**
+ * Sets the position to trim.
+ * Inputs:
+ * 0: 0x00100140
+ * 1: u8 selected port
+ * 2: x start
+ * 3: y start
+ * 4: x end (exclusive)
+ * 5: y end (exclusive)
+ * Outputs:
+ * 0: 0x00100040
+ * 1: ResultCode
+ */
+void SetTrimmingParams(Service::Interface* self);
+
+/**
+ * Gets the position to trim.
+ * Inputs:
+ * 0: 0x00110040
+ * 1: u8 selected port
+ *
+ * Outputs:
+ * 0: 0x00110140
+ * 1: ResultCode
+ * 2: x start
+ * 3: y start
+ * 4: x end (exclusive)
+ * 5: y end (exclusive)
+ */
+void GetTrimmingParams(Service::Interface* self);
+
+/**
+ * Sets the position to trim by giving the width and height. The trimming window is always at the
+ * center.
* Inputs:
* 0: 0x00120140
- * 1: u8 Camera port (`Port` enum)
- * 2: s16 Trim width(?)
- * 3: s16 Trim height(?)
- * 4: s16 Camera width(?)
- * 5: s16 Camera height(?)
+ * 1: u8 selected port
+ * 2: s16 Trim width
+ * 3: s16 Trim height
+ * 4: s16 Camera width
+ * 5: s16 Camera height
* Outputs:
* 0: 0x00120040
* 1: ResultCode
@@ -324,7 +467,7 @@ void SetTrimmingParamsCenter(Service::Interface* self);
* Selects up to two physical cameras to enable.
* Inputs:
* 0: 0x00130040
- * 1: u8 Cameras to activate (`CameraSelect` enum)
+ * 1: u8 selected camera
* Outputs:
* 0: 0x00130040
* 1: ResultCode
@@ -332,12 +475,24 @@ void SetTrimmingParamsCenter(Service::Interface* self);
void Activate(Service::Interface* self);
/**
- * Unknown
+ * Switches the context of camera settings.
+ * Inputs:
+ * 0: 0x00140080
+ * 1: u8 selected camera
+ * 2: u8 selected context
+ * Outputs:
+ * 0: 0x00140040
+ * 1: ResultCode
+ */
+void SwitchContext(Service::Interface* self);
+
+/**
+ * Sets flipping of images
* Inputs:
* 0: 0x001D00C0
- * 1: u8 Camera select (`CameraSelect` enum)
+ * 1: u8 selected camera
* 2: u8 Type of flipping to perform (`Flip` enum)
- * 3: u8 Context (`Context` enum)
+ * 3: u8 selected context
* Outputs:
* 0: 0x001D0040
* 1: ResultCode
@@ -345,12 +500,30 @@ void Activate(Service::Interface* self);
void FlipImage(Service::Interface* self);
/**
- * Unknown
+ * Sets camera resolution from custom parameters. For more details see the Resolution struct.
+ * Inputs:
+ * 0: 0x001E0200
+ * 1: u8 selected camera
+ * 2: width
+ * 3: height
+ * 4: crop x0
+ * 5: crop y0
+ * 6: crop x1
+ * 7: crop y1
+ * 8: u8 selected context
+ * Outputs:
+ * 0: 0x001E0040
+ * 1: ResultCode
+ */
+void SetDetailSize(Service::Interface* self);
+
+/**
+ * Sets camera resolution from preset resolution parameters.
* Inputs:
* 0: 0x001F00C0
- * 1: u8 Camera select (`CameraSelect` enum)
+ * 1: u8 selected camera
* 2: u8 Camera frame resolution (`Size` enum)
- * 3: u8 Context id (`Context` enum)
+ * 3: u8 selected context
* Outputs:
* 0: 0x001F0040
* 1: ResultCode
@@ -358,10 +531,10 @@ void FlipImage(Service::Interface* self);
void SetSize(Service::Interface* self);
/**
- * Unknown
+ * Sets camera framerate.
* Inputs:
* 0: 0x00200080
- * 1: u8 Camera select (`CameraSelect` enum)
+ * 1: u8 selected camera
* 2: u8 Camera framerate (`FrameRate` enum)
* Outputs:
* 0: 0x00200040
@@ -370,6 +543,44 @@ void SetSize(Service::Interface* self);
void SetFrameRate(Service::Interface* self);
/**
+ * Sets effect on the output image
+ * Inputs:
+ * 0: 0x002200C0
+ * 1: u8 selected camera
+ * 2: u8 image effect (`Effect` enum)
+ * 3: u8 selected context
+ * Outputs:
+ * 0: 0x00220040
+ * 1: ResultCode
+ */
+void SetEffect(Service::Interface* self);
+
+/**
+ * Sets format of the output image
+ * Inputs:
+ * 0: 0x002500C0
+ * 1: u8 selected camera
+ * 2: u8 image format (`OutputFormat` enum)
+ * 3: u8 selected context
+ * Outputs:
+ * 0: 0x00250040
+ * 1: ResultCode
+ */
+void SetOutputFormat(Service::Interface* self);
+
+/**
+ * Synchronizes the V-Sync timing of two cameras.
+ * Inputs:
+ * 0: 0x00290080
+ * 1: u8 selected camera 1
+ * 2: u8 selected camera 2
+ * Outputs:
+ * 0: 0x00280040
+ * 1: ResultCode
+ */
+void SynchronizeVsyncTiming(Service::Interface* self);
+
+/**
* Returns calibration data relating the outside cameras to eachother, for use in AR applications.
*
* Inputs:
@@ -382,6 +593,45 @@ void SetFrameRate(Service::Interface* self);
void GetStereoCameraCalibrationData(Service::Interface* self);
/**
+ * Batch-configures context-free settings.
+ *
+ * Inputs:
+ * 0: 0x003302C0
+ * 1-7: struct PachageParameterWithoutContext
+ * 8-11: unused
+ * Outputs:
+ * 0: 0x00330040
+ * 1: ResultCode
+ */
+void SetPackageParameterWithoutContext(Service::Interface* self);
+
+/**
+ * Batch-configures context-related settings with preset resolution parameters.
+ *
+ * Inputs:
+ * 0: 0x00340140
+ * 1-2: struct PackageParameterWithContext
+ * 3-5: unused
+ * Outputs:
+ * 0: 0x00340040
+ * 1: ResultCode
+ */
+void SetPackageParameterWithContext(Service::Interface* self);
+
+/**
+ * Batch-configures context-related settings with custom resolution parameters
+ *
+ * Inputs:
+ * 0: 0x003501C0
+ * 1-4: struct PackageParameterWithContextDetail
+ * 5-7: unused
+ * Outputs:
+ * 0: 0x00350040
+ * 1: ResultCode
+ */
+void SetPackageParameterWithContextDetail(Service::Interface* self);
+
+/**
* Unknown
* Inputs:
* 0: 0x00360000
diff --git a/src/core/hle/service/cam/cam_u.cpp b/src/core/hle/service/cam/cam_u.cpp
index af2123e5b..251c1e6d4 100644
--- a/src/core/hle/service/cam/cam_u.cpp
+++ b/src/core/hle/service/cam/cam_u.cpp
@@ -11,24 +11,24 @@ namespace CAM {
const Interface::FunctionInfo FunctionTable[] = {
{0x00010040, StartCapture, "StartCapture"},
{0x00020040, StopCapture, "StopCapture"},
- {0x00030040, nullptr, "IsBusy"},
- {0x00040040, nullptr, "ClearBuffer"},
+ {0x00030040, IsBusy, "IsBusy"},
+ {0x00040040, ClearBuffer, "ClearBuffer"},
{0x00050040, GetVsyncInterruptEvent, "GetVsyncInterruptEvent"},
{0x00060040, GetBufferErrorInterruptEvent, "GetBufferErrorInterruptEvent"},
{0x00070102, SetReceiving, "SetReceiving"},
- {0x00080040, nullptr, "IsFinishedReceiving"},
+ {0x00080040, IsFinishedReceiving, "IsFinishedReceiving"},
{0x00090100, SetTransferLines, "SetTransferLines"},
{0x000A0080, GetMaxLines, "GetMaxLines"},
- {0x000B0100, nullptr, "SetTransferBytes"},
+ {0x000B0100, SetTransferBytes, "SetTransferBytes"},
{0x000C0040, GetTransferBytes, "GetTransferBytes"},
- {0x000D0080, nullptr, "GetMaxBytes"},
+ {0x000D0080, GetMaxBytes, "GetMaxBytes"},
{0x000E0080, SetTrimming, "SetTrimming"},
- {0x000F0040, nullptr, "IsTrimming"},
- {0x00100140, nullptr, "SetTrimmingParams"},
- {0x00110040, nullptr, "GetTrimmingParams"},
+ {0x000F0040, IsTrimming, "IsTrimming"},
+ {0x00100140, SetTrimmingParams, "SetTrimmingParams"},
+ {0x00110040, GetTrimmingParams, "GetTrimmingParams"},
{0x00120140, SetTrimmingParamsCenter, "SetTrimmingParamsCenter"},
{0x00130040, Activate, "Activate"},
- {0x00140080, nullptr, "SwitchContext"},
+ {0x00140080, SwitchContext, "SwitchContext"},
{0x00150080, nullptr, "SetExposure"},
{0x00160080, nullptr, "SetWhiteBalance"},
{0x00170080, nullptr, "SetWhiteBalanceWithoutBaseUp"},
@@ -38,18 +38,18 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x001B0080, nullptr, "SetAutoWhiteBalance"},
{0x001C0040, nullptr, "IsAutoWhiteBalance"},
{0x001D00C0, FlipImage, "FlipImage"},
- {0x001E0200, nullptr, "SetDetailSize"},
+ {0x001E0200, SetDetailSize, "SetDetailSize"},
{0x001F00C0, SetSize, "SetSize"},
{0x00200080, SetFrameRate, "SetFrameRate"},
{0x00210080, nullptr, "SetPhotoMode"},
- {0x002200C0, nullptr, "SetEffect"},
+ {0x002200C0, SetEffect, "SetEffect"},
{0x00230080, nullptr, "SetContrast"},
{0x00240080, nullptr, "SetLensCorrection"},
- {0x002500C0, nullptr, "SetOutputFormat"},
+ {0x002500C0, SetOutputFormat, "SetOutputFormat"},
{0x00260140, nullptr, "SetAutoExposureWindow"},
{0x00270140, nullptr, "SetAutoWhiteBalanceWindow"},
{0x00280080, nullptr, "SetNoiseFilter"},
- {0x00290080, nullptr, "SynchronizeVsyncTiming"},
+ {0x00290080, SynchronizeVsyncTiming, "SynchronizeVsyncTiming"},
{0x002A0080, nullptr, "GetLatestVsyncTiming"},
{0x002B0000, GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"},
{0x002C0400, nullptr, "SetStereoCameraCalibrationData"},
@@ -59,9 +59,9 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00300080, nullptr, "ReadMcuVariableI2cExclusive"},
{0x00310180, nullptr, "SetImageQualityCalibrationData"},
{0x00320000, nullptr, "GetImageQualityCalibrationData"},
- {0x003302C0, nullptr, "SetPackageParameterWithoutContext"},
- {0x00340140, nullptr, "SetPackageParameterWithContext"},
- {0x003501C0, nullptr, "SetPackageParameterWithContextDetail"},
+ {0x003302C0, SetPackageParameterWithoutContext, "SetPackageParameterWithoutContext"},
+ {0x00340140, SetPackageParameterWithContext, "SetPackageParameterWithContext"},
+ {0x003501C0, SetPackageParameterWithContextDetail, "SetPackageParameterWithContextDetail"},
{0x00360000, GetSuitableY2rStandardCoefficient, "GetSuitableY2rStandardCoefficient"},
{0x00370202, nullptr, "PlayShutterSoundWithWave"},
{0x00380040, PlayShutterSound, "PlayShutterSound"},
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index 59dd6d1cd..4ddb1bc90 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -3,6 +3,8 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <array>
+#include <cryptopp/sha.h>
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
@@ -176,14 +178,29 @@ void SecureInfoGetRegion(Service::Interface* self) {
}
void GenHashConsoleUnique(Service::Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
- u32 app_id_salt = cmd_buff[1];
-
- cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[2] = 0x33646D6F ^ (app_id_salt & 0xFFFFF); // 3dmoo hash
- cmd_buff[3] = 0x6F534841 ^ (app_id_salt & 0xFFFFF);
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 0);
+ const u32 app_id_salt = rp.Pop<u32>() & 0x000FFFFF;
+
+ IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
+
+ std::array<u8, 12> buffer;
+ const ResultCode result = GetConfigInfoBlock(ConsoleUniqueID2BlockID, 8, 2, buffer.data());
+ rb.Push(result);
+ if (result.IsSuccess()) {
+ std::memcpy(&buffer[8], &app_id_salt, sizeof(u32));
+ std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash;
+ CryptoPP::SHA256().CalculateDigest(hash.data(), buffer.data(), sizeof(buffer));
+ u32 low, high;
+ memcpy(&low, &hash[hash.size() - 8], sizeof(u32));
+ memcpy(&high, &hash[hash.size() - 4], sizeof(u32));
+ rb.Push(low);
+ rb.Push(high);
+ } else {
+ rb.Push<u32>(0);
+ rb.Push<u32>(0);
+ }
- LOG_WARNING(Service_CFG, "(STUBBED) called app_id_salt=0x%X", app_id_salt);
+ LOG_DEBUG(Service_CFG, "called app_id_salt=0x%X", app_id_salt);
}
void GetRegionCanadaUSA(Service::Interface* self) {
@@ -322,47 +339,11 @@ static ResultVal<void*> GetConfigInfoBlockPointer(u32 block_id, u32 size, u32 fl
return MakeResult<void*>(pointer);
}
-/// Checks if the language is available in the chosen region, and returns a proper one
-static u8 AdjustLanguageInfoBlock(u32 region, u8 language) {
- static const std::array<std::vector<u8>, 7> region_languages{{
- // JPN
- {LANGUAGE_JP},
- // USA
- {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_ES, LANGUAGE_PT},
- // EUR
- {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT,
- LANGUAGE_RU},
- // AUS
- {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT,
- LANGUAGE_RU},
- // CHN
- {LANGUAGE_ZH},
- // KOR
- {LANGUAGE_KO},
- // TWN
- {LANGUAGE_TW},
- }};
- const auto& available = region_languages[region];
- if (std::find(available.begin(), available.end(), language) == available.end()) {
- return available[0];
- }
- return language;
-}
-
ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, void* output) {
void* pointer;
CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag));
memcpy(output, pointer, size);
- // override the language setting if the region setting is auto
- if (block_id == LanguageBlockID &&
- Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) {
- u8 language;
- memcpy(&language, output, sizeof(u8));
- language = AdjustLanguageInfoBlock(preferred_region_code, language);
- memcpy(output, &language, sizeof(u8));
- }
-
return RESULT_SUCCESS;
}
@@ -586,9 +567,47 @@ void Init() {
void Shutdown() {}
+/// Checks if the language is available in the chosen region, and returns a proper one
+static SystemLanguage AdjustLanguageInfoBlock(u32 region, SystemLanguage language) {
+ static const std::array<std::vector<SystemLanguage>, 7> region_languages{{
+ // JPN
+ {LANGUAGE_JP},
+ // USA
+ {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_ES, LANGUAGE_PT},
+ // EUR
+ {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT,
+ LANGUAGE_RU},
+ // AUS
+ {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT,
+ LANGUAGE_RU},
+ // CHN
+ {LANGUAGE_ZH},
+ // KOR
+ {LANGUAGE_KO},
+ // TWN
+ {LANGUAGE_TW},
+ }};
+ const auto& available = region_languages[region];
+ if (std::find(available.begin(), available.end(), language) == available.end()) {
+ return available[0];
+ }
+ return language;
+}
+
void SetPreferredRegionCode(u32 region_code) {
preferred_region_code = region_code;
LOG_INFO(Service_CFG, "Preferred region code set to %u", preferred_region_code);
+
+ if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) {
+ const SystemLanguage current_language = GetSystemLanguage();
+ const SystemLanguage adjusted_language =
+ AdjustLanguageInfoBlock(region_code, current_language);
+ if (current_language != adjusted_language) {
+ LOG_WARNING(Service_CFG, "System language %d does not fit the region. Adjusted to %d",
+ static_cast<int>(current_language), static_cast<int>(adjusted_language));
+ SetSystemLanguage(adjusted_language);
+ }
+ }
}
void SetUsername(const std::u16string& name) {
diff --git a/src/core/hle/service/err_f.cpp b/src/core/hle/service/err_f.cpp
index cd0a1a598..9da55f328 100644
--- a/src/core/hle/service/err_f.cpp
+++ b/src/core/hle/service/err_f.cpp
@@ -227,6 +227,8 @@ static void ThrowFatalError(Interface* self) {
LOG_CRITICAL(Service_ERR, "FINST2: 0x%08X",
errtype.exception_data.exception_info.fpinst2);
break;
+ case ExceptionType::Undefined:
+ break; // Not logging exception_info for this case
}
LOG_CRITICAL(Service_ERR, "Datetime: %s", GetCurrentSystemTime().c_str());
break;
diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h
index 519c1f3a9..2ea956e0b 100644
--- a/src/core/hle/service/fs/archive.h
+++ b/src/core/hle/service/fs/archive.h
@@ -26,7 +26,7 @@ namespace FS {
/// Supported archive types
enum class ArchiveIdCode : u32 {
- RomFS = 0x00000003,
+ SelfNCCH = 0x00000003,
SaveData = 0x00000004,
ExtSaveData = 0x00000006,
SharedExtSaveData = 0x00000007,
diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp
index 337da1387..33b290699 100644
--- a/src/core/hle/service/fs/fs_user.cpp
+++ b/src/core/hle/service/fs/fs_user.cpp
@@ -54,15 +54,17 @@ static void Initialize(Service::Interface* self) {
* 3 : File handle
*/
static void OpenFile(Service::Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
+ // The helper should be passed by argument to the function
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), {0x080201C2});
+ rp.Pop<u32>(); // Always 0 ?
- ArchiveHandle archive_handle = MakeArchiveHandle(cmd_buff[2], cmd_buff[3]);
- auto filename_type = static_cast<FileSys::LowPathType>(cmd_buff[4]);
- u32 filename_size = cmd_buff[5];
+ ArchiveHandle archive_handle = rp.Pop<u64>();
+ auto filename_type = static_cast<FileSys::LowPathType>(rp.Pop<u32>());
+ u32 filename_size = rp.Pop<u32>();
FileSys::Mode mode;
- mode.hex = cmd_buff[6];
- u32 attributes = cmd_buff[7]; // TODO(Link Mauve): do something with those attributes.
- u32 filename_ptr = cmd_buff[9];
+ mode.hex = rp.Pop<u32>();
+ u32 attributes = rp.Pop<u32>(); // TODO(Link Mauve): do something with those attributes.
+ VAddr filename_ptr = rp.PopStaticBuffer();
FileSys::Path file_path(filename_type, filename_size, filename_ptr);
LOG_DEBUG(Service_FS, "path=%s, mode=%u attrs=%u", file_path.DebugStr().c_str(), mode.hex,
@@ -70,16 +72,17 @@ static void OpenFile(Service::Interface* self) {
ResultVal<std::shared_ptr<File>> file_res =
OpenFileFromArchive(archive_handle, file_path, mode);
- cmd_buff[1] = file_res.Code().raw;
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+ rb.Push(file_res.Code());
if (file_res.Succeeded()) {
std::shared_ptr<File> file = *file_res;
auto sessions = ServerSession::CreateSessionPair(file->GetName(), file);
file->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions));
- cmd_buff[3] = Kernel::g_handle_table
- .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions))
- .MoveFrom();
+ rb.PushMoveHandles(Kernel::g_handle_table
+ .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions))
+ .MoveFrom());
} else {
- cmd_buff[3] = 0;
+ rb.PushMoveHandles(0);
LOG_ERROR(Service_FS, "failed to get a handle for file %s", file_path.DebugStr().c_str());
}
}
diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp
index a8c1331ed..a960778a7 100644
--- a/src/core/hle/service/gsp_gpu.cpp
+++ b/src/core/hle/service/gsp_gpu.cpp
@@ -4,6 +4,7 @@
#include "common/bit_field.h"
#include "common/microprofile.h"
+#include "core/core.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/result.h"
@@ -118,10 +119,10 @@ static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, VAddr data_va
* Updates sequential GSP GPU hardware registers using parallel arrays of source data and masks.
* For each register, the value is updated only where the mask is high
*
- * @param base_address The address of the first register in the sequence
+ * @param base_address The address of the first register in the sequence
* @param size_in_bytes The number of registers to update (size of data)
- * @param data A pointer to the source data to use for updates
- * @param masks A pointer to the masks
+ * @param data_vaddr A virtual address to the source data to use for updates
+ * @param masks_vaddr A virtual address to the masks
* @return RESULT_SUCCESS if the parameters are valid, error code otherwise
*/
static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, VAddr data_vaddr,
@@ -280,6 +281,7 @@ ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) {
if (screen_id == 0) {
MicroProfileFlip();
+ Core::System::GetInstance().perf_stats.EndGameFrame();
}
return RESULT_SUCCESS;
@@ -705,6 +707,33 @@ static void ReleaseRight(Interface* self) {
LOG_WARNING(Service_GSP, "called");
}
+/**
+ * GSP_GPU::StoreDataCache service function
+ *
+ * This Function is a no-op, We aren't emulating the CPU cache any time soon.
+ *
+ * Inputs:
+ * 0 : Header code [0x001F0082]
+ * 1 : Address
+ * 2 : Size
+ * 3 : Value 0, some descriptor for the KProcess Handle
+ * 4 : KProcess handle
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void StoreDataCache(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ u32 address = cmd_buff[1];
+ u32 size = cmd_buff[2];
+ u32 process = cmd_buff[4];
+
+ cmd_buff[0] = IPC::MakeHeader(0x1F, 0x1, 0);
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+
+ LOG_DEBUG(Service_GSP, "(STUBBED) called address=0x%08X, size=0x%08X, process=0x%08X", address,
+ size, process);
+}
+
const Interface::FunctionInfo FunctionTable[] = {
{0x00010082, WriteHWRegs, "WriteHWRegs"},
{0x00020084, WriteHWRegsWithMask, "WriteHWRegsWithMask"},
@@ -736,7 +765,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x001C0040, nullptr, "SetLedForceOff"},
{0x001D0040, nullptr, "SetTestCommand"},
{0x001E0080, nullptr, "SetInternalPriorities"},
- {0x001F0082, nullptr, "StoreDataCache"},
+ {0x001F0082, StoreDataCache, "StoreDataCache"},
};
GSP_GPU::GSP_GPU() {
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index f14ab3811..b19e831fe 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -2,10 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
+#include <atomic>
#include <cmath>
+#include <memory>
#include "common/logging/log.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
+#include "core/frontend/input.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/hid/hid.h"
@@ -32,8 +36,8 @@ static u32 next_touch_index;
static u32 next_accelerometer_index;
static u32 next_gyroscope_index;
-static int enable_accelerometer_count = 0; // positive means enabled
-static int enable_gyroscope_count = 0; // positive means enabled
+static int enable_accelerometer_count; // positive means enabled
+static int enable_gyroscope_count; // positive means enabled
static int pad_update_event;
static int accelerometer_update_event;
@@ -44,6 +48,11 @@ constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234;
constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104;
constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101;
+static std::atomic<bool> is_device_reload_pending;
+static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
+ buttons;
+static std::unique_ptr<Input::AnalogDevice> circle_pad;
+
static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
// 30 degree and 60 degree are angular thresholds for directions
constexpr float TAN30 = 0.577350269f;
@@ -74,14 +83,48 @@ static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
return state;
}
+static void LoadInputDevices() {
+ std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
+ Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
+ buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
+ circle_pad = Input::CreateDevice<Input::AnalogDevice>(
+ Settings::values.analogs[Settings::NativeAnalog::CirclePad]);
+}
+
+static void UnloadInputDevices() {
+ for (auto& button : buttons) {
+ button.reset();
+ }
+ circle_pad.reset();
+}
+
static void UpdatePadCallback(u64 userdata, int cycles_late) {
SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
- PadState state = VideoCore::g_emu_window->GetPadState();
+ if (is_device_reload_pending.exchange(false))
+ LoadInputDevices();
+
+ PadState state;
+ using namespace Settings::NativeButton;
+ state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
+ state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
+ state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
+ state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
+ state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus());
+ state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus());
+ state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus());
+ state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus());
+ state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
+ state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
+ state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus());
+ state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus());
// Get current circle pad position and update circle pad direction
- s16 circle_pad_x, circle_pad_y;
- std::tie(circle_pad_x, circle_pad_y) = VideoCore::g_emu_window->GetCirclePadState();
+ float circle_pad_x_f, circle_pad_y_f;
+ std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus();
+ constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position
+ s16 circle_pad_x = static_cast<s16>(circle_pad_x_f * MAX_CIRCLEPAD_POS);
+ s16 circle_pad_y = static_cast<s16>(circle_pad_y_f * MAX_CIRCLEPAD_POS);
state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex;
mem->pad.current_state.hex = state.hex;
@@ -313,6 +356,8 @@ void Init() {
AddService(new HID_U_Interface);
AddService(new HID_SPVR_Interface);
+ is_device_reload_pending.store(true);
+
using Kernel::MemoryPermission;
shared_mem =
SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read,
@@ -323,6 +368,9 @@ void Init() {
next_accelerometer_index = 0;
next_gyroscope_index = 0;
+ enable_accelerometer_count = 0;
+ enable_gyroscope_count = 0;
+
// Create event handles
event_pad_or_touch_1 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch1");
event_pad_or_touch_2 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch2");
@@ -347,6 +395,11 @@ void Shutdown() {
event_accelerometer = nullptr;
event_gyroscope = nullptr;
event_debug_pad = nullptr;
+ UnloadInputDevices();
+}
+
+void ReloadInputDevices() {
+ is_device_reload_pending.store(true);
}
} // namespace HID
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 21e66dfe0..b505cdcd5 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -39,15 +39,6 @@ struct PadState {
BitField<10, 1, u32> x;
BitField<11, 1, u32> y;
- BitField<14, 1, u32> zl;
- BitField<15, 1, u32> zr;
-
- BitField<20, 1, u32> touch;
-
- BitField<24, 1, u32> c_right;
- BitField<25, 1, u32> c_left;
- BitField<26, 1, u32> c_up;
- BitField<27, 1, u32> c_down;
BitField<28, 1, u32> circle_right;
BitField<29, 1, u32> circle_left;
BitField<30, 1, u32> circle_up;
@@ -185,35 +176,6 @@ ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A);
#undef ASSERT_REG_POSITION
#endif // !defined(_MSC_VER)
-// Pre-defined PadStates for single button presses
-const PadState PAD_NONE = {{0}};
-const PadState PAD_A = {{1u << 0}};
-const PadState PAD_B = {{1u << 1}};
-const PadState PAD_SELECT = {{1u << 2}};
-const PadState PAD_START = {{1u << 3}};
-const PadState PAD_RIGHT = {{1u << 4}};
-const PadState PAD_LEFT = {{1u << 5}};
-const PadState PAD_UP = {{1u << 6}};
-const PadState PAD_DOWN = {{1u << 7}};
-const PadState PAD_R = {{1u << 8}};
-const PadState PAD_L = {{1u << 9}};
-const PadState PAD_X = {{1u << 10}};
-const PadState PAD_Y = {{1u << 11}};
-
-const PadState PAD_ZL = {{1u << 14}};
-const PadState PAD_ZR = {{1u << 15}};
-
-const PadState PAD_TOUCH = {{1u << 20}};
-
-const PadState PAD_C_RIGHT = {{1u << 24}};
-const PadState PAD_C_LEFT = {{1u << 25}};
-const PadState PAD_C_UP = {{1u << 26}};
-const PadState PAD_C_DOWN = {{1u << 27}};
-const PadState PAD_CIRCLE_RIGHT = {{1u << 28}};
-const PadState PAD_CIRCLE_LEFT = {{1u << 29}};
-const PadState PAD_CIRCLE_UP = {{1u << 30}};
-const PadState PAD_CIRCLE_DOWN = {{1u << 31}};
-
/**
* HID::GetIPCHandles service function
* Inputs:
@@ -301,5 +263,8 @@ void Init();
/// Shutdown HID service
void Shutdown();
+
+/// Reload input devices. Used when input configuration changed
+void ReloadInputDevices();
}
}
diff --git a/src/core/hle/service/ir/ir.cpp b/src/core/hle/service/ir/ir.cpp
index 7f1731a50..7ac34a990 100644
--- a/src/core/hle/service/ir/ir.cpp
+++ b/src/core/hle/service/ir/ir.cpp
@@ -2,9 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "core/hle/kernel/event.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/ir/ir.h"
#include "core/hle/service/ir/ir_rst.h"
#include "core/hle/service/ir/ir_u.h"
@@ -14,101 +11,18 @@
namespace Service {
namespace IR {
-static Kernel::SharedPtr<Kernel::Event> handle_event;
-static Kernel::SharedPtr<Kernel::Event> conn_status_event;
-static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory;
-static Kernel::SharedPtr<Kernel::SharedMemory> transfer_shared_memory;
-
-void GetHandles(Service::Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[2] = 0x4000000;
- cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom();
- cmd_buff[4] = Kernel::g_handle_table.Create(Service::IR::handle_event).MoveFrom();
-}
-
-void InitializeIrNopShared(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- u32 transfer_buff_size = cmd_buff[1];
- u32 recv_buff_size = cmd_buff[2];
- u32 unk1 = cmd_buff[3];
- u32 send_buff_size = cmd_buff[4];
- u32 unk2 = cmd_buff[5];
- u8 baud_rate = cmd_buff[6] & 0xFF;
- Kernel::Handle handle = cmd_buff[8];
-
- if (Kernel::g_handle_table.IsValid(handle)) {
- transfer_shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle);
- transfer_shared_memory->name = "IR:TransferSharedMemory";
- }
-
- cmd_buff[1] = RESULT_SUCCESS.raw;
-
- LOG_WARNING(Service_IR, "(STUBBED) called, transfer_buff_size=%d, recv_buff_size=%d, "
- "unk1=%d, send_buff_size=%d, unk2=%d, baud_rate=%u, handle=0x%08X",
- transfer_buff_size, recv_buff_size, unk1, send_buff_size, unk2, baud_rate, handle);
-}
-
-void RequireConnection(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- conn_status_event->Signal();
-
- cmd_buff[1] = RESULT_SUCCESS.raw;
-
- LOG_WARNING(Service_IR, "(STUBBED) called");
-}
-
-void Disconnect(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- cmd_buff[1] = RESULT_SUCCESS.raw;
-
- LOG_WARNING(Service_IR, "(STUBBED) called");
-}
-
-void GetConnectionStatusEvent(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom();
-
- LOG_WARNING(Service_IR, "(STUBBED) called");
-}
-
-void FinalizeIrNop(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- cmd_buff[1] = RESULT_SUCCESS.raw;
-
- LOG_WARNING(Service_IR, "(STUBBED) called");
-}
-
void Init() {
- using namespace Kernel;
-
AddService(new IR_RST_Interface);
AddService(new IR_U_Interface);
AddService(new IR_User_Interface);
- using Kernel::MemoryPermission;
- shared_memory = SharedMemory::Create(nullptr, 0x1000, Kernel::MemoryPermission::ReadWrite,
- Kernel::MemoryPermission::ReadWrite, 0,
- Kernel::MemoryRegion::BASE, "IR:SharedMemory");
- transfer_shared_memory = nullptr;
-
- // Create event handle(s)
- handle_event = Event::Create(ResetType::OneShot, "IR:HandleEvent");
- conn_status_event = Event::Create(ResetType::OneShot, "IR:ConnectionStatusEvent");
+ InitUser();
+ InitRST();
}
void Shutdown() {
- transfer_shared_memory = nullptr;
- shared_memory = nullptr;
- handle_event = nullptr;
- conn_status_event = nullptr;
+ ShutdownUser();
+ ShutdownRST();
}
} // namespace IR
diff --git a/src/core/hle/service/ir/ir.h b/src/core/hle/service/ir/ir.h
index 72d44ce60..c741498e2 100644
--- a/src/core/hle/service/ir/ir.h
+++ b/src/core/hle/service/ir/ir.h
@@ -10,63 +10,6 @@ class Interface;
namespace IR {
-/**
- * IR::GetHandles service function
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- * 2 : Translate header, used by the ARM11-kernel
- * 3 : Shared memory handle
- * 4 : Event handle
- */
-void GetHandles(Interface* self);
-
-/**
- * IR::InitializeIrNopShared service function
- * Inputs:
- * 1 : Size of transfer buffer
- * 2 : Recv buffer size
- * 3 : unknown
- * 4 : Send buffer size
- * 5 : unknown
- * 6 : BaudRate (u8)
- * 7 : 0
- * 8 : Handle of transfer shared memory
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- */
-void InitializeIrNopShared(Interface* self);
-
-/**
- * IR::FinalizeIrNop service function
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- */
-void FinalizeIrNop(Interface* self);
-
-/**
- * IR::GetConnectionStatusEvent service function
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- * 2 : Connection Status Event handle
- */
-void GetConnectionStatusEvent(Interface* self);
-
-/**
- * IR::Disconnect service function
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- */
-void Disconnect(Interface* self);
-
-/**
- * IR::RequireConnection service function
- * Inputs:
- * 1 : unknown (u8), looks like always 1
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- */
-void RequireConnection(Interface* self);
-
/// Initialize IR service
void Init();
diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp
index 1f10ebd3d..3f1275c53 100644
--- a/src/core/hle/service/ir/ir_rst.cpp
+++ b/src/core/hle/service/ir/ir_rst.cpp
@@ -2,12 +2,34 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/hle/kernel/event.h"
+#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/ir/ir.h"
#include "core/hle/service/ir/ir_rst.h"
namespace Service {
namespace IR {
+static Kernel::SharedPtr<Kernel::Event> handle_event;
+static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory;
+
+/**
+ * IR::GetHandles service function
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Translate header, used by the ARM11-kernel
+ * 3 : Shared memory handle
+ * 4 : Event handle
+ */
+static void GetHandles(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = 0x4000000;
+ cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom();
+ cmd_buff[4] = Kernel::g_handle_table.Create(Service::IR::handle_event).MoveFrom();
+}
+
const Interface::FunctionInfo FunctionTable[] = {
{0x00010000, GetHandles, "GetHandles"},
{0x00020080, nullptr, "Initialize"},
@@ -19,5 +41,20 @@ IR_RST_Interface::IR_RST_Interface() {
Register(FunctionTable);
}
+void InitRST() {
+ using namespace Kernel;
+
+ shared_memory =
+ SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite,
+ MemoryPermission::ReadWrite, 0, MemoryRegion::BASE, "IR:SharedMemory");
+
+ handle_event = Event::Create(ResetType::OneShot, "IR:HandleEvent");
+}
+
+void ShutdownRST() {
+ shared_memory = nullptr;
+ handle_event = nullptr;
+}
+
} // namespace IR
} // namespace Service
diff --git a/src/core/hle/service/ir/ir_rst.h b/src/core/hle/service/ir/ir_rst.h
index a492e15c9..75b732627 100644
--- a/src/core/hle/service/ir/ir_rst.h
+++ b/src/core/hle/service/ir/ir_rst.h
@@ -18,5 +18,8 @@ public:
}
};
+void InitRST();
+void ShutdownRST();
+
} // namespace IR
} // namespace Service
diff --git a/src/core/hle/service/ir/ir_u.cpp b/src/core/hle/service/ir/ir_u.cpp
index 429615f31..ce00d5732 100644
--- a/src/core/hle/service/ir/ir_u.cpp
+++ b/src/core/hle/service/ir/ir_u.cpp
@@ -27,7 +27,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00100000, nullptr, "GetErrorStatus"},
{0x00110040, nullptr, "SetSleepModeActive"},
{0x00120040, nullptr, "SetSleepModeState"},
- // clang-format off
+ // clang-format on
};
IR_U_Interface::IR_U_Interface() {
diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp
index 6cff1d544..b326d7fc7 100644
--- a/src/core/hle/service/ir/ir_user.cpp
+++ b/src/core/hle/service/ir/ir_user.cpp
@@ -2,12 +2,112 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/hle/kernel/event.h"
+#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/ir/ir.h"
#include "core/hle/service/ir/ir_user.h"
namespace Service {
namespace IR {
+static Kernel::SharedPtr<Kernel::Event> conn_status_event;
+static Kernel::SharedPtr<Kernel::SharedMemory> transfer_shared_memory;
+
+/**
+ * IR::InitializeIrNopShared service function
+ * Inputs:
+ * 1 : Size of transfer buffer
+ * 2 : Recv buffer size
+ * 3 : unknown
+ * 4 : Send buffer size
+ * 5 : unknown
+ * 6 : BaudRate (u8)
+ * 7 : 0
+ * 8 : Handle of transfer shared memory
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void InitializeIrNopShared(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ u32 transfer_buff_size = cmd_buff[1];
+ u32 recv_buff_size = cmd_buff[2];
+ u32 unk1 = cmd_buff[3];
+ u32 send_buff_size = cmd_buff[4];
+ u32 unk2 = cmd_buff[5];
+ u8 baud_rate = cmd_buff[6] & 0xFF;
+ Kernel::Handle handle = cmd_buff[8];
+
+ if (Kernel::g_handle_table.IsValid(handle)) {
+ transfer_shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle);
+ transfer_shared_memory->name = "IR:TransferSharedMemory";
+ }
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+
+ LOG_WARNING(Service_IR, "(STUBBED) called, transfer_buff_size=%d, recv_buff_size=%d, "
+ "unk1=%d, send_buff_size=%d, unk2=%d, baud_rate=%u, handle=0x%08X",
+ transfer_buff_size, recv_buff_size, unk1, send_buff_size, unk2, baud_rate, handle);
+}
+
+/**
+ * IR::RequireConnection service function
+ * Inputs:
+ * 1 : unknown (u8), looks like always 1
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void RequireConnection(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ conn_status_event->Signal();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+
+ LOG_WARNING(Service_IR, "(STUBBED) called");
+}
+
+/**
+ * IR::Disconnect service function
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void Disconnect(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+
+ LOG_WARNING(Service_IR, "(STUBBED) called");
+}
+
+/**
+ * IR::GetConnectionStatusEvent service function
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Connection Status Event handle
+ */
+static void GetConnectionStatusEvent(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom();
+
+ LOG_WARNING(Service_IR, "(STUBBED) called");
+}
+
+/**
+ * IR::FinalizeIrNop service function
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void FinalizeIrNop(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+
+ LOG_WARNING(Service_IR, "(STUBBED) called");
+}
+
const Interface::FunctionInfo FunctionTable[] = {
{0x00010182, nullptr, "InitializeIrNop"},
{0x00020000, FinalizeIrNop, "FinalizeIrNop"},
@@ -41,5 +141,17 @@ IR_User_Interface::IR_User_Interface() {
Register(FunctionTable);
}
+void InitUser() {
+ using namespace Kernel;
+
+ transfer_shared_memory = nullptr;
+ conn_status_event = Event::Create(ResetType::OneShot, "IR:ConnectionStatusEvent");
+}
+
+void ShutdownUser() {
+ transfer_shared_memory = nullptr;
+ conn_status_event = nullptr;
+}
+
} // namespace IR
} // namespace Service
diff --git a/src/core/hle/service/ir/ir_user.h b/src/core/hle/service/ir/ir_user.h
index 71c932ffa..3849bd923 100644
--- a/src/core/hle/service/ir/ir_user.h
+++ b/src/core/hle/service/ir/ir_user.h
@@ -18,5 +18,8 @@ public:
}
};
+void InitUser();
+void ShutdownUser();
+
} // namespace IR
} // namespace Service
diff --git a/src/core/hle/service/ldr_ro/cro_helper.h b/src/core/hle/service/ldr_ro/cro_helper.h
index 060d5a55f..3bc10dbdc 100644
--- a/src/core/hle/service/ldr_ro/cro_helper.h
+++ b/src/core/hle/service/ldr_ro/cro_helper.h
@@ -57,7 +57,7 @@ public:
* @param is_crs true if the module itself is the static module
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
- ResultCode Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment_addresss,
+ ResultCode Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment_address,
u32 data_segment_size, VAddr bss_segment_address, u32 bss_segment_size,
bool is_crs);
@@ -102,7 +102,7 @@ public:
/**
* Registers this module and adds it to the module list.
* @param crs_address the virtual address of the static module
- * @auto_link whether to register as an auto link module
+ * @param auto_link whether to register as an auto link module
*/
void Register(VAddr crs_address, bool auto_link);
diff --git a/src/core/hle/service/ldr_ro/ldr_ro.cpp b/src/core/hle/service/ldr_ro/ldr_ro.cpp
index 8d00a7577..7af76676b 100644
--- a/src/core/hle/service/ldr_ro/ldr_ro.cpp
+++ b/src/core/hle/service/ldr_ro/ldr_ro.cpp
@@ -6,6 +6,7 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
+#include "core/core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/service/ldr_ro/cro_helper.h"
diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp
index 0be94322c..63c334cb2 100644
--- a/src/core/hle/service/nim/nim.cpp
+++ b/src/core/hle/service/nim/nim.cpp
@@ -19,7 +19,7 @@ void CheckSysUpdateAvailable(Service::Interface* self) {
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = 0; // No update available
- LOG_WARNING(Service_NWM, "(STUBBED) called");
+ LOG_WARNING(Service_NIM, "(STUBBED) called");
}
void Init() {
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index a7ba7688f..e6a5f1417 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -10,6 +10,7 @@
#include <boost/container/flat_map.hpp>
#include "common/common_types.h"
#include "core/hle/ipc.h"
+#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/result.h"
diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp
index 31bb466fc..907d9c8fa 100644
--- a/src/core/hle/service/y2r_u.cpp
+++ b/src/core/hle/service/y2r_u.cpp
@@ -281,37 +281,39 @@ static void GetTransferEndEvent(Interface* self) {
}
static void SetSendingY(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- conversion.src_Y.address = cmd_buff[1];
- conversion.src_Y.image_size = cmd_buff[2];
- conversion.src_Y.transfer_unit = cmd_buff[3];
- conversion.src_Y.gap = cmd_buff[4];
+ // The helper should be passed by argument to the function
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x00100102);
+ conversion.src_Y.address = rp.Pop<u32>();
+ conversion.src_Y.image_size = rp.Pop<u32>();
+ conversion.src_Y.transfer_unit = rp.Pop<u32>();
+ conversion.src_Y.gap = rp.Pop<u32>();
+ Kernel::Handle src_process_handle = rp.PopHandle();
- cmd_buff[0] = IPC::MakeHeader(0x10, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, "
"src_process_handle=0x%08X",
conversion.src_Y.image_size, conversion.src_Y.transfer_unit, conversion.src_Y.gap,
- cmd_buff[6]);
+ src_process_handle);
}
static void SetSendingU(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
+ // The helper should be passed by argument to the function
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x00110102);
+ conversion.src_U.address = rp.Pop<u32>();
+ conversion.src_U.image_size = rp.Pop<u32>();
+ conversion.src_U.transfer_unit = rp.Pop<u32>();
+ conversion.src_U.gap = rp.Pop<u32>();
+ Kernel::Handle src_process_handle = rp.PopHandle();
- conversion.src_U.address = cmd_buff[1];
- conversion.src_U.image_size = cmd_buff[2];
- conversion.src_U.transfer_unit = cmd_buff[3];
- conversion.src_U.gap = cmd_buff[4];
-
- cmd_buff[0] = IPC::MakeHeader(0x11, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, "
"src_process_handle=0x%08X",
conversion.src_U.image_size, conversion.src_U.transfer_unit, conversion.src_U.gap,
- cmd_buff[6]);
+ src_process_handle);
}
static void SetSendingV(Interface* self) {
@@ -561,11 +563,10 @@ static void GetAlpha(Interface* self) {
}
static void SetDitheringWeightParams(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
- std::memcpy(&dithering_weight_params, &cmd_buff[1], sizeof(DitheringWeightParams));
-
- cmd_buff[0] = IPC::MakeHeader(0x24, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x24, 8, 0); // 0x240200
+ rp.PopRaw(dithering_weight_params);
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called");
}
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp
index 96db39ad9..4e0c3fb8b 100644
--- a/src/core/hle/svc.cpp
+++ b/src/core/hle/svc.cpp
@@ -556,11 +556,21 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 ent
break;
}
- if (processor_id == THREADPROCESSORID_1 || processor_id == THREADPROCESSORID_ALL ||
- (processor_id == THREADPROCESSORID_DEFAULT &&
- Kernel::g_current_process->ideal_processor == THREADPROCESSORID_1)) {
- LOG_WARNING(Kernel_SVC,
- "Newly created thread is allowed to be run in the SysCore, unimplemented.");
+ if (processor_id == THREADPROCESSORID_ALL) {
+ LOG_INFO(Kernel_SVC,
+ "Newly created thread is allowed to be run in any Core, unimplemented.");
+ }
+
+ if (processor_id == THREADPROCESSORID_DEFAULT &&
+ Kernel::g_current_process->ideal_processor == THREADPROCESSORID_1) {
+ LOG_WARNING(
+ Kernel_SVC,
+ "Newly created thread is allowed to be run in the SysCore (Core1), unimplemented.");
+ }
+
+ if (processor_id == THREADPROCESSORID_1) {
+ LOG_ERROR(Kernel_SVC,
+ "Newly created thread must run in the SysCore (Core1), unimplemented.");
}
CASCADE_RESULT(SharedPtr<Thread> thread, Kernel::Thread::Create(name, entry_point, priority,
@@ -837,6 +847,11 @@ static ResultCode SetTimer(Kernel::Handle handle, s64 initial, s64 interval) {
LOG_TRACE(Kernel_SVC, "called timer=0x%08X", handle);
+ if (initial < 0 || interval < 0) {
+ return ResultCode(ErrorDescription::OutOfRange, ErrorModule::Kernel,
+ ErrorSummary::InvalidArgument, ErrorLevel::Permanent);
+ }
+
SharedPtr<Timer> timer = Kernel::g_handle_table.Get<Timer>(handle);
if (timer == nullptr)
return ERR_INVALID_HANDLE;
diff --git a/src/core/hw/aes/arithmetic128.cpp b/src/core/hw/aes/arithmetic128.cpp
new file mode 100644
index 000000000..55b954a52
--- /dev/null
+++ b/src/core/hw/aes/arithmetic128.cpp
@@ -0,0 +1,47 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <functional>
+#include "core/hw/aes/arithmetic128.h"
+
+namespace HW {
+namespace AES {
+
+AESKey Lrot128(const AESKey& in, u32 rot) {
+ AESKey out;
+ rot %= 128;
+ const u32 byte_shift = rot / 8;
+ const u32 bit_shift = rot % 8;
+
+ for (u32 i = 0; i < 16; i++) {
+ const u32 wrap_index_a = (i + byte_shift) % 16;
+ const u32 wrap_index_b = (i + byte_shift + 1) % 16;
+ out[i] = ((in[wrap_index_a] << bit_shift) | (in[wrap_index_b] >> (8 - bit_shift))) & 0xFF;
+ }
+ return out;
+}
+
+AESKey Add128(const AESKey& a, const AESKey& b) {
+ AESKey out;
+ u32 carry = 0;
+ u32 sum = 0;
+
+ for (int i = 15; i >= 0; i--) {
+ sum = a[i] + b[i] + carry;
+ carry = sum >> 8;
+ out[i] = static_cast<u8>(sum & 0xff);
+ }
+
+ return out;
+}
+
+AESKey Xor128(const AESKey& a, const AESKey& b) {
+ AESKey out;
+ std::transform(a.cbegin(), a.cend(), b.cbegin(), out.begin(), std::bit_xor<>());
+ return out;
+}
+
+} // namespace AES
+} // namespace HW
diff --git a/src/core/hw/aes/arithmetic128.h b/src/core/hw/aes/arithmetic128.h
new file mode 100644
index 000000000..d670e2ce2
--- /dev/null
+++ b/src/core/hw/aes/arithmetic128.h
@@ -0,0 +1,17 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hw/aes/key.h"
+
+namespace HW {
+namespace AES {
+AESKey Lrot128(const AESKey& in, u32 rot);
+AESKey Add128(const AESKey& a, const AESKey& b);
+AESKey Xor128(const AESKey& a, const AESKey& b);
+
+} // namspace AES
+} // namespace HW
diff --git a/src/core/hw/aes/ccm.cpp b/src/core/hw/aes/ccm.cpp
new file mode 100644
index 000000000..dc7035ab6
--- /dev/null
+++ b/src/core/hw/aes/ccm.cpp
@@ -0,0 +1,95 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cryptopp/aes.h>
+#include <cryptopp/ccm.h>
+#include <cryptopp/cryptlib.h>
+#include <cryptopp/filters.h>
+#include "common/alignment.h"
+#include "common/logging/log.h"
+#include "core/hw/aes/ccm.h"
+#include "core/hw/aes/key.h"
+
+namespace HW {
+namespace AES {
+
+namespace {
+
+// 3DS uses a non-standard AES-CCM algorithm, so we need to derive a sub class from the standard one
+// and override with the non-standard part.
+using CryptoPP::lword;
+using CryptoPP::AES;
+using CryptoPP::CCM_Final;
+using CryptoPP::CCM_Base;
+template <bool T_IsEncryption>
+class CCM_3DSVariant_Final : public CCM_Final<AES, CCM_MAC_SIZE, T_IsEncryption> {
+public:
+ void UncheckedSpecifyDataLengths(lword header_length, lword message_length,
+ lword footer_length) override {
+ // 3DS uses the aligned size to generate B0 for authentication, instead of the original size
+ lword aligned_message_length = Common::AlignUp(message_length, AES_BLOCK_SIZE);
+ CCM_Base::UncheckedSpecifyDataLengths(header_length, aligned_message_length, footer_length);
+ CCM_Base::m_messageLength = message_length; // restore the actual message size
+ }
+};
+
+class CCM_3DSVariant {
+public:
+ using Encryption = CCM_3DSVariant_Final<true>;
+ using Decryption = CCM_3DSVariant_Final<false>;
+};
+
+} // namespace
+
+std::vector<u8> EncryptSignCCM(const std::vector<u8>& pdata, const CCMNonce& nonce,
+ size_t slot_id) {
+ if (!IsNormalKeyAvailable(slot_id)) {
+ LOG_ERROR(HW_AES, "Key slot %d not available. Will use zero key.", slot_id);
+ }
+ const AESKey normal = GetNormalKey(slot_id);
+ std::vector<u8> cipher(pdata.size() + CCM_MAC_SIZE);
+
+ try {
+ CCM_3DSVariant::Encryption e;
+ e.SetKeyWithIV(normal.data(), AES_BLOCK_SIZE, nonce.data(), CCM_NONCE_SIZE);
+ e.SpecifyDataLengths(0, pdata.size(), 0);
+ CryptoPP::ArraySource as(pdata.data(), pdata.size(), true,
+ new CryptoPP::AuthenticatedEncryptionFilter(
+ e, new CryptoPP::ArraySink(cipher.data(), cipher.size())));
+ } catch (const CryptoPP::Exception& e) {
+ LOG_ERROR(HW_AES, "FAILED with: %s", e.what());
+ }
+ return cipher;
+}
+
+std::vector<u8> DecryptVerifyCCM(const std::vector<u8>& cipher, const CCMNonce& nonce,
+ size_t slot_id) {
+ if (!IsNormalKeyAvailable(slot_id)) {
+ LOG_ERROR(HW_AES, "Key slot %d not available. Will use zero key.", slot_id);
+ }
+ const AESKey normal = GetNormalKey(slot_id);
+ const std::size_t pdata_size = cipher.size() - CCM_MAC_SIZE;
+ std::vector<u8> pdata(pdata_size);
+
+ try {
+ CCM_3DSVariant::Decryption d;
+ d.SetKeyWithIV(normal.data(), AES_BLOCK_SIZE, nonce.data(), CCM_NONCE_SIZE);
+ d.SpecifyDataLengths(0, pdata_size, 0);
+ CryptoPP::AuthenticatedDecryptionFilter df(
+ d, new CryptoPP::ArraySink(pdata.data(), pdata_size));
+ CryptoPP::ArraySource as(cipher.data(), cipher.size(), true, new CryptoPP::Redirector(df));
+ if (!df.GetLastResult()) {
+ LOG_ERROR(HW_AES, "FAILED");
+ return {};
+ }
+ } catch (const CryptoPP::Exception& e) {
+ LOG_ERROR(HW_AES, "FAILED with: %s", e.what());
+ return {};
+ }
+ return pdata;
+}
+
+} // namespace AES
+} // namespace HW
diff --git a/src/core/hw/aes/ccm.h b/src/core/hw/aes/ccm.h
new file mode 100644
index 000000000..bf4146e80
--- /dev/null
+++ b/src/core/hw/aes/ccm.h
@@ -0,0 +1,40 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <vector>
+#include "common/common_types.h"
+
+namespace HW {
+namespace AES {
+
+constexpr size_t CCM_NONCE_SIZE = 12;
+constexpr size_t CCM_MAC_SIZE = 16;
+
+using CCMNonce = std::array<u8, CCM_NONCE_SIZE>;
+
+/**
+ * Encrypts and adds a MAC to the given data using AES-CCM algorithm.
+ * @param pdata The plain text data to encrypt
+ * @param nonce The nonce data to use for encryption
+ * @param slot_id The slot ID of the key to use for encryption
+ * @returns a vector of u8 containing the encrypted data with MAC at the end
+ */
+std::vector<u8> EncryptSignCCM(const std::vector<u8>& pdata, const CCMNonce& nonce, size_t slot_id);
+
+/**
+ * Decrypts and verify the MAC of the given data using AES-CCM algorithm.
+ * @param cipher The cipher text data to decrypt, with MAC at the end to verify
+ * @param nonce The nonce data to use for decryption
+ * @param slot_id The slot ID of the key to use for decryption
+ * @returns a vector of u8 containing the decrypted data; an empty vector if the verification fails
+ */
+std::vector<u8> DecryptVerifyCCM(const std::vector<u8>& cipher, const CCMNonce& nonce,
+ size_t slot_id);
+
+} // namespace AES
+} // namespace HW
diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp
new file mode 100644
index 000000000..4e8a8a59a
--- /dev/null
+++ b/src/core/hw/aes/key.cpp
@@ -0,0 +1,173 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <exception>
+#include <sstream>
+#include <boost/optional.hpp>
+#include "common/common_paths.h"
+#include "common/file_util.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/hw/aes/arithmetic128.h"
+#include "core/hw/aes/key.h"
+
+namespace HW {
+namespace AES {
+
+namespace {
+
+boost::optional<AESKey> generator_constant;
+
+struct KeySlot {
+ boost::optional<AESKey> x;
+ boost::optional<AESKey> y;
+ boost::optional<AESKey> normal;
+
+ void SetKeyX(const AESKey& key) {
+ x = key;
+ if (y && generator_constant) {
+ GenerateNormalKey();
+ }
+ }
+
+ void SetKeyY(const AESKey& key) {
+ y = key;
+ if (x && generator_constant) {
+ GenerateNormalKey();
+ }
+ }
+
+ void SetNormalKey(const AESKey& key) {
+ normal = key;
+ }
+
+ void GenerateNormalKey() {
+ normal = Lrot128(Add128(Xor128(Lrot128(*x, 2), *y), *generator_constant), 87);
+ }
+
+ void Clear() {
+ x.reset();
+ y.reset();
+ normal.reset();
+ }
+};
+
+std::array<KeySlot, KeySlotID::MaxKeySlotID> key_slots;
+
+void ClearAllKeys() {
+ for (KeySlot& slot : key_slots) {
+ slot.Clear();
+ }
+ generator_constant.reset();
+}
+
+AESKey HexToKey(const std::string& hex) {
+ if (hex.size() < 32) {
+ throw std::invalid_argument("hex string is too short");
+ }
+
+ AESKey key;
+ for (size_t i = 0; i < key.size(); ++i) {
+ key[i] = static_cast<u8>(std::stoi(hex.substr(i * 2, 2), 0, 16));
+ }
+
+ return key;
+}
+
+void LoadPresetKeys() {
+ const std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + AES_KEYS;
+ FileUtil::CreateFullPath(filepath); // Create path if not already created
+ std::ifstream file;
+ OpenFStream(file, filepath, std::ios_base::in);
+ if (!file) {
+ return;
+ }
+
+ while (!file.eof()) {
+ std::string line;
+ std::getline(file, line);
+ std::vector<std::string> parts;
+ Common::SplitString(line, '=', parts);
+ if (parts.size() != 2) {
+ LOG_ERROR(HW_AES, "Failed to parse %s", line.c_str());
+ continue;
+ }
+
+ const std::string& name = parts[0];
+ AESKey key;
+ try {
+ key = HexToKey(parts[1]);
+ } catch (const std::logic_error& e) {
+ LOG_ERROR(HW_AES, "Invalid key %s: %s", parts[1].c_str(), e.what());
+ continue;
+ }
+
+ if (name == "generator") {
+ generator_constant = key;
+ continue;
+ }
+
+ size_t slot_id;
+ char key_type;
+ if (std::sscanf(name.c_str(), "slot0x%zXKey%c", &slot_id, &key_type) != 2) {
+ LOG_ERROR(HW_AES, "Invalid key name %s", name.c_str());
+ continue;
+ }
+
+ if (slot_id >= MaxKeySlotID) {
+ LOG_ERROR(HW_AES, "Out of range slot ID 0x%zX", slot_id);
+ continue;
+ }
+
+ switch (key_type) {
+ case 'X':
+ key_slots.at(slot_id).SetKeyX(key);
+ break;
+ case 'Y':
+ key_slots.at(slot_id).SetKeyY(key);
+ break;
+ case 'N':
+ key_slots.at(slot_id).SetNormalKey(key);
+ break;
+ default:
+ LOG_ERROR(HW_AES, "Invalid key type %c", key_type);
+ break;
+ }
+ }
+}
+
+} // namespace
+
+void InitKeys() {
+ ClearAllKeys();
+ LoadPresetKeys();
+}
+
+void SetGeneratorConstant(const AESKey& key) {
+ generator_constant = key;
+}
+
+void SetKeyX(size_t slot_id, const AESKey& key) {
+ key_slots.at(slot_id).SetKeyX(key);
+}
+
+void SetKeyY(size_t slot_id, const AESKey& key) {
+ key_slots.at(slot_id).SetKeyY(key);
+}
+
+void SetNormalKey(size_t slot_id, const AESKey& key) {
+ key_slots.at(slot_id).SetNormalKey(key);
+}
+
+bool IsNormalKeyAvailable(size_t slot_id) {
+ return key_slots.at(slot_id).normal.is_initialized();
+}
+
+AESKey GetNormalKey(size_t slot_id) {
+ return key_slots.at(slot_id).normal.value_or(AESKey{});
+}
+
+} // namespace AES
+} // namespace HW
diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h
new file mode 100644
index 000000000..b01d04f13
--- /dev/null
+++ b/src/core/hw/aes/key.h
@@ -0,0 +1,35 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include "common/common_types.h"
+
+namespace HW {
+namespace AES {
+
+enum KeySlotID : size_t {
+ APTWrap = 0x31,
+
+ MaxKeySlotID = 0x40,
+};
+
+constexpr size_t AES_BLOCK_SIZE = 16;
+
+using AESKey = std::array<u8, AES_BLOCK_SIZE>;
+
+void InitKeys();
+
+void SetGeneratorConstant(const AESKey& key);
+void SetKeyX(size_t slot_id, const AESKey& key);
+void SetKeyY(size_t slot_id, const AESKey& key);
+void SetNormalKey(size_t slot_id, const AESKey& key);
+
+bool IsNormalKeyAvailable(size_t slot_id);
+AESKey GetNormalKey(size_t slot_id);
+
+} // namspace AES
+} // namespace HW
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp
index fa8c13d36..42809c731 100644
--- a/src/core/hw/gpu.cpp
+++ b/src/core/hw/gpu.cpp
@@ -8,17 +8,13 @@
#include "common/color.h"
#include "common/common_types.h"
#include "common/logging/log.h"
-#include "common/math_util.h"
#include "common/microprofile.h"
-#include "common/thread.h"
-#include "common/timer.h"
#include "common/vector_math.h"
#include "core/core_timing.h"
#include "core/hle/service/gsp_gpu.h"
#include "core/hw/gpu.h"
#include "core/hw/hw.h"
#include "core/memory.h"
-#include "core/settings.h"
#include "core/tracer/recorder.h"
#include "video_core/command_processor.h"
#include "video_core/debug_utils/debug_utils.h"
@@ -32,19 +28,9 @@ namespace GPU {
Regs g_regs;
/// 268MHz CPU clocks / 60Hz frames per second
-const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / 60;
+const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / SCREEN_REFRESH_RATE;
/// Event id for CoreTiming
static int vblank_event;
-/// Total number of frames drawn
-static u64 frame_count;
-/// Start clock for frame limiter
-static u32 time_point;
-/// Total delay caused by slow frames
-static float time_delay;
-constexpr float FIXED_FRAME_TIME = 1000.0f / 60;
-// Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher
-// values increases time needed to limit frame rate after spikes
-constexpr float MAX_LAG_TIME = 18;
template <typename T>
inline void Read(T& var, const u32 raw_addr) {
@@ -522,24 +508,8 @@ template void Write<u32>(u32 addr, const u32 data);
template void Write<u16>(u32 addr, const u16 data);
template void Write<u8>(u32 addr, const u8 data);
-static void FrameLimiter() {
- time_delay += FIXED_FRAME_TIME;
- time_delay = MathUtil::Clamp(time_delay, -MAX_LAG_TIME, MAX_LAG_TIME);
- s32 desired_time = static_cast<s32>(time_delay);
- s32 elapsed_time = static_cast<s32>(Common::Timer::GetTimeMs() - time_point);
-
- if (elapsed_time < desired_time) {
- Common::SleepCurrentThread(desired_time - elapsed_time);
- }
-
- u32 frame_time = Common::Timer::GetTimeMs() - time_point;
-
- time_delay -= frame_time;
-}
-
/// Update hardware
static void VBlankCallback(u64 userdata, int cycles_late) {
- frame_count++;
VideoCore::g_renderer->SwapBuffers();
// Signal to GSP that GPU interrupt has occurred
@@ -550,12 +520,6 @@ static void VBlankCallback(u64 userdata, int cycles_late) {
Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC0);
Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC1);
- if (!Settings::values.use_vsync && Settings::values.toggle_framelimit) {
- FrameLimiter();
- }
-
- time_point = Common::Timer::GetTimeMs();
-
// Reschedule recurrent event
CoreTiming::ScheduleEvent(frame_ticks - cycles_late, vblank_event);
}
@@ -590,9 +554,6 @@ void Init() {
framebuffer_sub.color_format.Assign(Regs::PixelFormat::RGB8);
framebuffer_sub.active_fb = 0;
- frame_count = 0;
- time_point = Common::Timer::GetTimeMs();
-
vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback);
CoreTiming::ScheduleEvent(frame_ticks, vblank_event);
diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h
index d53381216..bdd997b2a 100644
--- a/src/core/hw/gpu.h
+++ b/src/core/hw/gpu.h
@@ -13,6 +13,8 @@
namespace GPU {
+constexpr float SCREEN_REFRESH_RATE = 60;
+
// Returns index corresponding to the Regs member labeled by field_name
// TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions
// when used with array elements (e.g. GPU_REG_INDEX(memory_fill_config[0])).
diff --git a/src/core/hw/hw.cpp b/src/core/hw/hw.cpp
index 9ff8825b2..8499f2ce6 100644
--- a/src/core/hw/hw.cpp
+++ b/src/core/hw/hw.cpp
@@ -4,6 +4,7 @@
#include "common/common_types.h"
#include "common/logging/log.h"
+#include "core/hw/aes/key.h"
#include "core/hw/gpu.h"
#include "core/hw/hw.h"
#include "core/hw/lcd.h"
@@ -85,6 +86,7 @@ void Update() {}
/// Initialize hardware
void Init() {
+ AES::InitKeys();
GPU::Init();
LCD::Init();
LOG_DEBUG(HW, "initialized OK");
diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp
index 09266e8b0..74e336487 100644
--- a/src/core/loader/3dsx.cpp
+++ b/src/core/loader/3dsx.cpp
@@ -5,7 +5,7 @@
#include <algorithm>
#include <vector>
#include "common/logging/log.h"
-#include "core/file_sys/archive_romfs.h"
+#include "core/file_sys/archive_selfncch.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/service/fs/archive.h"
@@ -277,8 +277,8 @@ ResultStatus AppLoader_THREEDSX::Load() {
Kernel::g_current_process->Run(48, Kernel::DEFAULT_STACK_SIZE);
- Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this),
- Service::FS::ArchiveIdCode::RomFS);
+ Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_SelfNCCH>(*this),
+ Service::FS::ArchiveIdCode::SelfNCCH);
is_loaded = true;
return ResultStatus::Success;
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 147bf8591..be719d74c 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -139,7 +139,7 @@ std::unique_ptr<AppLoader> GetLoader(const std::string& filename) {
type = filename_type;
}
- LOG_INFO(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type));
+ LOG_DEBUG(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type));
return GetFileLoader(std::move(file), type, filename_filename, filename);
}
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index a6c2a745f..1d80766ae 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -54,7 +54,7 @@ FileType IdentifyFile(const std::string& file_name);
* @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine
* a filetype, and will never return FileType::Error.
*/
-FileType GuessFromExtension(const std::string& extension_);
+FileType GuessFromExtension(const std::string& extension);
/**
* Convert a FileType into a string which can be displayed to the user.
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp
index 5df33f6d2..1a4e3efa8 100644
--- a/src/core/loader/ncch.cpp
+++ b/src/core/loader/ncch.cpp
@@ -3,12 +3,13 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <cinttypes>
#include <cstring>
#include <memory>
#include "common/logging/log.h"
#include "common/string_util.h"
#include "common/swap.h"
-#include "core/file_sys/archive_romfs.h"
+#include "core/file_sys/archive_selfncch.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/service/cfg/cfg.h"
@@ -253,7 +254,7 @@ ResultStatus AppLoader_NCCH::LoadExeFS() {
// Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)...
if (MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
- LOG_WARNING(Loader, "Only loading the first (bootable) NCCH within the NCSD file!");
+ LOG_DEBUG(Loader, "Only loading the first (bootable) NCCH within the NCSD file!");
ncch_offset = 0x4000;
file.Seek(ncch_offset, SEEK_SET);
file.ReadBytes(&ncch_header, sizeof(NCCH_Header));
@@ -277,8 +278,8 @@ ResultStatus AppLoader_NCCH::LoadExeFS() {
priority = exheader_header.arm11_system_local_caps.priority;
resource_limit_category = exheader_header.arm11_system_local_caps.resource_limit_category;
- LOG_INFO(Loader, "Name: %s", exheader_header.codeset_info.name);
- LOG_INFO(Loader, "Program ID: %016llX", ncch_header.program_id);
+ LOG_DEBUG(Loader, "Name: %s", exheader_header.codeset_info.name);
+ LOG_DEBUG(Loader, "Program ID: %016" PRIX64, ncch_header.program_id);
LOG_DEBUG(Loader, "Code compressed: %s", is_compressed ? "yes" : "no");
LOG_DEBUG(Loader, "Entry point: 0x%08X", entry_point);
LOG_DEBUG(Loader, "Code size: 0x%08X", code_size);
@@ -336,14 +337,16 @@ ResultStatus AppLoader_NCCH::Load() {
if (result != ResultStatus::Success)
return result;
+ LOG_INFO(Loader, "Program ID: %016" PRIX64, ncch_header.program_id);
+
is_loaded = true; // Set state to loaded
result = LoadExec(); // Load the executable into memory for booting
if (ResultStatus::Success != result)
return result;
- Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this),
- Service::FS::ArchiveIdCode::RomFS);
+ Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_SelfNCCH>(*this),
+ Service::FS::ArchiveIdCode::SelfNCCH);
ParseRegionLockoutInfo();
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
new file mode 100644
index 000000000..2cdfb9ded
--- /dev/null
+++ b/src/core/perf_stats.cpp
@@ -0,0 +1,105 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include <mutex>
+#include <thread>
+#include "common/math_util.h"
+#include "core/hw/gpu.h"
+#include "core/perf_stats.h"
+#include "core/settings.h"
+
+using namespace std::chrono_literals;
+using DoubleSecs = std::chrono::duration<double, std::chrono::seconds::period>;
+using std::chrono::duration_cast;
+using std::chrono::microseconds;
+
+namespace Core {
+
+void PerfStats::BeginSystemFrame() {
+ std::lock_guard<std::mutex> lock(object_mutex);
+
+ frame_begin = Clock::now();
+}
+
+void PerfStats::EndSystemFrame() {
+ std::lock_guard<std::mutex> lock(object_mutex);
+
+ auto frame_end = Clock::now();
+ accumulated_frametime += frame_end - frame_begin;
+ system_frames += 1;
+
+ previous_frame_length = frame_end - previous_frame_end;
+ previous_frame_end = frame_end;
+}
+
+void PerfStats::EndGameFrame() {
+ std::lock_guard<std::mutex> lock(object_mutex);
+
+ game_frames += 1;
+}
+
+PerfStats::Results PerfStats::GetAndResetStats(u64 current_system_time_us) {
+ std::lock_guard<std::mutex> lock(object_mutex);
+
+ auto now = Clock::now();
+ // Walltime elapsed since stats were reset
+ auto interval = duration_cast<DoubleSecs>(now - reset_point).count();
+
+ auto system_us_per_second =
+ static_cast<double>(current_system_time_us - reset_point_system_us) / interval;
+
+ Results results{};
+ results.system_fps = static_cast<double>(system_frames) / interval;
+ results.game_fps = static_cast<double>(game_frames) / interval;
+ results.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() /
+ static_cast<double>(system_frames);
+ results.emulation_speed = system_us_per_second / 1'000'000.0;
+
+ // Reset counters
+ reset_point = now;
+ reset_point_system_us = current_system_time_us;
+ accumulated_frametime = Clock::duration::zero();
+ system_frames = 0;
+ game_frames = 0;
+
+ return results;
+}
+
+double PerfStats::GetLastFrameTimeScale() {
+ std::lock_guard<std::mutex> lock(object_mutex);
+
+ constexpr double FRAME_LENGTH = 1.0 / GPU::SCREEN_REFRESH_RATE;
+ return duration_cast<DoubleSecs>(previous_frame_length).count() / FRAME_LENGTH;
+}
+
+void FrameLimiter::DoFrameLimiting(u64 current_system_time_us) {
+ // Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher
+ // values increase the time needed to recover and limit framerate again after spikes.
+ constexpr microseconds MAX_LAG_TIME_US = 25ms;
+
+ if (!Settings::values.toggle_framelimit) {
+ return;
+ }
+
+ auto now = Clock::now();
+
+ frame_limiting_delta_err += microseconds(current_system_time_us - previous_system_time_us);
+ frame_limiting_delta_err -= duration_cast<microseconds>(now - previous_walltime);
+ frame_limiting_delta_err =
+ MathUtil::Clamp(frame_limiting_delta_err, -MAX_LAG_TIME_US, MAX_LAG_TIME_US);
+
+ if (frame_limiting_delta_err > microseconds::zero()) {
+ std::this_thread::sleep_for(frame_limiting_delta_err);
+
+ auto now_after_sleep = Clock::now();
+ frame_limiting_delta_err -= duration_cast<microseconds>(now_after_sleep - now);
+ now = now_after_sleep;
+ }
+
+ previous_system_time_us = current_system_time_us;
+ previous_walltime = now;
+}
+
+} // namespace Core
diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h
new file mode 100644
index 000000000..362b205c8
--- /dev/null
+++ b/src/core/perf_stats.h
@@ -0,0 +1,83 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <chrono>
+#include <mutex>
+#include "common/common_types.h"
+
+namespace Core {
+
+/**
+ * Class to manage and query performance/timing statistics. All public functions of this class are
+ * thread-safe unless stated otherwise.
+ */
+class PerfStats {
+public:
+ using Clock = std::chrono::high_resolution_clock;
+
+ struct Results {
+ /// System FPS (LCD VBlanks) in Hz
+ double system_fps;
+ /// Game FPS (GSP frame submissions) in Hz
+ double game_fps;
+ /// Walltime per system frame, in seconds, excluding any waits
+ double frametime;
+ /// Ratio of walltime / emulated time elapsed
+ double emulation_speed;
+ };
+
+ void BeginSystemFrame();
+ void EndSystemFrame();
+ void EndGameFrame();
+
+ Results GetAndResetStats(u64 current_system_time_us);
+
+ /**
+ * Gets the ratio between walltime and the emulated time of the previous system frame. This is
+ * useful for scaling inputs or outputs moving between the two time domains.
+ */
+ double GetLastFrameTimeScale();
+
+private:
+ std::mutex object_mutex;
+
+ /// Point when the cumulative counters were reset
+ Clock::time_point reset_point = Clock::now();
+ /// System time when the cumulative counters were reset
+ u64 reset_point_system_us = 0;
+
+ /// Cumulative duration (excluding v-sync/frame-limiting) of frames since last reset
+ Clock::duration accumulated_frametime = Clock::duration::zero();
+ /// Cumulative number of system frames (LCD VBlanks) presented since last reset
+ u32 system_frames = 0;
+ /// Cumulative number of game frames (GSP frame submissions) since last reset
+ u32 game_frames = 0;
+
+ /// Point when the previous system frame ended
+ Clock::time_point previous_frame_end = reset_point;
+ /// Point when the current system frame began
+ Clock::time_point frame_begin = reset_point;
+ /// Total visible duration (including frame-limiting, etc.) of the previous system frame
+ Clock::duration previous_frame_length = Clock::duration::zero();
+};
+
+class FrameLimiter {
+public:
+ using Clock = std::chrono::high_resolution_clock;
+
+ void DoFrameLimiting(u64 current_system_time_us);
+
+private:
+ /// Emulated system time (in microseconds) at the last limiter invocation
+ u64 previous_system_time_us = 0;
+ /// Walltime at the last limiter invocation
+ Clock::time_point previous_walltime = Clock::now();
+
+ /// Accumulated difference between walltime and emulated time
+ std::chrono::microseconds frame_limiting_delta_err{0};
+};
+
+} // namespace Core
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 9afaf79ec..a598f9f2f 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -4,6 +4,7 @@
#include "audio_core/audio_core.h"
#include "core/gdbstub/gdbstub.h"
+#include "core/hle/service/hid/hid.h"
#include "settings.h"
#include "video_core/video_core.h"
@@ -15,7 +16,7 @@ Values values = {};
void Apply() {
- GDBStub::SetServerPort(static_cast<u32>(values.gdbstub_port));
+ GDBStub::SetServerPort(values.gdbstub_port);
GDBStub::ToggleServer(values.use_gdbstub);
VideoCore::g_hw_renderer_enabled = values.use_hw_renderer;
@@ -29,6 +30,8 @@ void Apply() {
AudioCore::SelectSink(values.sink_id);
AudioCore::EnableStretching(values.enable_audio_stretching);
+
+ Service::HID::ReloadInputDevices();
}
} // namespace
diff --git a/src/core/settings.h b/src/core/settings.h
index 52cebc627..03c64c94c 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -7,6 +7,7 @@
#include <array>
#include <string>
#include "common/common_types.h"
+#include "core/hle/service/cam/cam.h"
namespace Settings {
@@ -16,64 +17,68 @@ enum class LayoutOption {
LargeScreen,
};
-namespace NativeInput {
-
+namespace NativeButton {
enum Values {
- // directly mapped keys
A,
B,
X,
Y,
+ Up,
+ Down,
+ Left,
+ Right,
L,
R,
+ Start,
+ Select,
+
ZL,
ZR,
- START,
- SELECT,
- HOME,
- DUP,
- DDOWN,
- DLEFT,
- DRIGHT,
- CUP,
- CDOWN,
- CLEFT,
- CRIGHT,
-
- // indirectly mapped keys
- CIRCLE_UP,
- CIRCLE_DOWN,
- CIRCLE_LEFT,
- CIRCLE_RIGHT,
- CIRCLE_MODIFIER,
-
- NUM_INPUTS
+
+ Home,
+
+ NumButtons,
};
-static const std::array<const char*, NUM_INPUTS> Mapping = {{
- // directly mapped keys
- "pad_a", "pad_b", "pad_x", "pad_y", "pad_l", "pad_r", "pad_zl", "pad_zr", "pad_start",
- "pad_select", "pad_home", "pad_dup", "pad_ddown", "pad_dleft", "pad_dright", "pad_cup",
- "pad_cdown", "pad_cleft", "pad_cright",
+constexpr int BUTTON_HID_BEGIN = A;
+constexpr int BUTTON_IR_BEGIN = ZL;
+constexpr int BUTTON_NS_BEGIN = Home;
- // indirectly mapped keys
- "pad_circle_up", "pad_circle_down", "pad_circle_left", "pad_circle_right",
- "pad_circle_modifier",
+constexpr int BUTTON_HID_END = BUTTON_IR_BEGIN;
+constexpr int BUTTON_IR_END = BUTTON_NS_BEGIN;
+constexpr int BUTTON_NS_END = NumButtons;
+
+constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
+constexpr int NUM_BUTTONS_IR = BUTTON_IR_END - BUTTON_IR_BEGIN;
+constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
+
+static const std::array<const char*, NumButtons> mapping = {{
+ "button_a", "button_b", "button_x", "button_y", "button_up", "button_down", "button_left",
+ "button_right", "button_l", "button_r", "button_start", "button_select", "button_zl",
+ "button_zr", "button_home",
}};
-static const std::array<Values, NUM_INPUTS> All = {{
- A, B, X, Y, L, R, ZL, ZR,
- START, SELECT, HOME, DUP, DDOWN, DLEFT, DRIGHT, CUP,
- CDOWN, CLEFT, CRIGHT, CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT, CIRCLE_MODIFIER,
+} // namespace NativeButton
+
+namespace NativeAnalog {
+enum Values {
+ CirclePad,
+ CStick,
+
+ NumAnalogs,
+};
+
+static const std::array<const char*, NumAnalogs> mapping = {{
+ "circle_pad", "c_stick",
}};
-}
+} // namespace NumAnalog
struct Values {
// CheckNew3DS
bool is_new_3ds;
// Controls
- std::array<int, NativeInput::NUM_INPUTS> input_mappings;
- float pad_circle_modifier_scale;
+ std::array<std::string, NativeButton::NumButtons> buttons;
+ std::array<std::string, NativeAnalog::NumAnalogs> analogs;
// Core
bool use_cpu_jit;
@@ -114,6 +119,10 @@ struct Values {
bool enable_audio_stretching;
std::string audio_device_id;
+ // Camera
+ std::array<std::string, Service::CAM::NumCameras> camera_name;
+ std::array<std::string, Service::CAM::NumCameras> camera_config;
+
// Debugging
bool use_gdbstub;
u16 gdbstub_port;