C#: A step into a new system, where game-specific formats can be defined as plugins, preventing users from having to download DSDecmp itself again and again. Such plugins must be stores in a 'Plugins' folder in the same location as the DSDecmp.exe.

To keep the base program mobile, all native NDS formats do not require plugins.

The 8-bit Huffman format cannot be used to compress at the moment; thanks to CUE a bug has been found where the offset-field of a node overflows, corrupting the compressed file.
This commit is contained in:
barubary 2011-11-13 16:14:45 +00:00
parent c1994426f3
commit d64137221a
29 changed files with 2635 additions and 296 deletions

View File

@ -1,8 +1,12 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual C# Express 2010
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DSDecmp", "DSDecmp\DSDecmp.csproj", "{E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoldenSunDD", "GoldenSunDD\GoldenSunDD.csproj", "{8CE72663-0036-4A94-BD70-99AFE7CEEC0C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuminousArc", "LuminousArc\LuminousArc.csproj", "{4BD8DF5C-E971-45D1-B170-340D22DDB351}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -13,6 +17,14 @@ Global
{E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Release|Any CPU.Build.0 = Release|Any CPU
{8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Release|Any CPU.Build.0 = Release|Any CPU
{4BD8DF5C-E971-45D1-B170-340D22DDB351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4BD8DF5C-E971-45D1-B170-340D22DDB351}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4BD8DF5C-E971-45D1-B170-340D22DDB351}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4BD8DF5C-E971-45D1-B170-340D22DDB351}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -36,6 +36,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DocumentationFile>bin\Release\DSDecmp.XML</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
@ -44,17 +45,19 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Exceptions\InputTooLargeException.cs" />
<Compile Include="Exceptions\NotEnoughDataException.cs" />
<Compile Include="Exceptions\StreamTooShortException.cs" />
<Compile Include="Exceptions\TooMuchInputException.cs" />
<Compile Include="Formats\CompressionFormat.cs" />
<Compile Include="Formats\GameSpecific\GoldenSunDD.cs" />
<Compile Include="Formats\LZOvl.cs" />
<Compile Include="Formats\CompositeFormat.cs" />
<Compile Include="Formats\Nitro\CompositeFormats.cs" />
<Compile Include="Formats\Nitro\Huffman.cs" />
<Compile Include="Formats\Nitro\LZ10.cs" />
<Compile Include="Formats\Nitro\LZ11.cs" />
<Compile Include="Formats\LZOvl.cs" />
<Compile Include="Formats\Nitro\NitroCFormat.cs" />
<Compile Include="Exceptions\NotEnoughDataException.cs" />
<Compile Include="Formats\Nitro\RLE.cs" />
<Compile Include="NewestProgram.cs" />
<Compile Include="NewProgram.cs" />
<Compile Include="TestProgram.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@ -62,6 +65,7 @@
<Compile Include="Utils\LZUtil.cs" />
<Compile Include="Utils\SimpleReversePrioQueue.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -4,8 +4,15 @@ using System.Text;
namespace DSDecmp
{
/// <summary>
/// An exception indicating that the file cannot be compressed, because the decompressed size
/// cannot be represented in the current compression format.
/// </summary>
public class InputTooLargeException : Exception
{
/// <summary>
/// Creates a new exception that indicates that the input is too big to be compressed.
/// </summary>
public InputTooLargeException()
: base("The compression ratio is not high enough to fit the input "
+ "in a single compressed file.") { }

View File

@ -11,6 +11,9 @@ namespace DSDecmp
/// </summary>
public class StreamTooShortException : EndOfStreamException
{
/// <summary>
/// Creates a new exception that indicates that the stream was shorter than the given input length.
/// </summary>
public StreamTooShortException()
: base("The end of the stream was reached "
+ "before the given amout of data was read.")

View File

@ -4,6 +4,10 @@ using System.Text;
namespace DSDecmp
{
/// <summary>
/// An exception indication that the input has more data than required in order
/// to decompress it. This may indicate that more sub-files are present in the file.
/// </summary>
public class TooMuchInputException : Exception
{
/// <summary>

View File

@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace DSDecmp.Formats
{
public abstract class CompositeFormat : CompressionFormat
{
private List<CompressionFormat> formats;
protected CompositeFormat(IEnumerable<CompressionFormat> formats)
{
this.formats = new List<CompressionFormat>(formats);
}
protected CompositeFormat(params CompressionFormat[] formats)
{
this.formats = new List<CompressionFormat>(formats);
}
public override bool Supports(System.IO.Stream stream, long inLength)
{
foreach (CompositeFormat fmt in this.formats)
{
if (fmt.Supports(stream, inLength))
return true;
}
return false;
}
public override long Decompress(System.IO.Stream instream, long inLength, System.IO.Stream outstream)
{
byte[] inputData = new byte[instream.Length];
instream.Read(inputData, 0, inputData.Length);
foreach (CompressionFormat format in this.formats)
{
if (!format.SupportsDecompression)
continue;
using (MemoryStream input = new MemoryStream(inputData))
{
if (!format.Supports(input, inputData.Length))
continue;
MemoryStream output = new MemoryStream();
try
{
long decLength = format.Decompress(input, inputData.Length, output);
if (decLength > 0)
{
output.WriteTo(outstream);
return decLength;
}
}
catch (Exception) { continue; }
}
}
throw new InvalidDataException("Input cannot be decompressed using the " + this.ShortFormatString + " formats.");
}
public string LastUsedCompressFormatString { get; private set; }
public override int Compress(System.IO.Stream instream, long inLength, System.IO.Stream outstream)
{
// only read the input data once from the file.
byte[] inputData = new byte[instream.Length];
instream.Read(inputData, 0, inputData.Length);
MemoryStream bestOutput = null;
string bestFormatString = "";
int minCompSize = int.MaxValue;
foreach (CompressionFormat format in formats)
{
if (!format.SupportsCompression)
continue;
#region compress the file in each format, and save the best one
MemoryStream currentOutput = new MemoryStream();
int currentOutSize;
try
{
using (MemoryStream input = new MemoryStream(inputData))
{
currentOutSize = format.Compress(input, input.Length, currentOutput);
}
}
catch (InputTooLargeException i)
{
Console.WriteLine(i.Message);
bestFormatString = format.ShortFormatString;
return -1;
}
catch (Exception)
{
continue;
}
if (currentOutSize < minCompSize)
{
bestOutput = currentOutput;
minCompSize = currentOutSize;
bestFormatString = format.ShortFormatString;
}
#endregion
}
if (bestOutput == null)
return -1;
bestOutput.WriteTo(outstream);
this.LastUsedCompressFormatString = bestFormatString;
return minCompSize;
}
public override int ParseCompressionOptions(string[] args)
{
// try each option on each of the formats.
// each pass over the formats lets them try to consume the options.
// if one or more formats consume at least one option, the maximum number
// of consumed options is treated as 'handled'; they are ignored in the
// next pass. This continues until none of the formats consume the next
// value in the options.
int totalOptionCount = 0;
bool usedOption = true;
while (usedOption)
{
usedOption = false;
if (args.Length <= totalOptionCount)
break;
int maxOptionCount = 0;
string[] subArray = new string[args.Length - totalOptionCount];
Array.Copy(args, totalOptionCount, subArray, 0, subArray.Length);
foreach (CompressionFormat format in this.formats)
{
int optCount = format.ParseCompressionOptions(subArray);
maxOptionCount = Math.Max(optCount, maxOptionCount);
}
if (maxOptionCount > 0)
{
totalOptionCount += maxOptionCount;
usedOption = true;
}
}
return totalOptionCount;
}
}
}

View File

@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Text;
using System.IO;
namespace DSDecmp.Formats
namespace DSDecmp
{
/// <summary>
/// Base class for compression formats.
/// Base class for all compression formats.
/// </summary>
public abstract class CompressionFormat
{
@ -86,7 +86,7 @@ namespace DSDecmp.Formats
/// </summary>
/// <param name="infile">The file to compress.</param>
/// <param name="outfile">The file to write the compressed data to.</param>
/// <returns>The size of the compressed file.</returns>
/// <returns>The size of the compressed file. If -1, the file could not be compressed.</returns>
public int Compress(string infile, string outfile)
{
// make sure the output directory exists
@ -108,7 +108,35 @@ namespace DSDecmp.Formats
/// <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>
/// <returns>The size of the compressed stream.</returns>
/// <returns>The size of the compressed stream. If -1, the file could not be compressed.</returns>
public abstract int Compress(Stream instream, long inLength, Stream outstream);
/// <summary>
/// Gets a short string identifying this compression format.
/// </summary>
public abstract string ShortFormatString { get; }
/// <summary>
/// Gets a short description of this compression format (used in the program usage).
/// </summary>
public abstract string Description { get; }
/// <summary>
/// Gets if this format supports compressing a file.
/// </summary>
public abstract bool SupportsCompression { get; }
/// <summary>
/// Gets if this format supports decompressing a file.
/// </summary>
public virtual bool SupportsDecompression { get { return true; } }
/// <summary>
/// Gets the value that must be given on the command line in order to compress using this format.
/// </summary>
public abstract string CompressionFlag { get; }
/// <summary>
/// Parses any input specific for this format. Does nothing by default.
/// </summary>
/// <param name="args">Any arguments that may be used by the format.</param>
/// <returns>The number of input arguments consumed by this format.</returns>
public virtual int ParseCompressionOptions(string[] args) { return 0; }
}
}

View File

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Text;
using System.IO;
using DSDecmp.Utils;
namespace DSDecmp.Formats
{
@ -12,8 +11,28 @@ namespace DSDecmp.Formats
/// Note that the last 12 bytes should not be included in the 'inLength' argument when
/// decompressing arm9.bin. This is done automatically if a file is given instead of a stream.
/// </summary>
public class LZOvl : CompressionFormat
public sealed class LZOvl : CompressionFormat
{
public override string ShortFormatString
{
get { return "LZ-Ovl"; }
}
public override string Description
{
get { return "Reverse LZ format, mainly used in 'overlay' files of NDS games."; }
}
public override string CompressionFlag
{
get { return "lzovl"; }
}
public override bool SupportsCompression
{
get { return true; }
}
private static bool lookAhead = false;
/// <summary>
/// Sets the flag that determines if 'look-ahead'/DP should be used when compressing
@ -25,6 +44,17 @@ namespace DSDecmp.Formats
set { lookAhead = value; }
}
public override int ParseCompressionOptions(string[] args)
{
if (args.Length > 0)
if (args[0] == "-opt")
{
LookAhead = true;
return 1;
}
return 0;
}
#region Method: Supports(string file)
public override bool Supports(string file)
{

View File

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DSDecmp.Formats.Nitro
{
public class CompositeGBAFormat : CompositeFormat
{
public CompositeGBAFormat()
: base(new Huffman4(), new Huffman8(), new LZ10()) { }
public override string ShortFormatString
{
get { return "GBA"; }
}
public override string Description
{
get { return "All formats natively supported by the GBA."; }
}
public override bool SupportsCompression
{
get { return true; }
}
public override string CompressionFlag
{
get { return "gba*"; }
}
}
public class CompositeNDSFormat : CompositeFormat
{
public CompositeNDSFormat()
: base(new Huffman4(), new Huffman8(), new LZ10(), new LZ11()) { }
public override string ShortFormatString
{
get { return "NDS"; }
}
public override string Description
{
get { return "All formats natively supported by the NDS."; }
}
public override bool SupportsCompression
{
get { return true; }
}
public override string CompressionFlag
{
get { return "nds*"; }
}
}
}

View File

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Text;
using System.IO;
using DSDecmp.Utils;
namespace DSDecmp.Formats.Nitro
{
@ -10,29 +9,24 @@ namespace DSDecmp.Formats.Nitro
/// Compressor and decompressor for the Huffman format used in many of the games for the
/// newer Nintendo consoles and handhelds.
/// </summary>
public class Huffman : NitroCFormat
public abstract class Huffman : NitroCFormat
{
public enum BlockSize : byte { FOURBIT = 0x24, EIGHTBIT = 0x28 }
/// <summary>
/// Sets the block size used when using the Huffman format to compress.
/// </summary>
public static BlockSize CompressBlockSize { get; set; }
public BlockSize CompressBlockSize { get; set; }
static Huffman()
public override bool SupportsCompression
{
CompressBlockSize = BlockSize.EIGHTBIT;
get { return true; }
}
public Huffman() : base(0) { }
public override bool Supports(System.IO.Stream stream, long inLength)
public Huffman(BlockSize blockSize)
: base((byte)blockSize)
{
base.magicByte = (byte)BlockSize.FOURBIT;
if (base.Supports(stream, inLength))
return true;
base.magicByte = (byte)BlockSize.EIGHTBIT;
return base.Supports(stream, inLength);
this.CompressBlockSize = blockSize;
}
#region Decompression method
@ -63,10 +57,7 @@ namespace DSDecmp.Formats.Nitro
long readBytes = 0;
byte type = (byte)instream.ReadByte();
BlockSize blockSize = BlockSize.FOURBIT;
if (type != (byte)blockSize)
blockSize = BlockSize.EIGHTBIT;
if (type != (byte)blockSize)
if (type != (byte)this.CompressBlockSize)
throw new InvalidDataException("The provided stream is not a valid Huffman "
+ "compressed stream (invalid type 0x" + type.ToString("X") + "); unknown block size.");
byte[] sizeBytes = new byte[3];
@ -147,7 +138,7 @@ namespace DSDecmp.Formats.Nitro
#endregion
#region write the data in the current node (when possible)
switch (blockSize)
switch (this.CompressBlockSize)
{
case BlockSize.EIGHTBIT:
{
@ -175,7 +166,7 @@ namespace DSDecmp.Formats.Nitro
break;
}
default:
throw new Exception("Unknown block size " + blockSize.ToString());
throw new Exception("Unknown block size " + this.CompressBlockSize.ToString());
}
#endregion
@ -200,18 +191,265 @@ namespace DSDecmp.Formats.Nitro
}
#endregion
public override int Compress(Stream instream, long inLength, Stream outstream)
#region Utility method: GetLowest(leafQueue, nodeQueue, out prio)
/// <summary>
/// Gets the tree node with the lowest priority (frequency) from the leaf and node queues.
/// If the priority is the same for both head items in the queues, the node from the leaf queue is picked.
/// </summary>
protected HuffTreeNode GetLowest(SimpleReversedPrioQueue<int, HuffTreeNode> leafQueue, SimpleReversedPrioQueue<int, HuffTreeNode> nodeQueue, out int prio)
{
switch (CompressBlockSize)
if (leafQueue.Count == 0)
return nodeQueue.Dequeue(out prio);
else if (nodeQueue.Count == 0)
return leafQueue.Dequeue(out prio);
else
{
case BlockSize.FOURBIT:
return Compress4(instream, inLength, outstream);
case BlockSize.EIGHTBIT:
return Compress8(instream, inLength, outstream);
default:
throw new Exception("Unhandled BlockSize " + CompressBlockSize);
int leafPrio, nodePrio;
leafQueue.Peek(out leafPrio);
nodeQueue.Peek(out nodePrio);
// pick a node from the leaf queue when the priorities are equal.
if (leafPrio <= nodePrio)
return leafQueue.Dequeue(out prio);
else
return nodeQueue.Dequeue(out prio);
}
}
#endregion
#region Utility class: HuffTreeNode
/// <summary>
/// A single node in a Huffman tree.
/// </summary>
public class HuffTreeNode
{
/// <summary>
/// The data contained in this node. May not mean anything when <code>isData == false</code>
/// </summary>
private byte data;
/// <summary>
/// A flag indicating if this node has been filled.
/// </summary>
private bool isFilled;
/// <summary>
/// The data contained in this node. May not mean anything when <code>isData == false</code>.
/// Throws a NullReferenceException when this node has not been defined (ie: reference was outside the
/// bounds of the tree definition)
/// </summary>
public byte Data
{
get
{
if (!this.isFilled) throw new NullReferenceException("Reference to an undefined node in the huffman tree.");
return this.data;
}
}
/// <summary>
/// A flag indicating if this node contains data. If not, this is not a leaf node.
/// </summary>
private bool isData;
/// <summary>
/// Returns true if this node represents data.
/// </summary>
public bool IsData { get { return this.isData; } }
/// <summary>
/// The child of this node at side 0
/// </summary>
private HuffTreeNode child0;
/// <summary>
/// The child of this node at side 0
/// </summary>
public HuffTreeNode Child0 { get { return this.child0; } }
/// <summary>
/// The child of this node at side 1
/// </summary>
private HuffTreeNode child1;
/// <summary>
/// The child of this node at side 1
/// </summary>
public HuffTreeNode Child1 { get { return this.child1; } }
/// <summary>
/// The parent node of this node.
/// </summary>
public HuffTreeNode Parent { get; private set; }
/// <summary>
/// Determines if this is the Child0 of the parent node. Assumes there is a parent.
/// </summary>
public bool IsChild0 { get { return this.Parent.child0 == this; } }
/// <summary>
/// Determines if this is the Child1 of the parent node. Assumes there is a parent.
/// </summary>
public bool IsChild1 { get { return this.Parent.child1 == this; } }
private int depth;
/// <summary>
/// Get or set the depth of this node. Will not be set automatically, but
/// will be set recursively (the depth of all child nodes will be updated when this is set).
/// </summary>
public int Depth
{
get { return this.depth; }
set
{
this.depth = value;
// recursively set the depth of the child nodes.
if (!this.isData)
{
this.child0.Depth = this.depth + 1;
this.child1.Depth = this.depth + 1;
}
}
}
/// <summary>
/// Calculates the size of the sub-tree with this node as root.
/// </summary>
public int Size
{
get
{
if (this.IsData)
return 1;
return 1 + this.child0.Size + this.child1.Size;
}
}
/// <summary>
/// Returns a seuqnce over the nodes of the sub-tree with this node as root in a pre-order fashion. (Root-Left-Right)
/// </summary>
public IEnumerable<HuffTreeNode> PreOrderTraversal
{
get
{
yield return this;
if (!this.IsData)
{
foreach (HuffTreeNode c in this.child0.PreOrderTraversal)
yield return c;
foreach (HuffTreeNode c in this.child1.PreOrderTraversal)
yield return c;
}
}
}
/// <summary>
/// Manually creates a new node for a huffman tree.
/// </summary>
/// <param name="data">The data for this node.</param>
/// <param name="isData">If this node represents data.</param>
/// <param name="child0">The child of this node on the 0 side.</param>
/// <param name="child1">The child of this node on the 1 side.</param>
public HuffTreeNode(byte data, bool isData, HuffTreeNode child0, HuffTreeNode child1)
{
this.data = data;
this.isData = isData;
this.child0 = child0;
this.child1 = child1;
this.isFilled = true;
if (!isData)
{
this.child0.Parent = this;
this.child1.Parent = this;
}
}
/// <summary>
/// Creates a new node in the Huffman tree.
/// </summary>
/// <param name="stream">The stream to read from. It is assumed that there is (at least)
/// one more byte available to read.</param>
/// <param name="isData">If this node is a data-node.</param>
/// <param name="relOffset">The offset of this node in the source data, relative to the start
/// of the compressed file.</param>
/// <param name="maxStreamPos">The indicated end of the huffman tree. If the stream is past
/// this position, the tree is invalid.</param>
public HuffTreeNode(Stream stream, bool isData, long relOffset, long maxStreamPos)
{
/*
Tree Table (list of 8bit nodes, starting with the root node)
Root Node and Non-Data-Child Nodes are:
Bit0-5 Offset to next child node,
Next child node0 is at (CurrentAddr AND NOT 1)+Offset*2+2
Next child node1 is at (CurrentAddr AND NOT 1)+Offset*2+2+1
Bit6 Node1 End Flag (1=Next child node is data)
Bit7 Node0 End Flag (1=Next child node is data)
Data nodes are (when End Flag was set in parent node):
Bit0-7 Data (upper bits should be zero if Data Size is less than 8)
*/
if (stream.Position >= maxStreamPos)
{
// this happens when part of the tree is unused.
this.isFilled = false;
return;
}
this.isFilled = true;
int readData = stream.ReadByte();
if (readData < 0)
throw new StreamTooShortException();
this.data = (byte)readData;
this.isData = isData;
if (!this.isData)
{
int offset = this.data & 0x3F;
bool zeroIsData = (this.data & 0x80) > 0;
bool oneIsData = (this.data & 0x40) > 0;
// off AND NOT 1 == off XOR (off AND 1)
long zeroRelOffset = (relOffset ^ (relOffset & 1)) + offset * 2 + 2;
long currStreamPos = stream.Position;
// position the stream right before the 0-node
stream.Position += (zeroRelOffset - relOffset) - 1;
// read the 0-node
this.child0 = new HuffTreeNode(stream, zeroIsData, zeroRelOffset, maxStreamPos);
this.child0.Parent = this;
// the 1-node is directly behind the 0-node
this.child1 = new HuffTreeNode(stream, oneIsData, zeroRelOffset + 1, maxStreamPos);
this.child1.Parent = this;
// reset the stream position to right behind this node's data
stream.Position = currStreamPos;
}
}
public override string ToString()
{
if (this.isData)
{
return "<" + this.data.ToString("X2") + ">";
}
else
{
return "[" + this.child0.ToString() + "," + this.child1.ToString() + "]";
}
}
}
#endregion
}
public class Huffman4 : Huffman
{
public override string ShortFormatString
{
get { return "Huffman-4"; }
}
public override string Description
{
get { return "Huffman compression scheme using 4-bit datablocks."; }
}
public override string CompressionFlag
{
get { return "huff4"; }
}
public Huffman4()
: base(BlockSize.FOURBIT) { }
#region 4-bit block size Compression method
/// <summary>
@ -221,7 +459,7 @@ namespace DSDecmp.Formats.Nitro
/// <param name="inLength">The length of the input stream.</param>
/// <param name="outstream">The stream to write the decompressed data to.</param>
/// <returns>The size of the decompressed data.</returns>
private int Compress4(Stream instream, long inLength, Stream outstream)
public override int Compress(Stream instream, long inLength, Stream outstream)
{
if (inLength > 0xFFFFFF)
throw new InputTooLargeException();
@ -375,6 +613,27 @@ namespace DSDecmp.Formats.Nitro
return compressedLength;
}
#endregion
}
public class Huffman8 : Huffman
{
public override string ShortFormatString
{
get { return "Huffman-8"; }
}
public override string Description
{
get { return "Huffman compression scheme using 8-bit datablocks."; }
}
public override string CompressionFlag
{
get { return "huff8"; }
}
public Huffman8()
: base(BlockSize.EIGHTBIT) { }
#region 8-bit block size Compression method
/// <summary>
@ -384,7 +643,7 @@ namespace DSDecmp.Formats.Nitro
/// <param name="inLength">The length of the input stream.</param>
/// <param name="outstream">The stream to write the decompressed data to.</param>
/// <returns>The size of the decompressed data.</returns>
private int Compress8(Stream instream, long inLength, Stream outstream)
public override int Compress(Stream instream, long inLength, Stream outstream)
{
if (inLength > 0xFFFFFF)
throw new InputTooLargeException();
@ -452,9 +711,33 @@ namespace DSDecmp.Formats.Nitro
outstream.WriteByte((byte)((nodeCount - 1) / 2));
compressedLength++;
// use a breadth-first traversal to store the tree, such that we do not need to store/calculate the side of each sub-tree.
LinkedList<HuffTreeNode> printQueue = new LinkedList<HuffTreeNode>();
printQueue.AddLast(root);
// use a breadth-first traversal to store the tree, such that we do not need to store/calculate the size of each sub-tree.
// NO! BF results in an ordering that may overflow the offset field. use pre-order instead (Self-Left-Right)
foreach (HuffTreeNode node in root.PreOrderTraversal)
{
if (node.Parent == null) // root node.
{
// bits 0-5: 'offset' = # nodes in queue left
// bit 6: node1 end flag
// bit 7: node0 end flag
byte data = 0;
if (node.Child0.IsData)
data |= 0x80;
if (node.Child1.IsData)
data |= 0x40;
outstream.WriteByte(data);
}
if (node.IsData)
continue;
else
{
// bits 0-5: 'offset': if this is left node, 0. if right node
// bit 6: node1 end flag
// bit 7: node0 end flag
}
compressedLength++;
}
/*
while (printQueue.Count > 0)
{
HuffTreeNode node = printQueue.First.Value;
@ -476,11 +759,11 @@ namespace DSDecmp.Formats.Nitro
data |= 0x40;
outstream.WriteByte(data);
printQueue.AddLast(node.Child0);
printQueue.AddLast(node.Child1);
printQueue.AddFirst(node.Child1);
printQueue.AddFirst(node.Child0);
}
compressedLength++;
}
}/**/
#endregion
@ -530,211 +813,31 @@ namespace DSDecmp.Formats.Nitro
return compressedLength;
}
#endregion
}
/// <summary>
/// Gets the tree node with the lowest priority (frequency) from the leaf and node queues.
/// If the priority is the same for both head items in the queues, the node from the leaf queue is picked.
/// </summary>
private HuffTreeNode GetLowest(SimpleReversedPrioQueue<int, HuffTreeNode> leafQueue, SimpleReversedPrioQueue<int, HuffTreeNode> nodeQueue, out int prio)
public class HuffmanAny : CompositeFormat
{
public HuffmanAny()
: base(new Huffman4(), new Huffman8()) { }
public override string ShortFormatString
{
if (leafQueue.Count == 0)
return nodeQueue.Dequeue(out prio);
else if (nodeQueue.Count == 0)
return leafQueue.Dequeue(out prio);
else
{
int leafPrio, nodePrio;
leafQueue.Peek(out leafPrio);
nodeQueue.Peek(out nodePrio);
// pick a node from the leaf queue when the priorities are equal.
if (leafPrio <= nodePrio)
return leafQueue.Dequeue(out prio);
else
return nodeQueue.Dequeue(out prio);
}
get { return "Huffman"; }
}
#region Utility class: HuffTreeNode
/// <summary>
/// A single node in a Huffman tree.
/// </summary>
public class HuffTreeNode
public override string Description
{
/// <summary>
/// The data contained in this node. May not mean anything when <code>isData == false</code>
/// </summary>
private byte data;
/// <summary>
/// A flag indicating if this node has been filled.
/// </summary>
private bool isFilled;
/// <summary>
/// The data contained in this node. May not mean anything when <code>isData == false</code>.
/// Throws a NullReferenceException when this node has not been defined (ie: reference was outside the
/// bounds of the tree definition)
/// </summary>
public byte Data
{
get
{
if (!this.isFilled) throw new NullReferenceException("Reference to an undefined node in the huffman tree.");
return this.data;
}
}
/// <summary>
/// A flag indicating if this node contains data. If not, this is not a leaf node.
/// </summary>
private bool isData;
/// <summary>
/// Returns true if this node represents data.
/// </summary>
public bool IsData { get { return this.isData; } }
/// <summary>
/// The child of this node at side 0
/// </summary>
private HuffTreeNode child0;
/// <summary>
/// The child of this node at side 0
/// </summary>
public HuffTreeNode Child0 { get { return this.child0; } }
/// <summary>
/// The child of this node at side 1
/// </summary>
private HuffTreeNode child1;
/// <summary>
/// The child of this node at side 1
/// </summary>
public HuffTreeNode Child1 { get { return this.child1; } }
/// <summary>
/// The parent node of this node.
/// </summary>
public HuffTreeNode Parent { get; private set; }
/// <summary>
/// Determines if this is the Child0 of the parent node. Assumes there is a parent.
/// </summary>
public bool IsChild0 { get { return this.Parent.child0 == this; } }
/// <summary>
/// Determines if this is the Child1 of the parent node. Assumes there is a parent.
/// </summary>
public bool IsChild1 { get { return this.Parent.child1 == this; } }
private int depth;
/// <summary>
/// Get or set the depth of this node. Will not be set automatically, but
/// will be set recursively (the depth of all child nodes will be updated when this is set).
/// </summary>
public int Depth
{
get { return this.depth; }
set
{
this.depth = value;
// recursively set the depth of the child nodes.
if (!this.isData)
{
this.child0.Depth = this.depth + 1;
this.child1.Depth = this.depth + 1;
}
}
}
/// <summary>
/// Manually creates a new node for a huffman tree.
/// </summary>
/// <param name="data">The data for this node.</param>
/// <param name="isData">If this node represents data.</param>
/// <param name="child0">The child of this node on the 0 side.</param>
/// <param name="child1">The child of this node on the 1 side.</param>
public HuffTreeNode(byte data, bool isData, HuffTreeNode child0, HuffTreeNode child1)
{
this.data = data;
this.isData = isData;
this.child0 = child0;
this.child1 = child1;
this.isFilled = true;
if (!isData)
{
this.child0.Parent = this;
this.child1.Parent = this;
}
}
/// <summary>
/// Creates a new node in the Huffman tree.
/// </summary>
/// <param name="stream">The stream to read from. It is assumed that there is (at least)
/// one more byte available to read.</param>
/// <param name="isData">If this node is a data-node.</param>
/// <param name="relOffset">The offset of this node in the source data, relative to the start
/// of the compressed file.</param>
/// <param name="maxStreamPos">The indicated end of the huffman tree. If the stream is past
/// this position, the tree is invalid.</param>
public HuffTreeNode(Stream stream, bool isData, long relOffset, long maxStreamPos)
{
/*
Tree Table (list of 8bit nodes, starting with the root node)
Root Node and Non-Data-Child Nodes are:
Bit0-5 Offset to next child node,
Next child node0 is at (CurrentAddr AND NOT 1)+Offset*2+2
Next child node1 is at (CurrentAddr AND NOT 1)+Offset*2+2+1
Bit6 Node1 End Flag (1=Next child node is data)
Bit7 Node0 End Flag (1=Next child node is data)
Data nodes are (when End Flag was set in parent node):
Bit0-7 Data (upper bits should be zero if Data Size is less than 8)
*/
if (stream.Position >= maxStreamPos)
{
// this happens when part of the tree is unused.
this.isFilled = false;
return;
}
this.isFilled = true;
int readData = stream.ReadByte();
if (readData < 0)
throw new StreamTooShortException();
this.data = (byte)readData;
this.isData = isData;
if (!this.isData)
{
int offset = this.data & 0x3F;
bool zeroIsData = (this.data & 0x80) > 0;
bool oneIsData = (this.data & 0x40) > 0;
// off AND NOT 1 == off XOR (off AND 1)
long zeroRelOffset = (relOffset ^ (relOffset & 1)) + offset * 2 + 2;
long currStreamPos = stream.Position;
// position the stream right before the 0-node
stream.Position += (zeroRelOffset - relOffset) - 1;
// read the 0-node
this.child0 = new HuffTreeNode(stream, zeroIsData, zeroRelOffset, maxStreamPos);
this.child0.Parent = this;
// the 1-node is directly behind the 0-node
this.child1 = new HuffTreeNode(stream, oneIsData, zeroRelOffset + 1, maxStreamPos);
this.child1.Parent = this;
// reset the stream position to right behind this node's data
stream.Position = currStreamPos;
}
}
public override string ToString()
{
if (this.isData)
{
return "<" + this.data.ToString("X2") + ">";
}
else
{
return "[" + this.child0.ToString() + "," + this.child1.ToString() + "]";
}
}
get { return "Either the Huffman-4 or Huffman-8 format."; }
}
public override bool SupportsCompression
{
get { return true; }
}
public override string CompressionFlag
{
get { return "huff"; }
}
#endregion
}
}

View File

@ -2,16 +2,34 @@
using System.Collections.Generic;
using System.Text;
using System.IO;
using DSDecmp.Utils;
namespace DSDecmp.Formats.Nitro
{
/// <summary>
/// Compressor and 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 sealed class LZ10 : NitroCFormat
{
public override string ShortFormatString
{
get { return "LZ-10"; }
}
public override string Description
{
get { return "Common LZ-type compression used in many post-GBC Nintendo games."; }
}
public override string CompressionFlag
{
get { return "lz10"; }
}
public override bool SupportsCompression
{
get { return true; }
}
private static bool lookAhead = false;
/// <summary>
/// Sets the flag that determines if 'look-ahead'/DP should be used when compressing
@ -25,6 +43,17 @@ namespace DSDecmp.Formats.Nitro
public LZ10() : base(0x10) { }
public override int ParseCompressionOptions(string[] args)
{
if (args.Length > 0)
if (args[0] == "-opt")
{
LookAhead = true;
return 1;
}
return 0;
}
#region 'Original' Decompression method
/// <summary>
/// Decompress a stream that is compressed in the LZ-10 format.

View File

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Text;
using System.IO;
using DSDecmp.Utils;
namespace DSDecmp.Formats.Nitro
{
@ -10,8 +9,27 @@ 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.
/// </summary>
public class LZ11 : NitroCFormat
public sealed class LZ11 : NitroCFormat
{
public override string ShortFormatString
{
get { return "LZ-11"; }
}
public override string Description
{
get { return "Variant of the LZ-0x10 format to support longer repetitions."; }
}
public override string CompressionFlag
{
get { return "lz11"; }
}
public override bool SupportsCompression
{
get { return true; }
}
private static bool lookAhead = false;
/// <summary>
@ -26,6 +44,18 @@ namespace DSDecmp.Formats.Nitro
public LZ11() : base(0x11) { }
public override int ParseCompressionOptions(string[] args)
{
LookAhead = false;
if (args.Length > 0)
if (args[0] == "-opt")
{
LookAhead = true;
return 1;
}
return 0;
}
#region Decompression method
public override long Decompress(Stream instream, long inLength, Stream outstream)
{

View File

@ -9,8 +9,28 @@ namespace DSDecmp.Formats.Nitro
/// Compressor and decompressor for the RLE format used in several of the games for the
/// newer Nintendo consoles and handhelds.
/// </summary>
public class RLE : NitroCFormat
public sealed class RLE : NitroCFormat
{
public override string ShortFormatString
{
get { return "RLE"; }
}
public override string Description
{
get { return "Run-Length Encoding used in some modern Nintendo games."; }
}
public override string CompressionFlag
{
get { return "rle"; }
}
public override bool SupportsCompression
{
get { return true; }
}
public RLE() : base(0x30) { }
public override long Decompress(Stream instream, long inLength, Stream outstream)

View File

@ -1,20 +1,23 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using DSDecmp.Formats.Nitro;
using DSDecmp.Formats;
using System.IO;
namespace DSDecmp
{
public static class NewProgram
{
/// <summary>
/// The formats allowed when compressing a file.
/// </summary>
public enum Formats
{
LZOVL, // keep this as the first one, as only the end of a file may be LZ-ovl-compressed (and overlay files are oftenly double-compressed)
LZOVL, // keep this as the first one, as only the end of a file may be LZ-ovl-compressed (and overlay files are oftenly double-compressed) (it needs to be attempted first when decompressing)
LZ10,
LZ11,
HUFF4,
@ -22,10 +25,10 @@ namespace DSDecmp
RLE,
HUFF,
NDS,
GBA,
GBA
}
public static void Main(string[] args)
public static void MainNewOld(string[] args)
{
if (args.Length == 0)
{
@ -275,8 +278,8 @@ namespace DSDecmp
case Formats.LZ11: fmt = new LZ11(); break;
case Formats.LZOVL: fmt = new LZOvl(); break;
case Formats.RLE: fmt = new RLE(); break;
case Formats.HUFF4: Huffman.CompressBlockSize = Huffman.BlockSize.FOURBIT; fmt = new Huffman(); break;
case Formats.HUFF8: Huffman.CompressBlockSize = Huffman.BlockSize.EIGHTBIT; fmt = new Huffman(); break;
case Formats.HUFF4: fmt = new Huffman4(); break;
case Formats.HUFF8: fmt = new Huffman8(); break;
case Formats.HUFF:
return CompressHuff(infile, output, out actualFormat);
case Formats.GBA:
@ -339,12 +342,15 @@ namespace DSDecmp
CompressionFormat realFormat = null;
switch (format)
{
case Formats.HUFF4: Huffman.CompressBlockSize = Huffman.BlockSize.FOURBIT; realFormat = new Huffman(); break;
case Formats.HUFF8: Huffman.CompressBlockSize = Huffman.BlockSize.EIGHTBIT; realFormat = new Huffman(); break;
case Formats.HUFF4: realFormat = new Huffman4(); break;
case Formats.HUFF8: realFormat = new Huffman8(); break;
case Formats.LZ10: realFormat = new LZ10(); break;
case Formats.LZ11: realFormat = new LZ11(); break;
case Formats.LZOVL: realFormat = new LZOvl(); break;
case Formats.RLE: realFormat = new RLE(); break;
default:
Console.WriteLine("Unsupported single format: "+format);
continue;
}
int currentOutSize;
@ -450,7 +456,7 @@ namespace DSDecmp
{
using (MemoryStream inStream = new MemoryStream(inData))
{
decSize = Decompress(inStream, decompressedData, f);
decSize = Decompress(inStream, decompressedData, null);
if (decSize >= 0)
{
usedFormat = f;
@ -515,29 +521,13 @@ namespace DSDecmp
}
private static long Decompress(MemoryStream inputStream, MemoryStream output, Formats format)
private static long Decompress(MemoryStream inputStream, MemoryStream output, CompressionFormat format)
{
CompressionFormat realFormat = null;
switch (format)
{
case Formats.HUFF:
realFormat = new Huffman(); break;
case Formats.LZ10:
realFormat = new LZ10(); break;
case Formats.LZ11:
realFormat = new LZ11(); break;
case Formats.LZOVL:
realFormat = new LZOvl(); break;
case Formats.RLE:
realFormat = new RLE(); break;
default:
return -1;
}
if (!realFormat.Supports(inputStream, inputStream.Length))
if (!format.Supports(inputStream, inputStream.Length))
return -1;
try
{
return realFormat.Decompress(inputStream, inputStream.Length, output);
return format.Decompress(inputStream, inputStream.Length, output);
}
catch (TooMuchInputException e)
{
@ -546,7 +536,7 @@ namespace DSDecmp
}
catch (Exception e)
{
Console.WriteLine("Could not decompress using the " + format.ToString() + " format; " + e.Message);
Console.WriteLine("Could not decompress using the " + format.ShortFormatString + " format; " + e.Message);
return -1;
}
}

View File

@ -0,0 +1,563 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
using DSDecmp.Formats;
using DSDecmp.Formats.Nitro;
namespace DSDecmp
{
class NewestProgram
{
#if DEBUG
public static string PluginFolder = "../../../PluginDistro/Debug";
#else
public static string PluginFolder = "./Plugins";
#endif
public static void Main(string[] args)
{
// I/O input:
// file -> read from file, save to file
// folder [-co] -> read all files from folder, save to folder_dec or folder_cmp (when decomp or comp resp.) (Same filenames)
// file newfile -> read from file, save to newfile
// folderin folderout [-co] -> read all files from folderin, save to folderout. (Same filenames)
// file1 file2 ... -> read file1, file2, etc
// file1 file2 ... folderout [-co] -> read file1, file2, etc, save to folderout. (same filenames)
// -> when -co is present, all files that could not be (de)compressed will be copied instead.
// preambles:
// <nothing> -> decompress input to output using first matched format
// -d [-ge] -> decompress input to output using first matched format. If -ge, then guess the extension based on first 4 bytes.
// -d [-ge] -f <format> -> decompress input to output using the indicated format. If -ge, then guess the extension based on first 4 bytes.
// -c <format> [opt1 opt2 ...] -> compress input to output using the specified format and its options.
// built-in formats:
// lz10 -> LZ-0x10, found in >= GBA
// lz11 -> LZ-0x11, found in >= NDS
// lzovl -> LZ-Ovl/Overlay / backwards LZ, found mostly in NDS overlay files.
// huff4 -> 4-bit Huffman, found in >= GBA
// huff8 -> 8-bit Huffman, found in >= GBA
// huff -> any Huffman format.
// gba* -> any format natively supported by the GBA
// nds* -> any format natively supported by the NDS, but not LZ-Ovl
// when compressing, the best format of the selected set is used. when decompression,
// only the formats in the selected set are used.
if (args.Length == 0)
{
PrintUsage();
//Console.ReadLine();
return;
}
if (args[0] == "-c")
{
if (args.Length <= 2) { Console.WriteLine("Too few arguments."); return; }
CompressionFormat format = FirstOrDefault(GetFormat(args[1]));
if (format == null) { return; }
string[] ioArgs = new string[args.Length - 2];
Array.Copy(args, 2, ioArgs, 0, ioArgs.Length);
int optionCount = format.ParseCompressionOptions(ioArgs);
string[] realIoArgs = new string[ioArgs.Length - optionCount];
Array.Copy(ioArgs, optionCount, realIoArgs, 0, realIoArgs.Length);
Compress(realIoArgs, format);
}
else if (args[0] == "-d")
{
if (args.Length <= 1) { PrintUsage(); return; }
int ioIdx = 1;
bool guessExtension = false;
if (args[ioIdx] == "-ge")
{
guessExtension = true;
ioIdx++;
}
IEnumerable<CompressionFormat> formats = GetAllFormats(false); // we do not need the built-in composite formats to decompress.
if (args[ioIdx] == "-f")
{
if (args.Length <= ioIdx + 2) { Console.WriteLine("Too few arguments."); return; }
formats = GetFormat(args[ioIdx + 1]);
ioIdx += 2;
}
if (formats == null) { return; }
if (args.Length <= ioIdx) { Console.WriteLine("Too few arguments."); return; }
string[] ioArgs = new string[args.Length - ioIdx];
Array.Copy(args, ioIdx, ioArgs, 0, ioArgs.Length);
Decompress(ioArgs, formats, guessExtension);
}
else
{
Decompress(args, GetAllFormats(false), false);
}
}
#region Usage printer
private static void PrintUsage()
{
Console.WriteLine("DSDecmp - Decompressor for compression formats used on the NDS - by Barubary");
Console.WriteLine();
Console.WriteLine("Usage:\tDSDecmp FMTARGS IOARGS");
Console.WriteLine();
Console.WriteLine("IOARGS can be:");
Console.WriteLine("-------------------------------------------------------------------------------");
Console.WriteLine("file -> read the file, overwrite it.");
Console.WriteLine("folder [-co] -> read all files from folder, save to folder_dec");
Console.WriteLine(" or folder_cmp.");
Console.WriteLine("file newfile -> read the file, save it to newfile.");
Console.WriteLine(" (newfile cannot exist yet)");
Console.WriteLine("folderin folderout [-co] -> read all files from folderin, save to folderout.");
Console.WriteLine("file1 file2 ... -> read file1, file2, etc; overwrite them.");
Console.WriteLine("file1 file2 ... folderout [-co] -> read file1, file2, etc; save to folderout.");
Console.WriteLine();
Console.WriteLine("When -co is present, all files that could not be handled will be copied to the");
Console.WriteLine(" indicated output folder.");
Console.WriteLine("-------------------------------------------------------------------------------");
Console.WriteLine();
Console.WriteLine("FMTARGS can be:");
Console.WriteLine("-------------------------------------------------------------------------------");
Console.WriteLine("<nothing> -> try to decompress input to output.");
Console.WriteLine("-d [-ge] -> try to decompress input to output.");
Console.WriteLine("-d [-ge] -f <format> -> try to decompress input to output, using fiven format");
Console.WriteLine("-c <format> [opt1 ...] -> compress intput to output using given format ");
Console.WriteLine(" and options.");
Console.WriteLine();
Console.WriteLine("When -ge is present, the extension of the output file will be determined by");
Console.WriteLine(" the first 4 bytes of the decompressed data. (of those are alphanuemric ASCII");
Console.WriteLine(" characters).");
Console.WriteLine("-------------------------------------------------------------------------------");
Console.WriteLine("Supported formats:");
Console.WriteLine("<format> -> description");
foreach (CompressionFormat fmt in GetAllFormats(true))
{
Console.WriteLine(fmt.CompressionFlag.PadRight(7, ' ') + "-> " + fmt.Description);
}
Console.WriteLine("-------------------------------------------------------------------------------");
}
#endregion
#region Method: Decompress(string[] ioArgs, IEnumerable<CompressionFormat> formats)
private static void Decompress(string[] ioArgs, IEnumerable<CompressionFormat> formats, bool guessExtension)
{
string[] inputFiles;
string outputDir;
bool copyErrors;
if (!ParseIOArguments(ioArgs, false, out inputFiles, out outputDir, out copyErrors))
return;
foreach (string input in inputFiles)
{
string outputFile = outputDir ?? IOUtils.GetParent(input);
if (Directory.Exists(outputDir))
outputFile = Path.Combine(outputFile + Path.DirectorySeparatorChar, Path.GetFileName(input));
try
{
// read the file only once.
byte[] inputData;
using (Stream inStream = File.OpenRead(input))
{
inputData = new byte[inStream.Length];
inStream.Read(inputData, 0, inputData.Length);
}
bool decompressed = false;
foreach (CompressionFormat format in formats)
{
#region try to decompress using the current format
using (MemoryStream inStr = new MemoryStream(inputData),
outStr = new MemoryStream())
{
if (!format.Supports(inStr, input.Length))
continue;
try
{
long decompSize = format.Decompress(inStr, inputData.Length, outStr);
if (decompSize < 0)
continue;
if (guessExtension)
{
string outFileName = Path.GetFileNameWithoutExtension(outputFile);
outStr.Position = 0;
byte[] magic = new byte[4];
outStr.Read(magic, 0, 4);
outStr.Position = 0;
outFileName += "." + GuessExtension(magic, Path.GetExtension(outputFile).Substring(1));
outputFile = outputFile.Replace(Path.GetFileName(outputFile), outFileName);
}
using (FileStream output = File.Create(outputFile))
{
outStr.WriteTo(output);
}
decompressed = true;
Console.WriteLine(format.ShortFormatString + "-decompressed " + input + " to " + outputFile);
break;
}
catch (TooMuchInputException tmie)
{
// a TMIE is fine. let the user know and continue saving the decompressed data.
Console.WriteLine(tmie.Message);
if (guessExtension)
{
string outFileName = Path.GetFileNameWithoutExtension(outputFile);
outStr.Position = 0;
byte[] magic = new byte[4];
outStr.Read(magic, 0, 4);
outStr.Position = 0;
outFileName += "." + GuessExtension(magic, Path.GetExtension(outputFile).Substring(1));
outputFile = outputFile.Replace(Path.GetFileName(outputFile), outFileName);
}
using (FileStream output = File.Create(outputFile))
{
outStr.WriteTo(output);
}
decompressed = true;
Console.WriteLine(format.ShortFormatString + "-decompressed " + input + " to " + outputFile);
break;
}
catch (Exception)
{
continue;
}
}
#endregion
}
if (!decompressed)
{
#region copy or print and continue
if (copyErrors)
{
Copy(input, outputFile);
}
else
Console.WriteLine("No suitable decompressor found for " + input + ".");
#endregion
}
}
catch (FileNotFoundException)
{
Console.WriteLine("The file " + input + " does not exist.");
continue;
}
catch (Exception ex)
{
Console.WriteLine("Could not load file " + input + ";");
Console.WriteLine(ex.Message);
#if DEBUG
Console.WriteLine(ex.StackTrace);
#endif
}
} // end foreach input
}
#endregion Method: Decompress
#region Method: Compress
private static void Compress(string[] ioArgs, CompressionFormat format)
{
string[] inputFiles;
string outputDir;
bool copyErrors;
if (!ParseIOArguments(ioArgs, true, out inputFiles, out outputDir, out copyErrors))
return;
foreach (string input in inputFiles)
{
string outputFile = outputDir ?? IOUtils.GetParent(input);
if (Directory.Exists(outputDir))
outputFile = Path.Combine(outputFile + Path.DirectorySeparatorChar, Path.GetFileName(input));
try
{
// read the file only once.
byte[] inputData;
using (Stream inStream = File.OpenRead(input))
{
inputData = new byte[inStream.Length];
inStream.Read(inputData, 0, inputData.Length);
}
bool compressed = false;
#region try to compress
using (MemoryStream inStr = new MemoryStream(inputData),
outStr = new MemoryStream())
{
try
{
long compSize = format.Compress(inStr, inputData.Length, outStr);
if (compSize > 0)
{
using (FileStream output = File.Create(outputFile))
{
outStr.WriteTo(output);
}
compressed = true;
Console.WriteLine(format.ShortFormatString + "-compressed " + input + " to " + outputFile);
}
}
catch (Exception) { }
}
#endregion
if (!compressed)
{
#region copy or print and continue
if (copyErrors)
{
Copy(input, outputFile);
}
else
Console.WriteLine("Could not " + format.ShortFormatString + "-compress " + input + ".");
#endregion
}
}
catch (FileNotFoundException)
{
Console.WriteLine("The file " + input + " does not exist.");
continue;
}
catch (Exception ex)
{
Console.WriteLine("Could not load file " + input + ";");
Console.WriteLine(ex.Message);
#if DEBUG
Console.WriteLine(ex.StackTrace);
#endif
}
} // end foreach input
}
#endregion Method: Compress
#region Method: ParseIOArguments
/// <summary>
/// Parses the IO arguments of the input.
/// </summary>
/// <param name="ioArgs">The arguments to parse.</param>
/// <param name="inputFiles">The files to handle as input.</param>
/// <param name="outputDir">The directory to save the handled files in. If this is null,
/// the files should be overwritten. If this does not exist, it is the output file
/// (the input may only contain one file if that si the case).</param>
/// <param name="copyErrors">If files that cannot be handled (properly) should be copied to the output directory.</param>
/// <returns>True iff parsing of the arguments succeeded.</returns>
private static bool ParseIOArguments(string[] ioArgs, bool compress, out string[] inputFiles, out string outputDir, out bool copyErrors)
{
inputFiles = null;
// when null, output dir = input dir. if it does not exist, it is the output file (only possible when only one input file).
outputDir = null;
copyErrors = false;
#region check if the -co flag is present
if (ioArgs.Length > 0 && ioArgs[ioArgs.Length - 1] == "-co")
{
string[] newIoArgs = new string[ioArgs.Length - 1];
Array.Copy(ioArgs, newIoArgs, newIoArgs.Length);
ioArgs = newIoArgs;
copyErrors = true;
}
#endregion
switch (ioArgs.Length)
{
case 0:
Console.WriteLine("No input file given.");
return false;
case 1:
if (Directory.Exists(ioArgs[0]))
{
inputFiles = Directory.GetFiles(ioArgs[0]);
if (compress)
outputDir = Path.GetFullPath(ioArgs[0]) + "_cmp";
else
outputDir = Path.GetFullPath(ioArgs[0]) + "_dec";
if (!Directory.Exists(outputDir))
Directory.CreateDirectory(outputDir);
break;
}
else if (File.Exists(ioArgs[0]))
{
inputFiles = ioArgs;
outputDir = null;
break;
}
else
{
Console.WriteLine("The file " + ioArgs[0] + " does not exist.");
return false;
}
case 2:
if (Directory.Exists(ioArgs[0]))
{
inputFiles = Directory.GetFiles(ioArgs[0]);
outputDir = ioArgs[1];
if (!Directory.Exists(outputDir))
Directory.CreateDirectory(outputDir);
break;
}
else if (File.Exists(ioArgs[0]))
{
if (File.Exists(ioArgs[1]))
{
inputFiles = ioArgs;
outputDir = null;
break;
}
else// if (Directory.Exists(ioArgs[1]))
// both nonexisting file and existing directory is handled the same.
{
inputFiles = new string[] { ioArgs[0] };
outputDir = ioArgs[1];
break;
}
}
else
{
Console.WriteLine("The file " + ioArgs[0] + " does not exist.");
return false;
}
default:
if (File.Exists(ioArgs[ioArgs.Length - 1]))
{
inputFiles = ioArgs;
outputDir = null;
break;
}
else //if (Directory.Exists(ioArgs[ioArgs.Length - 1]))
// both existing and nonexisting directories are fine.
{
outputDir = ioArgs[ioArgs.Length - 1];
inputFiles = new string[ioArgs.Length - 1];
Array.Copy(ioArgs, inputFiles, inputFiles.Length);
// but we must make sure the output directory exists.
if (!Directory.Exists(outputDir))
Directory.CreateDirectory(outputDir);
break;
}
}
return true;
}
#endregion ParseIOArguments
private static string GuessExtension(byte[] magic, string defaultExt)
{
string ext = "";
for (int i = 0; i < magic.Length && i < 4; i++)
{
if ((magic[i] >= 'a' && magic[i] <= 'z') || (magic[i] >= 'A' && magic[i] <= 'Z')
|| char.IsDigit((char)magic[i]))
{
ext += (char)magic[i];
}
else
break;
}
if (ext.Length <= 1)
return defaultExt;
return ext;
}
/// <summary>
/// Copies the source file to the destination path.
/// </summary>
private static void Copy(string sourcefile, string destfile)
{
if (Path.GetFullPath(sourcefile) == Path.GetFullPath(destfile))
return;
File.Copy(sourcefile, destfile);
Console.WriteLine("Copied " + sourcefile + " to " + destfile);
}
#region Format sequence getters
/// <summary>
/// Gets the compression format corresponding to the given format string.
/// </summary>
private static IEnumerable<CompressionFormat> GetFormat(string formatstring)
{
if (formatstring == null)
yield break;
foreach (CompressionFormat fmt in GetAllFormats(true))
if (fmt.CompressionFlag == formatstring)
{
yield return fmt;
yield break;
}
Console.WriteLine("No such compression format: " + formatstring);
}
/// <summary>
/// Gets a sequence over all compression formats currently supported; both built-in and plugin-based.
/// </summary>
private static IEnumerable<CompressionFormat> GetAllFormats(bool alsoBuiltInCompositeFormats)
{
foreach (CompressionFormat fmt in GetBuiltInFormats(alsoBuiltInCompositeFormats))
yield return fmt;
foreach (CompressionFormat fmt in GetPluginFormats())
yield return fmt;
}
/// <summary>
/// Gets a sequence over all built-in compression formats.
/// </summary>
/// <param name="alsoCompositeFormats">If the built-in composite formats should also be part of the sequence.</param>
private static IEnumerable<CompressionFormat> GetBuiltInFormats(bool alsoCompositeFormats)
{
yield return new LZOvl();
yield return new LZ10();
yield return new LZ11();
yield return new Huffman4();
yield return new Huffman8();
yield return new RLE();
if (alsoCompositeFormats)
{
}
}
/// <summary>
/// Gets a sequence over all formats that can be used from plugins.
/// </summary>
private static IEnumerable<CompressionFormat> GetPluginFormats()
{
string pluginPath = Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName;
pluginPath = Path.Combine(pluginPath, PluginFolder);
if (System.IO.Directory.Exists(pluginPath))
{
foreach (CompressionFormat fmt in IOUtils.LoadCompressionPlugins(pluginPath))
yield return fmt;
}
else
{
Console.WriteLine("Plugin folder " + pluginPath + " is not present; only built-in formats are supported.");
}
}
#endregion
/// <summary>
/// Gets the first item from the given sequence, or the default value of the type in the sequence
/// if it is empty.
/// </summary>
private static T FirstOrDefault<T>(IEnumerable<T> sequence)
{
if (sequence != null)
{
IEnumerator<T> enumerator = sequence.GetEnumerator();
if (enumerator.MoveNext())
return enumerator.Current;
}
return default(T);
}
}
}

View File

@ -48,7 +48,14 @@ namespace DSDecmp
static bool AllowOVL = true;
static bool ForceOVL = false;
/*
static void Main(string[] args)
{
Console.WriteLine(-14 % 10);
Console.ReadLine();
}/**/
public static void Main1(string[] args)
{
if (args.Length == 0) { Usage(); return; }
@ -452,7 +459,8 @@ namespace DSDecmp
byte treeSize = br.ReadByte();
HuffTreeNode.maxInpos = 4 + (treeSize + 1) * 2;
//Console.WriteLine("Tree Size: {0:x}", treeSize);
Console.WriteLine("Tree Size: {0:x}", treeSize);
Console.WriteLine("Tee end: 0x{0:X}", HuffTreeNode.maxInpos);
HuffTreeNode rootNode = new HuffTreeNode();
rootNode.parseData(br);
@ -476,11 +484,16 @@ namespace DSDecmp
string codestr = "";
LinkedList<byte> code = new LinkedList<byte>();
int value;
decomp_size = 0x100;
while (curr_size < decomp_size)
{
try
{
codestr += uint_to_bits(indata[++idx]);
string newstr = uint_to_bits(indata[++idx]);
codestr += newstr;
Console.WriteLine("next uint: "+newstr);
}
catch (IndexOutOfRangeException e)
{
@ -489,9 +502,11 @@ namespace DSDecmp
while (codestr.Length > 0)
{
code.AddFirst(byte.Parse(codestr[0] + ""));
//Console.Write(code.First.Value);
codestr = codestr.Remove(0, 1);
if (rootNode.getValue(code.Last, out value))
{
//Console.WriteLine(" -> "+value.ToString("X"));
try
{
outdata[curr_size++] = (byte)value;
@ -505,6 +520,7 @@ namespace DSDecmp
}
}
}
/*
if (codestr.Length > 0 || idx < indata.Length-1)
{
while (idx < indata.Length-1)
@ -512,7 +528,9 @@ namespace DSDecmp
codestr = codestr.Replace("0", "");
if (codestr.Length > 0)
Console.WriteLine("too much data; str={0:s}, idx={1:g}/{2:g}", codestr, idx, indata.Length);
}
}/**/
br.Close();
byte[] realout;
if (dataSize == 4)
@ -1426,7 +1444,7 @@ namespace DSDecmp
public override string ToString()
{
if (data < 0)
if (data < 0 && node0 != null && node1 != null)
return "<" + node0.ToString() + ", " + node1.ToString() + ">";
else
return String.Format("[{0:x}]", data);

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using DSDecmp.Formats.Nitro;
using System.IO;
namespace DSDecmp
@ -9,10 +8,44 @@ namespace DSDecmp
class TestProgram
{
public static void Main2(string[] args)
public static void MainTest(string[] args)
{
/*
new RLE().Compress("tests/rle/testdata.dat", "tests/rle/cmp/testdata.rle.dat");
new RLE().Decompress("tests/rle/cmp/testdata.rle.dat", "tests/rle/dec/testdata.elr.dat");
/**/
//Program.Main1(new string[] { "tests/huff/00.dat", "tests/huff/dec2/" });
//Console.WriteLine("-----------------------------------------------------");
//new Huffman().Decompress("tests/huff/00.dat", "tests/huff/dec/00.ffuh.dat");
/**/
//new LZ11().Decompress("tests/lz11/game_over_NCGR.cdat", "tests/lz11/dec/game_over.11zl.NCGR");
//new LZOvl().Decompress("tests/lzovl/overlay_0001.bin", "tests/lzovl/dec/overlay_0001.dat");
//new LZ10().Decompress("tests/lz10/npc002_LZ.bin", "tests/lz10/dec/npc002.narc");
//LZ10.LookAhead = true;
//new LZ10().Compress("tests/lz10/dec/npc002.narc", "tests/lz10/cmp/npc002_d.narc.lz");
//new LZ10().Decompress("tests/lz10/cmp/npc002_d.narc.lz", "tests/lz10/cmpdec/npc002.narc");
//LZ11.LookAhead = true;
//new LZ11().Compress("tests/lz11/dec/game_over.11zl.NCGR", "tests/lz11/cmp/game_over.NCGR2.lz11");
//new LZ11().Decompress("tests/lz11/cmp/game_over.NCGR2.lz11", "tests/lz11/cmpdec/game_over.NCGR");
//LZOvl.LookAhead = true;
//new LZOvl().Compress("tests/lzovl/dec/overlay_0001.dat", "tests/lzovl/cmp/overlay_0001b.bin");
//new LZOvl().Decompress("tests/lzovl/cmp/overlay_0001b.bin", "tests/lzovl/cmpdec/overlay_0001.dat");
//Huffman.CompressBlockSize = Huffman.BlockSize.FOURBIT;
//new Huffman().Compress("tests/huff/dec/00.ffuh.dat", "tests/huff/cmp/00.huff4");
//new Huffman().Decompress("tests/huff/cmp/00.huff4", "tests/huff/cmpdec/00.dat");
//new Huffman().Compress("tests/huff/test.dat", "tests/huff/cmp/test.huff");
//new Huffman().Decompress("tests/huff/cmp/test.huff", "tests/huff/cmpdec/test.dat");
//new LZOvl().Decompress("tests/lzovl2/overlay_0001.bin", "tests/lzovl2/overlay_0001.dat");
//new LuminousArc().Decompress("tests/Le/advimg00.imb", "tests/Le/dec/advimg00.imb");
Console.WriteLine("Success?");
Console.ReadLine();

View File

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
namespace DSDecmp
{
@ -9,6 +11,8 @@ namespace DSDecmp
/// </summary>
public static class IOUtils
{
#region byte[] <-> (u)int
/// <summary>
/// Returns a 4-byte unsigned integer as used on the NDS converted from four bytes
/// at a specified position in a byte array.
@ -66,5 +70,78 @@ namespace DSDecmp
| (buffer[offset + 1] << 8)
| (buffer[offset + 2] << 16));
}
#endregion
#region Plugin loading
/// <summary>
/// (Attempts to) load compression formats from the given file.
/// </summary>
/// <param name="file">The dll file to load.</param>
/// <param name="printFailures">If formats without an empty contrsuctor should get a print.</param>
/// <returns>A list with an instance of all compression formats found in the given dll file.</returns>
/// <exception cref="FileNotFoundException">If the given file does not exist.</exception>
/// <exception cref="FileLoadException">If the file could not be loaded.</exception>
/// <exception cref="BadImageFormatException">If the file is not a valid assembly, or the loaded
/// assembly is compiled with a higher version of .NET.</exception>
internal static IEnumerable<CompressionFormat> LoadCompressionPlugin(string file, bool printFailures = false)
{
if (file == null)
throw new FileNotFoundException("A null-path cannot be loaded.");
List<CompressionFormat> newFormats = new List<CompressionFormat>();
string fullPath = Path.GetFullPath(file);
Assembly dll = Assembly.LoadFile(fullPath);
foreach (Type dllType in dll.GetTypes())
{
if (dllType.IsSubclassOf(typeof(CompressionFormat))
&& !dllType.IsAbstract)
{
try
{
newFormats.Add(Activator.CreateInstance(dllType) as CompressionFormat);
}
catch (MissingMethodException)
{
if (printFailures)
Console.WriteLine(dllType + " is a compression format, but does not have a parameterless constructor. Format cannot be loaded from " + fullPath + ".");
}
}
}
return newFormats;
}
/// <summary>
/// Loads all compression formats found in the given folder.
/// </summary>
/// <param name="folder">The folder to load plugins from.</param>
/// <returns>A list with an instance of all compression formats found in the given folder.</returns>
internal static IEnumerable<CompressionFormat> LoadCompressionPlugins(string folder)
{
List<CompressionFormat> formats = new List<CompressionFormat>();
foreach (string file in Directory.GetFiles(folder))
{
try
{
formats.AddRange(LoadCompressionPlugin(file, false));
}
catch (Exception) { }
}
return formats;
}
#endregion
/// <summary>
/// Gets the full path to the parent directory of the given path.
/// </summary>
/// <param name="path">The path to get the parent directory path of.</param>
/// <returns>The full path to the parent directory of teh given path.</returns>
public static string GetParent(string path)
{
return Directory.GetParent(path).FullName;
}
}
}

View File

@ -2,9 +2,12 @@
using System.Collections.Generic;
using System.Text;
namespace DSDecmp.Utils
namespace DSDecmp
{
internal static class LZUtil
/// <summary>
/// Utility class for compression using LZ-like compression schemes.
/// </summary>
public static class LZUtil
{
/// <summary>
/// Determine the maximum size of a LZ-compressed block starting at newPtr, using the already compressed data
@ -16,7 +19,7 @@ namespace DSDecmp.Utils
/// <param name="oldLength">The number of bytes already compressed.</param>
/// <param name="disp">The offset of the start of the longest block to refer to.</param>
/// <returns>The length of the longest sequence of bytes that can be copied from the already decompressed data.</returns>
internal static unsafe int GetOccurrenceLength(byte* newPtr, int newLength, byte* oldPtr, int oldLength, out int disp)
public static unsafe int GetOccurrenceLength(byte* newPtr, int newLength, byte* oldPtr, int oldLength, out int disp)
{
disp = 0;
if (newLength == 0)

View File

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Text;
namespace DSDecmp.Utils
namespace DSDecmp
{
/// <summary>
/// Very simplistic implementation of a priority queue that returns items with lowest priority first.
@ -17,14 +17,25 @@ namespace DSDecmp.Utils
private SortedDictionary<TPrio, LinkedList<TValue>> items;
private int itemCount;
/// <summary>
/// Gets the number of items in this queue.
/// </summary>
public int Count { get { return this.itemCount; } }
/// <summary>
/// Creates a new, empty reverse priority queue.
/// </summary>
public SimpleReversedPrioQueue()
{
this.items = new SortedDictionary<TPrio, LinkedList<TValue>>();
this.itemCount = 0;
}
/// <summary>
/// Enqueues the given value, using the given priority.
/// </summary>
/// <param name="priority">The priority of the value.</param>
/// <param name="value">The value to enqueue.</param>
public void Enqueue(TPrio priority, TValue value)
{
if (!this.items.ContainsKey(priority))
@ -33,6 +44,12 @@ namespace DSDecmp.Utils
this.itemCount++;
}
/// <summary>
/// Gets the current value with the lowest priority from this queue, without dequeueing the value.
/// </summary>
/// <param name="priority">The priority of the returned value.</param>
/// <returns>The current value with the lowest priority.</returns>
/// <exception cref="IndexOutOfRangeException">If there are no items left in this queue.</exception>
public TValue Peek(out TPrio priority)
{
if (this.itemCount == 0)
@ -45,6 +62,12 @@ namespace DSDecmp.Utils
throw new IndexOutOfRangeException();
}
/// <summary>
/// Dequeues the current value at the head of thisreverse priority queue.
/// </summary>
/// <param name="priority">The priority of the dequeued value.</param>
/// <returns>The dequeued value, that used to be at the head of this queue.</returns>
/// <exception cref="IndexOutOfRangeException">If this queue does not contain any items.</exception>
public TValue Dequeue(out TPrio priority)
{
if (this.itemCount == 0)

View File

@ -2,11 +2,33 @@
using System.Collections.Generic;
using System.Text;
using System.IO;
using DSDecmp;
namespace DSDecmp.Formats.GameSpecific
namespace GameFormats
{
public class GoldenSunDD : CompressionFormat
{
public override string ShortFormatString
{
get { return "GSDD"; }
}
public override string Description
{
get { return "A variant of the LZ-0x11 scheme found in Golden Sun: Dark Dawn."; }
}
public override string CompressionFlag
{
get { return "gsdd"; }
}
public override bool SupportsCompression
{
get { return false; }
}
public override bool Supports(System.IO.Stream stream, long inLength)
{
long streamStart = stream.Position;
@ -50,11 +72,7 @@ namespace DSDecmp.Formats.GameSpecific
*
* 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)
* multiply by -1 to get the proper flags (1->compressed, 0->raw)
* - then come 8 blocks:
* - a non-compressed block is simply one single byte
* - a compressed block can have 3 sizes:
@ -112,6 +130,7 @@ namespace DSDecmp.Formats.GameSpecific
// determine which blocks are compressed
int b = 0;
expandedFlags = new bool[8];
// flags = -flags
while (flags > 0)
{
bool bit = (flags & 0x80) > 0;

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{8CE72663-0036-4A94-BD70-99AFE7CEEC0C}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>GoldenSunDD</RootNamespace>
<AssemblyName>GoldenSunDD</AssemblyName>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\PluginDistro\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\PluginDistro\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="GoldenSunDD.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DSDecmp\DSDecmp.csproj">
<Project>{E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}</Project>
<Name>DSDecmp</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("GoldenSunDD")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("GoldenSunDD")]
[assembly: AssemblyCopyright("Copyright © 2011")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("15c63bf0-dd3c-4ef2-b3c4-6c6278475ec9")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,304 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using DSDecmp;
namespace GameFormats
{
/// <summary>
/// Compressor/decompressor for the LZE format found in Luminous Arc games. Format specification by Roger Pepitone; http://pastebin.com/qNgSB2f9
/// </summary>
public class LuminousArc : CompressionFormat
{
public override string ShortFormatString
{
get { return "LZE/Le"; }
}
public override string Description
{
get { return "A variant of an LZ/RLE scheme found in Luminous Arc games."; }
}
public override string CompressionFlag
{
get { return "Le"; }
}
public override bool SupportsCompression
{
get { return false; }
}
/*
* An LZE / Le file consists of the following:
- A six byte header
- A series of blocks
The header consists of:
- 2 bytes: 0x4c, 0x65 ("Le" in ASCII)
- 4 bytes: the size of the uncompressed data in little-endian format
Each block consists of:
- 1 byte: the types for the following mini-records
(2 bits per type, stored with the first type at the least
significant bit)
- 4 mini-records
Each mini-record consists of:
- If its type is 0:
-- 2 bytes BYTE1 BYTE2: Write (3 + (BYTE2 >> 4)) bytes from
back (5 + (BYTE1 | ((BYTE2 & 0xf) << 8))) to output
- If its type is 1:
-- 1 byte BYTE1: Write (2 + (BYTE >> 2)) bytes from
back (1 + (BYTE & 3)) to output
- If its type is 2:
-- 1 byte: (copied to output stream)
- If its type is 3:
-- 3 bytes: (copied to output stream)
The last block may go over the end; if so, ignore any excess data.
*/
#region Method: Supports(Stream, inLength)
/// <summary>
/// Determines if this format may potentially be used to decompress the given stream.
/// Does not guarantee success when returning true, but does guarantee failure when returning false.
/// </summary>
public override bool Supports(System.IO.Stream stream, long inLength)
{
long streamStart = stream.Position;
try
{
if (inLength <= 6) // min 6 byte header
return false;
byte[] header = new byte[2];
stream.Read(header, 0, 2);
if (header[0] != 'L' || header[1] != 'e')
return false;
byte[] outLength = new byte[4];
stream.Read(outLength, 0, 4);
if (IOUtils.ToNDSu32(outLength, 0) == 0)
return inLength == 6;
// as long as the magic is OK, anything else is fine for now. (for this superficial check)
return true;
}
finally
{
stream.Position = streamStart;
}
}
#endregion
public override long Decompress(System.IO.Stream instream, long inLength, System.IO.Stream outstream)
{
long readBytes = 0;
byte[] magic = new byte[2];
instream.Read(magic, 0, 2);
if (magic[0] != 'L' || magic[1] != 'e')
throw new InvalidDataException("The provided stream is not a valid LZE (Le) "
+ "compressed stream (invalid magic '" + (char)magic[0] + (char)magic[1] + "')");
byte[] sizeBytes = new byte[4];
instream.Read(sizeBytes, 0, 4);
uint decompressedSize = IOUtils.ToNDSu32(sizeBytes, 0);
readBytes += 4;
// the maximum 'DISP-5' is 0xFFF.
int bufferLength = 0xFFF + 5;
byte[] buffer = new byte[bufferLength];
int bufferOffset = 0;
int currentOutSize = 0;
int flags = 0, mask = 3;
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 == 3)
{
if (readBytes >= inLength)
throw new NotEnoughDataException(currentOutSize, decompressedSize);
flags = instream.ReadByte(); readBytes++;
if (flags < 0)
throw new StreamTooShortException();
mask = 0xC0;
}
else
{
mask >>= 2;
flags >>= 2;
}
#endregion
switch (flags & 0x3)
{
case 0:
#region 0 -> LZ10-like format
{
#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 = byte2 >> 4;
length += 3;
// from where the bytes should be copied (relatively)
int disp = ((byte2 & 0x0F) << 8) | byte1;
disp += 5;
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"));
#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;
break;
}
#endregion
case 1:
#region 1 -> compact LZ10-like format
{
#region Get length and displacement('disp') values from next byte
// there are < 2 bytes available when the end is at most 1 byte away
if (readBytes >= inLength)
{
throw new NotEnoughDataException(currentOutSize, decompressedSize);
}
int b = instream.ReadByte(); readBytes++;
if (b < 0)
throw new StreamTooShortException();
// the number of bytes to copy
int length = b >> 2;
length += 2;
// from where the bytes should be copied (relatively)
int disp = (b & 0x03);
disp += 1;
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 - 1).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;
break;
}
#endregion
case 2:
#region 2 -> copy 1 byte
{
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;
break;
}
#endregion
case 3:
#region 3 -> copy 3 bytes
{
for (int i = 0; i < 3; i++)
{
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;
}
break;
}
#endregion
default:
throw new Exception("BUG: Mask is not 2 bits long!");
}
outstream.Flush();
}
if (readBytes < inLength)
{
// the input may be 4-byte aligned.
if ((readBytes ^ (readBytes & 3)) + 4 < inLength)
throw new TooMuchInputException(readBytes, inLength);
// (this happens rather often for Le files?)
}
return decompressedSize;
}
private int MaskRsh(int value, int mask)
{
int maskCopy = mask;
int masked = value & mask;
if (maskCopy == 0)
return masked;
while ((maskCopy & 1) == 0)
{
masked >>= 1;
maskCopy >>= 1;
}
return masked;
}
public override int Compress(System.IO.Stream instream, long inLength, System.IO.Stream outstream)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{4BD8DF5C-E971-45D1-B170-340D22DDB351}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>LuminousArc</RootNamespace>
<AssemblyName>LuminousArc</AssemblyName>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\PluginDistro\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\PluginDistro\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="LuminousArc.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DSDecmp\DSDecmp.csproj">
<Project>{E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}</Project>
<Name>DSDecmp</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("LuminousArc")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("LuminousArc")]
[assembly: AssemblyCopyright("Copyright © 2011")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d74a7ef8-8f65-4ed0-ae05-a4a97130f047")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,647 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>DSDecmp</name>
</assembly>
<members>
<member name="T:DSDecmp.TooMuchInputException">
<summary>
An exception indication that the input has more data than required in order
to decompress it. This may indicate that more sub-files are present in the file.
</summary>
</member>
<member name="M:DSDecmp.TooMuchInputException.#ctor(System.Int64,System.Int64)">
<summary>
Creates a new exception indicating that the input has more data than necessary for
decompressing th stream. It may indicate that other data is present after the compressed
stream.
</summary>
<param name="readBytes">The number of bytes read by the decompressor.</param>
<param name="totLength">The indicated length of the input stream.</param>
</member>
<member name="P:DSDecmp.TooMuchInputException.ReadBytes">
<summary>
Gets the number of bytes read by the decompressed to decompress the stream.
</summary>
</member>
<member name="T:DSDecmp.InputTooLargeException">
<summary>
An exception indicating that the file cannot be compressed, because the decompressed size
cannot be represented in the current compression format.
</summary>
</member>
<member name="M:DSDecmp.InputTooLargeException.#ctor">
<summary>
Creates a new exception that indicates that the input is too big to be compressed.
</summary>
</member>
<member name="T:DSDecmp.IOUtils">
<summary>
Class for I/O-related utility methods.
</summary>
</member>
<member name="M:DSDecmp.IOUtils.ToNDSu32(System.Byte[],System.Int32)">
<summary>
Returns a 4-byte unsigned integer as used on the NDS converted from four bytes
at a specified position in a byte array.
</summary>
<param name="buffer">The source of the data.</param>
<param name="offset">The location of the data in the source.</param>
<returns>The indicated 4 bytes converted to uint</returns>
</member>
<member name="M:DSDecmp.IOUtils.ToNDSs32(System.Byte[],System.Int32)">
<summary>
Returns a 4-byte signed integer as used on the NDS converted from four bytes
at a specified position in a byte array.
</summary>
<param name="buffer">The source of the data.</param>
<param name="offset">The location of the data in the source.</param>
<returns>The indicated 4 bytes converted to int</returns>
</member>
<member name="M:DSDecmp.IOUtils.FromNDSu32(System.UInt32)">
<summary>
Converts a u32 value into a sequence of bytes that would make ToNDSu32 return
the given input value.
</summary>
</member>
<member name="M:DSDecmp.IOUtils.ToNDSu24(System.Byte[],System.Int32)">
<summary>
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,
</summary>
<param name="buffer">The source of the data.</param>
<param name="offset">The location of the data in the source.</param>
<returns>The indicated 3 bytes converted to an integer.</returns>
</member>
<member name="M:DSDecmp.IOUtils.LoadCompressionPlugin(System.String,System.Boolean)">
<summary>
(Attempts to) load compression formats from the given file.
</summary>
<param name="file">The dll file to load.</param>
<param name="printFailures">If formats without an empty contrsuctor should get a print.</param>
<returns>A list with an instance of all compression formats found in the given dll file.</returns>
<exception cref="T:System.IO.FileNotFoundException">If the given file does not exist.</exception>
<exception cref="T:System.IO.FileLoadException">If the file could not be loaded.</exception>
<exception cref="T:System.BadImageFormatException">If the file is not a valid assembly, or the loaded
assembly is compiled with a higher version of .NET.</exception>
</member>
<member name="M:DSDecmp.IOUtils.LoadCompressionPlugins(System.String)">
<summary>
Loads all compression formats found in the given folder.
</summary>
<param name="folder">The folder to load plugins from.</param>
<returns>A list with an instance of all compression formats found in the given folder.</returns>
</member>
<member name="M:DSDecmp.IOUtils.GetParent(System.String)">
<summary>
Gets the full path to the parent directory of the given path.
</summary>
<param name="path">The path to get the parent directory path of.</param>
<returns>The full path to the parent directory of teh given path.</returns>
</member>
<member name="T:DSDecmp.CompressionFormat">
<summary>
Base class for all compression formats.
</summary>
</member>
<member name="M:DSDecmp.CompressionFormat.Supports(System.String)">
<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>
</member>
<member name="M:DSDecmp.CompressionFormat.Supports(System.IO.Stream,System.Int64)">
<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>
<param name="inLength">The length of the input stream.</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>
</member>
<member name="M:DSDecmp.CompressionFormat.Decompress(System.String,System.String)">
<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>
</member>
<member name="M:DSDecmp.CompressionFormat.Decompress(System.IO.Stream,System.Int64,System.IO.Stream)">
<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>
<returns>The length of the output data.</returns>
<exception cref="T:DSDecmp.NotEnoughDataException">When the given length of the input data
is not enough to properly decompress the input.</exception>
</member>
<member name="M:DSDecmp.CompressionFormat.Compress(System.String,System.String)">
<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>
<returns>The size of the compressed file. If -1, the file could not be compressed.</returns>
</member>
<member name="M:DSDecmp.CompressionFormat.Compress(System.IO.Stream,System.Int64,System.IO.Stream)">
<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>
<returns>The size of the compressed stream. If -1, the file could not be compressed.</returns>
</member>
<member name="M:DSDecmp.CompressionFormat.ParseCompressionOptions(System.String[])">
<summary>
Parses any input specific for this format. Does nothing by default.
</summary>
<param name="args">Any arguments that may be used by the format.</param>
<returns>The number of input arguments consumed by this format.</returns>
</member>
<member name="P:DSDecmp.CompressionFormat.ShortFormatString">
<summary>
Gets a short string identifying this compression format.
</summary>
</member>
<member name="P:DSDecmp.CompressionFormat.Description">
<summary>
Gets a short description of this compression format (used in the program usage).
</summary>
</member>
<member name="P:DSDecmp.CompressionFormat.SupportsCompression">
<summary>
Gets if this format supports compressing a file.
</summary>
</member>
<member name="P:DSDecmp.CompressionFormat.SupportsDecompression">
<summary>
Gets if this format supports decompressing a file.
</summary>
</member>
<member name="P:DSDecmp.CompressionFormat.CompressionFlag">
<summary>
Gets the value that must be given on the command line in order to compress using this format.
</summary>
</member>
<member name="T:DSDecmp.LZUtil">
<summary>
Utility class for compression using LZ-like compression schemes.
</summary>
</member>
<member name="M:DSDecmp.LZUtil.GetOccurrenceLength(System.Byte*,System.Int32,System.Byte*,System.Int32,System.Int32@)">
<summary>
Determine the maximum size of a LZ-compressed block starting at newPtr, using the already compressed data
starting at oldPtr. Takes O(inLength * oldLength) = O(n^2) time.
</summary>
<param name="newPtr">The start of the data that needs to be compressed.</param>
<param name="newLength">The number of bytes that still need to be compressed.</param>
<param name="oldPtr">The start of the raw file.</param>
<param name="oldLength">The number of bytes already compressed.</param>
<param name="disp">The offset of the start of the longest block to refer to.</param>
<returns>The length of the longest sequence of bytes that can be copied from the already decompressed data.</returns>
</member>
<member name="T:DSDecmp.Formats.Nitro.NitroCFormat">
<summary>
Base class for Nitro-based decompressors. Uses the 1-byte magic and 3-byte decompression
size format.
</summary>
</member>
<member name="F:DSDecmp.Formats.Nitro.NitroCFormat.SkipLargePlaintexts">
<summary>
If true, Nitro Decompressors will not decompress files that have a decompressed
size (plaintext size) larger than MaxPlaintextSize.
</summary>
</member>
<member name="F:DSDecmp.Formats.Nitro.NitroCFormat.MaxPlaintextSize">
<summary>
The maximum allowed size of the decompressed file (plaintext size) allowed for Nitro
Decompressors. Only used when SkipLargePlaintexts = true.
</summary>
</member>
<member name="F:DSDecmp.Formats.Nitro.NitroCFormat.magicByte">
<summary>
The first byte of every file compressed with the format for this particular
Nitro Dcompressor instance.
</summary>
</member>
<member name="T:DSDecmp.StreamTooShortException">
<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>
</member>
<member name="M:DSDecmp.StreamTooShortException.#ctor">
<summary>
Creates a new exception that indicates that the stream was shorter than the given input length.
</summary>
</member>
<member name="T:DSDecmp.Formats.Nitro.LZ11">
<summary>
Compressor and decompressor for the LZ-0x11 format used in many of the games for the
newer Nintendo consoles and handhelds.
</summary>
</member>
<member name="M:DSDecmp.Formats.Nitro.LZ11.CompressWithLA(System.IO.Stream,System.Int64,System.IO.Stream)">
<summary>
Variation of the original compression method, making use of Dynamic Programming to 'look ahead'
and determine the optimal 'length' values for the compressed blocks. Is not 100% optimal,
as the flag-bytes are not taken into account.
</summary>
</member>
<member name="M:DSDecmp.Formats.Nitro.LZ11.GetOptimalCompressionLengths(System.Byte*,System.Int32,System.Int32[]@,System.Int32[]@)">
<summary>
Gets the optimal compression lengths for each start of a compressed block using Dynamic Programming.
This takes O(n^2) time, although in practice it will often be O(n^3) since one of the constants is 0x10110
(the maximum length of a compressed block)
</summary>
<param name="indata">The data to compress.</param>
<param name="inLength">The length of the data to compress.</param>
<param name="lengths">The optimal 'length' of the compressed blocks. For each byte in the input data,
this value is the optimal 'length' value. If it is 1, the block should not be compressed.</param>
<param name="disps">The 'disp' values of the compressed blocks. May be 0, in which case the
corresponding length will never be anything other than 1.</param>
</member>
<member name="P:DSDecmp.Formats.Nitro.LZ11.LookAhead">
<summary>
Sets the flag that determines if 'look-ahead'/DP should be used when compressing
with the LZ-11 format. The default is false, which is what is used in the original
implementation.
</summary>
</member>
<member name="T:DSDecmp.Formats.Nitro.LZ10">
<summary>
Compressor and decompressor for the LZ-0x10 format used in many of the games for the
newer Nintendo consoles and handhelds.
</summary>
</member>
<member name="M:DSDecmp.Formats.Nitro.LZ10.Decompress(System.IO.Stream,System.Int64,System.IO.Stream)">
<summary>
Decompress a stream that is compressed in the LZ-10 format.
</summary>
<param name="instream">The compressed stream.</param>
<param name="inLength">The length of the input stream.</param>
<param name="outstream">The output stream, where the decompressed data is written to.</param>
</member>
<member name="M:DSDecmp.Formats.Nitro.LZ10.CompressWithLA(System.IO.Stream,System.Int64,System.IO.Stream)">
<summary>
Variation of the original compression method, making use of Dynamic Programming to 'look ahead'
and determine the optimal 'length' values for the compressed blocks. Is not 100% optimal,
as the flag-bytes are not taken into account.
</summary>
</member>
<member name="M:DSDecmp.Formats.Nitro.LZ10.GetOptimalCompressionLengths(System.Byte*,System.Int32,System.Int32[]@,System.Int32[]@)">
<summary>
Gets the optimal compression lengths for each start of a compressed block using Dynamic Programming.
This takes O(n^2) time.
</summary>
<param name="indata">The data to compress.</param>
<param name="inLength">The length of the data to compress.</param>
<param name="lengths">The optimal 'length' of the compressed blocks. For each byte in the input data,
this value is the optimal 'length' value. If it is 1, the block should not be compressed.</param>
<param name="disps">The 'disp' values of the compressed blocks. May be 0, in which case the
corresponding length will never be anything other than 1.</param>
</member>
<member name="P:DSDecmp.Formats.Nitro.LZ10.LookAhead">
<summary>
Sets the flag that determines if 'look-ahead'/DP should be used when compressing
with the LZ-10 format. The default is false, which is what is used in the original
implementation.
</summary>
</member>
<member name="T:DSDecmp.Formats.Nitro.RLE">
<summary>
Compressor and decompressor for the RLE format used in several of the games for the
newer Nintendo consoles and handhelds.
</summary>
</member>
<member name="T:DSDecmp.Formats.LZOvl">
<summary>
The LZ-Overlay compression format. Compresses part of the file from end to start.
Is used for the 'overlay' files in NDS games, as well as arm9.bin.
Note that the last 12 bytes should not be included in the 'inLength' argument when
decompressing arm9.bin. This is done automatically if a file is given instead of a stream.
</summary>
</member>
<member name="M:DSDecmp.Formats.LZOvl.CompressNormal(System.IO.Stream,System.Int64,System.IO.Stream)">
<summary>
Compresses the given input stream with the LZ-Ovl compression, but compresses _forward_
instad of backwards.
</summary>
<param name="instream">The input stream to compress.</param>
<param name="inLength">The length of the input stream.</param>
<param name="outstream">The stream to write to.</param>
</member>
<member name="M:DSDecmp.Formats.LZOvl.CompressWithLA(System.IO.Stream,System.Int64,System.IO.Stream)">
<summary>
Variation of the original compression method, making use of Dynamic Programming to 'look ahead'
and determine the optimal 'length' values for the compressed blocks. Is not 100% optimal,
as the flag-bytes are not taken into account.
</summary>
</member>
<member name="M:DSDecmp.Formats.LZOvl.GetOptimalCompressionLengths(System.Byte*,System.Int32,System.Int32[]@,System.Int32[]@)">
<summary>
Gets the optimal compression lengths for each start of a compressed block using Dynamic Programming.
This takes O(n^2) time.
</summary>
<param name="indata">The data to compress.</param>
<param name="inLength">The length of the data to compress.</param>
<param name="lengths">The optimal 'length' of the compressed blocks. For each byte in the input data,
this value is the optimal 'length' value. If it is 1, the block should not be compressed.</param>
<param name="disps">The 'disp' values of the compressed blocks. May be less than 3, in which case the
corresponding length will never be anything other than 1.</param>
</member>
<member name="M:DSDecmp.Formats.LZOvl.GetOptimalCompressionPartLength(System.Int32[])">
<summary>
Gets the 'optimal' length of the compressed part of the file.
Or rather: the length in such a way that compressing any more will not
result in a shorter file.
</summary>
<param name="blocklengths">The lengths of the compressed blocks, as gotten from GetOptimalCompressionLengths.</param>
<returns>The 'optimal' length of the compressed part of the file.</returns>
</member>
<member name="P:DSDecmp.Formats.LZOvl.LookAhead">
<summary>
Sets the flag that determines if 'look-ahead'/DP should be used when compressing
with the LZ-Ovl format. The default is false, which is what is used in the original
implementation.
</summary>
</member>
<member name="T:DSDecmp.NewProgram.Formats">
<summary>
The formats allowed when compressing a file.
</summary>
</member>
<member name="M:DSDecmp.NewestProgram.ParseIOArguments(System.String[],System.Boolean,System.String[]@,System.String@,System.Boolean@)">
<summary>
Parses the IO arguments of the input.
</summary>
<param name="ioArgs">The arguments to parse.</param>
<param name="inputFiles">The files to handle as input.</param>
<param name="outputDir">The directory to save the handled files in. If this is null,
the files should be overwritten. If this does not exist, it is the output file
(the input may only contain one file if that si the case).</param>
<param name="copyErrors">If files that cannot be handled (properly) should be copied to the output directory.</param>
<returns>True iff parsing of the arguments succeeded.</returns>
</member>
<member name="M:DSDecmp.NewestProgram.Copy(System.String,System.String)">
<summary>
Copies the source file to the destination path.
</summary>
</member>
<member name="M:DSDecmp.NewestProgram.GetFormat(System.String)">
<summary>
Gets the compression format corresponding to the given format string.
</summary>
</member>
<member name="M:DSDecmp.NewestProgram.GetAllFormats(System.Boolean)">
<summary>
Gets a sequence over all compression formats currently supported; both built-in and plugin-based.
</summary>
</member>
<member name="M:DSDecmp.NewestProgram.GetBuiltInFormats(System.Boolean)">
<summary>
Gets a sequence over all built-in compression formats.
</summary>
<param name="alsoCompositeFormats">If the built-in composite formats should also be part of the sequence.</param>
</member>
<member name="M:DSDecmp.NewestProgram.GetPluginFormats">
<summary>
Gets a sequence over all formats that can be used from plugins.
</summary>
</member>
<member name="M:DSDecmp.NewestProgram.FirstOrDefault``1(System.Collections.Generic.IEnumerable{``0})">
<summary>
Gets the first item from the given sequence, or the default value of the type in the sequence
if it is empty.
</summary>
</member>
<member name="T:DSDecmp.NotEnoughDataException">
<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>
</member>
<member name="M:DSDecmp.NotEnoughDataException.#ctor(System.Int64,System.Int64)">
<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>
</member>
<member name="P:DSDecmp.NotEnoughDataException.WrittenLength">
<summary>
Gets the actual number of written bytes.
</summary>
</member>
<member name="P:DSDecmp.NotEnoughDataException.DesiredLength">
<summary>
Gets the number of bytes that was supposed to be written.
</summary>
</member>
<member name="T:DSDecmp.SimpleReversedPrioQueue`2">
<summary>
Very simplistic implementation of a priority queue that returns items with lowest priority first.
This is not the most efficient implementation, but required the least work while using the classes
from the .NET collections, and without requiring importing another dll or several more class files
in order to make it work.
</summary>
<typeparam name="TPrio">The type of the priority values.</typeparam>
<typeparam name="TValue">The type of item to put into the queue.</typeparam>
</member>
<member name="M:DSDecmp.SimpleReversedPrioQueue`2.#ctor">
<summary>
Creates a new, empty reverse priority queue.
</summary>
</member>
<member name="M:DSDecmp.SimpleReversedPrioQueue`2.Enqueue(`0,`1)">
<summary>
Enqueues the given value, using the given priority.
</summary>
<param name="priority">The priority of the value.</param>
<param name="value">The value to enqueue.</param>
</member>
<member name="M:DSDecmp.SimpleReversedPrioQueue`2.Peek(`0@)">
<summary>
Gets the current value with the lowest priority from this queue, without dequeueing the value.
</summary>
<param name="priority">The priority of the returned value.</param>
<returns>The current value with the lowest priority.</returns>
<exception cref="T:System.IndexOutOfRangeException">If there are no items left in this queue.</exception>
</member>
<member name="M:DSDecmp.SimpleReversedPrioQueue`2.Dequeue(`0@)">
<summary>
Dequeues the current value at the head of thisreverse priority queue.
</summary>
<param name="priority">The priority of the dequeued value.</param>
<returns>The dequeued value, that used to be at the head of this queue.</returns>
<exception cref="T:System.IndexOutOfRangeException">If this queue does not contain any items.</exception>
</member>
<member name="P:DSDecmp.SimpleReversedPrioQueue`2.Count">
<summary>
Gets the number of items in this queue.
</summary>
</member>
<member name="T:DSDecmp.Formats.Nitro.Huffman">
<summary>
Compressor and decompressor for the Huffman format used in many of the games for the
newer Nintendo consoles and handhelds.
</summary>
</member>
<member name="M:DSDecmp.Formats.Nitro.Huffman.GetLowest(DSDecmp.SimpleReversedPrioQueue{System.Int32,DSDecmp.Formats.Nitro.Huffman.HuffTreeNode},DSDecmp.SimpleReversedPrioQueue{System.Int32,DSDecmp.Formats.Nitro.Huffman.HuffTreeNode},System.Int32@)">
<summary>
Gets the tree node with the lowest priority (frequency) from the leaf and node queues.
If the priority is the same for both head items in the queues, the node from the leaf queue is picked.
</summary>
</member>
<member name="P:DSDecmp.Formats.Nitro.Huffman.CompressBlockSize">
<summary>
Sets the block size used when using the Huffman format to compress.
</summary>
</member>
<member name="T:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode">
<summary>
A single node in a Huffman tree.
</summary>
</member>
<member name="F:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.data">
<summary>
The data contained in this node. May not mean anything when <code>isData == false</code>
</summary>
</member>
<member name="F:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.isFilled">
<summary>
A flag indicating if this node has been filled.
</summary>
</member>
<member name="F:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.isData">
<summary>
A flag indicating if this node contains data. If not, this is not a leaf node.
</summary>
</member>
<member name="F:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.child0">
<summary>
The child of this node at side 0
</summary>
</member>
<member name="F:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.child1">
<summary>
The child of this node at side 1
</summary>
</member>
<member name="M:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.#ctor(System.Byte,System.Boolean,DSDecmp.Formats.Nitro.Huffman.HuffTreeNode,DSDecmp.Formats.Nitro.Huffman.HuffTreeNode)">
<summary>
Manually creates a new node for a huffman tree.
</summary>
<param name="data">The data for this node.</param>
<param name="isData">If this node represents data.</param>
<param name="child0">The child of this node on the 0 side.</param>
<param name="child1">The child of this node on the 1 side.</param>
</member>
<member name="M:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.#ctor(System.IO.Stream,System.Boolean,System.Int64,System.Int64)">
<summary>
Creates a new node in the Huffman tree.
</summary>
<param name="stream">The stream to read from. It is assumed that there is (at least)
one more byte available to read.</param>
<param name="isData">If this node is a data-node.</param>
<param name="relOffset">The offset of this node in the source data, relative to the start
of the compressed file.</param>
<param name="maxStreamPos">The indicated end of the huffman tree. If the stream is past
this position, the tree is invalid.</param>
</member>
<member name="P:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.Data">
<summary>
The data contained in this node. May not mean anything when <code>isData == false</code>.
Throws a NullReferenceException when this node has not been defined (ie: reference was outside the
bounds of the tree definition)
</summary>
</member>
<member name="P:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.IsData">
<summary>
Returns true if this node represents data.
</summary>
</member>
<member name="P:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.Child0">
<summary>
The child of this node at side 0
</summary>
</member>
<member name="P:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.Child1">
<summary>
The child of this node at side 1
</summary>
</member>
<member name="P:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.Parent">
<summary>
The parent node of this node.
</summary>
</member>
<member name="P:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.IsChild0">
<summary>
Determines if this is the Child0 of the parent node. Assumes there is a parent.
</summary>
</member>
<member name="P:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.IsChild1">
<summary>
Determines if this is the Child1 of the parent node. Assumes there is a parent.
</summary>
</member>
<member name="P:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.Depth">
<summary>
Get or set the depth of this node. Will not be set automatically, but
will be set recursively (the depth of all child nodes will be updated when this is set).
</summary>
</member>
<member name="P:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.Size">
<summary>
Calculates the size of the sub-tree with this node as root.
</summary>
</member>
<member name="P:DSDecmp.Formats.Nitro.Huffman.HuffTreeNode.PreOrderTraversal">
<summary>
Returns a seuqnce over the nodes of the sub-tree with this node as root in a pre-order fashion. (Root-Left-Right)
</summary>
</member>
<member name="M:DSDecmp.Formats.Nitro.Huffman4.Compress(System.IO.Stream,System.Int64,System.IO.Stream)">
<summary>
Applies Huffman compression with a datablock size of 4 bits.
</summary>
<param name="instream">The stream to compress.</param>
<param name="inLength">The length of the input stream.</param>
<param name="outstream">The stream to write the decompressed data to.</param>
<returns>The size of the decompressed data.</returns>
</member>
<member name="M:DSDecmp.Formats.Nitro.Huffman8.Compress(System.IO.Stream,System.Int64,System.IO.Stream)">
<summary>
Applies Huffman compression with a datablock size of 8 bits.
</summary>
<param name="instream">The stream to compress.</param>
<param name="inLength">The length of the input stream.</param>
<param name="outstream">The stream to write the decompressed data to.</param>
<returns>The size of the decompressed data.</returns>
</member>
</members>
</doc>

Binary file not shown.

Binary file not shown.