mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-12-09 13:19:00 +00:00
Simple IPC for external control (#3345)
* add simple IPC protocol to allow communication via stdin/stdout * ipc: add PATCH_MEMORY command enables patches & cheates
This commit is contained in:
@@ -760,6 +760,8 @@ set(CORE src/core/aerolib/stubs.cpp
|
|||||||
src/core/file_format/trp.h
|
src/core/file_format/trp.h
|
||||||
src/core/file_sys/fs.cpp
|
src/core/file_sys/fs.cpp
|
||||||
src/core/file_sys/fs.h
|
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.cpp
|
||||||
src/core/loader/dwarf.h
|
src/core/loader/dwarf.h
|
||||||
src/core/loader/elf.cpp
|
src/core/loader/elf.cpp
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ EXPORT uintptr_t g_eboot_address;
|
|||||||
uint64_t g_eboot_image_size;
|
uint64_t g_eboot_image_size;
|
||||||
std::string g_game_serial;
|
std::string g_game_serial;
|
||||||
std::string patchFile;
|
std::string patchFile;
|
||||||
|
bool patches_applied = false;
|
||||||
std::vector<patchInfo> pending_patches;
|
std::vector<patchInfo> pending_patches;
|
||||||
|
|
||||||
std::string toHex(u64 value, size_t byteSize) {
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApplyPendingPatches();
|
||||||
|
|
||||||
void OnGameLoaded() {
|
void OnGameLoaded() {
|
||||||
|
|
||||||
if (!patchFile.empty()) {
|
if (!patchFile.empty()) {
|
||||||
@@ -377,20 +380,26 @@ void OnGameLoaded() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AddPatchToQueue(patchInfo patchToAdd) {
|
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);
|
pending_patches.push_back(patchToAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplyPendingPatches() {
|
void ApplyPendingPatches() {
|
||||||
|
patches_applied = true;
|
||||||
for (size_t i = 0; i < pending_patches.size(); ++i) {
|
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;
|
continue;
|
||||||
|
|
||||||
PatchMemory(currentPatch.modNameStr, currentPatch.offsetStr, currentPatch.valueStr, "", "",
|
PatchMemory(currentPatch.modNameStr, currentPatch.offsetStr, currentPatch.valueStr,
|
||||||
currentPatch.isOffset, currentPatch.littleEndian, currentPatch.patchMask,
|
currentPatch.targetStr, currentPatch.sizeStr, currentPatch.isOffset,
|
||||||
currentPatch.maskOffset);
|
currentPatch.littleEndian, currentPatch.patchMask, currentPatch.maskOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
pending_patches.clear();
|
pending_patches.clear();
|
||||||
|
|||||||
@@ -30,19 +30,18 @@ struct patchInfo {
|
|||||||
std::string modNameStr;
|
std::string modNameStr;
|
||||||
std::string offsetStr;
|
std::string offsetStr;
|
||||||
std::string valueStr;
|
std::string valueStr;
|
||||||
|
std::string targetStr;
|
||||||
|
std::string sizeStr;
|
||||||
bool isOffset;
|
bool isOffset;
|
||||||
bool littleEndian;
|
bool littleEndian;
|
||||||
PatchMask patchMask;
|
PatchMask patchMask;
|
||||||
int maskOffset;
|
int maskOffset;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern std::vector<patchInfo> pending_patches;
|
|
||||||
|
|
||||||
std::string convertValueToHex(const std::string type, const std::string valueStr);
|
std::string convertValueToHex(const std::string type, const std::string valueStr);
|
||||||
|
|
||||||
void OnGameLoaded();
|
void OnGameLoaded();
|
||||||
void AddPatchToQueue(patchInfo patchToAdd);
|
void AddPatchToQueue(patchInfo patchToAdd);
|
||||||
void ApplyPendingPatches();
|
|
||||||
|
|
||||||
void PatchMemory(std::string modNameStr, std::string offsetStr, std::string valueStr,
|
void PatchMemory(std::string modNameStr, std::string offsetStr, std::string valueStr,
|
||||||
std::string targetStr, std::string sizeStr, bool isOffset, bool littleEndian,
|
std::string targetStr, std::string sizeStr, bool isOffset, bool littleEndian,
|
||||||
|
|||||||
113
src/core/ipc/ipc.cpp
Normal file
113
src/core/ipc/ipc.cpp
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "ipc.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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<MemoryPatcher::PatchMask>(next_u64());
|
||||||
|
entry.maskOffset = static_cast<int>(next_u64());
|
||||||
|
MemoryPatcher::AddPatchToQueue(entry);
|
||||||
|
} else {
|
||||||
|
std::cerr << "UNKNOWN CMD: " << cmd << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/core/ipc/ipc.h
Normal file
39
src/core/ipc/ipc.h
Normal file
@@ -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 <semaphore>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
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<IPC>::Instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Init();
|
||||||
|
|
||||||
|
operator bool() const {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsEnabled() const {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaitForStart() {
|
||||||
|
start_semaphore.acquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[noreturn]] void InputLoop();
|
||||||
|
};
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "core/linker.h"
|
#include "core/linker.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
#include "core/tls.h"
|
#include "core/tls.h"
|
||||||
|
#include "ipc/ipc.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
@@ -115,6 +116,10 @@ void Linker::Execute(const std::vector<std::string> args) {
|
|||||||
|
|
||||||
main_thread.Run([this, module, args](std::stop_token) {
|
main_thread.Run([this, module, args](std::stop_token) {
|
||||||
Common::SetCurrentThreadName("GAME_MainThread");
|
Common::SetCurrentThreadName("GAME_MainThread");
|
||||||
|
if (auto& ipc = IPC::Instance()) {
|
||||||
|
ipc.WaitForStart();
|
||||||
|
}
|
||||||
|
|
||||||
LoadSharedLibraries();
|
LoadSharedLibraries();
|
||||||
|
|
||||||
// Simulate libSceGnmDriver initialization, which maps a chunk of direct memory.
|
// Simulate libSceGnmDriver initialization, which maps a chunk of direct memory.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "common/memory_patcher.h"
|
#include "common/memory_patcher.h"
|
||||||
#include "common/path_util.h"
|
#include "common/path_util.h"
|
||||||
#include "core/file_sys/fs.h"
|
#include "core/file_sys/fs.h"
|
||||||
|
#include "core/ipc/ipc.h"
|
||||||
#include "emulator.h"
|
#include "emulator.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -22,6 +23,7 @@ int main(int argc, char* argv[]) {
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
SetConsoleOutputCP(CP_UTF8);
|
SetConsoleOutputCP(CP_UTF8);
|
||||||
#endif
|
#endif
|
||||||
|
IPC::Instance().Init();
|
||||||
|
|
||||||
// Load configurations
|
// Load configurations
|
||||||
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||||
@@ -169,13 +171,12 @@ int main(int argc, char* argv[]) {
|
|||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "Unknown argument: " << cur_arg << ", see --help for info.\n";
|
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 no game directory is set and no command line argument, prompt for it
|
||||||
if (Config::getGameInstallDirs().empty()) {
|
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 <folder_name> argument\n";
|
" with the --add-game-folder <folder_name> argument\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user