Added support for saving/loading game projects

This commit is contained in:
parax0 2016-07-05 20:09:21 -06:00
parent f55b3666a0
commit 12bd4eff90
7 changed files with 260 additions and 146 deletions

View File

@ -1,6 +1,7 @@
#include "CGameExporter.h" #include "CGameExporter.h"
#include "Core/GameProject/CResourceStore.h" #include "Core/GameProject/CResourceStore.h"
#include "Core/Resource/CWorld.h" #include "Core/Resource/CWorld.h"
#include "Core/Resource/Script/CMasterTemplate.h"
#include <FileIO/FileIO.h> #include <FileIO/FileIO.h>
#include <Common/AssertMacro.h> #include <Common/AssertMacro.h>
#include <Common/CompressionUtil.h> #include <Common/CompressionUtil.h>
@ -8,11 +9,11 @@
#include <Common/FileUtil.h> #include <Common/FileUtil.h>
#include <tinyxml2.h> #include <tinyxml2.h>
#define COPY_DISC_DATA 0 #define COPY_DISC_DATA 1
#define LOAD_PAKS 1 #define LOAD_PAKS 1
#define SAVE_PACKAGE_DEFINITIONS 1 #define SAVE_PACKAGE_DEFINITIONS 1
#define EXPORT_WORLDS 0 #define EXPORT_WORLDS 1
#define EXPORT_COOKED 0 #define EXPORT_COOKED 1
CGameExporter::CGameExporter(const TString& rkInputDir, const TString& rkOutputDir) CGameExporter::CGameExporter(const TString& rkInputDir, const TString& rkOutputDir)
: mStore(this) : mStore(this)
@ -91,12 +92,7 @@ void CGameExporter::CopyDiscData()
// Detect paks // Detect paks
if (FullPath.GetFileExtension().ToLower() == L"pak") if (FullPath.GetFileExtension().ToLower() == L"pak")
{ mPaks.push_back(FullPath);
if (FullPath.GetFileName(false).StartsWith(L"Metroid", false) || RelPath.Contains(L"Worlds", false))
mWorldPaks.push_back(FullPath);
else
mResourcePaks.push_back(FullPath);
}
#if COPY_DISC_DATA #if COPY_DISC_DATA
// Create directory // Create directory
@ -110,6 +106,8 @@ void CGameExporter::CopyDiscData()
} }
ASSERT(Game() != eUnknownVersion); ASSERT(Game() != eUnknownVersion);
mpProject->SetGame(Game());
mpProject->SetProjectName(CMasterTemplate::FindGameName(Game()));
} }
void CGameExporter::LoadAssetList() void CGameExporter::LoadAssetList()
@ -168,14 +166,9 @@ void CGameExporter::LoadPaks()
{ {
#if LOAD_PAKS #if LOAD_PAKS
SCOPED_TIMER(LoadPaks); SCOPED_TIMER(LoadPaks);
for (u32 iList = 0; iList < 2; iList++)
{
const TWideStringList& rkList = (iList == 0 ? mWorldPaks : mResourcePaks);
bool IsWorldPak = (iList == 0);
EUIDLength IDLength = (Game() < eCorruptionProto ? e32Bit : e64Bit); EUIDLength IDLength = (Game() < eCorruptionProto ? e32Bit : e64Bit);
for (auto It = rkList.begin(); It != rkList.end(); It++) for (auto It = mPaks.begin(); It != mPaks.end(); It++)
{ {
TWideString PakPath = *It; TWideString PakPath = *It;
TString CharPak = PakPath.ToUTF8(); TString CharPak = PakPath.ToUTF8();
@ -296,12 +289,11 @@ void CGameExporter::LoadPaks()
} }
// Add package to project and save // Add package to project and save
mpProject->AddPackage(pPackage, IsWorldPak); mpProject->AddPackage(pPackage);
#if SAVE_PACKAGE_DEFINITIONS #if SAVE_PACKAGE_DEFINITIONS
pPackage->Save(); pPackage->Save();
#endif #endif
} }
}
#endif #endif
} }
@ -406,9 +398,9 @@ void CGameExporter::ExportWorlds()
#if EXPORT_WORLDS #if EXPORT_WORLDS
SCOPED_TIMER(ExportWorlds); SCOPED_TIMER(ExportWorlds);
for (u32 iPak = 0; iPak < mpProject->NumWorldPaks(); iPak++) for (u32 iPak = 0; iPak < mpProject->NumPackages(); iPak++)
{ {
CPackage *pPak = mpProject->WorldPakByIndex(iPak); CPackage *pPak = mpProject->PackageByIndex(iPak);
// 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\. // 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. // Other games have all paks in the game root dir so we're fine just taking the original root dir-relative directory.
@ -460,20 +452,11 @@ void CGameExporter::ExportWorlds()
if (pTable) GameAreaName = pTable->String("ENGL", 0); if (pTable) GameAreaName = pTable->String("ENGL", 0);
if (GameAreaName.IsEmpty()) GameAreaName = InternalAreaName; if (GameAreaName.IsEmpty()) GameAreaName = InternalAreaName;
// Load area
CUniqueID AreaID = pWorld->AreaResourceID(iArea);
CGameArea *pArea = (CGameArea*) mStore.LoadResource(AreaID, "MREA");
if (!pArea)
{
Log::Error("Unable to export area " + GameAreaName.ToUTF8() + " from world " + rkRes.Name + "; couldn't load area");
continue;
}
// Export area // Export area
TWideString AreaDir = WorldDir + TWideString::FromInt32(iArea, 2, 10) + L"_" + FileUtil::SanitizeName(GameAreaName, true) + L"\\"; TWideString AreaDir = WorldDir + TWideString::FromInt32(iArea, 2, 10) + L"_" + FileUtil::SanitizeName(GameAreaName, true) + L"\\";
FileUtil::CreateDirectory(mCookedDir + AreaDir); FileUtil::CreateDirectory(mCookedDir + AreaDir);
CUniqueID AreaID = pWorld->AreaResourceID(iArea);
SResourceInstance *pInst = FindResourceInstance(AreaID); SResourceInstance *pInst = FindResourceInstance(AreaID);
ASSERT(pInst != nullptr); ASSERT(pInst != nullptr);
@ -483,11 +466,6 @@ void CGameExporter::ExportWorlds()
mStore.DestroyUnreferencedResources(); mStore.DestroyUnreferencedResources();
} }
else
{
Log::Error("Unexpected named resource type in world pak: " + rkRes.Type.ToString());
}
} }
} }
#endif #endif
@ -506,11 +484,14 @@ void CGameExporter::ExportCookedResources()
ExportResource(rRes); ExportResource(rRes);
} }
} }
#endif
{ {
SCOPED_TIMER(SaveResourceDatabase); SCOPED_TIMER(SaveResourceDatabase);
#if EXPORT_COOKED
mStore.SaveResourceDatabase(this->mExportDir.ToUTF8() + "ResourceDatabase.rdb"); mStore.SaveResourceDatabase(this->mExportDir.ToUTF8() + "ResourceDatabase.rdb");
}
#endif #endif
mpProject->Save();
}
} }
void CGameExporter::ExportResource(SResourceInstance& rRes) void CGameExporter::ExportResource(SResourceInstance& rRes)

