Patch: Add "bytes" type

Allows patching an arbitrary range of bytes.
This commit is contained in:
Stenzek 2023-06-03 16:42:43 +10:00 committed by refractionpcsx2
parent 940e211bb6
commit 9d3de8631c
5 changed files with 210 additions and 15 deletions

View File

@ -493,6 +493,69 @@ void iopMemWrite32(u32 mem, u32 value)
}
}
int iopMemSafeCmpBytes(u32 mem, const void* src, u32 size)
{
// can memcpy so long as pages aren't crossed
const u8* sptr = static_cast<const u8*>(src);
const u8* const sptr_end = sptr + size;
while (sptr != sptr_end)
{
u8* dst = iopVirtMemW<u8>(mem);
if (!dst)
return -1;
const u32 remaining_in_page = std::min(0x1000 - (mem & 0xfff), static_cast<u32>(sptr_end - sptr));
const int res = std::memcmp(sptr, dst, remaining_in_page);
if (res != 0)
return res;
sptr += remaining_in_page;
mem += remaining_in_page;
}
return 0;
}
bool iopMemSafeReadBytes(u32 mem, void* dst, u32 size)
{
// can memcpy so long as pages aren't crossed
u8* dptr = static_cast<u8*>(dst);
u8* const dptr_end = dptr + size;
while (dptr != dptr_end)
{
const u8* src = iopVirtMemR<u8>(mem);
if (!src)
return false;
const u32 remaining_in_page = std::min(0x1000 - (mem & 0xfff), static_cast<u32>(dptr_end - dptr));
std::memcpy(dptr, src, remaining_in_page);
dptr += remaining_in_page;
mem += remaining_in_page;
}
return true;
}
bool iopMemSafeWriteBytes(u32 mem, const void* src, u32 size)
{
// can memcpy so long as pages aren't crossed
const u8* sptr = static_cast<const u8*>(src);
const u8* const sptr_end = sptr + size;
while (sptr != sptr_end)
{
u8* dst = iopVirtMemW<u8>(mem);
if (!dst)
return false;
const u32 remaining_in_page = std::min(0x1000 - (mem & 0xfff), static_cast<u32>(sptr_end - sptr));
std::memcpy(dst, sptr, remaining_in_page);
sptr += remaining_in_page;
mem += remaining_in_page;
}
return true;
}
std::string iopMemReadString(u32 mem, int maxlen)
{
std::string ret;

View File

@ -83,6 +83,11 @@ extern void iopMemWrite8 (u32 mem, u8 value);
extern void iopMemWrite16(u32 mem, u16 value);
extern void iopMemWrite32(u32 mem, u32 value);
// NOTE: Does not call MMIO handlers.
extern int iopMemSafeCmpBytes(u32 mem, const void* src, u32 size);
extern bool iopMemSafeReadBytes(u32 mem, void* dst, u32 size);
extern bool iopMemSafeWriteBytes(u32 mem, const void* src, u32 size);
std::string iopMemReadString(u32 mem, int maxlen = 65536);
namespace IopMemory

View File

@ -58,13 +58,14 @@ namespace Patch
EXTENDED_T,
SHORT_BE_T,
WORD_BE_T,
DOUBLE_BE_T
DOUBLE_BE_T,
BYTES_T
};
static constexpr std::array<const char*, 3> s_place_to_string = {{"0", "1", "2"}};
static constexpr std::array<const char*, 2> s_cpu_to_string = {{"EE", "IOP"}};
static constexpr std::array<const char*, 8> s_type_to_string = {
{"byte", "short", "word", "double", "extended", "beshort", "beword", "bedouble"}};
static constexpr std::array<const char*, 9> s_type_to_string = {
{"byte", "short", "word", "double", "extended", "beshort", "beword", "bedouble", "bytes"}};
template <typename EnumType, class ArrayType>
static inline std::optional<EnumType> LookupEnumName(const std::string_view& val, const ArrayType& arr)
@ -84,8 +85,29 @@ namespace Patch
patch_data_type type;
u32 addr;
u64 data;
u8* data_ptr;
// needed because of the pointer
PatchCommand() { std::memset(this, 0, sizeof(*this)); }
PatchCommand(const PatchCommand& p) = delete;
PatchCommand(PatchCommand&& p)
{
std::memcpy(this, &p, sizeof(*this));
p.data_ptr = nullptr;
}
~PatchCommand()
{
if (data_ptr)
std::free(data_ptr);
}
PatchCommand& operator=(const PatchCommand& p) = delete;
PatchCommand& operator=(PatchCommand&& p)
{
std::memcpy(this, &p, sizeof(*this));
p.data_ptr = nullptr;
return *this;
}
bool operator==(const PatchCommand& p) const { return std::memcmp(this, &p, sizeof(*this)) == 0; }
bool operator!=(const PatchCommand& p) const { return std::memcmp(this, &p, sizeof(*this)) != 0; }
@ -96,7 +118,7 @@ namespace Patch
s_cpu_to_string[static_cast<u8>(cpu)], s_type_to_string[static_cast<u8>(type)], addr, data);
}
};
static_assert(sizeof(PatchCommand) == 16, "IniPatch has no padding");
static_assert(sizeof(PatchCommand) == 24, "IniPatch has no padding");
struct PatchGroup
{
@ -656,9 +678,9 @@ void Patch::UnloadPatches()
s_active_dynamic_patches = {};
s_enabled_patches = {};
s_enabled_cheats = {};
s_cheat_patches = {};
s_game_patches = {};
s_gamedb_patches = {};
decltype(s_cheat_patches)().swap(s_cheat_patches);
decltype(s_game_patches)().swap(s_game_patches);
decltype(s_gamedb_patches)().swap(s_gamedb_patches);
}
// PatchFunc Functions.
@ -680,7 +702,8 @@ void Patch::PatchFunc::patch(PatchGroup* group, const std::string_view& cmd, con
const std::optional<patch_cpu_type> cpu = LookupEnumName<patch_cpu_type>(pieces[1], s_cpu_to_string);
const std::optional<u32> addr = StringUtil::FromChars<u32>(pieces[2], 16, &addr_end);
const std::optional<patch_data_type> type = LookupEnumName<patch_data_type>(pieces[3], s_type_to_string);
const std::optional<u64> data = StringUtil::FromChars<u64>(pieces[4], 16, &data_end);
std::optional<u64> data = StringUtil::FromChars<u64>(pieces[4], 16, &data_end);
u8* data_ptr = nullptr;
if (!placetopatch.has_value())
{
@ -692,11 +715,6 @@ void Patch::PatchFunc::patch(PatchGroup* group, const std::string_view& cmd, con
PATCH_ERROR("Malformed address '{}', a hex number without prefix (e.g. 0123ABCD) is expected", pieces[2]);
return;
}
else if (!data.has_value() || !data_end.empty())
{
PATCH_ERROR("Malformed data '{}', a hex number without prefix (e.g. 0123ABCD) is expected", pieces[4]);
return;
}
if (!cpu.has_value())
{
PATCH_ERROR("Unrecognized CPU Target: '%.*s'", pieces[1]);
@ -707,6 +725,28 @@ void Patch::PatchFunc::patch(PatchGroup* group, const std::string_view& cmd, con
PATCH_ERROR("Unrecognized Operand Size: '%.*s'", pieces[3]);
return;
}
if (type.value() != BYTES_T)
{
if (!data.has_value() || !data_end.empty())
{
PATCH_ERROR("Malformed data '{}', a hex number without prefix (e.g. 0123ABCD) is expected", pieces[4]);
return;
}
}
else
{
// bit crappy to copy it, but eh, saves writing a new routine
std::optional<std::vector<u8>> bytes = StringUtil::DecodeHex(pieces[4]);
if (!bytes.has_value() || bytes->empty())
{
PATCH_ERROR("Malformed data '{}', a hex string without prefix (e.g. 0123ABCD) is expected", pieces[4]);
return;
}
data = bytes->size();
data_ptr = static_cast<u8*>(std::malloc(bytes->size()));
std::memcpy(data_ptr, bytes->data(), bytes->size());
}
PatchCommand iPatch;
iPatch.placetopatch = placetopatch.value();
@ -714,7 +754,8 @@ void Patch::PatchFunc::patch(PatchGroup* group, const std::string_view& cmd, con
iPatch.addr = addr.value();
iPatch.type = type.value();
iPatch.data = data.value();
group->patches.push_back(iPatch);
iPatch.data_ptr = data_ptr;
group->patches.push_back(std::move(iPatch));
#undef PATCH_ERROR
}
@ -769,7 +810,7 @@ void Patch::LoadDynamicPatches(const std::vector<DynamicPatch>& patches)
}
static u32 SkipCount = 0, IterationCount = 0;
static u32 IterationIncrement = 0, ValueIncrement = 0;
static u32 IterationIncrement = 0;
static u32 PrevCheatType = 0, PrevCheatAddr = 0, LastType = 0;
void Patch::writeCheat()
@ -1307,6 +1348,14 @@ void Patch::ApplyPatch(const PatchCommand* p)
memWrite64(p->addr, (u64)ledata);
break;
case BYTES_T:
{
// We compare before writing so the rec doesn't get upset and invalidate when there's no change.
if (vtlb_memSafeCmpBytes(p->addr, p->data_ptr, static_cast<u32>(p->data)) != 0)
vtlb_memSafeWriteBytes(p->addr, p->data_ptr, static_cast<u32>(p->data));
}
break;
default:
break;
}
@ -1327,6 +1376,13 @@ void Patch::ApplyPatch(const PatchCommand* p)
if (iopMemRead32(p->addr) != (u32)p->data)
iopMemWrite32(p->addr, (u32)p->data);
break;
case BYTES_T:
{
if (iopMemSafeCmpBytes(p->addr, p->data_ptr, static_cast<u32>(p->data)) != 0)
iopMemSafeWriteBytes(p->addr, p->data_ptr, static_cast<u32>(p->data));
}
break;
default:
break;
}

