/*==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 "plString.h" #include "pnKeyedObject/plKeyImp.h" #include "plStatusLog/plStatusLog.h" #include "pnFactory/plFactory.h" #include "plVersion.h" plRegistryPageNode::plRegistryPageNode(const plFileName& path) : fValid(kPageCorrupt) , fPath(path) , fLoadedTypes(0) , fOpenRequests(0) , fIsNewPage(false) { hsStream* stream = OpenStream(); if (stream) { fPageInfo.Read(&fStream); fValid = IVerify(); CloseStream(); } } plRegistryPageNode::plRegistryPageNode(const plLocation& location, const plString& age, const plString& page, const plFileName& dataPath) : fValid(kPageOk) , fPageInfo(location) , fLoadedTypes(0) , fOpenRequests(0) , fIsNewPage(true) { fPageInfo.SetStrings(age, page); // Time to construct our actual file name. For now, we'll use the same old format // of age_page.extension fPath = plFileName::Join(dataPath, plString::Format("%s_District_%s.prp", fPageInfo.GetAge().c_str(), fPageInfo.GetPage().c_str())); } plRegistryPageNode::~plRegistryPageNode() { UnloadKeys(); } PageCond plRegistryPageNode::IVerify() { // Check the checksum values first, to make sure the files aren't corrupt uint32_t 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_t 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, plString::Format("plRegistryPageNode::LoadKeysFromSource - bad stream %s,%s", GetPageInfo().GetAge().c_str(), GetPageInfo().GetPage().c_str()).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_t oldPos = stream->GetPosition(); stream->SetPosition(GetPageInfo().GetIndexStart()); // Read in the number of key types uint32_t numTypes = stream->ReadLE32(); for (uint32_t i = 0; i < numTypes; i++) { uint16_t classType = stream->ReadLE16(); plRegistryKeyList* keyList = IGetKeyList(classType); if (!keyList) { keyList = new plRegistryKeyList(classType); fKeyLists[classType] = keyList; } keyList->Read(stream); } stream->SetPosition(oldPos); CloseStream(); fLoadedTypes = fKeyLists.size(); } void plRegistryPageNode::UnloadKeys() { KeyMap::iterator it = fKeyLists.begin(); for (; it != fKeyLists.end(); it++) { plRegistryKeyList* keyList = it->second; delete keyList; } fKeyLists.clear(); fLoadedTypes = 0; } //// plWriteIterator ///////////////////////////////////////////////////////// // Key iterator for writing objects class plWriteIterator : public plRegistryKeyIterator { protected: hsStream* fStream; public: plWriteIterator(hsStream* s) : fStream(s) {} virtual bool 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; 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.WriteLE32(fKeyLists.size()); for (it = fKeyLists.begin(); it != fKeyLists.end(); it++) { plRegistryKeyList* keyList = it->second; fStream.WriteLE16(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 ///////////////////////////////////////////////////////////// bool 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. bool plRegistryPageNode::IterateKeys(plRegistryKeyIterator* iterator, uint16_t classToRestrictTo) const { plRegistryKeyList* keyList = IGetKeyList(classToRestrictTo); if (keyList != nil) return keyList->IterateKeys(iterator); return true; } plKeyImp* plRegistryPageNode::FindKey(uint16_t classType, const plString& 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_t classType = key->GetUoid().GetClassType(); plRegistryKeyList* keys = fKeyLists[classType]; if (keys == nil) { keys = 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); bool recovered = false; // Attempt recovery for (int i = 0; i < 500; i++) { plString tempName = plString::Format("%s%d", key->GetUoid().GetObjectName().c_str(), 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::kTypeLoaded) ++fLoadedTypes; } void plRegistryPageNode::SetKeyUsed(plKeyImp* key) { plRegistryKeyList* keys = IGetKeyList(key->GetUoid().GetClassType()); if (keys == nil) return; keys->SetKeyUsed(key); } bool plRegistryPageNode::SetKeyUnused(plKeyImp* key) { plRegistryKeyList* keys = IGetKeyList(key->GetUoid().GetClassType()); if (keys == nil) return false; plRegistryKeyList::LoadStatus loadStatusChange; bool removed = keys->SetKeyUnused(key, loadStatusChange); // If the key type just changed load status, update our load counts if (loadStatusChange == plRegistryKeyList::kTypeUnloaded) --fLoadedTypes; return removed; } plRegistryKeyList* plRegistryPageNode::IGetKeyList(uint16_t 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"); plFileSystem::Unlink(fPath); }