Added functionality to generate asset names

This commit is contained in:
parax0
2016-12-12 01:33:46 -07:00
parent efa85036c2
commit 2e44e5b119
35 changed files with 82035 additions and 311 deletions

View File

@@ -0,0 +1,438 @@
#include "AssetNameGeneration.h"
#include "CGameProject.h"
#include "CResourceIterator.h"
#include "Core/Resource/CFont.h"
#include "Core/Resource/CScan.h"
#include "Core/Resource/CWorld.h"
#include "Core/Resource/Script/CScriptLayer.h"
#include <Math/MathUtil.h>
#define PROCESS_WORLDS 1
#define PROCESS_AREAS 1
#define PROCESS_MODELS 1
#define PROCESS_AUDIO_GROUPS 1
#define PROCESS_ANIM_CHAR_SETS 1
#define PROCESS_STRINGS 1
#define PROCESS_SCANS 1
#define PROCESS_FONTS 1
void ApplyGeneratedName(CResourceEntry *pEntry, const TWideString& rkDir, const TWideString& rkName)
{
TWideString SanitizedName = FileUtil::SanitizeName(rkName, false);
TWideString SanitizedDir = FileUtil::SanitizePath(rkDir, true);
if (SanitizedName.IsEmpty()) return;
CVirtualDirectory *pNewDir = pEntry->ResourceStore()->GetVirtualDirectory(SanitizedDir, false, true);
if (pEntry->Directory() == pNewDir && pEntry->Name() == SanitizedName) return;
TWideString Name = SanitizedName;
int AppendNum = 0;
while (pNewDir->FindChildResource(Name, pEntry->ResourceType()) != nullptr)
{
Name = TWideString::Format(L"%s_%d", *SanitizedName, AppendNum);
AppendNum++;
}
bool Success = pEntry->Move(SanitizedDir, Name);
ASSERT(Success);
}
void GenerateAssetNames(CGameProject *pProj)
{
// todo: CAUD/CSMP
CResourceStore *pStore = pProj->ResourceStore();
// Generate names for package named resources first
for (u32 iPkg = 0; iPkg < pProj->NumPackages(); iPkg++)
{
CPackage *pPkg = pProj->PackageByIndex(iPkg);
for (u32 iCol = 0; iCol < pPkg->NumCollections(); iCol++)
{
CResourceCollection *pCol = pPkg->CollectionByIndex(iCol);
for (u32 iRes = 0; iRes < pCol->NumResources(); iRes++)
{
const SNamedResource& rkRes = pCol->ResourceByIndex(iRes);
if (rkRes.Name.EndsWith("NODEPEND")) continue;
CResourceEntry *pRes = pStore->FindEntry(rkRes.ID);
ApplyGeneratedName(pRes, pPkg->Name().ToUTF16(), rkRes.Name.ToUTF16());
}
}
}
#if PROCESS_WORLDS
// Generate world/area names
const TWideString kWorldsRoot = L"Worlds\\";
for (TResourceIterator<eWorld> It(pStore); It; ++It)
{
// World common stuff
TWideString WorldName = It->Name();
// Remove date from the end of the world name
if (WorldName.EndsWith(L"_#SERIAL#"))
WorldName = WorldName.ChopBack(9);
// Verify the second-to-last character is a number to make sure there is actually a date in the world name
// note MP2 multiplayer worlds do not have dates in their names
else if (WorldName[WorldName.Size() - 2] >= '0' && WorldName[WorldName.Size() - 2] <= '9')
{
bool StartedDate = false;
while (!WorldName.IsEmpty())
{
wchar_t Chr = WorldName.Back();
if (!StartedDate && Chr >= L'0' && Chr <= L'9')
StartedDate = true;
else if (StartedDate && Chr != L'_' && (Chr < L'0' || Chr > L'9'))
break;
WorldName = WorldName.ChopBack(1);
}
}
TWideString WorldDir = kWorldsRoot + WorldName + L'\\';
ApplyGeneratedName(*It, WorldDir, WorldName);
CWorld *pWorld = (CWorld*) It->Load();
CModel *pSkyModel = pWorld->DefaultSkybox();
CStringTable *pWorldNameTable = pWorld->WorldName();
CStringTable *pDarkWorldNameTable = pWorld->DarkWorldName();
CResource *pSaveWorld = pWorld->SaveWorld();
CResource *pMapWorld = pWorld->MapWorld();
if (pSkyModel && !pSkyModel->Entry()->IsCategorized())
{
CResourceEntry *pSkyEntry = pSkyModel->Entry();
ApplyGeneratedName(pSkyEntry, WorldDir, TWideString::Format(L"%s_Sky", *WorldName));
}
if (pWorldNameTable)
{
CResourceEntry *pNameEntry = pWorldNameTable->Entry();
ApplyGeneratedName(pNameEntry, WorldDir, pNameEntry->Name());
}
if (pDarkWorldNameTable)
{
CResourceEntry *pDarkNameEntry = pDarkWorldNameTable->Entry();
ApplyGeneratedName(pDarkNameEntry, WorldDir, pDarkNameEntry->Name());
}
if (pSaveWorld)
ApplyGeneratedName(pSaveWorld->Entry(), WorldDir, TWideString::Format(L"%s_SaveInfo", *WorldName));
if (pMapWorld)
ApplyGeneratedName(pMapWorld->Entry(), WorldDir, TWideString::Format(L"%s_Map", *WorldName));
// Areas
for (u32 iArea = 0; iArea < pWorld->NumAreas(); iArea++)
{
// Determine area name
CAssetID AreaID = pWorld->AreaResourceID(iArea);
TString InternalAreaName = pWorld->AreaInternalName(iArea);
CStringTable *pAreaNameTable = pWorld->AreaName(iArea);
TWideString AreaName;
if (pAreaNameTable)
AreaName = pAreaNameTable->String("ENGL", 0);
else if (!InternalAreaName.IsEmpty())
AreaName = L"!!" + InternalAreaName.ToUTF16();
else
AreaName = L"!!" + AreaID.ToString().ToUTF16();
TWideString AreaDir = TWideString::Format(L"%s%02d_%s\\", *WorldDir, iArea, *AreaName);
// Rename area stuff
CResourceEntry *pAreaEntry = pStore->FindEntry(AreaID);
ASSERT(pAreaEntry != nullptr);
ApplyGeneratedName(pAreaEntry, AreaDir, AreaName);
if (pAreaNameTable)
ApplyGeneratedName(pAreaNameTable->Entry(), AreaDir, pAreaNameTable->Entry()->Name());
if (pMapWorld)
{
ASSERT(pMapWorld->Type() == eDependencyGroup);
CDependencyGroup *pGroup = static_cast<CDependencyGroup*>(pMapWorld);
CAssetID MapID = pGroup->DependencyByIndex(iArea);
CResourceEntry *pMapEntry = pStore->FindEntry(MapID);
ASSERT(pMapEntry != nullptr);
ApplyGeneratedName(pMapEntry, AreaDir, TWideString::Format(L"%s_Map", *AreaName));
}
}
}
#endif
#if PROCESS_AREAS
// Generate area stuff
for (TResourceIterator<eArea> It(pStore); It; ++It)
{
TWideString AreaDir = It->DirectoryPath();
TWideString AreaName = It->Name();
CGameArea *pArea = (CGameArea*) It->Load();
// Area lightmaps
TWideString LightmapDir = AreaDir + L"Lightmaps\\";
CMaterialSet *pMaterials = pArea->Materials();
for (u32 iMat = 0; iMat < pMaterials->NumMaterials(); iMat++)
{
CMaterial *pMat = pMaterials->MaterialByIndex(iMat);
if (pMat->Options().HasFlag(CMaterial::eLightmap))
{
CTexture *pLightmapTex = pMat->Pass(0)->Texture();
CResourceEntry *pTexEntry = pLightmapTex->Entry();
TWideString TexName = TWideString::Format(L"lit_%s_%dx%d", *pLightmapTex->ID().ToString().ToUTF16(), pLightmapTex->Width(), pLightmapTex->Height());
ApplyGeneratedName(pTexEntry, LightmapDir, TexName);
pTexEntry->SetHidden(true);
}
}
// Generate names from script instance names
for (u32 iLyr = 0; iLyr < pArea->NumScriptLayers(); iLyr++)
{
CScriptLayer *pLayer = pArea->ScriptLayer(iLyr);
for (u32 iInst = 0; iInst < pLayer->NumInstances(); iInst++)
{
CScriptObject *pInst = pLayer->InstanceByIndex(iInst);
if (pInst->ObjectTypeID() == 0x42 || pInst->ObjectTypeID() == FOURCC("POIN"))
{
TString Name = pInst->InstanceName();
if (Name.EndsWith(".scan"))
{
TIDString ScanIDString = (pProj->Game() <= ePrime ? "0x4:0x0" : "0xBDBEC295:0xB94E9BE7");
TAssetProperty *pScanProperty = TPropCast<TAssetProperty>(pInst->PropertyByIDString(ScanIDString));
if (pScanProperty)
{
CAssetID ScanID = pScanProperty->Get();
CResourceEntry *pEntry = pStore->FindEntry(ScanID);
if (pEntry && !pEntry->IsNamed())
{
TWideString ScanName = Name.ToUTF16().ChopBack(5);
ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), ScanName);
CScan *pScan = (CScan*) pEntry->Load();
if (pScan && pScan->ScanText())
{
CResourceEntry *pStringEntry = pScan->ScanText()->Entry();
ApplyGeneratedName(pStringEntry, pStringEntry->DirectoryPath(), ScanName);
}
}
}
}
}
}
}
// Other area assets
CResourceEntry *pPathEntry = pStore->FindEntry(pArea->PathID());
CResourceEntry *pPoiMapEntry = pArea->PoiToWorldMap() ? pArea->PoiToWorldMap()->Entry() : nullptr;
CResourceEntry *pPortalEntry = pStore->FindEntry(pArea->PortalAreaID());
if (pPathEntry)
ApplyGeneratedName(pPathEntry, AreaDir, TWideString::Format(L"%s_Path", *AreaName));
if (pPoiMapEntry)
ApplyGeneratedName(pPoiMapEntry, AreaDir, TWideString::Format(L"%s_EGMC", *AreaName));
if (pPortalEntry)
ApplyGeneratedName(pPortalEntry, AreaDir, TWideString::Format(L"%s_PortalArea", *AreaName));
pStore->DestroyUnreferencedResources();
}
#endif
#if PROCESS_MODELS
// Generate Model Lightmap names
for (TResourceIterator<eModel> It(pStore); It; ++It)
{
CModel *pModel = (CModel*) It->Load();
for (u32 iSet = 0; iSet < pModel->GetMatSetCount(); iSet++)
{
CMaterialSet *pSet = pModel->GetMatSet(iSet);
for (u32 iMat = 0; iMat < pSet->NumMaterials(); iMat++)
{
CMaterial *pMat = pSet->MaterialByIndex(iMat);
if (pMat->Options().HasFlag(CMaterial::eLightmap))
{
CTexture *pLightmapTex = pMat->Pass(0)->Texture();
CResourceEntry *pTexEntry = pLightmapTex->Entry();
if (pTexEntry->IsNamed() || pTexEntry->IsCategorized()) continue;
TWideString TexName = TWideString::Format(L"lit_%s_%dx%d", *pLightmapTex->ID().ToString().ToUTF16(), pLightmapTex->Width(), pLightmapTex->Height());
ApplyGeneratedName(pTexEntry, pModel->Entry()->DirectoryPath(), TexName);
pTexEntry->SetHidden(true);
}
}
}
pStore->DestroyUnreferencedResources();
}
#endif
#if PROCESS_AUDIO_GROUPS
// Generate Audio Group names
for (TResourceIterator<eAudioGroup> It(pStore); It; ++It)
{
CAudioGroup *pGroup = (CAudioGroup*) It->Load();
TWideString GroupName = pGroup->GroupName().ToUTF16();
ApplyGeneratedName(*It, L"AudioGrp\\", GroupName);
}
#endif
#if PROCESS_ANIM_CHAR_SETS
// Generate animation format names
for (TResourceIterator<eAnimSet> It(pStore); It; ++It)
{
TWideString SetDir = It->DirectoryPath();
TWideString NewSetName;
CAnimSet *pSet = (CAnimSet*) It->Load();
for (u32 iChar = 0; iChar < pSet->NumCharacters(); iChar++)
{
const SSetCharacter *pkChar = pSet->Character(iChar);
TWideString CharName = pkChar->Name.ToUTF16();
if (iChar == 0) NewSetName = CharName;
if (pkChar->pModel) ApplyGeneratedName(pkChar->pModel->Entry(), SetDir, CharName);
if (pkChar->pSkeleton) ApplyGeneratedName(pkChar->pSkeleton->Entry(), SetDir, CharName);
if (pkChar->pSkin) ApplyGeneratedName(pkChar->pSkin->Entry(), SetDir, CharName);
if (pkChar->IceModel.IsValid() || pkChar->IceSkin.IsValid())
{
TWideString IceName = TWideString::Format(L"%s_IceOverlay", *CharName);
if (pkChar->IceModel.IsValid())
{
CResourceEntry *pIceModelEntry = pStore->FindEntry(pkChar->IceModel);
ApplyGeneratedName(pIceModelEntry, SetDir, IceName);
}
if (pkChar->IceSkin.IsValid())
{
CResourceEntry *pIceSkinEntry = pStore->FindEntry(pkChar->IceSkin);
ApplyGeneratedName(pIceSkinEntry, SetDir, IceName);
}
}
}
if (!NewSetName.IsEmpty())
ApplyGeneratedName(*It, SetDir, NewSetName);
std::set<CAnimPrimitive> AnimPrimitives;
pSet->GetUniquePrimitives(AnimPrimitives);
for (auto It = AnimPrimitives.begin(); It != AnimPrimitives.end(); It++)
{
const CAnimPrimitive& rkPrim = *It;
CAnimation *pAnim = rkPrim.Animation();
if (pAnim)
{
ApplyGeneratedName(pAnim->Entry(), SetDir, rkPrim.Name().ToUTF16());
CAnimEventData *pEvents = pAnim->EventData();
if (pEvents)
ApplyGeneratedName(pEvents->Entry(), SetDir, rkPrim.Name().ToUTF16());
}
}
}
#endif
#if PROCESS_STRINGS
// Generate string names
for (TResourceIterator<eStringTable> It(pStore); It; ++It)
{
CStringTable *pString = (CStringTable*) It->Load();
if (pString->Entry()->IsNamed()) continue;
TWideString String;
for (u32 iStr = 0; iStr < pString->NumStrings() && String.IsEmpty(); iStr++)
String = CStringTable::StripFormatting( pString->String("ENGL", iStr) ).Trimmed();
if (!String.IsEmpty())
{
TWideString Name = String.SubString(0, Math::Min<u32>(String.Size(), 50)).Trimmed();
Name.Replace(L"\n", L" ");
while (Name.EndsWith(L".") || TWideString::IsWhitespace(Name.Back()))
Name = Name.ChopBack(1);
ApplyGeneratedName(pString->Entry(), pString->Entry()->DirectoryPath(), Name);
}
}
#endif
#if PROCESS_SCANS
// Generate scan names
for (TResourceIterator<eScan> It(pStore); It; ++It)
{
if (It->IsNamed()) continue;
CScan *pScan = (CScan*) It->Load();
TWideString ScanName;
if (pProj->Game() >= eEchoesDemo)
{
CAssetID DisplayAsset = pScan->LogbookDisplayAssetID();
CResourceEntry *pEntry = pStore->FindEntry(DisplayAsset);
if (pEntry && pEntry->IsNamed()) ScanName = pEntry->Name();
}
if (ScanName.IsEmpty())
{
CStringTable *pString = pScan->ScanText();
if (pString) ScanName = pString->Entry()->Name();
}
ApplyGeneratedName(pScan->Entry(), It->DirectoryPath(), ScanName);
if (!ScanName.IsEmpty() && pProj->Game() <= ePrime)
{
CAssetID FrameID = pScan->GuiFrame();
CResourceEntry *pEntry = pStore->FindEntry(FrameID);
if (pEntry) ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), L"ScanFrame");
for (u32 iImg = 0; iImg < 4; iImg++)
{
CAssetID ImageID = pScan->ScanImage(iImg);
CResourceEntry *pImgEntry = pStore->FindEntry(ImageID);
if (pImgEntry) ApplyGeneratedName(pImgEntry, pImgEntry->DirectoryPath(), TWideString::Format(L"%s_Image%d", *ScanName, iImg));
}
}
}
#endif
#if PROCESS_FONTS
// Generate font names
for (TResourceIterator<eFont> It(pStore); It; ++It)
{
CFont *pFont = (CFont*) It->Load();
if (pFont)
{
ApplyGeneratedName(pFont->Entry(), pFont->Entry()->DirectoryPath(), pFont->FontName().ToUTF16());
CTexture *pFontTex = pFont->Texture();
if (pFontTex)
ApplyGeneratedName(pFontTex->Entry(), pFont->Entry()->DirectoryPath(), pFont->Entry()->Name());
}
}
#endif
pStore->ConditionalSaveStore();
}

