From 0f9b368b04ac306bcd6cd6c05fab36c4b0d7d14e Mon Sep 17 00:00:00 2001 From: barubary Date: Wed, 18 May 2011 12:24:16 +0000 Subject: [PATCH] C#: added the Golden Sun: Dark Dawn format in the new structure. This version hasn't been tested yet, and thus hasn't been included in the main program. I also need to think of how to include these game-specific formats in the main program, as they may conflict with built-in formats regarding their header (with GSDD I just try to decompress the file when checking if a file is supported). --- CSharp/DSDecmp/DSDecmp.csproj | 1 + .../Formats/GameSpecific/GoldenSunDD.cs | 232 ++++++++++++++++++ CSharp/DSDecmp/Formats/Nitro/Huffman.cs | 4 +- CSharp/DSDecmp/Formats/Nitro/LZ10.cs | 8 +- CSharp/DSDecmp/Formats/Nitro/LZ11.cs | 4 +- CSharp/DSDecmp/Formats/Nitro/NitroCFormat.cs | 17 +- CSharp/DSDecmp/Formats/Nitro/RLE.cs | 4 +- CSharp/DSDecmp/Utils/IOUtils.cs | 29 +++ 8 files changed, 274 insertions(+), 25 deletions(-) create mode 100644 CSharp/DSDecmp/Formats/GameSpecific/GoldenSunDD.cs diff --git a/CSharp/DSDecmp/DSDecmp.csproj b/CSharp/DSDecmp/DSDecmp.csproj index 311418a..2f80cd7 100644 --- a/CSharp/DSDecmp/DSDecmp.csproj +++ b/CSharp/DSDecmp/DSDecmp.csproj @@ -47,6 +47,7 @@ + diff --git a/CSharp/DSDecmp/Formats/GameSpecific/GoldenSunDD.cs b/CSharp/DSDecmp/Formats/GameSpecific/GoldenSunDD.cs new file mode 100644 index 0000000..59e9677 --- /dev/null +++ b/CSharp/DSDecmp/Formats/GameSpecific/GoldenSunDD.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace DSDecmp.Formats.GameSpecific +{ + public class GoldenSunDD : CompressionFormat + { + public override bool Supports(System.IO.Stream stream, long inLength) + { + long streamStart = stream.Position; + try + { + // because of the specific format, and especially since it overlaps with + // the LZH8 header format, we'll need to try and decompress the file in + // order to check if it is supported. + try + { + using (MemoryStream tempStream = new MemoryStream()) + this.Decompress(stream, inLength, tempStream); + return true; + } + catch (TooMuchInputException) + { + // too much input is still OK. + return true; + } + catch + { + // anything else is not OK. + return false; + } + } + finally + { + stream.Position = streamStart; + } + } + + #region Decompression method + public override long Decompress(System.IO.Stream instream, long inLength, System.IO.Stream outstream) + { + #region format specification + // no NDSTEK-like specification for this one; I seem to not be able to get those right. + /* + * byte tag; // 0x40 + * byte[3] decompressedSize; + * the rest is the data; + * + * for each chunk: + * - first byte determines which blocks are compressed + * - block i is compressed iff: + * - the i'th MSB is the last 1-bit in the byte + * - OR the i'th MSB is a 0-bit, not directly followed by other 0-bits. + * - note that there will never be more than one 0-bit before any 1-bit in this byte + * (look at the corresponding code, it may clarify this a bit more) + * - then come 8 blocks: + * - a non-compressed block is simply one single byte + * - a compressed block can have 3 sizes: + * - A0 CD EF + * -> Length = EF + 0x10, Disp = CDA + * - A1 CD EF GH + * -> Length = GHEF + 0x110, Disp = CDA + * - AB CD (B > 1) + * -> Length = B, Disp = CDA + * Copy bytes from Dest- to Dest (with similar to the NDSTEK specs) + */ + #endregion + + long readBytes = 0; + + byte type = (byte)instream.ReadByte(); + if (type != 0x40) + throw new InvalidDataException("The provided stream is not a valid 'LZ-0x40' " + + "compressed stream (invalid type 0x" + type.ToString("X") + ")"); + byte[] sizeBytes = new byte[3]; + instream.Read(sizeBytes, 0, 3); + int decompressedSize = IOUtils.ToNDSu24(sizeBytes, 0); + readBytes += 4; + if (decompressedSize == 0) + { + sizeBytes = new byte[4]; + instream.Read(sizeBytes, 0, 4); + decompressedSize = IOUtils.ToNDSs32(sizeBytes, 0); + readBytes += 4; + } + + // the maximum 'DISP' is 0xFFF. + int bufferLength = 0x1000; + byte[] buffer = new byte[bufferLength]; + int bufferOffset = 0; + + int currentOutSize = 0; + int currentBlock = 0; + // the expended flag byte + bool[] expandedFlags = null; + 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 (currentBlock == 8) + { + if (readBytes >= inLength) + throw new NotEnoughDataException(currentOutSize, decompressedSize); + int flags = instream.ReadByte(); readBytes++; + if (flags < 0) + throw new StreamTooShortException(); + + // determine which blocks are compressed + int b = 0; + expandedFlags = new bool[8]; + while (flags > 0) + { + bool bit = (flags & 0x80) > 0; + flags = (flags & 0x7F) << 1; + expandedFlags[b++] = (flags == 0) || !bit; + } + + currentBlock = 0; + } + else + { + currentBlock++; + } + #endregion + + // bit = 1 <=> compressed. + if (expandedFlags[currentBlock]) + { + // (throws when < 2, 3 or 4 bytes are available) + #region Get length and displacement('disp') values from next 2, 3 or 4 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(); + + int disp, length; + disp = (byte1 >> 4) + (byte2 << 4); + if (disp > currentOutSize) + throw new InvalidDataException("Cannot go back more than already written. " + + "DISP = 0x" + disp.ToString("X") + ", #written bytes = 0x" + currentOutSize.ToString("X") + + " at 0x" + (instream.Position - 2).ToString("X")); + + switch (byte1 & 0x0F) + { + case 0: + { + if (readBytes >= inLength) + throw new NotEnoughDataException(currentOutSize, decompressedSize); + int byte3 = instream.ReadByte(); readBytes++; + if (byte3 < 0) + throw new StreamTooShortException(); + length = byte3 + 0x10; + break; + } + case 1: + { + if (readBytes + 1 >= inLength) + throw new NotEnoughDataException(currentOutSize, decompressedSize); + int byte3 = instream.ReadByte(); readBytes++; + int byte4 = instream.ReadByte(); readBytes++; + if (byte4 < 0) + throw new StreamTooShortException(); + length = ((byte3 << 8) + byte4) + 0x110; + break; + } + default: + { + length = byte1 & 0x0F; + break; + } + } + #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(); + + currentOutSize++; + outstream.WriteByte((byte)next); + buffer[bufferOffset] = (byte)next; + bufferOffset = (bufferOffset + 1) % bufferLength; + } + outstream.Flush(); + } + + if (readBytes < inLength) + { + // the input may be 4-byte aligned. + if ((readBytes ^ (readBytes & 3)) + 4 < inLength) + throw new TooMuchInputException(readBytes, inLength); + } + + return decompressedSize; + } + #endregion + + public override int Compress(System.IO.Stream instream, long inLength, System.IO.Stream outstream) + { + throw new NotImplementedException(); + } + } +} diff --git a/CSharp/DSDecmp/Formats/Nitro/Huffman.cs b/CSharp/DSDecmp/Formats/Nitro/Huffman.cs index cdd61cc..d9d3829 100644 --- a/CSharp/DSDecmp/Formats/Nitro/Huffman.cs +++ b/CSharp/DSDecmp/Formats/Nitro/Huffman.cs @@ -71,13 +71,13 @@ namespace DSDecmp.Formats.Nitro + "compressed stream (invalid type 0x" + type.ToString("X") + "); unknown block size."); byte[] sizeBytes = new byte[3]; instream.Read(sizeBytes, 0, 3); - int decompressedSize = base.Bytes2Size(sizeBytes); + int decompressedSize = IOUtils.ToNDSu24(sizeBytes, 0); readBytes += 4; if (decompressedSize == 0) { sizeBytes = new byte[4]; instream.Read(sizeBytes, 0, 4); - decompressedSize = base.Bytes2Size(sizeBytes); + decompressedSize = IOUtils.ToNDSs32(sizeBytes, 0); readBytes += 4; } diff --git a/CSharp/DSDecmp/Formats/Nitro/LZ10.cs b/CSharp/DSDecmp/Formats/Nitro/LZ10.cs index 5d55ae2..7ce63de 100644 --- a/CSharp/DSDecmp/Formats/Nitro/LZ10.cs +++ b/CSharp/DSDecmp/Formats/Nitro/LZ10.cs @@ -35,7 +35,7 @@ namespace DSDecmp.Formats.Nitro public override long Decompress(Stream instream, long inLength, Stream outstream) { - #region format definition form GBATEK/NDSTEK + #region format definition from GBATEK/NDSTEK /* Data header (32bit) Bit 0-3 Reserved Bit 4-7 Compressed type (must be 1 for LZ77) @@ -60,13 +60,13 @@ namespace DSDecmp.Formats.Nitro + "compressed stream (invalid type 0x" + type.ToString("X") + ")"); byte[] sizeBytes = new byte[3]; instream.Read(sizeBytes, 0, 3); - int decompressedSize = base.Bytes2Size(sizeBytes); + int decompressedSize = IOUtils.ToNDSu24(sizeBytes, 0); readBytes += 4; if (decompressedSize == 0) { sizeBytes = new byte[4]; instream.Read(sizeBytes, 0, 4); - decompressedSize = base.Bytes2Size(sizeBytes); + decompressedSize = IOUtils.ToNDSs32(sizeBytes, 0); readBytes += 4; } @@ -130,7 +130,7 @@ namespace DSDecmp.Formats.Nitro if (disp > currentOutSize) throw new InvalidDataException("Cannot go back more than already written. " + "DISP = 0x" + disp.ToString("X") + ", #written bytes = 0x" + currentOutSize.ToString("X") - + " at 0x" + instream.Position.ToString("X")); + + " at 0x" + (instream.Position - 2).ToString("X")); #endregion int bufIdx = bufferOffset + bufferLength - disp; diff --git a/CSharp/DSDecmp/Formats/Nitro/LZ11.cs b/CSharp/DSDecmp/Formats/Nitro/LZ11.cs index aa8ae76..8cfada1 100644 --- a/CSharp/DSDecmp/Formats/Nitro/LZ11.cs +++ b/CSharp/DSDecmp/Formats/Nitro/LZ11.cs @@ -74,13 +74,13 @@ namespace DSDecmp.Formats.Nitro + "compressed stream (invalid type 0x" + type.ToString("X") + ")"); byte[] sizeBytes = new byte[3]; instream.Read(sizeBytes, 0, 3); - int decompressedSize = base.Bytes2Size(sizeBytes); + int decompressedSize = IOUtils.ToNDSu24(sizeBytes, 0); readBytes += 4; if (decompressedSize == 0) { sizeBytes = new byte[4]; instream.Read(sizeBytes, 0, 4); - decompressedSize = base.Bytes2Size(sizeBytes); + decompressedSize = IOUtils.ToNDSs32(sizeBytes, 0); readBytes += 4; } diff --git a/CSharp/DSDecmp/Formats/Nitro/NitroCFormat.cs b/CSharp/DSDecmp/Formats/Nitro/NitroCFormat.cs index babdafa..1199d73 100644 --- a/CSharp/DSDecmp/Formats/Nitro/NitroCFormat.cs +++ b/CSharp/DSDecmp/Formats/Nitro/NitroCFormat.cs @@ -32,19 +32,6 @@ namespace DSDecmp.Formats.Nitro this.magicByte = magicByte; } - /// - /// Converts an array of (at least) 3 bytes into an integer, using the format used - /// in Nitro compression formats to store the decompressed size. - /// If the size is not 3, the fourth byte will also be included. - /// - protected int Bytes2Size(byte[] bytes) - { - if (bytes.Length == 3) - return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16); - else - return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); - } - public override bool Supports(System.IO.Stream stream, long inLength) { long startPosition = stream.Position; @@ -58,12 +45,12 @@ namespace DSDecmp.Formats.Nitro return true; byte[] sizeBytes = new byte[3]; stream.Read(sizeBytes, 0, 3); - int outSize = this.Bytes2Size(sizeBytes); + int outSize = IOUtils.ToNDSu24(sizeBytes, 0); if (outSize == 0) { sizeBytes = new byte[4]; stream.Read(sizeBytes, 0, 4); - outSize = this.Bytes2Size(sizeBytes); + outSize = (int)IOUtils.ToNDSu32(sizeBytes, 0); } return outSize <= MaxPlaintextSize; } diff --git a/CSharp/DSDecmp/Formats/Nitro/RLE.cs b/CSharp/DSDecmp/Formats/Nitro/RLE.cs index c385799..7914b7e 100644 --- a/CSharp/DSDecmp/Formats/Nitro/RLE.cs +++ b/CSharp/DSDecmp/Formats/Nitro/RLE.cs @@ -35,13 +35,13 @@ namespace DSDecmp.Formats.Nitro + "compressed stream (invalid type 0x" + type.ToString("X") + ")"); byte[] sizeBytes = new byte[3]; instream.Read(sizeBytes, 0, 3); - int decompressedSize = base.Bytes2Size(sizeBytes); + int decompressedSize = IOUtils.ToNDSu24(sizeBytes, 0); readBytes += 4; if (decompressedSize == 0) { sizeBytes = new byte[4]; instream.Read(sizeBytes, 0, 4); - decompressedSize = base.Bytes2Size(sizeBytes); + decompressedSize = IOUtils.ToNDSs32(sizeBytes, 0); readBytes += 4; } diff --git a/CSharp/DSDecmp/Utils/IOUtils.cs b/CSharp/DSDecmp/Utils/IOUtils.cs index f98c27f..7a30fca 100644 --- a/CSharp/DSDecmp/Utils/IOUtils.cs +++ b/CSharp/DSDecmp/Utils/IOUtils.cs @@ -24,6 +24,21 @@ namespace DSDecmp | (buffer[offset + 3] << 24)); } + /// + /// Returns a 4-byte signed integer as used on the NDS converted from four bytes + /// at a specified position in a byte array. + /// + /// The source of the data. + /// The location of the data in the source. + /// The indicated 4 bytes converted to int + public static int ToNDSs32(byte[] buffer, int offset) + { + return (int)(buffer[offset] + | (buffer[offset + 1] << 8) + | (buffer[offset + 2] << 16) + | (buffer[offset + 3] << 24)); + } + /// /// Converts a u32 value into a sequence of bytes that would make ToNDSu32 return /// the given input value. @@ -37,5 +52,19 @@ namespace DSDecmp (byte)((value >> 24) & 0xFF) }; } + + /// + /// Returns a 3-byte integer as used in the built-in compression + /// formats in the DS, convrted from three bytes at a specified position in a byte array, + /// + /// The source of the data. + /// The location of the data in the source. + /// The indicated 3 bytes converted to an integer. + public static int ToNDSu24(byte[] buffer, int offset) + { + return (int)(buffer[offset] + | (buffer[offset + 1] << 8) + | (buffer[offset + 2] << 16)); + } } }