diff --git a/CMakeLists.txt b/CMakeLists.txt index d0aafa533..70609142f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -760,6 +760,8 @@ set(CORE src/core/aerolib/stubs.cpp src/core/file_format/trp.h src/core/file_sys/fs.cpp src/core/file_sys/fs.h + src/core/ipc/ipc.cpp + src/core/ipc/ipc.h src/core/loader/dwarf.cpp src/core/loader/dwarf.h src/core/loader/elf.cpp diff --git a/src/common/memory_patcher.cpp b/src/common/memory_patcher.cpp index 29ab6376a..3b0b02148 100644 --- a/src/common/memory_patcher.cpp +++ b/src/common/memory_patcher.cpp @@ -29,6 +29,7 @@ EXPORT uintptr_t g_eboot_address; uint64_t g_eboot_image_size; std::string g_game_serial; std::string patchFile; +bool patches_applied = false; std::vector pending_patches; std::string toHex(u64 value, size_t byteSize) { @@ -119,6 +120,8 @@ std::string convertValueToHex(const std::string type, const std::string valueStr return result; } +void ApplyPendingPatches(); + void OnGameLoaded() { if (!patchFile.empty()) { @@ -377,20 +380,26 @@ void OnGameLoaded() { } void AddPatchToQueue(patchInfo patchToAdd) { + if (patches_applied) { + PatchMemory(patchToAdd.modNameStr, patchToAdd.offsetStr, patchToAdd.valueStr, + patchToAdd.targetStr, patchToAdd.sizeStr, patchToAdd.isOffset, + patchToAdd.littleEndian, patchToAdd.patchMask, patchToAdd.maskOffset); + return; + } pending_patches.push_back(patchToAdd); } void ApplyPendingPatches() { - + patches_applied = true; for (size_t i = 0; i < pending_patches.size(); ++i) { - patchInfo currentPatch = pending_patches[i]; + const patchInfo& currentPatch = pending_patches[i]; - if (currentPatch.gameSerial != g_game_serial) + if (currentPatch.gameSerial != "*" && currentPatch.gameSerial != g_game_serial) continue; - PatchMemory(currentPatch.modNameStr, currentPatch.offsetStr, currentPatch.valueStr, "", "", - currentPatch.isOffset, currentPatch.littleEndian, currentPatch.patchMask, - currentPatch.maskOffset); + PatchMemory(currentPatch.modNameStr, currentPatch.offsetStr, currentPatch.valueStr, + currentPatch.targetStr, currentPatch.sizeStr, currentPatch.isOffset, + currentPatch.littleEndian, currentPatch.patchMask, currentPatch.maskOffset); } pending_patches.clear(); diff --git a/src/common/memory_patcher.h b/src/common/memory_patcher.h index 968903a85..93306f540 100644 --- a/src/common/memory_patcher.h +++ b/src/common/memory_patcher.h @@ -30,19 +30,18 @@ struct patchInfo { std::string modNameStr; std::string offsetStr; std::string valueStr; + std::string targetStr; + std::string sizeStr; bool isOffset; bool littleEndian; PatchMask patchMask; int maskOffset; }; -extern std::vector pending_patches; - std::string convertValueToHex(const std::string type, const std::string valueStr); void OnGameLoaded(); void AddPatchToQueue(patchInfo patchToAdd); -void ApplyPendingPatches(); void PatchMemory(std::string modNameStr, std::string offsetStr, std::string valueStr, std::string targetStr, std::string sizeStr, bool isOffset, bool littleEndian, diff --git a/src/core/ipc/ipc.cpp b/src/core/ipc/ipc.cpp new file mode 100644 index 000000000..b66c00efc --- /dev/null +++ b/src/core/ipc/ipc.cpp @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ipc.h" + +#include +#include + +#include "common/memory_patcher.h" +#include "common/thread.h" +#include "common/types.h" + +/** + * 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 conintue the execution + **/ + +/** + * Command list: + * - CAPABILITIES: + * - ENABLE_MEMORY_PATCH: enables PATCH_MEMORY command + * - 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 + * - OUTPUT CMD: + * - N/A + **/ + +void IPC::Init() { + const char* enabledEnv = std::getenv("SHADPS4_ENABLE_IPC"); + enabled = enabledEnv && strcmp(enabledEnv, "true") == 0; + if (!enabled) { + return; + } + + 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 << ";#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::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 { + std::cerr << "UNKNOWN CMD: " << cmd << std::endl; + } + } +} \ No newline at end of file diff --git a/src/core/ipc/ipc.h b/src/core/ipc/ipc.h new file mode 100644 index 000000000..28481d524 --- /dev/null +++ b/src/core/ipc/ipc.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/singleton.h" + +#include +#include + +class IPC { + bool enabled{false}; + std::jthread input_thread{}; + + std::binary_semaphore run_semaphore{0}; + std::binary_semaphore start_semaphore{0}; + +public: + static IPC& Instance() { + return *Common::Singleton::Instance(); + } + + void Init(); + + operator bool() const { + return enabled; + } + + [[nodiscard]] bool IsEnabled() const { + return enabled; + } + + void WaitForStart() { + start_semaphore.acquire(); + } + +private: + [[noreturn]] void InputLoop(); +}; diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 1f45caf12..492714285 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -18,6 +18,7 @@ #include "core/linker.h" #include "core/memory.h" #include "core/tls.h" +#include "ipc/ipc.h" namespace Core { @@ -115,6 +116,10 @@ void Linker::Execute(const std::vector args) { main_thread.Run([this, module, args](std::stop_token) { Common::SetCurrentThreadName("GAME_MainThread"); + if (auto& ipc = IPC::Instance()) { + ipc.WaitForStart(); + } + LoadSharedLibraries(); // Simulate libSceGnmDriver initialization, which maps a chunk of direct memory. diff --git a/src/main.cpp b/src/main.cpp index fe245d104..c2a3ed221 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #include "common/memory_patcher.h" #include "common/path_util.h" #include "core/file_sys/fs.h" +#include "core/ipc/ipc.h" #include "emulator.h" #ifdef _WIN32 @@ -22,6 +23,7 @@ int main(int argc, char* argv[]) { #ifdef _WIN32 SetConsoleOutputCP(CP_UTF8); #endif + IPC::Instance().Init(); // Load configurations const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); @@ -169,13 +171,12 @@ int main(int argc, char* argv[]) { break; } else { std::cerr << "Unknown argument: " << cur_arg << ", see --help for info.\n"; - return 1; } } // If no game directory is set and no command line argument, prompt for it if (Config::getGameInstallDirs().empty()) { - std::cout << "Warning: No game folder set, please set it by calling shadps4" + std::cerr << "Warning: No game folder set, please set it by calling shadps4" " with the --add-game-folder argument\n"; }