Fix cheats and rpc server affecting the wrong processes (#956)

This commit is contained in:
PabloMK7 2025-04-23 17:09:37 +02:00 committed by GitHub
parent 391f91f735
commit eba3c2c08f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 363 additions and 67 deletions

View file

@ -1,15 +1,21 @@
# Copyright Citra Emulator Project / Azahar Emulator Project
# Licensed under GPLv2 or any later version
# Refer to the license.txt file included.
import struct
import random
import enum
import socket
CURRENT_REQUEST_VERSION = 1
MAX_REQUEST_DATA_SIZE = 32
MAX_PACKET_SIZE = 48
MAX_REQUEST_DATA_SIZE = 1024
MAX_PACKET_SIZE = 1024 + 0x10
class RequestType(enum.IntEnum):
ReadMemory = 1,
WriteMemory = 2
WriteMemory = 2,
ProcessList = 3,
SetGetProcess = 4,
CITRA_PORT = 45987
@ -34,6 +40,55 @@ class Citra:
return raw_reply[4*4:]
return None
def process_list(self):
processes = {}
read_processes = 0
while True:
request_data = struct.pack("II", read_processes, 0x7FFFFFFF)
request, request_id = self._generate_header(RequestType.ProcessList, len(request_data))
request += request_data
self.socket.sendto(request, (self.address, CITRA_PORT))
raw_reply = self.socket.recv(MAX_PACKET_SIZE)
reply_data = self._read_and_validate_header(raw_reply, request_id, RequestType.ProcessList)
if reply_data:
read_count = struct.unpack("I", reply_data[0:4])[0]
reply_data = reply_data[4:]
if read_count == 0:
break
read_processes += read_count
for i in range(read_count):
proc_data = reply_data[i * 0x14 : (i + 1) * 0x14]
proc_id, title_id, proc_name = struct.unpack("<IQ8s", proc_data)
proc_name = proc_name.rstrip(b"\x00").decode("ascii")
processes[proc_id] = (title_id, proc_name)
else:
break
return processes
def get_process(self):
request_data = struct.pack("II", 0, 0)
request, request_id = self._generate_header(RequestType.SetGetProcess, len(request_data))
request += request_data
self.socket.sendto(request, (self.address, CITRA_PORT))
raw_reply = self.socket.recv(MAX_PACKET_SIZE)
reply_data = self._read_and_validate_header(raw_reply, request_id, RequestType.SetGetProcess)
if reply_data:
return struct.unpack("I", reply_data)[0]
else:
return None
def set_process(self, process_id):
request_data = struct.pack("II", 1, process_id)
request, request_id = self._generate_header(RequestType.SetGetProcess, len(request_data))
request += request_data
self.socket.sendto(request, (self.address, CITRA_PORT))
self.socket.recv(MAX_PACKET_SIZE)
def read_memory(self, read_address, read_size):
"""
>>> c.read_memory(0x100000, 4)

View file

@ -1,10 +1,11 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
#include "common/common_types.h"
namespace Core {
class System;
@ -14,7 +15,7 @@ namespace Cheats {
class CheatBase {
public:
virtual ~CheatBase();
virtual void Execute(Core::System& system) const = 0;
virtual void Execute(Core::System& system, u32 process_id) const = 0;
virtual bool IsEnabled() const = 0;
virtual void SetEnabled(bool enabled) = 0;

View file

@ -1,4 +1,4 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -25,7 +25,8 @@ CheatEngine::~CheatEngine() {
}
}
void CheatEngine::Connect() {
void CheatEngine::Connect(u32 process_id) {
this->process_id = process_id;
event = system.CoreTiming().RegisterEvent(
"CheatCore::run_event",
[this](u64 thread_id, s64 cycle_late) { RunCallback(thread_id, cycle_late); });
@ -107,7 +108,7 @@ void CheatEngine::RunCallback([[maybe_unused]] std::uintptr_t user_data, s64 cyc
std::shared_lock lock{cheats_list_mutex};
for (const auto& cheat : cheats_list) {
if (cheat->IsEnabled()) {
cheat->Execute(system);
cheat->Execute(system, process_id);
}
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -30,7 +30,7 @@ public:
~CheatEngine();
/// Registers the cheat execution callback.
void Connect();
void Connect(u32 process_id);
/// Returns a span of the currently active cheats.
std::span<const std::shared_ptr<CheatBase>> GetCheats() const;
@ -50,6 +50,10 @@ public:
/// Saves currently active cheats to file for the specified title id.
void SaveCheatFile(u64 title_id) const;
u32 GetConnectedPID() const {
return process_id;
}
private:
/// The cheat execution callback.
void RunCallback(std::uintptr_t user_data, s64 cycles_late);
@ -58,6 +62,7 @@ private:
Core::System& system;
Core::TimingEventType* event;
std::optional<u64> loaded_title_id;
u32 process_id = 0xFFFFFFFF;
std::vector<std::shared_ptr<CheatBase>> cheats_list;
mutable std::shared_mutex cheats_list_mutex;
};

View file

@ -56,10 +56,10 @@ static inline std::enable_if_t<std::is_integral_v<T>> CompOp(const GatewayCheat:
}
}
static inline void LoadOffsetOp(Memory::MemorySystem& memory, const GatewayCheat::CheatLine& line,
State& state) {
static inline void LoadOffsetOp(Memory::MemorySystem& memory, const Kernel::Process& process,
const GatewayCheat::CheatLine& line, State& state) {
u32 addr = line.address + state.offset;
state.offset = memory.Read32(addr);
state.offset = memory.Read32(process, addr);
}
static inline void LoopOp(const GatewayCheat::CheatLine& line, State& state) {
@ -145,6 +145,7 @@ static inline void JokerOp(const GatewayCheat::CheatLine& line, State& state,
}
static inline void PatchOp(const GatewayCheat::CheatLine& line, State& state, Core::System& system,
const Kernel::Process& process,
std::span<const GatewayCheat::CheatLine> cheat_lines) {
if (state.if_flag > 0) {
// Skip over the additional patch lines
@ -166,7 +167,7 @@ static inline void PatchOp(const GatewayCheat::CheatLine& line, State& state, Co
state.current_line_nr++;
}
first = !first;
system.Memory().Write32(addr, tmp);
system.Memory().Write32(process, addr, tmp);
addr += 4;
num_bytes -= 4;
}
@ -174,7 +175,7 @@ static inline void PatchOp(const GatewayCheat::CheatLine& line, State& state, Co
u32 tmp = (first ? cheat_lines[state.current_line_nr].first
: cheat_lines[state.current_line_nr].value) >>
bit_offset;
system.Memory().Write8(addr, static_cast<u8>(tmp));
system.Memory().Write8(process, addr, static_cast<u8>(tmp));
addr += 1;
num_bytes -= 1;
bit_offset += 8;
@ -229,16 +230,27 @@ GatewayCheat::GatewayCheat(std::string name_, std::string code, std::string comm
GatewayCheat::~GatewayCheat() = default;
void GatewayCheat::Execute(Core::System& system) const {
void GatewayCheat::Execute(Core::System& system, u32 process_id) const {
State state;
Memory::MemorySystem& memory = system.Memory();
auto Read8 = [&memory](VAddr addr) { return memory.Read8(addr); };
auto Read16 = [&memory](VAddr addr) { return memory.Read16(addr); };
auto Read32 = [&memory](VAddr addr) { return memory.Read32(addr); };
auto Write8 = [&memory](VAddr addr, u8 value) { memory.Write8(addr, value); };
auto Write16 = [&memory](VAddr addr, u16 value) { memory.Write16(addr, value); };
auto Write32 = [&memory](VAddr addr, u32 value) { memory.Write32(addr, value); };
std::shared_ptr<Kernel::Process> process = system.Kernel().GetProcessById(process_id);
if (!process) {
return;
}
auto Read8 = [&memory, &process](VAddr addr) { return memory.Read8(*process, addr); };
auto Read16 = [&memory, &process](VAddr addr) { return memory.Read16(*process, addr); };
auto Read32 = [&memory, &process](VAddr addr) { return memory.Read32(*process, addr); };
auto Write8 = [&memory, &process](VAddr addr, u8 value) {
memory.Write8(*process, addr, value);
};
auto Write16 = [&memory, &process](VAddr addr, u16 value) {
memory.Write16(*process, addr, value);
};
auto Write32 = [&memory, &process](VAddr addr, u32 value) {
memory.Write32(*process, addr, value);
};
for (state.current_line_nr = 0; state.current_line_nr < cheat_lines.size();
state.current_line_nr++) {
@ -261,7 +273,7 @@ void GatewayCheat::Execute(Core::System& system) const {
// EXXXXXXX YYYYYYYY
// Copies YYYYYYYY bytes from (current code location + 8) to [XXXXXXXX + offset].
// We need to call this here to skip the additional patch lines
PatchOp(line, state, system, cheat_lines);
PatchOp(line, state, system, *process, cheat_lines);
break;
case CheatType::Terminator:
// D0000000 00000000 - ENDIF
@ -336,7 +348,7 @@ void GatewayCheat::Execute(Core::System& system) const {
break;
case CheatType::LoadOffset:
// BXXXXXXX 00000000 - offset = word[XXXXXXX+offset]
LoadOffsetOp(system.Memory(), line, state);
LoadOffsetOp(system.Memory(), *process, line, state);
break;
case CheatType::Loop: {
// C0000000 YYYYYYYY - LOOP next block YYYYYYYY times
@ -417,7 +429,7 @@ void GatewayCheat::Execute(Core::System& system) const {
case CheatType::Patch: {
// EXXXXXXX YYYYYYYY
// Copies YYYYYYYY bytes from (current code location + 8) to [XXXXXXXX + offset].
PatchOp(line, state, system, cheat_lines);
PatchOp(line, state, system, *process, cheat_lines);
break;
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -59,7 +59,7 @@ public:
GatewayCheat(std::string name, std::string code, std::string comments);
~GatewayCheat();
void Execute(Core::System& system) const override;
void Execute(Core::System& system, u32 process_id) const override;
bool IsEnabled() const override;
void SetEnabled(bool enabled) override;

View file

@ -394,7 +394,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
}
cheat_engine.LoadCheatFile(title_id);
cheat_engine.Connect();
cheat_engine.Connect(process->process_id);
perf_stats = std::make_unique<PerfStats>(title_id);
@ -814,9 +814,11 @@ void System::serialize(Archive& ar, const unsigned int file_version) {
// This needs to be set from somewhere - might as well be here!
if (Archive::is_loading::value) {
u32 cheats_pid;
ar & cheats_pid;
timing->UnlockEventQueue();
memory->SetDSP(*dsp_core);
cheat_engine.Connect();
cheat_engine.Connect(cheats_pid);
gpu->Sync();
// Re-register gpu callback, because gsp service changed after service_manager got
@ -824,6 +826,9 @@ void System::serialize(Archive& ar, const unsigned int file_version) {
auto gsp = service_manager->GetService<Service::GSP::GSP_GPU>("gsp::Gpu");
gpu->SetInterruptHandler(
[gsp](Service::GSP::InterruptId interrupt_id) { gsp->SignalInterrupt(interrupt_id); });
} else {
u32 cheats_pid = cheat_engine.GetConnectedPID();
ar & cheats_pid;
}
save_state_status = SaveStateStatus::NONE;

View file

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -448,8 +448,8 @@ void MemorySystem::UnregisterPageTable(std::shared_ptr<PageTable> page_table) {
}
template <typename T>
T MemorySystem::Read(const VAddr vaddr) {
const u8* page_pointer = impl->current_page_table->pointers[vaddr >> CITRA_PAGE_BITS];
T MemorySystem::Read(const std::shared_ptr<PageTable>& page_table, const VAddr vaddr) {
const u8* page_pointer = page_table->pointers[vaddr >> CITRA_PAGE_BITS];
if (page_pointer) {
// NOTE: Avoid adding any extra logic to this fast-path block
T value;
@ -472,7 +472,7 @@ T MemorySystem::Read(const VAddr vaddr) {
}
}
PageType type = impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS];
PageType type = page_table->attributes[vaddr >> CITRA_PAGE_BITS];
switch (type) {
case PageType::Unmapped:
LOG_ERROR(HW_Memory, "unmapped Read{} @ 0x{:08X} at PC 0x{:08X}", sizeof(T) * 8, vaddr,
@ -496,8 +496,9 @@ T MemorySystem::Read(const VAddr vaddr) {
}
template <typename T>
void MemorySystem::Write(const VAddr vaddr, const T data) {
u8* page_pointer = impl->current_page_table->pointers[vaddr >> CITRA_PAGE_BITS];
void MemorySystem::Write(const std::shared_ptr<PageTable>& page_table, const VAddr vaddr,
const T data) {
u8* page_pointer = page_table->pointers[vaddr >> CITRA_PAGE_BITS];
if (page_pointer) {
// NOTE: Avoid adding any extra logic to this fast-path block
std::memcpy(&page_pointer[vaddr & CITRA_PAGE_MASK], &data, sizeof(T));
@ -521,7 +522,7 @@ void MemorySystem::Write(const VAddr vaddr, const T data) {
}
}
PageType type = impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS];
PageType type = page_table->attributes[vaddr >> CITRA_PAGE_BITS];
switch (type) {
case PageType::Unmapped:
LOG_ERROR(HW_Memory, "unmapped Write{} 0x{:08X} @ 0x{:08X} at PC 0x{:08X}",
@ -773,19 +774,35 @@ void MemorySystem::RasterizerMarkRegionCached(PAddr start, u32 size, bool cached
}
u8 MemorySystem::Read8(const VAddr addr) {
return Read<u8>(addr);
return Read<u8>(impl->current_page_table, addr);
}
u8 MemorySystem::Read8(const Kernel::Process& process, VAddr addr) {
return Read<u8>(process.vm_manager.page_table, addr);
}
u16 MemorySystem::Read16(const VAddr addr) {
return Read<u16_le>(addr);
return Read<u16_le>(impl->current_page_table, addr);
}
u16 MemorySystem::Read16(const Kernel::Process& process, VAddr addr) {
return Read<u16_le>(process.vm_manager.page_table, addr);
}
u32 MemorySystem::Read32(const VAddr addr) {
return Read<u32_le>(addr);
return Read<u32_le>(impl->current_page_table, addr);
}
u32 MemorySystem::Read32(const Kernel::Process& process, VAddr addr) {
return Read<u32_le>(process.vm_manager.page_table, addr);
}
u64 MemorySystem::Read64(const VAddr addr) {
return Read<u64_le>(addr);
return Read<u64_le>(impl->current_page_table, addr);
}
u64 MemorySystem::Read64(const Kernel::Process& process, VAddr addr) {
return Read<u64_le>(process.vm_manager.page_table, addr);
}
void MemorySystem::ReadBlock(const Kernel::Process& process, const VAddr src_addr,
@ -799,19 +816,35 @@ void MemorySystem::ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size
}
void MemorySystem::Write8(const VAddr addr, const u8 data) {
Write<u8>(addr, data);
Write<u8>(impl->current_page_table, addr, data);
}
void MemorySystem::Write8(const Kernel::Process& process, const VAddr addr, const u8 data) {
Write<u8>(process.vm_manager.page_table, addr, data);
}
void MemorySystem::Write16(const VAddr addr, const u16 data) {
Write<u16_le>(addr, data);
Write<u16_le>(impl->current_page_table, addr, data);
}
void MemorySystem::Write16(const Kernel::Process& process, const VAddr addr, const u16 data) {
Write<u16_le>(process.vm_manager.page_table, addr, data);
}
void MemorySystem::Write32(const VAddr addr, const u32 data) {
Write<u32_le>(addr, data);
Write<u32_le>(impl->current_page_table, addr, data);
}
void MemorySystem::Write32(const Kernel::Process& process, const VAddr addr, const u32 data) {
Write<u32_le>(process.vm_manager.page_table, addr, data);
}
void MemorySystem::Write64(const VAddr addr, const u64 data) {
Write<u64_le>(addr, data);
Write<u64_le>(impl->current_page_table, addr, data);
}
void MemorySystem::Write64(const Kernel::Process& process, const VAddr addr, const u64 data) {
Write<u64_le>(process.vm_manager.page_table, addr, data);
}
bool MemorySystem::WriteExclusive8(const VAddr addr, const u8 data, const u8 expected) {

View file

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -286,6 +286,17 @@ public:
*/
u8 Read8(VAddr addr);
/**
* Reads an 8-bit unsigned value from the process' address space
* at the given virtual address.
*
* @param process The process to read from.
* @param addr The virtual address to read the 8-bit value from.
*
* @returns the read 8-bit unsigned value.
*/
u8 Read8(const Kernel::Process& process, VAddr addr);
/**
* Reads a 16-bit unsigned value from the current process' address space
* at the given virtual address.
@ -296,6 +307,17 @@ public:
*/
u16 Read16(VAddr addr);
/**
* Reads a 16-bit unsigned value from the process' address space
* at the given virtual address.
*
* @param process The process to read from.
* @param addr The virtual address to read the 16-bit value from.
*
* @returns the read 16-bit unsigned value.
*/
u16 Read16(const Kernel::Process& process, VAddr addr);
/**
* Reads a 32-bit unsigned value from the current process' address space
* at the given virtual address.
@ -306,6 +328,17 @@ public:
*/
u32 Read32(VAddr addr);
/**
* Reads a 32-bit unsigned value from the process' address space
* at the given virtual address.
*
* @param process The process to read from.
* @param addr The virtual address to read the 32-bit value from.
*
* @returns the read 32-bit unsigned value.
*/
u32 Read32(const Kernel::Process& process, VAddr addr);
/**
* Reads a 64-bit unsigned value from the current process' address space
* at the given virtual address.
@ -316,6 +349,17 @@ public:
*/
u64 Read64(VAddr addr);
/**
* Reads a 64-bit unsigned value from the process' address space
* at the given virtual address.
*
* @param process The process to read from.
* @param addr The virtual address to read the 64-bit value from.
*
* @returns the read 64-bit value.
*/
u64 Read64(const Kernel::Process& process, VAddr addr);
/**
* Writes an 8-bit unsigned integer to the given virtual address in
* the current process' address space.
@ -327,6 +371,18 @@ public:
*/
void Write8(VAddr addr, u8 data);
/**
* Writes an 8-bit unsigned integer to the given virtual address in
* the process' address space.
*
* @param process The process to write to.
* @param addr The virtual address to write the 8-bit unsigned integer to.
* @param data The 8-bit unsigned integer to write to the given virtual address.
*
* @post The memory at the given virtual address contains the specified data value.
*/
void Write8(const Kernel::Process& process, VAddr addr, u8 data);
/**
* Writes a 16-bit unsigned integer to the given virtual address in
* the current process' address space.
@ -338,6 +394,18 @@ public:
*/
void Write16(VAddr addr, u16 data);
/**
* Writes a 16-bit unsigned integer to the given virtual address in
* the process' address space.
*
* @param process The process to write to.
* @param addr The virtual address to write the 16-bit unsigned integer to.
* @param data The 16-bit unsigned integer to write to the given virtual address.
*
* @post The memory range [addr, sizeof(data)) contains the given data value.
*/
void Write16(const Kernel::Process& process, VAddr addr, u16 data);
/**
* Writes a 32-bit unsigned integer to the given virtual address in
* the current process' address space.
@ -349,6 +417,18 @@ public:
*/
void Write32(VAddr addr, u32 data);
/**
* Writes a 32-bit unsigned integer to the given virtual address in
* the process' address space.
*
* @param process The process to write to.
* @param addr The virtual address to write the 32-bit unsigned integer to.
* @param data The 32-bit unsigned integer to write to the given virtual address.
*
* @post The memory range [addr, sizeof(data)) contains the given data value.
*/
void Write32(const Kernel::Process& process, VAddr addr, u32 data);
/**
* Writes a 64-bit unsigned integer to the given virtual address in
* the current process' address space.
@ -360,6 +440,18 @@ public:
*/
void Write64(VAddr addr, u64 data);
/**
* Writes a 64-bit unsigned integer to the given virtual address in
* the process' address space.
*
* @param process The process to write to.
* @param addr The virtual address to write the 64-bit unsigned integer to.
* @param data The 64-bit unsigned integer to write to the given virtual address.
*
* @post The memory range [addr, sizeof(data)) contains the given data value.
*/
void Write64(const Kernel::Process& process, VAddr addr, u64 data);
/**
* Writes a {8, 16, 32, 64}-bit unsigned integer to the given virtual address in
* the current process' address space if and only if the address contains
@ -555,10 +647,10 @@ public:
private:
template <typename T>
T Read(const VAddr vaddr);
T Read(const std::shared_ptr<PageTable>& page_table, const VAddr vaddr);
template <typename T>
void Write(const VAddr vaddr, const T data);
void Write(const std::shared_ptr<PageTable>& page_table, const VAddr vaddr, const T data);
template <typename T>
bool WriteExclusive(const VAddr vaddr, const T data, const T expected);

View file

@ -1,4 +1,4 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -15,6 +15,8 @@ enum class PacketType : u32 {
Undefined = 0,
ReadMemory = 1,
WriteMemory = 2,
ProcessList = 3,
SetGetProcess = 4,
};
struct PacketHeader {
@ -24,11 +26,21 @@ struct PacketHeader {
u32 packet_size;
};
#pragma pack(push, 1)
struct ProcessInfo {
u32 process_id;
u64 title_id;
std::array<u8, 8> process_name;
};
static_assert(sizeof(ProcessInfo) == 0x14, "Incorrect ProcessInfo size");
#pragma pack(pop)
constexpr u32 CURRENT_VERSION = 1;
constexpr u32 MIN_PACKET_SIZE = sizeof(PacketHeader);
constexpr u32 MAX_PACKET_DATA_SIZE = 32;
constexpr u32 MAX_PACKET_DATA_SIZE = 1024;
constexpr u32 MAX_PACKET_SIZE = MIN_PACKET_SIZE + MAX_PACKET_DATA_SIZE;
constexpr u32 MAX_READ_SIZE = MAX_PACKET_DATA_SIZE;
constexpr u32 MAX_PROCESSES_IN_LIST = (MAX_PACKET_DATA_SIZE - sizeof(u32)) / sizeof(ProcessInfo);
class Packet {
public:
@ -69,9 +81,6 @@ public:
}
private:
void HandleReadMemory(u32 address, u32 data_size);
void HandleWriteMemory(u32 address, std::span<const u8> data);
struct PacketHeader header;
std::array<u8, MAX_PACKET_DATA_SIZE> packet_data;

View file

@ -1,9 +1,10 @@
// Copyright 2019 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/kernel/process.h"
#include "core/memory.h"
#include "core/rpc/packet.h"
#include "core/rpc/rpc_server.h"
@ -22,10 +23,23 @@ void RPCServer::HandleReadMemory(Packet& packet, u32 address, u32 data_size) {
if (data_size > MAX_READ_SIZE) {
return;
}
u32 read_size = data_size;
// Note: Memory read occurs asynchronously from the state of the emulator
system.Memory().ReadBlock(address, packet.GetPacketData().data(), data_size);
packet.SetPacketDataSize(data_size);
if (selected_pid == 0xFFFFFFFF) {
LOG_ERROR(RPC_Server, "No target process selected, memory access may be invalid.");
system.Memory().ReadBlock(address, packet.GetPacketData().data(), data_size);
} else {
auto process = system.Kernel().GetProcessById(selected_pid);
if (process) {
system.Memory().ReadBlock(*process, address, packet.GetPacketData().data(), data_size);
} else {
LOG_ERROR(RPC_Server, "Selected process does not exist.");
read_size = 0;
}
}
packet.SetPacketDataSize(read_size);
packet.SendReply();
}
@ -33,9 +47,21 @@ void RPCServer::HandleWriteMemory(Packet& packet, u32 address, std::span<const u
// Only allow writing to certain memory regions
if ((address >= Memory::PROCESS_IMAGE_VADDR && address <= Memory::PROCESS_IMAGE_VADDR_END) ||
(address >= Memory::HEAP_VADDR && address <= Memory::HEAP_VADDR_END) ||
(address >= Memory::LINEAR_HEAP_VADDR && address <= Memory::LINEAR_HEAP_VADDR_END) ||
(address >= Memory::N3DS_EXTRA_RAM_VADDR && address <= Memory::N3DS_EXTRA_RAM_VADDR_END)) {
// Note: Memory write occurs asynchronously from the state of the emulator
system.Memory().WriteBlock(address, data.data(), data.size());
if (selected_pid == 0xFFFFFFFF) {
LOG_ERROR(RPC_Server, "No target process selected, memory access may be invalid.");
system.Memory().WriteBlock(address, data.data(), data.size());
} else {
auto process = system.Kernel().GetProcessById(selected_pid);
if (process) {
system.Memory().WriteBlock(*process, address, data.data(), data.size());
} else {
LOG_ERROR(RPC_Server, "Selected process does not exist.");
}
}
// If the memory happens to be executable code, make sure the changes become visible
// Is current core correct here?
@ -45,11 +71,57 @@ void RPCServer::HandleWriteMemory(Packet& packet, u32 address, std::span<const u
packet.SendReply();
}
void RPCServer::HandleProcessList(Packet& packet, u32 start_index, u32 max_amount) {
const auto process_list = system.Kernel().GetProcessList();
const u32 start = std::min(start_index, static_cast<u32>(process_list.size()));
const u32 end = std::min(start + max_amount, static_cast<u32>(process_list.size()));
const u32 count = std::min(end - start, MAX_PROCESSES_IN_LIST);
u8* out_data = packet.GetPacketData().data();
u32 written_bytes = 0;
memcpy(out_data + written_bytes, &count, sizeof(count));
written_bytes += sizeof(count);
for (u32 i = start; i < start + count; i++) {
ProcessInfo info{};
info.process_id = process_list[i]->process_id;
info.title_id = process_list[i]->codeset->program_id;
memcpy(info.process_name.data(), process_list[i]->codeset->name.data(),
std::min(process_list[i]->codeset->name.size(), info.process_name.size()));
memcpy(out_data + written_bytes, &info, sizeof(ProcessInfo));
written_bytes += sizeof(ProcessInfo);
}
packet.SetPacketDataSize(written_bytes);
packet.SendReply();
}
void RPCServer::HandleSetGetProcess(Packet& packet, u32 operation, u32 process_id) {
u8* out_data = packet.GetPacketData().data();
u32 written_bytes = 0;
if (operation == 0) {
// Get
memcpy(out_data + written_bytes, &selected_pid, sizeof(selected_pid));
written_bytes += sizeof(selected_pid);
} else {
// Set
selected_pid = process_id;
}
packet.SetPacketDataSize(written_bytes);
packet.SendReply();
}
bool RPCServer::ValidatePacket(const PacketHeader& packet_header) {
if (packet_header.version <= CURRENT_VERSION) {
switch (packet_header.packet_type) {
case PacketType::ReadMemory:
case PacketType::WriteMemory:
case PacketType::ProcessList:
case PacketType::SetGetProcess:
if (packet_header.packet_size >= (sizeof(u32) * 2)) {
return true;
}
@ -66,26 +138,34 @@ void RPCServer::HandleSingleRequest(std::unique_ptr<Packet> request_packet) {
const auto packet_data = request_packet->GetPacketData();
if (ValidatePacket(request_packet->GetHeader())) {
// Currently, all request types use the address/data_size wire format
u32 address = 0;
u32 data_size = 0;
std::memcpy(&address, packet_data.data(), sizeof(address));
std::memcpy(&data_size, packet_data.data() + sizeof(address), sizeof(data_size));
// Currently, all request types use to arguments
u32 arg1 = 0;
u32 arg2 = 0;
std::memcpy(&arg1, packet_data.data(), sizeof(arg1));
std::memcpy(&arg2, packet_data.data() + sizeof(arg1), sizeof(arg2));
switch (request_packet->GetPacketType()) {
case PacketType::ReadMemory:
if (data_size > 0 && data_size <= MAX_READ_SIZE) {
HandleReadMemory(*request_packet, address, data_size);
if (arg2 > 0 && arg2 <= MAX_READ_SIZE) {
HandleReadMemory(*request_packet, arg1, arg2);
success = true;
}
break;
case PacketType::WriteMemory:
if (data_size > 0 && data_size <= MAX_PACKET_DATA_SIZE - (sizeof(u32) * 2)) {
const auto data = packet_data.subspan(sizeof(u32) * 2, data_size);
HandleWriteMemory(*request_packet, address, data);
if (arg2 > 0 && arg2 <= MAX_PACKET_DATA_SIZE - (sizeof(u32) * 2)) {
const auto data = packet_data.subspan(sizeof(u32) * 2, arg2);
HandleWriteMemory(*request_packet, arg1, data);
success = true;
}
break;
case PacketType::ProcessList:
HandleProcessList(*request_packet, arg1, arg2);
success = true;
break;
case PacketType::SetGetProcess:
HandleSetGetProcess(*request_packet, arg1, arg2);
success = true;
break;
default:
break;
}

View file

@ -1,4 +1,4 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -29,6 +29,8 @@ public:
private:
void HandleReadMemory(Packet& packet, u32 address, u32 data_size);
void HandleWriteMemory(Packet& packet, u32 address, std::span<const u8> data);
void HandleProcessList(Packet& packet, u32 start_index, u32 max_amount);
void HandleSetGetProcess(Packet& packet, u32 operation, u32 process_id);
bool ValidatePacket(const PacketHeader& packet_header);
void HandleSingleRequest(std::unique_ptr<Packet> request);
void HandleRequestsLoop(std::stop_token stop_token);
@ -37,6 +39,7 @@ private:
Core::System& system;
Common::SPSCQueue<std::unique_ptr<Packet>, true> request_queue;
std::jthread request_handler_thread;
u32 selected_pid = 0xFFFFFFFF;
};
} // namespace Core::RPC