/*==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;
}