mirror of
https://foundry.openuru.org/gitblit/r/CWE-ou-minkata.git
synced 2025-07-20 04:09:16 +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 plLauncherInfo s_launcherInfo;
|
||||
static HANDLE s_thread;
|
||||
static HANDLE s_event;
|
||||
static CEvent s_shutdownDesiredEvent(kEventManualReset);
|
||||
static HINSTANCE s_hInstance;
|
||||
static HWND s_dialog;
|
||||
static CEvent s_dialogCreateEvent(kEventManualReset);
|
||||
static CCritSect s_critsect;
|
||||
static LISTDECL(WndEvent, link) s_eventQ;
|
||||
static CEvent s_shutdownEvent(kEventManualReset);
|
||||
static CEvent s_shutdownDialogEvent(kEventManualReset);
|
||||
static wchar s_workingDir[MAX_PATH];
|
||||
static CEvent s_statusEvent(kEventManualReset);
|
||||
|
||||
@ -311,7 +310,7 @@ static void TerminateGame () {
|
||||
|
||||
//============================================================================
|
||||
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)
|
||||
{
|
||||
@ -329,7 +328,7 @@ static void Recv_SetProgress (HWND hwnd, const SetProgressEvent &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)
|
||||
{
|
||||
@ -347,7 +346,7 @@ static void Recv_SetText (HWND hwnd, const SetTextEvent &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)
|
||||
{
|
||||
SendMessage(GetDlgItem(s_dialog, IDC_TIMEREMAINING), WM_SETTEXT, 0, (LPARAM) "estimating...");
|
||||
SendMessage(GetDlgItem(s_launcherInfo.dialog, IDC_TIMEREMAINING), WM_SETTEXT, 0, (LPARAM) "...");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -392,7 +391,7 @@ static void Recv_SetTimeRemaining (HWND hwnd, const SetTimeRemainingEvent &event
|
||||
StrPrintf(text, arrsize(text), "%s%d min ", text, minutes);
|
||||
if( seconds || !text[0])
|
||||
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 bytes = event.bytes;
|
||||
|
||||
if (bytes == 0xffffffff) {
|
||||
SendMessage(GetDlgItem(s_launcherInfo.dialog, IDC_BYTESREMAINING), WM_SETTEXT, 0, (LPARAM) "...");
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned GB = bytes / 1000000000;
|
||||
if(GB)
|
||||
{
|
||||
@ -416,7 +420,7 @@ static void Recv_SetBytesRemaining (HWND hwnd, const SetBytesRemainingEvent &eve
|
||||
decimal = bytes / 100000; // to one decimal place
|
||||
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
|
||||
const DWORD result = MsgWaitForMultipleObjects(
|
||||
1,
|
||||
&s_event,
|
||||
&s_shutdownDesiredEvent.Handle(),
|
||||
false,
|
||||
INFINITE,
|
||||
QS_ALLEVENTS
|
||||
);
|
||||
if (result == WAIT_OBJECT_0)
|
||||
return;
|
||||
if (result == WAIT_OBJECT_0)
|
||||
PostQuitMessage(0);
|
||||
|
||||
// process windows messages
|
||||
MSG msg;
|
||||
|
||||
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
|
||||
if (!IsDialogMessage(s_dialog, &msg)) {
|
||||
if (!IsDialogMessage(s_launcherInfo.dialog, &msg)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
@ -503,8 +507,8 @@ BOOL CALLBACK SplashDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM l
|
||||
if(!s_shutdown)
|
||||
{
|
||||
s_shutdown = true;
|
||||
SendMessage(GetDlgItem(s_dialog, IDC_TEXT), WM_SETTEXT, 0, (LPARAM) "Shutting Down...");
|
||||
EnableWindow(GetDlgItem(s_dialog, IDCANCEL), false);
|
||||
SendMessage(GetDlgItem(s_launcherInfo.dialog, IDC_TEXT), WM_SETTEXT, 0, (LPARAM) "Shutting Down...");
|
||||
EnableWindow(GetDlgItem(s_launcherInfo.dialog, IDCANCEL), false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -538,12 +542,6 @@ BOOL CALLBACK SplashDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM l
|
||||
static void WindowThreadProc(void *) {
|
||||
|
||||
InitCommonControls();
|
||||
s_event = CreateEvent(
|
||||
(LPSECURITY_ATTRIBUTES) 0,
|
||||
false, // auto reset
|
||||
false, // initial state off
|
||||
(LPCTSTR) 0 // name
|
||||
);
|
||||
|
||||
if (TGIsCider)
|
||||
{
|
||||
@ -553,22 +551,22 @@ static void WindowThreadProc(void *) {
|
||||
pTGApp = pTGLaunchUNIXApp (TG_OLD_DIALOG_POPEN_PATH, "w");
|
||||
}
|
||||
|
||||
s_dialog = ::CreateDialog( s_hInstance, MAKEINTRESOURCE( IDD_DIALOG ), NULL, SplashDialogProc );
|
||||
SetWindowText(s_dialog, "URU Launcher");
|
||||
s_launcherInfo.dialog = ::CreateDialog( s_hInstance, MAKEINTRESOURCE( IDD_DIALOG ), NULL, SplashDialogProc );
|
||||
SetWindowText(s_launcherInfo.dialog, "URU Launcher");
|
||||
|
||||
|
||||
::SetDlgItemText( s_dialog, IDC_TEXT, "Initializing patcher...");
|
||||
SetTimer(s_dialog, kEventTimer, 250, 0);
|
||||
::SetDlgItemText( s_launcherInfo.dialog, IDC_TEXT, "Initializing patcher...");
|
||||
SetTimer(s_launcherInfo.dialog, kEventTimer, 250, 0);
|
||||
|
||||
char productString[256];
|
||||
wchar productStringW[256];
|
||||
ProductString(productStringW, arrsize(productStringW));
|
||||
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();
|
||||
|
||||
MessagePump(s_dialog);
|
||||
MessagePump(s_launcherInfo.dialog);
|
||||
|
||||
if (pTGApp)
|
||||
{
|
||||
@ -577,9 +575,9 @@ static void WindowThreadProc(void *) {
|
||||
pTGApp = NULL;
|
||||
}
|
||||
|
||||
s_dialog = 0;
|
||||
s_launcherInfo.dialog = 0;
|
||||
s_shutdown = true;
|
||||
s_shutdownEvent.Signal();
|
||||
s_shutdownDialogEvent.Signal();
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
@ -712,8 +710,12 @@ static void StatusCallback(void *)
|
||||
HINTERNET hConnect = 0;
|
||||
|
||||
// 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())
|
||||
{
|
||||
hSession = WinHttpOpen(
|
||||
@ -744,12 +746,7 @@ static void StatusCallback(void *)
|
||||
WinHttpCloseHandle(hConnect);
|
||||
WinHttpCloseHandle(hSession);
|
||||
}
|
||||
|
||||
for(unsigned i = 0; i < UPDATE_STATUSMSG_SECONDS && !s_shutdown; ++i)
|
||||
{
|
||||
Sleep(1000);
|
||||
}
|
||||
}
|
||||
} while (!s_shutdown);
|
||||
|
||||
s_statusEvent.Signal();
|
||||
}
|
||||
@ -1062,16 +1059,17 @@ int __stdcall WinMain (
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (s_event)
|
||||
CloseHandle(s_event);
|
||||
|
||||
s_eventQ.Clear();
|
||||
break;
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||||
|
||||
#include <process.h>
|
||||
#include <time.h>
|
||||
#include "hsThread.h"
|
||||
#include "pnUtils/pnUtils.h"
|
||||
#include "pnNetBase/pnNetBase.h"
|
||||
#include "pnAsyncCore/pnAsyncCore.h"
|
||||
|
@ -48,6 +48,11 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||||
#include "Pch.h"
|
||||
#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";
|
||||
#endif
|
||||
|
||||
static const wchar s_depManifest[] = L"DependencyPatcher";
|
||||
|
||||
class SelfPatcherStream : public plZlibStream {
|
||||
public:
|
||||
public:
|
||||
SelfPatcherStream();
|
||||
virtual UInt32 Write(UInt32 byteCount, const void* buffer);
|
||||
static plLauncherInfo *info;
|
||||
static unsigned totalBytes;
|
||||
static unsigned progress;
|
||||
static DWORD startTime;
|
||||
};
|
||||
|
||||
unsigned SelfPatcherStream::totalBytes = 0;
|
||||
unsigned SelfPatcherStream::progress = 0;
|
||||
DWORD SelfPatcherStream::startTime = 0;
|
||||
|
||||
static bool s_downloadComplete;
|
||||
static long s_numFiles;
|
||||
static ENetError s_patchResult;
|
||||
static bool s_updated;
|
||||
static wchar s_newPatcherFile[MAX_PATH];
|
||||
//============================================================================
|
||||
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);
|
||||
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) {
|
||||
REF(protocol);
|
||||
LogMsg(kLogError, L"NetErr: %s", NetErrorToString(error));
|
||||
if (IS_NET_SUCCESS(s_patchResult))
|
||||
s_patchResult = error;
|
||||
s_downloadComplete = true;
|
||||
static bool CheckMD5(const wchar* path, const wchar* hash)
|
||||
{
|
||||
plMD5Checksum localMD5;
|
||||
plMD5Checksum remoteMD5;
|
||||
|
||||
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;
|
||||
}
|
||||
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 void DownloadCallback (
|
||||
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();
|
||||
delete writer;
|
||||
AtomicAdd(&s_numFiles, -1);
|
||||
|
||||
if(!s_numFiles) {
|
||||
s_downloadComplete = true;
|
||||
s_updated = true;
|
||||
}
|
||||
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 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);
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
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 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_downloadComplete = false;
|
||||
s_patchResult = kNetSuccess;
|
||||
|
||||
NetClientInitialize();
|
||||
NetClientSetErrorHandler(NetErrorHandler);
|
||||
|
||||
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) {
|
||||
s_selfPatcher.fLauncherInfo = info;
|
||||
s_selfPatcher.Start();
|
||||
while(s_selfPatcher.Active() && !*abort) {
|
||||
NetClientUpdate();
|
||||
AsyncSleep(10);
|
||||
}
|
||||
s_selfPatcher.Stop();
|
||||
|
||||
NetCliFileDisconnect();
|
||||
NetClientUpdate();
|
||||
if (s_selfPatcher.GetResult() == kNetPending)
|
||||
*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
|
||||
STARTUPINFOW si;
|
||||
@ -270,7 +251,7 @@ static bool SelfPatcherProc (bool * abort, plLauncherInfo *info) {
|
||||
si.cb = sizeof(si);
|
||||
|
||||
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
|
||||
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) {
|
||||
progress += byteCount;
|
||||
float p = (float)progress / (float)totalBytes * 100; // progress
|
||||
float p = (float)progress / (float)totalBytes * 1000; // progress
|
||||
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);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* 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...");
|
||||
patched = SelfPatcherProc(abort, info);
|
||||
}
|
||||
*result = s_patchResult;
|
||||
*result = s_selfPatcher.GetResult();
|
||||
return patched;
|
||||
}
|
||||
|
||||
|
@ -87,6 +87,7 @@ struct plLauncherInfo {
|
||||
PatchInfo patchInfo;
|
||||
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.
|
||||
HWND dialog;
|
||||
};
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user