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:
parent
5f2064178c
commit
f15aca3f99
|
@ -0,0 +1,43 @@
|
|||
#ifndef CSCOPEDTIMER
|
||||
#define CSCOPEDTIMER
|
||||
|
||||
#include "CTimer.h"
|
||||
#include "Log.h"
|
||||
#include "TString.h"
|
||||
|
||||
// Runs a timer and automatically stops + prints the time to the log when it goes out of scope.
|
||||
class CScopedTimer
|
||||
{
|
||||
CTimer mTimer;
|
||||
TString mTimerName;
|
||||
bool mStopped;
|
||||
|
||||
public:
|
||||
CScopedTimer(const TString& rkTimeoutMessage)
|
||||
: mTimerName(rkTimeoutMessage)
|
||||
, mStopped(false)
|
||||
{
|
||||
mTimer.Start();
|
||||
}
|
||||
|
||||
~CScopedTimer()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void Stop()
|
||||
{
|
||||
if (!mStopped)
|
||||
{
|
||||
Log::Write(mTimerName + " finished in " + TString::FromFloat((float) mTimer.Stop()) + "s");
|
||||
mStopped = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#define SCOPED_TIMER(TimerName) \
|
||||
CScopedTimer TimerName(#TimerName); \
|
||||
(void) TimerName; // This avoids "unused local variable" compiler warnings
|
||||
|
||||
#endif // CSCOPEDTIMER
|
||||
|
|
@ -73,7 +73,8 @@ HEADERS += \
|
|||
types.h \
|
||||
Log.h \
|
||||
FileUtil.h \
|
||||
AssertMacro.h
|
||||
AssertMacro.h \
|
||||
CScopedTimer.h
|
||||
|
||||
# Source Files
|
||||
SOURCES += \
|
||||
|
|
|
@ -75,6 +75,7 @@ bool DeleteFile(const TWideString& rkFilePath)
|
|||
|
||||
bool DeleteDirectory(const TWideString& rkDirPath)
|
||||
{
|
||||
// This is an extremely destructive function, be careful using it!
|
||||
if (!IsDirectory(rkDirPath)) return false;
|
||||
|
||||
// Sanity check - don't delete root
|
||||
|
@ -94,6 +95,7 @@ bool DeleteDirectory(const TWideString& rkDirPath)
|
|||
|
||||
bool ClearDirectory(const TWideString& rkDirPath)
|
||||
{
|
||||
// This is an extremely destructive function, be careful using it!
|
||||
if (!IsDirectory(rkDirPath)) return false;
|
||||
|
||||
// Sanity check - don't clear root
|
||||
|
@ -126,9 +128,14 @@ bool ClearDirectory(const TWideString& rkDirPath)
|
|||
return true;
|
||||
}
|
||||
|
||||
int FileSize(const TWideString &rkFilePath)
|
||||
u64 FileSize(const TWideString &rkFilePath)
|
||||
{
|
||||
return (int) (Exists(*rkFilePath) ? file_size(*rkFilePath) : -1);
|
||||
return (u64) (Exists(*rkFilePath) ? file_size(*rkFilePath) : -1);
|
||||
}
|
||||
|
||||
u64 LastModifiedTime(const TWideString& rkFilePath)
|
||||
{
|
||||
return (u64) last_write_time(*rkFilePath);
|
||||
}
|
||||
|
||||
TWideString WorkingDirectory()
|
||||
|
|
|
@ -17,9 +17,10 @@ bool CopyDirectory(const TWideString& rkOrigPath, const TWideString& rkNewPath);
|
|||
bool MoveFile(const TWideString& rkOldPath, const TWideString& rkNewPath);
|
||||
bool MoveDirectory(const TWideString& rkOldPath, const TWideString& rkNewPath);
|
||||
bool DeleteFile(const TWideString& rkFilePath);
|
||||
bool DeleteDirectory(const TWideString& rkDirPath);
|
||||
bool ClearDirectory(const TWideString& rkDirPath);
|
||||
int FileSize(const TWideString& rkFilePath);
|
||||
bool DeleteDirectory(const TWideString& rkDirPath); // This is an extremely destructive function, be careful using it!
|
||||
bool ClearDirectory(const TWideString& rkDirPath); // This is an extremely destructive function, be careful using it!
|
||||
u64 FileSize(const TWideString& rkFilePath);
|
||||
u64 LastModifiedTime(const TWideString& rkFilePath);
|
||||
TWideString WorkingDirectory();
|
||||
TWideString MakeAbsolute(TWideString Path);
|
||||
TWideString MakeRelative(const TWideString& rkPath, const TWideString& rkRelativeTo = WorkingDirectory());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
class CCollisionMeshGroup : public CResource
|
||||
{
|
||||
DECLARE_RESOURCE_TYPE(eCollisionMeshGroup)
|
||||
DECLARE_RESOURCE_TYPE(eDynamicCollision)
|
||||
std::vector<CCollisionMesh*> mMeshes;
|
||||
|
||||
public:
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
class CPoiToWorld : public CResource
|
||||
{
|
||||
DECLARE_RESOURCE_TYPE(ePoiToWorld)
|
||||
DECLARE_RESOURCE_TYPE(eStaticGeometryMap)
|
||||
|
||||
public:
|
||||
struct SPoiMap
|
||||
|
|
|
@ -1,69 +1,140 @@
|
|||
#include "CResource.h"
|
||||
#include <Common/AssertMacro.h>
|
||||
#include <map>
|
||||
|
||||
std::map<u32, EResType> gExtensionTypeMap;
|
||||
std::map<u32, TString> gTypeExtensionMap;
|
||||
|
||||
u32 GetGameTypeID(EGame Game, EResType ResType)
|
||||
{
|
||||
return ((Game & 0xFFFF) << 16) | (ResType & 0xFFFF);
|
||||
}
|
||||
|
||||
// ************ STATIC ************
|
||||
EResType CResource::ResTypeForExtension(CFourCC Extension)
|
||||
{
|
||||
Extension = Extension.ToUpper();
|
||||
auto Find = gExtensionTypeMap.find(Extension.ToLong());
|
||||
|
||||
if (Extension < "FONT")
|
||||
if (Find == gExtensionTypeMap.end())
|
||||
{
|
||||
if (Extension < "CSKR")
|
||||
{
|
||||
if (Extension == "AFSM") return eStateMachine;
|
||||
if (Extension == "AGSC") return eAudioGrp;
|
||||
if (Extension == "ANCS") return eAnimSet;
|
||||
if (Extension == "ANIM") return eAnimation;
|
||||
if (Extension == "ATBL") return eAudioTable;
|
||||
if (Extension == "CAUD") return eAudioData;
|
||||
if (Extension == "CHAR") return eAnimSet;
|
||||
if (Extension == "CINF") return eSkeleton;
|
||||
if (Extension == "CMDL") return eModel;
|
||||
if (Extension == "CRSC") return eCollisionResponse;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Extension == "CSKR") return eSkin;
|
||||
if (Extension == "CSMP") return eAudioSample;
|
||||
if (Extension == "CSNG") return eMidi;
|
||||
if (Extension == "CTWK") return eTweak;
|
||||
if (Extension == "DCLN") return eCollisionMeshGroup;
|
||||
if (Extension == "DGRP") return eDependencyGroup;
|
||||
if (Extension == "DSP ") return eMusicTrack;
|
||||
if (Extension == "DUMB") return eDataDump;
|
||||
if (Extension == "ELSC") return eParticleElectric;
|
||||
if (Extension == "EVNT") return eAnimEventData;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Extension < "PAK ")
|
||||
{
|
||||
if (Extension == "FONT") return eFont;
|
||||
if (Extension == "FRME") return eGuiFrame;
|
||||
if (Extension == "FSM2") return eStateMachine;
|
||||
if (Extension == "HINT") return eHintSystem;
|
||||
if (Extension == "MAPA") return eMapArea;
|
||||
if (Extension == "MAPW") return eMapWorld;
|
||||
if (Extension == "MAPU") return eMapUniverse;
|
||||
if (Extension == "MLVL") return eWorld;
|
||||
if (Extension == "MREA") return eArea;
|
||||
if (Extension == "NTWK") return eTweak;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Extension == "PAK ") return ePackFile;
|
||||
if (Extension == "PART") return eParticle;
|
||||
if (Extension == "PATH") return eNavMesh;
|
||||
if (Extension == "SAVW") return eSaveWorld;
|
||||
if (Extension == "SCAN") return eScan;
|
||||
if (Extension == "STRG") return eStringTable;
|
||||
if (Extension == "STRM") return eAudioStream;
|
||||
if (Extension == "SWHC") return eParticleSwoosh;
|
||||
if (Extension == "THP ") return eVideo;
|
||||
if (Extension == "TXTR") return eTexture;
|
||||
if (Extension == "WPSC") return eProjectile;
|
||||
}
|
||||
Log::Error("Couldn't find resource type for requested cooked extension: " + Extension.ToString());
|
||||
return eInvalidResType;
|
||||
}
|
||||
|
||||
return eInvalidResType;
|
||||
return Find->second;
|
||||
}
|
||||
|
||||
// Implementation of functions declared in EResType.h
|
||||
TString GetRawExtension(EResType /*Type*/, EGame /*Game*/)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
TString GetCookedExtension(EResType Type, EGame Game)
|
||||
{
|
||||
u32 GameTypeID = GetGameTypeID(Game, Type);
|
||||
auto Find = gTypeExtensionMap.find(GameTypeID);
|
||||
if (Find != gTypeExtensionMap.end()) return Find->second;
|
||||
else return "";
|
||||
}
|
||||
|
||||
// ************ TYPE REGISTRATIONS ************
|
||||
/* This macro registers a resource's cooked extension with an EResType enum, which allows for
|
||||
* a resource type to be looked up via its extension, and vice versa. Because certain EResType
|
||||
* enumerators are reused with different extensions between games, it's possible to set up a
|
||||
* registration to be valid for only a specific range of games, and then tie a different
|
||||
* extension to the same enumerator for a different game. This allows you to always look up the
|
||||
* correct extension for a given resource type with a combination of an EResType and an EGame.
|
||||
*
|
||||
* You shouldn't need to add any new resource types, as the currently registered ones cover every
|
||||
* resource type for every game from the MP1 demo up to DKCR. However, if you do, simply add an
|
||||
* extra REGISTER_RESOURCE_TYPE line to the list below.
|
||||
*/
|
||||
#define REGISTER_RESOURCE_TYPE(CookedExtension, TypeEnum, FirstGame, LastGame) \
|
||||
class CResourceTypeRegistrant__##CookedExtension \
|
||||
{ \
|
||||
public: \
|
||||
CResourceTypeRegistrant__##CookedExtension() \
|
||||
{ \
|
||||
ASSERT(FirstGame != eUnknownVersion); \
|
||||
\
|
||||
/* Register extension with resource type (should be consistent across all games) */ \
|
||||
u32 IntExt = CFourCC(#CookedExtension).ToLong(); \
|
||||
auto ExtFind = gExtensionTypeMap.find(IntExt); \
|
||||
if (ExtFind != gExtensionTypeMap.end()) \
|
||||
ASSERT(ExtFind->second == TypeEnum); \
|
||||
\
|
||||
gExtensionTypeMap[IntExt] = TypeEnum; \
|
||||
\
|
||||
/* Register resource type with extension for the specified game range */ \
|
||||
EGame Game = FirstGame; \
|
||||
\
|
||||
while (Game <= LastGame) \
|
||||
{ \
|
||||
u32 GameTypeID = GetGameTypeID(Game, TypeEnum); \
|
||||
auto Find = gTypeExtensionMap.find(GameTypeID); \
|
||||
ASSERT(Find == gTypeExtensionMap.end()); \
|
||||
gTypeExtensionMap[GameTypeID] = #CookedExtension; \
|
||||
Game = (EGame) ((int) Game + 1); \
|
||||
} \
|
||||
} \
|
||||
}; \
|
||||
CResourceTypeRegistrant__##CookedExtension gResourceTypeRegistrant__##CookedExtension;
|
||||
|
||||
REGISTER_RESOURCE_TYPE(AFSM, eStateMachine, ePrimeDemo, eEchoes)
|
||||
REGISTER_RESOURCE_TYPE(AGSC, eAudioGroupSet, ePrimeDemo, eCorruptionProto)
|
||||
REGISTER_RESOURCE_TYPE(ANCS, eAnimSet, ePrimeDemo, eEchoes)
|
||||
REGISTER_RESOURCE_TYPE(ANIM, eAnimation, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(ATBL, eAudioLookupTable, ePrimeDemo, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(BFRC, eBurstFireData, eCorruptionProto, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(CAAD, eUnknown_CAAD, eCorruption, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(CAUD, eAudioMacro, eCorruptionProto, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(CHAR, eCharacter, eCorruptionProto, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(CINF, eSkeleton, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(CMDL, eModel, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(CRSC, eParticleCollisionResponse, ePrimeDemo, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(CPRM, eAnimCollisionPrimData, eReturns, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(CSKR, eSkin, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(CSMP, eAudioSample, eCorruptionProto, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(CSNG, eMidi, ePrimeDemo, eEchoes)
|
||||
REGISTER_RESOURCE_TYPE(CSPP, eSpatialPrimitive, eEchoesDemo, eEchoes)
|
||||
REGISTER_RESOURCE_TYPE(CTWK, eTweak, ePrimeDemo, ePrime)
|
||||
REGISTER_RESOURCE_TYPE(DCLN, eDynamicCollision, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(DGRP, eDependencyGroup, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(DPSC, eParticleDecal, ePrimeDemo, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(DUMB, eBinaryData, ePrimeDemo, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(EGMC, eStaticGeometryMap, eEchoesDemo, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(ELSC, eParticleElectric, ePrimeDemo, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(EVNT, eAnimEventData, ePrimeDemo, ePrime)
|
||||
REGISTER_RESOURCE_TYPE(FONT, eFont, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(FRME, eGuiFrame, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(FSM2, eStateMachine2, eEchoesDemo, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(FSMC, eStateMachine, eReturns, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(HINT, eHintSystem, ePrime, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(KFAM, eGuiKeyFrame, ePrimeDemo, ePrimeDemo)
|
||||
REGISTER_RESOURCE_TYPE(MAPA, eMapArea, ePrimeDemo, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(MAPU, eMapUniverse, ePrimeDemo, eEchoes)
|
||||
REGISTER_RESOURCE_TYPE(MAPW, eMapWorld, ePrimeDemo, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(MLVL, eWorld, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(MREA, eArea, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(NTWK, eTweak, eEchoesDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(PAK , ePackage, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(PART, eParticle, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(PATH, eNavMesh, ePrimeDemo, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(PTLA, ePortalArea, eEchoesDemo, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(RULE, eRuleSet, eEchoesDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(SAND, eSourceAnimData, eCorruptionProto, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(SAVA, eSaveArea, eCorruptionProto, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(SAVW, eSaveWorld, ePrime, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(SCAN, eScan, ePrimeDemo, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(SPSC, eParticleSpawn, eEchoesDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(SRSC, eParticleSorted, eEchoesDemo, eEchoes)
|
||||
REGISTER_RESOURCE_TYPE(STLC, eStringList, eEchoesDemo, eCorruptionProto)
|
||||
REGISTER_RESOURCE_TYPE(STRG, eStringTable, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(STRM, eStreamedAudio, eCorruptionProto, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(SWHC, eParticleSwoosh, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(THP , eVideo, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(TXTR, eTexture, ePrimeDemo, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(USRC, eUserEvaluatorData, eCorruptionProto, eCorruption)
|
||||
REGISTER_RESOURCE_TYPE(XFSC, eParticleTransform, eReturns, eReturns)
|
||||
REGISTER_RESOURCE_TYPE(WPSC, eParticleWeapon, ePrimeDemo, eCorruption)
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
// Global version enum that can be easily shared between loaders
|
||||
enum EGame
|
||||
{
|
||||
ePrimeDemo = 0,
|
||||
ePrime = 1,
|
||||
eEchoesDemo = 2,
|
||||
eEchoes = 3,
|
||||
eCorruptionProto = 4,
|
||||
eCorruption = 5,
|
||||
eReturns = 6,
|
||||
eUnknownVersion = -1
|
||||
ePrimeDemo,
|
||||
ePrime,
|
||||
eEchoesDemo,
|
||||
eEchoes,
|
||||
eCorruptionProto,
|
||||
eCorruption,
|
||||
eReturns,
|
||||
eUnknownVersion = -1
|
||||
};
|
||||
|
||||
#endif // EGAME_H
|
||||
|
|
|
@ -1,52 +1,75 @@
|
|||
#ifndef ERESTYPE
|
||||
#define ERESTYPE
|
||||
|
||||
#include "EGame.h"
|
||||
#include <Common/TString.h>
|
||||
|
||||
enum EResType
|
||||
{
|
||||
eAnimation = 0,
|
||||
eAnimEventData = 1,
|
||||
eAnimSet = 2,
|
||||
eArea = 3,
|
||||
eAudioData = 4,
|
||||
eAudioGrp = 5,
|
||||
eAudioSample = 6,
|
||||
eAudioStream = 7,
|
||||
eAudioTable = 8,
|
||||
eCharacter = 9,
|
||||
eCollisionMeshGroup = 10,
|
||||
eCollisionResponse = 11,
|
||||
eDataDump = 12,
|
||||
eDecal = 13,
|
||||
eDependencyGroup = 14,
|
||||
eFont = 15,
|
||||
eGuiFrame = 16,
|
||||
eHintSystem = 17,
|
||||
eInvalidResType = 18,
|
||||
eMapArea = 19,
|
||||
eMapWorld = 20,
|
||||
eMapUniverse = 21,
|
||||
eMidi = 22,
|
||||
eModel = 23,
|
||||
eMusicTrack = 24,
|
||||
eNavMesh = 25,
|
||||
ePackFile = 26,
|
||||
eParticle = 27,
|
||||
eParticleElectric = 28,
|
||||
eParticleSwoosh = 29,
|
||||
ePoiToWorld = 30,
|
||||
eProjectile = 31,
|
||||
eResource = 32,
|
||||
eSaveWorld = 33,
|
||||
eScan = 34,
|
||||
eSkeleton = 35,
|
||||
eSkin = 36,
|
||||
eStateMachine = 37,
|
||||
eStringTable = 38,
|
||||
eTexture = 39,
|
||||
eTweak = 40,
|
||||
eVideo = 41,
|
||||
eWorld = 42
|
||||
eAnimation,
|
||||
eAnimCollisionPrimData,
|
||||
eAnimEventData,
|
||||
eAnimSet,
|
||||
eArea,
|
||||
eAudioMacro,
|
||||
eAudioGroupSet,
|
||||
eAudioSample,
|
||||
eStreamedAudio,
|
||||
eAudioLookupTable,
|
||||
eBinaryData,
|
||||
eBurstFireData,
|
||||
eCharacter,
|
||||
eDependencyGroup,
|
||||
eDynamicCollision,
|
||||
eFont,
|
||||
eGuiFrame,
|
||||
eGuiKeyFrame,
|
||||
eHintSystem,
|
||||
eInvalidResType,
|
||||
eMapArea,
|
||||
eMapWorld,
|
||||
eMapUniverse,
|
||||
eMidi,
|
||||
eModel,
|
||||
eMusicTrack,
|
||||
eNavMesh,
|
||||
ePackage,
|
||||
eParticle,
|
||||
eParticleCollisionResponse,
|
||||
eParticleDecal,
|
||||
eParticleElectric,
|
||||
eParticleSorted,
|
||||
eParticleSpawn,
|
||||
eParticleSwoosh,
|
||||
eParticleTransform,
|
||||
eParticleWeapon,
|
||||
ePortalArea,
|
||||
eResource,
|
||||
eRuleSet,
|
||||
eSaveArea,
|
||||
eSaveWorld,
|
||||
eScan,
|
||||
eSkeleton,
|
||||
eSkin,
|
||||
eSourceAnimData,
|
||||
eSpatialPrimitive,
|
||||
eStateMachine,
|
||||
eStateMachine2, // For distinguishing AFSM/FSM2
|
||||
eStaticGeometryMap,
|
||||
eStringList,
|
||||
eStringTable,
|
||||
eTexture,
|
||||
eTweak,
|
||||
eUnknown_CAAD,
|
||||
eUserEvaluatorData,
|
||||
eVideo,
|
||||
eWorld
|
||||
};
|
||||
|
||||
// defined in CResource.cpp
|
||||
TString GetTypeName(EResType Type);
|
||||
TString GetRawExtension(EResType Type, EGame Game);
|
||||
TString GetCookedExtension(EResType Type, EGame Game);
|
||||
|
||||
#endif // ERESTYPE
|
||||
|
||||
|
|
|
@ -232,7 +232,7 @@ CCollisionMeshGroup* CScriptTemplate::FindCollision(CPropertyStruct *pProperties
|
|||
}
|
||||
|
||||
// Verify resource exists + is correct type
|
||||
if (pRes && (pRes->Type() == eCollisionMeshGroup))
|
||||
if (pRes && (pRes->Type() == eDynamicCollision))
|
||||
return static_cast<CCollisionMeshGroup*>(pRes);
|
||||
}
|
||||
|
||||
|
|
|
@ -258,15 +258,19 @@ void CStartWindow::About()
|
|||
void CStartWindow::ExportGame()
|
||||
{
|
||||
// TEMP - hardcoded names for convenience. will remove later!
|
||||
#define USE_HARDCODED_NAMES 1
|
||||
#define USE_HARDCODED_GAME_ROOT 0
|
||||
#define USE_HARDCODED_EXPORT_DIR 1
|
||||
|
||||
#if USE_HARDCODED_NAMES
|
||||
QString GameRoot = "E:/Unpacked/DKCR Dolphin";
|
||||
QString ExportDir = "E:/Unpacked/ExportTest";
|
||||
#if USE_HARDCODED_GAME_ROOT
|
||||
QString GameRoot = "E:/Unpacked/Metroid Prime 2";
|
||||
#else
|
||||
QString GameRoot = QFileDialog::getExistingDirectory(this, "Select game root directory");
|
||||
if (GameRoot.isEmpty()) return;
|
||||
#endif
|
||||
|
||||
#if USE_HARDCODED_EXPORT_DIR
|
||||
QString ExportDir = "E:/Unpacked/ExportTest";
|
||||
#else
|
||||
QString ExportDir = QFileDialog::getExistingDirectory(this, "Select output export directory");
|
||||
if (ExportDir.isEmpty()) return;
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue