2009-05-08 13:28:41 +00:00
/*
* OpenClonk , http : //www.openclonk.org
*
2013-12-17 20:01:09 +00:00
* Copyright ( c ) 2001 - 2009 , RedWolf Design GmbH , http : //www.clonk.de/
2016-04-03 18:18:29 +00:00
* Copyright ( c ) 2009 - 2016 , The OpenClonk Team and contributors
2009-05-08 13:28:41 +00:00
*
2013-12-17 20:01:09 +00:00
* Distributed under the terms of the ISC license ; see accompanying file
* " COPYING " for details .
2009-05-08 13:28:41 +00:00
*
2013-12-17 20:01:09 +00:00
* " Clonk " is a registered trademark of Matthes Bender , used with permission .
* See accompanying file " TRADEMARK " for details .
2009-05-08 13:28:41 +00:00
*
2013-12-17 20:01:09 +00:00
* To redistribute this file separately , substitute the full license texts
* for the above references .
2009-05-08 13:28:41 +00:00
*/
2016-04-03 18:07:56 +00:00
# include "C4Include.h"
# include "c4group/C4Update.h"
2009-05-08 13:28:41 +00:00
# include "C4Version.h"
2016-04-03 18:07:56 +00:00
# include "c4group/C4Components.h"
# include "c4group/C4Group.h"
# include "lib/C4Log.h"
2009-05-08 13:28:41 +00:00
C4Config * GetCfg ( ) ;
# ifdef _WIN32
2016-04-03 18:07:56 +00:00
# include "platform/C4windowswrapper.h"
2009-05-08 13:28:41 +00:00
# include <direct.h>
# endif
// helper
2009-08-15 18:50:32 +00:00
bool C4Group_CopyEntry ( C4Group * pFrom , C4Group * pTo , const char * strItemName )
2009-05-08 13:28:41 +00:00
{
// read entry
char * pData ; size_t iSize ;
2010-03-28 18:58:01 +00:00
if ( ! pFrom - > LoadEntry ( strItemName , & pData , & iSize ) )
2009-08-15 18:50:32 +00:00
return false ;
2011-05-01 11:37:36 +00:00
if ( ! pTo - > Add ( strItemName , pData , iSize , false , true ) )
2009-08-15 18:50:32 +00:00
return false ;
return true ;
2009-05-08 13:28:41 +00:00
}
2010-12-26 19:49:04 +00:00
bool C4Group_ApplyUpdate ( C4Group & hGroup , unsigned long ParentProcessID )
2009-05-08 13:28:41 +00:00
{
2010-12-26 19:49:04 +00:00
// Wait for parent process to terminate (so we can safely replace the executable)
# ifdef _WIN32
if ( ParentProcessID )
{
HANDLE ParentProcess = OpenProcess ( SYNCHRONIZE , FALSE , ParentProcessID ) ;
if ( ParentProcess )
{
// If we couldn't find a handle then either
// a) the process terminated already, which is great.
// b) OpenProcess() failed, which is not so great. But let's still try to do
// the update.
printf ( " Waiting for parent process to terminate... " ) ;
DWORD res = WaitForSingleObject ( ParentProcess , 10000 ) ;
if ( res = = WAIT_TIMEOUT )
fprintf ( stderr , " Parent process did not terminate after 10 seconds. Continuing... " ) ;
}
}
# else
// We could use waitpid on Unix, but we don't need that functionality there anyway...
# endif
2009-05-08 13:28:41 +00:00
// Process object update group (GRPUP_Entries.txt found)
C4UpdatePackage Upd ;
if ( hGroup . FindEntry ( C4CFN_UpdateEntries ) )
if ( Upd . Load ( & hGroup ) )
{
// Do update check first (ensure packet has everything it needs in order to perfom the update)
int iRes = Upd . Check ( & hGroup ) ;
2010-03-28 18:58:01 +00:00
switch ( iRes )
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
// Bad version - checks against version of the applying executable (major version must match, minor version must be equal or higher)
2009-05-08 13:28:41 +00:00
case C4UPD_CHK_BAD_VERSION :
fprintf ( stderr , " This update %s can only be applied using version %d.%d.%d.%d or higher. \n " , Upd . Name , Upd . RequireVersion [ 0 ] , Upd . RequireVersion [ 1 ] , Upd . RequireVersion [ 2 ] , Upd . RequireVersion [ 3 ] ) ;
return false ;
2010-03-28 18:58:01 +00:00
// Target not found: keep going
2009-05-08 13:28:41 +00:00
case C4UPD_CHK_NO_SOURCE :
fprintf ( stderr , " Target %s for update %s not found. Ignoring. \n " , Upd . DestPath , Upd . Name ) ;
return true ;
2010-03-28 18:58:01 +00:00
// Target mismatch: abort updating
2009-05-08 13:28:41 +00:00
case C4UPD_CHK_BAD_SOURCE :
fprintf ( stderr , " Target %s incorrect version for update %s. Ignoring. \n " , Upd . DestPath , Upd . Name ) ;
return true ;
2010-03-28 18:58:01 +00:00
// Target already updated: keep going
2009-05-08 13:28:41 +00:00
case C4UPD_CHK_ALREADY_UPDATED :
fprintf ( stderr , " Target %s already up-to-date at %s. \n " , Upd . DestPath , Upd . Name ) ;
return true ;
2010-03-28 18:58:01 +00:00
// Ok to perform update
2009-05-08 13:28:41 +00:00
case C4UPD_CHK_OK :
printf ( " Updating %s to %s... " , Upd . DestPath , Upd . Name ) ;
// Make sure the user sees the message while the work is in progress
fflush ( stdout ) ;
// Execute update
if ( Upd . Execute ( & hGroup ) )
{
printf ( " Ok \n " ) ;
return true ;
}
else
{
printf ( " Failed \n " ) ;
return false ;
}
2010-03-28 18:58:01 +00:00
// Unknown return value from update
2009-05-08 13:28:41 +00:00
default :
fprintf ( stderr , " Unknown error while updating. \n " ) ;
return false ;
}
}
// Process binary update group (AutoUpdate.txt found, additional binary files found)
if ( hGroup . EntryCount ( C4CFN_UpdateCore ) )
2011-03-13 16:09:17 +00:00
if ( hGroup . EntryCount ( ) - hGroup . EntryCount ( C4CFN_UpdateCore ) - hGroup . EntryCount ( " *.ocu " ) > 0 )
2009-05-08 13:28:41 +00:00
{
// Notice: AutoUpdate.txt is currently not processed...
char strEntry [ _MAX_FNAME + 1 ] = " " ;
StdStrBuf strList ;
printf ( " Updating binaries... \n " ) ;
hGroup . ResetSearch ( ) ;
// Look for binaries
while ( hGroup . FindNextEntry ( " * " , strEntry ) )
2011-03-13 16:09:17 +00:00
// Accept everything except *.ocu, AutoUpdate.txt, and c4group.exe (which is assumed not to work under Windows)
if ( ! WildcardMatch ( " *.ocu " , strEntry ) & & ! WildcardMatch ( C4CFN_UpdateCore , strEntry ) & & ! WildcardMatch ( " c4group.exe " , strEntry ) )
2009-05-08 13:28:41 +00:00
{ strList + = strEntry ; strList + = " ; " ; }
// Extract binaries to current working directory
if ( ! hGroup . Extract ( strList . getData ( ) ) )
return false ;
// If extracted file is a group, explode it (this is meant for Clonk.app on Mac)
for ( int i = 0 ; SGetModule ( strList . getData ( ) , i , strEntry ) ; i + + )
if ( C4Group_IsGroup ( strEntry ) )
{
printf ( " Exploding: %s \n " , strEntry ) ;
if ( ! C4Group_ExplodeDirectory ( strEntry ) )
return false ;
}
}
2011-03-13 16:09:17 +00:00
// Process any child updates (*.ocu)
if ( hGroup . FindEntry ( " *.ocu " ) )
2009-05-08 13:28:41 +00:00
{
// Process all children
char strEntry [ _MAX_FNAME + 1 ] = " " ;
C4Group hChild ;
hGroup . ResetSearch ( ) ;
2011-03-13 16:09:17 +00:00
while ( hGroup . FindNextEntry ( " *.ocu " , strEntry ) )
2009-05-08 13:28:41 +00:00
if ( hChild . OpenAsChild ( & hGroup , strEntry ) )
{
2010-12-26 19:49:04 +00:00
bool ok = C4Group_ApplyUpdate ( hChild , 0 ) ;
2009-05-08 13:28:41 +00:00
hChild . Close ( ) ;
// Failure on child update
if ( ! ok ) return false ;
}
}
// Success
return true ;
}
// *** C4GroupEx
class C4GroupEx : public C4Group
{
public :
// some funcs to alter internal values of groups.
// Needed to create byte-correct updated files
void SetHead ( C4Group & rByGrp )
{
// Cheat away the protection
2010-01-25 04:00:59 +00:00
C4GroupHeader * pHdr = & static_cast < C4GroupEx & > ( rByGrp ) . Head ;
2009-05-08 13:28:41 +00:00
// save Entries
int Entries = Head . Entries ;
// copy
memcpy ( & Head , pHdr , sizeof ( Head ) ) ;
// restore
Head . Entries = Entries ;
}
bool HeadIdentical ( C4Group & rByGrp , bool fLax )
{
2010-01-25 04:00:59 +00:00
// Cheat away the protection while avoiding
// gcc strict aliasing violation warnings.
intptr_t iGroup = ( intptr_t ) & rByGrp ;
C4GroupHeader * pHdr = & ( ( C4GroupEx * ) iGroup ) - > Head ;
2009-05-08 13:28:41 +00:00
// overwrite entries field
int Entries = Head . Entries ;
Head . Entries = pHdr - > Entries ;
// compare
bool fIdentical = ! memcmp ( & Head , pHdr , sizeof ( C4GroupHeader ) ) ;
// restore field values
Head . Entries = Entries ;
// okay
return fIdentical ;
}
C4GroupEntryCore SavedCore ;
void SaveEntryCore ( C4Group & rByGrp , const char * szEntry )
{
C4GroupEntryCore * pCore = ( ( C4GroupEx & ) rByGrp ) . GetEntry ( szEntry ) ;
// copy core
2011-05-01 11:37:36 +00:00
SavedCore . Executable = pCore - > Executable ;
2009-05-08 13:28:41 +00:00
}
void SetSavedEntryCore ( const char * szEntry )
{
C4GroupEntryCore * pCore = GetEntry ( szEntry ) ;
// copy core
2011-05-01 11:37:36 +00:00
pCore - > Executable = SavedCore . Executable ;
2010-03-27 16:05:02 +00:00
}
2009-05-08 13:28:41 +00:00
2010-03-27 16:05:02 +00:00
void SetNoSort ( const char * szEntry )
{
2009-05-08 13:28:41 +00:00
C4GroupEntry * pEntry = GetEntry ( szEntry ) ;
2010-03-28 18:58:01 +00:00
if ( pEntry ) pEntry - > NoSort = true ;
2010-03-27 16:05:02 +00:00
}
2009-05-08 13:28:41 +00:00
// close without header update
2009-08-15 18:50:32 +00:00
bool Close ( bool fHeaderUpdate )
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
if ( fHeaderUpdate ) return C4Group : : Close ( ) ; else { bool fSuccess = Save ( false ) ; Clear ( ) ; return fSuccess ; }
2009-05-08 13:28:41 +00:00
}
} ;
// *** C4UpdatePackageCore
void C4UpdatePackageCore : : CompileFunc ( StdCompiler * pComp )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
pComp - > Value ( mkNamingAdapt ( toC4CArr ( RequireVersion ) , " RequireVersion " ) ) ;
pComp - > Value ( mkNamingAdapt ( toC4CStr ( Name ) , " Name " , " " ) ) ;
pComp - > Value ( mkNamingAdapt ( toC4CStr ( DestPath ) , " DestPath " , " " ) ) ;
pComp - > Value ( mkNamingAdapt ( GrpUpdate , " GrpUpdate " , 0 ) ) ;
pComp - > Value ( mkNamingAdapt ( UpGrpCnt , " TargetCount " , 0 ) ) ;
pComp - > Value ( mkNamingAdapt ( toC4CArrU ( GrpChks1 ) , " GrpChks1 " ) ) ;
pComp - > Value ( mkNamingAdapt ( GrpChks2 , " GrpChks2 " , 0u ) ) ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
2009-08-15 18:50:32 +00:00
bool C4UpdatePackageCore : : Load ( C4Group & hGroup )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// Load from group
StdStrBuf Source ;
2011-03-05 01:44:26 +00:00
if ( ! hGroup . LoadEntryString ( C4CFN_UpdateCore , & Source ) )
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
try
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// Compile data
CompileFromBuf < StdCompilerINIRead > ( mkNamingAdapt ( * this , " Update " ) , Source ) ;
2010-03-28 18:58:01 +00:00
}
catch ( StdCompiler : : Exception * pExc )
{
2009-05-08 13:28:41 +00:00
delete pExc ;
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
}
2010-03-28 18:58:01 +00:00
return true ;
}
2009-05-08 13:28:41 +00:00
2009-08-15 18:50:32 +00:00
bool C4UpdatePackageCore : : Save ( C4Group & hGroup )
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
try
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// decompile data
StdStrBuf Core = DecompileToBuf < StdCompilerINIWrite > ( mkNamingAdapt ( * this , " Update " ) ) ;
char * stupid_buffer = new char [ Core . getLength ( ) + 1 ] ;
memcpy ( stupid_buffer , Core . getMData ( ) , Core . getLength ( ) + 1 ) ;
// add to group
2009-08-15 18:50:32 +00:00
return hGroup . Add ( C4CFN_UpdateCore , stupid_buffer , Core . getLength ( ) , false , true ) ;
2010-03-28 18:58:01 +00:00
}
catch ( StdCompiler : : Exception * pExc )
{
2009-05-08 13:28:41 +00:00
delete pExc ;
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
}
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// *** C4UpdatePackage
2009-08-15 18:50:32 +00:00
bool C4UpdatePackage : : Load ( C4Group * pGroup )
2009-05-08 13:28:41 +00:00
{
// read update core
StdStrBuf Source ;
2011-03-05 01:44:26 +00:00
if ( ! pGroup - > LoadEntryString ( C4CFN_UpdateCore , & Source ) )
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
try
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// Compile data
CompileFromBuf < StdCompilerINIRead > ( mkNamingAdapt ( * this , " Update " ) , Source ) ;
2010-03-28 18:58:01 +00:00
}
catch ( StdCompiler : : Exception * pExc )
{
2009-05-08 13:28:41 +00:00
StdStrBuf Name = pGroup - > GetFullName ( ) + DirSep + C4CFN_UpdateCore ;
WriteLog ( " ERROR: %s (in %s) " , pExc - > Msg . getData ( ) , Name . getData ( ) ) ;
delete pExc ;
2009-08-15 18:50:32 +00:00
return false ;
2010-03-28 18:58:01 +00:00
}
2009-08-15 18:50:32 +00:00
return true ;
2009-05-08 13:28:41 +00:00
}
// #define UPDATE_DEBUG
2009-08-15 18:50:32 +00:00
bool C4UpdatePackage : : Execute ( C4Group * pGroup )
2009-05-08 13:28:41 +00:00
{
// search target
C4GroupEx TargetGrp ;
char strTarget [ _MAX_PATH ] ; SCopy ( DestPath , strTarget , _MAX_PATH ) ;
char * p = strTarget , * lp = strTarget ;
2010-03-28 18:58:01 +00:00
while ( ( p = strchr ( p + 1 , ' \\ ' ) ) )
2009-05-08 13:28:41 +00:00
{
* p = 0 ;
2010-03-28 18:58:01 +00:00
if ( ! * ( p + 1 ) ) break ;
if ( ! SEqual ( lp , " .. " ) )
2010-01-25 04:00:59 +00:00
{
2010-03-28 18:58:01 +00:00
if ( TargetGrp . Open ( strTarget ) )
2009-05-08 13:28:41 +00:00
{
// packed?
bool fPacked = TargetGrp . IsPacked ( ) ;
// Close Group
2009-08-15 18:50:32 +00:00
TargetGrp . Close ( true ) ;
2010-03-28 18:58:01 +00:00
if ( fPacked )
2009-05-08 13:28:41 +00:00
// Unpack
C4Group_UnpackDirectory ( strTarget ) ;
}
else
{
// GrpUpdate -> file must exist
2010-03-28 18:58:01 +00:00
if ( GrpUpdate ) return false ;
2009-05-08 13:28:41 +00:00
// create dir
2009-07-10 19:33:55 +00:00
CreatePath ( strTarget ) ;
2009-05-08 13:28:41 +00:00
}
2010-01-25 04:00:59 +00:00
}
2009-05-08 13:28:41 +00:00
* p = ' \\ ' ; lp = p + 1 ;
}
// try to open it
2010-03-28 18:58:01 +00:00
if ( ! TargetGrp . Open ( strTarget , ! GrpUpdate ) )
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
// check if the update is allowed
2010-03-28 18:58:01 +00:00
if ( GrpUpdate )
2009-05-08 13:28:41 +00:00
{
// check checksum
uint32_t iCRC32 ;
2011-09-15 18:16:00 +00:00
if ( ! GetFileCRC ( TargetGrp . GetFullName ( ) . getData ( ) , & iCRC32 ) )
2009-08-15 18:50:32 +00:00
return false ;
2010-03-28 18:58:01 +00:00
int i = 0 ;
for ( ; i < UpGrpCnt ; i + + )
if ( iCRC32 = = GrpChks1 [ i ] )
2009-05-08 13:28:41 +00:00
break ;
2010-03-28 18:58:01 +00:00
if ( i > = UpGrpCnt )
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
}
else
{
2011-03-13 15:16:45 +00:00
// only allow Extra.ocg-Updates
if ( ! SEqual2 ( DestPath , " Extra.ocg " ) )
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
}
// update children
char ItemFileName [ _MAX_PATH ] ;
pGroup - > ResetSearch ( ) ;
2010-03-28 18:58:01 +00:00
while ( pGroup - > FindNextEntry ( " * " , ItemFileName ) )
if ( ! SEqual ( ItemFileName , C4CFN_UpdateCore ) & & ! SEqual ( ItemFileName , C4CFN_UpdateEntries ) )
2009-05-08 13:28:41 +00:00
DoUpdate ( pGroup , & TargetGrp , ItemFileName ) ;
// do GrpUpdate
2010-03-28 18:58:01 +00:00
if ( GrpUpdate )
2009-05-08 13:28:41 +00:00
DoGrpUpdate ( pGroup , & TargetGrp ) ;
// close the group
2009-08-15 18:50:32 +00:00
TargetGrp . Close ( false ) ;
2009-05-08 13:28:41 +00:00
2010-03-28 18:58:01 +00:00
if ( GrpUpdate )
2009-05-08 13:28:41 +00:00
{
// check the result
uint32_t iResChks ;
2011-09-15 18:16:00 +00:00
if ( ! GetFileCRC ( strTarget , & iResChks ) )
2009-08-15 18:50:32 +00:00
return false ;
2010-03-28 18:58:01 +00:00
if ( iResChks ! = GrpChks2 )
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
# ifdef UPDATE_DEBUG
2009-05-08 13:28:41 +00:00
char * pData ; int iSize ;
2009-08-15 18:50:32 +00:00
CStdFile MyFile ; MyFile . Load ( strTarget , ( BYTE * * ) & pData , & iSize , 0 , true ) ;
MyFile . Create ( " DiesesDingIstMist.txt " , false ) ;
2009-05-08 13:28:41 +00:00
MyFile . Write ( pData , iSize ) ;
MyFile . Close ( ) ;
2010-03-28 18:58:01 +00:00
# endif
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
}
}
2009-08-15 18:50:32 +00:00
return true ;
2009-05-08 13:28:41 +00:00
}
2009-08-15 18:50:32 +00:00
bool C4UpdatePackage : : Optimize ( C4Group * pGroup , const char * strTarget )
2009-05-08 13:28:41 +00:00
{
// Open target group
C4GroupEx TargetGrp ;
2010-03-28 18:58:01 +00:00
if ( ! TargetGrp . Open ( strTarget ) )
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
// Both groups must be packed
2010-03-28 18:58:01 +00:00
if ( ! pGroup - > IsPacked ( ) | | ! TargetGrp . IsPacked ( ) )
{
2009-08-15 18:50:32 +00:00
TargetGrp . Close ( false ) ;
return false ;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// update children
char ItemFileName [ _MAX_PATH ] ;
pGroup - > ResetSearch ( ) ;
2010-03-28 18:58:01 +00:00
while ( pGroup - > FindNextEntry ( " * " , ItemFileName ) )
if ( ! SEqual ( ItemFileName , C4CFN_UpdateCore ) & & ! SEqual ( ItemFileName , C4CFN_UpdateEntries ) )
2009-05-08 13:28:41 +00:00
Optimize ( pGroup , & TargetGrp , ItemFileName ) ;
// set header
2010-03-28 18:58:01 +00:00
if ( TargetGrp . HeadIdentical ( * pGroup , true ) )
2009-05-08 13:28:41 +00:00
TargetGrp . SetHead ( * pGroup ) ;
// save
2009-08-15 18:50:32 +00:00
TargetGrp . Close ( false ) ;
2009-05-08 13:28:41 +00:00
// okay
2009-08-15 18:50:32 +00:00
return true ;
2009-05-08 13:28:41 +00:00
}
int C4UpdatePackage : : Check ( C4Group * pGroup )
{
// Version requirement is set
if ( RequireVersion [ 0 ] )
{
// Engine and game version must match (rest ignored)
if ( ( C4XVER1 ! = RequireVersion [ 0 ] ) | | ( C4XVER2 ! = RequireVersion [ 1 ] ) )
2010-03-28 18:58:01 +00:00
return C4UPD_CHK_BAD_VERSION ;
2009-05-08 13:28:41 +00:00
}
// only group updates have any special needs
2010-03-28 18:58:01 +00:00
if ( ! GrpUpdate ) return C4UPD_CHK_OK ;
2009-05-08 13:28:41 +00:00
// check source file
C4Group TargetGrp ;
2010-03-28 18:58:01 +00:00
if ( ! TargetGrp . Open ( DestPath ) )
2009-05-08 13:28:41 +00:00
return C4UPD_CHK_NO_SOURCE ;
2010-03-28 18:58:01 +00:00
if ( ! TargetGrp . IsPacked ( ) )
2009-05-08 13:28:41 +00:00
return C4UPD_CHK_BAD_SOURCE ;
TargetGrp . Close ( ) ;
// check source crc
uint32_t iCRC32 ;
2011-09-15 18:16:00 +00:00
if ( ! GetFileCRC ( DestPath , & iCRC32 ) )
2009-05-08 13:28:41 +00:00
return C4UPD_CHK_BAD_SOURCE ;
// equal to destination group?
2010-03-28 18:58:01 +00:00
if ( iCRC32 = = GrpChks2 )
2009-05-08 13:28:41 +00:00
// so there's nothing to do
return C4UPD_CHK_ALREADY_UPDATED ;
// check if it's one of our registered sources
2010-03-28 18:58:01 +00:00
int i = 0 ;
for ( ; i < UpGrpCnt ; i + + )
if ( iCRC32 = = GrpChks1 [ i ] )
2009-05-08 13:28:41 +00:00
break ;
2010-03-28 18:58:01 +00:00
if ( i > = UpGrpCnt )
2009-05-08 13:28:41 +00:00
return C4UPD_CHK_BAD_SOURCE ;
// ok
return C4UPD_CHK_OK ;
}
2009-08-15 18:50:32 +00:00
bool C4UpdatePackage : : DoUpdate ( C4Group * pGrpFrom , C4GroupEx * pGrpTo , const char * strFileName )
2009-05-08 13:28:41 +00:00
{
// group file?
C4Group ItemGroupFrom ;
2010-03-28 18:58:01 +00:00
if ( ItemGroupFrom . OpenAsChild ( pGrpFrom , strFileName ) )
2009-05-08 13:28:41 +00:00
{
// try to open target group
C4GroupEx ItemGroupTo ;
char strTempGroup [ _MAX_PATH + 1 ] ; strTempGroup [ 0 ] = 0 ;
2010-03-28 18:58:01 +00:00
if ( ! ItemGroupTo . OpenAsChild ( pGrpTo , strFileName , false , true ) )
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
// update children
char ItemFileName [ _MAX_PATH ] ;
ItemGroupFrom . ResetSearch ( ) ;
2010-03-28 18:58:01 +00:00
while ( ItemGroupFrom . FindNextEntry ( " * " , ItemFileName ) )
if ( ! SEqual ( ItemFileName , C4CFN_UpdateCore ) & & ! SEqual ( ItemFileName , C4CFN_UpdateEntries ) )
2009-05-08 13:28:41 +00:00
DoUpdate ( & ItemGroupFrom , & ItemGroupTo , ItemFileName ) ;
2010-03-28 18:58:01 +00:00
if ( GrpUpdate )
2009-05-08 13:28:41 +00:00
{
DoGrpUpdate ( & ItemGroupFrom , & ItemGroupTo ) ;
// write group (do not change any headers set by DoGrpUpdate!)
2009-08-15 18:50:32 +00:00
ItemGroupTo . Close ( false ) ;
2009-05-08 13:28:41 +00:00
// set core (C4Group::Save overwrites it)
pGrpTo - > SaveEntryCore ( * pGrpFrom , strFileName ) ;
pGrpTo - > SetSavedEntryCore ( strFileName ) ;
// flag as no-resort
pGrpTo - > SetNoSort ( strFileName ) ;
}
else
{
// write group
2009-08-15 18:50:32 +00:00
ItemGroupTo . Close ( true ) ;
2009-05-08 13:28:41 +00:00
// temporary group?
2010-03-28 18:58:01 +00:00
if ( strTempGroup [ 0 ] )
if ( ! pGrpTo - > Move ( strTempGroup , strFileName ) )
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
}
}
else
{
2011-08-11 13:42:00 +00:00
# ifdef _WIN32
OutputDebugString ( FormatString ( " updating %s \\ %s \n " , pGrpTo - > GetFullName ( ) . getData ( ) , strFileName ) . GetWideChar ( ) ) ;
2015-02-08 23:58:19 +00:00
# elif defined(_DEBUG)
2011-08-14 23:35:32 +00:00
printf ( " updating %s \\ %s \n " , pGrpTo - > GetFullName ( ) . getData ( ) , strFileName ) ;
2009-05-08 13:28:41 +00:00
# endif
2010-03-28 18:58:01 +00:00
if ( ! C4Group_CopyEntry ( pGrpFrom , pGrpTo , strFileName ) )
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
// set core
pGrpTo - > SaveEntryCore ( * pGrpFrom , strFileName ) ;
pGrpTo - > SetSavedEntryCore ( strFileName ) ;
}
// ok
2009-08-15 18:50:32 +00:00
return true ;
2009-05-08 13:28:41 +00:00
}
2009-08-15 18:50:32 +00:00
bool C4UpdatePackage : : DoGrpUpdate ( C4Group * pUpdateData , C4GroupEx * pGrpTo )
2009-05-08 13:28:41 +00:00
{
char * pData ;
// sort entries
2010-03-28 18:58:01 +00:00
if ( pUpdateData - > LoadEntry ( C4CFN_UpdateEntries , & pData , NULL , 1 ) )
2009-05-08 13:28:41 +00:00
{
// delete all entries that do not appear in the entries list
char strItemName [ _MAX_FNAME + 1 ] , strItemName2 [ _MAX_FNAME + 1 ] ;
pGrpTo - > ResetSearch ( ) ;
2010-03-28 18:58:01 +00:00
while ( pGrpTo - > FindNextEntry ( " * " , strItemName ) )
2009-05-08 13:28:41 +00:00
{
2009-08-15 18:50:32 +00:00
bool fGotIt = false ;
2010-03-28 18:58:01 +00:00
for ( int i = 0 ; ( fGotIt = SCopySegment ( pData , i , strItemName2 , ' | ' , _MAX_FNAME ) ) ; i + + )
2010-03-27 16:05:02 +00:00
{
2010-04-01 21:08:06 +00:00
// remove separator
2010-03-27 16:05:02 +00:00
char * pSep = strchr ( strItemName2 , ' = ' ) ;
2010-03-28 18:58:01 +00:00
if ( pSep ) * pSep = ' \0 ' ;
2010-03-27 16:05:02 +00:00
// in list?
2010-03-28 18:58:01 +00:00
if ( SEqual ( strItemName , strItemName2 ) )
2009-05-08 13:28:41 +00:00
break ;
2010-03-27 16:05:02 +00:00
}
2010-03-28 18:58:01 +00:00
if ( ! fGotIt )
2009-05-08 13:28:41 +00:00
pGrpTo - > DeleteEntry ( strItemName ) ;
}
2010-03-27 16:05:02 +00:00
// set entry times, set sort list
char strSortList [ 32767 ] = " " ;
2010-03-28 18:58:01 +00:00
for ( int i = 0 ; SCopySegment ( pData , i , strItemName , ' | ' , _MAX_FNAME ) ; i + + )
2010-03-27 16:05:02 +00:00
{
2011-05-01 11:37:36 +00:00
// strip checksum/time (if given)
2010-03-27 16:05:02 +00:00
char * pTime = strchr ( strItemName , ' = ' ) ;
2011-05-01 11:37:36 +00:00
if ( pTime ) * pTime = ' \0 ' ;
2010-03-27 16:05:02 +00:00
// copy to sort list
SAppend ( strItemName , strSortList ) ;
SAppendChar ( ' | ' , strSortList ) ;
}
2009-05-08 13:28:41 +00:00
// sort by list
pGrpTo - > Sort ( strSortList ) ;
delete [ ] pData ;
}
// copy header from update group
pGrpTo - > SetHead ( * pUpdateData ) ;
// ok
2009-08-15 18:50:32 +00:00
return true ;
2009-05-08 13:28:41 +00:00
}
2009-08-15 18:50:32 +00:00
bool C4UpdatePackage : : Optimize ( C4Group * pGrpFrom , C4GroupEx * pGrpTo , const char * strFileName )
2009-05-08 13:28:41 +00:00
{
// group file?
C4Group ItemGroupFrom ;
2010-03-28 18:58:01 +00:00
if ( ! ItemGroupFrom . OpenAsChild ( pGrpFrom , strFileName ) )
2009-08-15 18:50:32 +00:00
return true ;
2009-05-08 13:28:41 +00:00
// try to open target group
C4GroupEx ItemGroupTo ;
2010-03-28 18:58:01 +00:00
if ( ! ItemGroupTo . OpenAsChild ( pGrpTo , strFileName ) )
2009-08-15 18:50:32 +00:00
return true ;
2009-05-08 13:28:41 +00:00
// update children
char ItemFileName [ _MAX_PATH ] ;
ItemGroupFrom . ResetSearch ( ) ;
2010-03-28 18:58:01 +00:00
while ( ItemGroupFrom . FindNextEntry ( " * " , ItemFileName ) )
2009-05-08 13:28:41 +00:00
Optimize ( & ItemGroupFrom , & ItemGroupTo , ItemFileName ) ;
// set head
2010-03-28 18:58:01 +00:00
if ( ItemGroupTo . HeadIdentical ( ItemGroupFrom , true ) )
2009-05-08 13:28:41 +00:00
ItemGroupTo . SetHead ( ItemGroupFrom ) ;
// write group (do not change any headers set by DoGrpUpdate!)
2009-08-15 18:50:32 +00:00
ItemGroupTo . Close ( false ) ;
2009-05-08 13:28:41 +00:00
// set core (C4Group::Save overwrites it)
pGrpTo - > SaveEntryCore ( * pGrpFrom , strFileName ) ;
pGrpTo - > SetSavedEntryCore ( strFileName ) ;
2009-08-15 18:50:32 +00:00
return true ;
2009-05-08 13:28:41 +00:00
}
void MemScramble ( BYTE * , int ) ;
2009-08-15 18:50:32 +00:00
bool C4UpdatePackage : : MakeUpdate ( const char * strFile1 , const char * strFile2 , const char * strUpdateFile , const char * strName )
2009-05-08 13:28:41 +00:00
{
# ifdef UPDATE_DEBUG
char * pData ; int iSize ;
2009-08-15 18:50:32 +00:00
CStdFile MyFile ; MyFile . Load ( strFile2 , ( BYTE * * ) & pData , & iSize , 0 , true ) ;
MyFile . Create ( " SoIstRichtig.txt " , false ) ;
2009-05-08 13:28:41 +00:00
MyFile . Write ( pData , iSize ) ;
MyFile . Close ( ) ;
MemScramble ( ( BYTE * ) pData , iSize ) ;
2009-08-15 18:50:32 +00:00
MyFile . Create ( " UndSoAuch.txt " , false ) ;
2009-05-08 13:28:41 +00:00
MyFile . Write ( pData , iSize ) ;
MyFile . Close ( ) ;
# endif
// open Log
2010-03-28 18:58:01 +00:00
if ( ! Log . Create ( " Update.log " ) )
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
// begin message
WriteLog ( " Source: %s \n Target: %s \n Output: %s \n \n " , strFile1 , strFile2 , strUpdateFile ) ;
// open both groups
C4Group Group1 , Group2 ;
2010-03-28 18:58:01 +00:00
if ( ! Group1 . Open ( strFile1 ) ) { WriteLog ( " Error: could not open %s! \n " , strFile1 ) ; return false ; }
if ( ! Group2 . Open ( strFile2 ) ) { WriteLog ( " Error: could not open %s! \n " , strFile2 ) ; return false ; }
2009-05-08 13:28:41 +00:00
// All groups to be compared need to be packed
2009-08-15 18:50:32 +00:00
if ( ! Group1 . IsPacked ( ) ) { WriteLog ( " Error: source group %s not packed! \n " , strFile1 ) ; return false ; }
if ( ! Group2 . IsPacked ( ) ) { WriteLog ( " Error: target group %s not packed! \n " , strFile2 ) ; return false ; }
if ( Group1 . HasPackedMother ( ) ) { WriteLog ( " Error: source group %s must not have a packed mother group! \n " , strFile1 ) ; return false ; }
if ( Group2 . HasPackedMother ( ) ) { WriteLog ( " Error: target group %s must not have a packed mother group! \n " , strFile2 ) ; return false ; }
2009-05-08 13:28:41 +00:00
// create/open update-group
C4GroupEx UpGroup ;
2010-03-28 18:58:01 +00:00
if ( ! UpGroup . Open ( strUpdateFile , true ) ) { WriteLog ( " Error: could not open %s! \n " , strUpdateFile ) ; return false ; }
2009-05-08 13:28:41 +00:00
// may be continued update-file -> try to load core
UpGrpCnt = 0 ;
2009-08-15 18:50:32 +00:00
bool fContinued = C4UpdatePackageCore : : Load ( UpGroup ) ;
2009-05-08 13:28:41 +00:00
// save crc2 for later check
unsigned int iOldChks2 = GrpChks2 ;
// create core info
2010-03-28 18:58:01 +00:00
if ( strName )
2009-05-08 13:28:41 +00:00
SCopy ( strName , Name , C4MaxName ) ;
else
sprintf ( Name , " %s Update " , GetFilename ( strFile1 ) ) ;
SCopy ( strFile1 , DestPath , _MAX_PATH ) ;
2009-08-15 18:50:32 +00:00
GrpUpdate = true ;
2011-09-15 18:16:00 +00:00
if ( ! GetFileCRC ( strFile1 , & GrpChks1 [ UpGrpCnt ] ) )
2009-08-15 18:50:32 +00:00
{ WriteLog ( " Error: could not calc checksum for %s! \n " , strFile1 ) ; return false ; }
2011-09-15 18:16:00 +00:00
if ( ! GetFileCRC ( strFile2 , & GrpChks2 ) )
2009-08-15 18:50:32 +00:00
{ WriteLog ( " Error: could not calc checksum for %s! \n " , strFile2 ) ; return false ; }
2010-03-28 18:58:01 +00:00
if ( fContinued )
2009-05-08 13:28:41 +00:00
{
// continuation check: GrpChks2 matches?
2010-03-28 18:58:01 +00:00
if ( GrpChks2 ! = iOldChks2 )
// that would mess up the update result...
2009-08-15 18:50:32 +00:00
{ WriteLog ( " Error: could not add to update package - target groups don't match (checksum error) \n " ) ; return false ; }
2009-05-08 13:28:41 +00:00
// already supported by this update?
2010-03-28 18:58:01 +00:00
int i = 0 ;
for ( ; i < UpGrpCnt ; i + + )
if ( GrpChks1 [ UpGrpCnt ] = = GrpChks1 [ i ] )
2009-05-08 13:28:41 +00:00
break ;
2010-03-28 18:58:01 +00:00
if ( i < UpGrpCnt )
2009-08-15 18:50:32 +00:00
{ WriteLog ( " This update already supports the version of the source file. \n " ) ; return false ; }
2009-05-08 13:28:41 +00:00
}
UpGrpCnt + + ;
// save core
2010-03-28 18:58:01 +00:00
if ( ! C4UpdatePackageCore : : Save ( UpGroup ) )
2009-08-15 18:50:32 +00:00
{ WriteLog ( " Could not save update package core! \n " ) ; return false ; }
2009-05-08 13:28:41 +00:00
// compare groups, create update
2009-08-15 18:50:32 +00:00
bool fModified = false ;
bool fSuccess = MkUp ( & Group1 , & Group2 , & UpGroup , & fModified ) ;
2009-05-08 13:28:41 +00:00
// close (save) it
2009-08-15 18:50:32 +00:00
UpGroup . Close ( false ) ;
2009-05-08 13:28:41 +00:00
// error?
2010-03-28 18:58:01 +00:00
if ( ! fSuccess )
2009-05-08 13:28:41 +00:00
{
WriteLog ( " Update package not created. \n " ) ;
2011-08-11 13:45:27 +00:00
EraseItem ( strUpdateFile ) ;
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
}
WriteLog ( " Update package created. \n " ) ;
2009-08-15 18:50:32 +00:00
return true ;
2009-05-08 13:28:41 +00:00
}
2009-06-17 19:45:41 +00:00
extern char C4Group_TempPath [ _MAX_PATH + 1 ] ;
2009-08-15 18:50:32 +00:00
bool C4UpdatePackage : : MkUp ( C4Group * pGrp1 , C4Group * pGrp2 , C4GroupEx * pUpGrp , bool * fModified )
2009-05-08 13:28:41 +00:00
{
// (CAUTION: pGrp1 may be NULL - that means that there is no counterpart for Grp2
// in the base group)
// compare headers
2011-05-01 11:37:36 +00:00
if ( ! pGrp1 | | pGrp1 - > EntryCRC32 ( ) ! = pGrp2 - > EntryCRC32 ( ) )
2009-08-15 18:50:32 +00:00
* fModified = true ;
2009-05-08 13:28:41 +00:00
// set header
pUpGrp - > SetHead ( * pGrp2 ) ;
// compare entries
char strItemName [ _MAX_PATH ] , strItemName2 [ _MAX_PATH ] ; StdStrBuf EntryList ;
strItemName [ 0 ] = strItemName2 [ 0 ] = 0 ;
2010-03-28 18:58:01 +00:00
pGrp2 - > ResetSearch ( ) ; if ( ! * fModified ) pGrp1 - > ResetSearch ( ) ;
2009-05-08 13:28:41 +00:00
int iChangedEntries = 0 ;
2011-03-05 01:45:27 +00:00
while ( pGrp2 - > FindNextEntry ( " * " , strItemName , NULL , ! ! strItemName [ 0 ] ) )
2009-05-08 13:28:41 +00:00
{
// add to entry list
2010-03-28 18:58:01 +00:00
if ( ! ! EntryList ) EntryList . AppendChar ( ' | ' ) ;
2011-05-01 11:37:36 +00:00
EntryList . AppendFormat ( " %s=%d " , strItemName , pGrp2 - > EntryCRC32 ( strItemName ) ) ;
2009-05-08 13:28:41 +00:00
// no modification detected yet? then check order
2010-03-28 18:58:01 +00:00
if ( ! * fModified )
2009-05-08 13:28:41 +00:00
{
2011-03-05 01:45:27 +00:00
if ( ! pGrp1 - > FindNextEntry ( " * " , strItemName2 , NULL , ! ! strItemName2 [ 0 ] ) )
2010-03-28 18:58:01 +00:00
* fModified = true ;
else if ( ! SEqual ( strItemName , strItemName2 ) )
2009-08-15 18:50:32 +00:00
* fModified = true ;
2009-05-08 13:28:41 +00:00
}
// TODO: write DeleteEntries.txt
// a child group?
C4GroupEx ChildGrp2 ;
2010-03-28 18:58:01 +00:00
if ( ChildGrp2 . OpenAsChild ( pGrp2 , strItemName ) )
2009-05-08 13:28:41 +00:00
{
// open in Grp1
C4Group * pChildGrp1 = new C4GroupEx ( ) ;
2010-03-28 18:58:01 +00:00
if ( ! pGrp1 | | ! pChildGrp1 - > OpenAsChild ( pGrp1 , strItemName ) )
2009-05-08 13:28:41 +00:00
{ delete pChildGrp1 ; pChildGrp1 = NULL ; }
// open group for update data
C4GroupEx UpdGroup ; char strTempGroupName [ _MAX_FNAME + 1 ] ;
strTempGroupName [ 0 ] = 0 ;
2010-03-28 18:58:01 +00:00
if ( ! UpdGroup . OpenAsChild ( pUpGrp , strItemName ) )
2009-05-08 13:28:41 +00:00
{
// create new group (may be temporary)
2009-06-17 19:45:41 +00:00
if ( C4Group_TempPath [ 0 ] ) { SCopy ( C4Group_TempPath , strTempGroupName , _MAX_FNAME ) ; SAppend ( " ~upd " , strTempGroupName , _MAX_FNAME ) ; }
else SCopy ( " ~upd " , strTempGroupName , _MAX_FNAME ) ;
2009-05-08 13:28:41 +00:00
MakeTempFilename ( strTempGroupName ) ;
2010-03-28 18:58:01 +00:00
if ( ! UpdGroup . Open ( strTempGroupName , true ) ) { delete pChildGrp1 ; WriteLog ( " Error: could not create temp group \n " ) ; return false ; }
2009-05-08 13:28:41 +00:00
}
// do nested MkUp-search
2009-08-15 18:50:32 +00:00
bool Modified = false ;
bool fSuccess = MkUp ( pChildGrp1 , & ChildGrp2 , & UpdGroup , & Modified ) ;
2009-05-08 13:28:41 +00:00
// sort & close
extern const char * * C4Group_SortList ;
UpdGroup . SortByList ( C4Group_SortList , ChildGrp2 . GetName ( ) ) ;
2009-08-15 18:50:32 +00:00
UpdGroup . Close ( false ) ;
2011-05-01 11:37:36 +00:00
// check entry crcs
if ( ! pGrp1 | | ( pGrp1 - > EntryCRC32 ( strItemName ) ! = pGrp2 - > EntryCRC32 ( strItemName ) ) )
2009-08-15 18:50:32 +00:00
Modified = true ;
2009-05-08 13:28:41 +00:00
// add group (if modified)
2010-03-28 18:58:01 +00:00
if ( fSuccess & & Modified )
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
if ( strTempGroupName [ 0 ] )
if ( ! pUpGrp - > Move ( strTempGroupName , strItemName ) )
2009-05-08 13:28:41 +00:00
{
WriteLog ( " Error: could not add modified group \n " ) ;
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
}
// copy core
pUpGrp - > SaveEntryCore ( * pGrp2 , strItemName ) ;
pUpGrp - > SetSavedEntryCore ( strItemName ) ;
// got a modification in a subgroup
2009-08-15 18:50:32 +00:00
* fModified = true ;
2009-05-08 13:28:41 +00:00
iChangedEntries + + ;
}
else
// delete group (do not remove groups that existed before!)
2010-03-28 18:58:01 +00:00
if ( strTempGroupName [ 0 ] )
2011-08-11 13:45:27 +00:00
if ( ! EraseItem ( strTempGroupName ) )
2010-03-28 18:58:01 +00:00
{ WriteLog ( " Error: could not delete temporary directory \n " ) ; return false ; }
2009-05-08 13:28:41 +00:00
delete pChildGrp1 ;
}
else
{
// compare them (size & crc32)
2010-03-28 18:58:01 +00:00
if ( ! pGrp1 | |
pGrp1 - > EntrySize ( strItemName ) ! = pGrp2 - > EntrySize ( strItemName ) | |
pGrp1 - > EntryCRC32 ( strItemName ) ! = pGrp2 - > EntryCRC32 ( strItemName ) )
2009-05-08 13:28:41 +00:00
{
2009-08-15 18:50:32 +00:00
bool fCopied = false ;
2009-05-08 13:28:41 +00:00
// save core (EntryCRC32 might set additional fields)
pUpGrp - > SaveEntryCore ( * pGrp2 , strItemName ) ;
// already in update grp?
2011-05-01 11:37:36 +00:00
if ( pUpGrp - > EntrySize ( strItemName ) ! = pGrp2 - > EntrySize ( strItemName ) | |
2010-03-28 18:58:01 +00:00
pUpGrp - > EntryCRC32 ( strItemName ) ! = pGrp2 - > EntryCRC32 ( strItemName ) )
2009-05-08 13:28:41 +00:00
{
// copy it
2010-03-28 18:58:01 +00:00
if ( ! C4Group_CopyEntry ( pGrp2 , pUpGrp , strItemName ) )
2009-05-08 13:28:41 +00:00
{
WriteLog ( " Error: could not add changed entry to update group \n " ) ;
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
}
// set entry core
pUpGrp - > SetSavedEntryCore ( strItemName ) ;
// modified...
2009-08-15 18:50:32 +00:00
* fModified = true ;
fCopied = true ;
2009-05-08 13:28:41 +00:00
}
iChangedEntries + + ;
WriteLog ( " %s \\ %s: update%s \n " , pGrp2 - > GetFullName ( ) . getData ( ) , strItemName , fCopied ? " " : " (already in group) " ) ;
}
}
}
// write entries list (always)
2010-03-28 18:58:01 +00:00
if ( ! pUpGrp - > Add ( C4CFN_UpdateEntries , EntryList , false , true ) )
2009-05-08 13:28:41 +00:00
{
WriteLog ( " Error: could not save entry list! " ) ;
2009-08-15 18:50:32 +00:00
return false ;
2009-05-08 13:28:41 +00:00
}
if ( iChangedEntries > 0 )
WriteLog ( " %s: %d/%d changed (%s) \n " , pGrp2 - > GetFullName ( ) . getData ( ) , iChangedEntries , pGrp2 - > EntryCount ( ) , * fModified ? " update " : " skip " ) ;
// success
2009-08-15 18:50:32 +00:00
return true ;
2009-05-08 13:28:41 +00:00
}
void C4UpdatePackage : : WriteLog ( const char * strMsg , . . . )
{
va_list arglist ; va_start ( arglist , strMsg ) ;
char strOutp [ 1024 ] ;
vsprintf ( strOutp , strMsg , arglist ) ;
Log . Write ( strOutp , strlen ( strOutp ) ) ;
Log . Flush ( ) ;
2009-06-17 20:07:52 +00:00
: : Log ( strOutp ) ;
2009-05-08 13:28:41 +00:00
}