Fixed up MP3 asset name generation, implemented a bunch of extra checks and safeguards to ensure asset names/directories are valid

This commit is contained in:
Aruki
2017-05-03 03:07:34 -06:00
parent 3fc35b7c09
commit 9d6798b7ae
16 changed files with 358 additions and 175 deletions

View File

@@ -183,18 +183,41 @@ void GenerateAssetNames(CGameProject *pProj)
for (u32 iMat = 0; iMat < pMaterials->NumMaterials(); iMat++)
{
CMaterial *pMat = pMaterials->MaterialByIndex(iMat);
bool FoundLightmap = false;
if (pMat->Options().HasFlag(CMaterial::eLightmap))
for (u32 iPass = 0; iPass < pMat->PassCount(); iPass++)
{
CTexture *pLightmapTex = pMat->Pass(0)->Texture();
CResourceEntry *pTexEntry = pLightmapTex->Entry();
if (pTexEntry->IsCategorized()) continue;
CMaterialPass *pPass = pMat->Pass(iPass);
TWideString TexName = TWideString::Format(L"%s_lit_lightmap%d", *AreaName, LightmapNum);
ApplyGeneratedName(pTexEntry, AreaCookedDir, TexName);
pTexEntry->SetHidden(true);
LightmapNum++;
bool IsLightmap = ( (pArea->Game() <= eEchoes && pMat->Options().HasFlag(CMaterial::eLightmap) && iPass == 0) ||
(pArea->Game() >= eCorruptionProto && pPass->Type() == "DIFF") );
bool IsBloomLightmap = (pArea->Game() >= eCorruptionProto && pPass->Type() == "BLOL");
TWideString TexName;
if (IsLightmap)
{
TexName = TWideString::Format(L"%s_lit_lightmap%d", *AreaName, LightmapNum);
}
else if (IsBloomLightmap)
{
TexName = TWideString::Format(L"%s_lit_lightmap_bloom%d", *AreaName, LightmapNum);
}
if (!TexName.IsEmpty())
{
CTexture *pLightmapTex = pPass->Texture();
CResourceEntry *pTexEntry = pLightmapTex->Entry();
if (pTexEntry->IsCategorized()) continue;
ApplyGeneratedName(pTexEntry, AreaCookedDir, TexName);
pTexEntry->SetHidden(true);
FoundLightmap = true;
}
}
if (FoundLightmap)
LightmapNum++;
}
// Generate names from script instance names
@@ -329,16 +352,24 @@ void GenerateAssetNames(CGameProject *pProj)
{
CMaterial *pMat = pSet->MaterialByIndex(iMat);
if (pMat->Options().HasFlag(CMaterial::eLightmap))
for (u32 iPass = 0; iPass < pMat->PassCount(); iPass++)
{
CTexture *pLightmapTex = pMat->Pass(0)->Texture();
CResourceEntry *pTexEntry = pLightmapTex->Entry();
if (pTexEntry->IsNamed() || pTexEntry->IsCategorized()) continue;
CMaterialPass *pPass = pMat->Pass(iPass);
TWideString TexName = TWideString::Format(L"%s_lightmap%d", *It->Name(), LightmapNum);
ApplyGeneratedName(pTexEntry, pModel->Entry()->DirectoryPath(), TexName);
pTexEntry->SetHidden(true);
LightmapNum++;
bool IsLightmap = ( (pMat->Version() <= eEchoes && pMat->Options().HasFlag(CMaterial::eLightmap) && iPass == 0) ||
(pMat->Version() >= eCorruptionProto && pPass->Type() == "DIFF") );
if (IsLightmap)
{
CTexture *pLightmapTex = pPass->Texture();
CResourceEntry *pTexEntry = pLightmapTex->Entry();
if (pTexEntry->IsNamed() || pTexEntry->IsCategorized()) continue;
TWideString TexName = TWideString::Format(L"%s_lightmap%d", *It->Name(), LightmapNum);
ApplyGeneratedName(pTexEntry, pModel->Entry()->DirectoryPath(), TexName);
pTexEntry->SetHidden(true);
LightmapNum++;
}
}
}
}
@@ -375,14 +406,27 @@ void GenerateAssetNames(CGameProject *pProj)
CResourceEntry *pSample = pStore->FindEntry(SampleID);
if (pSample && !pSample->IsNamed())
ApplyGeneratedName(pSample, kSfxDir, TWideString::Format(L"%s_sample%d", *MacroName, iSamp));
{
TWideString SampleName;
if (pMacro->NumSamples() == 1)
SampleName = MacroName;
else
SampleName = TWideString::Format(L"%s_%d", *MacroName, iSamp);
ApplyGeneratedName(pSample, kSfxDir, SampleName);
}
}
}
#endif
#if PROCESS_ANIM_CHAR_SETS
// Generate animation format names
for (TResourceIterator<eAnimSet> It(pStore); It; ++It)
// Hacky syntax because animsets are under eAnimSet in MP1/2 and eCharacter in MP3/DKCR
CResourceIterator *pIter = (pProj->Game() <= eEchoes ? (CResourceIterator*) new TResourceIterator<eAnimSet> : (CResourceIterator*) new TResourceIterator<eCharacter>);
CResourceIterator& It = *pIter;
for (; It; ++It)
{
TWideString SetDir = It->DirectoryPath();
TWideString NewSetName;
@@ -462,6 +506,7 @@ void GenerateAssetNames(CGameProject *pProj)
}
}
}
delete pIter;
#endif
#if PROCESS_STRINGS

