Browse Source

Merge pull request #352 from Hoikas/patcher-thread

Unified Patcher (Part 1: In-Game)
Darryl Pogue 11 years ago
parent
commit
9d0112de19
  1. 1
      Sources/Plasma/Apps/plClient/CMakeLists.txt
  2. 71
      Sources/Plasma/Apps/plClient/plClient.cpp
  3. 1
      Sources/Plasma/Apps/plClient/plClient.h
  4. 2
      Sources/Plasma/CoreLib/hsStream.h
  5. 1
      Sources/Plasma/CoreLib/hsThread.h
  6. 1
      Sources/Plasma/CoreLib/hsThread_Unix.cpp
  7. 1
      Sources/Plasma/CoreLib/hsThread_Win.cpp
  8. 24
      Sources/Plasma/CoreLib/plFileSystem.cpp
  9. 3
      Sources/Plasma/CoreLib/plFileSystem.h
  10. 2
      Sources/Plasma/FeatureLib/CMakeLists.txt
  11. 1
      Sources/Plasma/FeatureLib/inc/pfAllCreatables.h
  12. 19
      Sources/Plasma/FeatureLib/pfPatcher/CMakeLists.txt
  13. 598
      Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp
  14. 125
      Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h
  15. 64
      Sources/Plasma/FeatureLib/pfPatcher/plManifests.cpp
  16. 43
      Sources/Plasma/FeatureLib/pfPatcher/plManifests.h
  17. 22
      Sources/Plasma/FeatureLib/pfSecurePreloader/CMakeLists.txt
  18. 414
      Sources/Plasma/FeatureLib/pfSecurePreloader/pfSecurePreloader.cpp
  19. 96
      Sources/Plasma/FeatureLib/pfSecurePreloader/pfSecurePreloader.h
  20. 4
      Sources/Plasma/NucleusLib/inc/plCreatableIndex.h
  21. 2
      Sources/Plasma/NucleusLib/pnKeyedObject/plFixedKey.cpp
  22. 1
      Sources/Plasma/NucleusLib/pnKeyedObject/plFixedKey.h
  23. 2
      Sources/Plasma/PubUtilLib/plAgeLoader/CMakeLists.txt
  24. 21
      Sources/Plasma/PubUtilLib/plAgeLoader/plAgeLoader.cpp
  25. 333
      Sources/Plasma/PubUtilLib/plAgeLoader/plResPatcher.cpp
  26. 62
      Sources/Plasma/PubUtilLib/plAgeLoader/plResPatcher.h
  27. 8
      Sources/Plasma/PubUtilLib/plFile/plSecureStream.cpp
  28. 28
      Sources/Plasma/PubUtilLib/plFile/plStreamSource.cpp
  29. 9
      Sources/Plasma/PubUtilLib/plFile/plStreamSource.h
  30. 1
      Sources/Plasma/PubUtilLib/plMessage/CMakeLists.txt
  31. 3
      Sources/Plasma/PubUtilLib/plMessage/plMessageCreatable.h
  32. 15
      Sources/Plasma/PubUtilLib/plMessage/plResPatcherMsg.h
  33. 27
      Sources/Plasma/PubUtilLib/plNetClient/plNetCliAgeJoiner.cpp
  34. 4
      Sources/Plasma/PubUtilLib/plNetClientComm/plNetClientComm.cpp
  35. 98
      Sources/Plasma/PubUtilLib/plPipeline/plDTProgressMgr.cpp
  36. 24
      Sources/Plasma/PubUtilLib/plProgressMgr/plProgressMgr.cpp
  37. 35
      Sources/Plasma/PubUtilLib/plProgressMgr/plProgressMgr.h
  38. 1
      Sources/Tools/MaxMain/CMakeLists.txt
  39. 1
      Sources/Tools/MaxPlasmaLights/CMakeLists.txt

1
Sources/Plasma/Apps/plClient/CMakeLists.txt

@ -86,7 +86,6 @@ target_link_libraries(plClient pfJournalBook)
target_link_libraries(plClient pfLocalizationMgr)
target_link_libraries(plClient pfMessage)
target_link_libraries(plClient pfPython)
target_link_libraries(plClient pfSecurePreloader)
target_link_libraries(plClient pfSurface)
target_link_libraries(plClient plAgeDescription)
target_link_libraries(plClient plAgeLoader)

71
Sources/Plasma/Apps/plClient/plClient.cpp

