{"record":{"id":"ccd57c925f77970e","repo":"stride3d/stride","slug":"unexpected-end-of-buffer","errorCode":null,"errorMessage":"Unexpected end of buffer","messagePattern":"Unexpected end of buffer","errorType":"validation","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/engine/Stride.Foundation/Graphics/DDSHelper.cs","lineNumber":1145,"sourceCode":"                tflags |= ScanlineFlags.Legacy;\n\n            int index = 0;\n\n            int checkSize = size;\n\n            for (int arrayIndex = 0; arrayIndex < metadata.ArraySize; arrayIndex++)\n            {\n                int d = metadata.Depth;\n                // Else we need to go through each mips/depth slice to convert all scanlines.\n                for (int level = 0; level < metadata.MipLevels; ++level)\n                {\n                    for (int slice = 0; slice < d; ++slice, ++index)\n                    {\n                        IntPtr pSrc = images[index].DataPointer;\n                        IntPtr pDest = imagesDst[index].DataPointer;\n                        checkSize -= images[index].BufferStride;\n                        if (checkSize < 0)\n                            throw new InvalidOperationException(\"Unexpected end of buffer\");\n\n                        if (metadata.Format.IsCompressed)\n                        {\n                            MemoryUtilities.CopyWithAlignmentFallback((void*)pDest, (void*)pSrc, (uint)Math.Min(images[index].BufferStride, imagesDst[index].BufferStride));\n                        }\n                        else\n                        {\n                            int spitch = images[index].RowStride;\n                            int dpitch = imagesDst[index].RowStride;\n\n                            for (int h = 0; h < images[index].Height; ++h)\n                            {\n                                if ((convFlags & ConversionFlags.Expand) != 0)\n                                {\n#if DIRECTX11_1\n                                if ((convFlags & (ConversionFlags.Format565 | ConversionFlags.Format5551 | ConversionFlags.Format4444)) != 0)\n#else\n                                    if ((convFlags & (ConversionFlags.Format565 | ConversionFlags.Format5551)) != 0)","sourceCodeStart":1127,"sourceCodeEnd":1163,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/engine/Stride.Foundation/Graphics/DDSHelper.cs#L1127-L1163","documentation":"Thrown by DDS/texture image copying helpers (e.g. Copy) when the sum of source image BufferStride values exceeds the size of the underlying data buffer. It means the caller provided image descriptions whose computed total size is larger than the actual data supplied, so the copy loop would read past the end of the buffer. The library throws eagerly to avoid a memory corruption or access violation during a raw memcpy of pixel data.","triggerScenarios":"Calling Image.Copy / DDS load helpers where the cumulative images[i].BufferStride exceeds checkSize (the total buffer size), e.g. constructing an Image with Description.MipLevels/ArraySize/Depth larger than the data buffer actually contains.","commonSituations":"Loading a truncated DDS/KTX file whose header claims more mipmaps or array slices than the file contains; hand-crafting Image from raw bytes with a wrong data size; format or block-size mismatch causing BufferStride to be computed larger than expected; off-by-one in mip chain calculation after a format change.","solutions":["Verify the total data buffer size is at least the sum of all per-mip/per-slice BufferStride values before calling the copy API","Recheck the Image Description (MipLevels, ArraySize, Depth, Format) against the actual data; regenerate or fix the source asset","Ensure the file/asset was not truncated during download or packaging (compare file size against header metadata)","If computing strides yourself, use the library's PixelBuffer helpers for the format instead of manual block-size math"],"exampleFix":"// before: trust header-derived mip count\nvar image = Image.New(loadDescription, dataPointer, totalSize);\n// after: verify buffer covers full mip chain\nlong required = 0;\nforeach (var m in ComputeMipSizes(loadDescription)) required += m;\nif (dataSize < required) throw new InvalidDataException($\"Truncated texture: need {required}, have {dataSize}\");\nvar image = Image.New(loadDescription, dataPointer, totalSize);","handlingStrategy":"validation","validationCode":"long required = 0;\nfor (int i = 0; i < imageCount; i++)\n    required += images[i].BufferStride;\nif (totalBufferSize < required)\n    throw new InvalidDataException($\"Texture data truncated: need {required} bytes, have {totalBufferSize}\");","typeGuard":"bool HasFullMipChain(Image image) =>\n    image.Description.Dimension == TextureDimension.Texture3D\n        ? image.Description.Depth > 0\n        : image.Description.MipLevels > 0 && image.TotalSizeInBytes > 0;","tryCatchPattern":"try\n{\n    image.Copy(destImages);\n}\ncatch (InvalidOperationException ex) when (ex.Message == \"Unexpected end of buffer\")\n{\n    log.Error(\"Truncated texture data: description does not match buffer size\", ex);\n    return LoadFallbackTexture();\n}","preventionTips":["Always compute total size from the Description via library helpers instead of trusting file headers","Validate asset file sizes against header metadata at build/import time","Treat truncated downloads as build failures; checksum assets in CI"],"tags":["graphics","textures","buffer-overflow","dds"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}