/*==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;
}

