From 4a539d5decd2d459ed8b1c911cea3e5370846463 Mon Sep 17 00:00:00 2001 From: Der_Floh Date: Sat, 27 Jun 2026 11:11:20 +0200 Subject: [PATCH] skylander portal ipc --- rpcs3/Emu/CMakeLists.txt | 2 + rpcs3/Emu/Io/Skylander.cpp | 20 +- rpcs3/Emu/Io/Skylander.h | 1 + rpcs3/Emu/Io/SkylanderPortalIPC.cpp | 281 +++++++++++++++++++++ rpcs3/Emu/Io/SkylanderPortalIPC.h | 20 ++ rpcs3/Emu/Io/SkylanderPortalIPC_config.cpp | 66 +++++ rpcs3/Emu/Io/SkylanderPortalIPC_config.h | 23 ++ rpcs3/Emu/System.cpp | 11 + rpcs3/emucore.vcxproj | 4 + rpcs3/rpcs3.vcxproj | 17 ++ rpcs3/rpcs3qt/CMakeLists.txt | 1 + rpcs3/rpcs3qt/main_window.cpp | 7 + rpcs3/rpcs3qt/main_window.ui | 9 + rpcs3/rpcs3qt/sky_ipc_settings_dialog.cpp | 78 ++++++ rpcs3/rpcs3qt/sky_ipc_settings_dialog.h | 10 + 15 files changed, 549 insertions(+), 1 deletion(-) create mode 100644 rpcs3/Emu/Io/SkylanderPortalIPC.cpp create mode 100644 rpcs3/Emu/Io/SkylanderPortalIPC.h create mode 100644 rpcs3/Emu/Io/SkylanderPortalIPC_config.cpp create mode 100644 rpcs3/Emu/Io/SkylanderPortalIPC_config.h create mode 100644 rpcs3/rpcs3qt/sky_ipc_settings_dialog.cpp create mode 100644 rpcs3/rpcs3qt/sky_ipc_settings_dialog.h diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 045fa30769..56d703ca9e 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -19,6 +19,7 @@ add_library(rpcs3_emu STATIC perf_monitor.cpp IPC_config.cpp IPC_socket.cpp + Io/SkylanderPortalIPC_config.cpp ) if(USE_LTO) @@ -431,6 +432,7 @@ target_sources(rpcs3_emu PRIVATE Io/RB3MidiKeyboard.cpp Io/recording_config.cpp Io/Skylander.cpp + Io/SkylanderPortalIPC.cpp Io/TopShotElite.cpp Io/TopShotFearmaster.cpp Io/Turntable.cpp diff --git a/rpcs3/Emu/Io/Skylander.cpp b/rpcs3/Emu/Io/Skylander.cpp index 974e9a887f..b018ff59de 100644 --- a/rpcs3/Emu/Io/Skylander.cpp +++ b/rpcs3/Emu/Io/Skylander.cpp @@ -178,7 +178,8 @@ u8 sky_portal::load_skylander(u8* buf, fs::file in_file) } } - ensure(found_slot != 0xFF); + if (found_slot == 0xFF) + return 0xFF; skylander& thesky = skylanders[found_slot]; memcpy(thesky.data.data(), buf, thesky.data.size()); @@ -191,6 +192,23 @@ u8 sky_portal::load_skylander(u8* buf, fs::file in_file) return found_slot; } +void sky_portal::get_figure_info(u8 sky_num, u8& out_status, u16& out_id, u16& out_variant) +{ + std::lock_guard lock(sky_mutex); + const auto& s = skylanders[sky_num]; + out_status = s.status; + if (s.status & 1) + { + out_id = read_from_ptr>(s.data.data() + 0x10); + out_variant = read_from_ptr>(s.data.data() + 0x1C); + } + else + { + out_id = 0; + out_variant = 0; + } +} + usb_device_skylander::usb_device_skylander(const std::array& location) : usb_device_emulated(location) { diff --git a/rpcs3/Emu/Io/Skylander.h b/rpcs3/Emu/Io/Skylander.h index be67c8c3cc..af1c9bc4cf 100644 --- a/rpcs3/Emu/Io/Skylander.h +++ b/rpcs3/Emu/Io/Skylander.h @@ -27,6 +27,7 @@ public: bool remove_skylander(u8 sky_num); u8 load_skylander(u8* buf, fs::file in_file); + void get_figure_info(u8 sky_num, u8& out_status, u16& out_id, u16& out_variant); protected: shared_mutex sky_mutex; diff --git a/rpcs3/Emu/Io/SkylanderPortalIPC.cpp b/rpcs3/Emu/Io/SkylanderPortalIPC.cpp new file mode 100644 index 0000000000..d8a75126a0 --- /dev/null +++ b/rpcs3/Emu/Io/SkylanderPortalIPC.cpp @@ -0,0 +1,281 @@ +#include "stdafx.h" +#include "SkylanderPortalIPC.h" +#include "SkylanderPortalIPC_config.h" + +#ifdef _WIN32 +#include +#include +using sky_socket_t = SOCKET; +static constexpr sky_socket_t sky_invalid_socket = INVALID_SOCKET; +static int sky_recv(sky_socket_t s, char* buf, int len) { return recv(s, buf, len, 0); } +static int sky_send(sky_socket_t s, const char* buf, int len) { return send(s, buf, len, 0); } +static void sky_close(sky_socket_t s) { closesocket(s); } +#else +#include +#include +#include +#include +using sky_socket_t = int; +static constexpr sky_socket_t sky_invalid_socket = -1; +static int sky_recv(sky_socket_t s, char* buf, int len) { return static_cast(read(s, buf, len)); } +static int sky_send(sky_socket_t s, const char* buf, int len) { return static_cast(write(s, buf, len)); } +static void sky_close(sky_socket_t s) { close(s); } +#endif + +#include "Skylander.h" + +LOG_CHANNEL(skylander_ipc_log, "SkylanderIPC"); + +static std::string handle_load(int slot, const std::string& path) +{ + if (slot < -1 || slot > 7) + return "error invalid slot\n"; + + fs::file sky_file(path, fs::read + fs::write + fs::lock); + if (!sky_file) + return "error cannot open file\n"; + + std::array buf{}; + if (sky_file.read(buf.data(), buf.size()) != buf.size()) + return "error file too small\n"; + + const u8 result = g_skyportal.load_skylander(buf.data(), std::move(sky_file)); + if (result == 0xFF) + return "error no free slot\n"; + + return "ok " + std::to_string(result) + "\n"; +} + +static std::string handle_remove(int slot) +{ + if (slot < 0 || slot > 7) + return "error invalid slot\n"; + + if (!g_skyportal.remove_skylander(static_cast(slot))) + return "error slot empty\n"; + + return "ok\n"; +} + +static std::string handle_status() +{ + std::string result; + for (u8 i = 0; i < 8; i++) + { + u8 status; + u16 id, variant; + g_skyportal.get_figure_info(i, status, id, variant); + if (status & 1) + result += "slot" + std::to_string(i) + " loaded " + std::to_string(id) + " " + std::to_string(variant) + "\n"; + else + result += "slot" + std::to_string(i) + " empty\n"; + } + result += "ok\n"; + return result; +} + +static std::string handle_clear() +{ + for (u8 i = 0; i < 8; i++) + g_skyportal.remove_skylander(i); + return "ok\n"; +} + +static bool parse_int(const std::string& s, int& out) +{ + if (s.empty()) + return false; + char* end; + errno = 0; + const long val = std::strtol(s.c_str(), &end, 10); + if (end == s.c_str() || *end != '\0' || errno != 0) + return false; + out = static_cast(val); + return true; +} + +static std::string process_command(const std::string& line) +{ + if (line.empty()) + return "error empty command\n"; + + const usz first_space = line.find(' '); + const std::string cmd = line.substr(0, first_space); + + if (cmd == "status") + return handle_status(); + + if (cmd == "clear") + return handle_clear(); + + if (cmd == "remove") + { + if (first_space == std::string::npos) + return "error missing slot\n"; + int slot; + if (!parse_int(line.substr(first_space + 1), slot)) + return "error invalid slot\n"; + return handle_remove(slot); + } + + if (cmd == "load") + { + if (first_space == std::string::npos) + return "error missing arguments\n"; + const std::string rest = line.substr(first_space + 1); + const usz second_space = rest.find(' '); + if (second_space == std::string::npos) + return "error missing path\n"; + int slot; + if (!parse_int(rest.substr(0, second_space), slot)) + return "error invalid arguments\n"; + const std::string path = rest.substr(second_space + 1); + return handle_load(slot, path); + } + + return "error unknown command\n"; +} + +void SkylanderPortalIPCServer::operator()() +{ + sky_socket_t listen_sock = sky_invalid_socket; + const int port = g_cfg_sky_ipc.get_port(); + +#ifdef _WIN32 + WSADATA wsa{}; + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) + { + skylander_ipc_log.error("Cannot initialize winsock"); + return; + } + + listen_sock = socket(AF_INET, SOCK_STREAM, 0); + if (listen_sock == sky_invalid_socket) + { + skylander_ipc_log.error("Cannot create socket"); + WSACleanup(); + return; + } + + sockaddr_in server{}; + server.sin_family = AF_INET; + if (!inet_pton(AF_INET, "127.0.0.1", &server.sin_addr.s_addr)) + { + skylander_ipc_log.error("inet_pton failed"); + sky_close(listen_sock); + WSACleanup(); + return; + } + server.sin_port = htons(static_cast(port)); + + if (bind(listen_sock, reinterpret_cast(&server), sizeof(server)) == SOCKET_ERROR) + { + skylander_ipc_log.error("Cannot bind on port %d (already in use?)", port); + sky_close(listen_sock); + WSACleanup(); + return; + } +#else + const std::string socket_path = "/tmp/rpcs3.skylanders.sock"; + unlink(socket_path.c_str()); + + listen_sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (listen_sock == sky_invalid_socket) + { + skylander_ipc_log.error("Cannot create socket"); + return; + } + + sockaddr_un server{}; + server.sun_family = AF_UNIX; + strncpy(server.sun_path, socket_path.c_str(), sizeof(server.sun_path) - 1); + + if (bind(listen_sock, reinterpret_cast(&server), sizeof(server)) < 0) + { + skylander_ipc_log.error("Cannot bind socket at %s", socket_path); + sky_close(listen_sock); + return; + } +#endif + + // Maximum queue before refusing; SOMAXCONN is unreliable on Windows + listen(listen_sock, 4096); + skylander_ipc_log.notice("Server started (port %d)", port); + + while (thread_ctrl::state() != thread_state::aborting) + { + pollfd pfd{}; + pfd.fd = listen_sock; + pfd.events = POLLIN; + +#ifdef _WIN32 + const int poll_result = WSAPoll(&pfd, 1, 10); +#else + const int poll_result = poll(&pfd, 1, 10); +#endif + if (poll_result <= 0) + continue; + + const sky_socket_t client_sock = accept(listen_sock, nullptr, nullptr); + if (client_sock == sky_invalid_socket) + continue; + + // Read one newline-terminated command (max 4 KB) + std::string line; + char ch; + while (line.size() < 4096) + { + if (sky_recv(client_sock, &ch, 1) <= 0) + break; + if (ch == '\n' || ch == '\r') + break; + line += ch; + } + + if (!line.empty()) + { + const std::string response = process_command(line); + sky_send(client_sock, response.c_str(), static_cast(response.size())); + } + + sky_close(client_sock); + } + + sky_close(listen_sock); +#ifdef _WIN32 + WSACleanup(); +#else + unlink(socket_path.c_str()); +#endif + + skylander_ipc_log.notice("Server stopped"); +} + +SkylanderPortalIPCServer& SkylanderPortalIPCServer::operator=(thread_state) +{ + return *this; +} + +SkylanderPortalIPCServerManager::SkylanderPortalIPCServerManager(bool enabled) +{ + set_server_enabled(enabled); +} + +void SkylanderPortalIPCServerManager::set_server_enabled(bool enabled) +{ + if (enabled) + { + const int port = g_cfg_sky_ipc.get_port(); + if (!m_server || port != m_old_port) + { + skylander_ipc_log.notice("Starting server with port %d", port); + m_server = std::make_unique(); + m_old_port = port; + } + } + else if (m_server) + { + skylander_ipc_log.notice("Stopping server"); + m_server.reset(); + } +} diff --git a/rpcs3/Emu/Io/SkylanderPortalIPC.h b/rpcs3/Emu/Io/SkylanderPortalIPC.h new file mode 100644 index 0000000000..ca88b16b1e --- /dev/null +++ b/rpcs3/Emu/Io/SkylanderPortalIPC.h @@ -0,0 +1,20 @@ +#pragma once +#include "Utilities/Thread.h" + +struct SkylanderPortalIPCServer +{ + static constexpr auto thread_name = "Skylanders Portal IPC"sv; + void operator()(); + SkylanderPortalIPCServer& operator=(thread_state); +}; + +class SkylanderPortalIPCServerManager +{ + using server_thread = named_thread; + std::unique_ptr m_server; + int m_old_port = 0; + +public: + explicit SkylanderPortalIPCServerManager(bool enabled); + void set_server_enabled(bool enabled); +}; diff --git a/rpcs3/Emu/Io/SkylanderPortalIPC_config.cpp b/rpcs3/Emu/Io/SkylanderPortalIPC_config.cpp new file mode 100644 index 0000000000..14c0013ff8 --- /dev/null +++ b/rpcs3/Emu/Io/SkylanderPortalIPC_config.cpp @@ -0,0 +1,66 @@ +#include "stdafx.h" +#include "SkylanderPortalIPC_config.h" + +cfg_sky_ipc g_cfg_sky_ipc; + +LOG_CHANNEL(skylander_ipc_log, "SkylanderIPC"); + +void cfg_sky_ipc::load() +{ + const std::string path = cfg_sky_ipc::get_path(); + + fs::file cfg_file(path, fs::read); + if (cfg_file) + { + skylander_ipc_log.notice("Loading Skylanders IPC config. Path: %s", path); + from_string(cfg_file.to_string()); + } + else + { + skylander_ipc_log.notice("Skylanders IPC config missing. Using default settings. Path: %s", path); + from_default(); + } +} + +void cfg_sky_ipc::save() const +{ +#ifdef _WIN32 + const std::string path_to_cfg = fs::get_config_dir(true); + if (!fs::create_path(path_to_cfg)) + { + skylander_ipc_log.error("Could not create path: %s", path_to_cfg); + } +#endif + + const std::string path = cfg_sky_ipc::get_path(); + + if (!cfg::node::save(path)) + { + skylander_ipc_log.error("Could not save config: %s (error=%s)", path, fs::g_tls_error); + } +} + +std::string cfg_sky_ipc::get_path() +{ + return fs::get_config_dir(true) + "sky_ipc.yml"; +} + +bool cfg_sky_ipc::get_server_enabled() const +{ + return sky_ipc_server_enabled.get(); +} + +int cfg_sky_ipc::get_port() const +{ + return sky_ipc_port; +} + +void cfg_sky_ipc::set_server_enabled(const bool enabled) +{ + this->sky_ipc_server_enabled.set(enabled); +} + +void cfg_sky_ipc::set_port(const int port) +{ + this->sky_ipc_port.set(port); +} diff --git a/rpcs3/Emu/Io/SkylanderPortalIPC_config.h b/rpcs3/Emu/Io/SkylanderPortalIPC_config.h new file mode 100644 index 0000000000..84091a9266 --- /dev/null +++ b/rpcs3/Emu/Io/SkylanderPortalIPC_config.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Utilities/Config.h" + +struct cfg_sky_ipc : cfg::node +{ + cfg::_bool sky_ipc_server_enabled{ this, "Skylanders IPC Server enabled", false }; + cfg::_int<1025, 65535> sky_ipc_port{ this, "Skylanders IPC Port", 28013 }; + + void load(); + void save() const; + + bool get_server_enabled() const; + int get_port() const; + + void set_server_enabled(const bool enabled); + void set_port(const int port); + +private: + static std::string get_path(); +}; + +extern cfg_sky_ipc g_cfg_sky_ipc; diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 8f36b9a899..2e53afc5fa 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -54,6 +54,8 @@ #include "Utilities/JIT.h" #include "Emu/IPC_socket.h" +#include "Emu/Io/SkylanderPortalIPC.h" +#include "Emu/Io/SkylanderPortalIPC_config.h" #if defined(HAVE_VULKAN) #include "Emu/RSX/VK/VulkanAPI.h" @@ -800,6 +802,15 @@ void Emulator::Init() { g_fxo->init(true); } + + // Load Skylanders IPC config and start server if enabled + g_cfg_sky_ipc.load(); + sys_log.notice("Using Skylanders IPC config:\n%s", g_cfg_sky_ipc.to_string()); + + if (g_cfg_sky_ipc.get_server_enabled()) + { + g_fxo->init(true); + } } void Emulator::SetUsr(const std::string& user) diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 770028936f..bc4e5e3d28 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -114,6 +114,7 @@ + @@ -479,6 +480,7 @@ + @@ -651,6 +653,7 @@ + @@ -865,6 +868,7 @@ + diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index c4338d2acb..919f911d2f 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -344,6 +344,9 @@ true + + true + true @@ -656,6 +659,9 @@ true + + true + true @@ -877,6 +883,7 @@ + @@ -1604,6 +1611,16 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\protobuf\protobuf\src" "-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" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + + $(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 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\protobuf\protobuf\src" "-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" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(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 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\protobuf\protobuf\src" "-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" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 5dd092d074..c6dcfa72f7 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -52,6 +52,7 @@ add_library(rpcs3_ui STATIC input_dialog.cpp instruction_editor_dialog.cpp ipc_settings_dialog.cpp + sky_ipc_settings_dialog.cpp kamen_rider_dialog.cpp kernel_explorer.cpp localized.cpp diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 4a1b33610e..9f8d762c72 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -35,6 +35,7 @@ #include "camera_settings_dialog.h" #include "ps_move_tracker_dialog.h" #include "ipc_settings_dialog.h" +#include "sky_ipc_settings_dialog.h" #include "config_checker.h" #include "shortcut_dialog.h" #include "steam_utils.h" @@ -3192,6 +3193,12 @@ void main_window::CreateConnects() dlg.exec(); }); + connect(ui->confSkyIPCAct, &QAction::triggered, this, [this]() + { + sky_ipc_settings_dialog dlg(this); + dlg.exec(); + }); + connect(ui->confAutopauseManagerAct, &QAction::triggered, this, [this]() { auto_pause_settings_dialog dlg(this); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index 14f23f5a1a..4790863874 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -305,6 +305,7 @@ + @@ -1352,6 +1353,14 @@ Configure IPC + + + Skylanders Portal IPC + + + Configure Skylanders Portal IPC + + Log Viewer diff --git a/rpcs3/rpcs3qt/sky_ipc_settings_dialog.cpp b/rpcs3/rpcs3qt/sky_ipc_settings_dialog.cpp new file mode 100644 index 0000000000..f5a70840f1 --- /dev/null +++ b/rpcs3/rpcs3qt/sky_ipc_settings_dialog.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sky_ipc_settings_dialog.h" +#include "Emu/Io/SkylanderPortalIPC_config.h" +#include "Emu/Io/SkylanderPortalIPC.h" +#include "Emu/IdManager.h" +#include "Emu/System.h" + +sky_ipc_settings_dialog::sky_ipc_settings_dialog(QWidget* parent) + : QDialog(parent) +{ + setWindowTitle(tr("Skylanders Portal IPC Settings")); + setObjectName("sky_ipc_settings_dialog"); + setMinimumSize(QSize(200, 100)); + + QVBoxLayout* vbox_global = new QVBoxLayout(); + + QCheckBox* checkbox_server_enabled = new QCheckBox(tr("Enable Skylanders Portal IPC Server")); + + QGroupBox* group_server_port = new QGroupBox(tr("IPC Server Port")); + QHBoxLayout* hbox_group_port = new QHBoxLayout(); + QLineEdit* line_edit_server_port = new QLineEdit(); + line_edit_server_port->setValidator(new QIntValidator(1025, 65535, this)); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Save); + buttons->button(QDialogButtonBox::Save)->setDefault(true); + + hbox_group_port->addWidget(line_edit_server_port); + group_server_port->setLayout(hbox_group_port); + + vbox_global->addWidget(checkbox_server_enabled); + vbox_global->addWidget(group_server_port); + vbox_global->addWidget(buttons); + + setLayout(vbox_global); + + connect(buttons, &QDialogButtonBox::accepted, this, [this, checkbox_server_enabled, line_edit_server_port]() + { + bool ok = true; + const bool server_enabled = checkbox_server_enabled->isChecked(); + const int server_port = line_edit_server_port->text().toInt(&ok); + + if (!ok || server_port < 1025 || server_port > 65535) + { + QMessageBox::critical(this, tr("Invalid port"), tr("The server port must be an integer in the range 1025 - 65535!"), QMessageBox::Ok); + return; + } + + g_cfg_sky_ipc.set_server_enabled(server_enabled); + g_cfg_sky_ipc.set_port(server_port); + g_cfg_sky_ipc.save(); + + if (auto manager = g_fxo->try_get()) + { + manager->set_server_enabled(server_enabled); + } + else if (server_enabled && Emu.IsRunning()) + { + g_fxo->init(true); + } + + accept(); + }); + + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + + g_cfg_sky_ipc.load(); + + checkbox_server_enabled->setChecked(g_cfg_sky_ipc.get_server_enabled()); + line_edit_server_port->setText(QString::number(g_cfg_sky_ipc.get_port())); +} diff --git a/rpcs3/rpcs3qt/sky_ipc_settings_dialog.h b/rpcs3/rpcs3qt/sky_ipc_settings_dialog.h new file mode 100644 index 0000000000..9241c3d5ce --- /dev/null +++ b/rpcs3/rpcs3qt/sky_ipc_settings_dialog.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class sky_ipc_settings_dialog : public QDialog +{ + Q_OBJECT +public: + sky_ipc_settings_dialog(QWidget* parent = nullptr); +};