diff --git a/src/Common/CScopedTimer.h b/src/Common/CScopedTimer.h new file mode 100644 index 00000000..a2bca6de --- /dev/null +++ b/src/Common/CScopedTimer.h @@ -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 + diff --git a/src/Common/Common.pro b/src/Common/Common.pro index 537c6da0..30080f56 100644 --- a/src/Common/Common.pro +++ b/src/Common/Common.pro @@ -73,7 +73,8 @@ HEADERS += \ types.h \ Log.h \ FileUtil.h \ - AssertMacro.h + AssertMacro.h \ + CScopedTimer.h # Source Files SOURCES += \ diff --git a/src/Common/FileUtil.cpp b/src/Common/FileUtil.cpp index f79c03a5..3840ff18 100644 --- a/src/Common/FileUtil.cpp +++ b/src/Common/FileUtil.cpp @@ -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() diff --git a/src/Common/FileUtil.h b/src/Common/FileUtil.h index 5db2a6ee..61a763f4 100644 --- a/src/Common/FileUtil.h +++ b/src/Common/FileUtil.h @@ -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()); diff --git a/src/Core/GameProject/CGameExporter.cpp b/src/Core/GameProject/CGameExporter.cpp index 678ec5eb..4d335412 100644 --- a/src/Core/GameProject/CGameExporter.cpp +++ b/src/Core/GameProject/CGameExporter.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #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 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 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 } diff --git a/src/Core/GameProject/CGameExporter.h b/src/Core/GameProject/CGameExporter.h index 2380de2a..1ac6e4a4 100644 --- a/src/Core/GameProject/CGameExporter.h +++ b/src/Core/GameProject/CGameExporter.h @@ -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; diff --git a/src/Core/GameProject/CGameProject.h b/src/Core/GameProject/CGameProject.h index 6a7143ec..c282d83f 100644 --- a/src/Core/GameProject/CGameProject.h +++ b/src/Core/GameProject/CGameProject.h @@ -12,20 +12,31 @@ class CGameProject { EGame mGame; TString mProjectName; - TString mProjectRoot; + TWideString mProjectRoot; CResourceDatabase *mpResourceDatabase; std::vector mWorldPaks; std::vector 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; } diff --git a/src/Core/GameProject/CResourceDatabase.cpp b/src/Core/GameProject/CResourceDatabase.cpp index 5274a4e1..8f0d4fc1 100644 --- a/src/Core/GameProject/CResourceDatabase.cpp +++ b/src/Core/GameProject/CResourceDatabase.cpp @@ -1 +1,215 @@ #include "CResourceDatabase.h" +#include "CGameProject.h" +#include "Core/Resource/CResCache.h" +#include +#include +#include +#include + +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; + } + } +} diff --git a/src/Core/GameProject/CResourceDatabase.h b/src/Core/GameProject/CResourceDatabase.h index 78fcdf4e..e7aeae93 100644 --- a/src/Core/GameProject/CResourceDatabase.h +++ b/src/Core/GameProject/CResourceDatabase.h @@ -1,29 +1,74 @@ #ifndef CRESOURCEDATABASE_H #define CRESOURCEDATABASE_H +#include "Core/Resource/CResource.h" #include #include #include +#include + +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 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 diff --git a/src/Core/Resource/CCollisionMeshGroup.h b/src/Core/Resource/CCollisionMeshGroup.h index a807c615..38fe14e7 100644 --- a/src/Core/Resource/CCollisionMeshGroup.h +++ b/src/Core/Resource/CCollisionMeshGroup.h @@ -8,7 +8,7 @@ class CCollisionMeshGroup : public CResource { - DECLARE_RESOURCE_TYPE(eCollisionMeshGroup) + DECLARE_RESOURCE_TYPE(eDynamicCollision) std::vector mMeshes; public: diff --git a/src/Core/Resource/CPoiToWorld.h b/src/Core/Resource/CPoiToWorld.h index 8598c0f4..6d9001ee 100644 --- a/src/Core/Resource/CPoiToWorld.h +++ b/src/Core/Resource/CPoiToWorld.h @@ -8,7 +8,7 @@ class CPoiToWorld : public CResource { - DECLARE_RESOURCE_TYPE(ePoiToWorld) + DECLARE_RESOURCE_TYPE(eStaticGeometryMap) public: struct SPoiMap diff --git a/src/Core/Resource/CResource.cpp b/src/Core/Resource/CResource.cpp index 3e14b456..0f305fd8 100644 --- a/src/Core/Resource/CResource.cpp +++ b/src/Core/Resource/CResource.cpp @@ -1,69 +1,140 @@ #include "CResource.h" +#include +#include + +std::map gExtensionTypeMap; +std::map 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) diff --git a/src/Core/Resource/EGame.h b/src/Core/Resource/EGame.h index abcf71b7..5bdfa1d5 100644 --- a/src/Core/Resource/EGame.h +++ b/src/Core/Resource/EGame.h @@ -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 diff --git a/src/Core/Resource/EResType.h b/src/Core/Resource/EResType.h index 4fe43ec7..9e7df1d5 100644 --- a/src/Core/Resource/EResType.h +++ b/src/Core/Resource/EResType.h @@ -1,52 +1,75 @@ #ifndef ERESTYPE #define ERESTYPE +#include "EGame.h" +#include + 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 diff --git a/src/Core/Resource/Script/CScriptTemplate.cpp b/src/Core/Resource/Script/CScriptTemplate.cpp index d344410a..75ad944b 100644 --- a/src/Core/Resource/Script/CScriptTemplate.cpp +++ b/src/Core/Resource/Script/CScriptTemplate.cpp @@ -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(pRes); } diff --git a/src/Editor/CStartWindow.cpp b/src/Editor/CStartWindow.cpp index 8114ff14..964f143b 100644 --- a/src/Editor/CStartWindow.cpp +++ b/src/Editor/CStartWindow.cpp @@ -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