This commit is contained in:
Der_Floh 2026-07-09 19:02:22 +02:00 committed by GitHub
commit 684abdd2ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 585 additions and 34 deletions

2
3rdparty/FAudio vendored

@ -1 +1 @@
Subproject commit 4a565648400ba904f7722a72610e878581545ec4
Subproject commit 75c79d4c8ab59dfc4313e40c1997e03f7d32229f

@ -1 +1 @@
Subproject commit f87239e71e42da91ca317a12eefb82cfbf3393eb
Subproject commit 8e37db5e797b6167f3a00d697d816a684bd259c7

View File

@ -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

View File

@ -153,32 +153,41 @@ bool sky_portal::remove_skylander(u8 sky_num)
return false;
}
u8 sky_portal::load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file in_file)
u8 sky_portal::load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file in_file, int requested_slot)
{
std::lock_guard lock(sky_mutex);
const u32 sky_serial = read_from_ptr<le_t<u32>>(data);
u8 found_slot = 0xFF;
// mimics spot retaining on the portal
for (u8 i = 0; i < 8; i++)
if (requested_slot >= 0 && requested_slot <= 7)
{
if ((skylanders[i].status & 1) == 0)
if ((skylanders[requested_slot].status & 1) == 0)
found_slot = static_cast<u8>(requested_slot);
}
else
{
// mimics spot retaining on the portal
for (u8 i = 0; i < 8; i++)
{
if (skylanders[i].last_id == sky_serial)
if ((skylanders[i].status & 1) == 0)
{
found_slot = i;
break;
}
if (skylanders[i].last_id == sky_serial)
{
found_slot = i;
break;
}
if (i < found_slot)
{
found_slot = i;
if (i < found_slot)
{
found_slot = i;
}
}
}
}
ensure(found_slot != 0xFF);
if (found_slot == 0xFF)
return 0xFF;
skylander& thesky = skylanders[found_slot];
memcpy(thesky.data.data(), data.data(), thesky.data.size());
@ -191,6 +200,23 @@ u8 sky_portal::load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::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<le_t<u16>>(s.data, 0x10);
out_variant = read_from_ptr<le_t<u16>>(s.data, 0x1C);
}
else
{
out_id = 0;
out_variant = 0;
}
}
usb_device_skylander::usb_device_skylander(const std::array<u8, 7>& location)
: usb_device_emulated(location)
{

View File

@ -27,7 +27,8 @@ public:
void write_block(u8 sky_num, u8 block, const u8* to_write_buf, u8* reply_buf);
bool remove_skylander(u8 sky_num);
u8 load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file in_file);
u8 load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file in_file, int requested_slot = -1);
void get_figure_info(u8 sky_num, u8& out_status, u16& out_id, u16& out_variant);
protected:
shared_mutex sky_mutex;

View File