View File

@@ -54,7 +54,6 @@ bool CGameExporter::Export(nod::DiscBase *pDisc, const TString& rkOutputDir, CAs
// Create project
mpProject = CGameProject::CreateProjectForExport(
this,
mExportDir,
mGame,
mRegion,
@@ -446,23 +445,13 @@ void CGameExporter::LoadResource(const SResourceInstance& rkResource, std::vecto
void CGameExporter::ExportCookedResources()
{
{
SCOPED_TIMER(ExportCookedResources);
FileUtil::MakeDirectory(mCookedDir);
SCOPED_TIMER(ExportCookedResources);
FileUtil::MakeDirectory(mCookedDir);
for (auto It = mResourceMap.begin(); It != mResourceMap.end(); It++)
{
SResourceInstance& rRes = It->second;
ExportResource(rRes);
}
}
for (auto It = mResourceMap.begin(); It != mResourceMap.end(); It++)
{
SCOPED_TIMER(SaveResourceDatabase);
#if EXPORT_COOKED
mpStore->SaveResourceDatabase();
#endif
bool SaveSuccess = mpProject->Save();
ASSERT(SaveSuccess);
SResourceInstance& rRes = It->second;
ExportResource(rRes);
}
}
@@ -512,8 +501,13 @@ void CGameExporter::ExportResourceEditorData()
}
}
{
// All resources should have dependencies generated, so save the cache file
SCOPED_TIMER(SaveResourceCacheData);
// All resources should have dependencies generated, so save the project files
SCOPED_TIMER(SaveResourceDatabase);
#if EXPORT_COOKED
mpStore->SaveResourceDatabase();
#endif
bool SaveSuccess = mpProject->Save();
ASSERT(SaveSuccess);
mpStore->SaveCacheFile();
}
}

View File

