openclonk/src/c4group/C4GroupSet.cpp

386 lines
11 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2010-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
// a set of group files
// manages system file overwriting by scearios or folders
#include "C4Include.h"
#include "c4group/C4GroupSet.h"
#include "c4group/C4Components.h"
#include "c4group/C4Group.h"
#include "lib/C4Log.h"
#include "c4group/C4Language.h"
C4GroupSetNode::C4GroupSetNode(C4GroupSet &rParent, C4GroupSetNode *pPrev, C4Group &rGroup, bool fGrpOwned, int32_t id)
{
// set parent
pParent = &rParent;
// link into list
this->pPrev=pPrev;
if (pPrev) { pNext=pPrev->pNext; pPrev->pNext=this; }
else { pNext=pParent->pFirst; pParent->pFirst=this; }
if (pNext) pNext->pPrev=this; else pParent->pLast=this;
// set group
pGroup = &rGroup;
this->fGrpOwned = fGrpOwned;
// set id
this->id = id;
}
C4GroupSetNode::~C4GroupSetNode()
{
// remove group
if (fGrpOwned) delete pGroup;
// unlink from list
(pPrev ? pPrev->pNext : pParent->pFirst) = pNext;
(pNext ? pNext->pPrev : pParent->pLast ) = pPrev;
}
void C4GroupSet::Clear()
{
// clear nodes
while (pFirst) delete pFirst;
pFirst = NULL;
}
void C4GroupSet::Default()
{
// zero fields
pFirst=pLast=NULL;
// do not reset index here, because group set IDs are meant to be unique
// for each instance of the engine
// see also C4GraphicsResource::RegisterGlobalGraphics
}
C4GroupSet::C4GroupSet()
{
// zero fields
Default();
iIndex=0;
}
C4GroupSet::C4GroupSet(const C4GroupSet &rCopy)
{
// zero fields
Default();
iIndex=0;
// copy from other group set
RegisterGroups(rCopy, C4GSCnt_All);
}
C4GroupSet::~C4GroupSet()
{
// clear nodes
Clear();
}
bool C4GroupSet::RegisterGroup(C4Group &rGroup, bool fOwnGrp, int32_t Priority, int32_t Contents, bool fCheckContent)
{
// get node to sort in
// begin at back end and search for higher priority
C4GroupSetNode *pNode;
for (pNode=pLast; pNode; pNode=pNode->pPrev)
if (pNode->Priority > Priority) break;
// create new node
C4GroupSetNode *pNewNode = new C4GroupSetNode(*this, pNode, rGroup, fOwnGrp, ++iIndex);
// check content
if (fCheckContent) Contents = CheckGroupContents(rGroup, Contents);
// set priority and contents mask
pNewNode->Priority = Priority;
pNewNode->Contents = Contents;
return true;
}
int32_t C4GroupSet::CheckGroupContents(C4Group &rGroup, int32_t Contents)
{
// update mask
if (Contents & C4GSCnt_Graphics) if (!rGroup.FindEntry(C4CFN_Graphics)) Contents=Contents&~C4GSCnt_Graphics;
if (Contents & C4GSCnt_Loaders)
{
if (!rGroup.FindEntry("Loader*.bmp")
&& !rGroup.FindEntry("Loader*.png")
&& !rGroup.FindEntry("Loader*.jpg")
&& !rGroup.FindEntry("Loader*.jpeg"))
{
Contents=Contents&~C4GSCnt_Loaders;
}
}
if (Contents & C4GSCnt_Material) if (!rGroup.FindEntry(C4CFN_Material)) Contents=Contents&~C4GSCnt_Material;
if (Contents & C4GSCnt_Music) if (!rGroup.FindEntry(C4CFN_Music)) Contents=Contents&~C4GSCnt_Music;
if (Contents & C4GSCnt_Definitions) if (!rGroup.FindEntry(C4CFN_DefFiles)) Contents=Contents&~C4GSCnt_Definitions;
if (Contents & C4GSCnt_FontDefs) if (!rGroup.FindEntry(C4CFN_FontFiles)) Contents=Contents&~C4GSCnt_FontDefs;
// return it
return Contents;
}
bool C4GroupSet::RegisterGroups(const C4GroupSet &rCopy, int32_t Contents, const char *szFilename, int32_t iMaxSkipID)
{
// get all groups of rCopy
int32_t Contents2;
for (C4GroupSetNode *pNode=rCopy.pFirst; pNode; pNode=pNode->pNext)
if ((Contents2 = pNode->Contents & Contents))
if (pNode->id > iMaxSkipID)
{
if (!szFilename)
// add group but don't check the content again!
RegisterGroup(*pNode->pGroup, false, pNode->Priority, Contents2, false);
else
{
// if a filename is given, open the child group
C4Group *pGroup = new C4Group();
if (!pGroup->OpenAsChild(pNode->pGroup, szFilename))
{ delete pGroup; continue; }
// add the child group to the local list; contents equal Contents2
// but this flag is not likely to be used
if (!RegisterGroup(*pGroup, true, pNode->Priority, Contents2, false))
delete pGroup;
}
}
// done, success
return true;
}
C4Group *C4GroupSet::FindGroup(int32_t Contents, C4Group *pAfter, bool fSamePrio)
{
// get priority
int32_t iPriority=-1;
// find group by matching content mask
for (C4GroupSetNode *pNode=pFirst; pNode; pNode=pNode->pNext)
{
// check contents
if (!pAfter && (pNode->Contents & Contents))
// check priority
if (iPriority==-1 || iPriority==pNode->Priority)
// success, found an entry
return pNode->pGroup;
// find next clear flag
if (pNode->pGroup == pAfter) { pAfter=NULL; if (fSamePrio) iPriority=pNode->Priority; }
}
// nothing found
return NULL;
}
C4Group *C4GroupSet::FindEntry(const char *szWildcard, int32_t *pPriority, int32_t *pID)
{
// find group that has this entry
for (C4GroupSetNode *pNode=pFirst; pNode; pNode=pNode->pNext)
if (pNode->pGroup->FindEntry(szWildcard))
{
// assign priority and ID, if ptrs is given
if (pPriority) *pPriority=pNode->Priority;
if (pID) *pID=pNode->id;
// return found group
return pNode->pGroup;
}
// nothing found
return NULL;
}
C4Group *C4GroupSet::FindSuitableFile(const char *szName, const char * const extensions[], char *szFileName, int32_t *pID)
{
C4Group *pGrp = 0;
C4Group *pGrp2;
int iPrio = -1;
int32_t iPrio2;
int32_t GroupID;
char FileName[_MAX_FNAME];
SCopy(szName, FileName);
for (int i = 0; extensions[i]; ++i)
{
EnforceExtension(FileName, extensions[i]);
pGrp2=FindEntry(FileName, &iPrio2, &GroupID);
if ((!pGrp || iPrio2 >= iPrio) && pGrp2)
{
if (pID) *pID = GroupID;
pGrp = pGrp2;
SCopy(FileName, szFileName);
}
}
// return found group, if any
return pGrp;
}
bool C4GroupSet::LoadEntry(const char *szEntryName, char **lpbpBuf, size_t *ipSize, int32_t iAppendZeros)
{
// Load the entry from the first group that has it
C4Group *pGroup;
if ((pGroup = FindEntry(szEntryName)))
return pGroup->LoadEntry(szEntryName, lpbpBuf, ipSize, iAppendZeros);
// Didn't find it
return false;
}
bool C4GroupSet::LoadEntryString(const char *szEntryName, StdStrBuf * rBuf)
{
// Load the entry from the first group that has it
C4Group *pGroup;
if ((pGroup = FindEntry(szEntryName)))
return pGroup->LoadEntryString(szEntryName, rBuf);
// Didn't find it
return false;
}
bool C4GroupSet::CloseFolders()
{
// close everything that has folder-priority
for (C4GroupSetNode *pNode=pFirst,*pNext; pNode; pNode=pNext)
{
// get next, as pNode might be destroyed
pNext=pNode->pNext;
// check if priority matches
if (Inside<int32_t>(pNode->Priority, C4GSPrio_Folder, C4GSPrio_Folder2) || pNode->Priority==C4GSPrio_Scenario)
// clear it!
delete pNode;
}
// done, success
return true;
}
int32_t C4GroupSet::GetGroupCount()
{
int32_t iCount = 0;
for (C4GroupSetNode *pNode = pFirst; pNode; pNode = pNode->pNext)
iCount++;
return iCount;
}
C4Group* C4GroupSet::GetGroup(int32_t iIndex)
{
// Invalid index
if (iIndex < 0)
return NULL;
// Find indicated group
for (C4GroupSetNode *pNode = pFirst; pNode; pNode = pNode->pNext)
if (iIndex == 0)
return pNode->pGroup;
else
iIndex--;
// Indicated group not found
return NULL;
}
bool C4GroupSet::UnregisterGroup(int32_t iIndex)
{
// Invalid index
if (iIndex < 0)
return false;
// Find indicated group
for (C4GroupSetNode *pNode = pFirst; pNode; pNode = pNode->pNext)
if (iIndex == 0)
{
// Delete found node
delete pNode;
return true;
}
else
iIndex--;
// Indicated group not found
return false;
}
C4Group *C4GroupSet::RegisterParentFolders(const char *szScenFilename)
{
// the scenario filename may be a scenario or directly a group folder
C4Group *pParentGroup=NULL; bool fParentC4F;
char szParentfolder[_MAX_PATH+1];
if (SEqualNoCase(GetExtension(szScenFilename), "ocf"))
{
fParentC4F = true;
SCopy(szScenFilename, szParentfolder, _MAX_PATH);
}
else
{
GetParentPath(szScenFilename,szParentfolder);
fParentC4F = SEqualNoCase(GetExtension(szParentfolder), "ocf");
}
if (fParentC4F)
{
// replace all (back)slashes with zero-fields
int32_t iOriginalLen=SLen(szParentfolder);
for (int32_t i=0; i<iOriginalLen; ++i) if (szParentfolder[i]==DirectorySeparator || szParentfolder[i]=='/') szParentfolder[i]=0;
// trace back until the file extension is no more .ocf
int32_t iPos=iOriginalLen-1;
while (iPos)
{
// ignore additional zero fields (double-backslashes)
while (iPos && !szParentfolder[iPos]) --iPos;
// break if end has been reached
if (!iPos) break;
// trace back until next zero field
while (iPos && szParentfolder[iPos]) --iPos;
// check extension of this folder
if (!SEqualNoCase(GetExtension(szParentfolder+iPos+1), "ocf", 3)) break;
// continue
}
// trace backwards, putting the (back)slashes in place again
if (iPos)
{
szParentfolder[iPos+1+SLen(szParentfolder+iPos+1)]=szParentfolder[iPos]=DirectorySeparator;
while (iPos--) if (!szParentfolder[iPos]) szParentfolder[iPos]=DirectorySeparator;
++iPos;
}
// trace forward again, opening all folders (iPos is 0 again)
int32_t iGroupIndex=0;
while (iPos<iOriginalLen)
{
// ignore additional zero-fields
while (iPos<iOriginalLen && !szParentfolder[iPos]) ++iPos;
// break if end has been reached
if (iPos>=iOriginalLen) break;
// open this folder
C4Group *pGroup = new C4Group();
if (pParentGroup)
{
if (!pGroup->OpenAsChild(pParentGroup, szParentfolder+iPos))
{
LogFatal(FormatString("%s: %s", LoadResStr("IDS_PRC_FILENOTFOUND"), szParentfolder+iPos).getData());
delete pGroup; return NULL;
}
}
else if (!Reloc.Open(*pGroup, szParentfolder+iPos))
{
LogFatal(FormatString("%s: %s", LoadResStr("IDS_PRC_FILENOTFOUND"), szParentfolder+iPos).getData());
delete pGroup; return NULL;
}
// set this group as new parent
pParentGroup=pGroup;
// add to group set, if this is a true scenario folder
int32_t iContentsMask;
if (WildcardMatch(C4CFN_FolderFiles, pParentGroup->GetName()))
iContentsMask = C4GSCnt_Folder;
else
iContentsMask = C4GSCnt_Directory;
if (!RegisterGroup(*pParentGroup, true, C4GSPrio_Folder+iGroupIndex++, iContentsMask))
{ delete pParentGroup; LogFatal ("RegGrp: internal error"); return NULL; }
// advance by file name length
iPos+=SLen(szParentfolder+iPos);
}
}
return pParentGroup;
}
int32_t C4GroupSet::PreCacheEntries(const char *szEntryMask)
{
// pre-cache all entries matching mask for packed files in all groups
// note this does not catch overloads.
int32_t result = 0;
for (C4GroupSetNode *pNode = pFirst; pNode; pNode = pNode->pNext)
if (pNode->pGroup) result += pNode->pGroup->PreCacheEntries(szEntryMask);
return result;
}