@ -48,6 +48,11 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
# include "Pch.h"
# include "Pch.h"
# pragma hdrstop
# pragma hdrstop
# ifndef SEE_MASK_NOASYNC
# define SEE_MASK_NOASYNC 0x00000100
# endif
# define PATCHER_FLAG_INSTALLER 0x10
/*****************************************************************************
/*****************************************************************************
*
*
@ -61,206 +66,182 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
static const wchar s_manifest [ ] = L " ExternalPatcher " ;
static const wchar s_manifest [ ] = L " ExternalPatcher " ;
# endif
# endif
static const wchar s_depManifest [ ] = L " DependencyPatcher " ;
class SelfPatcherStream : public plZlibStream {
class SelfPatcherStream : public plZlibStream {
public :
public :
SelfPatcherStream ( ) ;
virtual UInt32 Write ( UInt32 byteCount , const void * buffer ) ;
virtual UInt32 Write ( UInt32 byteCount , const void * buffer ) ;
static plLauncherInfo * info ;
static unsigned totalBytes ;
static unsigned totalBytes ;
static unsigned progress ;
static unsigned progress ;
static DWORD startTime ;
} ;
} ;
unsigned SelfPatcherStream : : totalBytes = 0 ;
unsigned SelfPatcherStream : : totalBytes = 0 ;
unsigned SelfPatcherStream : : progress = 0 ;
unsigned SelfPatcherStream : : progress = 0 ;
DWORD SelfPatcherStream : : startTime = 0 ;
static bool s_downloadComplete ;
//============================================================================
static long s_numFiles ;
class plSelfPatcher : public hsThread
static ENetError s_patchResult ;
{
static bool s_updated ;
enum RequestType
static wchar s_newPatcherFile [ MAX_PATH ] ;
{
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 ) ;
* Private Functions
void IEnqueueWork ( PatcherWork * & wk , bool priority = false ) ;
*
void IDequeueWork ( PatcherWork * & wk ) ;
* * */
void IFatalError ( const wchar * msg ) ;
void IReportServerBusy ( ) ;
//============================================================================
// This worker thread
static void NetErrorHandler ( ENetProtocol protocol , ENetError error ) {
void ICheckAndRequest ( PatcherWork * & wk ) ;
REF ( protocol ) ;
void IDownloadFile ( PatcherWork * & wk ) ;
LogMsg ( kLogError , L " NetErr: %s " , NetErrorToString ( error ) ) ;
void IVerifyFile ( PatcherWork * & wk ) ;
if ( IS_NET_SUCCESS ( s_patchResult ) )
void IIssueManifestRequest ( PatcherWork * & wk ) ;
s_patchResult = error ;
HANDLE ICreateProcess ( const wchar * path , const wchar * args , bool forceShell = false ) const ;
s_downloadComplete = true ;
void IInstallDep ( PatcherWork * & wk ) ;
DWORD IWaitProcess ( HANDLE hProcess ) ;
switch ( error ) {
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_patchResult = kNetErrServerBusy ;
s_downloadComplete = true ;
break ;
}
}
//============================================================================
void IRun ( ) ;
static void DownloadCallback (
void IQuit ( ) ;
ENetError result ,
void * param ,
const wchar filename [ ] ,
hsStream * writer
) {
REF ( param ) ;
REF ( filename ) ;
if ( IS_NET_ERROR ( result ) ) {
switch ( result ) {
case kNetErrTimeout :
writer - > Rewind ( ) ;
NetCliFileDownloadRequest ( filename , writer , DownloadCallback , param ) ;
break ;
default :
LogMsg ( kLogError , L " Error getting patcher file: %s " , NetErrorToString ( result ) ) ;
if ( IS_NET_SUCCESS ( s_patchResult ) )
s_patchResult = result ;
break ;
}
return ;
}
writer - > Close ( ) ;
public :
delete writer ;
plSelfPatcher ( ) ;
AtomicAdd ( & s_numFiles , - 1 ) ;
if ( ! s_numFiles ) {
bool Active ( ) const { return GetQuit ( ) = = 0 ; }
s_downloadComplete = true ;
const wchar * GetNewPatcherFileName ( ) const { return fNewPatcherFileName ; }
s_updated = true ;
ENetError GetResult ( ) const { return fResult ; }
}
}
//============================================================================
void IssueManifestRequests ( ) ;
static bool MD5Check ( const char filename [ ] , const wchar md5 [ ] ) {
// Do md5 check
char md5copy [ MAX_PATH ] ;
plMD5Checksum existingMD5 ( filename ) ;
plMD5Checksum latestMD5 ;
StrToAnsi ( md5copy , md5 , arrsize ( md5copy ) ) ;
latestMD5 . SetFromHexString ( md5copy ) ;
return ( existingMD5 = = latestMD5 ) ;
}
//============================================================================
void Start ( ) ; // override;
static void ManifestCallback (
hsError Run ( ) ; // override;
ENetError result ,
void Stop ( ) ; // override;
void * param ,
const wchar group [ ] ,
const NetCliFileManifestEntry manifest [ ] ,
unsigned entryCount
) {
REF ( param ) ;
REF ( group ) ;
if ( IS_NET_ERROR ( result ) ) {
switch ( result ) {
case kNetErrTimeout :
NetCliFileManifestRequest ( ManifestCallback , nil , s_manifest ) ;
break ;
default :
LogMsg ( kLogError , L " Error getting patcher manifest: %s " , NetErrorToString ( result ) ) ;
if ( IS_NET_SUCCESS ( s_patchResult ) )
s_patchResult = result ;
break ;
}
return ;
}
char ansi [ MAX_PATH ] ;
public :
plLauncherInfo * fLauncherInfo ;
// MD5 check current patcher against value in manifest
public :
ASSERT ( entryCount = = 1 ) ;
// NetCli callbacks
wchar curPatcherFile [ MAX_PATH ] ;
static void NetErrorHandler ( ENetProtocol protocol , ENetError error ) ;
PathGetProgramName ( curPatcherFile , arrsize ( curPatcherFile ) ) ;
static void OnFileSrvIP ( ENetError result , void * param , const wchar addr [ ] ) ;
StrToAnsi ( ansi , curPatcherFile , arrsize ( ansi ) ) ;
static void OnFileSrvManifest ( ENetError result , void * param , const wchar group [ ] , const NetCliFileManifestEntry manifest [ ] , unsigned entryCount ) ;
if ( ! MD5Check ( ansi , manifest [ 0 ] . md5 ) ) {
static void OnFileSrvDownload ( ENetError result , void * param , const wchar filename [ ] , hsStream * writer ) ;
// MessageBox(GetTopWindow(nil), "MD5 failed", "Msg", MB_OK);
SelfPatcherStream : : totalBytes + = manifest [ 0 ] . zipSize ;
AtomicAdd ( & s_numFiles , 1 ) ;
} s_selfPatcher ;
SetText ( " Downloading new patcher... " ) ;
StrToAnsi ( ansi , s_newPatcherFile , arrsize ( ansi ) ) ;
SelfPatcherStream * stream = NEWZERO ( SelfPatcherStream ) ;
if ( ! stream - > Open ( ansi , " wb " ) )
ErrorFatal ( __LINE__ , __FILE__ , " Failed to create file: %s, errno: %u " , ansi , errno ) ;
NetCliFileDownloadRequest ( manifest [ 0 ] . downloadName , stream , DownloadCallback , nil ) ;
/*****************************************************************************
}
*
else {
* Private Functions
s_downloadComplete = true ;
*
}
* * */
}
//============================================================================
//============================================================================
static void FileSrvIpAddressCallback (
static bool CheckMD5 ( const wchar * path , const wchar * hash )
ENetError result ,
{
void * param ,
plMD5Checksum localMD5 ;
const wchar addr [ ]
plMD5Checksum remoteMD5 ;
) {
REF ( param ) ;
hsUNIXStream s ;
s . Open ( path ) ;
NetCliGateKeeperDisconnect ( ) ;
localMD5 . CalcFromStream ( & s ) ;
s . Close ( ) ;
if ( IS_NET_ERROR ( result ) ) {
LogMsg ( kLogDebug , L " FileSrvIpAddressRequest failed: %s " , NetErrorToString ( result ) ) ;
// Some silly goose decided to send an md5 hash as UCS-2 instead of ASCII
s_patchResult = result ;
char ansi [ 33 ] ;
s_downloadComplete = true ;
StrToAnsi ( ansi , hash , arrsize ( ansi ) ) ;
}
remoteMD5 . SetFromHexString ( ansi ) ;
// Start connecting to the server
return localMD5 = = remoteMD5 ;
NetCliFileStartConnect ( & addr , 1 , true ) ;
}
PathGetProgramDirectory ( s_newPatcherFile , arrsize ( s_newPatcherFile ) ) ;
//============================================================================
GetTempFileNameW ( s_newPatcherFile , kPatcherExeFilename , 0 , s_newPatcherFile ) ;
static wchar * FormatSystemError ( )
PathDeleteFile ( s_newPatcherFile ) ;
{
wchar * error ;
FormatMessageW ( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM ,
NULL ,
GetLastError ( ) ,
MAKELANGID ( LANG_NEUTRAL , SUBLANG_DEFAULT ) ,
( LPWSTR ) & error ,
0 ,
NULL ) ;
return error ;
}
NetCliFileManifestRequest ( ManifestCallback , nil , s_manifest ) ;
//============================================================================
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 ) {
static bool SelfPatcherProc ( bool * abort , plLauncherInfo * info ) {
bool patched = false ;
bool patched = false ;
s_downloadComplete = false ;
s_patchResult = kNetSuccess ;
NetClientInitialize ( ) ;
s_selfPatcher . fLauncherInfo = info ;
NetClientSetErrorHandler ( NetErrorHandler ) ;
s_selfPatcher . Start ( ) ;
while ( s_selfPatcher . Active ( ) & & ! * abort ) {
const wchar * * addrs ;
unsigned count ;
count = GetGateKeeperSrvHostnames ( & addrs ) ;
// Start connecting to the server
NetCliGateKeeperStartConnect ( addrs , count ) ;
// request a file server ip address
NetCliGateKeeperFileSrvIpAddressRequest ( FileSrvIpAddressCallback , nil , true ) ;
while ( ! s_downloadComplete & & ! * abort ) {
NetClientUpdate ( ) ;
NetClientUpdate ( ) ;
AsyncSleep ( 10 ) ;
AsyncSleep ( 10 ) ;
}
}
s_selfPatcher . Stop ( ) ;
NetCliFileDisconnect ( ) ;
if ( s_selfPatcher . GetResult ( ) = = kNetPending )
NetClientUpdate ( ) ;
* abort = true ;
// Shutdown the client/server networking subsystem
NetClientDestroy ( ) ;
if ( s_downloadComplete & & ! * abort & & s_updated & & IS_NET_SUCCESS ( s_patchResult ) ) {
if ( ! * abort & & * s_selfPatcher . GetNewPatcherFileName ( ) & & IS_NET_SUCCESS ( s_selfPatcher . GetResult ( ) ) ) {
// launch new patcher
// launch new patcher
STARTUPINFOW si ;
STARTUPINFOW si ;
@ -270,7 +251,7 @@ static bool SelfPatcherProc (bool * abort, plLauncherInfo *info) {
si . cb = sizeof ( si ) ;
si . cb = sizeof ( si ) ;
wchar cmdline [ MAX_PATH ] ;
wchar cmdline [ MAX_PATH ] ;
StrPrintf ( cmdline , arrsize ( cmdline ) , L " %s %s " , s_n ewPatcherFile , info - > cmdLine ) ;
StrPrintf ( cmdline , arrsize ( cmdline ) , L " %s %s " , s_selfPatcher . GetN ewPatcherFileName ( ) , info - > cmdLine ) ;
// we have only successfully patched if we actually launch the new version of the patcher
// we have only successfully patched if we actually launch the new version of the patcher
patched = CreateProcessW (
patched = CreateProcessW (
@ -301,14 +282,614 @@ static bool SelfPatcherProc (bool * abort, plLauncherInfo *info) {
*
*
* * */
* * */
//============================================================================
SelfPatcherStream : : SelfPatcherStream ( )
: plZlibStream ( )
{
if ( startTime = = 0 )
startTime = TimeGetSecondsSince2001Utc ( ) ;
}
//============================================================================
//============================================================================
UInt32 SelfPatcherStream : : Write ( UInt32 byteCount , const void * buffer ) {
UInt32 SelfPatcherStream : : Write ( UInt32 byteCount , const void * buffer ) {
progress + = byteCount ;
progress + = byteCount ;
float p = ( float ) progress / ( float ) totalBytes * 100 ; // progress
float p = ( float ) progress / ( float ) totalBytes * 1000 ; // progress
SetProgress ( ( int ) p ) ;
SetProgress ( ( int ) p ) ;
if ( progress > = totalBytes ) {
SetBytesRemaining ( 0 ) ;
SetTimeRemaining ( 0 ) ;
} else {
SetBytesRemaining ( totalBytes - byteCount ) ;
DWORD bytesPerSec = ( progress ) / max ( TimeGetSecondsSince2001Utc ( ) - startTime , 1 ) ;
SetTimeRemaining ( ( totalBytes - progress ) / max ( bytesPerSec , 1 ) ) ;
}
return plZlibStream : : Write ( byteCount , buffer ) ;
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 ) ) ;
// Kludge: any EXE we have here that isn't the launcher is clearly an installer.
if ( StrCmpI ( file . clientName , kPatcherExeFilename ) ! = 0 ) {
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 ;
}
}
// New patchers need to be downloaded FIRST, and we want to re-run the entire thing before
// continuing with the process.
bool isPatcher = IsPatcherFile ( wk - > fEntry . clientName ) ;
if ( isPatcher )
wk - > fFlags | = kRequestNewPatcher ;
LogMsg ( kLogDebug , L " plSelfPatcher::ICheckAndRequest: File '%s' needs to be downloaded. " , wk - > fEntry . clientName ) ;
wk - > fType = kDownload ;
IEnqueueWork ( wk , isPatcher ) ;
}
//============================================================================
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 : : totalBytes + = ( wk - > fEntry . zipSize ! = 0 ) ? wk - > fEntry . zipSize : wk - > fEntry . fileSize ;
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 if ( wk - > fFlags & kRequestNewPatcher ) {
LogMsg ( kLogPerf , L " plSelfPatcher::IVerifyFile: Downloaded a new patcher! '%s' " , wk - > fEntry . clientName ) ;
// Need to restart here w/new patcher.
fResult = kNetSuccess ;
IQuit ( ) ;
} 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.
wchar exePath [ MAX_PATH ] ;
PathGetCurrentDirectory ( exePath , arrsize ( exePath ) ) ;
PathAddFilename ( exePath , exePath , path , arrsize ( exePath ) ) ;
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 ] ;
const wchar * exeFilename = PathFindFilename ( path ) ;
StrPrintf ( cmdline , arrsize ( cmdline ) , L " %s %s " , exeFilename , args ) ;
BOOL result = CreateProcessW ( exePath ,
cmdline ,
NULL ,
NULL ,
FALSE ,
DETACHED_PROCESS ,
NULL ,
NULL ,
& si ,
& pi ) ;
CloseHandle ( pi . hThread ) ;
if ( result ! = FALSE ) {
free ( exePath ) ;
return pi . hProcess ;
} else {
wchar * error = FormatSystemError ( ) ;
LogMsg ( kLogError , L " plSelfPatcher::ICreateProcess: CreateProcessW failed for '%s': %u %s " ,
exePath , 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 = exePath ;
info . lpParameters = args ;
if ( ShellExecuteExW ( & info ) = = FALSE ) {
wchar * error = FormatSystemError ( ) ;
LogMsg ( kLogError , L " plSelfPatcher::ICreateProcess: ShellExecuteExW failed for '%s': %u %s " ,
exePath , GetLastError ( ) , error ) ;
LocalFree ( error ) ;
}
free ( exePath ) ;
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 ) ;
bool forceShell = false ;
wchar * extension = PathFindExtension ( wk - > fFileName ) ;
wchar * process ;
wchar args [ MAX_PATH ] ;
args [ 0 ] = 0 ;
// 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 ) ) ;
process = wk - > fFileName ;
} else if ( extension & & StrCmpI ( extension , L " .msi " ) = = 0 ) {
StrPrintf ( args , arrsize ( args ) , L " /i %s /qr /norestart " , wk - > fFileName ) ;
process = L " msiexec " ;
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 ) ! = ERROR_SUCCESS )
PathDeleteFile ( wk - > fFileName ) ;
CloseHandle ( hProcess ) ;
IDequeueWork ( wk ) ;
} else {
IFatalError ( L " Failed to run installer. " ) ;
}
}
//============================================================================
DWORD plSelfPatcher : : IWaitProcess ( HANDLE hProcess )
{
DWORD returncode = ERROR_SUCCESS ;
// 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 ) ;
returncode = - 1 ;
break ;
}
} else if ( idx = = kWaitProcess ) {
GetExitCodeProcess ( hProcess , & returncode ) ;
switch ( returncode ) {
case ERROR_SUCCESS :
case ERROR_PRODUCT_VERSION : // It's already installed...
case ERROR_SUCCESS_REBOOT_REQUIRED : // LMFTFY s/REQUIRED/DESIRED/
case ERROR_SUCCESS_RESTART_REQUIRED :
LogMsg ( kLogDebug , " plSelfPatcher::IWaitProcess: Process finished successfully! " ) ;
returncode = ERROR_SUCCESS ; // makes life easier for us.
break ;
default :
LogMsg ( kLogError , " plSelfPatcher::IWaitProcess: Process failed! Returncode: %u " , returncode ) ;
IFatalError ( L " Failed to install update. " ) ;
break ;
}
break ;
} else {
FATAL ( " Invalid wait index " ) ;
}
} else if ( waitStatus = = WAIT_FAILED ) {
wchar * error = FormatSystemError ( ) ;
LogMsg ( kLogError , " plSelfPatcher::IWaitProcess: WaitForMultipleObjects failed! %s " , error ) ;
LocalFree ( error ) ;
IFatalError ( L " Internal Error. " ) ;
returncode = - 1 ;
break ;
}
AsyncSleep ( 10 ) ;
} while ( 1 ) ;
return returncode ;
}
//============================================================================
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 , ( wk - > fFlags & kRequestNewPatcher ) ) ;
}
}
/*****************************************************************************
/*****************************************************************************
*
*
@ -324,7 +905,7 @@ bool SelfPatch (bool noSelfPatch, bool * abort, ENetError * result, plLauncherIn
SetText ( " Checking for patcher update... " ) ;
SetText ( " Checking for patcher update... " ) ;
patched = SelfPatcherProc ( abort , info ) ;
patched = SelfPatcherProc ( abort , info ) ;
}
}
* result = s_patchResult ;
* result = s_selfPatcher . GetResult ( ) ;
return patched ;
return patched ;
}
}