/*==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 < http : //www.gnu.org/licenses/>.
Additional permissions under GNU GPL version 3 section 7
If you modify this Program , or any covered work , by linking or
combining it with any of RAD Game Tools Bink SDK , Autodesk 3 ds Max SDK ,
NVIDIA PhysX SDK , Microsoft DirectX SDK , OpenSSL library , Independent
JPEG Group JPEG library , Microsoft Windows Media SDK , or Apple QuickTime SDK
( or a modified version of those libraries ) ,
containing parts covered by the terms of the Bink SDK EULA , 3 ds Max EULA ,
PhysX SDK EULA , DirectX SDK EULA , OpenSSL and SSLeay licenses , IJG
JPEG Library README , Windows Media SDK EULA , or QuickTime SDK EULA , the
licensors of this Program grant you additional
permission to convey the resulting work . Corresponding Source for a
non - source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work .
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 = = */
/*****************************************************************************
*
* $ / Plasma20 / Sources / Plasma / Apps / plClientPatcher / UruPlayer . cpp
*
* * */
# include "Pch.h"
# include "plStatusLog/plStatusLog.h"
# include <queue>
# pragma hdrstop
/*****************************************************************************
*
* Private
*
* * */
# ifndef PLASMA_EXTERNAL_RELEASE
static const wchar_t s_manifest [ ] = L " Internal " ;
static const wchar_t s_thinmanifest [ ] = L " ThinInternal " ;
# else
static const wchar_t s_manifest [ ] = L " External " ;
static const wchar_t s_thinmanifest [ ] = L " ThinExternal " ;
# endif
struct ManifestFile
{
ManifestFile ( const wchar_t clientName [ ] , const wchar_t downloadName [ ] , const wchar_t md5val [ ] , int flags , plLauncherInfo * info )
{
StrCopy ( filename , clientName , arrsize ( filename ) ) ;
StrCopy ( zipName , downloadName , arrsize ( zipName ) ) ;
StrCopy ( md5 , md5val , arrsize ( md5 ) ) ;
this - > flags = flags ;
this - > info = info ;
md5failed = false ;
}
wchar_t filename [ MAX_PATH ] ;
wchar_t zipName [ MAX_PATH ] ;
wchar_t md5 [ MAX_PATH ] ;
int flags ;
bool md5failed ;
plLauncherInfo * info ;
} ;
class ProgressStream : public plZlibStream {
public :
virtual uint32_t Write ( uint32_t byteCount , const void * buffer ) ;
static plLauncherInfo * info ;
static long totalBytes ;
static unsigned progress ;
// for updating bytes per second
static uint32_t startTime ;
} ;
struct ProcessManifestEntryParam {
struct ManifestResult * mr ;
unsigned index ;
static long totalSize ;
static long progress ;
static double startTime ;
bool exists ; // marked as true if the file exists before MD5 check
} ;
struct ManifestResult {
wchar_t group [ MAX_PATH ] ;
ARRAY ( NetCliFileManifestEntry ) manifest ;
long * indicator ;
plLauncherInfo * info ;
hsMutex critsect ;
ARRAY ( unsigned ) indices ;
} ;
static void DownloadCallback (
ENetError result ,
void * param ,
const wchar_t filename [ ] ,
hsStream * writer
) ;
/*****************************************************************************
*
* Private data
*
* * */
static const unsigned kMaxManifestFileRequests = 5 ;
static const unsigned kMinThreads = 16 ;
static const unsigned kMaxThreads = 64 ;
static unsigned s_fileRequests ;
static unsigned s_fileListRequests ;
static bool s_patchComplete ;
static PROCESS_INFORMATION s_pi ;
static long s_numFiles ;
static char s_workingDir [ MAX_PATH ] ;
static bool s_patchError ;
static long s_asyncCoreInitCount ;
static long s_numConnectFailures ;
static bool s_running ;
static std : : queue < ManifestFile * > manifestQueue ;
//static AsyncThreadTaskList * s_taskList;
// error strings
static const char s_fileOpenError [ ] = " Unable to create file. Hard drive may be full. " ;
static const char s_md5CheckError [ ] = " Error downloading file from server, exiting... " ;
enum {
kPerfThreadTaskCount ,
kNumPerf
} ;
static long s_perf [ kNumPerf ] ;
long ProgressStream : : totalBytes ;
unsigned ProgressStream : : progress ;
plLauncherInfo * ProgressStream : : info ;
uint32_t ProgressStream : : startTime = 0 ;
long ProcessManifestEntryParam : : progress = 0 ;
long ProcessManifestEntryParam : : totalSize = 0 ;
double ProcessManifestEntryParam : : startTime = 0 ;
/*****************************************************************************
*
* Exported data
*
* * */
// IMPORTANT: This string may NEVER change. Doing so will break self-patching,
// leaving clients with older patchers "dead in the water", without
// a way to play Uru.
# ifdef PLASMA_EXTERNAL_RELEASE
const wchar_t kPatcherExeFilename [ ] = L " UruLauncher.exe " ;
# else
const wchar_t kPatcherExeFilename [ ] = L " plUruLauncher.exe " ;
# endif
//============================================================================
// External client file list
//============================================================================
# ifdef PLASMA_EXTERNAL_RELEASE
# ifdef HS_DEBUGGING
static wchar_t s_clientExeName [ ] = L " plClient_dbg.exe " ;
# else
static wchar_t s_clientExeName [ ] = L " UruExplorer.exe " ;
# endif // HS_DEBUGGING
//============================================================================
// Internal client file list
//============================================================================
# else
# ifdef HS_DEBUGGING
static wchar_t s_clientExeName [ ] = L " plClient_dbg.exe " ;
# else
static wchar_t s_clientExeName [ ] = L " plClient.exe " ;
# endif // HS_DEBUGGING
# endif // PLASMA_EXTERNAL_RELEASE
/*****************************************************************************
*
* Private Functions
*
* * */
//============================================================================
static void NetErrorHandler ( ENetProtocol protocol , ENetError error ) {
const wchar_t * srv ;
switch ( protocol ) {
case kNetProtocolNil : srv = L " Notify " ; break ;
case kNetProtocolCli2File : srv = L " File " ; break ;
case kNetProtocolCli2GateKeeper : srv = L " GateKeeper " ; break ;
DEFAULT_FATAL ( protocol ) ;
}
switch ( error ) {
case kNetErrConnectFailed :
case kNetErrTimeout :
+ + s_numConnectFailures ;
break ;
case kNetErrDisconnected :
s_patchError = true ;
break ;
case kNetErrServerBusy :
MessageBox ( 0 , " Due to the high demand, the server is currently busy. Please try again later, or for alternative download options visit: http://www.mystonline.com/play/ " , " UruLauncher " , MB_OK ) ;
s_running = false ;
break ;
}
plString msg = plString : : Format ( " NetErr: %S: %S " , srv , NetErrorToString ( error ) ) ;
plStatusLog : : AddLineS ( " patcher.log " , msg . c_str ( ) ) ;
// Notify GameTap something bad happened.
if ( ! s_patchError ) {
MessageBox (
nil ,
" Unable to connect to server. " ,
" Error " ,
MB_ICONERROR
) ;
s_patchError = true ;
}
/*AsyncAppCallback(
kPlayerNotifyFailed ,
kCmdResultFailed ,
( void * ) NetErrorToString ( error )
) ; */
}
/*
//============================================================================
static void WaitUruExitProc ( void * param ) {
# ifdef USE_VLD
VLDEnable ( ) ;
# endif
plLauncherInfo * info = ( plLauncherInfo * ) param ;
WaitForSingleObject ( s_pi . hProcess , INFINITE ) ;
DWORD exitcode ;
GetExitCodeProcess ( s_pi . hProcess , & exitcode ) ;
CloseHandle ( s_pi . hThread ) ;
CloseHandle ( s_pi . hProcess ) ;
if ( exitcode = = kExitCodeTerminated ) {
info - > stopCallback ( kStatusOk , nil ) ; // notify of succesful stop
}
else {
info - > exitCallback ( kStatusOk , nil ) ;
}
}
*/
//============================================================================
static bool MD5Check ( const char filename [ ] , const wchar_t md5 [ ] ) {
// Do md5 check
char md5copy [ MAX_PATH ] ;
plMD5Checksum existingMD5 ( filename ) ;
plMD5Checksum latestMD5 ;
StrToAnsi ( md5copy , md5 , arrsize ( md5copy ) ) ;
latestMD5 . SetFromHexString ( md5copy ) ;
return ( existingMD5 = = latestMD5 ) ;
}
//============================================================================
static void DecompressOgg ( ManifestFile * mf ) {
unsigned flags = mf - > flags ;
for ( ; ; )
{
// decompress ogg if necessary
if ( ( hsCheckBits ( flags , plManifestFile : : kSndFlagCacheSplit ) | | hsCheckBits ( flags , plManifestFile : : kSndFlagCacheStereo ) ) )
{
char path [ MAX_PATH ] ;
StrPrintf ( path , arrsize ( path ) , " %s%S " , s_workingDir , mf - > filename ) ;
plAudioFileReader * reader = plAudioFileReader : : CreateReader ( path , plAudioCore : : kAll , plAudioFileReader : : kStreamNative ) ;
if ( ! reader )
{
break ;
}
uint32_t size = reader - > GetDataSize ( ) ;
delete reader ;
ULARGE_INTEGER freeBytesAvailable , totalNumberOfBytes , neededBytes ;
if ( GetDiskFreeSpaceEx ( NULL , & freeBytesAvailable , & totalNumberOfBytes , NULL ) )
{
neededBytes . HighPart = 0 ;
neededBytes . LowPart = size ;
if ( neededBytes . QuadPart > freeBytesAvailable . QuadPart )
{
//PatcherLog(kInfo, "Not enough disk space (asked for %d bytes)", bytesNeeded);
break ;
}
}
if ( hsCheckBits ( flags , plManifestFile : : kSndFlagCacheSplit ) )
plAudioFileReader : : CacheFile ( path , true , true ) ;
if ( hsCheckBits ( flags , plManifestFile : : kSndFlagCacheStereo ) )
plAudioFileReader : : CacheFile ( path , false , true ) ;
}
break ;
}
}
//============================================================================
void Shutdown ( plLauncherInfo * info ) {
info - > SetText ( " Shutting Down... " ) ;
s_patchError = true ;
s_running = false ;
}
//============================================================================
static void RequestNextManifestFile ( ) {
bool success = true ;
if ( ! manifestQueue . size ( ) )
return ;
ManifestFile * nextfile = manifestQueue . front ( ) ;
manifestQueue . pop ( ) ;
char path [ MAX_PATH ] ;
wchar_t basePath [ MAX_PATH ] ;
StrPrintf ( path , arrsize ( path ) , " %s%S " , s_workingDir , nextfile - > filename ) ;
StrToUnicode ( basePath , path , arrsize ( basePath ) ) ;
PathRemoveFilename ( basePath , basePath , arrsize ( basePath ) ) ;
PathCreateDirectory ( basePath , kPathCreateDirFlagEntireTree ) ;
ProgressStream * writer = new ProgressStream ( ) ; // optimization: dont delete and recreate. Doesn't seem to be working currently, ZLibStream is breaking
if ( ! writer - > Open ( path , " wb " ) )
{
writer - > Close ( ) ;
delete writer ;
success = false ;
}
if ( success )
{
# ifndef PLASMA_EXTERNAL_RELEASE
char text [ 256 ] ;
StrPrintf ( text , arrsize ( text ) , " Updating URU... %S " , nextfile - > filename ) ;
nextfile - > info - > SetText ( text ) ;
# endif
NetCliFileDownloadRequest ( nextfile - > zipName , writer , DownloadCallback , nextfile , nextfile - > info - > buildId ) ;
}
}
//============================================================================
static void DownloadCallback (
ENetError result ,
void * param ,
const wchar_t filename [ ] ,
hsStream * writer
) {
s_numConnectFailures = 0 ;
ManifestFile * mf = ( ManifestFile * ) param ;
if ( IS_NET_ERROR ( result ) & & s_running & & ! s_patchError ) {
if ( result = = kNetErrFileNotFound ) {
char str [ 256 ] ;
StrPrintf ( str , arrsize ( str ) , " File not found on server: %S " , filename ) ;
MessageBox ( nil , str , " URU Launcher " , MB_ICONERROR ) ;
s_patchError = true ;
}
else if ( result = = kNetErrRemoteShutdown ) {
s_patchError = true ;
}
else {
// failed, re-queue the file to be downloaded
// (after rewinding the stream)
writer - > Rewind ( ) ;
plLauncherInfo * info = mf - > info ;
NetCliFileDownloadRequest ( filename , writer , DownloadCallback , mf , info - > buildId ) ;
return ;
}
}
writer - > Close ( ) ;
delete writer ; // delete our stream
char path [ MAX_PATH ] ;
StrPrintf (
path ,
arrsize ( path ) ,
" %s%S " ,
s_workingDir ,
mf - > filename
) ;
if ( s_running )
{
if ( ! MD5Check ( path , mf - > md5 ) ) {
if ( mf - > md5failed )
{
# ifdef PLASMA_EXTERNAL_RELEASE
MessageBox ( nil , s_md5CheckError , " URU Launcher " , MB_ICONERROR ) ;
# else
char str [ 256 ] ;
StrPrintf ( str , arrsize ( str ) , " %s %s " , path , s_md5CheckError ) ;
MessageBox ( nil , str , " URU Launcher " , MB_ICONERROR ) ;
# endif // PLASMA_EXTERNAL_RELEASE
Shutdown ( mf - > info ) ;
}
writer = new ProgressStream ( ) ;
if ( ! writer - > Open ( path , " wb " ) ) {
# ifdef PLASMA_EXTERNAL_RELEASE
MessageBox ( nil , s_fileOpenError , " URU Launcher " , MB_ICONERROR ) ;
# else
char str [ 256 ] ;
StrPrintf ( str , arrsize ( str ) , " %s %s " , s_fileOpenError , path ) ;
MessageBox ( nil , str , " URU Launcher " , MB_ICONERROR ) ;
# endif // PLASMA_EXTERNAL_RELEASE
Shutdown ( mf - > info ) ;
}
mf - > md5failed = true ;
plLauncherInfo * info = mf - > info ;
NetCliFileDownloadRequest ( filename , writer , DownloadCallback , mf , info - > buildId ) ;
return ;
}
}
AtomicAdd ( & s_numFiles , - 1 ) ;
if ( s_running )
{
wchar_t ext [ MAX_EXT ] ;
PathSplitPath ( mf - > filename , nil , nil , nil , ext ) ;
if ( ! StrCmpI ( L " .ogg " , ext ) )
{
DecompressOgg ( mf ) ;
}
}
delete mf ; // delete manifest file entry
// if we are not still running don't request any more file downloads
if ( s_running )
{
if ( ! s_numFiles ) {
s_patchComplete = true ;
}
else
{
RequestNextManifestFile ( ) ;
}
}
}
//============================================================================
static void ProcessManifestEntry ( void * param , ENetError error ) {
ProcessManifestEntryParam * p = ( ProcessManifestEntryParam * ) param ;
# ifndef PLASMA_EXTERNAL_RELEASE
char text [ 256 ] ;
StrPrintf ( text , arrsize ( text ) , " Checking for updates... %S " , p - > mr - > manifest [ p - > index ] . clientName ) ;
p - > mr - > info - > SetText ( text ) ;
# endif
char path [ MAX_PATH ] ;
StrPrintf (
path ,
arrsize ( path ) ,
" %s%S " ,
s_workingDir ,
p - > mr - > manifest [ p - > index ] . clientName
) ;
uint32_t start = ( uint32_t ) ( TimeGetTime ( ) / kTimeIntervalsPerMs ) ;
if ( ! MD5Check ( path , p - > mr - > manifest [ p - > index ] . md5 ) ) {
p - > mr - > critsect . Lock ( ) ;
p - > mr - > indices . Add ( p - > index ) ;
p - > mr - > critsect . Unlock ( ) ;
AtomicAdd ( & ProgressStream : : totalBytes , p - > mr - > manifest [ p - > index ] . zipSize ) ;
}
// if we have a file that was cached the MD5 check will be really fast throwing off our approx time remaining.
uint32_t t = ( uint32_t ) ( TimeGetTime ( ) / kTimeIntervalsPerMs - start ) ;
if ( t < 25 )
{
// cached file
AtomicAdd ( & ProcessManifestEntryParam : : totalSize , - p - > mr - > manifest [ p - > index ] . zipSize ) ;
p - > exists = false ;
}
// p->mr->info->SetBytesRemaining(ProcessManifestEntryParam::totalSize); // for testing purposes only
if ( p - > exists )
{
AtomicAdd ( & ProcessManifestEntryParam : : progress , p - > mr - > manifest [ p - > index ] . zipSize ) ;
PatchInfo patchInfo ;
patchInfo . stage = 0 ;
patchInfo . progressStage = 0 ;
patchInfo . progress = ( unsigned ) ( ( float ) ( ProcessManifestEntryParam : : progress ) / ( float ) ProcessManifestEntryParam : : totalSize * 1000.0f ) ;
p - > mr - > info - > progressCallback ( kStatusPending , & patchInfo ) ;
if ( ProcessManifestEntryParam : : progress > ProcessManifestEntryParam : : totalSize )
{
p - > mr - > info - > SetTimeRemaining ( 0 ) ;
}
else
{
if ( TimeGetTime ( ) / kTimeIntervalsPerMs ! = ProcessManifestEntryParam : : startTime )
{
double timeElapsed = ( TimeGetTime ( ) / kTimeIntervalsPerMs - ProcessManifestEntryParam : : startTime ) / 1000 ;
double bytesPerSec = ( float ) ( ProcessManifestEntryParam : : progress ) / timeElapsed ;
p - > mr - > info - > SetTimeRemaining ( bytesPerSec ? ( int ) ( ( ProcessManifestEntryParam : : totalSize - ProcessManifestEntryParam : : progress ) / bytesPerSec ) : 0 ) ;
}
}
}
}
//============================================================================
static void ProcessManifest ( void * param ) {
# ifdef USE_VLD
VLDEnable ( ) ;
# endif
wchar_t basePath [ MAX_PATH ] ;
char path [ MAX_PATH ] ;
AtomicAdd ( & s_perf [ kPerfThreadTaskCount ] , 1 ) ;
ManifestResult * mr = ( ManifestResult * ) param ;
PatchInfo patchInfo ;
patchInfo . stage = 0 ;
patchInfo . progressStage = 0 ;
patchInfo . progress = 0 ;
mr - > info - > progressCallback ( kStatusPending , & patchInfo ) ;
char text [ 256 ] ;
StrPrintf ( text , arrsize ( text ) , " Checking for updates... " ) ;
mr - > info - > SetText ( text ) ;
unsigned entryCount = mr - > manifest . Count ( ) ;
NetCliFileManifestEntry * manifest = mr - > manifest . Ptr ( ) ;
FILE * fd = nil ;
ARRAY ( ProcessManifestEntryParam ) params ;
params . Reserve ( mr - > manifest . Count ( ) ) ;
for ( unsigned i = 0 ; i < entryCount ; + + i ) {
ProcessManifestEntryParam * p = params . New ( ) ;
p - > index = i ;
p - > mr = mr ;
p - > exists = false ;
StrPrintf ( path , arrsize ( path ) , " %s%S " , s_workingDir , mr - > manifest [ i ] . clientName ) ;
fd = fopen ( path , " r " ) ;
if ( fd )
{
p - > exists = true ;
p - > totalSize + = p - > mr - > manifest [ i ] . zipSize ;
fclose ( fd ) ;
}
}
ProcessManifestEntryParam : : startTime = ( double ) ( TimeGetTime ( ) / kTimeIntervalsPerMs ) ;
for ( unsigned i = 0 ; i < entryCount & & s_running ; + + i ) {
ProcessManifestEntry ( & params [ i ] , kNetSuccess ) ;
}
if ( s_running )
{
PatchInfo patchInfo ;
patchInfo . stage = 0 ;
patchInfo . progressStage = 0 ;
patchInfo . progress = 1000 ;
mr - > info - > progressCallback ( kStatusPending , & patchInfo ) ;
AtomicAdd ( & s_numFiles , mr - > indices . Count ( ) ) ;
if ( ! s_numFiles | | ! s_running ) {
s_patchComplete = true ;
}
else {
mr - > info - > SetText ( " Updating URU... " ) ;
PatchInfo patchInfo ;
patchInfo . stage = 0 ;
patchInfo . progressStage = 0 ;
patchInfo . progress = 0 ;
mr - > info - > progressCallback ( kStatusPending , & patchInfo ) ;
for ( unsigned i = 0 ; i < mr - > indices . Count ( ) ; + + i )
{
if ( s_running )
{
unsigned index = mr - > indices [ i ] ;
StrPrintf ( path , arrsize ( path ) , " %s%S " , s_workingDir , manifest [ index ] . clientName ) ;
StrToUnicode ( basePath , path , arrsize ( basePath ) ) ;
PathRemoveFilename ( basePath , basePath , arrsize ( basePath ) ) ;
PathCreateDirectory ( basePath , kPathCreateDirFlagEntireTree ) ;
ManifestFile * mf = new ManifestFile (
manifest [ index ] . clientName ,
manifest [ index ] . downloadName ,
manifest [ index ] . md5 ,
manifest [ index ] . flags ,
mr - > info
) ;
if ( i < kMaxManifestFileRequests ) {
ProgressStream * stream = new ProgressStream ;
if ( ! stream - > Open ( path , " wb " ) ) {
# ifdef PLASMA_EXTERNAL_RELEASE
MessageBox ( nil , s_fileOpenError , " URU Launcher " , MB_ICONERROR ) ;
# else
char str [ 256 ] ;
StrPrintf ( str , arrsize ( str ) , " %s %s " , path , s_fileOpenError ) ;
MessageBox ( nil , str , " URU Launcher " , MB_ICONERROR ) ;
# endif
Shutdown ( mr - > info ) ;
}
# ifndef PLASMA_EXTERNAL_RELEASE
char text [ 256 ] ;
StrPrintf ( text , arrsize ( text ) , " Updating URU... %S " , manifest [ i ] . clientName ) ;
mr - > info - > SetText ( text ) ;
# endif
// fire off our initial requests. The remaining will be added as files are downloaded
NetCliFileDownloadRequest ( mf - > zipName , stream , DownloadCallback , mf , mr - > info - > buildId ) ;
}
else {
// queue up this file download
manifestQueue . push ( mf ) ;
}
}
}
}
}
delete mr ;
AtomicAdd ( & s_perf [ kPerfThreadTaskCount ] , - 1 ) ;
}
//============================================================================
static void ManifestCallback (
ENetError result ,
void * param ,
const wchar_t group [ ] ,
const NetCliFileManifestEntry manifest [ ] ,
unsigned entryCount
) {
s_numConnectFailures = 0 ;
plLauncherInfo * info = ( plLauncherInfo * ) param ;
if ( ! s_running | | IS_NET_ERROR ( result ) ) {
if ( s_running & & ! s_patchError ) {
switch ( result ) {
case kNetErrTimeout :
NetCliFileManifestRequest ( ManifestCallback , param , group ) ;
break ;
default : {
char str [ 256 ] ;
StrPrintf ( str , arrsize ( str ) , " Failed to download manifest from server " ) ;
MessageBox ( nil , str , " URU Launcher " , MB_ICONERROR ) ;
s_patchError = true ;
}
break ;
}
}
return ;
}
ManifestResult * mr = new ManifestResult ( ) ;
StrCopy ( mr - > group , group , arrsize ( mr - > group ) ) ;
mr - > manifest . Set ( manifest , entryCount ) ;
mr - > info = info ;
// sort our requests by size(this must be done for the next step to work)
QSORT (
NetCliFileManifestEntry ,
mr - > manifest . Ptr ( ) ,
mr - > manifest . Count ( ) ,
elem1 . fileSize > elem2 . fileSize
) ;
// remove duplicate entries. This can cause some bad problems if not done. It will cause MD5 checks to fail, since it can be writing a file while MD5 checking it.
ARRAY ( NetCliFileManifestEntry ) noDuplicates ;
noDuplicates . Reserve ( mr - > manifest . Count ( ) ) ;
for ( unsigned i = 0 ; i < entryCount - 1 ; + + i )
{
if ( StrCmp ( mr - > manifest [ i ] . clientName , mr - > manifest [ i + 1 ] . clientName ) )
{
noDuplicates . Add ( mr - > manifest [ i ] ) ;
}
}
noDuplicates . Add ( mr - > manifest [ entryCount - 1 ] ) ;
// adjust our array and set data
mr - > manifest . ShrinkBy ( mr - > manifest . Count ( ) - noDuplicates . Count ( ) ) ;
mr - > manifest . Set ( noDuplicates . Ptr ( ) , noDuplicates . Count ( ) ) ;
( void ) _beginthread ( ProcessManifest , 0 , mr ) ;
}
//============================================================================
static void ThinManifestCallback (
ENetError result ,
void * param ,
const wchar_t group [ ] ,
const NetCliFileManifestEntry manifest [ ] ,
unsigned entryCount
) {
s_numConnectFailures = 0 ;
plLauncherInfo * info = ( plLauncherInfo * ) param ;
char text [ 256 ] ;
StrPrintf ( text , arrsize ( text ) , " Checking for updates... " ) ;
info - > SetText ( text ) ;
if ( ! s_running | | IS_NET_ERROR ( result ) ) {
if ( s_running & & ! s_patchError ) {
switch ( result ) {
case kNetErrTimeout :
NetCliFileManifestRequest ( ManifestCallback , param , group ) ;
break ;
default : {
char str [ 256 ] ;
StrPrintf ( str , arrsize ( str ) , " Failed to download manifest from server " ) ;
MessageBox ( nil , str , " URU Launcher " , MB_ICONERROR ) ;
s_patchError = true ;
}
break ;
}
}
return ;
}
s_patchComplete = true ;
char path [ MAX_PATH ] ;
for ( unsigned i = 0 ; i < entryCount ; + + i ) {
if ( ! s_running ) return ;
StrPrintf ( path , arrsize ( path ) , " %s%S " , s_workingDir , manifest [ i ] . clientName ) ;
if ( ! MD5Check ( path , manifest [ i ] . md5 ) ) {
s_patchComplete = false ;
NetCliFileManifestRequest ( ManifestCallback , info , s_manifest , info - > buildId ) ;
break ;
}
PatchInfo patchInfo ;
patchInfo . stage = 0 ;
patchInfo . progressStage = 0 ;
patchInfo . progress = ( unsigned ) ( ( float ) i / ( float ) entryCount * 1000.0f ) ;
info - > progressCallback ( kStatusPending , & patchInfo ) ;
# ifndef PLASMA_EXTERNAL_RELEASE
char text [ 256 ] ;
StrPrintf ( text , arrsize ( text ) , " Checking for updates... %S " , manifest [ i ] . clientName ) ;
info - > SetText ( text ) ;
# endif
}
}
/*****************************************************************************
*
* ProgressStream Functions
*
* * */
//============================================================================
uint32_t ProgressStream : : Write ( uint32_t byteCount , const void * buffer ) {
if ( ! s_running | | s_patchError )
return 0 ;
if ( ! startTime ) {
startTime = TimeGetSecondsSince2001Utc ( ) ;
}
progress + = byteCount ;
float p = ( float ) progress / ( float ) totalBytes * 1000 ; // progress
PatchInfo patchInfo ;
patchInfo . stage = 1 ;
patchInfo . progress = ( unsigned ) p ;
patchInfo . progressStage = 50 ;
info - > progressCallback ( kStatusPending , ( void * ) & patchInfo ) ;
// there seems to, sometimes, be a slight discrepency in progress and totalBytes.
if ( progress > totalBytes )
{
info - > SetBytesRemaining ( 0 ) ;
info - > SetTimeRemaining ( 0 ) ;
}
else
{
info - > SetBytesRemaining ( totalBytes - progress ) ;
if ( TimeGetSecondsSince2001Utc ( ) ! = startTime )
{
uint32_t bytesPerSec = ( progress ) / ( TimeGetSecondsSince2001Utc ( ) - startTime ) ;
info - > SetTimeRemaining ( bytesPerSec ? ( totalBytes - progress ) / bytesPerSec : 0 ) ;
}
}
return plZlibStream : : Write ( byteCount , buffer ) ;
}
//============================================================================
static void FileSrvIpAddressCallback (
ENetError result ,
void * param ,
const wchar_t addr [ ]
) {
NetCliGateKeeperDisconnect ( ) ;
if ( IS_NET_ERROR ( result ) ) {
plString msg = plString : : Format ( " FileSrvIpAddressRequest failed: %S " , NetErrorToString ( result ) ) ;
plStatusLog : : AddLineS ( " patcher.log " , msg . c_str ( ) ) ;
s_patchError = true ;
return ;
}
plLauncherInfo * info = ( plLauncherInfo * ) param ;
// Start connecting to the server
const char * caddr = hsWStringToString ( addr ) ;
NetCliFileStartConnect ( & caddr , 1 , true ) ;
delete [ ] caddr ;
NetCliFileManifestRequest ( ThinManifestCallback , info , s_thinmanifest , info - > buildId ) ;
ProgressStream : : info = info ;
PatchInfo patchInfo ;
patchInfo . stage = 0 ;
patchInfo . progressStage = 0 ;
patchInfo . progress = 0 ;
info - > progressCallback ( kStatusPending , & patchInfo ) ;
}
/*****************************************************************************
*
* Public Functions
*
* * */
//============================================================================
void InitAsyncCore ( ) {
if ( AtomicAdd ( & s_asyncCoreInitCount , 1 ) > 0 )
return ;
AsyncCoreInitialize ( ) ;
wchar_t productString [ 256 ] ;
ProductString ( productString , arrsize ( productString ) ) ;
char * log = hsWStringToString ( productString ) ;
plStatusLog : : AddLineS ( " patcher.log " , log ) ;
delete [ ] log ;
}
//============================================================================
void ShutdownAsyncCore ( ) {
if ( AtomicAdd ( & s_asyncCoreInitCount , - 1 ) > 1 )
return ;
ASSERT ( s_asyncCoreInitCount > = 0 ) ;
while ( s_perf [ kPerfThreadTaskCount ] )
AsyncSleep ( 10 ) ;
AsyncCoreDestroy ( 30 * 1000 ) ;
}
//============================================================================
// param = URU_PreparationRequest
void UruPrepProc ( void * param ) {
# ifdef USE_VLD
VLDEnable ( ) ;
# endif
s_running = true ;
plLauncherInfo * info = ( plLauncherInfo * ) param ;
StrToAnsi ( s_workingDir , info - > path , arrsize ( s_workingDir ) ) ;
InitAsyncCore ( ) ;
NetClientInitialize ( ) ;
NetClientSetErrorHandler ( NetErrorHandler ) ;
NetClientSetTransTimeoutMs ( 5 * 60 * 1000 ) ; // five minute timeout
s_patchComplete = false ;
s_patchError = false ;
const char * * addrs ;
unsigned count ;
count = GetGateKeeperSrvHostnames ( & addrs ) ;
// Start connecting to the server
NetCliGateKeeperStartConnect ( addrs , count ) ;
// request a file server ip address
NetCliGateKeeperFileSrvIpAddressRequest ( FileSrvIpAddressCallback , param , true ) ;
do {
NetClientUpdate ( ) ;
AsyncSleep ( 10 ) ;
} while ( ( ! s_patchComplete & & ! s_patchError & & s_running ) | | s_perf [ kPerfThreadTaskCount ] ) ;
while ( manifestQueue . size ( ) )
{
ManifestFile * mf = manifestQueue . front ( ) ;
manifestQueue . pop ( ) ;
delete mf ;
}
// If s_patchError, we don't wait around for s_numFiles
// to drop to zero because it never does for reasons
// I'm not willing to debug at the moment, so we just
// bail on them. This causes a race condition with
// the outstanding file object cancel/deletion and
// subsequently a memory leak. -eap
if ( s_patchError ) {
info - > SetText ( " Exiting... " ) ;
}
else {
PatchInfo patchInfo ;
patchInfo . stage = 2 ;
patchInfo . progressStage = 100 ;
patchInfo . progress = 1000 ;
info - > progressCallback ( kStatusOk , & patchInfo ) ;
}
ProgressStream : : info = nil ;
NetCliFileDisconnect ( ) ;
NetClientUpdate ( ) ;
// Shutdown the client/server networking subsystem
NetClientDestroy ( ) ;
info - > prepCallback ( s_patchError ? kStatusError : kStatusOk , nil ) ;
}
//============================================================================
void PlayerStopProc ( void * param ) {
# ifdef USE_VLD
VLDEnable ( ) ;
# endif
s_running = false ;
plLauncherInfo * info = ( plLauncherInfo * ) param ;
//TerminateProcess(s_pi.hProcess, kExitCodeTerminated);
info - > stopCallback ( kStatusOk , nil ) ;
}
//============================================================================
void PlayerTerminateProc ( void * param ) {
# ifdef USE_VLD
VLDEnable ( ) ;
# endif
s_running = false ;
plLauncherInfo * info = ( plLauncherInfo * ) param ;
ShutdownAsyncCore ( ) ;
info - > terminateCallback ( kStatusOk , nil ) ;
}
//============================================================================
void UruStartProc ( void * param ) {
# ifdef USE_VLD
VLDEnable ( ) ;
# endif
if ( ! s_running )
return ;
plLauncherInfo * info = ( plLauncherInfo * ) param ;
wchar_t workDir [ MAX_PATH ] ;
StrPrintf ( workDir , arrsize ( workDir ) , L " %s " , info - > path ) ;
//fprintf(stderr, "URUPlayer StartProc gamePath is:%ws\n", workDir);
wchar_t cmdLine [ MAX_PATH ] ;
StrPrintf ( cmdLine , arrsize ( cmdLine ) , L " %s%s %s " , workDir , s_clientExeName , info - > cmdLine ) ;
// Create the named event so the client won't restart us (Windows will clean it up when we exit)
HANDLE hPatcherEvent = CreateEventW ( nil , TRUE , FALSE , L " UruPatcherEvent " ) ;
if ( hPatcherEvent = = NULL ) {
info - > startCallback ( kStatusError , nil ) ;
return ;
}
fprintf ( stderr , " URUPlayer StartProc, running game process at dir:%ws, cmd:%ws for application:%ws \n " , workDir , cmdLine , s_clientExeName ) ;
STARTUPINFOW si ;
memset ( & si , 0 , sizeof ( si ) ) ;
memset ( & s_pi , 0 , sizeof ( s_pi ) ) ;
si . cb = sizeof ( si ) ;
info - > SetText ( " Launching URU... " ) ;
BOOL success = CreateProcessW (
NULL ,
cmdLine ,
NULL , // plProcessAttributes
NULL , // plThreadAttributes
FALSE , // bInheritHandles
0 , // dwCreationFlags
NULL , // lpEnvironment
workDir , // lpCurrentDirectory
& si ,
& s_pi
) ;
if ( success )
{
fprintf ( stderr , " %d " , GetLastError ( ) ) ;
info - > returnCode = s_pi . dwProcessId ;
CloseHandle ( s_pi . hThread ) ;
CloseHandle ( s_pi . hProcess ) ;
// This may smooth the visual transition from GameTap to Uru, or it may make it worse.
WaitForInputIdle ( s_pi . hProcess , INFINITE ) ;
//_beginthread(WaitUruExitProc, 0, param);
// wait for the event to signal (give the client 10 seconds to start up, then die)
DWORD wait = WaitForSingleObject ( hPatcherEvent , 10000 ) ;
if ( wait = = WAIT_TIMEOUT )
info - > startCallback ( kStatusOk , nil ) ;
else
info - > startCallback ( kStatusOk , nil ) ;
}
else
{
info - > startCallback ( kStatusError , nil ) ;
}
}