private static Dictionary <string, MetadataValue> ParseMetadata(byte[] inputArray, bool shouldSwapEndianness) { Dictionary <string, MetadataValue> returnDictionary = new Dictionary <string, MetadataValue>(); int position = 0; while (position < inputArray.Length) { uint combinedKeyAndValueSizeInBytes = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(BitConverter.ToUInt32(inputArray, position)) : BitConverter.ToUInt32(inputArray, position); // Pair must be larger than 0 bytes if (combinedKeyAndValueSizeInBytes == 0) { throw new InvalidOperationException("Metadata: combinedKeyAndValueSize cannot be 0!"); } position += Common.sizeOfUint; // Error out in case size is larger than bytes left if (combinedKeyAndValueSizeInBytes + 4 > (uint)inputArray.Length) { throw new InvalidOperationException("Metadata: combinedKeyAndValueSize cannot be larger than whole metadata!"); } // Find NUL since key should always have it int indexOfFirstNul = Array.IndexOf(inputArray, Common.nulByte, position); if (indexOfFirstNul < 0) { throw new InvalidOperationException("Metadata: No Nul found when looking for key"); } int keyLength = indexOfFirstNul - position; if (keyLength > combinedKeyAndValueSizeInBytes) { throw new InvalidOperationException("Metadata: Key length is longer than combinedKeyAndValueSizeInBytes!"); } string key = System.Text.Encoding.UTF8.GetString(bytes: inputArray, index: position, count: keyLength); position += (keyLength + 1 /* Because we have to skip nul byte*/); int valueLength = (int)combinedKeyAndValueSizeInBytes - keyLength; byte[] bytesOfValue = new byte[valueLength]; Buffer.BlockCopy(src: inputArray, srcOffset: position, dst: bytesOfValue, dstOffset: 0, count: valueLength); returnDictionary[key] = new MetadataValue(bytesOfValue); position += valueLength; // Skip value paddings if there are any while (position % 4 != 0) { position++; } } return(returnDictionary); }
public static (bool isValid, string possibleError) ValidateTextureData(MemoryStream memoryStream, KtxHeader header, uint expectedTextureDataSize) { // Use the memory stream in a binary reader. try { using (BinaryReader reader = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: true)) { // Specs say that if value of certain things is zero (0) then it should be used as one (1) uint mipmapLevels = (header.numberOfMipmapLevels == 0) ? 1 : header.numberOfMipmapLevels; uint numberOfArrayElements = (header.numberOfArrayElements == 0) ? 1 : header.numberOfArrayElements; uint pixelDepth = (header.pixelDepth == 0) ? 1 : header.pixelDepth; uint pixelHeight = (header.pixelHeight == 0) ? 1 : header.pixelHeight; uint totalLengthOfTextureDataSection = 0; // Check if length reads should be endian swapped bool shouldSwapEndianness = (header.endiannessValue != Common.expectedEndianValue); // Check each mipmap level separately for (uint u = 0; u < mipmapLevels; u++) { uint imageSize = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); totalLengthOfTextureDataSection += (imageSize + (uint)Common.sizeOfUint); if (imageSize > expectedTextureDataSize || totalLengthOfTextureDataSection > expectedTextureDataSize) { return(isValid : false, "Texture data: More data than expected!"); } // TODO: More checks! // Read but do not use data for anything reader.ReadBytes((int)imageSize); // Skip possible padding bytes while (imageSize % 4 != 0) { imageSize++; // Read but ignore values reader.ReadByte(); } } } } catch (Exception e) { return(isValid : false, e.ToString()); } return(isValid : true, possibleError : ""); }
private static (bool isValid, string possibleError) ValidateMetadata(BinaryReader reader, uint bytesOfKeyValueData, bool shouldSwapEndianness) { uint currentPosition = 0; while (currentPosition < bytesOfKeyValueData) { uint combinedKeyAndValueSize = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); currentPosition += (uint)Common.sizeOfUint; if ((currentPosition + combinedKeyAndValueSize) > bytesOfKeyValueData) { return(isValid : false, possibleError : "Metadata: combinedKeyAndValueSize would go beyond Metadata array!"); } // There should be at least NUL byte[] keyAndValueAsBytes = reader.ReadBytes((int)combinedKeyAndValueSize); if (!keyAndValueAsBytes.Contains(Common.nulByte)) { return(isValid : false, possibleError : "Metadata: KeyValue pair does not contain NUL byte!"); } // Check if key is valid UTF-8 byte combination try { UTF8Encoding utf8ThrowException = new UTF8Encoding(false, true); string notUsed = utf8ThrowException.GetString(keyAndValueAsBytes, 0, keyAndValueAsBytes.Length); } catch (Exception e) { return(isValid : false, possibleError : $"Byte array to UTF-8 failed: {e}!"); } currentPosition += combinedKeyAndValueSize; // Skip value paddings if there are any while (currentPosition % 4 != 0) { currentPosition++; reader.ReadByte(); } } return(isValid : true, possibleError : ""); }
private static (bool isValid, string possibleError) ValidateMetadata(BinaryReader reader, uint bytesOfKeyValueData, bool shouldSwapEndianness) { uint currentPosition = 0; while (currentPosition < bytesOfKeyValueData) { uint combinedKeyAndValueSize = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); currentPosition += (uint)Common.sizeOfUint; if ((currentPosition + combinedKeyAndValueSize) > bytesOfKeyValueData) { return(isValid : false, possibleError : "Metadata: combinedKeyAndValueSize would go beyond Metadata array!"); } // There should be at least NUL byte[] keyAndValueAsBytes = reader.ReadBytes((int)combinedKeyAndValueSize); if (!keyAndValueAsBytes.Contains(Common.nulByte)) { return(isValid : false, possibleError : "Metadata: KeyValue pair does not contain NUL byte!"); } // TODO: Check if key is valid UTF-8 byte combination currentPosition += combinedKeyAndValueSize; // Skip value paddings if there are any while (currentPosition % 4 != 0) { currentPosition++; reader.ReadByte(); } } return(isValid : true, possibleError : ""); }
/// <summary> /// Constructor for texture data /// </summary> /// <param name="header">Header</param> /// <param name="stream">Stream for reading</param> public KtxTextureData(KtxHeader header, Stream stream) { //this.totalTextureDataLength = (uint)stream.Length; // Try to figure out texture type basic bool containsMipmaps = header.numberOfMipmapLevels > 1; if (header.numberOfArrayElements == 0 || header.numberOfArrayElements == 1) { // Is NOT texture array if (header.numberOfFaces == 0 || header.numberOfFaces == 1) { // Is NOT cube texture if (header.pixelDepth == 0 || header.pixelDepth == 1) { // Is not 3D texture if (header.pixelHeight == 0 || header.pixelHeight == 1) { // 1D texture this.textureType = containsMipmaps ? TextureTypeBasic.Basic1DWithMipmaps : TextureTypeBasic.Basic1DNoMipmaps; } else { // 2D texture this.textureType = containsMipmaps ? TextureTypeBasic.Basic2DWithMipmaps : TextureTypeBasic.Basic2DNoMipmaps; } } else { // Is 3D texture this.textureType = containsMipmaps ? TextureTypeBasic.Basic3DWithMipmaps : TextureTypeBasic.Basic3DNoMipmaps; } } else { // Is cube texture } } else { // Is Texture array } uint mipmapLevels = header.numberOfMipmapLevels; if (mipmapLevels == 0) { mipmapLevels = 1; } // Since we know how many mipmap levels there are, allocate the capacity this.textureDataOfMipmapLevel = new List <byte[]>((int)mipmapLevels); // Check if length reads should be endian swapped bool shouldSwapEndianness = (header.endiannessValue != Common.expectedEndianValue); using (BinaryReader reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true)) { for (int i = 0; i < mipmapLevels; i++) { uint amountOfDataInThisMipmapLevel = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); this.textureDataOfMipmapLevel.Add(reader.ReadBytes((int)amountOfDataInThisMipmapLevel)); // Skip possible padding bytes while (amountOfDataInThisMipmapLevel % 4 != 0) { amountOfDataInThisMipmapLevel++; // Read but ignore values reader.ReadByte(); } } } }
private static void WriteUintAsBigEndian(BinaryWriter writer, uint value) { writer.Write(KtxBitFiddling.SwapEndian(value)); }
/// <summary> /// KtxHeader constructor /// </summary> /// <param name="stream">Stream for reading</param> public KtxHeader(Stream stream) { // Skip first 12 bytes since they only contain identifier stream.Seek(12, SeekOrigin.Begin); // Read endianness as bytes byte[] endiannessBytes = new byte[4]; int bytesRead = stream.Read(buffer: endiannessBytes, offset: 0, count: endiannessBytes.Length); if (bytesRead != 4) { throw new InvalidOperationException("Cannot read enough bytes from stream!"); } if (!Common.littleEndianAsBytes.SequenceEqual(endiannessBytes) && !Common.bigEndianAsBytes.SequenceEqual(endiannessBytes)) { throw new InvalidOperationException("Endianness info in header is not valid!"); } this.isInputLittleEndian = Common.littleEndianAsBytes.SequenceEqual(endiannessBytes); // Turn endianness as bytes to uint this.endiannessValue = BitConverter.ToUInt32(endiannessBytes, 0); // See if following uint reads need endian swap bool shouldSwapEndianness = (this.endiannessValue != Common.expectedEndianValue); // Use the stream in a binary reader. using (BinaryReader reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true)) { // Swap endianness for every KTX variable if needed this.glTypeAsUint = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); if (GlDataType.IsDefined(typeof(GlDataType), this.glTypeAsUint)) { this.glDataType = (GlDataType)this.glTypeAsUint; } else { this.glDataType = GlDataType.NotKnown; } this.glTypeSizeAsUint = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); this.glFormatAsUint = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); if (GlPixelFormat.IsDefined(typeof(GlPixelFormat), this.glFormatAsUint)) { this.glFormat = (GlPixelFormat)this.glFormatAsUint; } else { this.glFormat = GlPixelFormat.NotKnown; } this.glInternalFormatAsUint = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); if (GlInternalFormat.IsDefined(typeof(GlInternalFormat), this.glInternalFormatAsUint)) { this.glInternalFormat = (GlInternalFormat)this.glInternalFormatAsUint; } else { this.glInternalFormat = GlInternalFormat.NotKnown; } this.glBaseInternalFormatAsUint = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); if (GlPixelFormat.IsDefined(typeof(GlPixelFormat), this.glBaseInternalFormatAsUint)) { this.glPixelFormat = (GlPixelFormat)this.glBaseInternalFormatAsUint; } else { this.glPixelFormat = GlPixelFormat.NotKnown; } this.pixelWidth = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); this.pixelHeight = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); this.pixelDepth = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); this.numberOfArrayElements = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); this.numberOfFaces = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); this.numberOfMipmapLevels = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); this.bytesOfKeyValueData = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); // Check that bytesOfKeyValueData is mod 4 if (this.bytesOfKeyValueData % 4 != 0) { throw new InvalidOperationException(ErrorGen.Modulo4Error(nameof(this.bytesOfKeyValueData), this.bytesOfKeyValueData)); } this.metadataDictionary = ParseMetadata(reader.ReadBytes((int)this.bytesOfKeyValueData), shouldSwapEndianness); } }
public static (bool isValid, string possibleError) ValidateHeaderData(MemoryStream memoryStream) { // Use the memory stream in a binary reader. try { using (BinaryReader reader = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: true)) { // Start validating header byte[] tempIdentifier = reader.ReadBytes(Common.onlyValidIdentifier.Length); if (!Common.onlyValidIdentifier.SequenceEqual(tempIdentifier)) { return(isValid : false, possibleError : "Identifier does not match requirements!"); } uint tempEndian = reader.ReadUInt32(); if (Common.expectedEndianValue != tempEndian && Common.otherValidEndianValue != tempEndian) { return(isValid : false, possibleError : "Endianness does not match requirements!"); } bool shouldSwapEndianness = (tempEndian != Common.expectedEndianValue); uint glTypeTemp = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); // TODO: uint glType to enum // If glType is 0 it should mean that this is compressed texture bool assumeCompressedTexture = (glTypeTemp == 0); uint glTypeSizeTemp = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); if (assumeCompressedTexture && glTypeSizeTemp != 1) { return(isValid : false, possibleError : "glTypeSize should be 1 for compressed textures!"); } uint glFormatTemp = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); if (assumeCompressedTexture && glFormatTemp != 0) { return(isValid : false, possibleError : "glFormat should be 0 for compressed textures!"); } uint glInternalFormatTemp = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); uint glBaseInternalFormatTemp = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); uint pixelWidthTemp = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); uint pixelHeightTemp = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); uint pixelDepthTemp = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); uint numberOfArrayElementsTemp = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); uint numberOfFacesTemp = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); uint numberOfMipmapLevelsTemp = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); uint sizeOfKeyValueDataTemp = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); if (sizeOfKeyValueDataTemp % 4 != 0) { return(isValid : false, possibleError : ErrorGen.Modulo4Error(nameof(sizeOfKeyValueDataTemp), sizeOfKeyValueDataTemp)); } // Validate metadata (bool validMedata, string possibleMetadataError) = ValidateMetadata(reader, sizeOfKeyValueDataTemp, shouldSwapEndianness); if (!validMedata) { return(isValid : false, possibleError : possibleMetadataError); } } } catch (Exception e) { return(isValid : false, e.ToString()); } return(isValid : true, possibleError : ""); }