Aeon Engine c550894
AeonGames Open Source Game Engine
Loading...
Searching...
No Matches
Pack.cpp
1/*
2Copyright (C) 2013,2018,2019,2022,2025,2026 Rodrigo Jose Hernandez Cordoba
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16#ifdef WIN32
17#include <windows.h>
18#endif
19#include <cstdio>
20#include <iostream>
21#include <iomanip>
22#include <sstream>
23#include <algorithm>
24#include <cassert>
25#include "Pack.h"
26#include "aeongames/Package.hpp"
27#include "aeongames/CRC.hpp"
28#include <sys/types.h>
29#include <sys/stat.h>
30#include "zlib.h"
31#ifndef WIN32
32#include <libgen.h>
33#include <dirent.h>
34#endif
35#include <cstring>
36
37namespace AeonGames
38{
39#define CHUNK 16384
40 /* Compress from file source to file dest until EOF on source.
41 def() returns Z_OK on success, Z_MEM_ERROR if memory could not be
42 allocated for processing, Z_STREAM_ERROR if an invalid compression
43 level is supplied, Z_VERSION_ERROR if the version of zlib.h and the
44 version of the library linked do not match, or Z_ERRNO if there is
45 an error reading or writing the files.
46 compressed_size contains the size in bytes of the compressed written file */
53 int write_deflated_data ( FILE *source, FILE *dest, int level, uint32_t& compressed_size )
54 {
55 int ret, flush;
56 unsigned have;
57 z_stream strm;
58 unsigned char in[CHUNK];
59 unsigned char out[CHUNK];
60 compressed_size = 0;
61 /* allocate deflate state */
62 strm.zalloc = Z_NULL;
63 strm.zfree = Z_NULL;
64 strm.opaque = Z_NULL;
65 ret = deflateInit ( &strm, level );
66 if ( ret != Z_OK )
67 {
68 return ret;
69 }
70
71 /* compress until end of file */
72 do
73 {
74 strm.avail_in = static_cast<uInt> ( fread ( in, 1, CHUNK, source ) );
75 if ( ferror ( source ) )
76 {
77 ( void ) deflateEnd ( &strm );
78 return Z_ERRNO;
79 }
80 flush = feof ( source ) ? Z_FINISH : Z_NO_FLUSH;
81 strm.next_in = in;
82
83 /* run deflate() on input until output buffer not full, finish
84 compression if all of source has been read in */
85 do
86 {
87 strm.avail_out = CHUNK;
88 strm.next_out = out;
89 ret = deflate ( &strm, flush ); /* no bad return value */
90 assert ( ret != Z_STREAM_ERROR ); /* state not clobbered */
91 have = CHUNK - strm.avail_out;
92 if ( fwrite ( out, 1, have, dest ) != have || ferror ( dest ) )
93 {
94 ( void ) deflateEnd ( &strm );
95 return Z_ERRNO;
96 }
97 compressed_size += have;
98 }
99 while ( strm.avail_out == 0 );
100 assert ( strm.avail_in == 0 ); /* all input will be used */
101
102 /* done when last data in file processed */
103 }
104 while ( flush != Z_FINISH );
105 assert ( ret == Z_STREAM_END ); /* stream will be complete */
106
107 /* clean up and return */
108 ( void ) deflateEnd ( &strm );
109 return Z_OK;
110 }
111 bool Pack::ProcessDirectory ( const std::string& path )
112 {
113#ifdef WIN32
114 std::string wildcard = mInputPath + path + "*";
115 WIN32_FIND_DATA wfd;
116 HANDLE hFind = FindFirstFile ( wildcard.c_str(), &wfd );
117 if ( INVALID_HANDLE_VALUE != hFind )
118 {
119 do
120 {
121 if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
122 {
123 if ( ( strncmp ( wfd.cFileName, ".\0", 2 ) != 0 ) && ( strncmp ( wfd.cFileName, "..\0", 3 ) != 0 ) )
124 {
125 ProcessDirectory ( path + wfd.cFileName + "/" );
126 }
127 }
128 else
129 {
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; //(wfd.nFileSizeHigh * (MAXDWORD+1)) + wfd.nFileSizeLow;
135 mDirectory[filepathcrc].extension_offset = static_cast<uint32_t> ( filepath.rfind ( '.' ) );
136 if ( mDirectory[filepathcrc].extension_offset != std::string::npos )
137 {
138 ++mDirectory[filepathcrc].extension_offset;
139 }
140 else
141 {
142 mDirectory[filepathcrc].extension_offset = 0;
143 }
144 }
145 }
146 while ( FindNextFile ( hFind, &wfd ) );
147 FindClose ( hFind );
148 }
149#else
150 std::string wildcard = mInputPath + path;
151 struct dirent* directory_entry;
152 DIR* dir;
153 if ( ( dir = opendir ( wildcard.c_str() ) ) == NULL )
154 {
155 return false;
156 }
157 while ( ( directory_entry = readdir ( dir ) ) != NULL )
158 {
159 struct stat file_stat;
160 std::string fullpath = wildcard + directory_entry->d_name;
161 if ( stat ( fullpath.c_str(), &file_stat ) == 0 )
162 {
163 if ( S_ISDIR ( file_stat.st_mode ) )
164 {
165 if ( ( strncmp ( directory_entry->d_name, ".\0", 2 ) != 0 ) && ( strncmp ( directory_entry->d_name, "..\0", 3 ) != 0 ) )
166 {
167 ProcessDirectory ( path + directory_entry->d_name + "/" );
168 }
169 }
170 else if ( S_ISREG ( file_stat.st_mode ) )
171 {
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 )
180 {
181 ++mDirectory[filepathcrc].extension_offset;
182 }
183 else
184 {
185 mDirectory[filepathcrc].extension_offset = 0;
186 }
187 }
188 }
189 }
190 closedir ( dir );
191#endif
192 return true;
193 }
194
195 Pack::Pack()
196 = default;
198 = default;
199 void Pack::ProcessArgs ( int argc, char** argv )
200 {
201 if ( argc < 2 || ( strcmp ( argv[1], "pack" ) != 0 ) )
202 {
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() );
206 }
207 for ( int i = 2; i < argc; ++i )
208 {
209 if ( argv[i][0] == '-' )
210 {
211 if ( argv[i][1] == '-' )
212 {
213 if ( strncmp ( &argv[i][2], "in", sizeof ( "in" ) ) == 0 )
214 {
215 i++;
216 mInputPath = argv[i];
217 }
218 else if ( strncmp ( &argv[i][2], "out", sizeof ( "out" ) ) == 0 )
219 {
220 i++;
221 mOutputFile = argv[i];
222 }
223 else if ( strncmp ( &argv[i][2], "extract\0", 8 ) == 0 )
224 {
225 mAction = Action::Extract;
226 }
227 else if ( strncmp ( &argv[i][2], "compress\0", 9 ) == 0 )
228 {
229 mAction = Action::Compress;
230 }
231 else if ( strncmp ( &argv[i][2], "directory\0", 10 ) == 0 )
232 {
233 mAction = Action::Directory;
234 }
235 }
236 else
237 {
238 switch ( argv[i][1] )
239 {
240 case 'i':
241 i++;
242 mInputPath = argv[i];
243 break;
244 case 'o':
245 i++;
246 mOutputFile = argv[i];
247 break;
248 case 'c':
249 mAction = Action::Compress;
250 break;
251 case 'e':
252 mAction = Action::Extract;
253 break;
254 case 'd':
255 mAction = Action::Directory;
256 break;
257 }
258 }
259 }
260 else
261 {
262 mInputPath = argv[i];
263 }
264 }
265 if ( mInputPath.empty() )
266 {
267 throw std::runtime_error ( "No Input path provided." );
268 }
269 }
270 int Pack::operator() ( int argc, char** argv )
271 {
272 mStringTable.clear();
273 mDirectory.clear();
274 ProcessArgs ( argc, argv );
275 switch ( mAction )
276 {
277 case Action::Compress:
278 return ExecCompress();
279 case Action::Extract:
280 return ExecExtract();
282 return ExecDirectory();
283 default:
284 break;
285 }
286 return -1;
287 }
288 int Pack::ExecExtract() const
289 {
290 return 0;
291 }
292 int Pack::ExecDirectory() const
293 {
294 try
295 {
296 Package package{mInputPath};
297 for ( auto& i : package.GetIndexTable() )
298 {
299 std::cout << std::setfill ( '0' ) << std::setw ( 10 ) << /*std::hex <<*/ i.first << "\t" << i.second << std::endl;
300 }
301 }
302 catch ( const std::runtime_error& e )
303 {
304 std::cout << e.what();
305 return -1;
306 }
307 return 0;
308 }
309 int Pack::ExecCompress() const
310 {
311#if 0
312// Commented out, this old file format might change in the future.
313#ifdef _WIN32
314 DWORD file_attributes = GetFileAttributes ( mInputPath.c_str() );
315 char drive[_MAX_DRIVE];
316 char dir[_MAX_DIR];
317 char fname[_MAX_FNAME];
318 _splitpath ( mInputPath.c_str(), drive, dir, fname, NULL );
319 if ( fname[0] == 0 )
320 {
321 dir[strlen ( dir ) - 1] = 0;
322 }
323 if ( mOutputFile.empty() )
324 {
325 mOutputFile = drive;
326 mOutputFile += dir;
327 mOutputFile += fname;
328 mOutputFile += ".pkg";
329 }
330
331 mRootPath = drive;
332 mRootPath += dir;
333 mRootPath += fname;
334 mBaseName = mRootPath.substr ( mRootPath.find_last_of ( '/' ) + 1 );
335 mRootPath.erase ( mRootPath.find_last_of ( '/' ) + 1 );
336 if ( file_attributes & FILE_ATTRIBUTE_DIRECTORY )
337 {
338 std::cout << mInputPath << " is a directory" << std::endl;
339 if ( mInputPath[mInputPath.length() - 1] != '/' )
340 {
341 mInputPath += "/";
342 }
343 if ( !ProcessDirectory ( "" ) )
344 {
345 return -1;
346 }
347 }
348 else if ( file_attributes != INVALID_FILE_ATTRIBUTES )
349 {
350 std::cout << mInputPath << " is a file" << std::endl;
351 }
352 else
353 {
354 std::cout << mInputPath << " is not a file or a directory" << std::endl;
355 return -1;
356 }
357#else
358 struct stat file_stat;
359 char path_buffer[1024];
360 char file_buffer[1024];
361 char* path;
362 char* file;
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() )
368 {
369 mOutputFile = path;
370 mOutputFile += "/";
371 mOutputFile += file;
372 mOutputFile += ".pkg";
373 }
374 mRootPath = path;
375 mRootPath += "/";
376 mRootPath += file;
377 if ( stat ( mInputPath.c_str(), &file_stat ) != 0 )
378 {
379 return -1;
380 }
381 if ( S_ISDIR ( file_stat.st_mode ) )
382 {
383 std::cout << mInputPath << " is a directory" << std::endl;
384 if ( mInputPath[mInputPath.length() - 1] != '/' )
385 {
386 mInputPath += "/";
387 }
388 if ( !ProcessDirectory ( "" ) )
389 {
390 return -1;
391 }
392 }
393#endif
394 std::cout << "Packing into: " << mOutputFile << std::endl;
395 PKGHeader header;
396 header.id[0] = 'A';
397 header.id[1] = 'E';
398 header.id[2] = 'O';
399 header.id[3] = 'N';
400 header.id[4] = 'P';
401 header.id[5] = 'K';
402 header.id[6] = 'G';
403 header.id[7] = 0;
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" );
408 if ( out == NULL )
409 {
410 return -1;
411 }
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 )
414 {
415 size_t bytes_read;
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;
419
420 i->second.compressed_size = 0;
421 FILE* in = fopen ( fullpath.c_str(), "rb" );
422 if ( in == NULL )
423 {
424 std::cout << "WARNING, could not read " << fullpath << std::endl;
425 continue;
426 }
427 i->second.offset = ftell ( out );
428 std::string extension;
429 size_t ext_idx = fullpath.rfind ( "." );
430 if ( ext_idx != std::string::npos )
431 {
432 extension = fullpath.substr ( ext_idx + 1 );
433 std::transform ( extension.begin(), extension.end(), extension.begin(), ::tolower );
434 }
435 else
436 {
437 extension.clear();
438 }
439 if ( ( mAction == Action::Compress ) && ( extension != "png" ) ) /* don't compress PNG files which are already compressed */
440 {
441 i->second.compression_type = 1;
442 if ( write_deflated_data ( in, out, 5, i->second.compressed_size ) != Z_OK )
443 {
444 std::cout << "Deflate Failed on" << fullpath << std::endl;
445 }
446 }
447 else
448 {
449 i->second.compression_type = 0;
450 while ( ( bytes_read = fread ( buffer, sizeof ( uint8_t ), 1024, in ) ) != 0 )
451 {
452 i->second.compressed_size += static_cast<uint32_t> ( fwrite ( buffer, sizeof ( uint8_t ), bytes_read, out ) );
453 }
454 assert ( ( i->second.compression_type == 0 ) && ( i->second.uncompressed_size == i->second.compressed_size ) );
455 }
456 fclose ( in );
457 }
458
459 // Write String table------------------------------------------------------------------------------------------------
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 )
466 {
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 );
470 }
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 )
473 {
474 fwrite ( &i->first, sizeof ( uint32_t ), 1, out );
475 fwrite ( &i->second, sizeof ( uint32_t ), 1, out );
476 }
477 //-------------------------------------------------------------------------------------------------------------------
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 )
483 {
484 fwrite ( &i->second, sizeof ( PKGDirectoryEntry ), 1, out );
485 }
486 fclose ( out );
487#endif
488 return 0;
489 }
490}
Header for the PKG file specification.
@ Compress
Compress assets into a package.
Definition Pack.h:35
@ Directory
List the directory of a package.
Definition Pack.h:36
@ Extract
Extract assets from a package.
Definition Pack.h:34
~Pack()
Destructor.
Pack()
Default constructor.
int operator()(int argc, char **argv) override
Execute the pack tool.
Definition Pack.cpp:270
<- This is here just for the literals
Definition AABB.hpp:31
int write_deflated_data(FILE *source, FILE *dest, int level, uint32_t &compressed_size)
Compress data from source file to dest file using deflate.
Definition Pack.cpp:53
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.
Definition CRC.cpp:27