/*==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 / plUruLauncher / SelfPatcher . cpp
*
* * */
# include "Pch.h"
# pragma hdrstop
# ifndef SEE_MASK_NOASYNC
# define SEE_MASK_NOASYNC 0x00000100
# endif
# define PATCHER_FLAG_INSTALLER 0x10
typedef bool ( * FVerifyReturnCode ) ( DWORD ) ;
/*****************************************************************************
*
* Private Data
*
* * */
# ifndef PLASMA_EXTERNAL_RELEASE
static const wchar s_manifest [ ] = L " InternalPatcher " ;
# else
static const wchar s_manifest [ ] = L " ExternalPatcher " ;
# endif
static const wchar s_depManifest [ ] = L " DependencyPatcher " ;
class SelfPatcherStream : public plZlibStream {
public :
SelfPatcherStream ( ) ;
virtual UInt32 Write ( UInt32 byteCount , const void * buffer ) ;
static unsigned totalBytes ;
static unsigned progress ;
static DWORD startTime ;
} ;
unsigned SelfPatcherStream : : totalBytes = 0 ;
unsigned SelfPatcherStream : : progress = 0 ;
DWORD SelfPatcherStream : : startTime = 0 ;
//============================================================================
class plSelfPatcher : public hsThread
{
enum RequestType
{
kUndefined = - 1 ,
kQuit ,
kRequestManifest ,
kHash ,
kDownload ,
kVerify ,
kInstall ,
} ;
enum RequestFlags
{
kRequestBlocked = ( 1 < < 0 ) ,
kRequestOptionalManifest = ( 1 < < 1 ) ,
kRequestNewPatcher = ( 1 < < 2 ) ,
} ;
class PatcherWork
{
public :
LINK ( PatcherWork ) link ;
RequestType fType ;
UInt32 fFlags ;
union
{
NetCliFileManifestEntry fEntry ;
wchar fFileName [ MAX_PATH ] ;
} ;
PatcherWork ( ) : fType ( kUndefined ) , fFlags ( 0 ) { }
PatcherWork ( RequestType type , const PatcherWork * cpy )
: fType ( type ) , fFlags ( 0 )
{
memcpy ( & fEntry , & cpy - > fEntry , sizeof ( fEntry ) ) ;
}
} ;
LISTDECL ( PatcherWork , link ) fReqs ;
CEvent fQueueEvent ;
CCritSect fMutex ;
ENetError fResult ;
wchar fNewPatcherFileName [ MAX_PATH ] ;
UInt32 fInstallerCount ;
UInt32 fInstallersExecuted ;
// Any thread
void IEnqueueFile ( const NetCliFileManifestEntry & file ) ;
void IEnqueueWork ( PatcherWork * & wk , bool priority = false ) ;
void IDequeueWork ( PatcherWork * & wk ) ;
void IFatalError ( const wchar * msg ) ;
void IReportServerBusy ( ) ;
// This worker thread
void ICheckAndRequest ( PatcherWork * & wk ) ;
void IDownloadFile ( PatcherWork * & wk ) ;
void IVerifyFile ( PatcherWork * & wk ) ;
void IIssueManifestRequest ( PatcherWork * & wk ) ;
HANDLE ICreateProcess ( const wchar * path , const wchar * args , bool forceShell = false ) const ;
void IInstallDep ( PatcherWork * & wk ) ;
bool IWaitProcess ( HANDLE hProcess , FVerifyReturnCode verify ) ;
static bool IValidateExeReturnCode ( DWORD returncode ) ;
static bool IValidateMsiReturnCode ( DWORD returncode ) ;
void IRun ( ) ;
void IQuit ( ) ;
public :
plSelfPatcher ( ) ;
bool Active ( ) const { return GetQuit ( ) = = 0 ; }
const wchar * GetNewPatcherFileName ( ) const { return fNewPatcherFileName ; }
ENetError GetResult ( ) const { return fResult ; }
void IssueManifestRequests ( ) ;
void Start ( ) ; // override;
hsError Run ( ) ; // override;
void Stop ( ) ; // override;
public :
plLauncherInfo * fLauncherInfo ;
public :
// NetCli callbacks
static void NetErrorHandler ( ENetProtocol protocol , ENetError error ) ;
static void OnFileSrvIP ( ENetError result , void * param , const wchar addr [ ] ) ;
static void OnFileSrvManifest ( ENetError result , void * param , const wchar group [ ] , const NetCliFileManifestEntry manifest [ ] , unsigned entryCount ) ;
static void OnFileSrvDownload ( ENetError result , void * param , const wchar filename [ ] , hsStream * writer ) ;
} s_selfPatcher ;
/*****************************************************************************
*
* Private Functions
*
* * */
//============================================================================
static bool CheckMD5 ( const wchar * path , const wchar * hash )
{
plMD5Checksum localMD5 ;
plMD5Checksum remoteMD5 ;
hsUNIXStream s ;
s . Open ( path ) ;
localMD5 . CalcFromStream ( & s ) ;
s . Close ( ) ;
// Some silly goose decided to send an md5 hash as UCS-2 instead of ASCII
char ansi [ 33 ] ;
StrToAnsi ( ansi , hash , arrsize ( ansi ) ) ;
remoteMD5 . SetFromHexString ( ansi ) ;
return localMD5 = = remoteMD5 ;
}
//============================================================================
static wchar * FormatSystemError ( )
{
wchar * error ;
FormatMessageW ( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM ,
NULL ,
GetLastError ( ) ,
MAKELANGID ( LANG_NEUTRAL , SUBLANG_DEFAULT ) ,
( LPWSTR ) & error ,
0 ,
NULL ) ;
return error ;
}
//============================================================================
static bool IsPatcherFile ( const wchar * filename )
{
wchar progPath [ MAX_PATH ] ;
PathGetProgramName ( progPath , arrsize ( progPath ) ) ;
wchar * progFilename = PathFindFilename ( progPath ) ;
return StrCmpI ( filename , progFilename ) = = 0 ;
}
//============================================================================
static bool SelfPatcherProc ( bool * abort , plLauncherInfo * info ) {
bool patched = false ;
s_selfPatcher . fLauncherInfo = info ;
s_selfPatcher . Start ( ) ;
while ( s_selfPatcher . Active ( ) & & ! * abort ) {
NetClientUpdate ( ) ;
AsyncSleep ( 10 ) ;
}
s_selfPatcher . Stop ( ) ;
if ( s_selfPatcher . GetResult ( ) = = kNetPending )
* abort = true ;
if ( ! * abort & & * s_selfPatcher . GetNewPatcherFileName ( ) & & IS_NET_SUCCESS ( s_selfPatcher . GetResult ( ) ) ) {
// launch new patcher
STARTUPINFOW si ;
PROCESS_INFORMATION pi ;
ZERO ( si ) ;
ZERO ( pi ) ;
si . cb = sizeof ( si ) ;
wchar cmdline [ MAX_PATH ] ;
StrPrintf ( cmdline , arrsize ( cmdline ) , L " %s %s " , s_selfPatcher . GetNewPatcherFileName ( ) , info - > cmdLine ) ;
// we have only successfully patched if we actually launch the new version of the patcher
patched = CreateProcessW (
NULL ,
cmdline ,
NULL ,
NULL ,
FALSE ,
DETACHED_PROCESS ,
NULL ,
NULL ,
& si ,
& pi
) ;
SetReturnCode ( pi . dwProcessId ) ;
CloseHandle ( pi . hThread ) ;
CloseHandle ( pi . hProcess ) ;
ASSERT ( patched ) ;
}
return patched ;
}
/*****************************************************************************
*
* ProgressStream Functions
*
* * */
//============================================================================
SelfPatcherStream : : SelfPatcherStream ( )
: plZlibStream ( )
{
if ( startTime = = 0 )
startTime = TimeGetSecondsSince2001Utc ( ) ;
}
//============================================================================
UInt32 SelfPatcherStream : : Write ( UInt32 byteCount , const void * buffer ) {
progress + = byteCount ;
float p = ( float ) progress / ( float ) totalBytes * 1000 ; // progress
SetProgress ( ( int ) p ) ;
if ( progress > = totalBytes ) {
SetBytesRemaining ( 0 ) ;
SetTimeRemaining ( 0 ) ;
} else {
SetBytesRemaining ( totalBytes - progress ) ;
DWORD bytesPerSec = ( progress ) / max ( TimeGetSecondsSince2001Utc ( ) - startTime , 1 ) ;
SetTimeRemaining ( ( totalBytes - progress ) / max ( bytesPerSec , 1 ) ) ;
}
return plZlibStream : : Write ( byteCount , buffer ) ;
}
/*****************************************************************************
*
* SelfPatcher Methods
*
* * */
//============================================================================
plSelfPatcher : : plSelfPatcher ( )
: fQueueEvent ( kEventAutoReset ) , fResult ( kNetPending ) , fLauncherInfo ( nil ) ,
fInstallerCount ( 0 ) , fInstallersExecuted ( 0 )
{
memset ( fNewPatcherFileName , 0 , sizeof ( fNewPatcherFileName ) ) ;
}
//============================================================================
void plSelfPatcher : : IEnqueueFile ( const NetCliFileManifestEntry & file )
{
LogMsg ( kLogDebug , L " plSelfPatcher::IEnqueueFile: Enqueueing hash check of '%s' " , file . downloadName ) ;
PatcherWork * wk = NEW ( PatcherWork ) ;
wk - > fType = kHash ;
memcpy ( & wk - > fEntry , & file , sizeof ( NetCliFileManifestEntry ) ) ;
// Are we the patcher? If not, any other exe or msi should be installed.
if ( IsPatcherFile ( wk - > fEntry . clientName ) ) {
wk - > fFlags | = kRequestNewPatcher ;
} else {
const wchar * extension = PathFindExtension ( file . clientName ) ;
if ( extension & & ( StrCmpI ( extension , L " .exe " ) = = 0 | | StrCmpI ( extension , L " .msi " ) = = 0 ) )
wk - > fEntry . flags | = PATCHER_FLAG_INSTALLER ;
}
IEnqueueWork ( wk ) ;
}
//============================================================================
void plSelfPatcher : : IEnqueueWork ( PatcherWork * & wk , bool priority )
{
fMutex . Enter ( ) ;
wk - > fFlags & = ~ kRequestBlocked ;
fReqs . Link ( wk , priority ? kListHead : kListTail ) ;
fMutex . Leave ( ) ;
fQueueEvent . Signal ( ) ;
// WHY?! You ask?
// If we don't, IRun() will reblock any reused requests. Also, from an ownership standpoint,
// the worker queue now owns the work, not whoever enqueued it.
wk = NULL ;
}
//============================================================================
void plSelfPatcher : : IDequeueWork ( PatcherWork * & wk )
{
ASSERT ( wk - > link . IsLinked ( ) ) ;
fMutex . Enter ( ) ;
fReqs . Unlink ( wk ) ;
fMutex . Leave ( ) ;
fQueueEvent . Signal ( ) ;
DEL ( wk ) ;
wk = NULL ;
}
//============================================================================
void plSelfPatcher : : IFatalError ( const wchar * msg )
{
# ifdef PLASMA_EXTERNAL_RELEASE
MessageBoxW ( NULL , msg , L " URU Launcher " , MB_OK | MB_ICONERROR ) ;
IQuit ( ) ;
# else
wchar finalmsg [ 1024 ] ;
StrPrintf ( finalmsg , arrsize ( finalmsg ) , L " %s Continue? " , msg ) ;
if ( MessageBoxW ( NULL , finalmsg , L " URU Launcher " , MB_YESNO | MB_ICONERROR ) = = IDNO ) {
IQuit ( ) ;
}
# endif
}
//============================================================================
void plSelfPatcher : : IReportServerBusy ( )
{
MessageBoxA ( NULL ,
" 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/ " ,
" URU Launcher " ,
MB_OK | MB_ICONINFORMATION ) ;
fResult = kNetPending ; // Don't show the unhandled error box.
IQuit ( ) ;
}
//============================================================================
void plSelfPatcher : : IssueManifestRequests ( )
{
{
PatcherWork * wk = NEW ( PatcherWork ) ;
wk - > fType = kRequestManifest ;
StrCopy ( wk - > fFileName , s_manifest , arrsize ( wk - > fFileName ) ) ;
IEnqueueWork ( wk ) ;
}
{
PatcherWork * wk = NEW ( PatcherWork ) ;
wk - > fType = kRequestManifest ;
wk - > fFlags | = kRequestOptionalManifest ;
StrCopy ( wk - > fFileName , s_depManifest , arrsize ( wk - > fFileName ) ) ;
IEnqueueWork ( wk ) ;
}
}
//============================================================================
void plSelfPatcher : : ICheckAndRequest ( PatcherWork * & wk )
{
// Patcher thread, can be as slow as molasses.
if ( PathDoesFileExist ( wk - > fEntry . clientName ) ) {
if ( CheckMD5 ( wk - > fEntry . clientName , wk - > fEntry . md5 ) ) {
LogMsg ( kLogDebug , L " plSelfPatcher::ICheckAndRequest: File '%s' appears to be up-to-date. " , wk - > fEntry . clientName ) ;
IDequeueWork ( wk ) ;
return ;
}
}
LogMsg ( kLogDebug , L " plSelfPatcher::ICheckAndRequest: File '%s' needs to be downloaded. " , wk - > fEntry . clientName ) ;
SelfPatcherStream : : totalBytes + = ( wk - > fEntry . zipSize ! = 0 ) ? wk - > fEntry . zipSize : wk - > fEntry . fileSize ;
wk - > fType = kDownload ;
IEnqueueWork ( wk ) ;
}
//============================================================================
void plSelfPatcher : : IDownloadFile ( PatcherWork * & wk )
{
// The patcher downloads to a temporary file.
if ( wk - > fFlags & kRequestNewPatcher ) {
PathGetProgramDirectory ( fNewPatcherFileName , arrsize ( fNewPatcherFileName ) ) ;
GetTempFileNameW ( fNewPatcherFileName , kPatcherExeFilename , 0 , fNewPatcherFileName ) ;
PathDeleteFile ( fNewPatcherFileName ) ;
StrCopy ( wk - > fEntry . clientName , fNewPatcherFileName , arrsize ( wk - > fEntry . clientName ) ) ;
SetText ( " Downloading new patcher... " ) ;
LogMsg ( kLogDebug , L " plSelfPatcher::IDownloadFile: New patcher will be downloaded as '%s' " , fNewPatcherFileName ) ;
} else {
if ( wk - > fEntry . flags & PATCHER_FLAG_INSTALLER )
SetText ( " Downloading update installer... " ) ;
else
SetText ( " Downloading update... " ) ;
}
SelfPatcherStream * s = NEWZERO ( SelfPatcherStream ) ;
if ( ! s - > Open ( wk - > fEntry . clientName , L " wb " ) ) {
LogMsg ( kLogError , L " plSelfPatcher::IDownloadFile: Failed to create file: %s, errno: %u " , wk - > fEntry . clientName , errno ) ;
IFatalError ( L " Failed to create file. " ) ;
} else {
LogMsg ( kLogDebug , L " plSelfPatcher::IDownloadFile: Downloading file '%s'. " , wk - > fEntry . downloadName ) ;
NetCliFileDownloadRequest ( wk - > fEntry . downloadName , s , OnFileSrvDownload , wk ) ;
}
}
//============================================================================
void plSelfPatcher : : IVerifyFile ( PatcherWork * & wk )
{
if ( ! CheckMD5 ( wk - > fEntry . clientName , wk - > fEntry . md5 ) ) {
LogMsg ( kLogError , L " plSelfPatcher::IVerifyFile: Hash mismatch on file '%s'. Expected: %s " ,
wk - > fEntry . clientName , wk - > fEntry . md5 ) ;
IFatalError ( L " File download verification failed. " ) ;
return ;
}
// If this is a redistributable dependency, it needs to be installed.
if ( wk - > fEntry . flags & PATCHER_FLAG_INSTALLER ) {
LogMsg ( kLogPerf , L " plSelfPatcher::IVerifyFile: Downloaded valid dependency installer '%s' " , wk - > fEntry . clientName ) ;
s_selfPatcher . fInstallerCount + + ;
wk - > fType = kInstall ;
s_selfPatcher . IEnqueueWork ( wk ) ;
} else {
LogMsg ( kLogPerf , L " plSelfPatcher::IVerifyFile: Downloaded valid standard file '%s' " , wk - > fEntry . clientName ) ;
s_selfPatcher . IDequeueWork ( wk ) ;
}
}
//============================================================================
void plSelfPatcher : : IIssueManifestRequest ( PatcherWork * & wk )
{
LogMsg ( kLogDebug , L " plSelfPatcher::IIssueManifestRequest: Issuing manifest request '%s'. " , wk - > fFileName ) ;
NetCliFileManifestRequest ( OnFileSrvManifest , wk , wk - > fFileName ) ;
}
//============================================================================
HANDLE plSelfPatcher : : ICreateProcess ( const wchar * path , const wchar * args , bool forceShell ) const
{
// Generally speaking, we would *like* to use CreateProcessW. Unfortunately, we cannot do that
// becuase on Windows Vista and above (read: what the world SHOULD be using...) CreateProcessW
// will not handle UAC split tokens and can fail with ERROR_ELEVATION_REQUIRED. For bonus fun,
// that error isn't even defined in the Platform SDK we're using here. The "official" solution
// is to use ShellExecuteEx with the verb "runas" so there you go. (See also: "runas.exe")
// Bonus chatter: on Windows XP, there is no "runas" feature because there are no split tokens
// or UAC. Also, Windows XP does not have the SEE_MASK_NOASYNC flag (it does have the DDEWAIT flag
// whose value is the same but functions slightly differently), which causes any dialogs
// launched by Windows (such as errors) to deadlock the UI quite horribly. Further,
// ShellExecuteExW pops up that weird "do you want to run this file you downloaded from the internet?"
// box, which we can't actually interact with due to the above.
if ( ! forceShell ) {
STARTUPINFOW si ;
PROCESS_INFORMATION pi ;
memset ( & si , 0 , sizeof ( si ) ) ;
memset ( & pi , 0 , sizeof ( pi ) ) ;
si . cb = sizeof ( si ) ;
wchar cmdline [ MAX_PATH ] ;
StrPrintf ( cmdline , arrsize ( cmdline ) , L " \" %s \" %s " , path , args ) ;
BOOL result = CreateProcessW ( path ,
cmdline ,
NULL ,
NULL ,
FALSE ,
DETACHED_PROCESS ,
NULL ,
NULL ,
& si ,
& pi ) ;
CloseHandle ( pi . hThread ) ;
if ( result ! = FALSE ) {
return pi . hProcess ;
} else {
wchar * error = FormatSystemError ( ) ;
LogMsg ( kLogError , L " plSelfPatcher::ICreateProcess: CreateProcessW failed for '%s': %u %s " ,
path , GetLastError ( ) , error ) ;
LocalFree ( error ) ;
// Purposefully falling through to ShellExecuteExW
}
}
SHELLEXECUTEINFOW info ;
memset ( & info , 0 , sizeof ( info ) ) ;
info . cbSize = sizeof ( info ) ;
info . fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI ;
info . hwnd = fLauncherInfo - > dialog ;
// Not explicitly setting lpVerb to L"runas" because this seemingly breaks msiexec.
info . lpFile = path ;
info . lpParameters = args ;
if ( ShellExecuteExW ( & info ) = = FALSE ) {
wchar * error = FormatSystemError ( ) ;
LogMsg ( kLogError , L " plSelfPatcher::ICreateProcess: ShellExecuteExW failed for '%s': %u %s " ,
path , GetLastError ( ) , error ) ;
LocalFree ( error ) ;
}
return info . hProcess ;
}
//============================================================================
void plSelfPatcher : : IInstallDep ( PatcherWork * & wk )
{
// Due to our dependence on Visual Studio .NET 2003, we cannot use the indeterminate/marquee
// progress bar from there. So, we'll have to dome some skullduggery to guesstimate a really
// crummy progress meter.
fInstallersExecuted + + ;
float progress = ( float ) fInstallersExecuted / ( ( float ) fInstallerCount + 1.f ) * 1000.f ;
SetProgress ( ( unsigned ) progress ) ;
// Best I can do for indeterminant progress.
SetTimeRemaining ( - 1 ) ;
SetBytesRemaining ( - 1 ) ;
// We are about to do something that MAY cause a UAC dialog to appear.
// So, let's at least pretend to be a good citizen and write something in the UI about that...
SetText ( " Installing updates... " ) ;
AsyncSleep ( 100 ) ;
wchar process [ MAX_PATH ] ;
PathGetCurrentDirectory ( process , arrsize ( process ) ) ;
PathAddFilename ( process , process , wk - > fFileName , arrsize ( process ) ) ;
wchar * extension = PathFindExtension ( wk - > fFileName ) ;
wchar args [ MAX_PATH ] ;
args [ 0 ] = 0 ;
bool forceShell = false ;
FVerifyReturnCode validateptr = NULL ;
// Apply arguments to the process to ensure it doesn't do weird stuff like start a big UI
// Creative OpenAL (oalinst.exe) uses '/s' for silent.
// The small DirectX 9.0c web installer (dxwebsetup.exe) uses "/q" and pops up an error on invalid args.
// The full monty DirectX 9.0c isntaller (dxsetup.exe) uses "/silent" and pops up an error on invalid args.
// The Visual C++ redist (vcredist_x86.exe and vcredist_x64.exe) may optionally restart the
// computer WITHOUT prompting when in quiet mode.
if ( extension & & StrCmpI ( extension , L " .exe " ) = = 0 ) {
wchar * filename = PathFindFilename ( wk - > fFileName ) ;
if ( StrCmpI ( filename , L " oalinst.exe " ) = = 0 )
StrPack ( args , L " /s " , arrsize ( args ) ) ;
else if ( StrCmpI ( filename , L " dxsetup.exe " ) = = 0 )
StrPack ( args , L " /silent " , arrsize ( args ) ) ;
else
StrPack ( args , L " /q " , arrsize ( args ) ) ;
if ( StrStrI ( filename , L " vcredist " ) )
StrPack ( args , L " /norestart " , arrsize ( args ) ) ;
validateptr = IValidateExeReturnCode ;
} else if ( extension & & StrCmpI ( extension , L " .msi " ) = = 0 ) {
StrPrintf ( args , arrsize ( args ) , L " /i \" %s \" /qr /norestart " , process ) ;
StrCopy ( process , L " msiexec " , arrsize ( process ) ) ;
validateptr = IValidateMsiReturnCode ;
forceShell = true ;
} else {
LogMsg ( kLogError , L " plSelfPatcher::IInstallDep: Invalid extension '%s' for installer '%s' " ,
extension ? extension : L " (NULL) " , wk - > fFileName ) ;
IDequeueWork ( wk ) ;
return ;
}
LogMsg ( kLogDebug , L " plSelfPatcher::IInstallDep: Installing '%s %s'. " , process , args ) ;
HANDLE hProcess = ICreateProcess ( process , args , forceShell ) ;
if ( hProcess ) {
if ( IWaitProcess ( hProcess , validateptr ) ) {
IDequeueWork ( wk ) ;
} else {
PathDeleteFile ( wk - > fFileName ) ;
IFatalError ( L " Failed to install update. " ) ;
}
CloseHandle ( hProcess ) ;
} else {
IFatalError ( L " Failed to run installer. " ) ;
}
}
//============================================================================
bool plSelfPatcher : : IWaitProcess ( HANDLE hProcess , FVerifyReturnCode verify )
{
// Since we have taken over the worker thread, we need to listen for any very very important
// requests added to the queue. The only one we care about is quit, the rest can just go to
// HEY HEY! and we're safe to just swallow the notifies. We delete our own request to resume
// the main proc.
enum { kWaitQueue , kWaitProcess } ;
HANDLE waitH [ ] = { fQueueEvent . Handle ( ) , hProcess } ;
do {
DWORD waitStatus = WaitForMultipleObjects ( arrsize ( waitH ) , waitH , FALSE , INFINITE ) ;
ASSERT ( waitStatus ! = WAIT_FAILED ) ;
if ( waitStatus > = WAIT_OBJECT_0 & & waitStatus < = ( WAIT_OBJECT_0 + arrsize ( waitH ) ) ) {
DWORD idx = waitStatus - WAIT_OBJECT_0 ;
if ( idx = = kWaitQueue ) {
fMutex . Enter ( ) ;
PatcherWork * quitWk = fReqs . Head ( ) ;
fMutex . Leave ( ) ;
if ( quitWk - > fType = = kQuit ) {
LogMsg ( kLogPerf , " plSelfPatcher::IWaitProcess: Got shutdown during wait, attempting to terminate process. " ) ;
TerminateProcess ( hProcess , 1 ) ;
return false ;
}
} else if ( idx = = kWaitProcess ) {
if ( verify ) {
DWORD returncode = 0 ;
GetExitCodeProcess ( hProcess , & returncode ) ;
return verify ( returncode ) ;
}
return true ;
} else {
FATAL ( " Invalid wait index " ) ;
return false ;
}
} else if ( waitStatus = = WAIT_FAILED ) {
wchar * error = FormatSystemError ( ) ;
LogMsg ( kLogError , L " plSelfPatcher::IWaitProcess: WaitForMultipleObjects failed! %s " , error ) ;
LocalFree ( error ) ;
IFatalError ( L " Internal Error. " ) ;
return false ;
} else {
LogMsg ( kLogError , " plSelfPatcher::IWaitProcess: Unhandled WaitForMultipleObjects result 0x%x " , waitStatus ) ;
return false ;
}
AsyncSleep ( 10 ) ;
} while ( 1 ) ;
}
//============================================================================
bool plSelfPatcher : : IValidateExeReturnCode ( DWORD returncode )
{
if ( returncode ! = 1 ) {
LogMsg ( kLogDebug , " plSelfPatcher::IValidateExeReturnCode: Process finished successfully! Returncode: %u " , returncode ) ;
return true ;
} else {
LogMsg ( kLogError , " plSelfPatcher::IValidateExeReturnCode: Process failed! Returncode: %u " , returncode ) ;
return false ;
}
}
//============================================================================
bool plSelfPatcher : : IValidateMsiReturnCode ( DWORD returncode )
{
switch ( returncode ) {
case ERROR_SUCCESS :
case ERROR_PRODUCT_VERSION :
case ERROR_SUCCESS_REBOOT_INITIATED :
case ERROR_SUCCESS_REBOOT_REQUIRED :
LogMsg ( kLogDebug , " plSelfPatcher::IValidateMsiReturnCode: Process finished successfully! " ) ;
return true ;
default :
LogMsg ( kLogError , " plSelfPatcher::IValidateMsiReturnCode: Process failed! Returncode: %u " , returncode ) ;
return false ;
}
}
//============================================================================
hsError plSelfPatcher : : Run ( )
{
do {
fQueueEvent . Wait ( - 1 ) ;
IRun ( ) ;
} while ( Active ( ) ) ;
return hsOK ;
}
//============================================================================
void plSelfPatcher : : IRun ( )
{
do {
fMutex . Enter ( ) ;
PatcherWork * wk = fReqs . Head ( ) ;
fMutex . Leave ( ) ;
if ( ! wk ) {
LogMsg ( kLogDebug , " plSelfPatcher::IRun: No work in queue, exiting. " ) ;
if ( ! IS_NET_ERROR ( fResult ) )
fResult = kNetSuccess ;
SetQuit ( 1 ) ;
return ;
}
if ( wk - > fFlags & kRequestBlocked )
return ;
switch ( wk - > fType ) {
case kQuit :
LogMsg ( kLogDebug , " plSelfPatcher::IRun: Explicit quit request. " ) ;
// An explicit quit should manage its own result code.
SetQuit ( 1 ) ;
return ;
case kRequestManifest :
IIssueManifestRequest ( wk ) ;
break ;
case kHash :
ICheckAndRequest ( wk ) ;
break ;
case kDownload :
IDownloadFile ( wk ) ;
break ;
case kInstall :
IInstallDep ( wk ) ;
break ;
case kVerify :
IVerifyFile ( wk ) ;
break ;
DEFAULT_FATAL ( wk . fType ) ;
}
if ( wk ) {
// this "blocks" the worker thread on a dependent task like a file download that is
// completing asyncrhonously, do not remove this request... The block is removed
// by some callback calling IDequeueWork() or, worse case, DEL(wk).
LogMsg ( kLogDebug , L " plSelfPatcher::IRun: Worker thread is now blocked on '%s'. " , wk - > fFileName ) ;
wk - > fFlags | = kRequestBlocked ;
break ;
}
} while ( 1 ) ;
}
//============================================================================
void plSelfPatcher : : IQuit ( )
{
PatcherWork * wk = NEW ( PatcherWork ) ;
wk - > fType = kQuit ;
IEnqueueWork ( wk , true ) ;
}
//============================================================================
void plSelfPatcher : : Start ( )
{
NetClientInitialize ( ) ;
NetClientSetErrorHandler ( NetErrorHandler ) ;
const wchar * * addrs ;
unsigned count ;
count = GetGateKeeperSrvHostnames ( & addrs ) ;
NetCliGateKeeperStartConnect ( addrs , count ) ;
// request a file server ip address
NetCliGateKeeperFileSrvIpAddressRequest ( OnFileSrvIP , NULL , true ) ;
hsThread : : Start ( ) ;
}
//============================================================================
void plSelfPatcher : : Stop ( )
{
// Post a quit message and wait for the thread to stop.
if ( Active ( ) )
IQuit ( ) ;
hsThread : : Stop ( ) ;
NetCliFileDisconnect ( ) ;
NetClientUpdate ( ) ;
// Shutdown the client/server networking subsystem
NetClientDestroy ( ) ;
}
//============================================================================
void plSelfPatcher : : NetErrorHandler ( ENetProtocol protocol , ENetError error )
{
REF ( protocol ) ;
LogMsg ( kLogError , L " plSelfPatcher::NetErrorHandler: %s " , NetErrorToString ( error ) ) ;
if ( IS_NET_SUCCESS ( s_selfPatcher . fResult ) )
s_selfPatcher . fResult = error ;
if ( error = = kNetErrServerBusy )
s_selfPatcher . IReportServerBusy ( ) ;
else
s_selfPatcher . IQuit ( ) ;
}
//============================================================================
void plSelfPatcher : : OnFileSrvIP ( ENetError error , void * param , const wchar addr [ ] )
{
NetCliGateKeeperDisconnect ( ) ;
if ( IS_NET_ERROR ( error ) ) {
LogMsg ( kLogError , L " plSelfPatcher::OnFileSrvIP: %s " , NetErrorToString ( error ) ) ;
s_selfPatcher . fResult = error ;
s_selfPatcher . IQuit ( ) ;
return ;
}
NetCliFileStartConnect ( & addr , 1 , true ) ;
s_selfPatcher . IssueManifestRequests ( ) ;
}
//============================================================================
void plSelfPatcher : : OnFileSrvManifest ( ENetError result , void * param , const wchar group [ ] ,
const NetCliFileManifestEntry manifest [ ] , unsigned entryCount )
{
PatcherWork * wk = ( PatcherWork * ) param ;
switch ( result ) {
case kNetErrTimeout :
NetCliFileManifestRequest ( OnFileSrvManifest , param , group ) ;
return ;
case kNetErrServerBusy :
s_selfPatcher . IReportServerBusy ( ) ;
return ;
}
if ( IS_NET_ERROR ( result ) & & ! ( wk - > fFlags & kRequestOptionalManifest ) ) {
s_selfPatcher . fResult = result ;
s_selfPatcher . IQuit ( ) ;
return ;
}
for ( unsigned i = 0 ; i < entryCount ; + + i )
s_selfPatcher . IEnqueueFile ( manifest [ i ] ) ;
s_selfPatcher . IDequeueWork ( wk ) ;
}
//============================================================================
void plSelfPatcher : : OnFileSrvDownload ( ENetError result , void * param ,
const wchar filename [ ] , hsStream * writer )
{
switch ( result ) {
case kNetErrTimeout :
writer - > Rewind ( ) ;
NetCliFileDownloadRequest ( filename , writer , OnFileSrvDownload , param ) ;
return ;
case kNetErrServerBusy :
s_selfPatcher . IReportServerBusy ( ) ;
writer - > Close ( ) ;
DEL ( writer ) ;
return ;
}
writer - > Close ( ) ;
DEL ( writer ) ;
if ( IS_NET_ERROR ( result ) ) {
LogMsg ( kLogError , L " plSelfPatcher::OnFileSrvDownload: Error downloading '%s': %s " , filename , NetErrorToString ( result ) ) ;
s_selfPatcher . fResult = result ;
s_selfPatcher . IQuit ( ) ;
} else {
PatcherWork * wk = ( PatcherWork * ) param ;
wk - > fType = kVerify ;
s_selfPatcher . IEnqueueWork ( wk ) ;
}
}
/*****************************************************************************
*
* Protected Functions
*
* * */
//============================================================================
// if return value is true, there was an update and the patcher should be shutdown, so the new patcher can take over
bool SelfPatch ( bool noSelfPatch , bool * abort , ENetError * result , plLauncherInfo * info ) {
bool patched = false ;
if ( ! noSelfPatch ) {
SetText ( " Checking for patcher update... " ) ;
patched = SelfPatcherProc ( abort , info ) ;
}
* result = s_selfPatcher . GetResult ( ) ;
return patched ;
}