mirror of
https://foundry.openuru.org/gitblit/r/CWE-ou-minkata.git
synced 2025-07-14 02:27:40 -04:00
Merge pull request #353 from Hoikas/new-launcher
Unified Patcher (Part 2: Nuke eap)
This commit is contained in:
@ -6,7 +6,6 @@ if(PLASMA_BUILD_LAUNCHER)
|
||||
endif()
|
||||
|
||||
if(PLASMA_BUILD_TOOLS)
|
||||
add_subdirectory(plClientPatcher)
|
||||
add_subdirectory(plPythonPack)
|
||||
add_subdirectory(plFileSecure)
|
||||
add_subdirectory(plFileEncrypt)
|
||||
|
@ -1,28 +0,0 @@
|
||||
add_definitions(-D_LIB)
|
||||
|
||||
include_directories("../../CoreLib")
|
||||
include_directories("../../NucleusLib/inc")
|
||||
include_directories("../../NucleusLib")
|
||||
include_directories("../../PubUtilLib")
|
||||
|
||||
include_directories(${OPENSSL_INCLUDE_DIR})
|
||||
|
||||
set(plClientPatcher_HEADERS
|
||||
Intern.h
|
||||
Pch.h
|
||||
UruPlayer.h
|
||||
)
|
||||
|
||||
set(plClientPatcher_SOURCES
|
||||
UruPlayer.cpp
|
||||
)
|
||||
|
||||
add_library(plClientPatcher STATIC ${plClientPatcher_HEADERS} ${plClientPatcher_SOURCES})
|
||||
target_link_libraries(plClientPatcher CoreLib plAudioCore plStatusLog)
|
||||
|
||||
if(USE_VLD)
|
||||
target_link_libraries(plClientPatcher ${VLD_LIBRARY})
|
||||
endif()
|
||||
|
||||
source_group("Header Files" FILES ${plClientPatcher_HEADERS})
|
||||
source_group("Source Files" FILES ${plClientPatcher_SOURCES})
|
@ -1,58 +0,0 @@
|
||||
/*==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/plClientPatcher/Intern.h
|
||||
*
|
||||
***/
|
||||
|
||||
#ifdef PLASMA20_SOURCES_PLASMA_APPS_PLCLIENTPATCHER_INTERN_H
|
||||
#error "Header $/Plasma20/Sources/Plasma/Apps/plClientPatcher/Intern.h included more than once"
|
||||
#endif
|
||||
#define PLASMA20_SOURCES_PLASMA_APPS_PLCLIENTPATCHER_INTERN_H
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* SelfPatcher.cpp
|
||||
*
|
||||
***/
|
@ -1,75 +0,0 @@
|
||||
/*==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/plClientPatcher/Pch.h
|
||||
*
|
||||
***/
|
||||
|
||||
#ifdef PLASMA20_SOURCES_PLASMA_APPS_PLCLIENTPATCHER_PCH_H
|
||||
#error "Header $/Plasma20/Sources/Plasma/Apps/plClientPatcher/Pch.h included more than once"
|
||||
#endif
|
||||
#define PLASMA20_SOURCES_PLASMA_APPS_PLCLIENTPATCHER_PCH_H
|
||||
|
||||
#include "hsWindows.h"
|
||||
#include <process.h>
|
||||
#include <ctime>
|
||||
#include "pnUtils/pnUtils.h"
|
||||
#include "pnNetBase/pnNetBase.h"
|
||||
#include "pnAsyncCore/pnAsyncCore.h"
|
||||
#include "plProduct.h"
|
||||
#include "pnNetCli/pnNetCli.h"
|
||||
#include "plNetGameLib/plNetGameLib.h"
|
||||
#include "pnEncryption/plChecksum.h"
|
||||
#include "plAgeDescription/plAgeManifest.h"
|
||||
#include "plAudioCore/plAudioFileReader.h"
|
||||
|
||||
#define USES_PROTOCOL_CLI2AUTH
|
||||
#include "pnNetProtocol/pnNetProtocol.h"
|
||||
|
||||
#include "UruPlayer.h"
|
||||
|
||||
#include "plCompression/plZlibStream.h"
|
||||
#include "Intern.h"
|
||||
#include "../plUruLauncher/plLauncherInfo.h"
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,67 +0,0 @@
|
||||
/*==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/plClientPatcher/UruPlayer.h
|
||||
*
|
||||
***/
|
||||
|
||||
#ifdef PLASMA20_SOURCES_PLASMA_APPS_PLCLIENTPATCHER_URUPLAYER_H
|
||||
#error "Header $/Plasma20/Sources/Plasma/Apps/plClientPatcher/UruPlayer.h included more than once"
|
||||
#endif
|
||||
#define PLASMA20_SOURCES_PLASMA_APPS_PLCLIENTPATCHER_URUPLAYER_H
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* UruPlayer.cpp
|
||||
*
|
||||
***/
|
||||
|
||||
void InitAsyncCore ();
|
||||
void ShutdownAsyncCore () ;
|
||||
void UruPrepProc (void * param);
|
||||
void UruStartProc (void * param);
|
||||
void PlayerTerminateProc (void * param);
|
||||
void PlayerStopProc (void * param);
|
||||
|
||||
extern plFileName kPatcherExeFilename;
|
@ -1,65 +0,0 @@
|
||||
/*==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/plLauncherCallback.h
|
||||
*
|
||||
***/
|
||||
|
||||
#ifdef PLASMA20_SOURCES_PLASMA_APPS_PLURULAUNCHER_PLLAUNCHERCALLBACK_H
|
||||
#error "Header $/Plasma20/Sources/Plasma/Apps/plUruLauncher/plLauncherCallback.h included more than once"
|
||||
#endif
|
||||
#define PLASMA20_SOURCES_PLASMA_APPS_PLURULAUNCHER_PLLAUNCHERCALLBACK_H
|
||||
|
||||
enum EStatus {
|
||||
kStatusOk,
|
||||
};
|
||||
|
||||
typedef void (*launcherCallback)(int status, void *param);
|
||||
struct plLauncherCallback {
|
||||
launcherCallback prepCallback;
|
||||
launcherCallback initCallback;
|
||||
launcherCallback startCallback;
|
||||
launcherCallback stopCallback;
|
||||
launcherCallback terminateCallback;
|
||||
|
||||
};
|
@ -9,14 +9,12 @@ include_directories(${OPENSSL_INCLUDE_DIR})
|
||||
include_directories(${CURL_INCLUDE_DIR})
|
||||
|
||||
set(plUruLauncher_HEADERS
|
||||
Intern.h
|
||||
Pch.h
|
||||
plLauncherInfo.h
|
||||
plClientLauncher.h
|
||||
)
|
||||
|
||||
set(plUruLauncher_SOURCES
|
||||
Main.cpp
|
||||
SelfPatcher.cpp
|
||||
plClientLauncher.cpp
|
||||
winmain.cpp
|
||||
)
|
||||
|
||||
set(plUruLauncher_RESOURCES
|
||||
@ -34,12 +32,10 @@ if(PLASMA_EXTERNAL_RELEASE)
|
||||
endif(PLASMA_EXTERNAL_RELEASE)
|
||||
target_link_libraries(plUruLauncher CoreLib)
|
||||
target_link_libraries(plUruLauncher pfConsoleCore)
|
||||
target_link_libraries(plUruLauncher pfPatcher)
|
||||
target_link_libraries(plUruLauncher plAudioCore)
|
||||
target_link_libraries(plUruLauncher plClientPatcher)
|
||||
target_link_libraries(plUruLauncher plCompression)
|
||||
target_link_libraries(plUruLauncher plFile)
|
||||
target_link_libraries(plUruLauncher plNetClient)
|
||||
target_link_libraries(plUruLauncher plNetClientComm)
|
||||
target_link_libraries(plUruLauncher plNetGameLib)
|
||||
target_link_libraries(plUruLauncher plNetMessage)
|
||||
target_link_libraries(plUruLauncher plNetTransport)
|
||||
|
@ -1,61 +0,0 @@
|
||||
/*==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/Intern.h
|
||||
*
|
||||
***/
|
||||
|
||||
#ifdef PLASMA20_SOURCES_PLASMA_APPS_PLURULAUNCHER_INTERN_H
|
||||
#error "Header $/Plasma20/Sources/Plasma/Apps/plUruLauncher/Intern.h included more than once"
|
||||
#endif
|
||||
#define PLASMA20_SOURCES_PLASMA_APPS_PLURULAUNCHER_INTERN_H
|
||||
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* SelfPatcher.cpp
|
||||
*
|
||||
***/
|
||||
bool SelfPatch (bool noSelfPatch, bool * abort, ENetError * result, plLauncherInfo *info);
|
||||
void SetReturnCode (DWORD retCode);
|
@ -1,884 +0,0 @@
|
||||
/*==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/Main.cpp
|
||||
*
|
||||
***/
|
||||
|
||||
#include "Pch.h"
|
||||
#include "hsThread.h"
|
||||
#include <algorithm>
|
||||
#pragma hdrstop
|
||||
|
||||
|
||||
#include "resource.h"
|
||||
#include <commctrl.h>
|
||||
#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 plFileName s_workingDir;
|
||||
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 *) {
|
||||
#ifdef USE_VLD
|
||||
VLDEnable();
|
||||
#endif
|
||||
|
||||
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);
|
||||
|
||||
SendMessage(GetDlgItem(s_dialog, IDC_PRODUCTSTRING), WM_SETTEXT, 0,
|
||||
(LPARAM)plProduct::ProductString().c_str());
|
||||
|
||||
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_t>(size * nmemb, 256));
|
||||
status[255] = 0;
|
||||
SetStatusText(status);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
static void StatusCallback(void *)
|
||||
{
|
||||
#ifdef USE_VLD
|
||||
VLDEnable();
|
||||
#endif
|
||||
|
||||
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;
|
||||
|
||||
bool isTempPatcher = false;
|
||||
|
||||
plFileName curPatcherFile = plFileSystem::GetCurrentAppPath();
|
||||
plFileName newPatcherFile = plFileName::Join(curPatcherFile.StripFileName(), kPatcherExeFilename);
|
||||
|
||||
// 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 != curPatcherFile.AsString().CompareI(newPatcherFile.AsString())) {
|
||||
isTempPatcher = true;
|
||||
}
|
||||
|
||||
CCmdParser cmdParser(s_cmdLineArgs, arrsize(s_cmdLineArgs));
|
||||
cmdParser.Parse();
|
||||
|
||||
if (!cmdParser.IsSpecified(kArgCwd))
|
||||
s_workingDir = plFileSystem::GetCurrentAppPath().StripFileName();
|
||||
|
||||
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);
|
||||
|
||||
plFileName serverIni = "server.ini";
|
||||
if (cmdParser.IsSpecified(kArgServerIni))
|
||||
serverIni = plString::FromWchar(cmdParser.GetString(kArgServerIni));
|
||||
|
||||
// Load the server.ini so we know what to connect to
|
||||
FILE *serverini = plFileSystem::Open(serverIni, "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.AsString().ToWchar());
|
||||
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 (!plFileSystem::Unlink(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 (!plFileSystem::Move(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
|
||||
plFileSystem::Unlink(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.AsString().c_str(), 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
|
||||
plFileName fileSpec = plFileSystem::GetCurrentAppPath().StripFileName();
|
||||
std::vector<plFileName> tmpFiles = plFileSystem::ListDir(fileSpec, "*.tmp");
|
||||
std::for_each(tmpFiles.begin(), tmpFiles.end(), [](const plFileName &tmp) {
|
||||
plFileSystem::Unlink(tmp);
|
||||
});
|
||||
|
||||
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.AsString().ToWchar(), 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);
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
/*==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/Pch.h
|
||||
*
|
||||
***/
|
||||
|
||||
#ifdef PLASMA20_SOURCES_PLASMA_APPS_PLURULAUNCHER_PCH_H
|
||||
#error "Header $/Plasma20/Sources/Plasma/Apps/plUruLauncher/Pch.h included more than once"
|
||||
#endif
|
||||
#define PLASMA20_SOURCES_PLASMA_APPS_PLURULAUNCHER_PCH_H
|
||||
|
||||
#include "hsWindows.h"
|
||||
#include <process.h>
|
||||
#include <ctime>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "pnUtils/pnUtils.h"
|
||||
#include "pnNetBase/pnNetBase.h"
|
||||
#include "pnAsyncCore/pnAsyncCore.h"
|
||||
#include "plProduct.h"
|
||||
#include "pnNetCli/pnNetCli.h"
|
||||
#include "plNetGameLib/plNetGameLib.h"
|
||||
#include "pnEncryption/plChecksum.h"
|
||||
|
||||
#include "plCompression/plZlibStream.h"
|
||||
#include "plClientPatcher/UruPlayer.h"
|
||||
|
||||
#include "plLauncherInfo.h"
|
||||
#include "Intern.h"
|
||||
|
@ -1,338 +0,0 @@
|
||||
/*==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"
|
||||
#include "plStatusLog/plStatusLog.h"
|
||||
#pragma hdrstop
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Private Data
|
||||
*
|
||||
***/
|
||||
|
||||
#ifndef PLASMA_EXTERNAL_RELEASE
|
||||
static const wchar_t s_manifest[] = L"InternalPatcher";
|
||||
#else
|
||||
static const wchar_t s_manifest[] = L"ExternalPatcher";
|
||||
#endif
|
||||
|
||||
class SelfPatcherStream : public plZlibStream {
|
||||
public:
|
||||
virtual uint32_t Write(uint32_t byteCount, const void* buffer);
|
||||
static plLauncherInfo *info;
|
||||
static unsigned totalBytes;
|
||||
static unsigned progress;
|
||||
};
|
||||
|
||||
unsigned SelfPatcherStream::totalBytes = 0;
|
||||
unsigned SelfPatcherStream::progress = 0;
|
||||
|
||||
static bool s_downloadComplete;
|
||||
static long s_numFiles;
|
||||
static ENetError s_patchResult;
|
||||
static bool s_updated;
|
||||
static plFileName s_newPatcherFile;
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Private Functions
|
||||
*
|
||||
***/
|
||||
|
||||
//============================================================================
|
||||
static void NetErrorHandler (ENetProtocol protocol, ENetError error) {
|
||||
plString msg = plString::Format("NetErr: %S", NetErrorToString(error));
|
||||
plStatusLog::AddLineS("patcher.log", msg.c_str());
|
||||
|
||||
if (IS_NET_SUCCESS(s_patchResult))
|
||||
s_patchResult = error;
|
||||
s_downloadComplete = true;
|
||||
|
||||
switch(error) {
|
||||
case kNetErrServerBusy:
|
||||
MessageBox(0, "Due to the high demand, the server is currently busy. Please try again later, or for alternative download options visit: http://www.mystonline.com/play/", "UruLauncher", MB_OK);
|
||||
s_patchResult = kNetErrServerBusy;
|
||||
s_downloadComplete = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
static void DownloadCallback (
|
||||
ENetError result,
|
||||
void * param,
|
||||
const plFileName & filename,
|
||||
hsStream * writer
|
||||
) {
|
||||
if(IS_NET_ERROR(result)) {
|
||||
switch (result) {
|
||||
case kNetErrTimeout:
|
||||
writer->Rewind();
|
||||
NetCliFileDownloadRequest(filename, writer, DownloadCallback, param);
|
||||
break;
|
||||
|
||||
default:
|
||||
plString msg = plString::Format("Error getting patcher file: %S", NetErrorToString(result));
|
||||
plStatusLog::AddLineS("patcher.log", msg.c_str());
|
||||
|
||||
if (IS_NET_SUCCESS(s_patchResult))
|
||||
s_patchResult = result;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
writer->Close();
|
||||
delete writer;
|
||||
AtomicAdd(&s_numFiles, -1);
|
||||
|
||||
if(!s_numFiles) {
|
||||
s_downloadComplete = true;
|
||||
s_updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
static bool MD5Check (const plFileName &filename, const char *md5) {
|
||||
// Do md5 check
|
||||
plMD5Checksum existingMD5(filename);
|
||||
plMD5Checksum latestMD5;
|
||||
|
||||
latestMD5.SetFromHexString(md5);
|
||||
return (existingMD5 == latestMD5);
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
static void ManifestCallback (
|
||||
ENetError result,
|
||||
void * param,
|
||||
const wchar_t group[],
|
||||
const NetCliFileManifestEntry manifest[],
|
||||
unsigned entryCount
|
||||
) {
|
||||
if(IS_NET_ERROR(result)) {
|
||||
switch (result) {
|
||||
case kNetErrTimeout:
|
||||
NetCliFileManifestRequest(ManifestCallback, nil, s_manifest);
|
||||
break;
|
||||
|
||||
default:
|
||||
plString msg = plString::Format("Error getting patcher manifest: %S", NetErrorToString(result));
|
||||
plStatusLog::AddLineS("patcher.log", msg.c_str());
|
||||
|
||||
if (IS_NET_SUCCESS(s_patchResult))
|
||||
s_patchResult = result;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef PLASMA_EXTERNAL_RELEASE
|
||||
if (entryCount == 0) { // dataserver does not contain a patcher
|
||||
s_downloadComplete = true;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// MD5 check current patcher against value in manifest
|
||||
ASSERT(entryCount == 1);
|
||||
plFileName curPatcherFile = plFileSystem::GetCurrentAppPath();
|
||||
if (!MD5Check(curPatcherFile, plString::FromWchar(manifest[0].md5, 32).c_str())) {
|
||||
// MessageBox(GetTopWindow(nil), "MD5 failed", "Msg", MB_OK);
|
||||
SelfPatcherStream::totalBytes += manifest[0].zipSize;
|
||||
|
||||
AtomicAdd(&s_numFiles, 1);
|
||||
SetText("Downloading new patcher...");
|
||||
|
||||
SelfPatcherStream * stream = new SelfPatcherStream;
|
||||
if (!stream->Open(s_newPatcherFile, "wb"))
|
||||
ErrorAssert(__LINE__, __FILE__, "Failed to create file: %s, errno: %u", s_newPatcherFile.AsString().c_str(), errno);
|
||||
|
||||
NetCliFileDownloadRequest(plString::FromWchar(manifest[0].downloadName), stream, DownloadCallback, nil);
|
||||
}
|
||||
else {
|
||||
s_downloadComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
static void FileSrvIpAddressCallback (
|
||||
ENetError result,
|
||||
void * param,
|
||||
const wchar_t addr[]
|
||||
) {
|
||||
NetCliGateKeeperDisconnect();
|
||||
|
||||
if (IS_NET_ERROR(result)) {
|
||||
plString msg = plString::Format("FileSrvIpAddressRequest failed: %S", NetErrorToString(result));
|
||||
plStatusLog::AddLineS("patcher.log", msg.c_str());
|
||||
|
||||
s_patchResult = result;
|
||||
s_downloadComplete = true;
|
||||
}
|
||||
|
||||
// Start connecting to the server
|
||||
const char* caddr = hsWStringToString(addr);
|
||||
NetCliFileStartConnect(&caddr, 1, true);
|
||||
delete[] caddr;
|
||||
|
||||
s_newPatcherFile = plFileSystem::GetCurrentAppPath().StripFileName();
|
||||
s_newPatcherFile = plFileSystem::GetTempFilename(kPatcherExeFilename.AsString().c_str(), s_newPatcherFile);
|
||||
plFileSystem::Unlink(s_newPatcherFile);
|
||||
|
||||
NetCliFileManifestRequest(ManifestCallback, nil, s_manifest);
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
static bool SelfPatcherProc (bool * abort, plLauncherInfo *info) {
|
||||
|
||||
bool patched = false;
|
||||
s_downloadComplete = false;
|
||||
s_patchResult = kNetSuccess;
|
||||
|
||||
NetClientInitialize();
|
||||
NetClientSetErrorHandler(NetErrorHandler);
|
||||
|
||||
const char** addrs;
|
||||
unsigned count;
|
||||
|
||||
count = GetGateKeeperSrvHostnames(&addrs);
|
||||
|
||||
// Start connecting to the server
|
||||
NetCliGateKeeperStartConnect(addrs, count);
|
||||
|
||||
// request a file server ip address
|
||||
NetCliGateKeeperFileSrvIpAddressRequest(FileSrvIpAddressCallback, nil, true);
|
||||
|
||||
while(!s_downloadComplete && !*abort) {
|
||||
NetClientUpdate();
|
||||
AsyncSleep(10);
|
||||
}
|
||||
|
||||
NetCliFileDisconnect();
|
||||
NetClientUpdate();
|
||||
|
||||
// Shutdown the client/server networking subsystem
|
||||
NetClientDestroy();
|
||||
|
||||
if (s_downloadComplete && !*abort && s_updated && IS_NET_SUCCESS(s_patchResult)) {
|
||||
|
||||
// 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",
|
||||
s_newPatcherFile.AsString().ToWchar().GetData(), 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
|
||||
*
|
||||
***/
|
||||
|
||||
//============================================================================
|
||||
uint32_t SelfPatcherStream::Write(uint32_t byteCount, const void* buffer) {
|
||||
progress += byteCount;
|
||||
float p = (float)progress / (float)totalBytes * 100; // progress
|
||||
SetProgress( (int)p );
|
||||
return plZlibStream::Write(byteCount, buffer);
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* 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_patchResult;
|
||||
return patched;
|
||||
}
|
||||
|
||||
|
||||
/* Enable themes in Windows XP and later */
|
||||
#pragma comment(linker,"\"/manifestdependency:type='win32' \
|
||||
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
|
||||
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
|
468
Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp
Normal file
468
Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp
Normal file
@ -0,0 +1,468 @@
|
||||
/*==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==*/
|
||||
|
||||
#include "HeadSpin.h"
|
||||
#include "plClientLauncher.h"
|
||||
#include "plFileSystem.h"
|
||||
#include "plProduct.h"
|
||||
#include "hsThread.h"
|
||||
#include "hsTimer.h"
|
||||
|
||||
#include "pnUtils/pnUtils.h" // for CCmdParser
|
||||
#include "pnAsyncCore/pnAsyncCore.h"
|
||||
#include "plNetGameLib/plNetGameLib.h"
|
||||
#include "plStatusLog/plStatusLog.h"
|
||||
|
||||
#include "pfPatcher/plManifests.h"
|
||||
#include "pfPatcher/pfPatcher.h"
|
||||
|
||||
#include "pfConsoleCore/pfConsoleEngine.h"
|
||||
PF_CONSOLE_LINK_FILE(Core)
|
||||
|
||||
#include <algorithm>
|
||||
#include <curl/curl.h>
|
||||
#include <deque>
|
||||
|
||||
plClientLauncher::ErrorFunc s_errorProc = nullptr; // don't even ask, cause I'm not happy about this.
|
||||
|
||||
const int kNetTransTimeout = 5 * 60 * 1000; // 5m
|
||||
const int kShardStatusUpdateTime = 5; // 5s
|
||||
const int kAsyncCoreShutdownTime = 2 * 1000; // 2s
|
||||
const int kNetCoreUpdateSleepTime = 10; // 10ms
|
||||
|
||||
// ===================================================
|
||||
|
||||
class plShardStatus : public hsThread
|
||||
{
|
||||
double fLastUpdate;
|
||||
volatile bool fRunning;
|
||||
hsEvent fUpdateEvent;
|
||||
char fCurlError[CURL_ERROR_SIZE];
|
||||
|
||||
public:
|
||||
plClientLauncher::StatusFunc fShardFunc;
|
||||
|
||||
plShardStatus() :
|
||||
fRunning(true), fLastUpdate(0)
|
||||
{ }
|
||||
|
||||
virtual hsError Run();
|
||||
void Shutdown();
|
||||
void Update();
|
||||
};
|
||||
|
||||
static size_t ICurlCallback(void* buffer, size_t size, size_t nmemb, void* thread)
|
||||
{
|
||||
static char status[256];
|
||||
|
||||
strncpy(status, (const char *)buffer, std::min<size_t>(size * nmemb, arrsize(status)));
|
||||
status[arrsize(status) - 1] = 0;
|
||||
static_cast<plShardStatus*>(thread)->fShardFunc(status);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
hsError plShardStatus::Run()
|
||||
{
|
||||
{
|
||||
const char* url = GetServerStatusUrl();
|
||||
|
||||
// initialize CURL
|
||||
std::unique_ptr<CURL, std::function<void(CURL*)>> curl(curl_easy_init(), curl_easy_cleanup);
|
||||
curl_easy_setopt(curl.get(), CURLOPT_ERRORBUFFER, fCurlError);
|
||||
curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, "UruClient/1.0");
|
||||
curl_easy_setopt(curl.get(), CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, this);
|
||||
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, ICurlCallback);
|
||||
|
||||
// we want to go ahead and run once
|
||||
fUpdateEvent.Signal();
|
||||
|
||||
// loop until we die!
|
||||
do
|
||||
{
|
||||
fUpdateEvent.Wait();
|
||||
if (!fRunning)
|
||||
break;
|
||||
|
||||
if (url[0] && curl_easy_perform(curl.get()))
|
||||
fShardFunc(fCurlError);
|
||||
fLastUpdate = hsTimer::GetSysSeconds();
|
||||
} while (fRunning);
|
||||
}
|
||||
|
||||
return hsOK;
|
||||
}
|
||||
|
||||
void plShardStatus::Shutdown()
|
||||
{
|
||||
fRunning = false;
|
||||
fUpdateEvent.Signal();
|
||||
}
|
||||
|
||||
void plShardStatus::Update()
|
||||
{
|
||||
double now = hsTimer::GetSysSeconds();
|
||||
if ((now - fLastUpdate) >= kShardStatusUpdateTime)
|
||||
fUpdateEvent.Signal();
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
class plRedistUpdater : public hsThread
|
||||
{
|
||||
bool fSuccess;
|
||||
|
||||
public:
|
||||
plClientLauncher* fParent;
|
||||
plClientLauncher::InstallRedistFunc fInstallProc;
|
||||
std::deque<plFileName> fRedistQueue;
|
||||
|
||||
plRedistUpdater()
|
||||
: fSuccess(true)
|
||||
{ }
|
||||
|
||||
~plRedistUpdater()
|
||||
{
|
||||
// If anything is left in the deque, it was not installed.
|
||||
// We should unlink them so the next launch will redownload and install them.
|
||||
std::for_each(fRedistQueue.begin(), fRedistQueue.end(),
|
||||
[] (const plFileName& file) {
|
||||
plFileSystem::Unlink(file);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
virtual void OnQuit()
|
||||
{
|
||||
// If we succeeded, then we should launch the game client...
|
||||
if (fSuccess)
|
||||
fParent->LaunchClient();
|
||||
}
|
||||
|
||||
virtual hsError Run()
|
||||
{
|
||||
while (!fRedistQueue.empty()) {
|
||||
if (fInstallProc(fRedistQueue.back()))
|
||||
fRedistQueue.pop_back();
|
||||
else {
|
||||
s_errorProc(kNetErrInternalError, fRedistQueue.back().AsString());
|
||||
fSuccess = false;
|
||||
return hsFail;
|
||||
}
|
||||
}
|
||||
return hsOK;
|
||||
}
|
||||
|
||||
virtual void Start()
|
||||
{
|
||||
if (fRedistQueue.empty())
|
||||
OnQuit();
|
||||
else
|
||||
hsThread::Start();
|
||||
}
|
||||
};
|
||||
|
||||
// ===================================================
|
||||
|
||||
plClientLauncher::plClientLauncher() :
|
||||
fFlags(0),
|
||||
fServerIni("server.ini"),
|
||||
fPatcherFactory(nullptr),
|
||||
fClientExecutable(plManifest::ClientExecutable()),
|
||||
fStatusThread(new plShardStatus()),
|
||||
fInstallerThread(new plRedistUpdater())
|
||||
{
|
||||
pfPatcher::GetLog()->AddLine(plProduct::ProductString().c_str());
|
||||
}
|
||||
|
||||
plClientLauncher::~plClientLauncher() { }
|
||||
|
||||
// ===================================================
|
||||
|
||||
plString plClientLauncher::GetAppArgs() const
|
||||
{
|
||||
// If -Repair was specified, there are no args for the next call...
|
||||
if (hsCheckBits(fFlags, kRepairGame)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
plStringStream ss;
|
||||
ss << "-ServerIni=";
|
||||
ss << fServerIni.AsString();
|
||||
|
||||
// optional args
|
||||
if (hsCheckBits(fFlags, kClientImage))
|
||||
ss << " -Image";
|
||||
|
||||
return ss.GetString();
|
||||
}
|
||||
|
||||
void plClientLauncher::IOnPatchComplete(ENetError result, const plString& msg)
|
||||
{
|
||||
if (IS_NET_SUCCESS(result)) {
|
||||
// a couple of options
|
||||
// 1. we self-patched and didn't update anything. patch the main client.
|
||||
// 2. we self-patched and did things and stuff... re-run myself.
|
||||
// 3. we patched the client... run it.
|
||||
if (!hsCheckBits(fFlags, kHaveSelfPatched) && (fClientExecutable == plManifest::ClientExecutable())) {
|
||||
// case 1
|
||||
hsSetBits(fFlags, kHaveSelfPatched);
|
||||
PatchClient();
|
||||
} else {
|
||||
// cases 2 & 3 -- update any redistributables, then launch the client.
|
||||
fInstallerThread->fParent = this;
|
||||
fInstallerThread->Start();
|
||||
}
|
||||
} else if (s_errorProc)
|
||||
s_errorProc(result, msg);
|
||||
}
|
||||
|
||||
bool plClientLauncher::IApproveDownload(const plFileName& file)
|
||||
{
|
||||
// So, for a repair, what we want to do is quite simple.
|
||||
// That is: download everything that is NOT in the root directory.
|
||||
plFileName path = file.StripFileName();
|
||||
return !path.AsString().IsEmpty();
|
||||
}
|
||||
|
||||
void plClientLauncher::LaunchClient() const
|
||||
{
|
||||
if (fStatusFunc)
|
||||
fStatusFunc("Launching...");
|
||||
fLaunchClientFunc(fClientExecutable, GetAppArgs());
|
||||
}
|
||||
|
||||
void plClientLauncher::PatchClient()
|
||||
{
|
||||
if (fStatusFunc) {
|
||||
if (hsCheckBits(fFlags, kGameDataOnly))
|
||||
fStatusFunc("Verifying game data...");
|
||||
else
|
||||
fStatusFunc("Checking for updates...");
|
||||
}
|
||||
hsAssert(fPatcherFactory, "why is the patcher factory nil?");
|
||||
|
||||
pfPatcher* patcher = fPatcherFactory();
|
||||
patcher->OnCompletion(std::bind(&plClientLauncher::IOnPatchComplete, this, std::placeholders::_1, std::placeholders::_2));
|
||||
patcher->OnSelfPatch([&](const plFileName& file) { fClientExecutable = file; });
|
||||
patcher->OnRedistUpdate([&](const plFileName& file) { fInstallerThread->fRedistQueue.push_back(file); });
|
||||
|
||||
// If this is a repair, we need to approve the downloads...
|
||||
if (hsCheckBits(fFlags, kGameDataOnly))
|
||||
patcher->OnFileDownloadDesired(std::bind(&plClientLauncher::IApproveDownload, this, std::placeholders::_1));
|
||||
|
||||
// Let's get 'er done.
|
||||
if (hsCheckBits(fFlags, kHaveSelfPatched)) {
|
||||
if (hsCheckBits(fFlags, kClientImage))
|
||||
patcher->RequestManifest(plManifest::ClientImageManifest());
|
||||
else
|
||||
patcher->RequestManifest(plManifest::ClientManifest());
|
||||
} else
|
||||
patcher->RequestManifest(plManifest::PatcherManifest());
|
||||
patcher->Start();
|
||||
}
|
||||
|
||||
bool plClientLauncher::CompleteSelfPatch(std::function<void(void)> waitProc) const
|
||||
{
|
||||
if (hsCheckBits(fFlags, kHaveSelfPatched))
|
||||
return false;
|
||||
|
||||
plString myExe = plFileSystem::GetCurrentAppPath().GetFileName();
|
||||
if (myExe.CompareI(plManifest::PatcherExecutable().AsString()) != 0) {
|
||||
waitProc();
|
||||
|
||||
// so now we need to unlink the old patcher, and move ME into that fool's place...
|
||||
// then we can continue on our merry way!
|
||||
if (!plFileSystem::Unlink(plManifest::PatcherExecutable())) {
|
||||
hsMessageBox("Failed to delete old patcher executable!", "Error", hsMessageBoxNormal, hsMessageBoxIconError);
|
||||
return true;
|
||||
}
|
||||
if (!plFileSystem::Move(plFileSystem::GetCurrentAppPath(), plManifest::PatcherExecutable())) {
|
||||
hsMessageBox("Failed to move patcher executable!", "Error", hsMessageBoxNormal, hsMessageBoxIconError);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Now, execute the new patcher...
|
||||
fLaunchClientFunc(plManifest::PatcherExecutable(), GetAppArgs() + " -NoSelfPatch");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
static void IGotFileServIPs(ENetError result, void* param, const wchar_t* addr)
|
||||
{
|
||||
plClientLauncher* launcher = static_cast<plClientLauncher*>(param);
|
||||
NetCliGateKeeperDisconnect();
|
||||
|
||||
if (IS_NET_SUCCESS(result)) {
|
||||
// bah... why do I even bother
|
||||
plString eapSucks = plString::FromWchar(addr);
|
||||
const char* eapReallySucks[] = { eapSucks.c_str() };
|
||||
NetCliFileStartConnect(eapReallySucks, 1, true);
|
||||
|
||||
// Who knows if we will actually connect. So let's start updating.
|
||||
launcher->PatchClient();
|
||||
} else if (s_errorProc)
|
||||
s_errorProc(result, "Failed to get FileServ addresses");
|
||||
}
|
||||
|
||||
static void IEapSucksErrorProc(ENetProtocol protocol, ENetError error)
|
||||
{
|
||||
if (s_errorProc) {
|
||||
plString msg = plString::Format("Protocol: %S", NetProtocolToString(protocol));
|
||||
s_errorProc(error, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void plClientLauncher::InitializeNetCore()
|
||||
{
|
||||
// initialize shard status
|
||||
hsTimer::SetRealTime(true);
|
||||
fStatusThread->Start();
|
||||
|
||||
// init eap...
|
||||
AsyncCoreInitialize();
|
||||
|
||||
NetClientInitialize();
|
||||
NetClientSetErrorHandler(IEapSucksErrorProc);
|
||||
NetClientSetTransTimeoutMs(kNetTransTimeout);
|
||||
|
||||
// Gotta grab the filesrvs from the gate
|
||||
const char** addrs;
|
||||
uint32_t num = GetGateKeeperSrvHostnames(&addrs);
|
||||
|
||||
NetCliGateKeeperStartConnect(addrs, num);
|
||||
NetCliGateKeeperFileSrvIpAddressRequest(IGotFileServIPs, this, true);
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
void plClientLauncher::PumpNetCore() const
|
||||
{
|
||||
// this ain't net core, but it needs to be pumped :(
|
||||
hsTimer::IncSysSeconds();
|
||||
|
||||
// pump eap
|
||||
NetClientUpdate();
|
||||
|
||||
// pump shard status
|
||||
fStatusThread->Update();
|
||||
|
||||
// don't nom all the CPU... kthx
|
||||
hsSleep::Sleep(kNetCoreUpdateSleepTime);
|
||||
}
|
||||
|
||||
void plClientLauncher::ShutdownNetCore() const
|
||||
{
|
||||
// shutdown shard status
|
||||
fStatusThread->Shutdown();
|
||||
|
||||
// unhook the neterr callback at this point because all transactions
|
||||
// will fail when we call NetClientDestroy
|
||||
s_errorProc = nullptr;
|
||||
|
||||
// shutdown eap
|
||||
NetCliGateKeeperDisconnect();
|
||||
NetCliFileDisconnect();
|
||||
NetClientDestroy();
|
||||
|
||||
// shutdown eap (part deux)
|
||||
AsyncCoreDestroy(kAsyncCoreShutdownTime);
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
bool plClientLauncher::LoadServerIni() const
|
||||
{
|
||||
PF_CONSOLE_INITIALIZE(Core);
|
||||
|
||||
pfConsoleEngine console;
|
||||
return console.ExecuteFile(fServerIni);
|
||||
}
|
||||
|
||||
void plClientLauncher::ParseArguments()
|
||||
{
|
||||
#define APPLY_FLAG(arg, flag) \
|
||||
if (cmdParser.GetBool(arg)) \
|
||||
fFlags |= flag;
|
||||
|
||||
enum { kArgServerIni, kArgNoSelfPatch, kArgImage, kArgRepairGame };
|
||||
const CmdArgDef cmdLineArgs[] = {
|
||||
{ kCmdArgFlagged | kCmdTypeString, L"ServerIni", kArgServerIni },
|
||||
{ kCmdArgFlagged | kCmdTypeBool, L"NoSelfPatch", kArgNoSelfPatch },
|
||||
{ kCmdArgFlagged | kCmdTypeBool, L"Image", kArgImage },
|
||||
{ kCmdArgFlagged | kCmdTypeBool, L"Repair", kArgRepairGame },
|
||||
};
|
||||
|
||||
CCmdParser cmdParser(cmdLineArgs, arrsize(cmdLineArgs));
|
||||
cmdParser.Parse();
|
||||
|
||||
// cache 'em
|
||||
if (cmdParser.IsSpecified(kArgServerIni))
|
||||
fServerIni = plString::FromWchar(cmdParser.GetString(kArgServerIni));
|
||||
APPLY_FLAG(kArgNoSelfPatch, kHaveSelfPatched);
|
||||
APPLY_FLAG(kArgImage, kClientImage);
|
||||
APPLY_FLAG(kArgRepairGame, kRepairGame);
|
||||
|
||||
// last chance setup
|
||||
if (hsCheckBits(fFlags, kRepairGame))
|
||||
fClientExecutable = plManifest::PatcherExecutable();
|
||||
|
||||
#undef APPLY_FLAG
|
||||
}
|
||||
|
||||
void plClientLauncher::SetErrorProc(ErrorFunc proc)
|
||||
{
|
||||
s_errorProc = proc;
|
||||
}
|
||||
|
||||
void plClientLauncher::SetInstallerProc(InstallRedistFunc proc)
|
||||
{
|
||||
fInstallerThread->fInstallProc = proc;
|
||||
}
|
||||
|
||||
void plClientLauncher::SetShardProc(StatusFunc proc)
|
||||
{
|
||||
fStatusThread->fShardFunc = proc;
|
||||
}
|
165
Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h
Normal file
165
Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h
Normal file
@ -0,0 +1,165 @@
|
||||
/*==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==*/
|
||||
|
||||
#ifndef _plClientLauncher_inc_
|
||||
#define _plClientLauncher_inc_
|
||||
|
||||
#include "plFileSystem.h"
|
||||
#include "pnNetBase/pnNbError.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
class plClientLauncher
|
||||
{
|
||||
public:
|
||||
typedef std::function<class pfPatcher*(void)> CreatePatcherFunc;
|
||||
typedef std::function<void(ENetError, const plString&)> ErrorFunc;
|
||||
typedef std::function<bool(const plFileName&)> InstallRedistFunc;
|
||||
typedef std::function<void(const plFileName&, const plString&)> LaunchClientFunc;
|
||||
typedef std::function<void(const plString&)> StatusFunc;
|
||||
|
||||
private:
|
||||
enum Flags
|
||||
{
|
||||
kHaveSelfPatched = 1<<0,
|
||||
kClientImage = 1<<1,
|
||||
kGameDataOnly = (1<<2),
|
||||
|
||||
kRepairGame = kHaveSelfPatched | kClientImage | kGameDataOnly,
|
||||
};
|
||||
|
||||
uint32_t fFlags;
|
||||
plFileName fServerIni;
|
||||
|
||||
plFileName fClientExecutable;
|
||||
|
||||
std::unique_ptr<class plShardStatus> fStatusThread;
|
||||
std::unique_ptr<class plRedistUpdater> fInstallerThread;
|
||||
|
||||
CreatePatcherFunc fPatcherFactory;
|
||||
LaunchClientFunc fLaunchClientFunc;
|
||||
StatusFunc fStatusFunc;
|
||||
|
||||
plString GetAppArgs() const;
|
||||
|
||||
void IOnPatchComplete(ENetError result, const plString& msg);
|
||||
bool IApproveDownload(const plFileName& file);
|
||||
|
||||
public:
|
||||
plClientLauncher();
|
||||
~plClientLauncher();
|
||||
|
||||
/** Launch whatever client we think is appropriate. Please note that you should not call this unless you know
|
||||
* absolutely without question what you are doing!
|
||||
*/
|
||||
void LaunchClient() const;
|
||||
|
||||
/** Begin the next logical patch operation. We are internally tracking if this is a self patch or a client patch.
|
||||
* All you need to do is make certain the doggone callbacks are set so that your UI will update. In theory, you
|
||||
* should never call this from your UI code since we manage this state for you.
|
||||
*/
|
||||
void PatchClient();
|
||||
|
||||
/** Attempt to complete a self-patch left in progress by an older launcher. Specifically, we want to rename
|
||||
* the launcher to something sane (UruLauncher.exe.tmp -> UruLauncher.exe). If we complete a self-patch in
|
||||
* here, then we need to relaunch ourselves so that the game client will look like what the server expects.
|
||||
* \returns True if a self-patch was completed. False if not.
|
||||
*/
|
||||
bool CompleteSelfPatch(std::function<void(void)> waitProc) const;
|
||||
|
||||
/** Start eap's weird network subsystem and the shard status pinger.
|
||||
* \remarks Please note that this will also enqueue the first patch.
|
||||
*/
|
||||
void InitializeNetCore();
|
||||
|
||||
/** This pumps eap's network subsystem and runs any queued transaction completion callbacks.
|
||||
* The thread that you call this from will be the thread that all your UI updates come from.
|
||||
* So be certain that you've thought that through!
|
||||
* \remarks This method will cause the thread to sleep so that we don't hog the CPU.
|
||||
*/
|
||||
void PumpNetCore() const;
|
||||
|
||||
/** Shutdown eap's netcore and purge any other crap that needs to happen while the app is
|
||||
* visible. In other words, tear down evil threaded crap.
|
||||
*/
|
||||
void ShutdownNetCore() const;
|
||||
|
||||
/** Load the server configuration file. Note that you MUST parse the command
|
||||
* arguments before calling this function!
|
||||
*/
|
||||
bool LoadServerIni() const;
|
||||
|
||||
/** Parse the command line options. */
|
||||
void ParseArguments();
|
||||
|
||||
/** Set a callback function that is called on a network error.
|
||||
* \remarks This will be called from the network thread.
|
||||
*/
|
||||
void SetErrorProc(ErrorFunc proc);
|
||||
|
||||
/** Set a callback that will execute and wait for redistributable installers.
|
||||
* \remarks This will be called from a worker thread.
|
||||
*/
|
||||
void SetInstallerProc(InstallRedistFunc proc);
|
||||
|
||||
/** Set a patcher factory. */
|
||||
void SetPatcherFactory(CreatePatcherFunc factory) { fPatcherFactory = factory; }
|
||||
|
||||
/** Set a callback that launches an arbitrary executable.
|
||||
* \remarks This will be called from an arbitrary thread.
|
||||
*/
|
||||
void SetLaunchClientProc(LaunchClientFunc proc) { fLaunchClientFunc = proc; }
|
||||
|
||||
/** Set a callback that displays the shard status.
|
||||
* \remarks This will be called from a worker thread.
|
||||
*/
|
||||
void SetShardProc(StatusFunc proc);
|
||||
|
||||
/** Set a callback that displays the patcher status.
|
||||
* \remarks This will be called from the network thread. Note that any time
|
||||
* this is called, you should consider it a state reset (so undo your progress bars).
|
||||
*/
|
||||
void SetStatusProc(StatusFunc proc) { fStatusFunc = proc; }
|
||||
};
|
||||
|
||||
#endif // _plClientLauncher_inc_
|
@ -1,102 +0,0 @@
|
||||
/*==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/plLauncherCallback.h
|
||||
*
|
||||
***/
|
||||
|
||||
#ifdef PLASMA20_SOURCES_PLASMA_APPS_PLURULAUNCHER_PLLAUNCHERCALLBACK_H
|
||||
#error "Header $/Plasma20/Sources/Plasma/Apps/plUruLauncher/plLauncherCallback.h included more than once"
|
||||
#endif
|
||||
#define PLASMA20_SOURCES_PLASMA_APPS_PLURULAUNCHER_PLLAUNCHERCALLBACK_H
|
||||
|
||||
enum EStatus {
|
||||
kStatusOk,
|
||||
kStatusError,
|
||||
kStatusPending,
|
||||
};
|
||||
|
||||
struct PatchInfo {
|
||||
unsigned progress;
|
||||
unsigned stage;
|
||||
unsigned progressStage;
|
||||
};
|
||||
|
||||
typedef void (*launcherCallback)(int status, void *param);
|
||||
typedef void (*setTextCallback)(const char text[]);
|
||||
typedef void (*setStatusTextCallback)(const char text[]);
|
||||
typedef void (*setTimeRemainingCallback)(unsigned seconds);
|
||||
typedef void (*setBytesRemainingCallback)(unsigned bytes);
|
||||
|
||||
struct plLauncherInfo {
|
||||
wchar_t path[MAX_PATH];
|
||||
wchar_t cmdLine[512];
|
||||
unsigned buildId; // buildId override
|
||||
launcherCallback prepCallback;
|
||||
launcherCallback initCallback;
|
||||
launcherCallback startCallback;
|
||||
launcherCallback stopCallback;
|
||||
launcherCallback terminateCallback;
|
||||
launcherCallback progressCallback;
|
||||
launcherCallback exitCallback;
|
||||
setTextCallback SetText;
|
||||
setStatusTextCallback SetStatusText;
|
||||
setTimeRemainingCallback SetTimeRemaining;
|
||||
setBytesRemainingCallback SetBytesRemaining;
|
||||
|
||||
PatchInfo patchInfo;
|
||||
DWORD returnCode; // used so we can pass a new process id back to gametap. That way gametap wont think uru has exited when the patcher quits.
|
||||
};
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Main.cpp
|
||||
*
|
||||
***/
|
||||
|
||||
void SetProgress (unsigned progress) ;
|
||||
void SetText (const char text[]);
|
||||
void SetStatusText (const char text[]);
|
||||
void SetTimeRemaining(unsigned seconds);
|
||||
void SetBytesRemaining(unsigned bytes);
|
@ -4,7 +4,16 @@
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#define IDC_STATIC (-1) // all static controls
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// English (U.S.) resources
|
||||
@ -32,6 +41,12 @@ BEGIN
|
||||
"\0"
|
||||
END
|
||||
|
||||
3 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
@ -40,7 +55,7 @@ END
|
||||
// Dialog
|
||||
//
|
||||
|
||||
IDD_DIALOG DIALOGEX 0, 0, 301, 180
|
||||
IDD_DIALOG DIALOGEX 0, 0, 301, 135
|
||||
STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS |
|
||||
DS_CENTER | WS_POPUP | WS_VISIBLE | WS_SYSMENU
|
||||
EXSTYLE WS_EX_APPWINDOW
|
||||
@ -48,17 +63,16 @@ FONT 8, "MS Shell Dlg", 400, 0, 0x1
|
||||
BEGIN
|
||||
CONTROL 109,IDB_BITMAP,"Static",SS_BITMAP | SS_SUNKEN,7,7,288,36
|
||||
CONTROL "",IDC_PROGRESS,"msctls_progress32",WS_BORDER | 0x1,7,
|
||||
162,234,11
|
||||
LTEXT "Static",IDC_TEXT,10,152,266,8
|
||||
PUSHBUTTON "Cancel",IDCANCEL,243,162,51,11
|
||||
111,234,11
|
||||
CONTROL "", IDC_MARQUEE, "msctls_progress32", WS_BORDER | 0x8, 7,
|
||||
111, 234, 11
|
||||
LTEXT "Static",IDC_TEXT,10,101,266,8
|
||||
PUSHBUTTON "Cancel",IDCANCEL,243,111,51,11
|
||||
LTEXT "Welcome to URU",IDC_STATUS_TEXT,19,57,266,17
|
||||
GROUPBOX "",IDC_STATIC,7,46,287,49
|
||||
GROUPBOX "Status",IDC_STATIC,7,103,287,44
|
||||
LTEXT "Approx. Time Remaining:",IDC_STATIC,19,117,88,11
|
||||
LTEXT "Update Remaining:",IDC_STATIC,20,130,73,9
|
||||
LTEXT "...",IDC_TIMEREMAINING,111,117,165,12
|
||||
LTEXT "...",IDC_BYTESREMAINING,111,130,42,12
|
||||
RTEXT "Product String",IDC_PRODUCTSTRING,19,85,272,8
|
||||
LTEXT "6 MiB / 666 MiB",IDC_DLSIZE,11,124,110,8
|
||||
LTEXT "128 KiB/s",IDC_DLSPEED,186,124,53,8,0,WS_EX_RIGHT
|
||||
END
|
||||
|
||||
|
||||
@ -75,7 +89,7 @@ BEGIN
|
||||
LEFTMARGIN, 7
|
||||
RIGHTMARGIN, 294
|
||||
TOPMARGIN, 7
|
||||
BOTTOMMARGIN, 173
|
||||
BOTTOMMARGIN, 135
|
||||
END
|
||||
END
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
@ -6,12 +6,15 @@
|
||||
#define IDB_BITMAP 109
|
||||
#define IDI_ICON1 111
|
||||
#define IDC_PROGRESS 1003
|
||||
#define IDC_MARQUEE 1004
|
||||
#define IDC_TEXT 1006
|
||||
#define IDC_STATUS_TEXT 1009
|
||||
#define IDC_TIMEREMAINING 1010
|
||||
#define IDC_BYTESREMAINING 1011
|
||||
#define IDC_PRODUCTSTRING 1012
|
||||
#define IDC_FILESREMAINING 1013
|
||||
#define IDC_DLSIZE 1014
|
||||
#define IDC_DLSPEED 1015
|
||||
#define IDC_STATIC -1
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
@ -19,7 +22,7 @@
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 112
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1014
|
||||
#define _APS_NEXT_CONTROL_VALUE 1016
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
||||
|
428
Sources/Plasma/Apps/plUruLauncher/winmain.cpp
Normal file
428
Sources/Plasma/Apps/plUruLauncher/winmain.cpp
Normal file
@ -0,0 +1,428 @@
|
||||
/*==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==*/
|
||||
|
||||
#include "HeadSpin.h"
|
||||
#include "plFileSystem.h"
|
||||
#include "plProduct.h"
|
||||
|
||||
#include "pfPatcher/plManifests.h"
|
||||
#include "pfPatcher/pfPatcher.h"
|
||||
|
||||
#include "plClientLauncher.h"
|
||||
|
||||
#include "hsWindows.h"
|
||||
#include "resource.h"
|
||||
#include <commctrl.h>
|
||||
#include <shellapi.h>
|
||||
#include <shlobj.h>
|
||||
|
||||
// ===================================================
|
||||
|
||||
#define PLASMA_PHAILURE 1
|
||||
#define PLASMA_OK 0
|
||||
|
||||
static HWND s_dialog;
|
||||
static plString s_error; // This is highly unfortunate.
|
||||
static plClientLauncher s_launcher;
|
||||
static UINT s_taskbarCreated = RegisterWindowMessageW(L"TaskbarButtonCreated");
|
||||
static ITaskbarList3* s_taskbar = nullptr;
|
||||
|
||||
// ===================================================
|
||||
|
||||
/** Create a global patcher mutex that is backwards compatible with eap's */
|
||||
static HANDLE CreatePatcherMutex()
|
||||
{
|
||||
return CreateMutexW(nullptr, FALSE, plManifest::PatcherExecutable().AsString().ToWchar());
|
||||
}
|
||||
|
||||
static bool IsPatcherRunning()
|
||||
{
|
||||
HANDLE mut = CreatePatcherMutex();
|
||||
return WaitForSingleObject(mut, 0) != WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
static void WaitForOldPatcher()
|
||||
{
|
||||
HANDLE mut = CreatePatcherMutex();
|
||||
WaitForSingleObject(mut, INFINITE);
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
static inline void IShowErrorDialog(const wchar_t* msg)
|
||||
{
|
||||
// This bypasses all that hsClientMinimizeGuard crap we have in CoreLib.
|
||||
MessageBoxW(nullptr, msg, L"Error", MB_ICONERROR | MB_OK);
|
||||
}
|
||||
|
||||
static inline void IQuit(int exitCode=PLASMA_OK)
|
||||
{
|
||||
// hey, guess what?
|
||||
// PostQuitMessage doesn't work if you're not on the main thread...
|
||||
PostMessageW(s_dialog, WM_QUIT, exitCode, 0);
|
||||
}
|
||||
|
||||
static inline void IShowMarquee(bool marquee=true)
|
||||
{
|
||||
// NOTE: This is a HACK to workaround a bug that causes progress bars that were ever
|
||||
// marquees to reanimate when changing the range or position
|
||||
ShowWindow(GetDlgItem(s_dialog, IDC_MARQUEE), marquee ? SW_SHOW : SW_HIDE);
|
||||
ShowWindow(GetDlgItem(s_dialog, IDC_PROGRESS), marquee ? SW_HIDE : SW_SHOW);
|
||||
PostMessageW(GetDlgItem(s_dialog, IDC_MARQUEE), PBM_SETMARQUEE, static_cast<WPARAM>(marquee), 0);
|
||||
}
|
||||
|
||||
BOOL CALLBACK PatcherDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
// NT6 Taskbar Majick
|
||||
if (uMsg == s_taskbarCreated) {
|
||||
if (s_taskbar)
|
||||
s_taskbar->Release();
|
||||
HRESULT result = CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_ALL, IID_ITaskbarList3, (void**)&s_taskbar);
|
||||
if (FAILED(result))
|
||||
s_taskbar = nullptr;
|
||||
}
|
||||
|
||||
switch (uMsg) {
|
||||
case WM_COMMAND:
|
||||
// Did they press cancel?
|
||||
if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDCANCEL) {
|
||||
EnableWindow(GetDlgItem(s_dialog, IDCANCEL), false);
|
||||
SetWindowTextW(GetDlgItem(s_dialog, IDC_TEXT), L"Shutting Down...");
|
||||
IQuit();
|
||||
}
|
||||
break;
|
||||
case WM_DESTROY:
|
||||
if (s_taskbar)
|
||||
s_taskbar->Release();
|
||||
PostQuitMessage(PLASMA_OK);
|
||||
s_dialog = nullptr;
|
||||
break;
|
||||
case WM_NCHITTEST:
|
||||
SetWindowLongW(hwndDlg, DWL_MSGRESULT, (LONG_PTR)HTCAPTION);
|
||||
return TRUE;
|
||||
case WM_QUIT:
|
||||
s_launcher.ShutdownNetCore();
|
||||
DestroyWindow(hwndDlg);
|
||||
break;
|
||||
}
|
||||
|
||||
return DefWindowProcW(hwndDlg, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
static void ShowPatcherDialog(HINSTANCE hInstance)
|
||||
{
|
||||
s_dialog = ::CreateDialogW(hInstance, MAKEINTRESOURCEW(IDD_DIALOG), nullptr, PatcherDialogProc);
|
||||
SetDlgItemTextW(s_dialog, IDC_TEXT, L"Connecting...");
|
||||
SetDlgItemTextW(s_dialog, IDC_PRODUCTSTRING, plProduct::ProductString().ToWchar());
|
||||
SetDlgItemTextW(s_dialog, IDC_DLSIZE, L"");
|
||||
SetDlgItemTextW(s_dialog, IDC_DLSPEED, L"");
|
||||
IShowMarquee();
|
||||
}
|
||||
|
||||
static void PumpMessages()
|
||||
{
|
||||
MSG msg;
|
||||
do {
|
||||
// Pump all Win32 messages
|
||||
while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) {
|
||||
if (!IsDialogMessageW(s_dialog, &msg)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Now we need to pump the netcore while we have some spare time...
|
||||
s_launcher.PumpNetCore();
|
||||
} while (msg.message != WM_QUIT);
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
static void IOnDownloadBegin(const plFileName& file)
|
||||
{
|
||||
plString msg = plString::Format("Downloading... %s", file.AsString().c_str());
|
||||
SetDlgItemTextW(s_dialog, IDC_TEXT, msg.ToWchar());
|
||||
}
|
||||
|
||||
static void IOnProgressTick(uint64_t curBytes, uint64_t totalBytes, const plString& status)
|
||||
{
|
||||
// Swap marquee/real progress
|
||||
IShowMarquee(false);
|
||||
|
||||
// DL size
|
||||
plString size = plString::Format("%s / %s", plFileSystem::ConvertFileSize(curBytes).c_str(),
|
||||
plFileSystem::ConvertFileSize(totalBytes).c_str());
|
||||
SetDlgItemTextW(s_dialog, IDC_DLSIZE, size.ToWchar());
|
||||
|
||||
// DL speed
|
||||
SetDlgItemTextW(s_dialog, IDC_DLSPEED, status.ToWchar());
|
||||
HWND progress = GetDlgItem(s_dialog, IDC_PROGRESS);
|
||||
|
||||
// hey look... ULONGLONG. that's exactly what we need >.<
|
||||
if (s_taskbar)
|
||||
s_taskbar->SetProgressValue(s_dialog, curBytes, totalBytes);
|
||||
|
||||
// Windows can only do signed 32-bit int progress bars.
|
||||
// So, chop it into smaller chunks until we get something we can represent.
|
||||
while (totalBytes > INT32_MAX) {
|
||||
totalBytes /= 1024;
|
||||
curBytes /= 1024;
|
||||
}
|
||||
|
||||
PostMessageW(progress, PBM_SETRANGE32, 0, static_cast<int32_t>(totalBytes));
|
||||
PostMessageW(progress, PBM_SETPOS, static_cast<int32_t>(curBytes), 0);
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
static void ISetDownloadStatus(const plString& status)
|
||||
{
|
||||
SetDlgItemTextW(s_dialog, IDC_TEXT, status.ToWchar());
|
||||
|
||||
// consider this a reset of the download status...
|
||||
IShowMarquee();
|
||||
SetDlgItemTextW(s_dialog, IDC_DLSIZE, L"");
|
||||
SetDlgItemTextW(s_dialog, IDC_DLSPEED, L"");
|
||||
|
||||
if (s_taskbar)
|
||||
s_taskbar->SetProgressState(s_dialog, TBPF_INDETERMINATE);
|
||||
}
|
||||
|
||||
|
||||
static HANDLE ICreateProcess(const plFileName& exe, const plString& args)
|
||||
{
|
||||
STARTUPINFOW si;
|
||||
PROCESS_INFORMATION pi;
|
||||
memset(&si, 0, sizeof(si));
|
||||
memset(&pi, 0, sizeof(pi));
|
||||
si.cb = sizeof(si);
|
||||
|
||||
// Create wchar things and stuff :/
|
||||
plString cmd = plString::Format("%s %s", exe.AsString().c_str(), args.c_str());
|
||||
plStringBuffer<wchar_t> file = exe.AsString().ToWchar();
|
||||
plStringBuffer<wchar_t> params = cmd.ToWchar();
|
||||
|
||||
// Guess what? CreateProcess isn't smart enough to throw up an elevation dialog... We need ShellExecute for that.
|
||||
// But guess what? ShellExecute won't run ".exe.tmp" files. GAAAAAAAAHHHHHHHHH!!!!!!!
|
||||
BOOL result = CreateProcessW(
|
||||
file,
|
||||
const_cast<wchar_t*>(params.GetData()),
|
||||
nullptr,
|
||||
nullptr,
|
||||
FALSE,
|
||||
DETACHED_PROCESS,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&si,
|
||||
&pi
|
||||
);
|
||||
|
||||
// So maybe it needs elevation... Or maybe everything arseploded.
|
||||
if (result != FALSE) {
|
||||
CloseHandle(pi.hThread);
|
||||
return pi.hProcess;
|
||||
} else if (GetLastError() == ERROR_ELEVATION_REQUIRED) {
|
||||
SHELLEXECUTEINFOW info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.cbSize = sizeof(info);
|
||||
info.lpFile = file.GetData();
|
||||
info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC;
|
||||
info.lpParameters = args.ToWchar();
|
||||
hsAssert(ShellExecuteExW(&info), "ShellExecuteExW phailed");
|
||||
|
||||
return info.hProcess;
|
||||
} else {
|
||||
wchar_t* msg = nullptr;
|
||||
FormatMessageW(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
||||
nullptr,
|
||||
GetLastError(),
|
||||
LANG_USER_DEFAULT,
|
||||
msg,
|
||||
0,
|
||||
nullptr
|
||||
);
|
||||
s_error = plString::FromWchar(msg);
|
||||
LocalFree(msg);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool IInstallRedist(const plFileName& exe)
|
||||
{
|
||||
ISetDownloadStatus(plString::Format("Installing... %s", exe.AsString().c_str()));
|
||||
Sleep(2500); // let's Sleep for a bit so the user can see that we're doing something before the UAC dialog pops up!
|
||||
|
||||
// Try to guess some arguments... Unfortunately, the file manifest format is fairly immutable.
|
||||
plStringStream ss;
|
||||
if (exe.AsString().CompareI("oalinst.exe") == 0)
|
||||
ss << "/s"; // rarg nonstandard
|
||||
else
|
||||
ss << "/q";
|
||||
if (exe.AsString().Find("vcredist", plString::kCaseInsensitive) != -1)
|
||||
ss << " /norestart"; // I don't want to image the accusations of viruses and hacking if this happened...
|
||||
|
||||
// Now fire up the process...
|
||||
HANDLE process = ICreateProcess(exe, ss.GetString());
|
||||
if (process) {
|
||||
WaitForSingleObject(process, INFINITE);
|
||||
|
||||
// Get the exit code so we can indicate success/failure to the redist thread
|
||||
DWORD code = PLASMA_OK;
|
||||
hsAssert(GetExitCodeProcess(process, &code), "failed to get redist exit code");
|
||||
CloseHandle(process);
|
||||
|
||||
return code != PLASMA_PHAILURE;
|
||||
}
|
||||
return PLASMA_PHAILURE;
|
||||
}
|
||||
|
||||
static void ILaunchClientExecutable(const plFileName& exe, const plString& args)
|
||||
{
|
||||
// Once we start launching something, we no longer need to trumpet any taskbar status
|
||||
if (s_taskbar)
|
||||
s_taskbar->SetProgressState(s_dialog, TBPF_NOPROGRESS);
|
||||
|
||||
// Only launch a client executable if we're given one. If not, that's probably a cue that we're
|
||||
// done with some service operation and need to go away.
|
||||
if (!exe.AsString().IsEmpty()) {
|
||||
HANDLE hEvent = CreateEventW(nullptr, TRUE, FALSE, L"UruPatcherEvent");
|
||||
HANDLE process = ICreateProcess(exe, args);
|
||||
|
||||
// if this is the real game client, then we need to make sure it gets this event...
|
||||
if (plManifest::ClientExecutable().AsString().CompareI(exe.AsString()) == 0) {
|
||||
WaitForInputIdle(process, 1000);
|
||||
WaitForSingleObject(hEvent, INFINITE);
|
||||
}
|
||||
|
||||
CloseHandle(process);
|
||||
CloseHandle(hEvent);
|
||||
}
|
||||
|
||||
// time to hara-kiri...
|
||||
IQuit();
|
||||
}
|
||||
|
||||
static void IOnNetError(ENetError result, const plString& msg)
|
||||
{
|
||||
if (s_taskbar)
|
||||
s_taskbar->SetProgressState(s_dialog, TBPF_ERROR);
|
||||
|
||||
s_error = plString::Format("Error: %S\r\n%s", NetErrorAsString(result), msg.c_str());
|
||||
IQuit(PLASMA_PHAILURE);
|
||||
}
|
||||
|
||||
static void ISetShardStatus(const plString& status)
|
||||
{
|
||||
SetDlgItemTextW(s_dialog, IDC_STATUS_TEXT, status.ToWchar());
|
||||
}
|
||||
|
||||
static pfPatcher* IPatcherFactory()
|
||||
{
|
||||
pfPatcher* patcher = new pfPatcher();
|
||||
patcher->OnFileDownloadBegin(IOnDownloadBegin);
|
||||
patcher->OnProgressTick(IOnProgressTick);
|
||||
|
||||
return patcher;
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLink, int nCmdShow)
|
||||
{
|
||||
// Let's initialize our plClientLauncher friend
|
||||
s_launcher.ParseArguments();
|
||||
s_launcher.SetErrorProc(IOnNetError);
|
||||
s_launcher.SetInstallerProc(IInstallRedist);
|
||||
s_launcher.SetLaunchClientProc(ILaunchClientExecutable);
|
||||
s_launcher.SetPatcherFactory(IPatcherFactory);
|
||||
s_launcher.SetShardProc(ISetShardStatus);
|
||||
s_launcher.SetStatusProc(ISetDownloadStatus);
|
||||
|
||||
// If we're newly updated, then our filename will be something we don't expect!
|
||||
// Let's go ahead and take care of that nao.
|
||||
if (s_launcher.CompleteSelfPatch(WaitForOldPatcher))
|
||||
return PLASMA_OK; // see you on the other side...
|
||||
|
||||
// Load the doggone server.ini
|
||||
if (!s_launcher.LoadServerIni()) {
|
||||
IShowErrorDialog(L"No server.ini file found. Please check your URU installation.");
|
||||
return PLASMA_PHAILURE;
|
||||
}
|
||||
|
||||
// Ensure there is only ever one patcher running...
|
||||
if (IsPatcherRunning()) {
|
||||
plString text = plString::Format("%s is already running", plProduct::LongName().c_str());
|
||||
IShowErrorDialog(text.ToWchar());
|
||||
return PLASMA_OK;
|
||||
}
|
||||
HANDLE _onePatcherMut = CreatePatcherMutex();
|
||||
|
||||
// Initialize the network core
|
||||
s_launcher.InitializeNetCore();
|
||||
|
||||
// Welp, now that we know we're (basically) sane, let's create our client window
|
||||
// and pump window messages until we're through.
|
||||
ShowPatcherDialog(hInstance);
|
||||
PumpMessages();
|
||||
|
||||
// So there appears to be some sort of issue with calling MessageBox once we've set up our dialog...
|
||||
// WTF?!?! So, to hack around that, we'll wait until everything shuts down to display any error.
|
||||
if (!s_error.IsEmpty())
|
||||
IShowErrorDialog(s_error.ToWchar());
|
||||
|
||||
// Alrighty now we just need to clean up behind ourselves!
|
||||
// NOTE: We shut down the netcore in the WM_QUIT handler so
|
||||
// we don't have a windowless, zombie process if that takes
|
||||
// awhile (it can... dang eap...)
|
||||
ReleaseMutex(_onePatcherMut);
|
||||
CloseHandle(_onePatcherMut);
|
||||
|
||||
// kthxbai
|
||||
return s_error.IsEmpty() ? PLASMA_OK : PLASMA_PHAILURE;
|
||||
}
|
||||
|
||||
/* Enable themes in Windows XP and later */
|
||||
#pragma comment(linker,"\"/manifestdependency:type='win32' \
|
||||
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
|
||||
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
|
@ -89,6 +89,13 @@ struct pfPatcherWorker : public hsThread
|
||||
|
||||
// Any file
|
||||
kFlagZipped = 1<<3,
|
||||
|
||||
// Executable flags
|
||||
kRedistUpdate = 1<<4,
|
||||
|
||||
// Begin internal flags
|
||||
kLastManifestFlag = 1<<5,
|
||||
kSelfPatch = 1<<6,
|
||||
};
|
||||
|
||||
std::deque<Request> fRequests;
|
||||
@ -100,9 +107,12 @@ struct pfPatcherWorker : public hsThread
|
||||
|
||||
pfPatcher::CompletionFunc fOnComplete;
|
||||
pfPatcher::FileDownloadFunc fFileBeginDownload;
|
||||
pfPatcher::FileDesiredFunc fFileDownloadDesired;
|
||||
pfPatcher::FileDownloadFunc fFileDownloaded;
|
||||
pfPatcher::GameCodeDiscoverFunc fGameCodeDiscovered;
|
||||
pfPatcher::ProgressTickFunc fProgressTick;
|
||||
pfPatcher::FileDownloadFunc fRedistUpdateDownloaded;
|
||||
pfPatcher::FileDownloadFunc fSelfPatch;
|
||||
|
||||
pfPatcher* fParent;
|
||||
volatile bool fStarted;
|
||||
@ -199,6 +209,8 @@ public:
|
||||
|
||||
void Begin() { fDLStartTime = hsTimer::GetSysSeconds(); }
|
||||
plFileName GetFileName() const { return fFilename; }
|
||||
bool IsRedistUpdate() const { return hsCheckBits(fFlags, pfPatcherWorker::kRedistUpdate); }
|
||||
bool IsSelfPatch() const { return hsCheckBits(fFlags, pfPatcherWorker::kSelfPatch); }
|
||||
void Unlink() const { plFileSystem::Unlink(fFilename); }
|
||||
};
|
||||
|
||||
@ -306,6 +318,10 @@ static void IFileThingDownloadCB(ENetError result, void* param, const plFileName
|
||||
if (IS_NET_SUCCESS(result)) {
|
||||
PatcherLogGreen("\tDownloaded File '%s'", stream->GetFileName().AsString().c_str());
|
||||
patcher->WhitelistFile(stream->GetFileName(), true);
|
||||
if (patcher->fSelfPatch && stream->IsSelfPatch())
|
||||
patcher->fSelfPatch(stream->GetFileName());
|
||||
if (patcher->fRedistUpdateDownloaded && stream->IsRedistUpdate())
|
||||
patcher->fRedistUpdateDownloaded(stream->GetFileName());
|
||||
patcher->IssueRequest();
|
||||
} else {
|
||||
PatcherLogRed("\tDownloaded Failed: File '%s'", stream->GetFileName().AsString().c_str());
|
||||
@ -454,7 +470,7 @@ hsError pfPatcherWorker::Run()
|
||||
void pfPatcherWorker::ProcessFile()
|
||||
{
|
||||
do {
|
||||
const NetCliFileManifestEntry& entry = fQueuedFiles.front();
|
||||
NetCliFileManifestEntry& entry = fQueuedFiles.front();
|
||||
|
||||
// eap sucks
|
||||
plFileName clName = plString::FromWchar(entry.clientName);
|
||||
@ -474,10 +490,28 @@ void pfPatcherWorker::ProcessFile()
|
||||
}
|
||||
}
|
||||
|
||||
// If you got here, they're different.
|
||||
// It's different... but do we want it?
|
||||
if (fFileDownloadDesired) {
|
||||
if (!fFileDownloadDesired(clName)) {
|
||||
PatcherLogRed("\tDeclined '%S'", entry.clientName);
|
||||
fQueuedFiles.pop_front();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If you got here, they're different and we want it.
|
||||
PatcherLogYellow("\tEnqueuing '%S'", entry.clientName);
|
||||
plFileSystem::CreateDir(plFileName(clName).StripFileName());
|
||||
|
||||
// If someone registered for SelfPatch notifications, then we should probably
|
||||
// let them handle the gruntwork... Otherwise, go nuts!
|
||||
if (fSelfPatch) {
|
||||
if (clName == plFileSystem::GetCurrentAppPath().GetFileName()) {
|
||||
clName += ".tmp"; // don't overwrite myself!
|
||||
entry.flags |= kSelfPatch;
|
||||
}
|
||||
}
|
||||
|
||||
pfPatcherStream* s = new pfPatcherStream(this, dlName, entry);
|
||||
s->Open(clName, "wb");
|
||||
|
||||
@ -547,6 +581,11 @@ void pfPatcher::OnFileDownloadBegin(FileDownloadFunc cb)
|
||||
fWorker->fFileBeginDownload = cb;
|
||||
}
|
||||
|
||||
void pfPatcher::OnFileDownloadDesired(FileDesiredFunc cb)
|
||||
{
|
||||
fWorker->fFileDownloadDesired = cb;
|
||||
}
|
||||
|
||||
void pfPatcher::OnFileDownloaded(FileDownloadFunc cb)
|
||||
{
|
||||
fWorker->fFileDownloaded = cb;
|
||||
@ -562,6 +601,16 @@ void pfPatcher::OnProgressTick(ProgressTickFunc cb)
|
||||
fWorker->fProgressTick = cb;
|
||||
}
|
||||
|
||||
void pfPatcher::OnRedistUpdate(FileDownloadFunc cb)
|
||||
{
|
||||
fWorker->fRedistUpdateDownloaded = cb;
|
||||
}
|
||||
|
||||
void pfPatcher::OnSelfPatch(FileDownloadFunc cb)
|
||||
{
|
||||
fWorker->fSelfPatch = cb;
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
void pfPatcher::RequestGameCode()
|
||||
|
@ -70,6 +70,9 @@ public:
|
||||
/** Represents a function that takes the status and an optional message on completion. */
|
||||
typedef std::function<void(ENetError, const plString&)> CompletionFunc;
|
||||
|
||||
/** Represents a function that takes (const plFileName&) and approves it. */
|
||||
typedef std::function<bool(const plFileName&)> FileDesiredFunc;
|
||||
|
||||
/** Represents a function that takes (const plFileName&) on an interesting file operation. */
|
||||
typedef std::function<void(const plFileName&)> FileDownloadFunc;
|
||||
|
||||
@ -95,6 +98,12 @@ public:
|
||||
*/
|
||||
void OnFileDownloadBegin(FileDownloadFunc cb);
|
||||
|
||||
/** Set a callback that will be fired when the patcher wants to download a file. You are
|
||||
* given the ability to approve or veto the download. With great power comes great responsibility...
|
||||
* \remarks This will be called from the patcher thread.
|
||||
*/
|
||||
void OnFileDownloadDesired(FileDesiredFunc cb);
|
||||
|
||||
/** Set a callback that will be fired when the patcher has finished downloading a file from the server.
|
||||
* \remarks This will be called from the network thread.
|
||||
*/
|
||||
@ -112,6 +121,15 @@ public:
|
||||
*/
|
||||
void OnProgressTick(ProgressTickFunc cb);
|
||||
|
||||
/** Set a callback that will be fired when the patcher downloads an updated redistributable. Such as
|
||||
* the Visual C++ runtime (vcredist_x86.exe). You are responsible for installing it.
|
||||
* \remarks This will be called from the network thread.
|
||||
*/
|
||||
void OnRedistUpdate(FileDownloadFunc cb);
|
||||
|
||||
/** This is called when the current application has been updated. */
|
||||
void OnSelfPatch(FileDownloadFunc cb);
|
||||
|
||||
void RequestGameCode();
|
||||
void RequestManifest(const plString& mfs);
|
||||
void RequestManifest(const std::vector<plString>& mfs);
|
||||
|
Reference in New Issue
Block a user