View File

@ -25,8 +25,7 @@ class CGameExporter
TWideString mWorldsDirName; TWideString mWorldsDirName;
// Resources // Resources
TWideStringList mWorldPaks; TWideStringList mPaks;
TWideStringList mResourcePaks;
struct SResourceInstance struct SResourceInstance
{ {

View File

@ -1,10 +1,103 @@
#include "CGameProject.h" #include "CGameProject.h"
#include "Core/Resource/Script/CMasterTemplate.h"
#include <tinyxml2.h>
void CGameProject::AddPackage(CPackage *pPackage, bool WorldPak) using namespace tinyxml2;
void CGameProject::Load()
{ {
if (WorldPak) TString ProjPath = ProjectPath().ToUTF8();
mWorldPaks.push_back(pPackage); XMLDocument Doc;
Doc.LoadFile(*ProjPath);
if (Doc.Error())
{
Log::Error("Unable to open game project at " + ProjPath);
return;
}
XMLElement *pRoot = Doc.FirstChildElement("GameProject");
//EProjectVersion Version = (EProjectVersion) TString(pRoot->Attribute("Version")).ToInt32(10);
// Verify all elements are present
XMLElement *pProjName = pRoot->FirstChildElement("Name");
XMLElement *pGame = pRoot->FirstChildElement("Game");
XMLElement *pResDB = pRoot->FirstChildElement("ResourceDB");
XMLElement *pPackages = pRoot->FirstChildElement("Packages");
if (!pProjName || !pGame || !pResDB || !pPackages)
{
TString MissingElem = pProjName ? (pGame ? (pResDB ? "Packages" : "ResourceDB") : "Game") : "Name";
Log::Error("Unable to load game project at " + ProjPath + "; " + MissingElem + " element is missing");
return;
}
mProjectName = pProjName->GetText();
mGame = CMasterTemplate::FindGameForName( pGame->GetText() );
mResourceDBPath = pResDB->GetText();
// Load packages
XMLElement *pPkgElem = pPackages->FirstChildElement("Package");
while (pPkgElem)
{
pPkgElem = pPkgElem->NextSiblingElement("Package");
TString Path = pPkgElem->Attribute("Path");
if (Path.IsEmpty())
Log::Error("Failed to load package in game project " + ProjPath + "; Path attribute is missing or empty");
else else
mResourcePaks.push_back(pPackage); {
CPackage *pPackage = new CPackage(this, Path.GetFileName(false), TString(Path.GetFileDirectory()).ToUTF16());
pPackage->Load();
mPackages.push_back(pPackage);
}
}
} }
void CGameProject::Save()
{
XMLDocument Doc;
XMLDeclaration *pDecl = Doc.NewDeclaration();
Doc.LinkEndChild(pDecl);
XMLElement *pRoot = Doc.NewElement("GameProject");
pRoot->SetAttribute("Version", eVer_Current);
Doc.LinkEndChild(pRoot);
XMLElement *pProjName = Doc.NewElement("Name");
pProjName->SetText(*mProjectName);
pRoot->LinkEndChild(pProjName);
XMLElement *pGame = Doc.NewElement("Game");
pGame->SetText(*CMasterTemplate::FindGameName(mGame));
pRoot->LinkEndChild(pGame);
XMLElement *pResDB = Doc.NewElement("ResourceDB");
pResDB->SetText(*mResourceDBPath.ToUTF8());
pRoot->LinkEndChild(pResDB);
XMLElement *pPackages = Doc.NewElement("Packages");
pRoot->LinkEndChild(pPackages);
for (u32 iPkg = 0; iPkg < mPackages.size(); iPkg++)
{
CPackage *pPackage = mPackages[iPkg];
TWideString FullDefPath = pPackage->DefinitionPath(false);
TWideString RelDefPath = FileUtil::MakeRelative(FullDefPath.GetFileDirectory(), PackagesDir(false));
TString DefPath = TWideString(RelDefPath + FullDefPath.GetFileName()).ToUTF8();
XMLElement *pPakElem = Doc.NewElement("Package");
pPakElem->SetAttribute("Path", *DefPath);
pPackages->LinkEndChild(pPakElem);
}
// Save Project
TString ProjPath = ProjectPath().ToUTF8();
XMLError Result = Doc.SaveFile(*ProjPath);
if (Result != XML_SUCCESS)
Log::Error("Failed to save game project at: " + ProjPath);
}

View File

@ -4,6 +4,7 @@
#include "CPackage.h" #include "CPackage.h"
#include "CResourceStore.h" #include "CResourceStore.h"
#include "Core/Resource/EGame.h" #include "Core/Resource/EGame.h"
#include <Common/FileUtil.h>
#include <Common/CUniqueID.h> #include <Common/CUniqueID.h>
#include <Common/TString.h> #include <Common/TString.h>
#include <Common/types.h> #include <Common/types.h>
@ -14,18 +15,25 @@ class CGameProject
TString mProjectName; TString mProjectName;
TWideString mProjectRoot; TWideString mProjectRoot;
TWideString mResourceDBPath; TWideString mResourceDBPath;
std::vector<CPackage*> mWorldPaks; std::vector<CPackage*> mPackages;
std::vector<CPackage*> mResourcePaks;
enum EProjectVersion
{
eVer_Initial,
eVer_Max,
eVer_Current = eVer_Max - 1
};
public: public:
CGameProject(const TWideString& rkProjRootDir) CGameProject(const TWideString& rkProjRootDir)
: mGame(eUnknownVersion) : mGame(eUnknownVersion)
, mProjectName("UnnamedProject") , mProjectName("Unnamed Project")
, mProjectRoot(rkProjRootDir) , mProjectRoot(rkProjRootDir)
, mResourceDBPath(L"ResourceDB.rdb") , mResourceDBPath(L"ResourceDB.rdb")
{} {}
void AddPackage(CPackage *pPackage, bool WorldPak); void Load();
void Save();
// Directory Handling // Directory Handling
inline TWideString ProjectRoot() const { return mProjectRoot; } inline TWideString ProjectRoot() const { return mProjectRoot; }
@ -34,13 +42,15 @@ public:
inline TWideString ContentDir(bool Relative) const { return Relative ? L"Content\\" : mProjectRoot + L"Content\\"; } 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 CookedDir(bool Relative) const { return Relative ? L"Cooked\\" : mProjectRoot + L"Cooked\\"; }
inline TWideString PackagesDir(bool Relative) const { return Relative ? L"Packages\\" : mProjectRoot + L"Packages\\"; } inline TWideString PackagesDir(bool Relative) const { return Relative ? L"Packages\\" : mProjectRoot + L"Packages\\"; }
inline TWideString ProjectPath() const { return mProjectRoot + FileUtil::SanitizeName(mProjectName.ToUTF16(), false) + L".prj"; }
// Accessors // Accessors
inline void SetGame(EGame Game) { mGame = Game; } inline void SetGame(EGame Game) { mGame = Game; }
inline void SetProjectName(const TString& rkName) { mProjectName = rkName; } inline void SetProjectName(const TString& rkName) { mProjectName = rkName; }
inline u32 NumWorldPaks() const { return mWorldPaks.size(); } inline u32 NumPackages() const { return mPackages.size(); }
inline CPackage* WorldPakByIndex(u32 Index) const { return mWorldPaks[Index]; } inline CPackage* PackageByIndex(u32 Index) const { return mPackages[Index]; }
inline void AddPackage(CPackage *pPackage) { mPackages.push_back(pPackage); }
inline EGame Game() const { return mGame; } inline EGame Game() const { return mGame; }
}; };

