From 81f5be30aa080ead84e32ad7fa27a5552ebdd6bd Mon Sep 17 00:00:00 2001 From: Joshua de Reeper Date: Fri, 31 Oct 2025 13:17:51 -0400 Subject: [PATCH] sys_usbd: Emulate Kamen Rider Summonride Ride Gate (#17605) This PR aims to implement another USB peripheral device, used in Kamen Rider Summonride. Code is very similar to Skylanders (for the loading/creating/removing of figures) and Disney Infinity for the actual USB commands and responses (minus any of the built in random number generation). I haven't been able to generate a full list of the Ride Chips (see [here](https://kamenrider.fandom.com/wiki/Kamen_Rider_Summonride#Characters) for a list of Characters and Chips) besides the ones I have myself from the Starter Set, so I am hoping that interested parties are able to play around and discover what other chips there may be, or if there are any issues when generating their own Characters and Chips. Implements https://github.com/RPCS3/rpcs3/issues/17122 --- rpcs3/Emu/CMakeLists.txt | 1 + rpcs3/Emu/Cell/lv2/sys_usbd.cpp | 3 +- rpcs3/Emu/Io/KamenRider.cpp | 291 ++++++++++++++++++ rpcs3/Emu/Io/KamenRider.h | 60 ++++ rpcs3/emucore.vcxproj | 2 + rpcs3/emucore.vcxproj.filters | 6 + rpcs3/rpcs3.vcxproj | 17 ++ rpcs3/rpcs3.vcxproj.filters | 15 + rpcs3/rpcs3qt/CMakeLists.txt | 1 + rpcs3/rpcs3qt/kamen_rider_dialog.cpp | 422 +++++++++++++++++++++++++++ rpcs3/rpcs3qt/kamen_rider_dialog.h | 49 ++++ rpcs3/rpcs3qt/main_window.cpp | 9 +- rpcs3/rpcs3qt/main_window.ui | 6 + 13 files changed, 880 insertions(+), 2 deletions(-) create mode 100644 rpcs3/Emu/Io/KamenRider.cpp create mode 100644 rpcs3/Emu/Io/KamenRider.h create mode 100644 rpcs3/rpcs3qt/kamen_rider_dialog.cpp create mode 100644 rpcs3/rpcs3qt/kamen_rider_dialog.h diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 2fcaf4361f..139688947d 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -402,6 +402,7 @@ target_sources(rpcs3_emu PRIVATE Io/GunCon3.cpp Io/Infinity.cpp Io/interception.cpp + Io/KamenRider.cpp Io/KeyboardHandler.cpp Io/midi_config_types.cpp Io/mouse_config.cpp diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp index e9f57dbec6..fd5257b03a 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp @@ -18,6 +18,7 @@ #include "Emu/Io/Skylander.h" #include "Emu/Io/Infinity.h" #include "Emu/Io/Dimensions.h" +#include "Emu/Io/KamenRider.h" #include "Emu/Io/GHLtar.h" #include "Emu/Io/ghltar_config.h" #include "Emu/Io/guncon3_config.h" @@ -175,7 +176,7 @@ private: {0x1430, 0x0150, 0x0150, "Skylanders Portal", &usb_device_skylander::get_num_emu_devices, &usb_device_skylander::make_instance}, {0x0E6F, 0x0129, 0x0129, "Disney Infinity Base", &usb_device_infinity::get_num_emu_devices, &usb_device_infinity::make_instance}, {0x0E6F, 0x0241, 0x0241, "Lego Dimensions Portal", &usb_device_dimensions::get_num_emu_devices, &usb_device_dimensions::make_instance}, - {0x0E6F, 0x200A, 0x200A, "Kamen Rider Summonride Portal", nullptr, nullptr}, + {0x0E6F, 0x200A, 0x200A, "Kamen Rider Summonride Portal", &usb_device_kamen_rider::get_num_emu_devices, &usb_device_kamen_rider::make_instance}, // Cameras // {0x1415, 0x0020, 0x2000, "Sony Playstation Eye", nullptr, nullptr}, // TODO: verifiy diff --git a/rpcs3/Emu/Io/KamenRider.cpp b/rpcs3/Emu/Io/KamenRider.cpp new file mode 100644 index 0000000000..aaa4836f08 --- /dev/null +++ b/rpcs3/Emu/Io/KamenRider.cpp @@ -0,0 +1,291 @@ +#include "stdafx.h" +#include "KamenRider.h" + +LOG_CHANNEL(kamen_rider_log, "kamen_rider"); + +rider_gate g_ridergate; + +void kamen_rider_figure::save() +{ + if (!kamen_file) + { + kamen_rider_log.error("Tried to save kamen rider figure to file but no kamen rider figure is active!"); + return; + } + kamen_file.seek(0, fs::seek_set); + kamen_file.write(data.data(), 0x14 * 0x10); +} + +u8 rider_gate::generate_checksum(const std::array& data, u32 num_of_bytes) const +{ + ensure(num_of_bytes <= data.size()); + int checksum = 0; + for (u32 i = 0; i < num_of_bytes; i++) + { + checksum += data[i]; + } + return (checksum & 0xFF); +} + +kamen_rider_figure& rider_gate::get_figure_by_uid(const std::array uid) +{ + for (kamen_rider_figure& figure : figures) + { + if (figure.uid == uid) + { + return figure; + } + } + return figures[7]; +} + +void rider_gate::get_blank_response(u8 command, u8 sequence, std::array& reply_buf) +{ + reply_buf = {0x55, 0x02, command, sequence}; + reply_buf[4] = generate_checksum(reply_buf, 4); +} + +void rider_gate::wake_rider_gate(std::array& reply_buf, u8 command, u8 sequence) +{ + std::lock_guard lock(kamen_mutex); + + m_is_awake = true; + reply_buf = {0x55, 0x1a, command, sequence, 0x00, 0x07, 0x00, 0x03, 0x02, + 0x09, 0x20, 0x03, 0xf5, 0x00, 0x19, 0x42, 0x52, 0xb7, + 0xb9, 0xa1, 0xae, 0x2b, 0x88, 0x42, 0x05, 0xfe, 0xe0, 0x1c, 0xac}; +} + +void rider_gate::get_list_tags(std::array& reply_buf, u8 command, u8 sequence) +{ + std::lock_guard lock(kamen_mutex); + + reply_buf = {0x55, 0x02, command, sequence}; + u8 index = 4; + for (const kamen_rider_figure& figure : figures) + { + if (figure.present) + { + reply_buf[index] = 0x09; + memcpy(&reply_buf[index + 1], figure.data.data(), 7); + index += 8; + reply_buf[1] += 8; + } + } + reply_buf[index] = generate_checksum(reply_buf, index); +} + +void rider_gate::query_block(std::array& reply_buf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block) +{ + std::lock_guard lock(kamen_mutex); + + reply_buf = {0x55, 0x13, command, sequence, 0x00}; + + const std::array uid_array = {uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6]}; + + const kamen_rider_figure& figure = get_figure_by_uid(uid_array); + if (figure.present) + { + if (sector < 5 && block < 4) + { + memcpy(&reply_buf[5], &figure.data[(sector * 4 * 16) + (block * 16)], 16); + } + } + reply_buf[21] = generate_checksum(reply_buf, 21); +} + +void rider_gate::write_block(std::array& replyBuf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block, const u8* to_write_buf) +{ + std::lock_guard lock(kamen_mutex); + + const std::array uid_array = {uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6]}; + + kamen_rider_figure& figure = get_figure_by_uid(uid_array); + if (figure.present) + { + if (sector < 5 && block < 4) + { + memcpy(&figure.data[(sector * 4 * 16) + (block * 16)], to_write_buf, 16); + } + } + + get_blank_response(command, sequence, replyBuf); +} + +std::optional> rider_gate::pop_added_removed_response() +{ + std::lock_guard lock(kamen_mutex); + + if (m_figure_added_removed_responses.empty()) + { + return std::nullopt; + } + + std::array response = m_figure_added_removed_responses.front(); + m_figure_added_removed_responses.pop(); + return response; +} + +bool rider_gate::remove_figure(u8 index) +{ + std::lock_guard lock(kamen_mutex); + + auto& figure = figures[index]; + + if (figure.present) + { + figure.present = false; + figure.save(); + figure.kamen_file.close(); + if (m_is_awake) + { + std::array figure_removed_response = {0x56, 0x09, 0x09, 0x00}; + memcpy(&figure_removed_response[4], figure.uid.data(), figure.uid.size()); + figure_removed_response[11] = generate_checksum(figure_removed_response, 11); + m_figure_added_removed_responses.push(std::move(figure_removed_response)); + } + figure.uid = {}; + return true; + } + + return false; +} + +u8 rider_gate::load_figure(const std::array& buf, fs::file in_file) +{ + std::lock_guard lock(kamen_mutex); + + u8 found_slot = 0xFF; + + // mimics spot retaining on the portal + for (auto i = 0; i < 7; i++) + { + if (!figures[i].present) + { + if (i < found_slot) + { + found_slot = i; + } + } + } + + if (found_slot != 0xFF) + { + auto& figure = figures[found_slot]; + memcpy(figure.data.data(), buf.data(), buf.size()); + figure.kamen_file = std::move(in_file); + figure.uid = {buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]}; + figure.present = true; + + if (m_is_awake) + { + std::array figure_added_response = {0x56, 0x09, 0x09, 0x01}; + memcpy(&figure_added_response[4], figure.uid.data(), figure.uid.size()); + figure_added_response[11] = generate_checksum(figure_added_response, 11); + m_figure_added_removed_responses.push(std::move(figure_added_response)); + } + } + return found_slot; +} + +usb_device_kamen_rider::usb_device_kamen_rider(const std::array& location) + : usb_device_emulated(location) +{ + device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{0x200, 0x0, 0x0, 0x0, 0x40, 0x0E6F, 0x200A, 0x100, 0x1, 0x2, 0x3, 0x1}); + auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, UsbDeviceConfiguration{0x29, 0x1, 0x1, 0x0, 0x80, 0xFA})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, UsbDeviceInterface{0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x81, 0x3, 0x40, 0x1})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x1, 0x3, 0x40, 0x1})); +} + +usb_device_kamen_rider::~usb_device_kamen_rider() +{ +} + +std::shared_ptr usb_device_kamen_rider::make_instance(u32, const std::array& location) +{ + return std::make_shared(location); +} + +u16 usb_device_kamen_rider::get_num_emu_devices() +{ + return 1; +} + +void usb_device_kamen_rider::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) +{ + usb_device_emulated::control_transfer(bmRequestType, bRequest, wValue, wIndex, wLength, buf_size, buf, transfer); +} + +void usb_device_kamen_rider::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) +{ + ensure(buf_size == 0x40); + + transfer->fake = true; + transfer->expected_count = buf_size; + transfer->expected_result = HC_CC_NOERR; + + if (endpoint == 0x81) + { + // Respond after FF command + transfer->expected_time = get_timestamp() + 1000; + std::optional> response = g_ridergate.pop_added_removed_response(); + if (response) + { + memcpy(buf, response.value().data(), 0x40); + } + else if (!m_queries.empty()) + { + memcpy(buf, m_queries.front().data(), 0x20); + m_queries.pop(); + } + else + { + transfer->expected_count = 0; + transfer->expected_result = EHCI_CC_HALTED; + } + } + else if (endpoint == 0x01) + { + const u8 command = buf[2]; + const u8 sequence = buf[3]; + + std::array q_result{}; + + switch (command) + { + case 0xB0: // Wake + { + g_ridergate.wake_rider_gate(q_result, command, sequence); + break; + } + case 0xC0: + case 0xC3: // Color Commands + { + g_ridergate.get_blank_response(command, sequence, q_result); + break; + } + case 0xD0: // Tag List + { + // Return list of figure UIDs, separated by an 09 + g_ridergate.get_list_tags(q_result, command, sequence); + break; + } + case 0xD2: // Read + { + // Read 16 bytes from figure with UID buf[4] - buf[10] + g_ridergate.query_block(q_result, command, sequence, &buf[4], buf[11], buf[12]); + break; + } + case 0xD3: + { + // Write 16 bytes to figure with UID buf[4] - buf[10] + g_ridergate.write_block(q_result, command, sequence, &buf[4], buf[11], buf[12], &buf[13]); + break; + } + default: + kamen_rider_log.error("Unhandled Query Type: 0x%02X", command); + break; + } + m_queries.push(std::move(q_result)); + } +} diff --git a/rpcs3/Emu/Io/KamenRider.h b/rpcs3/Emu/Io/KamenRider.h new file mode 100644 index 0000000000..0e30024b06 --- /dev/null +++ b/rpcs3/Emu/Io/KamenRider.h @@ -0,0 +1,60 @@ +#pragma once + +#include "Emu/Io/usb_device.h" +#include "Utilities/mutex.h" +#include +#include +#include + +struct kamen_rider_figure +{ + fs::file kamen_file; + std::array data{}; + std::array uid{}; + bool present = false; + void save(); +}; + +class rider_gate +{ +public: + void get_blank_response(u8 command, u8 sequence, std::array& reply_buf); + void wake_rider_gate(std::array& replyBuf, u8 command, u8 sequence); + void get_list_tags(std::array& replyBuf, u8 command, u8 sequence); + void query_block(std::array& replyBuf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block); + void write_block(std::array& replyBuf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block, const u8* to_write_buf); + std::optional> pop_added_removed_response(); + + bool remove_figure(u8 position); + u8 load_figure(const std::array& buf, fs::file in_file); + +protected: + shared_mutex kamen_mutex; + std::array figures{}; + +private: + u8 generate_checksum(const std::array& data, u32 num_of_bytes) const; + kamen_rider_figure& get_figure_by_uid(const std::array uid); + + std::queue> m_figure_added_removed_responses; + + bool m_is_awake = false; +}; + +extern rider_gate g_ridergate; + +class usb_device_kamen_rider : public usb_device_emulated +{ +public: + usb_device_kamen_rider(const std::array& location); + ~usb_device_kamen_rider(); + + static std::shared_ptr make_instance(u32 controller_index, const std::array& location); + static u16 get_num_emu_devices(); + + void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override; + void interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) override; + +protected: + std::queue> m_queries; +}; diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 2cc3d31e39..eaf4c63193 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -450,6 +450,7 @@ + @@ -809,6 +810,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index c91fbb2a50..cf0b6a5a87 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -879,6 +879,9 @@ Emu\Io + + Emu\Io + Emu\Cell\lv2 @@ -2079,6 +2082,9 @@ Emu\Io + + Emu\Io + Crypto diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index bca9d45f6a..fe26cc1968 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -328,6 +328,9 @@ true + + true + true @@ -616,6 +619,9 @@ true + + true + true @@ -819,6 +825,7 @@ + @@ -1665,6 +1672,16 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + Moc%27ing %(Identity)... .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 2ffaca09b5..7c98b7c735 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -107,6 +107,9 @@ {f5fcca0d-918b-46ba-bb91-2f2f9d9ddbba} + + {8b4d2dff-2b4e-4794-9859-4379ef0e75c0} + {c25f8f80-cc74-4760-8488-a291b3026b1d} @@ -639,6 +642,9 @@ Gui\infinity + + Gui\infinity + Gui\skylanders @@ -753,6 +759,12 @@ Generated Files\Release + + Generated Files\Debug + + + Generated Files\Release + Generated Files\Debug @@ -1621,6 +1633,9 @@ Gui\infinity + + Gui\kamen_rider + Gui\skylanders diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 7f7605cb74..f194b7550b 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -45,6 +45,7 @@ add_library(rpcs3_ui STATIC input_dialog.cpp instruction_editor_dialog.cpp ipc_settings_dialog.cpp + kamen_rider_dialog.cpp kernel_explorer.cpp localized.cpp localized_emu.cpp diff --git a/rpcs3/rpcs3qt/kamen_rider_dialog.cpp b/rpcs3/rpcs3qt/kamen_rider_dialog.cpp new file mode 100644 index 0000000000..7d194b0ab5 --- /dev/null +++ b/rpcs3/rpcs3qt/kamen_rider_dialog.cpp @@ -0,0 +1,422 @@ +#include "stdafx.h" +#include "Utilities/File.h" +#include "kamen_rider_dialog.h" +#include "Emu/Io/KamenRider.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +kamen_rider_dialog* kamen_rider_dialog::inst = nullptr; +std::array>, UI_FIG_NUM> kamen_rider_dialog::figure_slots = {}; +QString last_kamen_rider_path; + +static const std::map, const std::string> list_kamen_riders = { + {{0x10, 0x10}, "Kamen Rider Drive Wind"}, + {{0x10, 0x20}, "Kamen Rider Drive Water"}, + {{0x10, 0x30}, "Kamen Rider Drive Fire"}, + {{0x10, 0x40}, "Kamen Rider Drive Light"}, + {{0x10, 0x50}, "Kamen Rider Drive Dark"}, + {{0x11, 0x10}, "Kamen Rider Gaim Wind"}, + {{0x11, 0x20}, "Kamen Rider Gaim Water"}, + {{0x12, 0x20}, "Kamen Rider Wizard Water"}, + {{0x12, 0x30}, "Kamen Rider Wizard Fire"}, + {{0x13, 0x40}, "Kamen Rider Fourze Light"}, + {{0x14, 0x20}, "Kamen Rider 000 Water"}, + {{0x15, 0x10}, "Kamen Rider Double Wind"}, + {{0x16, 0x50}, "Kamen Rider Decade Dark"}, + {{0x17, 0x50}, "Kamen Rider Kiva Dark"}, + {{0x18, 0x40}, "Kamen Rider Den-O Light"}, + {{0x19, 0x30}, "Kamen Rider Kabuto Fire"}, + {{0x1A, 0x30}, "Kamen Rider Hibiki Fire"}, + {{0x1B, 0x50}, "Kamen Rider Blade Dark"}, + {{0x1C, 0x50}, "Kamen Rider Faiz Dark"}, + {{0x1D, 0x10}, "Kamen Rider Ryuki Wind"}, + {{0x1E, 0x20}, "Kamen Rider Agito Water"}, + {{0x1F, 0x40}, "Kamen Rider Kuuga Light"}, + {{0x20, 0x00}, "Type Wild"}, + {{0x21, 0x00}, "Kamen Rider Zangetsu"}, + {{0x22, 0x00}, "All Dragon"}, + {{0x31, 0x00}, "Kachidoki Arms"}, +}; + +static u32 kamen_rider_crc32(const std::array& buffer) +{ + static constexpr std::array CRC32_TABLE{ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, + 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, + 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, + 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, + 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, + 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, + 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, + 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, + 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, + 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, + 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, + 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, + 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, + 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, + 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, + 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, + 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, + 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, + 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, + 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, + 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, + 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, + 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, + 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, + 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d}; + + // Kamen Rider figures calculate their CRC32 based on 12 bytes in the block of 16 + u32 ret = 0; + for (u32 i = 0; i < 12; ++i) + { + const u8 index = u8(ret & 0xFF) ^ buffer[i]; + ret = ((ret >> 8) ^ CRC32_TABLE[index]); + } + + return ret; +} + +kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent) + : QDialog(parent) +{ + setWindowTitle(tr("Kamen Rider Creator")); + setObjectName("kamen_rider_creator"); + setMinimumSize(QSize(500, 150)); + + QVBoxLayout* vbox_panel = new QVBoxLayout(); + + QComboBox* combo_figlist = new QComboBox(); + QStringList filterlist; + for (const auto& [entry, figure_name] : list_kamen_riders) + { + const uint qvar = (entry.first << 8) | entry.second; + QString name = QString::fromStdString(figure_name); + combo_figlist->addItem(name, QVariant(qvar)); + filterlist << std::move(name); + } + combo_figlist->addItem(tr("--Unknown--"), QVariant(0xFFFF)); + combo_figlist->setEditable(true); + combo_figlist->setInsertPolicy(QComboBox::NoInsert); + combo_figlist->model()->sort(0, Qt::AscendingOrder); + + QCompleter* co_compl = new QCompleter(filterlist, this); + co_compl->setCaseSensitivity(Qt::CaseInsensitive); + co_compl->setCompletionMode(QCompleter::PopupCompletion); + co_compl->setFilterMode(Qt::MatchContains); + combo_figlist->setCompleter(co_compl); + + vbox_panel->addWidget(combo_figlist); + + QFrame* line = new QFrame(); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + vbox_panel->addWidget(line); + + QHBoxLayout* hbox_idvar = new QHBoxLayout(); + QLabel* label_id = new QLabel(tr("ID:")); + QLabel* label_type = new QLabel(tr("Type:")); + QLineEdit* edit_id = new QLineEdit("0"); + QLineEdit* edit_type = new QLineEdit("0"); + QRegularExpressionValidator* rxv = new QRegularExpressionValidator(QRegularExpression("\\d*"), this); + edit_id->setValidator(rxv); + edit_type->setValidator(rxv); + hbox_idvar->addWidget(label_id); + hbox_idvar->addWidget(edit_id); + hbox_idvar->addWidget(label_type); + hbox_idvar->addWidget(edit_type); + vbox_panel->addLayout(hbox_idvar); + + QHBoxLayout* hbox_buttons = new QHBoxLayout(); + QPushButton* btn_create = new QPushButton(tr("Create"), this); + QPushButton* btn_cancel = new QPushButton(tr("Cancel"), this); + hbox_buttons->addStretch(); + hbox_buttons->addWidget(btn_create); + hbox_buttons->addWidget(btn_cancel); + vbox_panel->addLayout(hbox_buttons); + + setLayout(vbox_panel); + + connect(combo_figlist, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) + { + const u16 fig_info = combo_figlist->itemData(index).toUInt(); + if (fig_info != 0xFFFF) + { + const u8 fig_id = fig_info >> 8; + const u8 fig_type = fig_info & 0xFF; + + edit_id->setText(QString::number(fig_id)); + edit_type->setText(QString::number(fig_type)); + } + }); + + connect(btn_create, &QAbstractButton::clicked, this, [=, this]() + { + bool ok_id = false, ok_var = false; + const u8 fig_id = edit_id->text().toUShort(&ok_id); + if (!ok_id) + { + QMessageBox::warning(this, tr("Error converting value"), tr("ID entered is invalid!"), QMessageBox::Ok); + return; + } + const u8 fig_type = edit_type->text().toUShort(&ok_var); + if (!ok_var) + { + QMessageBox::warning(this, tr("Error converting value"), tr("Variant entered is invalid!"), QMessageBox::Ok); + return; + } + + QString predef_name = last_kamen_rider_path; + const auto found_fig = list_kamen_riders.find(std::make_pair(fig_id, fig_type)); + if (found_fig != list_kamen_riders.cend()) + { + predef_name += QString::fromStdString(found_fig->second + ".bin"); + } + else + { + predef_name += QString("Unknown(%1 %2).bin").arg(fig_id).arg(fig_type); + } + + file_path = QFileDialog::getSaveFileName(this, tr("Create Kamen Rider File"), predef_name, tr("Kamen Rider Object (*.bin);;All Files (*)")); + if (file_path.isEmpty()) + { + return; + } + + fs::file fig_file(file_path.toStdString(), fs::read + fs::write + fs::create); + if (!fig_file) + { + QMessageBox::warning(this, tr("Failed to create kamen rider file!"), tr("Failed to create kamen rider file:\n%1").arg(file_path), QMessageBox::Ok); + return; + } + + std::array buf{}; + + buf[0] = 0x04; + buf[6] = 0x80; + + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution dist(0, 255); + + buf[1] = dist(mt); + buf[2] = dist(mt); + buf[3] = dist(mt); + buf[4] = dist(mt); + buf[5] = dist(mt); + + buf[7] = 0x89; + buf[8] = 0x44; + buf[10] = 0xc2; + std::array figure_data = {u8(dist(mt)), 0x03, 0x00, 0x00, 0x01, 0x0e, 0x0a, 0x0a, 0x10, fig_type, 0x01, fig_id}; + write_to_ptr>(figure_data.data(), 0xC, kamen_rider_crc32(figure_data)); + memcpy(&buf[16], figure_data.data(), figure_data.size()); + fig_file.write(buf.data(), buf.size()); + fig_file.close(); + + last_kamen_rider_path = QFileInfo(file_path).absolutePath() + "/"; + accept(); + }); + + connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject); + + connect(co_compl, QOverload::of(&QCompleter::activated), [=](const QString& text) + { + combo_figlist->setCurrentText(text); + combo_figlist->setCurrentIndex(combo_figlist->findText(text)); + }); +} + +QString kamen_rider_creator_dialog::get_file_path() const +{ + return file_path; +} + +kamen_rider_dialog::kamen_rider_dialog(QWidget* parent) + : QDialog(parent) +{ + setWindowTitle(tr("kamen_riders Manager")); + setObjectName("kamen_riders_manager"); + setAttribute(Qt::WA_DeleteOnClose); + setMinimumSize(QSize(700, 200)); + + QVBoxLayout* vbox_panel = new QVBoxLayout(); + + auto add_line = [](QVBoxLayout* vbox) + { + QFrame* line = new QFrame(); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + vbox->addWidget(line); + }; + + QGroupBox* group_kamen_riders = new QGroupBox(tr("Active Portal Kamen Riders:")); + QVBoxLayout* vbox_group = new QVBoxLayout(); + + for (auto i = 0; i < UI_FIG_NUM; i++) + { + if (i != 0) + { + add_line(vbox_group); + } + + QHBoxLayout* hbox_kamen_rider = new QHBoxLayout(); + QLabel* label_figname = new QLabel(QString(tr("Kamen Rider %1")).arg(i + 1)); + edit_kamen_riders[i] = new QLineEdit(); + edit_kamen_riders[i]->setEnabled(false); + + QPushButton* clear_btn = new QPushButton(tr("Clear")); + QPushButton* create_btn = new QPushButton(tr("Create")); + QPushButton* load_btn = new QPushButton(tr("Load")); + + connect(clear_btn, &QAbstractButton::clicked, this, [this, i]() + { + clear_kamen_rider(i); + }); + connect(create_btn, &QAbstractButton::clicked, this, [this, i]() + { + create_kamen_rider(i); + }); + connect(load_btn, &QAbstractButton::clicked, this, [this, i]() + { + load_kamen_rider(i); + }); + + hbox_kamen_rider->addWidget(label_figname); + hbox_kamen_rider->addWidget(edit_kamen_riders[i]); + hbox_kamen_rider->addWidget(clear_btn); + hbox_kamen_rider->addWidget(create_btn); + hbox_kamen_rider->addWidget(load_btn); + + vbox_group->addLayout(hbox_kamen_rider); + } + + group_kamen_riders->setLayout(vbox_group); + vbox_panel->addWidget(group_kamen_riders); + setLayout(vbox_panel); + + update_edits(); +} + +kamen_rider_dialog::~kamen_rider_dialog() +{ + inst = nullptr; +} + +kamen_rider_dialog* kamen_rider_dialog::get_dlg(QWidget* parent) +{ + if (inst == nullptr) + inst = new kamen_rider_dialog(parent); + + return inst; +} + +void kamen_rider_dialog::clear_kamen_rider(u8 slot) +{ + if (const auto& slot_infos = ::at32(figure_slots, slot)) + { + const auto& [cur_slot, id, var] = slot_infos.value(); + g_ridergate.remove_figure(cur_slot); + figure_slots[slot] = {}; + update_edits(); + } +} + +void kamen_rider_dialog::create_kamen_rider(u8 slot) +{ + kamen_rider_creator_dialog create_dlg(this); + if (create_dlg.exec() == Accepted) + { + load_kamen_rider_path(slot, create_dlg.get_file_path()); + } +} + +void kamen_rider_dialog::load_kamen_rider(u8 slot) +{ + const QString file_path = QFileDialog::getOpenFileName(this, tr("Select Kamen Rider File"), last_kamen_rider_path, tr("Kamen Rider (*.bin);;All Files (*)")); + if (file_path.isEmpty()) + { + return; + } + + last_kamen_rider_path = QFileInfo(file_path).absolutePath() + "/"; + + load_kamen_rider_path(slot, file_path); +} + +void kamen_rider_dialog::load_kamen_rider_path(u8 slot, const QString& path) +{ + fs::file fig_file(path.toStdString(), fs::read + fs::write + fs::lock); + if (!fig_file) + { + QMessageBox::warning(this, tr("Failed to open the kamen rider file!"), tr("Failed to open the kamen rider file(%1)!\nFile may already be in use on the portal.").arg(path), QMessageBox::Ok); + return; + } + + std::array data; + if (fig_file.read(data.data(), data.size()) != data.size()) + { + QMessageBox::warning(this, tr("Failed to read the kamen rider file!"), tr("Failed to read the kamen rider file(%1)!\nFile was too small.").arg(path), QMessageBox::Ok); + return; + } + + clear_kamen_rider(slot); + + u8 fig_id = data[0x19]; + u8 fig_type = data[0x1B]; + + u8 portal_slot = g_ridergate.load_figure(data, std::move(fig_file)); + figure_slots[slot] = std::tuple(portal_slot, fig_id, fig_type); + + update_edits(); +} + +void kamen_rider_dialog::update_edits() +{ + for (auto i = 0; i < UI_FIG_NUM; i++) + { + QString display_string; + if (const auto& sd = figure_slots[i]) + { + const auto& [portal_slot, fig_id, fig_type] = sd.value(); + const auto found_fig = list_kamen_riders.find(std::make_pair(fig_id, fig_type)); + if (found_fig != list_kamen_riders.cend()) + { + display_string = QString::fromStdString(found_fig->second); + } + else + { + display_string = QString(tr("Unknown (Id:%1 Var:%2)")).arg(fig_id).arg(fig_type); + } + } + else + { + display_string = tr("None"); + } + + edit_kamen_riders[i]->setText(display_string); + } +} diff --git a/rpcs3/rpcs3qt/kamen_rider_dialog.h b/rpcs3/rpcs3qt/kamen_rider_dialog.h new file mode 100644 index 0000000000..3eb040d2b6 --- /dev/null +++ b/rpcs3/rpcs3qt/kamen_rider_dialog.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include "util/types.hpp" + +#include +#include + +constexpr auto UI_FIG_NUM = 8; + +class kamen_rider_creator_dialog : public QDialog +{ + Q_OBJECT + +public: + explicit kamen_rider_creator_dialog(QWidget* parent); + QString get_file_path() const; + +protected: + QString file_path; +}; + +class kamen_rider_dialog : public QDialog +{ + Q_OBJECT + +public: + explicit kamen_rider_dialog(QWidget* parent); + ~kamen_rider_dialog(); + static kamen_rider_dialog* get_dlg(QWidget* parent); + + kamen_rider_dialog(kamen_rider_dialog const&) = delete; + void operator=(kamen_rider_dialog const&) = delete; + +protected: + void clear_kamen_rider(u8 slot); + void create_kamen_rider(u8 slot); + void load_kamen_rider(u8 slot); + void load_kamen_rider_path(u8 slot, const QString& path); + + void update_edits(); + +protected: + std::array edit_kamen_riders{}; + static std::array>, UI_FIG_NUM> figure_slots; + +private: + static kamen_rider_dialog* inst; +}; diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 2e7533cf83..38767ceabb 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -23,6 +23,7 @@ #include "skylander_dialog.h" #include "infinity_dialog.h" #include "dimensions_dialog.h" +#include "kamen_rider_dialog.h" #include "cheat_manager.h" #include "patch_manager_dialog.h" #include "patch_creator_dialog.h" @@ -2441,7 +2442,7 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri } } } - + if (!game_data_shortcuts.empty() && !locations.empty()) { m_game_list_frame->CreateShortcuts(game_data_shortcuts, locations); @@ -2999,6 +3000,12 @@ void main_window::CreateConnects() dim_dlg->show(); }); + connect(ui->actionManage_KamenRider_RideGate, &QAction::triggered, this, [this] + { + kamen_rider_dialog* kam_dlg = kamen_rider_dialog::get_dlg(this); + kam_dlg->show(); + }); + connect(ui->actionManage_Cheats, &QAction::triggered, this, [this] { cheat_manager_dialog* cheat_manager = cheat_manager_dialog::get_dlg(this); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index 91654cbb67..e10abf4668 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -302,6 +302,7 @@ + @@ -1173,6 +1174,11 @@ Dimensions Toypad + + + Kamen Rider Ride Gate + + Cheats