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