2
3
mirror of https://foundry.openuru.org/gitblit/r/CWE-ou-minkata.git synced 2025-07-14 02:27:40 -04:00

CWE Directory Reorganization

Rearrange directory structure of CWE to be loosely equivalent to
the H'uru Plasma repository.

Part 1: Movement of directories and files.
This commit is contained in:
rarified
2021-05-15 12:49:46 -06:00
parent c3f4a640a3
commit 96903e8dca
4002 changed files with 159 additions and 644 deletions

View File

@ -0,0 +1,471 @@
/*==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 "plAgeLoader.h"
#include "hsStream.h"
#include "plgDispatch.h"
#include "hsResMgr.h"
//#include "hsTimer.h"
#include "plResPatcher.h"
#include "plBackgroundDownloader.h"
#include "process.h" // for getpid()
#include "../pnProduct/pnProduct.h"
#include "../pnKeyedObject/plKey.h"
#include "../pnKeyedObject/plFixedKey.h"
#include "../pnSceneObject/plSceneObject.h"
#include "../pnMessage/plClientMsg.h"
#include "../pnNetCommon/plNetApp.h"
#include "../plScene/plRelevanceMgr.h"
#include "../plResMgr/plKeyFinder.h"
#include "../plAgeDescription/plAgeDescription.h"
#include "../plSDL/plSDL.h"
#include "../plNetClient/plNetClientMgr.h"
#include "../plResMgr/plRegistryHelpers.h"
#include "../plResMgr/plRegistryNode.h"
#include "../plResMgr/plResManager.h"
#include "../plFile/plEncryptedStream.h"
/// TEMP HACK TO LOAD CONSOLE INIT FILES ON AGE LOAD
#include "../plMessage/plConsoleMsg.h"
#include "../plMessage/plLoadAvatarMsg.h"
#include "../plMessage/plAgeLoadedMsg.h"
extern hsBool gDataServerLocal;
extern hsBool gUseBackgroundDownloader;
// static
plAgeLoader* plAgeLoader::fInstance=nil;
//
// CONSTRUCT
//
plAgeLoader::plAgeLoader() :
fInitialAgeState(nil),
fFlags(0)
{
}
//
// DESTRUCT
//
plAgeLoader::~plAgeLoader()
{
delete fInitialAgeState;
fInitialAgeState=nil;
if ( PendingAgeFniFiles().size() )
plNetClientApp::StaticErrorMsg( "~plAgeLoader(): %d pending age fni files", PendingAgeFniFiles().size() );
if ( PendingPageOuts().size() )
plNetClientApp::StaticErrorMsg( "~plAgeLoader(): %d pending page outs", PendingPageOuts().size() );
ClearPageExcludeList(); // Clear our debugging exclude list, just to be tidy
if (fInstance==this)
SetInstance(nil);
}
void plAgeLoader::Shutdown()
{
}
void plAgeLoader::Init()
{
RegisterAs( kAgeLoader_KEY );
plgDispatch::Dispatch()->RegisterForExactType(plInitialAgeStateLoadedMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plClientMsg::Index(), GetKey());
if (!gDataServerLocal && gUseBackgroundDownloader)
plBackgroundDownloader::StartThread();
}
//
// STATIC
//
plAgeLoader* plAgeLoader::GetInstance()
{
return fInstance;
}
//
// STATIC
//
void plAgeLoader::SetInstance(plAgeLoader* inst)
{
fInstance=inst;
}
//
// Plasma Msg Handler
//
hsBool plAgeLoader::MsgReceive(plMessage* msg)
{
plInitialAgeStateLoadedMsg *stateMsg = plInitialAgeStateLoadedMsg::ConvertNoRef( msg );
if( stateMsg != nil )
{
// done receiving the initial state of the age from the server
return true;
}
plClientMsg* clientMsg = plClientMsg::ConvertNoRef(msg);
if (clientMsg && clientMsg->GetClientMsgFlag()==plClientMsg::kInitComplete)
{
ExecPendingAgeFniFiles(); // exec age-specific fni files
return true;
}
return plReceiver::MsgReceive(msg);
}
//
// read in the age desc file and page in/out the rooms belonging to the specified age.
// return false on error
//
//============================================================================
bool plAgeLoader::LoadAge(const char ageName[])
{
return ILoadAge(ageName);
}
//============================================================================
bool plAgeLoader::UpdateAge(const char ageName[])
{
bool result = true;
if (!gDataServerLocal)
{
plResPatcher myPatcher(ageName);
result = myPatcher.Update();
}
return result;
}
//============================================================================
void plAgeLoader::NotifyAgeLoaded( bool loaded )
{
if ( loaded )
fFlags &= ~kLoadingAge;
else
fFlags &= ~kUnLoadingAge;
plAgeLoadedMsg * msg = TRACKED_NEW plAgeLoadedMsg;
msg->fLoaded = loaded;
msg->Send();
}
//// ILoadAge ////////////////////////////////////////////////////////////////
// Does the loading-specific stuff for queueing an age to load
bool plAgeLoader::ILoadAge(const char ageName[])
{
plNetClientApp* nc = plNetClientApp::GetInstance();
ASSERT(!nc->GetFlagsBit(plNetClientApp::kPlayingGame));
StrCopy(fAgeName, ageName, arrsize(fAgeName));
nc->DebugMsg( "Net: Loading age %s", fAgeName);
if ((fFlags & kLoadMask) != 0)
ErrorFatal(__LINE__, __FILE__, "Fatal Error:\nAlready loading or unloading an age.\n%S will now exit.", ProductShortName());
fFlags |= kLoadingAge;
plAgeBeginLoadingMsg* ageBeginLoading = TRACKED_NEW plAgeBeginLoadingMsg();
ageBeginLoading->Send();
///////////////////////////////////////////////////////
/// Step 1: Update all of the dat files for this age
/*
UpdateAge(fAgeName);
*/
/// Step 2: Load the keys for this age, so we can find sceneNodes for them
// exec age .fni file when data is done loading
char consoleIniName[ 256 ];
sprintf( consoleIniName, "dat\\%s.fni", fAgeName);
fPendingAgeFniFiles.push_back( consoleIniName );
char csvName[256];
sprintf(csvName, "dat\\%s.csv", fAgeName);
fPendingAgeCsvFiles.push_back(csvName);
plSynchEnabler p( false ); // turn off dirty tracking while in this function
hsStream* stream=GetAgeDescFileStream(fAgeName);
if (!stream)
{
nc->ErrorMsg("Failed loading age. Age desc file %s has nil stream", fAgeName);
fFlags &= ~kLoadingAge;
return false;
}
plAgeDescription ad;
ad.Read(stream);
ad.SetAgeName(fAgeName);
stream->Close();
delete stream;
ad.SeekFirstPage();
plAgePage *page;
plKey clientKey = hsgResMgr::ResMgr()->FindKey( kClient_KEY );
// Copy, exclude pages we want excluded, and collect our scene nodes
fCurAgeDescription.CopyFrom(ad);
while( ( page = ad.GetNextPage() ) != nil )
{
if( IsPageExcluded( page, fAgeName) )
continue;
plKey roomKey = plKeyFinder::Instance().FindSceneNodeKey( fAgeName, page->GetName() );
if( roomKey != nil )
AddPendingPageInRoomKey( roomKey );
}
ad.SeekFirstPage();
// Tell the client to load-and-hold all the keys for this age, to make the loading process work better
plClientMsg *loadAgeKeysMsg = TRACKED_NEW plClientMsg( plClientMsg::kLoadAgeKeys );
loadAgeKeysMsg->SetAgeName( fAgeName);
loadAgeKeysMsg->Send( clientKey );
//
// Load the Age's SDL Hook object (and it's python modifier)
//
plUoid oid=nc->GetAgeSDLObjectUoid(fAgeName);
plKey ageSDLObjectKey = hsgResMgr::ResMgr()->FindKey(oid);
if (ageSDLObjectKey)
hsgResMgr::ResMgr()->AddViaNotify(ageSDLObjectKey, TRACKED_NEW plGenRefMsg(nc->GetKey(), plRefMsg::kOnCreate, -1,
plNetClientMgr::kAgeSDLHook), plRefFlags::kActiveRef);
int nPages = 0;
plClientMsg* pMsg1 = TRACKED_NEW plClientMsg(plClientMsg::kLoadRoom);
pMsg1->SetAgeName(fAgeName);
// Loop and ref!
while( ( page = ad.GetNextPage() ) != nil )
{
if( IsPageExcluded( page, fAgeName) )
{
nc->DebugMsg( "\tExcluding page %s\n", page->GetName() );
continue;
}
nPages++;
pMsg1->AddRoomLoc(ad.CalcPageLocation(page->GetName()));
nc->DebugMsg("\tPaging in room %s\n", page->GetName());
}
pMsg1->Send(clientKey);
// Send the client a message to let go of the extra keys it was holding on to
plClientMsg *dumpAgeKeys = TRACKED_NEW plClientMsg( plClientMsg::kReleaseAgeKeys );
dumpAgeKeys->SetAgeName( fAgeName);
dumpAgeKeys->Send( clientKey );
if ( nPages==0 )
{
// age is done loading because it has no pages?
fFlags &= ~kLoadingAge;
}
return true;
}
//// plUnloadAgeCollector ////////////////////////////////////////////////////
// Registry page iterator to collect all the loaded pages of a given age
// Note: we have to do an IterateAllPages(), since we want to also catch
// pages that are partially loaded, which are skipped in the vanilla
// IteratePages() call.
class plUnloadAgeCollector : public plRegistryPageIterator
{
public:
hsTArray<plRegistryPageNode *> fPages;
const char *fAge;
plUnloadAgeCollector( const char *a ) : fAge( a ) {}
virtual hsBool EatPage( plRegistryPageNode *page )
{
if( fAge && stricmp( page->GetPageInfo().GetAge(), fAge ) == 0 )
{
fPages.Append( page );
}
return true;
}
};
//// IUnloadAge //////////////////////////////////////////////////////////////
// Does the UNloading-specific stuff for queueing an age to unload.
// Far simpler that ILoadAge :)
bool plAgeLoader::IUnloadAge()
{
plNetClientApp* nc = plNetClientApp::GetInstance();
nc->DebugMsg( "Net: Unloading age %s", fAgeName);
hsAssert( (fFlags & kLoadMask)==0, "already loading or unloading an age?");
fFlags |= kUnLoadingAge;
plAgeBeginLoadingMsg* msg = TRACKED_NEW plAgeBeginLoadingMsg();
msg->fLoading = false;
msg->Send();
// Note: instead of going from the .age file, we just want a list of what
// is REALLY paged in for this age. So ask the resMgr!
plUnloadAgeCollector collector( fAgeName);
// WARNING: unsafe cast here, but it's ok, until somebody is mean and makes a non-plResManager resMgr
( (plResManager *)hsgResMgr::ResMgr() )->IterateAllPages( &collector );
// Dat was easy...
plKey clientKey = hsgResMgr::ResMgr()->FindKey( kClient_KEY );
// Build up a list of all the rooms we're going to page out
plKeyVec newPageOuts;
int i;
for( i = 0; i < collector.fPages.GetCount(); i++ )
{
plRegistryPageNode *page = collector.fPages[ i ];
plKey roomKey = plKeyFinder::Instance().FindSceneNodeKey( page->GetPageInfo().GetLocation() );
if( roomKey != nil && roomKey->ObjectIsLoaded() )
{
nc->DebugMsg( "\tPaging out room %s\n", page->GetPageInfo().GetPage() );
newPageOuts.push_back(roomKey);
}
}
// Put them in our pending page outs
for( i = 0; i < newPageOuts.size(); i++ )
fPendingPageOuts.push_back(newPageOuts[i]);
// ...then send the unload messages. That way we ensure the list is complete
// before any messages get processed
for( i = 0; i < newPageOuts.size(); i++ )
{
plClientMsg *pMsg1 = TRACKED_NEW plClientMsg( plClientMsg::kUnloadRoom );
pMsg1->AddRoomLoc(newPageOuts[i]->GetUoid().GetLocation());
pMsg1->Send( clientKey );
}
if ( newPageOuts.size()==0 )
{
// age is done unloading because it has no pages?
NotifyAgeLoaded( false );
}
return true;
}
void plAgeLoader::ExecPendingAgeFniFiles()
{
int i;
for (i=0;i<PendingAgeFniFiles().size(); i++)
{
plConsoleMsg *cMsg = TRACKED_NEW plConsoleMsg( plConsoleMsg::kExecuteFile, fPendingAgeFniFiles[i].c_str() );
plgDispatch::MsgSend( cMsg );
}
fPendingAgeFniFiles.clear();
}
void plAgeLoader::ExecPendingAgeCsvFiles()
{
int i;
for (i=0;i<PendingAgeCsvFiles().size(); i++)
{
hsStream* stream = plEncryptedStream::OpenEncryptedFile(fPendingAgeCsvFiles[i].c_str());
if (stream)
{
plRelevanceMgr::Instance()->ParseCsvInput(stream);
stream->Close();
delete stream;
}
}
fPendingAgeCsvFiles.clear();
}
//
// return alloced stream or nil
// static
//
hsStream* plAgeLoader::GetAgeDescFileStream(const char* ageName)
{
if (!ageName)
return nil;
char ageDescFileName[256];
sprintf(ageDescFileName, "dat\\%s.age", ageName);
hsStream* stream = plEncryptedStream::OpenEncryptedFile(ageDescFileName);
if (!stream)
{
char str[256];
sprintf(str, "Can't find age desc file %s", ageDescFileName);
hsAssert(false, str);
return nil;
}
return stream;
}
//
// sent from server with joinAck
//
void plAgeLoader::ISetInitialAgeState(plStateDataRecord* s)
{
hsAssert(fInitialAgeState != s, "duplicate initial age state");
delete fInitialAgeState;
fInitialAgeState=s;
}

