Support Sin .sin pak files

Apparently the only differences to Q2 paks are the header being "SPAK"
instead of "PACK" and directory entry's names being 120chars long
instead of 56, see:
https://github.com/id-Software/RTCW-SP/blob/master/src/bspc/l_qfiles.h#L71

This even seems to work.
Refactored a bit to easily support Q2, DK and Sin (and maybe more?) paks
and rewrote mktree() in a shorter, more elegant way without malloc() :)
This commit is contained in:
Daniel Gibson 2017-04-08 22:57:06 +02:00
parent 1248383e46
commit 218f2e2bdf
2 changed files with 78 additions and 49 deletions

19
README
View file

@ -29,6 +29,8 @@ following order:
in bytes. Since every directory entry is 64 bytes in bytes. Since every directory entry is 64 bytes
long this value modulo 64 must be 0: long this value modulo 64 must be 0:
(dir_length % 64) == 0; (dir_length % 64) == 0;
Also this means that the number of directory entries
can be calculated with dir_length / 64
The directory can be anywere in the file but most times The directory can be anywere in the file but most times
it's written to the end. In consists of datablocks, it's written to the end. In consists of datablocks,
@ -41,6 +43,21 @@ in the following order:
- A 4 byte integer giving the length of the file - A 4 byte integer giving the length of the file
in bytes. in bytes.
------------------------------------------------------
The Sin Pak File Format (.sin)
------------------------------
It's very similar to the Quake II format, there are only
small differences:
- The header on the start of the file starts with "SPAK"
instead of "PACK"
- The filename in the directory entry is 120 bytes long
=> a directory entry is 128 bytes long instead of 64
=> the number of directory entries is dir_length / 128
instead of dir_length / 64
------------------------------------------------------- -------------------------------------------------------
The Daikatana Pak File Format The Daikatana Pak File Format
@ -51,7 +68,7 @@ additionally supports compressing files within the pak.
Daikatana only did that for .tga .bmp .wal .pcx and .bsp files and might Daikatana only did that for .tga .bmp .wal .pcx and .bsp files and might
expect other files to be uncompressed (in case you want to write a compressor). expect other files to be uncompressed (in case you want to write a compressor).
In Daikatana directory entries are 72 bytes long, because they have to In Daikatana directory entries are 72 bytes long, because they have two
additional 4byte integer fields at the end: additional 4byte integer fields at the end:
- A 4 byte integer giving the compressed length of the file in bytes, - A 4 byte integer giving the compressed length of the file in bytes,
if it is compressed. if it is compressed.

View file

