├── LICENSE ├── README.md ├── dub.json ├── images ├── Bin2D_example.gif └── ProjectFolder1.PNG └── source └── Bin2D.d /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Richard Andrew Cattermole 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bin2D 2 | ===== 3 | 4 | A Command line application that produces D module files, which can be compiled into an executable and extracted at startup. 5 | 6 | ## Features: 7 | 8 | - Limit code generated by: 9 | - package modifier 10 | - ``version(unittest)`` 11 | - Option for enum for usage at compile time, instead of ``const(ubyte[])`` 12 | - Automatic finding/inclusion of files in folders. 13 | - Outputs included files at runtime to a specified directory or temporary directory 14 | - __Warning extra files in specified folder will be removed__ 15 | 16 | ## Known limitations 17 | - Does not allow for filenames used in different directories 18 | 19 | ## Basic usage: 20 | Basic usage is as follows 21 | ```Bin2D [=] ``` 22 | 23 | **Example** 24 | I have a tkd project that I want to pack up into a single executable. 25 | I need some files and dll's for it to work. 26 | 27 | Folder of needed stuff: 28 | 29 | ![Folder of needed stuff](images/ProjectFolder1.PNG) 30 | 31 | I added the Bin2d.exe to my path for convience. 32 | 33 | ![The process](images/Bin2D_example.gif) 34 | 35 | ```Bin2D MODULE.d=Resource_Reference library tk86t.dll tcl86t.dll "my tkd app.exe" ``` 36 | 37 | Create this(MAIN.d) file and added to my C:\temp folder. 38 | ```D 39 | import std.stdio; 40 | import std.process; 41 | import PKG = Resource_Reference; 42 | 43 | void main() { 44 | string[string] FILE_LOCATIONS = PKG.outputFilesToFileSystem(); 45 | 46 | foreach(string key; PKG.originalNames){ 47 | writeln("extracting: ", key , " : " , FILE_LOCATIONS[key] ); 48 | } 49 | execute(FILE_LOCATIONS["my tkd app.exe"]); 50 | PKG.cleanup(); 51 | } 52 | ``` 53 | Compile with: 54 | 55 | ```dmd MAIN.d MODULE.d``` 56 | 57 | If you want to do what I did with a gui app you might want to link to windows:subsystem. 58 | ## But what if I don't know the name at compile time? 59 | To get access to *all* the values with names you need to iterate over two seperate arrays. 60 | The first ``names`` will give you the mangled names. The second ``values`` will give you the values based upon the index in assetNames. 61 | 62 | ## So how do you extract? 63 | 64 | This will extract any files given to it. With specific output directory. 65 | It returns an array of the file systems names with the original extension. Directories have been encoded away however. 66 | ```D 67 | import modulename; 68 | outputFilesToFileSystem("output/stored/here"); 69 | ``` 70 | 71 | **And for a temporary directories?** 72 | ```D 73 | import modulename; 74 | outputFilesToFileSystem(); 75 | ``` 76 | It does return the same result as output outputBin2D2FS(string) does. 77 | 78 | ## Why not string imports? 79 | - String mixins at least on Windows are bugged. They cannot use subdirectories. 80 | In newer versions this should be fixed. 81 | - Assets do not change often so regeneration process can be manual. 82 | - Easy export to file system. 83 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bin2d", 3 | "description": "A tool that enables files to be compiled into an executable and extracted at startup.", 4 | "homepage": "https://github.com/rikkimax/Bin2D", 5 | "copyright": "Copyright © 2013, Richard Andrew Cattermole", 6 | "authors": [ 7 | "Richard Andrew Cattermole" 8 | ], 9 | "license": "MIT", 10 | "name": "bin2d", 11 | "targetType": "executable", 12 | "sourceFiles": ["source/Bin2D.d"], 13 | "targetName": "Bin2D", 14 | "targetPath": "bin" 15 | } 16 | -------------------------------------------------------------------------------- /images/Bin2D_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rikkimax/Bin2D/3c968c28f979c6e687f545e3a23ee7f76e2a25de/images/Bin2D_example.gif -------------------------------------------------------------------------------- /images/ProjectFolder1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rikkimax/Bin2D/3c968c28f979c6e687f545e3a23ee7f76e2a25de/images/ProjectFolder1.PNG -------------------------------------------------------------------------------- /source/Bin2D.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rdmd 2 | module Bin2D; 3 | import std.file : exists, isFile, isDir, dirEntries, SpanMode; 4 | import std.stdio : writeln, File, SEEK_CUR, write, stdout; 5 | import std.string : indexOf, lastIndexOf, tr; 6 | import std.path : baseName, dirName; 7 | import std.math : ceil; 8 | import std.conv : to; 9 | import std.getopt : getopt, defaultGetoptPrinter, config; 10 | import std.format : formattedWrite; 11 | import std.array : Appender; 12 | 13 | static ubyte[4096] buffer; 14 | 15 | int main(string[] args) { 16 | string[] originalArgs = args; 17 | 18 | string outputFile; 19 | string modulename; 20 | 21 | if (args.length > 2) { 22 | outputFile = args[1]; 23 | if (outputFile.indexOf("=") > 0) { 24 | modulename = outputFile[outputFile.indexOf("=") + 1 .. $]; 25 | outputFile = outputFile[0 .. outputFile.indexOf("=")]; 26 | } 27 | 28 | args = args[0] ~ args[2 .. $]; 29 | } else 30 | args = [args[0]]; 31 | 32 | bool usePackage; 33 | bool useUnittest; 34 | bool useEnum; 35 | 36 | auto procArgs = getopt( 37 | args, 38 | "onlyPackage", "Limits the embedded files using package modifier", &usePackage, 39 | "onlyUnittest", "Limits the embedded files using version(unittest)", &useUnittest, 40 | "useEnum", `Use enum instead of const(ubyte[]) to store the data. 41 | Allows for usage at compile time but memory increase for runtime. 42 | Will require usage of the enum __ctfeValues instead of values for CTFE access.`, &useEnum 43 | ); 44 | args = args[1 .. $]; 45 | 46 | if (args.length > 0 && !procArgs.helpWanted) { 47 | File output = File(outputFile, "w"); 48 | scope(exit) output.close; 49 | 50 | output.write("/**\n * Generated_By $(WEB, github.com/rikkimax/bin2d, Bin2D)\n", 51 | " * Copyright: Richard (Rikki) Andrew Cattermole 2014 - 2015\n * \n", 52 | " * Using_Command: "); 53 | foreach(arg; originalArgs) { 54 | output.write(arg ~ " "); 55 | } 56 | 57 | output.seek(-1, SEEK_CUR); 58 | output.write("\n * \n * Do $(I not) modify this file directly.\n"); 59 | 60 | output.write(" */\nmodule ", modulename, ";\n"); 61 | 62 | if (usePackage && useUnittest) 63 | output.write("version(unittest) package:\n"); 64 | else if (usePackage) 65 | output.write("package:\n"); 66 | else if (useUnittest) 67 | output.write("version(unittest):\n"); 68 | 69 | // file name processing 70 | 71 | string[] files; 72 | string[] filesWithoutScanDir; 73 | foreach (file; args) { 74 | if (file[$-1] == '/' || file[$-1] == '\\') //Clean off paths with slash on end eg. folder\ 75 | file.length--; 76 | file = file.tr("\\", "/"); //use forward slashes 77 | 78 | if (exists(file)) { 79 | if (isFile(file)) { 80 | files ~= file; 81 | filesWithoutScanDir ~= baseName(file); 82 | } else if (isDir(file)) { 83 | foreach (entry; dirEntries(file, SpanMode.breadth)) { 84 | if (isFile(entry)) { 85 | files ~= entry.tr("\\", "/"); 86 | filesWithoutScanDir ~= entry[file.length + 1 .. $].tr("\\", "/"); 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | ushort longestFileName; 94 | string[] filenames; 95 | foreach(file; files){ 96 | // not ideal in terms of allocation 97 | // but atleast it is only the file names, 98 | // if we were duplicating or actually storing the file data 99 | // then we might start having problems 100 | char[] t = cast(char[])file.dup; 101 | // has to be duplicated because of modification would override the values 102 | // of course if this was string, it would do something similar 103 | // with implicit allocations + dup. 104 | 105 | if (lastIndexOf(t, "/") > 0) 106 | t = t[lastIndexOf(t, "/") + 1 .. $]; 107 | 108 | // sanitises file names 109 | foreach(i, c; t) { 110 | switch(c) { 111 | case 'a': .. case 'z': 112 | case 'A': .. case 'Z': 113 | case '0': .. case '9': 114 | case '_': 115 | break; 116 | default: 117 | t[i] = '_'; 118 | } 119 | } 120 | filenames ~= cast(string)t; 121 | 122 | if (file.length > longestFileName) 123 | longestFileName = cast(ushort)file.length; 124 | } 125 | 126 | output.write( 127 | /*BEGIN FILE HEADER P1*/ 128 | ` 129 | import std.file : write, isDir, exists, mkdirRecurse, rmdirRecurse, tempDir, mkdir; 130 | import std.path : buildPath, dirName; 131 | import std.process : thisProcessID; 132 | import std.conv : text; 133 | 134 | deprecated("Use outputFilesToFileSystem instead") 135 | alias outputBin2D2FS = outputFilesToFileSystem; 136 | 137 | deprecated("Use names instead") 138 | alias assetNames = names; 139 | 140 | deprecated("Use values instead") 141 | alias assetValues = values; 142 | 143 | deprecated("Use originalNames instead") 144 | alias assetOriginalNames = originalNames; 145 | 146 | string rootDirectory; 147 | 148 | void cleanup(){ 149 | rmdirRecurse(rootDirectory); 150 | } 151 | 152 | string[string] outputFilesToFileSystem() { 153 | return outputFilesToFileSystem(buildPath(tempDir(), text(thisProcessID()))); 154 | } 155 | 156 | string[string] outputFilesToFileSystem(string dir) 157 | in { 158 | rootDirectory = dir; 159 | if (exists(dir)) { 160 | if (isDir(dir)) { 161 | rmdirRecurse(dir); 162 | mkdirRecurse(dir); 163 | } else { 164 | mkdirRecurse(dir); 165 | } 166 | } else { 167 | mkdirRecurse(dir); 168 | } 169 | } body { 170 | ` 171 | /*END FILE HEADER P1*/); 172 | if (useEnum) { 173 | output.write(/*BEGIN FILE HEADER P2*/` 174 | string[string] files;`); 175 | foreach(i, file; files) { 176 | output.write(" 177 | if (!buildPath(dir, \"", file, "\").dirName().exists()) 178 | mkdir(cast(string)buildPath(dir, \"", file, "\").dirName()); 179 | files[\"", file, "\"] ~= cast(string)buildPath(dir, \"", file, "\"); 180 | write(buildPath(dir, \"", file, "\"), ", filenames[i], ");"); 181 | } 182 | 183 | output.write(` 184 | return files; 185 | } 186 | ` 187 | /*END FILE HEADER p2*/); 188 | } else { 189 | output.write(/*BEGIN FILE HEADER P2*/` 190 | string[string] files; 191 | foreach(i, name; names) { 192 | string realname = originalNames[i]; 193 | if (!buildPath(dir, realname).dirName().exists()) 194 | mkdir(cast(string)buildPath(dir, realname).dirName()); 195 | files[cast(string)realname] ~= cast(string)buildPath(dir, realname); 196 | write(buildPath(dir, realname), *values[i]); 197 | } 198 | return files; 199 | } 200 | ` 201 | /*END FILE HEADER p2*/); 202 | } 203 | 204 | // write report heading 205 | ushort lenNames = cast(ushort)(longestFileName + 2); 206 | 207 | write("|"); 208 | foreach(i; 0 .. (lenNames * 2) + 1) 209 | write("-"); 210 | writeln("|"); 211 | write("|"); 212 | foreach(i; 0 .. lenNames-3) 213 | write(" "); 214 | write("REPORT"); 215 | foreach(i; 0 .. lenNames-2) 216 | write(" "); 217 | writeln("|"); 218 | 219 | write("|"); 220 | foreach(i; 0 .. lenNames) 221 | write("-"); 222 | write("|"); 223 | foreach(i; 0 .. lenNames) 224 | write("-"); 225 | writeln("|"); 226 | stdout.flush; 227 | 228 | Appender!(char[]) dfout; 229 | // giant chunk of memory that should be able to hold exactly one chunk read 230 | // is reused, so memory use of program shouldn't be all that high 231 | dfout.reserve(buffer.length * 3); 232 | 233 | foreach(i, file; files) { 234 | // output file contents 235 | if (useEnum) 236 | output.write("enum ubyte[] ", filenames[i], " = cast(ubyte[])x\""); 237 | else 238 | output.write("const(ubyte[]) ", filenames[i], " = cast(const(ubyte[]))x\""); 239 | 240 | File readFrom = File(file, "rb"); 241 | bool readSome; 242 | foreach(bytes; readFrom.byChunk(buffer)) { 243 | foreach(b; bytes) { 244 | readSome = true; 245 | formattedWrite(dfout, "%02x ", b); 246 | } 247 | 248 | output.write(dfout.data()); 249 | dfout.clear(); 250 | } 251 | if (readSome) 252 | output.seek(-1, SEEK_CUR); 253 | output.write("\";\n"); 254 | readFrom.close; 255 | 256 | // output report for file 257 | string replac = filenames[i]; 258 | 259 | write('|'); 260 | if (file.length > lenNames-2) 261 | write(' ', file[0 .. lenNames-2], ' '); 262 | else { 263 | foreach(j; 0 .. lenNames/2 - cast(uint)ceil(file.length / 2f) + 1) 264 | write(' '); 265 | write(file); 266 | foreach(j; 0 .. lenNames/2 - (file.length / 2)) 267 | write(' '); 268 | } 269 | write('|'); 270 | if (replac.length > lenNames - 2) 271 | write(' ', replac[0 .. lenNames-2], ' '); 272 | else { 273 | foreach(j; 0 .. lenNames/2 - cast(uint)ceil(replac.length / 2f) + 1) 274 | write(' '); 275 | write(replac); 276 | foreach(j; 0 .. lenNames/2 - (replac.length / 2)) 277 | write(' '); 278 | } 279 | writeln('|'); 280 | 281 | stdout.flush; 282 | } 283 | 284 | // close report table 285 | 286 | write("|"); 287 | foreach(i; 0 .. lenNames) 288 | write("-"); 289 | write("|"); 290 | foreach(i; 0 .. lenNames) 291 | write("-"); 292 | writeln("|"); 293 | stdout.flush; 294 | 295 | // write names/originalNames/values out 296 | 297 | output.write("\n\n"); 298 | 299 | if (useEnum) 300 | output.write("enum string[] names = ["); 301 | else 302 | output.write("const(string[]) names = ["); 303 | 304 | foreach(i, name; filenames) { 305 | output.write("\"", name , "\", "); 306 | } 307 | output.seek(-2, SEEK_CUR); 308 | output.write("];\n"); 309 | 310 | if (useEnum) 311 | output.write("enum string[] originalNames = ["); 312 | else 313 | output.write("const(string[]) originalNames = ["); 314 | 315 | foreach(name; files) { 316 | output.write("`", name.tr("/","\\"), "`, "); 317 | } 318 | output.seek(-2, SEEK_CUR); 319 | output.write("];\n"); 320 | 321 | if (useEnum) { 322 | output.write("enum ubyte[][] __ctfeValues = ["); 323 | foreach(i, name; filenames) { 324 | output.write(name, ", "); 325 | } 326 | output.seek(-2, SEEK_CUR); 327 | output.write("];\n"); 328 | } 329 | 330 | if (useEnum) { 331 | output.write(` 332 | const(ubyte[]*[]) values; 333 | shared static this() { 334 | ubyte[]*[] ret; 335 | ret.length = __ctfeValues.length; 336 | foreach(i, v; __ctfeValues) 337 | ret[i] = &v; 338 | values = cast(const)ret; 339 | }`); 340 | } else { 341 | output.write("const(ubyte[]*[]) values = ["); 342 | foreach(i, name; filenames) { 343 | if (!useEnum) 344 | output.write("&", name, ", "); 345 | } 346 | output.seek(-2, SEEK_CUR); 347 | output.write("];"); 348 | } 349 | 350 | return 0; 351 | } else { 352 | defaultGetoptPrinter(" 353 | Usage: Bin2D [=] [switches] 354 | Bin2D is a resource compiler for the D programming language. 355 | It compiles resources down to D source code. 356 | For inclusion into a build. Later accessible given an optional module name. 357 | 358 | Switches:"[1 .. $], procArgs.options); 359 | 360 | return -1; 361 | } 362 | } 363 | --------------------------------------------------------------------------------