From 9023b80d5cfb12881c76a1ef4277c8139dd7356d Mon Sep 17 00:00:00 2001 From: Tzeentchnet <58480142+Tzeentchnet@users.noreply.github.com> Date: Tue, 10 Nov 2026 13:57:54 -0800 Subject: [PATCH] fix: wrap VIF MPG uploads within micro memory --- ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp | 52 ++++++------ ps2xTest/src/ps2_memory_tests.cpp | 88 ++++++++++++++++++++ 2 files changed, 114 insertions(+), 26 deletions(-) diff --git a/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp b/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp index f872edd156..a3280a83d5 100644 --- a/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp +++ b/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp @@ -1,5 +1,6 @@ // Based on Blackline Interactive implementation #include "runtime/ps2_memory.h" +#include #include enum VIFCmd : uint8_t @@ -79,6 +80,15 @@ namespace return 0u; } + + void uploadMicroMemory(uint8_t *micro, uint32_t microSize, uint32_t address, + const uint8_t *data, uint32_t size) + { + const uint32_t firstSize = std::min(size, microSize - address); + std::memcpy(micro + address, data, firstSize); + if (firstSize < size) + std::memcpy(micro, data + firstSize, size - firstSize); + } } void PS2Memory::processVIF0Data(uint32_t srcPhys, uint32_t sizeBytes) @@ -170,25 +180,19 @@ void PS2Memory::processVIF0Data(const uint8_t *data, uint32_t sizeBytes) } else if (opcode == VIF_MPG) { - const uint32_t destAddr = static_cast(imm & 0x1FFu) * 8u; + const uint32_t destAddr = + (static_cast(imm) * 8u) & (PS2_VU0_CODE_SIZE - 1u); const uint32_t instructionCount = (num == 0u) ? 256u : static_cast(num); const uint32_t mpgBytes = instructionCount * 8u; - uint32_t copyBytes = 0u; - if (m_vu0Code && destAddr < PS2_VU0_CODE_SIZE && mpgBytes > 0u) + if (sizeBytes - pos < mpgBytes) + break; + + if (m_vu0Code) { - copyBytes = mpgBytes; - if (destAddr + copyBytes > PS2_VU0_CODE_SIZE) - copyBytes = PS2_VU0_CODE_SIZE - destAddr; - if (pos + copyBytes <= sizeBytes) - { - std::memcpy(m_vu0Code + destAddr, data + pos, copyBytes); - markVU0CodeModified(); - } + uploadMicroMemory(m_vu0Code, PS2_VU0_CODE_SIZE, destAddr, data + pos, mpgBytes); + markVU0CodeModified(); } - pos += mpgBytes; - if (pos > sizeBytes) - break; continue; } else if ((opcode & 0x60u) == 0x60u) @@ -466,23 +470,19 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes) } else if (opcode == VIF_MPG) { - uint32_t destAddr = (uint32_t)imm * 8u; + const uint32_t destAddr = + (static_cast(imm) * 8u) & (PS2_VU1_CODE_SIZE - 1u); const uint32_t instructionCount = (num == 0u) ? 256u : static_cast(num); const uint32_t mpgBytes = instructionCount * 8u; - if (m_vu1Code && destAddr < PS2_VU1_CODE_SIZE && mpgBytes > 0) + if (sizeBytes - pos < mpgBytes) + break; + + if (m_vu1Code) { - uint32_t copyBytes = mpgBytes; - if (destAddr + copyBytes > PS2_VU1_CODE_SIZE) - copyBytes = PS2_VU1_CODE_SIZE - destAddr; - if (pos + copyBytes <= sizeBytes) - { - std::memcpy(m_vu1Code + destAddr, data + pos, copyBytes); - markVU1CodeModified(); - } + uploadMicroMemory(m_vu1Code, PS2_VU1_CODE_SIZE, destAddr, data + pos, mpgBytes); + markVU1CodeModified(); } pos += mpgBytes; - if (pos > sizeBytes) - break; continue; } else if (opcode == VIF_DIRECT || opcode == VIF_DIRECTHL) diff --git a/ps2xTest/src/ps2_memory_tests.cpp b/ps2xTest/src/ps2_memory_tests.cpp index 0b22fe03c6..9fd22e8b2f 100644 --- a/ps2xTest/src/ps2_memory_tests.cpp +++ b/ps2xTest/src/ps2_memory_tests.cpp @@ -410,6 +410,94 @@ void register_ps2_memory_tests() t.IsTrue(matches, "MPG num=0 should copy 2048 bytes into VU1 code memory"); }); + tc.Run("VIF0 MPG masks and wraps micro memory addresses", [](TestCase &t) + { + PS2Memory mem; + t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed"); + + std::memset(mem.getVU0Code(), 0xCD, PS2_VU0_CODE_SIZE); + std::vector packet; + appendU32(packet, makeVifCmd(0x4Au, 2u, 0x01FFu)); + for (uint32_t i = 0; i < 16u; ++i) + packet.push_back(static_cast(0x10u + i)); + + mem.processVIF0Data(packet.data(), static_cast(packet.size())); + + const uint8_t *code = mem.getVU0Code(); + bool tailMatches = true; + bool wrappedStartMatches = true; + for (uint32_t i = 0; i < 8u; ++i) + { + tailMatches &= code[PS2_VU0_CODE_SIZE - 8u + i] == static_cast(0x10u + i); + wrappedStartMatches &= code[i] == static_cast(0x18u + i); + } + t.IsTrue(tailMatches, "MPG should write through the end of VU0 micro memory"); + t.IsTrue(wrappedStartMatches, "MPG payload should wrap to the start of VU0 micro memory"); + + packet.clear(); + appendU32(packet, makeVifCmd(0x4Au, 1u, 0x0200u)); + for (uint32_t i = 0; i < 8u; ++i) + packet.push_back(static_cast(0x80u + i)); + mem.processVIF0Data(packet.data(), static_cast(packet.size())); + + bool maskedStartMatches = true; + for (uint32_t i = 0; i < 8u; ++i) + maskedStartMatches &= code[i] == static_cast(0x80u + i); + t.IsTrue(maskedStartMatches, "MPG immediate 0x200 should wrap to VU0 micro address zero"); + + std::memset(mem.getVU0Code(), 0xCD, PS2_VU0_CODE_SIZE); + packet.clear(); + appendU32(packet, makeVifCmd(0x4Au, 2u, 0x01FFu)); + packet.resize(packet.size() + 8u, 0x55u); + mem.processVIF0Data(packet.data(), static_cast(packet.size())); + t.Equals(static_cast(code[PS2_VU0_CODE_SIZE - 1u]), 0xCDu, + "truncated MPG payload should not partially modify VU0 micro memory"); + }); + + tc.Run("VIF1 MPG masks and wraps micro memory addresses", [](TestCase &t) + { + PS2Memory mem; + t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed"); + + std::memset(mem.getVU1Code(), 0xCD, PS2_VU1_CODE_SIZE); + std::vector packet; + appendU32(packet, makeVifCmd(0x4Au, 2u, 0x07FFu)); + for (uint32_t i = 0; i < 16u; ++i) + packet.push_back(static_cast(0x10u + i)); + + mem.processVIF1Data(packet.data(), static_cast(packet.size())); + + const uint8_t *code = mem.getVU1Code(); + bool tailMatches = true; + bool wrappedStartMatches = true; + for (uint32_t i = 0; i < 8u; ++i) + { + tailMatches &= code[PS2_VU1_CODE_SIZE - 8u + i] == static_cast(0x10u + i); + wrappedStartMatches &= code[i] == static_cast(0x18u + i); + } + t.IsTrue(tailMatches, "MPG should write through the end of VU1 micro memory"); + t.IsTrue(wrappedStartMatches, "MPG payload should wrap to the start of VU1 micro memory"); + + packet.clear(); + appendU32(packet, makeVifCmd(0x4Au, 1u, 0x0800u)); + for (uint32_t i = 0; i < 8u; ++i) + packet.push_back(static_cast(0x80u + i)); + mem.processVIF1Data(packet.data(), static_cast(packet.size())); + + bool maskedStartMatches = true; + for (uint32_t i = 0; i < 8u; ++i) + maskedStartMatches &= code[i] == static_cast(0x80u + i); + t.IsTrue(maskedStartMatches, "MPG immediate 0x800 should wrap to VU1 micro address zero"); + + std::memset(mem.getVU1Code(), 0xCD, PS2_VU1_CODE_SIZE); + packet.clear(); + appendU32(packet, makeVifCmd(0x4Au, 2u, 0x07FFu)); + packet.resize(packet.size() + 8u, 0x55u); + mem.processVIF1Data(packet.data(), static_cast(packet.size())); + t.Equals(static_cast(code[PS2_VU1_CODE_SIZE - 1u]), 0xCDu, + "truncated MPG payload should not partially modify VU1 micro memory"); + }); + tc.Run("VIF UNPACK num zero uploads 256 vectors", [](TestCase &t) { PS2Memory mem;