/*==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 . 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 "plRegistryNode.h" #include "plRegistryKeyList.h" #include "plRegistryHelpers.h" #include "../pnKeyedObject/plKeyImp.h" #include "../plStatusLog/plStatusLog.h" #include "../pnFactory/plFactory.h" #include "../plFile/plFileUtils.h" #include "hsStlUtils.h" #include "plVersion.h" plRegistryPageNode::plRegistryPageNode(const char* path) : fValid(kPageCorrupt) , fPath(nil) , fDynLoadedTypes(0) , fStaticLoadedTypes(0) , fOpenRequests(0) , fIsNewPage(false) { fPath = hsStrcpy(path); hsStream* stream = OpenStream(); if (stream) { fPageInfo.Read(&fStream); fValid = IVerify(); CloseStream(); } } plRegistryPageNode::plRegistryPageNode(const plLocation& location, const char* age, const char* page, const char* dataPath) : fValid(kPageOk) , fPath(nil) , fPageInfo(location) , fDynLoadedTypes(0) , fStaticLoadedTypes(0) , fOpenRequests(0) , fIsNewPage(true) { fPageInfo.SetStrings(age, page); char filePath[512]; // Copy the path over strncpy(filePath, dataPath, sizeof(filePath)); plFileUtils::AddSlash(filePath); // Time to construct our actual file name. For now, we'll use the same old format // of age_page.extension strncat(filePath, fPageInfo.GetAge(), sizeof(filePath)); strncat(filePath, "_District_", sizeof(filePath)); strncat(filePath, fPageInfo.GetPage(), sizeof(filePath)); strncat(filePath, ".prp", sizeof(filePath)); fPath = hsStrcpy(filePath); } plRegistryPageNode::~plRegistryPageNode() { delete [] fPath; UnloadKeys(); } PageCond plRegistryPageNode::IVerify() { // Check the checksum values first, to make sure the files aren't corrupt UInt32 ourChecksum = 0; hsStream* stream = OpenStream(); if (stream) { ourChecksum = stream->GetEOF() - fPageInfo.GetDataStart(); CloseStream(); } if (ourChecksum != fPageInfo.GetChecksum()) return kPageCorrupt; // If major version out-of-date, entire location is screwed if (fPageInfo.GetMajorVersion() > plVersion::GetMajorVersion()) return kPageTooNew; else if (fPageInfo.GetMajorVersion() < plVersion::GetMajorVersion()) return kPageOutOfDate; // Check the minor versions const plPageInfo::ClassVerVec& classVersions = fPageInfo.GetClassVersions(); for (int i = 0; i < classVersions.size(); i++) { const plPageInfo::ClassVersion& cv = classVersions[i]; UInt16 curVersion = plVersion::GetCreatableVersion(cv.Class); if (curVersion > cv.Version) return kPageOutOfDate; else if (curVersion < cv.Version) return kPageTooNew; } return kPageOk; } hsStream* plRegistryPageNode::OpenStream() { if (fOpenRequests == 0) { if (!fStream.Open(fPath, "rb")) return nil; } fOpenRequests++; return &fStream; } void plRegistryPageNode::CloseStream() { if (fOpenRequests > 0) fOpenRequests--; if (fOpenRequests == 0) fStream.Close(); } void plRegistryPageNode::LoadKeys() { hsAssert(IsValid(), "Trying to load keys for invalid page"); hsAssert(!fIsNewPage, "Trying to read a new page"); if (IsFullyLoaded()) return; hsStream* stream = OpenStream(); if (!stream) { hsAssert(0, xtl::format("plRegistryPageNode::LoadKeysFromSource - bad stream %s,%s", GetPageInfo().GetAge(), GetPageInfo().GetPage()).c_str()); return; } // If we're loading keys in the middle of a read because FindKey() failed, we'd better // make note of our stream position and restore it when we're done. UInt32 oldPos = stream->GetPosition(); stream->SetPosition(GetPageInfo().GetIndexStart()); // Read in the number of key types UInt32 numTypes = stream->ReadSwap32(); for (UInt32 i = 0; i < numTypes; i++) { UInt16 classType = stream->ReadSwap16(); plRegistryKeyList* keyList = IGetKeyList(classType); if (!keyList) { keyList = TRACKED_NEW plRegistryKeyList(classType); fKeyLists[classType] = keyList; } keyList->Read(stream); } stream->SetPosition(oldPos); CloseStream(); fStaticLoadedTypes = fKeyLists.size(); } void plRegistryPageNode::UnloadKeys() { KeyMap::iterator it = fKeyLists.begin(); for (; it != fKeyLists.end(); it++) { plRegistryKeyList* keyList = it->second; it->second = nullptr; delete keyList; } fKeyLists.clear(); fDynLoadedTypes = 0; fStaticLoadedTypes = 0; } //// plWriteIterator ///////////////////////////////////////////////////////// // Key iterator for writing objects class plWriteIterator : public plRegistryKeyIterator { protected: hsStream* fStream; public: plWriteIterator(hsStream* s) : fStream(s) {} virtual hsBool EatKey(const plKey& key) { plKeyImp* imp = (plKeyImp*)key; imp->WriteObject(fStream); return true; } }; void plRegistryPageNode::Write() { hsAssert(fOpenRequests == 0, "Trying to write while the page is open for reading"); if (!fStream.Open(fPath, "wb")) { hsAssert(0, "Couldn't open file for writing"); return; } // Some prep stuff. Assign object IDs for every key in this page, and put the // versions of all our creatable types in the pageinfo. fPageInfo.ClearClassVersions(); KeyMap::const_iterator it; for (it = fKeyLists.begin(); it != fKeyLists.end(); it++) { plRegistryKeyList* keyList = it->second; keyList->PrepForWrite(); int ver = plVersion::GetCreatableVersion(keyList->GetClassType()); fPageInfo.AddClassVersion(keyList->GetClassType(), ver); } // First thing we write is the pageinfo. Later we'll rewind and overwrite this with the final values fPageInfo.Write(&fStream); fPageInfo.SetDataStart(fStream.GetPosition()); // Write all our objects plWriteIterator writer(&fStream); IterateKeys(&writer); fPageInfo.SetIndexStart(fStream.GetPosition()); // Write our keys fStream.WriteSwap32(fKeyLists.size()); for (it = fKeyLists.begin(); it != fKeyLists.end(); it++) { plRegistryKeyList* keyList = it->second; fStream.WriteSwap16(keyList->GetClassType()); keyList->Write(&fStream); } // Rewind and write the pageinfo with the correct data and index offsets fStream.Rewind(); fPageInfo.SetChecksum(fStream.GetEOF() - fPageInfo.GetDataStart()); fPageInfo.Write(&fStream); fStream.Close(); } //// IterateKeys ///////////////////////////////////////////////////////////// hsBool plRegistryPageNode::IterateKeys(plRegistryKeyIterator* iterator) const { KeyMap::const_iterator it = fKeyLists.begin(); for (; it != fKeyLists.end(); it++) { plRegistryKeyList* keyList = it->second; if (!keyList->IterateKeys(iterator)) return false; } return true; } //// IterateKeys ///////////////////////////////////////////////////////////// // Restricted version that only iterates through the keys of a given class // type. hsBool plRegistryPageNode::IterateKeys(plRegistryKeyIterator* iterator, UInt16 classToRestrictTo) const { plRegistryKeyList* keyList = IGetKeyList(classToRestrictTo); if (keyList != nil) return keyList->IterateKeys(iterator); return true; } plKeyImp* plRegistryPageNode::FindKey(UInt16 classType, const char* name) const { plRegistryKeyList* keys = IGetKeyList(classType); if (keys == nil) return nil; return keys->FindKey(name); } plKeyImp* plRegistryPageNode::FindKey(const plUoid& uoid) const { plRegistryKeyList* keys = IGetKeyList(uoid.GetClassType()); if (keys == nil) return nil; return keys->FindKey(uoid); } void plRegistryPageNode::AddKey(plKeyImp* key) { UInt16 classType = key->GetUoid().GetClassType(); plRegistryKeyList* keys = fKeyLists[classType]; if (keys == nil) { keys = TRACKED_NEW plRegistryKeyList(classType); fKeyLists[classType] = keys; } // Error check if (keys->FindKey(key->GetUoid().GetObjectName()) != nil) { //char str[512], tempStr[128]; //sprintf(str, "Attempting to add a key with a duplicate name. Not allowed." // "\n\n(Key name: %s, Class: %s, Loc: %s)", key->GetUoid().GetObjectName(), // plFactory::GetNameOfClass(classType), key->GetUoid().GetLocation().StringIze(tempStr)); //hsStatusMessage(str); hsBool recovered = false; // Attempt recovery for (int i = 0; i < 500; i++) { char tempName[512]; sprintf(tempName, "%s%d", key->GetUoid().GetObjectName(), i); if (keys->FindKey(tempName) == nil) { plUoid uoid(key->GetUoid().GetLocation(), key->GetUoid().GetClassType(), tempName, key->GetUoid().GetLoadMask()); key->SetUoid(uoid); recovered = true; break; } } if (!recovered) { hsAssert(0, "Couldn't allocate a unique key"); return; } } plRegistryKeyList::LoadStatus loadStatusChange; keys->AddKey(key, loadStatusChange); if (loadStatusChange == plRegistryKeyList::kDynLoaded) fDynLoadedTypes++; } void plRegistryPageNode::SetKeyUsed(plKeyImp* key) { plRegistryKeyList* keys = IGetKeyList(key->GetUoid().GetClassType()); if (keys == nil) return; keys->SetKeyUsed(key); } hsBool plRegistryPageNode::SetKeyUnused(plKeyImp* key) { plRegistryKeyList* keys = IGetKeyList(key->GetUoid().GetClassType()); if (keys == nil) return false; plRegistryKeyList::LoadStatus loadStatusChange; hsBool removed = keys->SetKeyUnused(key, loadStatusChange); // If the key type just changed load status, update our load counts if (loadStatusChange == plRegistryKeyList::kDynUnloaded) fDynLoadedTypes--; else if (loadStatusChange == plRegistryKeyList::kStaticUnloaded) fStaticLoadedTypes--; return removed; } plRegistryKeyList* plRegistryPageNode::IGetKeyList(UInt16 classType) const { KeyMap::const_iterator it = fKeyLists.find(classType); if (it != fKeyLists.end()) return it->second; return nil; } void plRegistryPageNode::DeleteSource() { hsAssert(fOpenRequests == 0, "Deleting a stream that's open for reading"); plFileUtils::RemoveFile(fPath); }