View File

@@ -0,0 +1,8 @@
#ifndef ASSETNAMEGENERATION
#define ASSETNAMEGENERATION
class CGameProject;
void GenerateAssetNames(CGameProject *pProj);
#endif // ASSETNAMEGENERATION

View File

@@ -0,0 +1,95 @@
#ifndef CASSETNAMEMAP
#define CASSETNAMEMAP
#include "CResourceIterator.h"
#include "CResourceStore.h"
#include <Common/CAssetID.h>
#include <Common/Serialization/XML.h>
#include <map>
#include <memory>
const TString gkAssetListDir = "..\\resources\\list\\";
struct SAssetNameInfo
{
TWideString Name;
TWideString Directory;
void Serialize(IArchive& rArc)
{
rArc << SERIAL_AUTO(Name) << SERIAL_AUTO(Directory);
}
};
class CAssetNameMap
{
typedef std::map<CAssetID, SAssetNameInfo> TAssetMap;
std::shared_ptr<TAssetMap> mpMap;
void Serialize(IArchive& rArc)
{
rArc << SERIAL_CONTAINER("AssetNameMap", *mpMap.get(), "Asset");
}
public:
CAssetNameMap()
{
mpMap = std::make_shared<TAssetMap>(TAssetMap());
}
void GetNameInfo(CAssetID ID, TString& rOutDirectory, TString& rOutName)
{
auto It = mpMap->find(ID);
if (It != mpMap->end())
{
SAssetNameInfo& rInfo = It->second;
rOutName = rInfo.Name;
rOutDirectory = rInfo.Directory;
}
else
{
rOutDirectory = "Uncategorized\\";
rOutName = ID.ToString();
}
}
static TString GetAssetListPath(EGame Game)
{
return gkAssetListDir + "AssetList" + GetGameShortName(Game) + ".xml";
}
static CAssetNameMap LoadAssetNames(EGame Game)
{
TString ListPath = GetAssetListPath(Game);
CXMLReader Reader(ListPath);
CAssetNameMap Map;
Map.Serialize(Reader);
return Map;
}
static void SaveAssetNames(CResourceStore *pStore = gpResourceStore)
{
CAssetNameMap Map;
for (CResourceIterator It(pStore); It; ++It)
{
if (It->IsCategorized() || It->IsNamed())
{
CAssetID ID = It->ID();
TWideString Name = It->Name();
TWideString Directory = It->Directory()->FullPath();
(*Map.mpMap)[ID] = SAssetNameInfo { Name, Directory };
}
}
TString ListPath = GetAssetListPath(pStore->Game());
CXMLWriter Writer(ListPath, "AssetList", 0, pStore->Game());
Map.Serialize(Writer);
}
};
#endif // CASSETNAMEMAP

