diff options
Diffstat (limited to 'src/core/hle/service')
-rw-r--r-- | src/core/hle/service/cfg/cfg.cpp | 55 | ||||
-rw-r--r-- | src/core/hle/service/cfg/cfg.h | 21 | ||||
-rw-r--r-- | src/core/hle/service/dsp_dsp.cpp | 18 | ||||
-rw-r--r-- | src/core/hle/service/hid/hid.cpp | 19 | ||||
-rw-r--r-- | src/core/hle/service/hid/hid.h | 10 | ||||
-rw-r--r-- | src/core/hle/service/ir/extra_hid.cpp | 231 | ||||
-rw-r--r-- | src/core/hle/service/ir/extra_hid.h | 48 | ||||
-rw-r--r-- | src/core/hle/service/ir/ir.cpp | 5 | ||||
-rw-r--r-- | src/core/hle/service/ir/ir.h | 3 | ||||
-rw-r--r-- | src/core/hle/service/ir/ir_rst.cpp | 186 | ||||
-rw-r--r-- | src/core/hle/service/ir/ir_rst.h | 3 | ||||
-rw-r--r-- | src/core/hle/service/ir/ir_user.cpp | 489 | ||||
-rw-r--r-- | src/core/hle/service/ir/ir_user.h | 33 | ||||
-rw-r--r-- | src/core/hle/service/ldr_ro/ldr_ro.cpp | 331 | ||||
-rw-r--r-- | src/core/hle/service/ptm/ptm.cpp | 66 | ||||
-rw-r--r-- | src/core/hle/service/ptm/ptm.h | 8 | ||||
-rw-r--r-- | src/core/hle/service/ptm/ptm_u.cpp | 2 |
17 files changed, 1234 insertions, 294 deletions
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 4ddb1bc90..8c8c1ec77 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -4,6 +4,7 @@ #include <algorithm> #include <array> +#include <cryptopp/osrng.h> #include <cryptopp/sha.h> #include "common/file_util.h" #include "common/logging/log.h" @@ -50,6 +51,7 @@ enum ConfigBlockID { SoundOutputModeBlockID = 0x00070001, ConsoleUniqueID1BlockID = 0x00090000, ConsoleUniqueID2BlockID = 0x00090001, + ConsoleUniqueID3BlockID = 0x00090002, UsernameBlockID = 0x000A0000, BirthdayBlockID = 0x000A0001, LanguageBlockID = 0x000A0002, @@ -86,7 +88,6 @@ struct ConsoleCountryInfo { static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exactly 4 bytes"); } -static const u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE; static const ConsoleModelInfo CONSOLE_MODEL = {NINTENDO_3DS_XL, {0, 0, 0}}; static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN; static const UsernameBlock CONSOLE_USERNAME_BLOCK = {u"CITRA", 0, 0}; @@ -438,13 +439,22 @@ ResultCode FormatConfig() { if (!res.IsSuccess()) return res; - res = CreateConfigInfoBlk(ConsoleUniqueID1BlockID, sizeof(CONSOLE_UNIQUE_ID), 0xE, - &CONSOLE_UNIQUE_ID); + u32 random_number; + u64 console_id; + GenerateConsoleUniqueId(random_number, console_id); + + u64_le console_id_le = console_id; + res = CreateConfigInfoBlk(ConsoleUniqueID1BlockID, sizeof(console_id_le), 0xE, &console_id_le); if (!res.IsSuccess()) return res; - res = CreateConfigInfoBlk(ConsoleUniqueID2BlockID, sizeof(CONSOLE_UNIQUE_ID), 0xE, - &CONSOLE_UNIQUE_ID); + res = CreateConfigInfoBlk(ConsoleUniqueID2BlockID, sizeof(console_id_le), 0xE, &console_id_le); + if (!res.IsSuccess()) + return res; + + u32_le random_number_le = random_number; + res = CreateConfigInfoBlk(ConsoleUniqueID3BlockID, sizeof(random_number_le), 0xE, + &random_number_le); if (!res.IsSuccess()) return res; @@ -663,5 +673,40 @@ SoundOutputMode GetSoundOutputMode() { return static_cast<SoundOutputMode>(block); } +void GenerateConsoleUniqueId(u32& random_number, u64& console_id) { + CryptoPP::AutoSeededRandomPool rng; + random_number = rng.GenerateWord32(0, 0xFFFF); + u64_le local_friend_code_seed; + rng.GenerateBlock(reinterpret_cast<byte*>(&local_friend_code_seed), + sizeof(local_friend_code_seed)); + console_id = (local_friend_code_seed & 0x3FFFFFFFF) | (static_cast<u64>(random_number) << 48); +} + +ResultCode SetConsoleUniqueId(u32 random_number, u64 console_id) { + u64_le console_id_le = console_id; + ResultCode res = + SetConfigInfoBlock(ConsoleUniqueID1BlockID, sizeof(console_id_le), 0xE, &console_id_le); + if (!res.IsSuccess()) + return res; + + res = SetConfigInfoBlock(ConsoleUniqueID2BlockID, sizeof(console_id_le), 0xE, &console_id_le); + if (!res.IsSuccess()) + return res; + + u32_le random_number_le = random_number; + res = SetConfigInfoBlock(ConsoleUniqueID3BlockID, sizeof(random_number_le), 0xE, + &random_number_le); + if (!res.IsSuccess()) + return res; + + return RESULT_SUCCESS; +} + +u64 GetConsoleUniqueId() { + u64_le console_id_le; + GetConfigInfoBlock(ConsoleUniqueID2BlockID, sizeof(console_id_le), 0xE, &console_id_le); + return console_id_le; +} + } // namespace CFG } // namespace Service diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index 618c9647e..1659ebf32 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -342,5 +342,26 @@ void SetSoundOutputMode(SoundOutputMode mode); */ SoundOutputMode GetSoundOutputMode(); +/** + * Generates a new random console unique id. + * @param random_number a random generated 16bit number stored at 0x90002, used for generating the + * console_id + * @param console_id the randomly created console id + */ +void GenerateConsoleUniqueId(u32& random_number, u64& console_id); + +/** + * Sets the random_number and the console unique id in the config savegame. + * @param random_number the random_number to set + * @param console_id the console id to set + */ +ResultCode SetConsoleUniqueId(u32 random_number, u64 console_id); + +/** + * Gets the console unique id from config savegame. + * @returns the console unique id + */ +u64 GetConsoleUniqueId(); + } // namespace CFG } // namespace Service diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp index fe8a6c2d6..39711ea97 100644 --- a/src/core/hle/service/dsp_dsp.cpp +++ b/src/core/hle/service/dsp_dsp.cpp @@ -303,6 +303,24 @@ static void WriteProcessPipe(Service::Interface* self) { message[i] = Memory::Read8(buffer + i); } + // This behaviour was confirmed by RE. + // The likely reason for this is that games tend to pass in garbage at these bytes + // because they read random bytes off the stack. + switch (pipe) { + case DSP::HLE::DspPipe::Audio: + ASSERT(message.size() >= 4); + message[2] = 0; + message[3] = 0; + break; + case DSP::HLE::DspPipe::Binary: + ASSERT(message.size() >= 8); + message[4] = 1; + message[5] = 0; + message[6] = 0; + message[7] = 0; + break; + } + DSP::HLE::PipeWrite(pipe, message); cmd_buff[0] = IPC::MakeHeader(0xD, 1, 0); diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index b19e831fe..64d01cdd7 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -53,30 +53,29 @@ static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton:: buttons; static std::unique_ptr<Input::AnalogDevice> circle_pad; -static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { +DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { // 30 degree and 60 degree are angular thresholds for directions constexpr float TAN30 = 0.577350269f; constexpr float TAN60 = 1 / TAN30; // a circle pad radius greater than 40 will trigger circle pad direction constexpr int CIRCLE_PAD_THRESHOLD_SQUARE = 40 * 40; - PadState state; - state.hex = 0; + DirectionState state{false, false, false, false}; if (circle_pad_x * circle_pad_x + circle_pad_y * circle_pad_y > CIRCLE_PAD_THRESHOLD_SQUARE) { float t = std::abs(static_cast<float>(circle_pad_y) / circle_pad_x); if (circle_pad_x != 0 && t < TAN60) { if (circle_pad_x > 0) - state.circle_right.Assign(1); + state.right = true; else - state.circle_left.Assign(1); + state.left = true; } if (circle_pad_x == 0 || t > TAN30) { if (circle_pad_y > 0) - state.circle_up.Assign(1); + state.up = true; else - state.circle_down.Assign(1); + state.down = true; } } @@ -125,7 +124,11 @@ static void UpdatePadCallback(u64 userdata, int cycles_late) { 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; + const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); + state.circle_up.Assign(direction.up); + state.circle_down.Assign(direction.down); + state.circle_left.Assign(direction.left); + state.circle_right.Assign(direction.right); mem->pad.current_state.hex = state.hex; mem->pad.index = next_pad_index; diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index b505cdcd5..1ef972e70 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -176,6 +176,16 @@ ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A); #undef ASSERT_REG_POSITION #endif // !defined(_MSC_VER) +struct DirectionState { + bool up; + bool down; + bool left; + bool right; +}; + +/// Translates analog stick axes to directions. This is exposed for ir_rst module to use. +DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y); + /** * HID::GetIPCHandles service function * Inputs: diff --git a/src/core/hle/service/ir/extra_hid.cpp b/src/core/hle/service/ir/extra_hid.cpp new file mode 100644 index 000000000..e7acc17a5 --- /dev/null +++ b/src/core/hle/service/ir/extra_hid.cpp @@ -0,0 +1,231 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/alignment.h" +#include "common/bit_field.h" +#include "common/string_util.h" +#include "core/core_timing.h" +#include "core/hle/service/ir/extra_hid.h" +#include "core/settings.h" + +namespace Service { +namespace IR { + +enum class RequestID : u8 { + /** + * ConfigureHIDPolling request + * Starts HID input polling, or changes the polling interval if it is already started. + * Inputs: + * byte 0: request ID + * byte 1: polling interval in ms + * byte 2: unknown + */ + ConfigureHIDPolling = 1, + + /** + * ReadCalibrationData request + * Reads the calibration data stored in circle pad pro. + * Inputs: + * byte 0: request ID + * byte 1: expected response time in ms? + * byte 2-3: data offset (aligned to 0x10) + * byte 4-5: data size (aligned to 0x10) + */ + ReadCalibrationData = 2, + + // TODO(wwylele): there are three more request types (id = 3, 4 and 5) +}; + +enum class ResponseID : u8 { + + /** + * PollHID response + * Sends current HID status + * Output: + * byte 0: response ID + * byte 1-3: Right circle pad position. This three bytes are two little-endian 12-bit + * fields. The first one is for x-axis and the second one is for y-axis. + * byte 4: bit[0:4] battery level; bit[5] ZL button; bit[6] ZR button; bit[7] R button + * Note that for the three button fields, the bit is set when the button is NOT pressed. + * byte 5: unknown + */ + PollHID = 0x10, + + /** + * ReadCalibrationData response + * Sends the calibration data reads from circle pad pro. + * Output: + * byte 0: resonse ID + * byte 1-2: data offset (aligned to 0x10) + * byte 3-4: data size (aligned to 0x10) + * byte 5-...: calibration data + */ + ReadCalibrationData = 0x11, +}; + +ExtraHID::ExtraHID(SendFunc send_func) : IRDevice(send_func) { + LoadInputDevices(); + + // The data below was retrieved from a New 3DS + // TODO(wwylele): this data is probably writable (via request 3?) and thus should be saved to + // and loaded from somewhere. + calibration_data = std::array<u8, 0x40>{{ + // 0x00 + 0x00, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, + // 0x08 + 0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0xF5, + // 0x10 + 0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, + // 0x18 + 0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65, + // 0x20 + 0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, + // 0x28 + 0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65, + // 0x30 + 0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, + // 0x38 + 0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65, + }}; + + hid_polling_callback_id = + CoreTiming::RegisterEvent("ExtraHID::SendHIDStatus", [this](u64, int cycles_late) { + SendHIDStatus(); + CoreTiming::ScheduleEvent(msToCycles(hid_period) - cycles_late, + hid_polling_callback_id); + }); +} + +ExtraHID::~ExtraHID() { + OnDisconnect(); +} + +void ExtraHID::OnConnect() {} + +void ExtraHID::OnDisconnect() { + CoreTiming::UnscheduleEvent(hid_polling_callback_id, 0); +} + +void ExtraHID::HandleConfigureHIDPollingRequest(const std::vector<u8>& request) { + if (request.size() != 3) { + LOG_ERROR(Service_IR, "Wrong request size (%zu): %s", request.size(), + Common::ArrayToString(request.data(), request.size()).c_str()); + return; + } + + // Change HID input polling interval + CoreTiming::UnscheduleEvent(hid_polling_callback_id, 0); + hid_period = request[1]; + CoreTiming::ScheduleEvent(msToCycles(hid_period), hid_polling_callback_id); +} + +void ExtraHID::HandleReadCalibrationDataRequest(const std::vector<u8>& request_buf) { + struct ReadCalibrationDataRequest { + RequestID request_id; + u8 expected_response_time; + u16_le offset; + u16_le size; + }; + static_assert(sizeof(ReadCalibrationDataRequest) == 6, + "ReadCalibrationDataRequest has wrong size"); + + if (request_buf.size() != sizeof(ReadCalibrationDataRequest)) { + LOG_ERROR(Service_IR, "Wrong request size (%zu): %s", request_buf.size(), + Common::ArrayToString(request_buf.data(), request_buf.size()).c_str()); + return; + } + + ReadCalibrationDataRequest request; + std::memcpy(&request, request_buf.data(), sizeof(request)); + + const u16 offset = Common::AlignDown(request.offset, 16); + const u16 size = Common::AlignDown(request.size, 16); + + if (offset + size > calibration_data.size()) { + LOG_ERROR(Service_IR, "Read beyond the end of calibration data! (offset=%u, size=%u)", + offset, size); + return; + } + + std::vector<u8> response(5); + response[0] = static_cast<u8>(ResponseID::ReadCalibrationData); + std::memcpy(&response[1], &request.offset, sizeof(request.offset)); + std::memcpy(&response[3], &request.size, sizeof(request.size)); + response.insert(response.end(), calibration_data.begin() + offset, + calibration_data.begin() + offset + size); + Send(response); +} + +void ExtraHID::OnReceive(const std::vector<u8>& data) { + switch (static_cast<RequestID>(data[0])) { + case RequestID::ConfigureHIDPolling: + HandleConfigureHIDPollingRequest(data); + break; + case RequestID::ReadCalibrationData: + HandleReadCalibrationDataRequest(data); + break; + default: + LOG_ERROR(Service_IR, "Unknown request: %s", + Common::ArrayToString(data.data(), data.size()).c_str()); + break; + } +} + +void ExtraHID::SendHIDStatus() { + if (is_device_reload_pending.exchange(false)) + LoadInputDevices(); + + struct { + union { + BitField<0, 8, u32_le> header; + BitField<8, 12, u32_le> c_stick_x; + BitField<20, 12, u32_le> c_stick_y; + } c_stick; + union { + BitField<0, 5, u8> battery_level; + BitField<5, 1, u8> zl_not_held; + BitField<6, 1, u8> zr_not_held; + BitField<7, 1, u8> r_not_held; + } buttons; + u8 unknown; + } response; + static_assert(sizeof(response) == 6, "HID status response has wrong size!"); + + constexpr int C_STICK_CENTER = 0x800; + // TODO(wwylele): this value is not accurately measured. We currently assume that the axis can + // take values in the whole range of a 12-bit integer. + constexpr int C_STICK_RADIUS = 0x7FF; + + float x, y; + std::tie(x, y) = c_stick->GetStatus(); + + response.c_stick.header.Assign(static_cast<u8>(ResponseID::PollHID)); + response.c_stick.c_stick_x.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * x)); + response.c_stick.c_stick_y.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * y)); + response.buttons.battery_level.Assign(0x1F); + response.buttons.zl_not_held.Assign(!zl->GetStatus()); + response.buttons.zr_not_held.Assign(!zr->GetStatus()); + response.buttons.r_not_held.Assign(1); + response.unknown = 0; + + std::vector<u8> response_buffer(sizeof(response)); + memcpy(response_buffer.data(), &response, sizeof(response)); + Send(response_buffer); +} + +void ExtraHID::RequestInputDevicesReload() { + is_device_reload_pending.store(true); +} + +void ExtraHID::LoadInputDevices() { + zl = Input::CreateDevice<Input::ButtonDevice>( + Settings::values.buttons[Settings::NativeButton::ZL]); + zr = Input::CreateDevice<Input::ButtonDevice>( + Settings::values.buttons[Settings::NativeButton::ZR]); + c_stick = Input::CreateDevice<Input::AnalogDevice>( + Settings::values.analogs[Settings::NativeAnalog::CStick]); +} + +} // namespace IR +} // namespace Service diff --git a/src/core/hle/service/ir/extra_hid.h b/src/core/hle/service/ir/extra_hid.h new file mode 100644 index 000000000..a2459a73a --- /dev/null +++ b/src/core/hle/service/ir/extra_hid.h @@ -0,0 +1,48 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <atomic> +#include "core/frontend/input.h" +#include "core/hle/service/ir/ir_user.h" + +namespace Service { +namespace IR { + +/** + * An IRDevice emulating Circle Pad Pro or New 3DS additional HID hardware. + * This device sends periodic udates at a rate configured by the 3DS, and sends calibration data if + * requested. + */ +class ExtraHID final : public IRDevice { +public: + explicit ExtraHID(SendFunc send_func); + ~ExtraHID(); + + void OnConnect() override; + void OnDisconnect() override; + void OnReceive(const std::vector<u8>& data) override; + + /// Requests input devices reload from current settings. Called when the input settings change. + void RequestInputDevicesReload(); + +private: + void SendHIDStatus(); + void HandleConfigureHIDPollingRequest(const std::vector<u8>& request); + void HandleReadCalibrationDataRequest(const std::vector<u8>& request); + void LoadInputDevices(); + + u8 hid_period; + int hid_polling_callback_id; + std::array<u8, 0x40> calibration_data; + std::unique_ptr<Input::ButtonDevice> zl; + std::unique_ptr<Input::ButtonDevice> zr; + std::unique_ptr<Input::AnalogDevice> c_stick; + std::atomic<bool> is_device_reload_pending; +}; + +} // namespace IR +} // namespace Service diff --git a/src/core/hle/service/ir/ir.cpp b/src/core/hle/service/ir/ir.cpp index 7ac34a990..f06dd552f 100644 --- a/src/core/hle/service/ir/ir.cpp +++ b/src/core/hle/service/ir/ir.cpp @@ -25,6 +25,11 @@ void Shutdown() { ShutdownRST(); } +void ReloadInputDevices() { + ReloadInputDevicesUser(); + ReloadInputDevicesRST(); +} + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir.h b/src/core/hle/service/ir/ir.h index c741498e2..6be3e950c 100644 --- a/src/core/hle/service/ir/ir.h +++ b/src/core/hle/service/ir/ir.h @@ -16,5 +16,8 @@ void Init(); /// Shutdown IR service void Shutdown(); +/// Reload input devices. Used when input configuration changed +void ReloadInputDevices(); + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp index 3f1275c53..53807cd91 100644 --- a/src/core/hle/service/ir/ir_rst.cpp +++ b/src/core/hle/service/ir/ir_rst.cpp @@ -2,16 +2,135 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <atomic> +#include "common/bit_field.h" +#include "core/core_timing.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" #include "core/hle/service/ir/ir.h" #include "core/hle/service/ir/ir_rst.h" +#include "core/settings.h" namespace Service { namespace IR { -static Kernel::SharedPtr<Kernel::Event> handle_event; +union PadState { + u32_le hex; + + BitField<14, 1, u32_le> zl; + BitField<15, 1, u32_le> zr; + + BitField<24, 1, u32_le> c_stick_right; + BitField<25, 1, u32_le> c_stick_left; + BitField<26, 1, u32_le> c_stick_up; + BitField<27, 1, u32_le> c_stick_down; +}; + +struct PadDataEntry { + PadState current_state; + PadState delta_additions; + PadState delta_removals; + + s16_le c_stick_x; + s16_le c_stick_y; +}; + +struct SharedMem { + u64_le index_reset_ticks; ///< CPU tick count for when HID module updated entry index 0 + u64_le index_reset_ticks_previous; ///< Previous `index_reset_ticks` + u32_le index; + INSERT_PADDING_WORDS(1); + std::array<PadDataEntry, 8> entries; ///< Last 8 pad entries +}; + +static_assert(sizeof(SharedMem) == 0x98, "SharedMem has wrong size!"); + +static Kernel::SharedPtr<Kernel::Event> update_event; static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; +static u32 next_pad_index; +static int update_callback_id; +static std::unique_ptr<Input::ButtonDevice> zl_button; +static std::unique_ptr<Input::ButtonDevice> zr_button; +static std::unique_ptr<Input::AnalogDevice> c_stick; +static std::atomic<bool> is_device_reload_pending; +static bool raw_c_stick; +static int update_period; + +static void LoadInputDevices() { + zl_button = Input::CreateDevice<Input::ButtonDevice>( + Settings::values.buttons[Settings::NativeButton::ZL]); + zr_button = Input::CreateDevice<Input::ButtonDevice>( + Settings::values.buttons[Settings::NativeButton::ZR]); + c_stick = Input::CreateDevice<Input::AnalogDevice>( + Settings::values.analogs[Settings::NativeAnalog::CStick]); +} + +static void UnloadInputDevices() { + zl_button = nullptr; + zr_button = nullptr; + c_stick = nullptr; +} + +static void UpdateCallback(u64 userdata, int cycles_late) { + SharedMem* mem = reinterpret_cast<SharedMem*>(shared_memory->GetPointer()); + + if (is_device_reload_pending.exchange(false)) + LoadInputDevices(); + + PadState state; + state.zl.Assign(zl_button->GetStatus()); + state.zr.Assign(zr_button->GetStatus()); + + // Get current c-stick position and update c-stick direction + float c_stick_x_f, c_stick_y_f; + std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); + constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius + const s16 c_stick_x = static_cast<s16>(c_stick_x_f * MAX_CSTICK_RADIUS); + const s16 c_stick_y = static_cast<s16>(c_stick_y_f * MAX_CSTICK_RADIUS); + + if (!raw_c_stick) { + const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y); + state.c_stick_up.Assign(direction.up); + state.c_stick_down.Assign(direction.down); + state.c_stick_left.Assign(direction.left); + state.c_stick_right.Assign(direction.right); + } + + // TODO (wwylele): implement raw C-stick data for raw_c_stick = true + + const u32 last_entry_index = mem->index; + mem->index = next_pad_index; + next_pad_index = (next_pad_index + 1) % mem->entries.size(); + + // Get the previous Pad state + PadState old_state{mem->entries[last_entry_index].current_state}; + + // Compute bitmask with 1s for bits different from the old state + PadState changed = {state.hex ^ old_state.hex}; + + // Get the current Pad entry + PadDataEntry& pad_entry = mem->entries[mem->index]; + + // Update entry properties + pad_entry.current_state.hex = state.hex; + pad_entry.delta_additions.hex = changed.hex & state.hex; + pad_entry.delta_removals.hex = changed.hex & old_state.hex; + pad_entry.c_stick_x = c_stick_x; + pad_entry.c_stick_y = c_stick_y; + + // If we just updated index 0, provide a new timestamp + if (mem->index == 0) { + mem->index_reset_ticks_previous = mem->index_reset_ticks; + mem->index_reset_ticks = CoreTiming::GetTicks(); + } + + update_event->Signal(); + + // Reschedule recurrent event + CoreTiming::ScheduleEvent(msToCycles(update_period) - cycles_late, update_callback_id); +} /** * IR::GetHandles service function @@ -22,18 +141,52 @@ static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; * 4 : Event handle */ static void GetHandles(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x01, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 3); + rb.Push(RESULT_SUCCESS); + rb.PushMoveHandles(Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(), + Kernel::g_handle_table.Create(Service::IR::update_event).MoveFrom()); +} + +/** + * IR::Initialize service function + * Inputs: + * 1 : pad state update period in ms + * 2 : bool output raw c-stick data + */ +static void Initialize(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x02, 2, 0); + update_period = static_cast<int>(rp.Pop<u32>()); + raw_c_stick = rp.Pop<bool>(); - 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(); + if (raw_c_stick) + LOG_ERROR(Service_IR, "raw C-stick data is not implemented!"); + + next_pad_index = 0; + is_device_reload_pending.store(true); + CoreTiming::ScheduleEvent(msToCycles(update_period), update_callback_id); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + + LOG_DEBUG(Service_IR, "called. update_period=%d, raw_c_stick=%d", update_period, raw_c_stick); +} + +static void Shutdown(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 0); + + CoreTiming::UnscheduleEvent(update_callback_id, 0); + UnloadInputDevices(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_IR, "called"); } const Interface::FunctionInfo FunctionTable[] = { {0x00010000, GetHandles, "GetHandles"}, - {0x00020080, nullptr, "Initialize"}, - {0x00030000, nullptr, "Shutdown"}, + {0x00020080, Initialize, "Initialize"}, + {0x00030000, Shutdown, "Shutdown"}, {0x00090000, nullptr, "WriteToTwoFields"}, }; @@ -43,17 +196,24 @@ IR_RST_Interface::IR_RST_Interface() { void InitRST() { using namespace Kernel; - + // Note: these two kernel objects are even available before Initialize service function is + // called. shared_memory = - SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, - MemoryPermission::ReadWrite, 0, MemoryRegion::BASE, "IR:SharedMemory"); + SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read, + 0, MemoryRegion::BASE, "IRRST:SharedMemory"); + update_event = Event::Create(ResetType::OneShot, "IRRST:UpdateEvent"); - handle_event = Event::Create(ResetType::OneShot, "IR:HandleEvent"); + update_callback_id = CoreTiming::RegisterEvent("IRRST:UpdateCallBack", UpdateCallback); } void ShutdownRST() { shared_memory = nullptr; - handle_event = nullptr; + update_event = nullptr; + UnloadInputDevices(); +} + +void ReloadInputDevicesRST() { + is_device_reload_pending.store(true); } } // namespace IR diff --git a/src/core/hle/service/ir/ir_rst.h b/src/core/hle/service/ir/ir_rst.h index 75b732627..d932bb7e5 100644 --- a/src/core/hle/service/ir/ir_rst.h +++ b/src/core/hle/service/ir/ir_rst.h @@ -21,5 +21,8 @@ public: void InitRST(); void ShutdownRST(); +/// Reload input devices. Used when input configuration changed +void ReloadInputDevicesRST(); + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index b326d7fc7..226af0083 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -2,110 +2,481 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <memory> +#include <boost/crc.hpp> +#include <boost/optional.hpp> +#include "common/string_util.h" +#include "common/swap.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" +#include "core/hle/service/ir/extra_hid.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; +// This is a header that will present in the ir:USER shared memory if it is initialized with +// InitializeIrNopShared service function. Otherwise the shared memory doesn't have this header if +// it is initialized with InitializeIrNop service function. +struct SharedMemoryHeader { + u32_le latest_receive_error_result; + u32_le latest_send_error_result; + // TODO(wwylele): for these fields below, make them enum when the meaning of values is known. + u8 connection_status; + u8 trying_to_connect_status; + u8 connection_role; + u8 machine_id; + u8 connected; + u8 network_id; + u8 initialized; + u8 unknown; + + // This is not the end of the shared memory. It is followed by a receive buffer and a send + // buffer. We handle receive buffer in the BufferManager class. For the send buffer, because + // games usually don't access it, we don't emulate it. +}; +static_assert(sizeof(SharedMemoryHeader) == 16, "SharedMemoryHeader has wrong size!"); + +/** + * A manager of the send/receive buffers in the shared memory. Currently it is only used for the + * receive buffer. + * + * A buffer consists of three parts: + * - BufferInfo: stores available count of packets, and their position in the PacketInfo + * circular queue. + * - PacketInfo circular queue: stores the position of each avaiable packets in the Packet data + * buffer. Each entry is a pair of {offset, size}. + * - Packet data circular buffer: stores the actual data of packets. + * + * IR packets can be put into and get from the buffer. + * + * When a new packet is put into the buffer, its data is put into the data circular buffer, + * following the end of previous packet data. A new entry is also added to the PacketInfo circular + * queue pointing to the added packet data. Then BufferInfo is updated. + * + * Packets can be released from the other end of the buffer. When releasing a packet, the front + * entry in thePacketInfo circular queue is removed, and as a result the corresponding memory in the + * data circular buffer is also released. BufferInfo is updated as well. + * + * The client application usually has a similar manager constructed over the same shared memory + * region, performing the same put/get/release operation. This way the client and the service + * communicate via a pair of manager of the same buffer. + * + * TODO(wwylele): implement Get function, which is used by ReceiveIrnop service function. + */ +class BufferManager { +public: + BufferManager(Kernel::SharedPtr<Kernel::SharedMemory> shared_memory_, u32 info_offset_, + u32 buffer_offset_, u32 max_packet_count_, u32 buffer_size) + : shared_memory(shared_memory_), info_offset(info_offset_), buffer_offset(buffer_offset_), + max_packet_count(max_packet_count_), + max_data_size(buffer_size - sizeof(PacketInfo) * max_packet_count_) { + UpdateBufferInfo(); + } + + /** + * Puts a packet to the head of the buffer. + * @params packet The data of the packet to put. + * @returns whether the operation is successful. + */ + bool Put(const std::vector<u8>& packet) { + if (info.packet_count == max_packet_count) + return false; + + u32 write_offset; + + // finds free space offset in data buffer + if (info.packet_count == 0) { + write_offset = 0; + if (packet.size() > max_data_size) + return false; + } else { + const u32 last_index = (info.end_index + max_packet_count - 1) % max_packet_count; + const PacketInfo first = GetPacketInfo(info.begin_index); + const PacketInfo last = GetPacketInfo(last_index); + write_offset = (last.offset + last.size) % max_data_size; + const u32 free_space = (first.offset + max_data_size - write_offset) % max_data_size; + if (packet.size() > free_space) + return false; + } + + // writes packet info + PacketInfo packet_info{write_offset, static_cast<u32>(packet.size())}; + SetPacketInfo(info.end_index, packet_info); + + // writes packet data + for (size_t i = 0; i < packet.size(); ++i) { + *GetDataBufferPointer((write_offset + i) % max_data_size) = packet[i]; + } + + // updates buffer info + info.end_index++; + info.end_index %= max_packet_count; + info.packet_count++; + UpdateBufferInfo(); + return true; + } + + /** + * Release packets from the tail of the buffer + * @params count Numbers of packets to release. + * @returns whether the operation is successful. + */ + bool Release(u32 count) { + if (info.packet_count < count) + return false; + + info.packet_count -= count; + info.begin_index += count; + info.begin_index %= max_packet_count; + UpdateBufferInfo(); + return true; + } + +private: + struct BufferInfo { + u32_le begin_index; + u32_le end_index; + u32_le packet_count; + u32_le unknown; + }; + static_assert(sizeof(BufferInfo) == 16, "BufferInfo has wrong size!"); + + struct PacketInfo { + u32_le offset; + u32_le size; + }; + static_assert(sizeof(PacketInfo) == 8, "PacketInfo has wrong size!"); + + u8* GetPacketInfoPointer(u32 index) { + return shared_memory->GetPointer(buffer_offset + sizeof(PacketInfo) * index); + } + + void SetPacketInfo(u32 index, const PacketInfo& packet_info) { + memcpy(GetPacketInfoPointer(index), &packet_info, sizeof(PacketInfo)); + } + + PacketInfo GetPacketInfo(u32 index) { + PacketInfo packet_info; + memcpy(&packet_info, GetPacketInfoPointer(index), sizeof(PacketInfo)); + return packet_info; + } + + u8* GetDataBufferPointer(u32 offset) { + return shared_memory->GetPointer(buffer_offset + sizeof(PacketInfo) * max_packet_count + + offset); + } + + void UpdateBufferInfo() { + if (info_offset) { + memcpy(shared_memory->GetPointer(info_offset), &info, sizeof(info)); + } + } + + BufferInfo info{0, 0, 0, 0}; + Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; + u32 info_offset; + u32 buffer_offset; + u32 max_packet_count; + u32 max_data_size; +}; + +static Kernel::SharedPtr<Kernel::Event> conn_status_event, send_event, receive_event; +static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; +static std::unique_ptr<ExtraHID> extra_hid; +static IRDevice* connected_device; +static boost::optional<BufferManager> receive_buffer; + +/// Wraps the payload into packet and puts it to the receive buffer +static void PutToReceive(const std::vector<u8>& payload) { + LOG_TRACE(Service_IR, "called, data=%s", + Common::ArrayToString(payload.data(), payload.size()).c_str()); + size_t size = payload.size(); + + std::vector<u8> packet; + + // Builds packet header. For the format info: + // https://www.3dbrew.org/wiki/IRUSER_Shared_Memory#Packet_structure + + // fixed value + packet.push_back(0xA5); + // destination network ID + u8 network_id = *(shared_memory->GetPointer(offsetof(SharedMemoryHeader, network_id))); + packet.push_back(network_id); + + // puts the size info. + // The highest bit of the first byte is unknown, which is set to zero here. The second highest + // bit is a flag that determines whether the size info is in extended form. If the packet size + // can be represent within 6 bits, the short form (1 byte) of size info is chosen, the size is + // put to the lower bits of this byte, and the flag is clear. If the packet size cannot be + // represent within 6 bits, the extended form (2 bytes) is chosen, the lower 8 bits of the size + // is put to the second byte, the higher bits of the size is put to the lower bits of the first + // byte, and the flag is set. Note that the packet size must be within 14 bits due to this + // format restriction, or it will overlap with the flag bit. + if (size < 0x40) { + packet.push_back(static_cast<u8>(size)); + } else if (size < 0x4000) { + packet.push_back(static_cast<u8>(size >> 8) | 0x40); + packet.push_back(static_cast<u8>(size)); + } else { + ASSERT(false); + } + + // puts the payload + packet.insert(packet.end(), payload.begin(), payload.end()); + + // calculates CRC and puts to the end + packet.push_back(boost::crc<8, 0x07, 0, 0, false, false>(packet.data(), packet.size())); + + if (receive_buffer->Put(packet)) { + receive_event->Signal(); + } else { + LOG_ERROR(Service_IR, "receive buffer is full!"); + } +} /** * IR::InitializeIrNopShared service function + * Initializes ir:USER service with a user provided shared memory. The shared memory is configured + * to shared mode (with SharedMemoryHeader at the beginning of the shared memory). * Inputs: - * 1 : Size of transfer buffer + * 1 : Size of shared memory * 2 : Recv buffer size - * 3 : unknown + * 3 : Recv buffer packet count * 4 : Send buffer size - * 5 : unknown + * 5 : Send buffer packet count * 6 : BaudRate (u8) - * 7 : 0 - * 8 : Handle of transfer shared memory + * 7 : 0 (Handle descriptor) + * 8 : Handle of shared memory * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ static void InitializeIrNopShared(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x18, 6, 2); + const u32 shared_buff_size = rp.Pop<u32>(); + const u32 recv_buff_size = rp.Pop<u32>(); + const u32 recv_buff_packet_count = rp.Pop<u32>(); + const u32 send_buff_size = rp.Pop<u32>(); + const u32 send_buff_packet_count = rp.Pop<u32>(); + const u8 baud_rate = rp.Pop<u8>(); + const Kernel::Handle handle = rp.PopHandle(); - 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]; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - if (Kernel::g_handle_table.IsValid(handle)) { - transfer_shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle); - transfer_shared_memory->name = "IR:TransferSharedMemory"; + shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle); + if (!shared_memory) { + LOG_CRITICAL(Service_IR, "invalid shared memory handle 0x%08X", handle); + rb.Push(ResultCode(ErrorDescription::InvalidHandle, ErrorModule::OS, + ErrorSummary::WrongArgument, ErrorLevel::Permanent)); + return; } + shared_memory->name = "IR_USER: shared memory"; - cmd_buff[1] = RESULT_SUCCESS.raw; + receive_buffer = + BufferManager(shared_memory, 0x10, 0x20, recv_buff_packet_count, recv_buff_size); + SharedMemoryHeader shared_memory_init{}; + shared_memory_init.initialized = 1; + std::memcpy(shared_memory->GetPointer(), &shared_memory_init, sizeof(SharedMemoryHeader)); - 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); + rb.Push(RESULT_SUCCESS); + + LOG_INFO(Service_IR, "called, shared_buff_size=%u, recv_buff_size=%u, " + "recv_buff_packet_count=%u, send_buff_size=%u, " + "send_buff_packet_count=%u, baud_rate=%u, handle=0x%08X", + shared_buff_size, recv_buff_size, recv_buff_packet_count, send_buff_size, + send_buff_packet_count, baud_rate, handle); } /** * IR::RequireConnection service function + * Searches for an IR device and connects to it. After connecting to the device, applications can + * use SendIrNop function, ReceiveIrNop function (or read from the buffer directly) to communicate + * with the device. * Inputs: - * 1 : unknown (u8), looks like always 1 + * 1 : device ID? always 1 for circle pad pro * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ static void RequireConnection(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x06, 1, 0); + const u8 device_id = rp.Pop<u8>(); + + u8* shared_memory_ptr = shared_memory->GetPointer(); + if (device_id == 1) { + // These values are observed on a New 3DS. The meaning of them is unclear. + // TODO (wwylele): should assign network_id a (random?) number + shared_memory_ptr[offsetof(SharedMemoryHeader, connection_status)] = 2; + shared_memory_ptr[offsetof(SharedMemoryHeader, connection_role)] = 2; + shared_memory_ptr[offsetof(SharedMemoryHeader, connected)] = 1; + + connected_device = extra_hid.get(); + connected_device->OnConnect(); + conn_status_event->Signal(); + } else { + LOG_WARNING(Service_IR, "unknown device id %u. Won't connect.", device_id); + shared_memory_ptr[offsetof(SharedMemoryHeader, connection_status)] = 1; + shared_memory_ptr[offsetof(SharedMemoryHeader, trying_to_connect_status)] = 2; + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + + LOG_INFO(Service_IR, "called, device_id = %u", device_id); +} + +/** + * IR::GetReceiveEvent service function + * Gets an event that is signaled when a packet is received from the IR device. + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : 0 (Handle descriptor) + * 3 : Receive event handle + */ +void GetReceiveEvent(Interface* self) { + IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0A, 1, 2); - conn_status_event->Signal(); + rb.Push(RESULT_SUCCESS); + rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::receive_event).MoveFrom()); + + LOG_INFO(Service_IR, "called"); +} + +/** + * IR::GetSendEvent service function + * Gets an event that is signaled when the sending of a packet is complete + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : 0 (Handle descriptor) + * 3 : Send event handle + */ +void GetSendEvent(Interface* self) { + IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0B, 1, 2); - cmd_buff[1] = RESULT_SUCCESS.raw; + rb.Push(RESULT_SUCCESS); + rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::send_event).MoveFrom()); - LOG_WARNING(Service_IR, "(STUBBED) called"); + LOG_INFO(Service_IR, "called"); } /** * IR::Disconnect service function + * Disconnects from the current connected IR device. * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ static void Disconnect(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + if (connected_device) { + connected_device->OnDisconnect(); + connected_device = nullptr; + conn_status_event->Signal(); + } + + u8* shared_memory_ptr = shared_memory->GetPointer(); + shared_memory_ptr[offsetof(SharedMemoryHeader, connection_status)] = 0; + shared_memory_ptr[offsetof(SharedMemoryHeader, connected)] = 0; - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x09, 1, 0); + rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_IR, "(STUBBED) called"); + LOG_INFO(Service_IR, "called"); } /** * IR::GetConnectionStatusEvent service function + * Gets an event that is signaled when the connection status is changed * Outputs: * 1 : Result of function, 0 on success, otherwise error code - * 2 : Connection Status Event handle + * 2 : 0 (Handle descriptor) + * 3 : Connection Status Event handle */ static void GetConnectionStatusEvent(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0C, 1, 2); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom(); + rb.Push(RESULT_SUCCESS); + rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom()); - LOG_WARNING(Service_IR, "(STUBBED) called"); + LOG_INFO(Service_IR, "called"); } /** * IR::FinalizeIrNop service function + * Finalize ir:USER service. * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ static void FinalizeIrNop(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + if (connected_device) { + connected_device->OnDisconnect(); + connected_device = nullptr; + } + + shared_memory = nullptr; + receive_buffer = boost::none; + + IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x02, 1, 0); + rb.Push(RESULT_SUCCESS); + + LOG_INFO(Service_IR, "called"); +} + +/** + * IR::SendIrNop service function + * Sends a packet to the connected IR device + * Inpus: + * 1 : Size of data to send + * 2 : 2 + (size << 14) (Static buffer descriptor) + * 3 : Data buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void SendIrNop(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0D, 1, 2); + const u32 size = rp.Pop<u32>(); + const VAddr address = rp.PopStaticBuffer(); + + std::vector<u8> buffer(size); + Memory::ReadBlock(address, buffer.data(), size); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + if (connected_device) { + connected_device->OnReceive(buffer); + send_event->Signal(); + rb.Push(RESULT_SUCCESS); + } else { + LOG_ERROR(Service_IR, "not connected"); + rb.Push(ResultCode(static_cast<ErrorDescription>(13), ErrorModule::IR, + ErrorSummary::InvalidState, ErrorLevel::Status)); + } + + LOG_TRACE(Service_IR, "called, data=%s", Common::ArrayToString(buffer.data(), size).c_str()); +} + +/** + * IR::ReleaseReceivedData function + * Release a specified amount of packet from the receive buffer. This is called after the + * application reads received packet from the buffer directly, to release the buffer space for + * future packets. + * Inpus: + * 1 : Number of packets to release + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void ReleaseReceivedData(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x19, 1, 0); + u32 count = rp.Pop<u32>(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + if (receive_buffer->Release(count)) { + rb.Push(RESULT_SUCCESS); + } else { + LOG_ERROR(Service_IR, "failed to release %u packets", count); + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::IR, ErrorSummary::NotFound, + ErrorLevel::Status)); + } - LOG_WARNING(Service_IR, "(STUBBED) called"); + LOG_TRACE(Service_IR, "called, count=%u", count); } const Interface::FunctionInfo FunctionTable[] = { @@ -118,10 +489,10 @@ const Interface::FunctionInfo FunctionTable[] = { {0x000702C0, nullptr, "AutoConnection"}, {0x00080000, nullptr, "AnyConnection"}, {0x00090000, Disconnect, "Disconnect"}, - {0x000A0000, nullptr, "GetReceiveEvent"}, - {0x000B0000, nullptr, "GetSendEvent"}, + {0x000A0000, GetReceiveEvent, "GetReceiveEvent"}, + {0x000B0000, GetSendEvent, "GetSendEvent"}, {0x000C0000, GetConnectionStatusEvent, "GetConnectionStatusEvent"}, - {0x000D0042, nullptr, "SendIrNop"}, + {0x000D0042, SendIrNop, "SendIrNop"}, {0x000E0042, nullptr, "SendIrNopLarge"}, {0x000F0040, nullptr, "ReceiveIrnop"}, {0x00100042, nullptr, "ReceiveIrnopLarge"}, @@ -133,7 +504,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00160000, nullptr, "GetSendSizeFreeAndUsed"}, {0x00170000, nullptr, "GetConnectionRole"}, {0x00180182, InitializeIrNopShared, "InitializeIrNopShared"}, - {0x00190040, nullptr, "ReleaseReceivedData"}, + {0x00190040, ReleaseReceivedData, "ReleaseReceivedData"}, {0x001A0040, nullptr, "SetOwnMachineId"}, }; @@ -144,13 +515,43 @@ IR_User_Interface::IR_User_Interface() { void InitUser() { using namespace Kernel; - transfer_shared_memory = nullptr; + shared_memory = nullptr; + conn_status_event = Event::Create(ResetType::OneShot, "IR:ConnectionStatusEvent"); + send_event = Event::Create(ResetType::OneShot, "IR:SendEvent"); + receive_event = Event::Create(ResetType::OneShot, "IR:ReceiveEvent"); + + receive_buffer = boost::none; + + extra_hid = std::make_unique<ExtraHID>(PutToReceive); + + connected_device = nullptr; } void ShutdownUser() { - transfer_shared_memory = nullptr; + if (connected_device) { + connected_device->OnDisconnect(); + connected_device = nullptr; + } + + extra_hid = nullptr; + receive_buffer = boost::none; + shared_memory = nullptr; conn_status_event = nullptr; + send_event = nullptr; + receive_event = nullptr; +} + +void ReloadInputDevicesUser() { + if (extra_hid) + extra_hid->RequestInputDevicesReload(); +} + +IRDevice::IRDevice(SendFunc send_func_) : send_func(send_func_) {} +IRDevice::~IRDevice() = default; + +void IRDevice::Send(const std::vector<u8>& data) { + send_func(data); } } // namespace IR diff --git a/src/core/hle/service/ir/ir_user.h b/src/core/hle/service/ir/ir_user.h index 3849bd923..930650406 100644 --- a/src/core/hle/service/ir/ir_user.h +++ b/src/core/hle/service/ir/ir_user.h @@ -4,11 +4,41 @@ #pragma once +#include <functional> #include "core/hle/service/service.h" namespace Service { namespace IR { +/// An interface representing a device that can communicate with 3DS via ir:USER service +class IRDevice { +public: + /** + * A function object that implements the method to send data to the 3DS, which takes a vector of + * data to send. + */ + using SendFunc = std::function<void(const std::vector<u8>& data)>; + + explicit IRDevice(SendFunc send_func); + virtual ~IRDevice(); + + /// Called when connected with 3DS + virtual void OnConnect() = 0; + + /// Called when disconnected from 3DS + virtual void OnDisconnect() = 0; + + /// Called when data is received from the 3DS. This is invoked by the ir:USER send function. + virtual void OnReceive(const std::vector<u8>& data) = 0; + +protected: + /// Sends data to the 3DS. The actual sending method is specified in the constructor + void Send(const std::vector<u8>& data); + +private: + const SendFunc send_func; +}; + class IR_User_Interface : public Service::Interface { public: IR_User_Interface(); @@ -21,5 +51,8 @@ public: void InitUser(); void ShutdownUser(); +/// Reload input devices. Used when input configuration changed +void ReloadInputDevicesUser(); + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ldr_ro/ldr_ro.cpp b/src/core/hle/service/ldr_ro/ldr_ro.cpp index 7af76676b..d1e6d869f 100644 --- a/src/core/hle/service/ldr_ro/ldr_ro.cpp +++ b/src/core/hle/service/ldr_ro/ldr_ro.cpp @@ -40,9 +40,6 @@ static const ResultCode ERROR_INVALID_MEMORY_STATE = // 0xD8A12C08 static const ResultCode ERROR_NOT_LOADED = // 0xD8A12C0D ResultCode(static_cast<ErrorDescription>(13), ErrorModule::RO, ErrorSummary::InvalidState, ErrorLevel::Permanent); -static const ResultCode ERROR_INVALID_DESCRIPTOR = // 0xD9001830 - ResultCode(ErrorDescription::OS_InvalidBufferDescriptor, ErrorModule::OS, - ErrorSummary::WrongArgument, ErrorLevel::Permanent); static MemorySynchronizer memory_synchronizer; @@ -71,66 +68,61 @@ static bool VerifyBufferState(VAddr buffer_ptr, u32 size) { * 1 : Result of function, 0 on success, otherwise error code */ static void Initialize(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - VAddr crs_buffer_ptr = cmd_buff[1]; - u32 crs_size = cmd_buff[2]; - VAddr crs_address = cmd_buff[3]; - u32 descriptor = cmd_buff[4]; - u32 process = cmd_buff[5]; - - LOG_DEBUG(Service_LDR, "called, crs_buffer_ptr=0x%08X, crs_address=0x%08X, crs_size=0x%X, " - "descriptor=0x%08X, process=0x%08X", - crs_buffer_ptr, crs_address, crs_size, descriptor, process); - - if (descriptor != 0) { - LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); - cmd_buff[0] = IPC::MakeHeader(0, 1, 0); - cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; - return; - } + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x01, 3, 2); + VAddr crs_buffer_ptr = rp.Pop<u32>(); + u32 crs_size = rp.Pop<u32>(); + VAddr crs_address = rp.Pop<u32>(); + // TODO (wwylele): RO service checks the descriptor here and return error 0xD9001830 for + // incorrect descriptor. This error return should be probably built in IPC::RequestParser. + // All other service functions below have the same issue. + Kernel::Handle process = rp.PopHandle(); + + LOG_DEBUG(Service_LDR, + "called, crs_buffer_ptr=0x%08X, crs_address=0x%08X, crs_size=0x%X, process=0x%08X", + crs_buffer_ptr, crs_address, crs_size, process); - cmd_buff[0] = IPC::MakeHeader(1, 1, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); if (loaded_crs != 0) { LOG_ERROR(Service_LDR, "Already initialized"); - cmd_buff[1] = ERROR_ALREADY_INITIALIZED.raw; + rb.Push(ERROR_ALREADY_INITIALIZED); return; } if (crs_size < CRO_HEADER_SIZE) { LOG_ERROR(Service_LDR, "CRS is too small"); - cmd_buff[1] = ERROR_BUFFER_TOO_SMALL.raw; + rb.Push(ERROR_BUFFER_TOO_SMALL); return; } if (crs_buffer_ptr & Memory::PAGE_MASK) { LOG_ERROR(Service_LDR, "CRS original address is not aligned"); - cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + rb.Push(ERROR_MISALIGNED_ADDRESS); return; } if (crs_address & Memory::PAGE_MASK) { LOG_ERROR(Service_LDR, "CRS mapping address is not aligned"); - cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + rb.Push(ERROR_MISALIGNED_ADDRESS); return; } if (crs_size & Memory::PAGE_MASK) { LOG_ERROR(Service_LDR, "CRS size is not aligned"); - cmd_buff[1] = ERROR_MISALIGNED_SIZE.raw; + rb.Push(ERROR_MISALIGNED_SIZE); return; } if (!VerifyBufferState(crs_buffer_ptr, crs_size)) { LOG_ERROR(Service_LDR, "CRS original buffer is in invalid state"); - cmd_buff[1] = ERROR_INVALID_MEMORY_STATE.raw; + rb.Push(ERROR_INVALID_MEMORY_STATE); return; } if (crs_address < Memory::PROCESS_IMAGE_VADDR || crs_address + crs_size > Memory::PROCESS_IMAGE_VADDR_END) { LOG_ERROR(Service_LDR, "CRS mapping address is not in the process image region"); - cmd_buff[1] = ERROR_ILLEGAL_ADDRESS.raw; + rb.Push(ERROR_ILLEGAL_ADDRESS); return; } @@ -145,7 +137,7 @@ static void Initialize(Interface* self) { .Code(); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error mapping memory block %08X", result.raw); - cmd_buff[1] = result.raw; + rb.Push(result); return; } @@ -153,7 +145,7 @@ static void Initialize(Interface* self) { Kernel::VMAPermission::Read); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw); - cmd_buff[1] = result.raw; + rb.Push(result); return; } @@ -172,7 +164,7 @@ static void Initialize(Interface* self) { result = crs.Rebase(0, crs_size, 0, 0, 0, 0, true); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error rebasing CRS 0x%08X", result.raw); - cmd_buff[1] = result.raw; + rb.Push(result); return; } @@ -180,7 +172,7 @@ static void Initialize(Interface* self) { loaded_crs = crs_address; - cmd_buff[1] = RESULT_SUCCESS.raw; + rb.Push(RESULT_SUCCESS); } /** @@ -196,25 +188,17 @@ static void Initialize(Interface* self) { * 1 : Result of function, 0 on success, otherwise error code */ static void LoadCRR(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 crr_buffer_ptr = cmd_buff[1]; - u32 crr_size = cmd_buff[2]; - u32 descriptor = cmd_buff[3]; - u32 process = cmd_buff[4]; - - if (descriptor != 0) { - LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); - cmd_buff[0] = IPC::MakeHeader(0, 1, 0); - cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; - return; - } + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x02, 2, 2); + VAddr crr_buffer_ptr = rp.Pop<u32>(); + u32 crr_size = rp.Pop<u32>(); + Kernel::Handle process = rp.PopHandle(); - cmd_buff[0] = IPC::MakeHeader(2, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_LDR, "(STUBBED) called, crr_buffer_ptr=0x%08X, crr_size=0x%08X, " - "descriptor=0x%08X, process=0x%08X", - crr_buffer_ptr, crr_size, descriptor, process); + LOG_WARNING(Service_LDR, + "(STUBBED) called, crr_buffer_ptr=0x%08X, crr_size=0x%08X, process=0x%08X", + crr_buffer_ptr, crr_size, process); } /** @@ -229,24 +213,15 @@ static void LoadCRR(Interface* self) { * 1 : Result of function, 0 on success, otherwise error code */ static void UnloadCRR(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 crr_buffer_ptr = cmd_buff[1]; - u32 descriptor = cmd_buff[2]; - u32 process = cmd_buff[3]; - - if (descriptor != 0) { - LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); - cmd_buff[0] = IPC::MakeHeader(0, 1, 0); - cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; - return; - } + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 2); + u32 crr_buffer_ptr = rp.Pop<u32>(); + Kernel::Handle process = rp.PopHandle(); - cmd_buff[0] = IPC::MakeHeader(3, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_LDR, - "(STUBBED) called, crr_buffer_ptr=0x%08X, descriptor=0x%08X, process=0x%08X", - crr_buffer_ptr, descriptor, process); + LOG_WARNING(Service_LDR, "(STUBBED) called, crr_buffer_ptr=0x%08X, process=0x%08X", + crr_buffer_ptr, process); } /** @@ -276,87 +251,85 @@ static void UnloadCRR(Interface* self) { * There is a dispatcher template below. */ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - VAddr cro_buffer_ptr = cmd_buff[1]; - VAddr cro_address = cmd_buff[2]; - u32 cro_size = cmd_buff[3]; - VAddr data_segment_address = cmd_buff[4]; - u32 zero = cmd_buff[5]; - u32 data_segment_size = cmd_buff[6]; - u32 bss_segment_address = cmd_buff[7]; - u32 bss_segment_size = cmd_buff[8]; - bool auto_link = (cmd_buff[9] & 0xFF) != 0; - u32 fix_level = cmd_buff[10]; - VAddr crr_address = cmd_buff[11]; - u32 descriptor = cmd_buff[12]; - u32 process = cmd_buff[13]; - - LOG_DEBUG(Service_LDR, - "called (%s), cro_buffer_ptr=0x%08X, cro_address=0x%08X, cro_size=0x%X, " - "data_segment_address=0x%08X, zero=%d, data_segment_size=0x%X, " - "bss_segment_address=0x%08X, bss_segment_size=0x%X, " - "auto_link=%s, fix_level=%d, crr_address=0x%08X, descriptor=0x%08X, process=0x%08X", + IPC::RequestParser rp(Kernel::GetCommandBuffer(), link_on_load_bug_fix ? 0x09 : 0x04, 11, 2); + VAddr cro_buffer_ptr = rp.Pop<u32>(); + VAddr cro_address = rp.Pop<u32>(); + u32 cro_size = rp.Pop<u32>(); + VAddr data_segment_address = rp.Pop<u32>(); + u32 zero = rp.Pop<u32>(); + u32 data_segment_size = rp.Pop<u32>(); + u32 bss_segment_address = rp.Pop<u32>(); + u32 bss_segment_size = rp.Pop<u32>(); + bool auto_link = rp.Pop<bool>(); + u32 fix_level = rp.Pop<u32>(); + VAddr crr_address = rp.Pop<u32>(); + Kernel::Handle process = rp.PopHandle(); + + LOG_DEBUG(Service_LDR, "called (%s), cro_buffer_ptr=0x%08X, cro_address=0x%08X, cro_size=0x%X, " + "data_segment_address=0x%08X, zero=%d, data_segment_size=0x%X, " + "bss_segment_address=0x%08X, bss_segment_size=0x%X, auto_link=%s, " + "fix_level=%d, crr_address=0x%08X, process=0x%08X", link_on_load_bug_fix ? "new" : "old", cro_buffer_ptr, cro_address, cro_size, data_segment_address, zero, data_segment_size, bss_segment_address, bss_segment_size, - auto_link ? "true" : "false", fix_level, crr_address, descriptor, process); - - if (descriptor != 0) { - LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); - cmd_buff[0] = IPC::MakeHeader(0, 1, 0); - cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; - return; - } + auto_link ? "true" : "false", fix_level, crr_address, process); - cmd_buff[0] = IPC::MakeHeader(link_on_load_bug_fix ? 9 : 4, 2, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); if (loaded_crs == 0) { LOG_ERROR(Service_LDR, "Not initialized"); - cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; + rb.Push(ERROR_NOT_INITIALIZED); + rb.Push<u32>(0); return; } if (cro_size < CRO_HEADER_SIZE) { LOG_ERROR(Service_LDR, "CRO too small"); - cmd_buff[1] = ERROR_BUFFER_TOO_SMALL.raw; + rb.Push(ERROR_BUFFER_TOO_SMALL); + rb.Push<u32>(0); return; } if (cro_buffer_ptr & Memory::PAGE_MASK) { LOG_ERROR(Service_LDR, "CRO original address is not aligned"); - cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + rb.Push(ERROR_MISALIGNED_ADDRESS); + rb.Push<u32>(0); return; } if (cro_address & Memory::PAGE_MASK) { LOG_ERROR(Service_LDR, "CRO mapping address is not aligned"); - cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + rb.Push(ERROR_MISALIGNED_ADDRESS); + rb.Push<u32>(0); return; } if (cro_size & Memory::PAGE_MASK) { LOG_ERROR(Service_LDR, "CRO size is not aligned"); - cmd_buff[1] = ERROR_MISALIGNED_SIZE.raw; + rb.Push(ERROR_MISALIGNED_SIZE); + rb.Push<u32>(0); return; } if (!VerifyBufferState(cro_buffer_ptr, cro_size)) { LOG_ERROR(Service_LDR, "CRO original buffer is in invalid state"); - cmd_buff[1] = ERROR_INVALID_MEMORY_STATE.raw; + rb.Push(ERROR_INVALID_MEMORY_STATE); + rb.Push<u32>(0); return; } if (cro_address < Memory::PROCESS_IMAGE_VADDR || cro_address + cro_size > Memory::PROCESS_IMAGE_VADDR_END) { LOG_ERROR(Service_LDR, "CRO mapping address is not in the process image region"); - cmd_buff[1] = ERROR_ILLEGAL_ADDRESS.raw; + rb.Push(ERROR_ILLEGAL_ADDRESS); + rb.Push<u32>(0); return; } if (zero) { LOG_ERROR(Service_LDR, "Zero is not zero %d", zero); - cmd_buff[1] = ResultCode(static_cast<ErrorDescription>(29), ErrorModule::RO, - ErrorSummary::Internal, ErrorLevel::Usage) - .raw; + rb.Push(ResultCode(static_cast<ErrorDescription>(29), ErrorModule::RO, + ErrorSummary::Internal, ErrorLevel::Usage)); + rb.Push<u32>(0); return; } @@ -371,7 +344,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) { .Code(); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error mapping memory block %08X", result.raw); - cmd_buff[1] = result.raw; + rb.Push(result); + rb.Push<u32>(0); return; } @@ -380,7 +354,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) { if (result.IsError()) { LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw); Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); - cmd_buff[1] = result.raw; + rb.Push(result); + rb.Push<u32>(0); return; } @@ -400,7 +375,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) { if (result.IsError()) { LOG_ERROR(Service_LDR, "Error verifying CRO in CRR %08X", result.raw); Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); - cmd_buff[1] = result.raw; + rb.Push(result); + rb.Push<u32>(0); return; } @@ -409,7 +385,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) { if (result.IsError()) { LOG_ERROR(Service_LDR, "Error rebasing CRO %08X", result.raw); Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); - cmd_buff[1] = result.raw; + rb.Push(result); + rb.Push<u32>(0); return; } @@ -417,7 +394,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) { if (result.IsError()) { LOG_ERROR(Service_LDR, "Error linking CRO %08X", result.raw); Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); - cmd_buff[1] = result.raw; + rb.Push(result); + rb.Push<u32>(0); return; } @@ -435,7 +413,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) { if (result.IsError()) { LOG_ERROR(Service_LDR, "Error unmapping memory block %08X", result.raw); Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size); - cmd_buff[1] = result.raw; + rb.Push(result); + rb.Push<u32>(0); return; } } @@ -453,7 +432,8 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) { if (result.IsError()) { LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw); Kernel::g_current_process->vm_manager.UnmapRange(cro_address, fix_size); - cmd_buff[1] = result.raw; + rb.Push(result); + rb.Push<u32>(0); return; } } @@ -463,8 +443,7 @@ static void LoadCRO(Interface* self, bool link_on_load_bug_fix) { LOG_INFO(Service_LDR, "CRO \"%s\" loaded at 0x%08X, fixed_end=0x%08X", cro.ModuleName().data(), cro_address, cro_address + fix_size); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = fix_size; + rb.Push(RESULT_SUCCESS, fix_size); } template <bool link_on_load_bug_fix> @@ -486,43 +465,35 @@ static void LoadCRO(Interface* self) { * 1 : Result of function, 0 on success, otherwise error code */ static void UnloadCRO(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - VAddr cro_address = cmd_buff[1]; - u32 zero = cmd_buff[2]; - VAddr cro_buffer_ptr = cmd_buff[3]; - u32 descriptor = cmd_buff[4]; - u32 process = cmd_buff[5]; - - LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, zero=%d, cro_buffer_ptr=0x%08X, " - "descriptor=0x%08X, process=0x%08X", - cro_address, zero, cro_buffer_ptr, descriptor, process); - - if (descriptor != 0) { - LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); - cmd_buff[0] = IPC::MakeHeader(0, 1, 0); - cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; - return; - } + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x05, 3, 2); + VAddr cro_address = rp.Pop<u32>(); + u32 zero = rp.Pop<u32>(); + VAddr cro_buffer_ptr = rp.Pop<u32>(); + Kernel::Handle process = rp.PopHandle(); + + LOG_DEBUG(Service_LDR, + "called, cro_address=0x%08X, zero=%d, cro_buffer_ptr=0x%08X, process=0x%08X", + cro_address, zero, cro_buffer_ptr, process); CROHelper cro(cro_address); - cmd_buff[0] = IPC::MakeHeader(5, 1, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); if (loaded_crs == 0) { LOG_ERROR(Service_LDR, "Not initialized"); - cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; + rb.Push(ERROR_NOT_INITIALIZED); return; } if (cro_address & Memory::PAGE_MASK) { LOG_ERROR(Service_LDR, "CRO address is not aligned"); - cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + rb.Push(ERROR_MISALIGNED_ADDRESS); return; } if (!cro.IsLoaded()) { LOG_ERROR(Service_LDR, "Invalid or not loaded CRO"); - cmd_buff[1] = ERROR_NOT_LOADED.raw; + rb.Push(ERROR_NOT_LOADED); return; } @@ -535,7 +506,7 @@ static void UnloadCRO(Interface* self) { ResultCode result = cro.Unlink(loaded_crs); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error unlinking CRO %08X", result.raw); - cmd_buff[1] = result.raw; + rb.Push(result); return; } @@ -545,7 +516,7 @@ static void UnloadCRO(Interface* self) { result = cro.ClearRelocations(); if (result.IsError()) { LOG_ERROR(Service_LDR, "Error clearing relocations %08X", result.raw); - cmd_buff[1] = result.raw; + rb.Push(result); return; } } @@ -565,7 +536,7 @@ static void UnloadCRO(Interface* self) { Core::CPU().ClearInstructionCache(); - cmd_buff[1] = result.raw; + rb.Push(result); } /** @@ -580,40 +551,31 @@ static void UnloadCRO(Interface* self) { * 1 : Result of function, 0 on success, otherwise error code */ static void LinkCRO(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - VAddr cro_address = cmd_buff[1]; - u32 descriptor = cmd_buff[2]; - u32 process = cmd_buff[3]; - - LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, descriptor=0x%08X, process=0x%08X", - cro_address, descriptor, process); - - if (descriptor != 0) { - LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); - cmd_buff[0] = IPC::MakeHeader(0, 1, 0); - cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; - return; - } + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x06, 1, 2); + VAddr cro_address = rp.Pop<u32>(); + Kernel::Handle process = rp.PopHandle(); + + LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, process=0x%08X", cro_address, process); CROHelper cro(cro_address); - cmd_buff[0] = IPC::MakeHeader(6, 1, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); if (loaded_crs == 0) { LOG_ERROR(Service_LDR, "Not initialized"); - cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; + rb.Push(ERROR_NOT_INITIALIZED); return; } if (cro_address & Memory::PAGE_MASK) { LOG_ERROR(Service_LDR, "CRO address is not aligned"); - cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + rb.Push(ERROR_MISALIGNED_ADDRESS); return; } if (!cro.IsLoaded()) { LOG_ERROR(Service_LDR, "Invalid or not loaded CRO"); - cmd_buff[1] = ERROR_NOT_LOADED.raw; + rb.Push(ERROR_NOT_LOADED); return; } @@ -627,7 +589,7 @@ static void LinkCRO(Interface* self) { memory_synchronizer.SynchronizeOriginalMemory(); Core::CPU().ClearInstructionCache(); - cmd_buff[1] = result.raw; + rb.Push(result); } /** @@ -642,40 +604,31 @@ static void LinkCRO(Interface* self) { * 1 : Result of function, 0 on success, otherwise error code */ static void UnlinkCRO(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - VAddr cro_address = cmd_buff[1]; - u32 descriptor = cmd_buff[2]; - u32 process = cmd_buff[3]; - - LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, descriptor=0x%08X, process=0x%08X", - cro_address, descriptor, process); - - if (descriptor != 0) { - LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); - cmd_buff[0] = IPC::MakeHeader(0, 1, 0); - cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; - return; - } + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x07, 1, 2); + VAddr cro_address = rp.Pop<u32>(); + Kernel::Handle process = rp.PopHandle(); + + LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, process=0x%08X", cro_address, process); CROHelper cro(cro_address); - cmd_buff[0] = IPC::MakeHeader(7, 1, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); if (loaded_crs == 0) { LOG_ERROR(Service_LDR, "Not initialized"); - cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; + rb.Push(ERROR_NOT_INITIALIZED); return; } if (cro_address & Memory::PAGE_MASK) { LOG_ERROR(Service_LDR, "CRO address is not aligned"); - cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw; + rb.Push(ERROR_MISALIGNED_ADDRESS); return; } if (!cro.IsLoaded()) { LOG_ERROR(Service_LDR, "Invalid or not loaded CRO"); - cmd_buff[1] = ERROR_NOT_LOADED.raw; + rb.Push(ERROR_NOT_LOADED); return; } @@ -689,7 +642,7 @@ static void UnlinkCRO(Interface* self) { memory_synchronizer.SynchronizeOriginalMemory(); Core::CPU().ClearInstructionCache(); - cmd_buff[1] = result.raw; + rb.Push(result); } /** @@ -704,29 +657,21 @@ static void UnlinkCRO(Interface* self) { * 1 : Result of function, 0 on success, otherwise error code */ static void Shutdown(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - VAddr crs_buffer_ptr = cmd_buff[1]; - u32 descriptor = cmd_buff[2]; - u32 process = cmd_buff[3]; - - LOG_DEBUG(Service_LDR, "called, crs_buffer_ptr=0x%08X, descriptor=0x%08X, process=0x%08X", - crs_buffer_ptr, descriptor, process); - - if (descriptor != 0) { - LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor); - cmd_buff[0] = IPC::MakeHeader(0, 1, 0); - cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw; - return; - } + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x08, 1, 2); + VAddr crs_buffer_ptr = rp.Pop<u32>(); + Kernel::Handle process = rp.PopHandle(); + + LOG_DEBUG(Service_LDR, "called, crs_buffer_ptr=0x%08X, process=0x%08X", crs_buffer_ptr, + process); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); if (loaded_crs == 0) { LOG_ERROR(Service_LDR, "Not initialized"); - cmd_buff[1] = ERROR_NOT_INITIALIZED.raw; + rb.Push(ERROR_NOT_INITIALIZED); return; } - cmd_buff[0] = IPC::MakeHeader(8, 1, 0); - CROHelper crs(loaded_crs); crs.Unrebase(true); @@ -744,7 +689,7 @@ static void Shutdown(Interface* self) { } loaded_crs = 0; - cmd_buff[1] = result.raw; + rb.Push(result); } const Interface::FunctionInfo FunctionTable[] = { diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index e373ed47a..319e8c946 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -27,67 +27,72 @@ static bool shell_open; static bool battery_is_charging; -void GetAdapterState(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); +static bool pedometer_is_counting; - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. +void GetAdapterState(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x5, 0, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = battery_is_charging ? 1 : 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(battery_is_charging); LOG_WARNING(Service_PTM, "(STUBBED) called"); } void GetShellState(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x6, 0, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = shell_open ? 1 : 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(shell_open); } void GetBatteryLevel(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x7, 0, 0); - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. - - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = - static_cast<u32>(ChargeLevels::CompletelyFull); // Set to a completely full battery + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u32>(ChargeLevels::CompletelyFull)); // Set to a completely full battery LOG_WARNING(Service_PTM, "(STUBBED) called"); } void GetBatteryChargeState(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x8, 0, 0); - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(battery_is_charging); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = battery_is_charging ? 1 : 0; + LOG_WARNING(Service_PTM, "(STUBBED) called"); +} + +void GetPedometerState(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 0, 0); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(pedometer_is_counting); LOG_WARNING(Service_PTM, "(STUBBED) called"); } void GetTotalStepCount(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xC, 0, 0); - // TODO: This function is only a stub, - // it returns 0 as the total step count - - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); LOG_WARNING(Service_PTM, "(STUBBED) called"); } void GetSoftwareClosedFlag(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x80F, 0, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(false); LOG_WARNING(Service_PTM, "(STUBBED) called"); } @@ -121,6 +126,7 @@ void Init() { shell_open = true; battery_is_charging = true; + pedometer_is_counting = false; // Open the SharedExtSaveData archive 0xF000000B and create the gamecoin.dat file if it doesn't // exist diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h index 683fb445b..e17e59835 100644 --- a/src/core/hle/service/ptm/ptm.h +++ b/src/core/hle/service/ptm/ptm.h @@ -75,6 +75,14 @@ void GetBatteryLevel(Interface* self); void GetBatteryChargeState(Interface* self); /** + * PTM::GetPedometerState service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Output of function, 0 = not counting steps, 1 = counting steps. + */ +void GetPedometerState(Interface* self); + +/** * PTM::GetTotalStepCount service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code diff --git a/src/core/hle/service/ptm/ptm_u.cpp b/src/core/hle/service/ptm/ptm_u.cpp index e0b65ba89..696a58a36 100644 --- a/src/core/hle/service/ptm/ptm_u.cpp +++ b/src/core/hle/service/ptm/ptm_u.cpp @@ -17,7 +17,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00060000, GetShellState, "GetShellState"}, {0x00070000, GetBatteryLevel, "GetBatteryLevel"}, {0x00080000, GetBatteryChargeState, "GetBatteryChargeState"}, - {0x00090000, nullptr, "GetPedometerState"}, + {0x00090000, GetPedometerState, "GetPedometerState"}, {0x000A0042, nullptr, "GetStepHistoryEntry"}, {0x000B00C2, nullptr, "GetStepHistory"}, {0x000C0000, GetTotalStepCount, "GetTotalStepCount"}, |