From ecb685fff14f9d6bedbe62ff7baf7fb48fcbaf0b Mon Sep 17 00:00:00 2001 From: "seer-by-sentry[bot]" <157164994+seer-by-sentry[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 21:33:09 +0000 Subject: [PATCH] bugfix(w3ddevice): Prevent crashes and out-of-bounds access in W3DTerrainTracks --- .../W3DDevice/GameClient/W3DTerrainTracks.cpp | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainTracks.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainTracks.cpp index 8d14b156fe6..9b426d28e79 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainTracks.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainTracks.cpp @@ -823,8 +823,24 @@ Try improving the fit to vertical surfaces like cliffs. //check if there is anything to draw and fill vertex buffer if (m_edgesToFlush >= 2) { + // Guard against a null vertex buffer (e.g. if ReleaseResources() was called + // without a matching ReAcquireResources() during a device reset). + if (!m_vertexBuffer) + { + m_edgesToFlush = 0; + return; + } + DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexBuffer); VertexFormatXYZDUV1 *verts = (VertexFormatXYZDUV1*)lockVtxBuffer.Get_Vertex_Array(); + + // Guard against a failed buffer lock returning a null pointer. + if (!verts) + { + m_edgesToFlush = 0; + return; + } + trackStartIndex=0; mod=m_usedModules; @@ -835,9 +851,17 @@ Try improving the fit to vertical surfaces like cliffs. Vector3 *endPoint; Vector2 *endPointUV; - if (mod->m_activeEdgeCount >= 2 && mod->Is_Really_Visible()) + // Clamp activeEdgeCount to m_maxTankTrackEdges to prevent writing past the + // end of the vertex buffer when m_maxTankTrackEdges was reduced (e.g. by a + // graphics-detail change) while tracks still hold edges from the old, larger + // limit. This also keeps index within the fixed-size m_edges[] array. + Int activeEdgeCount = mod->m_activeEdgeCount; + if (activeEdgeCount > m_maxTankTrackEdges) + activeEdgeCount = m_maxTankTrackEdges; + + if (activeEdgeCount >= 2 && mod->Is_Really_Visible()) { - for (i=0,index=mod->m_bottomIndex; im_activeEdgeCount; i++,index++) + for (i=0,index=mod->m_bottomIndex; i= m_maxTankTrackEdges) index=0; @@ -847,10 +871,10 @@ Try improving the fit to vertical surfaces like cliffs. distanceFade=1.0f; - if ((mod->m_activeEdgeCount -1 -i) >= m_maxTankTrackOpaqueEdges)// && i < (MAX_PER_TRACK_EDGE_COUNT-FORCE_FADE_AT_EDGE)) + if ((activeEdgeCount -1 -i) >= m_maxTankTrackOpaqueEdges)// && i < (MAX_PER_TRACK_EDGE_COUNT-FORCE_FADE_AT_EDGE)) { //we're getting close to the limit on the number of track pieces allowed //so force it to fade out. - distanceFade=1.0f-(float)((mod->m_activeEdgeCount -i)-m_maxTankTrackOpaqueEdges)/numFadedEdges; + distanceFade=1.0f-(float)((activeEdgeCount -i)-m_maxTankTrackOpaqueEdges)/numFadedEdges; } distanceFade *= mod->m_edges[index].alpha; //adjust fade with distance from start of track @@ -898,13 +922,20 @@ Try improving the fit to vertical surfaces like cliffs. DX8Wrapper::Set_Transform(D3DTS_WORLD,mod->Transform); while (mod) { - if (mod->m_activeEdgeCount >= 2 && mod->Is_Really_Visible()) + // Use the same clamped edge count as the vertex-fill pass above so that + // trackStartIndex stays consistent and we don't read past the index buffer + // (which is only allocated for m_maxTankTrackEdges-1 segments). + Int activeEdgeCount = mod->m_activeEdgeCount; + if (activeEdgeCount > m_maxTankTrackEdges) + activeEdgeCount = m_maxTankTrackEdges; + + if (activeEdgeCount >= 2 && mod->Is_Really_Visible()) { DX8Wrapper::Set_Texture(0,mod->m_stageZeroTexture); DX8Wrapper::Set_Index_Buffer_Index_Offset(trackStartIndex); - DX8Wrapper::Draw_Triangles( 0,(mod->m_activeEdgeCount-1)*2, 0, mod->m_activeEdgeCount*2); + DX8Wrapper::Draw_Triangles( 0,(activeEdgeCount-1)*2, 0, activeEdgeCount*2); - trackStartIndex += mod->m_activeEdgeCount*2; + trackStartIndex += activeEdgeCount*2; } mod=mod->m_nextSystem; }