View File

@@ -14,7 +14,7 @@
#define COPY_DISC_DATA 1
#define LOAD_PAKS 1
#define SAVE_PACKAGE_DEFINITIONS 1
#define EXPORT_WORLDS 1
#define USE_ASSET_NAME_MAP 0
#define EXPORT_COOKED 1
CGameExporter::CGameExporter(const TString& rkInputDir, const TString& rkOutputDir)
@@ -50,13 +50,15 @@ bool CGameExporter::Export()
mContentDir = mpStore->RawDir(false);
mCookedDir = mpStore->CookedDir(false);
#if USE_ASSET_NAME_MAP
mNameMap = CAssetNameMap::LoadAssetNames(mGame);
#endif
// Export game data
CResourceStore *pOldStore = gpResourceStore;
gpResourceStore = mpStore;
LoadAssetList();
LoadPaks();
ExportWorlds();
ExportCookedResources();
mpProject->AudioManager()->LoadAssets();
ExportResourceEditorData();
@@ -128,57 +130,6 @@ void CGameExporter::CopyDiscData()
ASSERT(mGame != eUnknownGame);
}
void CGameExporter::LoadAssetList()
{
SCOPED_TIMER(LoadAssetList);
// Determine the asset list to use
TString ListFile = "../resources/list/AssetList";
switch (mGame)
{
case ePrimeDemo: ListFile += "MP1Demo"; break;
case ePrime: ListFile += "MP1"; break;
case eEchoesDemo: ListFile += "MP2Demo"; break;
case eEchoes: ListFile += "MP2"; break;
case eCorruptionProto: ListFile += "MP3Proto"; break;
case eCorruption: ListFile += "MP3"; break;
case eReturns: ListFile += "DKCR"; break;
default: ASSERT(false);
}
ListFile += ".xml";
// Load list
tinyxml2::XMLDocument List;
List.LoadFile(*ListFile);
if (List.Error())
{
Log::Error("Couldn't open asset list: " + ListFile);
return;
}
tinyxml2::XMLElement *pRoot = List.FirstChildElement("AssetList");
tinyxml2::XMLElement *pAsset = pRoot->FirstChildElement("Asset");
while (pAsset)
{
CAssetID ResourceID = TString(pAsset->Attribute("ID")).ToInt64(16);
tinyxml2::XMLElement *pDir = pAsset->FirstChildElement("Dir");
TString Dir = pDir ? pDir->GetText() : "";
tinyxml2::XMLElement *pName = pAsset->FirstChildElement("Name");
TString Name = pName ? pName->GetText() : "";
if (!Dir.EndsWith("/") && !Dir.EndsWith("\\")) Dir.Append("\\");
SetResourcePath(ResourceID, Dir.ToUTF16(), Name.ToUTF16());
pAsset = pAsset->NextSiblingElement("Asset");
}
}
// ************ RESOURCE LOADING ************
void CGameExporter::LoadPaks()
{
@@ -221,7 +172,6 @@ void CGameExporter::LoadPaks()
u32 NameLen = Pak.ReadLong();
TString Name = Pak.ReadString(NameLen);
pCollection->AddResource(Name, ResID, ResType);
SetResourcePath(ResID, PakName + L"\\", Name.ToUTF16());
}
u32 NumResources = Pak.ReadLong();
@@ -296,7 +246,6 @@ void CGameExporter::LoadPaks()
CFourCC ResType = Pak.ReadLong();
CAssetID ResID(Pak, mGame);
pCollection->AddResource(Name, ResID, ResType);
SetResourcePath(ResID, PakName + L"\\", Name.ToUTF16());
}
}
@@ -448,84 +397,6 @@ void CGameExporter::LoadResource(const SResourceInstance& rkResource, std::vecto
}
}
void CGameExporter::ExportWorlds()
{
#if EXPORT_WORLDS
SCOPED_TIMER(ExportWorlds);
for (u32 iPak = 0; iPak < mpProject->NumPackages(); iPak++)
{
CPackage *pPak = mpProject->PackageByIndex(iPak);
// Get output path. DKCR paks are stored in a Worlds folder so we should get the path relative to that so we don't have Worlds\Worlds\.
// Other games have all paks in the game root dir so we're fine just taking the original root dir-relative directory.
TWideString PakPath = pPak->Path();
TWideString GameWorldsDir = PakPath.GetParentDirectoryPath(L"Worlds", false);
if (!GameWorldsDir.IsEmpty())
PakPath = FileUtil::MakeRelative(PakPath, GameWorldsDir);
// Note since there's no collections in the cooked data we're guaranteed that every pak will have exactly one collection.
CResourceCollection *pCollection = pPak->CollectionByIndex(0);
for (u32 iRes = 0; iRes < pCollection->NumResources(); iRes++)
{
const SNamedResource& rkRes = pCollection->ResourceByIndex(iRes);
if (rkRes.Type == "MLVL" && !rkRes.Name.EndsWith("NODEPEND"))
{
// Load world
CWorld *pWorld = (CWorld*) mpStore->LoadResource(rkRes.ID, rkRes.Type);
if (!pWorld)
{
Log::Error("Couldn't load world " + rkRes.Name + " from package " + pPak->Name() + "; unable to export");
continue;
}
// Export world
TWideString Name = rkRes.Name.ToUTF16();
TWideString WorldDir = mWorldsDirName + PakPath + FileUtil::SanitizeName(Name, true) + L"\\";
FileUtil::CreateDirectory(mCookedDir + WorldDir);
SResourceInstance *pInst = FindResourceInstance(rkRes.ID);
ASSERT(pInst != nullptr);
SetResourcePath(rkRes.ID, WorldDir, Name);
ExportResource(*pInst);
// Export areas
for (u32 iArea = 0; iArea < pWorld->NumAreas(); iArea++)
{
// Determine area names
TWideString InternalAreaName = pWorld->AreaInternalName(iArea).ToUTF16();
bool HasInternalName = !InternalAreaName.IsEmpty();
if (!HasInternalName) InternalAreaName = TWideString::FromInt32(iArea, 2, 10);
TWideString GameAreaName;
CStringTable *pTable = pWorld->AreaName(iArea);
if (pTable) GameAreaName = pTable->String("ENGL", 0);
if (GameAreaName.IsEmpty()) GameAreaName = InternalAreaName;
// Export area
TWideString AreaDir = WorldDir + TWideString::FromInt32(iArea, 2, 10) + L"_" + FileUtil::SanitizeName(GameAreaName, true) + L"\\";
FileUtil::CreateDirectory(mCookedDir + AreaDir);
CAssetID AreaID = pWorld->AreaResourceID(iArea);
SResourceInstance *pInst = FindResourceInstance(AreaID);
ASSERT(pInst != nullptr);
SetResourcePath(AreaID, AreaDir, GameAreaName);
ExportResource(*pInst);
}
}
}
mpStore->DestroyUnreferencedResources();
}
#endif
}
void CGameExporter::ExportCookedResources()
{
{
@@ -597,21 +468,10 @@ void CGameExporter::ExportResource(SResourceInstance& rRes)
std::vector<u8> ResourceData;
LoadResource(rRes, ResourceData);
// Determine output path
SResourcePath *pPath = FindResourcePath(rRes.ResourceID);
TWideString OutName, OutDir;
if (pPath)
{
OutName = pPath->Name;
OutDir = pPath->Dir;
}
if (OutName.IsEmpty()) OutName = rRes.ResourceID.ToString().ToUTF16();
if (OutDir.IsEmpty()) OutDir = L"Uncategorized\\";
// Register resource and write to file
CResourceEntry *pEntry = mpStore->RegisterResource(rRes.ResourceID, CResource::ResTypeForExtension(rRes.ResourceType), OutDir, OutName);
TString Directory, Name;
mNameMap.GetNameInfo(rRes.ResourceID, Directory, Name);
CResourceEntry *pEntry = mpStore->RegisterResource(rRes.ResourceID, CResource::ResTypeForExtension(rRes.ResourceType), Directory, Name);
#if EXPORT_COOKED
// Save cooked asset

View File

@@ -1,6 +1,7 @@
#ifndef CGAMEEXPORTER_H
#define CGAMEEXPORTER_H
#include "CAssetNameMap.h"
#include "CGameProject.h"
#include "CResourceStore.h"
#include <Common/CAssetID.h>
@@ -28,6 +29,7 @@ class CGameExporter
// Resources
TWideStringList mPaks;
std::map<CAssetID, bool> mAreaDuplicateMap;
CAssetNameMap mNameMap;
struct SResourceInstance
{
@@ -41,13 +43,6 @@ class CGameExporter
};
std::map<CAssetID, SResourceInstance> mResourceMap;
struct SResourcePath
{
TWideString Dir;
TWideString Name;
};
std::map<CAssetID, SResourcePath> mResourcePaths;
public:
CGameExporter(const TString& rkInputDir, const TString& rkOutputDir);
bool Export();
@@ -55,10 +50,8 @@ public:
protected:
void CopyDiscData();
void LoadAssetList();
void LoadPaks();
void LoadResource(const SResourceInstance& rkResource, std::vector<u8>& rBuffer);
void ExportWorlds();
void ExportCookedResources();
void ExportResourceEditorData();
void ExportResource(SResourceInstance& rRes);
@@ -70,18 +63,6 @@ protected:
auto Found = mResourceMap.find(IntegralID);
return (Found == mResourceMap.end() ? nullptr : &Found->second);
}
inline SResourcePath* FindResourcePath(const CAssetID& rkID)
{
u64 IntegralID = rkID.ToLongLong();
auto Found = mResourcePaths.find(IntegralID);
return (Found == mResourcePaths.end() ? nullptr : &Found->second);
}
inline void SetResourcePath(const CAssetID& rkID, const TWideString& rkDir, const TWideString& rkName)
{
mResourcePaths[rkID] = SResourcePath { rkDir, rkName };
}
};
#endif // CGAMEEXPORTER_H

