You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1020 lines
33 KiB
1020 lines
33 KiB
/*==LICENSE==* |
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools |
|
Copyright (C) 2011 Cyan Worlds, Inc. |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
Additional permissions under GNU GPL version 3 section 7 |
|
|
|
If you modify this Program, or any covered work, by linking or |
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, |
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent |
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK |
|
(or a modified version of those libraries), |
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, |
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG |
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the |
|
licensors of this Program grant you additional |
|
permission to convey the resulting work. Corresponding Source for a |
|
non-source form of such a combination shall include the source code for |
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered |
|
work. |
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com |
|
or by snail mail at: |
|
Cyan Worlds, Inc. |
|
14617 N Newport Hwy |
|
Mead, WA 99021 |
|
|
|
*==LICENSE==*/ |
|
/***************************************************************************** |
|
* |
|
* $/Plasma20/Sources/Plasma/Apps/plClientPatcher/UruPlayer.cpp |
|
* |
|
***/ |
|
|
|
#include "Pch.h" |
|
#include "plStatusLog/plStatusLog.h" |
|
#include <queue> |
|
#pragma hdrstop |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Private |
|
* |
|
***/ |
|
#ifndef PLASMA_EXTERNAL_RELEASE |
|
static const wchar_t s_manifest[] = L"Internal"; |
|
static const wchar_t s_thinmanifest[] = L"ThinInternal"; |
|
#else |
|
static const wchar_t s_manifest[] = L"External"; |
|
static const wchar_t s_thinmanifest[] = L"ThinExternal"; |
|
#endif |
|
|
|
struct ManifestFile |
|
{ |
|
ManifestFile(const plFileName &clientName, const plFileName &downloadName, const plString &md5val, int flags, plLauncherInfo *info) |
|
{ |
|
filename = clientName; |
|
zipName = downloadName; |
|
md5 = md5val; |
|
this->flags = flags; |
|
this->info = info; |
|
md5failed = false; |
|
} |
|
|
|
plFileName filename; |
|
plFileName zipName; |
|
plString md5; |
|
int flags; |
|
bool md5failed; |
|
plLauncherInfo *info; |
|
}; |
|
|
|
|
|
class ProgressStream : public plZlibStream { |
|
public: |
|
virtual uint32_t Write(uint32_t byteCount, const void* buffer); |
|
static plLauncherInfo *info; |
|
static long totalBytes; |
|
static unsigned progress; |
|
|
|
// for updating bytes per second |
|
static uint32_t startTime; |
|
}; |
|
|
|
struct ProcessManifestEntryParam { |
|
struct ManifestResult * mr; |
|
unsigned index; |
|
static long totalSize; |
|
static long progress; |
|
static double startTime; |
|
bool exists; // marked as true if the file exists before MD5 check |
|
}; |
|
|
|
struct ManifestResult { |
|
wchar_t group[MAX_PATH]; |
|
ARRAY(NetCliFileManifestEntry) manifest; |
|
long * indicator; |
|
plLauncherInfo * info; |
|
|
|
hsMutex critsect; |
|
ARRAY(unsigned) indices; |
|
}; |
|
|
|
|
|
static void DownloadCallback ( |
|
ENetError result, |
|
void * param, |
|
const plFileName & filename, |
|
hsStream * writer |
|
); |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Private data |
|
* |
|
***/ |
|
|
|
static const unsigned kMaxManifestFileRequests = 5; |
|
static const unsigned kMinThreads = 16; |
|
static const unsigned kMaxThreads = 64; |
|
|
|
|
|
static unsigned s_fileRequests; |
|
static unsigned s_fileListRequests; |
|
static bool s_patchComplete; |
|
static PROCESS_INFORMATION s_pi; |
|
static long s_numFiles; |
|
static plFileName s_workingDir; |
|
static bool s_patchError; |
|
static long s_asyncCoreInitCount; |
|
static long s_numConnectFailures; |
|
static bool s_running; |
|
static std::queue<ManifestFile*> manifestQueue; |
|
//static AsyncThreadTaskList * s_taskList; |
|
|
|
// error strings |
|
static const char s_fileOpenError[] = "Unable to create file. Hard drive may be full."; |
|
static const char s_md5CheckError[] = "Error downloading file from server, exiting..."; |
|
|
|
enum { |
|
kPerfThreadTaskCount, |
|
kNumPerf |
|
}; |
|
|
|
static long s_perf[kNumPerf]; |
|
|
|
|
|
long ProgressStream::totalBytes; |
|
unsigned ProgressStream::progress; |
|
plLauncherInfo * ProgressStream::info; |
|
uint32_t ProgressStream::startTime = 0; |
|
long ProcessManifestEntryParam::progress = 0; |
|
long ProcessManifestEntryParam::totalSize = 0; |
|
double ProcessManifestEntryParam::startTime = 0; |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Exported data |
|
* |
|
***/ |
|
|
|
// IMPORTANT: This string may NEVER change. Doing so will break self-patching, |
|
// leaving clients with older patchers "dead in the water", without |
|
// a way to play Uru. |
|
#ifdef PLASMA_EXTERNAL_RELEASE |
|
plFileName kPatcherExeFilename = "UruLauncher.exe"; |
|
#else |
|
plFileName kPatcherExeFilename = "plUruLauncher.exe"; |
|
#endif |
|
|
|
|
|
//============================================================================ |
|
// External client file list |
|
//============================================================================ |
|
#ifdef PLASMA_EXTERNAL_RELEASE |
|
#ifdef HS_DEBUGGING |
|
static wchar_t s_clientExeName[] = L"plClient_dbg.exe"; |
|
#else |
|
static wchar_t s_clientExeName[] = L"UruExplorer.exe"; |
|
#endif // HS_DEBUGGING |
|
|
|
//============================================================================ |
|
// Internal client file list |
|
//============================================================================ |
|
#else |
|
#ifdef HS_DEBUGGING |
|
static wchar_t s_clientExeName[] = L"plClient_dbg.exe"; |
|
#else |
|
static wchar_t s_clientExeName[] = L"plClient.exe"; |
|
#endif // HS_DEBUGGING |
|
#endif // PLASMA_EXTERNAL_RELEASE |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Private Functions |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
static void NetErrorHandler (ENetProtocol protocol, ENetError error) { |
|
|
|
const wchar_t * srv; |
|
switch (protocol) { |
|
case kNetProtocolNil: srv = L"Notify"; break; |
|
case kNetProtocolCli2File: srv = L"File"; break; |
|
case kNetProtocolCli2GateKeeper: srv = L"GateKeeper"; break; |
|
DEFAULT_FATAL(protocol); |
|
} |
|
|
|
switch (error) { |
|
case kNetErrConnectFailed: |
|
case kNetErrTimeout: |
|
++s_numConnectFailures; |
|
break; |
|
|
|
case kNetErrDisconnected: |
|
s_patchError = true; |
|
break; |
|
|
|
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_running = false; |
|
break; |
|
} |
|
|
|
plString msg = plString::Format("NetErr: %S: %S", srv, NetErrorToString(error)); |
|
plStatusLog::AddLineS("patcher.log", msg.c_str()); |
|
|
|
// Notify GameTap something bad happened. |
|
if (!s_patchError) { |
|
MessageBox( |
|
nil, |
|
"Unable to connect to server.", |
|
"Error", |
|
MB_ICONERROR |
|
); |
|
s_patchError = true; |
|
} |
|
|
|
/*AsyncAppCallback( |
|
kPlayerNotifyFailed, |
|
kCmdResultFailed, |
|
(void *)NetErrorToString(error) |
|
);*/ |
|
} |
|
|
|
/* |
|
//============================================================================ |
|
static void WaitUruExitProc (void * param) { |
|
#ifdef USE_VLD |
|
VLDEnable(); |
|
#endif |
|
|
|
plLauncherInfo *info = (plLauncherInfo *) param; |
|
WaitForSingleObject(s_pi.hProcess, INFINITE); |
|
DWORD exitcode; |
|
GetExitCodeProcess(s_pi.hProcess, &exitcode); |
|
CloseHandle( s_pi.hThread ); |
|
CloseHandle( s_pi.hProcess ); |
|
|
|
if(exitcode == kExitCodeTerminated) { |
|
info->stopCallback(kStatusOk, nil); // notify of succesful stop |
|
} |
|
else { |
|
info->exitCallback(kStatusOk, nil); |
|
} |
|
} |
|
*/ |
|
|
|
//============================================================================ |
|
static bool MD5Check (const plFileName& filename, const char *md5) { |
|
plMD5Checksum existingMD5(filename); |
|
plMD5Checksum latestMD5; |
|
|
|
latestMD5.SetFromHexString(md5); |
|
return (existingMD5 == latestMD5); |
|
} |
|
|
|
//============================================================================ |
|
static void DecompressOgg (ManifestFile *mf) { |
|
unsigned flags = mf->flags; |
|
for(;;) |
|
{ |
|
// decompress ogg if necessary |
|
if ( (hsCheckBits(flags, plManifestFile::kSndFlagCacheSplit) || hsCheckBits(flags, plManifestFile::kSndFlagCacheStereo)) ) |
|
{ |
|
plFileName path = plFileName::Join(s_workingDir, mf->filename); |
|
|
|
plAudioFileReader* reader = plAudioFileReader::CreateReader(path, plAudioCore::kAll, plAudioFileReader::kStreamNative); |
|
if (!reader) |
|
{ |
|
break; |
|
} |
|
|
|
uint32_t size = reader->GetDataSize(); |
|
delete reader; |
|
|
|
ULARGE_INTEGER freeBytesAvailable, totalNumberOfBytes, neededBytes; |
|
if (GetDiskFreeSpaceEx(NULL, &freeBytesAvailable, &totalNumberOfBytes, NULL)) |
|
{ |
|
neededBytes.HighPart = 0; |
|
neededBytes.LowPart = size; |
|
|
|
if (neededBytes.QuadPart > freeBytesAvailable.QuadPart) |
|
{ |
|
//PatcherLog(kInfo, "Not enough disk space (asked for %d bytes)", bytesNeeded); |
|
break; |
|
} |
|
} |
|
|
|
if (hsCheckBits(flags, plManifestFile::kSndFlagCacheSplit)) |
|
plAudioFileReader::CacheFile(path, true, true); |
|
if (hsCheckBits(flags, plManifestFile::kSndFlagCacheStereo)) |
|
plAudioFileReader::CacheFile(path, false, true); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
//============================================================================ |
|
void Shutdown(plLauncherInfo *info) { |
|
info->SetText("Shutting Down..."); |
|
s_patchError = true; |
|
s_running = false; |
|
} |
|
|
|
//============================================================================ |
|
static void RequestNextManifestFile () { |
|
bool success = true; |
|
|
|
if (!manifestQueue.size()) |
|
return; |
|
|
|
ManifestFile* nextfile = manifestQueue.front(); |
|
manifestQueue.pop(); |
|
|
|
plFileName path = plFileName::Join(s_workingDir, nextfile->filename); |
|
plFileSystem::CreateDir(path.StripFileName(), true); |
|
|
|
ProgressStream *writer = new ProgressStream(); // optimization: dont delete and recreate. Doesn't seem to be working currently, ZLibStream is breaking |
|
if(!writer->Open(path, "wb")) |
|
{ |
|
writer->Close(); |
|
delete writer; |
|
success = false; |
|
} |
|
|
|
if(success) |
|
{ |
|
#ifndef PLASMA_EXTERNAL_RELEASE |
|
char text[256]; |
|
StrPrintf(text, arrsize(text), "Updating URU... %s", nextfile->filename.AsString().c_str()); |
|
nextfile->info->SetText(text); |
|
#endif |
|
NetCliFileDownloadRequest(nextfile->zipName, writer, DownloadCallback, nextfile, nextfile->info->buildId); |
|
} |
|
} |
|
|
|
//============================================================================ |
|
static void DownloadCallback ( |
|
ENetError result, |
|
void * param, |
|
const plFileName & filename, |
|
hsStream * writer |
|
) { |
|
s_numConnectFailures = 0; |
|
|
|
ManifestFile *mf = (ManifestFile *)param; |
|
if (IS_NET_ERROR(result) && s_running && !s_patchError) { |
|
if (result == kNetErrFileNotFound) { |
|
plString str = plString::Format("File not found on server: %s", filename.AsString().c_str()); |
|
MessageBox(nil, str.c_str(), "URU Launcher", MB_ICONERROR); |
|
s_patchError = true; |
|
} |
|
else if (result == kNetErrRemoteShutdown) { |
|
s_patchError = true; |
|
} |
|
else { |
|
// failed, re-queue the file to be downloaded |
|
// (after rewinding the stream) |
|
writer->Rewind(); |
|
plLauncherInfo *info = mf->info; |
|
NetCliFileDownloadRequest(filename, writer, DownloadCallback, mf, info->buildId); |
|
return; |
|
} |
|
} |
|
|
|
writer->Close(); |
|
delete writer; // delete our stream |
|
|
|
plFileName path = plFileName::Join(s_workingDir, mf->filename); |
|
if (s_running) |
|
{ |
|
if (!MD5Check(path, mf->md5.c_str())) { |
|
if (mf->md5failed) |
|
{ |
|
#ifdef PLASMA_EXTERNAL_RELEASE |
|
MessageBox(nil, s_md5CheckError, "URU Launcher", MB_ICONERROR); |
|
#else |
|
char str[256]; |
|
StrPrintf(str, arrsize(str), "%s %s ", path.AsString().c_str(), s_md5CheckError); |
|
MessageBox(nil, str, "URU Launcher", MB_ICONERROR); |
|
#endif // PLASMA_EXTERNAL_RELEASE |
|
Shutdown(mf->info); |
|
} |
|
writer = new ProgressStream(); |
|
if (!writer->Open(path, "wb")) { |
|
#ifdef PLASMA_EXTERNAL_RELEASE |
|
MessageBox(nil, s_fileOpenError, "URU Launcher", MB_ICONERROR); |
|
#else |
|
char str[256]; |
|
StrPrintf(str, arrsize(str), "%s %s", s_fileOpenError, path.AsString().c_str()); |
|
MessageBox(nil, str, "URU Launcher", MB_ICONERROR); |
|
#endif // PLASMA_EXTERNAL_RELEASE |
|
Shutdown(mf->info); |
|
} |
|
mf->md5failed = true; |
|
plLauncherInfo *info = mf->info; |
|
NetCliFileDownloadRequest(filename, writer, DownloadCallback, mf, info->buildId); |
|
return; |
|
} |
|
} |
|
|
|
AtomicAdd(&s_numFiles, -1); |
|
|
|
if (s_running) |
|
{ |
|
if (!mf->filename.GetFileExt().CompareI("ogg")) |
|
{ |
|
DecompressOgg(mf); |
|
} |
|
} |
|
|
|
delete mf; // delete manifest file entry |
|
|
|
// if we are not still running don't request any more file downloads |
|
if(s_running) |
|
{ |
|
if(!s_numFiles) { |
|
s_patchComplete = true; |
|
} |
|
else |
|
{ |
|
RequestNextManifestFile(); |
|
} |
|
} |
|
} |
|
|
|
//============================================================================ |
|
static void ProcessManifestEntry (void * param, ENetError error) { |
|
ProcessManifestEntryParam * p = (ProcessManifestEntryParam*)param; |
|
|
|
#ifndef PLASMA_EXTERNAL_RELEASE |
|
char text[256]; |
|
StrPrintf(text, arrsize(text), "Checking for updates... %S", p->mr->manifest[p->index].clientName); |
|
p->mr->info->SetText(text); |
|
#endif |
|
plFileName path = plFileName::Join(s_workingDir, p->mr->manifest[p->index].clientName); |
|
uint32_t start = (uint32_t)(TimeGetTime() / kTimeIntervalsPerMs); |
|
if (!MD5Check(path, p->mr->manifest[p->index].md5.c_str())) { |
|
p->mr->critsect.Lock(); |
|
p->mr->indices.Add(p->index); |
|
p->mr->critsect.Unlock(); |
|
AtomicAdd(&ProgressStream::totalBytes, p->mr->manifest[p->index].zipSize); |
|
} |
|
|
|
// if we have a file that was cached the MD5 check will be really fast throwing off our approx time remaining. |
|
uint32_t t = (uint32_t)(TimeGetTime() / kTimeIntervalsPerMs - start); |
|
if(t < 25) |
|
{ |
|
// cached file |
|
AtomicAdd(&ProcessManifestEntryParam::totalSize, -p->mr->manifest[p->index].zipSize); |
|
p->exists = false; |
|
} |
|
|
|
// p->mr->info->SetBytesRemaining(ProcessManifestEntryParam::totalSize); // for testing purposes only |
|
if(p->exists) |
|
{ |
|
AtomicAdd(&ProcessManifestEntryParam::progress, p->mr->manifest[p->index].zipSize); |
|
|
|
PatchInfo patchInfo; |
|
patchInfo.stage = 0; |
|
patchInfo.progressStage = 0; |
|
patchInfo.progress = (unsigned)((float)(ProcessManifestEntryParam::progress) / (float)ProcessManifestEntryParam::totalSize * 1000.0f); |
|
p->mr->info->progressCallback(kStatusPending, &patchInfo); |
|
if(ProcessManifestEntryParam::progress > ProcessManifestEntryParam::totalSize) |
|
{ |
|
p->mr->info->SetTimeRemaining(0); |
|
} |
|
else |
|
{ |
|
if(TimeGetTime() / kTimeIntervalsPerMs != ProcessManifestEntryParam::startTime) |
|
{ |
|
double timeElapsed = (TimeGetTime() / kTimeIntervalsPerMs - ProcessManifestEntryParam::startTime) / 1000; |
|
double bytesPerSec = (float)(ProcessManifestEntryParam::progress ) / timeElapsed; |
|
p->mr->info->SetTimeRemaining(bytesPerSec ? (int)((ProcessManifestEntryParam::totalSize - ProcessManifestEntryParam::progress) / bytesPerSec) : 0); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//============================================================================ |
|
static void ProcessManifest (void * param) { |
|
#ifdef USE_VLD |
|
VLDEnable(); |
|
#endif |
|
|
|
AtomicAdd(&s_perf[kPerfThreadTaskCount], 1); |
|
|
|
ManifestResult * mr = (ManifestResult *)param; |
|
|
|
PatchInfo patchInfo; |
|
patchInfo.stage = 0; |
|
patchInfo.progressStage = 0; |
|
patchInfo.progress = 0; |
|
mr->info->progressCallback(kStatusPending, &patchInfo); |
|
|
|
char text[256]; |
|
StrPrintf(text, arrsize(text), "Checking for updates..."); |
|
mr->info->SetText(text); |
|
|
|
unsigned entryCount = mr->manifest.Count(); |
|
NetCliFileManifestEntry * manifest = mr->manifest.Ptr(); |
|
|
|
FILE *fd = nil; |
|
ARRAY(ProcessManifestEntryParam) params; |
|
params.Reserve(mr->manifest.Count()); |
|
for (unsigned i = 0; i < entryCount; ++i) { |
|
ProcessManifestEntryParam * p = params.New(); |
|
p->index = i; |
|
p->mr = mr; |
|
p->exists = false; |
|
plFileName path = plFileName::Join(s_workingDir, mr->manifest[i].clientName); |
|
fd = plFileSystem::Open(path, "r"); |
|
if (fd) |
|
{ |
|
p->exists = true; |
|
p->totalSize += p->mr->manifest[i].zipSize; |
|
fclose(fd); |
|
} |
|
} |
|
|
|
ProcessManifestEntryParam::startTime = (double)(TimeGetTime() / kTimeIntervalsPerMs); |
|
|
|
for (unsigned i = 0; i < entryCount && s_running; ++i){ |
|
ProcessManifestEntry(¶ms[i], kNetSuccess); |
|
} |
|
|
|
if(s_running) |
|
{ |
|
PatchInfo patchInfo; |
|
patchInfo.stage = 0; |
|
patchInfo.progressStage = 0; |
|
patchInfo.progress = 1000; |
|
mr->info->progressCallback(kStatusPending, &patchInfo); |
|
|
|
AtomicAdd(&s_numFiles, mr->indices.Count()); |
|
if(!s_numFiles || !s_running) { |
|
s_patchComplete = true; |
|
} |
|
else { |
|
mr->info->SetText("Updating URU..."); |
|
|
|
PatchInfo patchInfo; |
|
patchInfo.stage = 0; |
|
patchInfo.progressStage = 0; |
|
patchInfo.progress = 0; |
|
mr->info->progressCallback(kStatusPending, &patchInfo); |
|
|
|
for (unsigned i = 0; i < mr->indices.Count(); ++i) |
|
{ |
|
if(s_running) |
|
{ |
|
unsigned index = mr->indices[i]; |
|
plFileName path = plFileName::Join(s_workingDir, manifest[index].clientName); |
|
plFileSystem::CreateDir(path.StripFileName(), true); |
|
|
|
ManifestFile* mf = new ManifestFile( |
|
manifest[index].clientName, |
|
manifest[index].downloadName, |
|
manifest[index].md5, |
|
manifest[index].flags, |
|
mr->info |
|
); |
|
|
|
if (i < kMaxManifestFileRequests) { |
|
ProgressStream * stream = new ProgressStream; |
|
if (!stream->Open(path, "wb")) { |
|
#ifdef PLASMA_EXTERNAL_RELEASE |
|
MessageBox(nil, s_fileOpenError, "URU Launcher", MB_ICONERROR); |
|
#else |
|
char str[256]; |
|
StrPrintf(str, arrsize(str), "%s %s", path.AsString().c_str(), s_fileOpenError); |
|
MessageBox(nil, str, "URU Launcher", MB_ICONERROR); |
|
#endif |
|
Shutdown(mr->info); |
|
} |
|
#ifndef PLASMA_EXTERNAL_RELEASE |
|
char text[256]; |
|
StrPrintf(text, arrsize(text), "Updating URU... %S", manifest[i].clientName); |
|
mr->info->SetText(text); |
|
#endif |
|
// fire off our initial requests. The remaining will be added as files are downloaded |
|
NetCliFileDownloadRequest(mf->zipName, stream, DownloadCallback, mf, mr->info->buildId); |
|
} |
|
else { |
|
// queue up this file download |
|
manifestQueue.push(mf); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
delete mr; |
|
AtomicAdd(&s_perf[kPerfThreadTaskCount], -1); |
|
} |
|
|
|
//============================================================================ |
|
static void ManifestCallback ( |
|
ENetError result, |
|
void * param, |
|
const wchar_t group[], |
|
const NetCliFileManifestEntry manifest[], |
|
unsigned entryCount |
|
){ |
|
s_numConnectFailures = 0; |
|
|
|
plLauncherInfo * info = (plLauncherInfo *) param; |
|
|
|
if(!s_running || IS_NET_ERROR(result)) { |
|
if (s_running && !s_patchError) { |
|
switch (result) { |
|
case kNetErrTimeout: |
|
NetCliFileManifestRequest(ManifestCallback, param, group); |
|
break; |
|
|
|
default: { |
|
char str[256]; |
|
StrPrintf(str, arrsize(str), "Failed to download manifest from server"); |
|
MessageBox(nil, str, "URU Launcher", MB_ICONERROR); |
|
s_patchError = true; |
|
} |
|
break; |
|
} |
|
} |
|
return; |
|
} |
|
|
|
ManifestResult * mr = new ManifestResult(); |
|
StrCopy(mr->group, group, arrsize(mr->group)); |
|
mr->manifest.Set(manifest, entryCount); |
|
mr->info = info; |
|
|
|
// sort our requests by size(this must be done for the next step to work) |
|
QSORT( |
|
NetCliFileManifestEntry, |
|
mr->manifest.Ptr(), |
|
mr->manifest.Count(), |
|
elem1.fileSize > elem2.fileSize |
|
); |
|
|
|
// remove duplicate entries. This can cause some bad problems if not done. It will cause MD5 checks to fail, since it can be writing a file while MD5 checking it. |
|
ARRAY(NetCliFileManifestEntry) noDuplicates; |
|
noDuplicates.Reserve(mr->manifest.Count()); |
|
for(unsigned i = 0; i < entryCount - 1; ++i) |
|
{ |
|
if (mr->manifest[i].clientName != mr->manifest[i+1].clientName) |
|
{ |
|
noDuplicates.Add(mr->manifest[i]); |
|
} |
|
} |
|
noDuplicates.Add(mr->manifest[entryCount - 1]); |
|
|
|
// adjust our array and set data |
|
mr->manifest.ShrinkBy(mr->manifest.Count() - noDuplicates.Count()); |
|
mr->manifest.Set(noDuplicates.Ptr(), noDuplicates.Count()); |
|
|
|
(void)_beginthread(ProcessManifest, 0, mr); |
|
} |
|
|
|
//============================================================================ |
|
static void ThinManifestCallback ( |
|
ENetError result, |
|
void * param, |
|
const wchar_t group[], |
|
const NetCliFileManifestEntry manifest[], |
|
unsigned entryCount |
|
){ |
|
s_numConnectFailures = 0; |
|
|
|
plLauncherInfo * info = (plLauncherInfo *) param; |
|
char text[256]; |
|
StrPrintf(text, arrsize(text), "Checking for updates..."); |
|
info->SetText(text); |
|
|
|
if(!s_running || IS_NET_ERROR(result)) { |
|
if (s_running && !s_patchError) { |
|
switch (result) { |
|
case kNetErrTimeout: |
|
NetCliFileManifestRequest(ManifestCallback, param, group); |
|
break; |
|
|
|
default: { |
|
char str[256]; |
|
StrPrintf(str, arrsize(str), "Failed to download manifest from server"); |
|
MessageBox(nil, str, "URU Launcher", MB_ICONERROR); |
|
s_patchError = true; |
|
} |
|
break; |
|
} |
|
} |
|
return; |
|
} |
|
s_patchComplete = true; |
|
for (unsigned i = 0; i < entryCount; ++i) { |
|
if (!s_running) |
|
return; |
|
|
|
plFileName path = plFileName::Join(s_workingDir, manifest[i].clientName); |
|
if (!MD5Check(path, manifest[i].md5.c_str())) { |
|
s_patchComplete = false; |
|
NetCliFileManifestRequest(ManifestCallback, info, s_manifest, info->buildId); |
|
break; |
|
} |
|
PatchInfo patchInfo; |
|
patchInfo.stage = 0; |
|
patchInfo.progressStage = 0; |
|
patchInfo.progress = (unsigned)((float)i / (float)entryCount * 1000.0f); |
|
info->progressCallback(kStatusPending, &patchInfo); |
|
#ifndef PLASMA_EXTERNAL_RELEASE |
|
char text[256]; |
|
StrPrintf(text, arrsize(text), "Checking for updates... %s", manifest[i].clientName.AsString().c_str()); |
|
info->SetText(text); |
|
#endif |
|
} |
|
} |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* ProgressStream Functions |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
uint32_t ProgressStream::Write(uint32_t byteCount, const void* buffer) { |
|
if(!s_running || s_patchError) |
|
return 0; |
|
if(!startTime) { |
|
startTime = TimeGetSecondsSince2001Utc(); |
|
} |
|
progress += byteCount; |
|
float p = (float)progress / (float)totalBytes * 1000; // progress |
|
|
|
PatchInfo patchInfo; |
|
patchInfo.stage = 1; |
|
patchInfo.progress = (unsigned) p; |
|
patchInfo.progressStage = 50; |
|
info->progressCallback(kStatusPending, (void *)&patchInfo); |
|
|
|
// there seems to, sometimes, be a slight discrepency in progress and totalBytes. |
|
if(progress > totalBytes) |
|
{ |
|
info->SetBytesRemaining(0); |
|
info->SetTimeRemaining(0); |
|
} |
|
else |
|
{ |
|
info->SetBytesRemaining(totalBytes - progress); |
|
if(TimeGetSecondsSince2001Utc() != startTime) |
|
{ |
|
uint32_t bytesPerSec = (progress ) / (TimeGetSecondsSince2001Utc() - startTime); |
|
info->SetTimeRemaining(bytesPerSec ? (totalBytes - progress) / bytesPerSec : 0); |
|
} |
|
} |
|
return plZlibStream::Write(byteCount, buffer); |
|
} |
|
|
|
|
|
//============================================================================ |
|
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_patchError = true; |
|
return; |
|
} |
|
|
|
plLauncherInfo *info = (plLauncherInfo *) param; |
|
|
|
// Start connecting to the server |
|
const char* caddr = hsWStringToString(addr); |
|
NetCliFileStartConnect(&caddr, 1, true); |
|
delete[] caddr; |
|
|
|
NetCliFileManifestRequest(ThinManifestCallback, info, s_thinmanifest, info->buildId); |
|
|
|
ProgressStream::info = info; |
|
PatchInfo patchInfo; |
|
patchInfo.stage = 0; |
|
patchInfo.progressStage = 0; |
|
patchInfo.progress = 0; |
|
info->progressCallback(kStatusPending, &patchInfo); |
|
} |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Public Functions |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
void InitAsyncCore () { |
|
if(AtomicAdd(&s_asyncCoreInitCount, 1) > 0) |
|
return; |
|
AsyncCoreInitialize(); |
|
|
|
plStatusLog::AddLineS("patcher.log", plProduct::ProductString().c_str()); |
|
} |
|
|
|
//============================================================================ |
|
void ShutdownAsyncCore () { |
|
if(AtomicAdd(&s_asyncCoreInitCount, -1) > 1) |
|
return; |
|
ASSERT(s_asyncCoreInitCount >= 0); |
|
|
|
while (s_perf[kPerfThreadTaskCount]) |
|
AsyncSleep(10); |
|
|
|
AsyncCoreDestroy(30 * 1000); |
|
} |
|
|
|
//============================================================================ |
|
// param = URU_PreparationRequest |
|
void UruPrepProc (void * param) { |
|
#ifdef USE_VLD |
|
VLDEnable(); |
|
#endif |
|
|
|
s_running = true; |
|
|
|
plLauncherInfo *info = (plLauncherInfo *) param; |
|
s_workingDir = plString::FromWchar(info->path); |
|
|
|
InitAsyncCore(); |
|
NetClientInitialize(); |
|
NetClientSetErrorHandler(NetErrorHandler); |
|
NetClientSetTransTimeoutMs(5 * 60 * 1000); // five minute timeout |
|
|
|
s_patchComplete = false; |
|
s_patchError = false; |
|
|
|
const char** addrs; |
|
unsigned count; |
|
|
|
count = GetGateKeeperSrvHostnames(&addrs); |
|
|
|
// Start connecting to the server |
|
NetCliGateKeeperStartConnect(addrs, count); |
|
|
|
// request a file server ip address |
|
NetCliGateKeeperFileSrvIpAddressRequest(FileSrvIpAddressCallback, param, true); |
|
|
|
do { |
|
NetClientUpdate(); |
|
AsyncSleep(10); |
|
} while ((!s_patchComplete && !s_patchError && s_running) || s_perf[kPerfThreadTaskCount]); |
|
|
|
while (manifestQueue.size()) |
|
{ |
|
ManifestFile* mf = manifestQueue.front(); |
|
manifestQueue.pop(); |
|
delete mf; |
|
} |
|
// If s_patchError, we don't wait around for s_numFiles |
|
// to drop to zero because it never does for reasons |
|
// I'm not willing to debug at the moment, so we just |
|
// bail on them. This causes a race condition with |
|
// the outstanding file object cancel/deletion and |
|
// subsequently a memory leak. -eap |
|
|
|
if (s_patchError) { |
|
info->SetText("Exiting..."); |
|
} |
|
else { |
|
PatchInfo patchInfo; |
|
patchInfo.stage = 2; |
|
patchInfo.progressStage = 100; |
|
patchInfo.progress = 1000; |
|
info->progressCallback(kStatusOk, &patchInfo); |
|
} |
|
|
|
ProgressStream::info = nil; |
|
|
|
NetCliFileDisconnect (); |
|
NetClientUpdate(); |
|
|
|
// Shutdown the client/server networking subsystem |
|
NetClientDestroy(); |
|
|
|
info->prepCallback(s_patchError ? kStatusError : kStatusOk, nil); |
|
} |
|
|
|
//============================================================================ |
|
void PlayerStopProc (void * param) { |
|
#ifdef USE_VLD |
|
VLDEnable(); |
|
#endif |
|
|
|
s_running = false; |
|
plLauncherInfo *info = (plLauncherInfo *) param; |
|
//TerminateProcess(s_pi.hProcess, kExitCodeTerminated); |
|
info->stopCallback(kStatusOk, nil); |
|
} |
|
|
|
//============================================================================ |
|
void PlayerTerminateProc (void * param) { |
|
#ifdef USE_VLD |
|
VLDEnable(); |
|
#endif |
|
|
|
s_running = false; |
|
plLauncherInfo *info = (plLauncherInfo *) param; |
|
ShutdownAsyncCore(); |
|
info->terminateCallback(kStatusOk, nil); |
|
} |
|
|
|
//============================================================================ |
|
void UruStartProc (void * param) { |
|
#ifdef USE_VLD |
|
VLDEnable(); |
|
#endif |
|
|
|
if(!s_running) |
|
return; |
|
|
|
plLauncherInfo *info = (plLauncherInfo *) param; |
|
|
|
wchar_t workDir[MAX_PATH]; |
|
StrPrintf(workDir, arrsize(workDir), L"%s", info->path); |
|
//fprintf(stderr, "URUPlayer StartProc gamePath is:%ws\n", workDir); |
|
|
|
wchar_t cmdLine[MAX_PATH]; |
|
StrPrintf(cmdLine, arrsize(cmdLine), L"%s%s %s", workDir, s_clientExeName, info->cmdLine); |
|
|
|
// Create the named event so the client won't restart us (Windows will clean it up when we exit) |
|
HANDLE hPatcherEvent = CreateEventW(nil, TRUE, FALSE, L"UruPatcherEvent"); |
|
if (hPatcherEvent == NULL) { |
|
info->startCallback(kStatusError, nil); |
|
return; |
|
} |
|
|
|
fprintf(stderr, "URUPlayer StartProc, running game process at dir:%ws, cmd:%ws for application:%ws\n", workDir, cmdLine, s_clientExeName); |
|
|
|
STARTUPINFOW si; |
|
memset(&si, 0, sizeof(si)); |
|
memset(&s_pi, 0, sizeof(s_pi)); |
|
si.cb = sizeof(si); |
|
|
|
info->SetText("Launching URU..."); |
|
BOOL success = CreateProcessW( |
|
NULL, |
|
cmdLine, |
|
NULL, // plProcessAttributes |
|
NULL, // plThreadAttributes |
|
FALSE, // bInheritHandles |
|
0, // dwCreationFlags |
|
NULL, // lpEnvironment |
|
workDir, // lpCurrentDirectory |
|
&si, |
|
&s_pi |
|
); |
|
|
|
if (success) |
|
{ |
|
fprintf(stderr, "%d", GetLastError()); |
|
info->returnCode = s_pi.dwProcessId; |
|
CloseHandle( s_pi.hThread ); |
|
CloseHandle( s_pi.hProcess ); |
|
// This may smooth the visual transition from GameTap to Uru, or it may make it worse. |
|
WaitForInputIdle(s_pi.hProcess, INFINITE); |
|
//_beginthread(WaitUruExitProc, 0, param); |
|
|
|
// wait for the event to signal (give the client 10 seconds to start up, then die) |
|
DWORD wait = WaitForSingleObject(hPatcherEvent, 10000); |
|
if (wait == WAIT_TIMEOUT) |
|
info->startCallback(kStatusOk, nil); |
|
else |
|
info->startCallback(kStatusOk, nil); |
|
} |
|
else |
|
{ |
|
info->startCallback(kStatusError, nil); |
|
} |
|
}
|
|
|