@ -73,7 +73,6 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#include "pnMessage/plCameraMsg.h"
#include "plMessage/plTransitionMsg.h"
#include "plMessage/plLinkToAgeMsg.h"
#include "plMessage/plPreloaderMsg.h"
#include "plMessage/plNetCommMsgs.h"
#include "plMessage/plAgeLoadedMsg.h"
#include "plMessage/plResPatcherMsg.h"
@ -151,8 +150,8 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#include "plNetCommon/plNetCommonConstants.h"
#include "plNetGameLib/plNetGameLib.h"
#include "pfSecurePreloader/pfSecurePreloader.h"
#include "pfLocalizationMgr/pfLocalizationMgr.h"
#include "pfPatcher/plManifests.h"
#include "plTweak.h"
@ -311,11 +310,6 @@ bool plClient::Shutdown()
plAgeLoader::GetInstance()->UnRegisterAs(kAgeLoader_KEY); // deletes instance
plAgeLoader::SetInstance(nil);
}
if (pfSecurePreloader::GetInstance())
{
pfSecurePreloader::GetInstance()->Shutdown(); // will unregister itself
}
if (fInputManager)
{
@ -852,14 +846,6 @@ bool plClient::MsgReceive(plMessage* msg)
return true;
}
//============================================================================
// plPreloaderMsg
//============================================================================
if (plPreloaderMsg * preloaderMsg = plPreloaderMsg::ConvertNoRef(msg)) {
IHandlePreloaderMsg(preloaderMsg);
return true;
}
//============================================================================
// plResPatcherMsg
//============================================================================
@ -1196,7 +1182,7 @@ void plClient::IRoomLoaded(plSceneNode* node, bool hold)
};
char name[256];
strcpy(name, &fProgressBar->GetTitle()[strlen("Loading ")]);
strcpy(name, &fProgressBar->GetTitle().c_str()[strlen("Loading ")]);
name[strlen(name)-3] = '\0';
// Get the precalculated value for how many messages will be
@ -1222,7 +1208,7 @@ void plClient::IRoomLoaded(plSceneNode* node, bool hold)
#ifndef PLASMA_EXTERNAL_RELEASE
if (plDispatchLogBase::IsLogging())
plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle(), "displaying messages");
plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle().c_str(), "displaying messages");
#endif // PLASMA_EXTERNAL_RELEASE
#endif
}
@ -1349,7 +1335,7 @@ void plClient::IStartProgress( const char *title, float len )
fProgressBar = plProgressMgr::GetInstance()->RegisterOperation(len, title, plProgressMgr::kNone, false, true);
#ifndef PLASMA_EXTERNAL_RELEASE
if (plDispatchLogBase::IsLogging())
plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle(), "starting");
plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle().c_str(), "starting");
#endif // PLASMA_EXTERNAL_RELEASE
((plResManager*)hsgResMgr::ResMgr())->SetProgressBarProc(IReadKeyedObjCallback);
@ -1371,7 +1357,7 @@ void plClient::IStopProgress( void )
{
#ifndef PLASMA_EXTERNAL_RELEASE
if (plDispatchLogBase::IsLogging())
plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle(), "done");
plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle().c_str(), "done");
#endif // PLASMA_EXTERNAL_RELEASE
plDispatch::SetMsgRecieveCallback(nil);
@ -1564,8 +1550,7 @@ bool plClient::StartInit()
plgDispatch::Dispatch()->RegisterForExactType(plNetCommAuthMsg::Index(), GetKey());
plNetClientMgr::GetInstance()->Init();
plAgeLoader::GetInstance()->Init();
pfSecurePreloader::GetInstance()->Init();
plCmdIfaceModMsg* pModMsg2 = new plCmdIfaceModMsg;
pModMsg2->SetBCastFlag(plMessage::kBCastByExactType);
pModMsg2->SetSender(fConsole->GetKey());
@ -1613,19 +1598,10 @@ bool plClient::StartInit()
//============================================================================
void plClient::IPatchGlobalAgeFiles( void )
{
plResPatcher* patcher = plResPatcher::GetInstance();
if (!gDataServerLocal)
{
patcher->RequestManifest("CustomAvatars");
patcher->RequestManifest("GlobalAnimations");
patcher->RequestManifest("GlobalAvatars");
patcher->RequestManifest("GlobalClothing");
patcher->RequestManifest("GlobalMarkers");
patcher->RequestManifest("GUI");
}
plgDispatch::Dispatch()->RegisterForExactType(plResPatcherMsg::Index(), GetKey());
patcher->Start();
plResPatcher* patcher = plResPatcher::GetInstance();
patcher->Update(plManifest::EssentialGameManifests());
}
void plClient::InitDLLs()
@ -2524,33 +2500,12 @@ void plClient::ICompleteInit () {
clientMsg->Send();
}
//============================================================================
void plClient::IHandlePreloaderMsg (plPreloaderMsg * msg) {
plgDispatch::Dispatch()->UnRegisterForExactType(plPreloaderMsg::Index(), GetKey());
if (pfSecurePreloader* sp = pfSecurePreloader::GetInstance())
sp->Shutdown();
if (!msg->fSuccess) {
char str[1024];
StrPrintf(
str,
arrsize(str),
"Secure file preloader failed"
);
plNetClientApp::GetInstance()->QueueDisableNet(true, str);
return;
}
IPatchGlobalAgeFiles();
}
//============================================================================
void plClient::IHandlePatcherMsg (plResPatcherMsg * msg) {
plgDispatch::Dispatch()->UnRegisterForExactType(plResPatcherMsg::Index(), GetKey());
if (!msg->Success()) {
plNetClientApp::GetInstance()->QueueDisableNet(true, msg->GetError());
plNetClientApp::GetInstance()->QueueDisableNet(true, msg->GetError().c_str());
return;
}
@ -2577,8 +2532,6 @@ void plClient::IHandleNetCommAuthMsg (plNetCommAuthMsg * msg) {
return;
}
plgDispatch::Dispatch()->RegisterForExactType(plPreloaderMsg::Index(), GetKey());
// Precache our secure files
pfSecurePreloader::GetInstance()->Start();
// Patch them global files!
IPatchGlobalAgeFiles();
}

1
Sources/Plasma/Apps/plClient/plClient.h

@ -181,7 +181,6 @@ protected:
void ICompleteInit ();
void IOnAsyncInitComplete ();
void IHandlePatcherMsg (plResPatcherMsg * msg);
void IHandlePreloaderMsg (plPreloaderMsg * msg);
void IHandleNetCommAuthMsg (plNetCommAuthMsg * msg);
bool IHandleAgeLoaded2Msg (plAgeLoaded2Msg * msg);

2
Sources/Plasma/CoreLib/hsStream.h

@ -350,7 +350,7 @@ public:
virtual ~hsRAMStream();
virtual bool Open(const plFileName &, const char *) { hsAssert(0, "hsRAMStream::Open NotImplemented"); return false; }
virtual bool Close() { hsAssert(0, "hsRAMStream::Close NotImplemented"); return false; }
virtual bool Close() { return false; }
virtual bool AtEnd();

1
Sources/Plasma/CoreLib/hsThread.h

@ -96,6 +96,7 @@ public:
virtual hsError Run() = 0; // override this to do your work
virtual void Start(); // initializes stuff and calls your Run() method
virtual void Stop(); // sets fQuit = true and the waits for the thread to stop
virtual void OnQuit() { }
// Static functions
static void* Alloc(size_t size); // does not call operator::new(), may return nil

1
Sources/Plasma/CoreLib/hsThread_Unix.cpp

@ -76,6 +76,7 @@ extern "C" {
pthread_mutex_lock(((hsThread*)param)->GetStartupMutex());
void* ret = (void*)(uintptr_t)((hsThread*)param)->Run();
pthread_mutex_unlock(((hsThread*)param)->GetStartupMutex());
((hsThread*)param)->OnQuit();
pthread_exit(ret);
return ret;
}

1
Sources/Plasma/CoreLib/hsThread_Win.cpp

@ -68,6 +68,7 @@ static unsigned int __stdcall gEntryPointBT(void* param)
WinThreadParam* wtp = (WinThreadParam*)param;
unsigned int result = wtp->fThread->Run();
::ReleaseSemaphore(wtp->fQuitSemaH, 1, nil); // signal that we've quit
wtp->fThread->OnQuit();
delete param;
return result;
}

24
Sources/Plasma/CoreLib/plFileSystem.cpp

@ -570,3 +570,27 @@ plFileName plFileSystem::GetTempFilename(const char *prefix, const plFileName &p
return result;
#endif
}
plString plFileSystem::ConvertFileSize(uint64_t size)
{
const char* labels[] = { "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" };
if (size < 1024)
return plString::Format("%i B");
uint64_t last_div = size;
for (size_t i = 0; i < arrsize(labels); ++i) {
uint64_t my_div = last_div / 1024;
if (my_div < 1024) {
float decimal = static_cast<float>(last_div) / 1024.f;
// Kilobytes are so small that we only care about whole numbers
if (i < 1)
return plString::Format("%.0f %s", decimal, labels[i]);
else
return plString::Format("%.2f %s", decimal, labels[i]);
}
last_div = my_div;
}
// this should never happen
return plString::Format("%i %s", last_div, labels[arrsize(labels) - 1]);
}

3
Sources/Plasma/CoreLib/plFileSystem.h

@ -338,6 +338,9 @@ namespace plFileSystem
* system temp path is used.
*/
plFileName GetTempFilename(const char *prefix = "tmp", const plFileName &path = "");
/** Convert a file size from bytes to a human readable size. */
plString ConvertFileSize(uint64_t size);
}
#endif // plFileSystem_Defined

2
Sources/Plasma/FeatureLib/CMakeLists.txt

@ -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)

1
Sources/Plasma/FeatureLib/inc/pfAllCreatables.h

