OpenClonk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
C4Group.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 
18 /* Handles group files */
19 
20 /* Needs to be compilable as Objective C++ on OS X */
21 
22 #include "C4Include.h"
23 #include "c4group/C4Group.h"
24 
25 #include "c4group/C4Components.h"
26 #include "lib/C4InputValidation.h"
27 #include <zlib.h>
28 
29 
30 //------------------------------ File Sort Lists -------------------------------------------
31 
32 const char *C4CFN_FLS[] =
33 {
45  nullptr, nullptr
46 };
47 
48 #ifdef _DEBUG
49 const char *szCurrAccessedEntry = nullptr;
50 int iC4GroupRewindFilePtrNoWarn=0;
51 #endif
52 
53 #ifdef _DEBUG
54 //#define C4GROUP_DUMP_ACCESS
55 #endif
56 
57 //---------------------------- Global C4Group_Functions -------------------------------------------
58 
60 char C4Group_Ignore[_MAX_PATH+1]="cvs;CVS;Thumbs.db;.orig;.svn";
61 const char **C4Group_SortList = nullptr;
62 bool (*C4Group_ProcessCallback)(const char *, int)=nullptr;
63 
64 void C4Group_SetProcessCallback(bool (*fnCallback)(const char *, int))
65 {
66  C4Group_ProcessCallback = fnCallback;
67 }
68 
69 void C4Group_SetSortList(const char **ppSortList)
70 {
71  C4Group_SortList = ppSortList;
72 }
73 
74 void C4Group_SetTempPath(const char *szPath)
75 {
76  if (!szPath || !szPath[0]) C4Group_TempPath[0]=0;
78 }
79 
80 const char *C4Group_GetTempPath()
81 {
82  return C4Group_TempPath;
83 }
84 
85 bool C4Group_TestIgnore(const char *szFilename)
86 {
87  if(!*szFilename) return true; //poke out empty strings
88  const char* name = GetFilename(szFilename);
89  return *name == '.' //no hidden files and the directory itself
90  || name[strlen(name) - 1] == '~' //no temp files
91  || SIsModule(C4Group_Ignore,name); //not on Blacklist
92 }
93 
94 bool C4Group_IsGroup(const char *szFilename)
95 {
96  C4Group hGroup; if (hGroup.Open(szFilename)) { hGroup.Close(); return true; }
97  return false;
98 }
99 
100 bool C4Group_CopyItem(const char *szSource, const char *szTarget1, bool fNoSort, bool fResetAttributes)
101 {
102  // Parameter check
103  if (!szSource || !szTarget1 || !szSource[0] || !szTarget1[0]) return false;
104  char szTarget[_MAX_PATH+1]; SCopy(szTarget1,szTarget,_MAX_PATH);
105 
106  // Backslash terminator indicates target is a path only (append filename)
107  if (szTarget[SLen(szTarget)-1]==DirectorySeparator) SAppend(GetFilename(szSource),szTarget);
108 
109  // Check for identical source and target
110  // Note that attributes aren't reset here
111  if (ItemIdentical(szSource,szTarget)) return true;
112 
113  // Source and target are simple items
114  if (ItemExists(szSource) && CreateItem(szTarget)) return CopyItem(szSource,szTarget, fResetAttributes);
115 
116  // For items within groups, attribute resetting isn't needed, because packing/unpacking will kill all
117  // attributes anyway
118 
119  // Source & target
120  C4Group hSourceParent, hTargetParent;
121  char szSourceParentPath[_MAX_PATH+1],szTargetParentPath[_MAX_PATH+1];
122  GetParentPath(szSource,szSourceParentPath); GetParentPath(szTarget,szTargetParentPath);
123 
124  // Temp filename
125  char szTempFilename[_MAX_PATH+1];
126  SCopy(C4Group_TempPath,szTempFilename,_MAX_PATH);
127  SAppend(GetFilename(szSource),szTempFilename);
128  MakeTempFilename(szTempFilename);
129 
130  // Extract source to temp file
131  if ( !hSourceParent.Open(szSourceParentPath)
132  || !hSourceParent.Extract(GetFilename(szSource),szTempFilename)
133  || !hSourceParent.Close() ) return false;
134 
135  // Move temp file to target
136  if ( !hTargetParent.Open(szTargetParentPath)
137  || !hTargetParent.SetNoSort(fNoSort)
138  || !hTargetParent.Move(szTempFilename, GetFilename(szTarget))
139  || !hTargetParent.Close() ) { EraseItem(szTempFilename); return false; }
140 
141  return true;
142 }
143 
144 bool C4Group_MoveItem(const char *szSource, const char *szTarget1, bool fNoSort)
145 {
146  // Parameter check
147  if (!szSource || !szTarget1 || !szSource[0] || !szTarget1[0]) return false;
148  char szTarget[_MAX_PATH+1]; SCopy(szTarget1,szTarget,_MAX_PATH);
149 
150  // Backslash terminator indicates target is a path only (append filename)
151  if (szTarget[SLen(szTarget)-1]==DirectorySeparator) SAppend(GetFilename(szSource),szTarget);
152 
153  // Check for identical source and target
154  if (ItemIdentical(szSource,szTarget)) return true;
155 
156  // Source and target are simple items
157  if (ItemExists(szSource) && CreateItem(szTarget))
158  {
159  // erase test file, because it may block moving a directory
160  EraseItem(szTarget);
161  return MoveItem(szSource,szTarget);
162  }
163 
164  // Source & target
165  C4Group hSourceParent, hTargetParent;
166  char szSourceParentPath[_MAX_PATH+1],szTargetParentPath[_MAX_PATH+1];
167  GetParentPath(szSource,szSourceParentPath); GetParentPath(szTarget,szTargetParentPath);
168 
169  // Temp filename
170  char szTempFilename[_MAX_PATH+1];
171  SCopy(C4Group_TempPath,szTempFilename,_MAX_PATH);
172  SAppend(GetFilename(szSource),szTempFilename);
173  MakeTempFilename(szTempFilename);
174 
175  // Extract source to temp file
176  if ( !hSourceParent.Open(szSourceParentPath)
177  || !hSourceParent.Extract(GetFilename(szSource),szTempFilename)
178  || !hSourceParent.Close() ) return false;
179 
180  // Move temp file to target
181  if ( !hTargetParent.Open(szTargetParentPath)
182  || !hTargetParent.SetNoSort(fNoSort)
183  || !hTargetParent.Move(szTempFilename, GetFilename(szTarget))
184  || !hTargetParent.Close() ) { EraseItem(szTempFilename); return false; }
185 
186  // Delete original file
187  if ( !hSourceParent.Open(szSourceParentPath)
188  || !hSourceParent.DeleteEntry(GetFilename(szSource))
189  || !hSourceParent.Close() ) return false;
190 
191  return true;
192 }
193 
194 bool C4Group_DeleteItem(const char *szItem, bool fRecycle)
195 {
196  // Parameter check
197  if (!szItem || !szItem[0]) return false;
198 
199  // simple item?
200  if (ItemExists(szItem))
201  {
202  if (fRecycle)
203  return EraseItemSafe(szItem);
204  else
205  return EraseItem(szItem);
206  }
207 
208  // delete from parent
209  C4Group hParent;
210  char szParentPath[_MAX_PATH+1];
211  GetParentPath(szItem,szParentPath);
212 
213  // Delete original file
214  if ( !hParent.Open(szParentPath)
215  || !hParent.DeleteEntry(GetFilename(szItem), fRecycle)
216  || !hParent.Close() ) return false;
217 
218  return true;
219 }
220 
221 bool C4Group_PackDirectoryTo(const char *szFilename, const char *szFilenameTo)
222 {
223  // Check file type
224  if (!DirectoryExists(szFilename)) return false;
225  // Target mustn't exist
226  if (FileExists(szFilenameTo)) return false;
227  // Ignore
228  if (C4Group_TestIgnore(szFilename))
229  return true;
230  // Process message
232  C4Group_ProcessCallback(szFilename,0);
233  // Create group file
234  C4Group hGroup;
235  if (!hGroup.Open(szFilenameTo,true))
236  return false;
237  // Add folder contents to group
238  DirectoryIterator i(szFilename);
239  for (; *i; i++)
240  {
241  // Ignore
242  if (C4Group_TestIgnore(*i))
243  continue;
244  // Must pack?
245  if (DirectoryExists(*i))
246  {
247  // Find temporary filename
248  char szTempFilename[_MAX_PATH+1];
249  // At C4Group temp path
250  SCopy(C4Group_TempPath, szTempFilename, _MAX_PATH);
251  SAppend(GetFilename(*i), szTempFilename, _MAX_PATH);
252  // Make temporary filename
253  MakeTempFilename(szTempFilename);
254  // Pack and move into group
255  if ( !C4Group_PackDirectoryTo(*i, szTempFilename)) break;
256  if (!hGroup.Move(szTempFilename, GetFilename(*i)))
257  {
258  EraseFile(szTempFilename);
259  break;
260  }
261  }
262  // Add normally otherwise
263  else if (!hGroup.Add(*i, nullptr))
264  break;
265  }
266  // Something went wrong?
267  if (*i)
268  {
269  // Close group and remove temporary file
270  hGroup.Close();
271  EraseItem(szFilenameTo);
272  return false;
273  }
274  // Reset iterator
275  i.Reset();
276  // Close group
277  hGroup.SortByList(C4Group_SortList,szFilename);
278  if (!hGroup.Close())
279  return false;
280  // Done
281  return true;
282 }
283 
284 bool C4Group_PackDirectory(const char *szFilename)
285 {
286  // Make temporary filename
287  char szTempFilename[_MAX_PATH+1];
288  SCopy(szFilename, szTempFilename, _MAX_PATH);
289  MakeTempFilename(szTempFilename);
290  // Pack directory
291  if (!C4Group_PackDirectoryTo(szFilename, szTempFilename))
292  return false;
293  // Rename folder
294  char szTempFilename2[_MAX_PATH+1];
295  SCopy(szFilename, szTempFilename2, _MAX_PATH);
296  MakeTempFilename(szTempFilename2);
297  if (!RenameFile(szFilename, szTempFilename2))
298  return false;
299  // Name group file
300  if (!RenameFile(szTempFilename,szFilename))
301  return false;
302  // Last: Delete folder
303  return EraseDirectory(szTempFilename2);
304 }
305 
306 bool C4Group_UnpackDirectory(const char *szFilename)
307 {
308  // Already unpacked: success
309  if (DirectoryExists(szFilename)) return true;
310 
311  // Not a real file: unpack parent directory first
312  char szParentFilename[_MAX_PATH+1];
313  if (!FileExists(szFilename))
314  if (GetParentPath(szFilename,szParentFilename))
315  if (!C4Group_UnpackDirectory(szParentFilename))
316  return false;
317 
318  // Open group
319  C4Group hGroup;
320  if (!hGroup.Open(szFilename)) return false;
321 
322  // Process message
324  C4Group_ProcessCallback(szFilename,0);
325 
326  // Create target directory
327  char szFoldername[_MAX_PATH+1];
328  SCopy(szFilename,szFoldername,_MAX_PATH);
329  MakeTempFilename(szFoldername);
330  if (!CreatePath(szFoldername)) { hGroup.Close(); return false; }
331 
332  // Extract files to folder
333  if (!hGroup.Extract("*",szFoldername)) { hGroup.Close(); return false; }
334 
335  // Close group
336  hGroup.Close();
337 
338  // Rename group file
339  char szTempFilename[_MAX_PATH+1];
340  SCopy(szFilename,szTempFilename,_MAX_PATH);
341  MakeTempFilename(szTempFilename);
342  if (!RenameFile(szFilename, szTempFilename)) return false;
343 
344  // Rename target directory
345  if (!RenameFile(szFoldername,szFilename)) return false;
346 
347  // Delete renamed group file
348  return EraseItem(szTempFilename);
349 }
350 
351 bool C4Group_ExplodeDirectory(const char *szFilename)
352 {
353  // Ignore
354  if (C4Group_TestIgnore(szFilename)) return true;
355 
356  // Unpack this directory
357  if (!C4Group_UnpackDirectory(szFilename)) return false;
358 
359  // Explode all children
361 
362  // Success
363  return true;
364 }
365 
366 bool C4Group_ReadFile(const char *szFile, char **pData, size_t *iSize)
367 {
368  // security
369  if (!szFile || !pData) return false;
370  // get mother path & file name
371  char szPath[_MAX_PATH + 1];
372  GetParentPath(szFile, szPath);
373  const char *pFileName = GetFilename(szFile);
374  // open mother group
375  C4Group MotherGroup;
376  if (!MotherGroup.Open(szPath)) return false;
377  // access the file
378  size_t iFileSize;
379  if (!MotherGroup.AccessEntry(pFileName, &iFileSize)) return false;
380  // create buffer
381  *pData = new char [iFileSize];
382  // read it
383  if (!MotherGroup.Read(*pData, iFileSize)) { delete [] *pData; *pData = nullptr; return false; }
384  // ok
385  MotherGroup.Close();
386  if (iSize) *iSize = iFileSize;
387  return true;
388 }
389 
390 void MemScramble(BYTE *bypBuffer, int iSize)
391 {
392  int cnt; BYTE temp;
393  // XOR deface
394  for (cnt=0; cnt<iSize; cnt++)
395  bypBuffer[cnt] ^= 237;
396  // BYTE swap
397  for (cnt=0; cnt+2<iSize; cnt+=3)
398  {
399  temp = bypBuffer[cnt];
400  bypBuffer[cnt] = bypBuffer[cnt+2];
401  bypBuffer[cnt+2] = temp;
402  }
403 }
404 
405 //---------------------------------- C4Group ---------------------------------------------
406 
408 {
410  {
411  // No source; C4Group inactive
413  // C4Group backed by archive file
415  // C4Group backed by raw file system
417  };
418 
420  std::string FileName;
421  // Parent status
422  C4Group *Mother = nullptr;
423  bool ExclusiveChild = false;
424  // File & Folder
427  size_t iCurrFileSize = 0; // size of last accessed file
428  // File only
429  int FilePtr = 0;
430  int MotherOffset = 0;
431  int EntryOffset = 0;
432  bool Modified = false;
434  BYTE *pInMemEntry = nullptr; size_t iInMemEntrySize = 0; // for reading from entries prefetched into memory
435 #ifdef _DEBUG
436  StdStrBuf sPrevAccessedEntry;
437 #endif
438  // Folder only
442 
443  bool StdOutput = false;
444  bool(*fnProcessCallback)(const char *, int) = nullptr;
445  std::string ErrorString;
446 
447  bool NoSort = false; // If this flag is set, all entries will be marked NoSort in AddEntry
448 };
449 
451 {
452  if (HoldBuffer)
453  if (bpMemBuf)
454  {
455  if (BufferIsStdbuf)
457  else
458  delete [] bpMemBuf;
459  }
460 }
461 
462 void C4GroupEntry::Set(const DirectoryIterator &iter, const char * path)
463 {
464  InplaceReconstruct(this);
465 
467  SCopy(*iter, DiskPath, _MAX_PATH-1);
468  Size = iter.GetFileSize();
470  Packed=false;
471  ChildGroup=false;
472  // Notice folder entries are not checked for ChildGroup status.
473  // This would cause extreme performance loss and be good for
474  // use in entry list display only.
475 }
476 
478  : p(new P)
479 {}
480 
481 void C4Group::Init()
482 {
483  auto new_p = std::make_unique<P>();
484  // Copy persistent variables
485  new_p->fnProcessCallback = p->fnProcessCallback;
486  new_p->NoSort = p->NoSort;
487  new_p->StdOutput = p->StdOutput;
488 
490  p = std::move(new_p);
491 }
492 
494 {
495  Clear();
496 }
497 
498 bool C4Group::Error(const char *szStatus)
499 {
500  p->ErrorString = szStatus;
501  return false;
502 }
503 
504 const char *C4Group::GetError()
505 {
506  return p->ErrorString.c_str();
507 }
508 
509 void C4Group::SetStdOutput(bool fStatus)
510 {
511  p->StdOutput=fStatus;
512 }
513 
514 bool C4Group::Open(const char *szGroupName, bool fCreate)
515 {
516  if (!szGroupName) return Error("Open: Null filename");
517  if (!szGroupName[0]) return Error("Open: Empty filename");
518 
519  char szGroupNameN[_MAX_FNAME];
520  SCopy(szGroupName,szGroupNameN,_MAX_FNAME);
521  // Convert to native path
522  SReplaceChar(szGroupNameN, '\\', DirectorySeparator);
523 
524  // Real reference
525  if (FileExists(szGroupNameN))
526  {
527  // Init
528  Init();
529  // Open group or folder
530  return OpenReal(szGroupNameN);
531  }
532 
533  // If requested, try creating a new group file
534  if (fCreate)
535  {
536  CStdFile temp;
537  if (temp.Create(szGroupNameN,false))
538  {
539  // Temporary file has been created
540  temp.Close();
541  // Init
542  Init();
543  p->SourceType=P::ST_Packed; p->Modified=true;
544  p->FileName = szGroupNameN;
545  return true;
546  }
547  }
548 
549  // While not a real reference (child group), trace back to mother group or folder.
550  // Open mother and child in exclusive mode.
551  char szRealGroup[_MAX_FNAME];
552  SCopy(szGroupNameN,szRealGroup,_MAX_FNAME);
553  do
554  { if (!TruncatePath(szRealGroup)) return Error(FormatString("Open(\"%s\"): File not found", szGroupNameN).getData()); }
555  while (!FileExists(szRealGroup));
556 
557  // Open mother and child in exclusive mode
558  C4Group *pMother = new C4Group;
559  pMother->SetStdOutput(p->StdOutput);
560  if (!pMother->Open(szRealGroup))
561  { Clear(); Error(pMother->GetError()); delete pMother; return false; }
562  if (!OpenAsChild(pMother,szGroupNameN+SLen(szRealGroup)+1,true))
563  { Clear(); return false; }
564 
565  // Success
566  return true;
567 
568 }
569 
570 bool C4Group::OpenReal(const char *szFilename)
571 {
572  // Get original filename
573  if (!szFilename) return false;
574  p->FileName = szFilename;
575 
576  // Folder
577  if (DirectoryExists(GetName()))
578  {
579  // Ignore
580  if (C4Group_TestIgnore(szFilename))
581  return Error(FormatString("OpenReal: filename '%s' ignored", szFilename).getData());
582  // OpenReal: Simply set status and return
583  p->SourceType=P::ST_Unpacked;
584  ResetSearch();
585  // Success
586  return true;
587  }
588 
589  // File: Try reading header and entries
590  if (OpenRealGrpFile())
591  {
592  p->SourceType=P::ST_Packed;
593  ResetSearch();
594  return true;
595  }
596  else
597  return false;
598 
599  return Error("OpenReal: Not a valid group");
600 }
601 
602 bool C4Group::OpenRealGrpFile()
603 {
604  int cnt,file_entries;
605  C4GroupEntryCore corebuf;
606 
607  // Open StdFile
608  if (!p->StdFile.Open(GetName(),true)) return Error("OpenRealGrpFile: Cannot open standard file");
609 
610  // Read header
611  if (!p->StdFile.Read((BYTE*)&Head,sizeof(C4GroupHeader))) return Error("OpenRealGrpFile: Error reading header");
612  MemScramble((BYTE*)&Head,sizeof(C4GroupHeader));
613  p->EntryOffset+=sizeof(C4GroupHeader);
614 
615  // Check Header
618  return Error("OpenRealGrpFile: Invalid header");
619 
620  // Read Entries
621  file_entries=Head.Entries;
622  Head.Entries=0; // Reset, will be recounted by AddEntry
623  for (cnt=0; cnt<file_entries; cnt++)
624  {
625  if (!p->StdFile.Read((BYTE*)&corebuf,sizeof(C4GroupEntryCore))) return Error("OpenRealGrpFile: Error reading entries");
626  // New C4Groups have filenames in UTF-8
627  StdStrBuf entryname(corebuf.FileName);
628  entryname.EnsureUnicode();
629  // Prevent overwriting of user stuff by malicuous groups
630  C4InVal::ValidateFilename(const_cast<char *>(entryname.getData()),entryname.getLength());
631  p->EntryOffset+=sizeof(C4GroupEntryCore);
632  if (!AddEntry(C4GroupEntry::C4GRES_InGroup,!!corebuf.ChildGroup,
633  corebuf.FileName,corebuf.Size,
634  entryname.getData(),
635  nullptr, false, false,
636  !!corebuf.Executable))
637  return Error("OpenRealGrpFile: Cannot add entry");
638  }
639 
640  return true;
641 }
642 
643 bool C4Group::AddEntry(C4GroupEntry::EntryStatus status,
644  bool childgroup,
645  const char *fname,
646  long size,
647  const char *entryname,
648  BYTE *membuf,
649  bool fDeleteOnDisk,
650  bool fHoldBuffer,
651  bool fExecutable,
652  bool fBufferIsStdbuf)
653 {
654 
655  // Folder: add file to folder immediately
656  if (p->SourceType==P::ST_Unpacked)
657  {
658 
659  // Close open StdFile
660  p->StdFile.Close();
661 
662  // Get path to target folder file
663  char tfname[_MAX_FNAME];
664  SCopy(GetName(),tfname,_MAX_FNAME);
665  AppendBackslash(tfname);
666  if (entryname) SAppend(entryname,tfname);
667  else SAppend(GetFilename(fname),tfname);
668 
669  switch (status)
670  {
671 
672  case C4GroupEntry::C4GRES_OnDisk: // Copy/move file to folder
673  if (!CopyItem(fname, tfname))
674  return false;
675  // Reset directory iterator to reflect new file
676  ResetSearch(true);
677  if (fDeleteOnDisk && !EraseItem(fname))
678  return false;
679  return true;
680 
681  case C4GroupEntry::C4GRES_InMemory: { // Save buffer to file in folder
682  CStdFile hFile;
683  bool fOkay = false;
684  if (hFile.Create(tfname, !!childgroup))
685  fOkay = !!hFile.Write(membuf,size);
686  hFile.Close();
687  ResetSearch(true);
688 
689  if (fHoldBuffer) { if (fBufferIsStdbuf) StdBuf::DeletePointer(membuf); else delete [] membuf; }
690 
691  return fOkay;
692  }
693 
694  default: break; // InGrp & Deleted ignored
695  }
696 
697  return Error("Add to folder: Invalid request");
698  }
699 
700 
701  // Group file: add to virtual entry list
702 
703  C4GroupEntry *nentry,*lentry,*centry;
704 
705  // Delete existing entries of same name
706  centry=GetEntry(GetFilename(entryname ? entryname : fname));
707  if (centry) { centry->Status = C4GroupEntry::C4GRES_Deleted; Head.Entries--; }
708 
709  // Allocate memory for new entry
710  nentry = new C4GroupEntry;
711 
712  // Find end of list
713  for (lentry=p->FirstEntry; lentry && lentry->Next; lentry=lentry->Next) {}
714 
715  // Init entry core data
716  if (entryname) SCopy(entryname,nentry->FileName,_MAX_FNAME);
717  else SCopy(GetFilename(fname),nentry->FileName,_MAX_FNAME);
718  nentry->Size=size;
719  nentry->ChildGroup=childgroup;
720  nentry->Offset=0;
721  nentry->Executable=fExecutable;
722  nentry->DeleteOnDisk=fDeleteOnDisk;
723  nentry->HoldBuffer=fHoldBuffer;
724  nentry->BufferIsStdbuf=fBufferIsStdbuf;
725  if (lentry) nentry->Offset=lentry->Offset+lentry->Size;
726 
727  // Init list entry data
728  SCopy(fname,nentry->DiskPath,_MAX_FNAME);
729  nentry->Status=status;
730  nentry->bpMemBuf=membuf;
731  nentry->Next=nullptr;
732  nentry->NoSort = p->NoSort;
733 
734  // Append entry to list
735  if (lentry) lentry->Next=nentry;
736  else p->FirstEntry=nentry;
737 
738  // Increase virtual file count of group
739  Head.Entries++;
740 
741  return true;
742 }
743 
744 C4GroupEntry* C4Group::GetEntry(const char *szName)
745 {
746  if (p->SourceType==P::ST_Unpacked) return nullptr;
747  C4GroupEntry *centry;
748  for (centry=p->FirstEntry; centry; centry=centry->Next)
749  if (centry->Status != C4GroupEntry::C4GRES_Deleted)
750  if (WildcardMatch(szName,centry->FileName))
751  return centry;
752  return nullptr;
753 }
754 
756 {
757  C4GroupEntry *centry;
758  bool fRewrite=false;
759 
760  if (p->SourceType==P::ST_None) return false;
761 
762  // Folder: just close
763  if (p->SourceType==P::ST_Unpacked)
764  { CloseExclusiveMother(); Clear(); return true; }
765 
766  // Rewrite check
767  for (centry=p->FirstEntry; centry; centry=centry->Next)
768  if (centry->Status != C4GroupEntry::C4GRES_InGroup)
769  fRewrite=true;
770  if (p->Modified) fRewrite=true;
771 
772  // No rewrite: just close
773  if (!fRewrite)
774  { CloseExclusiveMother(); Clear(); return true; }
775 
776  if (p->StdOutput) printf("Writing group file...\n");
777 
778  // Set new version
781 
782  // Automatic sort
784 
785  // Save group contents to disk
786  bool fSuccess = Save(false);
787 
788  // Close exclusive mother
789  CloseExclusiveMother();
790 
791  // Close file
792  Clear();
793 
794  return !!fSuccess;
795 }
796 
797 bool C4Group::Save(bool fReOpen)
798 {
799 
800  int cscore;
801  C4GroupEntryCore *save_core;
802  C4GroupEntry *centry;
803  char szTempFileName[_MAX_FNAME+1],szGrpFileName[_MAX_FNAME+1];
804 
805  // Create temporary core list with new actual offsets to be saved
806  int32_t iContentsSize = 0;
807  save_core = new C4GroupEntryCore [Head.Entries];
808  cscore=0;
809  for (centry=p->FirstEntry; centry; centry=centry->Next)
810  if (centry->Status != C4GroupEntry::C4GRES_Deleted)
811  {
812  save_core[cscore]=(C4GroupEntryCore)*centry;
813  // Make actual offset
814  save_core[cscore].Offset = iContentsSize;
815  iContentsSize += centry->Size;
816  cscore++;
817  }
818 
819  // Hold contents in memory?
820  bool fToMemory = !fReOpen && p->Mother && iContentsSize < C4GroupSwapThreshold;
821  if (!fToMemory)
822  {
823  // Create target temp file (in temp directory!)
824  SCopy(GetName(),szGrpFileName,_MAX_FNAME);
825  if (C4Group_TempPath[0]) { SCopy(C4Group_TempPath,szTempFileName,_MAX_FNAME); SAppend(GetFilename(GetName()),szTempFileName,_MAX_FNAME); }
826  else SCopy(GetName(),szTempFileName,_MAX_FNAME);
827  MakeTempFilename(szTempFileName);
828  // (Temp file must not have the same name as the group.)
829  if (SEqual(szTempFileName,szGrpFileName))
830  {
831  SAppend(".tmp",szTempFileName); // Add a second temp extension
832  MakeTempFilename(szTempFileName);
833  }
834  }
835 
836  // Create the new (temp) group file
837  CStdFile tfile;
838  if (!tfile.Create(szTempFileName,true,false,fToMemory))
839  { delete [] save_core; return Error("Close: ..."); }
840 
841  // Save header and core list
842  C4GroupHeader headbuf = Head;
843  MemScramble((BYTE*)&headbuf,sizeof(C4GroupHeader));
844  if (!tfile.Write((BYTE*)&headbuf,sizeof(C4GroupHeader))
845  || !tfile.Write((BYTE*)save_core,Head.Entries*sizeof(C4GroupEntryCore)))
846  { tfile.Close(); delete [] save_core; return Error("Close: ..."); }
847  delete [] save_core;
848 
849  // Save Entries to temp file
850  int iTotalSize=0,iSizeDone=0;
851  for (centry=p->FirstEntry; centry; centry=centry->Next) iTotalSize+=centry->Size;
852  for (centry=p->FirstEntry; centry; centry=centry->Next)
853  if (AppendEntry2StdFile(centry,tfile))
854  { iSizeDone+=centry->Size; if (iTotalSize && p->fnProcessCallback) p->fnProcessCallback(centry->FileName,100*iSizeDone/iTotalSize); }
855  else
856  {
857  tfile.Close(); return false;
858  }
859 
860  // Write
861  StdBuf *pBuf;
862  tfile.Close(fToMemory ? &pBuf : nullptr);
863 
864  // Child: move temp file to mother
865  if (p->Mother)
866  {
867  if (fToMemory)
868  {
869  if (!p->Mother->Add(GetFilename(GetName()), *pBuf, true, true))
870  { delete pBuf; CloseExclusiveMother(); Clear(); return Error("Close: Cannot move rewritten child data to mother"); }
871  delete pBuf;
872  }
873  else
874  {
875  if (!p->Mother->Move(szTempFileName,GetFilename(GetName())))
876  { CloseExclusiveMother(); Clear(); return Error("Close: Cannot move rewritten child temp file to mother"); }
877  }
878  Clear();
879  return true;
880  }
881 
882  // Clear (close file)
883  Clear();
884 
885  // Delete old group file, rename new file
886  if (!EraseFile(szGrpFileName))
887  return Error("Close: Cannot erase temp file");
888  if (!RenameFile(szTempFileName,szGrpFileName))
889  return Error("Close: Cannot rename group file");
890 
891  // Should reopen the file?
892  if (fReOpen)
893  OpenReal(szGrpFileName);
894 
895  return true;
896 }
897 
899 {
900  if (p)
901  {
902  // Delete entries
903  C4GroupEntry *next;
904  while (p->FirstEntry)
905  {
906  next = p->FirstEntry->Next;
907  delete p->FirstEntry;
908  p->FirstEntry = next;
909  }
910  // Close std file
911  p->StdFile.Close();
912  // Delete mother
913  if (p->Mother && p->ExclusiveChild)
914  {
915  delete p->Mother;
916  p->Mother = nullptr;
917  }
918  }
919  // Reset
920  Init();
921 }
922 
923 bool C4Group::AppendEntry2StdFile(C4GroupEntry *centry, CStdFile &hTarget)
924 {
925  CStdFile hSource;
926  long csize;
927  BYTE fbuf;
928 
929  switch (centry->Status)
930  {
931 
932  case C4GroupEntry::C4GRES_InGroup: // Copy from group to std file
933  if (!SetFilePtr(centry->Offset))
934  return Error("AE2S: Cannot set file pointer");
935  for (csize=centry->Size; csize>0; csize--)
936  {
937  if (!Read(&fbuf,1))
938  return Error("AE2S: Cannot read entry from group file");
939  if (!hTarget.Write(&fbuf,1))
940  return Error("AE2S: Cannot write to target file");
941  }
942  break;
943 
944  case C4GroupEntry::C4GRES_OnDisk: // Copy/move from disk item to std file
945  {
946  char szFileSource[_MAX_FNAME+1];
947  SCopy(centry->DiskPath,szFileSource,_MAX_FNAME);
948 
949  // Disk item is a directory
950  if (DirectoryExists(centry->DiskPath))
951  return Error("AE2S: Cannot add directory to group file");
952 
953  // Resort group if neccessary
954  // (The group might be renamed by adding, forcing a resort)
955  bool fTempFile = false;
956  if (centry->ChildGroup)
957  if (!centry->NoSort)
958  if (!SEqual(GetFilename(szFileSource), centry->FileName))
959  {
960  // copy group
961  MakeTempFilename(szFileSource);
962  if (!CopyItem(centry->DiskPath, szFileSource))
963  return Error("AE2S: Cannot copy item");
964  // open group and resort
965  C4Group SortGrp;
966  if (!SortGrp.Open(szFileSource))
967  return Error("AE2S: Cannot open group");
968  if (!SortGrp.SortByList(C4Group_SortList, centry->FileName))
969  return Error("AE2S: Cannot resort group");
970  fTempFile = true;
971  // close group (won't be saved if the sort didn't change)
972  SortGrp.Close();
973  }
974 
975  // Append disk source to target file
976  if (!hSource.Open(szFileSource, !!centry->ChildGroup))
977  return Error("AE2S: Cannot open on-disk file");
978  for (csize=centry->Size; csize>0; csize--)
979  {
980  if (!hSource.Read(&fbuf,1))
981  { hSource.Close(); return Error("AE2S: Cannot read on-disk file"); }
982  if (!hTarget.Write(&fbuf,1))
983  { hSource.Close(); return Error("AE2S: Cannot write to target file"); }
984  }
985  hSource.Close();
986 
987  // Erase temp file
988  if (fTempFile)
989  EraseItem(szFileSource);
990  // Erase disk source if requested
991  if (centry->DeleteOnDisk)
992  EraseItem(centry->DiskPath);
993 
994  break;
995  }
996 
997  case C4GroupEntry::C4GRES_InMemory: // Copy from mem to std file
998  if (!centry->bpMemBuf) return Error("AE2S: no buffer");
999  if (!hTarget.Write(centry->bpMemBuf,centry->Size)) return Error("AE2S: writing error");
1000  break;
1001 
1002  case C4GroupEntry::C4GRES_Deleted: // Don't save
1003  break;
1004 
1005  default: // Unknown file status
1006  return Error("AE2S: Unknown file status");
1007 
1008  }
1009 
1010  return true;
1011 }
1012 
1013 void C4Group::ResetSearch(bool reload_contents)
1014 {
1015  switch (p->SourceType)
1016  {
1017  case P::ST_Unpacked:
1018  p->SearchPtr=nullptr;
1019  p->FolderSearch.Reset(GetName(), reload_contents);
1020  if (*p->FolderSearch)
1021  {
1022  p->FolderSearchEntry.Set(p->FolderSearch, GetName());
1023  p->SearchPtr=&p->FolderSearchEntry;
1024  }
1025  break;
1026  case P::ST_Packed:
1027  p->SearchPtr=p->FirstEntry;
1028  break;
1029  default: break; // InGrp & Deleted ignored
1030  }
1031 }
1032 
1033 C4GroupEntry* C4Group::GetNextFolderEntry()
1034 {
1035  if (*++p->FolderSearch)
1036  {
1037  p->FolderSearchEntry.Set(p->FolderSearch, GetName());
1038  return &p->FolderSearchEntry;
1039  }
1040  else
1041  {
1042  return nullptr;
1043  }
1044 }
1045 
1046 C4GroupEntry* C4Group::SearchNextEntry(const char *szName)
1047 {
1048  // Wildcard "*.*" is expected to find all files: substitute correct wildcard "*"
1049  if (SEqual(szName, "*.*"))
1050  szName = "*";
1051  // Search by group type
1052  C4GroupEntry *pEntry;
1053  switch (p->SourceType)
1054  {
1055  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1056  case P::ST_Packed:
1057  for (pEntry=p->SearchPtr; pEntry; pEntry=pEntry->Next)
1058  if (pEntry->Status != C4GroupEntry::C4GRES_Deleted)
1059  if (WildcardMatch(szName,pEntry->FileName))
1060  {
1061  p->SearchPtr=pEntry->Next;
1062  return pEntry;
1063  }
1064  break;
1065  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1066  case P::ST_Unpacked:
1067  for (pEntry=p->SearchPtr; pEntry; pEntry=GetNextFolderEntry())
1068  if (WildcardMatch(szName,pEntry->FileName))
1069  if (!C4Group_TestIgnore(pEntry->FileName))
1070  {
1071  p->LastFolderSearchEntry=(*pEntry);
1072  pEntry=&p->LastFolderSearchEntry;
1073  p->SearchPtr=GetNextFolderEntry();
1074  return pEntry;
1075  }
1076  break;
1077  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1078  default: break; // InGrp & Deleted ignored
1079  }
1080  // No entry found: reset search pointer
1081  p->SearchPtr=nullptr;
1082  return nullptr;
1083 }
1084 
1085 bool C4Group::SetFilePtr(int iOffset)
1086 {
1087 
1088  if (p->SourceType==P::ST_Unpacked)
1089  return Error("SetFilePtr not implemented for Folders");
1090 
1091  // ensure mother is at correct pos
1092  if (p->Mother) p->Mother->EnsureChildFilePtr(this);
1093 
1094  // Rewind if necessary
1095  if (p->FilePtr>iOffset)
1096  if (!RewindFilePtr()) return false;
1097 
1098  // Advance to target pointer
1099  if (p->FilePtr<iOffset)
1100  if (!AdvanceFilePtr(iOffset- p->FilePtr)) return false;
1101 
1102  return true;
1103 }
1104 
1105 bool C4Group::Advance(int iOffset)
1106 {
1107  assert(iOffset >= 0);
1108  // cached advance
1109  if (p->pInMemEntry)
1110  {
1111  if (p->iInMemEntrySize < size_t(iOffset)) return false;
1112  p->iInMemEntrySize -= iOffset;
1113  p->pInMemEntry += iOffset;
1114  return true;
1115  }
1116  // uncached advance
1117  if (p->SourceType == P::ST_Unpacked) return !!p->StdFile.Advance(iOffset);
1118  // FIXME: reading the file one byte at a time sounds just slow.
1119  BYTE buf;
1120  for (; iOffset>0; iOffset--)
1121  if (!Read(&buf,1)) return false;
1122  return true;
1123 }
1124 
1125 bool C4Group::Read(void *pBuffer, size_t iSize)
1126 {
1127  // Access cached entry from memory?
1128  if (p->pInMemEntry)
1129  {
1130  if (p->iInMemEntrySize < iSize) return Error("ReadCached:");
1131  memcpy(pBuffer, p->pInMemEntry, iSize);
1132  p->iInMemEntrySize -= iSize;
1133  p->pInMemEntry += iSize;
1134  return true;
1135  }
1136  // Not cached. Read from file.
1137  switch (p->SourceType)
1138  {
1139  case P::ST_Packed:
1140  // Child group: read from mother group
1141  if (p->Mother)
1142  {
1143  if (!p->Mother->Read(pBuffer,iSize))
1144  { RewindFilePtr(); return Error("Read:"); }
1145  }
1146  // Regular group: read from standard file
1147  else
1148  {
1149  if (!p->StdFile.Read(pBuffer,iSize))
1150  { RewindFilePtr(); return Error("Read:"); }
1151  }
1152  p->FilePtr+=iSize;
1153  break;
1154  case P::ST_Unpacked:
1155  if (!p->StdFile.Read(pBuffer,iSize)) return Error("Read: Error reading from folder contents");
1156  break;
1157  default: break; // InGrp & Deleted ignored
1158  }
1159 
1160  return true;
1161 }
1162 
1163 bool C4Group::AdvanceFilePtr(int iOffset)
1164 {
1165  // Child group file: pass command to mother
1166  if ((p->SourceType==P::ST_Packed) && p->Mother)
1167  {
1168 
1169  // Ensure mother file ptr for it may have been moved by foreign access to mother
1170  if (!p->Mother->EnsureChildFilePtr(this))
1171  return false;
1172 
1173  if (!p->Mother->AdvanceFilePtr(iOffset))
1174  return false;
1175 
1176  }
1177  // Regular group
1178  else if (p->SourceType==P::ST_Packed)
1179  {
1180  if (!p->StdFile.Advance(iOffset))
1181  return false;
1182  }
1183  // Open folder
1184  else
1185  {
1186  if (!p->StdFile.Advance(iOffset))
1187  return false;
1188  }
1189 
1190  // Advanced
1191  p->FilePtr+=iOffset;
1192 
1193  return true;
1194 }
1195 
1196 bool C4Group::RewindFilePtr()
1197 {
1198 
1199 #ifdef _DEBUG
1200  if (szCurrAccessedEntry && !iC4GroupRewindFilePtrNoWarn)
1201  {
1202  LogF("C4Group::RewindFilePtr() for %s (%s) after %s", szCurrAccessedEntry ? szCurrAccessedEntry : "???", GetName(), p->sPrevAccessedEntry.getLength() ? p->sPrevAccessedEntry.getData() : "???");
1203  szCurrAccessedEntry=nullptr;
1204  }
1205 #endif
1206 
1207  // Child group file: pass command to mother
1208  if ((p->SourceType==P::ST_Packed) && p->Mother)
1209  {
1210  if (!p->Mother->SetFilePtr2Entry(GetName(),true)) // Set to group file start
1211  return false;
1212  if (!p->Mother->AdvanceFilePtr(p->EntryOffset)) // Advance data offset
1213  return false;
1214  }
1215  // Regular group or open folder: rewind standard file
1216  else
1217  {
1218  if (!p->StdFile.Rewind()) // Set to group file start
1219  return false;
1220  if (!p->StdFile.Advance(p->EntryOffset)) // Advance data offset
1221  return false;
1222  }
1223 
1224  p->FilePtr=0;
1225 
1226  return true;
1227 }
1228 
1229 bool C4Group::Merge(const char *szFolders)
1230 {
1231  bool fMove = true;
1232 
1233  if (p->StdOutput) printf("%s...\n",fMove ? "Moving" : "Adding");
1234 
1235  // Add files & directories
1236  char szFileName[_MAX_FNAME+1];
1237  int iFileCount = 0;
1239 
1240  // Process segmented path & search wildcards
1241  char cSeparator = (SCharCount(';', szFolders) ? ';' : '|');
1242  for (int cseg=0; SCopySegment(szFolders, cseg, szFileName, cSeparator); cseg++)
1243  {
1244  i.Reset(szFileName);
1245  while (*i)
1246  {
1247  // File count
1248  iFileCount++;
1249  // Process output & callback
1250  if (p->StdOutput) printf("%s\n",GetFilename(*i));
1251  if (p->fnProcessCallback)
1252  p->fnProcessCallback(GetFilename(*i),0); // cbytes/tbytes
1253  // AddEntryOnDisk
1254  AddEntryOnDisk(*i, nullptr, fMove);
1255  ++i;
1256  }
1257  }
1258 
1259  if (p->StdOutput) printf("%d file(s) %s.\n",iFileCount,fMove ? "moved" : "added");
1260 
1261  return true;
1262 }
1263 
1264 bool C4Group::AddEntryOnDisk(const char *szFilename,
1265  const char *szAddAs,
1266  bool fMove)
1267 {
1268 
1269  // Do not process yourself
1270  if (ItemIdentical(szFilename, GetName())) return true;
1271 
1272  // File is a directory: copy to temp path, pack, and add packed file
1273  if (DirectoryExists(szFilename))
1274  {
1275  // Ignore
1276  if (C4Group_TestIgnore(szFilename)) return true;
1277  // Temp filename
1278  char szTempFilename[_MAX_PATH+1];
1279  if (C4Group_TempPath[0]) { SCopy(C4Group_TempPath,szTempFilename,_MAX_PATH); SAppend(GetFilename(szFilename),szTempFilename,_MAX_PATH); }
1280  else SCopy(szFilename,szTempFilename,_MAX_PATH);
1281  MakeTempFilename(szTempFilename);
1282  // Copy or move item to temp file (moved items might be killed if later process fails)
1283  if (fMove) { if (!MoveItem(szFilename,szTempFilename)) return Error("AddEntryOnDisk: Move failure"); }
1284  else { if (!CopyItem(szFilename,szTempFilename)) return Error("AddEntryOnDisk: Copy failure"); }
1285  // Pack temp file
1286  if (!C4Group_PackDirectory(szTempFilename)) return Error("AddEntryOnDisk: Pack directory failure");
1287  // Add temp file
1288  if (!szAddAs) szAddAs = GetFilename(szFilename);
1289  szFilename = szTempFilename;
1290  fMove = true;
1291  }
1292 
1293  // Determine size
1294  bool fIsGroup = !!C4Group_IsGroup(szFilename);
1295  int iSize = fIsGroup ? UncompressedFileSize(szFilename) : FileSize(szFilename);
1296 
1297  // Determine executable bit (linux only)
1298  bool fExecutable = false;
1299 #ifdef __linux__
1300  fExecutable = (access(szFilename, X_OK) == 0);
1301 #endif
1302 
1303  // AddEntry
1304  return AddEntry(C4GroupEntry::C4GRES_OnDisk,
1305  fIsGroup,
1306  szFilename,
1307  iSize,
1308  szAddAs,
1309  nullptr,
1310  fMove,
1311  false,
1312  fExecutable);
1313 
1314 }
1315 
1316 bool C4Group::Add(const char *szFile, const char *szAddAs)
1317 {
1318  bool fMove = false;
1319 
1320  if (p->StdOutput) printf("%s %s as %s...\n",fMove ? "Moving" : "Adding",GetFilename(szFile),szAddAs);
1321 
1322  return AddEntryOnDisk(szFile, szAddAs, fMove);
1323 }
1324 
1325 bool C4Group::Move(const char *szFile, const char *szAddAs)
1326 {
1327  bool fMove = true;
1328 
1329  if (p->StdOutput) printf("%s %s as %s...\n",fMove ? "Moving" : "Adding",GetFilename(szFile),szAddAs);
1330 
1331  return AddEntryOnDisk(szFile, szAddAs, fMove);
1332 }
1333 
1334 bool C4Group::Delete(const char *szFiles, bool fRecursive)
1335 {
1336  int fcount = 0;
1337  C4GroupEntry *tentry;
1338 
1339  // Segmented file specs
1340  if (SCharCount(';', szFiles) || SCharCount('|', szFiles))
1341  {
1342  char cSeparator = (SCharCount(';', szFiles) ? ';' : '|');
1343  bool success = true;
1344  char filespec[_MAX_FNAME+1];
1345  for (int cseg = 0; SCopySegment(szFiles, cseg, filespec, cSeparator, _MAX_FNAME); cseg++)
1346  if (!Delete(filespec, fRecursive))
1347  success=false;
1348  return success; // Would be nicer to return the file count and add up all counts from recursive actions...
1349  }
1350 
1351  // Delete all matching Entries
1352  ResetSearch();
1353  while ((tentry = SearchNextEntry(szFiles)))
1354  {
1355  // StdOutput
1356  if (p->StdOutput) printf("%s\n",tentry->FileName);
1357  if (!DeleteEntry(tentry->FileName))
1358  return Error("Delete: Could not delete entry");
1359  fcount++;
1360  }
1361 
1362  // Recursive: process sub groups
1363  if (fRecursive)
1364  {
1365  C4Group hChild;
1366  ResetSearch();
1367  while ((tentry = SearchNextEntry("*")))
1368  if (tentry->ChildGroup)
1369  if (hChild.OpenAsChild(this, tentry->FileName))
1370  {
1371  hChild.SetStdOutput(p->StdOutput);
1372  hChild.Delete(szFiles, fRecursive);
1373  hChild.Close();
1374  }
1375  }
1376 
1377  // StdOutput
1378  if (p->StdOutput)
1379  printf("%d file(s) deleted.\n",fcount);
1380 
1381  return true; // Would be nicer to return the file count and add up all counts from recursive actions...
1382 }
1383 
1384 bool C4Group::DeleteEntry(const char *szFilename, bool fRecycle)
1385 {
1386  switch (p->SourceType)
1387  {
1388  case P::ST_Packed:
1389  // Get entry
1390  C4GroupEntry *pEntry;
1391  if (!(pEntry=GetEntry(szFilename))) return false;
1392  // Delete moved source files
1393  if (pEntry->Status == C4GroupEntry::C4GRES_OnDisk)
1394  if (pEntry->DeleteOnDisk)
1395  {
1396  EraseItem(pEntry->DiskPath);
1397  }
1398  // (moved buffers are deleted by ~C4GroupEntry)
1399  // Delete status and update virtual file count
1401  Head.Entries--;
1402  break;
1403  case P::ST_Unpacked:
1404  p->StdFile.Close();
1405  char szPath[_MAX_FNAME+1];
1406  sprintf(szPath,"%s%c%s", GetName(),DirectorySeparator,szFilename);
1407 
1408  if (fRecycle)
1409  {
1410  if (!EraseItemSafe(szPath)) return false;
1411  }
1412  else
1413  {
1414  if (!EraseItem(szPath)) return false;
1415  }
1416  break;
1417  // refresh file list
1418  ResetSearch(true);
1419  default: break; // InGrp & Deleted ignored
1420  }
1421  return true;
1422 }
1423 
1424 bool C4Group::Rename(const char *szFile, const char *szNewName)
1425 {
1426 
1427  if (p->StdOutput) printf("Renaming %s to %s...\n",szFile,szNewName);
1428 
1429  switch (p->SourceType)
1430  {
1431  case P::ST_Packed:
1432  // Get entry
1433  C4GroupEntry *pEntry;
1434  if (!(pEntry=GetEntry(szFile))) return Error("Rename: File not found");
1435  // Check double name
1436  if (GetEntry(szNewName) && !SEqualNoCase(szNewName, szFile)) return Error("Rename: File exists already");
1437  // Rename
1438  SCopy(szNewName,pEntry->FileName,_MAX_FNAME);
1439  p->Modified=true;
1440  break;
1441  case P::ST_Unpacked:
1442  p->StdFile.Close();
1443  char path[_MAX_FNAME+1]; SCopy(GetName(),path,_MAX_PATH-1);
1444  AppendBackslash(path); SAppend(szFile,path,_MAX_PATH);
1445  char path2[_MAX_FNAME+1]; SCopy(GetName(),path2,_MAX_PATH-1);
1446  AppendBackslash(path2); SAppend(szNewName,path2,_MAX_PATH);
1447  if (!RenameFile(path,path2)) return Error("Rename: Failure");
1448  // refresh file list
1449  ResetSearch(true);
1450  break;
1451  default: break; // InGrp & Deleted ignored
1452  }
1453 
1454  return true;
1455 }
1456 
1457 bool C4Group_IsExcluded(const char *szFile, const char *szExcludeList)
1458 {
1459  // No file or no exclude list
1460  if (!szFile || !szFile[0] || !szExcludeList || !szExcludeList[0]) return false;
1461  // Process segmented exclude list
1462  char cSeparator = (SCharCount(';', szExcludeList) ? ';' : '|');
1463  char szSegment[_MAX_PATH + 1];
1464  for (int i = 0; SCopySegment(szExcludeList, i, szSegment, cSeparator); i++)
1465  if (WildcardMatch(szSegment, GetFilename(szFile)))
1466  return true;
1467  // No match
1468  return false;
1469 }
1470 
1471 bool C4Group::Extract(const char *szFiles, const char *szExtractTo, const char *szExclude)
1472 {
1473 
1474  // StdOutput
1475  if (p->StdOutput)
1476  {
1477  printf("Extracting");
1478  if (szExtractTo) printf(" to %s",szExtractTo);
1479  printf("...\n");
1480  }
1481 
1482  int fcount=0;
1483  int cbytes,tbytes;
1484  C4GroupEntry *tentry;
1485 
1486  cbytes=0; tbytes=EntrySize();
1487 
1488  // Process segmented list
1489  char cSeparator = (SCharCount(';', szFiles) ? ';' : '|');
1490  char szFileName[_MAX_PATH + 1];
1491  for (int cseg=0; SCopySegment(szFiles, cseg, szFileName, cSeparator); cseg++)
1492  {
1493  // Search all entries
1494  ResetSearch();
1495  while ((tentry = SearchNextEntry(szFileName)))
1496  {
1497  // skip?
1498  if (C4Group_IsExcluded(tentry->FileName, szExclude)) continue;
1499  // Process data & output
1500  if (p->StdOutput) printf("%s\n",tentry->FileName);
1501  cbytes+=tentry->Size;
1502  if (p->fnProcessCallback)
1503  p->fnProcessCallback(tentry->FileName,100*cbytes/std::max(tbytes,1));
1504 
1505  // Extract
1506  if (!ExtractEntry(tentry->FileName,szExtractTo))
1507  return Error("Extract: Could not extract entry");
1508 
1509  fcount++;
1510  }
1511  }
1512 
1513  if (p->StdOutput) printf("%d file(s) extracted.\n",fcount);
1514 
1515  return true;
1516 }
1517 
1518 bool C4Group::ExtractEntry(const char *szFilename, const char *szExtractTo)
1519 {
1520  CStdFile tfile;
1521  CStdFile hDummy;
1522  char szTempFName[_MAX_FNAME+1],szTargetFName[_MAX_FNAME+1];
1523 
1524  // Target file name
1525  if (szExtractTo)
1526  {
1527  SCopy(szExtractTo,szTargetFName,_MAX_FNAME-1);
1528  if (DirectoryExists(szTargetFName))
1529  {
1530  AppendBackslash(szTargetFName);
1531  SAppend(szFilename,szTargetFName,_MAX_FNAME);
1532  }
1533  }
1534  else
1535  SCopy(szFilename,szTargetFName,_MAX_FNAME);
1536 
1537  // Extract
1538  switch (p->SourceType)
1539  {
1540  case P::ST_Packed: // Copy entry to target
1541  // Get entry
1542  C4GroupEntry *pEntry;
1543  if (!(pEntry=GetEntry(szFilename))) return Error("Extract: Entry not found");
1544  // Create dummy file to reserve target file name
1545  hDummy.Create(szTargetFName,false);
1546  hDummy.Write("Dummy",5);
1547  hDummy.Close();
1548  // Make temp target file name
1549  SCopy(szTargetFName,szTempFName,_MAX_FNAME);
1550  MakeTempFilename(szTempFName);
1551  // Create temp target file
1552  if (!tfile.Create(szTempFName, !!pEntry->ChildGroup, !!pEntry->Executable))
1553  return Error("Extract: Cannot create target file");
1554  // Write entry file to temp target file
1555  if (!AppendEntry2StdFile(pEntry,tfile))
1556  {
1557  // Failure: close and erase temp target file
1558  tfile.Close();
1559  EraseItem(szTempFName);
1560  // Also erase reservation target file
1561  EraseItem(szTargetFName);
1562  // Failure
1563  return false;
1564  }
1565  // Close target file
1566  tfile.Close();
1567  // Make temp file to original file
1568  if (!EraseItem(szTargetFName))
1569  return Error("Extract: Cannot erase temporary file");
1570  if (!RenameItem(szTempFName,szTargetFName))
1571  return Error("Extract: Cannot rename temporary file");
1572  break;
1573  case P::ST_Unpacked: // Copy item from folder to target
1574  char szPath[_MAX_FNAME+1];
1575  sprintf(szPath,"%s%c%s", GetName(),DirectorySeparator,szFilename);
1576  if (!CopyItem(szPath,szTargetFName))
1577  return Error("ExtractEntry: Cannot copy item");
1578  break;
1579  default: break; // InGrp & Deleted ignored
1580  }
1581  return true;
1582 }
1583 
1584 
1586  const char *szEntryName, bool fExclusive, bool fCreate)
1587 {
1588 
1589  if (!pMother) return Error("OpenAsChild: No mother specified");
1590 
1591  if (SCharCount('*',szEntryName)) return Error("OpenAsChild: No wildcards allowed");
1592 
1593  // Open nested child group check: If szEntryName is a reference to
1594  // a nested group, open the first mother (in specified mode), then open the child
1595  // in exclusive mode
1596 
1597  if (SCharCount(DirectorySeparator,szEntryName))
1598  {
1599  char mothername[_MAX_FNAME+1];
1600  SCopyUntil(szEntryName,mothername,DirectorySeparator,_MAX_FNAME);
1601 
1602  C4Group *pMother2;
1603  pMother2 = new C4Group;
1604  pMother2->SetStdOutput(p->StdOutput);
1605  if (!pMother2->OpenAsChild(pMother, mothername, fExclusive))
1606  {
1607  delete pMother2;
1608  return Error("OpenAsChild: Cannot open mother");
1609  }
1610  return OpenAsChild(pMother2, szEntryName + SLen(mothername) + 1, true);
1611  }
1612 
1613  // Init
1614  Init();
1615  p->FileName = szEntryName;
1616  p->Mother=pMother;
1617  p->ExclusiveChild=fExclusive;
1618 
1619  // Folder: Simply set status and return
1620  char path[_MAX_FNAME+1];
1621  SCopy( GetFullName().getData(), path, _MAX_FNAME);
1622  if (DirectoryExists(path))
1623  {
1624  p->FileName = path;
1625  p->SourceType=P::ST_Unpacked;
1626  ResetSearch();
1627  return true;
1628  }
1629 
1630  // Get original entry name
1631  C4GroupEntry *centry;
1632  if ((centry = p->Mother->GetEntry(GetName())))
1633  p->FileName = centry->FileName;
1634 
1635  // Access entry in mother group
1636  size_t iSize;
1637  if ((!p->Mother->AccessEntry(GetName(), &iSize, nullptr, true)))
1638  {
1639  if (!fCreate)
1640  { CloseExclusiveMother(); Clear(); return Error("OpenAsChild: Entry not in mother group"); }
1641  else
1642  {
1643  // Create - will be added to mother in Close()
1644  p->SourceType=P::ST_Packed; p->Modified=true;
1645  return true;
1646  }
1647  }
1648 
1649  // Child Group?
1650  if (centry && !centry->ChildGroup)
1651  { CloseExclusiveMother(); Clear(); return Error("OpenAsChild: Is not a child group"); }
1652 
1653  // Read header
1654  // Do not do size checks for packed subgroups of unpacked groups (there will be no entry),
1655  // because that would be the PACKED size which can actually be smaller than sizeof(C4GroupHeader)!
1656  if (iSize < sizeof(C4GroupHeader) && centry)
1657  { CloseExclusiveMother(); Clear(); return Error("OpenAsChild: Entry too small"); }
1658  if (!p->Mother->Read(&Head,sizeof(C4GroupHeader)))
1659  { CloseExclusiveMother(); Clear(); return Error("OpenAsChild: Entry reading error"); }
1660  MemScramble((BYTE*)&Head,sizeof(C4GroupHeader));
1661  p->EntryOffset+=sizeof(C4GroupHeader);
1662 
1663  // Check Header
1664  if (!SEqual(Head.id,C4GroupFileID)
1666  { CloseExclusiveMother(); Clear(); return Error("OpenAsChild: Invalid Header"); }
1667 
1668  // Read Entries
1669  C4GroupEntryCore corebuf;
1670  int file_entries=Head.Entries;
1671  Head.Entries=0; // Reset, will be recounted by AddEntry
1672  for (int cnt=0; cnt<file_entries; cnt++)
1673  {
1674  if (!p->Mother->Read(&corebuf,sizeof(C4GroupEntryCore)))
1675  { CloseExclusiveMother(); Clear(); return Error("OpenAsChild: Entry reading error"); }
1676  p->EntryOffset+=sizeof(C4GroupEntryCore);
1677  if (!AddEntry(C4GroupEntry::C4GRES_InGroup, !!corebuf.ChildGroup,
1678  corebuf.FileName,corebuf.Size,
1679  nullptr, nullptr, false, false,
1680  !!corebuf.Executable))
1681  { CloseExclusiveMother(); Clear(); return Error("OpenAsChild: Insufficient memory"); }
1682  }
1683 
1684  ResetSearch();
1685 
1686  // File
1687  p->SourceType=P::ST_Packed;
1688 
1689  // save position in mother group
1690  if (centry) p->MotherOffset = centry->Offset;
1691 
1692  return true;
1693 }
1694 
1695 bool C4Group::AccessEntry(const char *szWildCard,
1696  size_t *iSize, char *sFileName,
1697  bool NeedsToBeAGroup)
1698 {
1699 #ifdef C4GROUP_DUMP_ACCESS
1700  LogF("Group access in %s: %s", GetFullName().getData(), szWildCard);
1701 #endif
1702  StdStrBuf fname;
1703  if (!FindEntry(szWildCard,&fname,&p->iCurrFileSize))
1704  return false;
1705 #ifdef _DEBUG
1706  szCurrAccessedEntry = fname.getMData();
1707 #endif
1708  bool fResult = SetFilePtr2Entry(fname.getData(), NeedsToBeAGroup);
1709 #ifdef _DEBUG
1710  p->sPrevAccessedEntry.Copy(szCurrAccessedEntry);
1711  szCurrAccessedEntry = nullptr;
1712 #endif
1713  if (!fResult) return false;
1714  if (sFileName) SCopy(fname.getData(),sFileName);
1715  if (iSize) *iSize=p->iCurrFileSize;
1716  return true;
1717 }
1718 
1719 bool C4Group::AccessNextEntry(const char *szWildCard,
1720  size_t *iSize, char *sFileName,
1721  bool fStartAtFilename)
1722 {
1723  char fname[_MAX_FNAME+1];
1724  if (!FindNextEntry(szWildCard,fname,&p->iCurrFileSize,fStartAtFilename)) return false;
1725 #ifdef _DEBUG
1726  szCurrAccessedEntry = fname;
1727 #endif
1728  bool fResult = SetFilePtr2Entry(fname);
1729 #ifdef _DEBUG
1730  szCurrAccessedEntry = nullptr;
1731 #endif
1732  if (!fResult) return false;
1733  if (sFileName) SCopy(fname,sFileName);
1734  if (iSize) *iSize=p->iCurrFileSize;
1735  return true;
1736 }
1737 
1738 bool C4Group::SetFilePtr2Entry(const char *szName, bool NeedsToBeAGroup)
1739 {
1740  C4GroupEntry *centry = GetEntry(szName);
1741  // Read cached entries directly from memory (except child groups. that is not supported.)
1742  if (centry && centry->bpMemBuf && !NeedsToBeAGroup)
1743  {
1744  p->pInMemEntry = centry->bpMemBuf;
1745  p->iInMemEntrySize = centry->Size;
1746  return true;
1747  }
1748  else
1749  {
1750  p->pInMemEntry = nullptr;
1751  }
1752 
1753  // Not cached. Access from disk.
1754  switch (p->SourceType)
1755  {
1756 
1757  case P::ST_Packed:
1758  if ((!centry) || (centry->Status != C4GroupEntry::C4GRES_InGroup)) return false;
1759  return SetFilePtr(centry->Offset);
1760 
1761  case P::ST_Unpacked: {
1762  p->StdFile.Close();
1763  char path[_MAX_FNAME+1]; SCopy(GetName(),path,_MAX_FNAME);
1764  AppendBackslash(path); SAppend(szName,path);
1765  bool fSuccess = p->StdFile.Open(path, NeedsToBeAGroup);
1766  return fSuccess;
1767  }
1768 
1769  default: break; // InGrp & Deleted ignored
1770  }
1771  return false;
1772 }
1773 
1774 bool C4Group::FindEntry(const char *szWildCard, StdStrBuf *sFileName, size_t *iSize)
1775 {
1776  ResetSearch();
1777  return FindNextEntry(szWildCard,sFileName,iSize);
1778 }
1779 
1780 bool C4Group::FindNextEntry(const char *szWildCard,
1781  StdStrBuf *sFileName,
1782  size_t *iSize,
1783  bool fStartAtFilename)
1784 {
1785  C4GroupEntry *centry;
1786  if (!szWildCard) return false;
1787 
1788  // Reset search to specified position
1789  if (fStartAtFilename) FindEntry(sFileName->getData());
1790 
1791  if (!(centry=SearchNextEntry(szWildCard))) return false;
1792  if (sFileName) sFileName->Copy(centry->FileName);
1793  if (iSize) *iSize=centry->Size;
1794  return true;
1795 }
1796 
1797 bool C4Group::Add(const char *szName, void *pBuffer, int iSize, bool fChild, bool fHoldBuffer, bool fExecutable)
1798 {
1799  return AddEntry(C4GroupEntry::C4GRES_InMemory,
1800  fChild,
1801  szName,
1802  iSize,
1803  szName,
1804  (BYTE*) pBuffer,
1805  false,
1806  fHoldBuffer,
1807  fExecutable);
1808 }
1809 
1810 bool C4Group::Add(const char *szName, StdBuf &pBuffer, bool fChild, bool fHoldBuffer, bool fExecutable)
1811 {
1812  if (!AddEntry(C4GroupEntry::C4GRES_InMemory,
1813  fChild,
1814  szName,
1815  pBuffer.getSize(),
1816  szName,
1817  (BYTE*) pBuffer.getMData(),
1818  false,
1819  fHoldBuffer,
1820  fExecutable,
1821  true)) return false;
1822  // Pointer is now owned and released by C4Group!
1823  if (fHoldBuffer) pBuffer.GrabPointer();
1824  return true;
1825 }
1826 
1827 bool C4Group::Add(const char *szName, StdStrBuf &pBuffer, bool fChild, bool fHoldBuffer, bool fExecutable)
1828 {
1829  if (!AddEntry(C4GroupEntry::C4GRES_InMemory,
1830  fChild,
1831  szName,
1832  pBuffer.getLength(),
1833  szName,
1834  (BYTE*) pBuffer.getMData(),
1835  false,
1836  fHoldBuffer,
1837  fExecutable,
1838  true)) return false;
1839  // Pointer is now owned and released by C4Group!
1840  if (fHoldBuffer) pBuffer.GrabPointer();
1841  return true;
1842 }
1843 
1844 
1845 const char* C4Group::GetName() const
1846 {
1847  return p->FileName.c_str();
1848 }
1849 
1850 int C4Group::EntryCount(const char *szWildCard)
1851 {
1852  int fcount;
1853  C4GroupEntry *tentry;
1854  // All files if no wildcard
1855  if (!szWildCard) szWildCard="*";
1856  // Match wildcard
1857  ResetSearch(); fcount=0;
1858  while ((tentry=SearchNextEntry(szWildCard))) fcount++;
1859  return fcount;
1860 }
1861 
1862 size_t C4Group::EntrySize(const char *szWildCard)
1863 {
1864  int fsize;
1865  C4GroupEntry *tentry;
1866  // All files if no wildcard
1867  if (!szWildCard) szWildCard="*";
1868  // Match wildcard
1869  ResetSearch(); fsize=0;
1870  while ((tentry=SearchNextEntry(szWildCard)))
1871  fsize+=tentry->Size;
1872  return fsize;
1873 }
1874 
1875 size_t C4Group::AccessedEntrySize() const { return p->iCurrFileSize; }
1876 
1877 unsigned int C4Group::EntryCRC32(const char *szWildCard)
1878 {
1879  if (!szWildCard) szWildCard="*";
1880  // iterate thorugh child
1881  C4GroupEntry *pEntry; unsigned int iCRC = 0;
1882  ResetSearch();
1883  while ((pEntry = SearchNextEntry(szWildCard)))
1884  {
1885  iCRC ^= CalcCRC32(pEntry);
1886  }
1887  // return
1888  return iCRC;
1889 }
1890 
1891 bool C4Group::IsOpen() const { return p->SourceType != P::ST_None; }
1892 
1893 bool C4Group::LoadEntry(const char *szEntryName, char **lpbpBuf, size_t *ipSize, int iAppendZeros)
1894 {
1895  size_t size;
1896 
1897  // Access entry, allocate buffer, read data
1898  (*lpbpBuf)=nullptr; if (ipSize) *ipSize=0;
1899  if (!AccessEntry(szEntryName,&size)) return Error("LoadEntry: Not found");
1900  *lpbpBuf = new char[size+iAppendZeros];
1901  if (!Read(*lpbpBuf,size))
1902  {
1903  delete [] (*lpbpBuf); *lpbpBuf = nullptr;
1904  return Error("LoadEntry: Reading error");
1905  }
1906 
1907  if (ipSize) *ipSize=size;
1908 
1909  if (iAppendZeros)
1910  ZeroMem( (*lpbpBuf)+size, iAppendZeros );
1911 
1912  return true;
1913 }
1914 
1915 bool C4Group::LoadEntry(const char *szEntryName, StdBuf * Buf)
1916 {
1917  size_t size;
1918  // Access entry, allocate buffer, read data
1919  if (!AccessEntry(szEntryName,&size)) return Error("LoadEntry: Not found");
1920  // Allocate memory
1921  Buf->New(size);
1922  // Load data
1923  if (!Read(Buf->getMData(),size))
1924  {
1925  Buf->Clear();
1926  return Error("LoadEntry: Reading error");
1927  }
1928  // ok
1929  return true;
1930 }
1931 
1932 bool C4Group::LoadEntryString(const char *szEntryName, StdStrBuf *Buf)
1933 {
1934  size_t size;
1935  // Access entry, allocate buffer, read data
1936  if (!AccessEntry(szEntryName,&size)) return Error("LoadEntry: Not found");
1937  // Allocate memory
1938  Buf->SetLength(size);
1939  // other parts crash when they get a zero length buffer, so fail here
1940  if (!size) return false;
1941  // Load data
1942  if (!Read(Buf->getMData(),size))
1943  {
1944  Buf->Clear();
1945  return Error("LoadEntry: Reading error");
1946  }
1947  // ok
1948  return true;
1949 }
1950 
1951 int SortRank(const char *szElement, const char *szSortList)
1952 {
1953  int cnt;
1954  char csegment[_MAX_FNAME+1];
1955 
1956  for (cnt=0; SCopySegment(szSortList,cnt,csegment,'|',_MAX_FNAME); cnt++)
1957  if (WildcardMatch(csegment,szElement))
1958  return (SCharCount('|',szSortList)+1)-cnt;
1959 
1960  return 0;
1961 }
1962 
1963 bool C4Group::Sort(const char *szSortList)
1964 {
1965  bool fBubble;
1966  C4GroupEntry *centry,*prev,*next,*nextnext;
1967 
1968  if (!szSortList || !szSortList[0]) return false;
1969 
1970  if (p->StdOutput) printf("Sorting...\n");
1971 
1972  do
1973  {
1974  fBubble=false;
1975 
1976  for (prev=nullptr,centry=p->FirstEntry; centry; prev=centry,centry=next)
1977  if ((next=centry->Next))
1978  {
1979  // primary sort by file list
1980  int iS1 = SortRank(centry->FileName,szSortList);
1981  int iS2 = SortRank(next->FileName,szSortList);
1982  if (iS1 > iS2) continue;
1983  // secondary sort by filename
1984  if (iS1 == iS2)
1985  if (stricmp(centry->FileName, next->FileName) <= 0) continue;
1986  // wrong order: Swap!
1987  nextnext=next->Next;
1988  if (prev) prev->Next=next;
1989  else p->FirstEntry=next;
1990  next->Next=centry;
1991  centry->Next=nextnext;
1992  next=nextnext;
1993 
1994  fBubble=true;
1995  p->Modified=true;
1996  }
1997 
1998  }
1999  while (fBubble);
2000 
2001  return true;
2002 }
2003 
2005 {
2006  return p->Mother;
2007 }
2008 
2009 bool C4Group::IsPacked() const { return p->SourceType == P::ST_Packed; }
2010 
2011 bool C4Group::HasPackedMother() const { if (!p->Mother) return false; return p->Mother->IsPacked(); }
2012 
2013 bool C4Group::SetNoSort(bool fNoSort) { p->NoSort = fNoSort; return true; }
2014 
2015 bool C4Group::CloseExclusiveMother()
2016 {
2017  if (p->Mother && p->ExclusiveChild)
2018  {
2019  p->Mother->Close();
2020  delete p->Mother;
2021  p->Mother=nullptr;
2022  return true;
2023  }
2024  return false;
2025 }
2026 
2027 bool C4Group::SortByList(const char **ppSortList, const char *szFilename)
2028 {
2029  // No sort list specified
2030  if (!ppSortList) return false;
2031  // No group name specified, use own
2032  if (!szFilename) szFilename = GetName();
2033  szFilename = GetFilename(szFilename);
2034  // Find matching filename entry in sort list
2035  const char **ppListEntry;
2036  for (ppListEntry = ppSortList; *ppListEntry; ppListEntry+=2)
2037  if (WildcardMatch( *ppListEntry, szFilename ))
2038  break;
2039  // Sort by sort list entry
2040  if (*ppListEntry && *(ppListEntry+1))
2041  Sort(*(ppListEntry+1));
2042  // Success
2043  return true;
2044 }
2045 
2046 bool C4Group::EnsureChildFilePtr(C4Group *pChild)
2047 {
2048 
2049  // group file
2050  if (p->SourceType == P::ST_Packed)
2051  {
2052  // check if FilePtr has to be moved
2053  if (p->FilePtr != pChild->p->MotherOffset + pChild->p->EntryOffset + pChild->p->FilePtr)
2054  // move it to the position the child thinks it is
2055  if (!SetFilePtr(pChild->p->MotherOffset + pChild->p->EntryOffset + pChild->p->FilePtr))
2056  return false;
2057  // ok
2058  return true;
2059  }
2060 
2061  // Open standard file is not the child file ...or StdFile ptr does not match pChild->FilePtr
2062  char szChildPath[_MAX_PATH+1]; sprintf(szChildPath,"%s%c%s", GetName(),DirectorySeparator,GetFilename(pChild->GetName()));
2063  if ( !ItemIdentical(p->StdFile.Name, szChildPath))
2064  {
2065  // Reopen correct child stdfile
2066  if ( !SetFilePtr2Entry( GetFilename(pChild->GetName()), true ) )
2067  return false;
2068  // Advance to child's old file ptr
2069  if ( !AdvanceFilePtr( pChild->p->EntryOffset + pChild->p->FilePtr ) )
2070  return false;
2071  }
2072 
2073  // Looks okay
2074  return true;
2075 
2076 }
2077 
2079 {
2080  char str[_MAX_PATH+1]; *str='\0';
2081  char sep[] = "/"; sep[0] = DirectorySeparator;
2082  for (const C4Group *pGroup=this; pGroup; pGroup=pGroup->p->Mother)
2083  {
2084  if (*str) SInsert(str, sep, 0, _MAX_PATH);
2085  // Avoid double slash
2086  if (pGroup == this || pGroup->p->FileName.length() > 1 || pGroup->p->FileName[0] != '/')
2087  SInsert(str, pGroup->GetName(), 0, _MAX_PATH);
2088  if (pGroup->p->SourceType == P::ST_Unpacked) break; // Folder is assumed to have full path
2089  }
2090  StdStrBuf sResult; sResult.Copy(str);
2091  return sResult;
2092 }
2093 
2094 uint32_t C4Group::CalcCRC32(C4GroupEntry *pEntry)
2095 {
2096  uint32_t CRC;
2097  // child group?
2098  if (pEntry->ChildGroup || (pEntry->Status == C4GroupEntry::C4GRES_OnDisk && (DirectoryExists(pEntry->DiskPath) || C4Group_IsGroup(pEntry->DiskPath))))
2099  {
2100  // open
2101  C4Group Child;
2102  switch (pEntry->Status)
2103  {
2105  if (!Child.OpenAsChild(this, pEntry->FileName))
2106  return 0;
2107  break;
2109  if (!Child.Open(pEntry->DiskPath))
2110  return 0;
2111  break;
2112  default:
2113  return 0;
2114  }
2115  // get checksum
2116  CRC = Child.EntryCRC32();
2117  }
2118  else if (!pEntry->Size)
2119  CRC = 0;
2120  else
2121  {
2122  BYTE *pData = nullptr; bool fOwnData; CStdFile f;
2123  // get data
2124  switch (pEntry->Status)
2125  {
2127  // create buffer
2128  pData = new BYTE [pEntry->Size]; fOwnData = true;
2129  // go to entry
2130  if (!SetFilePtr2Entry(pEntry->FileName)) { delete [] pData; return false; }
2131  // read
2132  if (!Read(pData, pEntry->Size)) { delete [] pData; return false; }
2133  break;
2135  // create buffer
2136  pData = new BYTE [pEntry->Size]; fOwnData = true;
2137  // open
2138  if (!f.Open(pEntry->DiskPath)) { delete [] pData; return false; }
2139  // read
2140  if (!f.Read(pData, pEntry->Size)) { delete [] pData; return false; }
2141  break;
2143  // set
2144  pData = pEntry->bpMemBuf; fOwnData = false;
2145  break;
2146  default:
2147  return false;
2148  }
2149  if (!pData) return false;
2150  // calc crc
2151  CRC = crc32(0, pData, pEntry->Size);
2152  // discard buffer
2153  if (fOwnData) delete [] pData;
2154  // add file name
2155  CRC = crc32(CRC, reinterpret_cast<BYTE *>(pEntry->FileName), SLen(pEntry->FileName));
2156  }
2157  // ok
2158  return CRC;
2159 }
2160 
2161 bool C4Group::OpenChild(const char* strEntry)
2162 {
2163  // hack: The seach-handle would be closed twice otherwise
2164  p->FolderSearch.Reset();
2165  // Create a memory copy of ourselves
2166  C4Group *pOurselves = new C4Group;
2167  *pOurselves->p = *p;
2168 
2169  // Open a child from the memory copy
2170  C4Group hChild;
2171  if (!hChild.OpenAsChild(pOurselves, strEntry, false))
2172  {
2173  // Silently delete our memory copy
2174  pOurselves->p.reset();
2175  delete pOurselves;
2176  return false;
2177  }
2178 
2179  // hack: The seach-handle would be closed twice otherwise
2180  p->FolderSearch.Reset();
2181  hChild.p->FolderSearch.Reset();
2182 
2183  // We now become our own child
2184  *p = *hChild.p;
2185 
2186  // Make ourselves exclusive (until we hit our memory copy parent)
2187  for (C4Group *pGroup = this; pGroup != pOurselves; pGroup = pGroup->p->Mother)
2188  pGroup->p->ExclusiveChild = true;
2189 
2190  // Reset the temporary child variable so it doesn't delete anything
2191  hChild.p.reset();
2192 
2193  // Yeehaw
2194  return true;
2195 }
2196 
2198 {
2199  // This only works if we are an exclusive child
2200  if (!p->Mother || !p->ExclusiveChild) return false;
2201 
2202  // Store a pointer to our mother
2203  C4Group *pMother = p->Mother;
2204 
2205  // Clear ourselves without deleting our mother
2206  p->ExclusiveChild = false;
2207  Clear();
2208 
2209  // hack: The seach-handle would be closed twice otherwise
2210  pMother->p->FolderSearch.Reset();
2211  p->FolderSearch.Reset();
2212  // We now become our own mother (whoa!)
2213  *this = std::move(*pMother);
2214 
2215  // Now silently delete our former mother
2216  delete pMother;
2217 
2218  // Yeehaw
2219  return true;
2220 }
2221 
2222 int C4Group::PreCacheEntries(const char *szSearchPattern, bool cache_previous)
2223 {
2224  assert(szSearchPattern);
2225  int result = 0;
2226  // pre-load entries to memory. return number of loaded entries.
2227  for (C4GroupEntry * e = p->FirstEntry; e; e = e->Next)
2228  {
2229  // is this to be cached?
2230  if (!WildcardListMatch(szSearchPattern, e->FileName)) continue;
2231  // if desired, cache all entries up to that one to allow rewind in unpacked memory
2232  // (only makes sense for groups)
2233  if (cache_previous && p->SourceType == P::ST_Packed)
2234  {
2235  for (C4GroupEntry * e_pre = p->FirstEntry; e_pre != e; e_pre = e_pre->Next)
2236  if (e_pre->Offset >= p->FilePtr)
2237  PreCacheEntry(e_pre);
2238  }
2239  // cache the given entry
2240  PreCacheEntry(e);
2241  }
2242  return result;
2243 }
2244 
2245 const C4GroupHeader &C4Group::GetHeader() const { return Head; }
2246 
2247 const C4GroupEntry *C4Group::GetFirstEntry() const { return p->FirstEntry; }
2248 
2249 void C4Group::PreCacheEntry(C4GroupEntry * e)
2250 {
2251  // skip some stuff that can not be cached or has already been cached
2252  if (e->ChildGroup || e->bpMemBuf || !e->Size) return;
2253  // now load it!
2254  StdBuf buf;
2255  if (!this->LoadEntry(e->FileName, &buf)) return;
2256  e->HoldBuffer = true;
2257  e->BufferIsStdbuf = true;
2258  e->Size = buf.getSize(); // update size in case group changed on disk between calls
2259  e->bpMemBuf = static_cast<BYTE *>(buf.GrabPointer());
2260 }
int MotherOffset
Definition: C4Group.cpp:430
bool SetNoSort(bool fNoSort)
Definition: C4Group.cpp:2013
char * GetFilename(char *szPath)
Definition: StdFile.cpp:55
const char * getData() const
Definition: StdBuf.h:450
bool CreateItem(const char *szItemname)
Definition: StdFile.cpp:825
bool C4Group_IsExcluded(const char *szFile, const char *szExcludeList)
Definition: C4Group.cpp:1457
bool BufferIsStdbuf
Definition: C4Group.h:124
bool FindEntry(const char *szWildCard, StdStrBuf *sFileName=nullptr, size_t *iSize=nullptr)
Definition: C4Group.cpp:1774
bool Close(StdBuf **ppMemory=nullptr)
Definition: CStdFile.cpp:155
bool C4Group_ExplodeDirectory(const char *szFilename)
Definition: C4Group.cpp:351
#define C4CFN_FolderFiles
Definition: C4Components.h:175
char * GrabPointer()
Definition: StdBuf.h:467
Definition: StdBuf.h:37
const int C4GroupFileVer2
Definition: C4Group.h:56
Definition: C4Group.h:116
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:122
int EntryCount(const char *szWildCard=nullptr)
Definition: C4Group.cpp:1850
#define C4FLS_Scenario
Definition: C4Components.h:188
bool HoldBuffer
Definition: C4Group.h:123
C4Group * GetMother()
Definition: C4Group.cpp:2004
void SAppend(const char *szSource, char *szTarget, int iMaxL)
Definition: Standard.cpp:227
bool CopyItem(const char *szSource, const char *szTarget, bool fResetAttributes)
Definition: StdFile.cpp:841
bool RenameItem(const char *szItemName, const char *szNewItemName)
Definition: StdFile.cpp:813
char Executable
Definition: C4Group.h:100
bool ItemIdentical(const char *szFilename1, const char *szFilename2)
Definition: StdFile.cpp:865
bool Merge(const char *szFolders)
Definition: C4Group.cpp:1229
bool CreatePath(const std::string &path)
Definition: StdFile.cpp:642
bool Create(const char *szFileName, bool fCompressed=false, bool fExecutable=false, bool fMemory=false)
Definition: CStdFile.cpp:53
bool AccessEntry(const char *szWildCard, size_t *iSize=nullptr, char *sFileName=nullptr, bool NeedsToBeAGroup=false)
Definition: C4Group.cpp:1695
void Clear()
Definition: StdBuf.h:474
#define sprintf
Definition: Standard.h:171
#define C4CFN_Material
Definition: C4Components.h:25
bool SCopySegment(const char *szString, int iSegment, char *sTarget, char cSeparator, int iMaxL, bool fSkipWhitespace)
Definition: Standard.cpp:243
int stricmp(const char *s1, const char *s2)
bool EraseFile(const char *szFilename)
Definition: StdFile.cpp:495
void C4Group_SetSortList(const char **ppSortList)
Definition: C4Group.cpp:69
bool AccessNextEntry(const char *szWildCard, size_t *iSize=nullptr, char *sFileName=nullptr, bool fStartAtFilename=false)
Definition: C4Group.cpp:1719
char FileName[260]
Definition: C4Group.h:94
void Clear()
Definition: StdBuf.h:198
char C4Group_Ignore[_MAX_PATH+1]
Definition: C4Group.cpp:60
static void DeletePointer(void *data)
Definition: StdBuf.h:204
void * GrabPointer()
Definition: StdBuf.h:141
bool SEqualNoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:177
#define C4FLS_Section
Definition: C4Components.h:189
const char * C4CFN_FLS[]
Definition: C4Group.cpp:32
bool EraseItemSafe(const char *szFilename)
Definition: C4GroupMain.cpp:41
bool LoadEntry(const char *szEntryName, char **lpbpBuf, size_t *ipSize=nullptr, int iAppendZeros=0)
Definition: C4Group.cpp:1893
uint8_t BYTE
bool Rename(const char *szFile, const char *szNewName)
Definition: C4Group.cpp:1424
bool C4Group_UnpackDirectory(const char *szFilename)
Definition: C4Group.cpp:306
bool SIsModule(const char *szList, const char *szString, int *ipIndex, bool fCaseSensitive)
Definition: Standard.cpp:511
char id[24+4]
Definition: C4Group.h:85
bool Delete(const char *szFiles, bool fRecursive=false)
Definition: C4Group.cpp:1334
#define _MAX_PATH
Definition: C4Group.h:92
#define C4CFN_ObjectInfoFiles
Definition: C4Components.h:169
size_t SLen(const char *sptr)
Definition: Standard.h:78
void AppendBackslash(char *szFilename)
Definition: StdFile.cpp:267
const C4GroupEntry * GetFirstEntry() const
Definition: C4Group.cpp:2247
bool SEqual(const char *szStr1, const char *szStr2)
Definition: Standard.h:97
EntryStatus
Definition: C4Group.h:111
unsigned int SCharCount(char cTarget, const char *szInStr, const char *cpUntil)
Definition: Standard.cpp:290
std::string FileName
Definition: C4Group.cpp:420
size_t getSize() const
Definition: StdBuf.h:109
C4Group * Mother
Definition: C4Group.cpp:422
const int C4GroupFileVer1
Definition: C4Group.h:56
C4GroupEntry * Next
Definition: C4Group.h:127
#define C4FLS_System
Definition: C4Components.h:202
bool GetParentPath(const char *szFilename, char *szBuffer)
Definition: StdFile.cpp:199
#define C4CFN_PlayerFiles
Definition: C4Components.h:167
bool Save(bool fReOpen)
Definition: C4Group.cpp:797
~C4Group()
Definition: C4Group.cpp:493
char * getMData()
Definition: StdBuf.h:451
bool OpenMother()
Definition: C4Group.cpp:2197
void C4Group_SetProcessCallback(bool(*fnCallback)(const char *, int))
Definition: C4Group.cpp:64
bool C4Group_PackDirectoryTo(const char *szFilename, const char *szFilenameTo)
Definition: C4Group.cpp:221
bool(* C4Group_ProcessCallback)(const char *, int)
Definition: C4Group.cpp:62
#define C4FLS_Music
Definition: C4Components.h:204
int32_t Packed
Definition: C4Group.h:95
bool Write(const void *pBuffer, int iSize)
Definition: CStdFile.cpp:244
void Set(const DirectoryIterator &iter, const char *szPath)
Definition: C4Group.cpp:462
bool SortByList(const char **ppSortList, const char *szFilename=nullptr)
Definition: C4Group.cpp:2027
bool IsOpen() const
Definition: C4Group.cpp:1891
#define C4FLS_Sound
Definition: C4Components.h:203
#define C4GroupFileID
Definition: C4Group.h:62
#define C4FLS_Graphics
Definition: C4Components.h:197
const char * GetName() const
Definition: C4Group.cpp:1845
StdStrBuf GetFullName() const
Definition: C4Group.cpp:2078
void SInsert(char *szString, const char *szInsert, int iPosition, int iMaxLen)
Definition: Standard.cpp:473
size_t GetFileSize() const
Definition: StdFile.cpp:1044
#define C4CFN_ScenarioSections
Definition: C4Components.h:38
bool Move(const char *szFile, const char *szAddAs)
Definition: C4Group.cpp:1325
bool Open(const char *szGroupName, bool fCreate=false)
Definition: C4Group.cpp:514
void SetStdOutput(bool fStatus)
Definition: C4Group.cpp:509
#define C4FLS_Def
Definition: C4Components.h:192
#define C4CFN_Music
Definition: C4Components.h:30
bool C4Group_CopyItem(const char *szSource, const char *szTarget1, bool fNoSort, bool fResetAttributes)
Definition: C4Group.cpp:100
bool EraseDirectory(const char *szDirName)
Definition: StdFile.cpp:771
const C4GroupHeader & GetHeader() const
Definition: C4Group.cpp:2245
C4GroupEntry LastFolderSearchEntry
Definition: C4Group.cpp:441
#define C4CFN_Sound
Definition: C4Components.h:26
unsigned int EntryCRC32(const char *szWildCard=nullptr)
Definition: C4Group.cpp:1877
bool C4Group_IsGroup(const char *szFilename)
Definition: C4Group.cpp:94
bool fRecursive
Definition: C4GroupMain.cpp:34
size_t AccessedEntrySize() const override
Definition: C4Group.cpp:1875
bool Close()
Definition: C4Group.cpp:755
int SortRank(const char *szElement, const char *szSortList)
Definition: C4Group.cpp:1951
int32_t Offset
Definition: C4Group.h:96
bool Read(void *pBuffer, size_t iSize) override
Definition: CStdFile.h:61
~C4GroupEntry()
Definition: C4Group.cpp:450
int32_t ChildGroup
Definition: C4Group.h:95
#define _MAX_FNAME
bool OpenChild(const char *strEntry)
Definition: C4Group.cpp:2161
int32_t Size
Definition: C4Group.h:96
C4GroupEntry * GetEntry(const char *szName)
Definition: C4Group.cpp:744
bool DeleteOnDisk
Definition: C4Group.h:122
C4GroupHeader Head
Definition: C4Group.h:144
C4GroupEntry * SearchPtr
Definition: C4Group.cpp:425
void New(size_t inSize)
Definition: StdBuf.h:154
const char * C4Group_GetTempPath()
Definition: C4Group.cpp:80
std::enable_if< std::is_nothrow_default_constructible< T >::value >::type InplaceReconstruct(T *obj)
Definition: Standard.h:35
#define C4CFN_Graphics
Definition: C4Components.h:28
const char * GetError()
Definition: C4Group.cpp:504
Definition: C4Group.h:113
bool(* fnProcessCallback)(const char *, int)
Definition: C4Group.cpp:444
int PreCacheEntries(const char *szSearchPattern, bool cache_previous=false)
Definition: C4Group.cpp:2222
bool DeleteEntry(const char *szFilename, bool fRecycle=false)
Definition: C4Group.cpp:1384
CStdFile StdFile
Definition: C4Group.cpp:426
Definition: C4Group.h:114
bool C4Group_MoveItem(const char *szSource, const char *szTarget1, bool fNoSort)
Definition: C4Group.cpp:144
bool DirectoryExists(const char *szFilename)
Definition: StdFile.cpp:694
void Reset(const char *dirname, bool force_reread=false)
Definition: StdFile.cpp:928
#define C4FLS_Folder
Definition: C4Components.h:195
size_t FileSize(const char *szFilename)
Definition: StdFile.cpp:448
bool NoSort
Definition: C4Group.h:125
bool ValidateFilename(char *szFilename, size_t iMaxSize=_MAX_PATH)
std::string ErrorString
Definition: C4Group.cpp:445
bool LoadEntryString(const char *szEntryName, StdStrBuf *Buf)
Definition: C4Group.cpp:1932
size_t iInMemEntrySize
Definition: C4Group.cpp:434
bool Modified
Definition: C4Group.cpp:432
bool FindNextEntry(const char *szWildCard, StdStrBuf *sFileName=nullptr, size_t *iSize=nullptr, bool fStartAtFilename=false)
Definition: C4Group.cpp:1780
C4GroupEntry FolderSearchEntry
Definition: C4Group.cpp:440
bool HasPackedMother() const
Definition: C4Group.cpp:2011
int UncompressedFileSize(const char *szFilename)
Definition: CStdFile.cpp:326
int EntryOffset
Definition: C4Group.cpp:431
bool Open(const char *szFileName, bool fCompressed=false)
Definition: CStdFile.cpp:99
bool NoSort
Definition: C4Group.cpp:447
bool EraseItem(const char *szItemName)
Definition: StdFile.cpp:819
bool WildcardMatch(const char *szWildcard, const char *szString)
Definition: StdFile.cpp:384
bool FileExists(const char *szFilename)
Definition: StdFile.cpp:439
Definition: C4Group.h:115
int ForEachFile(const char *szDirName, bool(*fnCallback)(const char *))
Definition: StdFile.cpp:1053
bool MoveItem(const char *szSource, const char *szTarget)
Definition: StdFile.cpp:859
void MemScramble(BYTE *bypBuffer, int iSize)
Definition: C4Group.cpp:390
#define C4FLS_Player
Definition: C4Components.h:193
C4GroupEntry * FirstEntry
Definition: C4Group.cpp:433
const int32_t C4GroupSwapThreshold
Definition: C4Group.h:60
bool ExtractEntry(const char *szFilename, const char *szExtractTo=nullptr)
Definition: C4Group.cpp:1518
bool Add(const char *szFile, const char *szAddAs)
Definition: C4Group.cpp:1316
#define C4CFN_ScenarioFiles
Definition: C4Components.h:174
void C4Group_SetTempPath(const char *szPath)
Definition: C4Group.cpp:74
#define C4CFN_DefFiles
Definition: C4Components.h:166
bool IsPacked() const
Definition: C4Group.cpp:2009
size_t EntrySize(const char *szWildCard=nullptr)
Definition: C4Group.cpp:1862
size_t iCurrFileSize
Definition: C4Group.cpp:427
size_t getLength() const
Definition: StdBuf.h:453
void ResetSearch(bool reload_contents=false)
Definition: C4Group.cpp:1013
void SetLength(size_t iLength)
Definition: StdBuf.h:517
bool TruncatePath(char *szPath)
Definition: StdFile.cpp:250
bool Sort(const char *szSortList)
Definition: C4Group.cpp:1963
EntryStatus Status
Definition: C4Group.h:121
bool C4Group_TestIgnore(const char *szFilename)
Definition: C4Group.cpp:85
std::enable_if< std::is_pod< T >::value >::type ZeroMem(T *lpMem, size_t dwSize)
Definition: Standard.h:63
void MakeTempFilename(char *szFilename)
Definition: StdFile.cpp:333
char DiskPath[_MAX_PATH+1]
Definition: C4Group.h:120
bool ExclusiveChild
Definition: C4Group.cpp:423
void Clear()
Definition: C4Group.cpp:898
#define C4CFN_System
Definition: C4Components.h:29
bool ItemExists(const char *szItemName)
Definition: StdFile.h:75
bool C4Group_DeleteItem(const char *szItem, bool fRecycle)
Definition: C4Group.cpp:194
bool Extract(const char *szFiles, const char *szExtractTo=nullptr, const char *szExclude=nullptr)
Definition: C4Group.cpp:1471
const char ** C4Group_SortList
Definition: C4Group.cpp:61
#define DirectorySeparator
bool C4Group_ReadFile(const char *szFile, char **pData, size_t *iSize)
Definition: C4Group.cpp:366
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:253
bool WildcardListMatch(const char *szWildcardList, const char *szString)
Definition: StdFile.cpp:361
bool StdOutput
Definition: C4Group.cpp:443
#define C4FLS_Material
Definition: C4Components.h:196
void Copy()
Definition: StdBuf.h:475
int Entries
Definition: C4Group.h:88
int FilePtr
Definition: C4Group.cpp:429
bool Advance(int iOffset) override
Definition: C4Group.cpp:1105
bool OpenAsChild(C4Group *pMother, const char *szEntryName, bool fExclusive=false, bool fCreate=false)
Definition: C4Group.cpp:1585
void SCopyUntil(const char *szSource, char *sTarget, char cUntil, int iMaxL, int iIndex)
Definition: Standard.cpp:138
void * getMData()
Definition: StdBuf.h:108
bool C4Group_PackDirectory(const char *szFilename)
Definition: C4Group.cpp:284
C4Group()
Definition: C4Group.cpp:477
void SReplaceChar(char *str, char fc, char tc)
Definition: Standard.cpp:318
Definition: C4Group.h:106
char C4Group_TempPath[_MAX_PATH+1]
Definition: C4Group.cpp:59
int iSize
Definition: TstC4NetIO.cpp:35
DirectoryIterator FolderSearch
Definition: C4Group.cpp:439
bool RenameFile(const char *szFilename, const char *szNewFilename)
Definition: StdFile.cpp:555
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:277
BYTE * bpMemBuf
Definition: C4Group.h:126
BYTE * pInMemEntry
Definition: C4Group.cpp:434
#define C4FLS_Object
Definition: C4Components.h:194
bool Read(void *pBuffer, size_t iSize) override
Definition: C4Group.cpp:1125