View File

@@ -7,6 +7,8 @@ CGameProject *CGameProject::mspActiveProject = nullptr;
CGameProject::~CGameProject()
{
ASSERT(!mpResourceStore->IsDirty());
if (IsActive())
mspActiveProject = nullptr;

View File

@@ -47,9 +47,9 @@ public:
, mProjectRoot(rkProjRootDir)
, mResourceDBPath(L"ResourceDB.rdb")
{
mProjectRoot.Replace(L"/", L"\\");
mpResourceStore = new CResourceStore(this);
mpAudioManager = new CAudioManager(this);
mProjectRoot.Replace(L"/", L"\\");
}
CGameProject(CGameExporter *pExporter, const TWideString& rkProjRootDir, EGame Game)
@@ -58,9 +58,9 @@ public:
, mProjectRoot(rkProjRootDir)
, mResourceDBPath(L"ResourceDB.rdb")
{
mProjectRoot.Replace(L"/", L"\\");
mpResourceStore = new CResourceStore(this, pExporter, L"Content\\", L"Cooked\\", Game);
mpAudioManager = new CAudioManager(this);
mProjectRoot.Replace(L"/", L"\\");
}
~CGameProject();
@@ -95,6 +95,4 @@ public:
static inline CGameProject* ActiveProject() { return mspActiveProject; }
};
extern CGameProject *gpProject;
#endif // CGAMEPROJECT_H

