/*==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 .
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/Main.cpp
*
***/
#include "Pch.h"
#include "hsThread.h"
#pragma hdrstop
#include "resource.h"
#include
#define WIN32_LEAN_AND_MEAN
#define WHITESPACE L" \"\t\r\n\x1A"
#define UPDATE_STATUSMSG_SECONDS 30 // Must be an int
/*****************************************************************************
*
* Private
*
***/
enum ELogSev {
kLogInfo,
kLogErr,
kNumLogSev
};
enum {
kEventTimer = 1,
};
enum EEventType {
kEventSetProgress,
kEventSetText,
kEventSetStatusText,
kEventSetTimeRemaining,
kEventSetBytesRemaining,
};
// base window event
struct WndEvent {
LINK(WndEvent) link;
EEventType type;
};
struct SetProgressEvent : WndEvent {
int progress;
};
struct SetTextEvent : WndEvent {
char text[MAX_PATH];
};
struct SetStatusTextEvent : WndEvent {
char text[MAX_PATH];
};
struct SetTimeRemainingEvent : WndEvent {
unsigned seconds;
};
struct SetBytesRemainingEvent : WndEvent {
unsigned bytes;
};
/*****************************************************************************
*
* Private data
*
***/
static bool s_shutdown;
static bool s_prepared;
static int s_retCode = 1;
static long s_terminationIssued;
static bool s_terminated;
static plLauncherInfo s_launcherInfo;
static HANDLE s_thread;
static HANDLE s_event;
static HINSTANCE s_hInstance;
static HWND s_dialog;
static hsSemaphore s_dialogCreateEvent(0);
static hsMutex s_critsect;
static LISTDECL(WndEvent, link) s_eventQ;
static hsSemaphore s_shutdownEvent(0);
static wchar_t s_workingDir[MAX_PATH];
static hsSemaphore s_statusEvent(0);
static char s_curlError[CURL_ERROR_SIZE];
/*****************************************************************************
*
* Local functions
*
***/
//============================================================================
static void Abort () {
s_retCode = 0;
s_shutdown = true;
}
//============================================================================
static void PostEvent (WndEvent *event) {
s_critsect.Lock();
s_eventQ.Link(event);
s_critsect.Unlock();
}
//============================================================================
static void LogV (ELogSev sev, const wchar_t fmt[], va_list args) {
static struct { FILE * file; const wchar_t * pre; } s_log[] = {
{ stdout, L"Inf" },
{ stderr, L"Err" },
};
static_assert(arrsize(s_log) == kNumLogSev, "Log severity array and enum have different sizes");
fwprintf (s_log[sev].file, L"%s: ", s_log[sev].pre);
vfwprintf(s_log[sev].file, fmt, args);
fwprintf (s_log[sev].file, L"\n");
if (sev >= kLogErr)
Abort();
}
//============================================================================
static void Log (ELogSev sev, const wchar_t fmt[], ...) {
va_list args;
va_start(args, fmt);
LogV(sev, fmt, args);
va_end(args);
}
//============================================================================
// NOTE: Must use LocalFree() on the return value of this function when finished with the string
static wchar_t *TranslateErrorCode(DWORD errorCode) {
LPVOID lpMsgBuf;
FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
errorCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(wchar_t *) &lpMsgBuf,
0,
NULL
);
return (wchar_t *)lpMsgBuf;
}
//============================================================================
static BOOL WINAPI CtrlHandler (DWORD) {
static unsigned s_ctrlCount;
if (++s_ctrlCount == 3)
_exit(1); // exit process immediately upon 3rd Ctrl-C.
Abort();
return TRUE;
}
//============================================================================
static void PrepareGame () {
SetText("Connecting to server...");
(void)_beginthread(UruPrepProc, 0, (void *) &s_launcherInfo);
}
//============================================================================
static void InitGame () {
s_launcherInfo.initCallback(kStatusOk, nil);
}
//============================================================================
static void StartGame () {
(void)_beginthread(UruStartProc, 0, (void *) &s_launcherInfo);
}
//============================================================================
static void StopGame () {
(void)_beginthread(PlayerStopProc, 0, (void *) &s_launcherInfo);
}
//============================================================================
static void TerminateGame () {
if (!AtomicSet(&s_terminationIssued, 1))
_beginthread(PlayerTerminateProc, 0, (void *) &s_launcherInfo);
}
//============================================================================
static void Recv_SetProgress (HWND hwnd, const SetProgressEvent &event) {
SendMessage(GetDlgItem(s_dialog, IDC_PROGRESS), PBM_SETPOS, event.progress, NULL);
}
//============================================================================
static void Recv_SetText (HWND hwnd, const SetTextEvent &event) {
bool b = SendMessage(GetDlgItem(s_dialog, IDC_TEXT), WM_SETTEXT, 0, (LPARAM) event.text);
}
//============================================================================
static void Recv_SetStatusText (HWND hwnd, const SetStatusTextEvent &event) {
bool b = SendMessage(GetDlgItem(s_dialog, IDC_STATUS_TEXT), WM_SETTEXT, 0, (LPARAM) event.text);
}
//============================================================================
static void Recv_SetTimeRemaining (HWND hwnd, const SetTimeRemainingEvent &event) {
unsigned days;
unsigned hours;
unsigned minutes;
unsigned seconds;
if(event.seconds == 0xffffffff)
{
SendMessage(GetDlgItem(s_dialog, IDC_TIMEREMAINING), WM_SETTEXT, 0, (LPARAM) "estimating...");
return;
}
seconds = event.seconds;
days = seconds / (60 * 60 * 24);
seconds -= (days * 60 * 60 * 24);
hours = seconds / (60 * 60);
seconds -= hours * 60 * 60;
minutes = seconds / 60;
seconds -= minutes * 60;
seconds = seconds;
char text[64] = {0};
if(days)
{
if(days > 1)
StrPrintf(text, arrsize(text), "%d days ", days);
else
StrPrintf(text, arrsize(text), "%d day ", days);
}
if(hours)
{
if(hours > 1)
StrPrintf(text, arrsize(text), "%s%d hours ", text, hours);
else
StrPrintf(text, arrsize(text), "%s%d hour ", text, hours);
}
if(minutes)
StrPrintf(text, arrsize(text), "%s%d min ", text, minutes);
if( seconds || !text[0])
StrPrintf(text, arrsize(text), "%s%d sec", text, seconds);
bool b = SendMessage(GetDlgItem(s_dialog, IDC_TIMEREMAINING), WM_SETTEXT, 0, (LPARAM) text);
}
//============================================================================
static void Recv_SetBytesRemaining (HWND hwnd, const SetBytesRemainingEvent &event) {
char text[32];
unsigned MB;
unsigned decimal;
unsigned bytes = event.bytes;
unsigned GB = bytes / 1000000000;
if(GB)
{
bytes -= GB * 1000000000;
decimal = bytes / 100000000; // to two decimal places
StrPrintf(text, arrsize(text), "%d.%d GB", GB, decimal);
}
else
{
MB = bytes / 1000000;
bytes -= MB * 1000000;
decimal = bytes / 100000; // to one decimal place
StrPrintf(text, arrsize(text), "%d.%d MB", MB, decimal);
}
bool b = SendMessage(GetDlgItem(s_dialog, IDC_BYTESREMAINING), WM_SETTEXT, 0, (LPARAM) text);
}
//============================================================================
static void DispatchEvents (HWND hwnd) {
LISTDECL(WndEvent, link) eventQ;
s_critsect.Lock();
{
eventQ.Link(&s_eventQ);
}
s_critsect.Unlock();
#define DISPATCH(a) case kEvent##a: Recv_##a(hwnd, *(const a##Event *) event); break
while (WndEvent *event = eventQ.Head()) {
switch (event->type) {
DISPATCH(SetProgress);
DISPATCH(SetText);
DISPATCH(SetStatusText);
DISPATCH(SetTimeRemaining);
DISPATCH(SetBytesRemaining);
DEFAULT_FATAL(event->type);
}
delete event; // unlinks from list
}
#undef DISPATCH
}
//============================================================================
static void OnTimer(HWND hwnd, unsigned int timerId) {
if(s_shutdown) return;
switch (timerId) {
case kEventTimer:
DispatchEvents(hwnd);
break;
DEFAULT_FATAL(timerId);
}
}
//===========================================================================
static void MessagePump (HWND hwnd) {
for (;;) {
// wait for a message or the shutdown event
const DWORD result = MsgWaitForMultipleObjects(
1,
&s_event,
false,
INFINITE,
QS_ALLEVENTS
);
if (result == WAIT_OBJECT_0)
return;
// process windows messages
MSG msg;
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
if (!IsDialogMessage(s_dialog, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (msg.message == WM_QUIT) {
return;
}
}
}
}
//============================================================================
BOOL CALLBACK SplashDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
switch( uMsg )
{
case WM_INITDIALOG:
{
PostMessage( GetDlgItem(hwndDlg, IDC_PROGRESS), PBM_SETRANGE, 0, MAKELPARAM(0, 1000));
}
break;
case WM_COMMAND:
if(HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDCANCEL) {
// we dont shutdown the window here, but instead let the patcher know it needs to shutdown, and display our shutting down message.
// setting s_shutdown also wont allow any more Set text messages.
if(!s_shutdown)
{
s_shutdown = true;
SendMessage(GetDlgItem(s_dialog, IDC_TEXT), WM_SETTEXT, 0, (LPARAM) "Shutting Down...");
EnableWindow(GetDlgItem(s_dialog, IDCANCEL), false);
}
}
break;
case WM_KEYDOWN:
break;
case WM_NCHITTEST:
SetWindowLongPtr(hwndDlg, DWL_MSGRESULT, (LONG_PTR)HTCAPTION);
return TRUE;
case WM_TIMER:
OnTimer(hwndDlg, wParam);
break;
case WM_QUIT:
::DestroyWindow(hwndDlg);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwndDlg, uMsg, wParam, lParam);
}
return TRUE;
}
//============================================================================
static void WindowThreadProc(void *) {
InitCommonControls();
s_event = CreateEvent(
(LPSECURITY_ATTRIBUTES) 0,
false, // auto reset
false, // initial state off
(LPCTSTR) 0 // name
);
s_dialog = ::CreateDialog( s_hInstance, MAKEINTRESOURCE( IDD_DIALOG ), NULL, SplashDialogProc );
SetWindowText(s_dialog, "URU Launcher");
::SetDlgItemText( s_dialog, IDC_TEXT, "Initializing patcher...");
SetTimer(s_dialog, kEventTimer, 250, 0);
char productString[256];
wchar_t productStringW[256];
ProductString(productStringW, arrsize(productStringW));
StrToAnsi(productString, productStringW, arrsize(productString));
SendMessage(GetDlgItem(s_dialog, IDC_PRODUCTSTRING), WM_SETTEXT, 0, (LPARAM) productString);
s_dialogCreateEvent.Signal();
MessagePump(s_dialog);
s_dialog = 0;
s_shutdown = true;
s_shutdownEvent.Signal();
}
//============================================================================
static size_t CurlCallback(void *buffer, size_t size, size_t nmemb, void *)
{
static char status[256];
strncpy(status, (const char *)buffer, std::min(size * nmemb, 256));
status[255] = 0;
SetStatusText(status);
return size * nmemb;
}
//============================================================================
static void StatusCallback(void *)
{
const char *serverUrl = GetServerStatusUrl();
CURL * hCurl = curl_easy_init();
curl_easy_setopt(hCurl, CURLOPT_ERRORBUFFER, s_curlError);
// update while we are running
while(!s_shutdown)
{
curl_easy_setopt(hCurl, CURLOPT_USERAGENT, "UruClient/1.0");
curl_easy_setopt(hCurl, CURLOPT_URL, serverUrl);
curl_easy_setopt(hCurl, CURLOPT_WRITEFUNCTION, &CurlCallback);
if (serverUrl[0] && curl_easy_perform(hCurl) != 0) // only perform request if there's actually a URL set
SetStatusText(s_curlError);
for(unsigned i = 0; i < UPDATE_STATUSMSG_SECONDS && !s_shutdown; ++i)
{
Sleep(1000);
}
}
curl_easy_cleanup(hCurl);
s_statusEvent.Signal();
}
/*****************************************************************************
*
* Exports
*
***/
//============================================================================
void PrepCallback (int id, void *param) {
s_prepared = true;
if (id)
s_shutdown = true;
if (!s_shutdown)
InitGame();
}
//============================================================================
void InitCallback (int id, void *param) {
if (id)
s_shutdown = true;
if (!s_shutdown)
StartGame();
}
//=============================================================================
void StartCallback( int id, void *param) {
if(id == kStatusError) {
MessageBox(nil, "Failed to launch URU", "URU Launcher", MB_ICONERROR);
}
StopGame();
}
//============================================================================
void StopCallback (int id, void *param) {
s_shutdown = true;
TerminateGame();
}
//============================================================================
void TerminateCallback (int id, void *param) {
s_shutdown = true;
s_terminated = true;
}
//============================================================================
void ExitCallback (int id, void *param) {
TerminateGame();
}
//============================================================================
void ProgressCallback (int id, void *param) {
PatchInfo *patchInfo = (PatchInfo *)param;
SetProgress(patchInfo->progress);
}
//============================================================================
void SetTextCallback (const char text[]) {
SetText(text);
}
//============================================================================
void SetStatusTextCallback (const char text[]) {
SetStatusText(text);
}
//============================================================================
void SetTimeRemainingCallback (unsigned seconds) {
SetTimeRemaining(seconds);
}
//============================================================================
void SetBytesRemainingCallback (unsigned bytes) {
SetBytesRemaining(bytes);
}
enum {
kArgServerIni,
kArgNoSelfPatch,
kArgBuildId,
kArgCwd,
};
static const CmdArgDef s_cmdLineArgs[] = {
{ kCmdArgFlagged | kCmdTypeString, L"ServerIni", kArgServerIni },
{ kCmdArgFlagged | kCmdTypeBool, L"NoSelfPatch", kArgNoSelfPatch },
{ kCmdArgFlagged | kCmdTypeInt, L"BuildId", kArgBuildId },
{ kCmdArgFlagged | kCmdTypeBool, L"Cwd", kArgCwd },
};
#include "pfConsoleCore/pfConsoleEngine.h"
PF_CONSOLE_LINK_FILE(Core)
//============================================================================
int __stdcall WinMain (
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
){
PF_CONSOLE_INITIALIZE(Core)
wchar_t token[256];
const wchar_t *appCmdLine = AppGetCommandLine();
StrTokenize(&appCmdLine, token, arrsize(token), WHITESPACE);
while(!StrStr(token, L".exe") && !StrStr(token, L".tmp"))
{
StrTokenize(&appCmdLine, token, arrsize(token), WHITESPACE);
}
while (*appCmdLine == L' ')
++appCmdLine;
wchar_t curPatcherFile[MAX_PATH];
wchar_t newPatcherFile[MAX_PATH];
bool isTempPatcher = false;
PathGetProgramName(curPatcherFile, arrsize(curPatcherFile));
PathRemoveFilename(newPatcherFile, curPatcherFile, arrsize(newPatcherFile));
PathAddFilename(newPatcherFile, newPatcherFile, kPatcherExeFilename, arrsize(newPatcherFile));
// If our exe name doesn't match the "real" patcher exe name, then we are a newly
// downloaded patcher that needs to be copied over to the "real" exe.. so do that,
// exec it, and exit.
if (0 != StrCmpI(curPatcherFile, newPatcherFile)) {
isTempPatcher = true;
}
CCmdParser cmdParser(s_cmdLineArgs, arrsize(s_cmdLineArgs));
cmdParser.Parse();
if (!cmdParser.IsSpecified(kArgCwd))
PathGetProgramDirectory(s_workingDir, arrsize(s_workingDir));
s_hInstance = hInstance;
memset(&s_launcherInfo, 0, sizeof(s_launcherInfo));
StrPrintf(s_launcherInfo.cmdLine, arrsize(s_launcherInfo.cmdLine), appCmdLine);
s_launcherInfo.returnCode = 0;
curl_global_init(CURL_GLOBAL_ALL);
const wchar_t *serverIni = L"server.ini";
if(cmdParser.IsSpecified(kArgServerIni))
serverIni = cmdParser.GetString(kArgServerIni);
// Load the server.ini so we know what to connect to
FILE *serverini = _wfopen(serverIni, L"rb");
if (serverini)
{
fclose(serverini);
pfConsoleEngine tempConsole;
tempConsole.ExecuteFile(serverIni);
}
else
{
hsMessageBox("No server.ini file found. Please check your URU installation.", "Error", hsMessageBoxNormal);
return 1;
}
if(!isTempPatcher)
{
// create window thread
s_thread = (HANDLE)_beginthread(
WindowThreadProc,
0,
nil
);
if(cmdParser.IsSpecified(kArgBuildId))
s_launcherInfo.buildId = cmdParser.GetInt(kArgBuildId);
// Wait for the dialog to be created
s_dialogCreateEvent.Wait();
_beginthread(StatusCallback, 0, nil); // get status
}
for (;;) {
// Wait for previous process to exit. This will happen if we just patched.
HANDLE mutex = CreateMutexW(NULL, TRUE, kPatcherExeFilename);
DWORD wait = WaitForSingleObject(mutex, 0);
while(!s_shutdown && wait != WAIT_OBJECT_0)
wait = WaitForSingleObject(mutex, 100);
// User canceled
if (s_shutdown)
break;
// If our exe name doesn't match the "real" patcher exe name, then we are a newly
// downloaded patcher that needs to be copied over to the "real" exe.. so do that,
// exec it, and exit.
if (isTempPatcher) {
// MessageBox(nil, "Replacing patcher file", "Msg", MB_OK);
// Wait for the other process to exit
Sleep(1000);
if (!plFileUtils::RemoveFile(newPatcherFile)) {
wchar_t error[256];
DWORD errorCode = GetLastError();
wchar_t *msg = TranslateErrorCode(errorCode);
StrPrintf(error, arrsize(error), L"Failed to delete old patcher executable. %s", msg);
MessageBoxW(GetTopWindow(nil), error, L"Error", MB_OK);
LocalFree(msg);
break;
}
if (!plFileUtils::FileMove(curPatcherFile, newPatcherFile)) {
wchar_t error[256];
DWORD errorCode = GetLastError();
wchar_t *msg = TranslateErrorCode(errorCode);
StrPrintf(error, arrsize(error), L"Failed to replace old patcher executable. %s", msg);
MessageBoxW(GetTopWindow(nil), error, L"Error", MB_OK);
// attempt to clean up this tmp file
plFileUtils::RemoveFile(curPatcherFile);
LocalFree(msg);
break;
}
// launch new patcher
STARTUPINFOW si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
memset(&pi, 0, sizeof(pi));
si.cb = sizeof(si);
wchar_t cmdline[MAX_PATH];
StrPrintf(cmdline, arrsize(cmdline), L"%s %s", newPatcherFile, s_launcherInfo.cmdLine);
// we have only successfully patched if we actually launch the new version of the patcher
(void)CreateProcessW(
NULL,
cmdline,
NULL,
NULL,
FALSE,
DETACHED_PROCESS,
NULL,
NULL,
&si,
&pi
);
SetReturnCode( pi.dwProcessId );
CloseHandle( pi.hThread );
CloseHandle( pi.hProcess );
// We're done.
break;
}
// Clean up old temp files
ARRAY(PathFind) paths;
wchar_t fileSpec[MAX_PATH];
PathGetProgramDirectory(fileSpec, arrsize(fileSpec));
PathAddFilename(fileSpec, fileSpec, L"*.tmp", arrsize(fileSpec));
PathFindFiles(&paths, fileSpec, kPathFlagFile);
for (PathFind * path = paths.Ptr(); path != paths.Term(); ++path)
plFileUtils::RemoveFile(path->name);
SetConsoleCtrlHandler(CtrlHandler, TRUE);
InitAsyncCore(); // must do this before self patch, since it needs to connect to the file server
// check to see if the patcher needs to be updated, and do it if so.
ENetError selfPatchResult;
if (false == (SelfPatch(cmdParser.IsSpecified(kArgNoSelfPatch), &s_shutdown, &selfPatchResult, &s_launcherInfo)) && IS_NET_SUCCESS(selfPatchResult)) {
// We didn't self-patch, so check for client updates and download them, then exec the client
StrCopy(s_launcherInfo.path, s_workingDir, arrsize(s_launcherInfo.path));
s_launcherInfo.prepCallback = PrepCallback;
s_launcherInfo.initCallback = InitCallback;
s_launcherInfo.startCallback = StartCallback;
s_launcherInfo.stopCallback = StopCallback;
s_launcherInfo.terminateCallback = TerminateCallback;
s_launcherInfo.progressCallback = ProgressCallback;
s_launcherInfo.exitCallback = ExitCallback;
s_launcherInfo.SetText = SetTextCallback;
s_launcherInfo.SetStatusText = SetStatusTextCallback;
s_launcherInfo.SetTimeRemaining = SetTimeRemainingCallback;
s_launcherInfo.SetBytesRemaining = SetBytesRemainingCallback;
PrepareGame();
while (!s_shutdown) // wait for window to be closed
AsyncSleep(10);
StopGame();
// Wait for the PrepareGame thread to exit
while (!s_prepared)
AsyncSleep(10);
// Wait for the StopGame thread to exit
while (!s_terminated)
Sleep(10);
}
else if (IS_NET_ERROR(selfPatchResult)) {
// Self-patch failed
SetText("Self-patch failed. Exiting...");
if (!s_shutdown) {
wchar_t str[256];
StrPrintf(str, arrsize(str), L"Patcher update failed. Error %u, %s", selfPatchResult, NetErrorToString(selfPatchResult));
MessageBoxW(GetTopWindow(nil), str, L"Error", MB_OK);
}
}
else {
// We self-patched, so just exit (self-patcher already launched the new patcher.
// it is now waiting for our process to shutdown and release the shared mutex).
SetText("Patcher updated. Restarting...");
s_shutdown = true;
}
ShutdownAsyncCore();
s_statusEvent.Wait();
PostMessage(s_dialog, WM_QUIT, 0, 0); // tell our window to shutdown
s_shutdownEvent.Wait(); // wait for our window to shutdown
SetConsoleCtrlHandler(CtrlHandler, FALSE);
if (s_event)
CloseHandle(s_event);
s_eventQ.Clear();
break;
}
curl_global_cleanup();
return s_launcherInfo.returnCode;
}
//============================================================================
void SetReturnCode (DWORD retCode) {
s_launcherInfo.returnCode = retCode;
}
/*****************************************************************************
*
* Window Events
*
***/
//============================================================================
void SetProgress (unsigned progress) {
SetProgressEvent *event = new SetProgressEvent();
event->type = kEventSetProgress;
event->progress = progress;
PostEvent(event);
}
//============================================================================
void SetText (const char text[]) {
SetTextEvent *event = new SetTextEvent();
event->type = kEventSetText;
StrCopy(event->text, text, arrsize(event->text));
PostEvent(event);
}
//============================================================================
void SetStatusText (const char text[]) {
SetTextEvent *event = new SetTextEvent();
event->type = kEventSetStatusText;
StrCopy(event->text, text, arrsize(event->text));
PostEvent(event);
}
//============================================================================
void SetTimeRemaining (unsigned seconds) {
SetTimeRemainingEvent *event = new SetTimeRemainingEvent;
event->type = kEventSetTimeRemaining;
event->seconds = seconds;
PostEvent(event);
}
//============================================================================
void SetBytesRemaining (unsigned bytes) {
SetBytesRemainingEvent *event = new SetBytesRemainingEvent;
event->type = kEventSetBytesRemaining;
event->bytes = bytes;
PostEvent(event);
}