mirror of
https://foundry.openuru.org/gitblit/r/CWE-ou-minkata.git
synced 2025-07-14 02:27:40 -04:00
Merge pull request #352 from Hoikas/patcher-thread
Unified Patcher (Part 1: In-Game)
This commit is contained in:
@ -17,6 +17,6 @@ add_subdirectory(pfJournalBook)
|
||||
# add_subdirectory(pfKI)
|
||||
add_subdirectory(pfLocalizationMgr)
|
||||
add_subdirectory(pfMessage)
|
||||
add_subdirectory(pfPatcher)
|
||||
add_subdirectory(pfPython)
|
||||
add_subdirectory(pfSecurePreloader)
|
||||
add_subdirectory(pfSurface)
|
||||
|
@ -57,6 +57,5 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||||
#include "pfCCR/plCCRCreatable.h"
|
||||
#include "pfJournalBook/pfJournalBookCreatable.h"
|
||||
#include "pfGameMgr/pfGameMgrCreatables.h"
|
||||
#include "pfSecurePreloader/pfSecurePreloaderCreatable.h"
|
||||
|
||||
#endif // pfAllCreatables_inc
|
||||
|
19
Sources/Plasma/FeatureLib/pfPatcher/CMakeLists.txt
Normal file
19
Sources/Plasma/FeatureLib/pfPatcher/CMakeLists.txt
Normal file
@ -0,0 +1,19 @@
|
||||
include_directories("../../CoreLib")
|
||||
include_directories("../../NucleusLib")
|
||||
include_directories("../../NucleusLib/inc")
|
||||
include_directories("../../PubUtilLib")
|
||||
|
||||
set(pfPatcher_SOURCES
|
||||
plManifests.cpp
|
||||
pfPatcher.cpp
|
||||
)
|
||||
|
||||
set(pfPatcher_HEADERS
|
||||
plManifests.h
|
||||
pfPatcher.h
|
||||
)
|
||||
|
||||
add_library(pfPatcher STATIC ${pfPatcher_SOURCES} ${pfPatcher_HEADERS})
|
||||
|
||||
source_group("Source Files" FILES ${pfPatcher_SOURCES})
|
||||
source_group("Header Files" FILES ${pfPatcher_HEADERS})
|
598
Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp
Normal file
598
Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp
Normal file
@ -0,0 +1,598 @@
|
||||
/*==LICENSE==*
|
||||
|
||||
CyanWorlds.com Engine - MMOG client, server and tools
|
||||
Copyright (C) 2011 Cyan Worlds, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Additional permissions under GNU GPL version 3 section 7
|
||||
|
||||
If you modify this Program, or any covered work, by linking or
|
||||
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
||||
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
||||
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
||||
(or a modified version of those libraries),
|
||||
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
||||
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
||||
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
||||
licensors of this Program grant you additional
|
||||
permission to convey the resulting work. Corresponding Source for a
|
||||
non-source form of such a combination shall include the source code for
|
||||
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
||||
work.
|
||||
|
||||
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||||
or by snail mail at:
|
||||
Cyan Worlds, Inc.
|
||||
14617 N Newport Hwy
|
||||
Mead, WA 99021
|
||||
|
||||
*==LICENSE==*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
|
||||
#include "pfPatcher.h"
|
||||
|
||||
#include "HeadSpin.h"
|
||||
#include "plCompression/plZlibStream.h"
|
||||
#include "pnEncryption/plChecksum.h"
|
||||
#include "plFileSystem.h"
|
||||
#include "pnNetBase/pnNbError.h"
|
||||
#include "plNetGameLib/plNetGameLib.h"
|
||||
#include "plStatusLog/plStatusLog.h"
|
||||
#include "hsStream.h"
|
||||
#include "hsThread.h"
|
||||
#include "hsTimer.h"
|
||||
|
||||
// Some log helper defines
|
||||
#define PatcherLogGreen(...) pfPatcher::GetLog()->AddLineF(plStatusLog::kGreen, __VA_ARGS__)
|
||||
#define PatcherLogRed(...) pfPatcher::GetLog()->AddLineF(plStatusLog::kRed, __VA_ARGS__)
|
||||
#define PatcherLogWhite(...) pfPatcher::GetLog()->AddLineF(plStatusLog::kWhite, __VA_ARGS__)
|
||||
#define PatcherLogYellow(...) pfPatcher::GetLog()->AddLineF(plStatusLog::kYellow, __VA_ARGS__)
|
||||
|
||||
/** Patcher grunt work thread */
|
||||
struct pfPatcherWorker : public hsThread
|
||||
{
|
||||
/** Represents a File/Auth download request */
|
||||
struct Request
|
||||
{
|
||||
enum { kFile, kManifest, kSecurePreloader, kAuthFile, kPythonList, kSdlList };
|
||||
|
||||
plString fName;
|
||||
uint8_t fType;
|
||||
class pfPatcherStream* fStream;
|
||||
|
||||
Request(const plString& name, uint8_t type, class pfPatcherStream* s=nullptr) :
|
||||
fName(name), fType(type), fStream(s)
|
||||
{ }
|
||||
};
|
||||
|
||||
/** Human readable file flags */
|
||||
enum FileFlags
|
||||
{
|
||||
// Sound files only
|
||||
kSndFlagCacheSplit = 1<<0,
|
||||
kSndFlagStreamCompressed = 1<<1,
|
||||
kSndFlagCacheStereo = 1<<2,
|
||||
|
||||
// Any file
|
||||
kFlagZipped = 1<<3,
|
||||
};
|
||||
|
||||
std::deque<Request> fRequests;
|
||||
std::deque<NetCliFileManifestEntry> fQueuedFiles;
|
||||
|
||||
hsMutex fRequestMut;
|
||||
hsMutex fFileMut;
|
||||
hsSemaphore fFileSignal;
|
||||
|
||||
pfPatcher::CompletionFunc fOnComplete;
|
||||
pfPatcher::FileDownloadFunc fFileBeginDownload;
|
||||
pfPatcher::FileDownloadFunc fFileDownloaded;
|
||||
pfPatcher::GameCodeDiscoverFunc fGameCodeDiscovered;
|
||||
pfPatcher::ProgressTickFunc fProgressTick;
|
||||
|
||||
pfPatcher* fParent;
|
||||
volatile bool fStarted;
|
||||
volatile bool fRequestActive;
|
||||
|
||||
uint64_t fCurrBytes;
|
||||
uint64_t fTotalBytes;
|
||||
|
||||
pfPatcherWorker();
|
||||
~pfPatcherWorker();
|
||||
|
||||
void OnQuit();
|
||||
|
||||
void EndPatch(ENetError result, const plString& msg=plString::Null);
|
||||
bool IssueRequest();
|
||||
virtual hsError Run();
|
||||
void ProcessFile();
|
||||
void WhitelistFile(const plFileName& file, bool justDownloaded, hsStream* s=nullptr);
|
||||
};
|
||||
|
||||
// ===================================================
|
||||
|
||||
class pfPatcherStream : public plZlibStream
|
||||
{
|
||||
pfPatcherWorker* fParent;
|
||||
plFileName fFilename;
|
||||
uint32_t fFlags;
|
||||
|
||||
uint64_t fBytesWritten;
|
||||
float fDLStartTime;
|
||||
|
||||
plString IMakeStatusMsg() const
|
||||
{
|
||||
float secs = hsTimer::GetSysSeconds() - fDLStartTime;
|
||||
float bytesPerSec = fBytesWritten / secs;
|
||||
return plFileSystem::ConvertFileSize(bytesPerSec) + "/s";
|
||||
}
|
||||
|
||||
void IUpdateProgress(uint32_t count)
|
||||
{
|
||||
fBytesWritten += count; // just this file
|
||||
fParent->fCurrBytes += count; // the entire everything
|
||||
|
||||
// tick-tick-tick, tick-tick-tock
|
||||
if (fParent->fProgressTick)
|
||||
fParent->fProgressTick(fParent->fCurrBytes, fParent->fTotalBytes, IMakeStatusMsg());
|
||||
}
|
||||
|
||||
public:
|
||||
pfPatcherStream(pfPatcherWorker* parent, const plFileName& filename, uint64_t size)
|
||||
: fParent(parent), fFilename(filename), fFlags(0), fBytesWritten(0)
|
||||
{
|
||||
fParent->fTotalBytes += size;
|
||||
fOutput = new hsRAMStream;
|
||||
}
|
||||
|
||||
pfPatcherStream(pfPatcherWorker* parent, const plFileName& filename, const NetCliFileManifestEntry& entry)
|
||||
: fParent(parent), fFlags(entry.flags), fBytesWritten(0)
|
||||
{
|
||||
// ugh. eap removed the compressed flag in his fail manifests
|
||||
if (filename.GetFileExt().CompareI("gz") == 0) {
|
||||
fFlags |= pfPatcherWorker::kFlagZipped;
|
||||
parent->fTotalBytes += entry.zipSize;
|
||||
} else
|
||||
parent->fTotalBytes += entry.fileSize;
|
||||
}
|
||||
|
||||
virtual bool Open(const plFileName& filename, const char* mode)
|
||||
{
|
||||
fFilename = filename;
|
||||
return plZlibStream::Open(filename, mode);
|
||||
}
|
||||
|
||||
virtual uint32_t Write(uint32_t count, const void* buf)
|
||||
{
|
||||
// tick whatever progress bar we have
|
||||
IUpdateProgress(count);
|
||||
|
||||
// write the appropriate blargs
|
||||
if (hsCheckBits(fFlags, pfPatcherWorker::kFlagZipped))
|
||||
return plZlibStream::Write(count, buf);
|
||||
else
|
||||
return fOutput->Write(count, buf);
|
||||
}
|
||||
|
||||
virtual bool AtEnd() { return fOutput->AtEnd(); }
|
||||
virtual uint32_t GetEOF() { return fOutput->GetEOF(); }
|
||||
virtual uint32_t GetPosition() const { return fOutput->GetPosition(); }
|
||||
virtual uint32_t GetSizeLeft() const { return fOutput->GetSizeLeft(); }
|
||||
virtual uint32_t Read(uint32_t count, void* buf) { return fOutput->Read(count, buf); }
|
||||
virtual void Rewind() { fOutput->Rewind(); }
|
||||
virtual void SetPosition(uint32_t pos) { fOutput->SetPosition(pos); }
|
||||
virtual void Skip(uint32_t deltaByteCount) { fOutput->Skip(deltaByteCount); }
|
||||
|
||||
void Begin() { fDLStartTime = hsTimer::GetSysSeconds(); }
|
||||
plFileName GetFileName() const { return fFilename; }
|
||||
void Unlink() const { plFileSystem::Unlink(fFilename); }
|
||||
};
|
||||
|
||||
// ===================================================
|
||||
|
||||
static void IAuthThingDownloadCB(ENetError result, void* param, const plFileName& filename, hsStream* writer)
|
||||
{
|
||||
pfPatcherWorker* patcher = static_cast<pfPatcherWorker*>(param);
|
||||
|
||||
if (IS_NET_SUCCESS(result)) {
|
||||
PatcherLogGreen("\tDownloaded Legacy File '%s'", filename.AsString().c_str());
|
||||
patcher->IssueRequest();
|
||||
|
||||
// Now, we pass our RAM-backed file to the game code handlers. In the main client,
|
||||
// this will trickle down and add a new friend to plStreamSource. This should never
|
||||
// happen in any other app...
|
||||
writer->Rewind();
|
||||
patcher->WhitelistFile(filename, true, writer);
|
||||
} else {
|
||||
PatcherLogRed("\tDownloaded Failed: File '%s'", filename.AsString().c_str());
|
||||
patcher->EndPatch(result, filename.AsString());
|
||||
}
|
||||
}
|
||||
|
||||
static void IGotAuthFileList(ENetError result, void* param, const NetCliAuthFileInfo infoArr[], unsigned infoCount)
|
||||
{
|
||||
pfPatcherWorker* patcher = static_cast<pfPatcherWorker*>(param);
|
||||
|
||||
if (IS_NET_SUCCESS(result)) {
|
||||
// so everything goes directly into the Requests deque because AuthSrv lists
|
||||
// don't have any hashes attached. WHY did eap think this was a good idea?!?!
|
||||
{
|
||||
hsTempMutexLock lock(patcher->fRequestMut);
|
||||
for (unsigned i = 0; i < infoCount; ++i) {
|
||||
PatcherLogYellow("\tEnqueuing Legacy File '%S'", infoArr[i].filename);
|
||||
|
||||
plFileName fn = plString::FromWchar(infoArr[i].filename);
|
||||
plFileSystem::CreateDir(fn.StripFileName());
|
||||
|
||||
// We purposefully do NOT Open this stream! This uses a special auth-file constructor that
|
||||
// utilizes a backing hsRAMStream. This will be fed to plStreamSource later...
|
||||
pfPatcherStream* s = new pfPatcherStream(patcher, fn, infoArr[i].filesize);
|
||||
pfPatcherWorker::Request req = pfPatcherWorker::Request(fn.AsString(), pfPatcherWorker::Request::kAuthFile, s);
|
||||
patcher->fRequests.push_back(req);
|
||||
}
|
||||
}
|
||||
patcher->IssueRequest();
|
||||
} else {
|
||||
PatcherLogRed("\tSHIT! Some legacy manifest phailed");
|
||||
patcher->EndPatch(result, "SecurePreloader failed");
|
||||
}
|
||||
}
|
||||
|
||||
static void IHandleManifestDownload(pfPatcherWorker* patcher, const wchar_t group[], const NetCliFileManifestEntry manifest[], unsigned entryCount)
|
||||
{
|
||||
PatcherLogGreen("\tDownloaded Manifest '%S'", group);
|
||||
{
|
||||
hsTempMutexLock lock(patcher->fFileMut);
|
||||
for (unsigned i = 0; i < entryCount; ++i)
|
||||
patcher->fQueuedFiles.push_back(manifest[i]);
|
||||
patcher->fFileSignal.Signal();
|
||||
}
|
||||
patcher->IssueRequest();
|
||||
}
|
||||
|
||||
static void IPreloaderManifestDownloadCB(ENetError result, void* param, const wchar_t group[], const NetCliFileManifestEntry manifest[], unsigned entryCount)
|
||||
{
|
||||
pfPatcherWorker* patcher = static_cast<pfPatcherWorker*>(param);
|
||||
|
||||
if (IS_NET_SUCCESS(result))
|
||||
IHandleManifestDownload(patcher, group, manifest, entryCount);
|
||||
else {
|
||||
PatcherLogYellow("\tWARNING: *** Falling back to AuthSrv file lists to get game code ***");
|
||||
|
||||
// so, we need to ask the AuthSrv about our game code
|
||||
{
|
||||
hsTempMutexLock lock(patcher->fRequestMut);
|
||||
patcher->fRequests.push_back(pfPatcherWorker::Request(plString::Null, pfPatcherWorker::Request::kPythonList));
|
||||
patcher->fRequests.push_back(pfPatcherWorker::Request(plString::Null, pfPatcherWorker::Request::kSdlList));
|
||||
}
|
||||
|
||||
// continue pumping requests
|
||||
patcher->IssueRequest();
|
||||
}
|
||||
}
|
||||
|
||||
static void IFileManifestDownloadCB(ENetError result, void* param, const wchar_t group[], const NetCliFileManifestEntry manifest[], unsigned entryCount)
|
||||
{
|
||||
pfPatcherWorker* patcher = static_cast<pfPatcherWorker*>(param);
|
||||
|
||||
if (IS_NET_SUCCESS(result))
|
||||
IHandleManifestDownload(patcher, group, manifest, entryCount);
|
||||
else {
|
||||
PatcherLogRed("\tDownload Failed: Manifest '%S'", group);
|
||||
patcher->EndPatch(result, plString::FromWchar(group));
|
||||
}
|
||||
}
|
||||
|
||||
static void IFileThingDownloadCB(ENetError result, void* param, const plFileName& filename, hsStream* writer)
|
||||
{
|
||||
pfPatcherWorker* patcher = static_cast<pfPatcherWorker*>(param);
|
||||
pfPatcherStream* stream = static_cast<pfPatcherStream*>(writer);
|
||||
stream->Close();
|
||||
|
||||
if (IS_NET_SUCCESS(result)) {
|
||||
PatcherLogGreen("\tDownloaded File '%s'", stream->GetFileName().AsString().c_str());
|
||||
patcher->WhitelistFile(stream->GetFileName(), true);
|
||||
patcher->IssueRequest();
|
||||
} else {
|
||||
PatcherLogRed("\tDownloaded Failed: File '%s'", stream->GetFileName().AsString().c_str());
|
||||
stream->Unlink();
|
||||
patcher->EndPatch(result, filename.AsString());
|
||||
}
|
||||
|
||||
delete stream;
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
pfPatcherWorker::pfPatcherWorker() :
|
||||
fStarted(false), fCurrBytes(0), fTotalBytes(0), fRequestActive(true)
|
||||
{ }
|
||||
|
||||
pfPatcherWorker::~pfPatcherWorker()
|
||||
{
|
||||
{
|
||||
hsTempMutexLock lock(fRequestMut);
|
||||
std::for_each(fRequests.begin(), fRequests.end(),
|
||||
[] (const Request& req) {
|
||||
if (req.fStream) req.fStream->Close();
|
||||
delete req.fStream;
|
||||
}
|
||||
);
|
||||
fRequests.clear();
|
||||
}
|
||||
|
||||
{
|
||||
hsTempMutexLock lock(fFileMut);
|
||||
fQueuedFiles.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void pfPatcherWorker::OnQuit()
|
||||
{
|
||||
// the thread's Run() has exited sanely... now we can commit hara-kiri
|
||||
delete fParent;
|
||||
}
|
||||
|
||||
void pfPatcherWorker::EndPatch(ENetError result, const plString& msg)
|
||||
{
|
||||
// Guard against multiple calls
|
||||
if (fStarted) {
|
||||
// Send end status
|
||||
if (fOnComplete)
|
||||
fOnComplete(result, msg);
|
||||
|
||||
// yay log hax
|
||||
if (IS_NET_SUCCESS(result))
|
||||
PatcherLogWhite("--- Patch Complete ---");
|
||||
else {
|
||||
PatcherLogRed("\tNetwork Error: %S", NetErrorToString(result));
|
||||
PatcherLogWhite("--- Patch Killed by Error ---");
|
||||
}
|
||||
}
|
||||
|
||||
fStarted = false;
|
||||
fFileSignal.Signal();
|
||||
}
|
||||
|
||||
bool pfPatcherWorker::IssueRequest()
|
||||
{
|
||||
hsTempMutexLock lock(fRequestMut);
|
||||
if (fRequests.empty()) {
|
||||
fRequestActive = false;
|
||||
fFileSignal.Signal(); // make sure the patch thread doesn't deadlock!
|
||||
return false;
|
||||
} else
|
||||
fRequestActive = true;
|
||||
|
||||
const Request& req = fRequests.front();
|
||||
switch (req.fType) {
|
||||
case Request::kFile:
|
||||
req.fStream->Begin();
|
||||
if (fFileBeginDownload)
|
||||
fFileBeginDownload(req.fStream->GetFileName());
|
||||
|
||||
NetCliFileDownloadRequest(req.fName, req.fStream, IFileThingDownloadCB, this);
|
||||
break;
|
||||
case Request::kManifest:
|
||||
NetCliFileManifestRequest(IFileManifestDownloadCB, this, req.fName.ToWchar());
|
||||
break;
|
||||
case Request::kSecurePreloader:
|
||||
// so, yeah, this is usually the "SecurePreloader" manifest on the file server...
|
||||
// except on legacy servers, this may not exist, so we need to fall back without nuking everything!
|
||||
NetCliFileManifestRequest(IPreloaderManifestDownloadCB, this, req.fName.ToWchar());
|
||||
break;
|
||||
case Request::kAuthFile:
|
||||
// ffffffuuuuuu
|
||||
req.fStream->Begin();
|
||||
if (fFileBeginDownload)
|
||||
fFileBeginDownload(req.fStream->GetFileName());
|
||||
|
||||
NetCliAuthFileRequest(req.fName, req.fStream, IAuthThingDownloadCB, this);
|
||||
break;
|
||||
case Request::kPythonList:
|
||||
NetCliAuthFileListRequest(L"Python", L"pak", IGotAuthFileList, this);
|
||||
break;
|
||||
case Request::kSdlList:
|
||||
NetCliAuthFileListRequest(L"SDL", L"sdl", IGotAuthFileList, this);
|
||||
break;
|
||||
DEFAULT_FATAL(req.fType);
|
||||
}
|
||||
|
||||
fRequests.pop_front();
|
||||
return true;
|
||||
}
|
||||
|
||||
hsError pfPatcherWorker::Run()
|
||||
{
|
||||
// So here's the rub:
|
||||
// We have one or many manifests in the fRequests deque. We begin issuing those requests one-by one, starting here.
|
||||
// As we receive the answer, the NetCli thread populates fQueuedFiles and pings the fFileSignal semaphore, then issues the next request...
|
||||
// In this non-UI/non-Net thread, we do the stutter-prone/time-consuming IO/hashing operations. (Typically, the UI thread == Net thread)
|
||||
// As we find files that need updating, we add them to fRequests.
|
||||
// If there is no net request from ME when we find a file, we issue the request
|
||||
// Once a file is downloaded, the next request is issued.
|
||||
// When there are no files in my deque and no requests in my deque, we exit without errors.
|
||||
|
||||
PatcherLogWhite("--- Patch Started (%i requests) ---", fRequests.size());
|
||||
fStarted = true;
|
||||
IssueRequest();
|
||||
|
||||
// Now, work until we're done processing files
|
||||
do {
|
||||
fFileSignal.Wait();
|
||||
|
||||
hsTempMutexLock fileLock(fFileMut);
|
||||
if (!fQueuedFiles.empty()) {
|
||||
ProcessFile();
|
||||
continue;
|
||||
}
|
||||
|
||||
// This makes sure both queues are empty before exiting.
|
||||
if (!fRequestActive)
|
||||
if(!IssueRequest())
|
||||
break;
|
||||
} while (fStarted);
|
||||
|
||||
EndPatch(kNetSuccess);
|
||||
return hsOK;
|
||||
}
|
||||
|
||||
void pfPatcherWorker::ProcessFile()
|
||||
{
|
||||
do {
|
||||
const NetCliFileManifestEntry& entry = fQueuedFiles.front();
|
||||
|
||||
// eap sucks
|
||||
plFileName clName = plString::FromWchar(entry.clientName);
|
||||
plString dlName = plString::FromWchar(entry.downloadName);
|
||||
|
||||
// Check to see if ours matches
|
||||
plFileInfo mine(clName);
|
||||
if (mine.FileSize() == entry.fileSize) {
|
||||
plMD5Checksum cliMD5(clName);
|
||||
plMD5Checksum srvMD5;
|
||||
srvMD5.SetFromHexString(plString::FromWchar(entry.md5, 32).c_str());
|
||||
|
||||
if (cliMD5 == srvMD5) {
|
||||
WhitelistFile(clName, false);
|
||||
fQueuedFiles.pop_front();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If you got here, they're different.
|
||||
PatcherLogYellow("\tEnqueuing '%S'", entry.clientName);
|
||||
plFileSystem::CreateDir(plFileName(clName).StripFileName());
|
||||
|
||||
pfPatcherStream* s = new pfPatcherStream(this, dlName, entry);
|
||||
s->Open(clName, "wb");
|
||||
|
||||
hsTempMutexLock lock(fRequestMut);
|
||||
fRequests.push_back(Request(dlName, Request::kFile, s));
|
||||
fQueuedFiles.pop_front();
|
||||
|
||||
if (!fRequestActive)
|
||||
IssueRequest();
|
||||
} while (!fQueuedFiles.empty());
|
||||
}
|
||||
|
||||
void pfPatcherWorker::WhitelistFile(const plFileName& file, bool justDownloaded, hsStream* stream)
|
||||
{
|
||||
// if this is a newly downloaded file, fire off a completion callback
|
||||
if (justDownloaded && fFileDownloaded)
|
||||
fFileDownloaded(file);
|
||||
|
||||
// we want to whitelist our game code, so here we go...
|
||||
if (fGameCodeDiscovered) {
|
||||
plString ext = file.GetFileExt();
|
||||
if (ext.CompareI("pak") == 0 || ext.CompareI("sdl") == 0) {
|
||||
if (!stream) {
|
||||
stream = new hsUNIXStream;
|
||||
stream->Open(file, "rb");
|
||||
}
|
||||
|
||||
// if something terrible goes wrong (eg bad encryption), we can exit sanely
|
||||
// callback eats stream
|
||||
if (!fGameCodeDiscovered(file, stream))
|
||||
EndPatch(kNetErrInternalError, "SecurePreloader failed.");
|
||||
}
|
||||
} else if (stream) {
|
||||
// no dad gum memory leaks, m'kay?
|
||||
stream->Close();
|
||||
delete stream;
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
plStatusLog* pfPatcher::GetLog()
|
||||
{
|
||||
static plStatusLog* log = nullptr;
|
||||
if (!log)
|
||||
{
|
||||
log = plStatusLogMgr::GetInstance().CreateStatusLog(
|
||||
20,
|
||||
"patcher.log",
|
||||
plStatusLog::kFilledBackground | plStatusLog::kAlignToTop | plStatusLog::kDeleteForMe);
|
||||
}
|
||||
return log;
|
||||
}
|
||||
|
||||
pfPatcher::pfPatcher() : fWorker(new pfPatcherWorker) { }
|
||||
pfPatcher::~pfPatcher() { }
|
||||
|
||||
// ===================================================
|
||||
|
||||
void pfPatcher::OnCompletion(CompletionFunc cb)
|
||||
{
|
||||
fWorker->fOnComplete = cb;
|
||||
}
|
||||
|
||||
void pfPatcher::OnFileDownloadBegin(FileDownloadFunc cb)
|
||||
{
|
||||
fWorker->fFileBeginDownload = cb;
|
||||
}
|
||||
|
||||
void pfPatcher::OnFileDownloaded(FileDownloadFunc cb)
|
||||
{
|
||||
fWorker->fFileDownloaded = cb;
|
||||
}
|
||||
|
||||
void pfPatcher::OnGameCodeDiscovery(GameCodeDiscoverFunc cb)
|
||||
{
|
||||
fWorker->fGameCodeDiscovered = cb;
|
||||
}
|
||||
|
||||
void pfPatcher::OnProgressTick(ProgressTickFunc cb)
|
||||
{
|
||||
fWorker->fProgressTick = cb;
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
void pfPatcher::RequestGameCode()
|
||||
{
|
||||
hsTempMutexLock lock(fWorker->fRequestMut);
|
||||
fWorker->fRequests.push_back(pfPatcherWorker::Request("SecurePreloader", pfPatcherWorker::Request::kSecurePreloader));
|
||||
}
|
||||
|
||||
void pfPatcher::RequestManifest(const plString& mfs)
|
||||
{
|
||||
hsTempMutexLock lock(fWorker->fRequestMut);
|
||||
fWorker->fRequests.push_back(pfPatcherWorker::Request(mfs, pfPatcherWorker::Request::kManifest));
|
||||
}
|
||||
|
||||
void pfPatcher::RequestManifest(const std::vector<plString>& mfs)
|
||||
{
|
||||
hsTempMutexLock lock(fWorker->fRequestMut);
|
||||
std::for_each(mfs.begin(), mfs.end(),
|
||||
[&] (const plString& name) {
|
||||
fWorker->fRequests.push_back(pfPatcherWorker::Request(name, pfPatcherWorker::Request::kManifest));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
bool pfPatcher::Start()
|
||||
{
|
||||
hsAssert(!fWorker->fStarted, "pfPatcher is one-use only. kthx.");
|
||||
if (!fWorker->fStarted) {
|
||||
fWorker->fParent = this; // wheeeee circular
|
||||
fWorker->Start();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
125
Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h
Normal file
125
Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h
Normal file
@ -0,0 +1,125 @@
|
||||
/*==LICENSE==*
|
||||
|
||||
CyanWorlds.com Engine - MMOG client, server and tools
|
||||
Copyright (C) 2011 Cyan Worlds, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Additional permissions under GNU GPL version 3 section 7
|
||||
|
||||
If you modify this Program, or any covered work, by linking or
|
||||
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
||||
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
||||
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
||||
(or a modified version of those libraries),
|
||||
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
||||
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
||||
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
||||
licensors of this Program grant you additional
|
||||
permission to convey the resulting work. Corresponding Source for a
|
||||
non-source form of such a combination shall include the source code for
|
||||
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
||||
work.
|
||||
|
||||
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||||
or by snail mail at:
|
||||
Cyan Worlds, Inc.
|
||||
14617 N Newport Hwy
|
||||
Mead, WA 99021
|
||||
|
||||
*==LICENSE==*/
|
||||
|
||||
#ifndef _pfPatcher_inc_
|
||||
#define _pfPatcher_inc_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "plString.h"
|
||||
#include "pnNetBase/pnNbError.h"
|
||||
|
||||
class plFileName;
|
||||
class plStatusLog;
|
||||
class hsStream;
|
||||
|
||||
/** Plasma File Patcher
|
||||
* This is used to patch the client with one or many manifests at once. It assumes that
|
||||
* we have permission to modify the game files, so be sure that you do! We memory manage
|
||||
* ourselves, so allocate a new pfPatcher, add your manifests, and Start!
|
||||
*/
|
||||
class pfPatcher
|
||||
{
|
||||
std::unique_ptr<struct pfPatcherWorker> fWorker;
|
||||
|
||||
public:
|
||||
static plStatusLog* GetLog();
|
||||
|
||||
public:
|
||||
/** Represents a function that takes the status and an optional message on completion. */
|
||||
typedef std::function<void(ENetError, const plString&)> CompletionFunc;
|
||||
|
||||
/** Represents a function that takes (const plFileName&) on an interesting file operation. */
|
||||
typedef std::function<void(const plFileName&)> FileDownloadFunc;
|
||||
|
||||
/** Represents a function that takes (const plFileName&, hsStream*) on game code discovery.
|
||||
* You are responsible for closing and deleting the provided stream.
|
||||
*/
|
||||
typedef std::function<bool(const plFileName&, hsStream*)> GameCodeDiscoverFunc;
|
||||
|
||||
/** Represents a function that takes (bytesDLed, totalBytes, statsStr) as a progress indicator. */
|
||||
typedef std::function<void(uint64_t, uint64_t, const plString&)> ProgressTickFunc;
|
||||
|
||||
pfPatcher();
|
||||
~pfPatcher();
|
||||
|
||||
/** Set a callback that will be fired when the patcher finishes its dirty work.
|
||||
* \remarks This may be called from any thread, so make sure your callback is
|
||||
* thread safe!
|
||||
*/
|
||||
void OnCompletion(CompletionFunc cb);
|
||||
|
||||
/** Set a callback that will be fired when the patcher issues a download request to the server.
|
||||
* \remarks This will be called from the network thread.
|
||||
*/
|
||||
void OnFileDownloadBegin(FileDownloadFunc 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.
|
||||
*/
|
||||
void OnFileDownloaded(FileDownloadFunc cb);
|
||||
|
||||
/** This is called when the patcher discovers an up-to-date Python package or SDL file.
|
||||
* \remarks This can be called from any thread when the patcher downloads or encounters an up-to-date
|
||||
* python package or SDL file that the server knows about.
|
||||
*/
|
||||
void OnGameCodeDiscovery(GameCodeDiscoverFunc cb);
|
||||
|
||||
/** Set a callback that will be fired when the patcher receives a chunk from the server. The status string
|
||||
* will contain the current download speed.
|
||||
* \remarks This will be called from the network thread.
|
||||
*/
|
||||
void OnProgressTick(ProgressTickFunc cb);
|
||||
|
||||
void RequestGameCode();
|
||||
void RequestManifest(const plString& mfs);
|
||||
void RequestManifest(const std::vector<plString>& mfs);
|
||||
|
||||
/** Start patching the requested manifests. Please note that after calling this, you should
|
||||
* discard your pointer to this object as it will memory-manage itself.
|
||||
*/
|
||||
bool Start();
|
||||
};
|
||||
|
||||
#endif // _pfPatcher_inc_
|
91
Sources/Plasma/FeatureLib/pfPatcher/plManifests.cpp
Normal file
91
Sources/Plasma/FeatureLib/pfPatcher/plManifests.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
/*==LICENSE==*
|
||||
|
||||
CyanWorlds.com Engine - MMOG client, server and tools
|
||||
Copyright (C) 2011 Cyan Worlds, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Additional permissions under GNU GPL version 3 section 7
|
||||
|
||||
If you modify this Program, or any covered work, by linking or
|
||||
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
||||
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
||||
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
||||
(or a modified version of those libraries),
|
||||
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
||||
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
||||
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
||||
licensors of this Program grant you additional
|
||||
permission to convey the resulting work. Corresponding Source for a
|
||||
non-source form of such a combination shall include the source code for
|
||||
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
||||
work.
|
||||
|
||||
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||||
or by snail mail at:
|
||||
Cyan Worlds, Inc.
|
||||
14617 N Newport Hwy
|
||||
Mead, WA 99021
|
||||
|
||||
*==LICENSE==*/
|
||||
|
||||
#include "plManifests.h"
|
||||
#include "plFileSystem.h"
|
||||
|
||||
// Helper that returns the appropriate string per build
|
||||
#ifdef PLASMA_EXTERNAL_RELEASE
|
||||
# define MANIFEST(in, ex) ex
|
||||
#else
|
||||
# define MANIFEST(in, ex) in
|
||||
#endif // PLASMA_EXTERNAL_RELEASE
|
||||
|
||||
plFileName plManifest::ClientExecutable()
|
||||
{
|
||||
return MANIFEST("plClient.exe", "UruExplorer.exe");
|
||||
}
|
||||
|
||||
plFileName plManifest::PatcherExecutable()
|
||||
{
|
||||
return MANIFEST("plUruLauncher.exe", "UruLauncher.exe");
|
||||
}
|
||||
|
||||
plString plManifest::ClientManifest()
|
||||
{
|
||||
return MANIFEST("ThinInternal", "ThinExternal");
|
||||
}
|
||||
|
||||
plString plManifest::ClientImageManifest()
|
||||
{
|
||||
return MANIFEST("Internal", "External");
|
||||
}
|
||||
|
||||
plString plManifest::PatcherManifest()
|
||||
{
|
||||
return MANIFEST("InternalPatcher", "ExternalPatcher");
|
||||
}
|
||||
|
||||
std::vector<plString> plManifest::EssentialGameManifests()
|
||||
{
|
||||
std::vector<plString> mfs;
|
||||
mfs.push_back("CustomAvatars");
|
||||
mfs.push_back("GlobalAnimations");
|
||||
mfs.push_back("GlobalAvatars");
|
||||
mfs.push_back("GlobalClothing");
|
||||
mfs.push_back("GlobalMarkers");
|
||||
mfs.push_back("GUI");
|
||||
mfs.push_back("StartUp");
|
||||
|
||||
return mfs;
|
||||
}
|
||||
|
@ -39,19 +39,34 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||||
Mead, WA 99021
|
||||
|
||||
*==LICENSE==*/
|
||||
/*****************************************************************************
|
||||
*
|
||||
* $/Plasma20/Sources/Plasma/FeatureLib/pfSecurePreloader/pfSecurePreloaderCreatable.h
|
||||
*
|
||||
***/
|
||||
|
||||
#ifndef PLASMA20_SOURCES_PLASMA_FEATURELIB_PFSECUREPRELOADER_PFSECUREPRELOADERCREATABLE_H
|
||||
#define PLASMA20_SOURCES_PLASMA_FEATURELIB_PFSECUREPRELOADER_PFSECUREPRELOADERCREATABLE_H
|
||||
#ifndef _plManifests_inc_
|
||||
#define _plManifests_inc_
|
||||
|
||||
#include "pnFactory/plCreator.h"
|
||||
#include <vector>
|
||||
|
||||
#include "pfSecurePreloader.h"
|
||||
REGISTER_NONCREATABLE(pfSecurePreloader);
|
||||
class plFileName;
|
||||
class plString;
|
||||
|
||||
namespace plManifest
|
||||
{
|
||||
/** Get the name of the client executable for this build type.*/
|
||||
plFileName ClientExecutable();
|
||||
|
||||
#endif // PLASMA20_SOURCES_PLASMA_FEATURELIB_PFSECUREPRELOADER_PFSECUREPRELOADERCREATABLE_H
|
||||
/** Get the name of the patcher executable for this build type.*/
|
||||
plFileName PatcherExecutable();
|
||||
|
||||
/** Get the name of the baseline client manifest for this build type. */
|
||||
plString ClientManifest();
|
||||
|
||||
/** Get the name of the full game manifest for this build type. */
|
||||
plString ClientImageManifest();
|
||||
|
||||
/** Get the name of the patcher manifest for this build type. */
|
||||
plString PatcherManifest();
|
||||
|
||||
/** Get a vector containing all manifests the game requires to initialize. */
|
||||
std::vector<plString> EssentialGameManifests();
|
||||
}
|
||||
|
||||
#endif // _plManifests_inc_
|
@ -1,22 +0,0 @@
|
||||
include_directories(../../CoreLib)
|
||||
include_directories(../../NucleusLib)
|
||||
include_directories(../../NucleusLib/inc)
|
||||
include_directories(../../PubUtilLib)
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-DWIN32)
|
||||
endif(WIN32)
|
||||
|
||||
set(pfSecurePreloader_SOURCES
|
||||
pfSecurePreloader.cpp
|
||||
)
|
||||
|
||||
set(pfSecurePreloader_HEADERS
|
||||
pfSecurePreloader.h
|
||||
pfSecurePreloaderCreatable.h
|
||||
)
|
||||
|
||||
add_library(pfSecurePreloader STATIC ${pfSecurePreloader_SOURCES} ${pfSecurePreloader_HEADERS})
|
||||
|
||||
source_group("Source Files" FILES ${pfSecurePreloader_SOURCES})
|
||||
source_group("Header Files" FILES ${pfSecurePreloader_HEADERS})
|
@ -1,414 +0,0 @@
|
||||
/*==LICENSE==*
|
||||
|
||||
CyanWorlds.com Engine - MMOG client, server and tools
|
||||
Copyright (C) 2011 Cyan Worlds, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Additional permissions under GNU GPL version 3 section 7
|
||||
|
||||
If you modify this Program, or any covered work, by linking or
|
||||
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
||||
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
||||
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
||||
(or a modified version of those libraries),
|
||||
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
||||
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
||||
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
||||
licensors of this Program grant you additional
|
||||
permission to convey the resulting work. Corresponding Source for a
|
||||
non-source form of such a combination shall include the source code for
|
||||
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
||||
work.
|
||||
|
||||
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||||
or by snail mail at:
|
||||
Cyan Worlds, Inc.
|
||||
14617 N Newport Hwy
|
||||
Mead, WA 99021
|
||||
|
||||
*==LICENSE==*/
|
||||
|
||||
#include "pfSecurePreloader.h"
|
||||
|
||||
#include "hsStream.h"
|
||||
#include "plgDispatch.h"
|
||||
#include "plCompression/plZlibStream.h"
|
||||
#include "pnEncryption/plChecksum.h"
|
||||
#include "plFile/plSecureStream.h"
|
||||
#include "plFile/plStreamSource.h"
|
||||
#include "plMessage/plNetCommMsgs.h"
|
||||
#include "plMessage/plPreloaderMsg.h"
|
||||
#include "plProgressMgr/plProgressMgr.h"
|
||||
|
||||
bool gSkipPreload = false;
|
||||
pfSecurePreloader* pfSecurePreloader::fInstance = nil;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef std::pair<const wchar_t*, const wchar_t*> WcharPair;
|
||||
|
||||
struct AuthRequestParams
|
||||
{
|
||||
pfSecurePreloader* fThis;
|
||||
std::queue<WcharPair> fFileGroups;
|
||||
|
||||
AuthRequestParams(pfSecurePreloader* parent)
|
||||
: fThis(parent) { }
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ProcAuthDownloadParams(AuthRequestParams* params);
|
||||
|
||||
void GotAuthSrvManifest(
|
||||
ENetError result,
|
||||
void* param,
|
||||
const NetCliAuthFileInfo infoArr[],
|
||||
uint32_t infoCount
|
||||
) {
|
||||
AuthRequestParams* arp = (AuthRequestParams*)param;
|
||||
if (IS_NET_ERROR(result))
|
||||
{
|
||||
FATAL("Failed to get AuthSrv manifest!");
|
||||
arp->fThis->Terminate();
|
||||
delete arp;
|
||||
} else {
|
||||
arp->fThis->PreloadManifest(infoArr, infoCount);
|
||||
ProcAuthDownloadParams(arp);
|
||||
}
|
||||
}
|
||||
|
||||
void GotFileSrvManifest(
|
||||
ENetError result,
|
||||
void* param,
|
||||
const wchar_t group[],
|
||||
const NetCliFileManifestEntry manifest[],
|
||||
uint32_t entryCount
|
||||
) {
|
||||
pfSecurePreloader* sp = (pfSecurePreloader*)param;
|
||||
if (result == kNetErrFileNotFound)
|
||||
{
|
||||
AuthRequestParams* params = new AuthRequestParams(sp);
|
||||
params->fFileGroups.push(WcharPair(L"Python", L"pak"));
|
||||
params->fFileGroups.push(WcharPair(L"SDL", L"sdl"));
|
||||
ProcAuthDownloadParams(params);
|
||||
return;
|
||||
} else if (!entryCount) {
|
||||
FATAL("SecurePreloader manifest empty!");
|
||||
sp->Terminate();
|
||||
return;
|
||||
}
|
||||
|
||||
sp->PreloadManifest(manifest, entryCount);
|
||||
}
|
||||
|
||||
void FileDownloaded(
|
||||
ENetError result,
|
||||
void* param,
|
||||
const plFileName & filename,
|
||||
hsStream* writer
|
||||
) {
|
||||
pfSecurePreloader* sp = (pfSecurePreloader*)param;
|
||||
if (IS_NET_ERROR(result))
|
||||
{
|
||||
FATAL("SecurePreloader download failed");
|
||||
sp->Terminate();
|
||||
} else {
|
||||
sp->FilePreloaded(filename, writer);
|
||||
}
|
||||
}
|
||||
|
||||
void ProcAuthDownloadParams(AuthRequestParams* params)
|
||||
{
|
||||
// Request the "manifests" until there are none left, then download the files
|
||||
if (params->fFileGroups.empty())
|
||||
{
|
||||
params->fThis->PreloadNextFile();
|
||||
delete params;
|
||||
} else {
|
||||
WcharPair wp = params->fFileGroups.front();
|
||||
params->fFileGroups.pop();
|
||||
NetCliAuthFileListRequest(wp.first, wp.second, GotAuthSrvManifest, params);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
class pfSecurePreloaderStream : public plZlibStream
|
||||
{
|
||||
plOperationProgress* fProgress;
|
||||
bool fIsZipped;
|
||||
|
||||
public:
|
||||
|
||||
pfSecurePreloaderStream(plOperationProgress* prog, bool zipped)
|
||||
: fProgress(prog), fIsZipped(zipped), plZlibStream()
|
||||
{
|
||||
fOutput = new hsRAMStream;
|
||||
}
|
||||
|
||||
~pfSecurePreloaderStream()
|
||||
{
|
||||
delete fOutput;
|
||||
fOutput = nil;
|
||||
plZlibStream::Close();
|
||||
}
|
||||
|
||||
bool AtEnd() { return fOutput->AtEnd(); }
|
||||
uint32_t GetEOF() { return fOutput->GetEOF(); }
|
||||
uint32_t GetPosition() const { return fOutput->GetPosition(); }
|
||||
uint32_t GetSizeLeft() const { return fOutput->GetSizeLeft(); }
|
||||
uint32_t Read(uint32_t count, void* buf) { return fOutput->Read(count, buf); }
|
||||
void Rewind() { fOutput->Rewind(); }
|
||||
void SetPosition(uint32_t pos) { fOutput->SetPosition(pos); }
|
||||
void Skip(uint32_t deltaByteCount) { fOutput->Skip(deltaByteCount); }
|
||||
|
||||
uint32_t Write(uint32_t count, const void* buf)
|
||||
{
|
||||
if (fProgress)
|
||||
fProgress->Increment((float)count);
|
||||
if (fIsZipped)
|
||||
return plZlibStream::Write(count, buf);
|
||||
else
|
||||
return fOutput->Write(count, buf);
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
hsRAMStream* pfSecurePreloader::LoadToMemory(const plFileName& file) const
|
||||
{
|
||||
if (!plFileInfo(file).Exists())
|
||||
return nil;
|
||||
|
||||
hsUNIXStream s;
|
||||
hsRAMStream* ram = new hsRAMStream;
|
||||
s.Open(file);
|
||||
|
||||
uint32_t loadLen = 1024 * 1024;
|
||||
uint8_t* buf = new uint8_t[loadLen];
|
||||
while (uint32_t read = s.Read(loadLen, buf))
|
||||
ram->Write(read, buf);
|
||||
delete[] buf;
|
||||
|
||||
s.Close();
|
||||
ram->Rewind();
|
||||
return ram;
|
||||
}
|
||||
|
||||
void pfSecurePreloader::SaveFile(hsStream* file, const plFileName& name) const
|
||||
{
|
||||
hsUNIXStream s;
|
||||
s.Open(name, "wb");
|
||||
uint32_t pos = file->GetPosition();
|
||||
file->Rewind();
|
||||
|
||||
uint32_t loadLen = 1024 * 1024;
|
||||
uint8_t* buf = new uint8_t[loadLen];
|
||||
while (uint32_t read = file->Read(loadLen, buf))
|
||||
s.Write(read, buf);
|
||||
file->SetPosition(pos);
|
||||
s.Close();
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
bool pfSecurePreloader::IsZipped(const plFileName& filename) const
|
||||
{
|
||||
return filename.GetFileExt().CompareI("gz") == 0;
|
||||
}
|
||||
|
||||
void pfSecurePreloader::PreloadNextFile()
|
||||
{
|
||||
if (fManifestEntries.empty())
|
||||
{
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
plFileName filename = fDownloadEntries.front();
|
||||
hsStream* s = new pfSecurePreloaderStream(fProgress, IsZipped(filename));
|
||||
|
||||
// Thankfully, both callbacks have the same arguments
|
||||
if (fLegacyMode)
|
||||
NetCliAuthFileRequest(filename, s, FileDownloaded, this);
|
||||
else
|
||||
NetCliFileDownloadRequest(filename, s, FileDownloaded, this);
|
||||
}
|
||||
|
||||
void pfSecurePreloader::Init()
|
||||
{
|
||||
if (!fInstance)
|
||||
fInstance = new pfSecurePreloader;
|
||||
|
||||
fInstance->RegisterAs(kSecurePreloader_KEY);
|
||||
// TODO: If we're going to support reconnects, then let's do it right.
|
||||
// Later...
|
||||
//plgDispatch::Dispatch()->RegisterForExactType(plNetCommAuthConnectedMsg::Index(), fInstance->GetKey());
|
||||
}
|
||||
|
||||
void pfSecurePreloader::Start()
|
||||
{
|
||||
#ifndef PLASMA_EXTERNAL_RELEASE
|
||||
// Finer grained control of the preloader allows us to have synched data but our own python/SDL
|
||||
// This is useful on outdated/black-box shards like MOULa
|
||||
if (gSkipPreload)
|
||||
{
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
NetCliAuthGetEncryptionKey(fEncryptionKey, 4);
|
||||
|
||||
// TODO: Localize
|
||||
fProgress = plProgressMgr::GetInstance()->RegisterOperation(0.0f, "Checking for updates", plProgressMgr::kUpdateText, false, true);
|
||||
|
||||
// Now, we need to fetch the "SecurePreloader" manifest from the file server, which will contain the python and SDL files.
|
||||
// We're basically reimplementing plResPatcher here, except preferring to keep everything in memory, then flush to disk
|
||||
// when we're done. If this fails, then we shall download everything from the AuthSrv like in the old days.
|
||||
NetCliFileManifestRequest(GotFileSrvManifest, this, L"SecurePreloader");
|
||||
}
|
||||
|
||||
void pfSecurePreloader::Terminate()
|
||||
{
|
||||
FATAL("pfSecurePreloader failure");
|
||||
fProgress->SetAborting();
|
||||
|
||||
plPreloaderMsg* msg = new plPreloaderMsg;
|
||||
msg->fSuccess = false;
|
||||
plgDispatch::Dispatch()->MsgSend(msg);
|
||||
}
|
||||
|
||||
void pfSecurePreloader::Finish()
|
||||
{
|
||||
plPreloaderMsg* msg = new plPreloaderMsg;
|
||||
msg->fSuccess = true;
|
||||
plgDispatch::Dispatch()->MsgSend(msg);
|
||||
}
|
||||
|
||||
void pfSecurePreloader::Shutdown()
|
||||
{
|
||||
SetInstance(nil);
|
||||
if (fProgress)
|
||||
{
|
||||
delete fProgress;
|
||||
fProgress = nil;
|
||||
}
|
||||
|
||||
// Takes care of UnReffing us
|
||||
UnRegisterAs(kSecurePreloader_KEY);
|
||||
}
|
||||
|
||||
void pfSecurePreloader::PreloadManifest(const NetCliAuthFileInfo manifestEntries[], uint32_t entryCount)
|
||||
{
|
||||
uint32_t totalBytes = 0;
|
||||
if (fProgress)
|
||||
totalBytes = (uint32_t)fProgress->GetMax();
|
||||
fLegacyMode = true;
|
||||
|
||||
for (uint32_t i = 0; i < entryCount; ++i)
|
||||
{
|
||||
const NetCliAuthFileInfo mfs = manifestEntries[i];
|
||||
plFileName filename = plString::FromWchar(mfs.filename);
|
||||
fDownloadEntries.push(filename);
|
||||
if (IsZipped(filename))
|
||||
fManifestEntries.push(filename.StripFileExt());
|
||||
else
|
||||
fManifestEntries.push(filename);
|
||||
|
||||
totalBytes += mfs.filesize;
|
||||
}
|
||||
|
||||
if (fProgress)
|
||||
{
|
||||
fProgress->SetLength((float)totalBytes);
|
||||
fProgress->SetTitle("Downloading...");
|
||||
}
|
||||
}
|
||||
|
||||
void pfSecurePreloader::PreloadManifest(const NetCliFileManifestEntry manifestEntries[], uint32_t entryCount)
|
||||
{
|
||||
uint32_t totalBytes = 0;
|
||||
for (uint32_t i = 0; i < entryCount; ++i)
|
||||
{
|
||||
const NetCliFileManifestEntry mfs = manifestEntries[i];
|
||||
bool fetchMe = true;
|
||||
hsRAMStream* s = nil;
|
||||
plFileName clientName = plString::FromWchar(mfs.clientName);
|
||||
plFileName downloadName = plString::FromWchar(mfs.downloadName);
|
||||
|
||||
if (plFileInfo(clientName).Exists())
|
||||
{
|
||||
s = LoadToMemory(clientName);
|
||||
if (s)
|
||||
{
|
||||
// Damn this
|
||||
plMD5Checksum srvHash;
|
||||
srvHash.SetFromHexString(plString::FromWchar(mfs.md5, 32).c_str());
|
||||
|
||||
// Now actually copare the hashes
|
||||
plMD5Checksum lclHash;
|
||||
lclHash.CalcFromStream(s);
|
||||
fetchMe = (srvHash != lclHash);
|
||||
}
|
||||
}
|
||||
|
||||
if (fetchMe)
|
||||
{
|
||||
fManifestEntries.push(clientName);
|
||||
fDownloadEntries.push(downloadName);
|
||||
if (IsZipped(downloadName))
|
||||
totalBytes += mfs.zipSize;
|
||||
else
|
||||
totalBytes += mfs.fileSize;
|
||||
} else {
|
||||
plSecureStream* ss = new plSecureStream(s, fEncryptionKey);
|
||||
plStreamSource::GetInstance()->InsertFile(clientName, ss);
|
||||
}
|
||||
|
||||
if (s)
|
||||
delete s;
|
||||
}
|
||||
|
||||
if (totalBytes && fProgress)
|
||||
{
|
||||
fProgress->SetLength((float)totalBytes);
|
||||
fProgress->SetTitle("Downloading...");
|
||||
}
|
||||
|
||||
// This method uses only one manifest, so we're good to go now!
|
||||
PreloadNextFile();
|
||||
}
|
||||
|
||||
void pfSecurePreloader::FilePreloaded(const plFileName& file, hsStream* stream)
|
||||
{
|
||||
// Clear out queue
|
||||
fDownloadEntries.pop();
|
||||
plFileName clientName = fManifestEntries.front();
|
||||
fManifestEntries.pop();
|
||||
|
||||
if (!fLegacyMode) // AuthSrv data caching is useless
|
||||
{
|
||||
plFileSystem::CreateDir(clientName.StripFileName(), true);
|
||||
SaveFile(stream, clientName);
|
||||
}
|
||||
|
||||
plSecureStream* ss = new plSecureStream(stream, fEncryptionKey);
|
||||
plStreamSource::GetInstance()->InsertFile(clientName, ss);
|
||||
delete stream; // SecureStream holds its own decrypted buffer
|
||||
|
||||
// Continue down the warpath
|
||||
PreloadNextFile();
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
/*==LICENSE==*
|
||||
|
||||
CyanWorlds.com Engine - MMOG client, server and tools
|
||||
Copyright (C) 2011 Cyan Worlds, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Additional permissions under GNU GPL version 3 section 7
|
||||
|
||||
If you modify this Program, or any covered work, by linking or
|
||||
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
||||
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
||||
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
||||
(or a modified version of those libraries),
|
||||
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
||||
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
||||
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
||||
licensors of this Program grant you additional
|
||||
permission to convey the resulting work. Corresponding Source for a
|
||||
non-source form of such a combination shall include the source code for
|
||||
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
||||
work.
|
||||
|
||||
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||||
or by snail mail at:
|
||||
Cyan Worlds, Inc.
|
||||
14617 N Newport Hwy
|
||||
Mead, WA 99021
|
||||
|
||||
*==LICENSE==*/
|
||||
#ifndef __pfSecurePreloader_h__
|
||||
#define __pfSecurePreloader_h__
|
||||
|
||||
#include "HeadSpin.h"
|
||||
#include "pnKeyedObject/hsKeyedObject.h"
|
||||
#include "plNetGameLib/plNetGameLib.h"
|
||||
#include <queue>
|
||||
|
||||
class plOperationProgress;
|
||||
class hsRAMStream;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// pfSecurePreloader - a class for handling files we want downloaded from the
|
||||
// server into a temporary directory, secured, and deleted on exit. Puts stuff
|
||||
// into plStreamSource for us
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class pfSecurePreloader : public hsKeyedObject
|
||||
{
|
||||
private:
|
||||
|
||||
static pfSecurePreloader* fInstance;
|
||||
std::queue<plFileName> fManifestEntries;
|
||||
std::queue<plFileName> fDownloadEntries;
|
||||
plOperationProgress* fProgress;
|
||||
uint32_t fEncryptionKey[4];
|
||||
bool fLegacyMode;
|
||||
|
||||
hsRAMStream* LoadToMemory(const plFileName& file) const;
|
||||
void SaveFile(hsStream* file, const plFileName& name) const;
|
||||
bool IsZipped(const plFileName& filename) const;
|
||||
|
||||
public:
|
||||
pfSecurePreloader() : fProgress(nil), fLegacyMode(false) { }
|
||||
|
||||
CLASSNAME_REGISTER(pfSecurePreloader);
|
||||
GETINTERFACE_ANY(pfSecurePreloader, hsKeyedObject);
|
||||
|
||||
void Init();
|
||||
void Start();
|
||||
void Terminate();
|
||||
void Finish();
|
||||
void Shutdown();
|
||||
|
||||
void PreloadManifest(const NetCliFileManifestEntry manifestEntries[], uint32_t entryCount);
|
||||
void PreloadManifest(const NetCliAuthFileInfo manifestEntries[], uint32_t entryCount);
|
||||
void PreloadNextFile();
|
||||
void FilePreloaded(const plFileName& filename, hsStream* stream);
|
||||
|
||||
plOperationProgress* GetProgressBar() { return fProgress; }
|
||||
|
||||
static pfSecurePreloader* GetInstance() { return fInstance; }
|
||||
static void SetInstance(pfSecurePreloader* instance) { fInstance = instance; }
|
||||
};
|
||||
|
||||
#endif // __pfSecurePreloader_h__
|
Reference in New Issue
Block a user