// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "ipc.h" #include #include #include #include "common/memory_patcher.h" #include "common/thread.h" #include "common/types.h" #include "core/debug_state.h" #include "core/debugger.h" #include "core/emulator_settings.h" #include "core/emulator_state.h" #include "core/libraries/audio/audioout.h" #include "input/input_handler.h" #include "sdl_window.h" #include "src/core/libraries/usbd/usbd.h" #include "video_core/renderer_vulkan/vk_presenter.h" extern std::unique_ptr presenter; /** * Protocol summary: * - IPC is enabled by setting the SHADPS4_ENABLE_IPC environment variable to "true" * - Input will be stdin & output stderr * - Strings are sent as UTF8 * - Each communication line is terminated by a newline character ('\n') * - Each command parameter will be separated by a newline character ('\n'), * variadic commands will start sending the number of parameters after the cmd word. * Any ('\n') in the parameter must be escaped by a backslash ('\\n') * - Numbers can be sent with any base. Must prefix the number with '0x' for hex, * '0b' for binary, or '0' for octal. Decimal numbers * will be sent without any prefix. * - Output will be started by (';') * - The IPC server(this) will send a block started by * #IPC_ENABLED * and ended by * #IPC_END * In between, it will send the current capabilities and commands before the emulator start * - The IPC client(e.g., launcher) will send RUN then START to continue the execution **/ /** * Command list: * - CAPABILITIES: * - ENABLE_MEMORY_PATCH: enables PATCH_MEMORY command * - ENABLE_EMU_CONTROL: enables PAUSE, RESUME, STOP, TOGGLE_FULLSCREEN commands * - INPUT CMD: * - RUN: start the emulator execution * - START: start the game execution * - PATCH_MEMORY( * modName: str, offset: str, value: str, * target: str, size: str, isOffset: number, littleEndian: number, * patchMask: number, maskOffset: number * ): add a memory patch, check @ref MemoryPatcher::PatchMemory for details * - PAUSE: pause the game execution * - RESUME: resume the game execution * - STOP: stop and quit the emulator * - TOGGLE_FULLSCREEN: enable / disable fullscreen * - OUTPUT CMD: * - RESTART(argn: number, argv: ...string): Request restart of the emulator, must call STOP **/ void IPC::Init() { const char* enabledEnv = std::getenv("SHADPS4_ENABLE_IPC"); enabled = enabledEnv && strcmp(enabledEnv, "true") == 0; if (!enabled) { return; } EmulatorState::GetInstance()->SetAutoPatchesLoadEnabled(false); input_thread = std::jthread([this] { Common::SetCurrentThreadName("IPC Read thread"); this->InputLoop(); }); std::cerr << ";#IPC_ENABLED\n"; std::cerr << ";ENABLE_MEMORY_PATCH\n"; std::cerr << ";ENABLE_EMU_CONTROL\n"; std::cerr << ";#IPC_END\n"; std::cerr.flush(); const auto ok = run_semaphore.try_acquire_for(std::chrono::seconds(5)); if (!ok) { std::cerr << "IPC: Failed to acquire run semaphore, closing process.\n"; exit(1); } } void IPC::SendRestart(const std::vector& args) { std::cerr << ";RESTART\n"; std::cerr << ";" << args.size() << "\n"; for (const auto& arg : args) { std::cerr << ";" << arg << "\n"; } std::cerr.flush(); } void IPC::InputLoop() { auto next_str = [&] -> const std::string& { static std::string line_buffer; do { std::getline(std::cin, line_buffer, '\n'); } while (!line_buffer.empty() && line_buffer.back() == '\\'); return line_buffer; }; auto next_u64 = [&] -> u64 { auto& str = next_str(); return std::stoull(str, nullptr, 0); }; while (true) { auto& cmd = next_str(); if (cmd.empty()) { continue; } if (cmd == "RUN") { run_semaphore.release(); } else if (cmd == "START") { start_semaphore.release(); } else if (cmd == "PATCH_MEMORY") { MemoryPatcher::patchInfo entry; entry.gameSerial = "*"; entry.modNameStr = next_str(); entry.offsetStr = next_str(); entry.valueStr = next_str(); entry.targetStr = next_str(); entry.sizeStr = next_str(); entry.isOffset = next_u64() != 0; entry.littleEndian = next_u64() != 0; entry.patchMask = static_cast(next_u64()); entry.maskOffset = static_cast(next_u64()); MemoryPatcher::AddPatchToQueue(entry); } else if (cmd == "PAUSE") { DebugState.PauseGuestThreads(); } else if (cmd == "RESUME") { DebugState.ResumeGuestThreads(); } else if (cmd == "STOP") { SDL_Event event; SDL_memset(&event, 0, sizeof(event)); event.type = SDL_EVENT_QUIT; SDL_PushEvent(&event); } else if (cmd == "TOGGLE_FULLSCREEN") { SDL_Event event; SDL_memset(&event, 0, sizeof(event)); event.type = SDL_EVENT_TOGGLE_FULLSCREEN; SDL_PushEvent(&event); } else if (cmd == "ADJUST_VOLUME") { int value = static_cast(next_u64()); bool is_game_specific = next_u64() != 0; EmulatorSettings.SetVolumeSlider(value); Libraries::AudioOut::AdjustVol(); } else if (cmd == "SET_FSR") { bool use_fsr = next_u64() != 0; if (presenter) { presenter->GetFsrSettingsRef().enable = use_fsr; } } else if (cmd == "SET_RCAS") { bool use_rcas = next_u64() != 0; if (presenter) { presenter->GetFsrSettingsRef().use_rcas = use_rcas; } } else if (cmd == "SET_RCAS_ATTENUATION") { int value = static_cast(next_u64()); if (presenter) { presenter->GetFsrSettingsRef().rcas_attenuation = static_cast(value / 1000.0f); } } else if (cmd == "USB_LOAD_FIGURE") { const auto ref = Libraries::Usbd::usb_backend->GetImplRef(); if (ref) { std::string file_name = next_str(); const u8 pad = next_u64(); const u8 slot = next_u64(); ref->LoadFigure(file_name, pad, slot); } } else if (cmd == "USB_REMOVE_FIGURE") { const auto ref = Libraries::Usbd::usb_backend->GetImplRef(); if (ref) { const u8 pad = next_u64(); const u8 slot = next_u64(); bool full_remove = next_u64() != 0; ref->RemoveFigure(pad, slot, full_remove); } } else if (cmd == "USB_MOVE_FIGURE") { const auto ref = Libraries::Usbd::usb_backend->GetImplRef(); if (ref) { const u8 new_pad = next_u64(); const u8 new_index = next_u64(); const u8 old_pad = next_u64(); const u8 old_index = next_u64(); ref->MoveFigure(new_pad, new_index, old_pad, old_index); } } else if (cmd == "USB_TEMP_REMOVE_FIGURE") { const auto ref = Libraries::Usbd::usb_backend->GetImplRef(); if (ref) { const u8 index = next_u64(); ref->TempRemoveFigure(index); } } else if (cmd == "USB_CANCEL_REMOVE_FIGURE") { const auto ref = Libraries::Usbd::usb_backend->GetImplRef(); if (ref) { const u8 index = next_u64(); ref->CancelRemoveFigure(index); } } else if (cmd == "RELOAD_INPUTS") { std::string config = next_str(); Input::ParseInputConfig(config); } else { std::cerr << ";UNKNOWN CMD: " << cmd << std::endl; } } }