/// <summary> /// Reads the given <see cref="Stream"/>. /// </summary> /// <param name="input">The input <see cref="Stream"/> to read from.</param> public void Read(Stream input) { Reader = new BinaryReader(input); FileSize = Reader.ReadUInt32(); if (FileSize == 0x55AA1234) { throw new InvalidDataException("Use ValvePak library to parse VPK files.\nSee https://github.com/SteamDatabase/ValvePak"); } if (FileSize == CompiledShader.MAGIC) { throw new InvalidDataException("Use CompiledShader() class to parse compiled shader files."); } // TODO: Some real files seem to have different file size if (FileSize != Reader.BaseStream.Length) { //throw new Exception(string.Format("File size does not match size specified in file. {0} != {1}", FileSize, Reader.BaseStream.Length)); } HeaderVersion = Reader.ReadUInt16(); if (HeaderVersion != KnownHeaderVersion) { throw new InvalidDataException(string.Format("Bad header version. ({0} != expected {1})", HeaderVersion, KnownHeaderVersion)); } Version = Reader.ReadUInt16(); var blockOffset = Reader.ReadUInt32(); var blockCount = Reader.ReadUInt32(); Reader.BaseStream.Position += blockOffset - 8; // 8 is 2 uint32s we just read for (var i = 0; i < blockCount; i++) { var blockType = Encoding.UTF8.GetString(Reader.ReadBytes(4)); var position = Reader.BaseStream.Position; var offset = (uint)position + Reader.ReadUInt32(); var size = Reader.ReadUInt32(); Block block = null; // Peek data to detect VKV3 // Valve has deprecated NTRO as reported by resourceinfo.exe // TODO: Find a better way without checking against resource type if (size >= 4 && blockType == "DATA" && !IshandledResourceType(ResourceType)) { Reader.BaseStream.Position = offset; var magic = Reader.ReadUInt32(); if (magic == BinaryKV3.MAGIC || magic == BinaryKV3.MAGIC2 || magic == BinaryKV3.MAGIC3) { block = new BinaryKV3(); } else if (magic == BinaryKV1.MAGIC) { block = new BinaryKV1(); } Reader.BaseStream.Position = position; } if (block == null) { block = ConstructFromType(blockType); } block.Offset = offset; block.Size = size; if (blockType == "REDI" || blockType == "NTRO") { block.Read(Reader, this); } Blocks.Add(block); switch (block.Type) { case BlockType.REDI: // Try to determine resource type by looking at first compiler indentifier if (ResourceType == ResourceType.Unknown && EditInfo.Structs.ContainsKey(ResourceEditInfo.REDIStruct.SpecialDependencies)) { var specialDeps = (SpecialDependencies)EditInfo.Structs[ResourceEditInfo.REDIStruct.SpecialDependencies]; if (specialDeps.List.Count > 0) { ResourceType = DetermineResourceTypeByCompilerIdentifier(specialDeps.List[0]); } } break; case BlockType.NTRO: if (ResourceType == ResourceType.Unknown && IntrospectionManifest.ReferencedStructs.Count > 0) { switch (IntrospectionManifest.ReferencedStructs[0].Name) { case "VSoundEventScript_t": ResourceType = ResourceType.SoundEventScript; break; case "CWorldVisibility": ResourceType = ResourceType.WorldVisibility; break; } } break; } Reader.BaseStream.Position = position + 8; } foreach (var block in Blocks) { if (block.Type != BlockType.REDI && block.Type != BlockType.NTRO) { block.Read(Reader, this); } } }
/// <summary> /// Reads the given <see cref="Stream"/>. /// </summary> /// <param name="input">The input <see cref="Stream"/> to read from.</param> /// <param name="verifyFileSize">Whether to verify that the stream was correctly consumed.</param> public void Read(Stream input, bool verifyFileSize = true) { Reader = new BinaryReader(input); FileSize = Reader.ReadUInt32(); if (FileSize == 0x55AA1234) { throw new InvalidDataException("Use ValvePak library to parse VPK files.\nSee https://github.com/SteamDatabase/ValvePak"); } if (FileSize == ShaderFile.MAGIC) { throw new InvalidDataException("Use CompiledShader() class to parse compiled shader files."); } HeaderVersion = Reader.ReadUInt16(); if (HeaderVersion != KnownHeaderVersion) { throw new UnexpectedMagicException($"Unexpected header (expected {KnownHeaderVersion})", HeaderVersion, nameof(HeaderVersion)); } if (FileName != null) { ResourceType = DetermineResourceTypeByFileExtension(); } Version = Reader.ReadUInt16(); var blockOffset = Reader.ReadUInt32(); var blockCount = Reader.ReadUInt32(); Reader.BaseStream.Position += blockOffset - 8; // 8 is 2 uint32s we just read for (var i = 0; i < blockCount; i++) { var blockType = Encoding.UTF8.GetString(Reader.ReadBytes(4)); var position = Reader.BaseStream.Position; var offset = (uint)position + Reader.ReadUInt32(); var size = Reader.ReadUInt32(); Block block = null; // Peek data to detect VKV3 // Valve has deprecated NTRO as reported by resourceinfo.exe // TODO: Find a better way without checking against resource type if (size >= 4 && blockType == nameof(BlockType.DATA) && !IsHandledResourceType(ResourceType)) { Reader.BaseStream.Position = offset; var magic = Reader.ReadUInt32(); if (magic == BinaryKV3.MAGIC || magic == BinaryKV3.MAGIC2 || magic == BinaryKV3.MAGIC3) { block = new BinaryKV3(); } else if (magic == BinaryKV1.MAGIC) { block = new BinaryKV1(); } Reader.BaseStream.Position = position; } if (block == null) { block = ConstructFromType(blockType); } block.Offset = offset; block.Size = size; Blocks.Add(block); switch (block.Type) { case BlockType.REDI: case BlockType.RED2: block.Read(Reader, this); EditInfo = (ResourceEditInfo)block; // Try to determine resource type by looking at first compiler indentifier if (ResourceType == ResourceType.Unknown && EditInfo.Structs.TryGetValue(ResourceEditInfo.REDIStruct.SpecialDependencies, out var specialBlock)) { var specialDeps = (SpecialDependencies)specialBlock; if (specialDeps.List.Count > 0) { ResourceType = DetermineResourceTypeByCompilerIdentifier(specialDeps.List[0]); } } // Try to determine resource type by looking at the input dependency if there is only one if (ResourceType == ResourceType.Unknown && EditInfo.Structs.TryGetValue(ResourceEditInfo.REDIStruct.InputDependencies, out var inputBlock)) { var inputDeps = (InputDependencies)inputBlock; if (inputDeps.List.Count == 1) { ResourceType = DetermineResourceTypeByFileExtension(Path.GetExtension(inputDeps.List[0].ContentRelativeFilename)); } } break; case BlockType.NTRO: block.Read(Reader, this); if (ResourceType == ResourceType.Unknown && IntrospectionManifest.ReferencedStructs.Count > 0) { switch (IntrospectionManifest.ReferencedStructs[0].Name) { case "VSoundEventScript_t": ResourceType = ResourceType.SoundEventScript; break; case "CWorldVisibility": ResourceType = ResourceType.WorldVisibility; break; } } break; } Reader.BaseStream.Position = position + 8; } foreach (var block in Blocks) { if (block.Type is not BlockType.REDI and not BlockType.RED2 and not BlockType.NTRO) { block.Read(Reader, this); } } if (verifyFileSize && Reader.BaseStream.Length != FullFileSize) { if (ResourceType == ResourceType.Texture) { var data = (Texture)DataBlock; // TODO: We do not currently have a way of calculating buffer size for these types // Texture.GenerateBitmap also just reads until end of the buffer if (data.Format == VTexFormat.JPEG_DXT5 || data.Format == VTexFormat.JPEG_RGBA8888) { return; } } throw new InvalidDataException($"File size ({Reader.BaseStream.Length}) does not match size specified in file ({FullFileSize}) ({ResourceType})."); } }