From 11a00be4ca31456cf654960f431e2dfd7f9b3cfb Mon Sep 17 00:00:00 2001 From: lifegpc Date: Tue, 14 Oct 2025 11:30:24 +0800 Subject: [PATCH] Add ZSTD support --- ArcFormats/ArcFormats.csproj | 10 +++- ArcFormats/Circus/ImageCRX.cs | 13 ++++- ArcFormats/ZstdStream.cs | 102 ++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 ArcFormats/ZstdStream.cs 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); + } + } + } + } +}