diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index 8dedf308c0..2a4b8656bc 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -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]; diff --git a/GPU/Common/VertexDecoderCommon.h b/GPU/Common/VertexDecoderCommon.h index 04f9eee009..a14f9c6aba 100644 --- a/GPU/Common/VertexDecoderCommon.h +++ b/GPU/Common/VertexDecoderCommon.h @@ -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; } diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 428ffc4c11..546bb658d8 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -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); diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 79c7086c56..405cb4dbbc 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -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)); diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp index 78f11798cb..5fe2b88991 100644 --- a/GPU/Software/BinManager.cpp +++ b/GPU/Software/BinManager.cpp @@ -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); diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 0ed0ec1fe3..cc2da84ce5 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -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));