GE: Bound block transfers, indexed draws and list reads to valid memory

- Block transfer sizes counted the stride in pixels rather than bytes, so
  the range check covered a quarter of a 32-bit copy and the memcpy wrote
  past the end of RAM or VRAM.
- Indexed draws, curves and bounding boxes now check the index buffer and
  every vertex the indices can reach (IsValidDrawRange). Only the start
  address was checked before.
- The run loop's downcount stops at the end of memory, and a list that
  runs off the end errors out. The PRIM fast loop, its CALL, the matrix
  fast-loaders and END's look at the previous word are bounded the same way.
- sceGeListUpdateStallAddr with an unaligned address no longer hangs the
  emulator, since the stall is now word aligned like the PC.
- The software renderer leaves out rows that would reach past the end of
  VRAM's mirrors with a wide 32-bit framebuffer stride.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-05 21:21:22 +01:00
1 parent 5b3ddcc062
commit 05cca98c31
6 files changed
+111 -74

No files matched your search

+19
View File
@@ -201,6 +201,25 @@ void GetIndexBounds(const void *inds, int count, u32 vertType, u16 *indexLowerBo
}
}
bool IsValidIndexedDrawRange(u32 vertexAddr, u32 indexAddr, int count, u32 vertType, int vertexSize) {
const u32 idx = vertType & GE_VTYPE_IDX_MASK;
const int indexShift = (idx >> GE_VTYPE_IDX_SHIFT) - 1;
if (!Memory::IsValidRange(indexAddr, count << indexShift)) {
return false;
}
// Indices are at most 16 bits, so this usually saves looking at them.
const u32 maxIndex = idx == GE_VTYPE_IDX_8BIT ? 0xFF : 0xFFFF;
if (Memory::IsValidRange(vertexAddr, (maxIndex + 1) * vertexSize)) {
return true;
}
if (count <= 0) {
return true;
}
u16 lowerBound, upperBound;
GetIndexBounds(Memory::GetPointerUnchecked(indexAddr), count, vertType, &lowerBound, &upperBound);
return Memory::IsValidRange(vertexAddr, (upperBound + 1) * vertexSize);
}
void PrintDecodedVertex(const VertexReader &vtx) {
if (vtx.hasNormal()) {
float nrm[3];
+12
View File
@@ -27,6 +27,7 @@
#include "Common/Data/Collections/Hashmaps.h"
#include "Common/Log.h"
#include "Common/LogReporting.h"
#include "Core/MemMap.h"
#include "GPU/ge_constants.h"
#include "GPU/Common/ShaderCommon.h"
#include "GPU/GPUState.h"
@@ -92,6 +93,17 @@ struct DecVtxFormat {
void GetIndexBounds(const void *inds, int count, u32 vertType, u16 *indexLowerBound, u16 *indexUpperBound);
bool IsValidIndexedDrawRange(u32 vertexAddr, u32 indexAddr, int count, u32 vertType, int vertexSize);
// Checks that the indices of a draw, and every vertex they can reach, are in valid memory.
// Inline since it runs on every draw, and nearly all of them are non-indexed.
inline bool IsValidDrawRange(u32 vertexAddr, u32 indexAddr, int count, u32 vertType, int vertexSize) {
if ((vertType & GE_VTYPE_IDX_MASK) == GE_VTYPE_IDX_NONE) {
return Memory::IsValidRange(vertexAddr, count * vertexSize);
}
return IsValidIndexedDrawRange(vertexAddr, indexAddr, count, vertType, vertexSize);
}
inline constexpr int RoundUp4(int x) {
return (x + 3) & ~3;
}
+26 -15
View File
@@ -696,7 +696,8 @@ u32 GPUCommon::UpdateStall(int listid, u32 newstall, bool *runList) {
if (dl.state == PSP_GE_DL_STATE_COMPLETED)
return SCE_KERNEL_ERROR_ALREADY;
dl.stall = newstall & 0x0FFFFFFF;
// The GE fetches whole words, and an unaligned stall would never equal the PC.
dl.stall = newstall & 0x0FFFFFFC;
gpuStats.perFrame.numUpdateStall++;
@@ -875,6 +876,12 @@ bool GPUCommon::SlowRunLoop(DisplayList &list) {
return true;
}
// How many commands can run from pc: up to the stall, and never past the end of memory.
static int ListDowncount(u32 pc, u32 stall) {
const u32 toStall = stall == 0 ? 0x0FFFFFFF : (stall - pc) / 4;
return (int)std::min(toStall, std::min(Memory::MaxSizeAtAddress(pc) / 4, (u32)0x0FFFFFFF));
}
// The newPC parameter is used for jumps, we don't count cycles between.
void GPUCommon::UpdatePC(u32 currentPC, u32 newPC) {
// Rough estimate, 2 CPU ticks (it's double the clock rate) per GPU instruction.
@@ -884,7 +891,7 @@ void GPUCommon::UpdatePC(u32 currentPC, u32 newPC) {
// Exit the runloop and recalculate things. This happens a lot in some games.
if (currentList)
downcount = currentList->stall == 0 ? 0x0FFFFFFF : (currentList->stall - newPC) / 4;
downcount = ListDowncount(newPC, currentList->stall);
else
downcount = 0;
}
@@ -1003,7 +1010,7 @@ DLResult GPUCommon::ProcessDLQueue() {
cycleLastPC = list.pc;
cyclesExecuted += 60;
downcount = list.stall == 0 ? 0x0FFFFFFF : (list.stall - list.pc) / 4;
downcount = ListDowncount(list.pc, list.stall);
list.state = PSP_GE_DL_STATE_RUNNING;
list.interrupted = false;
@@ -1016,7 +1023,7 @@ DLResult GPUCommon::ProcessDLQueue() {
resumingFromDebugBreak_ = false;
// The bottom part of the gpuState loop below, that wasn't executed
// when we bailed.
downcount = list.stall == 0 ? 0x0FFFFFFF : (list.stall - list.pc) / 4;
downcount = ListDowncount(list.pc, list.stall);
if (gpuState == GPUSTATE_STALL && list.pc != list.stall) {
// Unstalled (Can this happen?)
gpuState = GPUSTATE_RUNNING;
@@ -1030,6 +1037,11 @@ DLResult GPUCommon::ProcessDLQueue() {
if (list.pc == list.stall) {
gpuState = GPUSTATE_STALL;
downcount = 0;
} else if (downcount == 0 && !Memory::IsValidAddress(list.pc)) {
// Ran (or jumped) off the end of memory. There's nothing more to run.
ERROR_LOG(Log::G3D, "Display list %d ran off the end of memory at %08x", listIndex, list.pc);
gpuState = GPUSTATE_ERROR;
break;
}
if (useFastRunLoop) {
@@ -1048,7 +1060,7 @@ DLResult GPUCommon::ProcessDLQueue() {
}
}
downcount = list.stall == 0 ? 0x0FFFFFFF : (list.stall - list.pc) / 4;
downcount = ListDowncount(list.pc, list.stall);
if (gpuState == GPUSTATE_STALL && list.pc != list.stall) {
// Unstalled (Can this happen?)
gpuState = GPUSTATE_RUNNING;
@@ -1250,7 +1262,7 @@ void GPUCommon::Execute_End(u32 op, u32 diff) {
Flush();
}
const u32 prev = Memory::ReadUnchecked_U32(currentList->pc - 4);
const u32 prev = Memory::IsValidAddress(currentList->pc - 4) ? Memory::ReadUnchecked_U32(currentList->pc - 4) : 0;
UpdatePC(currentList->pc, currentList->pc);
// Count in a few extra cycles on END.
cyclesExecuted += 60;
@@ -1480,8 +1492,7 @@ void GPUCommon::Execute_BoundingBox(u32 op, u32 diff) {
const void *inds = nullptr;
if (useInds) {
const int indexSizeShift = ((vertType & GE_VTYPE_IDX_MASK) >> GE_VTYPE_IDX_SHIFT) - 1;
if (!Memory::IsValidRange(gstate_c.indexAddr, count << indexSizeShift)) {
if (!IsValidDrawRange(gstate_c.vertexAddr, gstate_c.indexAddr, count, vertType, dec->VertexSize())) {
ERROR_LOG_REPORT_ONCE(boundingboxInds, Log::G3D, "Invalid inds in bounding box check");
currentList->bboxResult = true;
return;
@@ -1680,7 +1691,7 @@ void GPUCommon::Execute_WorldMtxNum(u32 op, u32 diff) {
// We must record the individual data commands while debugRecording_.
bool fastLoad = !debugRecording_ && end > 0;
// Stalling in the middle of a matrix would be stupid, I doubt this check is necessary.
if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) {
if ((currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) || !Memory::IsValidRange(currentList->pc + 4, end * 4)) {
fastLoad = false;
}
@@ -1733,7 +1744,7 @@ void GPUCommon::Execute_ViewMtxNum(u32 op, u32 diff) {
int i = 0;
bool fastLoad = !debugRecording_ && end > 0;
if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) {
if ((currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) || !Memory::IsValidRange(currentList->pc + 4, end * 4)) {
fastLoad = false;
}
@@ -1786,7 +1797,7 @@ void GPUCommon::Execute_ProjMtxNum(u32 op, u32 diff) {
int i = 0;
bool fastLoad = !debugRecording_;
if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) {
if ((currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) || !Memory::IsValidRange(currentList->pc + 4, end * 4)) {
fastLoad = false;
}
@@ -1840,7 +1851,7 @@ void GPUCommon::Execute_TgenMtxNum(u32 op, u32 diff) {
int i = 0;
bool fastLoad = !debugRecording_ && end > 0;
if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) {
if ((currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) || !Memory::IsValidRange(currentList->pc + 4, end * 4)) {
fastLoad = false;
}
@@ -1894,7 +1905,7 @@ void GPUCommon::Execute_BoneMtxNum(u32 op, u32 diff) {
int i = 0;
bool fastLoad = !debugRecording_ && end > 0;
if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) {
if ((currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) || !Memory::IsValidRange(currentList->pc + 4, end * 4)) {
fastLoad = false;
}
@@ -2387,8 +2398,8 @@ void GPUCommon::DoBlockTransfer(u32 skipDrawReason) {
// NOTE: The sizes are only used for validity checks and memory info tracking.
const uint32_t src = srcBasePtr + (srcY * srcStride + srcX) * bpp;
const uint32_t dst = dstBasePtr + (dstY * dstStride + dstX) * bpp;
const uint32_t srcSize = ((height - 1) * srcStride) + width * bpp;
const uint32_t dstSize = ((height - 1) * dstStride) + width * bpp;
const uint32_t srcSize = ((height - 1) * srcStride + width) * bpp;
const uint32_t dstSize = ((height - 1) * dstStride + width) * bpp;
bool srcDstOverlap = src + srcSize > dst && dst + dstSize > src;
bool srcValid = Memory::IsValidRange(src, srcSize);
+25 -32
View File
@@ -986,7 +986,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
VertexDecoder *decoder = drawEngineCommon_->GetVertexDecoder(vertTypeID);
// Nothing is mapped past the end of RAM or VRAM, so a draw running off the end would fault on the first read.
if (!inds && !Memory::IsValidRange(gstate_c.vertexAddr, count * decoder->VertexSize())) {
if (!IsValidDrawRange(gstate_c.vertexAddr, gstate_c.indexAddr, count, vertexType, decoder->VertexSize())) {
ERROR_LOG(Log::G3D, "Bad vertex range %08x (%d verts of %d bytes)!", gstate_c.vertexAddr, count, decoder->VertexSize());
return;
}
@@ -1088,6 +1088,11 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
const u32_le *start = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4);
const u32_le *src = start;
const u32_le *stall = currentList->stall ? (const u32_le *)Memory::GetPointerUnchecked(currentList->stall) : 0;
// Without a stall ahead, stop at the end of memory.
const u32_le *end = start + Memory::MaxSizeAtAddress(currentList->pc + 4) / 4;
if (!stall || stall < start || stall > end) {
stall = end;
}
// Optimized submission of sequences of PRIM. Allows us to avoid going through all the mess
// above for each one. This can be expanded to support additional games that intersperse
@@ -1101,7 +1106,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
if (!useFastRunLoop_ || readsFramebufferPerDraw)
goto bail; // we're either recording or stepping, or each draw has to go alone.
while (src != stall) {
while (src < stall) {
uint32_t data = *src;
switch (data >> 24) {
case GE_CMD_PRIM:
@@ -1131,19 +1136,13 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
verts = Memory::GetPointerUnchecked(gstate_c.vertexAddr);
inds = nullptr;
if (!IsValidDrawRange(gstate_c.vertexAddr, gstate_c.indexAddr, count, vertexType, decoder->VertexSize())) {
// Bad index or vertex range, leave it to Execute_Prim.
goto bail;
}
if ((vertexType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {
const u32 indexAddr = gstate_c.indexAddr;
const int indexShift = ((vertexType & GE_VTYPE_IDX_MASK) >> GE_VTYPE_IDX_SHIFT) - 1;
if (!Memory::IsValidRange(gstate_c.indexAddr, count << indexShift)) {
// Bad index range. Let's give up the fast loop.
goto bail;
}
inds = Memory::GetPointerUnchecked(indexAddr);
inds = Memory::GetPointerUnchecked(gstate_c.indexAddr);
} else {
if (!Memory::IsValidRange(gstate_c.vertexAddr, count * decoder->VertexSize())) {
// Bad vertex range, leave it to Execute_Prim.
goto bail;
}
// We can extend again after submitting a normal draw.
canExtend = isTriangle;
}
@@ -1267,7 +1266,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
{
// A bone matrix probably. If not we bail.
const u32 target = gstate_c.getRelativeAddress(data & 0x00FFFFFC);
if (currentList->HardwareCallDepth() < 2 &&
if (currentList->HardwareCallDepth() < 2 && Memory::IsValidRange(target, 13 * 4) &&
(Memory::ReadUnchecked_U32(target) >> 24) == GE_CMD_BONEMATRIXDATA &&
(Memory::ReadUnchecked_U32(target + 11 * 4) >> 24) == GE_CMD_BONEMATRIXDATA &&
(Memory::ReadUnchecked_U32(target + 12 * 4) >> 24) == GE_CMD_RET &&
@@ -1352,17 +1351,14 @@ void GPUCommonHW::Execute_Bezier(u32 op, u32 diff) {
const void *control_points = Memory::GetPointerUnchecked(gstate_c.vertexAddr);
const void *indices = NULL;
const u32 vertType = gstate.vertType;
if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {
if (!Memory::IsValidAddress(gstate_c.indexAddr)) {
ERROR_LOG(Log::G3D, "Bad index address %08x!", gstate_c.indexAddr);
return;
}
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
} else if (!Memory::IsValidRange(gstate_c.vertexAddr, (op & 0xFF) * ((op >> 8) & 0xFF) * drawEngineCommon_->GetVertexDecoder(GetVertTypeID(vertType, gstate.getUVGenMode()))->VertexSize())) {
// Nothing is mapped past the end of RAM or VRAM, so this would fault on the first read.
ERROR_LOG(Log::G3D, "Bad control point range %08x!", gstate_c.vertexAddr);
// Nothing is mapped past the end of RAM or VRAM, so this would fault on the first read.
if (!IsValidDrawRange(gstate_c.vertexAddr, gstate_c.indexAddr, (op & 0xFF) * ((op >> 8) & 0xFF), vertType, drawEngineCommon_->GetVertexDecoder(GetVertTypeID(vertType, gstate.getUVGenMode()))->VertexSize())) {
ERROR_LOG(Log::G3D, "Bad control point range %08x (indices %08x)!", gstate_c.vertexAddr, gstate_c.indexAddr);
return;
}
if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
}
if (vertTypeIsSkinningEnabled(vertType)) {
DEBUG_LOG_REPORT(Log::G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", vertType, (vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(vertType));
@@ -1429,17 +1425,14 @@ void GPUCommonHW::Execute_Spline(u32 op, u32 diff) {
const void *control_points = Memory::GetPointerUnchecked(gstate_c.vertexAddr);
const void *indices = NULL;
const u32 vertType = gstate.vertType;
if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {
if (!Memory::IsValidAddress(gstate_c.indexAddr)) {
ERROR_LOG(Log::G3D, "Bad index address %08x!", gstate_c.indexAddr);
return;
}
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
} else if (!Memory::IsValidRange(gstate_c.vertexAddr, (op & 0xFF) * ((op >> 8) & 0xFF) * drawEngineCommon_->GetVertexDecoder(GetVertTypeID(vertType, gstate.getUVGenMode()))->VertexSize())) {
// Nothing is mapped past the end of RAM or VRAM, so this would fault on the first read.
ERROR_LOG(Log::G3D, "Bad control point range %08x!", gstate_c.vertexAddr);
// Nothing is mapped past the end of RAM or VRAM, so this would fault on the first read.
if (!IsValidDrawRange(gstate_c.vertexAddr, gstate_c.indexAddr, (op & 0xFF) * ((op >> 8) & 0xFF), vertType, drawEngineCommon_->GetVertexDecoder(GetVertTypeID(vertType, gstate.getUVGenMode()))->VertexSize())) {
ERROR_LOG(Log::G3D, "Bad control point range %08x (indices %08x)!", gstate_c.vertexAddr, gstate_c.indexAddr);
return;
}
if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
}
if (vertTypeIsSkinningEnabled(vertType)) {
WARN_LOG_ONCE(unusualcurve, Log::G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", vertType, (vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(vertType));
+11
View File
@@ -262,6 +262,17 @@ void BinManager::UpdateState() {
drawTargetAddr_ = gstate.getFrameBufAddress();
DrawingCoords scissorTL(gstate.getScissorX1(), gstate.getScissorY1());
DrawingCoords scissorBR(std::min(gstate.getScissorX2(), gstate.getRegionX2()), std::min(gstate.getScissorY2(), gstate.getRegionY2()));
// fb.data points into VRAM's mirrors, which end 8MB from its start. A wide 32-bit stride can
// reach past that within 1024 rows, so leave out the rows that would.
const int fbStride = gstate.FrameBufStride();
if (fbStride > 0) {
const int bpp = gstate.FrameBufFormat() == GE_FORMAT_8888 ? 4 : 2;
const int bytesLeft = 0x00800000 - (int)gstate.getFrameBufRawAddress() - (scissorBR.x + 1) * bpp;
const int maxY = bytesLeft / (fbStride * bpp);
if (scissorBR.y > maxY) {
scissorBR.y = maxY;
}
}
ScreenCoords screenScissorTL = TransformUnit::DrawingToScreen(scissorTL, 0);
ScreenCoords screenScissorBR = TransformUnit::DrawingToScreen(scissorBR, 0);
+18 -27
View File
@@ -969,17 +969,14 @@ void SoftGPU::Execute_Prim(u32 op, u32 diff) {
const void *indices = NULL;
const u32 vertType = gstate.vertType;
if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {
if (!Memory::IsValidAddress(gstate_c.indexAddr)) {
ERROR_LOG(Log::G3D, "Software: Bad index address %08x!", gstate_c.indexAddr);
return;
}
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
} else if (!Memory::IsValidRange(gstate_c.vertexAddr, count * drawEngine_->FindVertexDecoder(vertType)->VertexSize())) {
// Nothing is mapped past the end of RAM or VRAM, so this would fault on the first read.
ERROR_LOG(Log::G3D, "Software: Bad vertex range %08x (%d verts)!", gstate_c.vertexAddr, count);
// Nothing is mapped past the end of RAM or VRAM, so this would fault on the first read.
if (!IsValidDrawRange(gstate_c.vertexAddr, gstate_c.indexAddr, count, vertType, drawEngine_->FindVertexDecoder(vertType)->VertexSize())) {
ERROR_LOG(Log::G3D, "Software: Bad vertex range %08x (%d verts, indices %08x)!", gstate_c.vertexAddr, count, gstate_c.indexAddr);
return;
}
if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
}
if (gstate.isModeThrough()) {
cyclesExecuted += EstimateFillCycles(prim, verts, indices, count, drawEngine_->FindVertexDecoder(vertType), vertType);
@@ -1052,17 +1049,14 @@ void SoftGPU::Execute_Bezier(u32 op, u32 diff) {
const void *control_points = Memory::GetPointerUnchecked(gstate_c.vertexAddr);
const void *indices = NULL;
const u32 vertType = gstate.vertType;
if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {
if (!Memory::IsValidAddress(gstate_c.indexAddr)) {
ERROR_LOG(Log::G3D, "Bad index address %08x!", gstate_c.indexAddr);
return;
}
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
} else if (!Memory::IsValidRange(gstate_c.vertexAddr, (op & 0xFF) * ((op >> 8) & 0xFF) * drawEngine_->FindVertexDecoder(vertType)->VertexSize())) {
// Nothing is mapped past the end of RAM or VRAM, so this would fault on the first read.
ERROR_LOG(Log::G3D, "Bad control point range %08x!", gstate_c.vertexAddr);
// Nothing is mapped past the end of RAM or VRAM, so this would fault on the first read.
if (!IsValidDrawRange(gstate_c.vertexAddr, gstate_c.indexAddr, (op & 0xFF) * ((op >> 8) & 0xFF), vertType, drawEngine_->FindVertexDecoder(vertType)->VertexSize())) {
ERROR_LOG(Log::G3D, "Bad control point range %08x (indices %08x)!", gstate_c.vertexAddr, gstate_c.indexAddr);
return;
}
if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
}
if ((vertType & GE_VTYPE_MORPHCOUNT_MASK) || vertTypeIsSkinningEnabled(vertType)) {
DEBUG_LOG(Log::G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", vertType, (vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(vertType));
@@ -1112,17 +1106,14 @@ void SoftGPU::Execute_Spline(u32 op, u32 diff) {
const void *control_points = Memory::GetPointerUnchecked(gstate_c.vertexAddr);
const void *indices = NULL;
const u32 vertType = gstate.vertType;
if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {
if (!Memory::IsValidAddress(gstate_c.indexAddr)) {
ERROR_LOG(Log::G3D, "Bad index address %08x!", gstate_c.indexAddr);
return;
}
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
} else if (!Memory::IsValidRange(gstate_c.vertexAddr, (op & 0xFF) * ((op >> 8) & 0xFF) * drawEngine_->FindVertexDecoder(vertType)->VertexSize())) {
// Nothing is mapped past the end of RAM or VRAM, so this would fault on the first read.
ERROR_LOG(Log::G3D, "Bad control point range %08x!", gstate_c.vertexAddr);
// Nothing is mapped past the end of RAM or VRAM, so this would fault on the first read.
if (!IsValidDrawRange(gstate_c.vertexAddr, gstate_c.indexAddr, (op & 0xFF) * ((op >> 8) & 0xFF), vertType, drawEngine_->FindVertexDecoder(vertType)->VertexSize())) {
ERROR_LOG(Log::G3D, "Bad control point range %08x (indices %08x)!", gstate_c.vertexAddr, gstate_c.indexAddr);
return;
}
if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
}
if ((vertType & GE_VTYPE_MORPHCOUNT_MASK) || vertTypeIsSkinningEnabled(vertType)) {
DEBUG_LOG(Log::G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", vertType, (vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(vertType));