Lephisto/utils/pack/pack.c
2013-02-03 17:35:44 +00:00

251 lines
6.5 KiB
C

#include <stdio.h>
#include <sys/stat.h> // S_IRUSR
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <malloc.h>
#include "pack.h"
#include "md5.h"
// == Store data in a funky format. =======================
// Format:
// -- Index (in 512 byte chunks).
// -- Magic number (16 bytes).
// -- Number of files (uint32_t).
// -- Files in format name/location.
// -- File name (128 bytes max, ending in \0).
// -- File location (uint32_t).
// -- File data in format Size/Data.
// -- File size (uint32_t).
// -- File data (char*).
// -- File MD5 (16 byte char*).
// -- EOF
//
// -- Write magic number and number of files.
// -- Write the index.
// -- Pack the files.
// ========================================================
// The read/write block size.
#define BLOCKSIZE 128*1024
// Max filename length.
#define MAX_FILENAME 100
#define LOG(str, args...)(fprintf(stdout, str"\n", ## args))
#define WARN(str, args...)(fprintf(stderr, "[%d] "str"\n", SDL_GetTicks(), ## args))
#define ERR(str, args...) (fprintf(stderr, "%s:%d: "str"\n", __FILE__, __LINE__, ## args))
#ifdef DEBUG
# undef DEBUG
# define DEBUG(str, args...) LOG(str, ## args)
# define DEBUGGING
#else
# define DEBUG(str, args...) do {;} while(0)
#endif
#define PERMS S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH
const uint64_t magic = 0x25524573; // sER%
// Grab the filesize.
static off_t getfilesize(const char* filename) {
struct stat file;
if(!stat(filename, &file))
return file.st_size;
ERR("Unable to get filesize of %s", filename);
return 0;
}
// Return true if filename is a Packfile.
int pack_check(char* filename) {
int fd = open(filename, O_RDONLY);
if(fd == -1) {
ERR("Error opening %s: %s", filename, strerror(errno));
return -1;
}
char* buf = malloc(sizeof(magic));
if(read(fd, buf, sizeof(magic)) != sizeof(magic)) {
ERR("Error reading magic number: %s", strerror(errno));
free(buf);
return -1;
}
int ret = (memcmp(buf, &magic, sizeof(magic))==0) ? 0 : 1;
free(buf);
return ret;
}
// Pack nfiles, infiles into outfile.
#define WRITE(f,b,n) if(write(f,b,n)==-1) { \
ERR("Error writing to file: %s", strerror(errno)); \
free(buf); return -1; }
int pack_files(char* outfile, char** infiles, uint32_t nfiles) {
void* buf;
struct stat file;
int i, namesize;
int outfd, infd;
uint32_t indexsize, pointer;
int bytes;
const uint8_t b = '\0';
for(namesize = 0, i = 0; i < nfiles; i++) {
// Make sure file exists before writing.
if(stat(infiles[i], &file)) {
ERR("File %s does not exist", infiles[i]);
return -1;
}
if(strlen(infiles[i]) < MAX_FILENAME) {
ERR("filename '%s' is too long, should be only %d characters", infiles[i], MAX_FILENAME);
return -1;
}
namesize += strlen(infiles[i]);
}
indexsize = (sizeof(magic) + 4 + // Magic number and number of files.
namesize + // Total length of file names.
(1+4)*nfiles); // File size and extra end of string char '\0'.
DEBUG("Index size is %d", indexsize);
// Create the output file.
outfd = creat(outfile, PERMS);
if(outfd == -1) {
ERR("Unable to open %s for writing", outfile);
return -1;
}
// Index!
buf = malloc(BLOCKSIZE);
// Magic number.
WRITE(outfd, &magic, sizeof(magic));
DEBUG("Wrote magic number");
// Number of files.
WRITE(outfd, &nfiles, sizeof(nfiles));
DEBUG("Wrote number of files: %d", nfiles);
// Create file dependent index part.
pointer = indexsize;
for(i = 0; i < nfiles; i++) {
WRITE(outfd, infiles[i], strlen(infiles[i]));
DEBUG("File: '%s' at %d", infiles[i], pointer);
WRITE(outfd, &b, 1);
WRITE(outfd, &pointer, 4);
pointer += getfilesize(infiles[i]) + 8; // Set pointer to be next file pos.
}
// Data!
md5_state_t md5;
md5_byte_t* md5val = malloc(16);
for(i = 0; i < nfiles; i++) {
bytes = (uint32_t)getfilesize(infiles[i]);
WRITE(outfd, &bytes, 4); // filesize.
DEBUG("About to write '%s' of %d bytes", infiles[i], bytes);
infd = open(infiles[i], O_RDONLY);
md5_init(&md5);
while((bytes = read(infd, buf, BLOCKSIZE))) {
WRITE(outfd, buf, bytes); // Data.
md5_append(&md5, buf, bytes);
}
md5_finish(&md5, md5val);
WRITE(outfd, md5val, sizeof(md5val));
close(infd);
DEBUG("Wrote file '%s'", infiles[i]);
}
free(md5val);
close(outfd);
free(buf);
DEBUG("Packfile success\n\t%d files\n\t%d bytes", nfiles, (int)getfilesize(outfile));
return 0;
}
#undef WRITE
// Opens the filename in packfile for reading.
#define READ(f,b,n) if(read(f,b,n) != n) { \
ERR("Too few bytes read. Expected more."); \
free(buf); return -1; }
int pack_open(Packfile* file, char* packfile, char* filename) {
int i, j;
uint32_t nfiles;
char* buf = (char*)malloc(MAX_FILENAME);
file = (Packfile*)malloc(sizeof(Packfile));
file->start = file->end = 0;
file->fd = open(packfile, O_RDONLY);
if(file->fd == -1) {
ERR("Error opening %s: %s", filename, strerror(errno));
return -1;
}
READ(file->fd, buf, sizeof(magic)); // Make sure it's a packfile.
if(memcmp(buf, &magic, sizeof(magic))) {
ERR("File %s is not a valid packfile", filename);
return -1;
}
READ(file->fd, &nfiles, 4);
for(i = 0; i < nfiles; i++) {
// Start to search files.
j = 0;
READ(file->fd, &buf[j], 1); // Get the name.
while(buf[j++] != '\0')
READ(file->fd, &buf[j], 1);
if(strcmp(filename, buf) == 0) {
// We found the file.
READ(file->fd, &file->start, 4);
DEBUG("'%s' found at %d", filename, file->start);
break;
}
lseek(file->fd, 4, SEEK_CUR); // Ignore the file location.
}
free(buf);
if(file->start) {
// Go to the beginning of the file.
if(lseek(file->fd, file->start, SEEK_SET) != file->start) {
ERR("Failure to seek to file start: %s", strerror(errno));
return -1;
}
if(read(file->fd, &file->end, 4) != 4) {
ERR("Too few bytes read. Expected more.");
return -1;
}
DEBUG("\t%d bytes", file->end);
file->pos = file->start;
file->end += file->start;
}
return 0;
}
#undef READ
// Read count bytes from file and put them into buf.
ssize_t pack_read(Packfile* file, void* buf, size_t count) {
if(file->pos + count > file->end) count = file->end - file->pos; // Can't go past the end!
if(count == 0) return 0;
int bytes;
bytes = read(file->fd, buf, count);
file->pos += bytes;
return bytes;
}
// Close the packfile.
int pack_close(Packfile* file) {
int i = close(file->fd);
free(file);
return (i) ? -1 : 0;
}