/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 1998-2000, Matthes Bender * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-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. */ /* Handles group files */ #ifndef INC_C4Group #define INC_C4Group #ifdef HAVE_IO_H #include #endif #include "c4group/CStdFile.h" #include "lib/StdBuf.h" // C4Group-Rewind-warning: // The current C4Group-implementation cannot handle random file access very well, // because all files are written within a single zlib-stream. // For every out-of-order-file accessed a group-rewind must be performed, and every // single file up to the accessed file unpacked. As a workaround, all C4Groups are // packed in a file order matching the reading order of the engine. // If the reading order doesn't match the packing order, and a rewind has to be performed, // a warning is issued in Debug-builds of the engine. But since some components require // random access because they are loaded on-demand at runtime (e.g. global sounds), the // warning may be temp disabled for those files using C4GRP_DISABLE_REWINDWARN and // C4GRP_ENABLE_REWINDWARN. A ref counter keeps track of nested calls to those functions. // // If you add any new components to scenario or definition files, remember to adjust the // sort order lists in C4Components.h accordingly, and enforce a reading order for that // component. // // Maybe some day, someone will write a C4Group-implementation that is probably capable of // random access... #ifdef _DEBUG extern int iC4GroupRewindFilePtrNoWarn; #define C4GRP_DISABLE_REWINDWARN ++iC4GroupRewindFilePtrNoWarn; #define C4GRP_ENABLE_REWINDWARN --iC4GroupRewindFilePtrNoWarn; #else #define C4GRP_DISABLE_REWINDWARN ; #define C4GRP_ENABLE_REWINDWARN ; #endif const int C4GroupFileVer1=1, C4GroupFileVer2=2; const int C4GroupMaxError = 100; const int32_t C4GroupSwapThreshold = 10 * 1024 * 1024; #define C4GroupFileID "RedWolf Design GrpFolder" bool C4Group_TestIgnore(const char *szFilename); void C4Group_SetTempPath(const char *szPath); const char* C4Group_GetTempPath(); void C4Group_SetSortList(const char **ppSortList); void C4Group_SetProcessCallback(bool (*fnCallback)(const char *, int)); bool C4Group_IsGroup(const char *szFilename); bool C4Group_CopyItem(const char *szSource, const char *szTarget, bool fNoSort=false, bool fResetAttributes=false); bool C4Group_MoveItem(const char *szSource, const char *szTarget, bool fNoSort=false); bool C4Group_DeleteItem(const char *szItem, bool fRecycle=false); bool C4Group_PackDirectoryTo(const char *szFilename, const char *szFilenameTo); bool C4Group_PackDirectory(const char *szFilename); bool C4Group_UnpackDirectory(const char *szFilename); bool C4Group_ExplodeDirectory(const char *szFilename); bool C4Group_ReadFile(const char *szFilename, char **pData, size_t *iSize); extern const char *C4CFN_FLS[]; #pragma pack (push, 1) struct C4GroupHeader { char id[24+4] = C4GroupFileID; int Ver1 = C4GroupFileVer1; int Ver2 = C4GroupFileVer2; int Entries = 0; char reserved[164] = { 0 }; }; struct C4GroupEntryCore { char FileName[260] = { 0 }; int32_t Packed = 0, ChildGroup = 0; int32_t Size = 0, reserved1 = 0, Offset = 0; int32_t reserved2 = 0; char reserved3 = '\0'; unsigned int reserved4 = 0; char Executable = '\0'; BYTE fbuf[26] = { 0 }; }; #pragma pack (pop) class C4GroupEntry: public C4GroupEntryCore { public: ~C4GroupEntry(); enum EntryStatus { C4GRES_InGroup, C4GRES_OnDisk, C4GRES_InMemory, C4GRES_Deleted }; public: char DiskPath[_MAX_PATH + 1] = { 0 }; EntryStatus Status = C4GRES_InGroup; bool DeleteOnDisk = false; bool HoldBuffer = false; bool BufferIsStdbuf = false; bool NoSort = false; BYTE *bpMemBuf = 0; C4GroupEntry *Next = 0; public: void Set(const DirectoryIterator & iter, const char * szPath); }; class C4Group : public CStdStream { public: C4Group(); ~C4Group(); protected: enum Status { GRPF_Inactive, GRPF_File, GRPF_Folder }; Status Status; char FileName[_MAX_PATH+1]; // Parent status C4Group *Mother; bool ExclusiveChild; // File & Folder C4GroupEntry *SearchPtr; CStdFile StdFile; size_t iCurrFileSize; // size of last accessed file // File only int FilePtr; int MotherOffset; int EntryOffset; bool Modified; C4GroupHeader Head; C4GroupEntry *FirstEntry; BYTE *pInMemEntry; size_t iInMemEntrySize; // for reading from entries prefetched into memory #ifdef _DEBUG StdStrBuf sPrevAccessedEntry; #endif // Folder only DirectoryIterator FolderSearch; C4GroupEntry FolderSearchEntry; C4GroupEntry LastFolderSearchEntry; bool StdOutput; bool (*fnProcessCallback)(const char *, int); char ErrorString[C4GroupMaxError+1]; bool NoSort; // If this flag is set, all entries will be marked NoSort in AddEntry public: bool Open(const char *szGroupName, bool fCreate=false); bool Close(); bool Save(bool fReOpen); bool OpenAsChild(C4Group *pMother, const char *szEntryName, bool fExclusive=false, bool fCreate=false); bool OpenChild(const char* strEntry); bool OpenMother(); bool Add(const char *szFile, const char *szAddAs); bool Add(const char *szName, void *pBuffer, int iSize, bool fChild = false, bool fHoldBuffer = false, bool fExecutable = false); bool Add(const char *szName, StdBuf &pBuffer, bool fChild = false, bool fHoldBuffer = false, bool fExecutable = false); bool Add(const char *szName, StdStrBuf &pBuffer, bool fChild = false, bool fHoldBuffer = false, bool fExecutable = false); bool Merge(const char *szFolders); bool Move(const char *szFile, const char *szAddAs); bool Extract(const char *szFiles, const char *szExtractTo=NULL, const char *szExclude=NULL); bool ExtractEntry(const char *szFilename, const char *szExtractTo=NULL); bool Delete(const char *szFiles, bool fRecursive = false); bool DeleteEntry(const char *szFilename, bool fRecycle=false); bool Rename(const char *szFile, const char *szNewName); bool Sort(const char *szSortList); bool SortByList(const char **ppSortList, const char *szFilename=NULL); bool AccessEntry(const char *szWildCard, size_t *iSize=NULL, char *sFileName=NULL, bool NeedsToBeAGroup = false); bool AccessNextEntry(const char *szWildCard, size_t *iSize=NULL, char *sFileName=NULL, bool fStartAtFilename=false); bool LoadEntry(const char *szEntryName, char **lpbpBuf, size_t *ipSize=NULL, int iAppendZeros=0); bool LoadEntry(const char *szEntryName, StdBuf * Buf); bool LoadEntry(const StdStrBuf & name, StdBuf * Buf) { return LoadEntry(name.getData(), Buf); } bool LoadEntryString(const char *szEntryName, StdStrBuf * Buf); bool LoadEntryString(const StdStrBuf & name, StdStrBuf * Buf) { return LoadEntryString(name.getData(), Buf); } bool FindEntry(const char *szWildCard, StdStrBuf *sFileName=NULL, size_t *iSize=NULL); bool FindEntry(const char *szWildCard, char *sFileName) { StdStrBuf name; bool r = FindEntry(szWildCard, &name); if(sFileName) SCopy(name.getData(),sFileName); return r; } bool FindNextEntry(const char *szWildCard, StdStrBuf *sFileName=NULL, size_t *iSize=NULL, bool fStartAtFilename=false); bool FindNextEntry(const char *szWildCard, char *sFileName, size_t *iSize=NULL, bool fStartAtFilename=false) { StdStrBuf name(fStartAtFilename ? sFileName : ""); bool r = FindNextEntry(szWildCard, &name, iSize, fStartAtFilename); if (r && sFileName) SCopy(name.getData(),sFileName); return r; } bool Read(void *pBuffer, size_t iSize); bool Advance(int iOffset); void SetStdOutput(bool fStatus); void ResetSearch(bool reload_contents=false); // reset search pointer so calls to FindNextEntry find first entry again. if reload_contents is set, the file list for directories is also refreshed. const char *GetError(); const char *GetName(); StdStrBuf GetFullName() const; int EntryCount(const char *szWildCard=NULL); size_t EntrySize(const char *szWildCard=NULL); size_t AccessedEntrySize() { return iCurrFileSize; } // retrieve size of last accessed entry unsigned int EntryCRC32(const char *szWildCard=NULL); inline bool IsOpen() { return Status != GRPF_Inactive; } C4Group *GetMother(); inline bool IsPacked() { return Status == GRPF_File; } inline bool HasPackedMother() { if (!Mother) return false; return Mother->IsPacked(); } inline bool SetNoSort(bool fNoSort) { NoSort = fNoSort; return true; } int PreCacheEntries(const char *szSearchPattern, bool cache_previous=false); // pre-load entries to memory. return number of loaded entries. const C4GroupHeader &GetHeader() const { return Head; } const C4GroupEntry *GetFirstEntry() const { return FirstEntry; } protected: void Init(); void Default(); void Clear(); void ProcessOut(const char *szMessage, int iProcess=0); bool EnsureChildFilePtr(C4Group *pChild); bool CloseExclusiveMother(); bool Error(const char *szStatus); bool OpenReal(const char *szGroupName); bool OpenRealGrpFile(); bool SetFilePtr(int iOffset); bool RewindFilePtr(); bool AdvanceFilePtr(int iOffset, C4Group *pByChild=NULL); bool AddEntry(C4GroupEntry::EntryStatus status, bool childgroup, const char *fname, long size, const char *entryname = NULL, BYTE *membuf = NULL, bool fDeleteOnDisk = false, bool fHoldBuffer = false, bool fExecutable = false, bool fBufferIsStdbuf = false); bool AddEntryOnDisk(const char *szFilename, const char *szAddAs=NULL, bool fMove=false); bool SetFilePtr2Entry(const char *szName, bool NeedsToBeAGroup = false); bool AppendEntry2StdFile(C4GroupEntry *centry, CStdFile &stdfile); C4GroupEntry *GetEntry(const char *szName); C4GroupEntry *SearchNextEntry(const char *szName); C4GroupEntry *GetNextFolderEntry(); uint32_t CalcCRC32(C4GroupEntry *pEntry); void PreCacheEntry(C4GroupEntry * p); }; #endif