/* This file is part of libWiiSharp * Copyright (C) 2009 Leathl * * libWiiSharp is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * libWiiSharp is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ //Lz77 (de-)compression algorithm based on gbalzss by Andre Perrot (Thanks!) using System; using System.IO; namespace libWiiSharp { public class Lz77 { private static uint lz77Magic = 0x4c5a3737; private const int N = 4096; private const int F = 18; private const int threshold = 2; private int[] leftSon = new int[N + 1]; private int[] rightSon = new int[N + 257]; private int[] dad = new int[N + 1]; private ushort[] textBuffer = new ushort[N + 17]; private int matchPosition = 0, matchLength = 0; /// /// Lz77 Magic. /// public static uint Lz77Magic { get { return lz77Magic; } } #region Public Functions /// /// Checks whether a file is Lz77 compressed or not. /// /// /// public static bool IsLz77Compressed(string file) { return IsLz77Compressed(File.ReadAllBytes(file)); } /// /// Checks whether a file is Lz77 compressed or not. /// /// /// public static bool IsLz77Compressed(byte[] file) { Headers.HeaderType h = Headers.DetectHeader(file); return (Shared.Swap(BitConverter.ToUInt32(file, (int)h)) == lz77Magic) ; } /// /// Checks whether a file is Lz77 compressed or not. /// /// /// public static bool IsLz77Compressed(Stream file) { Headers.HeaderType h = Headers.DetectHeader(file); byte[] temp = new byte[4]; file.Seek((long)h, SeekOrigin.Begin); file.Read(temp, 0, temp.Length); return (Shared.Swap(BitConverter.ToUInt32(temp, 0)) == lz77Magic); } /// /// Compresses a file using the Lz77 algorithm. /// /// /// public void Compress(string inFile, string outFile) { Stream compressedFile; using (FileStream fsIn = new FileStream(inFile, FileMode.Open)) compressedFile = compress(fsIn); byte[] output = new byte[compressedFile.Length]; compressedFile.Read(output, 0, output.Length); if (File.Exists(outFile)) File.Delete(outFile); using (FileStream fs = new FileStream(outFile, FileMode.Create)) fs.Write(output, 0, output.Length); } /// /// Compresses the byte array using the Lz77 algorithm. /// /// /// public byte[] Compress(byte[] file) { return ((MemoryStream)compress(new MemoryStream(file))).ToArray(); } /// /// Compresses the stream using the Lz77 algorithm. /// /// /// public Stream Compress(Stream file) { return compress(file); } /// /// Decompresses a file using the Lz77 algorithm. /// /// /// public void Decompress(string inFile, string outFile) { Stream compressedFile; using (FileStream fsIn = new FileStream(inFile, FileMode.Open)) compressedFile = decompress(fsIn); byte[] output = new byte[compressedFile.Length]; compressedFile.Read(output, 0, output.Length); if (File.Exists(outFile)) File.Delete(outFile); using (FileStream fs = new FileStream(outFile, FileMode.Create)) fs.Write(output, 0, output.Length); } /// /// Decompresses the byte array using the Lz77 algorithm. /// /// /// public byte[] Decompress(byte[] file) { return ((MemoryStream)decompress(new MemoryStream(file))).ToArray(); } public Stream Decompress(Stream file) { return decompress(file); } #endregion #region Private Functions private Stream decompress(Stream inFile) { if (!Lz77.IsLz77Compressed(inFile)) return inFile; inFile.Seek(0, SeekOrigin.Begin); int i, j, k, r, c, z; uint flags, decompressedSize, currentSize = 0; Headers.HeaderType h = Headers.DetectHeader(inFile); byte[] temp = new byte[8]; inFile.Seek((int)h, SeekOrigin.Begin); inFile.Read(temp, 0, 8); if (Shared.Swap(BitConverter.ToUInt32(temp, 0)) != lz77Magic) { inFile.Dispose(); throw new Exception("Invaild Magic!"); } if (temp[4] != 0x10) { inFile.Dispose(); throw new Exception("Unsupported Compression Type!"); } decompressedSize = (BitConverter.ToUInt32(temp, 4)) >> 8; for (i = 0; i < N - F; i++) textBuffer[i] = 0xdf; r = N - F; flags = 7; z = 7; MemoryStream outFile = new MemoryStream(); while (true) { flags <<= 1; z++; if (z == 8) { if ((c = inFile.ReadByte()) == -1) break; flags = (uint)c; z = 0; } if ((flags & 0x80) == 0) { if ((c = inFile.ReadByte()) == inFile.Length - 1) break; if (currentSize < decompressedSize) outFile.WriteByte((byte)c); textBuffer[r++] = (byte)c; r &= (N - 1); currentSize++; } else { if ((i = inFile.ReadByte()) == -1) break; if ((j = inFile.ReadByte()) == -1) break; j = j | ((i << 8) & 0xf00); i = ((i >> 4) & 0x0f) + threshold; for (k = 0; k <= i; k++) { c = textBuffer[(r - j - 1) & (N - 1)]; if (currentSize < decompressedSize) outFile.WriteByte((byte)c); textBuffer[r++] = (byte)c; r &= (N - 1); currentSize++; } } } return outFile; } private Stream compress(Stream inFile) { if (Lz77.IsLz77Compressed(inFile)) return inFile; inFile.Seek(0, SeekOrigin.Begin); int textSize = 0; int codeSize = 0; int i, c, r, s, length, lastMatchLength, codeBufferPointer, mask; int[] codeBuffer = new int[17]; uint fileSize = ((Convert.ToUInt32(inFile.Length)) << 8) + 0x10; MemoryStream outFile = new MemoryStream(); outFile.Write(BitConverter.GetBytes(Shared.Swap(lz77Magic)), 0, 4); outFile.Write(BitConverter.GetBytes(fileSize), 0, 4); InitTree(); codeBuffer[0] = 0; codeBufferPointer = 1; mask = 0x80; s = 0; r = N - F; for (i = s; i < r; i++) textBuffer[i] = 0xffff; for (length = 0; length < F && (c = (int)inFile.ReadByte()) != -1; length++) textBuffer[r + length] = (ushort)c; if ((textSize = length) == 0) return inFile; for (i = 1; i <= F; i++) InsertNode(r - i); InsertNode(r); do { if (matchLength > length) matchLength = length; if (matchLength <= threshold) { matchLength = 1; codeBuffer[codeBufferPointer++] = textBuffer[r]; } else { codeBuffer[0] |= mask; codeBuffer[codeBufferPointer++] = (char) (((r - matchPosition - 1) >> 8) & 0x0f) | ((matchLength - (threshold + 1)) << 4); codeBuffer[codeBufferPointer++] = (char)((r - matchPosition - 1) & 0xff); } if ((mask >>= 1) == 0) { for (i = 0; i < codeBufferPointer; i++) outFile.WriteByte((byte)codeBuffer[i]); codeSize += codeBufferPointer; codeBuffer[0] = 0; codeBufferPointer = 1; mask = 0x80; } lastMatchLength = matchLength; for (i = 0; i < lastMatchLength && (c = (int)inFile.ReadByte()) != -1; i++) { DeleteNode(s); textBuffer[s] = (ushort)c; if (s < F - 1) textBuffer[s + N] = (ushort)c; s = (s + 1) & (N - 1); r = (r + 1) & (N - 1); InsertNode(r); } while (i++ < lastMatchLength) { DeleteNode(s); s = (s + 1) & (N - 1); r = (r + 1) & (N - 1); if (--length != 0) InsertNode(r); } } while (length > 0); if (codeBufferPointer > 1) { for (i = 0; i < codeBufferPointer; i++) outFile.WriteByte((byte)codeBuffer[i]); codeSize += codeBufferPointer; } if (codeSize % 4 != 0) for (i = 0; i < 4 - (codeSize % 4); i++) outFile.WriteByte(0x00); return outFile; } private void InitTree() { int i; for (i = N + 1; i <= N + 256; i++) rightSon[i] = N; for (i = 0; i < N; i++) dad[i] = N; } private void InsertNode(int r) { int i, p, cmp; cmp = 1; p = N + 1 + (textBuffer[r] == 0xffff ? 0 : textBuffer[r]); rightSon[r] = leftSon[r] = N; matchLength = 0; for (; ; ) { if (cmp >= 0) { if (rightSon[p] != N) p = rightSon[p]; else { rightSon[p] = r; dad[r] = p; return; } } else { if (leftSon[p] != N) p = leftSon[p]; else { leftSon[p] = r; dad[r] = p; return; } } for (i = 1; i < F; i++) if ((cmp = textBuffer[r + i] - textBuffer[p + i]) != 0) break; if (i > matchLength) { matchPosition = p; if ((matchLength = i) >= F) break; } } dad[r] = dad[p]; leftSon[r] = leftSon[p]; rightSon[r] = rightSon[p]; dad[leftSon[p]] = r; dad[rightSon[p]] = r; if (rightSon[dad[p]] == p) rightSon[dad[p]] = r; else leftSon[dad[p]] = r; dad[p] = N; } private void DeleteNode(int p) { int q; if (dad[p] == N) return; if (rightSon[p] == N) q = leftSon[p]; else if (leftSon[p] == N) q = rightSon[p]; else { q = leftSon[p]; if (rightSon[q] != N) { do { q = rightSon[q]; } while (rightSon[q] != N); rightSon[dad[q]] = leftSon[q]; dad[leftSon[q]] = dad[q]; leftSon[q] = leftSon[p]; dad[leftSon[p]] = q; } rightSon[q] = rightSon[p]; dad[rightSon[p]] = q; } dad[q] = dad[p]; if (rightSon[dad[p]] == p) rightSon[dad[p]] = q; else leftSon[dad[p]] = q; dad[p] = N; } #endregion } }