#include <zlib.h>
#include <dirent.h>
#include <vector>
#include <string>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
using namespace std;
// make a file archive
typedef unsigned int u32;
const size_t FILENAME_LEN = 64;
const size_t BLOCKSIZE = 128;
const u32 VERSION = 0x10001000;
// 16 bytes
struct Header {
char id[4];
u32 version;
u32 size;
u32 files;
};
// 96 bytes
struct FileEntry {
char filename[FILENAME_LEN];
u32 size;
u32 offset;
u32 namehash;
u32 checksum;
u32 flags;
u32 locale;
u32 reserved1;
u32 reserved2;
};
enum FLAGS
{
FLAG_LOCALIZED = 1<<0,
FLAG_TSC = 1<<1
};
u32 calc_crc32(const void *data, u32 size)
{
u32 crc = crc32(0L, Z_NULL, 0);
crc = crc32(crc, (const Bytef*)data, size);
return crc;
}
vector<FileEntry> files;
vector<string> filepaths;
u32 gsize;
void addFile(const char *filename, const struct stat *st)
{
assert(filename);
size_t namelen = strlen(filename);
if (namelen>FILENAME_LEN) {
fprintf(stderr, "Skipping file: %s (filename too long)\n", filename);
return;
}
string origname(filename);
FileEntry fe;
memset(&fe, 0, sizeof(FileEntry));
// check name for a locale?
if (!strcmp(filename+namelen-3,".en")) fe.locale = 1;
else if (!strcmp(filename+namelen-3,".jp")) fe.locale = 2;
if (fe.locale) {
namelen -= 3;
fe.flags |= FLAG_LOCALIZED;
}
strncpy(fe.filename, filename, namelen);
fe.size = st->st_size;
fe.namehash = calc_crc32(filename, namelen);
if (!strncmp(filename+namelen-4,".tsc",4)) fe.flags |= FLAG_TSC;
FILE *f = fopen(origname.c_str(),"rb");
char *buf = new char[fe.size];
fread(buf, fe.size, 1, f);
fclose(f);
fe.checksum = calc_crc32(buf,fe.size);
delete[] buf;
files.push_back(fe);
filepaths.push_back(origname);
}
void scanDir(const char *dirname) {
assert(dirname);
string basepath(dirname);
DIR *dir = opendir(dirname);
while (dirent *de = readdir(dir)) {
const char *filename = de->d_name;
string fullpath = basepath + "/";
fullpath += filename;
if (filename[0]!='.') {
//printf("%s\n",fullpath.c_str());
struct stat st;
stat(fullpath.c_str(), &st);
if (S_ISDIR(st.st_mode)) {
// recurse
scanDir(fullpath.c_str());
} else {
addFile(fullpath.c_str(), &st);
}
}
}
closedir(dir);
}
int roundUp(int value, int block = BLOCKSIZE)
{
int rem = value % block;
return rem ? (value + block - rem) : value;
}
void buildFile(const char *nameOut)
{
int num = files.size();
static char dummy[BLOCKSIZE];
memset(dummy, 0, BLOCKSIZE);
// fix up offsets
int headersize = sizeof(Header) + num * sizeof(FileEntry);
int firstoffset = roundUp(headersize);
int of = firstoffset;
for (vector<FileEntry>::iterator it = files.begin(); it != files.end(); ++it) {
it->offset = of;
of = roundUp(of+it->size);
}
Header h;
strncpy(h.id, "Cave", 4);
h.version = VERSION;
h.size = of;
gsize = of;
h.files = num;
FILE *fo = fopen(nameOut,"wb");
// write header
fwrite(&h, sizeof(Header), 1, fo);
// write file entries
for (vector<FileEntry>::iterator it = files.begin(); it != files.end(); ++it) {
FileEntry *fe = &(*it);
fwrite(fe, sizeof(FileEntry), 1, fo);
}
// pad
fwrite(dummy, firstoffset-headersize, 1, fo);
// write the files
for (u32 i=0; i<num; i++) {
FileEntry &fe = files[i];
string &name = filepaths[i];
FILE *fi = fopen(name.c_str(), "rb");
char *buf = new char[fe.size];
fread(buf, fe.size, 1, fi);
fclose(fi);
fwrite(buf, fe.size, 1, fo);
delete[] buf;
// pad
int toPad = roundUp(fe.size) - fe.size;
if (toPad) fwrite(dummy, toPad, 1, fo);
}
fclose(fo);
printf("%d files packed.\n", num);
}
void compressFile(const char *nameIn, const char *nameOut)
{
FILE *f = fopen(nameIn, "rb");
char *buf = new char[gsize];
fread(buf, gsize, 1, f);
fclose(f);
u32 len = compressBound(gsize);
char *buf2 = new char[len];
int res = compress2((Bytef*)buf2, (uLongf*)&len, (const Bytef*)buf, gsize, 9);
if (res != Z_OK) {
fprintf(stderr, "Couldn't compress data\n");
} else {
f = fopen(nameOut, "wb");
fwrite(&gsize, sizeof(u32), 1, f);
fwrite(buf2, len, 1, f);
fclose(f);
delete[] buf;
delete[] buf2;
printf("Compressed %d -> %d bytes\n", gsize, len);
}
}
int main(int argc, char *argv[])
{
scanDir("data");
buildFile("archive.dat");
compressFile("archive.dat", "data.csz");
return 0;
}