@@ -144,7 +144,6 @@ CAssetID CGameProject::FindNamedResource(const TString& rkName) const
}
CGameProject* CGameProject::CreateProjectForExport(
CGameExporter *pExporter,
const TWideString& rkProjRootDir,
EGame Game,
ERegion Region,
@@ -168,7 +167,7 @@ CGameProject* CGameProject::CreateProjectForExport(
pProj->mProjectRoot = rkProjRootDir;
pProj->mProjectRoot.Replace(L"/", L"\\");
pProj->mpResourceStore = new CResourceStore(pProj, pExporter, L"Content\\", L"Cooked\\", Game);
pProj->mpResourceStore = new CResourceStore(pProj, L"Content\\", L"Cooked\\", Game);
pProj->mpGameInfo->LoadGameInfo(Game);
pProj->mLoadSuccess = true;
return pProj;

View File

@@ -70,7 +70,6 @@ public:
// Static
static CGameProject* CreateProjectForExport(
CGameExporter *pExporter,
const TWideString& rkProjRootDir,
EGame Game,
ERegion Region,

View File

@@ -356,8 +356,8 @@ bool CResourceEntry::CanMoveTo(const TWideString& rkDir, const TWideString& rkNa
// 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;
// Validate that the path/name are valid
if (!mpStore->IsValidResourcePath(rkDir, rkName)) 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);

View File

@@ -18,7 +18,6 @@ CResourceStore *gpEditorStore = nullptr;
CResourceStore::CResourceStore(const TWideString& rkDatabasePath)
: mpProj(nullptr)
, mGame(eUnknownGame)
, mpExporter(nullptr)
, mDatabaseDirty(false)
, mCacheFileDirty(false)
{
@@ -28,12 +27,11 @@ CResourceStore::CResourceStore(const TWideString& rkDatabasePath)
}
// Constructor for game exporter
CResourceStore::CResourceStore(CGameProject *pProject, CGameExporter *pExporter, const TWideString& rkRawDir, const TWideString& rkCookedDir, EGame Game)
CResourceStore::CResourceStore(CGameProject *pProject, const TWideString& rkRawDir, const TWideString& rkCookedDir, EGame Game)
: mpProj(nullptr)
, mGame(Game)
, mRawDir(rkRawDir)
, mCookedDir(rkCookedDir)
, mpExporter(pExporter)
, mDatabaseDirty(false)
, mCacheFileDirty(false)
{
@@ -45,7 +43,6 @@ CResourceStore::CResourceStore(CGameProject *pProject)
: mpProj(nullptr)
, mGame(eUnknownGame)
, mpDatabaseRoot(nullptr)
, mpExporter(nullptr)
, mDatabaseDirty(false)
, mCacheFileDirty(false)
{
@@ -352,8 +349,8 @@ void CResourceStore::ConditionalDeleteDirectory(CVirtualDirectory *pDir)
// 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());
FileUtil::DeleteDirectory(RawDir(false) + pDir->FullPath(), true);
FileUtil::DeleteDirectory(CookedDir(false) + pDir->FullPath(), true);
}
CVirtualDirectory *pParent = pDir->Parent();
@@ -398,8 +395,15 @@ CResourceEntry* CResourceStore::RegisterResource(const CAssetID& rkID, EResType
else
{
pEntry = new CResourceEntry(this, rkID, rkDir, rkName, Type);
mResourceEntries[rkID] = pEntry;
// Validate directory
if (IsValidResourcePath(rkDir, rkName))
{
pEntry = new CResourceEntry(this, rkID, rkDir, rkName, Type);
mResourceEntries[rkID] = pEntry;
}
else
Log::Error("Invalid resource path, failed to register: " + rkDir.ToUTF8() + rkName.ToUTF8());
}
return pEntry;
@@ -434,50 +438,32 @@ CResource* CResourceStore::LoadResource(const CAssetID& rkID, const CFourCC& rkT
if (Find != mLoadedResources.end())
return Find->second->Resource();
// With Game Exporter - Get data buffer from exporter
if (mpExporter)
{
std::vector<u8> DataBuffer;
mpExporter->LoadResource(rkID, DataBuffer);
if (DataBuffer.empty()) return nullptr;
// Check for resource in store
CResourceEntry *pEntry = FindEntry(rkID);
if (pEntry) return pEntry->Load();
CMemoryInStream MemStream(DataBuffer.data(), DataBuffer.size(), IOUtil::eBigEndian);
EResType Type = CResTypeInfo::TypeForCookedExtension(mGame, rkType)->Type();
CResourceEntry *pEntry = RegisterTransientResource(Type, rkID);
CResource *pRes = pEntry->LoadCooked(MemStream);
// Check in transient load directory - this only works for cooked
EResType Type = CResTypeInfo::TypeForCookedExtension(mGame, rkType)->Type();
if (Type != eInvalidResType)
{
// Note the entry may not be able to find the resource on its own (due to not knowing what game
// it is) so we will attempt to open the file stream ourselves and pass it to the entry instead.
TString Name = rkID.ToString();
CResourceEntry *pEntry = RegisterTransientResource(Type, mTransientLoadDir, Name.ToUTF16());
TString Path = mTransientLoadDir.ToUTF8() + Name + "." + rkType.ToString();
CFileInStream File(Path.ToStdString(), IOUtil::eBigEndian);
CResource *pRes = pEntry->LoadCooked(File);
if (!pRes) DeleteResourceEntry(pEntry);
return pRes;
}
// Without Game Exporter - Check store resource entries and transient load directory.
else
{
// Check for resource in store
CResourceEntry *pEntry = FindEntry(rkID);
if (pEntry) return pEntry->Load();
// Check in transient load directory - this only works for cooked
EResType Type = CResTypeInfo::TypeForCookedExtension(mGame, rkType)->Type();
if (Type != eInvalidResType)
{
// Note the entry may not be able to find the resource on its own (due to not knowing what game
// it is) so we will attempt to open the file stream ourselves and pass it to the entry instead.
TString Name = rkID.ToString();
CResourceEntry *pEntry = RegisterTransientResource(Type, mTransientLoadDir, Name.ToUTF16());
TString Path = mTransientLoadDir.ToUTF8() + Name + "." + rkType.ToString();
CFileInStream File(Path.ToStdString(), IOUtil::eBigEndian);
CResource *pRes = pEntry->LoadCooked(File);
if (!pRes) DeleteResourceEntry(pEntry);
return pRes;
}
else
{
Log::Error("Can't load requested resource with ID \"" + rkID.ToString() + "\"; can't locate resource. Note: Loading raw assets from an arbitrary directory is unsupported.");;
return nullptr;
}
Log::Error("Can't load requested resource with ID \"" + rkID.ToString() + "\"; can't locate resource. Note: Loading raw assets from an arbitrary directory is unsupported.");;
return nullptr;
}
}
@@ -662,7 +648,7 @@ void CResourceStore::ImportNamesFromPakContentsTxt(const TString& rkTxtPath, boo
u32 IDEnd = Line.IndexOf(" \t", IDStart);
u32 PathStart = IDEnd + 1;
u32 PathEnd = Line.Size() - 4;
u32 PathEnd = Line.Size() - 5;
TString IDStr = Line.SubString(IDStart, IDEnd - IDStart);
TString Path = Line.SubString(PathStart, PathEnd - PathStart);
@@ -679,8 +665,9 @@ void CResourceStore::ImportNamesFromPakContentsTxt(const TString& rkTxtPath, boo
if (RepStart != -1)
Path = Path.ChopFront(RepStart + 5);
// If the "x_rep" folder doesn't exist in this path for some reason, then just chop off the drive letter
else
// If the "x_rep" folder doesn't exist in this path for some reason, but this is still a path, then just chop off the drive letter.
// Otherwise, this is most likely just a standalone name, so use the full name as-is.
else if (Path[1] == ':')
Path = Path.ChopFront(3);
PathMap[pEntry] = Path;
@@ -696,9 +683,23 @@ void CResourceStore::ImportNamesFromPakContentsTxt(const TString& rkTxtPath, boo
if (UnnamedOnly && pEntry->IsNamed()) continue;
TWideString Path = Iter->second.ToUTF16();
pEntry->Move(Path.GetFileDirectory(), Path.GetFileName(false));
TWideString Dir = Path.GetFileDirectory();
TWideString Name = Path.GetFileName(false);
if (Dir.IsEmpty()) Dir = pEntry->DirectoryPath();
pEntry->Move(Dir, Name);
}
// Save
ConditionalSaveStore();
}
bool CResourceStore::IsValidResourcePath(const TWideString& rkPath, const TWideString& rkName)
{
// Path must not be an absolute path and must not go outside the project structure.
// Name must not be a path.
return ( CVirtualDirectory::IsValidDirectoryPath(rkPath) &&
FileUtil::IsValidName(rkName, false) &&
!rkName.Contains(L'/') &&
!rkName.Contains(L'\\') );
}

View File

@@ -35,9 +35,6 @@ class CResourceStore
TWideString mCookedDir;
TWideString mTransientLoadDir;
// Game exporter currently in use - lets us load from paks being exported
CGameExporter *mpExporter;
enum EDatabaseVersion
{
eVer_Initial,
@@ -48,7 +45,7 @@ class CResourceStore
public:
CResourceStore(const TWideString& rkDatabasePath);
CResourceStore(CGameProject *pProject, CGameExporter *pExporter, const TWideString& rkRawDir, const TWideString& rkCookedDir, EGame Game);
CResourceStore(CGameProject *pProject, const TWideString& rkRawDir, const TWideString& rkCookedDir, EGame Game);
CResourceStore(CGameProject *pProject);
~CResourceStore();
void SerializeResourceDatabase(IArchive& rArc);
@@ -78,6 +75,8 @@ public:
void ImportNamesFromPakContentsTxt(const TString& rkTxtPath, bool UnnamedOnly);
static bool IsValidResourcePath(const TWideString& rkPath, const TWideString& rkName);
// Accessors
inline CGameProject* Project() const { return mpProj; }
inline EGame Game() const { return mGame; }

View File

@@ -10,11 +10,15 @@ CVirtualDirectory::CVirtualDirectory(CResourceStore *pStore)
CVirtualDirectory::CVirtualDirectory(const TWideString& rkName, CResourceStore *pStore)
: mpParent(nullptr), mName(rkName), mpStore(pStore)
{}
{
ASSERT(!mName.IsEmpty() && FileUtil::IsValidName(mName, true));
}
CVirtualDirectory::CVirtualDirectory(CVirtualDirectory *pParent, const TWideString& rkName, CResourceStore *pStore)
: mpParent(pParent), mName(rkName), mpStore(pStore)
{}
{
ASSERT(!mName.IsEmpty() && FileUtil::IsValidName(mName, true));
}
CVirtualDirectory::~CVirtualDirectory()
{
@@ -34,7 +38,10 @@ bool CVirtualDirectory::IsEmpty() const
TWideString CVirtualDirectory::FullPath() const
{
return (mpParent && !mpParent->IsRoot() ? mpParent->FullPath() + mName + L'\\' : mName + L'\\');
if (IsRoot())
return L"";
else
return (mpParent && !mpParent->IsRoot() ? mpParent->FullPath() + mName + L'\\' : mName + L'\\');
}
CVirtualDirectory* CVirtualDirectory::GetRoot()
@@ -70,10 +77,12 @@ CVirtualDirectory* CVirtualDirectory::FindChildDirectory(const TWideString& rkNa
if (AllowCreate)
{
AddChild(rkName, nullptr);
CVirtualDirectory *pOut = FindChildDirectory(rkName, false);
ASSERT(pOut != nullptr);
return pOut;
if ( AddChild(rkName, nullptr) )
{
CVirtualDirectory *pOut = FindChildDirectory(rkName, false);
ASSERT(pOut != nullptr);
return pOut;
}
}
return nullptr;
@@ -111,21 +120,28 @@ CResourceEntry* CVirtualDirectory::FindChildResource(const TWideString& rkName,
return nullptr;
}
void CVirtualDirectory::AddChild(const TWideString &rkPath, CResourceEntry *pEntry)
bool CVirtualDirectory::AddChild(const TWideString &rkPath, CResourceEntry *pEntry)
{
if (rkPath.IsEmpty())
{
if (pEntry)
{
mResources.push_back(pEntry);
return true;
}
else
return false;
}
else
else if (IsValidDirectoryPath(rkPath))
{
u32 SlashIdx = rkPath.IndexOf(L"\\/");
TWideString DirName = (SlashIdx == -1 ? rkPath : rkPath.SubString(0, SlashIdx));
CVirtualDirectory *pSubdir = nullptr;
TWideString Remaining = (SlashIdx == -1 ? L"" : rkPath.SubString(SlashIdx + 1, rkPath.Size() - SlashIdx));
// Check if this subdirectory already exists
CVirtualDirectory *pSubdir = nullptr;
for (u32 iSub = 0; iSub < mSubdirectories.size(); iSub++)
{
if (mSubdirectories[iSub]->Name() == DirName)
@@ -137,17 +153,41 @@ void CVirtualDirectory::AddChild(const TWideString &rkPath, CResourceEntry *pEnt
if (!pSubdir)
{
// Create new subdirectory
pSubdir = new CVirtualDirectory(this, DirName, mpStore);
mSubdirectories.push_back(pSubdir);
std::sort(mSubdirectories.begin(), mSubdirectories.end(), [](CVirtualDirectory *pLeft, CVirtualDirectory *pRight) -> bool {
return (pLeft->Name().ToUpper() < pRight->Name().ToUpper());
});
// As an optimization, don't recurse here. We've already verified the full path is valid, so we don't need to do it again.
// We also know none of the remaining directories already exist because this is a new, empty directory.
TWideStringList Components = Remaining.Split(L"/\\");
for (auto Iter = Components.begin(); Iter != Components.end(); Iter++)
{
pSubdir = new CVirtualDirectory(pSubdir, *Iter, mpStore);
pSubdir->Parent()->mSubdirectories.push_back(pSubdir);
}
if (pEntry)
pSubdir->mResources.push_back(pEntry);
return true;
}
TWideString Remaining = (SlashIdx == -1 ? L"" : rkPath.SubString(SlashIdx + 1, rkPath.Size() - SlashIdx));
pSubdir->AddChild(Remaining, pEntry);
// If we have another valid child to add, return whether that operation completed successfully
else if (!Remaining.IsEmpty() || pEntry)
return pSubdir->AddChild(Remaining, pEntry);
// Otherwise, we're done, so just return true
else
return true;
}
else
return false;
}
bool CVirtualDirectory::RemoveChildDirectory(CVirtualDirectory *pSubdir)
@@ -180,3 +220,32 @@ bool CVirtualDirectory::RemoveChildResource(CResourceEntry *pEntry)
return false;
}
// ************ STATIC ************
bool CVirtualDirectory::IsValidDirectoryName(const TWideString& rkName)
{
return ( rkName != L"." &&
rkName != L".." &&
FileUtil::IsValidName(rkName, true) );
}
bool CVirtualDirectory::IsValidDirectoryPath(TWideString Path)
{
// Entirely empty path is valid - this refers to root
if (Path.IsEmpty())
return true;
// One trailing slash is allowed, but will cause IsValidName to fail, so we remove it here
if (Path.EndsWith(L'/') || Path.EndsWith(L'\\'))
Path = Path.ChopBack(1);
TWideStringList Parts = Path.Split(L"/\\", true);
for (auto Iter = Parts.begin(); Iter != Parts.end(); Iter++)
{
if (!IsValidDirectoryName(*Iter))
return false;
}
return true;
}

View File

@@ -30,10 +30,13 @@ public:
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 AddChild(const TWideString& rkPath, CResourceEntry *pEntry);
bool RemoveChildDirectory(CVirtualDirectory *pSubdir);
bool RemoveChildResource(CResourceEntry *pEntry);
static bool IsValidDirectoryName(const TWideString& rkName);
static bool IsValidDirectoryPath(TWideString Path);
// Accessors
inline CVirtualDirectory* Parent() const { return mpParent; }
inline bool IsRoot() const { return !mpParent; }