Last active
August 29, 2015 14:06
-
-
Save SirTony/f0144b0105e4c1e4a109 to your computer and use it in GitHub Desktop.
UI archive compression tool for https://github.com/Evairfairy/PacketRogue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/+ | |
Compile with: | |
dmd pack.d | |
Options: | |
Flag Type Required Default Description | |
--------------------------------------------------------- | |
--out string yes <none> The name of the .ui file to output. | |
--dir string yes <none> The source directory to pack. | |
--width uint yes <none> Window client size width. | |
--height uint yes <none> Window client size height. | |
--silent bool no flase Do not print to the console. | |
--recurse bool no true Resursively search --dir | |
--overwrite bool no false Force overwrite of --out if it already exists. | |
+/ | |
import std.path, | |
std.file, | |
std.stream, | |
std.zlib, | |
std.getopt, | |
std.array, | |
std.random, | |
std.string, | |
io = std.stdio; | |
string output; | |
string root; | |
bool overwrite = false; | |
bool silent = false; | |
bool recurse = true; | |
int width = -1; | |
int height = -1; | |
enum BUFFER_SIZE = 10_240; | |
enum ubyte[] MAGIC = [ 0x50, 0x52, 0x55, 0x49 ]; | |
enum ubyte VERSION_MAJOR = 1; | |
enum ubyte VERSION_MINOR = 0; | |
void main( string[] args ) | |
{ | |
args.getopt( | |
"out", &output, | |
"dir", &root, | |
"width", &width, | |
"height", &height, | |
"silent", &silent, | |
"recurse", &recurse, | |
"overwrite", &overwrite | |
); | |
if( width <= 0 || height <= 0 ) | |
{ | |
io.stderr.writeln( "Width and height must be greater than zero." ); | |
return; | |
} | |
if( output is null || output.length == 0 ) | |
{ | |
io.stderr.writeln( "Output file is required." ); | |
return; | |
} | |
if( output.exists && output.isDir ) | |
{ | |
io.stderr.writeln( "Output is a directory, not a file." ); | |
return; | |
} | |
if( output.exists && !overwrite ) | |
{ | |
io.stdout.writef( "'%s' already exists. Overwrite? [Y/n] ", output ); | |
auto _in = io.stdin.readln().chomp.strip.toLower; | |
if( _in.length < 1 || _in[0] != 'y' ) | |
return; | |
} | |
if( !output.toLower.endsWith( ".ui" ) ) | |
output ~= ".ui"; | |
if( root is null || ( !root.exists && !root.isDir ) ) | |
{ | |
io.stderr.writefln( "The directory '%s' does not exist or is not a directory.", | |
root is null ? "<null>" : root ); | |
return; | |
} | |
if( output.exists ) | |
output.remove(); | |
auto mode = !recurse ? SpanMode.shallow : SpanMode.breadth; | |
auto entries = root.dirEntries( mode ).array; | |
auto tempName = "~" ~ randomString() ~ ".tmp"; | |
auto arch = new File( output, FileMode.In | FileMode.Out ); | |
auto temp = new File( tempName, FileMode.In | FileMode.OutNew ); | |
assert( temp.seekable ); | |
Entry[] files; | |
auto getHeaderSize() | |
{ | |
auto size = 4 //Magic ID length | |
+ 2 //version length | |
+ 4 //width length | |
+ 4 //height length | |
+ 4; //count length | |
if( files.length > 0 ) | |
{ | |
foreach( e; files ) | |
{ | |
size += 4 //compressed size | |
+ 4 //uncompressed size | |
+ 4; //offset | |
size += 2 + e.MIME.length; | |
size += 2 + e.Name.length; | |
} | |
} | |
return size; | |
} | |
arch.write( MAGIC ); | |
arch.write( VERSION_MAJOR ); | |
arch.write( VERSION_MINOR ); | |
arch.write( width ); | |
arch.write( height ); | |
while( entries.length ) | |
{ | |
auto entry = entries.back; | |
entries.popBack(); | |
if( entry.isDir && recurse ) | |
{ | |
entries ~= entry.name.dirEntries( mode ).array; | |
continue; | |
} | |
string name = entry.name.normalize(); | |
Entry file = { | |
0, 0, 0, | |
name.getMimeType(), | |
name, | |
entry | |
}; | |
files ~= file; | |
} | |
arch.write( files.length ); | |
arch.flush(); | |
auto headerSize = getHeaderSize(); | |
if( files.length == 0 ) | |
goto End; | |
foreach( ref entry; files ) | |
{ | |
printfln( "Packing '%s'.", entry.Name ); | |
auto input = new File( entry.path, FileMode.In | FileMode.Out ); | |
auto compressor = new Compress( 9, HeaderFormat.deflate ); | |
auto before = temp.position; | |
const( void )[] chunk; | |
while( !input.eof ) | |
{ | |
auto left = input.available > BUFFER_SIZE | |
? BUFFER_SIZE | |
: input.available; | |
ubyte[] buf = new ubyte[]( cast(uint)left ); | |
input.read( buf ); | |
chunk = compressor.compress( cast(void[])buf ); | |
temp.write( cast(ubyte[])chunk ); | |
} | |
chunk = compressor.flush(); | |
temp.write( cast(ubyte[])chunk ); | |
temp.flush(); | |
entry.UncompressedSize = cast(int)input.size; | |
entry.CompressedSize = cast(int)( temp.position - before ); | |
entry.Offset = cast(int)( headerSize + before ); | |
input.close(); | |
arch.write( entry.CompressedSize ); | |
arch.write( entry.UncompressedSize ); | |
arch.write( entry.Offset ); | |
short len = cast(short)entry.MIME.length; | |
ubyte[] buf = cast(ubyte[])entry.MIME.dup; | |
arch.write( len ); | |
arch.write( buf ); | |
len = cast(short)entry.Name.length; | |
buf = cast(ubyte[])entry.Name.dup; | |
arch.write( len ); | |
arch.write( buf ); | |
arch.flush(); | |
} | |
temp.seekSet( 0 ); | |
arch.copyFrom( temp ); | |
arch.flush(); | |
End: | |
temp.close(); | |
arch.close(); | |
tempName.remove(); | |
println( "Done." ); | |
} | |
enum MimeLookup = [ | |
"ai": "application/postscript", | |
"aif": "audio/x-aiff", | |
"aifc": "audio/x-aiff", | |
"aiff": "audio/x-aiff", | |
"asc": "text/plain", | |
"atom": "application/atom+xml", | |
"au": "audio/basic", | |
"avi": "video/x-msvideo", | |
"bcpio": "application/x-bcpio", | |
"bin": "application/octet-stream", | |
"bmp": "image/bmp", | |
"cdf": "application/x-netcdf", | |
"cgm": "image/cgm", | |
"class": "application/octet-stream", | |
"cpio": "application/x-cpio", | |
"cpt": "application/mac-compactpro", | |
"csh": "application/x-csh", | |
"css": "text/css", | |
"dcr": "application/x-director", | |
"dif": "video/x-dv", | |
"dir": "application/x-director", | |
"djv": "image/vnd.djvu", | |
"djvu": "image/vnd.djvu", | |
"dll": "application/octet-stream", | |
"dmg": "application/octet-stream", | |
"dms": "application/octet-stream", | |
"doc": "application/msword", | |
"dtd": "application/xml-dtd", | |
"dv": "video/x-dv", | |
"dvi": "application/x-dvi", | |
"dxr": "application/x-director", | |
"eps": "application/postscript", | |
"etx": "text/x-setext", | |
"exe": "application/octet-stream", | |
"ez": "application/andrew-inset", | |
"gif": "image/gif", | |
"gram": "application/srgs", | |
"grxml": "application/srgs+xml", | |
"gtar": "application/x-gtar", | |
"hdf": "application/x-hdf", | |
"hqx": "application/mac-binhex40", | |
"htm": "text/html", | |
"html": "text/html", | |
"ice": "x-conference/x-cooltalk", | |
"ico": "image/x-icon", | |
"ics": "text/calendar", | |
"ief": "image/ief", | |
"ifb": "text/calendar", | |
"iges": "model/iges", | |
"igs": "model/iges", | |
"jnlp": "application/x-java-jnlp-file", | |
"jp2": "image/jp2", | |
"jpe": "image/jpeg", | |
"jpeg": "image/jpeg", | |
"jpg": "image/jpeg", | |
"js": "application/x-javascript", | |
"kar": "audio/midi", | |
"latex": "application/x-latex", | |
"lha": "application/octet-stream", | |
"lzh": "application/octet-stream", | |
"m3u": "audio/x-mpegurl", | |
"m4a": "audio/mp4a-latm", | |
"m4b": "audio/mp4a-latm", | |
"m4p": "audio/mp4a-latm", | |
"m4u": "video/vnd.mpegurl", | |
"m4v": "video/x-m4v", | |
"mac": "image/x-macpaint", | |
"man": "application/x-troff-man", | |
"mathml": "application/mathml+xml", | |
"me": "application/x-troff-me", | |
"mesh": "model/mesh", | |
"mid": "audio/midi", | |
"midi": "audio/midi", | |
"mif": "application/vnd.mif", | |
"mov": "video/quicktime", | |
"movie": "video/x-sgi-movie", | |
"mp2": "audio/mpeg", | |
"mp3": "audio/mpeg", | |
"mp4": "video/mp4", | |
"mpe": "video/mpeg", | |
"mpeg": "video/mpeg", | |
"mpg": "video/mpeg", | |
"mpga": "audio/mpeg", | |
"ms": "application/x-troff-ms", | |
"msh": "model/mesh", | |
"mxu": "video/vnd.mpegurl", | |
"nc": "application/x-netcdf", | |
"oda": "application/oda", | |
"ogg": "application/ogg", | |
"pbm": "image/x-portable-bitmap", | |
"pct": "image/pict", | |
"pdb": "chemical/x-pdb", | |
"pdf": "application/pdf", | |
"pgm": "image/x-portable-graymap", | |
"pgn": "application/x-chess-pgn", | |
"pic": "image/pict", | |
"pict": "image/pict", | |
"png": "image/png", | |
"pnm": "image/x-portable-anymap", | |
"pnt": "image/x-macpaint", | |
"pntg": "image/x-macpaint", | |
"ppm": "image/x-portable-pixmap", | |
"ppt": "application/vnd.ms-powerpoint", | |
"ps": "application/postscript", | |
"qt": "video/quicktime", | |
"qti": "image/x-quicktime", | |
"qtif": "image/x-quicktime", | |
"ra": "audio/x-pn-realaudio", | |
"ram": "audio/x-pn-realaudio", | |
"ras": "image/x-cmu-raster", | |
"rdf": "application/rdf+xml", | |
"rgb": "image/x-rgb", | |
"rm": "application/vnd.rn-realmedia", | |
"roff": "application/x-troff", | |
"rtf": "text/rtf", | |
"rtx": "text/richtext", | |
"sgm": "text/sgml", | |
"sgml": "text/sgml", | |
"sh": "application/x-sh", | |
"shar": "application/x-shar", | |
"silo": "model/mesh", | |
"sit": "application/x-stuffit", | |
"skd": "application/x-koan", | |
"skm": "application/x-koan", | |
"skp": "application/x-koan", | |
"skt": "application/x-koan", | |
"smi": "application/smil", | |
"smil": "application/smil", | |
"snd": "audio/basic", | |
"so": "application/octet-stream", | |
"spl": "application/x-futuresplash", | |
"src": "application/x-wais-source", | |
"sv4cpio": "application/x-sv4cpio", | |
"sv4crc": "application/x-sv4crc", | |
"svg": "image/svg+xml", | |
"swf": "application/x-shockwave-flash", | |
"t": "application/x-troff", | |
"tar": "application/x-tar", | |
"tcl": "application/x-tcl", | |
"tex": "application/x-tex", | |
"texi": "application/x-texinfo", | |
"texinfo": "application/x-texinfo", | |
"tif": "image/tiff", | |
"tiff": "image/tiff", | |
"tr": "application/x-troff", | |
"tsv": "text/tab-separated-values", | |
"txt": "text/plain", | |
"ustar": "application/x-ustar", | |
"vcd": "application/x-cdlink", | |
"vrml": "model/vrml", | |
"vxml": "application/voicexml+xml", | |
"wav": "audio/x-wav", | |
"wbmp": "image/vnd.wap.wbmp", | |
"wbmxl": "application/vnd.wap.wbxml", | |
"wml": "text/vnd.wap.wml", | |
"wmlc": "application/vnd.wap.wmlc", | |
"wmls": "text/vnd.wap.wmlscript", | |
"wmlsc": "application/vnd.wap.wmlscriptc", | |
"wrl": "model/vrml", | |
"xbm": "image/x-xbitmap", | |
"xht": "application/xhtml+xml", | |
"xhtml": "application/xhtml+xml", | |
"xls": "application/vnd.ms-excel", | |
"xml": "application/xml", | |
"xpm": "image/x-xpixmap", | |
"xsl": "application/xml", | |
"xslt": "application/xslt+xml", | |
"xul": "application/vnd.mozilla.xul+xml", | |
"xwd": "image/x-xwindowdump", | |
"xyz": "chemical/x-xyz", | |
"zip": "application/zip" | |
]; | |
struct Entry | |
{ | |
int CompressedSize; | |
int UncompressedSize; | |
int Offset; | |
string MIME; | |
string Name; | |
string path; //This is an internal field and | |
//won't be written to the archive. | |
} | |
string getMimeType( in string file ) | |
{ | |
auto ext = file.extension.toLower[1 .. $]; | |
auto mime = ext in MimeLookup; | |
if( !mime ) | |
return "application/octet-stream"; | |
else | |
return *mime; | |
} | |
string randomString( int length = 15, char[] chars = null ) | |
{ | |
if( chars is null ) | |
chars = "ABCDEFGIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz".dup; | |
string result; | |
foreach( _; 0 .. length ) | |
result ~= chars[uniform( 0, chars.length )]; | |
return result; | |
} | |
void printfln( T... )( string format, T args ) | |
{ | |
if( silent ) | |
return; | |
io.writefln( format, args ); | |
} | |
void println( T... )( T args ) | |
{ | |
if( silent ) | |
return; | |
io.writeln( args ); | |
} | |
string normalize( in string str ) | |
{ | |
return str[root.length + 1 .. $].replace( "\\", "/" ); | |
} | |
bool endsWith( in string str, in string value ) | |
{ | |
if( value.length > str.length ) | |
return false; | |
if( value == str ) | |
return true; | |
return str[str.length - value.length .. $] == value; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment