Added editor game info system, exporter now fetches game build version, merged asset name maps for all games, resource browser can now import/export names to/from a map XML, reworked asset name generation to more closely match Retro's organization scheme, bug fixes

This commit is contained in:
Aruki
2017-01-31 11:23:28 -07:00
parent 5ac292ebc5
commit 4f03c2431e
37 changed files with 28455 additions and 11098 deletions

View File

@@ -38,6 +38,79 @@ void ApplyGeneratedName(CResourceEntry *pEntry, const TWideString& rkDir, const
ASSERT(Success);
}
TWideString MakeWorldName(EGame Game, TWideString RawName)
{
// MP1 demo - Remove ! from the beginning
if (Game == ePrimeDemo)
{
if (RawName.StartsWith(L'!'))
RawName = RawName.ChopFront(1);
}
// MP1 - Remove prefix characters and ending date
else if (Game == ePrime)
{
RawName = RawName.ChopFront(2);
bool StartedDate = false;
while (!RawName.IsEmpty())
{
wchar_t Chr = RawName.Back();
if (!StartedDate && Chr >= L'0' && Chr <= L'9')
StartedDate = true;
else if (StartedDate && Chr != L'_' && (Chr < L'0' || Chr > L'9'))
break;
RawName = RawName.ChopBack(1);
}
}
// MP2 demo - Use text between the first and second underscores
else if (Game == eEchoesDemo)
{
u32 UnderscoreA = RawName.IndexOf(L'_');
u32 UnderscoreB = RawName.IndexOf(L'_', UnderscoreA + 1);
RawName = RawName.SubString(UnderscoreA + 1, UnderscoreB - UnderscoreA - 1);
}
// MP3 proto - Remove ! from the beginning and all text after last underscore
else if (Game == eCorruptionProto)
{
if (RawName.StartsWith(L'!'))
RawName = RawName.ChopFront(1);
u32 LastUnderscore = RawName.LastIndexOf(L"_");
RawName = RawName.ChopBack(RawName.Size() - LastUnderscore);
}
// MP2/3 - Remove text before first underscore and after last underscore, strip remaining underscores (except multiplayer maps, which have one underscore)
else if (Game == eEchoes || Game == eCorruption)
{
u32 FirstUnderscore = RawName.IndexOf(L'_');
u32 LastUnderscore = RawName.LastIndexOf(L"_");
if (FirstUnderscore != LastUnderscore)
{
RawName = RawName.ChopBack(RawName.Size() - LastUnderscore);
RawName = RawName.ChopFront(FirstUnderscore + 1);
}
RawName.Remove(L'_');
}
// DKCR - Remove text after second-to-last underscore
else if (Game == eReturns)
{
u32 Underscore = RawName.LastIndexOf(L"_");
RawName = RawName.ChopBack(RawName.Size() - Underscore);
Underscore = RawName.LastIndexOf(L"_");
RawName = RawName.ChopBack(RawName.Size() - Underscore);
}
return RawName;
}
void GenerateAssetNames(CGameProject *pProj)
{
// todo: CAUD/CSMP
@@ -69,34 +142,17 @@ void GenerateAssetNames(CGameProject *pProj)
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);
}
}
// Generate world name
TWideString WorldName = MakeWorldName(pProj->Game(), It->Name());
TWideString WorldDir = kWorldsRoot + WorldName + L'\\';
ApplyGeneratedName(*It, WorldDir, WorldName);
TWideString WorldMasterName = L"!" + WorldName + L"_Master";
TWideString WorldMasterDir = WorldDir + WorldMasterName + L'\\';
ApplyGeneratedName(*It, WorldMasterDir, WorldMasterName);
// Move world stuff
const TWideString WorldNamesDir = L"Strings\\Worlds\\General\\";
const TWideString AreaNamesDir = TWideString::Format(L"Strings\\Worlds\\%s\\", *WorldName);
CWorld *pWorld = (CWorld*) It->Load();
CModel *pSkyModel = pWorld->DefaultSkybox();
@@ -105,53 +161,68 @@ void GenerateAssetNames(CGameProject *pProj)
CResource *pSaveWorld = pWorld->SaveWorld();
CResource *pMapWorld = pWorld->MapWorld();
if (pSaveWorld)
ApplyGeneratedName(pSaveWorld->Entry(), WorldMasterDir, WorldMasterName);
if (pMapWorld)
ApplyGeneratedName(pMapWorld->Entry(), WorldMasterDir, WorldMasterName);
if (pSkyModel && !pSkyModel->Entry()->IsCategorized())
{
// Move sky model
CResourceEntry *pSkyEntry = pSkyModel->Entry();
ApplyGeneratedName(pSkyEntry, WorldDir, TWideString::Format(L"%s_Sky", *WorldName));
ApplyGeneratedName(pSkyEntry, WorldDir + L"sky\\cooked\\", L"sky");
// Move sky textures
for (u32 iSet = 0; iSet < pSkyModel->GetMatSetCount(); iSet++)
{
CMaterialSet *pSet = pSkyModel->GetMatSet(iSet);
for (u32 iMat = 0; iMat < pSet->NumMaterials(); iMat++)
{
CMaterial *pMat = pSet->MaterialByIndex(iMat);
for (u32 iPass = 0; iPass < pMat->PassCount(); iPass++)
{
CMaterialPass *pPass = pMat->Pass(iPass);
if (pPass->Texture() && !pPass->Texture()->Entry()->IsCategorized())
ApplyGeneratedName(pPass->Texture()->Entry(), WorldDir + L"sky\\sourceimages\\", pPass->Texture()->Entry()->Name());
}
}
}
}
if (pWorldNameTable)
{
CResourceEntry *pNameEntry = pWorldNameTable->Entry();
ApplyGeneratedName(pNameEntry, WorldDir, pNameEntry->Name());
ApplyGeneratedName(pNameEntry, WorldNamesDir, WorldName);
}
if (pDarkWorldNameTable)
{
CResourceEntry *pDarkNameEntry = pDarkWorldNameTable->Entry();
ApplyGeneratedName(pDarkNameEntry, WorldDir, pDarkNameEntry->Name());
ApplyGeneratedName(pDarkNameEntry, WorldNamesDir, WorldName + L"Dark");
}
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
TWideString AreaName = pWorld->AreaInternalName(iArea).ToUTF16();
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);
if (AreaName.IsEmpty())
AreaName = AreaID.ToString().ToUTF16();
// Rename area stuff
CResourceEntry *pAreaEntry = pStore->FindEntry(AreaID);
ASSERT(pAreaEntry != nullptr);
ApplyGeneratedName(pAreaEntry, AreaDir, AreaName);
ApplyGeneratedName(pAreaEntry, WorldMasterDir, AreaName);
CStringTable *pAreaNameTable = pWorld->AreaName(iArea);
if (pAreaNameTable)
ApplyGeneratedName(pAreaNameTable->Entry(), AreaDir, pAreaNameTable->Entry()->Name());
ApplyGeneratedName(pAreaNameTable->Entry(), AreaNamesDir, AreaName);
if (pMapWorld)
{
@@ -161,128 +232,149 @@ void GenerateAssetNames(CGameProject *pProj)
CResourceEntry *pMapEntry = pStore->FindEntry(MapID);
ASSERT(pMapEntry != nullptr);
ApplyGeneratedName(pMapEntry, AreaDir, TWideString::Format(L"%s_Map", *AreaName));
ApplyGeneratedName(pMapEntry, WorldMasterDir, 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();
// Move area dependencies
TWideString AreaCookedDir = WorldDir + AreaName + L"\\cooked\\";
CGameArea *pArea = (CGameArea*) pAreaEntry->Load();
// Area lightmaps
TWideString LightmapDir = AreaDir + L"Lightmaps\\";
CMaterialSet *pMaterials = pArea->Materials();
// Area lightmaps
u32 LightmapNum = 0;
CMaterialSet *pMaterials = pArea->Materials();
for (u32 iMat = 0; iMat < pMaterials->NumMaterials(); iMat++)
{
CMaterial *pMat = pMaterials->MaterialByIndex(iMat);
if (pMat->Options().HasFlag(CMaterial::eLightmap))
for (u32 iMat = 0; iMat < pMaterials->NumMaterials(); iMat++)
{
CTexture *pLightmapTex = pMat->Pass(0)->Texture();
CResourceEntry *pTexEntry = pLightmapTex->Entry();
CMaterial *pMat = pMaterials->MaterialByIndex(iMat);
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"))
if (pMat->Options().HasFlag(CMaterial::eLightmap))
{
TString Name = pInst->InstanceName();
CTexture *pLightmapTex = pMat->Pass(0)->Texture();
CResourceEntry *pTexEntry = pLightmapTex->Entry();
if (pTexEntry->IsCategorized()) continue;
if (Name.EndsWith(".SCAN", false))
TWideString TexName = TWideString::Format(L"%s_lit_lightmap%d", *AreaName, LightmapNum);
ApplyGeneratedName(pTexEntry, AreaCookedDir, TexName);
pTexEntry->SetHidden(true);
LightmapNum++;
}
}
// 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"))
{
TIDString ScanIDString = (pProj->Game() <= ePrime ? "0x4:0x0" : "0xBDBEC295:0xB94E9BE7");
TAssetProperty *pScanProperty = TPropCast<TAssetProperty>(pInst->PropertyByIDString(ScanIDString));
ASSERT(pScanProperty); // Temporary assert to remind myself later to update this code when uncooked properties are added to the template
TString Name = pInst->InstanceName();
if (pScanProperty)
if (Name.EndsWith(".SCAN", false))
{
CAssetID ScanID = pScanProperty->Get();
CResourceEntry *pEntry = pStore->FindEntry(ScanID);
TIDString ScanIDString = (pProj->Game() <= ePrime ? "0x4:0x0" : "0xBDBEC295:0xB94E9BE7");
TAssetProperty *pScanProperty = TPropCast<TAssetProperty>(pInst->PropertyByIDString(ScanIDString));
ASSERT(pScanProperty); // Temporary assert to remind myself later to update this code when uncooked properties are added to the template
if (pEntry && !pEntry->IsNamed())
if (pScanProperty)
{
TWideString ScanName = Name.ToUTF16().ChopBack(5);
CAssetID ScanID = pScanProperty->Get();
CResourceEntry *pEntry = pStore->FindEntry(ScanID);
if (ScanName.StartsWith(L"POI_"))
ScanName = ScanName.ChopFront(4);
ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), ScanName);
CScan *pScan = (CScan*) pEntry->Load();
if (pScan && pScan->ScanText())
if (pEntry && !pEntry->IsNamed())
{
CResourceEntry *pStringEntry = pScan->ScanText()->Entry();
ApplyGeneratedName(pStringEntry, pStringEntry->DirectoryPath(), ScanName);
TWideString ScanName = Name.ToUTF16().ChopBack(5);
if (ScanName.StartsWith(L"POI_"))
ScanName = ScanName.ChopFront(4);
ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), ScanName);
CScan *pScan = (CScan*) pEntry->Load();
if (pScan && pScan->ScanText())
{
CResourceEntry *pStringEntry = pScan->ScanText()->Entry();
ApplyGeneratedName(pStringEntry, pStringEntry->DirectoryPath(), ScanName);
}
}
}
}
}
}
else if (pInst->ObjectTypeID() == 0x17 || pInst->ObjectTypeID() == FOURCC("MEMO"))
{
TString Name = pInst->InstanceName();
if (Name.EndsWith(".STRG", false))
else if (pInst->ObjectTypeID() == 0x17 || pInst->ObjectTypeID() == FOURCC("MEMO"))
{
u32 StringPropID = (pProj->Game() <= ePrime ? 0x4 : 0x9182250C);
TAssetProperty *pStringProperty = TPropCast<TAssetProperty>(pInst->Properties()->PropertyByID(StringPropID));
ASSERT(pStringProperty); // Temporary assert to remind myself later to update this code when uncooked properties are added to the template
TString Name = pInst->InstanceName();
if (pStringProperty)
if (Name.EndsWith(".STRG", false))
{
CAssetID StringID = pStringProperty->Get();
CResourceEntry *pEntry = pStore->FindEntry(StringID);
u32 StringPropID = (pProj->Game() <= ePrime ? 0x4 : 0x9182250C);
TAssetProperty *pStringProperty = TPropCast<TAssetProperty>(pInst->Properties()->PropertyByID(StringPropID));
ASSERT(pStringProperty); // Temporary assert to remind myself later to update this code when uncooked properties are added to the template
if (pEntry && !pEntry->IsNamed())
if (pStringProperty)
{
TWideString StringName = Name.ToUTF16().ChopBack(5);
CAssetID StringID = pStringProperty->Get();
CResourceEntry *pEntry = pStore->FindEntry(StringID);
if (StringName.StartsWith(L"HUDMemo - "))
StringName = StringName.ChopFront(10);
if (pEntry && !pEntry->IsNamed())
{
TWideString StringName = Name.ToUTF16().ChopBack(5);
ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), StringName);
if (StringName.StartsWith(L"HUDMemo - "))
StringName = StringName.ChopFront(10);
ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), StringName);
}
}
}
}
// Look for lightmapped models - these are going to be unique to this area
else if (pInst->ObjectTypeID() == 0x0 || pInst->ObjectTypeID() == FOURCC("ACTR") ||
pInst->ObjectTypeID() == 0x8 || pInst->ObjectTypeID() == FOURCC("PLAT"))
{
u32 ModelPropID = (pProj->Game() <= ePrime ? (pInst->ObjectTypeID() == 0x0 ? 0xA : 0x6) : 0xC27FFA8F);
TAssetProperty *pModelProperty = TPropCast<TAssetProperty>(pInst->Properties()->PropertyByID(ModelPropID));
ASSERT(pModelProperty); // Temporary assert to remind myself later to update this code when uncooked properties are added to the template
if (pModelProperty)
{
CAssetID ModelID = pModelProperty->Get();
CResourceEntry *pEntry = pStore->FindEntry(ModelID);
if (pEntry && !pEntry->IsCategorized())
{
CModel *pModel = (CModel*) pEntry->Load();
if (pModel->IsLightmapped())
ApplyGeneratedName(pEntry, AreaCookedDir, pEntry->Name());
}
}
}
}
}
// 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, WorldMasterDir, AreaName);
if (pPoiMapEntry)
ApplyGeneratedName(pPoiMapEntry, WorldMasterDir, AreaName);
if (pPortalEntry)
ApplyGeneratedName(pPortalEntry, WorldMasterDir, AreaName);
pStore->DestroyUnreferencedResources();
#endif
}
// 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
@@ -291,6 +383,7 @@ void GenerateAssetNames(CGameProject *pProj)
for (TResourceIterator<eModel> It(pStore); It; ++It)
{
CModel *pModel = (CModel*) It->Load();
u32 LightmapNum = 0;
for (u32 iSet = 0; iSet < pModel->GetMatSetCount(); iSet++)
{
@@ -306,9 +399,10 @@ void GenerateAssetNames(CGameProject *pProj)
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());
TWideString TexName = TWideString::Format(L"%s_lightmap%d", *It->Name(), LightmapNum);
ApplyGeneratedName(pTexEntry, pModel->Entry()->DirectoryPath(), TexName);
pTexEntry->SetHidden(true);
LightmapNum++;
}
}
}