View File

@@ -196,9 +196,10 @@ bool CResourceEntry::Save(bool SkipCacheSave /*= false*/)
// Resource has been saved, now update dependencies + cache file
UpdateDependencies();
mpStore->SetCacheDataDirty();
if (!SkipCacheSave)
mpStore->SaveCacheFile();
mpStore->ConditionalSaveStore();
if (ShouldCollectGarbage)
mpStore->DestroyUnreferencedResources();
@@ -277,40 +278,92 @@ bool CResourceEntry::Unload()
return true;
}
void CResourceEntry::Move(const TWideString& rkDir, const TWideString& rkName)
bool CResourceEntry::CanMoveTo(const TWideString& rkDir, const TWideString& rkName)
{
// Transient resources can't be moved
if (IsTransient()) return false;
// Validate that the path/name are valid file paths
if (!FileUtil::IsValidPath(rkDir, true) || !FileUtil::IsValidName(rkName, false)) return false;
// We need to validate the path isn't taken already - either the directory doesn't exist, or doesn't have a resource by this name
CVirtualDirectory *pDir = mpStore->GetVirtualDirectory(rkDir, false, false);
if (pDir && pDir->FindChildResource(rkName, mType)) return false;
// All checks are true
return true;
}
bool CResourceEntry::Move(const TWideString& rkDir, const TWideString& rkName)
{
if (!CanMoveTo(rkDir, rkName)) return false;
// Store old paths
CVirtualDirectory *pOldDir = mpDirectory;
TWideString OldName = mName;
TString OldCookedPath = CookedAssetPath();
TString OldRawPath = RawAssetPath();
// Set new directory and name
bool HasDirectory = mpDirectory != nullptr;
CVirtualDirectory *pNewDir = mpStore->GetVirtualDirectory(rkDir, IsTransient(), true);
if (pNewDir == mpDirectory && rkName == mName) return false;
if (pNewDir != mpDirectory)
// Check if we can legally move to this spot
ASSERT(pNewDir->FindChildResource(rkName, mType) == nullptr); // this check should be guaranteed to pass due to CanMoveTo() having already checked it
mpDirectory = pNewDir;
mName = rkName;
TString NewCookedPath = CookedAssetPath();
TString NewRawPath = RawAssetPath();
// If the old/new paths are the same then we should have already exited as CanMoveTo() should have returned false
ASSERT(OldCookedPath != NewCookedPath && OldRawPath != NewRawPath);
// The cooked/raw asset paths should not exist right now!!!
bool FSMoveSuccess = false;
if (!HasRawVersion() && !HasCookedVersion())
{
if (mpDirectory)
mpDirectory->RemoveChildResource(this);
mpDirectory = pNewDir;
FSMoveSuccess = true;
if (FileUtil::Exists(OldRawPath))
{
FSMoveSuccess = FileUtil::CopyFile(OldRawPath, NewRawPath);
if (!FSMoveSuccess) FileUtil::DeleteFile(NewRawPath);
}
if (FSMoveSuccess && FileUtil::Exists(OldCookedPath))
{
FSMoveSuccess = FileUtil::CopyFile(OldCookedPath, NewCookedPath);
if (!FSMoveSuccess) FileUtil::DeleteFile(NewCookedPath);
}
}
if (mName != rkName)
ASSERT(mpDirectory->FindChildResource(rkName) == nullptr);
mName = rkName;
mCachedUppercaseName = rkName.ToUpper();
// Move files
if (HasDirectory)
// If we succeeded, finish the move
if (FSMoveSuccess)
{
TString CookedPath = CookedAssetPath();
TString RawPath = RawAssetPath();
if (mpDirectory != pOldDir)
{
FSMoveSuccess = pOldDir->RemoveChildResource(this);
ASSERT(FSMoveSuccess == true); // this shouldn't be able to fail
mpDirectory->AddChild(L"", this);
mpStore->ConditionalDeleteDirectory(pOldDir);
}
if (FileUtil::Exists(OldCookedPath) && CookedPath != OldCookedPath)
FileUtil::MoveFile(OldCookedPath, CookedPath);
mpStore->SetDatabaseDirty();
mCachedUppercaseName = rkName.ToUpper();
FileUtil::DeleteFile(OldRawPath);
FileUtil::DeleteFile(OldCookedPath);
return true;
}
if (FileUtil::Exists(OldRawPath) && RawPath != OldRawPath)
FileUtil::MoveFile(OldRawPath, RawPath);
// Otherwise, revert changes and let the caller know the move failed
else
{
mpDirectory = pOldDir;
mName = OldName;
mpStore->ConditionalDeleteDirectory(pNewDir);
return false;
}
}

