mirror of
https://foundry.openuru.org/gitblit/r/CWE-ou-minkata.git
synced 2025-07-14 02:27:40 -04:00
Merge pull request #352 from Hoikas/patcher-thread
Unified Patcher (Part 1: In-Game)
This commit is contained in:
@ -86,7 +86,6 @@ target_link_libraries(plClient pfJournalBook)
|
|||||||
target_link_libraries(plClient pfLocalizationMgr)
|
target_link_libraries(plClient pfLocalizationMgr)
|
||||||
target_link_libraries(plClient pfMessage)
|
target_link_libraries(plClient pfMessage)
|
||||||
target_link_libraries(plClient pfPython)
|
target_link_libraries(plClient pfPython)
|
||||||
target_link_libraries(plClient pfSecurePreloader)
|
|
||||||
target_link_libraries(plClient pfSurface)
|
target_link_libraries(plClient pfSurface)
|
||||||
target_link_libraries(plClient plAgeDescription)
|
target_link_libraries(plClient plAgeDescription)
|
||||||
target_link_libraries(plClient plAgeLoader)
|
target_link_libraries(plClient plAgeLoader)
|
||||||
|
@ -73,7 +73,6 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
#include "pnMessage/plCameraMsg.h"
|
#include "pnMessage/plCameraMsg.h"
|
||||||
#include "plMessage/plTransitionMsg.h"
|
#include "plMessage/plTransitionMsg.h"
|
||||||
#include "plMessage/plLinkToAgeMsg.h"
|
#include "plMessage/plLinkToAgeMsg.h"
|
||||||
#include "plMessage/plPreloaderMsg.h"
|
|
||||||
#include "plMessage/plNetCommMsgs.h"
|
#include "plMessage/plNetCommMsgs.h"
|
||||||
#include "plMessage/plAgeLoadedMsg.h"
|
#include "plMessage/plAgeLoadedMsg.h"
|
||||||
#include "plMessage/plResPatcherMsg.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 "plNetCommon/plNetCommonConstants.h"
|
||||||
#include "plNetGameLib/plNetGameLib.h"
|
#include "plNetGameLib/plNetGameLib.h"
|
||||||
|
|
||||||
#include "pfSecurePreloader/pfSecurePreloader.h"
|
|
||||||
#include "pfLocalizationMgr/pfLocalizationMgr.h"
|
#include "pfLocalizationMgr/pfLocalizationMgr.h"
|
||||||
|
#include "pfPatcher/plManifests.h"
|
||||||
|
|
||||||
#include "plTweak.h"
|
#include "plTweak.h"
|
||||||
|
|
||||||
@ -311,11 +310,6 @@ bool plClient::Shutdown()
|
|||||||
plAgeLoader::GetInstance()->UnRegisterAs(kAgeLoader_KEY); // deletes instance
|
plAgeLoader::GetInstance()->UnRegisterAs(kAgeLoader_KEY); // deletes instance
|
||||||
plAgeLoader::SetInstance(nil);
|
plAgeLoader::SetInstance(nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pfSecurePreloader::GetInstance())
|
|
||||||
{
|
|
||||||
pfSecurePreloader::GetInstance()->Shutdown(); // will unregister itself
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fInputManager)
|
if (fInputManager)
|
||||||
{
|
{
|
||||||
@ -852,14 +846,6 @@ bool plClient::MsgReceive(plMessage* msg)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//============================================================================
|
|
||||||
// plPreloaderMsg
|
|
||||||
//============================================================================
|
|
||||||
if (plPreloaderMsg * preloaderMsg = plPreloaderMsg::ConvertNoRef(msg)) {
|
|
||||||
IHandlePreloaderMsg(preloaderMsg);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
// plResPatcherMsg
|
// plResPatcherMsg
|
||||||
//============================================================================
|
//============================================================================
|
||||||
@ -1196,7 +1182,7 @@ void plClient::IRoomLoaded(plSceneNode* node, bool hold)
|
|||||||
};
|
};
|
||||||
|
|
||||||
char name[256];
|
char name[256];
|
||||||
strcpy(name, &fProgressBar->GetTitle()[strlen("Loading ")]);
|
strcpy(name, &fProgressBar->GetTitle().c_str()[strlen("Loading ")]);
|
||||||
name[strlen(name)-3] = '\0';
|
name[strlen(name)-3] = '\0';
|
||||||
|
|
||||||
// Get the precalculated value for how many messages will be
|
// 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
|
#ifndef PLASMA_EXTERNAL_RELEASE
|
||||||
if (plDispatchLogBase::IsLogging())
|
if (plDispatchLogBase::IsLogging())
|
||||||
plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle(), "displaying messages");
|
plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle().c_str(), "displaying messages");
|
||||||
#endif // PLASMA_EXTERNAL_RELEASE
|
#endif // PLASMA_EXTERNAL_RELEASE
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -1349,7 +1335,7 @@ void plClient::IStartProgress( const char *title, float len )
|
|||||||
fProgressBar = plProgressMgr::GetInstance()->RegisterOperation(len, title, plProgressMgr::kNone, false, true);
|
fProgressBar = plProgressMgr::GetInstance()->RegisterOperation(len, title, plProgressMgr::kNone, false, true);
|
||||||
#ifndef PLASMA_EXTERNAL_RELEASE
|
#ifndef PLASMA_EXTERNAL_RELEASE
|
||||||
if (plDispatchLogBase::IsLogging())
|
if (plDispatchLogBase::IsLogging())
|
||||||
plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle(), "starting");
|
plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle().c_str(), "starting");
|
||||||
#endif // PLASMA_EXTERNAL_RELEASE
|
#endif // PLASMA_EXTERNAL_RELEASE
|
||||||
|
|
||||||
((plResManager*)hsgResMgr::ResMgr())->SetProgressBarProc(IReadKeyedObjCallback);
|
((plResManager*)hsgResMgr::ResMgr())->SetProgressBarProc(IReadKeyedObjCallback);
|
||||||
@ -1371,7 +1357,7 @@ void plClient::IStopProgress( void )
|
|||||||
{
|
{
|
||||||
#ifndef PLASMA_EXTERNAL_RELEASE
|
#ifndef PLASMA_EXTERNAL_RELEASE
|
||||||
if (plDispatchLogBase::IsLogging())
|
if (plDispatchLogBase::IsLogging())
|
||||||
plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle(), "done");
|
plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle().c_str(), "done");
|
||||||
#endif // PLASMA_EXTERNAL_RELEASE
|
#endif // PLASMA_EXTERNAL_RELEASE
|
||||||
|
|
||||||
plDispatch::SetMsgRecieveCallback(nil);
|
plDispatch::SetMsgRecieveCallback(nil);
|
||||||
@ -1564,8 +1550,7 @@ bool plClient::StartInit()
|
|||||||
plgDispatch::Dispatch()->RegisterForExactType(plNetCommAuthMsg::Index(), GetKey());
|
plgDispatch::Dispatch()->RegisterForExactType(plNetCommAuthMsg::Index(), GetKey());
|
||||||
plNetClientMgr::GetInstance()->Init();
|
plNetClientMgr::GetInstance()->Init();
|
||||||
plAgeLoader::GetInstance()->Init();
|
plAgeLoader::GetInstance()->Init();
|
||||||
pfSecurePreloader::GetInstance()->Init();
|
|
||||||
|
|
||||||
plCmdIfaceModMsg* pModMsg2 = new plCmdIfaceModMsg;
|
plCmdIfaceModMsg* pModMsg2 = new plCmdIfaceModMsg;
|
||||||
pModMsg2->SetBCastFlag(plMessage::kBCastByExactType);
|
pModMsg2->SetBCastFlag(plMessage::kBCastByExactType);
|
||||||
pModMsg2->SetSender(fConsole->GetKey());
|
pModMsg2->SetSender(fConsole->GetKey());
|
||||||
@ -1613,19 +1598,10 @@ bool plClient::StartInit()
|
|||||||
//============================================================================
|
//============================================================================
|
||||||
void plClient::IPatchGlobalAgeFiles( void )
|
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());
|
plgDispatch::Dispatch()->RegisterForExactType(plResPatcherMsg::Index(), GetKey());
|
||||||
patcher->Start();
|
|
||||||
|
plResPatcher* patcher = plResPatcher::GetInstance();
|
||||||
|
patcher->Update(plManifest::EssentialGameManifests());
|
||||||
}
|
}
|
||||||
|
|
||||||
void plClient::InitDLLs()
|
void plClient::InitDLLs()
|
||||||
@ -2524,33 +2500,12 @@ void plClient::ICompleteInit () {
|
|||||||
clientMsg->Send();
|
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) {
|
void plClient::IHandlePatcherMsg (plResPatcherMsg * msg) {
|
||||||
plgDispatch::Dispatch()->UnRegisterForExactType(plResPatcherMsg::Index(), GetKey());
|
plgDispatch::Dispatch()->UnRegisterForExactType(plResPatcherMsg::Index(), GetKey());
|
||||||
|
|
||||||
if (!msg->Success()) {
|
if (!msg->Success()) {
|
||||||
plNetClientApp::GetInstance()->QueueDisableNet(true, msg->GetError());
|
plNetClientApp::GetInstance()->QueueDisableNet(true, msg->GetError().c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2577,8 +2532,6 @@ void plClient::IHandleNetCommAuthMsg (plNetCommAuthMsg * msg) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
plgDispatch::Dispatch()->RegisterForExactType(plPreloaderMsg::Index(), GetKey());
|
// Patch them global files!
|
||||||
|
IPatchGlobalAgeFiles();
|
||||||
// Precache our secure files
|
|
||||||
pfSecurePreloader::GetInstance()->Start();
|
|
||||||
}
|
}
|
||||||
|
@ -181,7 +181,6 @@ protected:
|
|||||||
void ICompleteInit ();
|
void ICompleteInit ();
|
||||||
void IOnAsyncInitComplete ();
|
void IOnAsyncInitComplete ();
|
||||||
void IHandlePatcherMsg (plResPatcherMsg * msg);
|
void IHandlePatcherMsg (plResPatcherMsg * msg);
|
||||||
void IHandlePreloaderMsg (plPreloaderMsg * msg);
|
|
||||||
void IHandleNetCommAuthMsg (plNetCommAuthMsg * msg);
|
void IHandleNetCommAuthMsg (plNetCommAuthMsg * msg);
|
||||||
bool IHandleAgeLoaded2Msg (plAgeLoaded2Msg * msg);
|
bool IHandleAgeLoaded2Msg (plAgeLoaded2Msg * msg);
|
||||||
|
|
||||||
|
@ -350,7 +350,7 @@ public:
|
|||||||
virtual ~hsRAMStream();
|
virtual ~hsRAMStream();
|
||||||
|
|
||||||
virtual bool Open(const plFileName &, const char *) { hsAssert(0, "hsRAMStream::Open NotImplemented"); return false; }
|
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();
|
virtual bool AtEnd();
|
||||||
|
@ -96,6 +96,7 @@ public:
|
|||||||
virtual hsError Run() = 0; // override this to do your work
|
virtual hsError Run() = 0; // override this to do your work
|
||||||
virtual void Start(); // initializes stuff and calls your Run() method
|
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 Stop(); // sets fQuit = true and the waits for the thread to stop
|
||||||
|
virtual void OnQuit() { }
|
||||||
|
|
||||||
// Static functions
|
// Static functions
|
||||||
static void* Alloc(size_t size); // does not call operator::new(), may return nil
|
static void* Alloc(size_t size); // does not call operator::new(), may return nil
|
||||||
|
@ -76,6 +76,7 @@ extern "C" {
|
|||||||
pthread_mutex_lock(((hsThread*)param)->GetStartupMutex());
|
pthread_mutex_lock(((hsThread*)param)->GetStartupMutex());
|
||||||
void* ret = (void*)(uintptr_t)((hsThread*)param)->Run();
|
void* ret = (void*)(uintptr_t)((hsThread*)param)->Run();
|
||||||
pthread_mutex_unlock(((hsThread*)param)->GetStartupMutex());
|
pthread_mutex_unlock(((hsThread*)param)->GetStartupMutex());
|
||||||
|
((hsThread*)param)->OnQuit();
|
||||||
pthread_exit(ret);
|
pthread_exit(ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,7 @@ static unsigned int __stdcall gEntryPointBT(void* param)
|
|||||||
WinThreadParam* wtp = (WinThreadParam*)param;
|
WinThreadParam* wtp = (WinThreadParam*)param;
|
||||||
unsigned int result = wtp->fThread->Run();
|
unsigned int result = wtp->fThread->Run();
|
||||||
::ReleaseSemaphore(wtp->fQuitSemaH, 1, nil); // signal that we've quit
|
::ReleaseSemaphore(wtp->fQuitSemaH, 1, nil); // signal that we've quit
|
||||||
|
wtp->fThread->OnQuit();
|
||||||
delete param;
|
delete param;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -570,3 +570,27 @@ plFileName plFileSystem::GetTempFilename(const char *prefix, const plFileName &p
|
|||||||
return result;
|
return result;
|
||||||
#endif
|
#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]);
|
||||||
|
}
|
||||||
|
@ -338,6 +338,9 @@ namespace plFileSystem
|
|||||||
* system temp path is used.
|
* system temp path is used.
|
||||||
*/
|
*/
|
||||||
plFileName GetTempFilename(const char *prefix = "tmp", const plFileName &path = "");
|
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
|
#endif // plFileSystem_Defined
|
||||||
|
@ -17,6 +17,6 @@ add_subdirectory(pfJournalBook)
|
|||||||
# add_subdirectory(pfKI)
|
# add_subdirectory(pfKI)
|
||||||
add_subdirectory(pfLocalizationMgr)
|
add_subdirectory(pfLocalizationMgr)
|
||||||
add_subdirectory(pfMessage)
|
add_subdirectory(pfMessage)
|
||||||
|
add_subdirectory(pfPatcher)
|
||||||
add_subdirectory(pfPython)
|
add_subdirectory(pfPython)
|
||||||
add_subdirectory(pfSecurePreloader)
|
|
||||||
add_subdirectory(pfSurface)
|
add_subdirectory(pfSurface)
|
||||||
|
@ -57,6 +57,5 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
#include "pfCCR/plCCRCreatable.h"
|
#include "pfCCR/plCCRCreatable.h"
|
||||||
#include "pfJournalBook/pfJournalBookCreatable.h"
|
#include "pfJournalBook/pfJournalBookCreatable.h"
|
||||||
#include "pfGameMgr/pfGameMgrCreatables.h"
|
#include "pfGameMgr/pfGameMgrCreatables.h"
|
||||||
#include "pfSecurePreloader/pfSecurePreloaderCreatable.h"
|
|
||||||
|
|
||||||
#endif // pfAllCreatables_inc
|
#endif // pfAllCreatables_inc
|
||||||
|
19
Sources/Plasma/FeatureLib/pfPatcher/CMakeLists.txt
Normal file
19
Sources/Plasma/FeatureLib/pfPatcher/CMakeLists.txt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
include_directories("../../CoreLib")
|
||||||
|
include_directories("../../NucleusLib")
|
||||||
|
include_directories("../../NucleusLib/inc")
|
||||||
|
include_directories("../../PubUtilLib")
|
||||||
|
|
||||||
|
set(pfPatcher_SOURCES
|
||||||
|
plManifests.cpp
|
||||||
|
pfPatcher.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(pfPatcher_HEADERS
|
||||||
|
plManifests.h
|
||||||
|
pfPatcher.h
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(pfPatcher STATIC ${pfPatcher_SOURCES} ${pfPatcher_HEADERS})
|
||||||
|
|
||||||
|
source_group("Source Files" FILES ${pfPatcher_SOURCES})
|
||||||
|
source_group("Header Files" FILES ${pfPatcher_HEADERS})
|
598
Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp
Normal file
598
Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp
Normal file
@ -0,0 +1,598 @@
|
|||||||
|
/*==LICENSE==*
|
||||||
|
|
||||||
|
CyanWorlds.com Engine - MMOG client, server and tools
|
||||||
|
Copyright (C) 2011 Cyan Worlds, Inc.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Additional permissions under GNU GPL version 3 section 7
|
||||||
|
|
||||||
|
If you modify this Program, or any covered work, by linking or
|
||||||
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
||||||
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
||||||
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
||||||
|
(or a modified version of those libraries),
|
||||||
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
||||||
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
||||||
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
||||||
|
licensors of this Program grant you additional
|
||||||
|
permission to convey the resulting work. Corresponding Source for a
|
||||||
|
non-source form of such a combination shall include the source code for
|
||||||
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
||||||
|
work.
|
||||||
|
|
||||||
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||||||
|
or by snail mail at:
|
||||||
|
Cyan Worlds, Inc.
|
||||||
|
14617 N Newport Hwy
|
||||||
|
Mead, WA 99021
|
||||||
|
|
||||||
|
*==LICENSE==*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
#include "pfPatcher.h"
|
||||||
|
|
||||||
|
#include "HeadSpin.h"
|
||||||
|
#include "plCompression/plZlibStream.h"
|
||||||
|
#include "pnEncryption/plChecksum.h"
|
||||||
|
#include "plFileSystem.h"
|
||||||
|
#include "pnNetBase/pnNbError.h"
|
||||||
|
#include "plNetGameLib/plNetGameLib.h"
|
||||||
|
#include "plStatusLog/plStatusLog.h"
|
||||||
|
#include "hsStream.h"
|
||||||
|
#include "hsThread.h"
|
||||||
|
#include "hsTimer.h"
|
||||||
|
|
||||||
|
// Some log helper defines
|
||||||
|
#define PatcherLogGreen(...) pfPatcher::GetLog()->AddLineF(plStatusLog::kGreen, __VA_ARGS__)
|
||||||
|
#define PatcherLogRed(...) pfPatcher::GetLog()->AddLineF(plStatusLog::kRed, __VA_ARGS__)
|
||||||
|
#define PatcherLogWhite(...) pfPatcher::GetLog()->AddLineF(plStatusLog::kWhite, __VA_ARGS__)
|
||||||
|
#define PatcherLogYellow(...) pfPatcher::GetLog()->AddLineF(plStatusLog::kYellow, __VA_ARGS__)
|
||||||
|
|
||||||
|
/** Patcher grunt work thread */
|
||||||
|
struct pfPatcherWorker : public hsThread
|
||||||
|
{
|
||||||
|
/** Represents a File/Auth download request */
|
||||||
|
struct Request
|
||||||
|
{
|
||||||
|
enum { kFile, kManifest, kSecurePreloader, kAuthFile, kPythonList, kSdlList };
|
||||||
|
|
||||||
|
plString fName;
|
||||||
|
uint8_t fType;
|
||||||
|
class pfPatcherStream* fStream;
|
||||||
|
|
||||||
|
Request(const plString& name, uint8_t type, class pfPatcherStream* s=nullptr) :
|
||||||
|
fName(name), fType(type), fStream(s)
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Human readable file flags */
|
||||||
|
enum FileFlags
|
||||||
|
{
|
||||||
|
// Sound files only
|
||||||
|
kSndFlagCacheSplit = 1<<0,
|
||||||
|
kSndFlagStreamCompressed = 1<<1,
|
||||||
|
kSndFlagCacheStereo = 1<<2,
|
||||||
|
|
||||||
|
// Any file
|
||||||
|
kFlagZipped = 1<<3,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::deque<Request> fRequests;
|
||||||
|
std::deque<NetCliFileManifestEntry> fQueuedFiles;
|
||||||
|
|
||||||
|
hsMutex fRequestMut;
|
||||||
|
hsMutex fFileMut;
|
||||||
|
hsSemaphore fFileSignal;
|
||||||
|
|
||||||
|
pfPatcher::CompletionFunc fOnComplete;
|
||||||
|
pfPatcher::FileDownloadFunc fFileBeginDownload;
|
||||||
|
pfPatcher::FileDownloadFunc fFileDownloaded;
|
||||||
|
pfPatcher::GameCodeDiscoverFunc fGameCodeDiscovered;
|
||||||
|
pfPatcher::ProgressTickFunc fProgressTick;
|
||||||
|
|
||||||
|
pfPatcher* fParent;
|
||||||
|
volatile bool fStarted;
|
||||||
|
volatile bool fRequestActive;
|
||||||
|
|
||||||
|
uint64_t fCurrBytes;
|
||||||
|
uint64_t fTotalBytes;
|
||||||
|
|
||||||
|
pfPatcherWorker();
|
||||||
|
~pfPatcherWorker();
|
||||||
|
|
||||||
|
void OnQuit();
|
||||||
|
|
||||||
|
void EndPatch(ENetError result, const plString& msg=plString::Null);
|
||||||
|
bool IssueRequest();
|
||||||
|
virtual hsError Run();
|
||||||
|
void ProcessFile();
|
||||||
|
void WhitelistFile(const plFileName& file, bool justDownloaded, hsStream* s=nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===================================================
|
||||||
|
|
||||||
|
class pfPatcherStream : public plZlibStream
|
||||||
|
{
|
||||||
|
pfPatcherWorker* fParent;
|
||||||
|
plFileName fFilename;
|
||||||
|
uint32_t fFlags;
|
||||||
|
|
||||||
|
uint64_t fBytesWritten;
|
||||||
|
float fDLStartTime;
|
||||||
|
|
||||||
|
plString IMakeStatusMsg() const
|
||||||
|
{
|
||||||
|
float secs = hsTimer::GetSysSeconds() - fDLStartTime;
|
||||||
|
float bytesPerSec = fBytesWritten / secs;
|
||||||
|
return plFileSystem::ConvertFileSize(bytesPerSec) + "/s";
|
||||||
|
}
|
||||||
|
|
||||||
|
void IUpdateProgress(uint32_t count)
|
||||||
|
{
|
||||||
|
fBytesWritten += count; // just this file
|
||||||
|
fParent->fCurrBytes += count; // the entire everything
|
||||||
|
|
||||||
|
// tick-tick-tick, tick-tick-tock
|
||||||
|
if (fParent->fProgressTick)
|
||||||
|
fParent->fProgressTick(fParent->fCurrBytes, fParent->fTotalBytes, IMakeStatusMsg());
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
pfPatcherStream(pfPatcherWorker* parent, const plFileName& filename, uint64_t size)
|
||||||
|
: fParent(parent), fFilename(filename), fFlags(0), fBytesWritten(0)
|
||||||
|
{
|
||||||
|
fParent->fTotalBytes += size;
|
||||||
|
fOutput = new hsRAMStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
pfPatcherStream(pfPatcherWorker* parent, const plFileName& filename, const NetCliFileManifestEntry& entry)
|
||||||
|
: fParent(parent), fFlags(entry.flags), fBytesWritten(0)
|
||||||
|
{
|
||||||
|
// ugh. eap removed the compressed flag in his fail manifests
|
||||||
|
if (filename.GetFileExt().CompareI("gz") == 0) {
|
||||||
|
fFlags |= pfPatcherWorker::kFlagZipped;
|
||||||
|
parent->fTotalBytes += entry.zipSize;
|
||||||
|
} else
|
||||||
|
parent->fTotalBytes += entry.fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool Open(const plFileName& filename, const char* mode)
|
||||||
|
{
|
||||||
|
fFilename = filename;
|
||||||
|
return plZlibStream::Open(filename, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual uint32_t Write(uint32_t count, const void* buf)
|
||||||
|
{
|
||||||
|
// tick whatever progress bar we have
|
||||||
|
IUpdateProgress(count);
|
||||||
|
|
||||||
|
// write the appropriate blargs
|
||||||
|
if (hsCheckBits(fFlags, pfPatcherWorker::kFlagZipped))
|
||||||
|
return plZlibStream::Write(count, buf);
|
||||||
|
else
|
||||||
|
return fOutput->Write(count, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool AtEnd() { return fOutput->AtEnd(); }
|
||||||
|
virtual uint32_t GetEOF() { return fOutput->GetEOF(); }
|
||||||
|
virtual uint32_t GetPosition() const { return fOutput->GetPosition(); }
|
||||||
|
virtual uint32_t GetSizeLeft() const { return fOutput->GetSizeLeft(); }
|
||||||
|
virtual uint32_t Read(uint32_t count, void* buf) { return fOutput->Read(count, buf); }
|
||||||
|
virtual void Rewind() { fOutput->Rewind(); }
|
||||||
|
virtual void SetPosition(uint32_t pos) { fOutput->SetPosition(pos); }
|
||||||
|
virtual void Skip(uint32_t deltaByteCount) { fOutput->Skip(deltaByteCount); }
|
||||||
|
|
||||||
|
void Begin() { fDLStartTime = hsTimer::GetSysSeconds(); }
|
||||||
|
plFileName GetFileName() const { return fFilename; }
|
||||||
|
void Unlink() const { plFileSystem::Unlink(fFilename); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===================================================
|
||||||
|
|
||||||
|
static void IAuthThingDownloadCB(ENetError result, void* param, const plFileName& filename, hsStream* writer)
|
||||||
|
{
|
||||||
|
pfPatcherWorker* patcher = static_cast<pfPatcherWorker*>(param);
|
||||||
|
|
||||||
|
if (IS_NET_SUCCESS(result)) {
|
||||||
|
PatcherLogGreen("\tDownloaded Legacy File '%s'", filename.AsString().c_str());
|
||||||
|
patcher->IssueRequest();
|
||||||
|
|
||||||
|
// Now, we pass our RAM-backed file to the game code handlers. In the main client,
|
||||||
|
// this will trickle down and add a new friend to plStreamSource. This should never
|
||||||
|
// happen in any other app...
|
||||||
|
writer->Rewind();
|
||||||
|
patcher->WhitelistFile(filename, true, writer);
|
||||||
|
} else {
|
||||||
|
PatcherLogRed("\tDownloaded Failed: File '%s'", filename.AsString().c_str());
|
||||||
|
patcher->EndPatch(result, filename.AsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IGotAuthFileList(ENetError result, void* param, const NetCliAuthFileInfo infoArr[], unsigned infoCount)
|
||||||
|
{
|
||||||
|
pfPatcherWorker* patcher = static_cast<pfPatcherWorker*>(param);
|
||||||
|
|
||||||
|
if (IS_NET_SUCCESS(result)) {
|
||||||
|
// so everything goes directly into the Requests deque because AuthSrv lists
|
||||||
|
// don't have any hashes attached. WHY did eap think this was a good idea?!?!
|
||||||
|
{
|
||||||
|
hsTempMutexLock lock(patcher->fRequestMut);
|
||||||
|
for (unsigned i = 0; i < infoCount; ++i) {
|
||||||
|
PatcherLogYellow("\tEnqueuing Legacy File '%S'", infoArr[i].filename);
|
||||||
|
|
||||||
|
plFileName fn = plString::FromWchar(infoArr[i].filename);
|
||||||
|
plFileSystem::CreateDir(fn.StripFileName());
|
||||||
|
|
||||||
|
// We purposefully do NOT Open this stream! This uses a special auth-file constructor that
|
||||||
|
// utilizes a backing hsRAMStream. This will be fed to plStreamSource later...
|
||||||
|
pfPatcherStream* s = new pfPatcherStream(patcher, fn, infoArr[i].filesize);
|
||||||
|
pfPatcherWorker::Request req = pfPatcherWorker::Request(fn.AsString(), pfPatcherWorker::Request::kAuthFile, s);
|
||||||
|
patcher->fRequests.push_back(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
patcher->IssueRequest();
|
||||||
|
} else {
|
||||||
|
PatcherLogRed("\tSHIT! Some legacy manifest phailed");
|
||||||
|
patcher->EndPatch(result, "SecurePreloader failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IHandleManifestDownload(pfPatcherWorker* patcher, const wchar_t group[], const NetCliFileManifestEntry manifest[], unsigned entryCount)
|
||||||
|
{
|
||||||
|
PatcherLogGreen("\tDownloaded Manifest '%S'", group);
|
||||||
|
{
|
||||||
|
hsTempMutexLock lock(patcher->fFileMut);
|
||||||
|
for (unsigned i = 0; i < entryCount; ++i)
|
||||||
|
patcher->fQueuedFiles.push_back(manifest[i]);
|
||||||
|
patcher->fFileSignal.Signal();
|
||||||
|
}
|
||||||
|
patcher->IssueRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IPreloaderManifestDownloadCB(ENetError result, void* param, const wchar_t group[], const NetCliFileManifestEntry manifest[], unsigned entryCount)
|
||||||
|
{
|
||||||
|
pfPatcherWorker* patcher = static_cast<pfPatcherWorker*>(param);
|
||||||
|
|
||||||
|
if (IS_NET_SUCCESS(result))
|
||||||
|
IHandleManifestDownload(patcher, group, manifest, entryCount);
|
||||||
|
else {
|
||||||
|
PatcherLogYellow("\tWARNING: *** Falling back to AuthSrv file lists to get game code ***");
|
||||||
|
|
||||||
|
// so, we need to ask the AuthSrv about our game code
|
||||||
|
{
|
||||||
|
hsTempMutexLock lock(patcher->fRequestMut);
|
||||||
|
patcher->fRequests.push_back(pfPatcherWorker::Request(plString::Null, pfPatcherWorker::Request::kPythonList));
|
||||||
|
patcher->fRequests.push_back(pfPatcherWorker::Request(plString::Null, pfPatcherWorker::Request::kSdlList));
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue pumping requests
|
||||||
|
patcher->IssueRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IFileManifestDownloadCB(ENetError result, void* param, const wchar_t group[], const NetCliFileManifestEntry manifest[], unsigned entryCount)
|
||||||
|
{
|
||||||
|
pfPatcherWorker* patcher = static_cast<pfPatcherWorker*>(param);
|
||||||
|
|
||||||
|
if (IS_NET_SUCCESS(result))
|
||||||
|
IHandleManifestDownload(patcher, group, manifest, entryCount);
|
||||||
|
else {
|
||||||
|
PatcherLogRed("\tDownload Failed: Manifest '%S'", group);
|
||||||
|
patcher->EndPatch(result, plString::FromWchar(group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IFileThingDownloadCB(ENetError result, void* param, const plFileName& filename, hsStream* writer)
|
||||||
|
{
|
||||||
|
pfPatcherWorker* patcher = static_cast<pfPatcherWorker*>(param);
|
||||||
|
pfPatcherStream* stream = static_cast<pfPatcherStream*>(writer);
|
||||||
|
stream->Close();
|
||||||
|
|
||||||
|
if (IS_NET_SUCCESS(result)) {
|
||||||
|
PatcherLogGreen("\tDownloaded File '%s'", stream->GetFileName().AsString().c_str());
|
||||||
|
patcher->WhitelistFile(stream->GetFileName(), true);
|
||||||
|
patcher->IssueRequest();
|
||||||
|
} else {
|
||||||
|
PatcherLogRed("\tDownloaded Failed: File '%s'", stream->GetFileName().AsString().c_str());
|
||||||
|
stream->Unlink();
|
||||||
|
patcher->EndPatch(result, filename.AsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
delete stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================
|
||||||
|
|
||||||
|
pfPatcherWorker::pfPatcherWorker() :
|
||||||
|
fStarted(false), fCurrBytes(0), fTotalBytes(0), fRequestActive(true)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
pfPatcherWorker::~pfPatcherWorker()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
hsTempMutexLock lock(fRequestMut);
|
||||||
|
std::for_each(fRequests.begin(), fRequests.end(),
|
||||||
|
[] (const Request& req) {
|
||||||
|
if (req.fStream) req.fStream->Close();
|
||||||
|
delete req.fStream;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
fRequests.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
hsTempMutexLock lock(fFileMut);
|
||||||
|
fQueuedFiles.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pfPatcherWorker::OnQuit()
|
||||||
|
{
|
||||||
|
// the thread's Run() has exited sanely... now we can commit hara-kiri
|
||||||
|
delete fParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pfPatcherWorker::EndPatch(ENetError result, const plString& msg)
|
||||||
|
{
|
||||||
|
// Guard against multiple calls
|
||||||
|
if (fStarted) {
|
||||||
|
// Send end status
|
||||||
|
if (fOnComplete)
|
||||||
|
fOnComplete(result, msg);
|
||||||
|
|
||||||
|
// yay log hax
|
||||||
|
if (IS_NET_SUCCESS(result))
|
||||||
|
PatcherLogWhite("--- Patch Complete ---");
|
||||||
|
else {
|
||||||
|
PatcherLogRed("\tNetwork Error: %S", NetErrorToString(result));
|
||||||
|
PatcherLogWhite("--- Patch Killed by Error ---");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fStarted = false;
|
||||||
|
fFileSignal.Signal();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pfPatcherWorker::IssueRequest()
|
||||||
|
{
|
||||||
|
hsTempMutexLock lock(fRequestMut);
|
||||||
|
if (fRequests.empty()) {
|
||||||
|
fRequestActive = false;
|
||||||
|
fFileSignal.Signal(); // make sure the patch thread doesn't deadlock!
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
fRequestActive = true;
|
||||||
|
|
||||||
|
const Request& req = fRequests.front();
|
||||||
|
switch (req.fType) {
|
||||||
|
case Request::kFile:
|
||||||
|
req.fStream->Begin();
|
||||||
|
if (fFileBeginDownload)
|
||||||
|
fFileBeginDownload(req.fStream->GetFileName());
|
||||||
|
|
||||||
|
NetCliFileDownloadRequest(req.fName, req.fStream, IFileThingDownloadCB, this);
|
||||||
|
break;
|
||||||
|
case Request::kManifest:
|
||||||
|
NetCliFileManifestRequest(IFileManifestDownloadCB, this, req.fName.ToWchar());
|
||||||
|
break;
|
||||||
|
case Request::kSecurePreloader:
|
||||||
|
// so, yeah, this is usually the "SecurePreloader" manifest on the file server...
|
||||||
|
// except on legacy servers, this may not exist, so we need to fall back without nuking everything!
|
||||||
|
NetCliFileManifestRequest(IPreloaderManifestDownloadCB, this, req.fName.ToWchar());
|
||||||
|
break;
|
||||||
|
case Request::kAuthFile:
|
||||||
|
// ffffffuuuuuu
|
||||||
|
req.fStream->Begin();
|
||||||
|
if (fFileBeginDownload)
|
||||||
|
fFileBeginDownload(req.fStream->GetFileName());
|
||||||
|
|
||||||
|
NetCliAuthFileRequest(req.fName, req.fStream, IAuthThingDownloadCB, this);
|
||||||
|
break;
|
||||||
|
case Request::kPythonList:
|
||||||
|
NetCliAuthFileListRequest(L"Python", L"pak", IGotAuthFileList, this);
|
||||||
|
break;
|
||||||
|
case Request::kSdlList:
|
||||||
|
NetCliAuthFileListRequest(L"SDL", L"sdl", IGotAuthFileList, this);
|
||||||
|
break;
|
||||||
|
DEFAULT_FATAL(req.fType);
|
||||||
|
}
|
||||||
|
|
||||||
|
fRequests.pop_front();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hsError pfPatcherWorker::Run()
|
||||||
|
{
|
||||||
|
// So here's the rub:
|
||||||
|
// We have one or many manifests in the fRequests deque. We begin issuing those requests one-by one, starting here.
|
||||||
|
// As we receive the answer, the NetCli thread populates fQueuedFiles and pings the fFileSignal semaphore, then issues the next request...
|
||||||
|
// In this non-UI/non-Net thread, we do the stutter-prone/time-consuming IO/hashing operations. (Typically, the UI thread == Net thread)
|
||||||
|
// As we find files that need updating, we add them to fRequests.
|
||||||
|
// If there is no net request from ME when we find a file, we issue the request
|
||||||
|
// Once a file is downloaded, the next request is issued.
|
||||||
|
// When there are no files in my deque and no requests in my deque, we exit without errors.
|
||||||
|
|
||||||
|
PatcherLogWhite("--- Patch Started (%i requests) ---", fRequests.size());
|
||||||
|
fStarted = true;
|
||||||
|
IssueRequest();
|
||||||
|
|
||||||
|
// Now, work until we're done processing files
|
||||||
|
do {
|
||||||
|
fFileSignal.Wait();
|
||||||
|
|
||||||
|
hsTempMutexLock fileLock(fFileMut);
|
||||||
|
if (!fQueuedFiles.empty()) {
|
||||||
|
ProcessFile();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This makes sure both queues are empty before exiting.
|
||||||
|
if (!fRequestActive)
|
||||||
|
if(!IssueRequest())
|
||||||
|
break;
|
||||||
|
} while (fStarted);
|
||||||
|
|
||||||
|
EndPatch(kNetSuccess);
|
||||||
|
return hsOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pfPatcherWorker::ProcessFile()
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
const NetCliFileManifestEntry& entry = fQueuedFiles.front();
|
||||||
|
|
||||||
|
// eap sucks
|
||||||
|
plFileName clName = plString::FromWchar(entry.clientName);
|
||||||
|
plString dlName = plString::FromWchar(entry.downloadName);
|
||||||
|
|
||||||
|
// Check to see if ours matches
|
||||||
|
plFileInfo mine(clName);
|
||||||
|
if (mine.FileSize() == entry.fileSize) {
|
||||||
|
plMD5Checksum cliMD5(clName);
|
||||||
|
plMD5Checksum srvMD5;
|
||||||
|
srvMD5.SetFromHexString(plString::FromWchar(entry.md5, 32).c_str());
|
||||||
|
|
||||||
|
if (cliMD5 == srvMD5) {
|
||||||
|
WhitelistFile(clName, false);
|
||||||
|
fQueuedFiles.pop_front();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you got here, they're different.
|
||||||
|
PatcherLogYellow("\tEnqueuing '%S'", entry.clientName);
|
||||||
|
plFileSystem::CreateDir(plFileName(clName).StripFileName());
|
||||||
|
|
||||||
|
pfPatcherStream* s = new pfPatcherStream(this, dlName, entry);
|
||||||
|
s->Open(clName, "wb");
|
||||||
|
|
||||||
|
hsTempMutexLock lock(fRequestMut);
|
||||||
|
fRequests.push_back(Request(dlName, Request::kFile, s));
|
||||||
|
fQueuedFiles.pop_front();
|
||||||
|
|
||||||
|
if (!fRequestActive)
|
||||||
|
IssueRequest();
|
||||||
|
} while (!fQueuedFiles.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void pfPatcherWorker::WhitelistFile(const plFileName& file, bool justDownloaded, hsStream* stream)
|
||||||
|
{
|
||||||
|
// if this is a newly downloaded file, fire off a completion callback
|
||||||
|
if (justDownloaded && fFileDownloaded)
|
||||||
|
fFileDownloaded(file);
|
||||||
|
|
||||||
|
// we want to whitelist our game code, so here we go...
|
||||||
|
if (fGameCodeDiscovered) {
|
||||||
|
plString ext = file.GetFileExt();
|
||||||
|
if (ext.CompareI("pak") == 0 || ext.CompareI("sdl") == 0) {
|
||||||
|
if (!stream) {
|
||||||
|
stream = new hsUNIXStream;
|
||||||
|
stream->Open(file, "rb");
|
||||||
|
}
|
||||||
|
|
||||||
|
// if something terrible goes wrong (eg bad encryption), we can exit sanely
|
||||||
|
// callback eats stream
|
||||||
|
if (!fGameCodeDiscovered(file, stream))
|
||||||
|
EndPatch(kNetErrInternalError, "SecurePreloader failed.");
|
||||||
|
}
|
||||||
|
} else if (stream) {
|
||||||
|
// no dad gum memory leaks, m'kay?
|
||||||
|
stream->Close();
|
||||||
|
delete stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================
|
||||||
|
|
||||||
|
plStatusLog* pfPatcher::GetLog()
|
||||||
|
{
|
||||||
|
static plStatusLog* log = nullptr;
|
||||||
|
if (!log)
|
||||||
|
{
|
||||||
|
log = plStatusLogMgr::GetInstance().CreateStatusLog(
|
||||||
|
20,
|
||||||
|
"patcher.log",
|
||||||
|
plStatusLog::kFilledBackground | plStatusLog::kAlignToTop | plStatusLog::kDeleteForMe);
|
||||||
|
}
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
pfPatcher::pfPatcher() : fWorker(new pfPatcherWorker) { }
|
||||||
|
pfPatcher::~pfPatcher() { }
|
||||||
|
|
||||||
|
// ===================================================
|
||||||
|
|
||||||
|
void pfPatcher::OnCompletion(CompletionFunc cb)
|
||||||
|
{
|
||||||
|
fWorker->fOnComplete = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pfPatcher::OnFileDownloadBegin(FileDownloadFunc cb)
|
||||||
|
{
|
||||||
|
fWorker->fFileBeginDownload = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pfPatcher::OnFileDownloaded(FileDownloadFunc cb)
|
||||||
|
{
|
||||||
|
fWorker->fFileDownloaded = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pfPatcher::OnGameCodeDiscovery(GameCodeDiscoverFunc cb)
|
||||||
|
{
|
||||||
|
fWorker->fGameCodeDiscovered = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pfPatcher::OnProgressTick(ProgressTickFunc cb)
|
||||||
|
{
|
||||||
|
fWorker->fProgressTick = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================
|
||||||
|
|
||||||
|
void pfPatcher::RequestGameCode()
|
||||||
|
{
|
||||||
|
hsTempMutexLock lock(fWorker->fRequestMut);
|
||||||
|
fWorker->fRequests.push_back(pfPatcherWorker::Request("SecurePreloader", pfPatcherWorker::Request::kSecurePreloader));
|
||||||
|
}
|
||||||
|
|
||||||
|
void pfPatcher::RequestManifest(const plString& mfs)
|
||||||
|
{
|
||||||
|
hsTempMutexLock lock(fWorker->fRequestMut);
|
||||||
|
fWorker->fRequests.push_back(pfPatcherWorker::Request(mfs, pfPatcherWorker::Request::kManifest));
|
||||||
|
}
|
||||||
|
|
||||||
|
void pfPatcher::RequestManifest(const std::vector<plString>& mfs)
|
||||||
|
{
|
||||||
|
hsTempMutexLock lock(fWorker->fRequestMut);
|
||||||
|
std::for_each(mfs.begin(), mfs.end(),
|
||||||
|
[&] (const plString& name) {
|
||||||
|
fWorker->fRequests.push_back(pfPatcherWorker::Request(name, pfPatcherWorker::Request::kManifest));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pfPatcher::Start()
|
||||||
|
{
|
||||||
|
hsAssert(!fWorker->fStarted, "pfPatcher is one-use only. kthx.");
|
||||||
|
if (!fWorker->fStarted) {
|
||||||
|
fWorker->fParent = this; // wheeeee circular
|
||||||
|
fWorker->Start();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
125
Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h
Normal file
125
Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*==LICENSE==*
|
||||||
|
|
||||||
|
CyanWorlds.com Engine - MMOG client, server and tools
|
||||||
|
Copyright (C) 2011 Cyan Worlds, Inc.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Additional permissions under GNU GPL version 3 section 7
|
||||||
|
|
||||||
|
If you modify this Program, or any covered work, by linking or
|
||||||
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
||||||
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
||||||
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
||||||
|
(or a modified version of those libraries),
|
||||||
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
||||||
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
||||||
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
||||||
|
licensors of this Program grant you additional
|
||||||
|
permission to convey the resulting work. Corresponding Source for a
|
||||||
|
non-source form of such a combination shall include the source code for
|
||||||
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
||||||
|
work.
|
||||||
|
|
||||||
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||||||
|
or by snail mail at:
|
||||||
|
Cyan Worlds, Inc.
|
||||||
|
14617 N Newport Hwy
|
||||||
|
Mead, WA 99021
|
||||||
|
|
||||||
|
*==LICENSE==*/
|
||||||
|
|
||||||
|
#ifndef _pfPatcher_inc_
|
||||||
|
#define _pfPatcher_inc_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "plString.h"
|
||||||
|
#include "pnNetBase/pnNbError.h"
|
||||||
|
|
||||||
|
class plFileName;
|
||||||
|
class plStatusLog;
|
||||||
|
class hsStream;
|
||||||
|
|
||||||
|
/** Plasma File Patcher
|
||||||
|
* This is used to patch the client with one or many manifests at once. It assumes that
|
||||||
|
* we have permission to modify the game files, so be sure that you do! We memory manage
|
||||||
|
* ourselves, so allocate a new pfPatcher, add your manifests, and Start!
|
||||||
|
*/
|
||||||
|
class pfPatcher
|
||||||
|
{
|
||||||
|
std::unique_ptr<struct pfPatcherWorker> fWorker;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static plStatusLog* GetLog();
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Represents a function that takes the status and an optional message on completion. */
|
||||||
|
typedef std::function<void(ENetError, const plString&)> CompletionFunc;
|
||||||
|
|
||||||
|
/** Represents a function that takes (const plFileName&) on an interesting file operation. */
|
||||||
|
typedef std::function<void(const plFileName&)> FileDownloadFunc;
|
||||||
|
|
||||||
|
/** Represents a function that takes (const plFileName&, hsStream*) on game code discovery.
|
||||||
|
* You are responsible for closing and deleting the provided stream.
|
||||||
|
*/
|
||||||
|
typedef std::function<bool(const plFileName&, hsStream*)> GameCodeDiscoverFunc;
|
||||||
|
|
||||||
|
/** Represents a function that takes (bytesDLed, totalBytes, statsStr) as a progress indicator. */
|
||||||
|
typedef std::function<void(uint64_t, uint64_t, const plString&)> ProgressTickFunc;
|
||||||
|
|
||||||
|
pfPatcher();
|
||||||
|
~pfPatcher();
|
||||||
|
|
||||||
|
/** Set a callback that will be fired when the patcher finishes its dirty work.
|
||||||
|
* \remarks This may be called from any thread, so make sure your callback is
|
||||||
|
* thread safe!
|
||||||
|
*/
|
||||||
|
void OnCompletion(CompletionFunc cb);
|
||||||
|
|
||||||
|
/** Set a callback that will be fired when the patcher issues a download request to the server.
|
||||||
|
* \remarks This will be called from the network thread.
|
||||||
|
*/
|
||||||
|
void OnFileDownloadBegin(FileDownloadFunc cb);
|
||||||
|
|
||||||
|
/** Set a callback that will be fired when the patcher has finished downloading a file from the server.
|
||||||
|
* \remarks This will be called from the network thread.
|
||||||
|
*/
|
||||||
|
void OnFileDownloaded(FileDownloadFunc cb);
|
||||||
|
|
||||||
|
/** This is called when the patcher discovers an up-to-date Python package or SDL file.
|
||||||
|
* \remarks This can be called from any thread when the patcher downloads or encounters an up-to-date
|
||||||
|
* python package or SDL file that the server knows about.
|
||||||
|
*/
|
||||||
|
void OnGameCodeDiscovery(GameCodeDiscoverFunc cb);
|
||||||
|
|
||||||
|
/** Set a callback that will be fired when the patcher receives a chunk from the server. The status string
|
||||||
|
* will contain the current download speed.
|
||||||
|
* \remarks This will be called from the network thread.
|
||||||
|
*/
|
||||||
|
void OnProgressTick(ProgressTickFunc cb);
|
||||||
|
|
||||||
|
void RequestGameCode();
|
||||||
|
void RequestManifest(const plString& mfs);
|
||||||
|
void RequestManifest(const std::vector<plString>& mfs);
|
||||||
|
|
||||||
|
/** Start patching the requested manifests. Please note that after calling this, you should
|
||||||
|
* discard your pointer to this object as it will memory-manage itself.
|
||||||
|
*/
|
||||||
|
bool Start();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _pfPatcher_inc_
|
@ -33,25 +33,59 @@ the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
|||||||
work.
|
work.
|
||||||
|
|
||||||
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||||||
or by snail mail at:
|
or by snail mail at:
|
||||||
Cyan Worlds, Inc.
|
Cyan Worlds, Inc.
|
||||||
14617 N Newport Hwy
|
14617 N Newport Hwy
|
||||||
Mead, WA 99021
|
Mead, WA 99021
|
||||||
|
|
||||||
*==LICENSE==*/
|
*==LICENSE==*/
|
||||||
/*****************************************************************************
|
|
||||||
*
|
|
||||||
* $/Plasma20/Sources/Plasma/FeatureLib/pfSecurePreloader/pfSecurePreloaderCreatable.h
|
|
||||||
*
|
|
||||||
***/
|
|
||||||
|
|
||||||
#ifndef PLASMA20_SOURCES_PLASMA_FEATURELIB_PFSECUREPRELOADER_PFSECUREPRELOADERCREATABLE_H
|
#include "plManifests.h"
|
||||||
#define PLASMA20_SOURCES_PLASMA_FEATURELIB_PFSECUREPRELOADER_PFSECUREPRELOADERCREATABLE_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"
|
plFileName plManifest::ClientExecutable()
|
||||||
REGISTER_NONCREATABLE(pfSecurePreloader);
|
{
|
||||||
|
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
|
|
@ -39,29 +39,34 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
Mead, WA 99021
|
Mead, WA 99021
|
||||||
|
|
||||||
*==LICENSE==*/
|
*==LICENSE==*/
|
||||||
/*****************************************************************************
|
|
||||||
*
|
|
||||||
* $/Plasma20/Sources/Plasma/PubUtilLib/plMessage/plPreloaderMsg.h
|
|
||||||
*
|
|
||||||
***/
|
|
||||||
|
|
||||||
#ifndef PLASMA20_SOURCES_PLASMA_PUBUTILLIB_PLMESSAGE_PLPRELOADERMSG_H
|
#ifndef _plManifests_inc_
|
||||||
#define PLASMA20_SOURCES_PLASMA_PUBUTILLIB_PLMESSAGE_PLPRELOADERMSG_H
|
#define _plManifests_inc_
|
||||||
|
|
||||||
#include "pnMessage/plMessage.h"
|
#include <vector>
|
||||||
|
|
||||||
class plPreloaderMsg : public plMessage {
|
class plFileName;
|
||||||
public:
|
class plString;
|
||||||
bool fSuccess;
|
|
||||||
|
|
||||||
plPreloaderMsg () { SetBCastFlag(kBCastByExactType); }
|
namespace plManifest
|
||||||
|
{
|
||||||
CLASSNAME_REGISTER(plPreloaderMsg);
|
/** Get the name of the client executable for this build type.*/
|
||||||
GETINTERFACE_ANY(plPreloaderMsg, plMessage);
|
plFileName ClientExecutable();
|
||||||
|
|
||||||
void Read (hsStream* stream, hsResMgr* ) { FATAL("plPreloaderMsg::Read"); }
|
/** Get the name of the patcher executable for this build type.*/
|
||||||
void Write (hsStream* stream, hsResMgr* ) { FATAL("plPreloaderMsg::Write"); }
|
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_
|
@ -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__
|
|
@ -84,7 +84,7 @@ CLASS_INDEX_LIST_START
|
|||||||
CLASS_INDEX(plModifier),
|
CLASS_INDEX(plModifier),
|
||||||
CLASS_INDEX(plSingleModifier),
|
CLASS_INDEX(plSingleModifier),
|
||||||
CLASS_INDEX(plSimpleModifier),
|
CLASS_INDEX(plSimpleModifier),
|
||||||
CLASS_INDEX(pfSecurePreloader),
|
CLASS_INDEX(UNUSED_pfSecurePreloader),
|
||||||
CLASS_INDEX(UNUSED_plRandomTMModifier),
|
CLASS_INDEX(UNUSED_plRandomTMModifier),
|
||||||
CLASS_INDEX(plInterestingModifier),
|
CLASS_INDEX(plInterestingModifier),
|
||||||
CLASS_INDEX(plDetectorModifier),
|
CLASS_INDEX(plDetectorModifier),
|
||||||
@ -725,7 +725,7 @@ CLASS_INDEX_LIST_START
|
|||||||
CLASS_INDEX(plAvBrainDrive),
|
CLASS_INDEX(plAvBrainDrive),
|
||||||
CLASS_INDEX(plAvBrainSample),
|
CLASS_INDEX(plAvBrainSample),
|
||||||
CLASS_INDEX(plAvBrainGeneric),
|
CLASS_INDEX(plAvBrainGeneric),
|
||||||
CLASS_INDEX(plPreloaderMsg),
|
CLASS_INDEX(UNUSED_plPreloaderMsg),
|
||||||
CLASS_INDEX(plAvBrainLadder),
|
CLASS_INDEX(plAvBrainLadder),
|
||||||
CLASS_INDEX(plInputIfaceMgrMsg),
|
CLASS_INDEX(plInputIfaceMgrMsg),
|
||||||
CLASS_INDEX(pfKIMsg),
|
CLASS_INDEX(pfKIMsg),
|
||||||
|
@ -118,8 +118,6 @@ plKeySeed SeedList[] = {
|
|||||||
{ kJournalBookMgr_KEY, CLASS_INDEX_SCOPED( pfJournalBook ), "kJournalBookMgr_KEY", },
|
{ kJournalBookMgr_KEY, CLASS_INDEX_SCOPED( pfJournalBook ), "kJournalBookMgr_KEY", },
|
||||||
{ kAgeLoader_KEY, CLASS_INDEX_SCOPED( plAgeLoader), "kAgeLoader_KEY", },
|
{ kAgeLoader_KEY, CLASS_INDEX_SCOPED( plAgeLoader), "kAgeLoader_KEY", },
|
||||||
{ kBuiltIn3rdPersonCamera_KEY, CLASS_INDEX_SCOPED( plCameraModifier1 ), "kBuiltIn3rdPersonCamera_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", }
|
{ kLast_Fixed_KEY, CLASS_INDEX_SCOPED( plSceneObject ), "kLast_Fixed_KEY", }
|
||||||
};
|
};
|
||||||
|
@ -85,7 +85,6 @@ enum plFixedKeyId
|
|||||||
kJournalBookMgr_KEY,
|
kJournalBookMgr_KEY,
|
||||||
kAgeLoader_KEY,
|
kAgeLoader_KEY,
|
||||||
kBuiltIn3rdPersonCamera_KEY,
|
kBuiltIn3rdPersonCamera_KEY,
|
||||||
kSecurePreloader_KEY,
|
|
||||||
|
|
||||||
kLast_Fixed_KEY
|
kLast_Fixed_KEY
|
||||||
};
|
};
|
||||||
|
@ -18,5 +18,7 @@ set(plAgeLoader_HEADERS
|
|||||||
|
|
||||||
add_library(plAgeLoader STATIC ${plAgeLoader_SOURCES} ${plAgeLoader_HEADERS})
|
add_library(plAgeLoader STATIC ${plAgeLoader_SOURCES} ${plAgeLoader_HEADERS})
|
||||||
|
|
||||||
|
target_link_libraries(plAgeLoader pfPatcher)
|
||||||
|
|
||||||
source_group("Source Files" FILES ${plAgeLoader_SOURCES})
|
source_group("Source Files" FILES ${plAgeLoader_SOURCES})
|
||||||
source_group("Header Files" FILES ${plAgeLoader_HEADERS})
|
source_group("Header Files" FILES ${plAgeLoader_HEADERS})
|
||||||
|
@ -69,15 +69,13 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
#include "plResMgr/plRegistryNode.h"
|
#include "plResMgr/plRegistryNode.h"
|
||||||
#include "plResMgr/plResManager.h"
|
#include "plResMgr/plResManager.h"
|
||||||
#include "plFile/plEncryptedStream.h"
|
#include "plFile/plEncryptedStream.h"
|
||||||
|
#include "plProgressMgr/plProgressMgr.h"
|
||||||
|
|
||||||
/// TEMP HACK TO LOAD CONSOLE INIT FILES ON AGE LOAD
|
/// TEMP HACK TO LOAD CONSOLE INIT FILES ON AGE LOAD
|
||||||
#include "plMessage/plConsoleMsg.h"
|
#include "plMessage/plConsoleMsg.h"
|
||||||
#include "plMessage/plLoadAvatarMsg.h"
|
#include "plMessage/plLoadAvatarMsg.h"
|
||||||
#include "plMessage/plAgeLoadedMsg.h"
|
#include "plMessage/plAgeLoadedMsg.h"
|
||||||
|
|
||||||
|
|
||||||
extern bool gDataServerLocal;
|
|
||||||
|
|
||||||
// static
|
// static
|
||||||
plAgeLoader* plAgeLoader::fInstance=nil;
|
plAgeLoader* plAgeLoader::fInstance=nil;
|
||||||
|
|
||||||
@ -119,6 +117,7 @@ void plAgeLoader::Init()
|
|||||||
RegisterAs( kAgeLoader_KEY );
|
RegisterAs( kAgeLoader_KEY );
|
||||||
plgDispatch::Dispatch()->RegisterForExactType(plInitialAgeStateLoadedMsg::Index(), GetKey());
|
plgDispatch::Dispatch()->RegisterForExactType(plInitialAgeStateLoadedMsg::Index(), GetKey());
|
||||||
plgDispatch::Dispatch()->RegisterForExactType(plClientMsg::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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sadface thread protection
|
||||||
|
if (plResPatcherMsg::ConvertNoRef(msg)) {
|
||||||
|
delete plResPatcher::GetInstance()->fProgress;
|
||||||
|
plResPatcher::GetInstance()->fProgress = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
return plReceiver::MsgReceive(msg);
|
return plReceiver::MsgReceive(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,14 +177,7 @@ bool plAgeLoader::LoadAge(const char ageName[])
|
|||||||
//============================================================================
|
//============================================================================
|
||||||
void plAgeLoader::UpdateAge(const char ageName[])
|
void plAgeLoader::UpdateAge(const char ageName[])
|
||||||
{
|
{
|
||||||
if (gDataServerLocal)
|
plResPatcher::GetInstance()->Update(ageName);
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
|
@ -42,153 +42,23 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
|
|
||||||
#include "plResPatcher.h"
|
#include "plResPatcher.h"
|
||||||
#include "hsResMgr.h"
|
#include "hsResMgr.h"
|
||||||
|
#include "plgDispatch.h"
|
||||||
|
|
||||||
#include "plAgeLoader/plAgeLoader.h"
|
#include "plAgeLoader/plAgeLoader.h"
|
||||||
#include "plCompression/plZlibStream.h"
|
#include "plFile/plEncryptedStream.h"
|
||||||
#include "pnEncryption/plChecksum.h"
|
#include "plFile/plStreamSource.h"
|
||||||
|
#include "plFile/plSecureStream.h"
|
||||||
#include "plMessage/plResPatcherMsg.h"
|
#include "plMessage/plResPatcherMsg.h"
|
||||||
#include "pnNetBase/pnNbError.h"
|
#include "pfPatcher/pfPatcher.h"
|
||||||
#include "plNetGameLib/plNetGameLib.h"
|
|
||||||
#include "plProgressMgr/plProgressMgr.h"
|
#include "plProgressMgr/plProgressMgr.h"
|
||||||
#include "plResMgr/plResManager.h"
|
#include "plResMgr/plResManager.h"
|
||||||
#include "plStatusLog/plStatusLog.h"
|
|
||||||
|
extern bool gDataServerLocal;
|
||||||
|
bool gSkipPreload = false;
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
class plResDownloadStream : public plZlibStream
|
plResPatcher* plResPatcher::fInstance = nullptr;
|
||||||
{
|
|
||||||
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); }
|
|
||||||
};
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
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::GetInstance()
|
plResPatcher* plResPatcher::GetInstance()
|
||||||
{
|
{
|
||||||
@ -199,134 +69,125 @@ plResPatcher* plResPatcher::GetInstance()
|
|||||||
|
|
||||||
void plResPatcher::Shutdown()
|
void plResPatcher::Shutdown()
|
||||||
{
|
{
|
||||||
// Better not call this while we're patching
|
|
||||||
delete fInstance;
|
delete fInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
plResPatcher::plResPatcher()
|
void plResPatcher::OnCompletion(ENetError result, const plString& status)
|
||||||
: fPatching(false), fProgress(nil) { }
|
|
||||||
|
|
||||||
plResPatcher::~plResPatcher()
|
|
||||||
{
|
{
|
||||||
if (fProgress)
|
plString error = plString::Null;
|
||||||
delete fProgress;
|
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;
|
fProgress->SetTitle(plString::Format("Downloading %s...", file.GetFileName().c_str()));
|
||||||
if (fRequests.empty())
|
|
||||||
// Wheee!
|
|
||||||
Finish();
|
|
||||||
else {
|
|
||||||
Request req = fRequests.front();
|
|
||||||
fRequests.pop();
|
|
||||||
|
|
||||||
plString title;
|
if (file.GetFileExt().CompareI("prp") == 0) {
|
||||||
if (req.fType == kManifest)
|
plResManager* mgr = static_cast<plResManager*>(hsgResMgr::ResMgr());
|
||||||
{
|
if (mgr)
|
||||||
PatcherLog(kMajorStatus, " Downloading manifest... %s", req.fFile.AsString().c_str());
|
mgr->RemoveSinglePage(file);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void plResPatcher::Finish(bool success)
|
void plResPatcher::OnFileDownloaded(const plFileName& file)
|
||||||
{
|
{
|
||||||
while (fRequests.size())
|
if (file.GetFileExt().CompareI("prp") == 0) {
|
||||||
fRequests.pop();
|
plResManager* mgr = static_cast<plResManager*>(hsgResMgr::ResMgr());
|
||||||
|
if (mgr)
|
||||||
fPatching = false;
|
mgr->AddSinglePage(file);
|
||||||
if (success)
|
|
||||||
PatcherLog(kHeader, "--- Patch Completed Successfully ---");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PatcherLog(kHeader, "--- Patch Killed by Error ---");
|
|
||||||
if (fProgress)
|
|
||||||
fProgress->SetAborting();
|
|
||||||
}
|
}
|
||||||
delete fProgress; fProgress = nil;
|
|
||||||
|
|
||||||
plResPatcherMsg* pMsg = new plResPatcherMsg(success, sLastError);
|
|
||||||
delete[] sLastError; sLastError = nil;
|
|
||||||
pMsg->Send(); // whoosh... off it goes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void plResPatcher::RequestFile(const plFileName& srvName, const plFileName& cliName)
|
bool plResPatcher::OnGameCodeDiscovered(const plFileName& file, hsStream* stream)
|
||||||
{
|
{
|
||||||
fRequests.push(Request(srvName, kFile, cliName));
|
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);
|
||||||
|
|
||||||
|
return true; // ASSume success for now...
|
||||||
}
|
}
|
||||||
|
|
||||||
void plResPatcher::RequestManifest(const plString& age)
|
void plResPatcher::OnProgressTick(uint64_t dl, uint64_t total, const plString& msg)
|
||||||
{
|
{
|
||||||
fRequests.push(Request(age, kManifest));
|
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::Start()
|
pfPatcher* plResPatcher::CreatePatcher()
|
||||||
{
|
{
|
||||||
hsAssert(!fPatching, "Too many calls to plResPatcher::Start");
|
pfPatcher* patcher = new pfPatcher;
|
||||||
fPatching = true;
|
patcher->OnCompletion(std::bind(&plResPatcher::OnCompletion, this, std::placeholders::_1, std::placeholders::_2));
|
||||||
PatcherLog(kHeader, "--- Patch Started (%i requests) ---", fRequests.size());
|
patcher->OnFileDownloadBegin(std::bind(&plResPatcher::OnFileDownloadBegin, this, std::placeholders::_1));
|
||||||
fProgress = plProgressMgr::GetInstance()->RegisterOperation(0.0, "Checking for updates...",
|
patcher->OnFileDownloaded(std::bind(&plResPatcher::OnFileDownloaded, this, std::placeholders::_1));
|
||||||
plProgressMgr::kUpdateText, false, true);
|
patcher->OnProgressTick(std::bind(&plResPatcher::OnProgressTick, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||||
IssueRequest();
|
|
||||||
|
// 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::InitProgress()
|
||||||
|
{
|
||||||
|
// 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;
|
delete fProgress;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static plStatusLog* gStatusLog = nil;
|
|
||||||
if (!gStatusLog)
|
|
||||||
{
|
|
||||||
gStatusLog = plStatusLogMgr::GetInstance().CreateStatusLog(
|
|
||||||
20,
|
|
||||||
"patcher.log",
|
|
||||||
plStatusLog::kFilledBackground | plStatusLog::kAlignToTop | plStatusLog::kDeleteForMe);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -39,57 +39,49 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
Mead, WA 99021
|
Mead, WA 99021
|
||||||
|
|
||||||
*==LICENSE==*/
|
*==LICENSE==*/
|
||||||
|
|
||||||
#ifndef plResPatcher_h_inc
|
#ifndef plResPatcher_h_inc
|
||||||
#define plResPatcher_h_inc
|
#define plResPatcher_h_inc
|
||||||
|
|
||||||
#include "HeadSpin.h"
|
|
||||||
#include "plFileSystem.h"
|
#include "plFileSystem.h"
|
||||||
#include <queue>
|
#include "pnNetBase/pnNbError.h"
|
||||||
#include <string>
|
#include <vector>
|
||||||
|
|
||||||
class plOperationProgress;
|
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
|
class plResPatcher
|
||||||
{
|
{
|
||||||
enum { kManifest, kFile };
|
plOperationProgress* fProgress;
|
||||||
struct Request
|
static plResPatcher* fInstance;
|
||||||
{
|
bool fRequestedGameCode;
|
||||||
plFileName fFile;
|
|
||||||
plFileName fFriendlyName;
|
|
||||||
uint8_t fType;
|
|
||||||
|
|
||||||
Request(const plFileName& file, uint8_t type, const plFileName& friendly = "")
|
friend class plAgeLoader;
|
||||||
: fFile(file), fFriendlyName(friendly), fType(type) { }
|
|
||||||
};
|
|
||||||
|
|
||||||
static plResPatcher* fInstance;
|
void OnCompletion(ENetError, const plString& msg);
|
||||||
std::queue<Request> fRequests;
|
void OnFileDownloadBegin(const plFileName& file);
|
||||||
plOperationProgress* fProgress;
|
void OnFileDownloaded(const plFileName& file);
|
||||||
bool fPatching;
|
bool OnGameCodeDiscovered(const plFileName& file, class hsStream* stream);
|
||||||
|
void OnProgressTick(uint64_t dl, uint64_t total, const plString& msg);
|
||||||
|
|
||||||
plResPatcher();
|
class pfPatcher* CreatePatcher();
|
||||||
~plResPatcher();
|
void InitProgress();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static plResPatcher* GetInstance();
|
static plResPatcher* GetInstance();
|
||||||
static void Shutdown();
|
static void Shutdown();
|
||||||
|
|
||||||
plOperationProgress* GetProgress() { return fProgress; }
|
public:
|
||||||
|
plResPatcher();
|
||||||
|
~plResPatcher();
|
||||||
|
|
||||||
void Finish(bool success = true);
|
void Update(const std::vector<plString>& manifests);
|
||||||
void IssueRequest();
|
void Update(const plString& manifest);
|
||||||
void RequestFile(const plFileName& file, const plFileName& friendlyName);
|
|
||||||
void RequestManifest(const plString& age);
|
|
||||||
void Start();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum PatcherLogType
|
|
||||||
{
|
|
||||||
kHeader,
|
|
||||||
kInfo,
|
|
||||||
kMajorStatus,
|
|
||||||
kStatus,
|
|
||||||
kError,
|
|
||||||
};
|
|
||||||
void PatcherLog(PatcherLogType type, const char* format, ...);
|
|
||||||
#endif // _plResPatcher_h
|
#endif // _plResPatcher_h
|
||||||
|
@ -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 */)
|
hsStream* plSecureStream::OpenSecureFile(const plFileName& fileName, const uint32_t flags /* = kRequireEncryption */, uint32_t* key /* = nil */)
|
||||||
{
|
{
|
||||||
bool requireEncryption = flags & kRequireEncryption;
|
bool requireEncryption = flags & kRequireEncryption;
|
||||||
#ifndef PLASMA_EXTERNAL_RELEASE
|
|
||||||
requireEncryption = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool deleteOnExit = flags & kDeleteOnExit;
|
bool deleteOnExit = flags & kDeleteOnExit;
|
||||||
bool isEncrypted = IsSecureFile(fileName);
|
bool isEncrypted = IsSecureFile(fileName);
|
||||||
|
|
||||||
hsStream* s = nil;
|
hsStream* s = nullptr;
|
||||||
if (isEncrypted)
|
if (isEncrypted)
|
||||||
s = new plSecureStream(deleteOnExit, key);
|
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;
|
s = new hsUNIXStream;
|
||||||
|
|
||||||
if (s)
|
if (s)
|
||||||
|
@ -40,7 +40,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
|
|
||||||
*==LICENSE==*/
|
*==LICENSE==*/
|
||||||
|
|
||||||
#include <string>
|
#include "HeadSpin.h"
|
||||||
#include "plStreamSource.h"
|
#include "plStreamSource.h"
|
||||||
#include "plSecureStream.h"
|
#include "plSecureStream.h"
|
||||||
#include "plEncryptedStream.h"
|
#include "plEncryptedStream.h"
|
||||||
@ -49,8 +49,15 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
# include <wctype.h>
|
# include <wctype.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
plStreamSource::plStreamSource()
|
||||||
|
{
|
||||||
|
memset(fServerKey, 0, arrsize(fServerKey));
|
||||||
|
}
|
||||||
|
|
||||||
void plStreamSource::ICleanup()
|
void plStreamSource::ICleanup()
|
||||||
{
|
{
|
||||||
|
hsTempMutexLock lock(fMutex);
|
||||||
|
|
||||||
// loop through all the file data records, and delete the streams
|
// loop through all the file data records, and delete the streams
|
||||||
decltype(fFileData.begin()) curData;
|
decltype(fFileData.begin()) curData;
|
||||||
for (curData = fFileData.begin(); curData != fFileData.end(); curData++)
|
for (curData = fFileData.begin(); curData != fFileData.end(); curData++)
|
||||||
@ -65,6 +72,8 @@ void plStreamSource::ICleanup()
|
|||||||
|
|
||||||
hsStream* plStreamSource::GetFile(const plFileName& filename)
|
hsStream* plStreamSource::GetFile(const plFileName& filename)
|
||||||
{
|
{
|
||||||
|
hsTempMutexLock lock(fMutex);
|
||||||
|
|
||||||
plFileName sFilename = filename.Normalize('/');
|
plFileName sFilename = filename.Normalize('/');
|
||||||
if (fFileData.find(sFilename) == fFileData.end())
|
if (fFileData.find(sFilename) == fFileData.end())
|
||||||
{
|
{
|
||||||
@ -78,14 +87,15 @@ hsStream* plStreamSource::GetFile(const plFileName& filename)
|
|||||||
fFileData[sFilename].fExt = sFilename.GetFileExt();
|
fFileData[sFilename].fExt = sFilename.GetFileExt();
|
||||||
if (plSecureStream::IsSecureFile(filename))
|
if (plSecureStream::IsSecureFile(filename))
|
||||||
{
|
{
|
||||||
uint32_t encryptionKey[4];
|
hsStream* ss = nullptr;
|
||||||
if (!plSecureStream::GetSecureEncryptionKey(filename, encryptionKey, 4))
|
|
||||||
{
|
|
||||||
FATAL("Hey camper... You need an NTD key file!");
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
else // otherwise it is an encrypted or plain stream, this call handles both
|
||||||
fFileData[sFilename].fStream = plEncryptedStream::OpenEncryptedFile(filename);
|
fFileData[sFilename].fStream = plEncryptedStream::OpenEncryptedFile(filename);
|
||||||
@ -102,6 +112,7 @@ std::vector<plFileName> plStreamSource::GetListOfNames(const plFileName& dir, co
|
|||||||
{
|
{
|
||||||
plFileName sDir = dir.Normalize('/');
|
plFileName sDir = dir.Normalize('/');
|
||||||
hsAssert(ext.CharAt(0) != '.', "Don't add a dot");
|
hsAssert(ext.CharAt(0) != '.', "Don't add a dot");
|
||||||
|
hsTempMutexLock lock(fMutex);
|
||||||
|
|
||||||
// loop through all the file data records, and create the list
|
// loop through all the file data records, and create the list
|
||||||
std::vector<plFileName> retVal;
|
std::vector<plFileName> retVal;
|
||||||
@ -131,6 +142,7 @@ bool plStreamSource::InsertFile(const plFileName& filename, hsStream* stream)
|
|||||||
{
|
{
|
||||||
plFileName sFilename = filename.Normalize('/');
|
plFileName sFilename = filename.Normalize('/');
|
||||||
|
|
||||||
|
hsTempMutexLock lock(fMutex);
|
||||||
if (fFileData.find(sFilename) != fFileData.end())
|
if (fFileData.find(sFilename) != fFileData.end())
|
||||||
return false; // duplicate entry, return failure
|
return false; // duplicate entry, return failure
|
||||||
|
|
||||||
|
@ -43,8 +43,8 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
#define plStreamSource_h_inc
|
#define plStreamSource_h_inc
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
|
||||||
#include "hsStream.h"
|
#include "hsStream.h"
|
||||||
|
#include "hsThread.h"
|
||||||
|
|
||||||
// A class for holding and accessing file streams. The preloader will insert
|
// 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
|
// 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
|
hsStream* fStream; // we own this pointer, so clean it up
|
||||||
};
|
};
|
||||||
std::map<plFileName, fileData, plFileName::less_i> fFileData; // key is filename
|
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
|
void ICleanup(); // closes all file pointers and cleans up after itself
|
||||||
|
|
||||||
plStreamSource() {}
|
plStreamSource();
|
||||||
public:
|
public:
|
||||||
~plStreamSource() {ICleanup();}
|
~plStreamSource() {ICleanup();}
|
||||||
|
|
||||||
@ -77,6 +79,9 @@ public:
|
|||||||
// For other classes to insert files (takes ownership of the stream if successful)
|
// For other classes to insert files (takes ownership of the stream if successful)
|
||||||
bool InsertFile(const plFileName& filename, hsStream* stream);
|
bool InsertFile(const plFileName& filename, hsStream* stream);
|
||||||
|
|
||||||
|
/** Gets a pointer to our encryption key */
|
||||||
|
uint32_t* GetEncryptionKey() { return fServerKey; }
|
||||||
|
|
||||||
// Instance handling
|
// Instance handling
|
||||||
static plStreamSource* GetInstance();
|
static plStreamSource* GetInstance();
|
||||||
};
|
};
|
||||||
|
@ -98,7 +98,6 @@ set(plMessage_HEADERS
|
|||||||
plOneShotMsg.h
|
plOneShotMsg.h
|
||||||
plParticleUpdateMsg.h
|
plParticleUpdateMsg.h
|
||||||
plPickedMsg.h
|
plPickedMsg.h
|
||||||
plPreloaderMsg.h
|
|
||||||
plRenderMsg.h
|
plRenderMsg.h
|
||||||
plRenderRequestMsg.h
|
plRenderRequestMsg.h
|
||||||
plReplaceGeometryMsg.h
|
plReplaceGeometryMsg.h
|
||||||
|
@ -319,9 +319,6 @@ REGISTER_CREATABLE(plNetCommPublicAgeListMsg);
|
|||||||
REGISTER_CREATABLE(plNetCommPublicAgeMsg);
|
REGISTER_CREATABLE(plNetCommPublicAgeMsg);
|
||||||
REGISTER_CREATABLE(plNetCommRegisterAgeMsg);
|
REGISTER_CREATABLE(plNetCommRegisterAgeMsg);
|
||||||
|
|
||||||
#include "plPreloaderMsg.h"
|
|
||||||
REGISTER_CREATABLE(plPreloaderMsg);
|
|
||||||
|
|
||||||
#include "plNetClientMgrMsg.h"
|
#include "plNetClientMgrMsg.h"
|
||||||
REGISTER_CREATABLE(plNetClientMgrMsg);
|
REGISTER_CREATABLE(plNetClientMgrMsg);
|
||||||
|
|
||||||
|
@ -42,31 +42,28 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
#ifndef _PLMESSAGE_PLRESPATCHERMSG_H
|
#ifndef _PLMESSAGE_PLRESPATCHERMSG_H
|
||||||
#define _PLMESSAGE_PLRESPATCHERMSG_H
|
#define _PLMESSAGE_PLRESPATCHERMSG_H
|
||||||
|
|
||||||
#include "HeadSpin.h"
|
#include "plString.h"
|
||||||
#include "pnMessage/plMessage.h"
|
#include "pnMessage/plMessage.h"
|
||||||
|
|
||||||
// This message is sent when plResPatcher has completed its async operation
|
// This message is sent when plResPatcher has completed its async operation
|
||||||
class plResPatcherMsg : public plMessage {
|
class plResPatcherMsg : public plMessage {
|
||||||
bool fSuccess;
|
bool fSuccess;
|
||||||
char* fError;
|
plString fError;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
plResPatcherMsg() : fSuccess(true), fError(nil) { SetBCastFlag(kBCastByExactType); }
|
plResPatcherMsg() : fSuccess(true) { SetBCastFlag(kBCastByExactType); }
|
||||||
plResPatcherMsg(bool success, const char* error) : fSuccess(success)
|
plResPatcherMsg(bool success, const plString& error) : fSuccess(success), fError(error)
|
||||||
{
|
{
|
||||||
SetBCastFlag(kBCastByExactType);
|
SetBCastFlag(kBCastByExactType);
|
||||||
fError = hsStrcpy(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~plResPatcherMsg() { delete[] fError; }
|
|
||||||
|
|
||||||
CLASSNAME_REGISTER(plResPatcherMsg);
|
CLASSNAME_REGISTER(plResPatcherMsg);
|
||||||
GETINTERFACE_ANY(plResPatcherMsg, plMessage);
|
GETINTERFACE_ANY(plResPatcherMsg, plMessage);
|
||||||
|
|
||||||
void Read (hsStream *, hsResMgr *) { FATAL("What the hell are you doing?"); }
|
void Read (hsStream *, hsResMgr *) { FATAL("What the hell are you doing?"); }
|
||||||
void Write (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; }
|
bool Success() const { return fSuccess; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ void plNCAgeJoiner::IDispatchMsgReceiveCallback () {
|
|||||||
void plNCAgeJoiner::IResMgrProgressBarCallback (plKey key) {
|
void plNCAgeJoiner::IResMgrProgressBarCallback (plKey key) {
|
||||||
#ifndef PLASMA_EXTERNAL_RELEASE
|
#ifndef PLASMA_EXTERNAL_RELEASE
|
||||||
if (s_instance)
|
if (s_instance)
|
||||||
s_instance->progressBar->SetStatusText(key->GetName().c_str());
|
s_instance->progressBar->SetStatusText(key->GetName());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,13 +224,18 @@ void plNCAgeJoiner::Start () {
|
|||||||
|
|
||||||
// if we're linking to startup then set the OfflineAge flag
|
// if we're linking to startup then set the OfflineAge flag
|
||||||
// so we by-pass the game server
|
// 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);
|
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);
|
nc->SetFlagsBit(plNetClientApp::kLinkingToOfflineAge, false);
|
||||||
|
|
||||||
plAgeLoader* al = plAgeLoader::GetInstance();
|
// we only need to update the age if we're using a GameSrv
|
||||||
al->UpdateAge(age.ageDatasetName);
|
plAgeLoader* al = plAgeLoader::GetInstance();
|
||||||
|
al->UpdateAge(age.ageDatasetName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
@ -252,12 +257,10 @@ void plNCAgeJoiner::ExecNextOp () {
|
|||||||
LogMsg(kLogPerf, L"AgeJoiner: Exec:kLoadAge");
|
LogMsg(kLogPerf, L"AgeJoiner: Exec:kLoadAge");
|
||||||
|
|
||||||
// Start progress bar
|
// Start progress bar
|
||||||
char str[256];
|
char str[128];
|
||||||
#ifdef PLASMA_EXTERNAL_RELEASE
|
#ifndef PLASMA_EXTERNAL_RELEASE
|
||||||
StrCopy(str, "Loading age...", arrsize(str));
|
snprintf(str, arrsize(str), "Loading age... %s", age.ageDatasetName);
|
||||||
#else
|
#endif
|
||||||
StrPrintf(str, arrsize(str), "Loading age %s...", age.ageDatasetName);
|
|
||||||
#endif
|
|
||||||
progressBar = plProgressMgr::GetInstance()->RegisterOperation(0, str, plProgressMgr::kNone, false, true);
|
progressBar = plProgressMgr::GetInstance()->RegisterOperation(0, str, plProgressMgr::kNone, false, true);
|
||||||
plDispatch::SetMsgRecieveCallback(IDispatchMsgReceiveCallback);
|
plDispatch::SetMsgRecieveCallback(IDispatchMsgReceiveCallback);
|
||||||
((plResManager*)hsgResMgr::ResMgr())->SetProgressBarProc(IResMgrProgressBarCallback);
|
((plResManager*)hsgResMgr::ResMgr())->SetProgressBarProc(IResMgrProgressBarCallback);
|
||||||
@ -389,7 +392,7 @@ bool plNCAgeJoiner::MsgReceive (plMessage * msg) {
|
|||||||
);
|
);
|
||||||
LogMsg(kLogPerf, L"AgeJoiner: Next:kNoOp (age updated)");
|
LogMsg(kLogPerf, L"AgeJoiner: Next:kNoOp (age updated)");
|
||||||
} else
|
} else
|
||||||
Complete(false, resMsg->GetError());
|
Complete(false, resMsg->GetError().c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
#include "plVault/plVault.h"
|
#include "plVault/plVault.h"
|
||||||
#include "plMessage/plAccountUpdateMsg.h"
|
#include "plMessage/plAccountUpdateMsg.h"
|
||||||
#include "plNetClient/plNetClientMgr.h"
|
#include "plNetClient/plNetClientMgr.h"
|
||||||
|
#include "plFile/plStreamSource.h"
|
||||||
|
|
||||||
#include "pfMessage/pfKIMsg.h"
|
#include "pfMessage/pfKIMsg.h"
|
||||||
|
|
||||||
@ -414,6 +415,9 @@ static void INetCliAuthLoginRequestCallback (
|
|||||||
if (!wantsStartUpAge && 0 == StrCmpI(s_players[i].playerName, s_iniStartupPlayerName, (unsigned)-1))
|
if (!wantsStartUpAge && 0 == StrCmpI(s_players[i].playerName, s_iniStartupPlayerName, (unsigned)-1))
|
||||||
s_player = &s_players[i];
|
s_player = &s_players[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// store this server's encryption key for posterity
|
||||||
|
NetCliAuthGetEncryptionKey(plStreamSource::GetInstance()->GetEncryptionKey(), 4);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
s_account.accountUuid = kNilUuid;
|
s_account.accountUuid = kNilUuid;
|
||||||
|
@ -57,6 +57,14 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
|
|
||||||
#include "hsTimer.h"
|
#include "hsTimer.h"
|
||||||
|
|
||||||
|
// Draw Colors
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
kTitleColor = 0xccb0b0b0,
|
||||||
|
kProgressBarColor = 0xff302b3a,
|
||||||
|
kInfoColor = 0xff635e6d,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
//// Constructor & Destructor ////////////////////////////////////////////////
|
//// Constructor & Destructor ////////////////////////////////////////////////
|
||||||
|
|
||||||
@ -178,85 +186,51 @@ bool plDTProgressMgr::IDrawTheStupidThing(plPipeline *p, plOperationProgress
|
|||||||
{
|
{
|
||||||
plDebugText &text = plDebugText::Instance();
|
plDebugText &text = plDebugText::Instance();
|
||||||
bool drew_something = false;
|
bool drew_something = false;
|
||||||
|
uint16_t downsz = (text.GetFontSize() << 1) + 4;
|
||||||
|
|
||||||
// Lets just set the color to blue
|
// draw the title
|
||||||
uint32_t color = 0xff302b3a;
|
if (!prog->GetTitle().IsEmpty()) {
|
||||||
|
y -= downsz;
|
||||||
|
text.DrawString_TEMP(x, y, prog->GetTitle(), kTitleColor);
|
||||||
|
y += downsz;
|
||||||
|
drew_something = true;
|
||||||
|
}
|
||||||
|
|
||||||
if( prog->GetMax() > 0.f )
|
// draw a progress bar
|
||||||
{
|
if (prog->GetMax() > 0.f) {
|
||||||
text.Draw3DBorder(x, y, x + width - 1, y + height - 1, color, color);
|
text.Draw3DBorder(x, y, x + width - 1, y + height - 1, kProgressBarColor, kProgressBarColor);
|
||||||
|
|
||||||
x += 2;
|
x += 2;
|
||||||
y += 2;
|
y += 2;
|
||||||
width -= 4;
|
width -= 4;
|
||||||
height -= 4;
|
height -= 4;
|
||||||
|
|
||||||
uint16_t drawWidth = width;
|
uint16_t drawWidth = width;
|
||||||
int16_t drawX = x;
|
int16_t drawX = x;
|
||||||
uint16_t rightX = drawX + drawWidth;
|
uint16_t rightX = drawX + drawWidth;
|
||||||
|
|
||||||
if (prog->GetProgress() <= prog->GetMax())
|
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;
|
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;
|
drew_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
y -= ( text.GetFontSize() << 1 ) + 4;
|
// draw the left justified status text
|
||||||
|
if (!prog->GetStatusText().IsEmpty()) {
|
||||||
|
text.DrawString_TEMP(x, y, prog->GetStatusText(), kInfoColor);
|
||||||
|
drew_something = true;
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef PLASMA_EXTERNAL_RELEASE
|
// draw the right justified info text
|
||||||
static bool drawText = true;
|
if (!prog->GetInfoText().IsEmpty()) {
|
||||||
#else
|
uint16_t right_x = 2 + x + width - text.CalcStringWidth_TEMP(prog->GetInfoText());
|
||||||
static bool drawText = false;
|
text.DrawString_TEMP(right_x, y, prog->GetInfoText(), kInfoColor);
|
||||||
#endif
|
drew_something = true;
|
||||||
|
|
||||||
if (drawText)
|
|
||||||
{
|
|
||||||
if (prog->GetTitle())
|
|
||||||
{
|
|
||||||
text.DrawString( x, y, prog->GetTitle(), (uint32_t)0xccb0b0b0 );
|
|
||||||
x += (uint16_t)text.CalcStringWidth( prog->GetTitle() );
|
|
||||||
drew_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prog->GetStatusText())
|
|
||||||
{
|
|
||||||
text.DrawString( x, y, prog->GetStatusText(), (uint32_t)0xccb0b0b0 );
|
|
||||||
drew_something = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return whether or not we drew stuff
|
// return whether or not we drew stuff
|
||||||
|
@ -268,8 +268,6 @@ plOperationProgress::plOperationProgress( float length ) :
|
|||||||
fRemainingSecs(0),
|
fRemainingSecs(0),
|
||||||
fAmtPerSec(0.f)
|
fAmtPerSec(0.f)
|
||||||
{
|
{
|
||||||
memset( fStatusText, 0, sizeof( fStatusText ) );
|
|
||||||
memset( fTitle, 0, sizeof( fTitle ) );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
plOperationProgress::~plOperationProgress()
|
plOperationProgress::~plOperationProgress()
|
||||||
@ -347,28 +345,6 @@ void plOperationProgress::SetHowMuch( float howMuch )
|
|||||||
plProgressMgr::GetInstance()->IUpdateCallbackProc( this );
|
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 ///////////////////////////////////////////////////////////////
|
//// SetLength ///////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void plOperationProgress::SetLength( float length )
|
void plOperationProgress::SetLength( float length )
|
||||||
|
@ -56,7 +56,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|||||||
#define _plProgressMgr_h
|
#define _plProgressMgr_h
|
||||||
|
|
||||||
#include "HeadSpin.h"
|
#include "HeadSpin.h"
|
||||||
|
#include "plString.h"
|
||||||
|
|
||||||
class plPipeline;
|
class plPipeline;
|
||||||
class plPlate;
|
class plPlate;
|
||||||
@ -74,10 +74,11 @@ class plOperationProgress
|
|||||||
protected:
|
protected:
|
||||||
|
|
||||||
float fValue, fMax;
|
float fValue, fMax;
|
||||||
char fStatusText[ 256 ];
|
plString fTitle;
|
||||||
char fTitle[ 256 ];
|
plString fStatusText;
|
||||||
uint32_t fContext;
|
plString fInfoText;
|
||||||
double fStartTime;
|
uint32_t fContext;
|
||||||
|
double fStartTime;
|
||||||
|
|
||||||
uint32_t fElapsedSecs, fRemainingSecs;
|
uint32_t fElapsedSecs, fRemainingSecs;
|
||||||
float fAmtPerSec;
|
float fAmtPerSec;
|
||||||
@ -109,11 +110,12 @@ class plOperationProgress
|
|||||||
|
|
||||||
~plOperationProgress();
|
~plOperationProgress();
|
||||||
|
|
||||||
float GetMax( void ) const { return fMax; }
|
float GetMax() const { return fMax; }
|
||||||
float GetProgress( void ) const { return fValue; }
|
float GetProgress() const { return fValue; }
|
||||||
const char * GetTitle( void ) const { return fTitle; }
|
plString GetTitle() const { return fTitle; }
|
||||||
const char * GetStatusText( void ) const { return fStatusText; }
|
plString GetStatusText() const { return fStatusText; }
|
||||||
uint32_t GetContext( void ) const { return fContext; }
|
plString GetInfoText() const { return fInfoText; }
|
||||||
|
uint32_t GetContext() const { return fContext; }
|
||||||
uint32_t GetElapsedSecs() { return fElapsedSecs; }
|
uint32_t GetElapsedSecs() { return fElapsedSecs; }
|
||||||
uint32_t GetRemainingSecs() { return fRemainingSecs; }
|
uint32_t GetRemainingSecs() { return fRemainingSecs; }
|
||||||
float GetAmtPerSec() { return fAmtPerSec; }
|
float GetAmtPerSec() { return fAmtPerSec; }
|
||||||
@ -127,16 +129,19 @@ class plOperationProgress
|
|||||||
// Set the length
|
// Set the length
|
||||||
void SetLength( float length );
|
void SetLength( float length );
|
||||||
|
|
||||||
// Sets the display text above the bar (nil for nothing)
|
/** Sets the progress bar's right justified info text */
|
||||||
void SetStatusText( const char *text );
|
void SetInfoText(const plString& info) { fInfoText = info; }
|
||||||
|
|
||||||
// Sets the title
|
/** Sets the progress bar's left justified status text */
|
||||||
void SetTitle( const char *title );
|
void SetStatusText(const plString& status) { fStatusText = status; }
|
||||||
|
|
||||||
|
/** Sets the progress bar's title */
|
||||||
|
void SetTitle(const plString& title) { fTitle = title; }
|
||||||
|
|
||||||
// Application data
|
// Application data
|
||||||
void SetContext( uint32_t context ) { fContext = context;}
|
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)
|
// True if this is the initial update (progress was just created)
|
||||||
bool IsInitUpdate() { return hsCheckBits(fFlags, kInitUpdate); }
|
bool IsInitUpdate() { return hsCheckBits(fFlags, kInitUpdate); }
|
||||||
|
@ -124,7 +124,6 @@ target_link_libraries(MaxMain pfJournalBook)
|
|||||||
target_link_libraries(MaxMain pfLocalizationMgr)
|
target_link_libraries(MaxMain pfLocalizationMgr)
|
||||||
target_link_libraries(MaxMain pfMessage)
|
target_link_libraries(MaxMain pfMessage)
|
||||||
target_link_libraries(MaxMain pfPython)
|
target_link_libraries(MaxMain pfPython)
|
||||||
target_link_libraries(MaxMain pfSecurePreloader)
|
|
||||||
target_link_libraries(MaxMain pfSurface)
|
target_link_libraries(MaxMain pfSurface)
|
||||||
target_link_libraries(MaxMain plAgeDescription)
|
target_link_libraries(MaxMain plAgeDescription)
|
||||||
target_link_libraries(MaxMain plAgeLoader)
|
target_link_libraries(MaxMain plAgeLoader)
|
||||||
|
@ -57,7 +57,6 @@ target_link_libraries(MaxPlasmaLights pfJournalBook)
|
|||||||
target_link_libraries(MaxPlasmaLights pfLocalizationMgr)
|
target_link_libraries(MaxPlasmaLights pfLocalizationMgr)
|
||||||
target_link_libraries(MaxPlasmaLights pfMessage)
|
target_link_libraries(MaxPlasmaLights pfMessage)
|
||||||
target_link_libraries(MaxPlasmaLights pfPython)
|
target_link_libraries(MaxPlasmaLights pfPython)
|
||||||
target_link_libraries(MaxPlasmaLights pfSecurePreloader)
|
|
||||||
target_link_libraries(MaxPlasmaLights pfSurface)
|
target_link_libraries(MaxPlasmaLights pfSurface)
|
||||||
target_link_libraries(MaxPlasmaLights plAgeDescription)
|
target_link_libraries(MaxPlasmaLights plAgeDescription)
|
||||||
target_link_libraries(MaxPlasmaLights plAgeLoader)
|
target_link_libraries(MaxPlasmaLights plAgeLoader)
|
||||||
|
Reference in New Issue
Block a user