/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code 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. Doom 3 BFG Edition Source Code 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. You should have received a copy of the GNU General Public License along with Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "../idlib/precompiled.h" #pragma hdrstop /* ================================================================================================ idResourceContainer ================================================================================================ */ /* ======================== idResourceContainer::ReOpen ======================== */ void idResourceContainer::ReOpen() { delete resourceFile; resourceFile = fileSystem->OpenFileRead( fileName ); } /* ======================== idResourceContainer::Init ======================== */ bool idResourceContainer::Init( const char *_fileName, uint8 containerIndex ) { if ( idStr::Icmp( _fileName, "_ordered.resources" ) == 0 ) { resourceFile = fileSystem->OpenFileReadMemory( _fileName ); } else { resourceFile = fileSystem->OpenFileRead( _fileName ); } if ( resourceFile == NULL ) { idLib::Warning( "Unable to open resource file %s", _fileName ); return false; } resourceFile->ReadBig( resourceMagic ); if ( resourceMagic != RESOURCE_FILE_MAGIC ) { idLib::FatalError( "resourceFileMagic != RESOURCE_FILE_MAGIC" ); } fileName = _fileName; resourceFile->ReadBig( tableOffset ); resourceFile->ReadBig( tableLength ); // read this into a memory buffer with a single read char * const buf = (char *)Mem_Alloc( tableLength, TAG_RESOURCE ); resourceFile->Seek( tableOffset, FS_SEEK_SET ); resourceFile->Read( buf, tableLength ); idFile_Memory memFile( "resourceHeader", (const char *)buf, tableLength ); // Parse the resourceFile header, which includes every resource used // by the game. memFile.ReadBig( numFileResources ); cacheTable.SetNum( numFileResources ); for ( int i = 0; i < numFileResources; i++ ) { idResourceCacheEntry &rt = cacheTable[ i ]; rt.Read( &memFile ); rt.filename.BackSlashesToSlashes(); rt.filename.ToLower(); rt.containerIndex = containerIndex; const int key = cacheHash.GenerateKey( rt.filename, false ); bool found = false; //for ( int index = cacheHash.GetFirst( key ); index != idHashIndex::NULL_INDEX; index = cacheHash.GetNext( index ) ) { // idResourceCacheEntry & rtc = cacheTable[ index ]; // if ( idStr::Icmp( rtc.filename, rt.filename ) == 0 ) { // found = true; // break; // } //} if ( !found ) { //idLib::Printf( "rez file name: %s\n", rt.filename.c_str() ); cacheHash.Add( key, i ); } } Mem_Free( buf ); return true; } /* ======================== idResourceContainer::WriteManifestFile ======================== */ void idResourceContainer::WriteManifestFile( const char *name, const idStrList &list ) { idStr filename( name ); filename.SetFileExtension( "manifest" ); filename.Insert( "maps/", 0 ); idFile *outFile = fileSystem->OpenFileWrite( filename ); if ( outFile != NULL ) { int num = list.Num(); outFile->WriteBig( num ); for ( int i = 0; i < num; i++ ) { outFile->WriteString( list[ i ] ); } delete outFile; } } /* ======================== idResourceContainer::ReadManifestFile ======================== */ int idResourceContainer::ReadManifestFile( const char *name, idStrList &list ) { idFile *inFile = fileSystem->OpenFileRead( name ); if ( inFile != NULL ) { list.SetGranularity( 16384 ); idStr str; int num; list.Clear(); inFile->ReadBig( num ); for ( int i = 0; i < num; i++ ) { inFile->ReadString( str ); list.Append( str ); } delete inFile; } return list.Num(); } /* ======================== idResourceContainer::UpdateResourceFile ======================== */ void idResourceContainer::UpdateResourceFile( const char *_filename, const idStrList &_filesToUpdate ) { idFile *outFile = fileSystem->OpenFileWrite( va( "%s.new", _filename ) ); if ( outFile == NULL ) { idLib::Warning( "Unable to open resource file %s or new output file", _filename ); return; } uint32 magic = 0; int _tableOffset = 0; int _tableLength = 0; idList< idResourceCacheEntry > entries; idStrList filesToUpdate = _filesToUpdate; idFile *inFile = fileSystem->OpenFileRead( _filename ); if ( inFile == NULL ) { magic = RESOURCE_FILE_MAGIC; outFile->WriteBig( magic ); outFile->WriteBig( _tableOffset ); outFile->WriteBig( _tableLength ); } else { inFile->ReadBig( magic ); if ( magic != RESOURCE_FILE_MAGIC ) { delete inFile; return; } inFile->ReadBig( _tableOffset ); inFile->ReadBig( _tableLength ); // read this into a memory buffer with a single read char * const buf = (char *)Mem_Alloc( _tableLength, TAG_RESOURCE ); inFile->Seek( _tableOffset, FS_SEEK_SET ); inFile->Read( buf, _tableLength ); idFile_Memory memFile( "resourceHeader", (const char *)buf, _tableLength ); int _numFileResources = 0; memFile.ReadBig( _numFileResources ); outFile->WriteBig( magic ); outFile->WriteBig( _tableOffset ); outFile->WriteBig( _tableLength ); entries.SetNum( _numFileResources ); for ( int i = 0; i < _numFileResources; i++ ) { entries[ i ].Read( &memFile ); idLib::Printf( "examining %s\n", entries[ i ].filename.c_str() ); byte * fileData = NULL; for ( int j = filesToUpdate.Num() - 1; j >= 0; j-- ) { if ( filesToUpdate[ j ].Icmp( entries[ i ].filename ) == 0 ) { idFile *newFile = fileSystem->OpenFileReadMemory( filesToUpdate[ j ] ); if ( newFile != NULL ) { idLib::Printf( "Updating %s\n", filesToUpdate[ j ].c_str() ); entries[ i ].length = newFile->Length(); fileData = (byte *)Mem_Alloc( entries[ i ].length, TAG_TEMP ); newFile->Read( fileData, newFile->Length() ); delete newFile; } filesToUpdate.RemoveIndex( j ); } } if ( fileData == NULL ) { inFile->Seek( entries[ i ].offset, FS_SEEK_SET ); fileData = (byte *)Mem_Alloc( entries[ i ].length, TAG_TEMP ); inFile->Read( fileData, entries[ i ].length ); } entries[ i ].offset = outFile->Tell(); outFile->Write( ( void* )fileData, entries[ i ].length ); Mem_Free( fileData ); } Mem_Free( buf ); } while ( filesToUpdate.Num() > 0 ) { idFile *newFile = fileSystem->OpenFileReadMemory( filesToUpdate[ 0 ] ); if ( newFile != NULL ) { idLib::Printf( "Appending %s\n", filesToUpdate[ 0 ].c_str() ); idResourceCacheEntry rt; rt.filename = filesToUpdate[ 0 ]; rt.length = newFile->Length(); byte * fileData = (byte *)Mem_Alloc( rt.length, TAG_TEMP ); newFile->Read( fileData, rt.length ); int idx = entries.Append( rt ); if ( idx >= 0 ) { entries[ idx ].offset = outFile->Tell(); outFile->Write( ( void* )fileData, entries[ idx ].length ); } delete newFile; Mem_Free( fileData ); } filesToUpdate.RemoveIndex( 0 ); } _tableOffset = outFile->Tell(); outFile->WriteBig( entries.Num() ); // write the individual resource entries for ( int i = 0; i < entries.Num(); i++ ) { entries[ i ].Write( outFile ); } // go back and write the header offsets again, now that we have file offsets and lengths _tableLength = outFile->Tell() - _tableOffset; outFile->Seek( 0, FS_SEEK_SET ); outFile->WriteBig( magic ); outFile->WriteBig( _tableOffset ); outFile->WriteBig( _tableLength ); delete outFile; delete inFile; } /* ======================== idResourceContainer::ExtractResourceFile ======================== */ void idResourceContainer::SetContainerIndex( const int & _idx ) { for ( int i = 0; i < cacheTable.Num(); i++ ) { cacheTable[ i ].containerIndex = _idx; } } /* ======================== idResourceContainer::ExtractResourceFile ======================== */ void idResourceContainer::ExtractResourceFile ( const char * _fileName, const char * _outPath, bool _copyWavs ) { idFile *inFile = fileSystem->OpenFileRead( _fileName ); if ( inFile == NULL ) { idLib::Warning( "Unable to open resource file %s", _fileName ); return; } uint32 magic; inFile->ReadBig( magic ); if ( magic != RESOURCE_FILE_MAGIC ) { delete inFile; return; } int _tableOffset; int _tableLength; inFile->ReadBig( _tableOffset ); inFile->ReadBig( _tableLength ); // read this into a memory buffer with a single read char * const buf = (char *)Mem_Alloc( _tableLength, TAG_RESOURCE ); inFile->Seek( _tableOffset, FS_SEEK_SET ); inFile->Read( buf, _tableLength ); idFile_Memory memFile( "resourceHeader", (const char *)buf, _tableLength ); int _numFileResources; memFile.ReadBig( _numFileResources ); for ( int i = 0; i < _numFileResources; i++ ) { idResourceCacheEntry rt; rt.Read( &memFile ); rt.filename.BackSlashesToSlashes(); rt.filename.ToLower(); byte *fbuf = NULL; if ( _copyWavs && ( rt.filename.Find( ".idwav" ) >= 0 || rt.filename.Find( ".idxma" ) >= 0 || rt.filename.Find( ".idmsf" ) >= 0 ) ) { rt.filename.SetFileExtension( "wav" ); rt.filename.Replace( "generated/", "" ); int len = fileSystem->GetFileLength( rt.filename ); fbuf = (byte *)Mem_Alloc( len, TAG_RESOURCE ); fileSystem->ReadFile( rt.filename, (void**)&fbuf, NULL ); } else { inFile->Seek( rt.offset, FS_SEEK_SET ); fbuf = (byte *)Mem_Alloc( rt.length, TAG_RESOURCE ); inFile->Read( fbuf, rt.length ); } idStr outName = _outPath; outName.AppendPath( rt.filename ); idFile *outFile = fileSystem->OpenExplicitFileWrite( outName ); if ( outFile != NULL ) { outFile->Write( ( byte* )fbuf, rt.length ); delete outFile; } Mem_Free( fbuf ); } delete inFile; Mem_Free( buf ); } /* ======================== idResourceContainer::Open ======================== */ void idResourceContainer::WriteResourceFile( const char *manifestName, const idStrList &manifest, const bool &_writeManifest ) { if ( manifest.Num() == 0 ) { return; } idLib::Printf( "Writing resource file %s\n", manifestName ); // build multiple output files at 1GB each idList < idStrList > outPutFiles; idFileManifest outManifest; int64 size = 0; idStrList flist; flist.SetGranularity( 16384 ); for ( int i = 0; i < manifest.Num(); i++ ) { flist.Append( manifest[ i ] ); size += fileSystem->GetFileLength( manifest[ i ] ); if ( size > 1024 * 1024 * 1024 ) { outPutFiles.Append( flist ); size = 0; flist.Clear(); } outManifest.AddFile( manifest[ i ] ); } outPutFiles.Append( flist ); if ( _writeManifest ) { idStr temp = manifestName; temp.Replace( "maps/", "manifests/" ); temp.StripFileExtension(); temp.SetFileExtension( "manifest" ); outManifest.WriteManifestFile( temp ); } for ( int idx = 0; idx < outPutFiles.Num(); idx++ ) { idStrList &fileList = outPutFiles[ idx ]; if ( fileList.Num() == 0 ) { continue; } idStr fileName = manifestName; if ( idx > 0 ) { fileName = va( "%s_%02d", manifestName, idx ); } fileName.SetFileExtension( "resources" ); idFile *resFile = fileSystem->OpenFileWrite( fileName ); if ( resFile == NULL ) { idLib::Warning( "Cannot open %s for writing.\n", fileName.c_str() ); return; } idLib::Printf( "Writing resource file %s\n", fileName.c_str() ); int tableOffset = 0; int tableLength = 0; int tableNewLength = 0; uint32 resourceFileMagic = RESOURCE_FILE_MAGIC; resFile->WriteBig( resourceFileMagic ); resFile->WriteBig( tableOffset ); resFile->WriteBig( tableLength ); idList< idResourceCacheEntry > entries; entries.Resize( fileList.Num() ); for ( int i = 0; i < fileList.Num(); i++ ) { idResourceCacheEntry ent; ent.filename = fileList[ i ]; ent.length = 0; ent.offset = 0; idFile *file = fileSystem->OpenFileReadMemory( ent.filename, false ); idFile_Memory *fm = dynamic_cast< idFile_Memory* >( file ); if ( fm == NULL ) { continue; } // if the entry is uncompressed, align the file pointer to a 16 byte boundary // so it will be usable if memory mapped ent.length = fm->Length(); // always get the offset, even if the file will have zero length ent.offset = resFile->Tell(); entries.Append( ent ); if ( ent.length == 0 ) { ent.filename = ""; delete fm; continue; } resFile->Write( fm->GetDataPtr(), ent.length ); delete fm; // pacifier every ten megs if ( ( ent.offset + ent.length ) / 10000000 != ent.offset / 10000000 ) { idLib::Printf( "." ); } } idLib::Printf( "\n" ); // write the table out now that we have all the files tableOffset = resFile->Tell(); // count how many we are going to write for this platform int numFileResources = entries.Num(); resFile->WriteBig( numFileResources ); // write the individual resource entries for ( int i = 0; i < entries.Num(); i++ ) { entries[ i ].Write( resFile ); if ( i + 1 == numFileResources ) { // we just wrote out the last new entry tableNewLength = resFile->Tell() - tableOffset; } } // go back and write the header offsets again, now that we have file offsets and lengths tableLength = resFile->Tell() - tableOffset; resFile->Seek( 0, FS_SEEK_SET ); resFile->WriteBig( resourceFileMagic ); resFile->WriteBig( tableOffset ); resFile->WriteBig( tableLength ); delete resFile; } }