From e6f9a83fde481155350b4c915f05f10209ea76f4 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 26 Nov 2013 23:12:20 -0500 Subject: [PATCH 1/6] Detect self-patching in pfPatcher --- .../Plasma/FeatureLib/pfPatcher/pfPatcher.cpp | 24 ++++++++++++++++++- .../Plasma/FeatureLib/pfPatcher/pfPatcher.h | 3 +++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp b/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp index d4705e62..07dff2f5 100644 --- a/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp +++ b/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp @@ -89,6 +89,10 @@ struct pfPatcherWorker : public hsThread // Any file kFlagZipped = 1<<3, + + // Begin internal flags + kLastManifestFlag = 1<<4, + kSelfPatch = 1<<5, }; std::deque fRequests; @@ -103,6 +107,7 @@ struct pfPatcherWorker : public hsThread pfPatcher::FileDownloadFunc fFileDownloaded; pfPatcher::GameCodeDiscoverFunc fGameCodeDiscovered; pfPatcher::ProgressTickFunc fProgressTick; + pfPatcher::FileDownloadFunc fSelfPatch; pfPatcher* fParent; volatile bool fStarted; @@ -199,6 +204,7 @@ public: void Begin() { fDLStartTime = hsTimer::GetSysSeconds(); } plFileName GetFileName() const { return fFilename; } + bool IsSelfPatch() const { return hsCheckBits(fFlags, pfPatcherWorker::kSelfPatch); } void Unlink() const { plFileSystem::Unlink(fFilename); } }; @@ -306,6 +312,8 @@ 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()); patcher->IssueRequest(); } else { PatcherLogRed("\tDownloaded Failed: File '%s'", stream->GetFileName().AsString().c_str()); @@ -454,7 +462,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); @@ -478,6 +486,15 @@ void pfPatcherWorker::ProcessFile() 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"); @@ -562,6 +579,11 @@ void pfPatcher::OnProgressTick(ProgressTickFunc cb) fWorker->fProgressTick = cb; } +void pfPatcher::OnSelfPatch(FileDownloadFunc cb) +{ + fWorker->fSelfPatch = cb; +} + // =================================================== void pfPatcher::RequestGameCode() diff --git a/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h b/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h index 6610b722..dee1b77b 100644 --- a/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h +++ b/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h @@ -112,6 +112,9 @@ public: */ void OnProgressTick(ProgressTickFunc 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& mfs); From a231b4db9d88fae8bd573453081eb54f2d0a585a Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 28 Nov 2013 14:44:15 -0500 Subject: [PATCH 2/6] Rewrite plUruLauncher Welcome to the glorious new regime. --- Sources/Plasma/Apps/CMakeLists.txt | 1 - .../Apps/plClientPatcher/CMakeLists.txt | 28 - Sources/Plasma/Apps/plClientPatcher/Intern.h | 58 - Sources/Plasma/Apps/plClientPatcher/Pch.h | 75 -- .../Plasma/Apps/plClientPatcher/UruPlayer.cpp | 1020 ----------------- .../Plasma/Apps/plClientPatcher/UruPlayer.h | 67 -- .../Apps/plClientPatcher/plLauncherCallback.h | 65 -- .../Plasma/Apps/plUruLauncher/CMakeLists.txt | 12 +- Sources/Plasma/Apps/plUruLauncher/Intern.h | 61 - Sources/Plasma/Apps/plUruLauncher/Main.cpp | 884 -------------- Sources/Plasma/Apps/plUruLauncher/Pch.h | 72 -- .../Plasma/Apps/plUruLauncher/SelfPatcher.cpp | 338 ------ .../Apps/plUruLauncher/plClientLauncher.cpp | 351 ++++++ .../Apps/plUruLauncher/plClientLauncher.h | 147 +++ .../Apps/plUruLauncher/plLauncherInfo.h | 102 -- .../Apps/plUruLauncher/plUruLauncher.rc | 36 +- Sources/Plasma/Apps/plUruLauncher/resource.h | 7 +- Sources/Plasma/Apps/plUruLauncher/winmain.cpp | 348 ++++++ 18 files changed, 880 insertions(+), 2792 deletions(-) delete mode 100644 Sources/Plasma/Apps/plClientPatcher/CMakeLists.txt delete mode 100644 Sources/Plasma/Apps/plClientPatcher/Intern.h delete mode 100644 Sources/Plasma/Apps/plClientPatcher/Pch.h delete mode 100644 Sources/Plasma/Apps/plClientPatcher/UruPlayer.cpp delete mode 100644 Sources/Plasma/Apps/plClientPatcher/UruPlayer.h delete mode 100644 Sources/Plasma/Apps/plClientPatcher/plLauncherCallback.h delete mode 100644 Sources/Plasma/Apps/plUruLauncher/Intern.h delete mode 100644 Sources/Plasma/Apps/plUruLauncher/Main.cpp delete mode 100644 Sources/Plasma/Apps/plUruLauncher/Pch.h delete mode 100644 Sources/Plasma/Apps/plUruLauncher/SelfPatcher.cpp create mode 100644 Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp create mode 100644 Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h delete mode 100644 Sources/Plasma/Apps/plUruLauncher/plLauncherInfo.h create mode 100644 Sources/Plasma/Apps/plUruLauncher/winmain.cpp diff --git a/Sources/Plasma/Apps/CMakeLists.txt b/Sources/Plasma/Apps/CMakeLists.txt index fef53526..f3a1efa7 100644 --- a/Sources/Plasma/Apps/CMakeLists.txt +++ b/Sources/Plasma/Apps/CMakeLists.txt @@ -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) diff --git a/Sources/Plasma/Apps/plClientPatcher/CMakeLists.txt b/Sources/Plasma/Apps/plClientPatcher/CMakeLists.txt deleted file mode 100644 index 10046d95..00000000 --- a/Sources/Plasma/Apps/plClientPatcher/CMakeLists.txt +++ /dev/null @@ -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}) diff --git a/Sources/Plasma/Apps/plClientPatcher/Intern.h b/Sources/Plasma/Apps/plClientPatcher/Intern.h deleted file mode 100644 index 692e602f..00000000 --- a/Sources/Plasma/Apps/plClientPatcher/Intern.h +++ /dev/null @@ -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 . - -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 -* -***/ diff --git a/Sources/Plasma/Apps/plClientPatcher/Pch.h b/Sources/Plasma/Apps/plClientPatcher/Pch.h deleted file mode 100644 index 39eb2515..00000000 --- a/Sources/Plasma/Apps/plClientPatcher/Pch.h +++ /dev/null @@ -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 . - -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 -#include -#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" - - diff --git a/Sources/Plasma/Apps/plClientPatcher/UruPlayer.cpp b/Sources/Plasma/Apps/plClientPatcher/UruPlayer.cpp deleted file mode 100644 index a5957d6b..00000000 --- a/Sources/Plasma/Apps/plClientPatcher/UruPlayer.cpp +++ /dev/null @@ -1,1020 +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 . - -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 -#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 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, plString::FromWchar(p->mr->manifest[p->index].clientName)); - uint32_t start = (uint32_t)(TimeGetTime() / kTimeIntervalsPerMs); - if (!MD5Check(path, plString::FromWchar(p->mr->manifest[p->index].md5, 32).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, plString::FromWchar(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, plString::FromWchar(manifest[index].clientName)); - plFileSystem::CreateDir(path.StripFileName(), true); - - ManifestFile* mf = new ManifestFile( - plString::FromWchar(manifest[index].clientName), - plString::FromWchar(manifest[index].downloadName), - plString::FromWchar(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, plString::FromWchar(manifest[i].clientName)); - if (!MD5Check(path, plString::FromWchar(manifest[i].md5, 32).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); - 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); - } -} diff --git a/Sources/Plasma/Apps/plClientPatcher/UruPlayer.h b/Sources/Plasma/Apps/plClientPatcher/UruPlayer.h deleted file mode 100644 index b769ed53..00000000 --- a/Sources/Plasma/Apps/plClientPatcher/UruPlayer.h +++ /dev/null @@ -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 . - -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; diff --git a/Sources/Plasma/Apps/plClientPatcher/plLauncherCallback.h b/Sources/Plasma/Apps/plClientPatcher/plLauncherCallback.h deleted file mode 100644 index e58275a7..00000000 --- a/Sources/Plasma/Apps/plClientPatcher/plLauncherCallback.h +++ /dev/null @@ -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 . - -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; - -}; \ No newline at end of file diff --git a/Sources/Plasma/Apps/plUruLauncher/CMakeLists.txt b/Sources/Plasma/Apps/plUruLauncher/CMakeLists.txt index 0ee292ed..40609b31 100644 --- a/Sources/Plasma/Apps/plUruLauncher/CMakeLists.txt +++ b/Sources/Plasma/Apps/plUruLauncher/CMakeLists.txt @@ -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) diff --git a/Sources/Plasma/Apps/plUruLauncher/Intern.h b/Sources/Plasma/Apps/plUruLauncher/Intern.h deleted file mode 100644 index bdd81d6c..00000000 --- a/Sources/Plasma/Apps/plUruLauncher/Intern.h +++ /dev/null @@ -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 . - -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); \ No newline at end of file diff --git a/Sources/Plasma/Apps/plUruLauncher/Main.cpp b/Sources/Plasma/Apps/plUruLauncher/Main.cpp deleted file mode 100644 index 5578633e..00000000 --- a/Sources/Plasma/Apps/plUruLauncher/Main.cpp +++ /dev/null @@ -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 . - -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 -#pragma hdrstop - - -#include "resource.h" -#include -#define WIN32_LEAN_AND_MEAN -#define WHITESPACE L" \"\t\r\n\x1A" -#define UPDATE_STATUSMSG_SECONDS 30 // Must be an int - - -/***************************************************************************** -* -* Private -* -***/ - -enum ELogSev { - kLogInfo, - kLogErr, - kNumLogSev -}; - -enum { - kEventTimer = 1, -}; - -enum EEventType { - kEventSetProgress, - kEventSetText, - kEventSetStatusText, - kEventSetTimeRemaining, - kEventSetBytesRemaining, -}; - -// base window event -struct WndEvent { - LINK(WndEvent) link; - EEventType type; -}; - -struct SetProgressEvent : WndEvent { - int progress; -}; - -struct SetTextEvent : WndEvent { - char text[MAX_PATH]; -}; - -struct SetStatusTextEvent : WndEvent { - char text[MAX_PATH]; -}; - -struct SetTimeRemainingEvent : WndEvent { - unsigned seconds; -}; - -struct SetBytesRemainingEvent : WndEvent { - unsigned bytes; -}; - - -/***************************************************************************** -* -* Private data -* -***/ - -static bool s_shutdown; -static bool s_prepared; -static int s_retCode = 1; -static long s_terminationIssued; -static bool s_terminated; -static plLauncherInfo s_launcherInfo; -static HANDLE s_thread; -static HANDLE s_event; -static HINSTANCE s_hInstance; -static HWND s_dialog; -static hsSemaphore s_dialogCreateEvent(0); -static hsMutex s_critsect; -static LISTDECL(WndEvent, link) s_eventQ; -static hsSemaphore s_shutdownEvent(0); -static 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 * 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 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); -} diff --git a/Sources/Plasma/Apps/plUruLauncher/Pch.h b/Sources/Plasma/Apps/plUruLauncher/Pch.h deleted file mode 100644 index 262b5071..00000000 --- a/Sources/Plasma/Apps/plUruLauncher/Pch.h +++ /dev/null @@ -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 . - -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 -#include - -#include - -#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" - diff --git a/Sources/Plasma/Apps/plUruLauncher/SelfPatcher.cpp b/Sources/Plasma/Apps/plUruLauncher/SelfPatcher.cpp deleted file mode 100644 index 1755b29c..00000000 --- a/Sources/Plasma/Apps/plUruLauncher/SelfPatcher.cpp +++ /dev/null @@ -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 . - -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='*'\"") diff --git a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp new file mode 100644 index 00000000..4a8b015b --- /dev/null +++ b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp @@ -0,0 +1,351 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com +or by snail mail at: +Cyan Worlds, Inc. +14617 N Newport Hwy +Mead, WA 99021 + +*==LICENSE==*/ + +#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 + +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 * nmemb, arrsize(status))); + status[arrsize(status) - 1] = 0; + static_cast(thread)->fShardFunc(status); + return size * nmemb; +} + +hsError plShardStatus::Run() +{ + { + const char* url = GetServerStatusUrl(); + + // initialize CURL + std::unique_ptr> 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(); +} + +// =================================================== + +plClientLauncher::plClientLauncher() : + fFlags(0), + fServerIni("server.ini"), + fPatcherFactory(nullptr), + fClientExecutable(plManifest::ClientExecutable()), + fStatusThread(new plShardStatus()) +{ + pfPatcher::GetLog()->AddLine(plProduct::ProductString().c_str()); +} + +plClientLauncher::~plClientLauncher() { } + +// =================================================== + +plString plClientLauncher::GetAppArgs() const +{ + plStringStream ss; + ss << "-ServerIni="; + ss << fServerIni.AsString(); + 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 + fLaunchClientFunc(fClientExecutable, GetAppArgs()); + } else if (s_errorProc) + s_errorProc(result, msg); +} + +void plClientLauncher::PatchClient() +{ + if (fStatusFunc) + 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; }); + + if (hsCheckBits(fFlags, kHaveSelfPatched)) + patcher->RequestManifest(plManifest::ClientManifest()); + else + patcher->RequestManifest(plManifest::PatcherManifest()); + patcher->Start(); +} + +bool plClientLauncher::CompleteSelfPatch(std::function 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(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() +{ + enum { kArgServerIni, kArgNoSelfPatch }; + const CmdArgDef cmdLineArgs[] = { + { kCmdArgFlagged | kCmdTypeString, L"ServerIni", kArgServerIni }, + { kCmdArgFlagged | kCmdTypeBool, L"NoSelfPatch", kArgNoSelfPatch }, + }; + + CCmdParser cmdParser(cmdLineArgs, arrsize(cmdLineArgs)); + cmdParser.Parse(); + + // cache 'em + if (cmdParser.GetBool(kArgNoSelfPatch)) + hsSetBits(fFlags, kHaveSelfPatched); + if (cmdParser.IsSpecified(kArgServerIni)) + fServerIni = plString::FromWchar(cmdParser.GetString(kArgServerIni)); +} + +void plClientLauncher::SetErrorProc(ErrorFunc proc) +{ + s_errorProc = proc; +} + +void plClientLauncher::SetShardProc(StatusFunc proc) +{ + fStatusThread->fShardFunc = proc; +} diff --git a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h new file mode 100644 index 00000000..13f20a3f --- /dev/null +++ b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h @@ -0,0 +1,147 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com +or by snail mail at: +Cyan Worlds, Inc. +14617 N Newport Hwy +Mead, WA 99021 + +*==LICENSE==*/ + +#ifndef _plClientLauncher_inc_ +#define _plClientLauncher_inc_ + +#include "plFileSystem.h" +#include "pnNetBase/pnNbError.h" + +#include +#include + +class plClientLauncher +{ +public: + typedef std::function CreatePatcherFunc; + typedef std::function ErrorFunc; + typedef std::function LaunchClientFunc; + typedef std::function StatusFunc; + +private: + enum Flags + { + kHaveSelfPatched = 1<<0, + }; + + uint32_t fFlags; + plFileName fServerIni; + + plFileName fClientExecutable; + std::unique_ptr fStatusThread; + + CreatePatcherFunc fPatcherFactory; + LaunchClientFunc fLaunchClientFunc; + StatusFunc fStatusFunc; + + plString GetAppArgs() const; + + void IOnPatchComplete(ENetError result, const plString& msg); + +public: + plClientLauncher(); + ~plClientLauncher(); + + /** 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 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 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_ diff --git a/Sources/Plasma/Apps/plUruLauncher/plLauncherInfo.h b/Sources/Plasma/Apps/plUruLauncher/plLauncherInfo.h deleted file mode 100644 index 5cc995bc..00000000 --- a/Sources/Plasma/Apps/plUruLauncher/plLauncherInfo.h +++ /dev/null @@ -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 . - -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); \ No newline at end of file diff --git a/Sources/Plasma/Apps/plUruLauncher/plUruLauncher.rc b/Sources/Plasma/Apps/plUruLauncher/plUruLauncher.rc index e6261ee9..0a6e552a 100644 --- a/Sources/Plasma/Apps/plUruLauncher/plUruLauncher.rc +++ b/Sources/Plasma/Apps/plUruLauncher/plUruLauncher.rc @@ -4,7 +4,16 @@ #define WIN32_LEAN_AND_MEAN #include -#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 diff --git a/Sources/Plasma/Apps/plUruLauncher/resource.h b/Sources/Plasma/Apps/plUruLauncher/resource.h index d54baf7a..612a5ffd 100644 --- a/Sources/Plasma/Apps/plUruLauncher/resource.h +++ b/Sources/Plasma/Apps/plUruLauncher/resource.h @@ -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 diff --git a/Sources/Plasma/Apps/plUruLauncher/winmain.cpp b/Sources/Plasma/Apps/plUruLauncher/winmain.cpp new file mode 100644 index 00000000..702018c6 --- /dev/null +++ b/Sources/Plasma/Apps/plUruLauncher/winmain.cpp @@ -0,0 +1,348 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com +or by snail mail at: +Cyan Worlds, Inc. +14617 N Newport Hwy +Mead, WA 99021 + +*==LICENSE==*/ + +#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 +#include + +// =================================================== + +#define PLASMA_PHAILURE 1 +#define PLASMA_OK 0 + +static HWND s_dialog; +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 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(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); + break; + case WM_NCHITTEST: + SetWindowLongW(hwndDlg, DWL_MSGRESULT, (LONG_PTR)HTCAPTION); + return TRUE; + case WM_QUIT: + s_launcher.ShutdownNetCore(); + DestroyWindow(hwndDlg); + break; + default: + return DefWindowProcW(hwndDlg, uMsg, wParam, lParam); + } + + return TRUE; +} + +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(totalBytes)); + PostMessageW(progress, PBM_SETPOS, static_cast(curBytes), 0); +} + +// =================================================== + +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()) { + STARTUPINFOW si; + PROCESS_INFORMATION pi; + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); + si.cb = sizeof(si); + + // This event will prevent the game from restarting the patcher + HANDLE hEvent = CreateEventW(nullptr, TRUE, FALSE, L"UruPatcherEvent"); + + // Fire up ye olde new process + plString cmd = plString::Format("%s %s", exe.AsString().c_str(), args.c_str()); + CreateProcessW( + exe.AsString().ToWchar(), + const_cast(cmd.ToWchar().GetData()), // windows claims that it may modify this... let's hope that doesn't happen. + nullptr, + nullptr, + FALSE, + DETACHED_PROCESS, + nullptr, + plFileSystem::GetCWD().AsString().ToWchar(), + &si, + &pi + ); + + // 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(pi.hProcess, 1000); + WaitForSingleObject(hEvent, INFINITE); + } + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + 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); + + plString text = plString::Format("Error: %S\r\n%s", NetErrorAsString(result), msg.c_str()); + hsMessageBox(text.c_str(), "Error", hsMessageBoxNormal); + IQuit(PLASMA_PHAILURE); +} + +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 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.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()) { + hsMessageBox("No server.ini file found. Please check your URU installation.", "Error", hsMessageBoxNormal); + return PLASMA_PHAILURE; + } + + // Ensure there is only ever one patcher running... + if (IsPatcherRunning()) { + hsMessageBox(plString::Format("%s is already running", plProduct::LongName().c_str()).c_str(), "Error", + hsMessageBoxNormal, hsMessageBoxIconError); + 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(); + + // 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 PLASMA_OK; +} + +/* 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='*'\"") From e7ab709b522c0c48aa0c06b38dde2f19c12fde68 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 29 Nov 2013 00:16:17 -0500 Subject: [PATCH 3/6] Client Image Flag This argument works just like it does in Guild Wars. It checks all the files (using Cyan's evil "Internal" and "External" manifests) for changes. Expect this to take a decent amount of time. --- .../Apps/plUruLauncher/plClientLauncher.cpp | 27 ++++++++++++++----- .../Apps/plUruLauncher/plClientLauncher.h | 1 + 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp index 4a8b015b..5eb775f5 100644 --- a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp +++ b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp @@ -164,6 +164,11 @@ plString plClientLauncher::GetAppArgs() const plStringStream ss; ss << "-ServerIni="; ss << fServerIni.AsString(); + + // optional args + if (hsCheckBits(fFlags, kClientImage)) + ss << " -Image"; + return ss.GetString(); } @@ -195,9 +200,12 @@ void plClientLauncher::PatchClient() patcher->OnCompletion(std::bind(&plClientLauncher::IOnPatchComplete, this, std::placeholders::_1, std::placeholders::_2)); patcher->OnSelfPatch([&](const plFileName& file) { fClientExecutable = file; }); - if (hsCheckBits(fFlags, kHaveSelfPatched)) - patcher->RequestManifest(plManifest::ClientManifest()); - else + if (hsCheckBits(fFlags, kHaveSelfPatched)) { + if (hsCheckBits(fFlags, kClientImage)) + patcher->RequestManifest(plManifest::ClientImageManifest()); + else + patcher->RequestManifest(plManifest::ClientManifest()); + } else patcher->RequestManifest(plManifest::PatcherManifest()); patcher->Start(); } @@ -324,20 +332,27 @@ bool plClientLauncher::LoadServerIni() const void plClientLauncher::ParseArguments() { - enum { kArgServerIni, kArgNoSelfPatch }; +#define APPLY_FLAG(arg, flag) \ + if (cmdParser.GetBool(arg)) \ + fFlags |= flag; + + enum { kArgServerIni, kArgNoSelfPatch, kArgImage }; const CmdArgDef cmdLineArgs[] = { { kCmdArgFlagged | kCmdTypeString, L"ServerIni", kArgServerIni }, { kCmdArgFlagged | kCmdTypeBool, L"NoSelfPatch", kArgNoSelfPatch }, + { kCmdArgFlagged | kCmdTypeBool, L"Image", kArgImage }, }; CCmdParser cmdParser(cmdLineArgs, arrsize(cmdLineArgs)); cmdParser.Parse(); // cache 'em - if (cmdParser.GetBool(kArgNoSelfPatch)) - hsSetBits(fFlags, kHaveSelfPatched); if (cmdParser.IsSpecified(kArgServerIni)) fServerIni = plString::FromWchar(cmdParser.GetString(kArgServerIni)); + APPLY_FLAG(kArgNoSelfPatch, kHaveSelfPatched); + APPLY_FLAG(kArgImage, kClientImage); + +#undef APPLY_FLAG } void plClientLauncher::SetErrorProc(ErrorFunc proc) diff --git a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h index 13f20a3f..ab97000d 100644 --- a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h +++ b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h @@ -61,6 +61,7 @@ private: enum Flags { kHaveSelfPatched = 1<<0, + kClientImage = 1<<1, }; uint32_t fFlags; From d855d864755f19e694cfec892277df3c4da758d6 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 29 Nov 2013 01:44:53 -0500 Subject: [PATCH 4/6] Game Repair Mode This adds a license-stopgap "feature" ... You can now update only the game data by using the -Repair argument on the launcher. You'll need to specify a stripped down MOULa server.ini with the GateKeeperSrv keys/host. --- .../Apps/plUruLauncher/plClientLauncher.cpp | 34 +++++++++++++++++-- .../Apps/plUruLauncher/plClientLauncher.h | 4 +++ .../Plasma/FeatureLib/pfPatcher/pfPatcher.cpp | 17 +++++++++- .../Plasma/FeatureLib/pfPatcher/pfPatcher.h | 9 +++++ 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp index 5eb775f5..fd3771ec 100644 --- a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp +++ b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp @@ -161,6 +161,11 @@ 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(); @@ -190,16 +195,33 @@ void plClientLauncher::IOnPatchComplete(ENetError result, const plString& msg) 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::PatchClient() { - if (fStatusFunc) - fStatusFunc("Checking for updates..."); + 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; }); + // 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()); @@ -336,11 +358,12 @@ void plClientLauncher::ParseArguments() if (cmdParser.GetBool(arg)) \ fFlags |= flag; - enum { kArgServerIni, kArgNoSelfPatch, kArgImage }; + 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)); @@ -351,6 +374,11 @@ void plClientLauncher::ParseArguments() 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 } diff --git a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h index ab97000d..464d3082 100644 --- a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h +++ b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h @@ -62,6 +62,9 @@ private: { kHaveSelfPatched = 1<<0, kClientImage = 1<<1, + kGameDataOnly = (1<<2), + + kRepairGame = kHaveSelfPatched | kClientImage | kGameDataOnly, }; uint32_t fFlags; @@ -77,6 +80,7 @@ private: plString GetAppArgs() const; void IOnPatchComplete(ENetError result, const plString& msg); + bool IApproveDownload(const plFileName& file); public: plClientLauncher(); diff --git a/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp b/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp index 07dff2f5..75b9615e 100644 --- a/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp +++ b/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp @@ -104,6 +104,7 @@ struct pfPatcherWorker : public hsThread pfPatcher::CompletionFunc fOnComplete; pfPatcher::FileDownloadFunc fFileBeginDownload; + pfPatcher::FileDesiredFunc fFileDownloadDesired; pfPatcher::FileDownloadFunc fFileDownloaded; pfPatcher::GameCodeDiscoverFunc fGameCodeDiscovered; pfPatcher::ProgressTickFunc fProgressTick; @@ -482,7 +483,16 @@ 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()); @@ -564,6 +574,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; diff --git a/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h b/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h index dee1b77b..3147cf6e 100644 --- a/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h +++ b/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h @@ -70,6 +70,9 @@ public: /** Represents a function that takes the status and an optional message on completion. */ typedef std::function CompletionFunc; + /** Represents a function that takes (const plFileName&) and approves it. */ + typedef std::function FileDesiredFunc; + /** Represents a function that takes (const plFileName&) on an interesting file operation. */ typedef std::function 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. */ From 1bcd17c850c4d8272cd14d57d5403f8f4252ebbf Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 29 Nov 2013 20:25:52 -0500 Subject: [PATCH 5/6] Redist Updating This is the crowning feature: the whole point of this exercise. You can now update redists from the client launcher itself. To activate this functionality, you will need to flag the file 0x10 in the manifest. I recommend listing your redists in the patcher manifests. --- .../Apps/plUruLauncher/plClientLauncher.cpp | 82 +++++++++- .../Apps/plUruLauncher/plClientLauncher.h | 15 +- Sources/Plasma/Apps/plUruLauncher/winmain.cpp | 145 +++++++++++++----- .../Plasma/FeatureLib/pfPatcher/pfPatcher.cpp | 16 +- .../Plasma/FeatureLib/pfPatcher/pfPatcher.h | 6 + 5 files changed, 219 insertions(+), 45 deletions(-) diff --git a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp index fd3771ec..1ca1dc1a 100644 --- a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp +++ b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp @@ -58,7 +58,9 @@ Mead, WA 99021 #include "pfConsoleCore/pfConsoleEngine.h" PF_CONSOLE_LINK_FILE(Core) +#include #include +#include plClientLauncher::ErrorFunc s_errorProc = nullptr; // don't even ask, cause I'm not happy about this. @@ -145,12 +147,69 @@ void plShardStatus::Update() // =================================================== +class plRedistUpdater : public hsThread +{ + bool fSuccess; + +public: + plClientLauncher* fParent; + plClientLauncher::InstallRedistFunc fInstallProc; + std::deque 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()) + fStatusThread(new plShardStatus()), + fInstallerThread(new plRedistUpdater()) { pfPatcher::GetLog()->AddLine(plProduct::ProductString().c_str()); } @@ -188,9 +247,11 @@ void plClientLauncher::IOnPatchComplete(ENetError result, const plString& msg) // case 1 hsSetBits(fFlags, kHaveSelfPatched); PatchClient(); - } else - // cases 2 & 3 - fLaunchClientFunc(fClientExecutable, GetAppArgs()); + } else { + // cases 2 & 3 -- update any redistributables, then launch the client. + fInstallerThread->fParent = this; + fInstallerThread->Start(); + } } else if (s_errorProc) s_errorProc(result, msg); } @@ -203,6 +264,13 @@ bool plClientLauncher::IApproveDownload(const plFileName& file) return !path.AsString().IsEmpty(); } +void plClientLauncher::LaunchClient() const +{ + if (fStatusFunc) + fStatusFunc("Launching..."); + fLaunchClientFunc(fClientExecutable, GetAppArgs()); +} + void plClientLauncher::PatchClient() { if (fStatusFunc) { @@ -216,6 +284,7 @@ void plClientLauncher::PatchClient() 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)) @@ -388,6 +457,11 @@ void plClientLauncher::SetErrorProc(ErrorFunc proc) s_errorProc = proc; } +void plClientLauncher::SetInstallerProc(InstallRedistFunc proc) +{ + fInstallerThread->fInstallProc = proc; +} + void plClientLauncher::SetShardProc(StatusFunc proc) { fStatusThread->fShardFunc = proc; diff --git a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h index 464d3082..d7ddc856 100644 --- a/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h +++ b/Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h @@ -54,6 +54,7 @@ class plClientLauncher public: typedef std::function CreatePatcherFunc; typedef std::function ErrorFunc; + typedef std::function InstallRedistFunc; typedef std::function LaunchClientFunc; typedef std::function StatusFunc; @@ -71,7 +72,9 @@ private: plFileName fServerIni; plFileName fClientExecutable; - std::unique_ptr fStatusThread; + + std::unique_ptr fStatusThread; + std::unique_ptr fInstallerThread; CreatePatcherFunc fPatcherFactory; LaunchClientFunc fLaunchClientFunc; @@ -86,6 +89,11 @@ 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. @@ -129,6 +137,11 @@ public: */ 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; } diff --git a/Sources/Plasma/Apps/plUruLauncher/winmain.cpp b/Sources/Plasma/Apps/plUruLauncher/winmain.cpp index 702018c6..d25404bb 100644 --- a/Sources/Plasma/Apps/plUruLauncher/winmain.cpp +++ b/Sources/Plasma/Apps/plUruLauncher/winmain.cpp @@ -52,6 +52,7 @@ Mead, WA 99021 #include "hsWindows.h" #include "resource.h" #include +#include #include // =================================================== @@ -207,6 +208,108 @@ static void IOnProgressTick(uint64_t curBytes, uint64_t totalBytes, const plStri // =================================================== +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 file = exe.AsString().ToWchar(); + plStringBuffer 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(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 buf[2048]; + FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, + GetLastError(), + LANG_USER_DEFAULT, + buf, + arrsize(buf), + nullptr + ); + hsMessageBox(buf, L"Error", hsMessageBoxNormal, hsMessageBoxIconError); + } + + 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 @@ -216,38 +319,16 @@ static void ILaunchClientExecutable(const plFileName& exe, const plString& args) // 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()) { - STARTUPINFOW si; - PROCESS_INFORMATION pi; - memset(&si, 0, sizeof(si)); - memset(&pi, 0, sizeof(pi)); - si.cb = sizeof(si); - - // This event will prevent the game from restarting the patcher HANDLE hEvent = CreateEventW(nullptr, TRUE, FALSE, L"UruPatcherEvent"); - - // Fire up ye olde new process - plString cmd = plString::Format("%s %s", exe.AsString().c_str(), args.c_str()); - CreateProcessW( - exe.AsString().ToWchar(), - const_cast(cmd.ToWchar().GetData()), // windows claims that it may modify this... let's hope that doesn't happen. - nullptr, - nullptr, - FALSE, - DETACHED_PROCESS, - nullptr, - plFileSystem::GetCWD().AsString().ToWchar(), - &si, - &pi - ); + 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(pi.hProcess, 1000); + WaitForInputIdle(process, 1000); WaitForSingleObject(hEvent, INFINITE); } - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); + CloseHandle(process); CloseHandle(hEvent); } @@ -265,19 +346,6 @@ static void IOnNetError(ENetError result, const plString& msg) IQuit(PLASMA_PHAILURE); } -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 void ISetShardStatus(const plString& status) { SetDlgItemTextW(s_dialog, IDC_STATUS_TEXT, status.ToWchar()); @@ -299,6 +367,7 @@ int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL // 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); diff --git a/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp b/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp index 75b9615e..5ee9f474 100644 --- a/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp +++ b/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp @@ -90,9 +90,12 @@ struct pfPatcherWorker : public hsThread // Any file kFlagZipped = 1<<3, + // Executable flags + kRedistUpdate = 1<<4, + // Begin internal flags - kLastManifestFlag = 1<<4, - kSelfPatch = 1<<5, + kLastManifestFlag = 1<<5, + kSelfPatch = 1<<6, }; std::deque fRequests; @@ -108,6 +111,7 @@ struct pfPatcherWorker : public hsThread pfPatcher::FileDownloadFunc fFileDownloaded; pfPatcher::GameCodeDiscoverFunc fGameCodeDiscovered; pfPatcher::ProgressTickFunc fProgressTick; + pfPatcher::FileDownloadFunc fRedistUpdateDownloaded; pfPatcher::FileDownloadFunc fSelfPatch; pfPatcher* fParent; @@ -205,6 +209,7 @@ 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); } }; @@ -315,6 +320,8 @@ static void IFileThingDownloadCB(ENetError result, void* param, const plFileName 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()); @@ -594,6 +601,11 @@ void pfPatcher::OnProgressTick(ProgressTickFunc cb) fWorker->fProgressTick = cb; } +void pfPatcher::OnRedistUpdate(FileDownloadFunc cb) +{ + fWorker->fRedistUpdateDownloaded = cb; +} + void pfPatcher::OnSelfPatch(FileDownloadFunc cb) { fWorker->fSelfPatch = cb; diff --git a/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h b/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h index 3147cf6e..d5d9dfea 100644 --- a/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h +++ b/Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h @@ -121,6 +121,12 @@ 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); From 4ae685aff9bd1ac149cfc6cf77ca60b2a8e015dd Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 4 Dec 2013 02:01:18 -0500 Subject: [PATCH 6/6] Hack around hidden/dead MessageBoxes :( So, it appears that once our dialog is created, the DialogBox windowproc is nuked. So, to hack around this, any errors that occur while it is open are cached and reported out after we close everything down. It sucks, but it works. I blame Microsoft. --- Sources/Plasma/Apps/plUruLauncher/winmain.cpp | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/Sources/Plasma/Apps/plUruLauncher/winmain.cpp b/Sources/Plasma/Apps/plUruLauncher/winmain.cpp index d25404bb..35841143 100644 --- a/Sources/Plasma/Apps/plUruLauncher/winmain.cpp +++ b/Sources/Plasma/Apps/plUruLauncher/winmain.cpp @@ -61,6 +61,7 @@ Mead, WA 99021 #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; @@ -87,6 +88,12 @@ static void WaitForOldPatcher() // =================================================== +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? @@ -127,6 +134,7 @@ BOOL CALLBACK PatcherDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM l if (s_taskbar) s_taskbar->Release(); PostQuitMessage(PLASMA_OK); + s_dialog = nullptr; break; case WM_NCHITTEST: SetWindowLongW(hwndDlg, DWL_MSGRESULT, (LONG_PTR)HTCAPTION); @@ -135,11 +143,9 @@ BOOL CALLBACK PatcherDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM l s_launcher.ShutdownNetCore(); DestroyWindow(hwndDlg); break; - default: - return DefWindowProcW(hwndDlg, uMsg, wParam, lParam); } - return TRUE; + return DefWindowProcW(hwndDlg, uMsg, wParam, lParam); } static void ShowPatcherDialog(HINSTANCE hInstance) @@ -265,17 +271,18 @@ static HANDLE ICreateProcess(const plFileName& exe, const plString& args) return info.hProcess; } else { - wchar_t buf[2048]; + wchar_t* msg = nullptr; FormatMessageW( - FORMAT_MESSAGE_FROM_SYSTEM, + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), LANG_USER_DEFAULT, - buf, - arrsize(buf), + msg, + 0, nullptr ); - hsMessageBox(buf, L"Error", hsMessageBoxNormal, hsMessageBoxIconError); + s_error = plString::FromWchar(msg); + LocalFree(msg); } return nullptr; @@ -341,8 +348,7 @@ static void IOnNetError(ENetError result, const plString& msg) if (s_taskbar) s_taskbar->SetProgressState(s_dialog, TBPF_ERROR); - plString text = plString::Format("Error: %S\r\n%s", NetErrorAsString(result), msg.c_str()); - hsMessageBox(text.c_str(), "Error", hsMessageBoxNormal); + s_error = plString::Format("Error: %S\r\n%s", NetErrorAsString(result), msg.c_str()); IQuit(PLASMA_PHAILURE); } @@ -380,14 +386,14 @@ int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL // Load the doggone server.ini if (!s_launcher.LoadServerIni()) { - hsMessageBox("No server.ini file found. Please check your URU installation.", "Error", hsMessageBoxNormal); + 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()) { - hsMessageBox(plString::Format("%s is already running", plProduct::LongName().c_str()).c_str(), "Error", - hsMessageBoxNormal, hsMessageBoxIconError); + plString text = plString::Format("%s is already running", plProduct::LongName().c_str()); + IShowErrorDialog(text.ToWchar()); return PLASMA_OK; } HANDLE _onePatcherMut = CreatePatcherMutex(); @@ -400,6 +406,11 @@ int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL 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 @@ -408,7 +419,7 @@ int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL CloseHandle(_onePatcherMut); // kthxbai - return PLASMA_OK; + return s_error.IsEmpty() ? PLASMA_OK : PLASMA_PHAILURE; } /* Enable themes in Windows XP and later */