diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 6311abd07d..61a7b13930 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -75,6 +75,7 @@ set(pcsx2Sources IopIrq.cpp IopMem.cpp IopSio2.cpp + IPC.cpp Mdec.cpp Memory.cpp MMI.cpp @@ -147,6 +148,7 @@ set(pcsx2Headers IopHw.h IopMem.h IopSio2.h + IPC.h Mdec.h MTVU.h Memory.h diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 1c3cddee9d..8331a91327 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -454,6 +454,7 @@ struct Pcsx2Config CdvdShareWrite :1, // allows the iso to be modified while it's loaded EnablePatches :1, // enables patch detection and application EnableCheats :1, // enables cheat detection and application + EnableIPC :1, // enables inter-process communication EnableWideScreenPatches :1, #ifndef DISABLE_RECORDING EnableRecordingTools :1, diff --git a/pcsx2/IPC.cpp b/pcsx2/IPC.cpp new file mode 100644 index 0000000000..51068f1e71 --- /dev/null +++ b/pcsx2/IPC.cpp @@ -0,0 +1,256 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include +#include +#include +#include +#if _WIN32 +#define read_portable(a, b, c) (recv(a, b, c, 0)) +#define write_portable(a, b, c) (send(a, b, c, 0)) +#define bzero(b, len) (memset((b), '\0', (len)), (void)0) +#include +#else +#define read_portable(a, b, c) (read(a, b, c)) +#define write_portable(a, b, c) (write(a, b, c)) +#include +#include +#endif + +#include "Common.h" +#include "Memory.h" +#include "System/SysThreads.h" +#include "IPC.h" + +SocketIPC::SocketIPC(SysCoreThread* vm) + : pxThread("IPC_Socket") +{ +#ifdef _WIN32 + WSADATA wsa; + SOCKET new_socket; + struct sockaddr_in server, client; + int c; + + + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) + { + Console.WriteLn(Color_Red, "IPC: Cannot initialize winsock! Shutting down..."); + return; + } + + if ((m_sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) + { + Console.WriteLn(Color_Red, "IPC: Cannot open socket! Shutting down..."); + return; + } + + // yes very good windows s/sun/sin/g sure is fine + server.sin_family = AF_INET; + // localhost only + server.sin_addr.s_addr = inet_addr("127.0.0.1"); + server.sin_port = htons(PORT); + + if (bind(m_sock, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) + { + Console.WriteLn(Color_Red, "IPC: Error while binding to socket! Shutting down..."); + return; + } + +#else + struct sockaddr_un server; + + m_sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (m_sock < 0) + { + Console.WriteLn(Color_Red, "IPC: Cannot open socket! Shutting down..."); + return; + } + server.sun_family = AF_UNIX; + strcpy(server.sun_path, SOCKET_NAME); + + // we unlink the socket so that when releasing this thread the socket gets + // freed even if we didn't close correctly the loop + unlink(SOCKET_NAME); + if (bind(m_sock, (struct sockaddr*)&server, sizeof(struct sockaddr_un))) + { + Console.WriteLn(Color_Red, "IPC: Error while binding to socket! Shutting down..."); + return; + } +#endif + + // maximum queue of SOMAXCONN commands before refusing, which stops the thread + listen(m_sock, SOMAXCONN); + + // we save a handle of the main vm object + m_vm = vm; + + // we start the thread + Start(); +} + +void SocketIPC::ExecuteTaskInThread() +{ + int msgsock = 0; + // for the sake of speed we malloc once a return buffer and reuse it by just + // cropping its size when needed, it is 450k long which is the size of 50k + // MsgWrite64 replies, should be good enough even if we implement batch IPC + // processing. Coincidentally 650k is the size of 50k MsgWrite64 REQUESTS so + // we just allocate a 1mb buffer in the end, lul + ret_buffer = (char*)malloc(450000 * sizeof(char)); + ipc_buffer = (char*)malloc(650000 * sizeof(char)); + while (true) + { + msgsock = accept(m_sock, 0, 0); + if (msgsock == -1) + { + return; + } + else + { + if (read_portable(msgsock, ipc_buffer, 650000) < 0) + { + return; + } + else + { + auto res = ParseCommand(ipc_buffer, ret_buffer); + if (write_portable(msgsock, res.second, res.first) < 0) + { + return; + } + } + } + } +} + +SocketIPC::~SocketIPC() +{ +#ifdef _WIN32 + closesocket(m_sock); + WSACleanup(); +#else + close(m_sock); + unlink(SOCKET_NAME); +#endif + free(ret_buffer); + free(ipc_buffer); + // destroy the thread + try + { + pxThread::Cancel(); + } + DESTRUCTOR_CATCHALL +} + +char* SocketIPC::MakeOkIPC(char* ret_buffer) +{ + ret_buffer[0] = (unsigned char)IPC_OK; + return ret_buffer; +} + +char* SocketIPC::MakeFailIPC(char* ret_buffer) +{ + ret_buffer[0] = (unsigned char)IPC_FAIL; + return ret_buffer; +} + +std::pair SocketIPC::ParseCommand(char* buf, char* ret_buffer) +{ + // currently all our instructions require a running VM so we check once + // here, will help perf when/if we implement multi-ipc processing in one + // socket roundtrip. + if (!m_vm->HasActiveMachine()) + return std::make_pair(1, MakeFailIPC(ret_buffer)); + + // 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 + IPCCommand opcode = (IPCCommand)buf[0]; + + // return value + std::pair rval; + + // YY YY YY YY from schema above + u32 a = FromArray(buf, 1); + switch (opcode) + { + case MsgRead8: + { + u8 res; + res = memRead8(a); + rval = std::make_pair(2, ToArray(MakeOkIPC(ret_buffer), res, 1)); + break; + } + case MsgRead16: + { + u16 res; + res = memRead16(a); + rval = std::make_pair(3, ToArray(MakeOkIPC(ret_buffer), res, 1)); + break; + } + case MsgRead32: + { + u32 res; + res = memRead32(a); + rval = std::make_pair(5, ToArray(MakeOkIPC(ret_buffer), res, 1)); + break; + } + case MsgRead64: + { + u64 res; + memRead64(a, &res); + rval = std::make_pair(9, ToArray(MakeOkIPC(ret_buffer), res, 1)); + break; + } + case MsgWrite8: + { + memWrite8(a, FromArray(buf, 5)); + rval = std::make_pair(1, MakeOkIPC(ret_buffer)); + break; + } + case MsgWrite16: + { + memWrite16(a, FromArray(buf, 5)); + rval = std::make_pair(1, MakeOkIPC(ret_buffer)); + break; + } + case MsgWrite32: + { + memWrite32(a, FromArray(buf, 5)); + rval = std::make_pair(1, MakeOkIPC(ret_buffer)); + break; + } + case MsgWrite64: + { + memWrite64(a, FromArray(buf, 5)); + rval = std::make_pair(1, MakeOkIPC(ret_buffer)); + break; + } + default: + { + rval = std::make_pair(1, MakeFailIPC(ret_buffer)); + break; + } + } + return rval; +} diff --git a/pcsx2/IPC.h b/pcsx2/IPC.h new file mode 100644 index 0000000000..fba77683fb --- /dev/null +++ b/pcsx2/IPC.h @@ -0,0 +1,122 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +/* Client code example for interfacing with the IPC interface is available + * here: https://code.govanify.com/govanify/pcsx2_ipc/ */ + +#pragma once + +#include "Utilities/PersistentThread.h" +#include "System/SysThreads.h" + +using namespace Threading; + +class SocketIPC : public pxThread +{ + + typedef pxThread _parent; + +protected: +#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. +#define PORT 28011 +#else + // absolute path of the socket. Stored in the temporary directory in linux since + // /run requires superuser permission + const char* SOCKET_NAME = "/tmp/pcsx2.sock"; +#endif + + // socket handlers +#ifdef _WIN32 + SOCKET m_sock = INVALID_SOCKET; +#else + int m_sock = 0; +#endif + + // buffers that store the ipc request and reply messages. + char* ret_buffer; + char* ipc_buffer; + + // possible command messages + enum IPCCommand + { + MsgRead8 = 0, + MsgRead16 = 1, + MsgRead32 = 2, + MsgRead64 = 3, + MsgWrite8 = 4, + MsgWrite16 = 5, + MsgWrite32 = 6, + MsgWrite64 = 7 + }; + + // possible result codes + enum IPCResult + { + IPC_OK = 0, + IPC_FAIL = 0xFF + }; + + // handle to the main vm thread + SysCoreThread* m_vm; + + /* Thread used to relay IPC commands. */ + void ExecuteTaskInThread(); + + /* Internal function, Parses an IPC command. + * buf: buffer containing the IPC command. + * ret_buffer: buffer that will be used to send the reply. + * return value: pair containing a buffer with the result + * of the command and its size. */ + std::pair ParseCommand(char* buf, char* ret_buffer); + + /* Formats an IPC buffer + * ret_buffer: return buffer to use. + * return value: buffer containing the status code allocated of size + * size */ + static inline char* MakeOkIPC(char* ret_buffer); + static inline char* MakeFailIPC(char* ret_buffer); + + /* Converts an uint to an char* in little endian + * res_array: the array to modify + * res: the value to convert + * i: when to insert it into the array + * return value: res_array + * NB: implicitely inlined */ + template + static char* ToArray(char* res_array, T res, int i) + { + memcpy((res_array + i), (char*)&res, sizeof(T)); + return res_array; + } + + /* Converts a char* to an uint in little endian + * arr: the array to convert + * i: when to load it from the array + * return value: the converted value + * NB: implicitely inlined */ + template + static T FromArray(char* arr, int i) + { + return *(T*)(arr + i); + } + +public: + /* Initializers */ + SocketIPC(SysCoreThread* vm); + virtual ~SocketIPC(); + +}; // class SocketIPC diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 4b41eeeb06..dd53674b85 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -436,6 +436,7 @@ void Pcsx2Config::LoadSave( IniInterface& ini ) IniBitBool( CdvdShareWrite ); IniBitBool( EnablePatches ); IniBitBool( EnableCheats ); + IniBitBool( EnableIPC ); IniBitBool( EnableWideScreenPatches ); #ifndef DISABLE_RECORDING IniBitBool( EnableRecordingTools ); diff --git a/pcsx2/System/SysCoreThread.cpp b/pcsx2/System/SysCoreThread.cpp index b26d182688..2c38c06d59 100644 --- a/pcsx2/System/SysCoreThread.cpp +++ b/pcsx2/System/SysCoreThread.cpp @@ -24,6 +24,7 @@ #include "Patch.h" #include "SysThreads.h" #include "MTVU.h" +#include "IPC.h" #include "../DebugTools/MIPSAnalyst.h" #include "../DebugTools/SymbolMap.h" @@ -243,6 +244,11 @@ void SysCoreThread::GameStartingInThread() #ifdef USE_SAVESLOT_UI_UPDATES UI_UpdateSysControls(); #endif + if(EmuConfig.EnableIPC && m_IpcState == OFF) + { + m_IpcState = ON; + m_socketIpc = std::make_unique(this); + } } bool SysCoreThread::StateCheckInThread() diff --git a/pcsx2/System/SysThreads.h b/pcsx2/System/SysThreads.h index 6ddf09fadb..790f47f37a 100644 --- a/pcsx2/System/SysThreads.h +++ b/pcsx2/System/SysThreads.h @@ -19,6 +19,7 @@ #include "Utilities/PersistentThread.h" #include "x86emitter/tools.h" +#include "IPC.h" using namespace Threading; @@ -170,6 +171,17 @@ protected: bool m_resetVsyncTimers; bool m_resetVirtualMachine; + // Stores the state of the socket IPC thread. + std::unique_ptr m_socketIpc; + + // Current state of the IPC thread + enum StateIPC + { + OFF, + ON + }; + StateIPC m_IpcState = OFF; + // Indicates if the system has an active virtual machine state. Pretty much always // true anytime between plugins being initialized and plugins being shutdown. Gets // set false when plugins are shutdown, the corethread is canceled, or when an error diff --git a/pcsx2/gui/App.h b/pcsx2/gui/App.h index 5b0031b5bc..fcc61b20e2 100644 --- a/pcsx2/gui/App.h +++ b/pcsx2/gui/App.h @@ -123,6 +123,7 @@ enum MenuIdentifiers MenuId_GameSettingsSubMenu, MenuId_EnablePatches, MenuId_EnableCheats, + MenuId_EnableIPC, MenuId_EnableWideScreenPatches, MenuId_EnableInputRecording, MenuId_EnableLuaTools, diff --git a/pcsx2/gui/MainFrame.cpp b/pcsx2/gui/MainFrame.cpp index 142ed15e5b..85b8202f35 100644 --- a/pcsx2/gui/MainFrame.cpp +++ b/pcsx2/gui/MainFrame.cpp @@ -216,6 +216,7 @@ void MainEmuFrame::ConnectMenus() Bind(wxEVT_MENU, &MainEmuFrame::Menu_EnablePatches_Click, this, MenuId_EnablePatches); Bind(wxEVT_MENU, &MainEmuFrame::Menu_EnableCheats_Click, this, MenuId_EnableCheats); + Bind(wxEVT_MENU, &MainEmuFrame::Menu_EnableIPC_Click, this, MenuId_EnableIPC); Bind(wxEVT_MENU, &MainEmuFrame::Menu_EnableWideScreenPatches_Click, this, MenuId_EnableWideScreenPatches); #ifndef DISABLE_RECORDING Bind(wxEVT_MENU, &MainEmuFrame::Menu_EnableRecordingTools_Click, this, MenuId_EnableInputRecording); @@ -368,6 +369,9 @@ void MainEmuFrame::CreatePcsx2Menu() m_GameSettingsSubmenu.Append(MenuId_EnableCheats, _("Enable &Cheats"), wxEmptyString, wxITEM_CHECK); + m_GameSettingsSubmenu.Append(MenuId_EnableIPC, _("Enable &IPC"), + wxEmptyString, wxITEM_CHECK); + m_GameSettingsSubmenu.Append(MenuId_EnableWideScreenPatches, _("Enable &Widescreen Patches"), _("Enabling Widescreen Patches may occasionally cause issues."), wxITEM_CHECK); @@ -744,6 +748,7 @@ void MainEmuFrame::ApplyConfigToGui(AppConfig& configToApply, int flags) {//these should not be affected by presets menubar.Check( MenuId_EnableBackupStates, configToApply.EmuOptions.BackupSavestate ); menubar.Check( MenuId_EnableCheats, configToApply.EmuOptions.EnableCheats ); + menubar.Check( MenuId_EnableIPC, configToApply.EmuOptions.EnableIPC ); menubar.Check( MenuId_EnableWideScreenPatches, configToApply.EmuOptions.EnableWideScreenPatches ); #ifndef DISABLE_RECORDING menubar.Check( MenuId_EnableInputRecording, configToApply.EmuOptions.EnableRecordingTools); diff --git a/pcsx2/gui/MainFrame.h b/pcsx2/gui/MainFrame.h index 81bc9f5466..b83689fafe 100644 --- a/pcsx2/gui/MainFrame.h +++ b/pcsx2/gui/MainFrame.h @@ -193,6 +193,7 @@ protected: void Menu_EnableBackupStates_Click(wxCommandEvent &event); void Menu_EnablePatches_Click(wxCommandEvent &event); void Menu_EnableCheats_Click(wxCommandEvent &event); + void Menu_EnableIPC_Click(wxCommandEvent &event); void Menu_EnableWideScreenPatches_Click(wxCommandEvent &event); #ifndef DISABLE_RECORDING void Menu_EnableRecordingTools_Click(wxCommandEvent &event); diff --git a/pcsx2/gui/MainMenuClicks.cpp b/pcsx2/gui/MainMenuClicks.cpp index aa72cf0cde..e8809900e1 100644 --- a/pcsx2/gui/MainMenuClicks.cpp +++ b/pcsx2/gui/MainMenuClicks.cpp @@ -528,6 +528,13 @@ void MainEmuFrame::Menu_EnableCheats_Click( wxCommandEvent& ) AppSaveSettings(); } +void MainEmuFrame::Menu_EnableIPC_Click( wxCommandEvent& ) +{ + g_Conf->EmuOptions.EnableIPC = GetMenuBar()->IsChecked( MenuId_EnableIPC ); + AppApplySettings(); + AppSaveSettings(); +} + void MainEmuFrame::Menu_EnableWideScreenPatches_Click( wxCommandEvent& ) { g_Conf->EmuOptions.EnableWideScreenPatches = GetMenuBar()->IsChecked( MenuId_EnableWideScreenPatches ); diff --git a/pcsx2/windows/VCprojects/pcsx2.vcxproj b/pcsx2/windows/VCprojects/pcsx2.vcxproj index 1dbd93413c..73e726f90c 100644 --- a/pcsx2/windows/VCprojects/pcsx2.vcxproj +++ b/pcsx2/windows/VCprojects/pcsx2.vcxproj @@ -188,6 +188,7 @@ + @@ -437,6 +438,7 @@ + @@ -624,4 +626,4 @@ - \ No newline at end of file + diff --git a/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters b/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters index 6009783928..4677afbed7 100644 --- a/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters +++ b/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters @@ -888,6 +888,8 @@ AppHost + + System @@ -1343,6 +1345,8 @@ AppHost\Include + + System\Include @@ -1428,4 +1432,4 @@ AppHost\Resources - \ No newline at end of file +