View File

@@ -1,23 +1,18 @@
#include "CAssetNameMap.h"
std::map<EGame, CAssetNameMap*> CAssetNameMap::smGameMap;
CAssetNameMap::CAssetNameMap(EGame Game)
: mGame(Game)
void CAssetNameMap::LoadAssetNames(TString Path /*= gkAssetMapPath*/)
{
TString ListPath = GetAssetListPath(mGame);
CXMLReader Reader(ListPath);
CXMLReader Reader(Path);
Serialize(Reader);
}
void CAssetNameMap::SaveAssetNames()
void CAssetNameMap::SaveAssetNames(TString Path /*= gkAssetMapPath*/)
{
TString ListPath = GetAssetListPath(mGame);
CXMLWriter Writer(ListPath, "AssetList", 0, mGame);
CXMLWriter Writer(Path, "AssetNameMap", 0, eUnknownGame);
Serialize(Writer);
}
void CAssetNameMap::GetNameInfo(CAssetID ID, TString& rOutDirectory, TString& rOutName)
bool CAssetNameMap::GetNameInfo(CAssetID ID, TString& rOutDirectory, TString& rOutName)
{
auto It = mMap.find(ID);
@@ -26,12 +21,14 @@ void CAssetNameMap::GetNameInfo(CAssetID ID, TString& rOutDirectory, TString& rO
SAssetNameInfo& rInfo = It->second;
rOutName = rInfo.Name;
rOutDirectory = rInfo.Directory;
return true;
}
else
{
rOutDirectory = "Uncategorized\\";
rOutName = ID.ToString();
return false;
}
}

