Browse Source

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)
hoikas/newpatcher-1
Adam Johnson 5 years ago committed by rarified
parent
commit
60b108a6b7
  1. 82
      MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plUruLauncher/Main.cpp
  2. 1
      MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plUruLauncher/Pch.h
  3. 911
      MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plUruLauncher/SelfPatcher.cpp
  4. 1
      MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plUruLauncher/plLauncherInfo.h

82
MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plUruLauncher/Main.cpp

@ -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();
// Signal teardown of our junk and stuff.
s_shutdownDesiredEvent.Signal();
// Wait for the hwnd and status event to shutdown
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
s_shutdownDialogEvent.Wait(kEventWaitForever);
SetConsoleCtrlHandler(CtrlHandler, FALSE);
if (s_event)
CloseHandle(s_event);
s_eventQ.Clear();
break;
}

1
MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plUruLauncher/Pch.h

@ -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"

911
MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plUruLauncher/SelfPatcher.cpp

@ -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,206 +66,182 @@ 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;
/*****************************************************************************
*
* Private Functions
*
***/
// 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();
//============================================================================
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;
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;
}
}
// 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);
//============================================================================
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;
}
void IRun();
void IQuit();
writer->Close();
delete writer;
AtomicAdd(&s_numFiles, -1);
public:
plSelfPatcher();
if(!s_numFiles) {
s_downloadComplete = true;
s_updated = true;
}
}
bool Active() const { return GetQuit() == 0; }
const wchar* GetNewPatcherFileName() const { return fNewPatcherFileName; }
ENetError GetResult() const { return fResult; }
//============================================================================
static bool MD5Check (const char filename[], const wchar md5[]) {
// Do md5 check
char md5copy[MAX_PATH];
plMD5Checksum existingMD5(filename);
plMD5Checksum latestMD5;
StrToAnsi(md5copy, md5, arrsize(md5copy));
latestMD5.SetFromHexString(md5copy);
return (existingMD5 == latestMD5);
}
void IssueManifestRequests();
//============================================================================
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;
}
void Start(); // override;
hsError Run(); // override;
void Stop(); // override;
char ansi[MAX_PATH];
public:
plLauncherInfo* fLauncherInfo;
// 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;
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);
AtomicAdd(&s_numFiles, 1);
SetText("Downloading new patcher...");
} s_selfPatcher;
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;
}
}
/*****************************************************************************
*
* Private Functions
*
***/
//============================================================================
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);
static bool CheckMD5(const wchar* path, const wchar* hash)
{
plMD5Checksum localMD5;
plMD5Checksum remoteMD5;
hsUNIXStream s;
s.Open(path);
localMD5.CalcFromStream(&s);
s.Close();
// Some silly goose decided to send an md5 hash as UCS-2 instead of ASCII
char ansi[33];
StrToAnsi(ansi, hash, arrsize(ansi));
remoteMD5.SetFromHexString(ansi);
return localMD5 == remoteMD5;
}
PathGetProgramDirectory(s_newPatcherFile, arrsize(s_newPatcherFile));
GetTempFileNameW(s_newPatcherFile, kPatcherExeFilename, 0, s_newPatcherFile);
PathDeleteFile(s_newPatcherFile);
//============================================================================
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;
}
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();
// Shutdown the client/server networking subsystem
NetClientDestroy();
if (s_selfPatcher.GetResult() == kNetPending)
*abort = true;
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;
}

1
MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plUruLauncher/plLauncherInfo.h

@ -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;
};

Loading…
Cancel
Save