Skip to content

Commit

Permalink
Merge pull request #1505 from SteamRE/vzip-cleanup
Browse files Browse the repository at this point in the history
Add tests for DepotChunk, validate PK zip magic
  • Loading branch information
xPaw authored Feb 7, 2025
2 parents ad531d9 + 7f33137 commit 293a0ce
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 9 deletions.
20 changes: 17 additions & 3 deletions SteamKit2/SteamKit2/Steam/CDN/DepotChunk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,30 @@ public static int Process( DepotManifest.ChunkData info, ReadOnlySpan<byte> data
try
{
var written = aes.DecryptCbc( data[ iv.Length.. ], iv, buffer, PaddingMode.PKCS7 );
var decryptedStream = new MemoryStream( buffer, 0, written );

if ( buffer.Length > 1 && buffer[ 0 ] == 'V' && buffer[ 1 ] == 'Z' )
if ( buffer.Length < 16 )
{
throw new InvalidDataException( $"Not enough data in the decrypted depot chunk (was {buffer.Length} bytes)." );
}

if ( buffer[ 0 ] == 'V' && buffer[ 1 ] == 'S' && buffer[ 2 ] == 'Z' && buffer[ 3 ] == 'a' ) // Zstd
{
throw new NotImplementedException( "Zstd compressed chunks are not yet implemented in SteamKit." );
}
else if ( buffer[ 0 ] == 'V' && buffer[ 1 ] == 'Z' && buffer[ 2 ] == 'a' ) // LZMA
{
using var decryptedStream = new MemoryStream( buffer, 0, written );
writtenDecompressed = VZipUtil.Decompress( decryptedStream, destination, verifyChecksum: false );
}
else
else if ( buffer[ 0 ] == 'P' && buffer[ 1 ] == 'K' && buffer[ 2 ] == 0x03 && buffer[ 3 ] == 0x04 ) // PKzip
{
using var decryptedStream = new MemoryStream( buffer, 0, written );
writtenDecompressed = ZipUtil.Decompress( decryptedStream, destination, verifyChecksum: false );
}
else
{
throw new InvalidDataException( $"Unexpected depot chunk compression (first four bytes are {Convert.ToHexString( buffer.AsSpan( 0, 4 ) )})." );
}
}
finally
{
Expand Down
10 changes: 5 additions & 5 deletions SteamKit2/SteamKit2/Util/VZipUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ static class VZipUtil
private const int HeaderLength = 7; // magic + version + timestamp/crc
private const int FooterLength = 10; // crc + decompressed size + magic

private const char Version = 'a';
private const byte Version = (byte)'a';

public static int Decompress( MemoryStream ms, byte[] destination, bool verifyChecksum = true )
{
using BinaryReader reader = new BinaryReader( ms );
if ( reader.ReadUInt16() != VZipHeader )
{
throw new Exception( "Expecting VZipHeader at start of stream" );
throw new InvalidDataException( "Expecting VZipHeader at start of stream" );
}

if ( reader.ReadChar() != Version )
if ( reader.ReadByte() != Version )
{
throw new Exception( "Expecting VZip version 'a'" );
throw new InvalidDataException( "Expecting VZip version 'a'" );
}

// Sometimes this is a creation timestamp (e.g. for Steam Client VZips).
Expand All @@ -45,7 +45,7 @@ public static int Decompress( MemoryStream ms, byte[] destination, bool verifyCh

if ( reader.ReadUInt16() != VZipFooter )
{
throw new Exception( "Expecting VZipFooter at end of stream" );
throw new InvalidDataException( "Expecting VZipFooter at end of stream" );
}

if ( destination.Length < sizeDecompressed )
Expand Down
2 changes: 1 addition & 1 deletion SteamKit2/SteamKit2/Util/ZipUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static int Decompress( MemoryStream ms, byte[] destination, bool verifyCh

if ( verifyChecksum && Crc32.HashToUInt32( destination.AsSpan()[ ..sizeDecompressed ] ) != entry.Crc32 )
{
throw new Exception( "Checksum validation failed for decompressed file" );
throw new InvalidDataException( "Checksum validation failed for decompressed file" );
}

return sizeDecompressed;
Expand Down
78 changes: 78 additions & 0 deletions SteamKit2/Tests/DepotChunkFacts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.IO;
using System.IO.Hashing;
using System.Reflection;
using System.Security.Cryptography;
using SteamKit2;
using SteamKit2.CDN;
using Xunit;

namespace Tests
{
public class DepotChunkFacts
{
[Fact]
public void DecryptsAndDecompressesDepotChunkPKZip()
{
var assembly = Assembly.GetExecutingAssembly();
using var stream = assembly.GetManifestResourceStream( "Tests.Files.depot_440_chunk_bac8e2657470b2eb70d6ddcd6c07004be8738697.bin" );
using var ms = new MemoryStream();
stream.CopyTo( ms );
var chunkData = ms.ToArray();

var chunk = new DepotManifest.ChunkData(
id: [], // id is not needed here
checksum: 2130218374,
offset: 0,
comp_length: 320,
uncomp_length: 544
);

var destination = new byte[ chunk.UncompressedLength ];
var writtenLength = DepotChunk.Process( chunk, chunkData, destination, [
0x44, 0xCE, 0x5C, 0x52, 0x97, 0xA4, 0x15, 0xA1,
0xA6, 0xF6, 0x9C, 0x85, 0x60, 0x37, 0xA5, 0xA2,
0xFD, 0xD8, 0x2C, 0xD4, 0x74, 0xFA, 0x65, 0x9E,
0xDF, 0xB4, 0xD5, 0x9B, 0x2A, 0xBC, 0x55, 0xFC
] );

Assert.Equal( chunk.CompressedLength, ( uint )chunkData.Length );
Assert.Equal( chunk.UncompressedLength, ( uint )writtenLength );

var hash = Convert.ToHexString( SHA1.HashData( destination ) );
Assert.Equal( "BAC8E2657470B2EB70D6DDCD6C07004BE8738697", hash );
}

[Fact]
public void DecryptsAndDecompressesDepotChunkVZip()
{
var assembly = Assembly.GetExecutingAssembly();
using var stream = assembly.GetManifestResourceStream( "Tests.Files.depot_232250_chunk_7b8567d9b3c09295cdbf4978c32b348d8e76c750.bin" );
using var ms = new MemoryStream();
stream.CopyTo( ms );
var chunkData = ms.ToArray();

var chunk = new DepotManifest.ChunkData(
id: [], // id is not needed here
checksum: 2894626744,
offset: 0,
comp_length: 304,
uncomp_length: 798
);

var destination = new byte[ chunk.UncompressedLength ];
var writtenLength = DepotChunk.Process( chunk, chunkData, destination, [
0xE5, 0xF6, 0xAE, 0xD5, 0x5E, 0x9E, 0xCE, 0x42,
0x9E, 0x56, 0xB8, 0x13, 0xFB, 0xF6, 0xBF, 0xE9,
0x24, 0xF3, 0xCF, 0x72, 0x97, 0x2F, 0xDB, 0xD0,
0x57, 0x1F, 0xFC, 0xAD, 0x9F, 0x2F, 0x7D, 0xAA,
] );

Assert.Equal( chunk.CompressedLength, ( uint )chunkData.Length );
Assert.Equal( chunk.UncompressedLength, ( uint )writtenLength );

var hash = Convert.ToHexString( SHA1.HashData( destination ) );
Assert.Equal( "7B8567D9B3C09295CDBF4978C32B348D8E76C750", hash );
}
}
}
2 changes: 2 additions & 0 deletions SteamKit2/Tests/Files/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.bin binary
*.manifest binary
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
��Ȕ�Y%�OY.�i֐ϭԐŎ�ō���
�c����*��,�N>�n�TsOآ��˝ ����ުXl��o�`,�4߱�p�E/�[ˆ���#�� T=�<��yӃ��R�"��#� �����<�F�7��.�w���bK�)��*}��CIӞә1�ֆtG���[ա��^Nm�)O'+�ۥ�Z�h�su�(ֻUh7U�1s�g�a*�1F��Ƌ/� >7$��^�9�t?�Y����/_Y�Uu�;�sPbw8 ��w�ު)� �\Q|���A� ܋-���" Cι�1ZWP$
Expand Down
Binary file not shown.

0 comments on commit 293a0ce

Please sign in to comment.