From f55b3666a0906ff0a6ec663d5989cfb503a79e41 Mon Sep 17 00:00:00 2001 From: parax0 Date: Tue, 5 Jul 2016 01:45:42 -0600 Subject: [PATCH] Added support for saving/loading package definitions --- src/Core/Core.pro | 3 +- src/Core/GameProject/CGameExporter.cpp | 28 +++-- src/Core/GameProject/CGameProject.h | 1 + src/Core/GameProject/CPackage.cpp | 144 +++++++++++++++++++++++++ src/Core/GameProject/CPackage.h | 61 +++++++++-- 5 files changed, 215 insertions(+), 22 deletions(-) create mode 100644 src/Core/GameProject/CPackage.cpp diff --git a/src/Core/Core.pro b/src/Core/Core.pro index 1b58fb01..ce8d9566 100644 --- a/src/Core/Core.pro +++ b/src/Core/Core.pro @@ -285,4 +285,5 @@ SOURCES += \ GameProject/CGameExporter.cpp \ GameProject/CResourceStore.cpp \ GameProject/CVirtualDirectory.cpp \ - GameProject/CResourceEntry.cpp + GameProject/CResourceEntry.cpp \ + GameProject/CPackage.cpp diff --git a/src/Core/GameProject/CGameExporter.cpp b/src/Core/GameProject/CGameExporter.cpp index 011e2056..03d8751e 100644 --- a/src/Core/GameProject/CGameExporter.cpp +++ b/src/Core/GameProject/CGameExporter.cpp @@ -10,8 +10,9 @@ #define COPY_DISC_DATA 0 #define LOAD_PAKS 1 -#define EXPORT_WORLDS 1 -#define EXPORT_COOKED 1 +#define SAVE_PACKAGE_DEFINITIONS 1 +#define EXPORT_WORLDS 0 +#define EXPORT_COOKED 0 CGameExporter::CGameExporter(const TString& rkInputDir, const TString& rkOutputDir) : mStore(this) @@ -186,7 +187,8 @@ void CGameExporter::LoadPaks() continue; } - CPackage *pPackage = new CPackage(CharPak.GetFileName(false), FileUtil::MakeRelative(PakPath.GetFileDirectory(), mGameDir)); + CPackage *pPackage = new CPackage(mpProject, CharPak.GetFileName(false), FileUtil::MakeRelative(PakPath.GetFileDirectory(), mGameDir)); + CResourceCollection *pCollection = pPackage->AddCollection("Default"); // MP1-MP3Proto if (Game() < eCorruption) @@ -207,7 +209,7 @@ void CGameExporter::LoadPaks() CUniqueID ResID(Pak, IDLength); u32 NameLen = Pak.ReadLong(); TString Name = Pak.ReadString(NameLen); - pPackage->AddNamedResource(Name, ResID, ResType); + pCollection->AddResource(Name, ResID, ResType); } u32 NumResources = Pak.ReadLong(); @@ -265,7 +267,7 @@ void CGameExporter::LoadPaks() TString Name = Pak.ReadString(); CFourCC ResType = Pak.ReadLong(); CUniqueID ResID(Pak, IDLength); - pPackage->AddNamedResource(Name, ResID, ResType); + pCollection->AddResource(Name, ResID, ResType); } } @@ -293,8 +295,11 @@ void CGameExporter::LoadPaks() } } - // Add package to project + // Add package to project and save mpProject->AddPackage(pPackage, IsWorldPak); +#if SAVE_PACKAGE_DEFINITIONS + pPackage->Save(); +#endif } } #endif @@ -407,15 +412,18 @@ void CGameExporter::ExportWorlds() // Get output path. DKCR paks are stored in a Worlds folder so we should get the path relative to that so we don't have Worlds\Worlds\. // Other games have all paks in the game root dir so we're fine just taking the original root dir-relative directory. - TWideString PakPath = pPak->PakPath(); + TWideString PakPath = pPak->Path(); TWideString GameWorldsDir = PakPath.GetParentDirectoryPath(L"Worlds", false); if (!GameWorldsDir.IsEmpty()) PakPath = FileUtil::MakeRelative(PakPath, GameWorldsDir); - for (u32 iRes = 0; iRes < pPak->NumNamedResources(); iRes++) + // Note since there's no collections in the cooked data we're guaranteed that every pak will have exactly one collection. + CResourceCollection *pCollection = pPak->CollectionByIndex(0); + + for (u32 iRes = 0; iRes < pCollection->NumResources(); iRes++) { - const SNamedResource& rkRes = pPak->NamedResourceByIndex(iRes); + const SNamedResource& rkRes = pCollection->ResourceByIndex(iRes); if (rkRes.Type == "MLVL" && !rkRes.Name.EndsWith("NODEPEND")) { @@ -424,7 +432,7 @@ void CGameExporter::ExportWorlds() if (!pWorld) { - Log::Error("Couldn't load world " + rkRes.Name + " from package " + pPak->PakName() + "; unable to export"); + Log::Error("Couldn't load world " + rkRes.Name + " from package " + pPak->Name() + "; unable to export"); continue; } diff --git a/src/Core/GameProject/CGameProject.h b/src/Core/GameProject/CGameProject.h index 2666483e..7a988d3b 100644 --- a/src/Core/GameProject/CGameProject.h +++ b/src/Core/GameProject/CGameProject.h @@ -33,6 +33,7 @@ public: inline TWideString DiscDir(bool Relative) const { return Relative ? L"Disc\\" : mProjectRoot + L"Disc\\"; } inline TWideString ContentDir(bool Relative) const { return Relative ? L"Content\\" : mProjectRoot + L"Content\\"; } inline TWideString CookedDir(bool Relative) const { return Relative ? L"Cooked\\" : mProjectRoot + L"Cooked\\"; } + inline TWideString PackagesDir(bool Relative) const { return Relative ? L"Packages\\" : mProjectRoot + L"Packages\\"; } // Accessors inline void SetGame(EGame Game) { mGame = Game; } diff --git a/src/Core/GameProject/CPackage.cpp b/src/Core/GameProject/CPackage.cpp new file mode 100644 index 00000000..d55388e3 --- /dev/null +++ b/src/Core/GameProject/CPackage.cpp @@ -0,0 +1,144 @@ +#include "CPackage.h" +#include "CGameProject.h" +#include +#include +#include + +using namespace tinyxml2; + +void CPackage::Load() +{ + TWideString DefPath = DefinitionPath(false); + + XMLDocument Doc; + Doc.LoadFile(*DefPath.ToUTF8()); + + if (Doc.Error()) + { + Log::Error("Couldn't open pak definition at path: " + DefPath.ToUTF8()); + return; + } + + XMLElement *pRoot = Doc.FirstChildElement("PackageDefinition"); + //EPackageDefinitionVersion Version = (EPackageDefinitionVersion) TString(pRoot->Attribute("Version")).ToInt32(10); + + XMLElement *pColElem = pRoot->FirstChildElement("ResourceCollection"); + + while (pColElem) + { + CResourceCollection *pCollection = AddCollection( pColElem->Attribute("Name") ); + XMLElement *pResElem = pColElem->FirstChildElement("NamedResource"); + + while (pResElem) + { + XMLElement *pNameElem = pResElem->FirstChildElement("Name"); + XMLElement *pIDElem = pResElem->FirstChildElement("ID"); + XMLElement *pTypeElem = pResElem->FirstChildElement("Type"); + + if (!pIDElem || !pNameElem || !pTypeElem) + { + TString ElemName = (pNameElem ? (pIDElem ? "Type" : "ID") : "Name"); + Log::Error("Can't add named resource from pak definition at " + DefPath.ToUTF8() + "; " + ElemName + " element missing"); + } + + else + { + CUniqueID ID = CUniqueID::FromString(pIDElem->GetText()); + TString Name = pNameElem->GetText(); + CFourCC Type = CFourCC(pTypeElem->GetText()); + pCollection->AddResource(Name, ID, Type); + } + + pResElem = pResElem->NextSiblingElement("NamedResource"); + } + + pColElem = pColElem->NextSiblingElement("ResourceCollection"); + } +} + +void CPackage::Save() +{ + TWideString DefPath = DefinitionPath(false); + FileUtil::CreateDirectory(DefPath.GetFileDirectory()); + + // Write XML + XMLDocument Doc; + + XMLDeclaration *pDecl = Doc.NewDeclaration(); + Doc.LinkEndChild(pDecl); + + XMLElement *pRoot = Doc.NewElement("PackageDefinition"); + pRoot->SetAttribute("Version", eVer_Current); + Doc.LinkEndChild(pRoot); + + for (u32 iCol = 0; iCol < mCollections.size(); iCol++) + { + CResourceCollection *pCollection = mCollections[iCol]; + + XMLElement *pColElem = Doc.NewElement("ResourceCollection"); + pColElem->SetAttribute("Name", *pCollection->Name()); + pRoot->LinkEndChild(pColElem); + + for (u32 iRes = 0; iRes < pCollection->NumResources(); iRes++) + { + const SNamedResource& rkRes = pCollection->ResourceByIndex(iRes); + + XMLElement *pResElem = Doc.NewElement("NamedResource"); + pColElem->LinkEndChild(pResElem); + + XMLElement *pName = Doc.NewElement("Name"); + pName->SetText(*rkRes.Name); + pResElem->LinkEndChild(pName); + + XMLElement *pID = Doc.NewElement("ID"); + pID->SetText(*rkRes.ID.ToString()); + pResElem->LinkEndChild(pID); + + XMLElement *pType = Doc.NewElement("Type"); + pType->SetText(*rkRes.Type.ToString()); + pResElem->LinkEndChild(pType); + } + } + + XMLError Error = Doc.SaveFile(*DefPath.ToUTF8()); + + if (Error != XML_SUCCESS) + Log::Error("Failed to save pak definition at path: " + DefPath.ToUTF8()); +} + +TWideString CPackage::DefinitionPath(bool Relative) const +{ + return mpProject->PackagesDir(Relative) + mPakPath + mPakName.ToUTF16() + L".pkd"; +} + +TWideString CPackage::CookedPackagePath(bool Relative) const +{ + return mpProject->DiscDir(Relative) + mPakPath + mPakName.ToUTF16() + L".pak"; +} + +CResourceCollection* CPackage::AddCollection(const TString& rkName) +{ + CResourceCollection *pCollection = new CResourceCollection(rkName); + mCollections.push_back(pCollection); + return pCollection; +} + +void CPackage::RemoveCollection(CResourceCollection *pCollection) +{ + for (u32 iCol = 0; iCol < mCollections.size(); iCol++) + { + if (mCollections[iCol] == pCollection) + { + RemoveCollection(iCol); + break; + } + } +} + +void CPackage::RemoveCollection(u32 Index) +{ + ASSERT(Index < mCollections.size()); + auto Iter = mCollections.begin() + Index; + delete *Iter; + mCollections.erase(Iter); +} diff --git a/src/Core/GameProject/CPackage.h b/src/Core/GameProject/CPackage.h index 6fd6f98e..adde7ca9 100644 --- a/src/Core/GameProject/CPackage.h +++ b/src/Core/GameProject/CPackage.h @@ -5,6 +5,8 @@ #include #include +class CGameProject; + struct SNamedResource { TString Name; @@ -12,29 +14,66 @@ struct SNamedResource CFourCC Type; }; +class CResourceCollection +{ + TString mName; + std::vector mNamedResources; + +public: + CResourceCollection() : mName("UNNAMED") {} + CResourceCollection(const TString& rkName) : mName(rkName) {} + + inline TString Name() const { return mName; } + inline u32 NumResources() const { return mNamedResources.size(); } + inline const SNamedResource& ResourceByIndex(u32 Idx) const { return mNamedResources[Idx]; } + + inline void AddResource(const TString& rkName, const CUniqueID& rkID, const CFourCC& rkType) + { + mNamedResources.push_back( SNamedResource { rkName, rkID, rkType } ); + } +}; + class CPackage { + CGameProject *mpProject; TString mPakName; TWideString mPakPath; - std::vector mNamedResources; - std::vector mPakResources; + std::vector mCollections; + + enum EPackageDefinitionVersion + { + eVer_Initial, + + eVer_Max, + eVer_Current = eVer_Max - 1 + }; public: CPackage() {} - CPackage(const TString& rkName, const TWideString& rkPath) - : mPakName(rkName) + CPackage(CGameProject *pProj, const TString& rkName, const TWideString& rkPath) + : mpProject(pProj) + , mPakName(rkName) , mPakPath(rkPath) {} - inline TString PakName() const { return mPakName; } - inline TWideString PakPath() const { return mPakPath; } - inline u32 NumNamedResources() const { return mNamedResources.size(); } - inline const SNamedResource& NamedResourceByIndex(u32 Index) const { return mNamedResources[Index]; } + void Load(); + void Save(); - inline void SetPakName(TString NewName) { mPakName = NewName; } - inline void AddNamedResource(TString Name, const CUniqueID& rkID, const CFourCC& rkType) { mNamedResources.push_back( SNamedResource { Name, rkID, rkType } ); } - inline void AddPakResource(const CUniqueID& rkID) { mPakResources.push_back(rkID); } + TWideString DefinitionPath(bool Relative) const; + TWideString CookedPackagePath(bool Relative) const; + + CResourceCollection* AddCollection(const TString& rkName); + void RemoveCollection(CResourceCollection *pCollection); + void RemoveCollection(u32 Index); + + // Accessors + inline TString Name() const { return mPakName; } + inline TWideString Path() const { return mPakPath; } + inline u32 NumCollections() const { return mCollections.size(); } + inline CResourceCollection* CollectionByIndex(u32 Idx) const { return mCollections[Idx]; } + + inline void SetPakName(TString NewName) { mPakName = NewName; } }; #endif // CPACKAGE