View File

@ -0,0 +1,145 @@
/*==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 plAgeLoader_h
#define plAgeLoader_h
#include "hsTypes.h"
#include "hsStlUtils.h"
#include "../pnUtils/pnUtils.h"
#include "../pnNetBase/pnNetBase.h"
#include "../pnKeyedObject/hsKeyedObject.h"
#include "../pnKeyedObject/plKey.h"
#include "../plAgeDescription/plAgeDescription.h"
#include "../plUUID/plUUID.h"
//
// A singleton class which manages loading and unloading ages and operations associated with that
//
// fwd decls
class plStateDataRecord;
class plMessage;
class plOperationProgress;
class plAgeLoader : public hsKeyedObject
{
friend class plNetClientMsgHandler;
friend class plNetClientJoinTask;
private:
typedef std::vector<plKey> plKeyVec;
typedef std::vector<std::string> plStringVec;
enum Flags
{
kLoadingAge = 0x1,
kUnLoadingAge = 0x2,
kLoadMask = (kLoadingAge | kUnLoadingAge)
};
static plAgeLoader* fInstance;
UInt32 fFlags;
plStringVec fPendingAgeFniFiles; // list of age .fni files to be parsed
plStringVec fPendingAgeCsvFiles; // list of age .csv files to be parsed
plKeyVec fPendingPageIns; // keys of rooms which are currently being paged in.
plKeyVec fPendingPageOuts; // keys of rooms which are currently being paged out.
plAgeDescription fCurAgeDescription;
plStateDataRecord* fInitialAgeState;
char fAgeName[kMaxAgeNameLength];
bool ILoadAge(const char ageName[]);
bool IUnloadAge();
void ISetInitialAgeState(plStateDataRecord* s); // sent from server with joinAck
const plStateDataRecord* IGetInitialAgeState() const { return fInitialAgeState; }
public:
plAgeLoader();
~plAgeLoader();
CLASSNAME_REGISTER( plAgeLoader);
GETINTERFACE_ANY( plAgeLoader, hsKeyedObject);
static plAgeLoader* GetInstance();
static void SetInstance(plAgeLoader* inst);
static hsStream* GetAgeDescFileStream(const char* ageName);
void Init();
void Shutdown();
hsBool MsgReceive(plMessage* msg);
bool LoadAge(const char ageName[]);
bool UnloadAge() { return IUnloadAge(); }
bool UpdateAge(const char ageName[]);
void NotifyAgeLoaded( bool loaded );
const plKeyVec& PendingPageOuts() const { return fPendingPageOuts; }
const plKeyVec& PendingPageIns() const { return fPendingPageIns; }
const plStringVec& PendingAgeCsvFiles() const { return fPendingAgeCsvFiles; }
const plStringVec& PendingAgeFniFiles() const { return fPendingAgeFniFiles; }
void AddPendingPageInRoomKey(plKey r);
bool RemovePendingPageInRoomKey(plKey r);
bool IsPendingPageInRoomKey(plKey p, int* idx=nil);
void ExecPendingAgeFniFiles();
void ExecPendingAgeCsvFiles();
// Fun debugging exclude commands (to prevent certain pages from loading)
void ClearPageExcludeList( void );
void AddExcludedPage( const char *pageName, const char *ageName = nil );
bool IsPageExcluded( const plAgePage *page, const char *ageName = nil );
const plAgeDescription &GetCurrAgeDesc( void ) const { return fCurAgeDescription; }
// paging
void FinishedPagingInRoom(plKey* rmKey, int numRms); // call when finished paging in/out a room
void StartPagingOutRoom(plKey* rmKey, int numRms); // call when starting to page in/out a room
void FinishedPagingOutRoom(plKey* rmKey, int numRms);
// Called on page-in-hold rooms, since we don't want them actually paging out in the NCM (i.e. sending info to the server)
void IgnorePagingOutRoom(plKey* rmKey, int numRms);
bool IsLoadingAge(){ return (fFlags & (kUnLoadingAge | kLoadingAge)); }
};
#endif // plAgeLoader_h

View File

@ -0,0 +1,50 @@
/*==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 plAgeLoaderCreatable_inc
#define plAgeLoaderCreatable_inc
#include "../pnFactory/plCreator.h"
#include "plAgeLoader.h"
REGISTER_CREATABLE( plAgeLoader);
#endif // plAgeLoaderCreatable_inc

View File

@ -0,0 +1,337 @@
/*==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 "plAgeLoader.h"
#include "hsTimer.h"
#include "hsResMgr.h"
#include "plgDispatch.h"
#include <algorithm>
#include "../pnNetCommon/plNetApp.h"
#include "../pnKeyedObject/plKey.h"
#include "../plMessage/plAgeLoadedMsg.h"
#include "../plNetMessage/plNetMessage.h"
#include "../plProgressMgr/plProgressMgr.h"
#include "../plSDL/plSDL.h"
#include "../pnDispatch/plDispatch.h"
#include "../plResMgr/plResManager.h"
#include "../plNetClient/plNetClientMgr.h"
//
// if room is reserved or for animations, don't report it to the server.
// the server only cares about rooms which have real, networked objects in them.
// The server already assigns a global room to everyone for things like avatar and builtin state .
//
bool ReportRoomToServer(const plKey &key)
{
plLocation keyLoc=key->GetUoid().GetLocation();
bool skip=(keyLoc.IsReserved() || keyLoc.IsVirtual() ||
// HACK ALERT - replace with new uoid type flags
(key->GetName() &&
(!strnicmp(key->GetName(), "global", 6) ||
strstr(key->GetName(), "_Male") ||
strstr(key->GetName(), "_Female")
)
)
);
if (skip)
hsLogEntry(plNetApp::StaticDebugMsg("Not reporting room %s to server, reserved=%d, virtual=%d",
key->GetName(), keyLoc.IsReserved(), keyLoc.IsVirtual()));
return !skip;
}
//
// call when finished paging in a room.
//
void plAgeLoader::FinishedPagingInRoom(plKey* rmKey, int numRms)
{
if (numRms==0)
return;
unsigned pendingPageIns = PendingPageIns().size();
plNetClientApp* nc = plNetClientApp::GetInstance();
// Send a msg to the server indicating that we have this room paged in
plNetMsgPagingRoom * pagingMsg = TRACKED_NEW plNetMsgPagingRoom;
pagingMsg->SetNetProtocol(kNetProtocolCli2Game);
int i;
for(i=0;i<numRms;i++)
{
plKey key=rmKey[i];
if (!RemovePendingPageInRoomKey(key)) // room is done paging in
continue; // we didn't queue this room
if (!ReportRoomToServer(key))
continue;
pagingMsg->AddRoom(key);
hsLogEntry(nc->DebugMsg("\tSending PageIn/RequestState msg, room=%s\n", key->GetName()));
}
if( pagingMsg->GetNumRooms() > 0 ) // all rooms were reserved
{
plNetClientMgr * mgr = plNetClientMgr::GetInstance();
mgr->AddPendingPagingRoomMsg( pagingMsg );
}
else
delete pagingMsg;
// If any of these rooms were queued for load by us, then we may be done loading the age.
if (pendingPageIns != PendingPageIns().size())
{
bool ageLoaded = (PendingPageIns().size()==0) && (fFlags & kLoadingAge);
if (ageLoaded)
{
plAgeLoaded2Msg * msg = TRACKED_NEW plAgeLoaded2Msg;
msg->Send();
// join task will call NotifyAgeLoaded for us later
}
}
}
//
// called by the client when a room is finished paging out
//
void plAgeLoader::FinishedPagingOutRoom(plKey* rmKey, int numRms)
{
plNetClientApp* nc = plNetClientApp::GetInstance();
nc->StayAlive(hsTimer::GetSysSeconds()); // alive
int i;
for(i=0;i<numRms;i++)
{
plKeyVec::iterator found = std::find( fPendingPageOuts.begin(), fPendingPageOuts.end(), rmKey[ i ] );
if( found != fPendingPageOuts.end() )
{
fPendingPageOuts.erase( found );
nc->DebugMsg("Finished paging out room %s", rmKey[i]->GetName());
}
}
if (PendingPageOuts().size() == 0 && (fFlags & kUnLoadingAge))
{
NotifyAgeLoaded( false );
}
}
//
// call when starting to page out a room.
// allows server to transfer ownership of room objects to someone else.
//
void plAgeLoader::StartPagingOutRoom(plKey* rmKey, int numRms)
{
plNetClientApp* nc = plNetClientApp::GetInstance();
plNetMsgPagingRoom pagingMsg;
pagingMsg.SetNetProtocol(kNetProtocolCli2Game);
pagingMsg.SetPagingOut(true);
int i;
for(i=0;i<numRms;i++)
{
plKey key=rmKey[i];
if (!ReportRoomToServer(key))
continue;
pagingMsg.AddRoom(rmKey[i]);
nc->DebugMsg("\tSending PageOut msg, room=%s", rmKey[i]->GetName());
}
if (!pagingMsg.GetNumRooms()) // all rooms were reserved
return;
nc->SendMsg(&pagingMsg);
}
// Client telling us that this page isn't going to get the start/finish combo
// on page out, most likely because it was a load-and-hold, not a load. So take
// it from our pending list but don't actually process it
// Note: right now it's just a dup of FinishPagingOutRoom(), but since the latter
// might change later, we go ahead and dup to avoid unnecessary bugs later
void plAgeLoader::IgnorePagingOutRoom(plKey* rmKey, int numRms)
{
plNetClientApp* nc = plNetClientApp::GetInstance();
nc->StayAlive(hsTimer::GetSysSeconds()); // alive
int i;
for(i=0;i<numRms;i++)
{
plKeyVec::iterator found = std::find( fPendingPageOuts.begin(), fPendingPageOuts.end(), rmKey[ i ] );
if( found != fPendingPageOuts.end() )
{
fPendingPageOuts.erase( found );
nc->DebugMsg("Ignoring paged out room %s", rmKey[i]->GetName());
}
}
if (PendingPageOuts().size() == 0 && (fFlags & kUnLoadingAge))
{
NotifyAgeLoaded( false );
}
}
///////////////////////////////////
bool plAgeLoader::IsPendingPageInRoomKey(plKey pKey, int *idx)
{
if (pKey)
{
plKeyVec::iterator result=std::find(fPendingPageIns.begin(), fPendingPageIns.end(), pKey);
bool found = result!=fPendingPageIns.end();
if (idx)
*idx = found ? result-fPendingPageIns.begin() : -1;
return found;
}
return false;
}
void plAgeLoader::AddPendingPageInRoomKey(plKey pKey)
{
if (!IsPendingPageInRoomKey(pKey))
{
fPendingPageIns.push_back(pKey);
}
}
bool plAgeLoader::RemovePendingPageInRoomKey(plKey pKey)
{
int idx;
if (IsPendingPageInRoomKey(pKey, &idx))
{
fPendingPageIns.erase(fPendingPageIns.begin()+idx); // remove key from list
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////////
//// Page Exclusion //////////////////////////////////////////////////////////
// //
// Fun debugging exclude commands (to prevent certain pages from loading) //
// //
//////////////////////////////////////////////////////////////////////////////
class plExcludePage
{
public:
char *fPageName;
char *fAgeName;
plExcludePage() { fPageName = nil; fAgeName = nil; }
plExcludePage( char *p, char *a )
{
fPageName = p;
fAgeName = a;
}
};
static hsTArray<plExcludePage> sExcludeList;
void plAgeLoader::ClearPageExcludeList( void )
{
int i;
for( i = 0; i < sExcludeList.GetCount(); i++ )
{
delete [] sExcludeList[ i ].fPageName;
delete [] sExcludeList[ i ].fAgeName;
}
}
void plAgeLoader::AddExcludedPage( const char *pageName, const char *ageName )
{
char *p = hsStrcpy( pageName );
char *a = nil;
if( ageName != nil )
a = hsStrcpy( ageName );
sExcludeList.Append( plExcludePage( p, a ) );
}
bool plAgeLoader::IsPageExcluded( const plAgePage *page, const char *ageName )
{
// check page flags
if (page->GetFlags() & plAgePage::kPreventAutoLoad)
return true;
// check exclude list
const char* pageName = page->GetName();
int i;
for( i = 0; i < sExcludeList.GetCount(); i++ )
{
if( stricmp( pageName, sExcludeList[ i ].fPageName ) == 0 )
{
if( ageName == nil || sExcludeList[ i ].fAgeName == nil ||
stricmp( ageName, sExcludeList[ i ].fAgeName ) == 0 )
{
return true;
}
}
}
// Check if pages are excluded due to age SDL vars
if (page->GetFlags() & plAgePage::kLoadIfSDLPresent)
{
if (IGetInitialAgeState())
{
plSimpleStateVariable* sdVar = IGetInitialAgeState()->FindVar(pageName);
if (!sdVar)
return true; // no sdl var, exclude
bool value;
sdVar->Get(&value);
return value ? false : true; // exclude if var is false
}
else
return true; // no age state, exclude
}
return false;
}

View File

@ -0,0 +1,545 @@
/*==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 "plBackgroundDownloader.h"
#include <process.h>
#include "../pnUtils/pnUtils.h"
#include "../pnNetBase/pnNetBase.h"
#include "../plEncryption/plChecksum.h"
#include "../NucleusLib/inc/hsResMgr.h"
#include "../plAgeDescription/plAgeManifest.h"
#include "../plResMgr/plResManager.h"
#include "../plFile/plFileUtils.h"
#include "../plFile/plEncryptedStream.h"
#include "../plCompression/plZlibStream.h"
#include "../plAudioCore/plAudioFileReader.h"
#include "../plProgressMgr/plProgressMgr.h"
#include "../pnAsyncCore/pnAsyncCore.h"
#include "../pnNetCli/pnNetCli.h"
#include "../plNetGameLib/plNetGameLib.h"
#include "../pnDispatch/plDispatch.h"
#include "../plStatusLog/plStatusLog.h"
static const unsigned kMaxDownloadTries = 10;
static const wchar s_manifest[] = L"AllAges";
plBackgroundDownloader* plBackgroundDownloader::fInstance = NULL;
hsBool gUseBackgroundDownloader = false;
//============================================================================
enum DownloaderLogType
{
kHeader,
kInfo,
kMajorStatus,
kStatus,
kError,
};
void BackgroundDownloaderLog(DownloaderLogType type, const char* format, ...)
{
UInt32 color = 0;
switch (type)
{
case kHeader: color = plStatusLog::kWhite; break;
case kInfo: color = plStatusLog::kBlue; break;
case kMajorStatus: color = plStatusLog::kYellow; break;
case kStatus: color = plStatusLog::kGreen; break;
case kError: color = plStatusLog::kRed; break;
}
static plStatusLog* gStatusLog = nil;
if (!gStatusLog)
{
gStatusLog = plStatusLogMgr::GetInstance().CreateStatusLog(
20,
"bgdownload.log",
plStatusLog::kFilledBackground | plStatusLog::kAlignToTop | plStatusLog::kDeleteForMe);
}
va_list args;
va_start(args, format);
gStatusLog->AddLineV(color, format, args);
va_end(args);
}
//============================================================================
class plBGDownloadStream : public plZlibStream
{
public:
plBGDownloadStream() : plZlibStream() {}
virtual ~plBGDownloadStream() {}
virtual UInt32 Write(UInt32 byteCount, const void* buffer);
};
UInt32 plBGDownloadStream::Write(UInt32 byteCount, const void* buffer)
{
return plZlibStream::Write(byteCount, buffer);
}
//============================================================================
static void DownloadFileCallback(ENetError result, void* param, const wchar filename[], hsStream* writer)
{
plBackgroundDownloader* bgdownloader = (plBackgroundDownloader*)param;
// Retry download unless shutting down or file not found
switch (result) {
case kNetSuccess:
writer->Close();
bgdownloader->DoneWithFile(true);
break;
case kNetErrFileNotFound:
case kNetErrRemoteShutdown:
writer->Close();
bgdownloader->DoneWithFile(false);
break;
default:
writer->Rewind();
NetCliFileDownloadRequest(
filename,
writer,
DownloadFileCallback,
param
);
break;
}
}
static void ManifestCallback(ENetError result, void* param, const wchar group[], const NetCliFileManifestEntry manifest[], unsigned entryCount)
{
plBackgroundDownloader* bgdownloader = (plBackgroundDownloader*)param;
bgdownloader->DoneWithManifest(result == kNetSuccess, manifest, entryCount);
}
//============================================================================
plBackgroundDownloader* plBackgroundDownloader::GetInstance()
{
return fInstance;
}
void plBackgroundDownloader::ThreadMain(void * param)
{
Init();
plBackgroundDownloader::GetInstance()->Run();
plBackgroundDownloader::GetInstance()->CleanUp();
Shutdown();
}
void plBackgroundDownloader::StartThread()
{
_beginthread(plBackgroundDownloader::ThreadMain, 0, NULL);
}
void plBackgroundDownloader::Init()
{
fInstance = TRACKED_NEW plBackgroundDownloader();
}
void plBackgroundDownloader::Shutdown()
{
delete fInstance;
fInstance = NULL;
}
plBackgroundDownloader::plBackgroundDownloader()
{
BackgroundDownloaderLog(kHeader, "--- Starting background download ---");
fBGDownloaderRun = CreateEvent(
NULL, // default security attributes
TRUE, // manual-reset event
FALSE, // initial state is signaled
NULL // unnamed
);
fBGDownloaderIsPaused = CreateEvent(
NULL, // default security attributes
FALSE, // manual-reset event
TRUE, // initial state is signaled
NULL // unnamed
);
}
plBackgroundDownloader::~plBackgroundDownloader()
{
HANDLE runHandle = fBGDownloaderRun;
fBGDownloaderRun = NULL;
CloseHandle(runHandle);
HANDLE pausedHandle = fBGDownloaderIsPaused;
fBGDownloaderIsPaused = NULL;
CloseHandle(pausedHandle);
BackgroundDownloaderLog(kHeader, "--- Background download done ---");
}
UInt32 plBackgroundDownloader::IGetDownloadSize()
{
if (!IGetDataManifest())
return 0;
UInt32 downloadSize = 0;
UInt32 downloadFiles = 0;
for (MfsFileVec::iterator i = fMfsVec.begin(); i != fMfsVec.end(); ++i)
{
plManifestFile* mfsFile = (*i);
if (!mfsFile->IsLocalUpToDate())
{
downloadFiles++;
downloadSize += mfsFile->GetDownloadSize();
}
}
BackgroundDownloaderLog(kInfo, "Got download stats, %d files, %d bytes", downloadFiles, downloadSize);
return downloadSize;
}
bool plBackgroundDownloader::CheckFreeSpace(UInt32 bytesNeeded)
{
#ifdef HS_BUILD_FOR_WIN32
ULARGE_INTEGER freeBytesAvailable, totalNumberOfBytes, neededBytes;
if (GetDiskFreeSpaceEx(NULL, &freeBytesAvailable, &totalNumberOfBytes, NULL))
{
neededBytes.HighPart = 0;
neededBytes.LowPart = bytesNeeded;
if (neededBytes.QuadPart > freeBytesAvailable.QuadPart)
{
BackgroundDownloaderLog(kInfo, "Not enough disk space (asked for %d bytes)", bytesNeeded);
return false;
}
}
#endif // HS_BUILD_FOR_WIN32
return true;
}
bool plBackgroundDownloader::IDecompressSound(plManifestFile* mfsFile, bool noOverwrite)
{
UInt32 flags = mfsFile->GetFlags();
if ( (hsCheckBits(flags, plManifestFile::kSndFlagCacheSplit) || hsCheckBits(flags, plManifestFile::kSndFlagCacheStereo)) && stricmp(plFileUtils::GetFileExt(mfsFile->GetName()), "ogg") == 0)
{
plAudioFileReader* reader = plAudioFileReader::CreateReader(mfsFile->GetName(), plAudioCore::kAll, plAudioFileReader::kStreamNative);
if (!reader)
{
BackgroundDownloaderLog(kInfo, "Unable to create audio file reader for %s", mfsFile->GetName());
return false;
}
UInt32 size = reader->GetDataSize();
delete reader;
// Make sure we have enough free space
if (!CheckFreeSpace(size))
return false;
if (hsCheckBits(flags, plManifestFile::kSndFlagCacheSplit))
plAudioFileReader::CacheFile(mfsFile->GetName(), true, noOverwrite);
if (hsCheckBits(flags, plManifestFile::kSndFlagCacheStereo))
plAudioFileReader::CacheFile(mfsFile->GetName(), false, noOverwrite);
}
return true;
}
bool plBackgroundDownloader::Run()
{
// Wait to be signaled that we've gotten at least as far as the startup age
WaitForSingleObject(fBGDownloaderRun, INFINITE);
IGetDataManifest();
plFileUtils::CreateDir("dat");
plFileUtils::CreateDir("sfx");
bool result = true;
plResManager* resMgr = ((plResManager*)hsgResMgr::ResMgr());
for (MfsFileVec::iterator i = fMfsVec.begin(); i != fMfsVec.end(); ++i)
{
plManifestFile* mfsFile = (*i);
if (!mfsFile->IsLocalUpToDate())
{
if (!CheckFreeSpace(mfsFile->GetDiskSize()))
return false;
FileType type = IGetFile(mfsFile);
if (type == kPrp)
{
// Checks for existence before attempting to remove
resMgr->RemoveSinglePage(mfsFile->GetName());
if (!resMgr->FindSinglePage(mfsFile->GetName()))
{
resMgr->AddSinglePage(mfsFile->GetName());
}
}
else if (type == kOther)
{
if (!IDecompressSound(mfsFile, false))
{
char text[MAX_PATH];
StrPrintf(text, arrsize(text), "%s could not be decompressed", mfsFile->GetName());
BackgroundDownloaderLog(kInfo, text );
hsAssert(false, text);
result = false;
}
}
else
{
char text[MAX_PATH];
StrPrintf(text, arrsize(text), "Failed downloading file: %s", mfsFile->GetName());
BackgroundDownloaderLog(kInfo, text );
hsAssert(false, text);
result = false;
}
}
}
return result;
}
void plBackgroundDownloader::CleanUp()
{
BackgroundDownloaderLog(kMajorStatus, "Cleaning up background downloader..." );
for (MfsFileVec::iterator i = fMfsVec.begin(); i != fMfsVec.end(); ++i)
{
plManifestFile* file = (*i);
delete file;
}
fMfsVec.clear();
}
void plBackgroundDownloader::Pause()
{
if (fBGDownloaderRun != NULL && fBGDownloaderIsPaused != NULL)
{
ResetEvent(fBGDownloaderRun);
WaitForSingleObject(fBGDownloaderIsPaused, INFINITE);
BackgroundDownloaderLog(kStatus, "--- Background download paused ---");
}
}
void plBackgroundDownloader::UnPause()
{
if (fBGDownloaderRun != NULL && fBGDownloaderIsPaused != NULL)
{
SetEvent(fBGDownloaderRun);
BackgroundDownloaderLog(kStatus, "--- Background download resumed ---");
}
}
plBackgroundDownloader::FileType plBackgroundDownloader::IGetFile(const plManifestFile* mfsFile)
{
BackgroundDownloaderLog(kInfo, " Setting up to download file %s", mfsFile->GetName());
bool downloadDone = false;
wchar* wServerPath = hsStringToWString(mfsFile->GetServerPath());
int numTries = 0;
while (!downloadDone)
{
if (WaitForSingleObject(fBGDownloaderRun, 0) == WAIT_TIMEOUT)
SignalObjectAndWait(fBGDownloaderIsPaused, fBGDownloaderRun, INFINITE, FALSE);
if (numTries >= kMaxDownloadTries)
{
BackgroundDownloaderLog(kInfo, " Max download tries exceeded (%d). Aborting download...", kMaxDownloadTries);
return kFail;
}
plBGDownloadStream* downloadStream = TRACKED_NEW plBGDownloadStream();
if (!downloadStream->Open(mfsFile->GetName(), "wb"))
{
BackgroundDownloaderLog(kInfo, " Unable to create file. Aborting download...");
return kFail;
}
BackgroundDownloaderLog(kInfo, " Downloading file %s...", mfsFile->GetName());
fSuccess = false;
fDoneWithFile = false;
NetCliFileDownloadRequest(
wServerPath,
downloadStream,
DownloadFileCallback,
this
);
while (!fDoneWithFile) {
AsyncSleep(100);
}
if (!fSuccess) {
// remove partial file and die (server didn't have the file or server is shutting down)
plFileUtils::RemoveFile(mfsFile->GetName(), true);
BackgroundDownloaderLog(kError, " File %s failed to download.", mfsFile->GetName());
}
else {
AsyncSleep(100);
if (downloadStream->DecompressedOk()) {
BackgroundDownloaderLog(kInfo, " Decompress successful." );
// download and decompress successful, do a md5 check on the resulting file
plMD5Checksum localMD5(mfsFile->GetName());
if (localMD5 != mfsFile->GetChecksum()) {
plFileUtils::RemoveFile(mfsFile->GetName(), true);
BackgroundDownloaderLog(kError, " File %s MD5 check FAILED.", mfsFile->GetName());
// don't set downloadDone so we attempt to re-download from the server
}
else {
BackgroundDownloaderLog(kInfo, " MD5 check succeeded.");
downloadDone = true;
}
}
else {
plFileUtils::RemoveFile(mfsFile->GetName(), true);
BackgroundDownloaderLog(kError, " File %s failed to decompress.", mfsFile->GetName());
// don't set downloadDone so we attempt to re-download from the server
}
}
delete downloadStream;
++numTries;
}
delete [] wServerPath;
if (!fSuccess)
return kFail;
if (stricmp(plFileUtils::GetFileExt(mfsFile->GetName()), "prp") == 0)
return kPrp;
return kOther;
}
bool plBackgroundDownloader::IGetDataManifest()
{
if (fMfsVec.size() > 0)
return true;
BackgroundDownloaderLog(kMajorStatus, "Downloading new manifest from data server..." );
fSuccess = false;
unsigned numTries = 0;
while (!fSuccess)
{
numTries++;
fDoneWithFile = false;
NetCliFileManifestRequest(ManifestCallback, this, s_manifest);
while (!fDoneWithFile)
{
NetClientUpdate();
plgDispatch::Dispatch()->MsgQueueProcess();
AsyncSleep(10);
}
if (!fSuccess)
{
fMfsVec.clear(); // clear out any bad data
if (numTries > kMaxDownloadTries)
break; // abort
}
}
if (fSuccess)
BackgroundDownloaderLog(kStatus, "New manifest read; number of files: %d", fMfsVec.size() );
else
BackgroundDownloaderLog(kStatus, "Failed to download manifest after trying %d times", kMaxDownloadTries);
return fSuccess;
}
void plBackgroundDownloader::DoneWithFile(bool success)
{
fDoneWithFile = true;
fSuccess = success;
}
void plBackgroundDownloader::DoneWithManifest(bool success, const NetCliFileManifestEntry manifestEntires[], unsigned entryCount)
{
BackgroundDownloaderLog(kStatus, "New age manifest received. Reading...");
if (success)
{
for (unsigned i = 0; i < entryCount; i++)
{
char* name = hsWStringToString(manifestEntires[i].clientName);
char* serverPath = hsWStringToString(manifestEntires[i].downloadName);
char* md5Str = hsWStringToString(manifestEntires[i].md5);
int size = manifestEntires[i].fileSize;
int zipsize = manifestEntires[i].zipSize;
int flags = manifestEntires[i].flags;
if (stricmp(plFileUtils::GetFileExt(name), "gz"))
flags |= plManifestFile::kFlagZipped; // add zipped flag if necessary
plMD5Checksum sum;
sum.SetFromHexString(md5Str);
fMfsVec.push_back(TRACKED_NEW plManifestFile(name, serverPath, sum, size, zipsize, flags, false));
delete [] name;
delete [] serverPath;
delete [] md5Str;
}
}
fDoneWithFile = true;
fSuccess = success;
}

View File

@ -0,0 +1,98 @@
/*==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 plBackgroundDownloader_h_inc
#define plBackgroundDownloader_h_inc
#include <vector>
#include "hsTypes.h"
class plManifestFile;
struct NetCliFileManifestEntry;
class plBackgroundDownloader
{
protected:
static plBackgroundDownloader* fInstance;
static void Init();
static void Shutdown();
static void ThreadMain(void * param);
plBackgroundDownloader();
~plBackgroundDownloader();
HANDLE fBGDownloaderRun;
HANDLE fBGDownloaderIsPaused;
enum FileType {kFail, kPrp, kOther};
typedef std::vector<plManifestFile*> MfsFileVec;
MfsFileVec fMfsVec;
public:
bool fDoneWithFile;
bool fSuccess;
bool IGetDataManifest();
FileType IGetFile(const plManifestFile* mfsFile);
UInt32 IGetDownloadSize();
bool IDecompressSound(plManifestFile* mfsFile, bool noOverwrite = false);
public:
static plBackgroundDownloader* GetInstance();
static void StartThread();
bool Run();
void CleanUp();
void Pause();
void UnPause();
static bool CheckFreeSpace(UInt32 bytesNeeded);
// called by download callbacks to tell it we are done with the current file
void DoneWithFile(bool success);
void DoneWithManifest(bool success, const NetCliFileManifestEntry manifestEntires[], unsigned entryCount);
};
#endif //plBackgroundDownloader_h_inc

View File

@ -0,0 +1,518 @@
/*==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 "plResPatcher.h"
#include "../NucleusLib/inc/hsResMgr.h"
#include "../plAgeDescription/plAgeManifest.h"
#include "../plResMgr/plResManager.h"
#include "../plFile/plFileUtils.h"
#include "../plFile/plEncryptedStream.h"
#include "../plCompression/plZlibStream.h"
#include "../plAudioCore/plAudioFileReader.h"
#include "../plProgressMgr/plProgressMgr.h"
#include "../pnAsyncCore/pnAsyncCore.h"
#include "../pnNetCli/pnNetCli.h"
#include "../plNetGameLib/plNetGameLib.h"
#include "../pnDispatch/plDispatch.h"
#include "../plStatusLog/plStatusLog.h"
static const unsigned kMaxDownloadTries = 10;
//////////////////////////////////////////////////////////////////////////////
class plDownloadStream : public plZlibStream
{
private:
plOperationProgress* fProgress;
unsigned fBytesReceived;
public:
plDownloadStream(plOperationProgress* progress) : fProgress(progress), fBytesReceived(0), plZlibStream() {}
virtual ~plDownloadStream() {}
virtual UInt32 Write(UInt32 byteCount, const void* buffer);
void RewindProgress() {fProgress->Increment(-(hsScalar)fBytesReceived);} // rewind the progress bar by as far as we got
};
UInt32 plDownloadStream::Write(UInt32 byteCount, const void* buffer)
{
fProgress->Increment((hsScalar)byteCount);
fBytesReceived += byteCount;
return plZlibStream::Write(byteCount, buffer);
}
//////////////////////////////////////////////////////////////////////////////
static void DownloadFileCallback(ENetError result, void* param, const wchar filename[], hsStream* writer)
{
plResPatcher* patcher = (plResPatcher*)param;
// Retry download unless shutting down or file not found
switch (result) {
case kNetSuccess:
writer->Close();
patcher->DoneWithFile(true);
break;
case kNetErrFileNotFound:
case kNetErrRemoteShutdown:
writer->Close();
patcher->DoneWithFile(false);
break;
default:
writer->Rewind();
NetCliFileDownloadRequest(
filename,
writer,
DownloadFileCallback,
param
);
break;
}
}
static void ManifestCallback(ENetError result, void* param, const wchar group[], const NetCliFileManifestEntry manifest[], unsigned entryCount)
{
plResPatcher* patcher = (plResPatcher*)param;
patcher->DoneWithManifest(result == kNetSuccess, manifest, entryCount);
}
//// Constructor/Destructor //////////////////////////////////////////////////
plResPatcher::plResPatcher(const char* ageToPatch, bool showAgeName)
{
fAgeToPatch = ageToPatch;
fAlwaysShowAgeName = showAgeName;
IInit();
}
void plResPatcher::IInit()
{
PatcherLog(kHeader, "--- Starting patch process for %s ---", fAgeToPatch.c_str());
}
plResPatcher::~plResPatcher()
{
PatcherLog(kHeader, "--- Patch process done for %s ---", fAgeToPatch.c_str());
for (MfsFileVec::iterator i = fMfsVec.begin(); i != fMfsVec.end(); ++i)
{
plManifestFile* file = (*i);
delete file;
}
fMfsVec.clear();
}
UInt32 plResPatcher::IGetDownloadSize()
{
if (!IGetAgeManifest())
return 0;
#ifdef PLASMA_EXTERNAL_RELEASE
bool showAgeName = fAlwaysShowAgeName;
#else
bool showAgeName = true;
#endif
char msg[128];
if (!fAgeToPatch.empty())
{
if (showAgeName)
sprintf(msg, "Checking age %s...", fAgeToPatch.c_str());
else
strcpy(msg, "Checking age...");
}
else
sprintf(msg, "Checking...");
plOperationProgress* progress = plProgressMgr::GetInstance()->RegisterOperation((hsScalar)(fMfsVec.size()), msg, plProgressMgr::kNone, false, true);
UInt32 downloadSize = 0;
UInt32 downloadFiles = 0;
for (MfsFileVec::iterator i = fMfsVec.begin(); i != fMfsVec.end(); ++i)
{
plManifestFile* mfsFile = (*i);
if (!mfsFile->IsLocalUpToDate())
{
downloadFiles++;
downloadSize += mfsFile->GetDownloadSize();
}
progress->Increment(1.f);
}
delete progress;
PatcherLog(kInfo, "Got download stats, %d files, %d bytes", downloadFiles, downloadSize);
return downloadSize;
}
bool plResPatcher::CheckFreeSpace(UInt32 bytesNeeded)
{
#ifdef HS_BUILD_FOR_WIN32
ULARGE_INTEGER freeBytesAvailable, totalNumberOfBytes, neededBytes;
if (GetDiskFreeSpaceEx(NULL, &freeBytesAvailable, &totalNumberOfBytes, NULL))
{
neededBytes.HighPart = 0;
neededBytes.LowPart = bytesNeeded;
if (neededBytes.QuadPart > freeBytesAvailable.QuadPart)
{
PatcherLog(kInfo, "Not enough disk space (asked for %d bytes)", bytesNeeded);
return false;
}
}
#endif // HS_BUILD_FOR_WIN32
return true;
}
bool plResPatcher::IDecompressSound(plManifestFile* mfsFile, bool noOverwrite)
{
UInt32 flags = mfsFile->GetFlags();
if ( (hsCheckBits(flags, plManifestFile::kSndFlagCacheSplit) || hsCheckBits(flags, plManifestFile::kSndFlagCacheStereo)) && stricmp(plFileUtils::GetFileExt(mfsFile->GetName()), "ogg") == 0)
{
plAudioFileReader* reader = plAudioFileReader::CreateReader(mfsFile->GetName(), plAudioCore::kAll, plAudioFileReader::kStreamNative);
if (!reader)
{
PatcherLog(kInfo, "Unable to create audio file reader for %s", mfsFile->GetName());
return false;
}
UInt32 size = reader->GetDataSize();
delete reader;
// Make sure we have enough free space
if (!CheckFreeSpace(size))
return false;
if (hsCheckBits(flags, plManifestFile::kSndFlagCacheSplit))
plAudioFileReader::CacheFile(mfsFile->GetName(), true, noOverwrite);
if (hsCheckBits(flags, plManifestFile::kSndFlagCacheStereo))
plAudioFileReader::CacheFile(mfsFile->GetName(), false, noOverwrite);
}
return true;
}
bool plResPatcher::Update()
{
UInt32 downloadSize = IGetDownloadSize();
// if download size is 0, nothing to download, but we still need to tell the res manager about the files
plFileUtils::CreateDir("dat");
plFileUtils::CreateDir("sfx");
if (!CheckFreeSpace(downloadSize))
return false;
#ifdef PLASMA_EXTERNAL_RELEASE
bool showAgeName = fAlwaysShowAgeName;
#else
bool showAgeName = true;
#endif
char msg[128];
if (!fAgeToPatch.empty())
{
if (showAgeName)
sprintf(msg, "Downloading %s data...", fAgeToPatch.c_str());
else
strcpy(msg, "Downloading age data...");
}
else
sprintf(msg, "Downloading...");
plOperationProgress* progress = plProgressMgr::GetInstance()->RegisterOverallOperation((hsScalar)downloadSize, msg, plProgressMgr::kUpdateText, true);
bool result = true;
plResManager* resMgr = ((plResManager*)hsgResMgr::ResMgr());
for (MfsFileVec::iterator i = fMfsVec.begin(); i != fMfsVec.end(); ++i)
{
plManifestFile* mfsFile = (*i);
if (!mfsFile->IsLocalUpToDate())
{
FileType type = IGetFile(mfsFile, progress);
if (type == kPrp)
{
// Checks for existence before attempting to remove
resMgr->RemoveSinglePage(mfsFile->GetName());
}
else if (type == kOther)
{
if (!IDecompressSound(mfsFile, false))
{
char text[MAX_PATH];
StrPrintf(text, arrsize(text), "%s could not be decompressed", mfsFile->GetName());
PatcherLog(kInfo, text );
hsAssert(false, text);
result = false;
}
}
else
{
char text[MAX_PATH];
StrPrintf(text, arrsize(text), "Failed downloading file: %s", mfsFile->GetName());
PatcherLog(kInfo, text );
hsAssert(false, text);
result = false;
}
}
else
{
if (!IDecompressSound(mfsFile, true))
{
char text[MAX_PATH];
StrPrintf(text, arrsize(text), "%s could not be decompressed", mfsFile->GetName());
PatcherLog(kInfo, text );
hsAssert(false, text);
result = false;
}
}
if (!resMgr->FindSinglePage(mfsFile->GetName()) && stricmp(plFileUtils::GetFileExt(mfsFile->GetName()), "prp") == 0)
{
resMgr->AddSinglePage(mfsFile->GetName());
}
}
PatcherLog(kMajorStatus, "Cleaning up patcher..." );
delete progress;
return result;
}
plResPatcher::FileType plResPatcher::IGetFile(const plManifestFile* mfsFile, plOperationProgress* progressBar)
{
PatcherLog(kInfo, " Setting up to download file %s", mfsFile->GetName());
bool downloadDone = false;
wchar* wServerPath = hsStringToWString(mfsFile->GetServerPath());
int numTries = 0;
while (!downloadDone)
{
if (numTries >= kMaxDownloadTries)
{
PatcherLog(kInfo, " Max download tries exceeded (%d). Aborting download...", kMaxDownloadTries);
return kFail;
}
plDownloadStream downloadStream(progressBar);
if (!downloadStream.Open(mfsFile->GetName(), "wb"))
{
PatcherLog(kInfo, " Unable to create file. Aborting download...");
return kFail;
}
PatcherLog(kInfo, " Downloading file %s...", mfsFile->GetName());
fSuccess = false;
fDoneWithFile = false;
NetCliFileDownloadRequest(
wServerPath,
&downloadStream,
DownloadFileCallback,
this
);
while (!fDoneWithFile) {
NetClientUpdate();
plgDispatch::Dispatch()->MsgQueueProcess();
AsyncSleep(10);
}
if (!fSuccess) {
// remove partial file and die (server didn't have the file or server is shutting down)
downloadStream.RewindProgress();
plFileUtils::RemoveFile(mfsFile->GetName(), true);
PatcherLog(kError, " File %s failed to download.", mfsFile->GetName());
downloadDone = true;
}
else {
if (downloadStream.DecompressedOk()) {
PatcherLog(kInfo, " Decompress successful." );
// download and decompress successful, do a md5 check on the resulting file
plMD5Checksum localMD5(mfsFile->GetName());
if (localMD5 != mfsFile->GetChecksum()) {
downloadStream.RewindProgress();
downloadStream.Close();
plFileUtils::RemoveFile(mfsFile->GetName(), true);
PatcherLog(kError, " File %s MD5 check FAILED.", mfsFile->GetName());
// don't set downloadDone so we attempt to re-download from the server
}
else {
downloadStream.Close();
PatcherLog(kInfo, " MD5 check succeeded.");
downloadDone = true;
}
}
else {
downloadStream.RewindProgress();
downloadStream.Close();
plFileUtils::RemoveFile(mfsFile->GetName(), true);
PatcherLog(kError, " File %s failed to decompress.", mfsFile->GetName());
// don't set downloadDone so we attempt to re-download from the server
}
}
++numTries;
}
FREE(wServerPath);
if (!fSuccess)
return kFail;
if (stricmp(plFileUtils::GetFileExt(mfsFile->GetName()), "prp") == 0)
return kPrp;
return kOther;
}
bool plResPatcher::IGetAgeManifest()
{
if (fMfsVec.size() > 0)
return true;
PatcherLog(kMajorStatus, "Downloading new manifest from data server..." );
fSuccess = false;
wchar* group = hsStringToWString(fAgeToPatch.c_str());
unsigned numTries = 0;
while (!fSuccess)
{
numTries++;
fDoneWithFile = false;
NetCliFileManifestRequest(ManifestCallback, this, group);
while (!fDoneWithFile)
{
NetClientUpdate();
plgDispatch::Dispatch()->MsgQueueProcess();
AsyncSleep(10);
}
if (!fSuccess)
{
fMfsVec.clear(); // clear out any bad data
if (numTries > kMaxDownloadTries)
break; // abort
}
}
delete [] group;
if (fSuccess)
PatcherLog(kStatus, "New age manifest read; number of files: %d", fMfsVec.size() );
else
PatcherLog(kStatus, "Failed to download manifest after trying %d times", kMaxDownloadTries);
return fSuccess;
}
void plResPatcher::DoneWithManifest(bool success, const NetCliFileManifestEntry manifestEntires[], unsigned entryCount)
{
PatcherLog(kStatus, "New age manifest received. Reading...");
if (success)
{
for (unsigned i = 0; i < entryCount; i++)
{
char* name = hsWStringToString(manifestEntires[i].clientName);
char* serverPath = hsWStringToString(manifestEntires[i].downloadName);
char* md5Str = hsWStringToString(manifestEntires[i].md5);
int size = manifestEntires[i].fileSize;
int zipsize = manifestEntires[i].zipSize;
int flags = manifestEntires[i].flags;
if (stricmp(plFileUtils::GetFileExt(name), "gz"))
flags |= plManifestFile::kFlagZipped; // add zipped flag if necessary
plMD5Checksum sum;
sum.SetFromHexString(md5Str);
fMfsVec.push_back(TRACKED_NEW plManifestFile(name, serverPath, sum, size, zipsize, flags));
delete [] name;
delete [] serverPath;
delete [] md5Str;
}
}
fDoneWithFile = true;
fSuccess = success;
}
void PatcherLog(PatcherLogType type, const char* format, ...)
{
UInt32 color = 0;
switch (type)
{
case kHeader: color = plStatusLog::kWhite; break;
case kInfo: color = plStatusLog::kBlue; break;
case kMajorStatus: color = plStatusLog::kYellow; break;
case kStatus: color = plStatusLog::kGreen; break;
case kError: color = plStatusLog::kRed; break;
}
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);
gStatusLog->AddLineV(color, format, args);
va_end(args);
}

View File

@ -0,0 +1,103 @@
/*==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 plResPatcher_h_inc
#define plResPatcher_h_inc
#include "hsStlUtils.h"
#include "../pnUtils/pnUtils.h"
#include "../pnNetBase/pnNetBase.h"
#include "../plEncryption/plChecksum.h"
class plManifest;
class plManifestFile;
class plOperationProgress;
struct NetCliFileManifestEntry;
class plResPatcher
{
protected:
enum FileType {kFail, kPrp, kOther};
std::string fAgeToPatch;
typedef std::vector<plManifestFile*> MfsFileVec;
MfsFileVec fMfsVec;
bool fDoneWithFile;
bool fSuccess;
bool fAlwaysShowAgeName;
void IInit();
static void ILog(UInt32 type, const char* format, ...);
FileType IGetFile(const plManifestFile* mfsFile, plOperationProgress* progressBar);
bool IGetAgeManifest();
UInt32 IGetDownloadSize();
bool IDecompressSound(plManifestFile* mfsFile, bool noOverwrite = false);
public:
plResPatcher(const char* ageToPatch, bool showAgeName = false);
~plResPatcher();
bool Update();
static bool CheckFreeSpace(UInt32 bytesNeeded);
// called by download callbacks to tell it we are done with the current file
void DoneWithFile(bool success) {fDoneWithFile = true; fSuccess = success;}
void DoneWithManifest(bool success, const NetCliFileManifestEntry manifestEntires[], unsigned entryCount);
};
enum PatcherLogType
{
kHeader,
kInfo,
kMajorStatus,
kStatus,
kError,
};
void PatcherLog(PatcherLogType type, const char* format, ...);
#endif // _plResPatcher_h