/* hpak.c - custom user package to send other clients Copyright (C) 2010 Uncle Mike 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 "common.h" #include "filesystem.h" #define HPAK_MAX_ENTRIES 0x8000 #define HPAK_MIN_SIZE (1 * 1024) #define HPAK_MAX_SIZE (128 * 1024) typedef struct hash_pack_queue_s { char *name; resource_t resource; size_t size; void *data; struct hash_pack_queue_s *next; } hash_pack_queue_t; convar_t *hpk_maxsize; hash_pack_queue_t *gp_hpak_queue = NULL; hpak_header_t hash_pack_header; hpak_info_t hash_pack_info; const char *HPAK_TypeFromIndex( int type ) { switch( type ) { case t_sound: return "decal"; case t_skin: return "skin"; case t_model: return "model"; case t_decal: return "decal"; case t_generic: return "generic"; case t_eventscript: return "event"; case t_world: return "map"; } return "?"; } static void HPAK_AddToQueue( const char *name, resource_t *pResource, void *data, file_t *f ) { hash_pack_queue_t *p; p = Z_Malloc( sizeof( hash_pack_queue_t )); p->name = copystring( name ); p->resource = *pResource; p->size = pResource->nDownloadSize; p->data = Z_Malloc( p->size ); if( data != NULL ) memcpy( p->data, data, p->size ); else if( f != NULL ) FS_Read( f, p->data, p->size ); else Host_Error( "HPAK_AddToQueue: data == NULL.\n" ); p->next = gp_hpak_queue; gp_hpak_queue = p; } void HPAK_FlushHostQueue( void ) { hash_pack_queue_t *p; for( p = gp_hpak_queue; p != NULL; p = gp_hpak_queue ) { gp_hpak_queue = p->next; HPAK_AddLump( false, p->name, &p->resource, p->data, NULL ); freestring( p->name ); Mem_Free( p->data ); Mem_Free( p ); } gp_hpak_queue = NULL; } void HPAK_CreatePak( const char *filename, resource_t *pResource, byte *pData, file_t *fin ) { int filelocation; string pakname; byte md5[16]; file_t *fout; MD5Context_t ctx; if( !COM_CheckString( filename )) return; if(( fin != NULL && pData != NULL ) || ( fin == NULL && pData == NULL )) return; Q_strncpy( pakname, filename, sizeof( pakname )); COM_ReplaceExtension( pakname, ".hpk" ); Con_Printf( "creating HPAK %s.\n", pakname ); fout = FS_Open( pakname, "wb", false ); if( !fout ) { Con_DPrintf( S_ERROR "HPAK_CreatePak: can't write %s.\n", pakname ); return; } // let's hash it. memset( &ctx, 0, sizeof( MD5Context_t )); MD5Init( &ctx ); if( pData == NULL ) { byte *temp; // there are better ways filelocation = FS_Tell( fin ); temp = Z_Malloc( pResource->nDownloadSize ); FS_Read( fin, temp, pResource->nDownloadSize ); FS_Seek( fin, filelocation, SEEK_SET ); MD5Update( &ctx, temp, pResource->nDownloadSize ); Mem_Free( temp ); } else { MD5Update( &ctx, pData, pResource->nDownloadSize ); } MD5Final( md5, &ctx ); if( memcmp( md5, pResource->rgucMD5_hash, 16 )) { Con_DPrintf( S_ERROR "HPAK_CreatePak: bad checksum for %s. Ignored\n", pakname ); return; } hash_pack_header.ident = IDHPAKHEADER; hash_pack_header.version = IDHPAK_VERSION; hash_pack_header.infotableofs = 0; FS_Write( fout, &hash_pack_header, sizeof( hash_pack_header )); hash_pack_info.count = 1; hash_pack_info.entries = Z_Malloc( sizeof( hpak_lump_t )); hash_pack_info.entries[0].resource = *pResource; hash_pack_info.entries[0].filepos = FS_Tell( fout ); hash_pack_info.entries[0].disksize = pResource->nDownloadSize; if( pData == NULL ) { FS_FileCopy( fout, fin, hash_pack_info.entries[0].disksize ); } else { FS_Write( fout, pData, hash_pack_info.entries[0].disksize ); } filelocation = FS_Tell( fout ); FS_Write( fout, &hash_pack_info.count, sizeof( hash_pack_info.count )); FS_Write( fout, &hash_pack_info.entries[0], sizeof( hpak_lump_t )); if( hash_pack_info.entries ) Mem_Free( hash_pack_info.entries ); memset( &hash_pack_info, 0, sizeof( hpak_info_t )); hash_pack_header.infotableofs = filelocation; FS_Seek( fout, 0, SEEK_SET ); FS_Write( fout, &hash_pack_header, sizeof( hpak_header_t )); FS_Close( fout ); } static qboolean HPAK_FindResource( hpak_info_t *hpk, byte *hash, resource_t *pResource ) { int i; for( i = 0; i < hpk->count; i++ ) { if( !memcmp( hpk->entries[i].resource.rgucMD5_hash, hash, 16 )) { if( pResource ) *pResource = hpk->entries[i].resource; return true; } } return false; } void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, byte *pData, file_t *pFile ) { int i, j, position, length; hpak_lump_t *pCurrentEntry = NULL; string srcname, dstname; hpak_info_t srcpak, dstpak; file_t *file_src; file_t *file_dst; byte md5[16]; MD5Context_t ctx; if( pData == NULL && pFile == NULL ) return; if( pResource->nDownloadSize < HPAK_MIN_SIZE || pResource->nDownloadSize > HPAK_MAX_SIZE ) { Con_Printf( S_ERROR "%s: invalid size %s\n", name, Q_pretifymem( pResource->nDownloadSize, 2 )); return; } // hash it memset( &ctx, 0, sizeof( MD5Context_t )); MD5Init( &ctx ); if( pData == NULL ) { byte *temp; // there are better ways position = FS_Tell( pFile ); temp = Z_Malloc( pResource->nDownloadSize ); FS_Read( pFile, temp, pResource->nDownloadSize ); FS_Seek( pFile, position, SEEK_SET ); MD5Update( &ctx, temp, pResource->nDownloadSize ); Mem_Free( temp ); } else { MD5Update( &ctx, pData, pResource->nDownloadSize ); } MD5Final( md5, &ctx ); if( memcmp( md5, pResource->rgucMD5_hash, 16 )) { Con_DPrintf( S_ERROR "HPAK_AddLump: bad checksum for %s. Ignored\n", pResource->szFileName ); return; } if( bUseQueue ) { HPAK_AddToQueue( name, pResource, pData, pFile ); return; } Q_strncpy( srcname, name, sizeof( srcname )); COM_ReplaceExtension( srcname, ".hpk" ); file_src = FS_Open( srcname, "rb", false ); if( !file_src ) { // just create new pack HPAK_CreatePak( name, pResource, pData, pFile ); return; } Q_strncpy( dstname, srcname, sizeof( dstname )); COM_ReplaceExtension( dstname, ".hp2" ); file_dst = FS_Open( dstname, "wb", false ); if( !file_dst ) { Con_DPrintf( S_ERROR "HPAK_AddLump: couldn't open %s.\n", srcname ); FS_Close( file_src ); return; } // load headers FS_Read( file_src, &hash_pack_header, sizeof( hpak_header_t )); if( hash_pack_header.version != IDHPAK_VERSION ) { // we don't check the HPAK bit for some reason. Con_DPrintf( S_ERROR "HPAK_AddLump: %s does not have a valid header.\n", srcname ); FS_Close( file_src ); FS_Close( file_dst ); return; } length = FS_FileLength( file_src ); FS_Seek( file_src, 0, SEEK_SET ); // rewind to start of file FS_FileCopy( file_dst, file_src, length ); FS_Seek( file_src, hash_pack_header.infotableofs, SEEK_SET ); FS_Read( file_src, &srcpak.count, sizeof( srcpak.count )); if( srcpak.count < 1 || srcpak.count > HPAK_MAX_ENTRIES ) { Con_DPrintf( S_ERROR "HPAK_AddLump: %s contain too many lumps.\n", srcname ); FS_Close( file_src ); FS_Close( file_dst ); return; } // load the data srcpak.entries = Z_Malloc( sizeof( hpak_lump_t ) * srcpak.count ); FS_Read( file_src, srcpak.entries, sizeof( hpak_lump_t ) * srcpak.count ); // check if already exists if( HPAK_FindResource( &srcpak, pResource->rgucMD5_hash, NULL )) { Z_Free( srcpak.entries ); FS_Close( file_src ); FS_Close( file_dst ); FS_Delete( dstname ); return; } // make a new container dstpak.count = srcpak.count + 1; dstpak.entries = Z_Malloc( sizeof( hpak_lump_t ) * dstpak.count ); memcpy( dstpak.entries, srcpak.entries, srcpak.count ); for( i = 0; i < srcpak.count; i++ ) { if( memcmp( md5, srcpak.entries[i].resource.rgucMD5_hash, 16 )) { pCurrentEntry = &dstpak.entries[i]; for( j = i; j < srcpak.count; j++ ) dstpak.entries[j + 1] = srcpak.entries[j]; } } if( !pCurrentEntry ) pCurrentEntry = &dstpak.entries[dstpak.count-1]; memset( pCurrentEntry, 0, sizeof( hpak_lump_t )); FS_Seek( file_dst, hash_pack_header.infotableofs, SEEK_SET ); pCurrentEntry->resource = *pResource; pCurrentEntry->filepos = FS_Tell( file_dst ); pCurrentEntry->disksize = pResource->nDownloadSize; if( !pData ) FS_FileCopy( file_dst, file_src, pCurrentEntry->disksize ); else FS_Write( file_dst, pData, pCurrentEntry->disksize ); hash_pack_header.infotableofs = FS_Tell( file_dst ); FS_Write( file_dst, &dstpak.count, sizeof( dstpak.count )); for( i = 0; i < dstpak.count; i++ ) { FS_Write( file_dst, &dstpak.entries[i], sizeof( hpak_lump_t )); } // finalize if( srcpak.entries ) Mem_Free( srcpak.entries ); if( dstpak.entries ) Mem_Free( dstpak.entries ); FS_Seek( file_dst, 0, SEEK_SET ); FS_Write( file_dst, &hash_pack_header, sizeof( hpak_header_t )); FS_Close( file_src ); FS_Close( file_dst ); FS_Delete( srcname ); FS_Rename( dstname, srcname ); } static qboolean HPAK_Validate( const char *filename, qboolean quiet ) { file_t *f; hpak_lump_t *dataDir; hpak_header_t hdr; byte *dataPak; int i, num_lumps; MD5Context_t MD5_Hash; string pakname; resource_t *pRes; byte md5[16]; if( quiet ) HPAK_FlushHostQueue(); // not an error - just flush queue if( !COM_CheckString( filename ) ) return true; Q_strncpy( pakname, filename, sizeof( pakname )); COM_ReplaceExtension( pakname, ".hpk" ); f = FS_Open( pakname, "rb", false ); if( !f ) { Con_DPrintf( S_ERROR "Couldn't find %s.\n", pakname ); return true; } if( !quiet ) Con_Printf( "Validating %s\n", pakname ); FS_Read( f, &hdr, sizeof( hdr )); if( hdr.ident != IDHPAKHEADER || hdr.version != IDHPAK_VERSION ) { Con_DPrintf( S_ERROR "HPAK_ValidatePak: %s does not have a valid HPAK header.\n", pakname ); FS_Close( f ); return false; } FS_Seek( f, hdr.infotableofs, SEEK_SET ); FS_Read( f, &num_lumps, sizeof( num_lumps )); if( num_lumps < 1 || num_lumps > MAX_FILES_IN_WAD ) { Con_DPrintf( S_ERROR "HPAK_ValidatePak: %s has too many lumps %u.\n", pakname, num_lumps ); FS_Close( f ); return false; } if( !quiet ) Con_Printf( "# of Entries: %i\n", num_lumps ); dataDir = Z_Malloc( sizeof( hpak_lump_t ) * num_lumps ); FS_Read( f, dataDir, sizeof( hpak_lump_t ) * num_lumps ); if( !quiet ) Con_Printf( "# Type Size FileName : MD5 Hash\n" ); for( i = 0; i < num_lumps; i++ ) { if( dataDir[i].disksize < 1 || dataDir[i].disksize > 131071 ) { // odd max size Con_DPrintf( S_ERROR "HPAK_ValidatePak: lump %i has invalid size %s\n", i, Q_pretifymem( dataDir[i].disksize, 2 )); Mem_Free( dataDir ); FS_Close(f); return false; } dataPak = Z_Malloc( dataDir[i].disksize ); FS_Seek( f, dataDir[i].filepos, SEEK_SET ); FS_Read( f, dataPak, dataDir[i].disksize ); memset( &MD5_Hash, 0, sizeof( MD5Context_t )); MD5Init( &MD5_Hash ); MD5Update( &MD5_Hash, dataPak, dataDir[i].disksize ); MD5Final( md5, &MD5_Hash ); pRes = &dataDir[i].resource; Con_Printf( "%i: %s %s %s: ", i, HPAK_TypeFromIndex( pRes->type ), Q_pretifymem( pRes->nDownloadSize, 2 ), pRes->szFileName ); if( memcmp( md5, pRes->rgucMD5_hash, 0x10 )) { if( quiet ) { Con_DPrintf( S_ERROR "HPAK_ValidatePak: %s has invalid checksum.\n", pakname ); Mem_Free( dataPak ); Mem_Free( dataDir ); FS_Close( f ); return false; } else Con_DPrintf( S_ERROR "failed\n" ); } else { if( !quiet ) Con_Printf( "OK\n" ); } // at this point, it's passed our checks. Mem_Free( dataPak ); } Mem_Free( dataDir ); FS_Close( f ); return true; } void HPAK_ValidatePak( const char *filename ) { HPAK_Validate( filename, true ); } void HPAK_CheckIntegrity( const char *filename ) { string pakname; if( !COM_CheckString( filename ) ) return; Q_strncpy( pakname, filename, sizeof( pakname )); COM_ReplaceExtension( pakname, ".hpk" ); HPAK_ValidatePak( pakname ); } void HPAK_CheckSize( const char *filename ) { string pakname; int maxsize; maxsize = hpk_maxsize->value; if( maxsize <= 0 ) return; if( !COM_CheckString( filename ) ) return; Q_strncpy( pakname, filename, sizeof( pakname )); COM_ReplaceExtension( pakname, ".hpk" ); if( FS_FileSize( pakname, false ) > ( maxsize * 1000000 )) { Con_Printf( "Server: Size of %s > %f MB, deleting.\n", filename, hpk_maxsize->value ); Log_Printf( "Server: Size of %s > %f MB, deleting.\n", filename, hpk_maxsize->value ); FS_Delete( filename ); } } qboolean HPAK_ResourceForHash( const char *filename, byte *hash, resource_t *pResource ) { hpak_info_t directory; hpak_header_t header; string pakname; qboolean bFound; file_t *f; hash_pack_queue_t *p; if( !COM_CheckString( filename )) return false; for( p = gp_hpak_queue; p != NULL; p = p->next ) { if( !Q_stricmp( p->name, filename ) && !memcmp( p->resource.rgucMD5_hash, hash, 16 )) { if( pResource != NULL ) *pResource = p->resource; return true; } } Q_strncpy( pakname, filename, sizeof( pakname )); COM_ReplaceExtension( pakname, ".hpk" ); f = FS_Open( pakname, "rb", false ); if( !f ) return false; FS_Read( f, &header, sizeof( header )); if( header.ident != IDHPAKHEADER ) { FS_Close( f ); return false; } if( header.version != IDHPAK_VERSION ) { FS_Close( f ); return false; } FS_Seek( f, header.infotableofs, SEEK_SET ); FS_Read( f, &directory.count, sizeof( directory.count )); if( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES ) { FS_Close( f ); return false; } directory.entries = Z_Malloc( sizeof( hpak_lump_t ) * directory.count ); FS_Read( f, directory.entries, sizeof( hpak_lump_t ) * directory.count ); bFound = HPAK_FindResource( &directory, hash, pResource ); Mem_Free( directory.entries ); FS_Close( f ); return bFound; } static qboolean HPAK_ResourceForIndex( const char *filename, int index, resource_t *pResource ) { hpak_header_t header; hpak_info_t directory; string pakname; file_t *f; if( !COM_CheckString( filename ) ) return false; Q_strncpy( pakname, filename, sizeof( pakname )); COM_ReplaceExtension( pakname, ".hpk" ); f = FS_Open( pakname, "rb", false ); if( !f ) { Con_DPrintf( S_ERROR "couldn't open %s.\n", pakname ); return false; } FS_Read( f, &header, sizeof( header )); if( header.ident != IDHPAKHEADER ) { Con_DPrintf( S_ERROR "%s is not an HPAK file\n", pakname ); FS_Close( f ); return false; } if( header.version != IDHPAK_VERSION ) { Con_DPrintf( S_ERROR "%s has invalid version (%i should be %i).\n", pakname, header.version, IDHPAK_VERSION ); FS_Close( f ); return false; } FS_Seek( f, header.infotableofs, SEEK_SET ); FS_Read( f, &directory.count, sizeof( directory.count )); if( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES ) { Con_DPrintf( S_ERROR "%s has too many lumps %u.\n", pakname, directory.count ); FS_Close( f ); return false; } if( index < 1 || index > directory.count ) { Con_DPrintf( S_ERROR "%s, lump with index %i doesn't exist.\n", pakname, index ); FS_Close( f ); return false; } directory.entries = Z_Malloc( sizeof( hpak_lump_t ) * directory.count ); FS_Read( f, directory.entries, sizeof( hpak_lump_t ) * directory.count ); *pResource = directory.entries[index-1].resource; Z_Free( directory.entries ); FS_Close( f ); return true; } qboolean HPAK_GetDataPointer( const char *filename, resource_t *pResource, byte **buffer, int *bufsize ) { byte *tmpbuf; string pakname; hpak_header_t header; hpak_info_t directory; hpak_lump_t *entry; hash_pack_queue_t *p; file_t *f; int i; if( !COM_CheckString( filename )) return false; if( buffer ) *buffer = NULL; if( bufsize ) *bufsize = 0; for( p = gp_hpak_queue; p != NULL; p = p->next ) { if( !Q_stricmp(p->name, filename ) && !memcmp( p->resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 )) { if( buffer ) { tmpbuf = Z_Malloc( p->size ); memcpy( tmpbuf, p->data, p->size ); *buffer = tmpbuf; } if( bufsize ) *bufsize = p->size; return true; } } Q_strncpy( pakname, filename, sizeof( pakname )); COM_ReplaceExtension( pakname, ".hpk" ); f = FS_Open( pakname, "rb", false ); if( !f ) return false; FS_Read( f, &header, sizeof( header )); if( header.ident != IDHPAKHEADER ) { Con_DPrintf( S_ERROR "%s it's not a HPK file.\n", pakname ); FS_Close( f ); return false; } if( header.version != IDHPAK_VERSION ) { Con_DPrintf( S_ERROR "%s has invalid version (%i should be %i).\n", pakname, header.version, IDHPAK_VERSION ); FS_Close( f ); return false; } FS_Seek( f, header.infotableofs, SEEK_SET ); FS_Read( f, &directory.count, sizeof( directory.count )); if( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES ) { Con_DPrintf( S_ERROR "HPAK_GetDataPointer: %s has too many lumps %u.\n", filename, directory.count ); FS_Close( f ); return false; } directory.entries = Z_Malloc( sizeof( hpak_lump_t ) * directory.count ); FS_Read( f, directory.entries, sizeof( hpak_lump_t ) * directory.count ); for( i = 0; i < directory.count; i++ ) { entry = &directory.entries[i]; if( !memcmp( entry->resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 )) { FS_Seek( f, entry->filepos, SEEK_SET ); if( buffer && entry->disksize > 0 ) { tmpbuf = Z_Malloc( entry->disksize ); FS_Read( f, tmpbuf, entry->disksize ); *buffer = tmpbuf; } if( bufsize ) *bufsize = entry->disksize; Mem_Free( directory.entries ); FS_Close( f ); return true; } } Mem_Free( directory.entries ); FS_Close( f ); return false; } void HPAK_RemoveLump( const char *name, resource_t *pResource ) { string read_path; string save_path; file_t *file_src; file_t *file_dst; hpak_info_t hpak_read; hpak_info_t hpak_save; int i, j; if( !COM_CheckString( name ) || !pResource ) return; HPAK_FlushHostQueue(); Q_strncpy( read_path, name, sizeof( read_path )); COM_ReplaceExtension( read_path, ".hpk" ); file_src = FS_Open( read_path, "rb", false ); if( !file_src ) { Con_DPrintf( S_ERROR "%s couldn't open.\n", read_path ); return; } Q_strncpy( save_path, read_path, sizeof( save_path )); COM_ReplaceExtension( save_path, ".hp2" ); file_dst = FS_Open( save_path, "wb", false ); if( !file_dst ) { Con_DPrintf( S_ERROR "%s couldn't open.\n", save_path ); FS_Close( file_src ); return; } FS_Seek( file_src, 0, SEEK_SET ); FS_Seek( file_dst, 0, SEEK_SET ); // header copy FS_Read( file_src, &hash_pack_header, sizeof( hpak_header_t )); FS_Write( file_dst, &hash_pack_header, sizeof( hpak_header_t )); if( hash_pack_header.ident != IDHPAKHEADER || hash_pack_header.version != IDHPAK_VERSION ) { Con_DPrintf( S_ERROR "%s has invalid header.\n", read_path ); FS_Close( file_src ); FS_Close( file_dst ); FS_Delete( save_path ); // delete temp file return; } FS_Seek( file_src, hash_pack_header.infotableofs, SEEK_SET ); FS_Read( file_src, &hpak_read.count, sizeof( hpak_read.count )); if( hpak_read.count < 1 || hpak_read.count > HPAK_MAX_ENTRIES ) { Con_DPrintf( S_ERROR "%s has invalid number of lumps.\n", read_path ); FS_Close( file_src ); FS_Close( file_dst ); FS_Delete( save_path ); // delete temp file return; } if( hpak_read.count == 1 ) { Con_DPrintf( S_WARN "%s only has one element, so HPAK will be removed\n", read_path ); FS_Close( file_src ); FS_Close( file_dst ); FS_Delete( read_path ); FS_Delete( save_path ); return; } hpak_save.count = hpak_read.count - 1; hpak_read.entries = Z_Malloc( sizeof( hpak_lump_t ) * hpak_read.count ); hpak_save.entries = Z_Malloc( sizeof( hpak_lump_t ) * hpak_save.count ); FS_Read( file_src, hpak_read.entries, sizeof( hpak_lump_t ) * hpak_read.count ); if( !HPAK_FindResource( &hpak_read, pResource->rgucMD5_hash, NULL )) { Con_DPrintf( S_ERROR "HPAK %s doesn't contain specified lump: %s\n", read_path, pResource->szFileName ); Mem_Free( hpak_read.entries ); Mem_Free( hpak_save.entries ); FS_Close( file_src ); FS_Close( file_dst ); FS_Delete( save_path ); return; } Con_Printf( "Removing %s from HPAK %s.\n", pResource->szFileName, read_path ); // If there's a collision, we've just corrupted this hpak. for( i = 0, j = 0; i < hpak_read.count; i++ ) { if( !memcmp( hpak_read.entries[i].resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 )) continue; hpak_save.entries[j] = hpak_read.entries[i]; hpak_save.entries[j].filepos = FS_Tell( file_dst ); FS_Seek( file_src, hpak_read.entries[j].filepos, SEEK_SET ); FS_FileCopy( file_dst, file_src, hpak_save.entries[j].disksize ); j++; } hash_pack_header.infotableofs = FS_Tell( file_dst ); FS_Write( file_dst, &hpak_save.count, sizeof( hpak_save.count )); for( i = 0; i < hpak_save.count; i++ ) FS_Write( file_dst, &hpak_save.entries[i], sizeof( hpak_lump_t )); FS_Seek( file_dst, 0, SEEK_SET ); FS_Write( file_dst, &hash_pack_header, sizeof( hpak_header_t )); Mem_Free( hpak_read.entries ); Mem_Free( hpak_save.entries ); FS_Close( file_src ); FS_Close( file_dst ); FS_Delete( read_path ); FS_Rename( save_path, read_path ); } void HPAK_List_f( void ) { int nCurrent; hpak_header_t header; hpak_info_t directory; hpak_lump_t *entry; string lumpname; string pakname; const char *type; const char *size; file_t *f; if( Cmd_Argc() != 2 ) { Con_Printf( S_USAGE "hpklist \n" ); return; } HPAK_FlushHostQueue(); Q_strncpy( pakname, Cmd_Argv( 1 ), sizeof( pakname )); COM_ReplaceExtension( pakname, ".hpk" ); Con_Printf( "Contents for %s.\n", pakname ); f = FS_Open( pakname, "rb", false ); if( !f ) { Con_DPrintf( S_ERROR "couldn't open %s.\n", pakname ); return; } FS_Read( f, &header, sizeof( hpak_header_t )); if( header.ident != IDHPAKHEADER ) { Con_DPrintf( S_ERROR "%s is not an HPAK file\n", pakname ); FS_Close( f ); return; } if( header.version != IDHPAK_VERSION ) { Con_DPrintf( S_ERROR "%s has invalid version (%i should be %i).\n", pakname, header.version, IDHPAK_VERSION ); FS_Close( f ); return; } FS_Seek( f, header.infotableofs, SEEK_SET ); FS_Read( f, &directory.count, sizeof( directory.count )); if( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES ) { Con_DPrintf( S_ERROR "%s has too many lumps %u.\n", pakname, directory.count ); FS_Close( f ); return; } Con_Printf( "# of Entries: %i\n", directory.count ); Con_Printf( "# Type Size FileName : MD5 Hash\n" ); directory.entries = Z_Malloc( directory.count * sizeof( hpak_lump_t )); FS_Read( f, directory.entries, directory.count * sizeof( hpak_lump_t )); for( nCurrent = 0; nCurrent < directory.count; nCurrent++ ) { entry = &directory.entries[nCurrent]; COM_FileBase( entry->resource.szFileName, lumpname ); type = HPAK_TypeFromIndex( entry->resource.type ); size = Q_memprint( entry->resource.nDownloadSize ); Con_Printf( "%i: %10s %s %s\n : %s\n", nCurrent + 1, type, size, lumpname, MD5_Print( entry->resource.rgucMD5_hash )); } if( directory.entries ) Mem_Free( directory.entries ); FS_Close( f ); } void HPAK_Extract_f( void ) { int nCurrent; hpak_header_t header; hpak_info_t directory; hpak_lump_t *entry; string lumpname; string pakname; string szFileOut; int nIndex; byte *pData; int nDataSize; const char *type; const char *size; file_t *f; if( Cmd_Argc() != 3 ) { Con_Printf( S_USAGE "hpkextract hpkname [all | single index]\n" ); return; } if( !Q_stricmp( Cmd_Argv( 2 ), "all" )) { nIndex = -1; } else { nIndex = Q_atoi( Cmd_Argv( 2 ) ); } HPAK_FlushHostQueue(); Q_strncpy( pakname, Cmd_Argv( 1 ), sizeof( pakname )); COM_ReplaceExtension( pakname, ".hpk" ); Con_Printf( "Contents for %s.\n", pakname ); f = FS_Open( pakname, "rb", false ); if( !f ) { Con_DPrintf( S_ERROR "couldn't open %s.\n", pakname ); return; } FS_Read( f, &header, sizeof( hpak_header_t )); if( header.ident != IDHPAKHEADER ) { Con_DPrintf( S_ERROR "%s is not an HPAK file\n", pakname ); FS_Close( f ); return; } if( header.version != IDHPAK_VERSION ) { Con_DPrintf( S_ERROR "%s has invalid version (%i should be %i).\n", pakname, header.version, IDHPAK_VERSION ); FS_Close( f ); return; } FS_Seek( f, header.infotableofs, SEEK_SET ); FS_Read( f, &directory.count, sizeof( directory.count )); if( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES ) { Con_DPrintf( S_ERROR "%s has too many lumps %u.\n", pakname, directory.count ); FS_Close( f ); return; } if( nIndex == -1 ) Con_Printf( "Extracting all lumps from %s.\n", pakname ); else Con_Printf( "Extracting lump %i from %s\n", nIndex, pakname ); directory.entries = Z_Malloc( directory.count * sizeof( hpak_lump_t )); FS_Read( f, directory.entries, directory.count * sizeof( hpak_lump_t )); for( nCurrent = 0; nCurrent < directory.count; nCurrent++ ) { entry = &directory.entries[nCurrent]; if( nIndex != -1 && nIndex != nCurrent ) continue; COM_FileBase( entry->resource.szFileName, lumpname ); type = HPAK_TypeFromIndex( entry->resource.type ); size = Q_memprint( entry->resource.nDownloadSize ); Con_Printf( "Extracting %i: %10s %s %s\n", nCurrent + 1, type, size, lumpname ); if( entry->disksize <= 0 || entry->disksize >= HPAK_MAX_SIZE ) { Con_DPrintf( S_WARN "Unable to extract data, size invalid: %s\n", Q_memprint( entry->disksize )); continue; } nDataSize = entry->disksize; pData = Z_Malloc( nDataSize + 1 ); FS_Seek( f, entry->filepos, SEEK_SET ); FS_Read( f, pData, nDataSize ); Q_snprintf( szFileOut, sizeof( szFileOut ), "hpklmps\\lmp%04i.bmp", nCurrent ); FS_WriteFile( szFileOut, pData, nDataSize ); if( pData ) Mem_Free( pData ); } if( directory.entries ) Mem_Free( directory.entries ); FS_Close( f ); } void HPAK_Remove_f( void ) { resource_t resource; HPAK_FlushHostQueue(); if( Cmd_Argc() != 3 ) { Con_Printf( S_USAGE "hpkremove \n" ); return; } if( HPAK_ResourceForIndex( Cmd_Argv( 1 ), Q_atoi( Cmd_Argv( 2 )), &resource )) { HPAK_RemoveLump( Cmd_Argv( 1 ), &resource ); } else { Con_DPrintf( S_ERROR "Could not locate resource %i in %s\n", Q_atoi( Cmd_Argv( 2 )), Cmd_Argv( 1 )); } } void HPAK_Validate_f( void ) { if( Cmd_Argc() != 2 ) { Con_Printf( S_USAGE "hpkval \n" ); return; } HPAK_Validate( Cmd_Argv( 1 ), false ); } void HPAK_Init( void ) { Cmd_AddCommand( "hpklist", HPAK_List_f, "list all files in specified HPK-file" ); Cmd_AddCommand( "hpkremove", HPAK_Remove_f, "remove specified file from HPK-file" ); Cmd_AddCommand( "hpkval", HPAK_Validate_f, "validate specified HPK-file" ); Cmd_AddCommand( "hpkextract", HPAK_Extract_f, "extract all lumps from specified HPK-file" ); hpk_maxsize = Cvar_Get( "hpk_maxsize", "0", FCVAR_ARCHIVE, "set limit by size for all HPK-files ( 0 - unlimited )" ); gp_hpak_queue = NULL; }