diff --git a/resources/ResourceCacheData.bin b/resources/ResourceCacheData.bin index 288a3b23..905aa1cf 100644 Binary files a/resources/ResourceCacheData.bin and b/resources/ResourceCacheData.bin differ diff --git a/src/Core/GameProject/CAssetNameMap.cpp b/src/Core/GameProject/CAssetNameMap.cpp index 83a65035..be175af7 100644 --- a/src/Core/GameProject/CAssetNameMap.cpp +++ b/src/Core/GameProject/CAssetNameMap.cpp @@ -9,7 +9,7 @@ bool CAssetNameMap::LoadAssetNames(TString Path /*= ""*/) if (Reader.IsValid()) { - CAssetID FileIDLength = CAssetID::GameIDLength(Reader.Game()); + EIDLength FileIDLength = CAssetID::GameIDLength(Reader.Game()); if (FileIDLength == mIDLength) { diff --git a/src/Core/GameProject/CGameExporter.cpp b/src/Core/GameProject/CGameExporter.cpp index 9434b00b..407e13e8 100644 --- a/src/Core/GameProject/CGameExporter.cpp +++ b/src/Core/GameProject/CGameExporter.cpp @@ -531,6 +531,10 @@ void CGameExporter::ExportResourceEditorData() It->Save(true); else It->UpdateDependencies(); + + // Set flags, save metadata + It->SetFlag(eREF_IsRetroResource); + It->SaveMetadata(true); } } diff --git a/src/Core/GameProject/CResourceEntry.cpp b/src/Core/GameProject/CResourceEntry.cpp index 87fd4595..fa3fed50 100644 --- a/src/Core/GameProject/CResourceEntry.cpp +++ b/src/Core/GameProject/CResourceEntry.cpp @@ -19,6 +19,7 @@ CResourceEntry::CResourceEntry(CResourceStore *pStore, const CAssetID& rkID, , mID(rkID) , mpDirectory(nullptr) , mName(rkFilename) + , mMetadataDirty(false) , mCachedSize(-1) , mCachedUppercaseName(rkFilename.ToUpper()) { @@ -35,12 +36,87 @@ CResourceEntry::~CResourceEntry() if (mpDependencies) delete mpDependencies; } -void CResourceEntry::SerializeCacheData(IArchive& rArc) +bool CResourceEntry::LoadMetadata() { + ASSERT(!mMetadataDirty); + TString Path = MetadataFilePath(); + + if (FileUtil::Exists(Path)) + { + // Validate file + CFileInStream MetaFile(Path, IOUtil::eBigEndian); + u32 Magic = MetaFile.ReadLong(); + + if (Magic == FOURCC('META')) + { + CSerialVersion Version(MetaFile); + CBinaryReader Reader(&MetaFile, Version); + SerializeMetadata(Reader); + return true; + } + else + { + Log::Error(Path + ": Failed to load metadata file, invalid magic: " + CFourCC(Magic).ToString()); + } + } + + return false; +} + +bool CResourceEntry::SaveMetadata(bool ForceSave /*= false*/) +{ + if (mMetadataDirty || ForceSave) + { + TString Path = MetadataFilePath(); + TString Dir = Path.GetFileDirectory(); + FileUtil::MakeDirectory(Dir); + + CFileOutStream MetaFile(Path, IOUtil::eBigEndian); + + if (MetaFile.IsValid()) + { + MetaFile.WriteLong(0); // Magic dummy + + CSerialVersion Version(IArchive::skCurrentArchiveVersion, 0, Game()); + Version.Write(MetaFile); + + // Scope the binary writer to ensure it finishes before we go back to write the magic value + { + CBinaryWriter Writer(&MetaFile, Version); + SerializeMetadata(Writer); + } + + MetaFile.GoTo(0); + MetaFile.WriteLong(FOURCC('META')); + + mMetadataDirty = false; + return true; + } + } + + return false; +} + +void CResourceEntry::SerializeMetadata(IArchive& rArc) +{ + // Serialize ID. If we already have a valid ID then don't allow the file to override it. + CAssetID ID = mID; + rArc << SERIAL("AssetID", ID); + + if (rArc.IsReader() && !mID.IsValid()) + mID = ID; + + // Serialize type + rArc << SERIAL("Type", mpTypeInfo); + + // Serialize flags u32 Flags = mFlags & eREF_SavedFlags; rArc << SERIAL_AUTO(Flags); if (rArc.IsReader()) mFlags = Flags & eREF_SavedFlags; +} +void CResourceEntry::SerializeCacheData(IArchive& rArc) +{ // Note: If the dependency tree format is changed this should be adjusted so that // we regenerate the dependencies from scratch instead of reading the tree if the // file version number is too low @@ -92,12 +168,12 @@ bool CResourceEntry::HasCookedVersion() const TString CResourceEntry::RawAssetPath(bool Relative) const { - return CookedAssetPath(Relative) + ".raw"; + return CookedAssetPath(Relative) + ".rsraw"; } TString CResourceEntry::RawExtension() const { - return CookedExtension().ToString() + ".raw"; + return CookedExtension().ToString() + ".rsraw"; } TString CResourceEntry::CookedAssetPath(bool Relative) const @@ -113,6 +189,11 @@ CFourCC CResourceEntry::CookedExtension() const return mpTypeInfo->CookedExtension(Game()); } +TString CResourceEntry::MetadataFilePath(bool Relative) const +{ + return CookedAssetPath(Relative) + ".rsmeta"; +} + bool CResourceEntry::IsInDirectory(CVirtualDirectory *pDir) const { CVirtualDirectory *pParentDir = mpDirectory; @@ -146,7 +227,7 @@ bool CResourceEntry::NeedsRecook() const // toggled to arbitrarily flag any asset for recook. if (!HasRawVersion()) return false; if (!HasCookedVersion()) return true; - if (mFlags.HasFlag(eREF_NeedsRecook)) return true; + if (HasFlag(eREF_NeedsRecook)) return true; return (FileUtil::LastModifiedTime(CookedAssetPath()) < FileUtil::LastModifiedTime(RawAssetPath())); } @@ -188,7 +269,7 @@ bool CResourceEntry::Save(bool SkipCacheSave /*= false*/) return false; } - mFlags |= eREF_NeedsRecook; + SetFlag(eREF_NeedsRecook); } // This resource type doesn't have a raw format; save cooked instead @@ -203,11 +284,10 @@ bool CResourceEntry::Save(bool SkipCacheSave /*= false*/) } } - // Resource has been saved; now make sure dependencies, cache data, and packages are all up to date - mFlags |= eREF_HasBeenModified; - + // Resource has been saved; now make sure metadata, dependencies, and packages are all up to date + SetFlag(eREF_HasBeenModified); + SaveMetadata(); UpdateDependencies(); - mpStore->SetCacheDataDirty(); if (!SkipCacheSave) { @@ -250,9 +330,9 @@ bool CResourceEntry::Cook() if (Success) { - mFlags &= ~eREF_NeedsRecook; - mFlags |= eREF_HasBeenModified; - mpStore->SetCacheDataDirty(); + ClearFlag(eREF_NeedsRecook); + SetFlag(eREF_HasBeenModified); + SaveMetadata(); } return Success; @@ -458,3 +538,21 @@ EGame CResourceEntry::Game() const { return mpStore ? mpStore->Game() : eUnknownGame; } + +void CResourceEntry::SetFlag(EResEntryFlag Flag) +{ + if (!HasFlag(Flag)) + { + mFlags.SetFlag(Flag); + mMetadataDirty = true; + } +} + +void CResourceEntry::ClearFlag(EResEntryFlag Flag) +{ + if (HasFlag(Flag)) + { + mFlags.ClearFlag(Flag); + mMetadataDirty = true; + } +} diff --git a/src/Core/GameProject/CResourceEntry.h b/src/Core/GameProject/CResourceEntry.h index d91a7eed..b014a29b 100644 --- a/src/Core/GameProject/CResourceEntry.h +++ b/src/Core/GameProject/CResourceEntry.h @@ -17,12 +17,14 @@ class CDependencyTree; enum EResEntryFlag { eREF_NeedsRecook = 0x00000001, // Resource has been updated but not recooked - // UNUSED = 0x00000002, + eREF_IsRetroResource = 0x00000002, // Resource is from the original game, not user-created 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 - eREF_IsUserResource = 0x00000010, // Resource was created by the user (i.e. isn't a Retro Studios asset) + eREF_AutoResName = 0x00000010, // Resource name is auto-generated + eREF_AutoResDir = 0x00000020, // Resource directory name is auto-generated // Flags that save to the cache file - eREF_SavedFlags = eREF_NeedsRecook | eREF_Hidden | eREF_HasBeenModified | eREF_IsUserResource + eREF_SavedFlags = eREF_NeedsRecook | eREF_IsRetroResource | eREF_Hidden | eREF_HasBeenModified | + eREF_AutoResName | eREF_AutoResDir }; DECLARE_FLAGS(EResEntryFlag, FResEntryFlags) @@ -37,6 +39,7 @@ class CResourceEntry TString mName; FResEntryFlags mFlags; + mutable bool mMetadataDirty; mutable u64 mCachedSize; mutable TString mCachedUppercaseName; // This is used to speed up case-insensitive sorting and filtering. @@ -46,6 +49,9 @@ public: EResType Type); ~CResourceEntry(); + bool LoadMetadata(); + bool SaveMetadata(bool ForceSave = false); + void SerializeMetadata(IArchive& rArc); void SerializeCacheData(IArchive& rArc); void UpdateDependencies(); @@ -55,6 +61,7 @@ public: TString RawExtension() const; TString CookedAssetPath(bool Relative = false) const; CFourCC CookedExtension() const; + TString MetadataFilePath(bool Relative = false) const; bool IsInDirectory(CVirtualDirectory *pDir) const; u64 Size() const; bool NeedsRecook() const; @@ -68,9 +75,14 @@ public: CGameProject* Project() const; EGame Game() const; + void SetFlag(EResEntryFlag Flag); + void ClearFlag(EResEntryFlag Flag); + // Accessors - void SetDirty() { mFlags.SetFlag(eREF_NeedsRecook); } - void SetHidden(bool Hidden) { Hidden ? mFlags.SetFlag(eREF_Hidden) : mFlags.ClearFlag(eREF_Hidden); } + inline void SetDirty() { SetFlag(eREF_NeedsRecook); } + inline void SetHidden(bool Hidden) { Hidden ? SetFlag(eREF_Hidden) : ClearFlag(eREF_Hidden); } + inline bool HasFlag(EResEntryFlag Flag) const { return mFlags.HasFlag(Flag); } + inline bool IsHidden() const { return HasFlag(eREF_Hidden); } inline bool IsLoaded() const { return mpResource != nullptr; } inline bool IsCategorized() const { return mpDirectory && mpDirectory->FullPath() != "Uncategorized/"; } @@ -85,7 +97,6 @@ public: inline TString Name() const { return mName; } inline const TString& UppercaseName() const { return mCachedUppercaseName; } inline EResType ResourceType() const { return mpTypeInfo->Type(); } - inline bool IsHidden() const { return mFlags.HasFlag(eREF_Hidden); } protected: CResource* InternalLoad(IInputStream& rInput); diff --git a/src/Core/GameProject/CResourceStore.cpp b/src/Core/GameProject/CResourceStore.cpp index 67c0ca5e..4423dfd2 100644 --- a/src/Core/GameProject/CResourceStore.cpp +++ b/src/Core/GameProject/CResourceStore.cpp @@ -138,7 +138,7 @@ bool CResourceStore::LoadCacheFile() // Cache header CFourCC Magic(CacheFile); - if (Magic != "CACH") + if (Magic != FOURCC('CACH')) { Log::Error("Invalid resource cache data magic: " + Magic.ToString()); return false; @@ -183,8 +183,8 @@ bool CResourceStore::SaveCacheFile() } // Cache header - CFourCC("CACH").Write(CacheFile); - CSerialVersion Version(0, 0, mGame); + CacheFile.WriteLong(0); // Magic dummy. Magic isn't written until the rest of the file is saved successfully. + CSerialVersion Version(IArchive::skCurrentArchiveVersion, 0, mGame); Version.Write(CacheFile); u32 ResCountOffset = CacheFile.Tell(); @@ -216,6 +216,8 @@ bool CResourceStore::SaveCacheFile() CacheFile.Seek(ResCountOffset, SEEK_SET); CacheFile.WriteLong(ResCount); + CacheFile.Seek(0, SEEK_SET); + CacheFile.WriteLong( FOURCC('CACH') ); mCacheFileDirty = false; return true; } @@ -295,7 +297,7 @@ 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 this directory is part of the project, then we should delete the corresponding filesystem directory if (pDir->GetRoot() == mpDatabaseRoot && !pDir->IsRoot()) { FileUtil::DeleteDirectory(ResourcesDir() + pDir->FullPath(), true); @@ -338,6 +340,7 @@ CResourceEntry* CResourceStore::RegisterResource(const CAssetID& rkID, EResType if (IsValidResourcePath(rkDir, rkName)) { pEntry = new CResourceEntry(this, rkID, rkDir, rkName, Type); + pEntry->LoadMetadata(); mResourceEntries[rkID] = pEntry; }