diff --git a/SteamKit2/SteamKit2/Steam/CDN/DepotChunk.cs b/SteamKit2/SteamKit2/Steam/CDN/DepotChunk.cs index 7a8f533c3..10de8409e 100644 --- a/SteamKit2/SteamKit2/Steam/CDN/DepotChunk.cs +++ b/SteamKit2/SteamKit2/Steam/CDN/DepotChunk.cs @@ -61,7 +61,7 @@ public static int Process( DepotManifest.ChunkData info, ReadOnlySpan data 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." ); + writtenDecompressed = VZstdUtil.Decompress( buffer.AsSpan( 0, written ), destination ); } else if ( buffer[ 0 ] == 'V' && buffer[ 1 ] == 'Z' && buffer[ 2 ] == 'a' ) // LZMA { diff --git a/SteamKit2/SteamKit2/SteamKit2.csproj b/SteamKit2/SteamKit2/SteamKit2.csproj index 15f4c81d1..ad97d3c3f 100644 --- a/SteamKit2/SteamKit2/SteamKit2.csproj +++ b/SteamKit2/SteamKit2/SteamKit2.csproj @@ -55,6 +55,7 @@ + diff --git a/SteamKit2/SteamKit2/Util/VZstdUtil.cs b/SteamKit2/SteamKit2/Util/VZstdUtil.cs new file mode 100644 index 000000000..b34e4b112 --- /dev/null +++ b/SteamKit2/SteamKit2/Util/VZstdUtil.cs @@ -0,0 +1,44 @@ +using System; +using System.Buffers; +using System.IO; +using System.Runtime.InteropServices; + +namespace SteamKit2 +{ + static class VZstdUtil + { + private const uint VZstdHeader = 0x56535A61; + + public static int Decompress( ReadOnlySpan buffer, byte[] destination ) + { + if ( MemoryMarshal.Read( buffer ) != VZstdHeader ) + { + throw new InvalidDataException( "Expecting VZstdHeader at start of stream" ); + } + + var sizeCompressed = MemoryMarshal.Read( buffer[ ^15.. ] ); // TODO: I am not convinced this is correct -- maybe its the frame size + var sizeDecompressed = MemoryMarshal.Read( buffer[ ^11.. ] ); + + if ( buffer[ ^3 ] != 'z' || buffer[ ^2 ] != 's' || buffer[ ^1 ] != 'v' ) + { + throw new InvalidDataException( "Expecting VZstdFooter at end of stream" ); + } + + if ( destination.Length < sizeDecompressed ) + { + throw new ArgumentException( "The destination buffer is smaller than the decompressed data size.", nameof( destination ) ); + } + + using var zstdDecompressor = new ZstdSharp.Decompressor(); + + var input = buffer[ 4..^12 ]; + + if ( !zstdDecompressor.TryUnwrap( input, destination, out var sizeWritten ) || sizeDecompressed != sizeWritten ) + { + throw new InvalidDataException( $"Failed to decompress Zstd (expected {sizeDecompressed} bytes, got {sizeWritten})." ); + } + + return sizeDecompressed; + } + } +}