diff --git a/pcsx2/IopMem.cpp b/pcsx2/IopMem.cpp index 1332a9eb7f..ff6c173c03 100644 --- a/pcsx2/IopMem.cpp +++ b/pcsx2/IopMem.cpp @@ -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(src); + const u8* const sptr_end = sptr + size; + while (sptr != sptr_end) + { + u8* dst = iopVirtMemW(mem); + if (!dst) + return -1; + + const u32 remaining_in_page = std::min(0x1000 - (mem & 0xfff), static_cast(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(dst); + u8* const dptr_end = dptr + size; + while (dptr != dptr_end) + { + const u8* src = iopVirtMemR(mem); + if (!src) + return false; + + const u32 remaining_in_page = std::min(0x1000 - (mem & 0xfff), static_cast(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(src); + const u8* const sptr_end = sptr + size; + while (sptr != sptr_end) + { + u8* dst = iopVirtMemW(mem); + if (!dst) + return false; + + const u32 remaining_in_page = std::min(0x1000 - (mem & 0xfff), static_cast(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; diff --git a/pcsx2/IopMem.h b/pcsx2/IopMem.h index a43aeee70d..372b2482b4 100644 --- a/pcsx2/IopMem.h +++ b/pcsx2/IopMem.h @@ -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 diff --git a/pcsx2/Patch.cpp b/pcsx2/Patch.cpp index 9910841df4..4896399665 100644 --- a/pcsx2/Patch.cpp +++ b/pcsx2/Patch.cpp @@ -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 s_place_to_string = {{"0", "1", "2"}}; static constexpr std::array s_cpu_to_string = {{"EE", "IOP"}}; - static constexpr std::array s_type_to_string = { - {"byte", "short", "word", "double", "extended", "beshort", "beword", "bedouble"}}; + static constexpr std::array s_type_to_string = { + {"byte", "short", "word", "double", "extended", "beshort", "beword", "bedouble", "bytes"}}; template static inline std::optional 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(cpu)], s_type_to_string[static_cast(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 cpu = LookupEnumName(pieces[1], s_cpu_to_string); const std::optional addr = StringUtil::FromChars(pieces[2], 16, &addr_end); const std::optional type = LookupEnumName(pieces[3], s_type_to_string); - const std::optional data = StringUtil::FromChars(pieces[4], 16, &data_end); + std::optional data = StringUtil::FromChars(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> 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(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& 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(p->data)) != 0) + vtlb_memSafeWriteBytes(p->addr, p->data_ptr, static_cast(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(p->data)) != 0) + iopMemSafeWriteBytes(p->addr, p->data_ptr, static_cast(p->data)); + } + break; + default: break; } diff --git a/pcsx2/vtlb.cpp b/pcsx2/vtlb.cpp index 46c6a09080..32fe35cd00 100644 --- a/pcsx2/vtlb.cpp +++ b/pcsx2/vtlb.cpp @@ -362,6 +362,72 @@ template bool vtlb_ramWrite(u32 mem, const mem32_t& data); template bool vtlb_ramWrite(u32 mem, const mem64_t& data); template bool vtlb_ramWrite(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(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(sptr_end - sptr)); + const int res = std::memcmp(sptr, reinterpret_cast(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(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(dptr_end - dptr)); + std::memcpy(dptr, reinterpret_cast(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(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(sptr_end - sptr)); + std::memcpy(reinterpret_cast(vmv.assumePtr(mem)), sptr, remaining_in_page); + sptr += remaining_in_page; + mem += remaining_in_page; + } + + return true; +} + // -------------------------------------------------------------------------------------- // TLB Miss / BusError Handlers // -------------------------------------------------------------------------------------- diff --git a/pcsx2/vtlb.h b/pcsx2/vtlb.h index ba36ca31d7..0f524f5006 100644 --- a/pcsx2/vtlb.h +++ b/pcsx2/vtlb.h @@ -110,6 +110,11 @@ extern DataType vtlb_ramRead(u32 mem); template 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);