//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: Implements a system for managing prefabs. There are two types // of prefabs implemented here: Half-Life style prefabs, and Half-Life 2 // style prefabs. // // For Half-Life, prefab libraries are stored as binary .OL files, each of // which contains multiple .RMF files that are the prefabs. // // For Half-Life 2, prefabs are stored in a tree of folders, each folder // representing a library, and the each .VMF file in the folder containing // a single prefab. // //=============================================================================// #include "stdafx.h" #include "Prefabs.h" #include "Prefab3D.h" #include "hammer.h" #include #include // memdbgon must be the last include file in a .cpp file!!! #include BOOL CPrefab::bCacheEnabled = TRUE; CPrefabList CPrefab::PrefabList; CPrefabList CPrefab::MRU; CPrefabLibraryList CPrefabLibrary::PrefabLibraryList; static char *pLibHeader = "Worldcraft Prefab Library\r\n\x1a"; static float fLibVersion = 0.1f; typedef struct { DWORD dwOffset; DWORD dwSize; char szName[31]; char szNotes[MAX_NOTES]; int iType; } PrefabHeader; typedef struct { float fVersion; DWORD dwDirOffset; DWORD dwNumEntries; char szNotes[MAX_NOTES]; } PrefabLibraryHeader; //----------------------------------------------------------------------------- // Purpose: Creates a prefab library from a given path. // Input : szFile - // Output : //----------------------------------------------------------------------------- CPrefabLibrary *CreatePrefabLibrary(const char *szFile) { CPrefabLibrary *pLibrary; if (stricmp(&szFile[strlen(szFile) - 2], ".ol") != 0) { pLibrary = new CPrefabLibraryVMF; } else { pLibrary = new CPrefabLibraryRMF; } if (pLibrary->Load(szFile) == -1) { delete pLibrary; return(NULL); } return(pLibrary); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPrefab::CPrefab() { static DWORD dwRunningID = 1; // assign running ID dwID = dwRunningID++; PrefabList.AddTail(this); // assign blank name/notes szName[0] = szNotes[0] = 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPrefab::~CPrefab() { POSITION p = PrefabList.Find(this); if(p) PrefabList.RemoveAt(p); p = MRU.Find(this); if(p) MRU.RemoveAt(p); } //----------------------------------------------------------------------------- // Purpose: // Input : dwID - // Output : CPrefab * //----------------------------------------------------------------------------- CPrefab * CPrefab::FindID(DWORD dwID) { POSITION p = PrefabList.GetHeadPosition(); while(p) { CPrefab *pPrefab = PrefabList.GetNext(p); if(pPrefab->dwID == dwID) return pPrefab; } return NULL; } //----------------------------------------------------------------------------- // Purpose: // Input : b - //----------------------------------------------------------------------------- void CPrefab::EnableCaching(BOOL b) { bCacheEnabled = b; } //----------------------------------------------------------------------------- // Purpose: // Input : *pPrefab - //----------------------------------------------------------------------------- void CPrefab::AddMRU(CPrefab *pPrefab) { if(!bCacheEnabled) return; POSITION p = MRU.Find(pPrefab); if(p) { // remove there and add to head MRU.RemoveAt(p); } else if(MRU.GetCount() == 5) { // uncache tail object p = MRU.GetTailPosition(); if(p) // might not be any yet { CPrefab *pUncache = MRU.GetAt(p); pUncache->FreeData(); MRU.RemoveAt(p); } } // add to head MRU.AddHead(pPrefab); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPrefab::FreeAllData() { // free all prefab data memory POSITION p = PrefabList.GetHeadPosition(); while(p) { CPrefab *pPrefab = PrefabList.GetNext(p); pPrefab->FreeData(); } } //----------------------------------------------------------------------------- // Purpose: // Input : pszFilename - // Output : CPrefab::pfiletype_t //----------------------------------------------------------------------------- CPrefab::pfiletype_t CPrefab::CheckFileType(LPCTSTR pszFilename) { // first check extensions const char *p = strrchr(pszFilename, '.'); if(p) { if(!strcmpi(p, ".rmf")) return pftRMF; else if(!strcmpi(p, ".map")) return pftMAP; else if(!strcmpi(p, ".os")) return pftScript; } std::fstream file(pszFilename, std::ios::in | std::ios::binary); // read first 16 bytes of file char szBuf[255]; file.read(szBuf, 16); // check 1: RMF float f = ((float*) szBuf)[0]; // 0.8 was version at which RMF tag was started if(f <= 0.7f || !strncmp(szBuf+sizeof(float), "RMF", 3)) { return pftRMF; } // check 2: script if(!strnicmp(szBuf, "[Script", 7)) { return pftScript; } // check 3: MAP int i = 500; while(i--) { file >> std::ws; file.getline(szBuf, 255); if(szBuf[0] == '{') return pftMAP; if(file.eof()) break; } return pftUnknown; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPrefabLibrary::CPrefabLibrary() { static DWORD dwRunningID = 1; // assign running ID dwID = dwRunningID++; m_szName[0] = '\0'; szNotes[0] = '\0'; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPrefabLibrary::~CPrefabLibrary() { FreePrefabs(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPrefabLibrary::FreePrefabs() { // nuke prefabs POSITION p = Prefabs.GetHeadPosition(); while (p != NULL) { CPrefab *pPrefab = Prefabs.GetNext(p); delete pPrefab; } } //----------------------------------------------------------------------------- // Purpose: // Input : *a - // *b - // Output : static int //----------------------------------------------------------------------------- static int __cdecl SortPrefabs(CPrefab *a, CPrefab *b) { return(strcmpi(a->GetName(), b->GetName())); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPrefabLibrary::Sort(void) { int nPrefabs = Prefabs.GetCount(); if (nPrefabs < 2) { return; } CPrefab **TmpPrefabArray = new CPrefab *[nPrefabs]; // // Make an array we can pass to qsort. // POSITION p = ENUM_START; CPrefab *pPrefab = EnumPrefabs(p); int iPrefab = 0; while (pPrefab != NULL) { TmpPrefabArray[iPrefab++] = pPrefab; pPrefab = EnumPrefabs(p); } // // Sort the prefabs array by name. // qsort(TmpPrefabArray, nPrefabs, sizeof(CPrefab *), (int (__cdecl *)(const void *, const void *))SortPrefabs); // // Store back in list in sorted order. // Prefabs.RemoveAll(); for (int i = 0; i < nPrefabs; i++) { Prefabs.AddTail(TmpPrefabArray[i]); } delete[] TmpPrefabArray; } //----------------------------------------------------------------------------- // Purpose: // Input : pszFilename - //----------------------------------------------------------------------------- void CPrefabLibrary::SetNameFromFilename(LPCTSTR pszFilename) { const char *cp = strrchr(pszFilename, '\\'); strcpy(m_szName, cp ? (cp + 1) : pszFilename); char *p = strchr(m_szName, '.'); if (p != NULL) { p[0] = '\0'; } } //----------------------------------------------------------------------------- // Purpose: Frees all the libraries in the prefab library list. //----------------------------------------------------------------------------- void CPrefabLibrary::FreeAllLibraries(void) { POSITION pos = PrefabLibraryList.GetHeadPosition(); while (pos != NULL) { CPrefabLibrary *pPrefabLibrary = PrefabLibraryList.GetNext(pos); if (pPrefabLibrary != NULL) { delete pPrefabLibrary; } } PrefabLibraryList.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: Load all libraries in the prefabs directory. //----------------------------------------------------------------------------- void CPrefabLibrary::LoadAllLibraries() { char szDir[MAX_PATH]; char szFile[MAX_PATH]; ((CHammer *)AfxGetApp())->GetDirectory(DIR_PREFABS, szDir); // // Add one prefab library for the root prefabs folder in case they put something there. // CPrefabLibrary *pLibrary = FindOpenLibrary(szDir); if (pLibrary == NULL) { pLibrary = CreatePrefabLibrary(szDir); if (pLibrary != NULL) { PrefabLibraryList.AddTail(pLibrary); } } else { pLibrary->Load(szDir); } strcat(szDir, "\\*.*"); WIN32_FIND_DATA fd; HANDLE hnd = FindFirstFile(szDir, &fd); strrchr(szDir, '\\')[0] = 0; // truncate that if (hnd == INVALID_HANDLE_VALUE) { return; // no libraries } do { if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (fd.cFileName[0] != '.')) { sprintf(szFile, "%s\\%s", szDir, fd.cFileName); pLibrary = FindOpenLibrary(szFile); if (pLibrary == NULL) { pLibrary = CreatePrefabLibrary(szFile); if (pLibrary != NULL) { PrefabLibraryList.AddTail(pLibrary); } } else { pLibrary->Load(szDir); } } } while (FindNextFile(hnd, &fd)); FindClose(hnd); } //----------------------------------------------------------------------------- // Purpose: // Input : *pPrefab - //----------------------------------------------------------------------------- void CPrefabLibrary::Add(CPrefab *pPrefab) { if(!Prefabs.Find(pPrefab)) Prefabs.AddTail(pPrefab); pPrefab->dwLibID = dwID; } //----------------------------------------------------------------------------- // Purpose: // Input : *pPrefab - //----------------------------------------------------------------------------- void CPrefabLibrary::Remove(CPrefab *pPrefab) { POSITION p = Prefabs.Find(pPrefab); if(p) Prefabs.RemoveAt(p); if(pPrefab->dwLibID == dwID) // make sure it doesn't reference this pPrefab->dwLibID = 0xffff; } //----------------------------------------------------------------------------- // Purpose: // Input : &p - // Output : CPrefab * //----------------------------------------------------------------------------- CPrefab * CPrefabLibrary::EnumPrefabs(POSITION &p) { if(p == ENUM_START) p = Prefabs.GetHeadPosition(); if(!p) return NULL; return Prefabs.GetNext(p); } //----------------------------------------------------------------------------- // Purpose: // Input : dwID - // Output : CPrefabLibrary * //----------------------------------------------------------------------------- CPrefabLibrary * CPrefabLibrary::FindID(DWORD dwID) { POSITION p = PrefabLibraryList.GetHeadPosition(); while(p) { CPrefabLibrary *pPrefabLibrary = PrefabLibraryList.GetNext(p); if(pPrefabLibrary->dwID == dwID) return pPrefabLibrary; } return NULL; } //----------------------------------------------------------------------------- // Purpose: // Input : pszFilename - // Output : //----------------------------------------------------------------------------- CPrefabLibrary *CPrefabLibrary::FindOpenLibrary(LPCTSTR pszFilename) { // checks to see if a library is open under that filename POSITION p = ENUM_START; CPrefabLibrary *pLibrary = EnumLibraries(p); while (pLibrary != NULL) { if (pLibrary->IsFile(pszFilename)) { return(pLibrary); } pLibrary = EnumLibraries(p); } return(NULL); } //----------------------------------------------------------------------------- // Purpose: Enumerates the prefab libraries of a given type. // Input : p - Iterator. // eType - Type of library to return, LibType_None returns all // library types. // Output : Returns the next library of the given type. //----------------------------------------------------------------------------- CPrefabLibrary *CPrefabLibrary::EnumLibraries(POSITION &p, LibraryType_t eType) { if (p == ENUM_START) { p = PrefabLibraryList.GetHeadPosition(); } while (p != NULL) { CPrefabLibrary *pLibrary = PrefabLibraryList.GetNext(p); if ((eType == LibType_None) || pLibrary->IsType(eType)) { return(pLibrary); } } return(NULL); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPrefabLibraryRMF::CPrefabLibraryRMF() { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPrefabLibraryRMF::~CPrefabLibraryRMF() { } //----------------------------------------------------------------------------- // Purpose: Returns true if this prefab represents the given filename, false if not. // Input : szFilename - Path of a prefab library or folder. //----------------------------------------------------------------------------- bool CPrefabLibraryRMF::IsFile(const char *szFilename) { return(strcmpi(m_strOpenFileName, szFilename) == 0); } //----------------------------------------------------------------------------- // Purpose: // Input : pszFilename - // Output : int //----------------------------------------------------------------------------- int CPrefabLibraryRMF::Load(LPCTSTR pszFilename) { FreePrefabs(); m_eType = LibType_HalfLife; // open file m_file.open(pszFilename, std::ios::in | std::ios::binary); m_strOpenFileName = pszFilename; if(!m_file.is_open()) return -1; char szBuf[128]; // read string header m_file.read(szBuf, strlen(pLibHeader)); if(strncmp(szBuf, pLibHeader, strlen(pLibHeader))) { // return return -1; } // read binary header PrefabLibraryHeader plh; m_file.read((char*)&plh, sizeof(plh)); strcpy(szNotes, plh.szNotes); // set name from filename SetNameFromFilename(pszFilename); // read directory PrefabHeader *ph = new PrefabHeader[plh.dwNumEntries]; m_dwDirOffset = plh.dwDirOffset; m_file.seekg(plh.dwDirOffset); m_file.read((char*)ph, plh.dwNumEntries * sizeof(PrefabHeader)); // // Read each prefab. // for(DWORD i = 0; i < plh.dwNumEntries; i++) { Assert(ph[i].iType == pt3D); CPrefabRMF *pPrefab = new CPrefabRMF; // seek to prefab m_file.seekg(ph[i].dwOffset); pPrefab->Init(m_file); // set its other info frm the dir entry pPrefab->SetName(ph[i].szName); pPrefab->SetNotes(ph[i].szNotes); pPrefab->dwFileSize = ph[i].dwSize; pPrefab->dwFileOffset = ph[i].dwOffset; Add(pPrefab); } // delete directory delete[] ph; return 1; } //----------------------------------------------------------------------------- // Purpose: Removes this prefab library from disk. // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CPrefabLibraryRMF::DeleteFile(void) { return(remove(m_strOpenFileName) == 0); } //----------------------------------------------------------------------------- // Purpose: // Input : pszFilename - // bIndexOnly - // Output : int //----------------------------------------------------------------------------- int CPrefabLibraryRMF::Save(LPCTSTR pszFilename, BOOL bIndexOnly) { // temp storage static char szFile[MAX_PATH]; // if only saving index, special code - if(bIndexOnly && m_file.is_open()) { // close file, reopen in binary write m_file.close(); if(Prefabs.GetCount()) { // change size of file first int iHandle = _open(m_strOpenFileName, _O_BINARY | _O_WRONLY); _chsize(iHandle, m_dwDirOffset); _close(iHandle); } std::fstream file(m_strOpenFileName, std::ios::binary | std::ios::out); // write string header file << pLibHeader; // write binary header (in case notes have changed) PrefabLibraryHeader plh; plh.dwNumEntries = Prefabs.GetCount(); plh.fVersion = fLibVersion; plh.dwDirOffset = m_dwDirOffset; strcpy(plh.szNotes, szNotes); file.write((char*)&plh, sizeof plh); // recreate a directory and write it PrefabHeader *ph = new PrefabHeader[Prefabs.GetCount()]; int iCur = 0; POSITION p = Prefabs.GetHeadPosition(); while(p) { CPrefab *pPrefab = Prefabs.GetNext(p); // setup this dir entry ph[iCur].dwOffset = pPrefab->dwFileOffset; ph[iCur].dwSize = pPrefab->dwFileSize; strcpy(ph[iCur].szName, pPrefab->GetName()); strcpy(ph[iCur].szNotes, pPrefab->GetNotes()); ph[iCur].iType = pPrefab->GetType(); ++iCur; // increase current directory entry } // write directory file.seekp(m_dwDirOffset); file.write((char*)ph, sizeof(*ph) * Prefabs.GetCount()); file.close(); // re-open m_file.open(m_strOpenFileName, std::ios::in | std::ios::binary); return 1; } if(pszFilename == NULL) { pszFilename = szFile; if(m_strOpenFileName.IsEmpty()) { char szNewFilename[MAX_PATH]; CHammer *pApp = (CHammer*) AfxGetApp(); pApp->GetDirectory(DIR_PREFABS, szNewFilename); sprintf(szNewFilename + strlen(szNewFilename), "\\%s.ol", m_szName); // make a name m_strOpenFileName = szNewFilename; } strcpy(szFile, m_strOpenFileName); } else { strcpy(szFile, pszFilename); SetNameFromFilename(pszFilename); } // open temp file to save to.. then delete & rename old one. CString strTempFileName = "Temporary Prefab Library.$$$"; std::fstream file; file.open(strTempFileName, std::ios::binary | std::ios::out); // write string header file << pLibHeader; // write binary header // save current position so we can seek back and rewrite it DWORD dwBinaryHeaderOffset = file.tellp(); PrefabLibraryHeader plh; plh.dwNumEntries = Prefabs.GetCount(); plh.fVersion = fLibVersion; strcpy(plh.szNotes, szNotes); file.write((char*)&plh, sizeof plh); // allocate memory for directory PrefabHeader *ph = new PrefabHeader[plh.dwNumEntries]; int iCur = 0; char *pCopyBuf = new char[64000]; // write each prefab POSITION p = Prefabs.GetHeadPosition(); while (p) { CPrefabRMF *pPrefab = (CPrefabRMF *)Prefabs.GetNext(p); // setup this dir entry ph[iCur].dwOffset = file.tellp(); strcpy(ph[iCur].szName, pPrefab->GetName()); strcpy(ph[iCur].szNotes, pPrefab->GetNotes()); ph[iCur].iType = pPrefab->GetType(); if(pPrefab->IsLoaded()) { // it's loaded - save in native method pPrefab->Save(file, CPrefab::lsUpdateFilePos); } else { // it's not loaded - save with quick method by copying // bytes directly from the existing file Assert(m_file.is_open()); m_file.seekg(pPrefab->dwFileOffset); DWORD dwToRead = 64000, dwCopied = 0; while(dwToRead == 64000) { if(dwCopied + dwToRead > pPrefab->dwFileSize) dwToRead = pPrefab->dwFileSize - dwCopied; m_file.read(pCopyBuf, dwToRead); file.write(pCopyBuf, dwToRead); dwCopied += dwToRead; } } // set offset info HERE because we might use it above pPrefab->dwFileOffset = ph[iCur].dwOffset; // set size info ph[iCur].dwSize = pPrefab->dwFileSize = file.tellp() - (std::streamoff)ph[iCur].dwOffset; ++iCur; // increase current directory entry } // delete copy buf delete[] pCopyBuf; // rewrite binary header plh.dwDirOffset = m_dwDirOffset = file.tellp(); file.seekp(dwBinaryHeaderOffset); file.write((char*)&plh, sizeof(plh)); file.seekp(0, std::ios::end); // write directory file.write((char*)ph, sizeof(*ph) * plh.dwNumEntries); file.close(); // close temp file // delete original and rename m_file.close(); // might already be open.. might not. remove(m_strOpenFileName); m_strOpenFileName = szFile; rename(strTempFileName, m_strOpenFileName); // reopen original m_file.open(m_strOpenFileName, std::ios::in | std::ios::binary); return 1; } //----------------------------------------------------------------------------- // Purpose: A library's name is based on its filename. We set the name here // and rename the file if it exists, then re-open it. // Input : pszName - // Output : Returns zero on error, nonzero on success. //----------------------------------------------------------------------------- int CPrefabLibraryRMF::SetName(LPCTSTR pszName) { // set szName strcpy(m_szName, pszName); char szNewFilename[MAX_PATH]; CHammer *pApp = (CHammer*) AfxGetApp(); pApp->GetDirectory(DIR_PREFABS, szNewFilename); sprintf(szNewFilename + strlen(szNewFilename), "\\%s.ol", pszName); if(m_file.is_open()) { // close it - m_file.close(); } else { // ensure destination name doesn't exist already - if(GetFileAttributes(szNewFilename) != 0xFFFFFFFF) return 0; // exists. } // rename and reopen rename(m_strOpenFileName, szNewFilename); m_strOpenFileName = szNewFilename; m_file.open(m_strOpenFileName, std::ios::in | std::ios::binary); return 1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPrefabLibraryVMF::CPrefabLibraryVMF() { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPrefabLibraryVMF::~CPrefabLibraryVMF() { } //----------------------------------------------------------------------------- // Purpose: Returns true if this prefab represents the given filename, false if not. // Input : szFilename - Path of a prefab library or folder. //----------------------------------------------------------------------------- bool CPrefabLibraryVMF::IsFile(const char *szFilename) { return(strcmpi(m_szFolderName, szFilename) == 0); } //----------------------------------------------------------------------------- // Purpose: // Input : pszFilename - // Output : int //----------------------------------------------------------------------------- int CPrefabLibraryVMF::Load(LPCTSTR pszFilename) { FreePrefabs(); SetNameFromFilename(pszFilename); strcpy(m_szFolderName, pszFilename); m_eType = LibType_HalfLife2; // dvs: new prefab libs have no notes! who cares? // // Read the prefabs - they are stored as individual VMF files. // char szDir[MAX_PATH]; strcpy(szDir, pszFilename); strcat(szDir, "\\*.vmf"); WIN32_FIND_DATA fd; HANDLE hnd = FindFirstFile(szDir, &fd); if (hnd == INVALID_HANDLE_VALUE) { // No prefabs in this folder. return(1); } *strrchr(szDir, '*') = '\0'; do { if (fd.cFileName[0] != '.') { // // Build the full path to the prefab file. // char szFile[MAX_PATH]; strcpy(szFile, szDir); strcat(szFile, fd.cFileName); CPrefabVMF *pPrefab = new CPrefabVMF; pPrefab->SetFilename(szFile); Add(pPrefab); } } while (FindNextFile(hnd, &fd)); FindClose(hnd); return 1; } //----------------------------------------------------------------------------- // Purpose: Removes this prefab library from disk. // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CPrefabLibraryVMF::DeleteFile(void) { // dvs: can't remove the prefab folder yet return(false); } //----------------------------------------------------------------------------- // Purpose: // Input : pszFilename - // bIndexOnly - // Output : int //----------------------------------------------------------------------------- int CPrefabLibraryVMF::Save(LPCTSTR pszFilename, BOOL bIndexOnly) { return 1; } //----------------------------------------------------------------------------- // Purpose: Set's the library's name by renaming the folder. // Input : pszName - // Output : Returns zero on error, nonzero on success. //----------------------------------------------------------------------------- int CPrefabLibraryVMF::SetName(LPCTSTR pszName) { // dvs: rename the folder - or maybe don't implement for VMF prefabs strcpy(m_szName, pszName); return 1; }