View File

@ -362,6 +362,72 @@ template bool vtlb_ramWrite<mem32_t>(u32 mem, const mem32_t& data);
template bool vtlb_ramWrite<mem64_t>(u32 mem, const mem64_t& data);
template bool vtlb_ramWrite<mem128_t>(u32 mem, const mem128_t& data);
int vtlb_memSafeCmpBytes(u32 mem, const void* src, u32 size)
{
// can memcpy so long as pages aren't crossed
const u8* sptr = static_cast<const u8*>(src);
const u8* const sptr_end = sptr + size;
while (sptr != sptr_end)
{
auto vmv = vtlbdata.vmap[mem >> VTLB_PAGE_BITS];
if (vmv.isHandler(mem))
return -1;
const size_t remaining_in_page =
std::min(VTLB_PAGE_SIZE - (mem & VTLB_PAGE_MASK), static_cast<u32>(sptr_end - sptr));
const int res = std::memcmp(sptr, reinterpret_cast<void*>(vmv.assumePtr(mem)), remaining_in_page);
if (res != 0)
return res;
sptr += remaining_in_page;
mem += remaining_in_page;
}
return 0;
}
bool vtlb_memSafeReadBytes(u32 mem, void* dst, u32 size)
{
// can memcpy so long as pages aren't crossed
u8* dptr = static_cast<u8*>(dst);
u8* const dptr_end = dptr + size;
while (dptr != dptr_end)
{
auto vmv = vtlbdata.vmap[mem >> VTLB_PAGE_BITS];
if (vmv.isHandler(mem))
return false;
const u32 remaining_in_page =
std::min(VTLB_PAGE_SIZE - (mem & VTLB_PAGE_MASK), static_cast<u32>(dptr_end - dptr));
std::memcpy(dptr, reinterpret_cast<void*>(vmv.assumePtr(mem)), remaining_in_page);
dptr += remaining_in_page;
mem += remaining_in_page;
}
return true;
}
bool vtlb_memSafeWriteBytes(u32 mem, const void* src, u32 size)
{
// can memcpy so long as pages aren't crossed
const u8* sptr = static_cast<const u8*>(src);
const u8* const sptr_end = sptr + size;
while (sptr != sptr_end)
{
auto vmv = vtlbdata.vmap[mem >> VTLB_PAGE_BITS];
if (vmv.isHandler(mem))
return false;
const size_t remaining_in_page =
std::min(VTLB_PAGE_SIZE - (mem & VTLB_PAGE_MASK), static_cast<u32>(sptr_end - sptr));
std::memcpy(reinterpret_cast<void*>(vmv.assumePtr(mem)), sptr, remaining_in_page);
sptr += remaining_in_page;
mem += remaining_in_page;
}
return true;
}
// --------------------------------------------------------------------------------------
// TLB Miss / BusError Handlers
// --------------------------------------------------------------------------------------

View File

@ -110,6 +110,11 @@ extern DataType vtlb_ramRead(u32 mem);
template <typename DataType>
extern bool vtlb_ramWrite(u32 mem, const DataType& value);
// NOTE: Does not call MMIO handlers.
extern int vtlb_memSafeCmpBytes(u32 mem, const void* src, u32 size);
extern bool vtlb_memSafeReadBytes(u32 mem, void* dst, u32 size);
extern bool vtlb_memSafeWriteBytes(u32 mem, const void* src, u32 size);
using vtlb_ReadRegAllocCallback = int(*)();
extern int vtlb_DynGenReadNonQuad(u32 bits, bool sign, bool xmm, int addr_reg, vtlb_ReadRegAllocCallback dest_reg_alloc = nullptr);
extern int vtlb_DynGenReadNonQuad_Const(u32 bits, bool sign, bool xmm, u32 addr_const, vtlb_ReadRegAllocCallback dest_reg_alloc = nullptr);