diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj
index 5094504e..7e7a5890 100644
--- a/ArcFormats/ArcFormats.csproj
+++ b/ArcFormats/ArcFormats.csproj
@@ -162,6 +162,7 @@
+
@@ -1266,7 +1267,14 @@
-
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
perl "$(SolutionDir)inc-revision.pl" "$(ProjectPath)" $(ConfigurationName)
diff --git a/ArcFormats/Circus/ImageCRX.cs b/ArcFormats/Circus/ImageCRX.cs
index b204f154..e0e2e10f 100644
--- a/ArcFormats/Circus/ImageCRX.cs
+++ b/ArcFormats/Circus/ImageCRX.cs
@@ -248,11 +248,22 @@ namespace GameRes.Formats.Circus
}
}
+ private Stream GetDecompressStream() {
+ long pos = m_input.Position;
+ uint header = m_input.ReadUInt32();
+ m_input.Position = pos;
+ if (header == 0xFD2FB528) {
+ var zstd = ZstdReader.Unpack(m_input.AsStream);
+ return new MemoryStream(zstd);
+ } else
+ return new ZLibStream(m_input.AsStream, CompressionMode.Decompress);
+ }
+
private void UnpackV2 ()
{
int pixel_size = m_bpp / 8;
int src_stride = m_width * pixel_size;
- using (var zlib = new ZLibStream (m_input.AsStream, CompressionMode.Decompress, true))
+ using (var zlib = GetDecompressStream())
using (var src = new BinaryReader (zlib))
{
if (m_bpp >= 24)
diff --git a/ArcFormats/ZstdStream.cs b/ArcFormats/ZstdStream.cs
new file mode 100644
index 00000000..0c37afa4
--- /dev/null
+++ b/ArcFormats/ZstdStream.cs
@@ -0,0 +1,102 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace GameRes.Compression
+{
+ public static class ZstdReader
+ {
+ private const int BUFFER_SIZE = 4096;
+
+ [DllImport("zstd.dll", CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr ZSTD_createDStream();
+
+ [DllImport("zstd.dll", CallingConvention = CallingConvention.Cdecl)]
+ private static extern int ZSTD_decompressStream(IntPtr zds, ref ZSTD_outBuffer output, ref ZSTD_inBuffer input);
+
+ [DllImport("zstd.dll", CallingConvention = CallingConvention.Cdecl)]
+ private static extern int ZSTD_freeDStream(IntPtr zds);
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct ZSTD_inBuffer
+ {
+ public IntPtr src;
+ public UIntPtr size;
+ public UIntPtr pos;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct ZSTD_outBuffer
+ {
+ public IntPtr dst;
+ public UIntPtr size;
+ public UIntPtr pos;
+ }
+
+ public static byte[] Unpack(Stream compressedStream)
+ {
+ if (compressedStream == null) throw new ArgumentNullException(nameof(compressedStream));
+
+ IntPtr dctx = ZSTD_createDStream();
+ if (dctx == IntPtr.Zero)
+ throw new InvalidOperationException("Failed to create ZSTD_DStream.");
+
+ byte[] inBuf = new byte[BUFFER_SIZE];
+ byte[] outBuf = new byte[BUFFER_SIZE];
+ using (MemoryStream result = new MemoryStream())
+ {
+ try
+ {
+ ZSTD_inBuffer input = new ZSTD_inBuffer();
+ ZSTD_outBuffer output = new ZSTD_outBuffer();
+
+ GCHandle inHandle = default, outHandle = default;
+ try
+ {
+ inHandle = GCHandle.Alloc(inBuf, GCHandleType.Pinned);
+ outHandle = GCHandle.Alloc(outBuf, GCHandleType.Pinned);
+
+ int lastRet;
+ while (true)
+ {
+ int bytesRead = compressedStream.Read(inBuf, 0, inBuf.Length);
+ if (bytesRead == 0) break;
+
+ input.src = inHandle.AddrOfPinnedObject();
+ input.size = (UIntPtr)(uint)bytesRead;
+ input.pos = UIntPtr.Zero;
+
+ while (input.pos.ToUInt64() < input.size.ToUInt64())
+ {
+ output.dst = outHandle.AddrOfPinnedObject();
+ output.size = (UIntPtr)(uint)outBuf.Length;
+ output.pos = UIntPtr.Zero;
+
+ lastRet = ZSTD_decompressStream(dctx, ref output, ref input);
+ if (lastRet < 0)
+ throw new InvalidDataException($"ZSTD_decompressStream error: {lastRet}");
+
+ if (output.pos.ToUInt64() > 0)
+ result.Write(outBuf, 0, (int)output.pos.ToUInt64());
+
+ if (lastRet == 0) break;
+ }
+ }
+ }
+ finally
+ {
+ if (inHandle.IsAllocated) inHandle.Free();
+ if (outHandle.IsAllocated) outHandle.Free();
+ }
+
+ return result.ToArray();
+ }
+ finally
+ {
+ ZSTD_freeDStream(dctx);
+ }
+ }
+ }
+ }
+}