using System; using System.Collections.Generic; using System.Text; using System.IO; namespace DSDecmp.Formats.Nitro { /// /// Decompressor for the LZ-0x10 format used in many of the games for the /// newer Nintendo consoles and handhelds. /// public class LZ10 : NitroCFormat { public LZ10() : base(0x10) { } public override void Decompress(Stream instream, long inLength, Stream outstream) { #region format definition form GBATEK/NDSTEK /* Data header (32bit) Bit 0-3 Reserved Bit 4-7 Compressed type (must be 1 for LZ77) Bit 8-31 Size of decompressed data Repeat below. Each Flag Byte followed by eight Blocks. Flag data (8bit) Bit 0-7 Type Flags for next 8 Blocks, MSB first Block Type 0 - Uncompressed - Copy 1 Byte from Source to Dest Bit 0-7 One data byte to be copied to dest Block Type 1 - Compressed - Copy N+3 Bytes from Dest-Disp-1 to Dest Bit 0-3 Disp MSBs Bit 4-7 Number of bytes to copy (minus 3) Bit 8-15 Disp LSBs */ #endregion long readBytes = 0; byte type = (byte)instream.ReadByte(); if (type != base.magicByte) throw new InvalidDataException("The provided stream is not a valid LZ-0x10 " + "compressed stream (invalid type 0x" + type.ToString("X") + ")"); byte[] sizeBytes = new byte[3]; instream.Read(sizeBytes, 0, 3); int decompressedSize = base.Bytes2Size(sizeBytes); readBytes += 4; if (decompressedSize == 0) { sizeBytes = new byte[4]; instream.Read(sizeBytes, 0, 4); decompressedSize = base.Bytes2Size(sizeBytes); readBytes += 4; } // the maximum 'DISP-1' is 0xFFF. int bufferLength = 0x1000; byte[] buffer = new byte[bufferLength]; int bufferOffset = 0; int currentOutSize = 0; int flags = 0, mask = 0; while (currentOutSize < decompressedSize) { // (throws when requested new flags byte is not available) #region Update the mask. If all flag bits have been read, get a new set. // the current mask is the mask used in the previous run. So if it masks the // last flag bit, get a new flags byte. if (mask == 1) { if (readBytes >= inLength) throw new NotEnoughDataException(currentOutSize, decompressedSize); flags = instream.ReadByte(); readBytes++; if (flags < 0) throw new StreamTooShortException(); mask = 0x80; } else { mask >>= 1; } #endregion // bit = 1 <=> compressed. if ((flags & mask) > 0) { // (throws when < 2 bytes are available) #region Get length and displacement('disp') values from next 2 bytes // there are < 2 bytes available when the end is at most 1 byte away if (readBytes + 1 >= inLength) { // make sure the stream is at the end if (readBytes < inLength) { instream.ReadByte(); readBytes++; } throw new NotEnoughDataException(currentOutSize, decompressedSize); } int byte1 = instream.ReadByte(); readBytes++; int byte2 = instream.ReadByte(); readBytes++; if (byte2 < 0) throw new StreamTooShortException(); // the number of bytes to copy int length = byte1 >> 4; length += 3; // from where the bytes should be copied (relatively) int disp = ((byte1 & 0x0F) << 8) | byte2; disp += 1; if (disp > currentOutSize) throw new InvalidDataException("Cannot go back more than already written. " + "DISP = " + disp + ", #written bytes = 0x" + currentOutSize.ToString("X") + " at 0x" + instream.Position.ToString("X")); #endregion int bufIdx = bufferOffset + bufferLength - disp; for (int i = 0; i < length; i++) { byte next = buffer[bufIdx % bufferLength]; bufIdx++; outstream.WriteByte(next); buffer[bufferOffset] = next; bufferOffset = (bufferOffset + 1) % bufferLength; } } else { if (readBytes >= inLength) throw new NotEnoughDataException(currentOutSize, decompressedSize); int next = instream.ReadByte(); readBytes++; if (next < 0) throw new StreamTooShortException(); currentOutSize++; outstream.WriteByte((byte)next); buffer[bufferOffset] = (byte)next; bufferOffset = (bufferOffset + 1) % bufferLength; } } } public override void Compress(Stream instream, long inLength, Stream outstream) { throw new NotImplementedException(); } } }