diff --git a/src/common/filesystem.c b/src/common/filesystem.c index a0f1973a..29981f69 100644 --- a/src/common/filesystem.c +++ b/src/common/filesystem.c @@ -51,6 +51,7 @@ typedef struct fsMode_t mode; FILE *file; /* Only one will be used. */ unzFile *zip; /* (file or zip) */ + int compressed_size; /* Should be zero for original PAK files */ } fsHandle_t; typedef struct fsLink_s @@ -66,6 +67,7 @@ typedef struct char name[MAX_QPATH]; int size; int offset; /* Ignored in PK3 files. */ + int compressed_size; /* Should be zero for original PAK files */ } fsPackFile_t; typedef struct @@ -87,6 +89,7 @@ typedef struct fsSearchPath_s typedef enum { + DAT, PAK, PK3 } fsPackFormat_t; @@ -104,6 +107,7 @@ fsSearchPath_t *fs_baseSearchPaths = NULL; /* Pack formats / suffixes. */ fsPackTypes_t fs_packtypes[] = { + {"dat", DAT}, {"pak", PAK}, {"pk2", PK3}, {"pk3", PK3}, @@ -449,10 +453,11 @@ FS_FOpenFile(const char *rawname, fileHandle_t *f, qboolean gamedir_only) // (relevant for savegames, when starting map with wrong case but it's still found // because it's from pak, but save/bla/MAPname.sav/sv2 will have wrong case and can't be found then) Q_strlcpy(handle->name, pack->files[i].name, sizeof(handle->name)); + handle->compressed_size = 0; if (pack->pak) { - /* PAK */ + /* PAK and DAT */ if (pack->isProtectedPak) { file_from_protected_pak = true; @@ -462,6 +467,7 @@ FS_FOpenFile(const char *rawname, fileHandle_t *f, qboolean gamedir_only) if (handle->file) { + handle->compressed_size = pack->files[i].compressed_size; fseek(handle->file, pack->files[i].offset, SEEK_SET); return pack->files[i].size; } @@ -536,6 +542,34 @@ FS_FOpenFile(const char *rawname, fileHandle_t *f, qboolean gamedir_only) return -1; } +static int +FS_DecompressFile(void *buffer, int size, fsHandle_t *handle) +{ + if (handle->compressed_size) + { + unsigned long uncomressed_size; + byte *comressed_buffer; + int status; + + comressed_buffer = malloc(handle->compressed_size); + uncomressed_size = size; + + memcpy(comressed_buffer, buffer, handle->compressed_size); + + status = uncompress(buffer, &uncomressed_size, comressed_buffer, handle->compressed_size); + + free(comressed_buffer); + + if (status != MZ_OK) + { + Com_Error(ERR_FATAL, "%s: can't decompress file '%s'", __func__, handle->name); + return 0; + } + } + + return size; +} + /* * Properly handles partial reads. */ @@ -551,7 +585,14 @@ FS_Read(void *buffer, int size, fileHandle_t f) handle = FS_GetFileByHandle(f); /* Read. */ - remaining = size; + if (handle->compressed_size) + { + remaining = handle->compressed_size; + } + else + { + remaining = size; + } buf = (byte *)buffer; while (remaining) @@ -592,7 +633,7 @@ FS_Read(void *buffer, int size, fileHandle_t f) buf += r; } - return size; + return FS_DecompressFile(buffer, size, handle); } /* @@ -618,7 +659,14 @@ FS_FRead(void *buffer, int size, int count, fileHandle_t f) while (loops) { /* Read in chunks. */ - remaining = size; + if (handle->compressed_size) + { + remaining = handle->compressed_size; + } + else + { + remaining = size; + } while (remaining) { @@ -651,8 +699,8 @@ FS_FRead(void *buffer, int size, int count, fileHandle_t f) else if (r == -1) { Com_Error(ERR_FATAL, - "FS_FRead: -1 bytes read from '%s'", - handle->name); + "%s: -1 bytes read from '%s'", + __func__, handle->name); } remaining -= r; @@ -662,7 +710,7 @@ FS_FRead(void *buffer, int size, int count, fileHandle_t f) loops--; } - return size; + return FS_DecompressFile(buffer, size, handle); } /* @@ -764,6 +812,110 @@ FS_FreeSearchPaths(fsSearchPath_t *start, fsSearchPath_t *end) return cur; } +/* + * Takes an explicit (not game tree related) path to a pak file. + * + * Loads the header and directory, adding the files at the beginning of the + * list so they override previous pack files. + */ +static fsPack_t * +FS_LoadDAT(const char *packPath) +{ + int i; /* Loop counter. */ + int numFiles; /* Number of files in DAT. */ + FILE *handle; /* File handle. */ + fsPackFile_t *files; /* List of files in DAT. */ + fsPack_t *pack; /* DAT file. */ + ddatheader_t header; /* DAT file header. */ + ddatfile_t *info = NULL; /* DAT info. */ + const char *prefixpos, *pos; + char prefix[MAX_QPATH]; + + handle = Q_fopen(packPath, "rb"); + + if (handle == NULL) + { + return NULL; + } + + fread(&header, 1, sizeof(ddatheader_t), handle); + + if (LittleLong(header.ident) != IDDATHEADER || + LittleLong(header.version) != IDDATVERSION) + { + fclose(handle); + Com_Error(ERR_FATAL, "%s: '%s' is not a dat file", __func__, packPath); + } + + header.dirofs = LittleLong(header.dirofs); + header.dirlen = LittleLong(header.dirlen); + + numFiles = header.dirlen / sizeof(ddatfile_t); + + if ((numFiles == 0) || (header.dirlen < 0) || (header.dirofs < 0)) + { + fclose(handle); + Com_Error(ERR_FATAL, "%s: '%s' is too short.", + __func__, packPath); + } + + if (numFiles > MAX_FILES_IN_PACK) + { + Com_Printf("%s: '%s' has %i > %i files\n", + __func__, packPath, numFiles, MAX_FILES_IN_PACK); + } + + info = malloc(header.dirlen); + if (!info) + { + Com_Error(ERR_FATAL, "%s: '%s' is to big for read %d", + __func__, packPath, header.dirlen); + } + + files = Z_Malloc(numFiles * sizeof(fsPackFile_t)); + + fseek(handle, header.dirofs, SEEK_SET); + fread(info, 1, header.dirlen, handle); + + prefixpos = pos = packPath; + while (*pos) + { + if (*pos == '\\' || *pos == '/') + { + prefixpos = pos; + } + pos++; + } + + /* '/' + '.dat' */ + memset(prefix, 0, sizeof(prefix)); + strncpy(prefix, prefixpos + 1, + Q_min(strlen(prefixpos) - 5, sizeof(prefix) - 1)); + + /* Parse the directory. */ + for (i = 0; i < numFiles; i++) + { + Q_strlcpy(files[i].name, prefix, sizeof(files[i].name)); + strncat(files[i].name, "/", sizeof(files[i].name) - 1); + strncat(files[i].name, info[i].name, sizeof(files[i].name) - i); + files[i].offset = LittleLong(info[i].filepos); + files[i].size = LittleLong(info[i].filelen); + files[i].compressed_size = LittleLong(info[i].compressedlen); + } + free(info); + + pack = Z_Malloc(sizeof(fsPack_t)); + Q_strlcpy(pack->name, packPath, sizeof(pack->name)); + pack->pak = handle; + pack->pk3 = NULL; + pack->numFiles = numFiles; + pack->files = files; + + Com_Printf("Added datfile '%s' (%i files).\n", pack->name, numFiles); + + return pack; +} + /* * Takes an explicit (not game tree related) path to a pak file. * @@ -832,6 +984,7 @@ FS_LoadPAK(const char *packPath) Q_strlcpy(files[i].name, info[i].name, sizeof(files[i].name)); files[i].offset = LittleLong(info[i].filepos); files[i].size = LittleLong(info[i].filelen); + files[i].compressed_size = 0; } free(info); @@ -1578,6 +1731,9 @@ FS_AddPAKFromGamedir(const char *pak) switch (fs_packtypes[i].format) { + case DAT: + pakfile = FS_LoadDAT(path); + break; case PAK: pakfile = FS_LoadPAK(path); break; @@ -1696,6 +1852,14 @@ FS_AddDirToSearchPath(char *dir, qboolean create) { switch (fs_packtypes[i].format) { + case DAT: + pack = FS_LoadDAT(path); + + if (pack) + { + pack->isProtectedPak = true; + } + case PAK: pack = FS_LoadPAK(path); @@ -1776,6 +1940,9 @@ FS_AddDirToSearchPath(char *dir, qboolean create) { switch (fs_packtypes[i].format) { + case DAT: + pack = FS_LoadDAT(list[j]); + break; case PAK: pack = FS_LoadPAK(list[j]); break; diff --git a/src/common/header/files.h b/src/common/header/files.h index 938855fb..549dfdc5 100644 --- a/src/common/header/files.h +++ b/src/common/header/files.h @@ -27,6 +27,28 @@ #ifndef CO_FILES_H #define CO_FILES_H +/* The .dat files are just a linear collapse of a directory tree */ + +#define IDDATHEADER (('T' << 24) + ('A' << 16) + ('D' << 8) + 'A') +#define IDDATVERSION 9 + +typedef struct +{ + char name[128]; + int filepos; + int filelen; + int compressedlen; + int checksum; +} ddatfile_t; + +typedef struct +{ + int ident; /* == IDDATHEADER */ + int dirofs; + int dirlen; + int version; /* == IDPAKVERSION */ +} ddatheader_t; + /* The .pak files are just a linear collapse of a directory tree */ #define IDPAKHEADER (('K' << 24) + ('C' << 16) + ('A' << 8) + 'P')