mirror of
https://foundry.openuru.org/gitblit/r/CWE-ou-minkata.git
synced 2025-07-20 12:19:10 +00:00
Rewrite SelfPatcher to include the ability to install updates.
To register an update for install, there are three options: - List any executable or msi file in the [External|Internal]Patcher manifest. - As above, but in the *optional* DependencyPatcher manifest. - Flag a file with the bit flag 0x10 in either of the above manifests. This also fixes a bug that caused the status thread to deadlock in certain situations, causing the launcher to get stuck open. (cherry picked from commit e0e918084395f93170abcea2853ad25ae3012385)
This commit is contained in:
@ -157,13 +157,12 @@ static long s_terminationIssued;
|
|||||||
static bool s_terminated;
|
static bool s_terminated;
|
||||||
static plLauncherInfo s_launcherInfo;
|
static plLauncherInfo s_launcherInfo;
|
||||||
static HANDLE s_thread;
|
static HANDLE s_thread;
|
||||||
static HANDLE s_event;
|
static CEvent s_shutdownDesiredEvent(kEventManualReset);
|
||||||
static HINSTANCE s_hInstance;
|
static HINSTANCE s_hInstance;
|
||||||
static HWND s_dialog;
|
|
||||||
static CEvent s_dialogCreateEvent(kEventManualReset);
|
static CEvent s_dialogCreateEvent(kEventManualReset);
|
||||||
static CCritSect s_critsect;
|
static CCritSect s_critsect;
|
||||||
static LISTDECL(WndEvent, link) s_eventQ;
|
static LISTDECL(WndEvent, link) s_eventQ;
|
||||||
static CEvent s_shutdownEvent(kEventManualReset);
|
static CEvent s_shutdownDialogEvent(kEventManualReset);
|
||||||
static wchar s_workingDir[MAX_PATH];
|
static wchar s_workingDir[MAX_PATH];
|
||||||
static CEvent s_statusEvent(kEventManualReset);
|
static CEvent s_statusEvent(kEventManualReset);
|
||||||
|
|
||||||
@ -311,7 +310,7 @@ static void TerminateGame () {
|
|||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
static void Recv_SetProgress (HWND hwnd, const SetProgressEvent &event) {
|
static void Recv_SetProgress (HWND hwnd, const SetProgressEvent &event) {
|
||||||
SendMessage(GetDlgItem(s_dialog, IDC_PROGRESS), PBM_SETPOS, event.progress, NULL);
|
SendMessage(GetDlgItem(s_launcherInfo.dialog, IDC_PROGRESS), PBM_SETPOS, event.progress, NULL);
|
||||||
|
|
||||||
if (pTGApp)
|
if (pTGApp)
|
||||||
{
|
{
|
||||||
@ -329,7 +328,7 @@ static void Recv_SetProgress (HWND hwnd, const SetProgressEvent &event) {
|
|||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
static void Recv_SetText (HWND hwnd, const SetTextEvent &event) {
|
static void Recv_SetText (HWND hwnd, const SetTextEvent &event) {
|
||||||
bool b = SendMessage(GetDlgItem(s_dialog, IDC_TEXT), WM_SETTEXT, 0, (LPARAM) event.text);
|
bool b = SendMessage(GetDlgItem(s_launcherInfo.dialog, IDC_TEXT), WM_SETTEXT, 0, (LPARAM) event.text);
|
||||||
|
|
||||||
if (pTGApp)
|
if (pTGApp)
|
||||||
{
|
{
|
||||||
@ -347,7 +346,7 @@ static void Recv_SetText (HWND hwnd, const SetTextEvent &event) {
|
|||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
static void Recv_SetStatusText (HWND hwnd, const SetStatusTextEvent &event) {
|
static void Recv_SetStatusText (HWND hwnd, const SetStatusTextEvent &event) {
|
||||||
bool b = SendMessage(GetDlgItem(s_dialog, IDC_STATUS_TEXT), WM_SETTEXT, 0, (LPARAM) event.text);
|
bool b = SendMessage(GetDlgItem(s_launcherInfo.dialog, IDC_STATUS_TEXT), WM_SETTEXT, 0, (LPARAM) event.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
@ -359,7 +358,7 @@ static void Recv_SetTimeRemaining (HWND hwnd, const SetTimeRemainingEvent &event
|
|||||||
|
|
||||||
if(event.seconds == 0xffffffff)
|
if(event.seconds == 0xffffffff)
|
||||||
{
|
{
|
||||||
SendMessage(GetDlgItem(s_dialog, IDC_TIMEREMAINING), WM_SETTEXT, 0, (LPARAM) "estimating...");
|
SendMessage(GetDlgItem(s_launcherInfo.dialog, IDC_TIMEREMAINING), WM_SETTEXT, 0, (LPARAM) "...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,7 +391,7 @@ static void Recv_SetTimeRemaining (HWND hwnd, const SetTimeRemainingEvent &event
|
|||||||
StrPrintf(text, arrsize(text), "%s%d min ", text, minutes);
|
StrPrintf(text, arrsize(text), "%s%d min ", text, minutes);
|
||||||
if( seconds || !text[0])
|
if( seconds || !text[0])
|
||||||
StrPrintf(text, arrsize(text), "%s%d sec", text, seconds);
|
StrPrintf(text, arrsize(text), "%s%d sec", text, seconds);
|
||||||
bool b = SendMessage(GetDlgItem(s_dialog, IDC_TIMEREMAINING), WM_SETTEXT, 0, (LPARAM) text);
|
bool b = SendMessage(GetDlgItem(s_launcherInfo.dialog, IDC_TIMEREMAINING), WM_SETTEXT, 0, (LPARAM) text);
|
||||||
}
|
}
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
@ -402,6 +401,11 @@ static void Recv_SetBytesRemaining (HWND hwnd, const SetBytesRemainingEvent &eve
|
|||||||
unsigned decimal;
|
unsigned decimal;
|
||||||
unsigned bytes = event.bytes;
|
unsigned bytes = event.bytes;
|
||||||
|
|
||||||
|
if (bytes == 0xffffffff) {
|
||||||
|
SendMessage(GetDlgItem(s_launcherInfo.dialog, IDC_BYTESREMAINING), WM_SETTEXT, 0, (LPARAM) "...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned GB = bytes / 1000000000;
|
unsigned GB = bytes / 1000000000;
|
||||||
if(GB)
|
if(GB)
|
||||||
{
|
{
|
||||||
@ -416,7 +420,7 @@ static void Recv_SetBytesRemaining (HWND hwnd, const SetBytesRemainingEvent &eve
|
|||||||
decimal = bytes / 100000; // to one decimal place
|
decimal = bytes / 100000; // to one decimal place
|
||||||
StrPrintf(text, arrsize(text), "%d.%d MB", MB, decimal);
|
StrPrintf(text, arrsize(text), "%d.%d MB", MB, decimal);
|
||||||
}
|
}
|
||||||
bool b = SendMessage(GetDlgItem(s_dialog, IDC_BYTESREMAINING), WM_SETTEXT, 0, (LPARAM) text);
|
bool b = SendMessage(GetDlgItem(s_launcherInfo.dialog, IDC_BYTESREMAINING), WM_SETTEXT, 0, (LPARAM) text);
|
||||||
}
|
}
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
@ -462,19 +466,19 @@ static void MessagePump (HWND hwnd) {
|
|||||||
// wait for a message or the shutdown event
|
// wait for a message or the shutdown event
|
||||||
const DWORD result = MsgWaitForMultipleObjects(
|
const DWORD result = MsgWaitForMultipleObjects(
|
||||||
1,
|
1,
|
||||||
&s_event,
|
&s_shutdownDesiredEvent.Handle(),
|
||||||
false,
|
false,
|
||||||
INFINITE,
|
INFINITE,
|
||||||
QS_ALLEVENTS
|
QS_ALLEVENTS
|
||||||
);
|
);
|
||||||
if (result == WAIT_OBJECT_0)
|
if (result == WAIT_OBJECT_0)
|
||||||
return;
|
PostQuitMessage(0);
|
||||||
|
|
||||||
// process windows messages
|
// process windows messages
|
||||||
MSG msg;
|
MSG msg;
|
||||||
|
|
||||||
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
|
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
|
||||||
if (!IsDialogMessage(s_dialog, &msg)) {
|
if (!IsDialogMessage(s_launcherInfo.dialog, &msg)) {
|
||||||
TranslateMessage(&msg);
|
TranslateMessage(&msg);
|
||||||
DispatchMessage(&msg);
|
DispatchMessage(&msg);
|
||||||
}
|
}
|
||||||
@ -503,8 +507,8 @@ BOOL CALLBACK SplashDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM l
|
|||||||
if(!s_shutdown)
|
if(!s_shutdown)
|
||||||
{
|
{
|
||||||
s_shutdown = true;
|
s_shutdown = true;
|
||||||
SendMessage(GetDlgItem(s_dialog, IDC_TEXT), WM_SETTEXT, 0, (LPARAM) "Shutting Down...");
|
SendMessage(GetDlgItem(s_launcherInfo.dialog, IDC_TEXT), WM_SETTEXT, 0, (LPARAM) "Shutting Down...");
|
||||||
EnableWindow(GetDlgItem(s_dialog, IDCANCEL), false);
|
EnableWindow(GetDlgItem(s_launcherInfo.dialog, IDCANCEL), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -538,12 +542,6 @@ BOOL CALLBACK SplashDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM l
|
|||||||
static void WindowThreadProc(void *) {
|
static void WindowThreadProc(void *) {
|
||||||
|
|
||||||
InitCommonControls();
|
InitCommonControls();
|
||||||
s_event = CreateEvent(
|
|
||||||
(LPSECURITY_ATTRIBUTES) 0,
|
|
||||||
false, // auto reset
|
|
||||||
false, // initial state off
|
|
||||||
(LPCTSTR) 0 // name
|
|
||||||
);
|
|
||||||
|
|
||||||
if (TGIsCider)
|
if (TGIsCider)
|
||||||
{
|
{
|
||||||
@ -553,22 +551,22 @@ static void WindowThreadProc(void *) {
|
|||||||
pTGApp = pTGLaunchUNIXApp (TG_OLD_DIALOG_POPEN_PATH, "w");
|
pTGApp = pTGLaunchUNIXApp (TG_OLD_DIALOG_POPEN_PATH, "w");
|
||||||
}
|
}
|
||||||
|
|
||||||
s_dialog = ::CreateDialog( s_hInstance, MAKEINTRESOURCE( IDD_DIALOG ), NULL, SplashDialogProc );
|
s_launcherInfo.dialog = ::CreateDialog( s_hInstance, MAKEINTRESOURCE( IDD_DIALOG ), NULL, SplashDialogProc );
|
||||||
SetWindowText(s_dialog, "URU Launcher");
|
SetWindowText(s_launcherInfo.dialog, "URU Launcher");
|
||||||
|
|
||||||
|
|
||||||
::SetDlgItemText( s_dialog, IDC_TEXT, "Initializing patcher...");
|
::SetDlgItemText( s_launcherInfo.dialog, IDC_TEXT, "Initializing patcher...");
|
||||||
SetTimer(s_dialog, kEventTimer, 250, 0);
|
SetTimer(s_launcherInfo.dialog, kEventTimer, 250, 0);
|
||||||
|
|
||||||
char productString[256];
|
char productString[256];
|
||||||
wchar productStringW[256];
|
wchar productStringW[256];
|
||||||
ProductString(productStringW, arrsize(productStringW));
|
ProductString(productStringW, arrsize(productStringW));
|
||||||
StrToAnsi(productString, productStringW, arrsize(productString));
|
StrToAnsi(productString, productStringW, arrsize(productString));
|
||||||
SendMessage(GetDlgItem(s_dialog, IDC_PRODUCTSTRING), WM_SETTEXT, 0, (LPARAM) productString);
|
SendMessage(GetDlgItem(s_launcherInfo.dialog, IDC_PRODUCTSTRING), WM_SETTEXT, 0, (LPARAM) productString);
|
||||||
|
|
||||||
s_dialogCreateEvent.Signal();
|
s_dialogCreateEvent.Signal();
|
||||||
|
|
||||||
MessagePump(s_dialog);
|
MessagePump(s_launcherInfo.dialog);
|
||||||
|
|
||||||
if (pTGApp)
|
if (pTGApp)
|
||||||
{
|
{
|
||||||
@ -577,9 +575,9 @@ static void WindowThreadProc(void *) {
|
|||||||
pTGApp = NULL;
|
pTGApp = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
s_dialog = 0;
|
s_launcherInfo.dialog = 0;
|
||||||
s_shutdown = true;
|
s_shutdown = true;
|
||||||
s_shutdownEvent.Signal();
|
s_shutdownDialogEvent.Signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
@ -712,8 +710,12 @@ static void StatusCallback(void *)
|
|||||||
HINTERNET hConnect = 0;
|
HINTERNET hConnect = 0;
|
||||||
|
|
||||||
// update while we are running
|
// update while we are running
|
||||||
while(!s_shutdown)
|
do
|
||||||
{
|
{
|
||||||
|
DWORD result = WaitForSingleObject(s_shutdownDesiredEvent.Handle(), UPDATE_STATUSMSG_SECONDS * 1000);
|
||||||
|
if (result == WAIT_OBJECT_0)
|
||||||
|
break;
|
||||||
|
|
||||||
if(BuildTypeServerStatusPath())
|
if(BuildTypeServerStatusPath())
|
||||||
{
|
{
|
||||||
hSession = WinHttpOpen(
|
hSession = WinHttpOpen(
|
||||||
@ -744,12 +746,7 @@ static void StatusCallback(void *)
|
|||||||
WinHttpCloseHandle(hConnect);
|
WinHttpCloseHandle(hConnect);
|
||||||
WinHttpCloseHandle(hSession);
|
WinHttpCloseHandle(hSession);
|
||||||
}
|
}
|
||||||
|
} while (!s_shutdown);
|
||||||
for(unsigned i = 0; i < UPDATE_STATUSMSG_SECONDS && !s_shutdown; ++i)
|
|
||||||
{
|
|
||||||
Sleep(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s_statusEvent.Signal();
|
s_statusEvent.Signal();
|
||||||
}
|
}
|
||||||
@ -1062,16 +1059,17 @@ int __stdcall WinMain (
|
|||||||
}
|
}
|
||||||
|
|
||||||
ShutdownAsyncCore();
|
ShutdownAsyncCore();
|
||||||
s_statusEvent.Wait(kEventWaitForever);
|
|
||||||
|
|
||||||
PostMessage(s_dialog, WM_QUIT, 0, 0); // tell our window to shutdown
|
|
||||||
s_shutdownEvent.Wait(kEventWaitForever); // wait for our window to shutdown
|
// Signal teardown of our junk and stuff.
|
||||||
|
s_shutdownDesiredEvent.Signal();
|
||||||
|
|
||||||
|
// Wait for the hwnd and status event to shutdown
|
||||||
|
s_statusEvent.Wait(kEventWaitForever);
|
||||||
|
s_shutdownDialogEvent.Wait(kEventWaitForever);
|
||||||
|
|
||||||
SetConsoleCtrlHandler(CtrlHandler, FALSE);
|
SetConsoleCtrlHandler(CtrlHandler, FALSE);
|
||||||
|
|
||||||
if (s_event)
|
|
||||||
CloseHandle(s_event);
|
|
||||||
|
|
||||||
s_eventQ.Clear();
|
s_eventQ.Clear();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
|
|
||||||
#include <process.h>
|
#include <process.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include "hsThread.h"
|
||||||
#include "pnUtils/pnUtils.h"
|
#include "pnUtils/pnUtils.h"
|
||||||
#include "pnNetBase/pnNetBase.h"
|
#include "pnNetBase/pnNetBase.h"
|
||||||
#include "pnAsyncCore/pnAsyncCore.h"
|
#include "pnAsyncCore/pnAsyncCore.h"
|
||||||
|
@ -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,22 +66,115 @@ 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);
|
||||||
|
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);
|
||||||
|
DWORD IWaitProcess(HANDLE hProcess);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
@ -86,181 +184,64 @@ static wchar s_newPatcherFile[MAX_PATH];
|
|||||||
***/
|
***/
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
static void NetErrorHandler (ENetProtocol protocol, ENetError error) {
|
static bool CheckMD5(const wchar* path, const wchar* hash)
|
||||||
REF(protocol);
|
{
|
||||||
LogMsg(kLogError, L"NetErr: %s", NetErrorToString(error));
|
plMD5Checksum localMD5;
|
||||||
if (IS_NET_SUCCESS(s_patchResult))
|
plMD5Checksum remoteMD5;
|
||||||
s_patchResult = error;
|
|
||||||
s_downloadComplete = true;
|
|
||||||
|
|
||||||
switch(error) {
|
hsUNIXStream s;
|
||||||
case kNetErrServerBusy:
|
s.Open(path);
|
||||||
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);
|
localMD5.CalcFromStream(&s);
|
||||||
s_patchResult = kNetErrServerBusy;
|
s.Close();
|
||||||
s_downloadComplete = true;
|
|
||||||
break;
|
// 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 void DownloadCallback (
|
static wchar* FormatSystemError()
|
||||||
ENetError result,
|
{
|
||||||
void * param,
|
wchar* error;
|
||||||
const wchar filename[],
|
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
||||||
hsStream * writer
|
NULL,
|
||||||
) {
|
GetLastError(),
|
||||||
REF(param);
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
REF(filename);
|
(LPWSTR)& error,
|
||||||
|
0,
|
||||||
if(IS_NET_ERROR(result)) {
|
NULL);
|
||||||
switch (result) {
|
return error;
|
||||||
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();
|
|
||||||
delete writer;
|
|
||||||
AtomicAdd(&s_numFiles, -1);
|
|
||||||
|
|
||||||
if(!s_numFiles) {
|
|
||||||
s_downloadComplete = true;
|
|
||||||
s_updated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
static bool MD5Check (const char filename[], const wchar md5[]) {
|
static bool IsPatcherFile(const wchar* filename)
|
||||||
// Do md5 check
|
{
|
||||||
char md5copy[MAX_PATH];
|
wchar progPath[MAX_PATH];
|
||||||
plMD5Checksum existingMD5(filename);
|
PathGetProgramName(progPath, arrsize(progPath));
|
||||||
plMD5Checksum latestMD5;
|
wchar* progFilename = PathFindFilename(progPath);
|
||||||
|
return StrCmpI(filename, progFilename) == 0;
|
||||||
StrToAnsi(md5copy, md5, arrsize(md5copy));
|
|
||||||
latestMD5.SetFromHexString(md5copy);
|
|
||||||
return (existingMD5 == latestMD5);
|
|
||||||
}
|
|
||||||
|
|
||||||
//============================================================================
|
|
||||||
static void ManifestCallback (
|
|
||||||
ENetError result,
|
|
||||||
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];
|
|
||||||
|
|
||||||
// MD5 check current patcher against value in manifest
|
|
||||||
ASSERT(entryCount == 1);
|
|
||||||
wchar curPatcherFile[MAX_PATH];
|
|
||||||
PathGetProgramName(curPatcherFile, arrsize(curPatcherFile));
|
|
||||||
StrToAnsi(ansi, curPatcherFile, arrsize(ansi));
|
|
||||||
if (!MD5Check(ansi, manifest[0].md5)) {
|
|
||||||
// MessageBox(GetTopWindow(nil), "MD5 failed", "Msg", MB_OK);
|
|
||||||
SelfPatcherStream::totalBytes += manifest[0].zipSize;
|
|
||||||
|
|
||||||
AtomicAdd(&s_numFiles, 1);
|
|
||||||
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 {
|
|
||||||
s_downloadComplete = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//============================================================================
|
|
||||||
static void FileSrvIpAddressCallback (
|
|
||||||
ENetError result,
|
|
||||||
void * param,
|
|
||||||
const wchar addr[]
|
|
||||||
) {
|
|
||||||
REF(param);
|
|
||||||
|
|
||||||
NetCliGateKeeperDisconnect();
|
|
||||||
|
|
||||||
if (IS_NET_ERROR(result)) {
|
|
||||||
LogMsg(kLogDebug, L"FileSrvIpAddressRequest failed: %s", NetErrorToString(result));
|
|
||||||
s_patchResult = result;
|
|
||||||
s_downloadComplete = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start connecting to the server
|
|
||||||
NetCliFileStartConnect(&addr, 1, true);
|
|
||||||
|
|
||||||
PathGetProgramDirectory(s_newPatcherFile, arrsize(s_newPatcherFile));
|
|
||||||
GetTempFileNameW(s_newPatcherFile, kPatcherExeFilename, 0, s_newPatcherFile);
|
|
||||||
PathDeleteFile(s_newPatcherFile);
|
|
||||||
|
|
||||||
NetCliFileManifestRequest(ManifestCallback, nil, s_manifest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
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
|
if (!*abort && *s_selfPatcher.GetNewPatcherFileName() && IS_NET_SUCCESS(s_selfPatcher.GetResult())) {
|
||||||
NetClientDestroy();
|
|
||||||
|
|
||||||
if (s_downloadComplete && !*abort && s_updated && IS_NET_SUCCESS(s_patchResult)) {
|
|
||||||
|
|
||||||
// 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_newPatcherFile, info->cmdLine);
|
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
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +87,7 @@ struct plLauncherInfo {
|
|||||||
PatchInfo patchInfo;
|
PatchInfo patchInfo;
|
||||||
bool IsTGCider;
|
bool IsTGCider;
|
||||||
DWORD returnCode; // used so we can pass a new process id back to gametap. That way gametap wont think uru has exited when the patcher quits.
|
DWORD returnCode; // used so we can pass a new process id back to gametap. That way gametap wont think uru has exited when the patcher quits.
|
||||||
|
HWND dialog;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user