@ -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

@ -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

@ -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

@ -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_

64
Sources/Plasma/FeatureLib/pfSecurePreloader/pfSecurePreloaderCreatable.h → Sources/Plasma/FeatureLib/pfPatcher/plManifests.cpp

@ -33,25 +33,59 @@ 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
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
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
#include "plManifests.h"
#include "plFileSystem.h"
#include "pnFactory/plCreator.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
#include "pfSecurePreloader.h"
REGISTER_NONCREATABLE(pfSecurePreloader);
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;
}
#endif // PLASMA20_SOURCES_PLASMA_FEATURELIB_PFSECUREPRELOADER_PFSECUREPRELOADERCREATABLE_H

43
Sources/Plasma/PubUtilLib/plMessage/plPreloaderMsg.h → Sources/Plasma/FeatureLib/pfPatcher/plManifests.h

@ -39,29 +39,34 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
Mead, WA 99021
*==LICENSE==*/
/*****************************************************************************
*
* $/Plasma20/Sources/Plasma/PubUtilLib/plMessage/plPreloaderMsg.h
*
***/
#ifndef PLASMA20_SOURCES_PLASMA_PUBUTILLIB_PLMESSAGE_PLPRELOADERMSG_H
#define PLASMA20_SOURCES_PLASMA_PUBUTILLIB_PLMESSAGE_PLPRELOADERMSG_H
#ifndef _plManifests_inc_
#define _plManifests_inc_
#include "pnMessage/plMessage.h"
#include <vector>
class plPreloaderMsg : public plMessage {
public:
bool fSuccess;
class plFileName;
class plString;
plPreloaderMsg () { SetBCastFlag(kBCastByExactType); }
CLASSNAME_REGISTER(plPreloaderMsg);
GETINTERFACE_ANY(plPreloaderMsg, plMessage);
namespace plManifest
{
/** Get the name of the client executable for this build type.*/
plFileName ClientExecutable();
void Read (hsStream* stream, hsResMgr* ) { FATAL("plPreloaderMsg::Read"); }
void Write (hsStream* stream, hsResMgr* ) { FATAL("plPreloaderMsg::Write"); }
};
/** 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();
#endif // PLASMA20_SOURCES_PLASMA_PUBUTILLIB_PLMESSAGE_PLPRELOADERMSG_H
/** 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_

22
Sources/Plasma/FeatureLib/pfSecurePreloader/CMakeLists.txt

@ -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})

414
Sources/Plasma/FeatureLib/pfSecurePreloader/pfSecurePreloader.cpp

@ -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();
}

96
Sources/Plasma/FeatureLib/pfSecurePreloader/pfSecurePreloader.h

@ -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__

4
Sources/Plasma/NucleusLib/inc/plCreatableIndex.h

@ -84,7 +84,7 @@ CLASS_INDEX_LIST_START
CLASS_INDEX(plModifier),
CLASS_INDEX(plSingleModifier),
CLASS_INDEX(plSimpleModifier),
CLASS_INDEX(pfSecurePreloader),
CLASS_INDEX(UNUSED_pfSecurePreloader),
CLASS_INDEX(UNUSED_plRandomTMModifier),
CLASS_INDEX(plInterestingModifier),
CLASS_INDEX(plDetectorModifier),
@ -725,7 +725,7 @@ CLASS_INDEX_LIST_START
CLASS_INDEX(plAvBrainDrive),
CLASS_INDEX(plAvBrainSample),
CLASS_INDEX(plAvBrainGeneric),
CLASS_INDEX(plPreloaderMsg),
CLASS_INDEX(UNUSED_plPreloaderMsg),
CLASS_INDEX(plAvBrainLadder),
CLASS_INDEX(plInputIfaceMgrMsg),
CLASS_INDEX(pfKIMsg),

2
Sources/Plasma/NucleusLib/pnKeyedObject/plFixedKey.cpp

@ -118,8 +118,6 @@ plKeySeed SeedList[] = {
{ kJournalBookMgr_KEY, CLASS_INDEX_SCOPED( pfJournalBook ), "kJournalBookMgr_KEY", },
{ kAgeLoader_KEY, CLASS_INDEX_SCOPED( plAgeLoader), "kAgeLoader_KEY", },
{ kBuiltIn3rdPersonCamera_KEY, CLASS_INDEX_SCOPED( plCameraModifier1 ), "kBuiltIn3rdPersonCamera_KEY", },
{ kSecurePreloader_KEY, CLASS_INDEX_SCOPED( pfSecurePreloader ), "kSecurePreloader_KEY", },
{ kLast_Fixed_KEY, CLASS_INDEX_SCOPED( plSceneObject ), "kLast_Fixed_KEY", }
};

1
Sources/Plasma/NucleusLib/pnKeyedObject/plFixedKey.h

@ -85,7 +85,6 @@ enum plFixedKeyId
kJournalBookMgr_KEY,
kAgeLoader_KEY,
kBuiltIn3rdPersonCamera_KEY,
kSecurePreloader_KEY,
kLast_Fixed_KEY
};

2
Sources/Plasma/PubUtilLib/plAgeLoader/CMakeLists.txt

@ -18,5 +18,7 @@ set(plAgeLoader_HEADERS
add_library(plAgeLoader STATIC ${plAgeLoader_SOURCES} ${plAgeLoader_HEADERS})
target_link_libraries(plAgeLoader pfPatcher)
source_group("Source Files" FILES ${plAgeLoader_SOURCES})
source_group("Header Files" FILES ${plAgeLoader_HEADERS})

21
Sources/Plasma/PubUtilLib/plAgeLoader/plAgeLoader.cpp

@ -69,15 +69,13 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#include "plResMgr/plRegistryNode.h"
#include "plResMgr/plResManager.h"
#include "plFile/plEncryptedStream.h"
#include "plProgressMgr/plProgressMgr.h"
/// TEMP HACK TO LOAD CONSOLE INIT FILES ON AGE LOAD
#include "plMessage/plConsoleMsg.h"
#include "plMessage/plLoadAvatarMsg.h"
#include "plMessage/plAgeLoadedMsg.h"
extern bool gDataServerLocal;
// static
plAgeLoader* plAgeLoader::fInstance=nil;
@ -119,6 +117,7 @@ void plAgeLoader::Init()
RegisterAs( kAgeLoader_KEY );
plgDispatch::Dispatch()->RegisterForExactType(plInitialAgeStateLoadedMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plClientMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plResPatcherMsg::Index(), GetKey());
}
//
@ -156,7 +155,12 @@ bool plAgeLoader::MsgReceive(plMessage* msg)
return true;
}
// sadface thread protection
if (plResPatcherMsg::ConvertNoRef(msg)) {
delete plResPatcher::GetInstance()->fProgress;
plResPatcher::GetInstance()->fProgress = nullptr;
}
return plReceiver::MsgReceive(msg);
}
@ -173,14 +177,7 @@ bool plAgeLoader::LoadAge(const char ageName[])
//============================================================================
void plAgeLoader::UpdateAge(const char ageName[])
{
if (gDataServerLocal)
// We have to send this msg ourselves since we're not actually updating
plgDispatch::Dispatch()->MsgSend(new plResPatcherMsg);
else
{
plResPatcher::GetInstance()->RequestManifest(ageName);
plResPatcher::GetInstance()->Start();
}
plResPatcher::GetInstance()->Update(ageName);
}
//============================================================================

333
Sources/Plasma/PubUtilLib/plAgeLoader/plResPatcher.cpp

@ -42,153 +42,23 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#include "plResPatcher.h"
#include "hsResMgr.h"
#include "plgDispatch.h"
#include "plAgeLoader/plAgeLoader.h"
#include "plCompression/plZlibStream.h"
#include "pnEncryption/plChecksum.h"
#include "plFile/plEncryptedStream.h"
#include "plFile/plStreamSource.h"
#include "plFile/plSecureStream.h"
#include "plMessage/plResPatcherMsg.h"
#include "pnNetBase/pnNbError.h"
#include "plNetGameLib/plNetGameLib.h"
#include "pfPatcher/pfPatcher.h"
#include "plProgressMgr/plProgressMgr.h"
#include "plResMgr/plResManager.h"
#include "plStatusLog/plStatusLog.h"
/////////////////////////////////////////////////////////////////////////////
class plResDownloadStream : public plZlibStream
{
plOperationProgress* fProgress;
plFileName fFilename;
bool fIsZipped;
public:
plResDownloadStream(plOperationProgress* prog, const plFileName& reqFile)
: fProgress(prog)
{
fIsZipped = reqFile.GetFileExt().CompareI("gz") == 0;
}
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)
{
fProgress->Increment((float)count);
if (fIsZipped)
return plZlibStream::Write(count, buf);
else
return fOutput->Write(count, buf);
}
plFileName GetFileName() const { return fFilename; }
bool IsZipped() const { return fIsZipped; }
void Unlink() const { plFileSystem::Unlink(fFilename); }
};
extern bool gDataServerLocal;
bool gSkipPreload = false;
/////////////////////////////////////////////////////////////////////////////
static void FileDownloaded(
ENetError result,
void* param,
const plFileName & filename,
hsStream* writer)
{
plResPatcher* patcher = (plResPatcher*)param;
plFileName file = filename;
if (((plResDownloadStream*)writer)->IsZipped())
file = file.StripFileExt(); // Kill off .gz
writer->Close();
switch (result)
{
case kNetSuccess:
{
PatcherLog(kStatus, " Download Complete: %s", file.AsString().c_str());
// If this is a PRP, then we need to add it to the ResManager
plFileName clientPath = static_cast<plResDownloadStream*>(writer)->GetFileName();
if (clientPath.GetFileExt().CompareI("prp") == 0)
{
plResManager* clientResMgr = static_cast<plResManager*>(hsgResMgr::ResMgr());
clientResMgr->AddSinglePage(clientPath);
}
// Continue down the warpath
patcher->IssueRequest();
delete writer;
return;
}
case kNetErrFileNotFound:
PatcherLog(kError, " Download Failed: %s not found", file.AsString().c_str());
break;
default:
char* error = hsWStringToString(NetErrorToString(result));
PatcherLog(kError, " Download Failed: %s", error);
delete[] error;
break;
}
// Failure case
static_cast<plResDownloadStream*>(writer)->Unlink();
patcher->Finish(false);
delete writer;
}
static void ManifestDownloaded(
ENetError result,
void* param,
const wchar_t group[],
const NetCliFileManifestEntry manifest[],
uint32_t entryCount)
{
plResPatcher* patcher = (plResPatcher*)param;
plString name = plString::FromWchar(group);
if (IS_NET_SUCCESS(result))
PatcherLog(kInfo, " Downloaded manifest %s", name.c_str());
else {
PatcherLog(kError, " Failed to download manifest %s", name.c_str());
patcher->Finish(false);
return;
}
for (uint32_t i = 0; i < entryCount; ++i)
{
const NetCliFileManifestEntry mfs = manifest[i];
plFileName fileName = plString::FromWchar(mfs.clientName);
plFileName downloadName = plString::FromWchar(mfs.downloadName);
// See if the files are the same
// 1. Check file size before we do time consuming md5 operations
// 2. Do wasteful md5. We should consider implementing a CRC instead.
if (plFileInfo(fileName).FileSize() == mfs.fileSize)
{
plMD5Checksum cliMD5(fileName);
plMD5Checksum srvMD5;
srvMD5.SetFromHexString(plString::FromWchar(mfs.md5, 32).c_str());
if (cliMD5 == srvMD5)
continue;
else
PatcherLog(kInfo, " Enqueueing %s: MD5 Checksums Differ", fileName.AsString().c_str());
} else
PatcherLog(kInfo, " Enqueueing %s: File Sizes Differ", fileName.AsString().c_str());
// If we're still here, then we need to update the file.
float size = mfs.zipSize ? (float)mfs.zipSize : (float)mfs.fileSize;
patcher->GetProgress()->SetLength(size + patcher->GetProgress()->GetMax());
patcher->RequestFile(downloadName, fileName);
}
patcher->IssueRequest();
}
/////////////////////////////////////////////////////////////////////////////
static char* sLastError = nil;
plResPatcher* plResPatcher::fInstance = nil;
plResPatcher* plResPatcher::fInstance = nullptr;
plResPatcher* plResPatcher::GetInstance()
{
@ -199,134 +69,125 @@ plResPatcher* plResPatcher::GetInstance()
void plResPatcher::Shutdown()
{
// Better not call this while we're patching
delete fInstance;
}
/////////////////////////////////////////////////////////////////////////////
plResPatcher::plResPatcher()
: fPatching(false), fProgress(nil) { }
plResPatcher::~plResPatcher()
void plResPatcher::OnCompletion(ENetError result, const plString& status)
{
if (fProgress)
delete fProgress;
plString error = plString::Null;
if (IS_NET_ERROR(result))
error = plString::Format("Update Failed: %S\n%s", NetErrorAsString(result), status.c_str());
plgDispatch::Dispatch()->MsgQueue(new plResPatcherMsg(IS_NET_SUCCESS(result), error));
}
void plResPatcher::IssueRequest()
void plResPatcher::OnFileDownloadBegin(const plFileName& file)
{
if (!fPatching) return;
if (fRequests.empty())
// Wheee!
Finish();
else {
Request req = fRequests.front();
fRequests.pop();
plString title;
if (req.fType == kManifest)
{
PatcherLog(kMajorStatus, " Downloading manifest... %s", req.fFile.AsString().c_str());
title = plString::Format("Checking %s for updates...", req.fFile.AsString().c_str());
NetCliFileManifestRequest(ManifestDownloaded, this, req.fFile.AsString().ToWchar());
} else if (req.fType == kFile) {
PatcherLog(kMajorStatus, " Downloading file... %s", req.fFriendlyName.AsString().c_str());
title = plString::Format("Downloading... %s", req.fFriendlyName.GetFileName().c_str());
// If this is a PRP, we need to unload it from the ResManager
if (req.fFriendlyName.GetFileExt().CompareI("prp") == 0)
((plResManager*)hsgResMgr::ResMgr())->RemoveSinglePage(req.fFriendlyName);
plFileSystem::CreateDir(req.fFriendlyName.StripFileName(), true);
plResDownloadStream* stream = new plResDownloadStream(fProgress, req.fFile);
if (stream->Open(req.fFriendlyName, "wb"))
NetCliFileDownloadRequest(req.fFile, stream, FileDownloaded, this);
else {
PatcherLog(kError, " Unable to create file %s", req.fFriendlyName.AsString().c_str());
Finish(false);
}
}
fProgress->SetTitle(title.c_str());
fProgress->SetTitle(plString::Format("Downloading %s...", file.GetFileName().c_str()));
if (file.GetFileExt().CompareI("prp") == 0) {
plResManager* mgr = static_cast<plResManager*>(hsgResMgr::ResMgr());
if (mgr)
mgr->RemoveSinglePage(file);
}
}
void plResPatcher::Finish(bool success)
void plResPatcher::OnFileDownloaded(const plFileName& file)
{
while (fRequests.size())
fRequests.pop();
fPatching = false;
if (success)
PatcherLog(kHeader, "--- Patch Completed Successfully ---");
else
{
PatcherLog(kHeader, "--- Patch Killed by Error ---");
if (fProgress)
fProgress->SetAborting();
if (file.GetFileExt().CompareI("prp") == 0) {
plResManager* mgr = static_cast<plResManager*>(hsgResMgr::ResMgr());
if (mgr)
mgr->AddSinglePage(file);
}
delete fProgress; fProgress = nil;
}
bool plResPatcher::OnGameCodeDiscovered(const plFileName& file, hsStream* stream)
{
plSecureStream* ss = new plSecureStream(false, plStreamSource::GetInstance()->GetEncryptionKey());
if (ss->Open(stream)) {
plStreamSource::GetInstance()->InsertFile(file, ss);
// SecureStream will hold a decrypted buffer...
stream->Close();
delete stream;
} else
plStreamSource::GetInstance()->InsertFile(file, stream);
plResPatcherMsg* pMsg = new plResPatcherMsg(success, sLastError);
delete[] sLastError; sLastError = nil;
pMsg->Send(); // whoosh... off it goes
return true; // ASSume success for now...
}
void plResPatcher::RequestFile(const plFileName& srvName, const plFileName& cliName)
void plResPatcher::OnProgressTick(uint64_t dl, uint64_t total, const plString& msg)
{
fRequests.push(Request(srvName, kFile, cliName));
if (dl && total) {
fProgress->SetLength(total);
fProgress->SetHowMuch(dl);
}
plString status = plString::Format("%s / %s",
plFileSystem::ConvertFileSize(dl).c_str(),
plFileSystem::ConvertFileSize(total).c_str()
);
fProgress->SetStatusText(status);
fProgress->SetInfoText(msg);
}
void plResPatcher::RequestManifest(const plString& age)
pfPatcher* plResPatcher::CreatePatcher()
{
fRequests.push(Request(age, kManifest));
pfPatcher* patcher = new pfPatcher;
patcher->OnCompletion(std::bind(&plResPatcher::OnCompletion, this, std::placeholders::_1, std::placeholders::_2));
patcher->OnFileDownloadBegin(std::bind(&plResPatcher::OnFileDownloadBegin, this, std::placeholders::_1));
patcher->OnFileDownloaded(std::bind(&plResPatcher::OnFileDownloaded, this, std::placeholders::_1));
patcher->OnProgressTick(std::bind(&plResPatcher::OnProgressTick, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
// sneaky hax: do the old SecurePreloader thing.... except here
if (!fRequestedGameCode && !gSkipPreload) {
patcher->OnGameCodeDiscovery(std::bind(&plResPatcher::OnGameCodeDiscovered, this, std::placeholders::_1, std::placeholders::_2));
patcher->RequestGameCode();
fRequestedGameCode = true;
}
return patcher;
}
void plResPatcher::Start()
void plResPatcher::InitProgress()
{
hsAssert(!fPatching, "Too many calls to plResPatcher::Start");
fPatching = true;
PatcherLog(kHeader, "--- Patch Started (%i requests) ---", fRequests.size());
fProgress = plProgressMgr::GetInstance()->RegisterOperation(0.0, "Checking for updates...",
plProgressMgr::kUpdateText, false, true);
IssueRequest();
// this is deleted in plAgeLoader::MsgReceive for thread safety
fProgress = plProgressMgr::GetInstance()->RegisterOperation(0.f, nullptr, plProgressMgr::kUpdateText);
}
/////////////////////////////////////////////////////////////////////////////
void PatcherLog(PatcherLogType type, const char* format, ...)
plResPatcher::plResPatcher()
: fProgress(nullptr), fRequestedGameCode(false) { }
plResPatcher::~plResPatcher()
{
uint32_t color = 0;
switch (type)
{
case kHeader: color = plStatusLog::kWhite; break;
case kInfo: color = plStatusLog::kBlue; break;
case kMajorStatus: color = plStatusLog::kYellow; break;
case kStatus: color = plStatusLog::kGreen; break;
case kError: color = plStatusLog::kRed; break;
}
delete fProgress;
}
static plStatusLog* gStatusLog = nil;
if (!gStatusLog)
{
gStatusLog = plStatusLogMgr::GetInstance().CreateStatusLog(
20,
"patcher.log",
plStatusLog::kFilledBackground | plStatusLog::kAlignToTop | plStatusLog::kDeleteForMe);
void plResPatcher::Update(const std::vector<plString>& manifests)
{
if (gDataServerLocal)
plgDispatch::Dispatch()->MsgSend(new plResPatcherMsg());
else {
InitProgress();
pfPatcher* patcher = CreatePatcher();
patcher->RequestManifest(manifests);
patcher->Start(); // whoosh... off it goes
}
}
va_list args;
va_start(args, format);
if (type == kError)
{
sLastError = new char[1024]; // Deleted by Finish(false)
vsnprintf(sLastError, 1024, format, args);
gStatusLog->AddLine(sLastError, color);
} else
gStatusLog->AddLineV(color, format, args);
va_end(args);
void plResPatcher::Update(const plString& manifest)
{
if (gDataServerLocal)
plgDispatch::Dispatch()->MsgSend(new plResPatcherMsg());
else {
InitProgress();
pfPatcher* patcher = CreatePatcher();
patcher->RequestManifest(manifest);
patcher->Start(); // whoosh... off it goes
}
}

62
Sources/Plasma/PubUtilLib/plAgeLoader/plResPatcher.h

@ -39,57 +39,49 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
Mead, WA 99021
*==LICENSE==*/
#ifndef plResPatcher_h_inc
#define plResPatcher_h_inc
#include "HeadSpin.h"
#include "plFileSystem.h"
#include <queue>
#include <string>
#include "pnNetBase/pnNbError.h"
#include <vector>
class plOperationProgress;
class plString;
/**
* Plasma Resource Patcher
* This is a thin wrapper around \sa pfPatcher that ensures updates are propagated to the rest of the engine.
*/
class plResPatcher
{
enum { kManifest, kFile };
struct Request
{
plFileName fFile;
plFileName fFriendlyName;
uint8_t fType;
Request(const plFileName& file, uint8_t type, const plFileName& friendly = "")
: fFile(file), fFriendlyName(friendly), fType(type) { }
};
static plResPatcher* fInstance;
std::queue<Request> fRequests;
plOperationProgress* fProgress;
bool fPatching;
plOperationProgress* fProgress;
static plResPatcher* fInstance;
bool fRequestedGameCode;
plResPatcher();
~plResPatcher();
friend class plAgeLoader;
void OnCompletion(ENetError, const plString& msg);
void OnFileDownloadBegin(const plFileName& file);
void OnFileDownloaded(const plFileName& file);
bool OnGameCodeDiscovered(const plFileName& file, class hsStream* stream);
void OnProgressTick(uint64_t dl, uint64_t total, const plString& msg);
class pfPatcher* CreatePatcher();
void InitProgress();
public:
static plResPatcher* GetInstance();
static void Shutdown();
plOperationProgress* GetProgress() { return fProgress; }
public:
plResPatcher();
~plResPatcher();
void Finish(bool success = true);
void IssueRequest();
void RequestFile(const plFileName& file, const plFileName& friendlyName);
void RequestManifest(const plString& age);
void Start();
void Update(const std::vector<plString>& manifests);
void Update(const plString& manifest);
};
enum PatcherLogType
{
kHeader,
kInfo,
kMajorStatus,
kStatus,
kError,
};
void PatcherLog(PatcherLogType type, const char* format, ...);
#endif // _plResPatcher_h

8
Sources/Plasma/PubUtilLib/plFile/plSecureStream.cpp

@ -667,17 +667,13 @@ bool plSecureStream::IsSecureFile(const plFileName& fileName)
hsStream* plSecureStream::OpenSecureFile(const plFileName& fileName, const uint32_t flags /* = kRequireEncryption */, uint32_t* key /* = nil */)
{
bool requireEncryption = flags & kRequireEncryption;
#ifndef PLASMA_EXTERNAL_RELEASE
requireEncryption = false;
#endif
bool deleteOnExit = flags & kDeleteOnExit;
bool isEncrypted = IsSecureFile(fileName);
hsStream* s = nil;
hsStream* s = nullptr;
if (isEncrypted)
s = new plSecureStream(deleteOnExit, key);
else if (!requireEncryption) // If this isn't an external release, let them use unencrypted data
else if (!requireEncryption)
s = new hsUNIXStream;
if (s)

28
Sources/Plasma/PubUtilLib/plFile/plStreamSource.cpp

@ -40,7 +40,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
*==LICENSE==*/
#include <string>
#include "HeadSpin.h"
#include "plStreamSource.h"
#include "plSecureStream.h"
#include "plEncryptedStream.h"
@ -49,8 +49,15 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
# include <wctype.h>
#endif
plStreamSource::plStreamSource()
{
memset(fServerKey, 0, arrsize(fServerKey));
}
void plStreamSource::ICleanup()
{
hsTempMutexLock lock(fMutex);
// loop through all the file data records, and delete the streams
decltype(fFileData.begin()) curData;
for (curData = fFileData.begin(); curData != fFileData.end(); curData++)
@ -65,6 +72,8 @@ void plStreamSource::ICleanup()
hsStream* plStreamSource::GetFile(const plFileName& filename)
{
hsTempMutexLock lock(fMutex);
plFileName sFilename = filename.Normalize('/');
if (fFileData.find(sFilename) == fFileData.end())
{
@ -78,14 +87,15 @@ hsStream* plStreamSource::GetFile(const plFileName& filename)
fFileData[sFilename].fExt = sFilename.GetFileExt();
if (plSecureStream::IsSecureFile(filename))
{
uint32_t encryptionKey[4];
if (!plSecureStream::GetSecureEncryptionKey(filename, encryptionKey, 4))
{
FATAL("Hey camper... You need an NTD key file!");
return nil;
}
hsStream* ss = nullptr;
fFileData[sFilename].fStream = plSecureStream::OpenSecureFile(filename, 0, encryptionKey);
uint32_t encryptionKey[4];
if (plSecureStream::GetSecureEncryptionKey(filename, encryptionKey, 4))
ss = plSecureStream::OpenSecureFile(filename, 0, encryptionKey);
else
ss = plSecureStream::OpenSecureFile(filename, 0, fServerKey);
fFileData[sFilename].fStream = ss;
hsAssert(ss, "failed to open a SecureStream for a disc file!");
}
else // otherwise it is an encrypted or plain stream, this call handles both
fFileData[sFilename].fStream = plEncryptedStream::OpenEncryptedFile(filename);
@ -102,6 +112,7 @@ std::vector<plFileName> plStreamSource::GetListOfNames(const plFileName& dir, co
{
plFileName sDir = dir.Normalize('/');
hsAssert(ext.CharAt(0) != '.', "Don't add a dot");
hsTempMutexLock lock(fMutex);
// loop through all the file data records, and create the list
std::vector<plFileName> retVal;
@ -131,6 +142,7 @@ bool plStreamSource::InsertFile(const plFileName& filename, hsStream* stream)
{
plFileName sFilename = filename.Normalize('/');
hsTempMutexLock lock(fMutex);
if (fFileData.find(sFilename) != fFileData.end())
return false; // duplicate entry, return failure

9
Sources/Plasma/PubUtilLib/plFile/plStreamSource.h

@ -43,8 +43,8 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#define plStreamSource_h_inc
#include <map>
#include <string>
#include "hsStream.h"
#include "hsThread.h"
// A class for holding and accessing file streams. The preloader will insert
// files in here once they are loaded. In internal builds, if a requested file
@ -60,10 +60,12 @@ private:
hsStream* fStream; // we own this pointer, so clean it up
};
std::map<plFileName, fileData, plFileName::less_i> fFileData; // key is filename
hsMutex fMutex;
uint32_t fServerKey[4];
void ICleanup(); // closes all file pointers and cleans up after itself
plStreamSource() {}
plStreamSource();
public:
~plStreamSource() {ICleanup();}
@ -77,6 +79,9 @@ public:
// For other classes to insert files (takes ownership of the stream if successful)
bool InsertFile(const plFileName& filename, hsStream* stream);
/** Gets a pointer to our encryption key */
uint32_t* GetEncryptionKey() { return fServerKey; }
// Instance handling
static plStreamSource* GetInstance();
};

1
Sources/Plasma/PubUtilLib/plMessage/CMakeLists.txt

@ -98,7 +98,6 @@ set(plMessage_HEADERS
plOneShotMsg.h
plParticleUpdateMsg.h
plPickedMsg.h
plPreloaderMsg.h
plRenderMsg.h
plRenderRequestMsg.h
plReplaceGeometryMsg.h

3
Sources/Plasma/PubUtilLib/plMessage/plMessageCreatable.h

@ -319,9 +319,6 @@ REGISTER_CREATABLE(plNetCommPublicAgeListMsg);
REGISTER_CREATABLE(plNetCommPublicAgeMsg);
REGISTER_CREATABLE(plNetCommRegisterAgeMsg);
#include "plPreloaderMsg.h"
REGISTER_CREATABLE(plPreloaderMsg);
#include "plNetClientMgrMsg.h"
REGISTER_CREATABLE(plNetClientMgrMsg);

15
Sources/Plasma/PubUtilLib/plMessage/plResPatcherMsg.h

@ -42,31 +42,28 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#ifndef _PLMESSAGE_PLRESPATCHERMSG_H
#define _PLMESSAGE_PLRESPATCHERMSG_H
#include "HeadSpin.h"
#include "plString.h"
#include "pnMessage/plMessage.h"
// This message is sent when plResPatcher has completed its async operation
class plResPatcherMsg : public plMessage {
bool fSuccess;
char* fError;
plString fError;
public:
plResPatcherMsg() : fSuccess(true), fError(nil) { SetBCastFlag(kBCastByExactType); }
plResPatcherMsg(bool success, const char* error) : fSuccess(success)
plResPatcherMsg() : fSuccess(true) { SetBCastFlag(kBCastByExactType); }
plResPatcherMsg(bool success, const plString& error) : fSuccess(success), fError(error)
{
SetBCastFlag(kBCastByExactType);
fError = hsStrcpy(error);
}
~plResPatcherMsg() { delete[] fError; }
CLASSNAME_REGISTER(plResPatcherMsg);
GETINTERFACE_ANY(plResPatcherMsg, plMessage);
void Read (hsStream *, hsResMgr *) { FATAL("What the hell are you doing?"); }
void Write (hsStream *, hsResMgr *) { FATAL("What the hell are you doing?"); }
const char* GetError() const { return fError; }
plString GetError() const { return fError; }
bool Success() const { return fSuccess; }
};

27
Sources/Plasma/PubUtilLib/plNetClient/plNetCliAgeJoiner.cpp

@ -188,7 +188,7 @@ void plNCAgeJoiner::IDispatchMsgReceiveCallback () {
void plNCAgeJoiner::IResMgrProgressBarCallback (plKey key) {
#ifndef PLASMA_EXTERNAL_RELEASE
if (s_instance)
s_instance->progressBar->SetStatusText(key->GetName().c_str());
s_instance->progressBar->SetStatusText(key->GetName());
#endif
}
@ -224,13 +224,18 @@ void plNCAgeJoiner::Start () {
// if we're linking to startup then set the OfflineAge flag
// so we by-pass the game server
if (StrLen(age.ageDatasetName) == 0 || StrCmpI(age.ageDatasetName, "StartUp") == 0)
if (StrLen(age.ageDatasetName) == 0 || StrCmpI(age.ageDatasetName, "StartUp") == 0) {
nc->SetFlagsBit(plNetClientApp::kLinkingToOfflineAge);
else
// no need to update if we're not using a GameSrv
plgDispatch::MsgSend(new plResPatcherMsg());
} else {
nc->SetFlagsBit(plNetClientApp::kLinkingToOfflineAge, false);
plAgeLoader* al = plAgeLoader::GetInstance();
al->UpdateAge(age.ageDatasetName);
// we only need to update the age if we're using a GameSrv
plAgeLoader* al = plAgeLoader::GetInstance();
al->UpdateAge(age.ageDatasetName);
}
}
//============================================================================
@ -252,12 +257,10 @@ void plNCAgeJoiner::ExecNextOp () {
LogMsg(kLogPerf, L"AgeJoiner: Exec:kLoadAge");
// Start progress bar
char str[256];
#ifdef PLASMA_EXTERNAL_RELEASE
StrCopy(str, "Loading age...", arrsize(str));
#else
StrPrintf(str, arrsize(str), "Loading age %s...", age.ageDatasetName);
#endif
char str[128];
#ifndef PLASMA_EXTERNAL_RELEASE
snprintf(str, arrsize(str), "Loading age... %s", age.ageDatasetName);
#endif
progressBar = plProgressMgr::GetInstance()->RegisterOperation(0, str, plProgressMgr::kNone, false, true);
plDispatch::SetMsgRecieveCallback(IDispatchMsgReceiveCallback);
((plResManager*)hsgResMgr::ResMgr())->SetProgressBarProc(IResMgrProgressBarCallback);
@ -389,7 +392,7 @@ bool plNCAgeJoiner::MsgReceive (plMessage * msg) {
);
LogMsg(kLogPerf, L"AgeJoiner: Next:kNoOp (age updated)");
} else
Complete(false, resMsg->GetError());
Complete(false, resMsg->GetError().c_str());
return true;
}

4
Sources/Plasma/PubUtilLib/plNetClientComm/plNetClientComm.cpp

@ -60,6 +60,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#include "plVault/plVault.h"
#include "plMessage/plAccountUpdateMsg.h"
#include "plNetClient/plNetClientMgr.h"
#include "plFile/plStreamSource.h"
#include "pfMessage/pfKIMsg.h"
@ -414,6 +415,9 @@ static void INetCliAuthLoginRequestCallback (
if (!wantsStartUpAge && 0 == StrCmpI(s_players[i].playerName, s_iniStartupPlayerName, (unsigned)-1))
s_player = &s_players[i];
}
// store this server's encryption key for posterity
NetCliAuthGetEncryptionKey(plStreamSource::GetInstance()->GetEncryptionKey(), 4);
}
else
s_account.accountUuid = kNilUuid;

98
Sources/Plasma/PubUtilLib/plPipeline/plDTProgressMgr.cpp

@ -57,6 +57,14 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#include "hsTimer.h"
// Draw Colors
enum
{
kTitleColor = 0xccb0b0b0,
kProgressBarColor = 0xff302b3a,
kInfoColor = 0xff635e6d,
};
//// Constructor & Destructor ////////////////////////////////////////////////
@ -178,85 +186,51 @@ bool plDTProgressMgr::IDrawTheStupidThing(plPipeline *p, plOperationProgress
{
plDebugText &text = plDebugText::Instance();
bool drew_something = false;
uint16_t downsz = (text.GetFontSize() << 1) + 4;
// Lets just set the color to blue
uint32_t color = 0xff302b3a;
// draw the title
if (!prog->GetTitle().IsEmpty()) {
y -= downsz;
text.DrawString_TEMP(x, y, prog->GetTitle(), kTitleColor);
y += downsz;
drew_something = true;
}
if( prog->GetMax() > 0.f )
{
text.Draw3DBorder(x, y, x + width - 1, y + height - 1, color, color);
// draw a progress bar
if (prog->GetMax() > 0.f) {
text.Draw3DBorder(x, y, x + width - 1, y + height - 1, kProgressBarColor, kProgressBarColor);
x += 2;
y += 2;
width -= 4;
height -= 4;
uint16_t drawWidth = width;
int16_t drawX = x;
uint16_t rightX = drawX + drawWidth;
uint16_t drawWidth = width;
int16_t drawX = x;
uint16_t rightX = drawX + drawWidth;
if (prog->GetProgress() <= prog->GetMax())
drawWidth = (uint16_t)( (float)width * prog->GetProgress() / prog->GetMax() );
drawWidth = (uint16_t)((float)width * prog->GetProgress() / prog->GetMax());
rightX = drawX + drawWidth;
if (drawWidth > 0)
text.DrawRect(drawX, y, rightX, y + height, kProgressBarColor);
y += height + 2;
if( drawWidth > 0 )
text.DrawRect( drawX, y, rightX, y + height, color );
uint32_t timeRemain = prog->fRemainingSecs;
if (timeRemain > 0) {
plStringStream ss;
ss << "APPROXIMATELY ";
if (timeRemain > 3600)
{
uint32_t hours = timeRemain / 3600;
const char* plural = (hours > 1) ? "S" : "";
ss << hours << " HOUR" << plural << " ";
timeRemain %= 3600;
}
if (timeRemain > 60)
{
uint32_t minutes = timeRemain / 60;
const char* plural = (minutes > 1) ? "S" : "";
ss << minutes << " MINUTE" << plural << " ";
timeRemain %= 60;
}
if (timeRemain > 0)
{
const char* plural = (timeRemain > 1) ? "S" : "";
ss << timeRemain << " SECOND" << plural << " ";
}
ss << "REMAINING";
text.DrawString(x, y + height + 2, ss.GetString().c_str(), (uint32_t)0xff635e6d);
}
x -= 2;
y -= 2;
drew_something = true;
}
y -= ( text.GetFontSize() << 1 ) + 4;
#ifndef PLASMA_EXTERNAL_RELEASE
static bool drawText = true;
#else
static bool drawText = false;
#endif
if (drawText)
{
if (prog->GetTitle())
{
text.DrawString( x, y, prog->GetTitle(), (uint32_t)0xccb0b0b0 );
x += (uint16_t)text.CalcStringWidth( prog->GetTitle() );
drew_something = true;
}
// draw the left justified status text
if (!prog->GetStatusText().IsEmpty()) {
text.DrawString_TEMP(x, y, prog->GetStatusText(), kInfoColor);
drew_something = true;
}
if (prog->GetStatusText())
{
text.DrawString( x, y, prog->GetStatusText(), (uint32_t)0xccb0b0b0 );
drew_something = true;
}
// draw the right justified info text
if (!prog->GetInfoText().IsEmpty()) {
uint16_t right_x = 2 + x + width - text.CalcStringWidth_TEMP(prog->GetInfoText());
text.DrawString_TEMP(right_x, y, prog->GetInfoText(), kInfoColor);
drew_something = true;
}
// return whether or not we drew stuff

24
Sources/Plasma/PubUtilLib/plProgressMgr/plProgressMgr.cpp

@ -268,8 +268,6 @@ plOperationProgress::plOperationProgress( float length ) :
fRemainingSecs(0),
fAmtPerSec(0.f)
{
memset( fStatusText, 0, sizeof( fStatusText ) );
memset( fTitle, 0, sizeof( fTitle ) );
}
plOperationProgress::~plOperationProgress()
@ -347,28 +345,6 @@ void plOperationProgress::SetHowMuch( float howMuch )
plProgressMgr::GetInstance()->IUpdateCallbackProc( this );
}
//// SetStatusText ///////////////////////////////////////////////////////////
void plOperationProgress::SetStatusText( const char *text )
{
if( text != nil )
strncpy( fStatusText, text, sizeof( fStatusText ) );
else
fStatusText[ 0 ] = 0;
}
//// SetTitle ////////////////////////////////////////////////////////////////
void plOperationProgress::SetTitle( const char *text )
{
if (text != nil)
{
strncpy(fTitle, text, sizeof(fTitle));
}
else
fTitle[0] = 0;
}
//// SetLength ///////////////////////////////////////////////////////////////
void plOperationProgress::SetLength( float length )

35
Sources/Plasma/PubUtilLib/plProgressMgr/plProgressMgr.h

@ -56,7 +56,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#define _plProgressMgr_h
#include "HeadSpin.h"
#include "plString.h"
class plPipeline;
class plPlate;
@ -74,10 +74,11 @@ class plOperationProgress
protected:
float fValue, fMax;
char fStatusText[ 256 ];
char fTitle[ 256 ];
uint32_t fContext;
double fStartTime;
plString fTitle;
plString fStatusText;
plString fInfoText;
uint32_t fContext;
double fStartTime;
uint32_t fElapsedSecs, fRemainingSecs;
float fAmtPerSec;
@ -109,11 +110,12 @@ class plOperationProgress
~plOperationProgress();
float GetMax( void ) const { return fMax; }
float GetProgress( void ) const { return fValue; }
const char * GetTitle( void ) const { return fTitle; }
const char * GetStatusText( void ) const { return fStatusText; }
uint32_t GetContext( void ) const { return fContext; }
float GetMax() const { return fMax; }
float GetProgress() const { return fValue; }
plString GetTitle() const { return fTitle; }
plString GetStatusText() const { return fStatusText; }
plString GetInfoText() const { return fInfoText; }
uint32_t GetContext() const { return fContext; }
uint32_t GetElapsedSecs() { return fElapsedSecs; }
uint32_t GetRemainingSecs() { return fRemainingSecs; }
float GetAmtPerSec() { return fAmtPerSec; }
@ -127,16 +129,19 @@ class plOperationProgress
// Set the length
void SetLength( float length );
// Sets the display text above the bar (nil for nothing)
void SetStatusText( const char *text );
/** Sets the progress bar's right justified info text */
void SetInfoText(const plString& info) { fInfoText = info; }
/** Sets the progress bar's left justified status text */
void SetStatusText(const plString& status) { fStatusText = status; }
// Sets the title
void SetTitle( const char *title );
/** Sets the progress bar's title */
void SetTitle(const plString& title) { fTitle = title; }
// Application data
void SetContext( uint32_t context ) { fContext = context;}
bool IsDone( void ) { return ( fValue < fMax ) ? false : true; }
bool IsDone() { return ( fValue < fMax ) ? false : true; }
// True if this is the initial update (progress was just created)
bool IsInitUpdate() { return hsCheckBits(fFlags, kInitUpdate); }

1
Sources/Tools/MaxMain/CMakeLists.txt

@ -124,7 +124,6 @@ target_link_libraries(MaxMain pfJournalBook)
target_link_libraries(MaxMain pfLocalizationMgr)
target_link_libraries(MaxMain pfMessage)
target_link_libraries(MaxMain pfPython)
target_link_libraries(MaxMain pfSecurePreloader)
target_link_libraries(MaxMain pfSurface)
target_link_libraries(MaxMain plAgeDescription)
target_link_libraries(MaxMain plAgeLoader)

1
Sources/Tools/MaxPlasmaLights/CMakeLists.txt

@ -57,7 +57,6 @@ target_link_libraries(MaxPlasmaLights pfJournalBook)
target_link_libraries(MaxPlasmaLights pfLocalizationMgr)
target_link_libraries(MaxPlasmaLights pfMessage)
target_link_libraries(MaxPlasmaLights pfPython)
target_link_libraries(MaxPlasmaLights pfSecurePreloader)
target_link_libraries(MaxPlasmaLights pfSurface)
target_link_libraries(MaxPlasmaLights plAgeDescription)
target_link_libraries(MaxPlasmaLights plAgeLoader)

Loading…
Cancel
Save