diff --git a/ArcFormats/ZLibStream.cs b/ArcFormats/ZLibStream.cs new file mode 100644 index 00000000..ac8d72c9 --- /dev/null +++ b/ArcFormats/ZLibStream.cs @@ -0,0 +1,230 @@ +//! \file ZLibStream.cs +//! \date Tue Jul 28 04:34:13 2015 +//! \brief RFC 1950 compatible wrapper around .Net DeflateStream class. +// +// Copyright (C) 2015 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.IO; +using System.IO.Compression; +using GameRes.Utility; + +namespace GameRes.Compression +{ + public enum CompressionMode + { + Compress, + Decompress + } + + /// + /// Enum for backwards compatibility with ZLibNet + /// + public enum CompressionLevel + { + NoCompression = 0, + BestSpeed = 1, + BestCompression = 9, + Default = 6, + Level0 = 0, + Level1 = 1, + Level2 = 2, + Level3 = 3, + Level4 = 4, + Level5 = 5, + Level6 = 6, + Level7 = 7, + Level8 = 8, + Level9 = 9 + } + + public class ZLibStream : Stream + { + DeflateStream m_stream; + CheckedStream m_adler; + bool m_writing; + int m_total_in = 0; + + public Stream BaseStream { get { return m_stream.BaseStream; } } + + /// + /// When compressing: returns total number of uncompressed bytes. Undefined for decompression streams. + /// + public int TotalIn { get { return m_total_in; } } + + public ZLibStream (Stream stream, CompressionMode mode, bool leave_open = false) + : this (stream, mode, CompressionLevel.Default, leave_open) + { + } + + public ZLibStream (Stream stream, CompressionMode mode, CompressionLevel level, bool leave_open = false) + { + if (CompressionMode.Decompress == mode) + InitDecompress (stream, leave_open); + else + InitCompress (stream, level, leave_open); + } + + private void InitDecompress (Stream stream, bool leave_open) + { + int b1 = stream.ReadByte(); + int b2 = stream.ReadByte(); + if (0x78 != b1 || 0 != (b1 << 8 | b2) % 31) + throw new InvalidDataException ("Data not recoginzed as zlib-compressed stream"); + m_stream = new DeflateStream (stream, System.IO.Compression.CompressionMode.Decompress, leave_open); + m_writing = false; + } + + private void InitCompress (Stream stream, CompressionLevel level, bool leave_open) + { + int flevel = (int)level; + System.IO.Compression.CompressionLevel sys_level; + if (0 == flevel) + { + sys_level = System.IO.Compression.CompressionLevel.NoCompression; + } + else if (flevel > 5) + { + sys_level = System.IO.Compression.CompressionLevel.Optimal; + flevel = 3; + } + else + { + sys_level = System.IO.Compression.CompressionLevel.Fastest; + flevel = 1; + } + int cmf = 0x7800 | flevel << 6; + cmf = ((cmf + 30) / 31) * 31; + stream.WriteByte ((byte)(cmf >> 8)); + stream.WriteByte ((byte)cmf); + m_stream = new DeflateStream (stream, sys_level, leave_open); + m_adler = new CheckedStream (m_stream, new Adler32()); + m_writing = true; + } + + #region IO.Stream Members + public override bool CanRead { get { return !m_writing; } } + public override bool CanSeek { get { return false; } } + public override bool CanWrite { get { return m_writing; } } + public override long Length { get { return m_stream.Length; } } + public override long Position + { + get { return m_stream.Position; } + set { m_stream.Position = value; } + } + + public override int Read (byte[] buffer, int offset, int count) + { + return m_stream.Read (buffer, offset, count); + } + + public override int ReadByte () + { + return m_stream.ReadByte(); + } + + public override void Flush() + { + m_stream.Flush(); + } + + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotSupportedException ("ZLibStream.Seek method not supported"); + } + + public override void SetLength (long length) + { + throw new NotSupportedException ("ZLibStream.SetLength method not supported"); + } + + public override void Write (byte[] buffer, int offset, int count) + { + m_adler.Write (buffer, offset, count); + m_total_in += count; + } + + public override void WriteByte (byte value) + { + m_adler.WriteByte (value); + m_total_in++; + } + #endregion + + #region IDisposable Members + bool m_disposed = false; + protected override void Dispose (bool disposing) + { + if (!m_disposed) + { + try + { + if (disposing) + { + if (m_writing) + { + uint checksum = m_adler.CheckSumValue; + m_stream.Flush(); + m_stream.BaseStream.WriteByte ((byte)(checksum >> 24)); + m_stream.BaseStream.WriteByte ((byte)(checksum >> 16)); + m_stream.BaseStream.WriteByte ((byte)(checksum >> 8)); + m_stream.BaseStream.WriteByte ((byte)(checksum)); + m_adler.Dispose(); + } + m_stream.Dispose(); + } + m_disposed = true; + } + finally + { + base.Dispose (disposing); + } + } + } + #endregion + } + + public class ZLibCompressor + { + public static MemoryStream Compress (Stream source) + { + var dest = new MemoryStream(); + using (var zs = new ZLibStream (dest, CompressionMode.Compress, true)) + { + source.CopyTo (zs); + } + dest.Position = 0; + return dest; + } + + public static MemoryStream DeCompress (Stream source) + { + var dest = new MemoryStream(); + using (var zs = new ZLibStream (source, CompressionMode.Decompress, true)) + { + zs.CopyTo (dest); + } + dest.Position = 0; + return dest; + } + } +}