View File

@@ -14,12 +14,13 @@ class CDependencyTree;
enum EResEntryFlag
{
eREF_NeedsRecook = 0x1, // Resource has been updated but not recooked
eREF_Transient = 0x2, // Resource is transient (not part of a game project resource DB)
eREF_HasThumbnail = 0x4, // Resource has a unique thumbnail
eREF_ThumbnailLoaded = 0x8, // Resource thumbnail is currently in memory
eREF_NeedsRecook = 0x00000001, // Resource has been updated but not recooked
eREF_Transient = 0x00000002, // Resource is transient (not part of a game project resource DB)
eREF_HasThumbnail = 0x00000004, // Resource has a unique thumbnail
eREF_ThumbnailLoaded = 0x00000008, // Resource thumbnail is currently in memory
eREF_Hidden = 0x00000010, // Resource is hidden, doesn't show up in resource browser
// Flags that save to the cache file
eREF_SavedFlags = eREF_NeedsRecook | eREF_HasThumbnail
eREF_SavedFlags = eREF_NeedsRecook | eREF_HasThumbnail | eREF_Hidden
};
DECLARE_FLAGS(EResEntryFlag, FResEntryFlags)
@@ -61,24 +62,30 @@ public:
CResource* Load();
CResource* LoadCooked(IInputStream& rInput);
bool Unload();
void Move(const TWideString& rkDir, const TWideString& rkName);
bool CanMoveTo(const TWideString& rkDir, const TWideString& rkName);
bool Move(const TWideString& rkDir, const TWideString& rkName);
void AddToProject(const TWideString& rkDir, const TWideString& rkName);
void RemoveFromProject();
// Accessors
void SetDirty() { mFlags.SetFlag(eREF_NeedsRecook); }
void SetHidden(bool Hidden) { Hidden ? mFlags.SetFlag(eREF_Hidden) : mFlags.ClearFlag(eREF_Hidden); }
inline bool IsLoaded() const { return mpResource != nullptr; }
inline bool IsCategorized() const { return mpDirectory && mpDirectory->FullPath() != L"Uncategorized\\"; }
inline bool IsNamed() const { return mName != mID.ToString().ToUTF16(); }
inline CResource* Resource() const { return mpResource; }
inline CResourceStore* ResourceStore() const { return mpStore; }
inline CDependencyTree* Dependencies() const { return mpDependencies; }
inline CAssetID ID() const { return mID; }
inline EGame Game() const { return mGame; }
inline CVirtualDirectory* Directory() const { return mpDirectory; }
inline TWideString DirectoryPath() const { return mpDirectory->FullPath(); }
inline TWideString Name() const { return mName; }
inline const TWideString& UppercaseName() const { return mCachedUppercaseName; }
inline EResType ResourceType() const { return mType; }
inline bool IsTransient() const { return mFlags.HasFlag(eREF_Transient); }
inline bool IsHidden() const { return mFlags.HasFlag(eREF_Hidden); }
protected:
CResource* InternalLoad(IInputStream& rInput);

