using System; using System.Collections.Generic; using System.Text; using System.IO; namespace DSDecmp.Formats.Nitro { /// /// Compressor and decompressor for the LZ-0x11 format used in many of the games for the /// newer Nintendo consoles and handhelds. /// public class LZ11 : NitroCFormat { public LZ11() : base(0x11) { } public override long Decompress(Stream instream, long inLength, Stream outstream) { #region Format definition in NDSTEK style /* Data header (32bit) Bit 0-3 Reserved Bit 4-7 Compressed type (must be 1 for LZ77) Bit 8-31 Size of decompressed data. if 0, the next 4 bytes are decompressed length 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 LEN Bytes from Dest-Disp-1 to Dest If Reserved is 0: - Default Bit 0-3 Disp MSBs Bit 4-7 LEN - 3 Bit 8-15 Disp LSBs If Reserved is 1: - Higher compression rates for files with (lots of) long repetitions Bit 4-7 Indicator If Indicator > 1: Bit 0-3 Disp MSBs Bit 4-7 LEN - 1 (same bits as Indicator) Bit 8-15 Disp LSBs If Indicator is 1: A(B CD E)(F GH) Bit 0-3 (LEN - 0x111) MSBs Bit 4-7 Indicator; unused Bit 8-15 (LEN- 0x111) 'middle'-SBs Bit 16-19 Disp MSBs Bit 20-23 (LEN - 0x111) LSBs Bit 24-31 Disp LSBs If Indicator is 0: Bit 0-3 (LEN - 0x11) MSBs Bit 4-7 Indicator; unused Bit 8-11 Disp MSBs Bit 12-15 (LEN - 0x11) LSBs Bit 16-23 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-0x11 " + "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 still 0xFFF. int bufferLength = 0x1000; byte[] buffer = new byte[bufferLength]; int bufferOffset = 0; int currentOutSize = 0; int flags = 0, mask = 1; 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 not enough bytes are available) #region Get length and displacement('disp') values from next 2, 3 or 4 bytes // read the first byte first, which also signals the size of the compressed block if (readBytes >= inLength) throw new NotEnoughDataException(currentOutSize, decompressedSize); int byte1 = instream.ReadByte(); readBytes++; if (byte1 < 0) throw new StreamTooShortException(); int length = byte1 >> 4; int disp = -1; if (length == 0) { #region case 0; (0B C)(D EF) + (0x11)(0x1) = (LEN)(DISP) // case 0: // data = AB CD EF (with A=0) // LEN = ABC + 0x11 == BC + 0x11 // DISP = DEF + 1 // we need two more bytes available if (readBytes + 1 >= inLength) throw new NotEnoughDataException(currentOutSize, decompressedSize); int byte2 = instream.ReadByte(); readBytes++; int byte3 = instream.ReadByte(); readBytes++; if (byte3 < 0) throw new StreamTooShortException(); length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11; disp = (((byte2 & 0x0F) << 8) | byte3) + 0x1; #endregion } else if (length == 1) { #region case 1: 1(B CD E)(F GH) + (0x111)(0x1) = (LEN)(DISP) // case 1: // data = AB CD EF GH (with A=1) // LEN = BCDE + 0x111 // DISP = FGH + 1 // we need three more bytes available if (readBytes + 2 >= inLength) throw new NotEnoughDataException(currentOutSize, decompressedSize); int byte2 = instream.ReadByte(); readBytes++; int byte3 = instream.ReadByte(); readBytes++; int byte4 = instream.ReadByte(); readBytes++; if (byte4 < 0) throw new StreamTooShortException(); length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111; disp = (((byte3 & 0x0F) << 8) | byte4) + 0x1; #endregion } else { #region case > 1: (A)(B CD) + (0x1)(0x1) = (LEN)(DISP) // case other: // data = AB CD // LEN = A + 1 // DISP = BCD + 1 // we need only one more byte available if (readBytes >= inLength) throw new NotEnoughDataException(currentOutSize, decompressedSize); int byte2 = instream.ReadByte(); readBytes++; if (byte2 < 0) throw new StreamTooShortException(); length = ((byte1 & 0xF0) >> 4) + 0x1; disp = (((byte1 & 0x0F) << 8) | byte2) + 0x1; #endregion } if (disp > currentOutSize) throw new InvalidDataException("Cannot go back more than already written. " + "DISP = " + disp + ", #written bytes = 0x" + currentOutSize.ToString("X") + " before 0x" + instream.Position.ToString("X") + " with indicator 0x" + (byte1 >> 4).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; } currentOutSize += length; } else { if (readBytes >= inLength) throw new NotEnoughDataException(currentOutSize, decompressedSize); int next = instream.ReadByte(); readBytes++; if (next < 0) throw new StreamTooShortException(); outstream.WriteByte((byte)next); currentOutSize++; buffer[bufferOffset] = (byte)next; bufferOffset = (bufferOffset + 1) % bufferLength; } } if (readBytes < inLength) { // the input may be 4-byte aligned. if ((readBytes ^ (readBytes & 3)) + 4 < inLength) throw new TooMuchInputException(readBytes, inLength); } return decompressedSize; } public override int Compress(Stream instream, long inLength, Stream outstream) { throw new NotImplementedException(); } } }