mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-10-19 17:28:55 +02:00
Skylanders Portal IPC Server
This commit is contained in:
1 parent
6a900d00e6
commit
dfc486fc6b
20 files changed
+778
-112
No files matched your search
Vendored
+64
-54
@@ -56,6 +56,63 @@ namespace pine
|
||||
|
||||
typedef unsigned long long SOCKET;
|
||||
|
||||
#ifndef _WIN32
|
||||
inline std::string get_socket_path(std::string_view file_name, int slot, int default_slot)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
const char* runtime_dir = std::getenv("TMPDIR");
|
||||
#else
|
||||
const char* runtime_dir = std::getenv("XDG_RUNTIME_DIR");
|
||||
#endif
|
||||
// fallback in case macOS or other OSes don't implement the XDG base
|
||||
// spec
|
||||
std::string path = runtime_dir ? std::string(runtime_dir) + '/' : std::string("/tmp/");
|
||||
path += file_name;
|
||||
|
||||
if (slot != default_slot)
|
||||
{
|
||||
fmt::append(path, ".%d", slot);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
inline const char* bind_and_listen(SOCKET sock, u16 port)
|
||||
{
|
||||
const int exclusive = 1;
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, reinterpret_cast<const char*>(&exclusive), sizeof(exclusive)) != 0)
|
||||
return "setsockopt(SO_EXCLUSIVEADDRUSE) failed";
|
||||
|
||||
sockaddr_in server{};
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_port = htons(port);
|
||||
|
||||
if (inet_pton(AF_INET, "127.0.0.1", &server.sin_addr.s_addr) != 1)
|
||||
return "inet_pton failed";
|
||||
#else
|
||||
inline const char* bind_and_listen(int sock, const std::string& socket_path)
|
||||
{
|
||||
sockaddr_un server{};
|
||||
|
||||
if (socket_path.size() >= sizeof(server.sun_path))
|
||||
return "socket path is too long";
|
||||
|
||||
server.sun_family = AF_UNIX;
|
||||
std::memcpy(server.sun_path, socket_path.c_str(), socket_path.size() + 1);
|
||||
|
||||
unlink(socket_path.c_str());
|
||||
#endif
|
||||
if (bind(sock, reinterpret_cast<sockaddr*>(&server), sizeof(server)) != 0)
|
||||
return "bind failed";
|
||||
|
||||
if (listen(sock, 4096) != 0)
|
||||
return "listen failed";
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename Impl>
|
||||
class pine_server : public Impl
|
||||
{
|
||||
@@ -528,7 +585,6 @@ namespace pine
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa {};
|
||||
struct sockaddr_in server {};
|
||||
m_sock = INVALID_SOCKET;
|
||||
m_msgsock = INVALID_SOCKET;
|
||||
|
||||
@@ -544,45 +600,9 @@ namespace pine
|
||||
return;
|
||||
}
|
||||
|
||||
// yes very good windows s/sun/sin/g sure is fine
|
||||
server.sin_family = AF_INET;
|
||||
// localhost only
|
||||
if (!inet_pton(server.sin_family, "127.0.0.1", &server.sin_addr.s_addr))
|
||||
{
|
||||
fmt::throw_exception("IPC: Failed to convert localhost");
|
||||
}
|
||||
server.sin_port = htons(Impl::get_port());
|
||||
|
||||
if (bind(m_sock, reinterpret_cast<struct sockaddr*>(&server), sizeof(server)) == SOCKET_ERROR)
|
||||
{
|
||||
Impl::error("IPC: Error while binding to socket! Shutting down...");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* failure = bind_and_listen(m_sock, static_cast<u16>(Impl::get_port()));
|
||||
#else
|
||||
char* runtime_dir = nullptr;
|
||||
#ifdef __APPLE__
|
||||
runtime_dir = std::getenv("TMPDIR");
|
||||
#else
|
||||
runtime_dir = std::getenv("XDG_RUNTIME_DIR");
|
||||
#endif
|
||||
// fallback in case macOS or other OSes don't implement the XDG base
|
||||
// spec
|
||||
if (runtime_dir == nullptr)
|
||||
m_socket_name = "/tmp/rpcs3.sock";
|
||||
else
|
||||
{
|
||||
m_socket_name = runtime_dir;
|
||||
m_socket_name += "/rpcs3.sock";
|
||||
}
|
||||
|
||||
const int slot = Impl::get_port();
|
||||
if (slot != IPC_DEFAULT_SLOT)
|
||||
{
|
||||
fmt::append(m_socket_name, ".%d", slot);
|
||||
}
|
||||
|
||||
struct sockaddr_un server {};
|
||||
m_socket_name = get_socket_path("rpcs3.sock", Impl::get_port(), IPC_DEFAULT_SLOT);
|
||||
|
||||
m_sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (m_sock < 0)
|
||||
@@ -590,23 +610,13 @@ namespace pine
|
||||
Impl::error("IPC: Cannot open socket! Shutting down...");
|
||||
return;
|
||||
}
|
||||
server.sun_family = AF_UNIX;
|
||||
strcpy(server.sun_path, m_socket_name.c_str());
|
||||
|
||||
// we unlink the socket so that when releasing this thread the socket gets
|
||||
// freed even if we didn't close correctly the loop
|
||||
unlink(m_socket_name.c_str());
|
||||
if (bind(m_sock, std::bit_cast<struct sockaddr*>(&server), sizeof(struct sockaddr_un)))
|
||||
{
|
||||
Impl::error("IPC: Error while binding to socket! Shutting down...");
|
||||
return;
|
||||
}
|
||||
const char* failure = bind_and_listen(m_sock, m_socket_name);
|
||||
#endif
|
||||
|
||||
// maximum queue of 4096 commands before refusing, approximated to the
|
||||
// nearest legal value. We do not use SOMAXCONN as windows have this idea
|
||||
// that a "reasonable" value is 5, which is not.
|
||||
listen(m_sock, 4096);
|
||||
if (failure)
|
||||
{
|
||||
Impl::error("IPC: Error while setting up socket (%s)! Shutting down...", failure);
|
||||
}
|
||||
}
|
||||
|
||||
pine_server(const pine_server&) = delete;
|
||||
|
||||
@@ -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)
|
||||
@@ -445,6 +446,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
|
||||
|
||||
+65
-12
@@ -6,6 +6,32 @@ LOG_CHANNEL(skylander_log, "skylander");
|
||||
|
||||
sky_portal g_skyportal;
|
||||
|
||||
u16 skylander_crc16(u16 init_value, const u8* buffer, u32 size)
|
||||
{
|
||||
constexpr unsigned short CRC_CCITT_TABLE[256] = {
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273,
|
||||
0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528,
|
||||
0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886,
|
||||
0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF,
|
||||
0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5,
|
||||
0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2,
|
||||
0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8,
|
||||
0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691,
|
||||
0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F,
|
||||
0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64,
|
||||
0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0};
|
||||
|
||||
u16 crc = init_value;
|
||||
|
||||
for (u32 i = 0; i < size; i++)
|
||||
{
|
||||
const u16 tmp = (crc >> 8) ^ buffer[i];
|
||||
crc = (crc << 8) ^ CRC_CCITT_TABLE[tmp];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
void skylander::save()
|
||||
{
|
||||
if (!sky_file)
|
||||
@@ -153,32 +179,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 +226,24 @@ 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)
|
||||
{
|
||||
ensure(sky_num < 8);
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
@@ -41,6 +42,8 @@ protected:
|
||||
|
||||
extern sky_portal g_skyportal;
|
||||
|
||||
u16 skylander_crc16(u16 init_value, const u8* buffer, u32 size);
|
||||
|
||||
class usb_device_skylander : public usb_device_emulated
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
#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_some(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); }
|
||||
static bool sky_interrupted() { return WSAGetLastError() == WSAEINTR; }
|
||||
|
||||
static bool sky_set_recv_timeout(sky_socket_t s, u32 timeout_ms)
|
||||
{
|
||||
const DWORD timeout = timeout_ms;
|
||||
return setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout), sizeof(timeout)) == 0;
|
||||
}
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.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_some(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); }
|
||||
static bool sky_interrupted() { return errno == EINTR; }
|
||||
|
||||
static bool sky_set_recv_timeout(sky_socket_t s, u32 timeout_ms)
|
||||
{
|
||||
timeval timeout{};
|
||||
timeout.tv_sec = static_cast<time_t>(timeout_ms / 1000);
|
||||
timeout.tv_usec = static_cast<suseconds_t>(timeout_ms % 1000 * 1000);
|
||||
return setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "Skylander.h"
|
||||
#include "3rdparty/pine/pine_server.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
LOG_CHANNEL(skylander_ipc_log, "SkylanderIPC");
|
||||
|
||||
static constexpr usz max_command_size = 4096;
|
||||
static constexpr u32 client_timeout_ms = 1000;
|
||||
|
||||
static bool is_absolute_local_path(std::string_view path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Requiring a drive letter also rules out network (\\server\share) and device (\\.\) paths
|
||||
const bool has_drive_letter = path.size() >= 3 && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) && path[1] == ':';
|
||||
return has_drive_letter && (path[2] == '\\' || path[2] == '/');
|
||||
#else
|
||||
return path.starts_with('/');
|
||||
#endif
|
||||
}
|
||||
|
||||
static std::string handle_load(int slot, const std::string& path)
|
||||
{
|
||||
if (slot < -1 || slot > 7)
|
||||
return "error invalid slot\n";
|
||||
|
||||
if (!is_absolute_local_path(path))
|
||||
return "error path must be absolute\n";
|
||||
|
||||
std::array<u8, 0x40 * 0x10> buf{};
|
||||
|
||||
// Checked before opening, so that devices and pipes are rejected without ever being opened
|
||||
fs::stat_t info{};
|
||||
if (!fs::get_stat(path, info) || info.is_directory || info.size != buf.size())
|
||||
return "error not a skylander file\n";
|
||||
|
||||
fs::file sky_file(path, fs::read + fs::write + fs::lock);
|
||||
if (!sky_file)
|
||||
return "error cannot open file\n";
|
||||
|
||||
if (sky_file.read(buf.data(), buf.size()) != buf.size())
|
||||
return "error file too small\n";
|
||||
|
||||
if (read_from_ptr<le_t<u16>>(buf, 0x1E) != skylander_crc16(0xFFFF, buf.data(), 0x1E))
|
||||
return "error not a skylander file\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";
|
||||
}
|
||||
|
||||
static bool sky_send(sky_socket_t s, std::string_view data)
|
||||
{
|
||||
while (!data.empty())
|
||||
{
|
||||
const int sent = sky_send_some(s, data.data(), ::narrow<int>(data.size()));
|
||||
|
||||
if (sent > 0)
|
||||
data.remove_prefix(sent);
|
||||
else if (sent < 0 && sky_interrupted())
|
||||
continue;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool read_command(sky_socket_t client_sock, std::string& line)
|
||||
{
|
||||
if (!sky_set_recv_timeout(client_sock, client_timeout_ms))
|
||||
return false;
|
||||
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(client_timeout_ms);
|
||||
|
||||
while (line.size() < max_command_size && std::chrono::steady_clock::now() < deadline)
|
||||
{
|
||||
char ch;
|
||||
const int received = sky_recv(client_sock, &ch, 1);
|
||||
|
||||
if (received == 0)
|
||||
return true;
|
||||
|
||||
if (received < 0)
|
||||
{
|
||||
if (sky_interrupted())
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ch == '\n' || ch == '\r')
|
||||
return true;
|
||||
|
||||
line += ch;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SkylanderPortalIPCServer::operator()()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
const sky_socket_t listen_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_sock == sky_invalid_socket)
|
||||
{
|
||||
skylander_ipc_log.error("Cannot create socket");
|
||||
WSACleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
if (const char* failure = pine::bind_and_listen(listen_sock, static_cast<u16>(port)))
|
||||
{
|
||||
skylander_ipc_log.error("Cannot start server on port %d: %s", port, failure);
|
||||
sky_close(listen_sock);
|
||||
WSACleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
skylander_ipc_log.notice("Server started on port %d", port);
|
||||
#else
|
||||
const std::string socket_path = pine::get_socket_path("rpcs3.skylanders.sock", port, g_cfg_sky_ipc.sky_ipc_port.def);
|
||||
|
||||
const sky_socket_t listen_sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (listen_sock == sky_invalid_socket)
|
||||
{
|
||||
skylander_ipc_log.error("Cannot create socket");
|
||||
return;
|
||||
}
|
||||
|
||||
if (const char* failure = pine::bind_and_listen(listen_sock, socket_path))
|
||||
{
|
||||
skylander_ipc_log.error("Cannot start server at %s: %s", socket_path, failure);
|
||||
sky_close(listen_sock);
|
||||
return;
|
||||
}
|
||||
|
||||
skylander_ipc_log.notice("Server started at %s", socket_path);
|
||||
#endif
|
||||
|
||||
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 && !sky_interrupted())
|
||||
{
|
||||
skylander_ipc_log.error("Waiting for connections failed, stopping server");
|
||||
break;
|
||||
}
|
||||
|
||||
if (poll_result <= 0)
|
||||
continue;
|
||||
|
||||
const sky_socket_t client_sock = accept(listen_sock, nullptr, nullptr);
|
||||
if (client_sock == sky_invalid_socket)
|
||||
continue;
|
||||
|
||||
std::string line;
|
||||
if (read_command(client_sock, line) && !line.empty())
|
||||
{
|
||||
if (!sky_send(client_sock, process_command(line)))
|
||||
skylander_ipc_log.warning("Failed to send the response");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -56,6 +56,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"
|
||||
@@ -822,6 +824,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)
|
||||
|
||||
@@ -115,6 +115,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" />
|
||||
@@ -486,6 +487,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" />
|
||||
@@ -663,6 +665,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" />
|
||||
@@ -883,6 +886,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" />
|
||||
|
||||
@@ -891,6 +891,12 @@
|
||||
<ClCompile Include="Emu\Io\Skylander.cpp">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\Io\SkylanderPortalIPC.cpp">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\Io\SkylanderPortalIPC_config.cpp">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\Io\KamenRider.cpp">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClCompile>
|
||||
@@ -2220,6 +2226,12 @@
|
||||
<ClInclude Include="Emu\Io\Skylander.h">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\Io\SkylanderPortalIPC.h">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\Io\SkylanderPortalIPC_config.h">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\Io\KamenRider.h">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -342,6 +342,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>
|
||||
@@ -657,6 +660,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>
|
||||
@@ -880,6 +886,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" />
|
||||
@@ -1618,6 +1625,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" />
|
||||
|
||||
@@ -1035,6 +1035,15 @@
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_ipc_settings_dialog.cpp">
|
||||
<Filter>Generated Files\Release</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\sky_ipc_settings_dialog.cpp">
|
||||
<Filter>Gui\ipc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_sky_ipc_settings_dialog.cpp">
|
||||
<Filter>Generated Files\Debug</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_sky_ipc_settings_dialog.cpp">
|
||||
<Filter>Generated Files\Release</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\uuid.cpp">
|
||||
<Filter>Gui\utils</Filter>
|
||||
</ClCompile>
|
||||
@@ -1861,6 +1870,9 @@
|
||||
<CustomBuild Include="rpcs3qt\ipc_settings_dialog.h">
|
||||
<Filter>Gui\ipc</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\sky_ipc_settings_dialog.h">
|
||||
<Filter>Gui\ipc</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\pad_motion_settings_dialog.h">
|
||||
<Filter>Gui\settings</Filter>
|
||||
</CustomBuild>
|
||||
|
||||
@@ -54,6 +54,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
|
||||
|
||||
@@ -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"
|
||||
@@ -3160,6 +3161,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);
|
||||
|
||||
@@ -324,6 +324,7 @@
|
||||
<addaction name="confRPCNAct"/>
|
||||
<addaction name="confClansAct"/>
|
||||
<addaction name="confIPCAct"/>
|
||||
<addaction name="confSkyIPCAct"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuPortals">
|
||||
<property name="title">
|
||||
@@ -1423,6 +1424,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>
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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 = {
|
||||
@@ -500,32 +499,6 @@ static const std::map<const std::pair<const u16, const u16>, const std::string>
|
||||
{{3503, 0x0000}, "Kaos Trophy"},
|
||||
};
|
||||
|
||||
u16 skylander_crc16(u16 init_value, const u8* buffer, u32 size)
|
||||
{
|
||||
constexpr unsigned short CRC_CCITT_TABLE[256] = {
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273,
|
||||
0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528,
|
||||
0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886,
|
||||
0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF,
|
||||
0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5,
|
||||
0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2,
|
||||
0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8,
|
||||
0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691,
|
||||
0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F,
|
||||
0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64,
|
||||
0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0};
|
||||
|
||||
u16 crc = init_value;
|
||||
|
||||
for (u32 i = 0; i < size; i++)
|
||||
{
|
||||
const u16 tmp = (crc >> 8) ^ buffer[i];
|
||||
crc = (crc << 8) ^ CRC_CCITT_TABLE[tmp];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
skylander_creator_dialog::skylander_creator_dialog(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
@@ -729,6 +702,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 +724,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,14 +767,13 @@ 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);
|
||||
|
||||
const u8 portal_slot = g_skyportal.load_skylander(data, std::move(sky_file), slot);
|
||||
update_edits();
|
||||
|
||||
if (portal_slot == 0xFF)
|
||||
{
|
||||
QMessageBox::warning(this, tr("Failed to load the skylander!"), tr("Failed to load the skylander(%1)!\nSkylander %2 is already in use on the portal.").arg(path).arg(slot + 1), QMessageBox::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
void skylander_dialog::update_edits()
|
||||
@@ -809,9 +781,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())
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
Reference in new issue
Block a user