mirror of
https://github.com/Barubary/dsdecmp.git
synced 2024-09-28 17:28:41 +02:00
added source code for both C# and Java.
Note that the Java algorithms are a port of the C# ones, and only the LZ10 and LZ11 have been tested of the Java implementation.
This commit is contained in:
commit
a597cd23a9
20
CSharp/DSDecmp.sln
Normal file
20
CSharp/DSDecmp.sln
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 9.00
|
||||
# Visual C# Express 2005
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DSDecmp", "DSDecmp\DSDecmp.csproj", "{E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
49
CSharp/DSDecmp/DSDecmp.csproj
Normal file
49
CSharp/DSDecmp/DSDecmp.csproj
Normal file
@ -0,0 +1,49 @@
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>8.0.50727</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>DSDecmp</RootNamespace>
|
||||
<AssemblyName>DSDecmp</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</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.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
965
CSharp/DSDecmp/Program.cs
Normal file
965
CSharp/DSDecmp/Program.cs
Normal file
@ -0,0 +1,965 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace DSDecmp
|
||||
{
|
||||
unsafe class Program
|
||||
{
|
||||
static int MAX_OUTSIZE = 0x200000;
|
||||
const int N = 4096, F = 18;
|
||||
const byte THRESHOLD = 2;
|
||||
const int NIL = N;
|
||||
static bool showAlways = true;
|
||||
|
||||
const int LZ77_TAG = 0x10, LZSS_TAG = 0x11, RLE_TAG = 0x30, HUFF_TAG = 0x20, NONE_TAG = 0x00;
|
||||
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
|
||||
/*args = new string[2];
|
||||
args[0] = "game_over_NCGR.cdat";
|
||||
args[1] = "dec"; /**/
|
||||
|
||||
if (args.Length != 2 && args.Length != 3)
|
||||
{
|
||||
Console.WriteLine("useage: DSDecmp infile outfolder [maxlen]\nor: DSDecmp infolder outfolder [maxlen]");
|
||||
Console.WriteLine("maxlen is optional and hexadecimal, and all files that would be larger than maxlen when decompressed are ignored");
|
||||
return;
|
||||
}
|
||||
if (args.Length == 3)
|
||||
MAX_OUTSIZE = int.Parse(args[2], System.Globalization.NumberStyles.HexNumber);/**/
|
||||
|
||||
|
||||
args[0] = makeSlashes(args[0]);
|
||||
args[1] = makeSlashes(args[1]);
|
||||
/**/
|
||||
|
||||
if (!Directory.Exists(args[1]))
|
||||
Directory.CreateDirectory(args[1]);
|
||||
if (File.Exists(args[0]))/**/
|
||||
Decompress(args[0], args[1]);
|
||||
else
|
||||
DecompressFolder(args[0], args[1]);/**/
|
||||
}
|
||||
|
||||
private static void WriteDebug(string s)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(s);
|
||||
if (showAlways)
|
||||
Console.WriteLine(s);
|
||||
}
|
||||
|
||||
#region method: DecompressFolder
|
||||
private static void DecompressFolder(string inflr, string outflr)
|
||||
{
|
||||
showAlways = false; // only print errors/failures
|
||||
|
||||
if (!outflr.EndsWith("/") && !outflr.EndsWith("\\"))
|
||||
outflr += "/";
|
||||
StreamWriter sw = null;
|
||||
if (!Directory.Exists(inflr))
|
||||
{
|
||||
Console.WriteLine("No such file or folder: " + inflr);
|
||||
return;
|
||||
}
|
||||
string[] files = Directory.GetFiles(inflr);
|
||||
foreach (string fname in files)
|
||||
try
|
||||
{
|
||||
Decompress(makeSlashes(fname), outflr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (sw == null)
|
||||
sw = new StreamWriter(new FileStream(outflr + "lzsslog.txt", FileMode.Create));
|
||||
Console.WriteLine(e.Message);
|
||||
sw.WriteLine(e.Message);
|
||||
string copied = fname.Replace(inflr, outflr);
|
||||
if (!File.Exists(copied))
|
||||
File.Copy(fname, copied);
|
||||
}
|
||||
Console.WriteLine("Done decompressing files in folder " + inflr);
|
||||
if (sw != null)
|
||||
{
|
||||
Console.WriteLine("Errors have been logged to " + outflr + "lzsslog.txt");
|
||||
sw.Flush();
|
||||
sw.Close();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Method: Decompress
|
||||
static void Decompress(string filein, string outflr)
|
||||
{
|
||||
FileStream fstr = File.OpenRead(filein);
|
||||
if (fstr.Length > int.MaxValue)
|
||||
throw new Exception("Files larger than 2GB cannot be decompressed by this program.");
|
||||
BinaryReader br = new BinaryReader(fstr);
|
||||
|
||||
byte tag = br.ReadByte();
|
||||
br.Close();
|
||||
try
|
||||
{
|
||||
switch (tag >> 4)
|
||||
{
|
||||
case LZ77_TAG >> 4:
|
||||
if (tag == LZ77_TAG)
|
||||
DecompressLZ77(filein, outflr);
|
||||
else if (tag == LZSS_TAG)
|
||||
Decompress11LZS(filein, outflr);
|
||||
else
|
||||
CopyFile(filein, outflr);
|
||||
break;
|
||||
case RLE_TAG >> 4: DecompressRLE(filein, outflr); break;
|
||||
case NONE_TAG >> 4: DecompressNone(filein, outflr); break;
|
||||
case HUFF_TAG >> 4: DecompressHuffman(filein, outflr); break;
|
||||
default: CopyFile(filein, outflr); break;
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
CopyFile(filein, outflr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Could not properly decompress {0:s};", filein);
|
||||
Console.WriteLine(e.Message);
|
||||
Console.WriteLine(e.StackTrace);
|
||||
Console.ReadLine();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Method: CopyFile
|
||||
/// <summary>
|
||||
/// Copies a file
|
||||
/// </summary>
|
||||
/// <param name="filein">The input file</param>
|
||||
/// <param name="outflr">The output folder. (the file keeps its name, other files get overwritten)</param>
|
||||
static void CopyFile(string filein, string outflr)
|
||||
{
|
||||
filein = makeSlashes(filein);
|
||||
string outfname = filein.Substring(filein.LastIndexOf("/") + 1);
|
||||
if (!outflr.EndsWith("/"))
|
||||
outflr += "/";
|
||||
outfname = outflr + outfname;
|
||||
File.Copy(filein, outfname, true);
|
||||
Console.WriteLine("Copied " + filein + " to " + outflr);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region RLE
|
||||
static void DecompressRLE(string filein, string outflr)
|
||||
{
|
||||
/* SWI 14h (GBA/NDS7/NDS9) - RLUnCompWram
|
||||
SWI 15h (GBA/NDS7/NDS9) - RLUnCompVram (NDS: with Callback)
|
||||
Expands run-length compressed data. The Wram function is faster, and writes in units of 8bits. For the Vram function the destination must be halfword aligned, data is written in units of 16bits.
|
||||
If the size of the compressed data is not a multiple of 4, please adjust it as much as possible by padding with 0. Align the source address to a 4Byte boundary.
|
||||
|
||||
r0 Source Address, pointing to data as such:
|
||||
Data header (32bit)
|
||||
Bit 0-3 Reserved
|
||||
Bit 4-7 Compressed type (must be 3 for run-length)
|
||||
Bit 8-31 Size of decompressed data
|
||||
Repeat below. Each Flag Byte followed by one or more Data Bytes.
|
||||
Flag data (8bit)
|
||||
Bit 0-6 Expanded Data Length (uncompressed N-1, compressed N-3)
|
||||
Bit 7 Flag (0=uncompressed, 1=compressed)
|
||||
Data Byte(s) - N uncompressed bytes, or 1 byte repeated N times
|
||||
r1 Destination Address
|
||||
r2 Callback parameter (NDS SWI 15h only, see Callback notes below)
|
||||
r3 Callback structure (NDS SWI 15h only, see Callback notes below)
|
||||
|
||||
Return: No return value, Data written to destination address.*/
|
||||
|
||||
FileStream fstr = new FileStream(filein, FileMode.Open);
|
||||
if (fstr.Length > int.MaxValue)
|
||||
throw new Exception("Files larger than 2GB cannot be RLE-compressed files.");
|
||||
BinaryReader br = new BinaryReader(fstr);
|
||||
|
||||
long decomp_size = 0, curr_size = 0;
|
||||
int i, rl;
|
||||
byte flag, b;
|
||||
bool compressed;
|
||||
|
||||
if (br.ReadByte() != RLE_TAG)
|
||||
throw new InvalidDataException(String.Format("File {0:s} is not a valid RLE file", filein));
|
||||
for (i = 0; i < 3; i++)
|
||||
decomp_size += br.ReadByte() << (i * 8);
|
||||
if (decomp_size > MAX_OUTSIZE)
|
||||
throw new Exception(String.Format("{0:s} will be larger than 0x{1:x} and will not be decompressed.", filein, MAX_OUTSIZE));
|
||||
|
||||
if (showAlways)
|
||||
Console.WriteLine("Decompressing {0:s}. (outsize: 0x{1:x})", filein, decomp_size);
|
||||
|
||||
#region decompress
|
||||
byte[] outdata = new byte[decomp_size];
|
||||
|
||||
while (true)
|
||||
{
|
||||
// get tag
|
||||
try { flag = br.ReadByte(); }
|
||||
catch (EndOfStreamException) { break; }
|
||||
compressed = (flag & 0x80) > 0;
|
||||
rl = flag & 0x7F;
|
||||
if (compressed)
|
||||
rl += 3;
|
||||
else
|
||||
rl += 1;
|
||||
//curr_size += rl;
|
||||
if (compressed)
|
||||
{
|
||||
try { b = br.ReadByte(); }
|
||||
catch (EndOfStreamException) { break; }// throw new Exception(String.Format("Invalid RLE format in file {0:s}; incomplete data near EOF.", filein)); }
|
||||
for (i = 0; i < rl; i++)
|
||||
outdata[curr_size++] = b;
|
||||
}
|
||||
else
|
||||
for (i = 0; i < rl; i++)
|
||||
try { outdata[curr_size++] = br.ReadByte(); }
|
||||
catch (EndOfStreamException) { break; }// throw new Exception(String.Format("Invalid RLE format in file {0:s}; incomplete data near EOF.", filein)); }
|
||||
|
||||
if (curr_size > decomp_size)
|
||||
{
|
||||
Console.WriteLine("curr_size > decomp_size; {0:x}>{1:x}", curr_size, decomp_size);
|
||||
break;// throw new Exception(String.Format("File {0:s} is not a valid LZSS file; actual output size > output size in header", filein));
|
||||
}
|
||||
if (curr_size == decomp_size)
|
||||
break;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region save
|
||||
string ext = "";
|
||||
for (i = 0; i < 4; i++)
|
||||
if (char.IsLetterOrDigit((char)outdata[i]))
|
||||
ext += (char)outdata[i];
|
||||
else
|
||||
break;
|
||||
if (ext.Length == 0)
|
||||
ext = "dat";
|
||||
ext = "." + ext;
|
||||
filein = filein.Replace("\\", "/");
|
||||
outflr = outflr.Replace("\\", "/");
|
||||
string outfname = filein.Substring(filein.LastIndexOf("/") + 1);
|
||||
outfname = outfname.Substring(0, outfname.LastIndexOf('.'));
|
||||
|
||||
if (!outflr.EndsWith("/"))
|
||||
outflr += "/";
|
||||
/*while (File.Exists(outflr + outfname + ext))
|
||||
outfname += "_";/**/
|
||||
|
||||
BinaryWriter bw = new BinaryWriter(new FileStream(outflr + outfname + ext, FileMode.Create));
|
||||
for (i = 0; i < outdata.Length; i++)
|
||||
bw.Write(outdata[i]);
|
||||
//bw.Write(outdata);
|
||||
bw.Flush();
|
||||
bw.Close();
|
||||
|
||||
#endregion
|
||||
|
||||
Console.WriteLine("RLE decompressed " + filein);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Huffman
|
||||
static void DecompressHuffman(String filename, String outflr)
|
||||
{
|
||||
/*
|
||||
Data Header (32bit)
|
||||
Bit0-3 Data size in bit units (normally 4 or 8)
|
||||
Bit4-7 Compressed type (must be 2 for Huffman)
|
||||
Bit8-31 24bit size of decompressed data in bytes
|
||||
Tree Size (8bit)
|
||||
Bit0-7 Size of Tree Table/2-1 (ie. Offset to Compressed Bitstream)
|
||||
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)
|
||||
Compressed Bitstream (stored in units of 32bits)
|
||||
Bit0-31 Node Bits (Bit31=First Bit) (0=Node0, 1=Node1)
|
||||
*/
|
||||
|
||||
BinaryReader br = new BinaryReader(File.OpenRead(filename));
|
||||
|
||||
byte firstByte = br.ReadByte();
|
||||
|
||||
int dataSize = firstByte & 0x0F;
|
||||
|
||||
if ((firstByte & 0xF0) != HUFF_TAG)
|
||||
throw new InvalidDataException(String.Format("Invalid huffman comressed file; invalid tag {0:x}", firstByte));
|
||||
|
||||
//Console.WriteLine("Data size: {0:x}", dataSize);
|
||||
if (dataSize != 8 && dataSize != 4)
|
||||
throw new InvalidDataException(String.Format("Unhandled dataSize {0:x}", dataSize));
|
||||
|
||||
int decomp_size = 0;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
decomp_size |= br.ReadByte() << (i * 8);
|
||||
}
|
||||
//Console.WriteLine("Decompressed size: {0:x}", decomp_size);
|
||||
|
||||
byte treeSize = br.ReadByte();
|
||||
HuffTreeNode.maxInpos = 4 + (treeSize + 1) * 2;
|
||||
|
||||
//Console.WriteLine("Tree Size: {0:x}", treeSize);
|
||||
|
||||
HuffTreeNode rootNode = new HuffTreeNode();
|
||||
rootNode.parseData(br);
|
||||
|
||||
//Console.WriteLine("Tree: {0:s}", rootNode.ToString());
|
||||
|
||||
br.BaseStream.Position = 4 + (treeSize + 1) * 2; // go to start of coded bitstream.
|
||||
// read all data
|
||||
uint[] indata = new uint[(br.BaseStream.Length - br.BaseStream.Position) / 4];
|
||||
for (int i = 0; i < indata.Length; i++)
|
||||
indata[i] = br.ReadUInt32();
|
||||
|
||||
//Console.WriteLine(indata[0]);
|
||||
//Console.WriteLine(uint_to_bits(indata[0]));
|
||||
|
||||
long curr_size = 0;
|
||||
decomp_size *= dataSize == 8 ? 1 : 2;
|
||||
byte[] outdata = new byte[decomp_size];
|
||||
|
||||
int idx = -1;
|
||||
string codestr = "";
|
||||
LinkedList<byte> code = new LinkedList<byte>();
|
||||
int value;
|
||||
while (curr_size < decomp_size)
|
||||
{
|
||||
try
|
||||
{
|
||||
codestr += uint_to_bits(indata[++idx]);
|
||||
}
|
||||
catch (IndexOutOfRangeException e)
|
||||
{
|
||||
throw new IndexOutOfRangeException("not enough data.", e);
|
||||
}
|
||||
while (codestr.Length > 0)
|
||||
{
|
||||
code.AddFirst(byte.Parse(codestr[0] + ""));
|
||||
codestr = codestr.Remove(0, 1);
|
||||
if (rootNode.getValue(code.Last, out value))
|
||||
{
|
||||
try
|
||||
{
|
||||
outdata[curr_size++] = (byte)value;
|
||||
}
|
||||
catch (IndexOutOfRangeException ex)
|
||||
{
|
||||
if (code.First.Value != 0)
|
||||
throw ex;
|
||||
}
|
||||
code.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (codestr.Length > 0 || idx < indata.Length-1)
|
||||
{
|
||||
while (idx < indata.Length-1)
|
||||
codestr += uint_to_bits(indata[++idx]);
|
||||
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);
|
||||
}
|
||||
|
||||
byte[] realout;
|
||||
if (dataSize == 4)
|
||||
{
|
||||
realout = new byte[decomp_size / 2];
|
||||
for (int i = 0; i < decomp_size / 2; i++)
|
||||
{
|
||||
if ((outdata[i * 2] & 0xF0) > 0
|
||||
|| (outdata[i * 2 + 1] & 0xF0) > 0)
|
||||
throw new Exception("first 4 bits of data should be 0 if dataSize = 4");
|
||||
realout[i] = (byte)((outdata[i * 2] << 4) | outdata[i * 2 + 1]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
realout = outdata;
|
||||
}
|
||||
|
||||
#region save
|
||||
string ext = "";
|
||||
for (int i = 0; i < 4; i++)
|
||||
if (char.IsLetterOrDigit((char)realout[i]))
|
||||
ext += (char)realout[i];
|
||||
else
|
||||
break;
|
||||
if (ext.Length == 0)
|
||||
ext = "dat";
|
||||
ext = "." + ext;
|
||||
filename = filename.Replace("\\", "/");
|
||||
outflr = outflr.Replace("\\", "/");
|
||||
string outfname = filename.Substring(filename.LastIndexOf("/") + 1);
|
||||
outfname = outfname.Substring(0, outfname.LastIndexOf('.'));
|
||||
|
||||
if (!outflr.EndsWith("/"))
|
||||
outflr += "/";
|
||||
while (File.Exists(outflr + outfname + ext))
|
||||
outfname += "_";
|
||||
|
||||
BinaryWriter bw = new BinaryWriter(new FileStream(outflr + outfname + ext, FileMode.CreateNew));
|
||||
bw.Write(realout);
|
||||
bw.Flush();
|
||||
bw.Close();
|
||||
|
||||
#endregion
|
||||
|
||||
Console.WriteLine("Huffman decompressed {0:s}", filename);
|
||||
//Console.ReadLine();
|
||||
/**/
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region None
|
||||
private static void DecompressNone(string filein, string outflr)
|
||||
{
|
||||
FileStream fstr = new FileStream(filein, FileMode.Open);
|
||||
if (fstr.Length > int.MaxValue)
|
||||
throw new Exception("Filer larger than 2GB cannot be NONE-compressed files.");
|
||||
BinaryReader br = new BinaryReader(fstr);
|
||||
|
||||
long decomp_size = 0;
|
||||
int i;
|
||||
|
||||
if (br.ReadByte() != NONE_TAG)
|
||||
throw new InvalidDataException(String.Format("File {0:s} is not a valid NONE file, it does not have the NONE-tag as first byte", filein));
|
||||
for (i = 0; i < 3; i++)
|
||||
decomp_size += br.ReadByte() << (i * 8);
|
||||
if (decomp_size != fstr.Length - 0x04)
|
||||
throw new InvalidDataException("File {0:s} is not a valid NONE file, the decompression size shold be the file size - 4");
|
||||
|
||||
#region save
|
||||
string ext = "";
|
||||
char c;
|
||||
for (i = 0; i < 4; i++)
|
||||
if (char.IsLetterOrDigit(c = (char)br.ReadByte()))
|
||||
ext += c;
|
||||
else
|
||||
break;
|
||||
if (ext.Length == 0)
|
||||
ext = "dat";
|
||||
ext = "." + ext;
|
||||
br.BaseStream.Position -= i == 4 ? 4 : i + 1;
|
||||
|
||||
filein = filein.Replace("\\", "/");
|
||||
outflr = outflr.Replace("\\", "/");
|
||||
string outfname = filein.Substring(filein.LastIndexOf("/") + 1);
|
||||
outfname = outfname.Substring(0, outfname.LastIndexOf('.'));
|
||||
|
||||
if (!outflr.EndsWith("/"))
|
||||
outflr += "/";
|
||||
while (File.Exists(outflr + outfname + ext))
|
||||
outfname += "_";
|
||||
|
||||
BinaryWriter bw = new BinaryWriter(new FileStream(outflr + outfname + ext, FileMode.CreateNew));
|
||||
|
||||
bw.Write(br.ReadBytes((int)decomp_size));
|
||||
|
||||
bw.Flush();
|
||||
bw.Close();
|
||||
|
||||
#endregion
|
||||
|
||||
Console.WriteLine("NONE-decompressed {0:s}", filein);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region tag 0x10 LZ77
|
||||
static void DecompressLZ77(string filein, string outflr)
|
||||
{
|
||||
/* Data header (32bit)
|
||||
Bit 0-3 Reserved
|
||||
Bit 4-7 Compressed type (must be 1 for LZ77)
|
||||
Bit 8-31 Size of decompressed data
|
||||
Repeat below. Each Flag Byte followed by eight Blocks.
|
||||
Flag data (8bit)
|
||||
Bit 0-7 Type Flags for next 8 Blocks, MSB first
|
||||
Block Type 0 - Uncompressed - Copy 1 Byte from Source to Dest
|
||||
Bit 0-7 One data byte to be copied to dest
|
||||
Block Type 1 - Compressed - Copy N+3 Bytes from Dest-Disp-1 to Dest
|
||||
Bit 0-3 Disp MSBs
|
||||
Bit 4-7 Number of bytes to copy (minus 3)
|
||||
Bit 8-15 Disp LSBs
|
||||
*/
|
||||
FileStream fstr = new FileStream(filein, FileMode.Open);
|
||||
if (fstr.Length > int.MaxValue)
|
||||
throw new Exception("Filer larger than 2GB cannot be RLE-compressed files.");
|
||||
BinaryReader br = new BinaryReader(fstr);
|
||||
|
||||
long decomp_size = 0, curr_size = 0;
|
||||
int flags, i, j, disp, n;
|
||||
bool flag;
|
||||
byte b;
|
||||
long cdest;
|
||||
|
||||
if (br.ReadByte() != LZ77_TAG)
|
||||
throw new InvalidDataException(String.Format("File {0:s} is not a valid LZ77 file", filein));
|
||||
for (i = 0; i < 3; i++)
|
||||
decomp_size += br.ReadByte() << (i * 8);
|
||||
if (decomp_size > MAX_OUTSIZE)
|
||||
throw new Exception(String.Format("{0:s} will be larger than 0x{1:x} (0x{2:x}) and will not be decompressed.", filein, MAX_OUTSIZE, decomp_size));
|
||||
else if (decomp_size == 0)
|
||||
for (i = 0; i < 4; i++)
|
||||
decomp_size += br.ReadByte() << (i * 8);
|
||||
if (decomp_size > MAX_OUTSIZE << 8)
|
||||
throw new Exception(String.Format("{0:s} will be larger than 0x{1:x} (0x{2:x}) and will not be decompressed.", filein, MAX_OUTSIZE, decomp_size));
|
||||
|
||||
if (showAlways)
|
||||
Console.WriteLine("Decompressing {0:s}. (outsize: 0x{1:x})", filein, decomp_size);
|
||||
|
||||
#region decompress
|
||||
|
||||
byte[] outdata = new byte[decomp_size];
|
||||
|
||||
while (curr_size < decomp_size)
|
||||
{
|
||||
try { flags = br.ReadByte(); }
|
||||
catch (EndOfStreamException) { break; }
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
flag = (flags & (0x80 >> i)) > 0;
|
||||
if (flag)
|
||||
{
|
||||
disp = 0;
|
||||
try { b = br.ReadByte(); }
|
||||
catch (EndOfStreamException) { throw new Exception("Incomplete data"); }
|
||||
n = b >> 4;
|
||||
disp = (b & 0x0F) << 8;
|
||||
try { disp |= br.ReadByte(); }
|
||||
catch (EndOfStreamException) { throw new Exception("Incomplete data"); }
|
||||
n += 3;
|
||||
cdest = curr_size;
|
||||
//Console.WriteLine("disp: 0x{0:x}", disp);
|
||||
if (disp > curr_size)
|
||||
throw new Exception("Cannot go back more than already written");
|
||||
for (j = 0; j < n; j++)
|
||||
outdata[curr_size++] = outdata[cdest - disp - 1 + j];
|
||||
//curr_size += len;
|
||||
if (curr_size > decomp_size)
|
||||
{
|
||||
//throw new Exception(String.Format("File {0:s} is not a valid LZ77 file; actual output size > output size in header", filein));
|
||||
//Console.WriteLine(String.Format("File {0:s} is not a valid LZ77 file; actual output size > output size in header; {1:x} > {2:x}.", filein, curr_size, decomp_size));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try { b = br.ReadByte(); }
|
||||
catch (EndOfStreamException) { break;}// throw new Exception("Incomplete data"); }
|
||||
try { outdata[curr_size++] = b; }
|
||||
catch (IndexOutOfRangeException) { if (b == 0) break; }
|
||||
//curr_size++;
|
||||
if (curr_size > decomp_size)
|
||||
{
|
||||
//throw new Exception(String.Format("File {0:s} is not a valid LZ77 file; actual output size > output size in header", filein));
|
||||
//Console.WriteLine(String.Format("File {0:s} is not a valid LZ77 file; actual output size > output size in header; {1:x} > {2:x}", filein, curr_size, decomp_size));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
while (br.ReadByte() == 0) { } // if we read a non-zero, print that there is still some data
|
||||
Console.WriteLine("Too many data in file; current INPOS = {0:x}", br.BaseStream.Position - 1);
|
||||
}
|
||||
catch (EndOfStreamException) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region save
|
||||
string ext = "";
|
||||
for (i = 0; i < 4; i++)
|
||||
if (char.IsLetterOrDigit((char)outdata[i]))
|
||||
ext += (char)outdata[i];
|
||||
else
|
||||
break;
|
||||
if (ext.Length == 0)
|
||||
ext = "dat";
|
||||
ext = "." + ext;
|
||||
filein = filein.Replace("\\", "/");
|
||||
outflr = outflr.Replace("\\", "/");
|
||||
string outfname = filein.Substring(filein.LastIndexOf("/") + 1);
|
||||
outfname = outfname.Substring(0, outfname.LastIndexOf('.'));
|
||||
|
||||
if (!outflr.EndsWith("/"))
|
||||
outflr += "/";
|
||||
while (File.Exists(outflr + outfname + ext))
|
||||
outfname += "_";
|
||||
|
||||
BinaryWriter bw = new BinaryWriter(new FileStream(outflr + outfname + ext, FileMode.CreateNew));
|
||||
bw.Write(outdata);
|
||||
bw.Flush();
|
||||
bw.Close();
|
||||
|
||||
#endregion
|
||||
|
||||
Console.WriteLine("LZ77 Decompressed " + filein);
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region tag 0x11 LZSS
|
||||
static void Decompress11LZS(string filein, string outflr)
|
||||
{
|
||||
/* Data header (32bit)
|
||||
Bit 0-3 Reserved
|
||||
Bit 4-7 Compressed type (must be 1 for LZ77)
|
||||
Bit 8-31 Size of decompressed data. if 0, the next 4 bytes are decompressed length
|
||||
Repeat below. Each Flag Byte followed by eight Blocks.
|
||||
Flag data (8bit)
|
||||
Bit 0-7 Type Flags for next 8 Blocks, MSB first
|
||||
Block Type 0 - Uncompressed - Copy 1 Byte from Source to Dest
|
||||
Bit 0-7 One data byte to be copied to dest
|
||||
Block Type 1 - Compressed - Copy LEN Bytes from Dest-Disp-1 to Dest
|
||||
If Reserved is 0: - Default
|
||||
Bit 0-3 Disp MSBs
|
||||
Bit 4-7 LEN - 3
|
||||
Bit 8-15 Disp LSBs
|
||||
If Reserved is 1: - Higher compression rates for files with (lots of) long repetitions
|
||||
Bit 4-7 Indicator
|
||||
If Indicator > 1:
|
||||
Bit 0-3 Disp MSBs
|
||||
Bit 4-7 LEN - 1 (same bits as Indicator)
|
||||
Bit 8-15 Disp LSBs
|
||||
If Indicator is 1:
|
||||
Bit 0-3 and 8-19 LEN - 0x111
|
||||
Bit 20-31 Disp
|
||||
If Indicator is 0:
|
||||
Bit 0-3 and 8-11 LEN - 0x11
|
||||
Bit 12-23 Disp
|
||||
|
||||
*/
|
||||
FileStream fstr = new FileStream(filein, FileMode.Open);
|
||||
if (fstr.Length > int.MaxValue)
|
||||
throw new Exception("Filer larger than 2GB cannot be LZSS-compressed files.");
|
||||
BinaryReader br = new BinaryReader(fstr);
|
||||
|
||||
int decomp_size = 0, curr_size = 0;
|
||||
int i, j, disp, len;
|
||||
bool flag;
|
||||
byte b1, bt, b2, b3, flags;
|
||||
int cdest;
|
||||
|
||||
int threshold = 1;
|
||||
|
||||
if (br.ReadByte() != LZSS_TAG)
|
||||
throw new InvalidDataException(String.Format("File {0:s} is not a valid LZSS-11 file", filein));
|
||||
for (i = 0; i < 3; i++)
|
||||
decomp_size += br.ReadByte() << (i * 8);
|
||||
if (decomp_size > MAX_OUTSIZE)
|
||||
throw new Exception(String.Format("{0:s} will be larger than 0x{1:x} (0x{2:x}) and will not be decompressed.", filein, MAX_OUTSIZE, decomp_size));
|
||||
else if (decomp_size == 0)
|
||||
for (i = 0; i < 4; i++)
|
||||
decomp_size += br.ReadByte() << (i * 8);
|
||||
if (decomp_size > MAX_OUTSIZE << 8)
|
||||
throw new Exception(String.Format("{0:s} will be larger than 0x{1:x} (0x{2:x}) and will not be decompressed.", filein, MAX_OUTSIZE, decomp_size));
|
||||
|
||||
if (showAlways)
|
||||
Console.WriteLine("Decompressing {0:s}. (outsize: 0x{1:x})", filein, decomp_size);
|
||||
|
||||
|
||||
byte[] outdata = new byte[decomp_size];
|
||||
|
||||
|
||||
while (curr_size < decomp_size)
|
||||
{
|
||||
try { flags = br.ReadByte(); }
|
||||
catch (EndOfStreamException) { break; }
|
||||
|
||||
for (i = 0; i < 8 && curr_size < decomp_size; i++)
|
||||
{
|
||||
flag = (flags & (0x80 >> i)) > 0;
|
||||
if (flag)
|
||||
{
|
||||
try { b1 = br.ReadByte(); }
|
||||
catch (EndOfStreamException) { throw new Exception("Incomplete data"); }
|
||||
|
||||
switch (b1 >> 4)
|
||||
{
|
||||
#region case 0
|
||||
case 0:
|
||||
// ab cd ef
|
||||
// =>
|
||||
// len = abc + 0x11 = bc + 0x11
|
||||
// disp = def
|
||||
|
||||
len = b1 << 4;
|
||||
try { bt = br.ReadByte(); }
|
||||
catch (EndOfStreamException) { throw new Exception("Incomplete data"); }
|
||||
len |= bt >> 4;
|
||||
len += 0x11;
|
||||
|
||||
disp = (bt & 0x0F) << 8;
|
||||
try { b2 = br.ReadByte(); }
|
||||
catch (EndOfStreamException) { throw new Exception("Incomplete data"); }
|
||||
disp |= b2;
|
||||
break;
|
||||
#endregion
|
||||
|
||||
#region case 1
|
||||
case 1:
|
||||
// ab cd ef gh
|
||||
// =>
|
||||
// len = bcde + 0x111
|
||||
// disp = fgh
|
||||
// 10 04 92 3F => disp = 0x23F, len = 0x149 + 0x11 = 0x15A
|
||||
|
||||
try { bt = br.ReadByte(); b2 = br.ReadByte(); b3 = br.ReadByte(); }
|
||||
catch (EndOfStreamException) { throw new Exception("Incomplete data"); }
|
||||
|
||||
len = (b1 & 0xF) << 12; // len = b000
|
||||
len |= bt << 4; // len = bcd0
|
||||
len |= (b2 >> 4); // len = bcde
|
||||
len += 0x111; // len = bcde + 0x111
|
||||
disp = (b2 & 0x0F) << 8; // disp = f
|
||||
disp |= b3; // disp = fgh
|
||||
break;
|
||||
#endregion
|
||||
|
||||
#region other
|
||||
default:
|
||||
// ab cd
|
||||
// =>
|
||||
// len = a + threshold = a + 1
|
||||
// disp = bcd
|
||||
|
||||
len = (b1 >> 4) + threshold;
|
||||
|
||||
disp = (b1 & 0x0F) << 8;
|
||||
try { b2 = br.ReadByte(); }
|
||||
catch (EndOfStreamException) { throw new Exception("Incomplete data"); }
|
||||
disp |= b2;
|
||||
break;
|
||||
#endregion
|
||||
}
|
||||
|
||||
if (disp > curr_size)
|
||||
throw new Exception("Cannot go back more than already written");
|
||||
|
||||
cdest = curr_size;
|
||||
|
||||
for (j = 0; j < len && curr_size < decomp_size; j++)
|
||||
outdata[curr_size++] = outdata[cdest - disp - 1 + j];
|
||||
|
||||
if (curr_size > decomp_size)
|
||||
{
|
||||
//throw new Exception(String.Format("File {0:s} is not a valid LZ77 file; actual output size > output size in header", filein));
|
||||
//Console.WriteLine(String.Format("File {0:s} is not a valid LZ77 file; actual output size > output size in header; {1:x} > {2:x}.", filein, curr_size, decomp_size));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try { outdata[curr_size++] = br.ReadByte(); }
|
||||
catch (EndOfStreamException) { break; }// throw new Exception("Incomplete data"); }
|
||||
|
||||
if (curr_size > decomp_size)
|
||||
{
|
||||
//throw new Exception(String.Format("File {0:s} is not a valid LZ77 file; actual output size > output size in header", filein));
|
||||
//Console.WriteLine(String.Format("File {0:s} is not a valid LZ77 file; actual output size > output size in header; {1:x} > {2:x}", filein, curr_size, decomp_size));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
while (br.ReadByte() == 0) { } // if we read a non-zero, print that there is still some data
|
||||
Console.WriteLine("Too much data in file; current INPOS = {0:x}", br.BaseStream.Position - 1);
|
||||
}
|
||||
catch (EndOfStreamException) { }
|
||||
|
||||
#region save
|
||||
string ext = "";
|
||||
for (i = 0; i < 4; i++)
|
||||
if (char.IsLetterOrDigit((char)outdata[i]))
|
||||
ext += (char)outdata[i];
|
||||
else
|
||||
break;
|
||||
if (ext.Length == 0)
|
||||
ext = "dat";
|
||||
ext = "." + ext;
|
||||
filein = filein.Replace("\\", "/");
|
||||
outflr = outflr.Replace("\\", "/");
|
||||
string outfname = filein.Substring(filein.LastIndexOf("/") + 1);
|
||||
outfname = outfname.Substring(0, outfname.LastIndexOf('.'));
|
||||
|
||||
if (!outflr.EndsWith("/"))
|
||||
outflr += "/";
|
||||
while (File.Exists(outflr + outfname + ext))
|
||||
outfname += "_";/**/
|
||||
|
||||
BinaryWriter bw = new BinaryWriter(new FileStream(outflr + outfname + ext, FileMode.Create));
|
||||
bw.Write(outdata);
|
||||
|
||||
bw.Flush();
|
||||
bw.Close();
|
||||
|
||||
#endregion
|
||||
|
||||
Console.WriteLine("LZSS-11 Decompressed " + filein);
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region helper methods
|
||||
private static string byte_to_bits(byte b)
|
||||
{
|
||||
string o = "";
|
||||
for (int i = 0; i < 8; i++)
|
||||
o = (((b & (1 << i)) >> i) & 1) + o;
|
||||
return o;
|
||||
}
|
||||
private static string uint_to_bits(uint u)
|
||||
{
|
||||
string o = "";
|
||||
for (int i = 3; i >-1; i--)
|
||||
o += byte_to_bits((byte)((u & (0xFF << (i * 8))) >> (i * 8)));
|
||||
return o;
|
||||
}
|
||||
|
||||
private static byte peekByte(BinaryReader br)
|
||||
{
|
||||
byte b = br.ReadByte();
|
||||
br.BaseStream.Position--;
|
||||
return b;
|
||||
}
|
||||
|
||||
private static string makeSlashes(string path)
|
||||
{
|
||||
StringBuilder sbin = new StringBuilder(path),
|
||||
sbout = new StringBuilder();
|
||||
char c;
|
||||
while (sbin.Length > 0)
|
||||
{
|
||||
c = sbin[0];
|
||||
sbin.Remove(0, 1);
|
||||
if (c == '\\')
|
||||
sbout.Append('/');
|
||||
else
|
||||
sbout.Append(c);
|
||||
}
|
||||
return sbout.ToString();
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
class HuffTreeNode
|
||||
{
|
||||
internal static int maxInpos = 0;
|
||||
internal HuffTreeNode node0, node1;
|
||||
internal int data = -1; // [-1,0xFF]
|
||||
/// <summary>
|
||||
/// To get a value, provide the last node of a list of bytes < 2.
|
||||
/// the list will be read from back to front.
|
||||
/// </summary>
|
||||
internal bool getValue(LinkedListNode<byte> code, out int value)
|
||||
{
|
||||
value = data;
|
||||
if (code == null)
|
||||
return node0 == null && node1 == null && data >= 0;
|
||||
|
||||
if(code.Value > 1)
|
||||
throw new Exception(String.Format("the list should be a list of bytes < 2. got:{0:g}", code.Value));
|
||||
|
||||
byte c = code.Value;
|
||||
bool retVal;
|
||||
HuffTreeNode n = c == 0 ? node0 : node1;
|
||||
retVal = n != null && n.getValue(code.Previous, out value);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
internal int this[string code]
|
||||
{
|
||||
get
|
||||
{
|
||||
LinkedList<byte> c = new LinkedList<byte>();
|
||||
foreach (char ch in code)
|
||||
c.AddFirst((byte)ch);
|
||||
int val = 1;
|
||||
return this.getValue(c.Last, out val) ? val : -1;
|
||||
}
|
||||
}
|
||||
|
||||
internal void parseData(BinaryReader br)
|
||||
{
|
||||
/*
|
||||
* 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)
|
||||
*/
|
||||
this.node0 = new HuffTreeNode();
|
||||
this.node1 = new HuffTreeNode();
|
||||
long currPos = br.BaseStream.Position;
|
||||
byte b = br.ReadByte();
|
||||
long offset = b & 0x3F;
|
||||
bool end0 = (b & 0x80) > 0, end1 = (b & 0x40) > 0;
|
||||
// parse data for node0
|
||||
br.BaseStream.Position = (currPos - (currPos & 1)) + offset * 2 + 2;
|
||||
if (br.BaseStream.Position < maxInpos)
|
||||
{
|
||||
if (end0)
|
||||
node0.data = br.ReadByte();
|
||||
else
|
||||
node0.parseData(br);
|
||||
}
|
||||
// parse data for node1
|
||||
br.BaseStream.Position = (currPos - (currPos & 1)) + offset * 2 + 2 + 1;
|
||||
if (br.BaseStream.Position < maxInpos)
|
||||
{
|
||||
if (end1)
|
||||
node1.data = br.ReadByte();
|
||||
else
|
||||
node1.parseData(br);
|
||||
}
|
||||
// reset position
|
||||
br.BaseStream.Position = currPos;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (data < 0)
|
||||
return "<" + node0.ToString() + ", " + node1.ToString() + ">";
|
||||
else
|
||||
return String.Format("[{0:x}]", data);
|
||||
}
|
||||
|
||||
internal int Depth
|
||||
{
|
||||
get
|
||||
{
|
||||
if (data < 0)
|
||||
return 0;
|
||||
else
|
||||
return 1 + Math.Max(node0.Depth, node1.Depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
CSharp/DSDecmp/Properties/AssemblyInfo.cs
Normal file
33
CSharp/DSDecmp/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,33 @@
|
||||
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("DSDecmp")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("DSDecmp")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2009")]
|
||||
[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("5c936297-7872-4fc0-88f7-c0289d20b297")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
296
Java/HexInputStream.java
Normal file
296
Java/HexInputStream.java
Normal file
@ -0,0 +1,296 @@
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Stack;
|
||||
|
||||
public class HexInputStream {
|
||||
|
||||
private volatile InputStream dis;
|
||||
private volatile long currPos;
|
||||
public long getPosition(){ return this.currPos; }
|
||||
public void setPosition(long newPos) throws IOException{ this.skip(newPos - currPos); }
|
||||
public void goTo(long pos) throws IOException{ this.setPosition(pos); }
|
||||
|
||||
private Stack<Long> positionStack;
|
||||
|
||||
public HexInputStream(InputStream baseInputStream) {
|
||||
this.dis = baseInputStream;
|
||||
this.currPos = 0;
|
||||
this.positionStack = new Stack<Long>();
|
||||
}
|
||||
public HexInputStream(String filename) throws FileNotFoundException {
|
||||
this.dis = new DataInputStream(new FileInputStream(new File(filename)));
|
||||
this.currPos = 0;
|
||||
this.positionStack = new Stack<Long>();
|
||||
}
|
||||
|
||||
public int available() throws IOException{
|
||||
return dis.available();
|
||||
}
|
||||
|
||||
/** Read the next byte from a file. If the EOF has been reached, -1 is returned */
|
||||
public int read() throws IOException{
|
||||
int b = dis.read();
|
||||
if(b != -1)
|
||||
currPos++;
|
||||
return b;
|
||||
}
|
||||
|
||||
/** Read an array of bytes from this stream */
|
||||
public int[] readBytes(int length) throws IOException{
|
||||
int[] data = new int[length];
|
||||
for(int i=0; i<length; i++)
|
||||
data[i] = readU8();
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Read a byte from this stream */
|
||||
public int readU8() throws IOException {
|
||||
int b = dis.read();
|
||||
currPos++;
|
||||
return b;
|
||||
}
|
||||
|
||||
/** Read a BigEndian s16 from this stream */
|
||||
public short readS16() throws IOException {
|
||||
short word = 0;
|
||||
for(int i = 0; i < 2; i++)
|
||||
word = (short)(word | (readU8() << (8 * i)));
|
||||
return word;
|
||||
}
|
||||
|
||||
/** Read a LittleEndian s16 from this stream */
|
||||
public short readlS16() throws IOException {
|
||||
short word = 0;
|
||||
for(int i = 0; i < 2; i++)
|
||||
word = (short)((word << 8) | readU8());
|
||||
return word;
|
||||
}
|
||||
|
||||
/** Read a BigEndian u16 from this stream */
|
||||
public int readU16() throws IOException {
|
||||
int word = 0;
|
||||
for(int i = 0; i < 2; i++)
|
||||
word = word | (readU8() << (8 * i));
|
||||
return word;
|
||||
}
|
||||
|
||||
/** Read a LittleEndian u16 from this stream */
|
||||
public int readlU16() throws IOException {
|
||||
int word = 0;
|
||||
for(int i = 0; i < 2; i++)
|
||||
word = (word << 8) | readU8();
|
||||
return word;
|
||||
}
|
||||
|
||||
/** Read a BigEndian s32 from this stream (a signed int) */
|
||||
public int readS32() throws IOException {
|
||||
int dword = 0;
|
||||
for(int i = 0; i < 4; i++)
|
||||
dword = dword | (readU8() << (8 * i));
|
||||
return dword;
|
||||
}
|
||||
/** Read a LittleEndian s32 from this stream (a signed int) */
|
||||
public int readlS32() throws IOException {
|
||||
int dword = 0;
|
||||
for(int i = 0; i < 4; i++)
|
||||
dword = (dword << 8) | readU8();
|
||||
return dword;
|
||||
}
|
||||
|
||||
/** Read a BigEndian u32 from this stream (an unsigned int) */
|
||||
public long readU32() throws IOException {
|
||||
long dword = 0;
|
||||
for(int i = 0; i < 4; i++)
|
||||
dword = dword | (readU8() << (8 * i));
|
||||
|
||||
return dword;
|
||||
}
|
||||
/** Read a LittleEndian u32 from this stream (an unsigned int) */
|
||||
public long readlU32() throws IOException {
|
||||
long dword = 0;
|
||||
for(int i = 0; i < 4; i++)
|
||||
dword = (dword << 8) | readU8();
|
||||
return dword;
|
||||
}
|
||||
|
||||
/** Read a BigEndian s32 from this stream (a signed int) */
|
||||
public long readS64() throws IOException {
|
||||
long qword = 0;
|
||||
for(int i = 0; i < 8; i++)
|
||||
qword = qword | (readU8() << (8 * i));
|
||||
return qword;
|
||||
}
|
||||
/** Read a LittleEndian s32 from this stream (a signed int) */
|
||||
public long readlS64() throws IOException {
|
||||
long qword = 0;
|
||||
for(int i = 0; i < 8; i++)
|
||||
qword = (qword << 8) | readU8();
|
||||
return qword;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Peek at the next u8 from this stream */
|
||||
public short peekU8() throws IOException{
|
||||
try{
|
||||
short b = (short)readU8();
|
||||
skip(-1);
|
||||
return b;
|
||||
} catch (EOFException ex){ return -1; }
|
||||
}
|
||||
/** peek at the next s16 from this stream */
|
||||
public short peekS16() throws IOException{
|
||||
try{
|
||||
short s = readS16();
|
||||
skip(-2);
|
||||
return s;
|
||||
} catch (EOFException ex){ return -1; }
|
||||
}
|
||||
/** peek at the next little-endian s16 from this stream */
|
||||
public short peeklS16() throws IOException{
|
||||
try{
|
||||
short s = readlS16();
|
||||
skip(-2);
|
||||
return s;
|
||||
} catch (EOFException ex){ return -1; }
|
||||
}
|
||||
/** peek at the next u16 from this stream */
|
||||
public int peekU16() throws IOException{
|
||||
try{
|
||||
int s = readU16();
|
||||
skip(-2);
|
||||
return s;
|
||||
} catch (EOFException ex){ return -1; }
|
||||
}
|
||||
/** peek at the next little-endian u16 from this stream */
|
||||
public int peeklU16() throws IOException{
|
||||
try{
|
||||
int s = readlU16();
|
||||
skip(-2);
|
||||
return s;
|
||||
} catch (EOFException ex){ return -1; }
|
||||
}
|
||||
/** peek at the next s32 from this stream */
|
||||
public int peekS32() throws IOException{
|
||||
try{
|
||||
int s = readS32();
|
||||
skip(-4);
|
||||
return s;
|
||||
} catch (EOFException ex){ return -1; }
|
||||
}
|
||||
/** peek at the next little-endian s32 from this stream */
|
||||
public int peeklS32() throws IOException{
|
||||
try{
|
||||
int s = readlS32();
|
||||
skip(-4);
|
||||
return s;
|
||||
} catch (EOFException ex){ return -1; }
|
||||
}
|
||||
/** peek at the next u32 from this stream */
|
||||
public long peekU32() throws IOException{
|
||||
try{
|
||||
long s = readU32();
|
||||
skip(-4);
|
||||
return s;
|
||||
} catch (EOFException ex){ return -1; }
|
||||
}
|
||||
/** peek at the next little-endian u32 from this stream */
|
||||
public long peeklU32() throws IOException{
|
||||
try{
|
||||
long s = readlU32();
|
||||
skip(-4);
|
||||
return s;
|
||||
} catch (EOFException ex){ return -1; }
|
||||
}
|
||||
/** peek at the next s64 from this stream */
|
||||
public long peekS64() throws IOException{
|
||||
try{
|
||||
long s = readS64();
|
||||
skip(-8);
|
||||
return s;
|
||||
} catch (EOFException ex){ return -1; }
|
||||
}
|
||||
/** peek at the next little-endian s64 from this stream */
|
||||
public long peeklS64() throws IOException{
|
||||
try{
|
||||
long s = readlS64();
|
||||
skip(-8);
|
||||
return s;
|
||||
} catch (EOFException ex){ return -1; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Read a String of a certain length from this stream */
|
||||
public String readString(int length) throws IOException{
|
||||
StringBuffer sbuf = new StringBuffer(length);
|
||||
for(int i=0; i<length; i++)
|
||||
sbuf.append((char)readU8());
|
||||
return sbuf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a \0-terminated string from this stream
|
||||
* @param totlength the total length of the filed in the data. If -1, read until a \0 is read.
|
||||
*/
|
||||
public String read0TerminatedString(int totlength) throws IOException{
|
||||
if(totlength == -1){
|
||||
StringBuffer sbuf = new StringBuffer();
|
||||
while(peekU8() != 0)
|
||||
sbuf.append((char)readU8());
|
||||
readU8(); // read the 0 as well
|
||||
return sbuf.toString();
|
||||
} else {
|
||||
StringBuffer sbuf = new StringBuffer();
|
||||
boolean read0 = false;
|
||||
for(int i=0; i<totlength; i++){
|
||||
if(peekU8() == 0)
|
||||
read0 = true;
|
||||
char c = (char)readU8();
|
||||
if(!read0)
|
||||
sbuf.append(c);
|
||||
}
|
||||
return sbuf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/** Close this stream */
|
||||
public void close() throws IOException{
|
||||
dis.close();
|
||||
}
|
||||
|
||||
/** Skip n bytes */
|
||||
public void skip(long n) throws IOException{
|
||||
dis.skip(n);
|
||||
currPos += n;
|
||||
}
|
||||
|
||||
/** Reset this stream */
|
||||
public void reset() throws IOException{
|
||||
this.goTo(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Save the current position on the local stack */
|
||||
public void savePosition(){
|
||||
this.positionStack.push(this.currPos);
|
||||
}
|
||||
|
||||
/** Pop the last saved position from the local stack and restore that position */
|
||||
public void loadPosition() throws IOException{
|
||||
if(!this.positionStack.isEmpty()){
|
||||
long pos = this.positionStack.peek();
|
||||
this.positionStack.pop();
|
||||
this.goTo(pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
14
Java/InvalidFileException.java
Normal file
14
Java/InvalidFileException.java
Normal file
@ -0,0 +1,14 @@
|
||||
import java.io.IOException;
|
||||
|
||||
public class InvalidFileException extends IOException{
|
||||
|
||||
private static final long serialVersionUID = -8354901572139075536L;
|
||||
|
||||
public InvalidFileException(String message){
|
||||
super(message);
|
||||
}
|
||||
public InvalidFileException(String message, Exception innerException){
|
||||
super(message, innerException);
|
||||
}
|
||||
|
||||
}
|
410
Java/JavaDSDecmp.java
Normal file
410
Java/JavaDSDecmp.java
Normal file
@ -0,0 +1,410 @@
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
|
||||
public class JavaDSDecmp {
|
||||
|
||||
public static int[] Decompress(HexInputStream his) throws IOException {
|
||||
switch(his.readU8()){
|
||||
case 0x10: return Decompress10LZ(his);
|
||||
case 0x11: return Decompress11LZ(his);
|
||||
case 0x24:
|
||||
case 0x28: return DecompressHuff(his);
|
||||
case 0x30: return DecompressRLE(his);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getLength(HexInputStream his) throws IOException {
|
||||
int length = 0;
|
||||
for(int i = 0; i < 3; i++)
|
||||
length = length | (his.readU8() << (i * 8));
|
||||
if(length == 0) // 0 length? then length is next 4 bytes
|
||||
length = his.readlS32();
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
private static int[] Decompress10LZ(HexInputStream his) throws IOException {
|
||||
int[] outData = new int[getLength(his)];
|
||||
|
||||
int curr_size = 0;
|
||||
int flags;
|
||||
boolean flag;
|
||||
int disp, n, b, cdest;
|
||||
|
||||
while (curr_size < outData.length)
|
||||
{
|
||||
try { flags = his.readU8(); }
|
||||
catch (EOFException ex) { break; }
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
flag = (flags & (0x80 >> i)) > 0;
|
||||
if (flag)
|
||||
{
|
||||
disp = 0;
|
||||
try { b = his.readU8(); }
|
||||
catch (EOFException ex) { throw new InvalidFileException("Incomplete data"); }
|
||||
n = b >> 4;
|
||||
disp = (b & 0x0F) << 8;
|
||||
try { disp |= his.readU8(); }
|
||||
catch (EOFException ex) { throw new InvalidFileException("Incomplete data"); }
|
||||
n += 3;
|
||||
cdest = curr_size;
|
||||
//Console.WriteLine("disp: 0x{0:x}", disp);
|
||||
if (disp > curr_size)
|
||||
throw new InvalidFileException("Cannot go back more than already written");
|
||||
for (int j = 0; j < n; j++)
|
||||
outData[curr_size++] = outData[cdest - disp - 1 + j];
|
||||
|
||||
if (curr_size > outData.length)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
try { b = his.readU8(); }
|
||||
catch (EOFException ex) { break;}// throw new Exception("Incomplete data"); }
|
||||
try { outData[curr_size++] = b; }
|
||||
catch (ArrayIndexOutOfBoundsException ex) { if (b == 0) break; }
|
||||
|
||||
if (curr_size > outData.length)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return outData;
|
||||
}
|
||||
|
||||
private static int[] Decompress11LZ(HexInputStream his) throws IOException {
|
||||
int[] outData = new int[getLength(his)];
|
||||
|
||||
int curr_size = 0;
|
||||
int flags;
|
||||
boolean flag;
|
||||
int b1, bt, b2, b3, len, disp, cdest;
|
||||
|
||||
while (curr_size < outData.length)
|
||||
{
|
||||
try { flags = his.readU8(); }
|
||||
catch (EOFException ex) { break; }
|
||||
|
||||
for (int i = 0; i < 8 && curr_size < outData.length; i++)
|
||||
{
|
||||
flag = (flags & (0x80 >> i)) > 0;
|
||||
if (flag)
|
||||
{
|
||||
try { b1 = his.readU8(); }
|
||||
catch (EOFException ex) { throw new InvalidFileException("Incomplete data"); }
|
||||
|
||||
switch (b1 >> 4)
|
||||
{
|
||||
case 0:
|
||||
// ab cd ef
|
||||
// =>
|
||||
// len = abc + 0x11 = bc + 0x11
|
||||
// disp = def
|
||||
|
||||
len = b1 << 4;
|
||||
try { bt = his.readU8(); }
|
||||
catch (EOFException ex) { throw new InvalidFileException("Incomplete data"); }
|
||||
len |= bt >> 4;
|
||||
len += 0x11;
|
||||
|
||||
disp = (bt & 0x0F) << 8;
|
||||
try { b2 = his.readU8(); }
|
||||
catch (EOFException ex) { throw new InvalidFileException("Incomplete data"); }
|
||||
disp |= b2;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// ab cd ef gh
|
||||
// =>
|
||||
// len = bcde + 0x111
|
||||
// disp = fgh
|
||||
// 10 04 92 3F => disp = 0x23F, len = 0x149 + 0x11 = 0x15A
|
||||
|
||||
try { bt = his.readU8(); b2 = his.readU8(); b3 = his.readU8(); }
|
||||
catch (EOFException ex) { throw new InvalidFileException("Incomplete data"); }
|
||||
|
||||
len = (b1 & 0xF) << 12; // len = b000
|
||||
len |= bt << 4; // len = bcd0
|
||||
len |= (b2 >> 4); // len = bcde
|
||||
len += 0x111; // len = bcde + 0x111
|
||||
disp = (b2 & 0x0F) << 8; // disp = f
|
||||
disp |= b3; // disp = fgh
|
||||
break;
|
||||
|
||||
default:
|
||||
// ab cd
|
||||
// =>
|
||||
// len = a + threshold = a + 1
|
||||
// disp = bcd
|
||||
|
||||
len = (b1 >> 4) + 1;
|
||||
|
||||
disp = (b1 & 0x0F) << 8;
|
||||
try { b2 = his.readU8(); }
|
||||
catch (EOFException ex) { throw new InvalidFileException("Incomplete data"); }
|
||||
disp |= b2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (disp > curr_size)
|
||||
throw new InvalidFileException("Cannot go back more than already written");
|
||||
|
||||
cdest = curr_size;
|
||||
|
||||
for (int j = 0; j < len && curr_size < outData.length; j++)
|
||||
outData[curr_size++] = outData[cdest - disp - 1 + j];
|
||||
|
||||
if (curr_size > outData.length)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
try { outData[curr_size++] = his.readU8(); }
|
||||
catch (EOFException ex) { break; }// throw new Exception("Incomplete data"); }
|
||||
|
||||
if (curr_size > outData.length)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return outData;
|
||||
}
|
||||
|
||||
// note: untested
|
||||
private static int[] DecompressRLE(HexInputStream his) throws IOException{
|
||||
|
||||
int[] outData = new int[getLength(his)];
|
||||
int curr_size = 0;
|
||||
int i, rl;
|
||||
int flag, b;
|
||||
boolean compressed;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// get tag
|
||||
try { flag = his.readU8(); }
|
||||
catch (EOFException ex) { break; }
|
||||
compressed = (flag & 0x80) > 0;
|
||||
rl = flag & 0x7F;
|
||||
if (compressed)
|
||||
rl += 3;
|
||||
else
|
||||
rl += 1;
|
||||
|
||||
if (compressed)
|
||||
{
|
||||
try { b = his.readU8(); }
|
||||
catch (EOFException ex) { break; }
|
||||
for (i = 0; i < rl; i++)
|
||||
outData[curr_size++] = b;
|
||||
}
|
||||
else
|
||||
for (i = 0; i < rl; i++)
|
||||
try { outData[curr_size++] = his.readU8(); }
|
||||
catch (EOFException ex) { break; }
|
||||
|
||||
if (curr_size > outData.length)
|
||||
throw new InvalidFileException("curr_size > decomp_size; "+curr_size+">"+outData.length);
|
||||
if (curr_size == outData.length)
|
||||
break;
|
||||
}
|
||||
return outData;
|
||||
}
|
||||
|
||||
// note: untested
|
||||
private static int[] DecompressHuff(HexInputStream his) throws IOException{
|
||||
|
||||
his.skip(-1);
|
||||
|
||||
int firstByte = his.readU8();
|
||||
|
||||
int dataSize = firstByte & 0x0F;
|
||||
|
||||
if (dataSize != 8 && dataSize != 4)
|
||||
throw new InvalidFileException("Unhandled dataSize "+Integer.toHexString(dataSize));
|
||||
|
||||
int decomp_size = getLength(his);
|
||||
|
||||
int treeSize = his.readU8();
|
||||
HuffTreeNode.maxInpos = 4 + (treeSize + 1) * 2;
|
||||
|
||||
HuffTreeNode rootNode = new HuffTreeNode();
|
||||
rootNode.parseData(his);
|
||||
|
||||
his.setPosition(4 + (treeSize + 1) * 2); // go to start of coded bitstream.
|
||||
// read all data
|
||||
int[] indata = new int[(int)(his.available() - his.getPosition()) / 4];
|
||||
for (int i = 0; i < indata.length; i++)
|
||||
indata[i] = his.readS32();
|
||||
|
||||
int curr_size = 0;
|
||||
decomp_size *= dataSize == 8 ? 1 : 2;
|
||||
int[] outdata = new int[decomp_size];
|
||||
|
||||
int idx = -1;
|
||||
String codestr = "";
|
||||
NLinkedList<Integer> code = new NLinkedList<Integer>();
|
||||
while (curr_size < decomp_size)
|
||||
{
|
||||
try
|
||||
{
|
||||
codestr += Integer.toBinaryString(indata[++idx]);
|
||||
}
|
||||
catch (ArrayIndexOutOfBoundsException e)
|
||||
{
|
||||
throw new InvalidFileException("not enough data.", e);
|
||||
}
|
||||
while (codestr.length() > 0)
|
||||
{
|
||||
code.addFirst(Integer.parseInt(codestr.charAt(0)+""));
|
||||
codestr = codestr.substring(1);
|
||||
Pair<Boolean, Integer> attempt = rootNode.getValue(code.getLast());
|
||||
if (attempt.getFirst())
|
||||
{
|
||||
try
|
||||
{
|
||||
outdata[curr_size++] = attempt.getSecond();
|
||||
}
|
||||
catch (ArrayIndexOutOfBoundsException ex)
|
||||
{
|
||||
if (code.getFirst().getValue() != 0)
|
||||
throw ex;
|
||||
}
|
||||
code.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (codestr.length() > 0 || idx < indata.length-1)
|
||||
{
|
||||
while (idx < indata.length-1)
|
||||
codestr += Integer.toBinaryString(indata[++idx]);
|
||||
codestr = codestr.replace("0", "");
|
||||
if (codestr.length() > 0)
|
||||
System.out.println("too much data; str="+codestr+", idx="+idx+"/"+indata.length);
|
||||
}
|
||||
|
||||
int[] realout;
|
||||
if (dataSize == 4)
|
||||
{
|
||||
realout = new int[decomp_size / 2];
|
||||
for (int i = 0; i < decomp_size / 2; i++)
|
||||
{
|
||||
if ((outdata[i * 2] & 0xF0) > 0
|
||||
|| (outdata[i * 2 + 1] & 0xF0) > 0)
|
||||
throw new InvalidFileException("first 4 bits of data should be 0 if dataSize = 4");
|
||||
realout[i] = (byte)((outdata[i * 2] << 4) | outdata[i * 2 + 1]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
realout = outdata;
|
||||
}
|
||||
return realout;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class HuffTreeNode
|
||||
{
|
||||
protected static int maxInpos = 0;
|
||||
protected HuffTreeNode node0, node1;
|
||||
protected int data = -1; // [-1,0xFF]
|
||||
/// <summary>
|
||||
/// To get a value, provide the last node of a list of bytes < 2.
|
||||
/// the list will be read from back to front.
|
||||
/// </summary>
|
||||
protected Pair<Boolean, Integer> getValue(NLinkedListNode<Integer> code) throws InvalidFileException
|
||||
{
|
||||
Pair<Boolean, Integer> outData = new Pair<Boolean, Integer>();
|
||||
outData.setSecond(data);
|
||||
if (code == null){
|
||||
outData.setFirst(node0 == null && node1 == null && data >= 0);
|
||||
return outData;
|
||||
}
|
||||
|
||||
if(code.getValue() > 1)
|
||||
throw new InvalidFileException("The list should be a list of bytes < 2. got: " + code.getValue());
|
||||
|
||||
int c = code.getValue();
|
||||
HuffTreeNode n = c == 0 ? node0 : node1;
|
||||
if(n == null)
|
||||
outData.setFirst(false);
|
||||
return n.getValue(code.getPrevious());
|
||||
}
|
||||
|
||||
protected int getValue(String code) throws InvalidFileException
|
||||
{
|
||||
NLinkedList<Integer> c = new NLinkedList<Integer>();
|
||||
for(char ch : code.toCharArray())
|
||||
c.addFirst((int)ch);
|
||||
|
||||
Pair<Boolean, Integer> attempt = this.getValue(c.getLast());
|
||||
if(attempt.getFirst())
|
||||
return attempt.getSecond();
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected void parseData(HexInputStream his) throws IOException
|
||||
{
|
||||
/*
|
||||
* 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)
|
||||
*/
|
||||
this.node0 = new HuffTreeNode();
|
||||
this.node1 = new HuffTreeNode();
|
||||
long currPos = his.getPosition();
|
||||
int b = his.readU8();
|
||||
long offset = b & 0x3F;
|
||||
boolean end0 = (b & 0x80) > 0, end1 = (b & 0x40) > 0;
|
||||
// parse data for node0
|
||||
his.setPosition((currPos - (currPos & 1)) + offset * 2 + 2);
|
||||
if (his.getPosition() < maxInpos)
|
||||
{
|
||||
if (end0)
|
||||
node0.data = his.readU8();
|
||||
else
|
||||
node0.parseData(his);
|
||||
}
|
||||
// parse data for node1
|
||||
his.setPosition((currPos - (currPos & 1)) + offset * 2 + 2 + 1);
|
||||
if (his.getPosition() < maxInpos)
|
||||
{
|
||||
if (end1)
|
||||
node1.data = his.readU8();
|
||||
else
|
||||
node1.parseData(his);
|
||||
}
|
||||
// reset position
|
||||
his.setPosition(currPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
if (data < 0)
|
||||
return "<" + node0.toString() + ", " + node1.toString() + ">";
|
||||
else
|
||||
return "["+Integer.toHexString(data)+"]";
|
||||
}
|
||||
|
||||
protected int getDepth()
|
||||
{
|
||||
if (data < 0)
|
||||
return 0;
|
||||
else
|
||||
return 1 + Math.max(node0.getDepth(), node1.getDepth());
|
||||
}
|
||||
}
|
99
Java/NLinkedList.java
Normal file
99
Java/NLinkedList.java
Normal file
@ -0,0 +1,99 @@
|
||||
import java.util.Iterator;
|
||||
|
||||
|
||||
public class NLinkedList<E> implements Iterable<E> {
|
||||
|
||||
private NLinkedListNode<E> head, tail;
|
||||
public NLinkedListNode<E> getFirst(){ return this.head; }
|
||||
public NLinkedListNode<E> getLast(){ return this.tail; }
|
||||
|
||||
public NLinkedList(){
|
||||
head = null;
|
||||
tail = null;
|
||||
}
|
||||
|
||||
public void addFirst(E value){
|
||||
this.addFirst(new NLinkedListNode<E>(value));
|
||||
}
|
||||
public void addFirst(NLinkedListNode<E> node){
|
||||
assert (head == null ? tail == null : false) : "Both head and tail must be null, or neither";
|
||||
|
||||
if(head == null){
|
||||
head = tail = node;
|
||||
node.setNext(null);
|
||||
node.setPrevious(null);
|
||||
} else {
|
||||
node.addBefore(head);
|
||||
head = node;
|
||||
}
|
||||
}
|
||||
public void removeFirst(){
|
||||
if(head != null){
|
||||
NLinkedListNode<E> nHead = head.getNext();
|
||||
head.remove();
|
||||
this.head = nHead;
|
||||
}
|
||||
}
|
||||
|
||||
public void addLast(E value){
|
||||
this.addLast(new NLinkedListNode<E>(value));
|
||||
}
|
||||
public void addLast(NLinkedListNode<E> node){
|
||||
assert (head == null ? tail == null : false) : "Both head and tail must be null, or neither";
|
||||
|
||||
if(tail == null){
|
||||
head = tail = node;
|
||||
node.setNext(null);
|
||||
node.setPrevious(null);
|
||||
} else {
|
||||
node.addAfter(tail);
|
||||
tail = node;
|
||||
}
|
||||
}
|
||||
public void removeLast(){
|
||||
if(tail != null){
|
||||
NLinkedListNode<E> nTail = tail.getPrevious();
|
||||
tail.remove();
|
||||
this.tail = nTail;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty(){
|
||||
return this.head == null;
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
while(!this.isEmpty())
|
||||
this.removeFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
return new Iterator<E>() {
|
||||
NLinkedListNode<E> current = getFirst();
|
||||
boolean removed = false;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return current != null && current.getNext() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E next() {
|
||||
removed = false;
|
||||
if(this.current != null)
|
||||
this.current = this.current.getNext();
|
||||
return this.current != null ? this.current.getValue() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if(removed || this.current == null)
|
||||
return;
|
||||
removed = true;
|
||||
this.current.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
66
Java/NLinkedListNode.java
Normal file
66
Java/NLinkedListNode.java
Normal file
@ -0,0 +1,66 @@
|
||||
public class NLinkedListNode<E> {
|
||||
|
||||
private E value;
|
||||
private NLinkedListNode<E> previous = null, next = null;
|
||||
|
||||
public NLinkedListNode(E value){
|
||||
this.setValue(value);
|
||||
}
|
||||
|
||||
public E getValue(){
|
||||
return this.value;
|
||||
}
|
||||
public void setValue(E value){
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public NLinkedListNode<E> getNext(){
|
||||
return this.next;
|
||||
}
|
||||
public void setNext(NLinkedListNode<E> next){
|
||||
if(this.next != null && this.next.previous == this)
|
||||
this.next.previous = null;
|
||||
this.next = next;
|
||||
if(this.next != null)
|
||||
this.next.previous = this;
|
||||
}
|
||||
public NLinkedListNode<E> getPrevious(){
|
||||
return this.previous;
|
||||
}
|
||||
public void setPrevious(NLinkedListNode<E> previous){
|
||||
if(this.previous != null && this.previous.next == this)
|
||||
this.previous.next = null;
|
||||
this.previous = previous;
|
||||
if(this.previous != null)
|
||||
this.previous.next = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds this node after another node
|
||||
* @param node the node this node should be after
|
||||
*/
|
||||
public void addAfter(NLinkedListNode<E> node){
|
||||
assert node != null : "Cannot add a node after null";
|
||||
NLinkedListNode<E> next = node.next;
|
||||
this.setPrevious(node);
|
||||
this.setNext(next);
|
||||
}
|
||||
/**
|
||||
* Adds this node before another node
|
||||
* @param node the node this node should be before
|
||||
*/
|
||||
public void addBefore(NLinkedListNode<E> node){
|
||||
assert node != null : "Cannot add a node before null";
|
||||
NLinkedListNode<E> prev = node.previous;
|
||||
this.setNext(node);
|
||||
this.setPrevious(prev);
|
||||
}
|
||||
|
||||
public void remove(){
|
||||
if(this.previous != null)
|
||||
this.previous.setNext(this.next);
|
||||
if(this.next != null)
|
||||
this.next.setPrevious(this.previous);
|
||||
}
|
||||
|
||||
}
|
18
Java/Pair.java
Normal file
18
Java/Pair.java
Normal file
@ -0,0 +1,18 @@
|
||||
public class Pair<T,U> {
|
||||
|
||||
private T first;
|
||||
private U second;
|
||||
|
||||
public Pair(T one, U two){
|
||||
this.first = one;
|
||||
this.second = two;
|
||||
}
|
||||
public Pair(){}
|
||||
|
||||
public T getFirst(){ return this.first; }
|
||||
public void setFirst(T value){ this.first = value; }
|
||||
public U getSecond(){ return this.second; }
|
||||
public void setSecond(U value){ this.second = value; }
|
||||
public boolean allSet(){ return this.first != null && this.second != null; }
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user