Files
Meep-Eep 780d7a74ae Padding is harmless.
git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@2735 8092fc87-c524-0410-9efc-e669fe64eaf9
2007-04-15 23:58:42 +00:00

1001 lines
27 KiB
C

/*
* .pkg file extractor
* By Serge van den Boom (svdb@stack.nl), 20021012
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <string.h>
#include <errno.h>
#include "unpkg.h"
// Variations on the packaging file format.
typedef enum {
Type_sc1, // PC/Amiga version of SC1
Type_pc, // PC version of SC2
Type_3do, // 3DO version of SC2; also The Horde (PC version)
} PackagingType;
struct options {
char *infile;
char *outdir;
char list;
char print;
char verbose;
PackagingType type;
};
index_header *readIndex(const struct options *opts, const uint8 *buf);
void printIndex(const struct options *opts, const index_header *h,
const uint8 *buf, FILE *out);
char *getFileName(const uint8 *buf, const index_header *h,
uint16 file_index);
void writeFiles(const index_header *h, const uint8 *data, const char *path);
void parse_arguments(int argc, char *argv[], struct options *opts);
void analyzeFilesStats(struct options *opts, const index_header *h,
const FilesStats *filesStats);
void listResources(const index_header *h, const uint8 *buf, FILE *out);
FilesStats *createFilesStats(const index_header *h, const uint8 *buf,
size_t bufSize);
int
main(int argc, char *argv[]) {
int in;
struct stat sb;
uint8 *buf;
index_header *h;
struct options opts;
off_t inSize;
parse_arguments(argc, argv, &opts);
in = open(opts.infile, O_RDONLY);
if (in == -1) {
fprintf(stderr, "Error: Could not open file %s: %s\n", opts.infile,
strerror(errno));
return EXIT_FAILURE;
}
if (fstat(in, &sb) == -1) {
perror("stat() failed");
return EXIT_FAILURE;
}
inSize = sb.st_size;
buf = mmap(NULL, inSize, PROT_READ, MAP_SHARED, in, 0);
if (buf == MAP_FAILED) {
perror("mmap() failed");
return EXIT_FAILURE;
}
close(in);
h = readIndex(&opts, buf);
if (opts.print) {
printIndex(&opts, h, buf, stdout);
fflush(stdout);
}
if (opts.list) {
listResources(h, buf, stdout);
fflush(stdout);
}
{
FilesStats *stats = createFilesStats(h, buf, inSize);
analyzeFilesStats(&opts, h, stats);
}
if (opts.outdir != NULL) {
size_t len;
struct stat sb;
if (!h->packaged) {
fprintf(stderr, "Error: Cannot unpack data from non-packaged "
"file.\n");
exit(EXIT_FAILURE);
}
if (access(opts.outdir, W_OK) == -1) {
fprintf(stderr, "Error: Invalid destination dir %s: %s\n",
opts.outdir, strerror(errno));
exit(EXIT_FAILURE);
}
if (stat(opts.outdir, &sb) == -1) {
fprintf(stderr, "Error: Could not stat %s: %s\n", opts.outdir,
strerror(errno));
exit(EXIT_FAILURE);
}
if (!S_ISDIR(sb.st_mode)) {
fprintf(stderr, "Error: %s is not a directory.\n",
opts.outdir);
exit(EXIT_FAILURE);
}
len = strlen(opts.outdir);
if (opts.outdir[len - 1] == '/')
opts.outdir[len - 1] = '\0';
writeFiles(h, buf, opts.outdir);
}
// freeIndex(h);
munmap(buf, inSize);
return EXIT_SUCCESS;
}
void
usage() {
fprintf(stderr, "unpkg [-o <outputdir>] [-l] [-p] [-v] <infile>\n"
"\t-o unpack to 'outputdir'\n"
"\t-l list the resources\n"
"\t-p print the index\n"
"\t-t package file type; one of \"sc1\", \"pc\", or \"3do\"\n"
"\t \"3do\" also works for the PC version of The Horde\n"
"\t-v verbose mode\n");
}
void
parse_arguments(int argc, char *argv[], struct options *opts) {
char ch;
memset(opts, '\0', sizeof (struct options));
opts->type = Type_3do;
while (1) {
ch = getopt(argc, argv, "hlo:pt:v");
if (ch == -1)
break;
switch(ch) {
case 'l':
opts->list = 1;
break;
case 'o':
opts->outdir = optarg;
break;
case 'p':
opts->print = 1;
break;
case 't':
if (strcmp(optarg, "sc1") == 0 ||
strcmp(optarg, "SC1") == 0) {
opts->type = Type_sc1;
} else if (strcmp(optarg, "pc") == 0 ||
strcmp(optarg, "PC") == 0) {
opts->type = Type_pc;
} else if (strcmp(optarg, "3do") == 0 ||
strcmp(optarg, "3DO") == 0) {
opts->type = Type_3do;
} else {
fprintf(stderr, "Invalid package file type.\n");
usage();
exit(EXIT_FAILURE);
}
break;
case 'v':
opts->verbose = 1;
break;
case '?':
case 'h':
default:
usage();
exit(EXIT_FAILURE);
}
}
argc -= optind;
argv += optind;
if (argc != 1) {
usage();
exit(EXIT_FAILURE);
}
opts->infile = argv[0];
}
// The various file formats reserve 2 bytes for the size of a resource.
// The actual size is the value from these bytes multiplied by a value
// which depends on the variant of the packaging file format.
int
resourceSizeMultiplier(PackagingType type) {
switch (type) {
case Type_sc1: return 1;
case Type_pc: return 2;
case Type_3do: return 4;
}
abort(); // Should not happen.
}
index_header *
readIndex(const struct options *opts, const uint8 *buf) {
index_header *h;
uint16 res_flags;
const uint8 *bufptr;
h = malloc(sizeof (index_header));
res_flags = MAKE_WORD(buf[0x00], buf[0x01]);
h->packaged = ((res_flags & 0x1) == 0x1) ? true : false;
switch (opts->type) {
case Type_sc1:
h->path_list_offs =
MAKE_DWORD(buf[0x02], buf[0x03], buf[0x04], buf[0x05]);
h->file_list_offs =
MAKE_DWORD(buf[0x06], buf[0x07], buf[0x08], buf[0x09]);
h->packmem_list_offs =
MAKE_DWORD(buf[0x0a], buf[0x0b], buf[0x0c], buf[0x0d]);
h->num_types = MAKE_WORD(buf[0x0e], buf[0x0f]);
h->num_packages = MAKE_WORD(buf[0x12], buf[0x13]);
h->header_len = 0;
break;
case Type_pc:
h->packmem_list_offs =
MAKE_DWORD(buf[0x02], buf[0x03], buf[0x04], buf[0x05]);
h->path_list_offs =
MAKE_DWORD(buf[0x06], buf[0x07], buf[0x08], buf[0x09]);
h->file_list_offs =
MAKE_DWORD(buf[0x0a], buf[0x0b], buf[0x0c], buf[0x0d]);
h->num_packages = MAKE_WORD(buf[0x0e], buf[0x0f]);
h->num_types = buf[0x10];
h->header_len = MAKE_DWORD(
buf[0x12], buf[0x13], buf[0x14], buf[0x15]);
break;
case Type_3do:
h->packmem_list_offs =
MAKE_DWORD(buf[0x02], buf[0x03], buf[0x04], buf[0x05]);
h->path_list_offs =
MAKE_DWORD(buf[0x06], buf[0x07], buf[0x08], buf[0x09]);
h->file_list_offs =
MAKE_DWORD(buf[0x0a], buf[0x0b], buf[0x0c], buf[0x0d]);
h->num_packages = MAKE_WORD(buf[0x0e], buf[0x0f]);
h->num_types = MAKE_WORD(buf[0x10], buf[0x11]);
h->header_len = MAKE_DWORD(
buf[0x12], buf[0x13], buf[0x14], buf[0x15]);
break;
}
bufptr = buf + 0x16;
h->package_list = malloc(h->num_packages * sizeof (package_desc));
{
int i;
uint32 temp;
for (i = 0; i < h->num_packages; i++) {
package_desc *package = &h->package_list[i];
package->index = i + 1;
temp = MAKE_DWORD(bufptr[0], bufptr[1], bufptr[2], bufptr[3]);
package->num_types = GET_TYPE(temp);
package->num_instances = GET_INSTANCE(temp);
package->file_index = GET_PACKAGE(temp);
bufptr += 4;
temp = MAKE_DWORD(bufptr[0], bufptr[1], bufptr[2], bufptr[3]);
package->flags = temp >> 24;
package->data_loc = temp & 0x00ffffff;
package->size = 0;
// Will be calculated from the sizes of the individual
// resource instances in the package below.
bufptr += 4;
}
}
// Type info:
h->type_list = malloc(h->num_types * sizeof (type_desc));
{
int i;
for (i = 0; i < h->num_types; i++) {
h->type_list[i].instance_count =
MAKE_WORD(bufptr[0], bufptr[1]);
bufptr += 2;
}
}
if ((uint32) (bufptr - buf) != h->packmem_list_offs) {
bufptr = buf + h->packmem_list_offs;
}
{
int i, j;
int num_types, num_instances;
for (i = 0; i < h->num_packages; i++) {
package_desc *package = &h->package_list[i];
uint32 next_offset;
if (h->packaged) {
// One file name per package
char *temp;
temp = getFileName(buf, h, package->file_index);
package->filename = (temp == NULL) ? NULL : strdup(temp);
} else {
// File name is specified per resource instance.
package->filename = NULL;
}
num_types = package->num_types;
package->type_list = malloc(num_types * sizeof (packtype_desc));
for (j = 0; j < num_types; j++) {
packtype_desc *type = &package->type_list[j];
uint32 temp;
temp = MAKE_DWORD(bufptr[0], bufptr[1], bufptr[2], bufptr[3]);
type->type = GET_TYPE(temp);
type->first_instance = GET_INSTANCE(temp);
type->num_instances = GET_PACKAGE(temp);
bufptr += 4;
}
next_offset = package->data_loc;
for (j = 0; j < num_types; j++) {
packtype_desc *type = &package->type_list[j];
int k;
num_instances = type->num_instances;
type->instances =
malloc(sizeof (packdef_instance) * num_instances);
for (k = 0; k < num_instances; k++) {
packdef_instance *instance = &type->instances[k];
if (h->packaged) {
instance->size = resourceSizeMultiplier(opts->type) *
MAKE_WORD(bufptr[0], bufptr[1]);
instance->offset = next_offset;
next_offset += instance->size;
instance->file_index = (uint16) -1;
instance->filename = NULL;
} else {
char *temp;
instance->size = 0;
instance->offset = 0;
instance->file_index =
MAKE_WORD(bufptr[0], bufptr[1]);
temp = getFileName(buf, h, instance->file_index);
instance->filename = (temp == NULL) ?
NULL : strdup(temp);
}
bufptr += 2;
} // for k
package->size = next_offset - package->data_loc;
} // for j
} // for i
}
return h;
}
void
printIndex(const struct options *opts, const index_header *h,
const uint8 *buf, FILE *out) {
const uint8 *bufptr;
fprintf(out, "0x00000000 File type: %s\n",
h->packaged ? "packaged" : "not packaged");
switch (opts->type) {
case Type_sc1:
fprintf(out, "0x00000002 path_list_offs: 0x%08x\n",
h->path_list_offs);
fprintf(out, "0x00000006 file_list_offs: 0x%08x\n",
h->file_list_offs);
fprintf(out, "0x0000000a packmem_list_offs: 0x%08x\n",
h->packmem_list_offs);
fprintf(out, "0x0000000e Number of package types: %d\n",
h->num_types);
fprintf(out, "0x00000010 Unknown: 0x%04x\n",
MAKE_WORD(buf[0x10], buf[0x11]));
fprintf(out, "0x00000012 Number of packages: %d\n",
h->num_packages);
fprintf(out, "0x00000013 Unknown: 0x%04x\n",
MAKE_WORD(buf[0x13], buf[0x14]));
break;
case Type_pc:
fprintf(out, "0x00000002 packmem_list_offs: 0x%08x\n",
h->packmem_list_offs);
fprintf(out, "0x00000006 path_list_offs: 0x%08x\n",
h->path_list_offs);
fprintf(out, "0x0000000a file_list_offs: 0x%08x\n",
h->file_list_offs);
fprintf(out, "0x0000000e Number of packages: %d\n",
h->num_packages);
fprintf(out, "0x00000010 Number of package types: %d\n",
h->num_types);
fprintf(out, "0x00000011 Unknown: 0x%02x\n",
buf[0x11]);
fprintf(out, "0x00000012 Header length: 0x%08x\n",
h->header_len);
break;
case Type_3do:
fprintf(out, "0x00000002 packmem_list_offs: 0x%08x\n",
h->packmem_list_offs);
fprintf(out, "0x00000006 path_list_offs: 0x%08x\n",
h->path_list_offs);
fprintf(out, "0x0000000a file_list_offs: 0x%08x\n",
h->file_list_offs);
fprintf(out, "0x0000000e Number of packages: %d\n",
h->num_packages);
fprintf(out, "0x00000010 Number of package types: %d\n",
h->num_types);
fprintf(out, "0x00000012 Header length: 0x%08x\n",
h->header_len);
break;
}
bufptr = buf + 0x16;
fprintf(out, "0x00000016 package info:\n");
{
int i;
for (i = 0; i < h->num_packages; i++) {
package_desc *package = &h->package_list[i];
fprintf(out, "0x%08x #%d, %d types, %d instances, "
"file index 0x%04x, ", bufptr - buf, package->index,
package->num_types, package->num_instances,
package->file_index);
bufptr += 4;
if (package->flags != 0xff) {
fprintf(out, "BAD OFFSET!\n"); } else {
fprintf(out, "offset 0x%06x\n", package->data_loc);
}
bufptr += 4;
}
}
// Type info:
fprintf(out, "0x%08x type info:\n", bufptr - buf);
{
int i;
for (i = 0; i < h->num_types; i++) {
fprintf(out, "0x%08x #%d, %d instances\n",
bufptr - buf, i + 1, h->type_list[i].instance_count);
bufptr += 2;
}
}
if ((uint32) (bufptr - buf) != h->packmem_list_offs) {
fprintf(out, "PACKMEM_LIST NOT IMMEDIATELY AFTER TYPE LIST!\n");
bufptr = buf + h->packmem_list_offs;
}
fprintf(out, "0x%08x packmem info:\n", bufptr - buf);
{
int i, j;
int num_types, num_instances;
for (i = 0; i < h->num_packages; i++) {
package_desc *package = &h->package_list[i];
if (h->packaged) {
fprintf(out, "0x%08x Package #%d (%s):\n",
bufptr - buf, package->index,
(package->filename == NULL) ?
"<<INTERNAL>>" : package->filename);
} else {
fprintf(out, "0x%08x Package #%d:\n",
bufptr - buf, package->index);
}
num_types = package->num_types;
for (j = 0; j < num_types; j++) {
fprintf(out, "0x%08x Type #%d: type %d, "
"%d instances starting with #%d\n",
bufptr - buf, j,
h->package_list[i].type_list[j].type,
h->package_list[i].type_list[j].num_instances,
h->package_list[i].type_list[j].first_instance);
bufptr += 4;
}
for (j = 0; j < num_types; j++) {
packtype_desc *type = &package->type_list[j];
int k;
num_instances = type->num_instances;
for (k = 0; k < num_instances; k++) {
packdef_instance *instance = &type->instances[k];
uint32 resId = MAKE_RESOURCE(package->index, type->type,
type->first_instance + k);
if (h->packaged) {
fprintf(out, "0x%08x [%08x]: Instance #%d of "
"type %d: size %d bytes\n", bufptr - buf,
resId, k + type->first_instance,
type->type, instance->size);
} else {
fprintf(out, "0x%08x [%08x]: Instance #%d of "
"type %d: %s\n", bufptr - buf, resId,
k + type->first_instance, type->type,
(instance->filename == NULL) ?
"<<UNNAMED>>" : instance->filename);
// NB. NULL filenames should not occur for
// non-packaged files.
}
bufptr += 2;
} // for k
} // for j
} // for i
}
}
// Returns 1 more than the largest used file index, or 0 if there are
// no file indices used at all (this can only happen if the package is
// empty).
size_t
countFileIndexUpper(const index_header *h) {
int i;
size_t result;
result = 0;
for (i = 0; i < h->num_packages; i++) {
package_desc *package = &h->package_list[i];
if (package->file_index >= result)
result = package->file_index + 1;
}
return result;
}
// returns "(uint32) -1" on error, in which case errno is set.
uint32
fileSize(const char *fileName) {
struct stat sb;
if (stat(fileName, &sb) == -1) {
// errno is set
return (uint32) -1;
}
return sb.st_size;
}
int
packageOffsetComparator(package_desc **p1, package_desc **p2) {
if ((*p1)->file_index < (*p2)->file_index)
return -1;
if ((*p1)->file_index > (*p2)->file_index)
return 1;
if ((*p1)->data_loc < (*p2)->data_loc)
return -1;
if ((*p1)->data_loc > (*p2)->data_loc)
return 1;
return 0;
}
FilesStats *
createFilesStats(const index_header *h, const uint8 *buf, size_t bufSize) {
FilesStats *result;
size_t statsI;
assert(h->packaged);
result = malloc(sizeof(FilesStats));
result->indexUpper = countFileIndexUpper(h);
result->fileStats = malloc(result->indexUpper * sizeof(FileStats));
// Initialise the FileStats structures.
for (statsI = 0; statsI < result->indexUpper; statsI++) {
FileStats *stats = &result->fileStats[statsI];
stats->numPackages = 0;
stats->fileSize = 0;
}
// Count the references to each file.
{
int i;
for (i = 0; i < h->num_packages; i++) {
package_desc *package = &h->package_list[i];
result->fileStats[package->file_index].numPackages++;
}
}
// Allocate the instances arrays of the FileStat structures.
for (statsI = 0; statsI < result->indexUpper; statsI++) {
FileStats *stats = &result->fileStats[statsI];
stats->packages =
malloc(stats->numPackages * sizeof (packdef_instance *));
}
// Clear the numPackages field of the FileStats structures,
// so it can grow again as the FileStats array is filled.
for (statsI = 0; statsI < result->indexUpper; statsI++) {
FileStats *stats = &result->fileStats[statsI];
stats->numPackages = 0;
}
// Fill the instances arrays of the FileStat structures.
{
int i;
for (i = 0; i < h->num_packages; i++) {
package_desc *package = &h->package_list[i];
FileStats *stats = &result->fileStats[package->file_index];
stats->packages[stats->numPackages] = package;
stats->numPackages++;
}
}
// Sort the packages array of each FileStats structure.
for (statsI = 0; statsI < result->indexUpper; statsI++) {
FileStats *stats = &result->fileStats[statsI];
qsort(stats->packages, stats->numPackages, sizeof (package_desc *),
(int (*)(const void *, const void *))
packageOffsetComparator);
}
// Determine the sizes of the files.
for (statsI = 0; statsI < result->indexUpper; statsI++) {
FileStats *stats = &result->fileStats[statsI];
const char *fileName;
fileName = getFileName(buf, h, statsI);
if (fileName == NULL) {
stats->fileSize = bufSize;
} else {
stats->fileSize = fileSize(fileName);
if (stats->fileSize == (uint32) -1) {
fprintf(stderr, "Could not determine size of file %s: %s\n",
fileName, strerror(errno));
stats->fileSize = 0;
}
}
}
return result;
}
packdef_instance *
lastInstance(package_desc *pack) {
packtype_desc *type = &pack->type_list[pack->num_types - 1];
packdef_instance *instance = &type->instances[type->num_instances - 1];
return instance;
}
void
analyzeFilesStats(struct options *opts, const index_header *h,
const FilesStats *filesStats) {
size_t filesStatsI;
int multiplier = resourceSizeMultiplier(opts->type);
for (filesStatsI = 0; filesStatsI < filesStats->indexUpper;
filesStatsI++) {
FileStats *stats = &filesStats->fileStats[filesStatsI];
size_t packI;
for (packI = 0; packI < stats->numPackages; packI++) {
package_desc *pack = stats->packages[packI];
uint32 off1 = pack->data_loc;
uint32 off2;
uint32 expectedSize;
if (packI < stats->numPackages - 1) {
off2 = stats->packages[packI + 1]->data_loc;
} else {
off2 = stats->fileSize;
if (off2 == (uint32) -1)
break;
}
expectedSize = off2 - off1;
if (pack->size != expectedSize) {
uint32 missing = expectedSize - pack->size;
// The package format only reserves two bytes for the
// size of a resource, and a multiplier, dependant on
// the variant of the package format, is used to determine
// the real value.
// In the resource files of the PC version of Star Control
// II, there are eight files which are still too large for
// the field, so an overflow occurs, which must be
// corrected. Fortunately, the only occurances are in
// packages which only have one instance, so the difference
// in offset between consecutive packages can be used as the
// real size.
// In the PC version of the Horde, an overflow exists for
// two packages which consist of more than one resource
// instance. We assume that it is the last instance which
// has the invalid size. This is reasonable, as an invalid
// size would cause the wrong offset to be used for
// subsequent instances in the package, which would likely
// have been noticed due to corruption in the game.
// In the Amiga version of SC1, an overflow exists in
// one package with 2 instances.
// The DOS version of SC1 and the 3DO version of SC2 have
// no such overflows.
fprintf(stderr, "Package #%d: Size 0x%08x; "
"Expected 0x%08x. %d instances. ", pack->index,
pack->size, expectedSize, pack->num_instances);
if (pack->size < expectedSize &&
(missing % (multiplier * 0x10000) == 0)) {
fprintf(stderr, "Correcting.\n");
pack->size = expectedSize;
lastInstance(pack)->size += missing;
} else if (packI == stats->numPackages - 1 &&
pack->size + multiplier > expectedSize) {
// It's possible that the package itself came from
// another package; in that case, there might be up to
// multiplier - 1 bytes of padding to the end of the
// package file to make the size a multiple of
// 'multiplier'.
fprintf(stderr, "Just padding.\n");
} else {
fprintf(stderr, "KEPT VALUE!\n");
}
}
}
}
}
void
listResources(const index_header *h, const uint8 *buf, FILE *out) {
int i, j;
int num_types, num_instances;
char *filename;
for (i = 0; i < h->num_packages; i++) {
package_desc *package = &h->package_list[i];
if (h->packaged) {
// One filename for all resources in a package.
filename = (package->filename == NULL) ?
"<<INTERNAL>>" : package->filename;
}
num_types = package->num_types;
for (j = 0; j < num_types; j++) {
packtype_desc *type = &package->type_list[j];
int k;
num_instances = type->num_instances;
for (k = 0; k < num_instances; k++) {
packdef_instance *instance = &type->instances[k];
if (!h->packaged)
filename = (instance->filename == NULL) ?
"<<UNNAMED>>" : instance->filename;
fprintf(out, "0x%08x %s\n", MAKE_RESOURCE(
package->index, type->type,
type->first_instance + k), filename);
} // for k
} // for j
} // for i
(void) buf;
}
char *
getFileName(const uint8 *buf, const index_header *h, uint16 file_index) {
static char result[PATH_MAX];
char *ptr;
file_info *fi;
uint16 path_offset;
ptr = result;
fi = &((file_info *) (buf + h->file_list_offs))[file_index];
if (fi->filename[0] == '\0')
return NULL;
path_offset = MAKE_WORD(fi->path_offset[0], fi->path_offset[1]);
if (path_offset != 0xffff) {
strcpy(ptr, buf + h->path_list_offs + path_offset);
ptr += strlen(ptr);
*ptr = '/';
ptr++;
}
ptr[8] = '\0';
strncpy(ptr, fi->filename, 8);
ptr += strlen(ptr);
*ptr = '.';
ptr++;
ptr[3] = '\0';
strncpy(ptr, fi->extension, 3);
return result;
}
void
writeFile(const char *file, const uint8 *data, size_t len) {
int fd;
// check if all the path components exist
{
const char *ptr;
char path[PATH_MAX];
ptr = file;
if (ptr[0] == '/')
ptr++;
while (1) {
ptr = strchr(ptr, '/');
if (ptr == NULL)
break;
ptr++;
memcpy(path, file, ptr - file);
path[ptr - file] = '\0';
if (mkdir(path, 0777) == -1) {
if (errno == EEXIST)
continue;
fprintf(stderr, "Error: Could not make directory %s: %s\n",
path, strerror(errno));
exit(EXIT_FAILURE);
}
}
}
fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd == -1) {
perror("Could not open file for writing");
exit(EXIT_FAILURE);
}
while (len > 0) {
ssize_t written;
written = write(fd, data, len);
if (written < 0) {
if (errno == EINTR)
continue;
perror("write failed");
exit(EXIT_FAILURE);
}
len -= written;
data += written;
}
close(fd);
}
// If everything is ok, 0 is returned.
// Returns -1 on error, in which case errno is set.
int
copyFileSegment(const char *src, FILE *inFile, size_t offset,
const char *dest, size_t size) {
FILE *outFile;
#define BUFSIZE 4096
uint8 buf[BUFSIZE];
if (fseek(inFile, offset, SEEK_SET) == -1) {
int savedErrno = errno;
fprintf(stderr, "Seeking in input file %s (to %ld) failed: %s.\n",
src, (long int) offset, strerror(errno));
errno = savedErrno;
return -1;
}
outFile = fopen(dest, "wb");
if (outFile == NULL) {
int savedErrno = errno;
fprintf(stderr, "Opening output file %s failed: %s.\n",
dest, strerror(errno));
errno = savedErrno;
return -1;
}
while (size > 0) {
size_t toRead;
size_t numRead;
size_t numWritten;
toRead = sizeof buf;
if (toRead > size)
toRead = size;
do {
numRead = fread(buf, 1, toRead, inFile);
} while (ferror(inFile) && errno == EINTR);
if (numRead != toRead) {
if (ferror(inFile)) {
int savedErrno = errno;
fprintf(stderr, "Error reading file %s: %s.\n", src,
strerror(errno));
fclose(outFile);
errno = savedErrno;
return -1;
} else {
// EOF
assert(feof(inFile));
fprintf(stderr, "Unexpected end-of-file while reading "
"file %s.\n", src);
fclose(outFile);
errno = EIO;
return -1;
}
}
do {
numWritten = fwrite(buf, 1, numRead, outFile);
} while (ferror(outFile) && errno == EINTR);
if (numWritten != numRead) {
int savedErrno = errno;
fprintf(stderr, "Error writing to file %s: %s.\n", dest,
strerror(errno));
fclose(outFile);
errno = savedErrno;
return -1;
}
size -= numWritten;
}
fclose(outFile);
return 0;
}
// Only for packaged files.
void
writeFiles(const index_header *h, const uint8 *data, const char *path) {
int i;
char filename[PATH_MAX];
int num_types, num_instances;
for (i = 0; i < h->num_packages; i++) {
package_desc *package = &h->package_list[i];
int j;
FILE *inFile = NULL;
if (package->filename != NULL) {
inFile = fopen(package->filename, "rb");
if (inFile == NULL) {
fprintf(stderr, "Error: Could not open input file %s.\n",
package->filename);
continue;
}
}
num_types = h->package_list[i].num_types;
for (j = 0; j < num_types; j++) {
packtype_desc *type = &package->type_list[j];
int k;
num_instances = type->num_instances;
for (k = 0; k < num_instances; k++) {
packdef_instance *instance = &type->instances[k];
sprintf(filename, "%s/%08x", path,
MAKE_RESOURCE(package->index, type->type,
type->first_instance + k));
#if 0
sprintf(filename, "%s/%08x=%03x-%02x-%03x",
path,
MAKE_RESOURCE(package->index, type->type,
type->first_instance + k), package->index,
type->type, type->first_instance + k);
#endif
if (package->filename != NULL) {
// Resource is contained in a different file.
copyFileSegment(instance->filename, inFile,
instance->offset, filename, instance->size);
} else {
// Resource is contained in this package file itself.
writeFile(filename, &data[instance->offset],
instance->size);
}
}
}
if (inFile != NULL)
fclose(inFile);
}
}