/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ ////////////////////////////////////////////////////////////////////////////// // // plDatMerger - Command line utility app that takes multiple dat files // and merges them into one // ////////////////////////////////////////////////////////////////////////////// #include "hsTypes.h" #include "hsStream.h" #include "hsUtils.h" #include "hsTimer.h" #include "../plFile/hsFiles.h" #include "plRawResManager.h" #include "plRawPageAccessor.h" #include "../plResMgr/plRegistryDiskSource.h" #include "../plResMgr/plRegistryDiskMergedSourceData.h" #include "../plResMgr/plRegistryHelpers.h" #include "../plResMgr/plRegistry.h" #include "../plResMgr/plRegistryNode.h" #include "../plResMgr/plResMgrSettings.h" #include "../plResMgr/plPageInfo.h" #include "../plAgeDescription/plAgeDescription.h" #include "../plFile/hsFiles.h" #include "../plFile/plFileUtils.h" #include "../pnKeyedObject/plKey.h" #include #include //// Tiny String Filter Class //////////////////////////////////////////////// class plStrFilter { protected: hsBool fWildCardSuffix, fInvert; char *fStr; plStrFilter *fChapFilter, *fPageFilter; hsBool IPass( const char *string ) { if( fWildCardSuffix ) { if( strnicmp( fStr, string, strlen( fStr ) ) == 0 ) return true; } else { if( stricmp( fStr, string ) == 0 ) return true; } return false; } public: plStrFilter() { fWildCardSuffix = fInvert = false; fStr = nil; fChapFilter = fPageFilter = nil; } plStrFilter( const char *initLine ) { if( initLine[ 0 ] == '-' ) { fInvert = true; initLine++; } else if( initLine[ 0 ] == '+' ) { initLine++; fInvert = false; } else fInvert = false; fWildCardSuffix = false; fStr = hsStrcpy( initLine ); fChapFilter = fPageFilter = nil; char *comma = strchr( fStr, ',' ); if( comma != nil ) { char *next = comma + 1; *comma = 0; comma = strchr( next, ',' ); if( comma != nil ) { fPageFilter = new plStrFilter( comma + 1 ); *comma = 0; } fChapFilter = new plStrFilter( next ); } if( fStr[ strlen( fStr ) - 1 ] == '*' ) { fWildCardSuffix = true; fStr[ strlen( fStr ) - 1 ] = 0; } } ~plStrFilter() { delete [] fStr; delete fChapFilter; delete fPageFilter; } hsBool Pass( const char *string ) { hsBool ret = IPass( string ); if( fInvert ) ret = !ret; return ret; } hsBool Pass( const plPageInfo &page ) { hsBool ret = IPass( page.GetAge() ) && fChapFilter->IPass( page.GetChapter() ) && fPageFilter->IPass( page.GetPage() ); if( fInvert ) ret = !ret; return ret; } static hsBool Passes( const char *string, hsTArray &filters ) { UInt32 i; for( i = 0; i < filters.GetCount(); i++ ) { if( !filters[ i ]->Pass( string ) ) return false; } return true; } static hsBool Passes( const plPageInfo &page, hsTArray &filters ) { UInt32 i; for( i = 0; i < filters.GetCount(); i++ ) { if( !filters[ i ]->Pass( page ) ) return false; } return true; } }; //// Globals ///////////////////////////////////////////////////////////////// plRawResManager *gResManager = nil; char gDatDirectory[ kFolderIterator_MaxPath ] = "."; char gDestFileName[ kFolderIterator_MaxPath ]; hsTArray fPageFilters; hsTArray *fCurrFilterList = nil; hsTArray fSourcePages; plRegistryDiskMergedSourceData *gDestMergeData = nil; //// PrintHelp /////////////////////////////////////////////////////////////// int PrintHelp( void ) { puts( "" ); puts( "Usage:\tplDatMerger [oldDir] [newDir] [patchDir] |-na| |-np| |-fp| |-lAgeName| |-anewAgeDir|" ); puts( "Where:" ); puts( "\toldDir is the directory containing the old data files" ); puts( "\tnewDir is the directory containing the new data files" ); puts( "\tpatchDir is the directory where the patch files will go" ); puts( "\t WARNING: This should point to a 'patches'" ); puts( "\t subdir under 'newDir'; don't use anything else" ); puts( "\t unless you REALLY know what you're doing." ); puts( "\t-na is a flag that keeps the builder from updating the" ); puts( "\t version numbers in the age files (for generating" ); puts( "\t previous patch versions, for example." ); puts( "\t-np is a flag that keeps the builder from actually creating the" ); puts( "\t patch files. Usually helpful when you want to" ); puts( "\t only update version numbers in the dat files" ); puts( "\t and the age files. -na and -np are mutually" ); puts( "\t exclusive." ); puts( "\t-fp forces writing of entire objects instead of just difference" ); puts( "\t buffers, for debugging purposes." ); puts( "\t-l limits processing to the single age given. Don't put a space between the l." ); puts( "\t and the age name." ); puts( "\t-a specifies a different directory to put the modified age files in. If not" ); puts( "\t specified, age files are overwritten in the newDir." ); puts( "" ); return -1; } hsBool ReadConfig( const char *filename ) { hsUNIXStream config; if( !config.Open( filename, "rt" ) ) return false; char line[ 512 ]; int lineNum = 1; while( config.ReadLn( line, sizeof( line ) ) ) { // Switch based on command if( stricmp( line, "[pageFilters]" ) == 0 ) fCurrFilterList = &fPageFilters; else if( fCurrFilterList != nil ) { fCurrFilterList->Append( new plStrFilter( line ) ); } else { char *tok = strtok( line, " \t=" ); if( tok != nil ) { if( stricmp( tok, "datDir" ) == 0 ) { tok = strtok( nil, " \t=" ); if( tok != nil ) strcpy( gDatDirectory, tok ); else { printf( "Parse error in init file, line %d", lineNum ); return false; } } else if( stricmp( tok, "destFile" ) == 0 ) { tok = strtok( nil, "\n\r" ); if( tok == nil ) { printf( "Parse error in init file, line %d", lineNum ); return false; } strcpy( gDestFileName, tok ); } else { printf( "Parse error in init file, line %d", lineNum ); } } } lineNum++; } config.Close(); return true; } //// Our Main Page Iterator ////////////////////////////////////////////////// class plPageStuffer : public plRegistryPageIterator { public: virtual hsBool EatPage( plRegistryPageNode *page ) { const plPageInfo &info = page->GetPageInfo(); if( plStrFilter::Passes( info, fPageFilters ) ) fSourcePages.Append( page ); return true; } }; //// IShutdown /////////////////////////////////////////////////////////////// void IShutdown( int retCode ) { UInt32 i; for( i = 0; i < fPageFilters.GetCount(); i++ ) delete fPageFilters[ i ]; delete gDestMergeData; hsgResMgr::Shutdown(); if( retCode == 0 ) printf( "Finished!\n" ); else exit( retCode ); } //// main //////////////////////////////////////////////////////////////////// int main( int argc, char *argv[] ) { puts( "-----------------------------------------------------" ); puts( "plDatMerger - Plasma 2 dat file merging utility" ); puts( "-----------------------------------------------------" ); if( argc < 1 || argc > 8 ) return PrintHelp(); // Read our config ReadConfig( argv[ 1 ] ); plResMgrSettings::Get().SetFilterNewerPageVersions( false ); plResMgrSettings::Get().SetFilterOlderPageVersions( false ); // Init our special resMgr puts( "Initializing resManager..." ); gResManager = new plRawResManager; hsgResMgr::Init( gResManager ); // Load the registry in to work with printf( "Loading registry from directory \"%s\"...\n", gDatDirectory ); gResManager->AddSource( new plRegistryDiskSource( gDatDirectory ) ); // Iterate and collect pages to merge printf( "Collecting pages...\n" ); plPageStuffer pageIter; gResManager->IterateAllPages( &pageIter ); if( fSourcePages.GetCount() == 0 ) { puts( "ERROR: No source pages found to merge!" ); IShutdown( -1 ); } // Create a merged data source to represent our dest page printf( "Merging %d pages to file(s) %s...\n", fSourcePages.GetCount(), gDestFileName ); gDestMergeData = new plRegistryDiskMergedSourceData( gDestFileName ); gDestMergeData->SetNumEntries( fSourcePages.GetCount() ); // Open the dest merged streams and write out our initial, incorrect, entry table so we can get positions right hsStream *destIdxStream = gDestMergeData->WriteEntries( true ); hsStream *destDatStream = gDestMergeData->OpenData( (UInt32)-1, "wb" ); UInt32 i, bytesRead; static UInt8 scratchBuffer[ 1024 * 64 ]; // 32k in size for( i = 0; i < fSourcePages.GetCount(); i++ ) { printf( " Merging %s>%s...\n", fSourcePages[ i ]->GetPageInfo().GetAge(), fSourcePages[ i ]->GetPageInfo().GetPage() ); // For each page, we open the source streams, read the ENTIRE thing in, front to back, and append it // to the dest stream. We then update the entry in the mergeData to reflect our info plMSDEntry &entry = gDestMergeData->GetEntry( i ); entry.fIdxOffset = destIdxStream->GetPosition(); entry.fDatOffset = destDatStream->GetPosition(); /// Actually transfer the data plRegistrySource *srcSource = fSourcePages[ i ]->GetSource(); // Idx first hsStream *srcStream = srcSource->OpenIndexStream( fSourcePages[ i ] ); UInt32 size = srcStream->GetEOF(); do { bytesRead = srcStream->Read( size > sizeof( scratchBuffer ) ? sizeof( scratchBuffer ) : size, scratchBuffer ); if( bytesRead > 0 ) destIdxStream->Write( bytesRead, scratchBuffer ); size -= bytesRead; } while( size > 0 && bytesRead > 0 ); srcSource->CloseIndexStream( fSourcePages[ i ] ); // Now dat srcStream = srcSource->OpenDataStream( fSourcePages[ i ] ); size = srcStream->GetEOF(); do { bytesRead = srcStream->Read( size > sizeof( scratchBuffer ) ? sizeof( scratchBuffer ) : size, scratchBuffer ); if( bytesRead > 0 ) destDatStream->Write( bytesRead, scratchBuffer ); size -= bytesRead; } while( size > 0 && bytesRead > 0 ); srcSource->CloseDataStream( fSourcePages[ i ] ); // Update lengths entry.fIdxLength = destIdxStream->GetPosition() - entry.fIdxOffset; entry.fDatLength = destDatStream->GetPosition() - entry.fDatOffset; } printf( "Closing destination files...\n" ); destIdxStream->Close(); destDatStream->Close(); // Re-write the entry table, now that it's correct printf( "Updating merged table...\n" ); gDestMergeData->WriteEntries( false ); puts( "Shutting down..." ); IShutdown( 0 ); return 0; }