27#include "aeongames/CRC.hpp"
58 unsigned char in[CHUNK];
59 unsigned char out[CHUNK];
65 ret = deflateInit ( &strm, level );
74 strm.avail_in =
static_cast<uInt
> ( fread ( in, 1, CHUNK, source ) );
75 if ( ferror ( source ) )
77 ( void ) deflateEnd ( &strm );
80 flush = feof ( source ) ? Z_FINISH : Z_NO_FLUSH;
87 strm.avail_out = CHUNK;
89 ret = deflate ( &strm, flush );
90 assert ( ret != Z_STREAM_ERROR );
91 have = CHUNK - strm.avail_out;
92 if ( fwrite ( out, 1, have, dest ) != have || ferror ( dest ) )
94 ( void ) deflateEnd ( &strm );
97 compressed_size += have;
99 while ( strm.avail_out == 0 );
100 assert ( strm.avail_in == 0 );
104 while ( flush != Z_FINISH );
105 assert ( ret == Z_STREAM_END );
108 ( void ) deflateEnd ( &strm );
111 bool Pack::ProcessDirectory (
const std::string& path )
114 std::string wildcard = mInputPath + path +
"*";
116 HANDLE hFind = FindFirstFile ( wildcard.c_str(), &wfd );
117 if ( INVALID_HANDLE_VALUE != hFind )
121 if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
123 if ( ( strncmp ( wfd.cFileName,
".\0", 2 ) != 0 ) && ( strncmp ( wfd.cFileName,
"..\0", 3 ) != 0 ) )
125 ProcessDirectory ( path + wfd.cFileName +
"/" );
130 std::string filepath = path + wfd.cFileName;
131 uint32_t filepathcrc =
crc32i ( filepath.c_str(), filepath.size() );
132 mStringTable[filepathcrc] = filepath;
133 mDirectory[filepathcrc].path = filepathcrc;
134 mDirectory[filepathcrc].uncompressed_size = wfd.nFileSizeLow;
135 mDirectory[filepathcrc].extension_offset =
static_cast<uint32_t
> ( filepath.rfind (
'.' ) );
136 if ( mDirectory[filepathcrc].extension_offset != std::string::npos )
138 ++mDirectory[filepathcrc].extension_offset;
142 mDirectory[filepathcrc].extension_offset = 0;
146 while ( FindNextFile ( hFind, &wfd ) );
150 std::string wildcard = mInputPath + path;
151 struct dirent* directory_entry;
153 if ( ( dir = opendir ( wildcard.c_str() ) ) == NULL )
157 while ( ( directory_entry = readdir ( dir ) ) != NULL )
159 struct stat file_stat;
160 std::string fullpath = wildcard + directory_entry->d_name;
161 if ( stat ( fullpath.c_str(), &file_stat ) == 0 )
163 if ( S_ISDIR ( file_stat.st_mode ) )
165 if ( ( strncmp ( directory_entry->d_name,
".\0", 2 ) != 0 ) && ( strncmp ( directory_entry->d_name,
"..\0", 3 ) != 0 ) )
167 ProcessDirectory ( path + directory_entry->d_name +
"/" );
170 else if ( S_ISREG ( file_stat.st_mode ) )
172 std::string filepath = path + directory_entry->d_name;
173 uint32_t filepathcrc =
crc32i ( filepath.c_str(), filepath.length() );
174 mStringTable[filepathcrc] = filepath;
175 mDirectory[filepathcrc].path = filepathcrc;
176 mDirectory[filepathcrc].uncompressed_size = file_stat.st_size;
177 mDirectory[filepathcrc].compressed_size = mDirectory[filepathcrc].uncompressed_size;
178 mDirectory[filepathcrc].extension_offset = filepath.rfind (
'.' );
179 if ( mDirectory[filepathcrc].extension_offset != std::string::npos )
181 ++mDirectory[filepathcrc].extension_offset;
185 mDirectory[filepathcrc].extension_offset = 0;
199 void Pack::ProcessArgs (
int argc,
char** argv )
201 if ( argc < 2 || ( strcmp ( argv[1],
"pack" ) != 0 ) )
203 std::ostringstream stream;
204 stream <<
"Invalid tool name, expected pack, got " << ( ( argc < 2 ) ?
"nothing" : argv[1] ) << std::endl;
205 throw std::runtime_error ( stream.str().c_str() );
207 for (
int i = 2; i < argc; ++i )
209 if ( argv[i][0] ==
'-' )
211 if ( argv[i][1] ==
'-' )
213 if ( strncmp ( &argv[i][2],
"in",
sizeof (
"in" ) ) == 0 )
216 mInputPath = argv[i];
218 else if ( strncmp ( &argv[i][2],
"out",
sizeof (
"out" ) ) == 0 )
221 mOutputFile = argv[i];
223 else if ( strncmp ( &argv[i][2],
"extract\0", 8 ) == 0 )
227 else if ( strncmp ( &argv[i][2],
"compress\0", 9 ) == 0 )
231 else if ( strncmp ( &argv[i][2],
"directory\0", 10 ) == 0 )
238 switch ( argv[i][1] )
242 mInputPath = argv[i];
246 mOutputFile = argv[i];
262 mInputPath = argv[i];
265 if ( mInputPath.empty() )
267 throw std::runtime_error (
"No Input path provided." );
272 mStringTable.clear();
274 ProcessArgs ( argc, argv );
278 return ExecCompress();
280 return ExecExtract();
282 return ExecDirectory();
288 int Pack::ExecExtract()
const
292 int Pack::ExecDirectory()
const
296 Package package{mInputPath};
297 for (
auto& i : package.GetIndexTable() )
299 std::cout << std::setfill (
'0' ) << std::setw ( 10 ) << i.first <<
"\t" << i.second << std::endl;
302 catch (
const std::runtime_error& e )
304 std::cout << e.what();
309 int Pack::ExecCompress()
const
314 DWORD file_attributes = GetFileAttributes ( mInputPath.c_str() );
315 char drive[_MAX_DRIVE];
317 char fname[_MAX_FNAME];
318 _splitpath ( mInputPath.c_str(), drive, dir, fname, NULL );
321 dir[strlen ( dir ) - 1] = 0;
323 if ( mOutputFile.empty() )
327 mOutputFile += fname;
328 mOutputFile +=
".pkg";
334 mBaseName = mRootPath.substr ( mRootPath.find_last_of (
'/' ) + 1 );
335 mRootPath.erase ( mRootPath.find_last_of (
'/' ) + 1 );
336 if ( file_attributes & FILE_ATTRIBUTE_DIRECTORY )
338 std::cout << mInputPath <<
" is a directory" << std::endl;
339 if ( mInputPath[mInputPath.length() - 1] !=
'/' )
343 if ( !ProcessDirectory (
"" ) )
348 else if ( file_attributes != INVALID_FILE_ATTRIBUTES )
350 std::cout << mInputPath <<
" is a file" << std::endl;
354 std::cout << mInputPath <<
" is not a file or a directory" << std::endl;
358 struct stat file_stat;
359 char path_buffer[1024];
360 char file_buffer[1024];
363 sprintf ( path_buffer,
"%s", mInputPath.c_str() );
364 sprintf ( file_buffer,
"%s", mInputPath.c_str() );
365 path = dirname ( path_buffer );
366 file = basename ( file_buffer );
367 if ( mOutputFile.empty() )
372 mOutputFile +=
".pkg";
377 if ( stat ( mInputPath.c_str(), &file_stat ) != 0 )
381 if ( S_ISDIR ( file_stat.st_mode ) )
383 std::cout << mInputPath <<
" is a directory" << std::endl;
384 if ( mInputPath[mInputPath.length() - 1] !=
'/' )
388 if ( !ProcessDirectory (
"" ) )
394 std::cout <<
"Packing into: " << mOutputFile << std::endl;
404 header.version[0] = 0;
405 header.version[1] = 1;
406 header.file_count =
static_cast<uint32_t
> ( mDirectory.size() );
407 FILE* out = fopen ( mOutputFile.c_str(),
"wb" );
412 fseek ( out,
sizeof ( PKGHeader ) + (
sizeof ( PKGDirectoryEntry ) *header.file_count ), SEEK_SET );
413 for ( std::unordered_map<uint32_t, PKGDirectoryEntry>::iterator i = mDirectory.begin(); i != mDirectory.end(); ++i )
416 uint8_t buffer[1024];
417 std::string fullpath = mInputPath + mStringTable[i->first];
418 std::cout << std::setfill (
'0' ) << std::setw ( 8 ) << std::hex << i->first <<
" " << fullpath << std::endl;
420 i->second.compressed_size = 0;
421 FILE* in = fopen ( fullpath.c_str(),
"rb" );
424 std::cout <<
"WARNING, could not read " << fullpath << std::endl;
427 i->second.offset = ftell ( out );
428 std::string extension;
429 size_t ext_idx = fullpath.rfind (
"." );
430 if ( ext_idx != std::string::npos )
432 extension = fullpath.substr ( ext_idx + 1 );
433 std::transform ( extension.begin(), extension.end(), extension.begin(), ::tolower );
441 i->second.compression_type = 1;
444 std::cout <<
"Deflate Failed on" << fullpath << std::endl;
449 i->second.compression_type = 0;
450 while ( ( bytes_read = fread ( buffer,
sizeof ( uint8_t ), 1024, in ) ) != 0 )
452 i->second.compressed_size +=
static_cast<uint32_t
> ( fwrite ( buffer,
sizeof ( uint8_t ), bytes_read, out ) );
454 assert ( ( i->second.compression_type == 0 ) && ( i->second.uncompressed_size == i->second.compressed_size ) );
460 header.string_table_offset = ftell ( out );
461 uint32_t string_table_size =
static_cast<uint32_t
> ( mStringTable.size() );
462 fwrite ( &string_table_size,
sizeof ( uint32_t ), 1, out );
463 fseek ( out,
sizeof ( uint32_t ) * 2 * string_table_size, SEEK_CUR );
464 std::unordered_map<uint32_t, uint32_t> string_table_offsets;
465 for ( std::unordered_map<uint32_t, std::string>::iterator i = mStringTable.begin(); i != mStringTable.end(); ++i )
467 string_table_offsets[i->first] = ftell ( out ) - header.string_table_offset;
468 fwrite ( i->second.c_str(), i->second.size(), 1, out );
469 fwrite (
"\0", 1, 1, out );
471 fseek ( out, header.string_table_offset + sizeof ( uint32_t ), SEEK_SET );
472 for ( std::unordered_map<uint32_t, uint32_t>::iterator i = string_table_offsets.begin(); i != string_table_offsets.end(); ++i )
474 fwrite ( &i->first, sizeof ( uint32_t ), 1, out );
475 fwrite ( &i->second, sizeof ( uint32_t ), 1, out );
478 fseek ( out, 0, SEEK_END );
479 header.file_size =
static_cast<uint32_t
> ( ftell ( out ) );
480 fseek ( out, 0, SEEK_SET );
481 fwrite ( &header,
sizeof ( PKGHeader ), 1, out );
482 for ( std::unordered_map<uint32_t, PKGDirectoryEntry>::iterator i = mDirectory.begin(); i != mDirectory.end(); ++i )
484 fwrite ( &i->second, sizeof ( PKGDirectoryEntry ), 1, out );
Header for the PKG file specification.
@ Compress
Compress assets into a package.
@ Directory
List the directory of a package.
@ Extract
Extract assets from a package.
Pack()
Default constructor.
int operator()(int argc, char **argv) override
Execute the pack tool.
<- This is here just for the literals
int write_deflated_data(FILE *source, FILE *dest, int level, uint32_t &compressed_size)
Compress data from source file to dest file using deflate.
uint32_t crc32i(const char *message, size_t size, uint32_t previous_crc)
Compute the CRC32 of a given message, continuing from a previous CRC value.