@ -0,0 +1,281 @@
#include "stdafx.h"
#include "SkylanderPortalIPC.h"
#include "SkylanderPortalIPC_config.h"
#ifdef _WIN32
#include <WinSock2.h>
#include <ws2tcpip.h>
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 <sys/socket.h>
#include <sys/un.h>
#include <poll.h>
#include <unistd.h>
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<int>(read(s, buf, len)); }
static int sky_send(sky_socket_t s, const char* buf, int len) { return static_cast<int>(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<u8, 0x40 * 0x10> 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, std::move(sky_file), slot);
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<u8>(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<int>(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<u_short>(port));
if (bind(listen_sock, reinterpret_cast<sockaddr*>(&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<sockaddr*>(&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<int>(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<server_thread>();
m_old_port = port;
}
}
else if (m_server)
{
skylander_ipc_log.notice("Stopping server");
m_server.reset();
}
}

View File

@ -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<SkylanderPortalIPCServer>;
std::unique_ptr<server_thread> m_server;
int m_old_port = 0;
public:
explicit SkylanderPortalIPCServerManager(bool enabled);
void set_server_enabled(bool enabled);
};

View File

@ -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);
}

View File

@ -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;

View File

@ -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"
@ -814,6 +816,15 @@ void Emulator::Init()
{
g_fxo->init<IPC_socket::IPC_server_manager>(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<SkylanderPortalIPCServerManager>(true);
}
}
void Emulator::SetUsr(const std::string& user)

View File

@ -114,6 +114,7 @@
<ClCompile Include="Emu\Io\pad_config_types.cpp" />
<ClCompile Include="Emu\IPC_config.cpp" />
<ClCompile Include="Emu\IPC_socket.cpp" />
<ClCompile Include="Emu\Io\SkylanderPortalIPC_config.cpp" />
<ClCompile Include="Emu\localized_string.cpp" />
<ClCompile Include="Emu\NP\clans_config.cpp" />
<ClCompile Include="Emu\NP\rpcn_config.cpp" />
@ -479,6 +480,7 @@
<ClCompile Include="Emu\Io\Dimensions.cpp" />
<ClCompile Include="Emu\Io\Infinity.cpp" />
<ClCompile Include="Emu\Io\Skylander.cpp" />
<ClCompile Include="Emu\Io\SkylanderPortalIPC.cpp" />
<ClCompile Include="Emu\Io\KamenRider.cpp" />
<ClCompile Include="Emu\Io\usb_device.cpp" />
<ClCompile Include="Emu\Io\usb_microphone.cpp" />
@ -651,6 +653,7 @@
<ClInclude Include="Emu\Io\LogitechG27Config.h" />
<ClInclude Include="Emu\IPC_config.h" />
<ClInclude Include="Emu\IPC_socket.h" />
<ClInclude Include="Emu\Io\SkylanderPortalIPC_config.h" />
<ClInclude Include="Emu\localized_string.h" />
<ClInclude Include="Emu\localized_string_id.h" />
<ClInclude Include="Emu\NP\clans_client.h" />
@ -865,6 +868,7 @@
<ClInclude Include="Emu\Io\Dimensions.h" />
<ClInclude Include="Emu\Io\Infinity.h" />
<ClInclude Include="Emu\Io\Skylander.h" />
<ClInclude Include="Emu\Io\SkylanderPortalIPC.h" />
<ClInclude Include="Emu\Io\KamenRider.h" />
<ClInclude Include="Emu\Io\usb_device.h" />
<ClInclude Include="Emu\Io\usb_microphone.h" />

View File

@ -344,6 +344,9 @@
<ClCompile Include="QTGeneratedFiles\Debug\moc_ipc_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_sky_ipc_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_content_integrity.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
@ -656,6 +659,9 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_ipc_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_sky_ipc_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_content_integrity.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
@ -877,6 +883,7 @@
<ClCompile Include="rpcs3qt\infinity_dialog.cpp" />
<ClCompile Include="rpcs3qt\input_dialog.cpp" />
<ClCompile Include="rpcs3qt\ipc_settings_dialog.cpp" />
<ClCompile Include="rpcs3qt\sky_ipc_settings_dialog.cpp" />
<ClCompile Include="rpcs3qt\kamen_rider_dialog.cpp" />
<ClCompile Include="rpcs3qt\localized.cpp" />
<ClCompile Include="rpcs3qt\localized_emu.cpp" />
@ -1604,6 +1611,16 @@
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(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"</Command>
</CustomBuild>
<CustomBuild Include="rpcs3qt\sky_ipc_settings_dialog.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(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"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(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"</Command>
</CustomBuild>
<ClInclude Include="rpcs3qt\hex_validator.h" />
<ClInclude Include="rpcs3qt\log_level_dialog.h" />
<ClInclude Include="rpcs3qt\movie_item.h" />

View File

@ -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

View File

@ -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);

View File

@ -305,6 +305,7 @@
<addaction name="confRPCNAct"/>
<addaction name="confClansAct"/>
<addaction name="confIPCAct"/>
<addaction name="confSkyIPCAct"/>
</widget>
<widget class="QMenu" name="menuPortals">
<property name="title">
@ -1352,6 +1353,14 @@
<string>Configure IPC</string>
</property>
</action>
<action name="confSkyIPCAct">
<property name="text">
<string>Skylanders Portal IPC</string>
</property>
<property name="toolTip">
<string>Configure Skylanders Portal IPC</string>
</property>
</action>
<action name="actionLog_Viewer">
<property name="text">
<string>Log Viewer</string>

View File

@ -0,0 +1,78 @@
#include <QMessageBox>
#include <QHBoxLayout>
#include <QPushButton>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QCheckBox>
#include <QLineEdit>
#include <QIntValidator>
#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<SkylanderPortalIPCServerManager>())
{
manager->set_server_enabled(server_enabled);
}
else if (server_enabled && Emu.IsRunning())
{
g_fxo->init<SkylanderPortalIPCServerManager>(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()));
}

View File

@ -0,0 +1,10 @@
#pragma once
#include <QDialog>
class sky_ipc_settings_dialog : public QDialog
{
Q_OBJECT
public:
sky_ipc_settings_dialog(QWidget* parent = nullptr);
};

View File

@ -14,7 +14,6 @@
#include <QCompleter>
skylander_dialog* skylander_dialog::inst = nullptr;
std::optional<std::tuple<u8, u16, u16>> skylander_dialog::sky_slots[UI_SKY_NUM];
QString last_skylander_path;
static const std::map<const std::pair<const u16, const u16>, const std::string> list_skylanders = {
@ -729,6 +728,11 @@ skylander_dialog::skylander_dialog(QWidget* parent)
setLayout(vbox_panel);
update_edits();
m_update_timer = new QTimer(this);
m_update_timer->setInterval(500);
connect(m_update_timer, &QTimer::timeout, this, &skylander_dialog::update_edits);
m_update_timer->start();
}
skylander_dialog::~skylander_dialog()
@ -746,13 +750,8 @@ skylander_dialog* skylander_dialog::get_dlg(QWidget* parent)
void skylander_dialog::clear_skylander(u8 slot)
{
if (const auto& slot_infos = sky_slots[slot])
{
const auto& [cur_slot, id, var] = slot_infos.value();
g_skyportal.remove_skylander(cur_slot);
sky_slots[slot] = {};
update_edits();
}
g_skyportal.remove_skylander(slot);
update_edits();
}
void skylander_dialog::create_skylander(u8 slot)
@ -794,13 +793,7 @@ void skylander_dialog::load_skylander_path(u8 slot, const QString& path)
}
clear_skylander(slot);
const u16 sky_id = reinterpret_cast<le_t<u16>&>(data[0x10]);
const u16 sky_var = reinterpret_cast<le_t<u16>&>(data[0x1C]);
const u8 portal_slot = g_skyportal.load_skylander(data, std::move(sky_file));
sky_slots[slot] = std::tuple(portal_slot, sky_id, sky_var);
g_skyportal.load_skylander(data, std::move(sky_file));
update_edits();
}
@ -809,9 +802,11 @@ void skylander_dialog::update_edits()
for (auto i = 0; i < UI_SKY_NUM; i++)
{
QString display_string;
if (const auto& sd = sky_slots[i])
u8 status;
u16 sky_id, sky_var;
g_skyportal.get_figure_info(static_cast<u8>(i), status, sky_id, sky_var);
if (status & 1)
{
const auto& [portal_slot, sky_id, sky_var] = sd.value();
const auto found_sky = list_skylanders.find(std::make_pair(sky_id, sky_var));
if (found_sky != list_skylanders.cend())
{

View File

@ -1,10 +1,10 @@
#pragma once
#include <optional>
#include "util/types.hpp"
#include <QDialog>
#include <QLineEdit>
#include <QTimer>
constexpr auto UI_SKY_NUM = 8;
@ -42,8 +42,8 @@ protected:
protected:
QLineEdit* edit_skylanders[UI_SKY_NUM]{};
static std::optional<std::tuple<u8, u16, u16>> sky_slots[UI_SKY_NUM];
private:
QTimer* m_update_timer;
static skylander_dialog* inst;
};