View File

@@ -66,14 +66,14 @@ public:
}
};
template<class ResType>
template<EResType ResType>
class TResourceIterator : public CResourceIterator
{
public:
TResourceIterator(CResourceStore *pStore = gpResourceStore)
: CResourceIterator(pStore)
{
if (mpCurEntry->ResourceType() != ResType::StaticType())
if (mpCurEntry->ResourceType() != ResType)
Next();
}
@@ -81,7 +81,7 @@ public:
{
do {
CResourceIterator::Next();
} while (mpCurEntry && mpCurEntry->ResourceType() != ResType::StaticType());
} while (mpCurEntry && mpCurEntry->ResourceType() != ResType);
return mpCurEntry;
}

View File

@@ -18,6 +18,8 @@ CResourceStore::CResourceStore(const TWideString& rkDatabasePath)
: mpProj(nullptr)
, mGame(eUnknownGame)
, mpExporter(nullptr)
, mDatabaseDirty(false)
, mCacheFileDirty(false)
{
mpDatabaseRoot = new CVirtualDirectory();
mDatabasePath = FileUtil::MakeAbsolute(rkDatabasePath.GetFileDirectory());
@@ -30,6 +32,8 @@ CResourceStore::CResourceStore(CGameProject *pProject, CGameExporter *pExporter,
, mRawDir(rkRawDir)
, mCookedDir(rkCookedDir)
, mpExporter(pExporter)
, mDatabaseDirty(false)
, mCacheFileDirty(false)
{
SetProject(pProject);
}
@@ -39,6 +43,8 @@ CResourceStore::CResourceStore(CGameProject *pProject)
, mGame(eUnknownGame)
, mpDatabaseRoot(nullptr)
, mpExporter(nullptr)
, mDatabaseDirty(false)
, mCacheFileDirty(false)
{
SetProject(pProject);
}
@@ -122,6 +128,7 @@ void CResourceStore::SaveResourceDatabase()
TString Path = DatabasePath().ToUTF8();
CXMLWriter Writer(Path, "ResourceDB", 0, mGame);
SerializeResourceDatabase(Writer);
mDatabaseDirty = false;
}
void CResourceStore::LoadCacheFile()
@@ -208,6 +215,13 @@ void CResourceStore::SaveCacheFile()
CacheFile.Seek(ResCountOffset, SEEK_SET);
CacheFile.WriteLong(ResCount);
mCacheFileDirty = false;
}
void CResourceStore::ConditionalSaveStore()
{
if (mDatabaseDirty) SaveResourceDatabase();
if (mCacheFileDirty) SaveCacheFile();
}
void CResourceStore::SetProject(CGameProject *pProj)
@@ -298,6 +312,21 @@ CVirtualDirectory* CResourceStore::GetVirtualDirectory(const TWideString& rkPath
else return nullptr;
}
void CResourceStore::ConditionalDeleteDirectory(CVirtualDirectory *pDir)
{
if (pDir->IsEmpty())
{
// If this directory is part of the project, then we should delete the corresponding filesystem directories
if (pDir->GetRoot() == mpDatabaseRoot)
{
FileUtil::DeleteDirectory(RawDir(false) + pDir->FullPath());
FileUtil::DeleteDirectory(CookedDir(false) + pDir->FullPath());
}
pDir->Parent()->RemoveChildDirectory(pDir);
}
}
CResourceEntry* CResourceStore::FindEntry(const CAssetID& rkID) const
{
if (!rkID.IsValid()) return nullptr;
@@ -316,7 +345,7 @@ bool CResourceStore::IsResourceRegistered(const CAssetID& rkID) const
return FindEntry(rkID) != nullptr;
}
CResourceEntry* CResourceStore::RegisterResource(const CAssetID& rkID, EResType Type, const TWideString& rkDir, const TWideString& rkFileName)
CResourceEntry* CResourceStore::RegisterResource(const CAssetID& rkID, EResType Type, const TWideString& rkDir, const TWideString& rkName)
{
CResourceEntry *pEntry = FindEntry(rkID);
@@ -325,16 +354,16 @@ CResourceEntry* CResourceStore::RegisterResource(const CAssetID& rkID, EResType
if (pEntry->IsTransient())
{
ASSERT(pEntry->ResourceType() == Type);
pEntry->AddToProject(rkDir, rkFileName);
pEntry->AddToProject(rkDir, rkName);
}
else
Log::Error("Attempted to register resource that's already tracked in the database: " + rkID.ToString() + " / " + rkDir.ToUTF8() + " / " + rkFileName.ToUTF8());
Log::Error("Attempted to register resource that's already tracked in the database: " + rkID.ToString() + " / " + rkDir.ToUTF8() + " / " + rkName.ToUTF8());
}
else
{
pEntry = new CResourceEntry(this, rkID, rkDir, rkFileName.GetFileName(false), Type);
pEntry = new CResourceEntry(this, rkID, rkDir, rkName, Type);
mResourceEntries[rkID] = pEntry;
}

View File

@@ -25,6 +25,8 @@ class CResourceStore
std::vector<CVirtualDirectory*> mTransientRoots;
std::map<CAssetID, CResourceEntry*> mResourceEntries;
std::map<CAssetID, CResourceEntry*> mLoadedResources;
bool mDatabaseDirty;
bool mCacheFileDirty;
// Directory paths
TWideString mDatabasePath;
@@ -54,12 +56,14 @@ public:
void SaveResourceDatabase();
void LoadCacheFile();
void SaveCacheFile();
void ConditionalSaveStore();
void SetProject(CGameProject *pProj);
void CloseProject();
CVirtualDirectory* GetVirtualDirectory(const TWideString& rkPath, bool Transient, bool AllowCreate);
void ConditionalDeleteDirectory(CVirtualDirectory *pDir);
bool IsResourceRegistered(const CAssetID& rkID) const;
CResourceEntry* RegisterResource(const CAssetID& rkID, EResType Type, const TWideString& rkDir, const TWideString& rkFileName);
CResourceEntry* RegisterResource(const CAssetID& rkID, EResType Type, const TWideString& rkDir, const TWideString& rkName);
CResourceEntry* FindEntry(const CAssetID& rkID) const;
CResourceEntry* FindEntry(const TWideString& rkPath) const;
CResourceEntry* RegisterTransientResource(EResType Type, const TWideString& rkDir = L"", const TWideString& rkFileName = L"");
@@ -84,6 +88,10 @@ public:
inline CVirtualDirectory* RootDirectory() const { return mpDatabaseRoot; }
inline u32 NumTotalResources() const { return mResourceEntries.size(); }
inline u32 NumLoadedResources() const { return mLoadedResources.size(); }
inline bool IsDirty() const { return mDatabaseDirty || mCacheFileDirty; }
inline void SetDatabaseDirty() { mDatabaseDirty = true; }
inline void SetCacheDataDirty() { mCacheFileDirty = true; }
};
extern CResourceStore *gpResourceStore;

