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 }