2022-07-01 18:37:21 +02:00
|
|
|
/*
|
|
|
|
zip.c - ZIP support for filesystem
|
|
|
|
Copyright (C) 2019 Mr0maks
|
2023-04-15 01:28:04 +02:00
|
|
|
Copyright (C) 2019-2023 Xash3D FWGS contributors
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
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 3 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
2022-08-01 02:12:35 +02:00
|
|
|
#if XASH_POSIX
|
2022-07-01 18:37:21 +02:00
|
|
|
#include <unistd.h>
|
2022-08-01 02:12:35 +02:00
|
|
|
#endif
|
2022-07-01 18:37:21 +02:00
|
|
|
#include <errno.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include STDINT_H
|
|
|
|
#include "port.h"
|
|
|
|
#include "filesystem_internal.h"
|
|
|
|
#include "crtlib.h"
|
|
|
|
#include "common/com_strings.h"
|
|
|
|
#include "miniz.h"
|
|
|
|
|
2023-01-04 15:15:40 +01:00
|
|
|
#define ZIP_HEADER_LF (('K'<<8)+('P')+(0x03<<16)+(0x04<<24))
|
2022-07-01 18:37:21 +02:00
|
|
|
#define ZIP_HEADER_SPANNED ((0x08<<24)+(0x07<<16)+('K'<<8)+'P')
|
|
|
|
|
|
|
|
#define ZIP_HEADER_CDF ((0x02<<24)+(0x01<<16)+('K'<<8)+'P')
|
|
|
|
#define ZIP_HEADER_EOCD ((0x06<<24)+(0x05<<16)+('K'<<8)+'P')
|
|
|
|
|
|
|
|
#define ZIP_COMPRESSION_NO_COMPRESSION 0
|
|
|
|
#define ZIP_COMPRESSION_DEFLATED 8
|
|
|
|
|
|
|
|
#define ZIP_ZIP64 0xffffffff
|
|
|
|
|
|
|
|
#pragma pack( push, 1 )
|
|
|
|
typedef struct zip_header_s
|
|
|
|
{
|
2023-01-04 15:15:40 +01:00
|
|
|
uint32_t signature; // little endian ZIP_HEADER
|
|
|
|
uint16_t version; // version of pkzip need to unpack
|
|
|
|
uint16_t flags; // flags (16 bits == 16 flags)
|
|
|
|
uint16_t compression_flags; // compression flags (bits)
|
|
|
|
uint32_t dos_date; // file modification time and file modification date
|
|
|
|
uint32_t crc32; //crc32
|
|
|
|
uint32_t compressed_size;
|
|
|
|
uint32_t uncompressed_size;
|
|
|
|
uint16_t filename_len;
|
|
|
|
uint16_t extrafield_len;
|
2022-07-01 18:37:21 +02:00
|
|
|
} zip_header_t;
|
|
|
|
|
|
|
|
/*
|
|
|
|
in zip64 comp and uncompr size == 0xffffffff remeber this
|
|
|
|
compressed and uncompress filesize stored in extra field
|
|
|
|
*/
|
|
|
|
|
|
|
|
typedef struct zip_header_extra_s
|
|
|
|
{
|
2023-01-04 15:15:40 +01:00
|
|
|
uint32_t signature; // ZIP_HEADER_SPANNED
|
|
|
|
uint32_t crc32;
|
|
|
|
uint32_t compressed_size;
|
|
|
|
uint32_t uncompressed_size;
|
2022-07-01 18:37:21 +02:00
|
|
|
} zip_header_extra_t;
|
|
|
|
|
|
|
|
typedef struct zip_cdf_header_s
|
|
|
|
{
|
2023-01-04 15:15:40 +01:00
|
|
|
uint32_t signature;
|
|
|
|
uint16_t version;
|
|
|
|
uint16_t version_need;
|
|
|
|
uint16_t generalPurposeBitFlag;
|
|
|
|
uint16_t flags;
|
|
|
|
uint16_t modification_time;
|
|
|
|
uint16_t modification_date;
|
|
|
|
uint32_t crc32;
|
|
|
|
uint32_t compressed_size;
|
|
|
|
uint32_t uncompressed_size;
|
|
|
|
uint16_t filename_len;
|
|
|
|
uint16_t extrafield_len;
|
|
|
|
uint16_t file_commentary_len;
|
|
|
|
uint16_t disk_start;
|
|
|
|
uint16_t internal_attr;
|
|
|
|
uint32_t external_attr;
|
|
|
|
uint32_t local_header_offset;
|
2022-07-01 18:37:21 +02:00
|
|
|
} zip_cdf_header_t;
|
|
|
|
|
|
|
|
typedef struct zip_header_eocd_s
|
|
|
|
{
|
2023-01-04 15:15:40 +01:00
|
|
|
uint16_t disk_number;
|
|
|
|
uint16_t start_disk_number;
|
|
|
|
uint16_t number_central_directory_record;
|
|
|
|
uint16_t total_central_directory_record;
|
|
|
|
uint32_t size_of_central_directory;
|
|
|
|
uint32_t central_directory_offset;
|
|
|
|
uint16_t commentary_len;
|
2022-07-01 18:37:21 +02:00
|
|
|
} zip_header_eocd_t;
|
|
|
|
#pragma pack( pop )
|
|
|
|
|
|
|
|
// ZIP errors
|
2023-01-04 15:15:40 +01:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
ZIP_LOAD_OK = 0,
|
|
|
|
ZIP_LOAD_COULDNT_OPEN,
|
|
|
|
ZIP_LOAD_BAD_HEADER,
|
|
|
|
ZIP_LOAD_BAD_FOLDERS,
|
|
|
|
ZIP_LOAD_NO_FILES,
|
|
|
|
ZIP_LOAD_CORRUPTED
|
|
|
|
};
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
typedef struct zipfile_s
|
|
|
|
{
|
|
|
|
char name[MAX_SYSPATH];
|
|
|
|
fs_offset_t offset; // offset of local file header
|
|
|
|
fs_offset_t size; //original file size
|
|
|
|
fs_offset_t compressed_size; // compressed file size
|
2023-01-04 15:15:40 +01:00
|
|
|
uint16_t flags;
|
2022-07-01 18:37:21 +02:00
|
|
|
} zipfile_t;
|
|
|
|
|
2022-08-06 19:33:01 +02:00
|
|
|
struct zip_s
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
2024-06-27 06:31:09 +02:00
|
|
|
file_t *handle;
|
2022-07-01 18:37:21 +02:00
|
|
|
int numfiles;
|
2024-06-27 06:31:09 +02:00
|
|
|
zipfile_t files[1]; // flexible
|
2022-08-06 19:33:01 +02:00
|
|
|
};
|
2022-07-01 18:37:21 +02:00
|
|
|
|
2023-12-04 01:01:11 +01:00
|
|
|
// #define ENABLE_CRC_CHECK // known to be buggy because of possible libpublic crc32 bug, disabled
|
|
|
|
|
2022-12-14 22:59:52 +01:00
|
|
|
/*
|
|
|
|
============
|
|
|
|
FS_CloseZIP
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
static void FS_CloseZIP( zip_t *zip )
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
2024-06-27 06:31:09 +02:00
|
|
|
if( zip->handle != NULL )
|
|
|
|
FS_Close( zip->handle );
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
Mem_Free( zip );
|
|
|
|
}
|
|
|
|
|
2022-12-14 22:59:52 +01:00
|
|
|
/*
|
|
|
|
============
|
|
|
|
FS_Close_ZIP
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
static void FS_Close_ZIP( searchpath_t *search )
|
2022-11-18 14:35:21 +01:00
|
|
|
{
|
|
|
|
FS_CloseZIP( search->zip );
|
|
|
|
}
|
|
|
|
|
2022-07-01 18:37:21 +02:00
|
|
|
/*
|
|
|
|
============
|
|
|
|
FS_SortZip
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
static int FS_SortZip( const void *a, const void *b )
|
|
|
|
{
|
2024-06-27 06:31:09 +02:00
|
|
|
return Q_stricmp(((zipfile_t *)a )->name, ((zipfile_t *)b )->name );
|
2022-07-01 18:37:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
FS_LoadZip
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
static zip_t *FS_LoadZip( const char *zipfile, int *error )
|
|
|
|
{
|
|
|
|
int numpackfiles = 0, i;
|
|
|
|
zip_cdf_header_t header_cdf;
|
|
|
|
zip_header_eocd_t header_eocd;
|
|
|
|
uint32_t signature;
|
2024-06-27 06:31:09 +02:00
|
|
|
fs_offset_t filepos = 0;
|
2022-07-01 18:37:21 +02:00
|
|
|
zipfile_t *info = NULL;
|
|
|
|
char filename_buffer[MAX_SYSPATH];
|
|
|
|
zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( *zip ));
|
|
|
|
fs_size_t c;
|
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
// TODO: use FS_Open to allow PK3 to be included into other archives
|
|
|
|
// Currently, it doesn't work with rodir due to FS_FindFile logic
|
|
|
|
zip->handle = FS_SysOpen( zipfile, "rb" );
|
2022-07-01 18:37:21 +02:00
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
if( zip->handle == NULL )
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
|
|
|
Con_Reportf( S_ERROR "%s couldn't open\n", zipfile );
|
|
|
|
|
|
|
|
if( error )
|
|
|
|
*error = ZIP_LOAD_COULDNT_OPEN;
|
|
|
|
|
|
|
|
FS_CloseZIP( zip );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
if( zip->handle->real_length > UINT32_MAX )
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
|
|
|
Con_Reportf( S_ERROR "%s bigger than 4GB.\n", zipfile );
|
|
|
|
|
|
|
|
if( error )
|
|
|
|
*error = ZIP_LOAD_COULDNT_OPEN;
|
|
|
|
|
|
|
|
FS_CloseZIP( zip );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
FS_Seek( zip->handle, 0, SEEK_SET );
|
2022-07-01 18:37:21 +02:00
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
c = FS_Read( zip->handle, &signature, sizeof( signature ));
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
if( c != sizeof( signature ) || signature == ZIP_HEADER_EOCD )
|
|
|
|
{
|
|
|
|
Con_Reportf( S_WARN "%s has no files. Ignored.\n", zipfile );
|
|
|
|
|
|
|
|
if( error )
|
|
|
|
*error = ZIP_LOAD_NO_FILES;
|
|
|
|
|
|
|
|
FS_CloseZIP( zip );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( signature != ZIP_HEADER_LF )
|
|
|
|
{
|
|
|
|
Con_Reportf( S_ERROR "%s is not a zip file. Ignored.\n", zipfile );
|
|
|
|
|
|
|
|
if( error )
|
|
|
|
*error = ZIP_LOAD_BAD_HEADER;
|
|
|
|
|
|
|
|
FS_CloseZIP( zip );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find oecd
|
2024-06-27 06:31:09 +02:00
|
|
|
filepos = zip->handle->real_length;
|
2022-07-01 18:37:21 +02:00
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
while( filepos > 0 )
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
2024-06-27 06:31:09 +02:00
|
|
|
FS_Seek( zip->handle, filepos, SEEK_SET );
|
|
|
|
c = FS_Read( zip->handle, &signature, sizeof( signature ));
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
if( c == sizeof( signature ) && signature == ZIP_HEADER_EOCD )
|
|
|
|
break;
|
|
|
|
|
|
|
|
filepos -= sizeof( char ); // step back one byte
|
|
|
|
}
|
|
|
|
|
|
|
|
if( ZIP_HEADER_EOCD != signature )
|
|
|
|
{
|
|
|
|
Con_Reportf( S_ERROR "cannot find EOCD in %s. Zip file corrupted.\n", zipfile );
|
|
|
|
|
|
|
|
if( error )
|
|
|
|
*error = ZIP_LOAD_BAD_HEADER;
|
|
|
|
|
|
|
|
FS_CloseZIP( zip );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
c = FS_Read( zip->handle, &header_eocd, sizeof( header_eocd ));
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
if( c != sizeof( header_eocd ))
|
|
|
|
{
|
|
|
|
Con_Reportf( S_ERROR "invalid EOCD header in %s. Zip file corrupted.\n", zipfile );
|
|
|
|
|
|
|
|
if( error )
|
|
|
|
*error = ZIP_LOAD_BAD_HEADER;
|
|
|
|
|
|
|
|
FS_CloseZIP( zip );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
if( header_eocd.total_central_directory_record == 0 ) // refuse to load empty ZIP archives
|
|
|
|
{
|
|
|
|
Con_Reportf( S_WARN "%s has no files (total records is zero). Ignored.\n", zipfile );
|
|
|
|
|
|
|
|
if( error )
|
|
|
|
*error = ZIP_LOAD_NO_FILES;
|
|
|
|
|
|
|
|
FS_CloseZIP( zip );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-07-01 18:37:21 +02:00
|
|
|
// Move to CDF start
|
2024-06-27 06:31:09 +02:00
|
|
|
FS_Seek( zip->handle, header_eocd.central_directory_offset, SEEK_SET );
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
// Calc count of files in archive
|
2024-06-27 06:31:09 +02:00
|
|
|
zip = (zip_t *)Mem_Realloc( fs_mempool, zip, sizeof( *zip ) + sizeof( *info ) * ( header_eocd.total_central_directory_record - 1 ));
|
|
|
|
info = zip->files;
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
for( i = 0; i < header_eocd.total_central_directory_record; i++ )
|
|
|
|
{
|
2024-06-27 06:31:09 +02:00
|
|
|
c = FS_Read( zip->handle, &header_cdf, sizeof( header_cdf ));
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
if( c != sizeof( header_cdf ) || header_cdf.signature != ZIP_HEADER_CDF )
|
|
|
|
{
|
|
|
|
Con_Reportf( S_ERROR "CDF signature mismatch in %s. Zip file corrupted.\n", zipfile );
|
|
|
|
|
|
|
|
if( error )
|
|
|
|
*error = ZIP_LOAD_BAD_HEADER;
|
|
|
|
|
|
|
|
Mem_Free( info );
|
|
|
|
FS_CloseZIP( zip );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
if( header_cdf.uncompressed_size && header_cdf.filename_len && header_cdf.filename_len < sizeof( filename_buffer ))
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
2024-06-27 06:31:09 +02:00
|
|
|
memset( &filename_buffer, '\0', sizeof( filename_buffer ));
|
|
|
|
c = FS_Read( zip->handle, &filename_buffer, header_cdf.filename_len );
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
if( c != header_cdf.filename_len )
|
|
|
|
{
|
|
|
|
Con_Reportf( S_ERROR "filename length mismatch in %s. Zip file corrupted.\n", zipfile );
|
|
|
|
|
|
|
|
if( error )
|
|
|
|
*error = ZIP_LOAD_CORRUPTED;
|
|
|
|
|
|
|
|
Mem_Free( info );
|
|
|
|
FS_CloseZIP( zip );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
Q_strncpy( info[numpackfiles].name, filename_buffer, sizeof( info[numpackfiles].name ));
|
2022-07-01 18:37:21 +02:00
|
|
|
info[numpackfiles].size = header_cdf.uncompressed_size;
|
|
|
|
info[numpackfiles].compressed_size = header_cdf.compressed_size;
|
|
|
|
info[numpackfiles].offset = header_cdf.local_header_offset;
|
|
|
|
numpackfiles++;
|
|
|
|
}
|
|
|
|
else
|
2024-06-27 06:31:09 +02:00
|
|
|
FS_Seek( zip->handle, header_cdf.filename_len, SEEK_CUR );
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
if( header_cdf.extrafield_len )
|
2024-06-27 06:31:09 +02:00
|
|
|
FS_Seek( zip->handle, header_cdf.extrafield_len, SEEK_CUR );
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
if( header_cdf.file_commentary_len )
|
2024-06-27 06:31:09 +02:00
|
|
|
FS_Seek( zip->handle, header_cdf.file_commentary_len, SEEK_CUR );
|
|
|
|
}
|
|
|
|
|
|
|
|
// refuse to load empty files again
|
|
|
|
if( numpackfiles == 0 )
|
|
|
|
{
|
|
|
|
Con_Reportf( S_WARN "%s has no files (recalculated). Ignored.\n", zipfile );
|
|
|
|
|
|
|
|
if( error )
|
|
|
|
*error = ZIP_LOAD_NO_FILES;
|
|
|
|
|
|
|
|
FS_CloseZIP( zip );
|
|
|
|
return NULL;
|
2022-07-01 18:37:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// recalculate offsets
|
|
|
|
for( i = 0; i < numpackfiles; i++ )
|
|
|
|
{
|
|
|
|
zip_header_t header;
|
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
FS_Seek( zip->handle, info[i].offset, SEEK_SET );
|
|
|
|
c = FS_Read( zip->handle, &header, sizeof( header ) );
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
if( c != sizeof( header ))
|
|
|
|
{
|
|
|
|
Con_Reportf( S_ERROR "header length mismatch in %s. Zip file corrupted.\n", zipfile );
|
|
|
|
|
|
|
|
if( error )
|
|
|
|
*error = ZIP_LOAD_CORRUPTED;
|
|
|
|
|
|
|
|
Mem_Free( info );
|
|
|
|
FS_CloseZIP( zip );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
info[i].flags = header.compression_flags;
|
|
|
|
info[i].offset = info[i].offset + header.filename_len + header.extrafield_len + sizeof( header );
|
|
|
|
}
|
|
|
|
|
|
|
|
zip->numfiles = numpackfiles;
|
|
|
|
qsort( zip->files, zip->numfiles, sizeof( *zip->files ), FS_SortZip );
|
|
|
|
|
|
|
|
if( error )
|
|
|
|
*error = ZIP_LOAD_OK;
|
|
|
|
|
|
|
|
return zip;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========
|
|
|
|
FS_OpenZipFile
|
|
|
|
|
|
|
|
Open a packed file using its package file descriptor
|
|
|
|
===========
|
|
|
|
*/
|
2023-05-27 19:49:06 +02:00
|
|
|
static file_t *FS_OpenFile_ZIP( searchpath_t *search, const char *filename, const char *mode, int pack_ind )
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
2024-06-27 06:31:09 +02:00
|
|
|
zipfile_t *pfile = &search->zip->files[pack_ind];
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
// compressed files handled in Zip_LoadFile
|
|
|
|
if( pfile->flags != ZIP_COMPRESSION_NO_COMPRESSION )
|
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Printf( S_ERROR "%s: can't open compressed file %s\n", __func__, pfile->name );
|
2022-07-01 18:37:21 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
return FS_OpenHandle( search, search->zip->handle->handle, pfile->offset, pfile->size );
|
2022-07-01 18:37:21 +02:00
|
|
|
}
|
|
|
|
|
2022-12-14 22:59:52 +01:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
FS_LoadZIPFile
|
|
|
|
|
|
|
|
===========
|
|
|
|
*/
|
2024-02-07 03:05:22 +01:00
|
|
|
static byte *FS_LoadZIPFile( searchpath_t *search, const char *path, int pack_ind, fs_offset_t *sizeptr, void *( *pfnAlloc )( size_t ), void ( *pfnFree )( void * ))
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
2023-05-27 19:48:48 +02:00
|
|
|
zipfile_t *file;
|
2022-07-01 18:37:21 +02:00
|
|
|
byte *compressed_buffer = NULL, *decompressed_buffer = NULL;
|
|
|
|
int zlib_result = 0;
|
|
|
|
z_stream decompress_stream;
|
|
|
|
size_t c;
|
2023-12-04 01:01:11 +01:00
|
|
|
#ifdef ENABLE_CRC_CHECK
|
|
|
|
dword test_crc, final_crc;
|
|
|
|
#endif // ENABLE_CRC_CHECK
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
if( sizeptr ) *sizeptr = 0;
|
|
|
|
|
2023-05-27 19:48:48 +02:00
|
|
|
file = &search->zip->files[pack_ind];
|
2022-07-01 18:37:21 +02:00
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
if( FS_Seek( search->zip->handle, file->offset, SEEK_SET ) == -1 )
|
2022-07-01 18:37:21 +02:00
|
|
|
return NULL;
|
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
/*if( FS_Read( search->zip->handle, &header, sizeof( header )) < 0 )
|
2022-07-01 18:37:21 +02:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if( header.signature != ZIP_HEADER_LF )
|
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Reportf( S_ERROR "%s: %s signature error\n", __func__, file->name );
|
2022-07-01 18:37:21 +02:00
|
|
|
return NULL;
|
|
|
|
}*/
|
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
decompressed_buffer = (byte *)pfnAlloc( file->size + 1 );
|
2024-02-07 03:05:22 +01:00
|
|
|
if( unlikely( !decompressed_buffer ))
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Reportf( S_ERROR "%s: can't alloc %li bytes, no free memory\n", __func__, (long)file->size + 1 );
|
2024-02-07 03:05:22 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
decompressed_buffer[file->size] = '\0';
|
2022-07-01 18:37:21 +02:00
|
|
|
|
2024-02-07 03:05:22 +01:00
|
|
|
if( file->flags == ZIP_COMPRESSION_NO_COMPRESSION )
|
|
|
|
{
|
2024-06-27 06:31:09 +02:00
|
|
|
c = FS_Read( search->zip->handle, decompressed_buffer, file->size );
|
2022-07-01 18:37:21 +02:00
|
|
|
if( c != file->size )
|
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Reportf( S_ERROR "%s: %s size doesn't match\n", __func__, file->name );
|
2022-07-01 18:37:21 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2023-12-04 01:01:11 +01:00
|
|
|
#ifdef ENABLE_CRC_CHECK
|
2022-07-01 18:37:21 +02:00
|
|
|
CRC32_Init( &test_crc );
|
|
|
|
CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size );
|
|
|
|
|
|
|
|
final_crc = CRC32_Final( test_crc );
|
|
|
|
|
|
|
|
if( final_crc != file->crc32 )
|
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Reportf( S_ERROR "%s: %s file crc32 mismatch\n", __func__, file->name );
|
2024-02-07 03:05:22 +01:00
|
|
|
pfnFree( decompressed_buffer );
|
2022-07-01 18:37:21 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
2023-12-04 01:01:11 +01:00
|
|
|
#endif // ENABLE_CRC_CHECK
|
|
|
|
|
2022-07-01 18:37:21 +02:00
|
|
|
if( sizeptr ) *sizeptr = file->size;
|
|
|
|
|
|
|
|
return decompressed_buffer;
|
|
|
|
}
|
|
|
|
else if( file->flags == ZIP_COMPRESSION_DEFLATED )
|
|
|
|
{
|
2024-06-27 06:31:09 +02:00
|
|
|
compressed_buffer = (byte *)Mem_Malloc( fs_mempool, file->compressed_size + 1 );
|
2022-07-01 18:37:21 +02:00
|
|
|
|
2024-06-27 06:31:09 +02:00
|
|
|
c = FS_Read( search->zip->handle, compressed_buffer, file->compressed_size );
|
2022-07-01 18:37:21 +02:00
|
|
|
if( c != file->compressed_size )
|
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Reportf( S_ERROR "%s: %s compressed size doesn't match\n", __func__, file->name );
|
2022-07-01 18:37:21 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset( &decompress_stream, 0, sizeof( decompress_stream ) );
|
|
|
|
|
|
|
|
decompress_stream.total_in = decompress_stream.avail_in = file->compressed_size;
|
|
|
|
decompress_stream.next_in = (Bytef *)compressed_buffer;
|
|
|
|
decompress_stream.total_out = decompress_stream.avail_out = file->size;
|
|
|
|
decompress_stream.next_out = (Bytef *)decompressed_buffer;
|
|
|
|
|
|
|
|
decompress_stream.zalloc = Z_NULL;
|
|
|
|
decompress_stream.zfree = Z_NULL;
|
|
|
|
decompress_stream.opaque = Z_NULL;
|
|
|
|
|
|
|
|
if( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK )
|
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Printf( S_ERROR "%s: inflateInit2 failed\n", __func__ );
|
2022-07-01 18:37:21 +02:00
|
|
|
Mem_Free( compressed_buffer );
|
|
|
|
Mem_Free( decompressed_buffer );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
zlib_result = inflate( &decompress_stream, Z_NO_FLUSH );
|
|
|
|
inflateEnd( &decompress_stream );
|
|
|
|
|
|
|
|
if( zlib_result == Z_OK || zlib_result == Z_STREAM_END )
|
|
|
|
{
|
|
|
|
Mem_Free( compressed_buffer ); // finaly free compressed buffer
|
2023-12-04 01:01:11 +01:00
|
|
|
#if ENABLE_CRC_CHECK
|
2022-07-01 18:37:21 +02:00
|
|
|
CRC32_Init( &test_crc );
|
|
|
|
CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size );
|
|
|
|
|
|
|
|
final_crc = CRC32_Final( test_crc );
|
|
|
|
|
|
|
|
if( final_crc != file->crc32 )
|
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Reportf( S_ERROR "%s: %s file crc32 mismatch\n", __func__, file->name );
|
2024-02-07 03:05:22 +01:00
|
|
|
pfnFree( decompressed_buffer );
|
2022-07-01 18:37:21 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if( sizeptr ) *sizeptr = file->size;
|
|
|
|
|
|
|
|
return decompressed_buffer;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Reportf( S_ERROR "%s: %s: error while file decompressing. Zlib return code %d.\n", __func__, file->name, zlib_result );
|
2022-07-01 18:37:21 +02:00
|
|
|
Mem_Free( compressed_buffer );
|
2024-02-07 03:05:22 +01:00
|
|
|
pfnFree( decompressed_buffer );
|
2022-07-01 18:37:21 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Reportf( S_ERROR "%s: %s: file compressed with unknown algorithm.\n", __func__, file->name );
|
2024-02-07 03:05:22 +01:00
|
|
|
pfnFree( decompressed_buffer );
|
2022-07-01 18:37:21 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-12-14 22:59:52 +01:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
FS_FileTime_ZIP
|
2022-07-01 18:37:21 +02:00
|
|
|
|
2022-12-14 22:59:52 +01:00
|
|
|
===========
|
|
|
|
*/
|
2023-05-27 19:49:06 +02:00
|
|
|
static int FS_FileTime_ZIP( searchpath_t *search, const char *filename )
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
2024-06-27 06:31:09 +02:00
|
|
|
return search->zip->handle->filetime;
|
2022-07-01 18:37:21 +02:00
|
|
|
}
|
|
|
|
|
2022-12-14 22:59:52 +01:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
FS_PrintInfo_ZIP
|
|
|
|
|
|
|
|
===========
|
|
|
|
*/
|
2023-05-27 19:49:06 +02:00
|
|
|
static void FS_PrintInfo_ZIP( searchpath_t *search, char *dst, size_t size )
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
2024-06-27 06:31:09 +02:00
|
|
|
if( search->zip->handle->searchpath )
|
|
|
|
Q_snprintf( dst, size, "%s (%i files)" S_CYAN " from %s" S_DEFAULT, search->filename, search->zip->numfiles, search->zip->handle->searchpath->filename );
|
|
|
|
else Q_snprintf( dst, size, "%s (%i files)", search->filename, search->zip->numfiles );
|
2022-07-01 18:37:21 +02:00
|
|
|
}
|
|
|
|
|
2022-12-14 22:59:52 +01:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
FS_FindFile_ZIP
|
|
|
|
|
|
|
|
===========
|
|
|
|
*/
|
2023-05-27 19:49:06 +02:00
|
|
|
static int FS_FindFile_ZIP( searchpath_t *search, const char *path, char *fixedname, size_t len )
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
|
|
|
int left, right, middle;
|
|
|
|
|
|
|
|
// look for the file (binary search)
|
|
|
|
left = 0;
|
2022-11-18 14:35:21 +01:00
|
|
|
right = search->zip->numfiles - 1;
|
2022-07-01 18:37:21 +02:00
|
|
|
while( left <= right )
|
|
|
|
{
|
|
|
|
int diff;
|
|
|
|
|
|
|
|
middle = (left + right) / 2;
|
2022-11-18 14:35:21 +01:00
|
|
|
diff = Q_stricmp( search->zip->files[middle].name, path );
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
// Found it
|
|
|
|
if( !diff )
|
2022-12-19 15:22:30 +01:00
|
|
|
{
|
|
|
|
if( fixedname )
|
|
|
|
Q_strncpy( fixedname, search->zip->files[middle].name, len );
|
2022-07-01 18:37:21 +02:00
|
|
|
return middle;
|
2022-12-19 15:22:30 +01:00
|
|
|
}
|
2022-07-01 18:37:21 +02:00
|
|
|
|
|
|
|
// if we're too far in the list
|
|
|
|
if( diff > 0 )
|
|
|
|
right = middle - 1;
|
|
|
|
else left = middle + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-12-14 22:59:52 +01:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
FS_Search_ZIP
|
|
|
|
|
|
|
|
===========
|
|
|
|
*/
|
2023-05-27 19:49:06 +02:00
|
|
|
static void FS_Search_ZIP( searchpath_t *search, stringlist_t *list, const char *pattern, int caseinsensitive )
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
|
|
|
string temp;
|
|
|
|
const char *slash, *backslash, *colon, *separator;
|
|
|
|
int j, i;
|
|
|
|
|
2022-11-18 14:35:21 +01:00
|
|
|
for( i = 0; i < search->zip->numfiles; i++ )
|
2022-07-01 18:37:21 +02:00
|
|
|
{
|
2022-11-18 14:35:21 +01:00
|
|
|
Q_strncpy( temp, search->zip->files[i].name, sizeof( temp ));
|
2022-07-01 18:37:21 +02:00
|
|
|
while( temp[0] )
|
|
|
|
{
|
|
|
|
if( matchpattern( temp, pattern, true ))
|
|
|
|
{
|
|
|
|
for( j = 0; j < list->numstrings; j++ )
|
|
|
|
{
|
|
|
|
if( !Q_strcmp( list->strings[j], temp ))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( j == list->numstrings )
|
|
|
|
stringlistappend( list, temp );
|
|
|
|
}
|
|
|
|
|
|
|
|
// strip off one path element at a time until empty
|
|
|
|
// this way directories are added to the listing if they match the pattern
|
|
|
|
slash = Q_strrchr( temp, '/' );
|
|
|
|
backslash = Q_strrchr( temp, '\\' );
|
|
|
|
colon = Q_strrchr( temp, ':' );
|
|
|
|
separator = temp;
|
|
|
|
if( separator < slash )
|
|
|
|
separator = slash;
|
|
|
|
if( separator < backslash )
|
|
|
|
separator = backslash;
|
|
|
|
if( separator < colon )
|
|
|
|
separator = colon;
|
|
|
|
*((char *)separator) = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-14 22:59:52 +01:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
FS_AddZip_Fullpath
|
|
|
|
|
|
|
|
===========
|
|
|
|
*/
|
2023-06-08 21:14:12 +02:00
|
|
|
searchpath_t *FS_AddZip_Fullpath( const char *zipfile, int flags )
|
2022-12-14 22:59:52 +01:00
|
|
|
{
|
2023-06-08 21:14:12 +02:00
|
|
|
searchpath_t *search;
|
|
|
|
zip_t *zip;
|
2023-12-04 01:00:29 +01:00
|
|
|
int errorcode = ZIP_LOAD_COULDNT_OPEN;
|
2022-12-14 22:59:52 +01:00
|
|
|
|
2023-06-08 21:14:12 +02:00
|
|
|
zip = FS_LoadZip( zipfile, &errorcode );
|
2022-12-14 22:59:52 +01:00
|
|
|
|
2023-06-08 21:14:12 +02:00
|
|
|
if( !zip )
|
2022-12-14 22:59:52 +01:00
|
|
|
{
|
|
|
|
if( errorcode != ZIP_LOAD_NO_FILES )
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Reportf( S_ERROR "%s: unable to load zip \"%s\"\n", __func__, zipfile );
|
2023-06-08 19:02:46 +02:00
|
|
|
return NULL;
|
2022-12-14 22:59:52 +01:00
|
|
|
}
|
2023-06-08 21:14:12 +02:00
|
|
|
|
|
|
|
search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ) );
|
|
|
|
Q_strncpy( search->filename, zipfile, sizeof( search->filename ));
|
|
|
|
search->zip = zip;
|
|
|
|
search->type = SEARCHPATH_ZIP;
|
|
|
|
search->flags = flags;
|
|
|
|
|
|
|
|
search->pfnPrintInfo = FS_PrintInfo_ZIP;
|
|
|
|
search->pfnClose = FS_Close_ZIP;
|
|
|
|
search->pfnOpenFile = FS_OpenFile_ZIP;
|
|
|
|
search->pfnFileTime = FS_FileTime_ZIP;
|
|
|
|
search->pfnFindFile = FS_FindFile_ZIP;
|
|
|
|
search->pfnSearch = FS_Search_ZIP;
|
|
|
|
search->pfnLoadFile = FS_LoadZIPFile;
|
|
|
|
|
2024-03-19 18:42:06 +01:00
|
|
|
Con_Reportf( "Adding ZIP: %s (%i files)\n", zipfile, zip->numfiles );
|
2023-06-08 21:14:12 +02:00
|
|
|
return search;
|
2022-12-14 22:59:52 +01:00
|
|
|
}
|
|
|
|
|