View File

@@ -8,7 +8,7 @@
#include <map>
#include <memory>
const TString gkAssetListDir = "..\\resources\\list\\";
const TString gkAssetMapPath = "..\\resources\\gameinfo\\AssetNameMap.xml";
class CAssetNameMap
{
@@ -23,38 +23,19 @@ class CAssetNameMap
}
};
EGame mGame;
std::map<CAssetID, SAssetNameInfo> mMap;
static std::map<EGame, CAssetNameMap*> smGameMap;
// Private Methods
CAssetNameMap(EGame Game);
void Serialize(IArchive& rArc)
{
rArc << SERIAL_CONTAINER("AssetNameMap", mMap, "Asset");
}
public:
void SaveAssetNames();
void GetNameInfo(CAssetID ID, TString& rOutDirectory, TString& rOutName);
void LoadAssetNames(TString Path = gkAssetMapPath);
void SaveAssetNames(TString Path = gkAssetMapPath);
bool GetNameInfo(CAssetID ID, TString& rOutDirectory, TString& rOutName);
void CopyFromStore(CResourceStore *pStore);
// Static Methods
static TString GetAssetListPath(EGame Game)
{
return gkAssetListDir + "AssetList" + GetGameShortName(Game) + ".xml";
}
static CAssetNameMap* GetGameNameMap(EGame Game)
{
auto Find = smGameMap.find(Game);
if (Find != smGameMap.end()) return Find->second;
CAssetNameMap *pMap = new CAssetNameMap(Game);
smGameMap[Game] = pMap;
return pMap;
}
};
#endif // CASSETNAMEMAP