View File

@@ -1,6 +1,7 @@
#include "CVirtualDirectory.h"
#include "CResourceEntry.h"
#include "CResourceStore.h"
#include "Core/Resource/CResource.h"
#include <algorithm>
CVirtualDirectory::CVirtualDirectory()
@@ -33,7 +34,7 @@ bool CVirtualDirectory::IsEmpty() const
TWideString CVirtualDirectory::FullPath() const
{
return (mpParent && !mpParent->IsRoot() ? mpParent->FullPath() + L'\\' + mName + L"\\" : mName + L"\\");
return (mpParent && !mpParent->IsRoot() ? mpParent->FullPath() + mName + L'\\' : mName + L'\\');
}
CVirtualDirectory* CVirtualDirectory::GetRoot()
@@ -50,7 +51,7 @@ CVirtualDirectory* CVirtualDirectory::FindChildDirectory(const TWideString& rkNa
{
CVirtualDirectory *pChild = mSubdirectories[iSub];
if (pChild->Name() == DirName)
if (pChild->Name().CaseInsensitiveCompare(DirName))
{
if (SlashIdx == -1)
return pChild;
@@ -81,7 +82,7 @@ CVirtualDirectory* CVirtualDirectory::FindChildDirectory(const TWideString& rkNa
CResourceEntry* CVirtualDirectory::FindChildResource(const TWideString& rkPath)
{
TWideString Dir = rkPath.GetFileDirectory();
TWideString Name = rkPath.GetFileName(false);
TWideString Name = rkPath.GetFileName();
if (!Dir.IsEmpty())
{
@@ -89,13 +90,22 @@ CResourceEntry* CVirtualDirectory::FindChildResource(const TWideString& rkPath)
if (pDir) return pDir->FindChildResource(Name);
}
else
else if (!Name.IsEmpty())
{
for (u32 iRes = 0; iRes < mResources.size(); iRes++)
{
if (mResources[iRes]->Name() == Name)
return mResources[iRes];
}
TWideString Ext = Name.GetFileExtension();
EResType Type = CResource::ResTypeForExtension(Ext);
return FindChildResource(Name.GetFileName(false), Type);
}
return nullptr;
}
CResourceEntry* CVirtualDirectory::FindChildResource(const TWideString& rkName, EResType Type)
{
for (u32 iRes = 0; iRes < mResources.size(); iRes++)
{
if (rkName.CaseInsensitiveCompare(mResources[iRes]->Name()) && mResources[iRes]->ResourceType() == Type)
return mResources[iRes];
}
return nullptr;
@@ -168,10 +178,6 @@ bool CVirtualDirectory::RemoveChildResource(CResourceEntry *pEntry)
if (*It == pEntry)
{
mResources.erase(It);
if (mpParent && IsEmpty())
mpParent->RemoveChildDirectory(this);
return true;
}
}

View File

@@ -2,6 +2,7 @@
#define CVIRTUALDIRECTORY
/* Virtual directory system used to look up resources by their location in the filesystem. */
#include "Core/Resource/EResType.h"
#include <Common/AssertMacro.h>
#include <Common/TString.h>
#include <vector>
@@ -26,6 +27,7 @@ public:
CVirtualDirectory* GetRoot();
CVirtualDirectory* FindChildDirectory(const TWideString& rkName, bool AllowCreate);
CResourceEntry* FindChildResource(const TWideString& rkPath);
CResourceEntry* FindChildResource(const TWideString& rkName, EResType Type);
void AddChild(const TWideString& rkPath, CResourceEntry *pEntry);
bool RemoveChildDirectory(CVirtualDirectory *pSubdir);
bool RemoveChildResource(CResourceEntry *pEntry);