mirror of
https://github.com/Barubary/dsdecmp.git
synced 2024-11-16 15:49:24 +01:00
The start of an attempt to make this program structured better, as well as for easier addition of more formats AND compression for supported formats.
This commit is contained in:
parent
052b9c0677
commit
8c777e2c02
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>8.0.50727</ProductVersion>
|
||||
<ProductVersion>9.0.21022</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
@ -40,6 +40,12 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Exceptions\StreamTooShortException.cs" />
|
||||
<Compile Include="Formats\CompressionFormat.cs" />
|
||||
<Compile Include="Formats\Nitro\LZ10.cs" />
|
||||
<Compile Include="Formats\Nitro\LZ11.cs" />
|
||||
<Compile Include="Formats\Nitro\NitroCFormat.cs" />
|
||||
<Compile Include="Exceptions\NotEnoughDataException.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
39
CSharp/DSDecmp/Exceptions/NotEnoughDataException.cs
Normal file
39
CSharp/DSDecmp/Exceptions/NotEnoughDataException.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace DSDecmp
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception that is thrown by the decompression functions when there
|
||||
/// is not enough data available in order to properly decompress the input.
|
||||
/// </summary>
|
||||
public class NotEnoughDataException : IOException
|
||||
{
|
||||
private long currentOutSize;
|
||||
private long totalOutSize;
|
||||
/// <summary>
|
||||
/// Gets the actual number of written bytes.
|
||||
/// </summary>
|
||||
public long WrittenLength { get { return this.currentOutSize; } }
|
||||
/// <summary>
|
||||
/// Gets the number of bytes that was supposed to be written.
|
||||
/// </summary>
|
||||
public long DesiredLength { get { return this.totalOutSize; } }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new NotEnoughDataException.
|
||||
/// </summary>
|
||||
/// <param name="currentOutSize">The actual number of written bytes.</param>
|
||||
/// <param name="totalOutSize">The desired number of written bytes.</param>
|
||||
public NotEnoughDataException(long currentOutSize, long totalOutSize)
|
||||
: base("Not enough data availble; 0x" + currentOutSize.ToString("X")
|
||||
+ " of " + (totalOutSize < 0 ? "???" : ("0x" + totalOutSize.ToString("X")))
|
||||
+ " bytes written.")
|
||||
{
|
||||
this.currentOutSize = currentOutSize;
|
||||
this.totalOutSize = totalOutSize;
|
||||
}
|
||||
}
|
||||
}
|
19
CSharp/DSDecmp/Exceptions/StreamTooShortException.cs
Normal file
19
CSharp/DSDecmp/Exceptions/StreamTooShortException.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace DSDecmp
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception thrown by the compression or decompression function, indicating that the
|
||||
/// given input length was too large for the given input stream.
|
||||
/// </summary>
|
||||
public class StreamTooShortException : EndOfStreamException
|
||||
{
|
||||
public StreamTooShortException()
|
||||
: base("The end of the stream was reached "
|
||||
+ "before the given amout of data was read.")
|
||||
{ }
|
||||
}
|
||||
}
|
102
CSharp/DSDecmp/Formats/CompressionFormat.cs
Normal file
102
CSharp/DSDecmp/Formats/CompressionFormat.cs
Normal file
@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace DSDecmp.Formats
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for compression formats.
|
||||
/// </summary>
|
||||
public abstract class CompressionFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the decompressor for this format supports the given file. Assumes the
|
||||
/// file exists. Returns false when it is certain that the given file is not supported.
|
||||
/// False positives may occur, as this method should not do any decompression, and
|
||||
/// may mis-interpret a similar file format as compressed.
|
||||
/// </summary>
|
||||
/// <param name="file">The name of the file to check.</param>
|
||||
/// <returns>False if the file can certainly not be decompressed using this decompressor.
|
||||
/// True if the file may potentially be decompressed using this decompressor.</returns>
|
||||
public bool Supports(string file)
|
||||
{
|
||||
// open the file, and delegate to the decompressor-specific code.
|
||||
using (FileStream fstr = new FileStream(file, FileMode.Open))
|
||||
{
|
||||
return this.Supports(fstr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the decompressor for this format supports the data from the given stream.
|
||||
/// Returns false when it is certain that the given data is not supported.
|
||||
/// False positives may occur, as this method should not do any decompression, and may
|
||||
/// mis-interpret a similar data format as compressed.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream that may or may not contain compressed data. The
|
||||
/// position of this stream may change during this call, but will be returned to its
|
||||
/// original position when the method returns.</param>
|
||||
/// <returns>False if the data can certainly not be decompressed using this decompressor.
|
||||
/// True if the data may potentially be decompressed using this decompressor.</returns>
|
||||
public abstract bool Supports(Stream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses the given file, writing the deocmpressed data to the given output file.
|
||||
/// The output file will be overwritten if it already exists.
|
||||
/// Assumes <code>Supports(infile)</code> returns <code>true</code>.
|
||||
/// </summary>
|
||||
/// <param name="infile">The file to decompress.</param>
|
||||
/// <param name="outfile">The target location of the decompressed file.</param>
|
||||
public void Decompress(string infile, string outfile)
|
||||
{
|
||||
// open the two given files, and delegate to the format-specific code.
|
||||
using (FileStream inStream = new FileStream(infile, FileMode.Open),
|
||||
outStream = new FileStream(outfile, FileMode.Create))
|
||||
{
|
||||
this.Decompress(inStream, inStream.Length, outStream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses the given stream, writing the decompressed data to the given output stream.
|
||||
/// Assumes <code>Supports(instream)</code> returns <code>true</code>.
|
||||
/// After this call, the input stream will be positioned at the end of the compressed stream,
|
||||
/// or at the initial position + <code>inLength</code>, whichever comes first.
|
||||
/// </summary>
|
||||
/// <param name="instream">The stream to decompress. At the end of this method, the position
|
||||
/// of this stream is directly after the compressed data.</param>
|
||||
/// <param name="inLength">The length of the input data. Not necessarily all of the
|
||||
/// input data may be read (if there is padding, for example), however never more than
|
||||
/// this number of bytes is read from the input stream.</param>
|
||||
/// <param name="outstream">The stream to write the decompressed data to.</param>
|
||||
/// <exception cref="NotEnoughDataException">When the given length of the input data
|
||||
/// is not enough to properly decompress the input.</exception>
|
||||
public abstract void Decompress(Stream instream, long inLength, Stream outstream);
|
||||
|
||||
/// <summary>
|
||||
/// Compresses the given input file, and writes the compressed data to the given
|
||||
/// output file.
|
||||
/// </summary>
|
||||
/// <param name="infile">The file to compress.</param>
|
||||
/// <param name="outfile">The file to write the compressed data to.</param>
|
||||
public void Compress(string infile, string outfile)
|
||||
{
|
||||
// open the proper Streams, and delegate to the format-specific code.
|
||||
using (FileStream inStream = File.Open(infile, FileMode.Open),
|
||||
outStream = File.Create(outfile))
|
||||
{
|
||||
this.Compress(inStream, inStream.Length, outStream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresses the next <code>inLength</code> bytes from the input stream,
|
||||
/// and writes the compressed data to the given output stream.
|
||||
/// </summary>
|
||||
/// <param name="instream">The stream to read plaintext data from.</param>
|
||||
/// <param name="inLength">The length of the plaintext data.</param>
|
||||
/// <param name="outstream">The stream to write the compressed data to.</param>
|
||||
public abstract void Compress(Stream instream, long inLength, Stream outstream);
|
||||
}
|
||||
}
|
146
CSharp/DSDecmp/Formats/Nitro/LZ10.cs
Normal file
146
CSharp/DSDecmp/Formats/Nitro/LZ10.cs
Normal file
@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace DSDecmp.Formats.Nitro
|
||||
{
|
||||
/// <summary>
|
||||
/// Decompressor for the LZ-0x10 format used in many of the games for the
|
||||
/// newer Nintendo consoles and handhelds.
|
||||
/// </summary>
|
||||
public class LZ10 : NitroCFormat
|
||||
{
|
||||
public LZ10() : base(0x10) { }
|
||||
|
||||
public override void Decompress(Stream instream, long inLength,
|
||||
Stream outstream)
|
||||
{
|
||||
/* 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
|
||||
*/
|
||||
|
||||
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' 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 of 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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
51
CSharp/DSDecmp/Formats/Nitro/LZ11.cs
Normal file
51
CSharp/DSDecmp/Formats/Nitro/LZ11.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace DSDecmp.Formats.Nitro
|
||||
{
|
||||
public class LZ11 : NitroCFormat
|
||||
{
|
||||
public LZ11() : base(0x11) { }
|
||||
|
||||
public override void Decompress(Stream instream, long inLength, Stream outstream)
|
||||
{
|
||||
/* 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:
|
||||
Bit 0-3 and 8-19 LEN - 0x111
|
||||
Bit 20-31 Disp
|
||||
If Indicator is 0:
|
||||
Bit 0-3 and 8-11 LEN - 0x11
|
||||
Bit 12-23 Disp
|
||||
|
||||
*/
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Compress(Stream instream, long inLength, Stream outstream)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
76
CSharp/DSDecmp/Formats/Nitro/NitroCFormat.cs
Normal file
76
CSharp/DSDecmp/Formats/Nitro/NitroCFormat.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace DSDecmp.Formats.Nitro
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for Nitro-based decompressors. Uses the 1-byte magic and 3-byte decompression
|
||||
/// size format.
|
||||
/// </summary>
|
||||
public abstract class NitroCFormat : CompressionFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, Nitro Decompressors will not decompress files that have a decompressed
|
||||
/// size (plaintext size) larger than MaxPlaintextSize.
|
||||
/// </summary>
|
||||
public static bool SkipLargePlaintexts = true;
|
||||
/// <summary>
|
||||
/// The maximum allowed size of the decompressed file (plaintext size) allowed for Nitro
|
||||
/// Decompressors. Only used when SkipLargePlaintexts = true.
|
||||
/// </summary>
|
||||
public static int MaxPlaintextSize = 0x180000;
|
||||
|
||||
/// <summary>
|
||||
/// The first byte of every file compressed with the format for this particular
|
||||
/// Nitro Dcompressor instance.
|
||||
/// </summary>
|
||||
protected byte magicByte;
|
||||
|
||||
public NitroCFormat(byte magicByte)
|
||||
{
|
||||
this.magicByte = magicByte;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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 startPosition = stream.Position;
|
||||
try
|
||||
{
|
||||
int firstByte = stream.ReadByte();
|
||||
if (firstByte != this.magicByte)
|
||||
return false;
|
||||
// no need to read the size info as well if it's used anyway.
|
||||
if (!SkipLargePlaintexts)
|
||||
return true;
|
||||
byte[] sizeBytes = new byte[3];
|
||||
stream.Read(sizeBytes, 0, 3);
|
||||
int outSize = this.Bytes2Size(sizeBytes);
|
||||
if (outSize == 0)
|
||||
{
|
||||
sizeBytes = new byte[4];
|
||||
stream.Read(sizeBytes, 0, 4);
|
||||
outSize = this.Bytes2Size(sizeBytes);
|
||||
}
|
||||
return outSize <= MaxPlaintextSize;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Position = startPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user