View File

@@ -1,6 +1,7 @@
#include "CGameExporter.h"
#include "Core/GameProject/CResourceIterator.h"
#include "Core/GameProject/CResourceStore.h"
#include "CGameInfo.h"
#include "CResourceIterator.h"
#include "CResourceStore.h"
#include "Core/Resource/CWorld.h"
#include "Core/Resource/Script/CMasterTemplate.h"
#include <FileIO/FileIO.h>
@@ -14,13 +15,14 @@
#define COPY_DISC_DATA 1
#define LOAD_PAKS 1
#define SAVE_PACKAGE_DEFINITIONS 1
#define USE_ASSET_NAME_MAP 0
#define USE_ASSET_NAME_MAP 1
#define EXPORT_COOKED 1
CGameExporter::CGameExporter(const TString& rkInputDir, const TString& rkOutputDir)
: mpNameMap(nullptr)
{
mGame = eUnknownGame;
mBuildVersion = 0.f;
mGameDir = FileUtil::MakeAbsolute(rkInputDir);
mExportDir = FileUtil::MakeAbsolute(rkOutputDir);
@@ -42,9 +44,10 @@ bool CGameExporter::Export()
// Initial analyze/copy of disc data
CopyDiscData();
FindBuildVersion();
// Create project
mpProject = new CGameProject(this, mExportDir, mGame);
mpProject = new CGameProject(this, mExportDir, mGame, mBuildVersion);
mpProject->SetProjectName(CMasterTemplate::FindGameName(mGame));
mpProject->SetActive();
mpStore = mpProject->ResourceStore();
@@ -52,7 +55,7 @@ bool CGameExporter::Export()
mCookedDir = mpStore->CookedDir(false);
#if USE_ASSET_NAME_MAP
mpNameMap = CAssetNameMap::GetGameNameMap(mGame);
mNameMap.LoadAssetNames();
#endif
// Export game data
@@ -113,6 +116,10 @@ void CGameExporter::CopyDiscData()
else if (Name == L"PreloadData") mGame = eReturns;
}
// Mark dol path. Note size != 0 check is needed because some ISO unpackers (*cough* GCRebuilder) can export bad dol files
if (mDolPath.IsEmpty() && FullPath.EndsWith(L".dol", false) && FileUtil::FileSize(FullPath) != 0)
mDolPath = FullPath;
// Detect paks
if (FullPath.GetFileExtension().ToLower() == L"pak")
mPaks.push_back(FullPath);
@@ -131,6 +138,45 @@ void CGameExporter::CopyDiscData()
ASSERT(mGame != eUnknownGame);
}
void CGameExporter::FindBuildVersion()
{
ASSERT(!mDolPath.IsEmpty());
// MP1 demo build doesn't have a build version
if (mGame == ePrimeDemo) return;
// Read entire file into a big buffer
CFileInStream File(mDolPath.ToUTF8().ToStdString(), IOUtil::eBigEndian);
std::vector<char> FileContents(File.Size());
File.ReadBytes(FileContents.data(), FileContents.size());
File.Close();
// Find build info string
const char *pkSearchText = "!#$MetroidBuildInfo!#$";
const int SearchTextSize = strlen(pkSearchText);
for (u32 SearchIdx = 0; SearchIdx < FileContents.size() - SearchTextSize + 1; SearchIdx++)
{
int Match = 0;
while (FileContents[SearchIdx + Match] == pkSearchText[Match] && Match < SearchTextSize)
Match++;
if (Match == SearchTextSize)
{
// Found the build info string; extract version number
TString BuildInfo = &FileContents[SearchIdx + SearchTextSize];
int BuildVerStart = BuildInfo.IndexOfPhrase("Build v") + 7;
ASSERT(BuildVerStart != 6);
mBuildVersion = BuildInfo.SubString(BuildVerStart, 5).ToFloat();
return;
}
}
Log::Error("Failed to find MetroidBuildInfo string. Build Version will be set to 0. DOL file: " + mDolPath.ToUTF8());
}
// ************ RESOURCE LOADING ************
void CGameExporter::LoadPaks()
{
@@ -471,7 +517,7 @@ void CGameExporter::ExportResource(SResourceInstance& rRes)
// Register resource and write to file
TString Directory, Name;
mpNameMap->GetNameInfo(rRes.ResourceID, Directory, Name);
mNameMap.GetNameInfo(rRes.ResourceID, Directory, Name);
CResourceEntry *pEntry = mpStore->RegisterResource(rRes.ResourceID, CResource::ResTypeForExtension(rRes.ResourceType), Directory, Name);
#if EXPORT_COOKED

View File

@@ -2,6 +2,7 @@
#define CGAMEEXPORTER_H
#include "CAssetNameMap.h"
#include "CGameInfo.h"
#include "CGameProject.h"
#include "CResourceStore.h"
#include <Common/CAssetID.h>
@@ -16,6 +17,7 @@ class CGameExporter
CGameProject *mpProject;
CResourceStore *mpStore;
EGame mGame;
float mBuildVersion;
// Directories
TWideString mGameDir;
@@ -26,10 +28,14 @@ class CGameExporter
TWideString mWorldsDirName;
// Files
TWideString mDolPath;
// Resources
TWideStringList mPaks;
std::map<CAssetID, bool> mAreaDuplicateMap;
CAssetNameMap *mpNameMap;
CAssetNameMap mNameMap;
CGameInfo mGameInfo;
struct SResourceInstance
{
@@ -50,6 +56,7 @@ public:
protected:
void CopyDiscData();
void FindBuildVersion();
void LoadPaks();
void LoadResource(const SResourceInstance& rkResource, std::vector<u8>& rBuffer);
void ExportCookedResources();

View File

@@ -0,0 +1,63 @@
#include "CGameInfo.h"
void CGameInfo::LoadGameInfo(EGame Game)
{
Game = RoundGame(Game);
mGame = Game;
LoadGameInfo(GetDefaultGameInfoPath(Game));
}
void CGameInfo::LoadGameInfo(TString Path)
{
CXMLReader Reader(Path);
Serialize(Reader);
}
void CGameInfo::SaveGameInfo(TString Path /*= ""*/)
{
ASSERT(mGame != eUnknownGame); // can't save game info that was never loaded
if (Path.IsEmpty()) Path = GetDefaultGameInfoPath(mGame);
CXMLWriter Writer(Path, "GameInfo", 0, mGame);
Serialize(Writer);
}
void CGameInfo::Serialize(IArchive& rArc)
{
// Validate game
if (rArc.IsReader() && mGame != eUnknownGame)
{
ASSERT(mGame == rArc.Game());
}
// Serialize data
if (mGame <= ePrime)
rArc << SERIAL_CONTAINER("AreaNameMap", mAreaNameMap, "AreaName");
}
TString CGameInfo::GetAreaName(const CAssetID &rkID) const
{
auto Iter = mAreaNameMap.find(rkID);
return (Iter == mAreaNameMap.end() ? "" : Iter->second);
}
// ************ STATIC ************
EGame CGameInfo::RoundGame(EGame Game)
{
if (Game == ePrimeDemo) return ePrime;
if (Game == eEchoesDemo) return eEchoes;
if (Game == eCorruptionProto) return eCorruption;
return Game;
}
TString CGameInfo::GetDefaultGameInfoPath(EGame Game)
{
Game = RoundGame(Game);
if (Game == eUnknownGame)
return "";
TString GameName = GetGameShortName(Game);
return TString::Format("%s\\GameInfo%s.xml", *gkGameInfoDir, *GameName);
}

View File

@@ -0,0 +1,40 @@
#ifndef CGAMEINFO
#define CGAMEINFO
#include <Common/AssertMacro.h>
#include <Common/CAssetID.h>
#include <Common/TString.h>
#include <Common/Serialization/IArchive.h>
#include <Common/Serialization/XML.h>
#include <map>
const TString gkGameInfoDir = "..\\resources\\gameinfo";
class CGameInfo
{
EGame mGame;
std::map<CAssetID, TString> mAreaNameMap;
public:
CGameInfo()
: mGame(eUnknownGame)
{}
void LoadGameInfo(EGame Game);
void LoadGameInfo(TString Path);
void SaveGameInfo(TString Path = "");
void Serialize(IArchive& rArc);
TString GetAreaName(const CAssetID& rkID) const;
// Accessors
inline EGame Game() const { return mGame; }
// Static
static CGameInfo* GetGameInfo(EGame Game);
static EGame RoundGame(EGame Game);
static TString GetDefaultGameInfoPath(EGame Game);
};
#endif // CGAMEINFO

View File

@@ -13,6 +13,7 @@ CGameProject::~CGameProject()
mspActiveProject = nullptr;
delete mpAudioManager;
delete mpGameInfo;
delete mpResourceStore;
}
@@ -23,10 +24,12 @@ bool CGameProject::Load(const TWideString& rkPath)
TString ProjPath = rkPath.ToUTF8();
CXMLReader Reader(ProjPath);
mGame = Reader.Game();
Serialize(Reader);
CTemplateLoader::LoadGameTemplates(mGame);
mpResourceStore->LoadResourceDatabase();
mpGameInfo->LoadGameInfo(mGame);
mpAudioManager->LoadAssets();
return true;
}
@@ -41,7 +44,7 @@ void CGameProject::Save()
void CGameProject::Serialize(IArchive& rArc)
{
rArc << SERIAL("Name", mProjectName)
<< SERIAL("Game", mGame)
<< SERIAL("BuildVersion", mBuildVersion)
<< SERIAL("ResourceDB", mResourceDBPath);
// Packages

View File

@@ -1,6 +1,7 @@
#ifndef CGAMEPROJECT_H
#define CGAMEPROJECT_H
#include "CGameInfo.h"
#include "CPackage.h"
#include "CResourceStore.h"
#include "Core/CAudioManager.h"
@@ -14,11 +15,13 @@
class CGameProject
{
EGame mGame;
float mBuildVersion;
TString mProjectName;
TWideString mProjectRoot;
TWideString mResourceDBPath;
std::vector<CPackage*> mPackages;
CResourceStore *mpResourceStore;
CGameInfo *mpGameInfo;
CAudioManager *mpAudioManager;
enum EProjectVersion
@@ -38,6 +41,7 @@ public:
, mResourceDBPath(L"ResourceDB.rdb")
{
mpResourceStore = new CResourceStore(this);
mpGameInfo = new CGameInfo();
mpAudioManager = new CAudioManager(this);
}
@@ -49,17 +53,21 @@ public:
{
mProjectRoot.Replace(L"/", L"\\");
mpResourceStore = new CResourceStore(this);
mpGameInfo = new CGameInfo();
mpAudioManager = new CAudioManager(this);
}
CGameProject(CGameExporter *pExporter, const TWideString& rkProjRootDir, EGame Game)
CGameProject(CGameExporter *pExporter, const TWideString& rkProjRootDir, EGame Game, float BuildVer)
: mGame(Game)
, mBuildVersion(BuildVer)
, mProjectName(CMasterTemplate::FindGameName(Game))
, mProjectRoot(rkProjRootDir)
, mResourceDBPath(L"ResourceDB.rdb")
{
mProjectRoot.Replace(L"/", L"\\");
mpResourceStore = new CResourceStore(this, pExporter, L"Content\\", L"Cooked\\", Game);
mpGameInfo = new CGameInfo();
mpGameInfo->LoadGameInfo(mGame);
mpAudioManager = new CAudioManager(this);
}
@@ -88,8 +96,10 @@ public:
inline CPackage* PackageByIndex(u32 Index) const { return mPackages[Index]; }
inline void AddPackage(CPackage *pPackage) { mPackages.push_back(pPackage); }
inline CResourceStore* ResourceStore() const { return mpResourceStore; }
inline CGameInfo* GameInfo() const { return mpGameInfo; }
inline CAudioManager* AudioManager() const { return mpAudioManager; }
inline EGame Game() const { return mGame; }
inline float BuildVersion() const { return mBuildVersion; }
inline bool IsActive() const { return mspActiveProject == this; }
static inline CGameProject* ActiveProject() { return mspActiveProject; }

View File

@@ -316,11 +316,14 @@ bool CResourceEntry::Move(const TWideString& rkDir, const TWideString& rkName)
TString NewCookedPath = CookedAssetPath();
TString NewRawPath = RawAssetPath();
Log::Write("MOVING RESOURCE: " + OldCookedPath + " --> " + NewCookedPath);
// 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;
TString MoveFailReason;
if (!HasRawVersion() && !HasCookedVersion())
{
@@ -329,15 +332,30 @@ bool CResourceEntry::Move(const TWideString& rkDir, const TWideString& rkName)
if (FileUtil::Exists(OldRawPath))
{
FSMoveSuccess = FileUtil::CopyFile(OldRawPath, NewRawPath);
if (!FSMoveSuccess) FileUtil::DeleteFile(NewRawPath);
if (!FSMoveSuccess)
{
FileUtil::DeleteFile(NewRawPath);
MoveFailReason = TString::Format("Failed to move raw file to new destination (%s --> %s)", *OldRawPath, *NewRawPath);
}
}
if (FSMoveSuccess && FileUtil::Exists(OldCookedPath))
{
FSMoveSuccess = FileUtil::CopyFile(OldCookedPath, NewCookedPath);
if (!FSMoveSuccess) FileUtil::DeleteFile(NewCookedPath);
if (!FSMoveSuccess)
{
FileUtil::DeleteFile(NewCookedPath);
MoveFailReason = TString::Format("Failed to move cooked file to new destination (%s --> %s)", *OldCookedPath, *NewCookedPath);
}
}
}
else
{
bool HasRaw = HasRawVersion();
MoveFailReason = TString::Format("File already exists at %s asset destination (%s)", HasRaw ? "raw" : "cooked", HasRaw ? *NewRawPath : *NewCookedPath);
}
// If we succeeded, finish the move
if (FSMoveSuccess)
@@ -360,6 +378,7 @@ bool CResourceEntry::Move(const TWideString& rkDir, const TWideString& rkName)
// Otherwise, revert changes and let the caller know the move failed
else
{
Log::Error("MOVE FAILED: " + MoveFailReason);
mpDirectory = pOldDir;
mName = OldName;
mpStore->ConditionalDeleteDirectory(pNewDir);

View File

@@ -16,11 +16,10 @@ enum EResEntryFlag
{
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
eREF_Hidden = 0x00000004, // Resource is hidden, doesn't show up in resource browser
eREF_HasBeenModified = 0x00000008, // Resource has been modified and resaved by the user
// Flags that save to the cache file
eREF_SavedFlags = eREF_NeedsRecook | eREF_HasThumbnail | eREF_Hidden
eREF_SavedFlags = eREF_NeedsRecook | eREF_Hidden | eREF_HasBeenModified
};
DECLARE_FLAGS(EResEntryFlag, FResEntryFlags)

View File

@@ -323,7 +323,9 @@ void CResourceStore::ConditionalDeleteDirectory(CVirtualDirectory *pDir)
FileUtil::DeleteDirectory(CookedDir(false) + pDir->FullPath());
}
pDir->Parent()->RemoveChildDirectory(pDir);
CVirtualDirectory *pParent = pDir->Parent();
pParent->RemoveChildDirectory(pDir);
ConditionalDeleteDirectory(pParent);
}
}

View File

@@ -160,10 +160,6 @@ bool CVirtualDirectory::RemoveChildDirectory(CVirtualDirectory *pSubdir)
{
mSubdirectories.erase(It);
delete pSubdir;
if (mpParent && IsEmpty())
mpParent->RemoveChildDirectory(this);
return true;
}
}