mirror of
https://github.com/AxioDL/PrimeWorldEditor.git
synced 2025-12-17 17:05:37 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user