2011-04-21 15:58:07 +02:00
using System ;
using System.Collections.Generic ;
using System.Text ;
2011-11-13 17:14:45 +01:00
using System.IO ;
2011-05-15 17:16:04 +02:00
using DSDecmp.Formats.Nitro ;
using DSDecmp.Formats ;
2011-04-21 15:58:07 +02:00
namespace DSDecmp
{
2011-11-13 17:14:45 +01:00
2011-04-21 15:58:07 +02:00
public static class NewProgram
{
2011-11-13 17:14:45 +01:00
2011-05-15 17:16:04 +02:00
/// <summary>
/// The formats allowed when compressing a file.
/// </summary>
public enum Formats
{
2011-11-13 17:14:45 +01:00
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)
2011-05-15 17:16:04 +02:00
LZ10 ,
LZ11 ,
HUFF4 ,
HUFF8 ,
RLE ,
HUFF ,
NDS ,
2011-11-13 17:14:45 +01:00
GBA
2011-05-15 17:16:04 +02:00
}
2011-11-13 17:14:45 +01:00
public static void MainNewOld ( string [ ] args )
2011-04-21 15:58:07 +02:00
{
if ( args . Length = = 0 )
{
PrintUsage ( ) ;
#if DEBUG
Console . ReadLine ( ) ;
#endif
return ;
}
2011-05-15 17:16:04 +02:00
int argIndex = 0 ;
bool compress = false ;
Formats compressFormat = Formats . NDS ;
#region check for the - c option and its parameter ( s )
if ( args [ argIndex ] . Equals ( "-c" ) )
{
argIndex + + ;
compress = true ;
if ( args . Length < argIndex + 2 )
{
Console . WriteLine ( "A compression format and input file is required in order to compress." ) ;
Console . WriteLine ( ) ;
PrintUsage ( ) ;
return ;
}
switch ( args [ argIndex ] . ToLower ( ) )
{
case "lz10" : compressFormat = Formats . LZ10 ; break ;
case "lz11" : compressFormat = Formats . LZ11 ; break ;
case "lzovl" : compressFormat = Formats . LZOVL ; break ;
case "rle" : compressFormat = Formats . RLE ; break ;
case "huff4" : compressFormat = Formats . HUFF4 ; break ;
case "huff8" : compressFormat = Formats . HUFF8 ; break ;
case "huff" : compressFormat = Formats . HUFF ; break ;
case "gba*" : compressFormat = Formats . GBA ; break ;
case "nds*" : compressFormat = Formats . NDS ; break ;
default :
Console . WriteLine ( "Unknown compression format " + args [ argIndex ] ) ;
Console . WriteLine ( ) ;
PrintUsage ( ) ;
return ;
}
argIndex + + ;
// handle the format options
switch ( compressFormat )
{
case Formats . LZ10 :
case Formats . GBA :
if ( args [ argIndex ] . Equals ( "-opt" ) )
{
LZ10 . LookAhead = true ;
argIndex + + ;
}
break ;
case Formats . LZ11 :
if ( args [ argIndex ] . Equals ( "-opt" ) )
{
LZ11 . LookAhead = true ;
argIndex + + ;
}
break ;
case Formats . LZOVL :
if ( args [ argIndex ] . Equals ( "-opt" ) )
{
LZOvl . LookAhead = true ;
argIndex + + ;
}
break ;
case Formats . NDS :
if ( args [ argIndex ] . Equals ( "-opt" ) )
{
LZ10 . LookAhead = true ;
LZ11 . LookAhead = true ;
LZOvl . LookAhead = true ;
argIndex + + ;
}
break ;
}
}
#endregion
if ( args . Length < argIndex + 1 )
throw new ArgumentException ( "No input file given." ) ;
bool guessExtension = false ;
if ( args [ argIndex ] . Equals ( "-ge" ) )
{
guessExtension = true ;
argIndex + + ;
}
if ( args . Length < argIndex + 1 )
throw new ArgumentException ( "No input file given." ) ;
string input = args [ argIndex + + ] ;
string output = null ;
if ( args . Length > argIndex )
output = args [ argIndex + + ] ;
if ( compress )
Compress ( input , output , compressFormat , guessExtension ) ;
else
Decompress ( input , output , guessExtension ) ;
2011-04-21 15:58:07 +02:00
#if DEBUG
Console . ReadLine ( ) ;
#endif
}
private static void PrintUsage ( )
{
Console . WriteLine ( "DSDecmp - Decompressor for compression formats used on the NDS - by Barubary" ) ;
Console . WriteLine ( ) ;
2011-05-15 17:16:04 +02:00
Console . WriteLine ( "Usage:\tDSDecmp (-c FORMAT FORMATOPT*) (-ge) input (output)" ) ;
2011-04-21 15:58:07 +02:00
Console . WriteLine ( ) ;
Console . WriteLine ( "Without the -c modifier, DSDecmp will decompress the input file to the output" ) ;
2011-05-15 17:49:21 +02:00
Console . WriteLine ( " file. If the output file is a directory, the output file will be placed in" ) ;
Console . WriteLine ( " that directory with the same filename as the original file. The extension will" ) ;
Console . WriteLine ( " be appended with a format-specific extension." ) ;
2011-04-21 15:58:07 +02:00
Console . WriteLine ( "The input can also be a directory. In that case, it would be the same as" ) ;
2011-05-15 17:49:21 +02:00
Console . WriteLine ( " calling DSDecmp for every non-directory in the given directory with the same" ) ;
Console . WriteLine ( " options, with one exception; the output is by default the input folder, but" ) ;
Console . WriteLine ( " with '_dec' appended." ) ;
Console . WriteLine ( "If the output does not exist, it is assumed to be the same type as the input" ) ;
Console . WriteLine ( " (file or directory)." ) ;
2011-04-21 15:58:07 +02:00
Console . WriteLine ( "If there is no output file given, it is assumed to be the directory of the" ) ;
2011-05-15 17:49:21 +02:00
Console . WriteLine ( " input file." ) ;
2011-04-21 15:58:07 +02:00
Console . WriteLine ( ) ;
Console . WriteLine ( "With the -ge option, instead of a format-specific extension, the extension" ) ;
2011-05-15 17:49:21 +02:00
Console . WriteLine ( " will be guessed from the first four bytes of the output file. Only" ) ;
Console . WriteLine ( " non-accented letters or numbers are considered in those four bytes." ) ;
2011-04-21 15:58:07 +02:00
Console . WriteLine ( ) ;
Console . WriteLine ( "With the -c option, the input is compressed instead of decompressed. FORMAT" ) ;
Console . WriteLine ( "indicates the desired compression format, and can be one of:" ) ;
Console . WriteLine ( " --- formats built-in in the NDS ---" ) ;
Console . WriteLine ( " lz10 - 'default' LZ-compression format." ) ;
2011-05-15 17:53:00 +02:00
Console . WriteLine ( " lz11 - LZ-compression format better suited for files with long repetitions" ) ;
2011-04-21 15:58:07 +02:00
Console . WriteLine ( " lzovl - LZ-compression used in 'overlay files'." ) ;
Console . WriteLine ( " rle - Run-Length Encoding 'compression'." ) ;
Console . WriteLine ( " huff4 - Huffman compression with 4-bit sized data blocks." ) ;
Console . WriteLine ( " huff8 - Huffman compression with 8-bit sized data blocks." ) ;
Console . WriteLine ( " --- utility 'formats' ---" ) ;
Console . WriteLine ( " huff - The Huffman compression that gives the bext compression ratio." ) ;
Console . WriteLine ( " nds* - The built-in compression format that gives the best compression" ) ;
2011-05-15 17:16:04 +02:00
Console . WriteLine ( " ratio. Will never compress using lzovl." ) ;
2011-04-21 15:58:07 +02:00
Console . WriteLine ( " gba* - The built-in compression format that gives the best compression" ) ;
Console . WriteLine ( " ratio, and is also supported by the GBA." ) ;
Console . WriteLine ( ) ;
2011-05-15 17:16:04 +02:00
Console . WriteLine ( "The following format options (FORMATOPT) are available:" ) ;
Console . WriteLine ( " lz10, lz11, lzovl, gba* and nds*:" ) ;
2011-05-14 16:19:11 +02:00
Console . WriteLine ( " -opt : employs a better compression algorithm to boost the compression" ) ;
Console . WriteLine ( " ratio. Not using this option will result in using the algorithm" ) ;
Console . WriteLine ( " originally used to compress the game files." ) ;
2011-05-15 17:16:04 +02:00
Console . WriteLine ( " Using this option for the gba* and nds* will only have effect on" ) ;
Console . WriteLine ( " the lz10, lz11 and lzovl algorithms." ) ;
Console . WriteLine ( ) ;
Console . WriteLine ( "If the input is a directory when the -c option, the default output directory" ) ;
2011-05-15 17:49:21 +02:00
Console . WriteLine ( " is the input directory appended with '_cmp'." ) ;
2011-05-14 16:19:11 +02:00
Console . WriteLine ( ) ;
2011-04-21 15:58:07 +02:00
Console . WriteLine ( "Supplying the -ge modifier together with the -c modifier, the extension of the" ) ;
2011-05-15 17:49:21 +02:00
Console . WriteLine ( " compressed files will be extended with the 'FORMAT' value that always results" ) ;
Console . WriteLine ( " in that particualr format (so 'lz11', 'rle', etc)." ) ;
2011-04-21 15:58:07 +02:00
Console . WriteLine ( "If the -ge modifier is not present, the extension of compressed files will be" ) ;
2011-05-15 17:49:21 +02:00
Console . WriteLine ( " extended with .cdat" ) ;
2011-04-21 15:58:07 +02:00
}
2011-05-15 17:16:04 +02:00
#region compression methods
private static void Compress ( string input , string output , Formats format , bool guessExtension )
{
if ( ! File . Exists ( input ) & & ! Directory . Exists ( input ) )
{
Console . WriteLine ( "Cannot compress a file or directory that does not exist (" + input + ")" ) ;
return ;
}
// set the default value of the output
if ( string . IsNullOrEmpty ( output ) )
{
if ( Directory . Exists ( input ) )
{
2011-05-15 17:49:21 +02:00
string newDir = Path . GetFullPath ( input ) + "_cmp" ;
if ( ! Directory . Exists ( newDir ) )
Directory . CreateDirectory ( newDir ) ;
2011-05-15 17:16:04 +02:00
foreach ( string file in Directory . GetFiles ( input ) )
{
2011-05-15 17:49:21 +02:00
Compress ( file , newDir , format , guessExtension ) ;
2011-05-15 17:16:04 +02:00
}
return ;
}
else
{
if ( ! guessExtension )
output = input ; // the .cdat extension is added automatically
else
output = Path . GetDirectoryName ( input ) ;
}
}
if ( Directory . Exists ( input ) )
{
if ( ! Directory . Exists ( output ) )
Directory . CreateDirectory ( output ) ;
foreach ( string file in Directory . GetFiles ( input ) )
{
Compress ( file , output , format , guessExtension ) ;
}
return ;
}
// compress the input
MemoryStream compressedData = new MemoryStream ( ) ;
Formats compressedFormat ;
int outsize = DoCompress ( input , compressedData , format , out compressedFormat ) ;
if ( outsize < 0 )
return ;
2011-05-15 17:49:21 +02:00
bool mustAppendExt = ! Directory . Exists ( output ) & & ! File . Exists ( output ) ;
2011-05-15 17:16:04 +02:00
if ( Directory . Exists ( output ) )
{
output = CombinePaths ( output , Path . GetFileName ( input ) ) ;
}
2011-05-15 17:49:21 +02:00
if ( mustAppendExt & & Path . GetExtension ( output ) = = ".dat" )
2011-05-15 17:16:04 +02:00
output = RemoveExtension ( output ) ;
if ( guessExtension )
output + = "." + compressedFormat . ToString ( ) . ToLower ( ) ;
2011-05-15 17:49:21 +02:00
else if ( mustAppendExt )
2011-05-15 17:16:04 +02:00
output + = ".cdat" ;
using ( FileStream outStream = File . Create ( output ) )
{
compressedData . WriteTo ( outStream ) ;
Console . WriteLine ( compressedFormat . ToString ( ) + "-compressed " + input + " to " + output ) ;
}
}
private static int DoCompress ( string infile , MemoryStream output , Formats format , out Formats actualFormat )
{
CompressionFormat fmt = null ;
switch ( format )
{
case Formats . LZ10 : fmt = new LZ10 ( ) ; break ;
case Formats . LZ11 : fmt = new LZ11 ( ) ; break ;
case Formats . LZOVL : fmt = new LZOvl ( ) ; break ;
case Formats . RLE : fmt = new RLE ( ) ; break ;
2011-11-13 17:14:45 +01:00
case Formats . HUFF4 : fmt = new Huffman4 ( ) ; break ;
case Formats . HUFF8 : fmt = new Huffman8 ( ) ; break ;
2011-05-15 17:16:04 +02:00
case Formats . HUFF :
return CompressHuff ( infile , output , out actualFormat ) ;
case Formats . GBA :
return CompressGBA ( infile , output , out actualFormat ) ;
case Formats . NDS :
return CompressNDS ( infile , output , out actualFormat ) ;
default :
throw new Exception ( "Unhandled compression format " + format ) ;
}
actualFormat = format ;
using ( FileStream inStream = File . OpenRead ( infile ) )
{
try
{
return fmt . Compress ( inStream , inStream . Length , output ) ;
}
catch ( Exception s )
{
// any exception generated by compression is a fatal exception
Console . WriteLine ( s . Message ) ;
return - 1 ;
}
}
}
private static int CompressHuff ( string infile , MemoryStream output , out Formats actualFormat )
{
return CompressBest ( infile , output , out actualFormat , Formats . HUFF4 , Formats . HUFF8 ) ;
}
private static int CompressGBA ( string infile , MemoryStream output , out Formats actualFormat )
{
return CompressBest ( infile , output , out actualFormat , Formats . HUFF4 , Formats . HUFF8 , Formats . LZ10 , Formats . RLE ) ;
}
private static int CompressNDS ( string infile , MemoryStream output , out Formats actualFormat )
{
return CompressBest ( infile , output , out actualFormat , Formats . HUFF4 , Formats . HUFF8 , Formats . LZ10 , Formats . LZ11 , Formats . RLE ) ;
}
private static int CompressBest ( string infile , MemoryStream output , out Formats actualFormat , params Formats [ ] formats )
{
// only read the input data once from the file.
byte [ ] inputData ;
using ( FileStream inStream = File . OpenRead ( infile ) )
{
inputData = new byte [ inStream . Length ] ;
inStream . Read ( inputData , 0 , inputData . Length ) ;
}
MemoryStream bestOutput = null ;
int minCompSize = int . MaxValue ;
actualFormat = Formats . GBA ;
foreach ( Formats format in formats )
{
#region compress the file in each format , and save the best one
MemoryStream currentOutput = new MemoryStream ( ) ;
CompressionFormat realFormat = null ;
switch ( format )
{
2011-11-13 17:14:45 +01:00
case Formats . HUFF4 : realFormat = new Huffman4 ( ) ; break ;
case Formats . HUFF8 : realFormat = new Huffman8 ( ) ; break ;
2011-05-15 17:16:04 +02:00
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 ;
2011-11-13 17:14:45 +01:00
default :
Console . WriteLine ( "Unsupported single format: " + format ) ;
continue ;
2011-05-15 17:16:04 +02:00
}
int currentOutSize ;
try
{
using ( MemoryStream inStream = new MemoryStream ( inputData ) )
{
currentOutSize = realFormat . Compress ( inStream , inStream . Length , currentOutput ) ;
}
}
catch ( InputTooLargeException i )
{
Console . WriteLine ( i . Message ) ;
actualFormat = format ;
return - 1 ;
}
catch ( Exception )
{
continue ;
}
if ( currentOutSize < minCompSize )
{
bestOutput = currentOutput ;
minCompSize = currentOutSize ;
actualFormat = format ;
}
#endregion
}
if ( bestOutput = = null )
{
Console . WriteLine ( "The file could not be compressed in any format." ) ;
return - 1 ;
}
bestOutput . WriteTo ( output ) ;
return minCompSize ;
}
#endregion
#region decompression methods
private static void Decompress ( string input , string output , bool guessExtension )
{
if ( ! File . Exists ( input ) & & ! Directory . Exists ( input ) )
{
Console . WriteLine ( "Cannot decompress a file or directory that does not exist (" + input + ")" ) ;
return ;
}
// set the default value of the output
if ( string . IsNullOrEmpty ( output ) )
{
if ( Directory . Exists ( input ) )
{
2011-05-15 17:49:21 +02:00
string newDir = Path . GetFullPath ( input ) + "_dec" ;
if ( ! Directory . Exists ( newDir ) )
Directory . CreateDirectory ( newDir ) ;
2011-05-15 17:16:04 +02:00
foreach ( string file in Directory . GetFiles ( input ) )
{
2011-05-15 17:49:21 +02:00
Decompress ( file , newDir , guessExtension ) ;
2011-05-15 17:16:04 +02:00
}
return ;
}
else
{
if ( ! guessExtension )
output = input ; // '.dat' gets added automatically if -ge is not given
else
output = Path . GetDirectoryName ( input ) ;
}
}
if ( Directory . Exists ( input ) )
{
if ( File . Exists ( output ) )
{
Console . WriteLine ( "Cannot decompress a folder to a single file." ) ;
return ;
}
if ( ! Directory . Exists ( output ) )
Directory . CreateDirectory ( output ) ;
foreach ( string file in Directory . GetFiles ( input ) )
{
Decompress ( file , output , guessExtension ) ;
}
return ;
}
byte [ ] inData ;
using ( FileStream inStream = File . OpenRead ( input ) )
{
inData = new byte [ inStream . Length ] ;
inStream . Read ( inData , 0 , inData . Length ) ;
}
MemoryStream decompressedData = new MemoryStream ( ) ;
long decSize = - 1 ;
Formats usedFormat = Formats . NDS ;
// just try all formats, and stop once one has been found that can decompress it.
foreach ( Formats f in Enum . GetValues ( typeof ( Formats ) ) )
{
using ( MemoryStream inStream = new MemoryStream ( inData ) )
{
2011-11-13 17:14:45 +01:00
decSize = Decompress ( inStream , decompressedData , null ) ;
2011-05-15 17:16:04 +02:00
if ( decSize > = 0 )
{
usedFormat = f ;
break ;
}
}
}
if ( decSize < 0 )
{
Console . WriteLine ( "Could not decompress " + input + "; no matching compression method found." ) ;
return ;
}
2011-05-15 17:49:21 +02:00
bool mustAppendExt = ! Directory . Exists ( output ) & & ! File . Exists ( output ) ;
2011-05-15 17:16:04 +02:00
if ( Directory . Exists ( output ) )
{
output = CombinePaths ( output , Path . GetFileName ( input ) ) ;
}
2011-05-15 17:49:21 +02:00
byte [ ] outData = decompressedData . ToArray ( ) ;
if ( mustAppendExt )
2011-05-15 17:16:04 +02:00
{
2011-05-15 17:49:21 +02:00
switch ( Path . GetExtension ( output ) )
{
case ".cdat" :
case ".lz10" :
case ".lz11" :
case ".lzovl" :
case ".rle" :
case ".huff4" :
case ".huff8" :
output = RemoveExtension ( output ) ;
break ;
}
2011-05-15 17:16:04 +02:00
}
if ( guessExtension )
{
string ext = "" ;
for ( int i = 0 ; i < 4 ; i + + )
{
if ( ( outData [ i ] > = 'a' & & outData [ i ] < = 'z' )
| | ( outData [ i ] > = 'A' & & outData [ i ] < = 'Z' )
| | char . IsDigit ( ( char ) outData [ i ] ) )
ext + = ( char ) outData [ i ] ;
else
break ;
}
if ( ext . Length > 0 )
output + = "." + ext ;
else
output + = ".dat" ;
}
2011-05-15 17:49:21 +02:00
else if ( mustAppendExt )
2011-05-15 17:16:04 +02:00
output + = ".dat" ;
using ( FileStream outStream = File . Create ( output ) )
{
outStream . Write ( outData , 0 , outData . Length ) ;
2011-05-15 17:49:21 +02:00
Console . WriteLine ( usedFormat . ToString ( ) + "-decompressed " + input + " to " + output ) ;
2011-05-15 17:16:04 +02:00
}
}
2011-11-13 17:14:45 +01:00
private static long Decompress ( MemoryStream inputStream , MemoryStream output , CompressionFormat format )
2011-05-15 17:16:04 +02:00
{
2011-11-13 17:14:45 +01:00
if ( ! format . Supports ( inputStream , inputStream . Length ) )
2011-05-15 17:16:04 +02:00
return - 1 ;
try
{
2011-11-13 17:14:45 +01:00
return format . Decompress ( inputStream , inputStream . Length , output ) ;
2011-05-15 17:16:04 +02:00
}
catch ( TooMuchInputException e )
{
Console . WriteLine ( e . Message ) ;
return output . Length ;
}
catch ( Exception e )
{
2011-11-13 17:14:45 +01:00
Console . WriteLine ( "Could not decompress using the " + format . ShortFormatString + " format; " + e . Message ) ;
2011-05-15 17:16:04 +02:00
return - 1 ;
}
}
#endregion
private static string CombinePaths ( string dir , string file )
{
if ( Path . IsPathRooted ( file ) )
return file ;
if ( ! dir . EndsWith ( Path . DirectorySeparatorChar + "" )
& & ! dir . EndsWith ( Path . AltDirectorySeparatorChar + "" ) )
return dir + Path . DirectorySeparatorChar + file ;
else
return dir + file ;
}
private static string RemoveExtension ( string path )
{
return Path . GetDirectoryName ( path ) + Path . DirectorySeparatorChar + Path . GetFileNameWithoutExtension ( path ) ;
}
2011-04-21 15:58:07 +02:00
}
}