@ -1,6 +1,6 @@
/*- /*-
* Copyright (c) 2012-2016 Yamagi Burmeister * Copyright (c) 2012-2016 Yamagi Burmeister
* 2015-2016 Daniel Gibson * 2015-2017 Daniel Gibson
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -31,14 +31,38 @@
#include <string.h> #include <string.h>
#include <unistd.h> // chdir() #include <unistd.h> // chdir()
#include <sys/stat.h> #include <sys/stat.h> // mkdir()
#include <assert.h> #include <assert.h>
enum { enum {
HDR_DIR_LEN_Q2 = 64, PAK_MODE_Q2, // standard Quake/Quake2 pak
HDR_DIR_LEN_DK = 72 PAK_MODE_SIN,
PAK_MODE_DK,
_NUM_PAK_MODES
};
static int pak_mode = PAK_MODE_Q2;
static const char* PAK_MODE_NAMES[_NUM_PAK_MODES] = {
"Quake(2)", // PAK_MODE_Q2
"Sin", // PAK_MODE_SIN
"Daikatana", // PAK_MODE_DK
};
static const int HDR_LEN[_NUM_PAK_MODES] = {
64, // PAK_MODE_Q2
128, // PAK_MODE_SIN
72, // PAK_MODE_DK
};
static const int DIR_FILENAME_LEN[_NUM_PAK_MODES] = {
56, // PAK_MODE_Q2
120, // PAK_MODE_SIN
56, // PAK_MODE_DK
}; };
/* Holds the pak header */ /* Holds the pak header */
@ -49,12 +73,11 @@ struct
int dir_length; int dir_length;
} header; } header;
int dk_pak_mode = 0;
/* A directory entry */ /* A directory entry */
typedef struct typedef struct
{ {
char file_name[56]; char file_name[120];
int file_pos; int file_pos;
int file_length; // in case of is_compressed: size after decompression int file_length; // in case of is_compressed: size after decompression
@ -72,34 +95,24 @@ typedef struct
* ommitted * ommitted
*/ */
static void static void
mktree(char *s) mktree(const char *s)
{ {
char *dir; char dir[128] = {0};
char *elements[28]; int i;
char *path; int sLen = strlen(s);
char *token;
int i, j;
path = calloc(56, sizeof(char)); strncpy(dir, s, sizeof(dir)-1);
dir = malloc(sizeof(char) * 56);
strncpy(dir, s, sizeof(char) * 56); for(i=1; i<sLen; ++i)
for (i = 0; (token = strsep(&dir, "/")) != NULL; i++)
{ {
elements[i] = token; if(dir[i] == '/')
{
dir[i] = '\0'; // this ends the string at this point and allows creating the directory up to here
mkdir(dir, 0700);
dir[i] = '/'; // restore the / so we can can go on to create next directory
}
} }
for (j = 0; j < i - 1; j++)
{
strcat(path, elements[j]);
strcat(path, "/");
mkdir(path, 0700);
}
free(path);
free(dir);
} }
/* /*
@ -133,21 +146,25 @@ read_header(FILE *fd)
// TODO: we could convert the ints to platform endianess now // TODO: we could convert the ints to platform endianess now
if (strncmp(header.signature, "PACK", 4) != 0) if (strncmp(header.signature, "SPAK", 4) == 0)
{
pak_mode = PAK_MODE_SIN;
}
else if (strncmp(header.signature, "PACK", 4) != 0)
{ {
fprintf(stderr, "Not a pak file\n"); fprintf(stderr, "Not a pak file\n");
return 0; return 0;
} }
int direntry_len = dk_pak_mode ? HDR_DIR_LEN_DK : HDR_DIR_LEN_Q2; int direntry_len = HDR_LEN[pak_mode];
// Note that this check is not reliable, it could pass and it could still be the wrong kind of pak! // Note that this check is not reliable, it could pass and it could still be the wrong kind of pak!
if ((header.dir_length % direntry_len) != 0) if ((header.dir_length % direntry_len) != 0)
{ {
const char* curmode = dk_pak_mode ? "Daikatana" : "Quake(2)"; const char* curmode = PAK_MODE_NAMES[pak_mode];
const char* othermode = (!dk_pak_mode) ? "Daikatana" : "Quake(2)"; const char* othermode = (pak_mode != PAK_MODE_DK) ? "Daikatana" : "Quake(2)";
fprintf(stderr, "Corrupt pak file - maybe it's not %s format but %s format?\n", curmode, othermode); fprintf(stderr, "Corrupt pak file - maybe it's not %s format but %s format?\n", curmode, othermode);
if(!dk_pak_mode) if(pak_mode != PAK_MODE_DK)
fprintf(stderr, "If this is a Daikatana .pak file, try adding '-dk' to command-line!\n"); fprintf(stderr, "If this is a Daikatana .pak file, try adding '-dk' to command-line!\n");
else else
fprintf(stderr, "Are you sure this is a Daikatana .pak file? Try removing '-dk' from command-line!\n"); fprintf(stderr, "Are you sure this is a Daikatana .pak file? Try removing '-dk' from command-line!\n");
@ -161,11 +178,11 @@ read_header(FILE *fd)
static int static int
read_dir_entry(directory* entry, FILE* fd) read_dir_entry(directory* entry, FILE* fd)
{ {
if(fread(entry->file_name, 56, 1, fd) != 1) return 0; if(fread(entry->file_name, DIR_FILENAME_LEN[pak_mode], 1, fd) != 1) return 0;
if(fread(&(entry->file_pos), 4, 1, fd) != 1) return 0; if(fread(&(entry->file_pos), 4, 1, fd) != 1) return 0;
if(fread(&(entry->file_length), 4, 1, fd) != 1) return 0; if(fread(&(entry->file_length), 4, 1, fd) != 1) return 0;
if(dk_pak_mode) if(pak_mode == PAK_MODE_DK)
{ {
if(fread(&(entry->compressed_length), 4, 1, fd) != 1) return 0; if(fread(&(entry->compressed_length), 4, 1, fd) != 1) return 0;
if(fread(&(entry->is_compressed), 4, 1, fd) != 1) return 0; if(fread(&(entry->is_compressed), 4, 1, fd) != 1) return 0;
@ -193,7 +210,7 @@ static directory *
read_directory(FILE *fd, int listOnly, int* num_entries) read_directory(FILE *fd, int listOnly, int* num_entries)
{ {
int i; int i;
int direntry_len = dk_pak_mode ? HDR_DIR_LEN_DK : HDR_DIR_LEN_Q2; int direntry_len = HDR_LEN[pak_mode];
int num_dir_entries = header.dir_length / direntry_len; int num_dir_entries = header.dir_length / direntry_len;
directory* dir = calloc(num_dir_entries, sizeof(directory)); directory* dir = calloc(num_dir_entries, sizeof(directory));
@ -222,7 +239,7 @@ read_directory(FILE *fd, int listOnly, int* num_entries)
{ {
printf("%s (%d bytes", cur->file_name, cur->file_length); printf("%s (%d bytes", cur->file_name, cur->file_length);
if(dk_pak_mode && cur->is_compressed) if((pak_mode == PAK_MODE_DK) && cur->is_compressed)
printf(", %d compressed", cur->compressed_length); printf(", %d compressed", cur->compressed_length);
printf(")\n"); printf(")\n");
@ -368,7 +385,7 @@ extract_files(FILE *fd, directory *dirs, int num_entries)
if(d->is_compressed) if(d->is_compressed)
{ {
assert(dk_pak_mode != 0 && "Only Daikatana paks contain compressed files!"); assert((pak_mode == PAK_MODE_DK) && "Only Daikatana paks contain compressed files!");
extract_compressed(fd, d); extract_compressed(fd, d);
} }
else else
@ -382,19 +399,14 @@ static void
printUsage(const char* argv0) printUsage(const char* argv0)
{ {
fprintf(stderr, "Extractor for Quake/Quake2 (and compatible) and Daikatana .pak files\n"); fprintf(stderr, "Extractor for Quake/Quake2 (and compatible) and Daikatana .pak and Sin .sin files\n");
fprintf(stderr, "Usage: %s [-l] [-dk] [-o output dir] pakfile\n", argv0); fprintf(stderr, "Usage: %s [-l] [-dk] [-o output dir] pakfile\n", argv0);
fprintf(stderr, " -l don't extract, just list contents\n"); fprintf(stderr, " -l don't extract, just list contents\n");
fprintf(stderr, " -dk Daikatana pak format (Quake is default)\n"); fprintf(stderr, " -dk Daikatana pak format (Quake is default, Sin is detected automatically)\n");
fprintf(stderr, " -o directory to extract to\n"); fprintf(stderr, " -o directory to extract to\n");
} }
/*
* A small programm to extract a Quake II pak file.
* The pak file is given as the first an only
* argument.
*/
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
@ -418,7 +430,7 @@ main(int argc, char *argv[])
{ {
const char* arg = argv[i]; const char* arg = argv[i];
if(strcmp(arg, "-l") == 0) list_only = 1; if(strcmp(arg, "-l") == 0) list_only = 1;
else if(strcmp(arg, "-dk") == 0) dk_pak_mode = 1; else if(strcmp(arg, "-dk") == 0) pak_mode = PAK_MODE_DK;
else if(strcmp(arg, "-o") == 0) else if(strcmp(arg, "-o") == 0)
{ {
++i; // go to next argument (should be out_dir) ++i; // go to next argument (should be out_dir)