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.
922 lines
28 KiB
922 lines
28 KiB
/*==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) { |
|
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; |
|
} |
|
|
|
|