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.
1017 lines
33 KiB
1017 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/>. |
|
|
|
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" |
|
#pragma hdrstop |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Private |
|
* |
|
***/ |
|
#ifndef PLASMA_EXTERNAL_RELEASE |
|
static const wchar s_manifest[] = L"Internal"; |
|
static const wchar s_macmanifest[] = L"macInternal"; |
|
static const wchar s_thinmanifest[] = L"ThinInternal"; |
|
#else |
|
static const wchar s_manifest[] = L"External"; |
|
static const wchar s_macmanifest[] = L"macExternal"; |
|
static const wchar s_thinmanifest[] = L"ThinExternal"; |
|
#endif |
|
|
|
struct ManifestFile |
|
{ |
|
LINK(ManifestFile) link; |
|
ManifestFile(const wchar clientName[], const wchar downloadName[], const wchar md5val[], int flags, plLauncherInfo *info) |
|
{ |
|
StrCopy(filename, clientName, arrsize(filename)); |
|
StrCopy(zipName, downloadName, arrsize(zipName)); |
|
StrCopy(md5, md5val, arrsize(md5)); |
|
this->flags = flags; |
|
this->info = info; |
|
md5failed = false; |
|
} |
|
|
|
wchar filename[MAX_PATH]; |
|
wchar zipName[MAX_PATH]; |
|
wchar md5[MAX_PATH]; |
|
int flags; |
|
bool md5failed; |
|
plLauncherInfo *info; |
|
}; |
|
|
|
|
|
class ProgressStream : public plZlibStream { |
|
public: |
|
virtual UInt32 Write(UInt32 byteCount, const void* buffer); |
|
static plLauncherInfo *info; |
|
static long totalBytes; |
|
static unsigned progress; |
|
|
|
// for updating bytes per second |
|
static dword 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 group[MAX_PATH]; |
|
ARRAY(NetCliFileManifestEntry) manifest; |
|
long * indicator; |
|
plLauncherInfo * info; |
|
|
|
CCritSect critsect; |
|
ARRAY(unsigned) indices; |
|
}; |
|
|
|
|
|
static void DownloadCallback ( |
|
ENetError result, |
|
void * param, |
|
const wchar 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 CCritSect s_critsect; |
|
static char s_workingDir[MAX_PATH]; |
|
static bool s_patchError; |
|
static long s_asyncCoreInitCount; |
|
static long s_numConnectFailures; |
|
static bool s_running; |
|
static LISTDECL(ManifestFile, link) s_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; |
|
dword 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 |
|
const wchar kPatcherExeFilename[] = L"UruLauncher.exe"; |
|
#else |
|
const wchar kPatcherExeFilename[] = L"plUruLauncher.exe"; |
|
#endif |
|
|
|
|
|
//============================================================================ |
|
// External client file list |
|
//============================================================================ |
|
#ifdef PLASMA_EXTERNAL_RELEASE |
|
#ifdef HS_DEBUGGING |
|
static wchar s_clientExeName[] = L"plClient_dbg.exe"; |
|
#else |
|
static wchar s_clientExeName[] = L"UruExplorer.exe"; |
|
#endif // HS_DEBUGGING |
|
|
|
//============================================================================ |
|
// Internal client file list |
|
//============================================================================ |
|
#else |
|
#ifdef HS_DEBUGGING |
|
static wchar s_clientExeName[] = L"plClient_dbg.exe"; |
|
#else |
|
static wchar s_clientExeName[] = L"plClient.exe"; |
|
#endif // HS_DEBUGGING |
|
#endif // PLASMA_EXTERNAL_RELEASE |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Private Functions |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
static void LogHandler (ELogSeverity severity, const wchar msg[]) { |
|
AsyncLogWriteMsg(L"UruPlayer", severity, msg); |
|
} |
|
|
|
//============================================================================ |
|
static void NetErrorHandler (ENetProtocol protocol, ENetError error) { |
|
|
|
const wchar * 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; |
|
} |
|
|
|
LogMsg(kLogError, L"NetErr: %s: %s", srv, NetErrorToString(error)); |
|
|
|
// 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) { |
|
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 char filename[], const wchar md5[]) { |
|
// Do md5 check |
|
char md5copy[MAX_PATH]; |
|
plMD5Checksum existingMD5(filename); |
|
plMD5Checksum latestMD5; |
|
|
|
StrToAnsi(md5copy, md5, arrsize(md5copy)); |
|
latestMD5.SetFromHexString(md5copy); |
|
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)) ) |
|
{ |
|
char path[MAX_PATH]; |
|
StrPrintf(path, arrsize(path), "%s%S", s_workingDir, mf->filename); |
|
|
|
plAudioFileReader* reader = plAudioFileReader::CreateReader(path, plAudioCore::kAll, plAudioFileReader::kStreamNative); |
|
if (!reader) |
|
{ |
|
break; |
|
} |
|
|
|
UInt32 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; |
|
ManifestFile *nextfile = s_manifestQueue.Head(); |
|
if(!nextfile) |
|
return; |
|
s_manifestQueue.Unlink(nextfile); |
|
char path[MAX_PATH]; |
|
wchar basePath[MAX_PATH]; |
|
StrPrintf(path, arrsize(path), "%s%S", s_workingDir, nextfile->filename); |
|
StrToUnicode(basePath, path, arrsize(basePath)); |
|
PathRemoveFilename(basePath, basePath, arrsize(basePath)); |
|
PathCreateDirectory(basePath, kPathCreateDirFlagEntireTree); |
|
|
|
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); |
|
nextfile->info->SetText(text); |
|
#endif |
|
NetCliFileDownloadRequest(nextfile->zipName, writer, DownloadCallback, nextfile, nextfile->info->buildId); |
|
} |
|
} |
|
|
|
//============================================================================ |
|
static void DownloadCallback ( |
|
ENetError result, |
|
void * param, |
|
const wchar filename[], |
|
hsStream * writer |
|
) { |
|
s_numConnectFailures = 0; |
|
|
|
ManifestFile *mf = (ManifestFile *)param; |
|
if (IS_NET_ERROR(result) && s_running && !s_patchError) { |
|
if (result == kNetErrFileNotFound) { |
|
char str[256]; |
|
StrPrintf(str, arrsize(str), "File not found on server: %S", filename); |
|
MessageBox(nil, 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 |
|
|
|
char path[MAX_PATH]; |
|
StrPrintf( |
|
path, |
|
arrsize(path), |
|
"%s%S", |
|
s_workingDir, |
|
mf->filename |
|
); |
|
if(s_running) |
|
{ |
|
if(!MD5Check(path, mf->md5)) { |
|
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, 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); |
|
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) |
|
{ |
|
wchar ext[MAX_EXT]; |
|
PathSplitPath(mf->filename, nil, nil, nil, ext); |
|
if(!StrCmpI(L".ogg", ext)) |
|
{ |
|
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 |
|
char path[MAX_PATH]; |
|
StrPrintf( |
|
path, |
|
arrsize(path), |
|
"%s%S", |
|
s_workingDir, |
|
p->mr->manifest[p->index].clientName |
|
); |
|
dword start = (dword)(TimeGetTime() / kTimeIntervalsPerMs); |
|
if(!MD5Check(path, p->mr->manifest[p->index].md5)) { |
|
p->mr->critsect.Enter(); |
|
p->mr->indices.Add(p->index); |
|
p->mr->critsect.Leave(); |
|
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. |
|
dword t = (dword)(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) { |
|
wchar basePath[MAX_PATH]; |
|
char path[MAX_PATH]; |
|
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; |
|
StrPrintf(path, arrsize(path), "%s%S", s_workingDir, mr->manifest[i].clientName); |
|
fd = fopen(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]; |
|
StrPrintf(path, arrsize(path), "%s%S", s_workingDir, manifest[index].clientName); |
|
StrToUnicode(basePath, path, arrsize(basePath)); |
|
PathRemoveFilename(basePath, basePath, arrsize(basePath)); |
|
PathCreateDirectory(basePath, kPathCreateDirFlagEntireTree); |
|
|
|
ManifestFile * mf = NEW(ManifestFile)( |
|
manifest[index].clientName, |
|
manifest[index].downloadName, |
|
manifest[index].md5, |
|
manifest[index].flags, |
|
mr->info |
|
); |
|
|
|
if (i < kMaxManifestFileRequests) { |
|
ProgressStream * stream; |
|
stream = NEWZERO(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, 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 |
|
s_manifestQueue.Link(mf); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
DEL(mr); |
|
AtomicAdd(&s_perf[kPerfThreadTaskCount], -1); |
|
} |
|
|
|
//============================================================================ |
|
static void ManifestCallback ( |
|
ENetError result, |
|
void * param, |
|
const wchar 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(StrCmp(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 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; |
|
char path[MAX_PATH]; |
|
for (unsigned i = 0; i < entryCount; ++i) { |
|
if(!s_running) return; |
|
StrPrintf(path, arrsize(path), "%s%S", s_workingDir, manifest[i].clientName); |
|
if(!MD5Check(path, manifest[i].md5)){ |
|
s_patchComplete = false; |
|
if (info->IsTGCider) |
|
NetCliFileManifestRequest(ManifestCallback, info, s_macmanifest, info->buildId); |
|
else |
|
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); |
|
info->SetText(text); |
|
#endif |
|
} |
|
} |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* ProgressStream Functions |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
UInt32 ProgressStream::Write(UInt32 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) |
|
{ |
|
dword 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 addr[] |
|
) { |
|
NetCliGateKeeperDisconnect(); |
|
|
|
if (IS_NET_ERROR(result)) { |
|
LogMsg(kLogDebug, L"FileSrvIpAddressRequest failed: %s", NetErrorToString(result)); |
|
s_patchError = true; |
|
return; |
|
} |
|
|
|
plLauncherInfo *info = (plLauncherInfo *) param; |
|
|
|
// Start connecting to the server |
|
NetCliFileStartConnect(&addr, 1, true); |
|
|
|
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; |
|
LogRegisterHandler(LogHandler); |
|
AsyncCoreInitialize(); |
|
AsyncLogInitialize(L"Log", false); |
|
|
|
wchar productString[256]; |
|
ProductString(productString, arrsize(productString)); |
|
LogMsg(kLogPerf, L"Patcher: %s", productString); |
|
} |
|
|
|
//============================================================================ |
|
void ShutdownAsyncCore () { |
|
if(AtomicAdd(&s_asyncCoreInitCount, -1) > 1) |
|
return; |
|
ASSERT(s_asyncCoreInitCount >= 0); |
|
|
|
while (s_perf[kPerfThreadTaskCount]) |
|
AsyncSleep(10); |
|
|
|
AsyncLogDestroy(); |
|
AsyncCoreDestroy(30 * 1000); |
|
LogUnregisterHandler(LogHandler); |
|
} |
|
|
|
//============================================================================ |
|
// param = URU_PreparationRequest |
|
void UruPrepProc (void * param) { |
|
s_running = true; |
|
|
|
plLauncherInfo *info = (plLauncherInfo *) param; |
|
|
|
StrToAnsi(s_workingDir, info->path, arrsize(s_workingDir)); |
|
|
|
InitAsyncCore(); |
|
NetClientInitialize(); |
|
NetClientSetErrorHandler(NetErrorHandler); |
|
NetClientSetTransTimeoutMs(5 * 60 * 1000); // five minute timeout |
|
|
|
s_patchComplete = false; |
|
s_patchError = false; |
|
|
|
const wchar ** 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(ManifestFile *mf = s_manifestQueue.Head()) |
|
{ |
|
DEL(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) { |
|
s_running = false; |
|
plLauncherInfo *info = (plLauncherInfo *) param; |
|
//TerminateProcess(s_pi.hProcess, kExitCodeTerminated); |
|
info->stopCallback(kStatusOk, nil); |
|
} |
|
|
|
//============================================================================ |
|
void PlayerTerminateProc (void * param) { |
|
s_running = false; |
|
plLauncherInfo *info = (plLauncherInfo *) param; |
|
ShutdownAsyncCore(); |
|
info->terminateCallback(kStatusOk, nil); |
|
} |
|
|
|
//============================================================================ |
|
void UruStartProc (void * param) { |
|
if(!s_running) |
|
return; |
|
|
|
plLauncherInfo *info = (plLauncherInfo *) param; |
|
|
|
wchar workDir[MAX_PATH]; |
|
StrPrintf(workDir, arrsize(workDir), L"%s", info->path); |
|
//fprintf(stderr, "URUPlayer StartProc gamePath is:%ws\n", workDir); |
|
|
|
wchar 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; |
|
ZERO(si); |
|
ZERO(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); |
|
} |
|
} |