mirror of https://github.com/PCSX2/pcsx2.git
728 lines
19 KiB
C++
728 lines
19 KiB
C++
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "BuildVersion.h"
|
|
#include "Common.h"
|
|
#include "Host.h"
|
|
#include "Memory.h"
|
|
#include "Elfheader.h"
|
|
#include "PINE.h"
|
|
#include "VMManager.h"
|
|
|
|
#include <atomic>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <span>
|
|
#include <sys/types.h>
|
|
#include <thread>
|
|
|
|
#include "fmt/format.h"
|
|
|
|
#if _WIN32
|
|
#define read_portable(a, b, c) (recv(a, (char*)b, c, 0))
|
|
#define write_portable(a, b, c) (send(a, (const char*)b, c, 0))
|
|
#define safe_close_portable(a) \
|
|
do \
|
|
{ \
|
|
if ((a) >= 0) \
|
|
{ \
|
|
closesocket((a)); \
|
|
(a) = INVALID_SOCKET; \
|
|
} \
|
|
} while (0)
|
|
#define bzero(b, len) (memset((b), '\0', (len)), (void)0)
|
|
#include "common/RedtapeWindows.h"
|
|
#include <WinSock2.h>
|
|
#else
|
|
#define read_portable(a, b, c) (read(a, b, c))
|
|
#define write_portable(a, b, c) (write(a, b, c))
|
|
#define safe_close_portable(a) \
|
|
do \
|
|
{ \
|
|
if ((a) >= 0) \
|
|
{ \
|
|
close((a)); \
|
|
(a) = -1; \
|
|
} \
|
|
} while (0)
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#define PINE_EMULATOR_NAME "pcsx2"
|
|
|
|
#ifdef _WIN32
|
|
|
|
static bool InitializeWinsock()
|
|
{
|
|
static bool initialized = false;
|
|
if (initialized)
|
|
return true;
|
|
|
|
WSADATA wsa = {};
|
|
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
|
|
return false;
|
|
|
|
initialized = true;
|
|
std::atexit([]() { WSACleanup(); });
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
namespace PINEServer
|
|
{
|
|
std::thread m_thread;
|
|
int m_slot;
|
|
|
|
#ifdef _WIN32
|
|
// windows claim to have support for AF_UNIX sockets but that is a blatant lie,
|
|
// their SDK won't even run their own examples, so we go on TCP sockets.
|
|
static SOCKET m_sock = INVALID_SOCKET;
|
|
// the message socket used in thread's accept().
|
|
static SOCKET m_msgsock = INVALID_SOCKET;
|
|
#else
|
|
// absolute path of the socket. Stored in XDG_RUNTIME_DIR, if unset /tmp
|
|
static std::string m_socket_name;
|
|
static int m_sock = -1;
|
|
// the message socket used in thread's accept().
|
|
static int m_msgsock = -1;
|
|
#endif
|
|
|
|
// Whether the socket processing thread should stop executing/is stopped.
|
|
static std::atomic_bool m_end{true};
|
|
|
|
/**
|
|
* Maximum memory used by an IPC message request.
|
|
* Equivalent to 50,000 Write64 requests.
|
|
*/
|
|
#define MAX_IPC_SIZE 650000
|
|
|
|
/**
|
|
* Maximum memory used by an IPC message reply.
|
|
* Equivalent to 50,000 Read64 replies.
|
|
*/
|
|
#define MAX_IPC_RETURN_SIZE 450000
|
|
|
|
/**
|
|
* IPC return buffer.
|
|
* A preallocated buffer used to store all IPC replies.
|
|
* to the size of 50.000 MsgWrite64 IPC calls.
|
|
*/
|
|
static std::vector<u8> m_ret_buffer;
|
|
|
|
/**
|
|
* IPC messages buffer.
|
|
* A preallocated buffer used to store all IPC messages.
|
|
*/
|
|
static std::vector<u8> m_ipc_buffer;
|
|
|
|
/**
|
|
* IPC Command messages opcodes.
|
|
* A list of possible operations possible by the IPC.
|
|
* Each one of them is what we call an "opcode" and is the first
|
|
* byte sent by the IPC to differentiate between commands.
|
|
*/
|
|
enum IPCCommand : unsigned char
|
|
{
|
|
MsgRead8 = 0, /**< Read 8 bit value to memory. */
|
|
MsgRead16 = 1, /**< Read 16 bit value to memory. */
|
|
MsgRead32 = 2, /**< Read 32 bit value to memory. */
|
|
MsgRead64 = 3, /**< Read 64 bit value to memory. */
|
|
MsgWrite8 = 4, /**< Write 8 bit value to memory. */
|
|
MsgWrite16 = 5, /**< Write 16 bit value to memory. */
|
|
MsgWrite32 = 6, /**< Write 32 bit value to memory. */
|
|
MsgWrite64 = 7, /**< Write 64 bit value to memory. */
|
|
MsgVersion = 8, /**< Returns PCSX2 version. */
|
|
MsgSaveState = 9, /**< Saves a savestate. */
|
|
MsgLoadState = 0xA, /**< Loads a savestate. */
|
|
MsgTitle = 0xB, /**< Returns the game title. */
|
|
MsgID = 0xC, /**< Returns the game ID. */
|
|
MsgUUID = 0xD, /**< Returns the game UUID. */
|
|
MsgGameVersion = 0xE, /**< Returns the game verion. */
|
|
MsgStatus = 0xF, /**< Returns the emulator status. */
|
|
MsgUnimplemented = 0xFF /**< Unimplemented IPC message. */
|
|
};
|
|
|
|
/**
|
|
* Emulator status enum.
|
|
* A list of possible emulator statuses.
|
|
*/
|
|
enum EmuStatus : uint32_t
|
|
{
|
|
Running = 0, /**< Game is running */
|
|
Paused = 1, /**< Game is paused */
|
|
Shutdown = 2 /**< Game is shutdown */
|
|
};
|
|
|
|
/**
|
|
* IPC message buffer.
|
|
* A list of all needed fields to store an IPC message.
|
|
*/
|
|
struct IPCBuffer
|
|
{
|
|
int size; /**< Size of the buffer. */
|
|
std::vector<u8> buffer; /**< Buffer. */
|
|
};
|
|
|
|
/**
|
|
* IPC result codes.
|
|
* A list of possible result codes the IPC can send back.
|
|
* Each one of them is what we call an "opcode" or "tag" and is the
|
|
* first byte sent by the IPC to differentiate between results.
|
|
*/
|
|
enum IPCResult : unsigned char
|
|
{
|
|
IPC_OK = 0, /**< IPC command successfully completed. */
|
|
IPC_FAIL = 0xFF /**< IPC command failed to complete. */
|
|
};
|
|
|
|
// Thread used to relay IPC commands.
|
|
void MainLoop();
|
|
void ClientLoop();
|
|
|
|
/**
|
|
* Internal function, Parses an IPC command.
|
|
* buf: buffer containing the IPC command.
|
|
* buf_size: size of the buffer announced.
|
|
* ret_buffer: buffer that will be used to send the reply.
|
|
* return value: IPCBuffer containing a buffer with the result
|
|
* of the command and its size.
|
|
*/
|
|
static IPCBuffer ParseCommand(std::span<u8> buf, std::vector<u8>& ret_buffer, u32 buf_size);
|
|
|
|
/**
|
|
* Formats an IPC buffer
|
|
* ret_buffer: return buffer to use.
|
|
* size: size of the IPC buffer.
|
|
* return value: buffer containing the status code allocated of size
|
|
*/
|
|
static std::vector<u8>& MakeOkIPC(std::vector<u8>& ret_buffer, uint32_t size);
|
|
static std::vector<u8>& MakeFailIPC(std::vector<u8>& ret_buffer, uint32_t size);
|
|
|
|
/**
|
|
* Initializes an open socket for IPC communication.
|
|
*/
|
|
bool AcceptClient();
|
|
|
|
/**
|
|
* Converts a primitive value to bytes in little endian
|
|
* res_vector: the vector to modify
|
|
* res: the value to convert
|
|
* i: where to insert it into the vector
|
|
* NB: implicitely inlined
|
|
*/
|
|
template <typename T>
|
|
static void ToResultVector(std::vector<u8>& res_vector, T res, int i)
|
|
{
|
|
memcpy(&res_vector[i], (char*)&res, sizeof(T));
|
|
}
|
|
|
|
/**
|
|
* Converts bytes in little endian to a primitive value
|
|
* span: the span to convert
|
|
* i: where to load it from the span
|
|
* return value: the converted value
|
|
* NB: implicitely inlined
|
|
*/
|
|
template <typename T>
|
|
static T FromSpan(std::span<u8> span, int i)
|
|
{
|
|
return *(T*)(&span[i]);
|
|
}
|
|
|
|
/**
|
|
* Ensures an IPC message isn't too big.
|
|
* return value: false if checks failed, true otherwise.
|
|
*/
|
|
static inline bool SafetyChecks(u32 command_len, int command_size, u32 reply_len, int reply_size = 0, u32 buf_size = MAX_IPC_SIZE - 1)
|
|
{
|
|
return !((command_len + command_size) > buf_size ||
|
|
(reply_len + reply_size) >= MAX_IPC_RETURN_SIZE);
|
|
}
|
|
} // namespace PINEServer
|
|
|
|
bool PINEServer::Initialize(int slot)
|
|
{
|
|
m_end.store(false, std::memory_order_release);
|
|
m_slot = slot;
|
|
|
|
#ifdef _WIN32
|
|
if (!InitializeWinsock())
|
|
{
|
|
Console.WriteLn(Color_Red, "PINE: Cannot initialize winsock! Shutting down...");
|
|
Deinitialize();
|
|
return false;
|
|
}
|
|
|
|
m_sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
if ((m_sock == INVALID_SOCKET) || slot > 65536)
|
|
{
|
|
Console.WriteLn(Color_Red, "PINE: Cannot open socket! Shutting down...");
|
|
Deinitialize();
|
|
return false;
|
|
}
|
|
|
|
sockaddr_in server = {};
|
|
server.sin_family = AF_INET;
|
|
server.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // localhost only
|
|
server.sin_port = htons(slot);
|
|
|
|
if (bind(m_sock, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR)
|
|
{
|
|
Console.WriteLn(Color_Red, "PINE: Error while binding to socket! Shutting down...");
|
|
Deinitialize();
|
|
return false;
|
|
}
|
|
|
|
#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/" PINE_EMULATOR_NAME ".sock";
|
|
else
|
|
{
|
|
m_socket_name = runtime_dir;
|
|
m_socket_name += "/" PINE_EMULATOR_NAME ".sock";
|
|
}
|
|
|
|
if (slot != PINE_DEFAULT_SLOT)
|
|
m_socket_name += "." + std::to_string(slot);
|
|
|
|
struct sockaddr_un server;
|
|
|
|
m_sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (m_sock < 0)
|
|
{
|
|
Console.WriteLn(Color_Red, "PINE: Cannot open socket! Shutting down...");
|
|
Deinitialize();
|
|
return false;
|
|
}
|
|
server.sun_family = AF_UNIX;
|
|
StringUtil::Strlcpy(server.sun_path, m_socket_name, sizeof(server.sun_path));
|
|
|
|
// 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, (struct sockaddr*)&server, sizeof(struct sockaddr_un)))
|
|
{
|
|
Console.WriteLn(Color_Red, "PINE: Error while binding to socket! Shutting down...");
|
|
Deinitialize();
|
|
return false;
|
|
}
|
|
#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.
|
|
if (listen(m_sock, 4096))
|
|
{
|
|
Console.WriteLn(Color_Red, "PINE: Cannot listen for connections! Shutting down...");
|
|
Deinitialize();
|
|
return false;
|
|
}
|
|
|
|
// we allocate once buffers to not have to do mallocs for each IPC
|
|
// request, as malloc is expansive when we optimize for µs.
|
|
m_ret_buffer.resize(MAX_IPC_RETURN_SIZE);
|
|
m_ipc_buffer.resize(MAX_IPC_SIZE);
|
|
|
|
// we start the thread
|
|
m_thread = std::thread(&PINEServer::MainLoop);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PINEServer::IsInitialized()
|
|
{
|
|
return !m_end.load(std::memory_order_acquire);
|
|
}
|
|
|
|
int PINEServer::GetSlot()
|
|
{
|
|
return m_slot;
|
|
}
|
|
|
|
std::vector<u8>& PINEServer::MakeOkIPC(std::vector<u8>& ret_buffer, uint32_t size = 5)
|
|
{
|
|
ToResultVector<uint32_t>(ret_buffer, size, 0);
|
|
ret_buffer[4] = IPC_OK;
|
|
return ret_buffer;
|
|
}
|
|
|
|
std::vector<u8>& PINEServer::MakeFailIPC(std::vector<u8>& ret_buffer, uint32_t size = 5)
|
|
{
|
|
ToResultVector<uint32_t>(ret_buffer, size, 0);
|
|
ret_buffer[4] = IPC_FAIL;
|
|
return ret_buffer;
|
|
}
|
|
|
|
bool PINEServer::AcceptClient()
|
|
{
|
|
m_msgsock = accept(m_sock, 0, 0);
|
|
if (m_msgsock >= 0)
|
|
{
|
|
// Gross C-style cast, but SOCKET is a handle on Windows.
|
|
Console.WriteLn("PINE: New client with FD %d connected.", (int)m_msgsock);
|
|
return true;
|
|
}
|
|
|
|
// everything else is non recoverable in our scope
|
|
// we also mark as recoverable socket errors where it would block a
|
|
// non blocking socket, even though our socket is blocking, in case
|
|
// we ever have to implement a non blocking socket.
|
|
#ifdef _WIN32
|
|
const int errno_w = WSAGetLastError();
|
|
if (!(errno_w == WSAECONNRESET || errno_w == WSAEINTR || errno_w == WSAEINPROGRESS || errno_w == WSAEMFILE || errno_w == WSAEWOULDBLOCK) && m_sock != INVALID_SOCKET)
|
|
Console.Error("PINE: accept() returned error %d", errno_w);
|
|
#else
|
|
if (!(errno == ECONNABORTED || errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) && m_sock >= 0)
|
|
Console.Error("PINE: accept() returned error %d", errno);
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
void PINEServer::MainLoop()
|
|
{
|
|
while (!m_end.load(std::memory_order_acquire))
|
|
{
|
|
if (!AcceptClient())
|
|
continue;
|
|
|
|
ClientLoop();
|
|
|
|
Console.WriteLn("PINE: Client disconnected.");
|
|
safe_close_portable(m_msgsock);
|
|
}
|
|
}
|
|
|
|
void PINEServer::ClientLoop()
|
|
{
|
|
while (!m_end.load(std::memory_order_acquire))
|
|
{
|
|
// either int or ssize_t depending on the platform, so we have to
|
|
// use a bunch of auto
|
|
auto receive_length = 0;
|
|
auto end_length = 4;
|
|
const std::span<u8> ipc_buffer_span(m_ipc_buffer);
|
|
|
|
// while we haven't received the entire packet, maybe due to
|
|
// socket datagram splittage, we continue to read
|
|
while (receive_length < end_length)
|
|
{
|
|
const auto tmp_length = read_portable(m_msgsock, &ipc_buffer_span[receive_length], MAX_IPC_SIZE - receive_length);
|
|
|
|
// we recreate the socket if an error happens
|
|
if (tmp_length <= 0)
|
|
return;
|
|
|
|
receive_length += tmp_length;
|
|
|
|
// if we got at least the final size then update
|
|
if (end_length == 4 && receive_length >= 4)
|
|
{
|
|
end_length = FromSpan<u32>(ipc_buffer_span, 0);
|
|
// we'd like to avoid a client trying to do OOB
|
|
if (end_length > MAX_IPC_SIZE || end_length < 4)
|
|
{
|
|
receive_length = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
PINEServer::IPCBuffer res;
|
|
|
|
// we remove 4 bytes to get the message size out of the IPC command
|
|
// size in ParseCommand.
|
|
// also, if we got a failed command, let's reset the state so we don't
|
|
// end up deadlocking by getting out of sync, eg when a client
|
|
// disconnects
|
|
if (receive_length != 0)
|
|
{
|
|
res = ParseCommand(ipc_buffer_span.subspan(4), m_ret_buffer, (u32)end_length - 4);
|
|
|
|
// if we cannot send back our answer restart the socket
|
|
if (write_portable(m_msgsock, res.buffer.data(), res.size) < 0)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PINEServer::Deinitialize()
|
|
{
|
|
m_end.store(true, std::memory_order_release);
|
|
|
|
#ifndef _WIN32
|
|
if (!m_socket_name.empty())
|
|
{
|
|
unlink(m_socket_name.c_str());
|
|
m_socket_name = {};
|
|
}
|
|
#endif
|
|
|
|
// shutdown() is needed, otherwise accept() will still block.
|
|
#ifdef _WIN32
|
|
if (m_sock != INVALID_SOCKET)
|
|
shutdown(m_sock, SD_BOTH);
|
|
#else
|
|
if (m_sock >= 0)
|
|
shutdown(m_sock, SHUT_RDWR);
|
|
#endif
|
|
|
|
safe_close_portable(m_sock);
|
|
safe_close_portable(m_msgsock);
|
|
|
|
if (m_thread.joinable())
|
|
m_thread.join();
|
|
}
|
|
|
|
PINEServer::IPCBuffer PINEServer::ParseCommand(std::span<u8> buf, std::vector<u8>& ret_buffer, u32 buf_size)
|
|
{
|
|
u32 ret_cnt = 5;
|
|
u32 buf_cnt = 0;
|
|
|
|
while (buf_cnt < buf_size)
|
|
{
|
|
if (!SafetyChecks(buf_cnt, 1, ret_cnt, 0, buf_size)) [[unlikely]]
|
|
return IPCBuffer{5, MakeFailIPC(ret_buffer)};
|
|
buf_cnt++;
|
|
// example IPC messages: MsgRead/Write
|
|
// refer to the client doc for more info on the format
|
|
// IPC Message event (1 byte)
|
|
// | Memory address (4 byte)
|
|
// | | argument (VLE)
|
|
// | | |
|
|
// format: XX YY YY YY YY ZZ ZZ ZZ ZZ
|
|
// reply code: 00 = OK, FF = NOT OK
|
|
// | return value (VLE)
|
|
// | |
|
|
// reply: XX ZZ ZZ ZZ ZZ
|
|
switch ((IPCCommand)buf[buf_cnt - 1])
|
|
{
|
|
case MsgRead8:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
if (!SafetyChecks(buf_cnt, 4, ret_cnt, 1, buf_size)) [[unlikely]]
|
|
goto error;
|
|
const u32 a = FromSpan<u32>(buf, buf_cnt);
|
|
const u8 res = memRead8(a);
|
|
ToResultVector(ret_buffer, res, ret_cnt);
|
|
ret_cnt += 1;
|
|
buf_cnt += 4;
|
|
break;
|
|
}
|
|
case MsgRead16:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
if (!SafetyChecks(buf_cnt, 4, ret_cnt, 2, buf_size)) [[unlikely]]
|
|
goto error;
|
|
const u32 a = FromSpan<u32>(buf, buf_cnt);
|
|
const u16 res = memRead16(a);
|
|
ToResultVector(ret_buffer, res, ret_cnt);
|
|
ret_cnt += 2;
|
|
buf_cnt += 4;
|
|
break;
|
|
}
|
|
case MsgRead32:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
if (!SafetyChecks(buf_cnt, 4, ret_cnt, 4, buf_size)) [[unlikely]]
|
|
goto error;
|
|
const u32 a = FromSpan<u32>(buf, buf_cnt);
|
|
const u32 res = memRead32(a);
|
|
ToResultVector(ret_buffer, res, ret_cnt);
|
|
ret_cnt += 4;
|
|
buf_cnt += 4;
|
|
break;
|
|
}
|
|
case MsgRead64:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
if (!SafetyChecks(buf_cnt, 4, ret_cnt, 8, buf_size)) [[unlikely]]
|
|
goto error;
|
|
const u32 a = FromSpan<u32>(buf, buf_cnt);
|
|
const u64 res = memRead64(a);
|
|
ToResultVector(ret_buffer, res, ret_cnt);
|
|
ret_cnt += 8;
|
|
buf_cnt += 4;
|
|
break;
|
|
}
|
|
case MsgWrite8:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
if (!SafetyChecks(buf_cnt, 1 + 4, ret_cnt, 0, buf_size)) [[unlikely]]
|
|
goto error;
|
|
const u32 a = FromSpan<u32>(buf, buf_cnt);
|
|
memWrite8(a, FromSpan<u8>(buf, buf_cnt + 4));
|
|
buf_cnt += 5;
|
|
break;
|
|
}
|
|
case MsgWrite16:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
if (!SafetyChecks(buf_cnt, 2 + 4, ret_cnt, 0, buf_size)) [[unlikely]]
|
|
goto error;
|
|
const u32 a = FromSpan<u32>(buf, buf_cnt);
|
|
memWrite16(a, FromSpan<u16>(buf, buf_cnt + 4));
|
|
buf_cnt += 6;
|
|
break;
|
|
}
|
|
case MsgWrite32:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
if (!SafetyChecks(buf_cnt, 4 + 4, ret_cnt, 0, buf_size)) [[unlikely]]
|
|
goto error;
|
|
const u32 a = FromSpan<u32>(buf, buf_cnt);
|
|
memWrite32(a, FromSpan<u32>(buf, buf_cnt + 4));
|
|
buf_cnt += 8;
|
|
break;
|
|
}
|
|
case MsgWrite64:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
if (!SafetyChecks(buf_cnt, 8 + 4, ret_cnt, 0, buf_size)) [[unlikely]]
|
|
goto error;
|
|
const u32 a = FromSpan<u32>(buf, buf_cnt);
|
|
memWrite64(a, FromSpan<u64>(buf, buf_cnt + 4));
|
|
buf_cnt += 12;
|
|
break;
|
|
}
|
|
case MsgVersion:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
u32 size = strlen(BuildVersion::GitRev) + 7;
|
|
if (!SafetyChecks(buf_cnt, 0, ret_cnt, size + 4, buf_size)) [[unlikely]]
|
|
goto error;
|
|
ToResultVector(ret_buffer, size, ret_cnt);
|
|
ret_cnt += 4;
|
|
snprintf(reinterpret_cast<char*>(&ret_buffer[ret_cnt]), size, "PCSX2 %s", BuildVersion::GitRev);
|
|
ret_cnt += size;
|
|
break;
|
|
}
|
|
case MsgSaveState:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
if (!SafetyChecks(buf_cnt, 1, ret_cnt, 0, buf_size)) [[unlikely]]
|
|
goto error;
|
|
Host::RunOnCPUThread([slot = FromSpan<u8>(buf, buf_cnt)] { VMManager::SaveStateToSlot(slot); });
|
|
buf_cnt += 1;
|
|
break;
|
|
}
|
|
case MsgLoadState:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
if (!SafetyChecks(buf_cnt, 1, ret_cnt, 0, buf_size)) [[unlikely]]
|
|
goto error;
|
|
Host::RunOnCPUThread([slot = FromSpan<u8>(buf, buf_cnt)] { VMManager::LoadStateFromSlot(slot); });
|
|
buf_cnt += 1;
|
|
break;
|
|
}
|
|
case MsgTitle:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
const std::string gameName = VMManager::GetTitle(false);
|
|
const u32 size = gameName.size() + 1;
|
|
if (!SafetyChecks(buf_cnt, 0, ret_cnt, size + 4, buf_size)) [[unlikely]]
|
|
goto error;
|
|
ToResultVector(ret_buffer, size, ret_cnt);
|
|
ret_cnt += 4;
|
|
memcpy(&ret_buffer[ret_cnt], gameName.c_str(), size);
|
|
ret_cnt += size;
|
|
break;
|
|
}
|
|
case MsgID:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
const std::string gameSerial = VMManager::GetDiscSerial();
|
|
const u32 size = gameSerial.size() + 1;
|
|
if (!SafetyChecks(buf_cnt, 0, ret_cnt, size + 4, buf_size)) [[unlikely]]
|
|
goto error;
|
|
ToResultVector(ret_buffer, size, ret_cnt);
|
|
ret_cnt += 4;
|
|
memcpy(&ret_buffer[ret_cnt], gameSerial.c_str(), size);
|
|
ret_cnt += size;
|
|
break;
|
|
}
|
|
case MsgUUID:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
const std::string crc = fmt::format("{:08x}", VMManager::GetDiscCRC());
|
|
const u32 size = crc.size() + 1;
|
|
if (!SafetyChecks(buf_cnt, 0, ret_cnt, size + 4, buf_size)) [[unlikely]]
|
|
goto error;
|
|
ToResultVector(ret_buffer, size, ret_cnt);
|
|
ret_cnt += 4;
|
|
memcpy(&ret_buffer[ret_cnt], crc.c_str(), size);
|
|
ret_cnt += size;
|
|
break;
|
|
}
|
|
case MsgGameVersion:
|
|
{
|
|
if (!VMManager::HasValidVM())
|
|
goto error;
|
|
|
|
const std::string ElfVersion = VMManager::GetDiscVersion();
|
|
const u32 size = ElfVersion.size() + 1;
|
|
if (!SafetyChecks(buf_cnt, 0, ret_cnt, size + 4, buf_size)) [[unlikely]]
|
|
goto error;
|
|
ToResultVector(ret_buffer, size, ret_cnt);
|
|
ret_cnt += 4;
|
|
memcpy(&ret_buffer[ret_cnt], ElfVersion.c_str(), size);
|
|
ret_cnt += size;
|
|
break;
|
|
}
|
|
case MsgStatus:
|
|
{
|
|
if (!SafetyChecks(buf_cnt, 0, ret_cnt, 4, buf_size)) [[unlikely]]
|
|
goto error;
|
|
EmuStatus status;
|
|
|
|
switch (VMManager::GetState())
|
|
{
|
|
case VMState::Running:
|
|
status = EmuStatus::Running;
|
|
break;
|
|
case VMState::Paused:
|
|
status = EmuStatus::Paused;
|
|
break;
|
|
default:
|
|
status = EmuStatus::Shutdown;
|
|
break;
|
|
}
|
|
|
|
ToResultVector(ret_buffer, status, ret_cnt);
|
|
ret_cnt += 4;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
error:
|
|
return IPCBuffer{5, MakeFailIPC(ret_buffer)};
|
|
}
|
|
}
|
|
}
|
|
return IPCBuffer{(int)ret_cnt, MakeOkIPC(ret_buffer, ret_cnt)};
|
|
}
|