Darryl Pogue
11 years ago
39 changed files with 1106 additions and 1062 deletions
@ -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}) |
@ -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; |
||||||
|
} |
@ -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_
|
@ -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__
|
|
Loading…
Reference in new issue