31
31
#include < vector>
32
32
33
33
#include < string.h>
34
+ #include < zstd.h>
34
35
35
36
using namespace utils ;
36
37
@@ -43,6 +44,8 @@ struct AppConfig {
43
44
bool generateC = false ;
44
45
bool quietMode = false ;
45
46
bool embedJson = false ;
47
+ bool compress = false ;
48
+ bool compressPackage = false ;
46
49
std::vector<Path> inputPaths;
47
50
};
48
51
@@ -83,6 +86,10 @@ is the basename of the input file. It produces the following set of files:
83
86
of all resource sizes and names. Useful for size analysis.
84
87
--quiet, -q
85
88
Suppress console output
89
+ --compress, -z
90
+ Compress each resource individually with zstd.
91
+ --compress-package, -Z
92
+ Compress the entire package with zstd.
86
93
87
94
Examples:
88
95
RESGEN -cp textures jungle.png beach.png
@@ -144,7 +151,7 @@ static std::string& replaceAll(std::string& context, const std::string& from, co
144
151
145
152
// Parses command-line arguments using getopt and populates the AppConfig struct.
146
153
static AppConfig handleArguments (int const argc, char * argv[]) {
147
- static constexpr const char * OPTSTR = " hLp:x:ktcqj " ;
154
+ static constexpr const char * OPTSTR = " hLp:x:ktcqjzZ " ;
148
155
static const option OPTIONS[] = {
149
156
{ " help" , no_argument, nullptr , ' h' },
150
157
{ " license" , no_argument, nullptr , ' L' },
@@ -155,6 +162,8 @@ static AppConfig handleArguments(int const argc, char* argv[]) {
155
162
{ " cfile" , no_argument, nullptr , ' c' },
156
163
{ " quiet" , no_argument, nullptr , ' q' },
157
164
{ " json" , no_argument, nullptr , ' j' },
165
+ { " compress" , no_argument, nullptr , ' z' },
166
+ { " compress-package" , no_argument, nullptr , ' Z' },
158
167
{ nullptr , 0 , nullptr , 0 }
159
168
};
160
169
@@ -189,12 +198,23 @@ static AppConfig handleArguments(int const argc, char* argv[]) {
189
198
case ' j' :
190
199
config.embedJson = true ;
191
200
break ;
201
+ case ' z' :
202
+ config.compress = true ;
203
+ break ;
204
+ case ' Z' :
205
+ config.compressPackage = true ;
206
+ break ;
192
207
default :
193
208
printUsage (argv[0 ]);
194
209
std::exit (1 );
195
210
}
196
211
}
197
212
213
+ if (config.compress && config.compressPackage ) {
214
+ std::cerr << " Cannot use -z and -Z at the same time." << std::endl;
215
+ exit (1 );
216
+ }
217
+
198
218
// Treat remaining arguments as input file paths.
199
219
for (int i = optind; i < argc; ++i) {
200
220
config.inputPaths .emplace_back (argv[i]);
@@ -302,10 +322,9 @@ int main(int argc, char* argv[]) {
302
322
replaceAll (asmstr, " {RESOURCES}" , packagePrefix);
303
323
replaceAll (asmstr, " {resources}" , packageFile);
304
324
305
- // Open all output file streams.
325
+ // Open file streams.
306
326
auto appleAsmStream = openOutputFile (appleAsmPath);
307
327
auto asmStream = openOutputFile (asmPath);
308
- auto binStream = openOutputFile (binPath, std::ios::binary);
309
328
310
329
// Begin constructing the C header file content.
311
330
std::ostringstream headerStream;
@@ -327,17 +346,18 @@ int main(int argc, char* argv[]) {
327
346
<< " const uint8_t " << packageSymbol << " [] = {\n " ;
328
347
}
329
348
349
+ std::vector<uint8_t > packageData;
350
+ std::vector<uint8_t > uncompressedPackageData;
351
+ size_t totalUncompressedSize = 0 ;
352
+
330
353
// Process each input file to build the resource collection.
331
354
jsonStream << " {" ;
332
355
std::size_t offset = 0 ;
333
356
for (const auto & inPath : inputPaths) {
334
- std::vector<std::uint8_t > content ;
357
+ std::vector<std::uint8_t > uncompressedContent ;
335
358
if (inPath != g_jsonMagicString) {
336
- // For a regular file, read its binary content.
337
- content = readFile (inPath);
359
+ uncompressedContent = readFile (inPath);
338
360
} else {
339
- // For the JSON placeholder, finalize and embed the JSON summary blob.
340
- // The blob is formatted as: __RESGEN__\0<size>\0{...json...}
341
361
std::string jsonString = jsonStream.str ();
342
362
jsonString[jsonString.size () - 1 ] = ' }' ;
343
363
std::ostringstream jsonBlob;
@@ -346,55 +366,115 @@ int main(int argc, char* argv[]) {
346
366
jsonBlob << jsonString;
347
367
jsonString = jsonBlob.str ();
348
368
const auto * jsonPtr = reinterpret_cast <const std::uint8_t *>(jsonString.c_str ());
349
- content .assign (jsonPtr, jsonPtr + jsonBlob.str ().size ());
369
+ uncompressedContent .assign (jsonPtr, jsonPtr + jsonBlob.str ().size ());
350
370
}
351
371
352
- // Optionally append a null terminator, useful for text resources.
353
372
if (config.appendNull ) {
354
- content.push_back (0 );
373
+ uncompressedContent.push_back (0 );
374
+ }
375
+
376
+ const size_t uncompressedSize = uncompressedContent.size ();
377
+ totalUncompressedSize += uncompressedSize;
378
+
379
+ std::vector<std::uint8_t > finalContent;
380
+ bool const shouldCompressThisResource = config.compress && inPath != g_jsonMagicString;
381
+
382
+ if (shouldCompressThisResource) {
383
+ size_t const cBuffSize = ZSTD_compressBound (uncompressedSize);
384
+ finalContent.resize (cBuffSize);
385
+ size_t const cSize = ZSTD_compress (finalContent.data (), cBuffSize,
386
+ uncompressedContent.data (), uncompressedSize, ZSTD_maxCLevel ());
387
+ if (ZSTD_isError (cSize)) {
388
+ std::cerr << " zstd compression error: " << ZSTD_getErrorName (cSize) << std::endl;
389
+ exit (1 );
390
+ }
391
+ finalContent.resize (cSize);
392
+ } else {
393
+ finalContent = std::move (uncompressedContent);
355
394
}
356
395
357
- // Generate the resource's symbol name from its file name.
358
396
std::string rname = config.keepExtension ? inPath.getName () : inPath.getNameWithoutExtension ();
359
397
replaceAll (rname, " ." , " _" );
360
398
std::transform (rname.begin (), rname.end (), rname.begin (), [](unsigned char c) { return std::toupper (c); });
361
399
const std::string prname = packagePrefix + rname;
362
400
363
- // Write the resource content to the aggregate binary file.
364
- binStream.write (reinterpret_cast <const char *>(content.data ()), content.size ());
401
+ if (config.compressPackage ) {
402
+ uncompressedPackageData.insert (uncompressedPackageData.end (), finalContent.begin (), finalContent.end ());
403
+ } else {
404
+ packageData.insert (packageData.end (), finalContent.begin (), finalContent.end ());
405
+ }
365
406
366
- // Generate C preprocessor macros for the resource's offset, size, and data pointer.
367
- headerMacros << " #define " << prname << " _OFFSET " << offset << " \n "
368
- << " #define " << prname << " _SIZE " << content.size () << " \n "
369
- << " #define " << prname << " _DATA (" << packageSymbol << " + " << prname << " _OFFSET)\n\n " ;
407
+ size_t const sizeForHeader = config.compressPackage ? uncompressedSize : finalContent.size ();
408
+
409
+ headerMacros << " #define " << prname << " _OFFSET " << offset << " \n " ;
410
+ if (shouldCompressThisResource) {
411
+ headerMacros << " #define " << prname << " _UNCOMPRESSED_SIZE " << uncompressedSize << " \n " ;
412
+ }
413
+ headerMacros << " #define " << prname << " _SIZE " << sizeForHeader << " \n " ;
414
+ if (!config.compressPackage ) {
415
+ headerMacros << " #define " << prname << " _DATA (" << packageSymbol << " + " << prname << " _OFFSET)\n\n " ;
416
+ }
370
417
371
- // If enabled, write the content to the C source file.
372
418
if (config.generateC ) {
373
- writeXxdEntry (xxdStream, rname, content);
419
+ writeXxdEntry (xxdStream, rname, finalContent);
420
+ }
421
+
422
+ if (inPath != g_jsonMagicString) {
423
+ jsonStream << " \" " << rname << " \" :{\" uncompressedSize\" :" << uncompressedSize;
424
+ if (config.compress ) {
425
+ jsonStream << " ,\" compressedSize\" :" << finalContent.size ();
426
+ }
427
+ jsonStream << " }," ;
374
428
}
429
+ offset += sizeForHeader;
430
+ }
375
431
376
- // Add an entry to the JSON summary and update the running offset.
377
- jsonStream << " \" " << rname << " \" :" << content.size () << " ," ;
378
- offset += content.size ();
432
+ if (config.compressPackage ) {
433
+ size_t const cBuffSize = ZSTD_compressBound (uncompressedPackageData.size ());
434
+ packageData.resize (cBuffSize);
435
+ size_t const cSize = ZSTD_compress (packageData.data (), cBuffSize,
436
+ uncompressedPackageData.data (), uncompressedPackageData.size (), ZSTD_maxCLevel ());
437
+ if (ZSTD_isError (cSize)) {
438
+ std::cerr << " zstd compression error: " << ZSTD_getErrorName (cSize) << std::endl;
439
+ exit (1 );
440
+ }
441
+ packageData.resize (cSize);
442
+ }
443
+
444
+ size_t const totalCompressedSize = packageData.size ();
445
+
446
+ if (config.compress || config.compressPackage ) {
447
+ headerStream << " // This package is compressed with zstd.\n " ;
448
+ if (config.compress ) {
449
+ headerStream << " // Each resource is compressed individually.\n "
450
+ << " // The _SIZE macro is the compressed size.\n "
451
+ << " // The _UNCOMPRESSED_SIZE macro is the original size.\n " ;
452
+ }
453
+ if (config.compressPackage ) {
454
+ headerStream << " // The entire package is compressed as a single blob.\n "
455
+ << " // The _SIZE and _OFFSET macros are for the uncompressed data.\n "
456
+ << " // You must decompress the entire package before using them.\n " ;
457
+ }
458
+ headerStream << " // Original package size: " << totalUncompressedSize << " bytes.\n "
459
+ << " // Compressed package size: " << totalCompressedSize << " bytes.\n\n " ;
379
460
}
380
461
381
- // Finalize the header file content.
382
462
headerStream << " }\n\n " ;
383
463
headerStream << headerMacros.str ();
384
464
headerStream << " #endif\n " ;
385
465
386
- // Write the header and assembly files.
466
+ auto binStream = openOutputFile (binPath, std::ios::binary);
467
+ binStream.write (reinterpret_cast <const char *>(packageData.data ()), packageData.size ());
468
+
387
469
writeHeaderIfChanged (headerPath, headerStream.str ());
388
470
asmStream << asmstr << std::endl;
389
471
appleAsmStream << aasmstr << std::endl;
390
472
391
- // Report generated files to the console unless in quiet mode.
392
473
if (!config.quietMode ) {
393
474
std::cout << " Generated files: " << headerPath << " " << asmPath << " " << appleAsmPath << " "
394
475
<< binPath;
395
476
}
396
477
397
- // Finalize the C file and report it.
398
478
if (config.generateC ) {
399
479
xxdStream << " };\n\n " ;
400
480
if (!config.quietMode ) {
@@ -407,4 +487,4 @@ int main(int argc, char* argv[]) {
407
487
}
408
488
409
489
return 0 ;
410
- }
490
+ }
0 commit comments