diff options
Diffstat (limited to 'src/core')
85 files changed, 3016 insertions, 1134 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index bd0e3c595..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,10 +20,10 @@ 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 @@ -33,13 +34,13 @@ set(SRCS 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 @@ -157,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 @@ -168,6 +172,7 @@ set(SRCS loader/smdh.cpp tracer/recorder.cpp memory.cpp + perf_stats.cpp settings.cpp ) @@ -176,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 @@ -194,14 +200,15 @@ 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 @@ -210,15 +217,17 @@ set(HEADERS 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 @@ -337,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 @@ -351,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 17572a74f..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. diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp index dd2fb167f..f454e7840 100644 --- a/src/core/file_sys/archive_extsavedata.cpp +++ b/src/core/file_sys/archive_extsavedata.cpp @@ -173,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 72ff05c65..679909d06 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp @@ -306,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/frontend/camera/factory.h b/src/core/frontend/camera/factory.h index d68be16e5..f46413fa7 100644 --- a/src/core/frontend/camera/factory.h +++ b/src/core/frontend/camera/factory.h @@ -16,8 +16,8 @@ public: /** * Creates a camera object based on the configuration string. - * @params config Configuration string to create the camera. The implementation can decide the - * meaning of this 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; diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 4f0f786ce..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) { @@ -106,8 +80,7 @@ void EmuWindow::AccelerometerChanged(float x, float y, float z) { 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 = static_cast<s16>(x * coef * stretch); gyro_y = static_cast<s16>(y * coef * stretch); @@ -116,17 +89,21 @@ void EmuWindow::GyroscopeChanged(float x, float y, float z) { void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) { Layout::FramebufferLayout layout; - switch (Settings::values.layout_option) { - case Settings::LayoutOption::SingleScreen: - layout = Layout::SingleFrameLayout(width, height, Settings::values.swap_screen); - break; - case Settings::LayoutOption::LargeScreen: - layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen); - break; - case Settings::LayoutOption::Default: - default: - layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen); - break; + if (Settings::values.custom_layout == true) { + layout = Layout::CustomFrameLayout(width, height); + } else { + switch (Settings::values.layout_option) { + case Settings::LayoutOption::SingleScreen: + layout = Layout::SingleFrameLayout(width, height, Settings::values.swap_screen); + break; + case Settings::LayoutOption::LargeScreen: + layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen); + break; + case Settings::LayoutOption::Default: + default: + layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen); + break; + } } NotifyFramebufferLayoutChanged(layout); } 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/function_wrappers.h b/src/core/hle/function_wrappers.h index 7875971ce..f6eb900f0 100644 --- a/src/core/hle/function_wrappers.h +++ b/src/core/hle/function_wrappers.h @@ -256,9 +256,9 @@ void Wrap() { func(((s64)PARAM(1) << 32) | PARAM(0)); } -template <void func(const char*)> +template <void func(const char*, int len)> void Wrap() { - func((char*)Memory::GetPointer(PARAM(0))); + func((char*)Memory::GetPointer(PARAM(0)), PARAM(1)); } template <void func(u8)> diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index 4e094faa7..3a5d481a5 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -10,7 +10,8 @@ namespace Kernel { -static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of header +/// Offset into command buffer of header +static const int kCommandHeaderOffset = 0x80; /** * Returns a pointer to the command buffer in the current thread's TLS @@ -18,12 +19,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); } + +/// Offset into static buffers, relative to command buffer header +static const int kStaticBuffersOffset = 0x100; + +/** + * 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 +55,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 +73,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 +97,7 @@ constexpr u32 CallingPidDesc() { return CallingPid; } -constexpr bool isHandleDescriptor(u32 descriptor) { +constexpr bool IsHandleDescriptor(u32 descriptor) { return (descriptor & 0xF) == 0x0; } @@ -91,18 +105,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 +137,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..06c4c5a85 --- /dev/null +++ b/src/core/hle/ipc_helpers.h @@ -0,0 +1,353 @@ +// 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); + } + + void Skip(unsigned size_in_words, bool set_to_null) { + if (set_to_null) + memset(cmdbuf + index, 0, size_in_words * sizeof(u32)); + index += size_in_words; + } + + /** + * @brief Retrieves the address of a static buffer, used when a buffer is needed for output + * @param buffer_id The index of the static buffer + * @param data_size If non-null, will store the size of the buffer + */ + VAddr PeekStaticBuffer(u8 buffer_id, size_t* data_size = nullptr) const { + u32* static_buffer = cmdbuf + Kernel::kStaticBuffersOffset / sizeof(u32) + buffer_id * 2; + if (data_size) + *data_size = StaticBufferDescInfo{static_buffer[0]}.size; + return static_buffer[1]; + } +}; + +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); + + template <typename First, typename... Other> + void Push(const First& first_value, const Other&... 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); + + // TODO : ensure that translate params are added after all regular params + template <typename... H> + void PushCopyHandles(H... handles); + + template <typename... H> + void PushMoveHandles(H... handles); + + void PushCurrentPIDHandle(); + + void PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id); + + void PushMappedBuffer(VAddr buffer_vaddr, u32 size, MappedBufferPermissions perms); +}; + +/// Push /// + +template <> +inline void RequestBuilder::Push(u32 value) { + cmdbuf[index++] = value; +} + +template <typename T> +void RequestBuilder::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 +} + +template <> +inline void RequestBuilder::Push(u8 value) { + PushRaw(value); +} + +template <> +inline void RequestBuilder::Push(u16 value) { + PushRaw(value); +} + +template <> +inline void RequestBuilder::Push(u64 value) { + Push(static_cast<u32>(value)); + Push(static_cast<u32>(value >> 32)); +} + +template <> +inline void RequestBuilder::Push(bool value) { + Push(static_cast<u8>(value)); +} + +template <> +inline void RequestBuilder::Push(ResultCode value) { + Push(value.raw); +} + +template <typename First, typename... Other> +void RequestBuilder::Push(const First& first_value, const Other&... other_values) { + Push(first_value); + Push(other_values...); +} + +template <typename... H> +inline void RequestBuilder::PushCopyHandles(H... handles) { + Push(CopyHandleDesc(sizeof...(H))); + Push(static_cast<Kernel::Handle>(handles)...); +} + +template <typename... H> +inline void RequestBuilder::PushMoveHandles(H... handles) { + Push(MoveHandleDesc(sizeof...(H))); + Push(static_cast<Kernel::Handle>(handles)...); +} + +inline void RequestBuilder::PushCurrentPIDHandle() { + Push(CallingPidDesc()); + Push(u32(0)); +} + +inline void RequestBuilder::PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id) { + Push(StaticBufferDesc(size, buffer_id)); + Push(buffer_vaddr); +} + +inline void RequestBuilder::PushMappedBuffer(VAddr buffer_vaddr, u32 size, + MappedBufferPermissions perms) { + Push(MappedBufferDesc(size, perms)); + Push(buffer_vaddr); +} + +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); + + /** + * @brief Reads the next normal parameters as a struct, by copying it into a new value + * @note: The output class must be correctly packed/padded to fit hardware layout. + */ + template <typename T> + T PopRaw(); +}; + +/// Pop /// + +template <> +inline u32 RequestParser::Pop() { + return cmdbuf[index++]; +} + +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 +} + +template <typename T> +T RequestParser::PopRaw() { + T value; + PopRaw(value); + return value; +} + +template <> +inline u8 RequestParser::Pop() { + return PopRaw<u8>(); +} + +template <> +inline u16 RequestParser::Pop() { + return PopRaw<u16>(); +} + +template <> +inline u64 RequestParser::Pop() { + const u64 lsw = Pop<u32>(); + const u64 msw = Pop<u32>(); + return msw << 32 | lsw; +} + +template <> +inline bool RequestParser::Pop() { + return Pop<u8>() != 0; +} + +template <> +inline ResultCode RequestParser::Pop() { + 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>(); +} + +} // 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..366d1eacf 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 { @@ -47,13 +49,13 @@ void SendParameter(const MessageParameter& parameter) { } void Initialize(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - u32 flags = cmd_buff[2]; - - cmd_buff[2] = IPC::CopyHandleDesc(2); - cmd_buff[3] = Kernel::g_handle_table.Create(notification_event).MoveFrom(); - cmd_buff[4] = Kernel::g_handle_table.Create(parameter_event).MoveFrom(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2, 2, 0); // 0x20080 + u32 app_id = rp.Pop<u32>(); + u32 flags = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 3); + rb.Push(RESULT_SUCCESS); + rb.PushCopyHandles(Kernel::g_handle_table.Create(notification_event).MoveFrom(), + Kernel::g_handle_table.Create(parameter_event).MoveFrom()); // TODO(bunnei): Check if these events are cleared every time Initialize is called. notification_event->Clear(); @@ -62,18 +64,16 @@ void Initialize(Service::Interface* self) { ASSERT_MSG((nullptr != lock), "Cannot initialize without lock"); lock->Release(); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_DEBUG(Service_APT, "called app_id=0x%08X, flags=0x%08X", app_id, flags); } void GetSharedFont(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x44, 0, 0); // 0x00440000 + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); if (!shared_font_mem) { LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds"); - cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2); - cmd_buff[1] = -1; // TODO: Find the right error code + rb.Push<u32>(-1); // TODO: Find the right error code + rb.Skip(1 + 2, true); return; } @@ -85,103 +85,110 @@ void GetSharedFont(Service::Interface* self) { BCFNT::RelocateSharedFont(shared_font_mem, target_address); shared_font_relocated = true; } - cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + rb.Push(RESULT_SUCCESS); // No error // Since the SharedMemory interface doesn't provide the address at which the memory was // allocated, the real APT service calculates this address by scanning the entire address space // (using svcQueryMemory) and searches for an allocation of the same size as the Shared Font. - cmd_buff[2] = target_address; - cmd_buff[3] = IPC::CopyHandleDesc(); - cmd_buff[4] = Kernel::g_handle_table.Create(shared_font_mem).MoveFrom(); + rb.Push(target_address); + rb.PushCopyHandles(Kernel::g_handle_table.Create(shared_font_mem).MoveFrom()); } void NotifyToWait(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x43, 1, 0); // 0x430040 + u32 app_id = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) app_id=%u", app_id); } void GetLockHandle(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1, 1, 0); // 0x10040 + // Bits [0:2] are the applet type (System, Library, etc) // Bit 5 tells the application that there's a pending APT parameter, // this will cause the app to wait until parameter_event is signaled. - u32 applet_attributes = cmd_buff[1]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - cmd_buff[2] = applet_attributes; // Applet Attributes, this value is passed to Enable. - cmd_buff[3] = 0; // Least significant bit = power button state - cmd_buff[4] = IPC::CopyHandleDesc(); - cmd_buff[5] = Kernel::g_handle_table.Create(lock).MoveFrom(); - - LOG_WARNING(Service_APT, "(STUBBED) called handle=0x%08X applet_attributes=0x%08X", cmd_buff[5], + u32 applet_attributes = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(applet_attributes); // Applet Attributes, this value is passed to Enable. + rb.Push<u32>(0); // Least significant bit = power button state + Kernel::Handle handle_copy = Kernel::g_handle_table.Create(lock).MoveFrom(); + rb.PushCopyHandles(handle_copy); + + LOG_WARNING(Service_APT, "(STUBBED) called handle=0x%08X applet_attributes=0x%08X", handle_copy, applet_attributes); } void Enable(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 attributes = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - parameter_event->Signal(); // Let the application know that it has been started + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3, 1, 0); // 0x30040 + u32 attributes = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error + parameter_event->Signal(); // Let the application know that it has been started LOG_WARNING(Service_APT, "(STUBBED) called attributes=0x%08X", attributes); } void GetAppletManInfo(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 unk = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0; - cmd_buff[3] = 0; - cmd_buff[4] = static_cast<u32>(AppletId::HomeMenu); // Home menu AppID - cmd_buff[5] = static_cast<u32>(AppletId::Application); // TODO(purpasmart96): Do this correctly + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x5, 1, 0); // 0x50040 + u32 unk = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push<u32>(0); + rb.Push<u32>(0); + rb.Push(static_cast<u32>(AppletId::HomeMenu)); // Home menu AppID + rb.Push(static_cast<u32>(AppletId::Application)); // TODO(purpasmart96): Do this correctly LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X", unk); } void IsRegistered(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 1, 0); // 0x90040 + u32 app_id = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error // TODO(Subv): An application is considered "registered" if it has already called APT::Enable // handle this properly once we implement multiprocess support. - cmd_buff[2] = 0; // Set to not registered by default + bool is_registered = false; // Set to not registered by default if (app_id == static_cast<u32>(AppletId::AnyLibraryApplet)) { - cmd_buff[2] = HLE::Applets::IsLibraryAppletRunning() ? 1 : 0; + is_registered = HLE::Applets::IsLibraryAppletRunning(); } else if (auto applet = HLE::Applets::Applet::Get(static_cast<AppletId>(app_id))) { - cmd_buff[2] = 1; // Set to registered + is_registered = true; // Set to registered } + rb.Push(is_registered); + LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); } void InquireNotification(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = static_cast<u32>(SignalType::None); // Signal type + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xB, 1, 0); // 0xB0040 + u32 app_id = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(static_cast<u32>(SignalType::None)); // Signal type LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); } void SendParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 src_app_id = cmd_buff[1]; - u32 dst_app_id = cmd_buff[2]; - u32 signal_type = cmd_buff[3]; - u32 buffer_size = cmd_buff[4]; - u32 value = cmd_buff[5]; - u32 handle = cmd_buff[6]; - u32 size = cmd_buff[7]; - u32 buffer = cmd_buff[8]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xC, 4, 4); // 0xC0104 + u32 src_app_id = rp.Pop<u32>(); + u32 dst_app_id = rp.Pop<u32>(); + u32 signal_type = rp.Pop<u32>(); + u32 buffer_size = rp.Pop<u32>(); + Kernel::Handle handle = rp.PopHandle(); + size_t size; + VAddr buffer = rp.PopStaticBuffer(&size); std::shared_ptr<HLE::Applets::Applet> dest_applet = HLE::Applets::Applet::Get(static_cast<AppletId>(dst_app_id)); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + if (dest_applet == nullptr) { LOG_ERROR(Service_APT, "Unknown applet id=0x%08X", dst_app_id); - cmd_buff[1] = -1; // TODO(Subv): Find the right error code + rb.Push<u32>(-1); // TODO(Subv): Find the right error code return; } @@ -193,88 +200,104 @@ void SendParameter(Service::Interface* self) { param.buffer.resize(buffer_size); Memory::ReadBlock(buffer, param.buffer.data(), param.buffer.size()); - cmd_buff[1] = dest_applet->ReceiveParameter(param).raw; + rb.Push(dest_applet->ReceiveParameter(param)); - LOG_WARNING( - Service_APT, - "(STUBBED) called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X," - "buffer_size=0x%08X, value=0x%08X, handle=0x%08X, size=0x%08X, in_param_buffer_ptr=0x%08X", - src_app_id, dst_app_id, signal_type, buffer_size, value, handle, size, buffer); + LOG_WARNING(Service_APT, + "(STUBBED) called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X," + "buffer_size=0x%08X, handle=0x%08X, size=0x%08zX, in_param_buffer_ptr=0x%08X", + src_app_id, dst_app_id, signal_type, buffer_size, handle, size, buffer); } void ReceiveParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - u32 buffer_size = cmd_buff[2]; - VAddr buffer = cmd_buff[0x104 >> 2]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = next_parameter.sender_id; - cmd_buff[3] = next_parameter.signal; // Signal type - cmd_buff[4] = next_parameter.buffer.size(); // Parameter buffer size - cmd_buff[5] = 0x10; - cmd_buff[6] = 0; - if (next_parameter.object != nullptr) - cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom(); - cmd_buff[7] = (next_parameter.buffer.size() << 14) | 2; - cmd_buff[8] = buffer; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xD, 2, 0); // 0xD0080 + u32 app_id = rp.Pop<u32>(); + u32 buffer_size = rp.Pop<u32>(); + + size_t static_buff_size; + VAddr buffer = rp.PeekStaticBuffer(0, &static_buff_size); + if (buffer_size > static_buff_size) + LOG_WARNING( + Service_APT, + "buffer_size is bigger than the size in the buffer descriptor (0x%08X > 0x%08zX)", + buffer_size, static_buff_size); + + IPC::RequestBuilder rb = rp.MakeBuilder(4, 4); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(next_parameter.sender_id); + rb.Push(next_parameter.signal); // Signal type + ASSERT_MSG(next_parameter.buffer.size() <= buffer_size, "Input static buffer is too small !"); + rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size + + rb.PushMoveHandles((next_parameter.object != nullptr) + ? Kernel::g_handle_table.Create(next_parameter.object).MoveFrom() + : 0); + rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0); Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size()); - LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); + LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size); } void GlanceParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[1]; - u32 buffer_size = cmd_buff[2]; - VAddr buffer = cmd_buff[0x104 >> 2]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xE, 2, 0); // 0xE0080 + u32 app_id = rp.Pop<u32>(); + u32 buffer_size = rp.Pop<u32>(); + + size_t static_buff_size; + VAddr buffer = rp.PeekStaticBuffer(0, &static_buff_size); + if (buffer_size > static_buff_size) + LOG_WARNING( + Service_APT, + "buffer_size is bigger than the size in the buffer descriptor (0x%08X > 0x%08zX)", + buffer_size, static_buff_size); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = next_parameter.sender_id; - cmd_buff[3] = next_parameter.signal; // Signal type - cmd_buff[4] = next_parameter.buffer.size(); // Parameter buffer size - cmd_buff[5] = 0x10; - cmd_buff[6] = 0; - if (next_parameter.object != nullptr) - cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom(); - cmd_buff[7] = (next_parameter.buffer.size() << 14) | 2; - cmd_buff[8] = buffer; + IPC::RequestBuilder rb = rp.MakeBuilder(4, 4); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(next_parameter.sender_id); + rb.Push(next_parameter.signal); // Signal type + ASSERT_MSG(next_parameter.buffer.size() <= buffer_size, "Input static buffer is too small !"); + rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size - Memory::WriteBlock(buffer, next_parameter.buffer.data(), - std::min(static_cast<size_t>(buffer_size), next_parameter.buffer.size())); + rb.PushCopyHandles((next_parameter.object != nullptr) + ? Kernel::g_handle_table.Create(next_parameter.object).MoveFrom() + : 0); + rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0); + + Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size()); - LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); + LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size); } void CancelParameter(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 flag1 = cmd_buff[1]; - u32 unk = cmd_buff[2]; - u32 flag2 = cmd_buff[3]; - u32 app_id = cmd_buff[4]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 1; // Set to Success - - LOG_WARNING(Service_APT, - "(STUBBED) called flag1=0x%08X, unk=0x%08X, flag2=0x%08X, app_id=0x%08X", flag1, - unk, flag2, app_id); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xF, 4, 0); // 0xF0100 + + u32 check_sender = rp.Pop<u32>(); + u32 sender_appid = rp.Pop<u32>(); + u32 check_receiver = rp.Pop<u32>(); + u32 receiver_appid = rp.Pop<u32>(); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(true); // Set to Success + + LOG_WARNING(Service_APT, "(STUBBED) called check_sender=0x%08X, sender_appid=0x%08X, " + "check_receiver=0x%08X, receiver_appid=0x%08X", + check_sender, sender_appid, check_receiver, receiver_appid); } void PrepareToStartApplication(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 title_info1 = cmd_buff[1]; - u32 title_info2 = cmd_buff[2]; - u32 title_info3 = cmd_buff[3]; - u32 title_info4 = cmd_buff[4]; - u32 flags = cmd_buff[5]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x15, 5, 0); // 0x00150140 + u32 title_info1 = rp.Pop<u32>(); + u32 title_info2 = rp.Pop<u32>(); + u32 title_info3 = rp.Pop<u32>(); + u32 title_info4 = rp.Pop<u32>(); + u32 flags = rp.Pop<u32>(); if (flags & 0x00000100) { unknown_ns_state_field = 1; } - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) called title_info1=0x%08X, title_info2=0x%08X, title_info3=0x%08X," @@ -283,172 +306,188 @@ void PrepareToStartApplication(Service::Interface* self) { } void StartApplication(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 buffer1_size = cmd_buff[1]; - u32 buffer2_size = cmd_buff[2]; - u32 flag = cmd_buff[3]; - u32 size1 = cmd_buff[4]; - u32 buffer1_ptr = cmd_buff[5]; - u32 size2 = cmd_buff[6]; - u32 buffer2_ptr = cmd_buff[7]; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1B, 3, 4); // 0x001B00C4 + u32 buffer1_size = rp.Pop<u32>(); + u32 buffer2_size = rp.Pop<u32>(); + u32 flag = rp.Pop<u32>(); + size_t size1; + VAddr buffer1_ptr = rp.PopStaticBuffer(&size1); + size_t size2; + VAddr buffer2_ptr = rp.PopStaticBuffer(&size2); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) called buffer1_size=0x%08X, buffer2_size=0x%08X, flag=0x%08X," - "size1=0x%08X, buffer1_ptr=0x%08X, size2=0x%08X, buffer2_ptr=0x%08X", + "size1=0x%08zX, buffer1_ptr=0x%08X, size2=0x%08zX, buffer2_ptr=0x%08X", buffer1_size, buffer2_size, flag, size1, buffer1_ptr, size2, buffer2_ptr); } void AppletUtility(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x4B, 3, 2); // 0x004B00C2 // These are from 3dbrew - I'm not really sure what they're used for. - u32 command = cmd_buff[1]; - u32 buffer1_size = cmd_buff[2]; - u32 buffer2_size = cmd_buff[3]; - u32 buffer1_addr = cmd_buff[5]; - u32 buffer2_addr = cmd_buff[65]; + u32 utility_command = rp.Pop<u32>(); + u32 input_size = rp.Pop<u32>(); + u32 output_size = rp.Pop<u32>(); + VAddr input_addr = rp.PopStaticBuffer(); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + VAddr output_addr = rp.PeekStaticBuffer(0); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, - "(STUBBED) called command=0x%08X, buffer1_size=0x%08X, buffer2_size=0x%08X, " - "buffer1_addr=0x%08X, buffer2_addr=0x%08X", - command, buffer1_size, buffer2_size, buffer1_addr, buffer2_addr); + "(STUBBED) called command=0x%08X, input_size=0x%08X, output_size=0x%08X, " + "input_addr=0x%08X, output_addr=0x%08X", + utility_command, input_size, output_size, input_addr, output_addr); } void SetAppCpuTimeLimit(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 value = cmd_buff[1]; - cpu_percent = cmd_buff[2]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x4F, 2, 0); // 0x4F0080 + u32 value = rp.Pop<u32>(); + cpu_percent = rp.Pop<u32>(); if (value != 1) { LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); } - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) called cpu_percent=%u, value=%u", cpu_percent, value); } void GetAppCpuTimeLimit(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 value = cmd_buff[1]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x50, 1, 0); // 0x500040 + u32 value = rp.Pop<u32>(); if (value != 1) { LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); } - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = cpu_percent; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(cpu_percent); LOG_WARNING(Service_APT, "(STUBBED) called value=%u", value); } void PrepareToStartLibraryApplet(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - AppletId applet_id = static_cast<AppletId>(cmd_buff[1]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x18, 1, 0); // 0x180040 + AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>()); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); auto applet = HLE::Applets::Applet::Get(applet_id); if (applet) { LOG_WARNING(Service_APT, "applet has already been started id=%08X", applet_id); - cmd_buff[1] = RESULT_SUCCESS.raw; + rb.Push(RESULT_SUCCESS); } else { - cmd_buff[1] = HLE::Applets::Applet::Create(applet_id).raw; + rb.Push(HLE::Applets::Applet::Create(applet_id)); } LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id); } void PreloadLibraryApplet(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - AppletId applet_id = static_cast<AppletId>(cmd_buff[1]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x16, 1, 0); // 0x160040 + AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>()); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); auto applet = HLE::Applets::Applet::Get(applet_id); if (applet) { LOG_WARNING(Service_APT, "applet has already been started id=%08X", applet_id); - cmd_buff[1] = RESULT_SUCCESS.raw; + rb.Push(RESULT_SUCCESS); } else { - cmd_buff[1] = HLE::Applets::Applet::Create(applet_id).raw; + rb.Push(HLE::Applets::Applet::Create(applet_id)); } LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id); } void StartLibraryApplet(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - AppletId applet_id = static_cast<AppletId>(cmd_buff[1]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1E, 2, 4); // 0x1E0084 + AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>()); std::shared_ptr<HLE::Applets::Applet> applet = HLE::Applets::Applet::Get(applet_id); LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id); if (applet == nullptr) { LOG_ERROR(Service_APT, "unknown applet id=%08X", applet_id); - cmd_buff[1] = -1; // TODO(Subv): Find the right error code + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0, false); + rb.Push<u32>(-1); // TODO(Subv): Find the right error code return; } - size_t buffer_size = cmd_buff[2]; - VAddr buffer_addr = cmd_buff[6]; + size_t buffer_size = rp.Pop<u32>(); + Kernel::Handle handle = rp.PopHandle(); + VAddr buffer_addr = rp.PopStaticBuffer(); AppletStartupParameter parameter; - parameter.object = Kernel::g_handle_table.GetGeneric(cmd_buff[4]); + parameter.object = Kernel::g_handle_table.GetGeneric(handle); parameter.buffer.resize(buffer_size); Memory::ReadBlock(buffer_addr, parameter.buffer.data(), parameter.buffer.size()); - cmd_buff[1] = applet->Start(parameter).raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(applet->Start(parameter)); } void CancelLibraryApplet(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 exiting = cmd_buff[1] & 0xFF; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3B, 1, 0); // 0x003B0040 + bool exiting = rp.Pop<bool>(); - cmd_buff[1] = 1; // TODO: Find the return code meaning + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push<u32>(1); // TODO: Find the return code meaning - LOG_WARNING(Service_APT, "(STUBBED) called exiting=%u", exiting); + LOG_WARNING(Service_APT, "(STUBBED) called exiting=%d", exiting); } void SetScreenCapPostPermission(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x55, 1, 0); // 0x00550040 - screen_capture_post_permission = static_cast<ScreencapPostPermission>(cmd_buff[1] & 0xF); + screen_capture_post_permission = static_cast<ScreencapPostPermission>(rp.Pop<u32>() & 0xF); - cmd_buff[0] = IPC::MakeHeader(0x55, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); // No error LOG_WARNING(Service_APT, "(STUBBED) screen_capture_post_permission=%u", screen_capture_post_permission); } void GetScreenCapPostPermission(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x56, 0, 0); // 0x00560000 - cmd_buff[0] = IPC::MakeHeader(0x56, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = static_cast<u32>(screen_capture_post_permission); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error + rb.Push(static_cast<u32>(screen_capture_post_permission)); LOG_WARNING(Service_APT, "(STUBBED) screen_capture_post_permission=%u", screen_capture_post_permission); } void GetAppletInfo(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - auto app_id = static_cast<AppletId>(cmd_buff[1]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x6, 1, 0); // 0x60040 + auto app_id = static_cast<AppletId>(rp.Pop<u32>()); if (auto applet = HLE::Applets::Applet::Get(app_id)) { // TODO(Subv): Get the title id for the current applet and write it in the response[2-3] - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[4] = static_cast<u32>(Service::FS::MediaType::NAND); - cmd_buff[5] = 1; // Registered - cmd_buff[6] = 1; // Loaded - cmd_buff[7] = 0; // Applet Attributes + IPC::RequestBuilder rb = rp.MakeBuilder(7, 0); + rb.Push(RESULT_SUCCESS); + u64 title_id = 0; + rb.Push(title_id); + rb.Push(static_cast<u32>(Service::FS::MediaType::NAND)); + rb.Push(true); // Registered + rb.Push(true); // Loaded + rb.Push<u32>(0); // Applet Attributes } else { - cmd_buff[1] = ResultCode(ErrorDescription::NotFound, ErrorModule::Applet, - ErrorSummary::NotFound, ErrorLevel::Status) - .raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, + ErrorLevel::Status)); } LOG_WARNING(Service_APT, "(stubbed) called appid=%u", app_id); } void GetStartupArgument(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 parameter_size = cmd_buff[1]; - StartupArgumentType startup_argument_type = static_cast<StartupArgumentType>(cmd_buff[2]); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x51, 2, 0); // 0x00510080 + u32 parameter_size = rp.Pop<u32>(); + StartupArgumentType startup_argument_type = static_cast<StartupArgumentType>(rp.Pop<u8>()); if (parameter_size >= 0x300) { LOG_ERROR( @@ -458,7 +497,14 @@ void GetStartupArgument(Service::Interface* self) { return; } - u32 addr = cmd_buff[65]; + size_t static_buff_size; + VAddr addr = rp.PeekStaticBuffer(0, &static_buff_size); + if (parameter_size > static_buff_size) + LOG_WARNING( + Service_APT, + "parameter_size is bigger than the size in the buffer descriptor (0x%08X > 0x%08zX)", + parameter_size, static_buff_size); + if (addr && parameter_size) { Memory::ZeroBlock(addr, parameter_size); } @@ -466,30 +512,133 @@ void GetStartupArgument(Service::Interface* self) { LOG_WARNING(Service_APT, "(stubbed) called startup_argument_type=%u , parameter_size=0x%08x", startup_argument_type, parameter_size); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); + rb.PushStaticBuffer(addr, parameter_size, 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(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x101, 0, 0); // 0x01010000 + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); if (unknown_ns_state_field) { - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); } else { - PTM::CheckNew3DS(self); + PTM::CheckNew3DS(rb); } - cmd_buff[0] = IPC::MakeHeader(0x101, 2, 0); LOG_WARNING(Service_APT, "(STUBBED) called"); } void CheckNew3DS(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x102, 0, 0); // 0x01020000 + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); - PTM::CheckNew3DS(self); + PTM::CheckNew3DS(rb); - cmd_buff[0] = IPC::MakeHeader(0x102, 2, 0); LOG_WARNING(Service_APT, "(STUBBED) called"); } 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.h b/src/core/hle/service/cam/cam.h index f6bff8bc6..34a9c8479 100644 --- a/src/core/hle/service/cam/cam.h +++ b/src/core/hle/service/cam/cam.h @@ -518,7 +518,7 @@ void FlipImage(Service::Interface* self); void SetDetailSize(Service::Interface* self); /** - * Sets camera resolution from preset resolution parameters. . + * Sets camera resolution from preset resolution parameters. * Inputs: * 0: 0x001F00C0 * 1: u8 selected camera diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 6f13cde27..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) { 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/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index 08fade320..ef6c5ebe3 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -1,16 +1,49 @@ -// Copyright 2014 Citra Emulator Project +// Copyright 2017 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cstring> +#include <unordered_map> +#include <vector> #include "common/common_types.h" #include "common/logging/log.h" +#include "core/core_timing.h" #include "core/hle/kernel/event.h" +#include "core/hle/kernel/shared_memory.h" +#include "core/hle/result.h" #include "core/hle/service/nwm/nwm_uds.h" +#include "core/memory.h" namespace Service { namespace NWM { -static Kernel::SharedPtr<Kernel::Event> uds_handle_event; +// Event that is signaled every time the connection status changes. +static Kernel::SharedPtr<Kernel::Event> connection_status_event; + +// Shared memory provided by the application to store the receive buffer. +// This is not currently used. +static Kernel::SharedPtr<Kernel::SharedMemory> recv_buffer_memory; + +// Connection status of this 3DS. +static ConnectionStatus connection_status{}; + +// Node information about the current 3DS. +// TODO(Subv): Keep an array of all nodes connected to the network, +// that data has to be retransmitted in every beacon frame. +static NodeInfo node_info; + +// Mapping of bind node ids to their respective events. +static std::unordered_map<u32, Kernel::SharedPtr<Kernel::Event>> bind_node_events; + +// The WiFi network channel that the network is currently on. +// Since we're not actually interacting with physical radio waves, this is just a dummy value. +static u8 network_channel = DefaultNetworkChannel; + +// Information about the network that we're currently connected to. +static NetworkInfo network_info; + +// Event that will generate and send the 802.11 beacon frames. +static int beacon_broadcast_event; /** * NWM_UDS::Shutdown service function @@ -32,14 +65,14 @@ static void Shutdown(Interface* self) { /** * NWM_UDS::RecvBeaconBroadcastData service function + * Returns the raw beacon data for nearby networks that match the supplied WlanCommId. * Inputs: * 1 : Output buffer max size - * 2 : Unknown - * 3 : Unknown - * 4 : MAC address? - * 6-14 : Unknown, usually zero / uninitialized? - * 15 : WLan Comm ID - * 16 : This is the ID also located at offset 0xE in the CTR-generation structure. + * 2-3 : Unknown + * 4-5 : Host MAC address. + * 6-14 : Unused + * 15 : WLan Comm Id + * 16 : Id * 17 : Value 0 * 18 : Input handle * 19 : (Size<<4) | 12 @@ -77,42 +110,274 @@ static void RecvBeaconBroadcastData(Interface* self) { /** * NWM_UDS::Initialize service function * Inputs: - * 1 : Unknown - * 2-11 : Input Structure - * 12 : Unknown u16 + * 1 : Shared memory size + * 2-11 : Input NodeInfo Structure + * 12 : 2-byte Version * 13 : Value 0 - * 14 : Handle + * 14 : Shared memory handle * Outputs: * 0 : Return header * 1 : Result of function, 0 on success, otherwise error code * 2 : Value 0 - * 3 : Output handle + * 3 : Output event handle */ static void InitializeWithVersion(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 unk1 = cmd_buff[1]; - u32 unk2 = cmd_buff[12]; - u32 value = cmd_buff[13]; - u32 handle = cmd_buff[14]; - - // Because NWM service is not implemented at all, we stub the Initialize function with an error - // code instead of success to prevent games from using the service and from causing more issues. - // The error code is from a real 3DS with wifi off, thus believed to be "network disabled". - /* - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; - cmd_buff[3] = Kernel::g_handle_table.Create(uds_handle_event) - .MoveFrom(); // TODO(purpasmart): Verify if this is a event handle - */ - cmd_buff[0] = IPC::MakeHeader(0x1B, 1, 2); - cmd_buff[1] = ResultCode(static_cast<ErrorDescription>(2), ErrorModule::UDS, - ErrorSummary::StatusChanged, ErrorLevel::Status) - .raw; - cmd_buff[2] = 0; - cmd_buff[3] = 0; - - LOG_WARNING(Service_NWM, "(STUBBED) called unk1=0x%08X, unk2=0x%08X, value=%u, handle=0x%08X", - unk1, unk2, value, handle); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1B, 12, 2); + + u32 sharedmem_size = rp.Pop<u32>(); + + // Update the node information with the data the game gave us. + rp.PopRaw(node_info); + + u16 version; + rp.PopRaw(version); + Kernel::Handle sharedmem_handle = rp.PopHandle(); + + recv_buffer_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(sharedmem_handle); + + ASSERT_MSG(recv_buffer_memory->size == sharedmem_size, "Invalid shared memory size."); + + // Reset the connection status, it contains all zeros after initialization, + // except for the actual status value. + connection_status = {}; + connection_status.status = static_cast<u32>(NetworkStatus::NotConnected); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).MoveFrom()); + + LOG_DEBUG(Service_NWM, "called sharedmem_size=0x%08X, version=0x%08X, sharedmem_handle=0x%08X", + sharedmem_size, version, sharedmem_handle); +} + +/** + * NWM_UDS::GetConnectionStatus service function. + * Returns the connection status structure for the currently open network connection. + * This structure contains information about the connection, + * like the number of connected nodes, etc. + * Inputs: + * 0 : Command header. + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + * 2-13 : Channel of the current WiFi network connection. + */ +static void GetConnectionStatus(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xB, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(13, 0); + + rb.Push(RESULT_SUCCESS); + rb.PushRaw(connection_status); + + LOG_DEBUG(Service_NWM, "called"); +} + +/** + * NWM_UDS::Bind service function. + * Binds a BindNodeId to a data channel and retrieves a data event. + * Inputs: + * 1 : BindNodeId + * 2 : Receive buffer size. + * 3 : u8 Data channel to bind to. + * 4 : Network node id. + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Copy handle descriptor. + * 3 : Data available event handle. + */ +static void Bind(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x12, 4, 0); + + u32 bind_node_id = rp.Pop<u32>(); + u32 recv_buffer_size = rp.Pop<u32>(); + u8 data_channel; + rp.PopRaw(data_channel); + u16 network_node_id; + rp.PopRaw(network_node_id); + + // TODO(Subv): Store the data channel and verify it when receiving data frames. + + LOG_DEBUG(Service_NWM, "called"); + + if (data_channel == 0) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::UDS, + ErrorSummary::WrongArgument, ErrorLevel::Usage)); + return; + } + + // Create a new event for this bind node. + // TODO(Subv): Signal this event when new data is received on this data channel. + auto event = Kernel::Event::Create(Kernel::ResetType::OneShot, + "NWM::BindNodeEvent" + std::to_string(bind_node_id)); + bind_node_events[bind_node_id] = event; + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + + rb.Push(RESULT_SUCCESS); + rb.PushCopyHandles(Kernel::g_handle_table.Create(event).MoveFrom()); +} + +/** + * NWM_UDS::BeginHostingNetwork service function. + * Creates a network and starts broadcasting its presence. + * Inputs: + * 1 : Passphrase buffer size. + * 3 : VAddr of the NetworkInfo structure. + * 5 : VAddr of the passphrase. + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ +static void BeginHostingNetwork(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1D, 1, 4); + + const u32 passphrase_size = rp.Pop<u32>(); + + size_t desc_size; + const VAddr network_info_address = rp.PopStaticBuffer(&desc_size, false); + ASSERT(desc_size == sizeof(NetworkInfo)); + const VAddr passphrase_address = rp.PopStaticBuffer(&desc_size, false); + ASSERT(desc_size == passphrase_size); + + // TODO(Subv): Store the passphrase and verify it when attempting a connection. + + LOG_DEBUG(Service_NWM, "called"); + + Memory::ReadBlock(network_info_address, &network_info, sizeof(NetworkInfo)); + + // The real UDS module throws a fatal error if this assert fails. + ASSERT_MSG(network_info.max_nodes > 1, "Trying to host a network of only one member."); + + connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsHost); + connection_status.max_nodes = network_info.max_nodes; + + // There's currently only one node in the network (the host). + connection_status.total_nodes = 1; + // The host is always the first node + connection_status.network_node_id = 1; + node_info.network_node_id = 1; + // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken. + connection_status.node_bitmask |= 1; + + // If the game has a preferred channel, use that instead. + if (network_info.channel != 0) + network_channel = network_info.channel; + + connection_status_event->Signal(); + + // Start broadcasting the network, send a beacon frame every 102.4ms. + CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU), + beacon_broadcast_event, 0); + + LOG_WARNING(Service_NWM, + "An UDS network has been created, but broadcasting it is unimplemented."); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +/** + * NWM_UDS::DestroyNetwork service function. + * Closes the network that we're currently hosting. + * Inputs: + * 0 : Command header. + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ +static void DestroyNetwork(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x08, 0, 0); + + // TODO(Subv): Find out what happens if this is called while + // no network is being hosted. + + // Unschedule the beacon broadcast event. + CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0); + + connection_status.status = static_cast<u8>(NetworkStatus::NotConnected); + connection_status_event->Signal(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + rb.Push(RESULT_SUCCESS); + + LOG_WARNING(Service_NWM, "called"); +} + +/** + * NWM_UDS::GetChannel service function. + * Returns the WiFi channel in which the network we're connected to is transmitting. + * Inputs: + * 0 : Command header. + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Channel of the current WiFi network connection. + */ +static void GetChannel(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + + bool is_connected = connection_status.status != static_cast<u32>(NetworkStatus::NotConnected); + + u8 channel = is_connected ? network_channel : 0; + + rb.Push(RESULT_SUCCESS); + rb.PushRaw(channel); + + LOG_DEBUG(Service_NWM, "called"); +} + +/** + * NWM_UDS::SetApplicationData service function. + * Updates the application data that is being broadcast in the beacon frames + * for the network that we're hosting. + * Inputs: + * 1 : Data size. + * 3 : VAddr of the data. + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Channel of the current WiFi network connection. + */ +static void SetApplicationData(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 1, 2); + + u32 size = rp.Pop<u32>(); + + size_t desc_size; + const VAddr address = rp.PopStaticBuffer(&desc_size, false); + ASSERT(desc_size == size); + + LOG_DEBUG(Service_NWM, "called"); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + if (size > ApplicationDataSize) { + rb.Push(ResultCode(ErrorDescription::TooLarge, ErrorModule::UDS, + ErrorSummary::WrongArgument, ErrorLevel::Usage)); + return; + } + + network_info.application_data_size = size; + Memory::ReadBlock(address, network_info.application_data.data(), size); + + rb.Push(RESULT_SUCCESS); +} + +// Sends a 802.11 beacon frame with information about the current network. +static void BeaconBroadcastCallback(u64 userdata, int cycles_late) { + // Don't do anything if we're not actually hosting a network + if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) + return; + + // TODO(Subv): Actually generate the beacon and send it. + + // Start broadcasting the network, send a beacon frame every 102.4ms. + CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late, + beacon_broadcast_event, 0); } const Interface::FunctionInfo FunctionTable[] = { @@ -123,23 +388,23 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00050040, nullptr, "EjectClient"}, {0x00060000, nullptr, "EjectSpectator"}, {0x00070080, nullptr, "UpdateNetworkAttribute"}, - {0x00080000, nullptr, "DestroyNetwork"}, + {0x00080000, DestroyNetwork, "DestroyNetwork"}, {0x00090442, nullptr, "ConnectNetwork (deprecated)"}, {0x000A0000, nullptr, "DisconnectNetwork"}, - {0x000B0000, nullptr, "GetConnectionStatus"}, + {0x000B0000, GetConnectionStatus, "GetConnectionStatus"}, {0x000D0040, nullptr, "GetNodeInformation"}, {0x000E0006, nullptr, "DecryptBeaconData (deprecated)"}, {0x000F0404, RecvBeaconBroadcastData, "RecvBeaconBroadcastData"}, - {0x00100042, nullptr, "SetApplicationData"}, + {0x00100042, SetApplicationData, "SetApplicationData"}, {0x00110040, nullptr, "GetApplicationData"}, - {0x00120100, nullptr, "Bind"}, + {0x00120100, Bind, "Bind"}, {0x00130040, nullptr, "Unbind"}, {0x001400C0, nullptr, "PullPacket"}, {0x00150080, nullptr, "SetMaxSendDelay"}, {0x00170182, nullptr, "SendTo"}, - {0x001A0000, nullptr, "GetChannel"}, + {0x001A0000, GetChannel, "GetChannel"}, {0x001B0302, InitializeWithVersion, "InitializeWithVersion"}, - {0x001D0044, nullptr, "BeginHostingNetwork"}, + {0x001D0044, BeginHostingNetwork, "BeginHostingNetwork"}, {0x001E0084, nullptr, "ConnectToNetwork"}, {0x001F0006, nullptr, "DecryptBeaconData"}, {0x00200040, nullptr, "Flush"}, @@ -148,13 +413,25 @@ const Interface::FunctionInfo FunctionTable[] = { }; NWM_UDS::NWM_UDS() { - uds_handle_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "NWM::uds_handle_event"); + connection_status_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "NWM::connection_status_event"); Register(FunctionTable); + + beacon_broadcast_event = + CoreTiming::RegisterEvent("UDS::BeaconBroadcastCallback", BeaconBroadcastCallback); } NWM_UDS::~NWM_UDS() { - uds_handle_event = nullptr; + network_info = {}; + bind_node_events.clear(); + connection_status_event = nullptr; + recv_buffer_memory = nullptr; + + connection_status = {}; + connection_status.status = static_cast<u32>(NetworkStatus::NotConnected); + + CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0); } } // namespace NWM diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h index 55db748f6..65349f9fd 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h @@ -4,6 +4,10 @@ #pragma once +#include <array> +#include <cstddef> +#include "common/common_types.h" +#include "common/swap.h" #include "core/hle/service/service.h" // Local-WLAN service @@ -11,6 +15,68 @@ namespace Service { namespace NWM { +const size_t ApplicationDataSize = 0xC8; +const u8 DefaultNetworkChannel = 11; + +// Number of milliseconds in a TU. +const double MillisecondsPerTU = 1.024; +// Interval measured in TU, the default value is 100TU = 102.4ms +const u16 DefaultBeaconInterval = 100; + +struct NodeInfo { + u64_le friend_code_seed; + std::array<u16_le, 10> username; + INSERT_PADDING_BYTES(4); + u16_le network_node_id; + INSERT_PADDING_BYTES(6); +}; + +static_assert(sizeof(NodeInfo) == 40, "NodeInfo has incorrect size."); + +enum class NetworkStatus { + NotConnected = 3, + ConnectedAsHost = 6, + ConnectedAsClient = 9, + ConnectedAsSpectator = 10, +}; + +struct ConnectionStatus { + u32_le status; + INSERT_PADDING_WORDS(1); + u16_le network_node_id; + INSERT_PADDING_BYTES(2); + INSERT_PADDING_BYTES(32); + u8 total_nodes; + u8 max_nodes; + u16_le node_bitmask; +}; + +static_assert(sizeof(ConnectionStatus) == 0x30, "ConnectionStatus has incorrect size."); + +struct NetworkInfo { + std::array<u8, 6> host_mac_address; + u8 channel; + INSERT_PADDING_BYTES(1); + u8 initialized; + INSERT_PADDING_BYTES(3); + std::array<u8, 3> oui_value; + u8 oui_type; + // This field is received as BigEndian from the game. + u32_be wlan_comm_id; + u8 id; + INSERT_PADDING_BYTES(1); + u16_be attributes; + u32_be network_id; + u8 total_nodes; + u8 max_nodes; + INSERT_PADDING_BYTES(2); + INSERT_PADDING_BYTES(0x1F); + u8 application_data_size; + std::array<u8, ApplicationDataSize> application_data; +}; + +static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size."); + class NWM_UDS final : public Interface { public: NWM_UDS(); diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index 8ff808fd9..e373ed47a 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -92,8 +92,7 @@ void GetSoftwareClosedFlag(Service::Interface* self) { LOG_WARNING(Service_PTM, "(STUBBED) called"); } -void CheckNew3DS(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); +void CheckNew3DS(IPC::RequestBuilder& rb) { const bool is_new_3ds = Settings::values.is_new_3ds; if (is_new_3ds) { @@ -101,12 +100,17 @@ void CheckNew3DS(Service::Interface* self) { "settings. Citra does not fully support New 3DS emulation yet!"); } - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = is_new_3ds ? 1 : 0; + rb.Push(RESULT_SUCCESS); + rb.Push(is_new_3ds); LOG_WARNING(Service_PTM, "(STUBBED) called isNew3DS = 0x%08x", static_cast<u32>(is_new_3ds)); } +void CheckNew3DS(Service::Interface* self) { + IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x40A, 0, 0); // 0x040A0000 + CheckNew3DS(rb); +} + void Init() { AddService(new PTM_Gets); AddService(new PTM_Play); @@ -134,9 +138,9 @@ void Init() { ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!"); FileSys::Path gamecoin_path("/gamecoin.dat"); + Service::FS::CreateFileInArchive(*archive_result, gamecoin_path, sizeof(GameCoin)); FileSys::Mode open_mode = {}; open_mode.write_flag.Assign(1); - open_mode.create_flag.Assign(1); // Open the file and write the default gamecoin information auto gamecoin_result = Service::FS::OpenFileFromArchive(*archive_result, gamecoin_path, open_mode); diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h index a1a628012..683fb445b 100644 --- a/src/core/hle/service/ptm/ptm.h +++ b/src/core/hle/service/ptm/ptm.h @@ -5,6 +5,7 @@ #pragma once #include "common/common_types.h" +#include "core/hle/ipc_helpers.h" namespace Service { @@ -97,6 +98,7 @@ void GetSoftwareClosedFlag(Interface* self); * 2: u8 output: 0 = Old3DS, 1 = New3DS. */ void CheckNew3DS(Interface* self); +void CheckNew3DS(IPC::RequestBuilder& rb); /// Initialize the PTM service 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/soc_u.cpp b/src/core/hle/service/soc_u.cpp index dcc5c3c90..530614e6f 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -362,18 +362,18 @@ static void Socket(Interface* self) { return; } - u32 socket_handle = static_cast<u32>(::socket(domain, type, protocol)); + u32 ret = static_cast<u32>(::socket(domain, type, protocol)); - if ((s32)socket_handle != SOCKET_ERROR_VALUE) - open_sockets[socket_handle] = {socket_handle, true}; + if ((s32)ret != SOCKET_ERROR_VALUE) + open_sockets[ret] = {ret, true}; int result = 0; - if ((s32)socket_handle == SOCKET_ERROR_VALUE) - result = TranslateError(GET_ERRNO); + if ((s32)ret == SOCKET_ERROR_VALUE) + ret = TranslateError(GET_ERRNO); cmd_buffer[0] = IPC::MakeHeader(2, 2, 0); cmd_buffer[1] = result; - cmd_buffer[2] = socket_handle; + cmd_buffer[2] = ret; } static void Bind(Interface* self) { @@ -393,15 +393,15 @@ static void Bind(Interface* self) { sockaddr sock_addr = CTRSockAddr::ToPlatform(ctr_sock_addr); - int res = ::bind(socket_handle, &sock_addr, std::max<u32>(sizeof(sock_addr), len)); + int ret = ::bind(socket_handle, &sock_addr, std::max<u32>(sizeof(sock_addr), len)); int result = 0; - if (res != 0) - result = TranslateError(GET_ERRNO); + if (ret != 0) + ret = TranslateError(GET_ERRNO); cmd_buffer[0] = IPC::MakeHeader(5, 2, 0); cmd_buffer[1] = result; - cmd_buffer[2] = res; + cmd_buffer[2] = ret; } static void Fcntl(Interface* self) { @@ -426,8 +426,7 @@ static void Fcntl(Interface* self) { #else int ret = ::fcntl(socket_handle, F_GETFL, 0); if (ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); - posix_ret = -1; + posix_ret = TranslateError(GET_ERRNO); return; } posix_ret = 0; @@ -439,8 +438,7 @@ static void Fcntl(Interface* self) { unsigned long tmp = (ctr_arg & 4 /* O_NONBLOCK */) ? 1 : 0; int ret = ioctlsocket(socket_handle, FIONBIO, &tmp); if (ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); - posix_ret = -1; + posix_ret = TranslateError(GET_ERRNO); return; } auto iter = open_sockets.find(socket_handle); @@ -449,8 +447,7 @@ static void Fcntl(Interface* self) { #else int flags = ::fcntl(socket_handle, F_GETFL, 0); if (flags == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); - posix_ret = -1; + posix_ret = TranslateError(GET_ERRNO); return; } @@ -460,15 +457,13 @@ static void Fcntl(Interface* self) { int ret = ::fcntl(socket_handle, F_SETFL, flags); if (ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); - posix_ret = -1; + posix_ret = TranslateError(GET_ERRNO); return; } #endif } else { LOG_ERROR(Service_SOC, "Unsupported command (%d) in fcntl call", ctr_cmd); - result = TranslateError(EINVAL); // TODO: Find the correct error - posix_ret = -1; + posix_ret = TranslateError(EINVAL); // TODO: Find the correct error return; } } @@ -481,7 +476,7 @@ static void Listen(Interface* self) { int ret = ::listen(socket_handle, backlog); int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[0] = IPC::MakeHeader(3, 2, 0); cmd_buffer[1] = result; @@ -504,7 +499,7 @@ static void Accept(Interface* self) { int result = 0; if ((s32)ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); } else { CTRSockAddr ctr_addr = CTRSockAddr::FromPlatform(addr); Memory::WriteBlock(cmd_buffer[0x104 >> 2], &ctr_addr, sizeof(ctr_addr)); @@ -545,7 +540,7 @@ static void Close(Interface* self) { int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; @@ -589,7 +584,7 @@ static void SendTo(Interface* self) { int result = 0; if (ret == SOCKET_ERROR_VALUE) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; @@ -638,7 +633,7 @@ static void RecvFrom(Interface* self) { int result = 0; int total_received = ret; if (ret == SOCKET_ERROR_VALUE) { - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); total_received = 0; } else { // Write only the data we received to avoid overwriting parts of the buffer with zeros @@ -673,7 +668,7 @@ static void Poll(Interface* self) { std::vector<pollfd> platform_pollfd(nfds); std::transform(ctr_fds.begin(), ctr_fds.end(), platform_pollfd.begin(), CTRPollFD::ToPlatform); - const int ret = ::poll(platform_pollfd.data(), nfds, timeout); + int ret = ::poll(platform_pollfd.data(), nfds, timeout); // Now update the output pollfd structure std::transform(platform_pollfd.begin(), platform_pollfd.end(), ctr_fds.begin(), @@ -683,7 +678,7 @@ static void Poll(Interface* self) { int result = 0; if (ret == SOCKET_ERROR_VALUE) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[1] = result; cmd_buffer[2] = ret; @@ -710,7 +705,7 @@ static void GetSockName(Interface* self) { int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; @@ -724,7 +719,7 @@ static void Shutdown(Interface* self) { int ret = ::shutdown(socket_handle, how); int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; } @@ -750,7 +745,7 @@ static void GetPeerName(Interface* self) { int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[2] = ret; cmd_buffer[1] = result; @@ -777,7 +772,7 @@ static void Connect(Interface* self) { int ret = ::connect(socket_handle, &input_addr, sizeof(input_addr)); int result = 0; if (ret != 0) - result = TranslateError(GET_ERRNO); + ret = TranslateError(GET_ERRNO); cmd_buffer[0] = IPC::MakeHeader(6, 2, 0); cmd_buffer[1] = result; @@ -815,7 +810,7 @@ static void GetSockOpt(Interface* self) { int optname = TranslateSockOpt(cmd_buffer[3]); socklen_t optlen = (socklen_t)cmd_buffer[4]; - int ret = -1; + int ret = 0; int err = 0; if (optname < 0) { @@ -830,9 +825,8 @@ static void GetSockOpt(Interface* self) { // >> 2 = convert to u32 offset instead of byte offset (cmd_buffer = u32*) char* optval = reinterpret_cast<char*>(Memory::GetPointer(cmd_buffer[0x104 >> 2])); - ret = ::getsockopt(socket_handle, level, optname, optval, &optlen); - err = 0; - if (ret == SOCKET_ERROR_VALUE) { + err = ::getsockopt(socket_handle, level, optname, optval, &optlen); + if (err == SOCKET_ERROR_VALUE) { err = TranslateError(GET_ERRNO); } } @@ -849,7 +843,7 @@ static void SetSockOpt(Interface* self) { u32 level = cmd_buffer[2]; int optname = TranslateSockOpt(cmd_buffer[3]); - int ret = -1; + int ret = 0; int err = 0; if (optname < 0) { @@ -862,9 +856,8 @@ static void SetSockOpt(Interface* self) { socklen_t optlen = static_cast<socklen_t>(cmd_buffer[4]); const char* optval = reinterpret_cast<const char*>(Memory::GetPointer(cmd_buffer[8])); - ret = static_cast<u32>(::setsockopt(socket_handle, level, optname, optval, optlen)); - err = 0; - if (ret == SOCKET_ERROR_VALUE) { + err = static_cast<u32>(::setsockopt(socket_handle, level, optname, optval, optlen)); + if (err == SOCKET_ERROR_VALUE) { err = TranslateError(GET_ERRNO); } } diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp index 31bb466fc..c0837d49d 100644 --- a/src/core/hle/service/y2r_u.cpp +++ b/src/core/hle/service/y2r_u.cpp @@ -189,11 +189,9 @@ static void SetSpacialDithering(Interface* self) { * 2 : u8, 0 = Disabled, 1 = Enabled */ static void GetSpacialDithering(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[0] = IPC::MakeHeader(0xA, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = spacial_dithering_enabled; + IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0xA, 2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(spacial_dithering_enabled != 0); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } @@ -281,37 +279,39 @@ static void GetTransferEndEvent(Interface* self) { } static void SetSendingY(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + // 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(); - 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]; - - 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(); - - 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]; + // 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(); - 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 +561,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/shared_page.cpp b/src/core/hle/shared_page.cpp index d0d92487d..5978ccdd4 100644 --- a/src/core/hle/shared_page.cpp +++ b/src/core/hle/shared_page.cpp @@ -6,6 +6,7 @@ #include <cstring> #include <ctime> #include "core/core_timing.h" +#include "core/hle/service/ptm/ptm.h" #include "core/hle/shared_page.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -73,6 +74,12 @@ void Init() { // Some games wait until this value becomes 0x1, before asking running_hw shared_page.unknown_value = 0x1; + // Set to a completely full battery + shared_page.battery_state.charge_level.Assign( + static_cast<u8>(Service::PTM::ChargeLevels::CompletelyFull)); + shared_page.battery_state.is_adapter_connected.Assign(1); + shared_page.battery_state.is_charging.Assign(1); + update_time_event = CoreTiming::RegisterEvent("SharedPage::UpdateTimeCallback", UpdateTimeCallback); CoreTiming::ScheduleEvent(0, update_time_event); diff --git a/src/core/hle/shared_page.h b/src/core/hle/shared_page.h index 106e47efc..864695ae1 100644 --- a/src/core/hle/shared_page.h +++ b/src/core/hle/shared_page.h @@ -10,6 +10,7 @@ * write access, according to 3dbrew; this is not emulated) */ +#include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" @@ -29,6 +30,13 @@ struct DateTime { }; static_assert(sizeof(DateTime) == 0x20, "Datetime size is wrong"); +union BatteryState { + u8 raw; + BitField<0, 1, u8> is_adapter_connected; + BitField<1, 1, u8> is_charging; + BitField<2, 3, u8> charge_level; +}; + struct SharedPageDef { // Most of these names are taken from the 3dbrew page linked above. u32_le date_time_counter; // 0 @@ -44,7 +52,7 @@ struct SharedPageDef { INSERT_PADDING_BYTES(0x80 - 0x68); // 68 float_le sliderstate_3d; // 80 u8 ledstate_3d; // 84 - INSERT_PADDING_BYTES(1); // 85 + BatteryState battery_state; // 85 u8 unknown_value; // 86 INSERT_PADDING_BYTES(0xA0 - 0x87); // 87 u64_le menu_title_id; // A0 diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 96db39ad9..2db823c61 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -467,8 +467,8 @@ static void Break(u8 break_reason) { } /// Used to output a message on a debug hardware unit - does nothing on a retail unit -static void OutputDebugString(const char* string) { - LOG_DEBUG(Debug_Emulated, "%s", string); +static void OutputDebugString(const char* string, int len) { + LOG_DEBUG(Debug_Emulated, "%.*s", len, string); } /// Get resource limit @@ -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 b6c75531f..03c64c94c 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -15,67 +15,70 @@ enum class LayoutOption { Default, SingleScreen, LargeScreen, - Custom, }; -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; + +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; - // indirectly mapped keys - "pad_circle_up", "pad_circle_down", "pad_circle_left", "pad_circle_right", - "pad_circle_modifier", +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; @@ -95,6 +98,15 @@ struct Values { LayoutOption layout_option; bool swap_screen; + bool custom_layout; + u16 custom_top_left; + u16 custom_top_top; + u16 custom_top_right; + u16 custom_top_bottom; + u16 custom_bottom_left; + u16 custom_bottom_top; + u16 custom_bottom_right; + u16 custom_bottom_bottom; float bg_red; float bg_green; |