diff --git a/src/Core/Core.pro b/src/Core/Core.pro index ac4282d1..732236a4 100644 --- a/src/Core/Core.pro +++ b/src/Core/Core.pro @@ -317,4 +317,5 @@ SOURCES += \ Resource/Animation/CSkeleton.cpp \ Resource/Animation/IMetaAnimation.cpp \ Resource/Animation/IMetaTransition.cpp \ - GameProject/AssetNameGeneration.cpp + GameProject/AssetNameGeneration.cpp \ + GameProject/CAssetNameMap.cpp diff --git a/src/Core/GameProject/AssetNameGeneration.cpp b/src/Core/GameProject/AssetNameGeneration.cpp index 1b1da61b..8e208401 100644 --- a/src/Core/GameProject/AssetNameGeneration.cpp +++ b/src/Core/GameProject/AssetNameGeneration.cpp @@ -207,10 +207,11 @@ void GenerateAssetNames(CGameProject *pProj) { TString Name = pInst->InstanceName(); - if (Name.EndsWith(".scan")) + if (Name.EndsWith(".SCAN", false)) { TIDString ScanIDString = (pProj->Game() <= ePrime ? "0x4:0x0" : "0xBDBEC295:0xB94E9BE7"); TAssetProperty *pScanProperty = TPropCast(pInst->PropertyByIDString(ScanIDString)); + ASSERT(pScanProperty); // Temporary assert to remind myself later to update this code when uncooked properties are added to the template if (pScanProperty) { @@ -220,6 +221,10 @@ void GenerateAssetNames(CGameProject *pProj) if (pEntry && !pEntry->IsNamed()) { 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(); @@ -232,6 +237,34 @@ void GenerateAssetNames(CGameProject *pProj) } } } + + else if (pInst->ObjectTypeID() == 0x17 || pInst->ObjectTypeID() == FOURCC("MEMO")) + { + TString Name = pInst->InstanceName(); + + if (Name.EndsWith(".STRG", false)) + { + u32 StringPropID = (pProj->Game() <= ePrime ? 0x4 : 0x9182250C); + TAssetProperty *pStringProperty = TPropCast(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 (pStringProperty) + { + CAssetID StringID = pStringProperty->Get(); + CResourceEntry *pEntry = pStore->FindEntry(StringID); + + if (pEntry && !pEntry->IsNamed()) + { + TWideString StringName = Name.ToUTF16().ChopBack(5); + + if (StringName.StartsWith(L"HUDMemo - ")) + StringName = StringName.ChopFront(10); + + ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), StringName); + } + } + } + } } } diff --git a/src/Core/GameProject/CAssetNameMap.cpp b/src/Core/GameProject/CAssetNameMap.cpp new file mode 100644 index 00000000..5d0dd956 --- /dev/null +++ b/src/Core/GameProject/CAssetNameMap.cpp @@ -0,0 +1,50 @@ +#include "CAssetNameMap.h" + +std::map CAssetNameMap::smGameMap; + +CAssetNameMap::CAssetNameMap(EGame Game) + : mGame(Game) +{ + TString ListPath = GetAssetListPath(mGame); + CXMLReader Reader(ListPath); + Serialize(Reader); +} + +void CAssetNameMap::SaveAssetNames() +{ + TString ListPath = GetAssetListPath(mGame); + CXMLWriter Writer(ListPath, "AssetList", 0, mGame); + Serialize(Writer); +} + +void CAssetNameMap::GetNameInfo(CAssetID ID, TString& rOutDirectory, TString& rOutName) +{ + auto It = mMap.find(ID); + + if (It != mMap.end()) + { + SAssetNameInfo& rInfo = It->second; + rOutName = rInfo.Name; + rOutDirectory = rInfo.Directory; + } + + else + { + rOutDirectory = "Uncategorized\\"; + rOutName = ID.ToString(); + } +} + +void CAssetNameMap::CopyFromStore(CResourceStore *pStore /*= gpResourceStore*/) +{ + for (CResourceIterator It(pStore); It; ++It) + { + if (It->IsCategorized() || It->IsNamed()) + { + CAssetID ID = It->ID(); + TWideString Name = It->Name(); + TWideString Directory = It->Directory()->FullPath(); + mMap[ID] = SAssetNameInfo { Name, Directory }; + } + } +} diff --git a/src/Core/GameProject/CAssetNameMap.h b/src/Core/GameProject/CAssetNameMap.h index 6320cca2..0a02c5cc 100644 --- a/src/Core/GameProject/CAssetNameMap.h +++ b/src/Core/GameProject/CAssetNameMap.h @@ -10,84 +10,50 @@ 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 TAssetMap; - std::shared_ptr mpMap; + struct SAssetNameInfo + { + TWideString Name; + TWideString Directory; + + void Serialize(IArchive& rArc) + { + rArc << SERIAL_AUTO(Name) << SERIAL_AUTO(Directory); + } + }; + + EGame mGame; + std::map mMap; + static std::map smGameMap; + + // Private Methods + CAssetNameMap(EGame Game); void Serialize(IArchive& rArc) { - rArc << SERIAL_CONTAINER("AssetNameMap", *mpMap.get(), "Asset"); + rArc << SERIAL_CONTAINER("AssetNameMap", mMap, "Asset"); } public: - CAssetNameMap() - { - mpMap = std::make_shared(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(); - } - } + void SaveAssetNames(); + void 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 LoadAssetNames(EGame Game) + static CAssetNameMap* GetGameNameMap(EGame Game) { - TString ListPath = GetAssetListPath(Game); - CXMLReader Reader(ListPath); + auto Find = smGameMap.find(Game); + if (Find != smGameMap.end()) return Find->second; - 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); + CAssetNameMap *pMap = new CAssetNameMap(Game); + smGameMap[Game] = pMap; + return pMap; } }; diff --git a/src/Core/GameProject/CGameExporter.cpp b/src/Core/GameProject/CGameExporter.cpp index 7c37c1cd..22d28379 100644 --- a/src/Core/GameProject/CGameExporter.cpp +++ b/src/Core/GameProject/CGameExporter.cpp @@ -18,6 +18,7 @@ #define EXPORT_COOKED 1 CGameExporter::CGameExporter(const TString& rkInputDir, const TString& rkOutputDir) + : mpNameMap(nullptr) { mGame = eUnknownGame; mGameDir = FileUtil::MakeAbsolute(rkInputDir); @@ -51,7 +52,7 @@ bool CGameExporter::Export() mCookedDir = mpStore->CookedDir(false); #if USE_ASSET_NAME_MAP - mNameMap = CAssetNameMap::LoadAssetNames(mGame); + mpNameMap = CAssetNameMap::GetGameNameMap(mGame); #endif // Export game data @@ -470,7 +471,7 @@ void CGameExporter::ExportResource(SResourceInstance& rRes) // Register resource and write to file TString Directory, Name; - mNameMap.GetNameInfo(rRes.ResourceID, Directory, Name); + mpNameMap->GetNameInfo(rRes.ResourceID, Directory, Name); CResourceEntry *pEntry = mpStore->RegisterResource(rRes.ResourceID, CResource::ResTypeForExtension(rRes.ResourceType), Directory, Name); #if EXPORT_COOKED diff --git a/src/Core/GameProject/CGameExporter.h b/src/Core/GameProject/CGameExporter.h index 865104e2..6cf38894 100644 --- a/src/Core/GameProject/CGameExporter.h +++ b/src/Core/GameProject/CGameExporter.h @@ -29,7 +29,7 @@ class CGameExporter // Resources TWideStringList mPaks; std::map mAreaDuplicateMap; - CAssetNameMap mNameMap; + CAssetNameMap *mpNameMap; struct SResourceInstance { diff --git a/src/Core/GameProject/CResourceStore.cpp b/src/Core/GameProject/CResourceStore.cpp index b8216b94..2be2beb3 100644 --- a/src/Core/GameProject/CResourceStore.cpp +++ b/src/Core/GameProject/CResourceStore.cpp @@ -620,3 +620,73 @@ void CResourceStore::SetTransientLoadDir(const TString& rkDir) mTransientLoadDir.EnsureEndsWith('\\'); Log::Write("Set resource directory: " + rkDir); } + +void CResourceStore::ImportNamesFromPakContentsTxt(const TString& rkTxtPath, bool UnnamedOnly) +{ + // Read file contents -first- then move assets -after-; this + // 1. avoids anything fucking up if the contents file is badly formatted and we crash, and + // 2. avoids extra redundant moves (since there are redundant entries in the file) + std::map PathMap; + FILE *pContentsFile; + fopen_s(&pContentsFile, *rkTxtPath, "r"); + + if (!pContentsFile) + { + Log::Error("Failed to open .contents.txt file: " + rkTxtPath); + return; + } + + while (!feof(pContentsFile)) + { + // Get new line, parse to extract the ID/path + char LineBuffer[512]; + fgets(LineBuffer, 512, pContentsFile); + + TString Line(LineBuffer); + if (Line.IsEmpty()) break; + + u32 IDStart = Line.IndexOfPhrase("0x") + 2; + if (IDStart == 1) continue; + + u32 IDEnd = Line.IndexOf(" \t", IDStart); + u32 PathStart = IDEnd + 1; + u32 PathEnd = Line.Size() - 4; + + TString IDStr = Line.SubString(IDStart, IDEnd - IDStart); + TString Path = Line.SubString(PathStart, PathEnd - PathStart); + + CAssetID ID = CAssetID::FromString(IDStr); + CResourceEntry *pEntry = FindEntry(ID); + + // Only process this entry if the ID exists + if (pEntry) + { + // Chop name to just after "x_rep" + u32 RepStart = Path.IndexOfPhrase("_rep"); + + 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 + Path = Path.ChopFront(3); + + PathMap[pEntry] = Path; + } + } + + fclose(pContentsFile); + + // Assign names + for (auto Iter = PathMap.begin(); Iter != PathMap.end(); Iter++) + { + CResourceEntry *pEntry = Iter->first; + if (UnnamedOnly && pEntry->IsNamed()) continue; + + TWideString Path = Iter->second.ToUTF16(); + pEntry->Move(Path.GetFileDirectory(), Path.GetFileName(false)); + } + + // Save + ConditionalSaveStore(); +} diff --git a/src/Core/GameProject/CResourceStore.h b/src/Core/GameProject/CResourceStore.h index 74d86274..86216a93 100644 --- a/src/Core/GameProject/CResourceStore.h +++ b/src/Core/GameProject/CResourceStore.h @@ -77,6 +77,8 @@ public: bool DeleteResourceEntry(CResourceEntry *pEntry); void SetTransientLoadDir(const TString& rkDir); + void ImportNamesFromPakContentsTxt(const TString& rkTxtPath, bool UnnamedOnly); + // Accessors inline CGameProject* Project() const { return mpProj; } inline EGame Game() const { return mGame; } diff --git a/src/Editor/ResourceBrowser/CResourceBrowser.cpp b/src/Editor/ResourceBrowser/CResourceBrowser.cpp index 29d697bd..b2b251f2 100644 --- a/src/Editor/ResourceBrowser/CResourceBrowser.cpp +++ b/src/Editor/ResourceBrowser/CResourceBrowser.cpp @@ -2,6 +2,8 @@ #include "ui_CResourceBrowser.h" #include "Editor/ModelEditor/CModelEditorWindow.h" #include "Editor/CharacterEditor/CCharacterEditor.h" +#include +#include #include CResourceBrowser::CResourceBrowser(QWidget *pParent) @@ -17,7 +19,6 @@ CResourceBrowser::CResourceBrowser(QWidget *pParent) mpProxyModel = new CResourceProxyModel(this); mpProxyModel->setSourceModel(mpModel); mpUI->ResourceTableView->setModel(mpProxyModel); - RefreshResources(); QHeaderView *pHeader = mpUI->ResourceTableView->horizontalHeader(); pHeader->setSectionResizeMode(0, QHeaderView::Stretch); @@ -26,9 +27,16 @@ CResourceBrowser::CResourceBrowser(QWidget *pParent) // Set up directory tree model mpDirectoryModel = new CVirtualDirectoryModel(this); - mpDirectoryModel->SetRoot(gpResourceStore ? gpResourceStore->RootDirectory() : nullptr); mpUI->DirectoryTreeView->setModel(mpDirectoryModel); - mpUI->DirectoryTreeView->expand(mpDirectoryModel->index(0, 0, QModelIndex())); + + RefreshResources(); + + // Set up Import Names menu + QMenu *pImportNamesMenu = new QMenu(this); + mpUI->ImportNamesButton->setMenu(pImportNamesMenu); + + QAction *pImportFromContentsTxtAction = new QAction("Import from Pak Contents List", this); + pImportNamesMenu->addAction(pImportFromContentsTxtAction); // Set up connections connect(mpUI->StoreComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnStoreChanged(int))); @@ -36,6 +44,7 @@ CResourceBrowser::CResourceBrowser(QWidget *pParent) connect(mpUI->SortComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSortModeChanged(int))); connect(mpUI->DirectoryTreeView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(OnDirectorySelectionChanged(QModelIndex,QModelIndex))); connect(mpUI->ResourceTableView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(OnDoubleClickResource(QModelIndex))); + connect(pImportFromContentsTxtAction, SIGNAL(triggered()), this, SLOT(OnImportPakContentsTxt())); } CResourceBrowser::~CResourceBrowser() @@ -45,19 +54,21 @@ CResourceBrowser::~CResourceBrowser() void CResourceBrowser::RefreshResources() { + // Fill resource table mpModel->FillEntryList(mpStore); + + // Fill directory tree + mpDirectoryModel->SetRoot(mpStore ? mpStore->RootDirectory() : nullptr); + QModelIndex RootIndex = mpDirectoryModel->index(0, 0, QModelIndex()); + mpUI->DirectoryTreeView->expand(RootIndex); + mpUI->DirectoryTreeView->clearSelection(); + OnDirectorySelectionChanged(QModelIndex(), QModelIndex()); } void CResourceBrowser::OnStoreChanged(int Index) { mpStore = (Index == 0 ? gpResourceStore : gpEditorStore); RefreshResources(); - - mpDirectoryModel->SetRoot(mpStore ? mpStore->RootDirectory() : nullptr); - QModelIndex RootIndex = mpDirectoryModel->index(0, 0, QModelIndex()); - mpUI->DirectoryTreeView->expand(RootIndex); - mpUI->DirectoryTreeView->clearSelection(); - OnDirectorySelectionChanged(QModelIndex(), QModelIndex()); } void CResourceBrowser::OnSortModeChanged(int Index) @@ -117,3 +128,14 @@ void CResourceBrowser::OnDoubleClickResource(QModelIndex Index) else QMessageBox::information(this, "Unsupported Resource", "The selected resource type is currently unsupported for editing."); } + +void CResourceBrowser::OnImportPakContentsTxt() +{ + QStringList PathList = QFileDialog::getOpenFileNames(this, "Open pak contents list", "", "*.pak.contents.txt"); + if (PathList.isEmpty()) return; + + foreach(const QString& rkPath, PathList) + mpStore->ImportNamesFromPakContentsTxt(TO_TSTRING(rkPath), false); + + RefreshResources(); +} diff --git a/src/Editor/ResourceBrowser/CResourceBrowser.h b/src/Editor/ResourceBrowser/CResourceBrowser.h index 7684698c..2d4fbac6 100644 --- a/src/Editor/ResourceBrowser/CResourceBrowser.h +++ b/src/Editor/ResourceBrowser/CResourceBrowser.h @@ -30,6 +30,7 @@ public slots: void OnSearchStringChanged(); void OnDirectorySelectionChanged(const QModelIndex& rkNewIndex, const QModelIndex& rkPrevIndex); void OnDoubleClickResource(QModelIndex Index); + void OnImportPakContentsTxt(); }; #endif // CRESOURCEBROWSER_H diff --git a/src/Editor/ResourceBrowser/CResourceBrowser.ui b/src/Editor/ResourceBrowser/CResourceBrowser.ui index 5e0da17f..53debcf3 100644 --- a/src/Editor/ResourceBrowser/CResourceBrowser.ui +++ b/src/Editor/ResourceBrowser/CResourceBrowser.ui @@ -187,6 +187,20 @@ + + + + Import Names + + + + + + + Export Names + + + diff --git a/src/Editor/ResourceBrowser/CResourceTableModel.h b/src/Editor/ResourceBrowser/CResourceTableModel.h index 18d61141..5b0fd4b9 100644 --- a/src/Editor/ResourceBrowser/CResourceTableModel.h +++ b/src/Editor/ResourceBrowser/CResourceTableModel.h @@ -36,8 +36,15 @@ public: if (Role == Qt::DisplayRole) { - if (Col == 0) return TO_QSTRING(pEntry->Name()); - if (Col == 1) return TO_QSTRING(GetResourceTypeName(pEntry->ResourceType())); + if (Col == 0) + { + return TO_QSTRING(pEntry->Name()); + } + + if (Col == 1) + { + return TO_QSTRING(GetResourceTypeName(pEntry->ResourceType())); + } if (Col == 2) {