View File

@ -119,6 +119,26 @@ std::list<CMasterTemplate*> CMasterTemplate::MasterList()
return list; return list;
} }
TString CMasterTemplate::FindGameName(EGame Game)
{
CMasterTemplate *pMaster = MasterForGame(Game);
return pMaster ? pMaster->GameName() : "Unknown Game";
}
EGame CMasterTemplate::FindGameForName(const TString& rkName)
{
std::list<CMasterTemplate*> Masters = MasterList();
for (auto It = Masters.begin(); It != Masters.end(); It++)
{
CMasterTemplate *pMaster = *It;
if (pMaster->GameName() == rkName)
return pMaster->Game();
}
return eUnknownVersion;
}
TString CMasterTemplate::PropertyName(u32 PropertyID) TString CMasterTemplate::PropertyName(u32 PropertyID)
{ {
auto it = smPropertyNames.find(PropertyID); auto it = smPropertyNames.find(PropertyID);

View File

@ -51,17 +51,20 @@ public:
CStructTemplate* StructAtSource(const TString& rkSource); CStructTemplate* StructAtSource(const TString& rkSource);
// Inline Accessors // Inline Accessors
EGame Game() const { return mGame; } inline EGame Game() const { return mGame; }
u32 NumGameVersions() const { return mGameVersions.empty() ? 1 : mGameVersions.size(); } inline TString GameName() const { return mGameName; }
u32 NumScriptTemplates() const { return mTemplates.size(); } inline u32 NumGameVersions() const { return mGameVersions.empty() ? 1 : mGameVersions.size(); }
u32 NumStates() const { return mStates.size(); } inline u32 NumScriptTemplates() const { return mTemplates.size(); }
u32 NumMessages() const { return mMessages.size(); } inline u32 NumStates() const { return mStates.size(); }
bool IsLoadedSuccessfully() { return mFullyLoaded; } inline u32 NumMessages() const { return mMessages.size(); }
TString GetDirectory() const { return mSourceFile.GetFileDirectory(); } inline bool IsLoadedSuccessfully() { return mFullyLoaded; }
inline TString GetDirectory() const { return mSourceFile.GetFileDirectory(); }
// Static // Static
static CMasterTemplate* MasterForGame(EGame Game); static CMasterTemplate* MasterForGame(EGame Game);
static std::list<CMasterTemplate*> MasterList(); static std::list<CMasterTemplate*> MasterList();
static TString FindGameName(EGame Game);
static EGame FindGameForName(const TString& rkName);
static TString PropertyName(u32 PropertyID); static TString PropertyName(u32 PropertyID);
static u32 CreatePropertyID(IPropertyTemplate *pTemp); static u32 CreatePropertyID(IPropertyTemplate *pTemp);
static void AddProperty(IPropertyTemplate *pTemp, const TString& rkTemplateName = ""); static void AddProperty(IPropertyTemplate *pTemp, const TString& rkTemplateName = "");

View File

@ -274,6 +274,14 @@ void CStartWindow::ExportGame()
if (ExportDir.isEmpty()) return; if (ExportDir.isEmpty()) return;
#endif #endif
// Verify valid game root by checking if opening.bnr exists
TString OpeningBNR = TO_TSTRING(GameRoot) + "/opening.bnr";
if (!FileUtil::Exists(OpeningBNR.ToUTF16()))
{
QMessageBox::warning(this, "Error", "Error; this is not a valid game root directory!");
return;
}
CGameExporter Exporter(TO_TSTRING(GameRoot), TO_TSTRING(ExportDir)); CGameExporter Exporter(TO_TSTRING(GameRoot), TO_TSTRING(ExportDir));
Exporter.Export(); Exporter.Export();
} }