duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

pine_server.cpp (18002B)


      1 // SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team, Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0)
      3 // NOTE: File has been rewritten completely compared to the original, only the enums remain.
      4 
      5 #include "pine_server.h"
      6 #include "cpu_core.h"
      7 #include "host.h"
      8 #include "settings.h"
      9 #include "system.h"
     10 
     11 #include "scmversion/scmversion.h"
     12 
     13 #include "util/sockets.h"
     14 
     15 #include "common/assert.h"
     16 #include "common/binary_reader_writer.h"
     17 #include "common/error.h"
     18 #include "common/file_system.h"
     19 #include "common/log.h"
     20 #include "common/path.h"
     21 #include "common/small_string.h"
     22 
     23 #include "fmt/format.h"
     24 
     25 Log_SetChannel(PINEServer);
     26 
     27 namespace PINEServer {
     28 static std::shared_ptr<ListenSocket> s_listen_socket;
     29 
     30 #ifndef _WIN32
     31 static std::string s_socket_path;
     32 #endif
     33 
     34 /**
     35  * Maximum memory used by an IPC message request.
     36  * Equivalent to 50,000 Write64 requests.
     37  */
     38 static constexpr u32 MAX_IPC_SIZE = 650000;
     39 
     40 /**
     41  * Maximum memory used by an IPC message reply.
     42  * Equivalent to 50,000 Read64 replies.
     43  */
     44 static constexpr u32 MAX_IPC_RETURN_SIZE = 450000;
     45 
     46 /**
     47  * IPC Command messages opcodes.
     48  * A list of possible operations possible by the IPC.
     49  * Each one of them is what we call an "opcode" and is the first
     50  * byte sent by the IPC to differentiate between commands.
     51  */
     52 enum IPCCommand : u8
     53 {
     54   MsgRead8 = 0,           /**< Read 8 bit value from memory. */
     55   MsgRead16 = 1,          /**< Read 16 bit value from memory. */
     56   MsgRead32 = 2,          /**< Read 32 bit value from memory. */
     57   MsgRead64 = 3,          /**< Read 64 bit value from memory. */
     58   MsgWrite8 = 4,          /**< Write 8 bit value from memory. */
     59   MsgWrite16 = 5,         /**< Write 16 bit value from memory. */
     60   MsgWrite32 = 6,         /**< Write 32 bit value from memory. */
     61   MsgWrite64 = 7,         /**< Write 64 bit value from memory. */
     62   MsgVersion = 8,         /**< Returns PCSX2 version. */
     63   MsgSaveState = 9,       /**< Saves a savestate. */
     64   MsgLoadState = 0xA,     /**< Loads a savestate. */
     65   MsgTitle = 0xB,         /**< Returns the game title. */
     66   MsgID = 0xC,            /**< Returns the game ID. */
     67   MsgUUID = 0xD,          /**< Returns the game UUID. */
     68   MsgGameVersion = 0xE,   /**< Returns the game verion. */
     69   MsgStatus = 0xF,        /**< Returns the emulator status. */
     70   MsgReadBytes = 0x20,    /**< Reads range of bytes from memory. */
     71   MsgWriteBytes = 0x21,   /**< Writes range of bytes to memory. */
     72   MsgUnimplemented = 0xFF /**< Unimplemented IPC message. */
     73 };
     74 
     75 /**
     76  * Emulator status enum.
     77  * A list of possible emulator statuses.
     78  */
     79 enum class EmuStatus : u32
     80 {
     81   Running = 0, /**< Game is running */
     82   Paused = 1,  /**< Game is paused */
     83   Shutdown = 2 /**< Game is shutdown */
     84 };
     85 
     86 /**
     87  * IPC result codes.
     88  * A list of possible result codes the IPC can send back.
     89  * Each one of them is what we call an "opcode" or "tag" and is the
     90  * first byte sent by the IPC to differentiate between results.
     91  */
     92 using IPCStatus = u8;
     93 static constexpr IPCStatus IPC_OK = 0;      /**< IPC command successfully completed. */
     94 static constexpr IPCStatus IPC_FAIL = 0xFF; /**< IPC command failed to complete. */
     95 
     96 namespace {
     97 class PINESocket final : public BufferedStreamSocket
     98 {
     99 public:
    100   PINESocket(SocketMultiplexer& multiplexer, SocketDescriptor descriptor);
    101   ~PINESocket() override;
    102 
    103 protected:
    104   void OnConnected() override;
    105   void OnDisconnected(const Error& error) override;
    106   void OnRead() override;
    107   void OnWrite() override;
    108 
    109 private:
    110   void ProcessCommandsInBuffer();
    111   bool HandleCommand(IPCCommand command, BinarySpanReader rdbuf);
    112 
    113   bool BeginReply(BinarySpanWriter& wrbuf, size_t required_bytes);
    114   bool EndReply(const BinarySpanWriter& sw);
    115 
    116   bool SendErrorReply();
    117 };
    118 } // namespace
    119 } // namespace PINEServer
    120 
    121 bool PINEServer::IsRunning()
    122 {
    123   return static_cast<bool>(s_listen_socket);
    124 }
    125 
    126 bool PINEServer::Initialize(u16 slot)
    127 {
    128   Error error;
    129   std::optional<SocketAddress> address;
    130 #ifdef _WIN32
    131   address = SocketAddress::Parse(SocketAddress::Type::IPv4, "127.0.0.1", slot, &error);
    132 #else
    133 #ifdef __APPLE__
    134   const char* runtime_dir = std::getenv("TMPDIR");
    135 #else
    136   const char* runtime_dir = std::getenv("XDG_RUNTIME_DIR");
    137 #endif
    138   // fallback in case macOS or other OSes don't implement the XDG base spec
    139   runtime_dir = runtime_dir ? runtime_dir : "/tmp";
    140 
    141   std::string socket_path;
    142   if (slot != Settings::DEFAULT_PINE_SLOT)
    143     socket_path = fmt::format("{}/duckstation.sock.{}", runtime_dir, slot);
    144   else
    145     socket_path = fmt::format("{}/duckstation.sock", runtime_dir);
    146 
    147   // we unlink the socket so that when releasing this thread the socket gets
    148   // freed even if we didn't close correctly the loop
    149   FileSystem::DeleteFile(socket_path.c_str());
    150 
    151   address = SocketAddress::Parse(SocketAddress::Type::Unix, socket_path.c_str(), 0, &error);
    152 #endif
    153 
    154   if (!address.has_value())
    155   {
    156     ERROR_LOG("PINE: Failed to resolve listen address: {}", error.GetDescription());
    157     return false;
    158   }
    159 
    160   SocketMultiplexer* multiplexer = System::GetSocketMultiplexer();
    161   if (!multiplexer)
    162     return false;
    163 
    164   s_listen_socket = multiplexer->CreateListenSocket<PINESocket>(address.value(), &error);
    165   if (!s_listen_socket)
    166   {
    167     ERROR_LOG("PINE: Failed to create listen socket: {}", error.GetDescription());
    168     System::ReleaseSocketMultiplexer();
    169     return false;
    170   }
    171 
    172 #ifndef _WIN32
    173   s_socket_path = std::move(socket_path);
    174 #endif
    175 
    176   return true;
    177 }
    178 
    179 void PINEServer::Shutdown()
    180 {
    181   // also closes the listener
    182   if (s_listen_socket)
    183   {
    184     s_listen_socket->Close();
    185     s_listen_socket.reset();
    186     System::ReleaseSocketMultiplexer();
    187   }
    188 
    189   // unlink the socket so nobody tries to connect to something no longer existent
    190 #ifndef _WIN32
    191   if (!s_socket_path.empty())
    192   {
    193     FileSystem::DeleteFile(s_socket_path.c_str());
    194     s_socket_path = {};
    195   }
    196 #endif
    197 }
    198 
    199 PINEServer::PINESocket::PINESocket(SocketMultiplexer& multiplexer, SocketDescriptor descriptor)
    200   : BufferedStreamSocket(multiplexer, descriptor, MAX_IPC_SIZE, MAX_IPC_RETURN_SIZE)
    201 {
    202 }
    203 
    204 PINEServer::PINESocket::~PINESocket() = default;
    205 
    206 void PINEServer::PINESocket::OnConnected()
    207 {
    208   INFO_LOG("New client at {} connected.", GetRemoteAddress().ToString());
    209 
    210   Error error;
    211   if (GetLocalAddress().IsIPAddress() && !SetNagleBuffering(false, &error))
    212     ERROR_LOG("Failed to disable nagle buffering: {}", error.GetDescription());
    213 }
    214 
    215 void PINEServer::PINESocket::OnDisconnected(const Error& error)
    216 {
    217   INFO_LOG("Client {} disconnected: {}", GetRemoteAddress().ToString(), error.GetDescription());
    218 }
    219 
    220 void PINEServer::PINESocket::OnRead()
    221 {
    222   ProcessCommandsInBuffer();
    223 }
    224 
    225 void PINEServer::PINESocket::OnWrite()
    226 {
    227   ProcessCommandsInBuffer();
    228 }
    229 
    230 void PINEServer::PINESocket::ProcessCommandsInBuffer()
    231 {
    232   std::span<const u8> rdbuf = AcquireReadBuffer();
    233   if (rdbuf.empty())
    234     return;
    235 
    236   size_t position = 0;
    237   size_t remaining = rdbuf.size();
    238   while (remaining >= sizeof(u32))
    239   {
    240     u32 packet_size;
    241     std::memcpy(&packet_size, &rdbuf[position], sizeof(u32));
    242     if (packet_size > MAX_IPC_SIZE || packet_size < 5)
    243     {
    244       ERROR_LOG("PINE: Received invalid packet size {}", packet_size);
    245       Close();
    246       return;
    247     }
    248 
    249     // whole thing received yet yet?
    250     if (packet_size > remaining)
    251       break;
    252 
    253     const IPCCommand command = static_cast<IPCCommand>(rdbuf[position + sizeof(u32)]);
    254     if (!HandleCommand(command, BinarySpanReader(rdbuf.subspan(position + sizeof(u32) + sizeof(u8),
    255                                                                packet_size - sizeof(u32) - sizeof(u8)))))
    256     {
    257       // Out of write buffer space, abort.
    258       break;
    259     }
    260 
    261     position += packet_size;
    262     remaining -= packet_size;
    263   }
    264 
    265   ReleaseReadBuffer(position);
    266   ReleaseWriteBuffer(0, true);
    267 }
    268 
    269 bool PINEServer::PINESocket::HandleCommand(IPCCommand command, BinarySpanReader rdbuf)
    270 {
    271   // example IPC messages: MsgRead/Write
    272   // refer to the client doc for more info on the format
    273   //         IPC Message event (1 byte)
    274   //         |  Memory address (4 byte)
    275   //         |  |           argument (VLE)
    276   //         |  |           |
    277   // format: XX YY YY YY YY ZZ ZZ ZZ ZZ
    278   //        reply code: 00 = OK, FF = NOT OK
    279   //        |  return value (VLE)
    280   //        |  |
    281   // reply: XX ZZ ZZ ZZ ZZ
    282 
    283   BinarySpanWriter reply;
    284   switch (command)
    285   {
    286     case MsgRead8:
    287     {
    288       if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress)) || !System::IsValid())
    289         return SendErrorReply();
    290       else if (!BeginReply(reply, sizeof(u8))) [[unlikely]]
    291         return false;
    292 
    293       const PhysicalMemoryAddress addr = rdbuf.ReadU32();
    294       u8 res = 0;
    295       reply << (CPU::SafeReadMemoryByte(addr, &res) ? IPC_OK : IPC_FAIL);
    296       reply << res;
    297       return EndReply(reply);
    298     }
    299 
    300     case MsgRead16:
    301     {
    302       if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress)) || !System::IsValid())
    303         return SendErrorReply();
    304       else if (!BeginReply(reply, sizeof(u16))) [[unlikely]]
    305         return false;
    306 
    307       const PhysicalMemoryAddress addr = rdbuf.ReadU32();
    308       u16 res = 0;
    309       reply << (CPU::SafeReadMemoryHalfWord(addr, &res) ? IPC_OK : IPC_FAIL);
    310       reply << res;
    311       return EndReply(reply);
    312     }
    313 
    314     case MsgRead32:
    315     {
    316       if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress)) || !System::IsValid())
    317         return SendErrorReply();
    318       else if (!BeginReply(reply, sizeof(u32))) [[unlikely]]
    319         return false;
    320 
    321       const PhysicalMemoryAddress addr = rdbuf.ReadU32();
    322       u32 res = 0;
    323       reply << (CPU::SafeReadMemoryWord(addr, &res) ? IPC_OK : IPC_FAIL);
    324       reply << res;
    325       return EndReply(reply);
    326     }
    327 
    328     case MsgRead64:
    329     {
    330       if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress)) || !System::IsValid())
    331         return SendErrorReply();
    332       else if (!BeginReply(reply, sizeof(u64))) [[unlikely]]
    333         return false;
    334 
    335       const PhysicalMemoryAddress addr = rdbuf.ReadU32();
    336       u32 res_low = 0, res_high = 0;
    337       reply << ((!CPU::SafeReadMemoryWord(addr, &res_low) || !CPU::SafeReadMemoryWord(addr + sizeof(u32), &res_high)) ?
    338                   IPC_FAIL :
    339                   IPC_OK);
    340       reply << ((ZeroExtend64(res_high) << 32) | ZeroExtend64(res_low));
    341       return EndReply(reply);
    342     }
    343 
    344     case MsgReadBytes:
    345     {
    346       if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress) + sizeof(u32)) || !System::IsValid())
    347         return SendErrorReply();
    348 
    349       const PhysicalMemoryAddress addr = rdbuf.ReadU32();
    350       const u32 num_bytes = rdbuf.ReadU32();
    351       if (num_bytes == 0) [[unlikely]]
    352         return SendErrorReply();
    353 
    354       if (!BeginReply(reply, num_bytes)) [[unlikely]]
    355         return false;
    356 
    357       const auto data = reply.GetRemainingSpan(sizeof(IPCStatus) + num_bytes);
    358       if (!CPU::SafeReadMemoryBytes(addr, data.data() + sizeof(IPCStatus), num_bytes)) [[unlikely]]
    359       {
    360         reply << IPC_FAIL;
    361       }
    362       else
    363       {
    364         reply << IPC_OK;
    365         reply.IncrementPosition(num_bytes);
    366       }
    367 
    368       return EndReply(reply);
    369     }
    370 
    371     case MsgWrite8:
    372     {
    373       // Don't do the actual write until we have space for the response, otherwise we might do it twice when we come
    374       // back around.
    375       if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress) + sizeof(u8)) || !System::IsValid())
    376         return SendErrorReply();
    377       else if (!BeginReply(reply, 0)) [[unlikely]]
    378         return false;
    379 
    380       const PhysicalMemoryAddress addr = rdbuf.ReadU32();
    381       const u8 value = rdbuf.ReadU8();
    382       reply << (CPU::SafeWriteMemoryByte(addr, value) ? IPC_OK : IPC_FAIL);
    383       return EndReply(reply);
    384     }
    385 
    386     case MsgWrite16:
    387     {
    388       if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress) + sizeof(u16)) || !System::IsValid())
    389         return SendErrorReply();
    390       else if (!BeginReply(reply, 0)) [[unlikely]]
    391         return false;
    392 
    393       const PhysicalMemoryAddress addr = rdbuf.ReadU32();
    394       const u16 value = rdbuf.ReadU16();
    395       reply << (CPU::SafeWriteMemoryHalfWord(addr, value) ? IPC_OK : IPC_FAIL);
    396       return EndReply(reply);
    397     }
    398 
    399     case MsgWrite32:
    400     {
    401       if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress) + sizeof(u32)) || !System::IsValid())
    402         return SendErrorReply();
    403       else if (!BeginReply(reply, 0)) [[unlikely]]
    404         return false;
    405 
    406       const PhysicalMemoryAddress addr = rdbuf.ReadU32();
    407       const u32 value = rdbuf.ReadU32();
    408       reply << (CPU::SafeWriteMemoryWord(addr, value) ? IPC_OK : IPC_FAIL);
    409       return EndReply(reply);
    410     }
    411 
    412     case MsgWrite64:
    413     {
    414       if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress) + sizeof(u64)) || !System::IsValid())
    415         return SendErrorReply();
    416       else if (!BeginReply(reply, 0)) [[unlikely]]
    417         return false;
    418 
    419       const PhysicalMemoryAddress addr = rdbuf.ReadU32();
    420       const u64 value = rdbuf.ReadU64();
    421       reply << ((!CPU::SafeWriteMemoryWord(addr, Truncate32(value)) ||
    422                  !CPU::SafeWriteMemoryWord(addr + sizeof(u32), Truncate32(value >> 32))) ?
    423                   IPC_FAIL :
    424                   IPC_OK);
    425       return EndReply(reply);
    426     }
    427 
    428     case MsgWriteBytes:
    429     {
    430       if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress) + sizeof(u32)) || !System::IsValid())
    431         return SendErrorReply();
    432 
    433       const PhysicalMemoryAddress addr = rdbuf.ReadU32();
    434       const u32 num_bytes = rdbuf.ReadU32();
    435       if (num_bytes == 0 || !rdbuf.CheckRemaining(num_bytes)) [[unlikely]]
    436         return SendErrorReply();
    437 
    438       if (!BeginReply(reply, 0)) [[unlikely]]
    439         return false;
    440 
    441       const auto data = rdbuf.GetRemainingSpan(num_bytes);
    442       reply << (CPU::SafeWriteMemoryBytes(addr, data.data(), num_bytes) ? IPC_OK : IPC_FAIL);
    443       return EndReply(reply);
    444     }
    445 
    446     case MsgVersion:
    447     {
    448       const TinyString version = TinyString::from_format("DuckStation {}", g_scm_tag_str);
    449       if (!BeginReply(reply, version.length() + 1)) [[unlikely]]
    450         return false;
    451 
    452       reply << IPC_OK << version;
    453       return EndReply(reply);
    454     }
    455 
    456     case MsgSaveState:
    457     {
    458       if (!rdbuf.CheckRemaining(sizeof(u8)) || !System::IsValid())
    459         return SendErrorReply();
    460 
    461       const std::string& serial = System::GetGameSerial();
    462       if (!serial.empty())
    463         return SendErrorReply();
    464 
    465       if (!BeginReply(reply, 0)) [[unlikely]]
    466         return false;
    467 
    468       std::string state_filename = System::GetGameSaveStateFileName(serial, rdbuf.ReadU8());
    469       Host::RunOnCPUThread([state_filename = std::move(state_filename)] {
    470         Error error;
    471         if (!System::SaveState(state_filename.c_str(), &error, false))
    472           ERROR_LOG("PINE: Save state failed: {}", error.GetDescription());
    473       });
    474 
    475       reply << IPC_OK;
    476       return EndReply(reply);
    477     }
    478 
    479     case MsgLoadState:
    480     {
    481       if (!rdbuf.CheckRemaining(sizeof(u8)) || !System::IsValid())
    482         return SendErrorReply();
    483 
    484       const std::string& serial = System::GetGameSerial();
    485       if (!serial.empty())
    486         return SendErrorReply();
    487 
    488       std::string state_filename = System::GetGameSaveStateFileName(serial, rdbuf.ReadU8());
    489       if (!FileSystem::FileExists(state_filename.c_str()))
    490         return SendErrorReply();
    491 
    492       if (!BeginReply(reply, 0)) [[unlikely]]
    493         return false;
    494 
    495       Host::RunOnCPUThread([state_filename = std::move(state_filename)] {
    496         Error error;
    497         if (!System::LoadState(state_filename.c_str(), &error, true))
    498           ERROR_LOG("PINE: Load state failed: {}", error.GetDescription());
    499       });
    500 
    501       reply << IPC_OK;
    502       return EndReply(reply);
    503     }
    504 
    505     case MsgTitle:
    506     {
    507       if (!System::IsValid())
    508         return SendErrorReply();
    509 
    510       const std::string& name = System::GetGameTitle();
    511       if (!BeginReply(reply, name.length() + 1)) [[unlikely]]
    512         return false;
    513 
    514       reply << IPC_OK << name;
    515       return EndReply(reply);
    516     }
    517 
    518     case MsgID:
    519     {
    520       if (!System::IsValid())
    521         return SendErrorReply();
    522 
    523       const std::string& serial = System::GetGameSerial();
    524       if (!BeginReply(reply, serial.length() + 1)) [[unlikely]]
    525         return false;
    526 
    527       reply << IPC_OK << serial;
    528       return EndReply(reply);
    529     }
    530 
    531     case MsgUUID:
    532     {
    533       if (!System::IsValid())
    534         return SendErrorReply();
    535 
    536       const TinyString crc = TinyString::from_format("{:016x}", System::GetGameHash());
    537       if (!BeginReply(reply, crc.length() + 1)) [[unlikely]]
    538         return false;
    539 
    540       reply << IPC_OK << crc;
    541       return EndReply(reply);
    542     }
    543 
    544     case MsgGameVersion:
    545     {
    546       ERROR_LOG("PINE: MsgGameVersion not supported.");
    547       return SendErrorReply();
    548     }
    549 
    550     case MsgStatus:
    551     {
    552       EmuStatus status;
    553       switch (System::GetState())
    554       {
    555         case System::State::Running:
    556           status = EmuStatus::Running;
    557           break;
    558         case System::State::Paused:
    559           status = EmuStatus::Paused;
    560           break;
    561         default:
    562           status = EmuStatus::Shutdown;
    563           break;
    564       }
    565 
    566       if (!BeginReply(reply, sizeof(u32))) [[unlikely]]
    567         return false;
    568 
    569       reply << IPC_OK << static_cast<u32>(status);
    570       return EndReply(reply);
    571     }
    572 
    573     default:
    574     {
    575       ERROR_LOG("PINE: Unhandled IPC command {:02X}", static_cast<u8>(command));
    576       return SendErrorReply();
    577     }
    578   }
    579 }
    580 
    581 bool PINEServer::PINESocket::BeginReply(BinarySpanWriter& wrbuf, size_t required_bytes)
    582 {
    583   wrbuf = (AcquireWriteBuffer(sizeof(u32) + sizeof(IPCStatus) + required_bytes, false));
    584   if (!wrbuf.IsValid()) [[unlikely]]
    585     return false;
    586 
    587   wrbuf << static_cast<u32>(0); // size placeholder
    588   return true;
    589 }
    590 
    591 bool PINEServer::PINESocket::EndReply(const BinarySpanWriter& sw)
    592 {
    593   DebugAssert(sw.IsValid());
    594   const size_t total_size = sw.GetBufferWritten();
    595   std::memcpy(&sw.GetSpan()[0], &total_size, sizeof(u32));
    596   ReleaseWriteBuffer(sw.GetBufferWritten(), false);
    597   return true;
    598 }
    599 
    600 bool PINEServer::PINESocket::SendErrorReply()
    601 {
    602   BinarySpanWriter reply;
    603   if (!BeginReply(reply, 0)) [[unlikely]]
    604     return false;
    605 
    606   reply << IPC_FAIL;
    607   return EndReply(reply);
    608 }