Skip to content

Commit 2edc80b

Browse files
committed
Add support for zstd compressed depot chunks
Fixes #1503.
1 parent 446bbe9 commit 2edc80b

File tree

5 files changed

+88
-1
lines changed

5 files changed

+88
-1
lines changed

SteamKit2/SteamKit2/Steam/CDN/DepotChunk.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public static int Process( DepotManifest.ChunkData info, ReadOnlySpan<byte> data
6161

6262
if ( buffer[ 0 ] == 'V' && buffer[ 1 ] == 'S' && buffer[ 2 ] == 'Z' && buffer[ 3 ] == 'a' ) // Zstd
6363
{
64-
throw new NotImplementedException( "Zstd compressed chunks are not yet implemented in SteamKit." );
64+
writtenDecompressed = VZstdUtil.Decompress( buffer.AsSpan( 0, written ), destination, verifyChecksum: false );
6565
}
6666
else if ( buffer[ 0 ] == 'V' && buffer[ 1 ] == 'Z' && buffer[ 2 ] == 'a' ) // LZMA
6767
{

SteamKit2/SteamKit2/SteamKit2.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
</PackageReference>
5656
<PackageReference Include="protobuf-net" Version="3.2.52" />
5757
<PackageReference Include="System.IO.Hashing" Version="9.0.4" />
58+
<PackageReference Include="ZstdSharp.Port" Version="0.8.5" />
5859
</ItemGroup>
5960

6061
</Project>

SteamKit2/SteamKit2/Util/VZstdUtil.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.IO;
4+
using System.IO.Hashing;
5+
using System.Runtime.InteropServices;
6+
7+
namespace SteamKit2
8+
{
9+
static class VZstdUtil
10+
{
11+
private const uint VZstdHeader = 0x615A5356;
12+
13+
public static int Decompress( ReadOnlySpan<byte> buffer, byte[] destination, bool verifyChecksum = true )
14+
{
15+
if ( MemoryMarshal.Read<uint>( buffer ) != VZstdHeader )
16+
{
17+
throw new InvalidDataException( "Expecting VZstdHeader at start of stream" );
18+
}
19+
20+
var crc32 = MemoryMarshal.Read<int>( buffer[ 4.. ] );
21+
var crc32_footer = MemoryMarshal.Read<int>( buffer[ ^15.. ] );
22+
var sizeDecompressed = MemoryMarshal.Read<int>( buffer[ ^11.. ] );
23+
24+
Debug.Assert( crc32 == crc32_footer ); // They write CRC32 twice?
25+
26+
if ( buffer[ ^3 ] != 'z' || buffer[ ^2 ] != 's' || buffer[ ^1 ] != 'v' )
27+
{
28+
throw new InvalidDataException( "Expecting VZstdFooter at end of stream" );
29+
}
30+
31+
if ( destination.Length < sizeDecompressed )
32+
{
33+
throw new ArgumentException( "The destination buffer is smaller than the decompressed data size.", nameof( destination ) );
34+
}
35+
36+
using var zstdDecompressor = new ZstdSharp.Decompressor();
37+
38+
var input = buffer[ 8..^15 ];
39+
40+
if ( !zstdDecompressor.TryUnwrap( input, destination, out var sizeWritten ) || sizeDecompressed != sizeWritten )
41+
{
42+
throw new InvalidDataException( $"Failed to decompress Zstd (expected {sizeDecompressed} bytes, got {sizeWritten})." );
43+
}
44+
45+
if ( verifyChecksum && Crc32.HashToUInt32( destination.AsSpan()[ ..sizeDecompressed ] ) != crc32_footer )
46+
{
47+
throw new InvalidDataException( "CRC does not match decompressed data. VZstd data may be corrupted." );
48+
}
49+
50+
return sizeDecompressed;
51+
}
52+
}
53+
}

SteamKit2/Tests/DepotChunkFacts.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,37 @@ public void DecryptsAndDecompressesDepotChunkVZip()
7474
var hash = Convert.ToHexString( SHA1.HashData( destination ) );
7575
Assert.Equal( "7B8567D9B3C09295CDBF4978C32B348D8E76C750", hash );
7676
}
77+
78+
[Fact]
79+
public void DecryptsAndDecompressesDepotChunkZStd()
80+
{
81+
var assembly = Assembly.GetExecutingAssembly();
82+
using var stream = assembly.GetManifestResourceStream( "Tests.Files.depot_3441461_chunk_9e72678e305540630a665b93e1463bc3983eb55a.bin" );
83+
using var ms = new MemoryStream();
84+
stream.CopyTo( ms );
85+
var chunkData = ms.ToArray();
86+
87+
var chunk = new DepotManifest.ChunkData(
88+
id: [], // id is not needed herein
89+
checksum: 3753325726,
90+
offset: 0,
91+
comp_length: 176,
92+
uncomp_length: 156
93+
);
94+
95+
var destination = new byte[ chunk.UncompressedLength ];
96+
var writtenLength = DepotChunk.Process( chunk, chunkData, destination, [
97+
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
98+
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
99+
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
100+
0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20
101+
] );
102+
103+
Assert.Equal( chunk.CompressedLength, ( uint )chunkData.Length );
104+
Assert.Equal( chunk.UncompressedLength, ( uint )writtenLength );
105+
106+
var hash = Convert.ToHexString( SHA1.HashData( destination ) );
107+
Assert.Equal( "9E72678E305540630A665B93E1463BC3983EB55A", hash );
108+
}
77109
}
78110
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
�˚b�|����tF#_ �< J��ݷ��z��(G�O�X��%94|��X����������)��Z��ϫRn�M`����"��&��>��ΫT[NE��P��!k��Ќ�9K���(ͮŎ楹ڟ��y����ի���=?�l�I0�i`�� i �ٱ����M��x��

0 commit comments

Comments
 (0)