Initial implementation of resource database (mainly creation and read/write), and added resource registration system with a registrant for every format from every game

This commit is contained in:
parax0
2016-05-31 00:45:30 -06:00
parent 5f2064178c
commit f15aca3f99
16 changed files with 622 additions and 176 deletions

View File

@@ -2,6 +2,7 @@
#include <FileIO/FileIO.h>
#include <Common/AssertMacro.h>
#include <Common/CompressionUtil.h>
#include <Common/CScopedTimer.h>
#include <Common/FileUtil.h>
#define COPY_DISC_DATA 1
@@ -9,20 +10,24 @@
#define EXPORT_COOKED 1
CGameExporter::CGameExporter(const TString& rkInputDir, const TString& rkOutputDir)
: mGameDir( FileUtil::MakeAbsolute(rkInputDir) )
, mExportDir( FileUtil::MakeAbsolute(rkOutputDir) )
, mDiscDir(mExportDir + L"Disc\\")
, mCookedResDir(mExportDir + L"Cooked\\Resources\\")
, mCookedWorldsDir(mExportDir + L"Cooked\\Worlds\\")
, mRawResDir(mExportDir + L"Raw\\Resources\\")
, mRawWorldsDir(mExportDir + L"Raw\\Worlds\\")
, mpProject(new CGameProject)
{
mGameDir = FileUtil::MakeAbsolute(rkInputDir);
mExportDir = FileUtil::MakeAbsolute(rkOutputDir);
mpProject = new CGameProject(mExportDir);
mDiscDir = mpProject->DiscDir();
mResDir = mpProject->ResourcesDir();
mWorldsDir = mpProject->WorldsDir();
mCookedDir = mpProject->CookedDir();
mCookedResDir = mpProject->CookedResourcesDir();
mCookedWorldsDir = mpProject->CookedWorldsDir();
}
bool CGameExporter::Export()
{
SCOPED_TIMER(ExportGame);
FileUtil::CreateDirectory(mExportDir);
FileUtil::ClearDirectory(mExportDir);
CopyDiscData();
LoadPaks();
ExportCookedResources();
@@ -33,6 +38,8 @@ bool CGameExporter::Export()
void CGameExporter::CopyDiscData()
{
#if COPY_DISC_DATA
SCOPED_TIMER(CopyDiscData);
// Create Disc output folder
FileUtil::CreateDirectory(mDiscDir);
#endif
@@ -64,7 +71,7 @@ void CGameExporter::CopyDiscData()
}
// Detect paks
if (FullPath.GetFileExtension() == L"pak")
if (FullPath.GetFileExtension().ToLower() == L"pak")
{
if (FullPath.GetFileName(false).StartsWith(L"Metroid", false) || RelPath.Contains(L"Worlds", false))
mWorldPaks.push_back(FullPath);
@@ -90,6 +97,8 @@ void CGameExporter::CopyDiscData()
void CGameExporter::LoadPaks()
{
#if LOAD_PAKS
SCOPED_TIMER(LoadPaks);
for (u32 iList = 0; iList < 2; iList++)
{
const TWideStringList& rkList = (iList == 0 ? mWorldPaks : mResourcePaks);
@@ -117,31 +126,35 @@ void CGameExporter::LoadPaks()
Pak.Seek(0x4, SEEK_CUR);
ASSERT(PakVersion == 0x00030005);
u32 NumNamedResources = Pak.ReadLong();
ASSERT(NumNamedResources > 0);
for (u32 iName = 0; iName < NumNamedResources; iName++)
// Echoes demo disc has a pak that ends right here.
if (!Pak.EoF())
{
Pak.Seek(0x4, SEEK_CUR); // Skip resource type
CUniqueID ResID(Pak, IDLength);
u32 NameLen = Pak.ReadLong();
TString Name = Pak.ReadString(NameLen);
pPackage->AddNamedResource(Name, ResID);
}
u32 NumNamedResources = Pak.ReadLong();
ASSERT(NumNamedResources > 0);
u32 NumResources = Pak.ReadLong();
for (u32 iName = 0; iName < NumNamedResources; iName++)
{
Pak.Seek(0x4, SEEK_CUR); // Skip resource type
CUniqueID ResID(Pak, IDLength);
u32 NameLen = Pak.ReadLong();
TString Name = Pak.ReadString(NameLen);
pPackage->AddNamedResource(Name, ResID);
}
for (u32 iRes = 0; iRes < NumResources; iRes++)
{
bool Compressed = (Pak.ReadLong() == 1);
CFourCC ResType = Pak.ReadLong();
CUniqueID ResID(Pak, IDLength);
u32 ResSize = Pak.ReadLong();
u32 ResOffset = Pak.ReadLong();
u32 NumResources = Pak.ReadLong();
u64 IntegralID = ResID.ToLongLong();
if (mResourceMap.find(IntegralID) == mResourceMap.end())
mResourceMap[IntegralID] = SResourceInstance { PakPath, ResID, ResType, ResOffset, ResSize, Compressed };
for (u32 iRes = 0; iRes < NumResources; iRes++)
{
bool Compressed = (Pak.ReadLong() == 1);
CFourCC ResType = Pak.ReadLong();
CUniqueID ResID(Pak, IDLength);
u32 ResSize = Pak.ReadLong();
u32 ResOffset = Pak.ReadLong();
u64 IntegralID = ResID.ToLongLong();
if (mResourceMap.find(IntegralID) == mResourceMap.end())
mResourceMap[IntegralID] = SResourceInstance { PakPath, ResID, ResType, ResOffset, ResSize, Compressed };
}
}
}
@@ -317,20 +330,32 @@ void CGameExporter::LoadPakResource(const SResourceInstance& rkResource, std::ve
void CGameExporter::ExportCookedResources()
{
#if EXPORT_COOKED
FileUtil::CreateDirectory(mCookedResDir);
for (auto It = mResourceMap.begin(); It != mResourceMap.end(); It++)
CResourceDatabase *pResDB = mpProject->ResourceDatabase();
{
const SResourceInstance& rkRes = It->second;
std::vector<u8> ResourceData;
LoadPakResource(rkRes, ResourceData);
SCOPED_TIMER(ExportCookedResources);
FileUtil::CreateDirectory(mCookedResDir);
TString OutName = rkRes.ResourceID.ToString() + "." + rkRes.ResourceType.ToString();
TString OutPath = mCookedResDir.ToUTF8() + "/" + OutName;
CFileOutStream Out(OutPath.ToStdString(), IOUtil::eBigEndian);
for (auto It = mResourceMap.begin(); It != mResourceMap.end(); It++)
{
const SResourceInstance& rkRes = It->second;
std::vector<u8> ResourceData;
LoadPakResource(rkRes, ResourceData);
if (Out.IsValid())
Out.WriteBytes(ResourceData.data(), ResourceData.size());
TString OutName = rkRes.ResourceID.ToString() + "." + rkRes.ResourceType.ToString();
TString OutDir = mCookedResDir.ToUTF8() + "\\";
TString OutPath = OutDir + OutName;
CFileOutStream Out(OutPath.ToStdString(), IOUtil::eBigEndian);
if (Out.IsValid())
Out.WriteBytes(ResourceData.data(), ResourceData.size());
// Add to resource DB
pResDB->RegisterResource(rkRes.ResourceID, FileUtil::MakeRelative(OutDir, mCookedDir), OutName, CResource::ResTypeForExtension(rkRes.ResourceType));
}
}
{
SCOPED_TIMER(SaveResourceDatabase);
pResDB->Save(this->mExportDir.ToUTF8() + "ResourceDatabase.rdb");
}
#endif
}

View File

@@ -17,10 +17,11 @@ class CGameExporter
TWideString mGameDir;
TWideString mExportDir;
TWideString mDiscDir;
TWideString mResDir;
TWideString mWorldsDir;
TWideString mCookedDir;
TWideString mCookedResDir;
TWideString mCookedWorldsDir;
TWideString mRawResDir;
TWideString mRawWorldsDir;
// Resources
TWideStringList mWorldPaks;

View File

@@ -12,20 +12,31 @@ class CGameProject
{
EGame mGame;
TString mProjectName;
TString mProjectRoot;
TWideString mProjectRoot;
CResourceDatabase *mpResourceDatabase;
std::vector<CPackage*> mWorldPaks;
std::vector<CPackage*> mResourcePaks;
public:
CGameProject()
CGameProject(const TWideString& rkProjRootDir)
: mGame(eUnknownVersion)
, mProjectName("UnnamedProject")
, mpResourceDatabase(new CResourceDatabase)
, mProjectRoot(rkProjRootDir)
, mpResourceDatabase(new CResourceDatabase(this))
{}
void AddPackage(CPackage *pPackage, bool WorldPak);
// Directory Handling
inline TWideString ProjectRoot() const { return mProjectRoot; }
inline TWideString DiscDir() const { return mProjectRoot + L"Disc\\"; }
inline TWideString ResourcesDir() const { return mProjectRoot + L"Resources\\"; }
inline TWideString WorldsDir() const { return mProjectRoot + L"Worlds\\"; }
inline TWideString CookedDir() const { return mProjectRoot + L"Cooked\\"; }
inline TWideString CookedResourcesDir() const { return CookedDir() + L"Resources\\"; }
inline TWideString CookedWorldsDir() const { return CookedDir() + L"Worlds\\"; }
// Accessors
inline void SetGame(EGame Game) { mGame = Game; }
inline void SetProjectName(const TString& rkName) { mProjectName = rkName; }

View File

@@ -1 +1,215 @@
#include "CResourceDatabase.h"
#include "CGameProject.h"
#include "Core/Resource/CResCache.h"
#include <Common/AssertMacro.h>
#include <Common/FileUtil.h>
#include <Common/Log.h>
#include <tinyxml2.h>
using namespace tinyxml2;
// ************ CResourceEntry ************
bool CResourceEntry::HasRawVersion() const
{
return FileUtil::Exists(RawAssetPath());
}
bool CResourceEntry::HasCookedVersion() const
{
return FileUtil::Exists(CookedAssetPath());
}
TString CResourceEntry::RawAssetPath() const
{
TWideString Ext = GetRawExtension(mResourceType, mpDatabase->GameProject()->Game()).ToUTF16();
return mpDatabase->GameProject()->ProjectRoot() + mFileDir + mFileName + L"." + Ext;
}
TString CResourceEntry::CookedAssetPath() const
{
TWideString Ext = GetCookedExtension(mResourceType, mpDatabase->GameProject()->Game()).ToUTF16();
return mpDatabase->GameProject()->CookedDir() + mFileDir + mFileName + L"." + Ext;
}
bool CResourceEntry::NeedsRecook() const
{
// Assets that do not have a raw version can't be recooked since they will always just be saved cooked to begin with.
// We will recook any asset where the raw version has been updated but not recooked yet. mNeedsRecook can also be
// toggled to arbitrarily flag any asset for recook.
if (!HasRawVersion()) return false;
if (!HasCookedVersion()) return true;
if (mNeedsRecook) return true;
return (FileUtil::LastModifiedTime(CookedAssetPath()) < FileUtil::LastModifiedTime(RawAssetPath()));
}
// ************ CResourceDatabase ************
CResourceDatabase::CResourceDatabase(CGameProject *pProj)
: mpProj(pProj)
{}
CResourceDatabase::~CResourceDatabase()
{
}
void CResourceDatabase::Load(const TString& rkPath)
{
XMLDocument Doc;
Doc.LoadFile(*rkPath);
if (!Doc.Error())
{
XMLElement *pRoot = Doc.FirstChildElement("ResourceDatabase");
//EVersion DatabaseVersion = (EVersion) TString(pRoot->Attribute("Version")).ToInt32(10); // Version currently unused
XMLElement *pResources = pRoot->FirstChildElement("Resources");
XMLElement *pRes = pResources->FirstChildElement("Resource");
u32 ResIndex = 0;
while (pRes)
{
XMLElement *pChild = pRes->FirstChildElement();
bool HasID = false, HasType = false, HasDir = false, HasName = false;
CUniqueID ID;
EResType Type;
TWideString FileDir;
TWideString FileName;
while (pChild)
{
TString NodeName = pChild->Name();
if (NodeName == "ID")
{
ID = CUniqueID::FromString(pChild->GetText());
HasID = true;
}
else if (NodeName == "Type")
{
Type = CResource::ResTypeForExtension(pChild->GetText());
HasType = true;
ASSERT(Type != eInvalidResType);
}
else if (NodeName == "FileDir")
{
FileDir = pChild->GetText();
HasDir = true;
}
else if (NodeName == "FileName")
{
FileName = pChild->GetText();
HasName = true;
}
pChild = pChild->NextSiblingElement();
}
if (HasID && HasType && HasDir && HasName)
RegisterResource(ID, FileDir, FileName, Type);
else
Log::Error("Error reading " + rkPath + ": Resource entry " + TString::FromInt32(ResIndex, 0, 10) + " is missing one or more components");
ResIndex++;
pRes = pRes->NextSiblingElement("Resource");
}
}
}
void CResourceDatabase::Save(const TString& rkPath) const
{
XMLDocument Doc;
XMLDeclaration *pDecl = Doc.NewDeclaration();
Doc.LinkEndChild(pDecl);
XMLElement *pRoot = Doc.NewElement("ResourceDatabase");
pRoot->SetAttribute("Version", eVer_Current);
Doc.LinkEndChild(pRoot);
XMLElement *pResources = Doc.NewElement("Resources");
pRoot->LinkEndChild(pResources);
for (auto It = mResourceMap.begin(); It != mResourceMap.end(); It++)
{
CResourceEntry *pEntry = It->second;
XMLElement *pRes = Doc.NewElement("Resource");
pResources->LinkEndChild(pRes);
XMLElement *pID = Doc.NewElement("ID");
pID->SetText(*pEntry->ID().ToString());
pRes->LinkEndChild(pID);
XMLElement *pType = Doc.NewElement("Type");
pType->SetText(*GetCookedExtension(pEntry->ResourceType(), mpProj->Game()));
pRes->LinkEndChild(pType);
XMLElement *pDir = Doc.NewElement("FileDir");
pDir->SetText(*pEntry->FileDirectory());
pRes->LinkEndChild(pDir);
XMLElement *pName = Doc.NewElement("FileName");
pName->SetText(*pEntry->FileName());
pRes->LinkEndChild(pName);
XMLElement *pRecook = Doc.NewElement("NeedsRecook");
pRecook->SetText(pEntry->NeedsRecook() ? "true" : "false");
pRes->LinkEndChild(pRecook);
}
Doc.SaveFile(*rkPath);
}
CResourceEntry* CResourceDatabase::FindResourceEntry(const CUniqueID& rkID) const
{
auto Found = mResourceMap.find(rkID);
if (Found == mResourceMap.end()) return nullptr;
else return Found->second;
}
CResource* CResourceDatabase::LoadResource(const CUniqueID& rkID) const
{
// todo: no handling for raw assets yet
CResourceEntry *pEntry = FindResourceEntry(rkID);
if (pEntry)
{
TString AssetPath = pEntry->CookedAssetPath();
if (FileUtil::Exists(AssetPath))
return gResCache.GetResource(pEntry->CookedAssetPath());
}
return nullptr;
}
bool CResourceDatabase::RegisterResource(const CUniqueID& rkID, const TWideString& rkDir, const TWideString& rkFileName, EResType Type)
{
CResourceEntry *pEntry = FindResourceEntry(rkID);
if (pEntry)
{
Log::Error("Attempted to register resource that's already tracked in the database: " + rkID.ToString() + " / " + rkDir.ToUTF8() + " / " + rkFileName.ToUTF8());
return false;
}
else
{
pEntry = new CResourceEntry(this, rkID, rkDir, rkFileName.GetFileName(false), Type);
if (!pEntry->HasCookedVersion() && !pEntry->HasRawVersion())
{
Log::Error("Attempted to register a resource that doesn't exist: " + rkID.ToString() + " | " + rkDir.ToUTF8() + " | " + rkFileName.ToUTF8());
delete pEntry;
return false;
}
else
{
mResourceMap[rkID] = pEntry;
return true;
}
}
}

View File

@@ -1,29 +1,74 @@
#ifndef CRESOURCEDATABASE_H
#define CRESOURCEDATABASE_H
#include "Core/Resource/CResource.h"
#include <Common/CUniqueID.h>
#include <Common/TString.h>
#include <Common/types.h>
#include <map>
class CGameProject;
class CResourceDatabase;
class CResourceEntry
{
CUniqueID ID;
TString DataPath;
CResourceDatabase *mpDatabase;
CUniqueID mID;
TWideString mFileDir;
TWideString mFileName;
EResType mResourceType;
bool mNeedsRecook;
public:
CResourceEntry(CResourceDatabase *pDatabase, const CUniqueID& rkID,
const TWideString& rkDir, const TWideString& rkFilename, EResType Type)
: mpDatabase(pDatabase)
, mID(rkID)
, mFileDir(rkDir)
, mFileName(rkFilename)
, mResourceType(Type)
, mNeedsRecook(false)
{}
bool HasRawVersion() const;
bool HasCookedVersion() const;
TString RawAssetPath() const;
TString CookedAssetPath() const;
bool NeedsRecook() const;
// Accessors
void SetDirty() { mNeedsRecook = true; }
inline CUniqueID ID() const { return mID; }
inline TString FileDirectory() const { return mFileDir; }
inline TString FileName() const { return mFileName; }
inline EResType ResourceType() const { return mResourceType; }
};
class CResourceDatabase
{
struct SResEntry
CGameProject *mpProj;
std::map<CUniqueID, CResourceEntry*> mResourceMap;
enum EVersion
{
CUniqueID ID;
TString DataPath;
eVer_Initial,
eVer_Max,
eVer_Current = eVer_Max - 1
};
public:
CResourceDatabase() {}
~CResourceDatabase() {}
CResourceDatabase(CGameProject *pProj);
~CResourceDatabase();
void Load(const TString& rkPath);
void Save(const TString& rkPath) const;
CResourceEntry* FindResourceEntry(const CUniqueID& rkID) const;
CResource* LoadResource(const CUniqueID& rkID) const;
bool RegisterResource(const CUniqueID& rkID, const TWideString& rkDir, const TWideString& rkFileName, EResType Type);
inline CGameProject* GameProject() const { return mpProj; }
};
#endif // CRESOURCEDATABASE_H