mirror of
https://github.com/yquake2/pakextract.git
synced 2024-11-25 13:21:15 +00:00
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:
parent
1248383e46
commit
218f2e2bdf
2 changed files with 78 additions and 49 deletions
19
README
19
README
|
@ -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.
|
||||||
|
|
102
pakextract.c
102
pakextract.c
|
@ -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] == '/')
|
||||||
}
|
|
||||||
|
|
||||||
for (j = 0; j < i - 1; j++)
|
|
||||||
{
|
{
|
||||||
strcat(path, elements[j]);
|
dir[i] = '\0'; // this ends the string at this point and allows creating the directory up to here
|
||||||
strcat(path, "/");
|
mkdir(dir, 0700);
|
||||||
|
|
||||||
mkdir(path, 0700);
|
dir[i] = '/'; // restore the / so we can can go on to create next directory
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
Loading…
Reference in a new issue