OpenClonk
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_LEN]="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 (*callback)(const char *, int))
65 {
66  C4Group_ProcessCallback = callback;
67 }
68 
69 void C4Group_SetSortList(const char **sort_list)
70 {
71  C4Group_SortList = sort_list;
72 }
73 
74 void C4Group_SetTempPath(const char *path)
75 {
76  if (!path || !path[0])
77  {
78  C4Group_TempPath[0] = 0;
79  }
80  else
81  {
84  }
85 }
86 
87 const char *C4Group_GetTempPath()
88 {
89  return C4Group_TempPath;
90 }
91 
92 bool C4Group_TestIgnore(const char *filename)
93 {
94  if (!*filename)
95  {
96  return true; //poke out empty strings
97  }
98  const char* name = GetFilename(filename);
99  return *name == '.' //no hidden files and the directory itself
100  || name[strlen(name) - 1] == '~' //no temp files
101  || SIsModule(C4Group_Ignore, name); //not on Blacklist
102 }
103 
104 bool C4Group_IsGroup(const char *filename)
105 {
106  C4Group group;
107  if (group.Open(filename))
108  {
109  group.Close();
110  return true;
111  }
112  return false;
113 }
114 
115 bool C4Group_CopyItem(const char *source, const char *target, bool no_sorting, bool reset_attributes)
116 {
117  // Parameter check
118  if (!source || !target || !source[0] || !target[0])
119  {
120  return false;
121  }
122  char target_path[_MAX_PATH_LEN];
123  SCopy(target, target_path, _MAX_PATH);
124 
125  // Backslash terminator indicates target is a path only (append filename)
126  if (target_path[SLen(target_path) - 1] == DirectorySeparator)
127  {
128  SAppend(GetFilename(source), target_path);
129  }
130 
131  // Check for identical source and target
132  // Note that attributes aren't reset here
133  if (ItemIdentical(source, target_path))
134  {
135  return true;
136  }
137 
138  // Source and target are simple items
139  if (ItemExists(source) && CreateItem(target_path))
140  {
141  return CopyItem(source, target_path, reset_attributes);
142  }
143 
144  // For items within groups, attribute resetting isn't needed, because packing/unpacking will kill all
145  // attributes anyway
146 
147  // Source & target
148  C4Group source_parent;
149  C4Group target_parent;
150  char source_parent_path[_MAX_PATH_LEN];
151  char target_parent_path[_MAX_PATH_LEN];
152  GetParentPath(source, source_parent_path);
153  GetParentPath(target_path, target_parent_path);
154 
155  // Temp filename
156  char temp_filename[_MAX_PATH_LEN];
157  SCopy(C4Group_TempPath, temp_filename, _MAX_PATH);
158  SAppend(GetFilename(source), temp_filename);
159  MakeTempFilename(temp_filename);
160 
161  // Extract source to temp file
162  if ( !source_parent.Open(source_parent_path)
163  || !source_parent.Extract(GetFilename(source), temp_filename)
164  || !source_parent.Close())
165  {
166  return false;
167  }
168 
169  // Move temp file to target
170  if ( !target_parent.Open(target_parent_path)
171  || !target_parent.SetNoSort(no_sorting)
172  || !target_parent.Move(temp_filename, GetFilename(target_path))
173  || !target_parent.Close())
174  {
175  EraseItem(temp_filename);
176  return false;
177  }
178 
179  return true;
180 }
181 
182 bool C4Group_MoveItem(const char *source, const char *target, bool no_sorting)
183 {
184  // Parameter check
185  if (!source || !target || !source[0] || !target[0])
186  {
187  return false;
188  }
189  char target_path[_MAX_PATH_LEN];
190  SCopy(target, target_path, _MAX_PATH);
191 
192  // Backslash terminator indicates target is a path only (append filename)
193  if (target_path[SLen(target_path)-1] == DirectorySeparator)
194  {
195  SAppend(GetFilename(source), target_path);
196  }
197 
198  // Check for identical source and target
199  if (ItemIdentical(source, target_path))
200  {
201  return true;
202  }
203 
204  // Source and target are simple items
205  if (ItemExists(source) && CreateItem(target_path))
206  {
207  // erase test file, because it may block moving a directory
208  EraseItem(target_path);
209  return MoveItem(source, target_path);
210  }
211 
212  // Source & target
213  C4Group source_parent;
214  C4Group target_parent;
215  char source_parent_path[_MAX_PATH_LEN];
216  char target_parent_path[_MAX_PATH_LEN];
217  GetParentPath(source, source_parent_path);
218  GetParentPath(target_path, target_parent_path);
219 
220  // Temp filename
221  char temp_filename[_MAX_PATH_LEN];
222  SCopy(C4Group_TempPath, temp_filename, _MAX_PATH);
223  SAppend(GetFilename(source),temp_filename);
224  MakeTempFilename(temp_filename);
225 
226  // Extract source to temp file
227  if ( !source_parent.Open(source_parent_path)
228  || !source_parent.Extract(GetFilename(source),temp_filename)
229  || !source_parent.Close())
230  {
231  return false;
232  }
233 
234  // Move temp file to target_path
235  if ( !target_parent.Open(target_parent_path)
236  || !target_parent.SetNoSort(no_sorting)
237  || !target_parent.Move(temp_filename, GetFilename(target_path))
238  || !target_parent.Close())
239  {
240  EraseItem(temp_filename);
241  return false;
242  }
243 
244  // Delete original file
245  if ( !source_parent.Open(source_parent_path)
246  || !source_parent.DeleteEntry(GetFilename(source))
247  || !source_parent.Close() )
248  {
249  return false;
250  }
251 
252  return true;
253 }
254 
255 bool C4Group_DeleteItem(const char *item_name, bool do_recycle)
256 {
257  // Parameter check
258  if (!item_name || !item_name[0])
259  {
260  return false;
261  }
262 
263  // simple item?
264  if (ItemExists(item_name))
265  {
266  if (do_recycle)
267  {
268  return EraseItemSafe(item_name);
269  }
270  else
271  {
272  return EraseItem(item_name);
273  }
274  }
275 
276  // delete from mother
277  C4Group parent;
278  char parent_path[_MAX_PATH_LEN];
279  GetParentPath(item_name, parent_path);
280 
281  // Delete original file
282  if ( !parent.Open(parent_path)
283  || !parent.DeleteEntry(GetFilename(item_name), do_recycle)
284  || !parent.Close() )
285  {
286  return false;
287  }
288 
289  return true;
290 }
291 
292 bool C4Group_PackDirectoryTo(const char *filename, const char *to_filename)
293 {
294  // Check file type
295  if (!DirectoryExists(filename))
296  {
297  return false;
298  }
299  // Target must not exist
300  if (FileExists(to_filename))
301  {
302  return false;
303  }
304  // Ignore
305  if (C4Group_TestIgnore(filename))
306  {
307  return true;
308  }
309  // Process message
311  {
312  C4Group_ProcessCallback(filename, 0);
313  }
314  // Create group file
315  C4Group group;
316  if (!group.Open(to_filename, true))
317  {
318  return false;
319  }
320  // Add folder contents to group
321  DirectoryIterator i(filename);
322  for (; *i; i++)
323  {
324  // Ignore
325  if (C4Group_TestIgnore(*i))
326  {
327  continue;
328  }
329  // Must pack?
330  if (DirectoryExists(*i))
331  {
332  // Find temporary filename
333  char temp_filename[_MAX_PATH_LEN];
334  // At C4Group temp path
335  SCopy(C4Group_TempPath, temp_filename, _MAX_PATH);
336  SAppend(GetFilename(*i), temp_filename, _MAX_PATH);
337  // Make temporary filename
338  MakeTempFilename(temp_filename);
339  // Pack and move into group
340  if (!C4Group_PackDirectoryTo(*i, temp_filename))
341  {
342  break;
343  }
344  if (!group.Move(temp_filename, GetFilename(*i)))
345  {
346  EraseFile(temp_filename);
347  break;
348  }
349  }
350  // Add normally otherwise
351  else if (!group.Add(*i, nullptr))
352  {
353  break;
354  }
355  }
356  // Something went wrong?
357  if (*i)
358  {
359  // Close group and remove temporary file
360  group.Close();
361  EraseItem(to_filename);
362  return false;
363  }
364  // Reset iterator
365  i.Reset();
366  // Close group
367  group.SortByList(C4Group_SortList, filename);
368  return group.Close();
369 }
370 
371 bool C4Group_PackDirectory(const char *filename)
372 {
373  // Make temporary filename
374  char temp_filename[_MAX_PATH_LEN];
375  SCopy(filename, temp_filename, _MAX_PATH);
376  MakeTempFilename(temp_filename);
377 
378  // Pack directory
379  if (!C4Group_PackDirectoryTo(filename, temp_filename))
380  {
381  return false;
382  }
383 
384  // Rename folder
385  char temp_filename2[_MAX_PATH_LEN];
386  SCopy(filename, temp_filename2, _MAX_PATH);
387  MakeTempFilename(temp_filename2);
388  if (!RenameFile(filename, temp_filename2))
389  {
390  return false;
391  }
392  // Name group file
393  if (!RenameFile(temp_filename, filename))
394  {
395  return false;
396  }
397  // Last: Delete folder
398  return EraseDirectory(temp_filename2);
399 }
400 
401 bool C4Group_UnpackDirectory(const char *filename)
402 {
403  // Already unpacked: success
404  if (DirectoryExists(filename))
405  {
406  return true;
407  }
408 
409  // Not a real file: unpack mother directory first
410  char parent_filename[_MAX_PATH_LEN];
411  if (!FileExists(filename)
412  && GetParentPath(filename, parent_filename)
413  && !C4Group_UnpackDirectory(parent_filename))
414  {
415  return false;
416  }
417 
418  // Open group
419  C4Group group;
420  if (!group.Open(filename))
421  {
422  return false;
423  }
424 
425  // Process message
427  {
428  C4Group_ProcessCallback(filename, 0);
429  }
430 
431  // Create target directory
432  char target_directory[_MAX_PATH_LEN];
433  SCopy(filename, target_directory, _MAX_PATH);
434  MakeTempFilename(target_directory);
435  if (!CreatePath(target_directory))
436  {
437  group.Close();
438  return false;
439  }
440 
441  // Extract files to folder
442  if (!group.Extract("*",target_directory))
443  {
444  group.Close();
445  return false;
446  }
447 
448  // Close group
449  group.Close();
450 
451  // Rename group file
452  char temp_filename[_MAX_PATH_LEN];
453  SCopy(filename, temp_filename, _MAX_PATH);
454  MakeTempFilename(temp_filename);
455  if (!RenameFile(filename, temp_filename))
456  {
457  return false;
458  }
459 
460  // Rename target directory
461  if (!RenameFile(target_directory, filename))
462  {
463  return false;
464  }
465 
466  // Delete renamed group file
467  return EraseItem(temp_filename);
468 }
469 
470 bool C4Group_ExplodeDirectory(const char *filename)
471 {
472  // Ignore
473  if (C4Group_TestIgnore(filename))
474  {
475  return true;
476  }
477 
478  // Unpack this directory
479  if (!C4Group_UnpackDirectory(filename))
480  {
481  return false;
482  }
483 
484  // Explode all children
486 
487  // Success
488  return true;
489 }
490 
491 bool C4Group_ReadFile(const char *filename, char **data, size_t *size)
492 {
493  // security
494  if (!filename || !data)
495  {
496  return false;
497  }
498 
499  // get mother path & file name
500  char parent_path[_MAX_PATH_LEN];
501  GetParentPath(filename, parent_path);
502  const char *entry_name = GetFilename(filename);
503 
504  // open parent group
505  C4Group parent_group;
506  if (!parent_group.Open(parent_path))
507  {
508  return false;
509  }
510  // access the file
511  size_t filesize;
512  if (!parent_group.AccessEntry(entry_name, &filesize))
513  {
514  return false;
515  }
516  // create buffer
517  *data = new char [filesize];
518  // read it
519  if (!parent_group.Read(*data, filesize))
520  {
521  delete [] *data;
522  *data = nullptr;
523  return false;
524  }
525  // ok
526  parent_group.Close();
527  if (size)
528  {
529  *size = filesize;
530  }
531  return true;
532 }
533 
534 void MemScramble(BYTE *buffer, int size)
535 {
536  // XOR deface
537  for (int cnt = 0; cnt < size; cnt++)
538  {
539  buffer[cnt] ^= 237;
540  }
541  // BYTE swap
542  for (int cnt = 0; cnt + 2 < size; cnt += 3)
543  {
544  BYTE temp = buffer[cnt];
545  buffer[cnt] = buffer[cnt + 2];
546  buffer[cnt + 2] = temp;
547  }
548 }
549 
550 //---------------------------------- C4Group ---------------------------------------------
551 
553 {
555  {
556  // No source; C4Group inactive
558  // C4Group backed by archive file
560  // C4Group backed by raw file system
562  };
563 
565  std::string FileName;
566  // Parent status
567  C4Group *Mother = nullptr;
568  bool ExclusiveChild = false;
569  // File & Folder
572  size_t iCurrFileSize = 0; // size of last accessed file
573  // File only
574  int FilePtr = 0;
575  int MotherOffset = 0;
576  int EntryOffset = 0;
577  bool Modified = false;
579  BYTE *pInMemEntry = nullptr;
580  size_t iInMemEntrySize = 0; // for reading from entries prefetched into memory
581 #ifdef _DEBUG
582  StdStrBuf sPrevAccessedEntry;
583 #endif
584  // Folder only
588 
589  bool LogToStdOutput = false;
590  bool(*ProcessCallback)(const char *, int) = nullptr;
591  std::string ErrorString;
592 
593  bool NoSort = false; // If this flag is set, all entries will be marked NoSort in AddEntry
594 };
595 
597 {
598  if (HoldBuffer)
599  if (MemoryBuffer)
600  {
601  if (BufferIsStdbuf)
603  else
604  delete [] MemoryBuffer;
605  }
606 }
607 
608 void C4GroupEntry::Set(const DirectoryIterator &directories, const char * path)
609 {
610  InplaceReconstruct(this);
611 
612  SCopy(GetFilename(*directories),FileName, _MAX_FNAME);
613  SCopy(*directories, DiskPath, _MAX_PATH-1);
614  Size = directories.GetFileSize();
616  Packed = false;
617  ChildGroup = false;
618  // Notice folder entries are not checked for ChildGroup status.
619  // This would cause extreme performance loss and be good for
620  // use in entry list display only.
621 }
622 
624  : p(new P)
625 {}
626 
627 void C4Group::Init()
628 {
629  auto new_p = std::make_unique<P>();
630  // Copy persistent variables
631  new_p->ProcessCallback = p->ProcessCallback;
632  new_p->NoSort = p->NoSort;
633  new_p->LogToStdOutput = p->LogToStdOutput;
634 
636  p = std::move(new_p);
637 }
638 
640 {
641  Clear();
642 }
643 
644 bool C4Group::Error(const char *status_message)
645 {
646  p->ErrorString = status_message;
647  return false;
648 }
649 
650 const char *C4Group::GetError()
651 {
652  return p->ErrorString.c_str();
653 }
654 
655 void C4Group::SetStdOutput(bool log_status)
656 {
657  p->LogToStdOutput = log_status;
658 }
659 
660 bool C4Group::Open(const char *group_name, bool do_create)
661 {
662  if (!group_name)
663  {
664  return Error("Open: Null filename");
665  }
666  if (!group_name[0])
667  {
668  return Error("Open: Empty filename");
669  }
670 
671  char group_name_native[_MAX_FNAME];
672  SCopy(group_name, group_name_native, _MAX_FNAME);
673  // Convert to native path
675 
676  // Real reference
677  if (FileExists(group_name_native))
678  {
679  Init();
680  return OpenReal(group_name_native);
681  }
682 
683  // If requested, try creating a new group file
684  if (do_create)
685  {
686  CStdFile temp;
687  if (temp.Create(group_name_native, false))
688  {
689  // Temporary file has been created
690  temp.Close();
691  Init();
692  p->SourceType = P::ST_Packed; p->Modified = true;
693  p->FileName = group_name_native;
694  return true;
695  }
696  }
697 
698  // While not a real reference (child group), trace back to mother group or folder.
699  // Open mother and child in exclusive mode.
700  char group_name_real[_MAX_FNAME];
701  SCopy(group_name_native, group_name_real, _MAX_FNAME);
702  do
703  {
704  if (!TruncatePath(group_name_real))
705  {
706  return Error(FormatString(R"(Open("%s"): File not found)", group_name_native).getData());
707  }
708  }
709  while (!FileExists(group_name_real));
710 
711  // Open mother and child in exclusive mode
712  C4Group *mother = new C4Group;
713  mother->SetStdOutput(p->LogToStdOutput);
714  if (!mother->Open(group_name_real))
715  {
716  Clear();
717  Error(mother->GetError());
718  delete mother;
719  return false;
720  }
721  if (!OpenAsChild(mother, group_name_native+SLen(group_name_real) + 1, true))
722  {
723  Clear();
724  return false;
725  }
726 
727  // Success
728  return true;
729 }
730 
731 bool C4Group::OpenReal(const char *filename)
732 {
733  // Get original filename
734  if (!filename)
735  {
736  return false;
737  }
738  p->FileName = filename;
739 
740  // Folder
741  if (DirectoryExists(GetName()))
742  {
743  // Ignore
744  if (C4Group_TestIgnore(filename))
745  {
746  return Error(FormatString("OpenReal: filename '%s' ignored", filename).getData());
747  }
748  // OpenReal: Simply set status and return
749  p->SourceType = P::ST_Unpacked;
750  ResetSearch();
751  // Success
752  return true;
753  }
754 
755  // File: Try reading header and entries
756  if (OpenRealGrpFile())
757  {
758  p->SourceType = P::ST_Packed;
759  ResetSearch();
760  return true;
761  }
762  else
763  {
764  return false;
765  }
766 
767  return Error("OpenReal: Not a valid group");
768 }
769 
770 bool C4Group::OpenRealGrpFile()
771 {
772 
773  // Open StdFile
774  if (!p->StdFile.Open(GetName(), true))
775  {
776  return Error("OpenRealGrpFile: Cannot open standard file");
777  }
778 
779  // Read header
780  if (!p->StdFile.Read((BYTE*)&Head, sizeof(C4GroupHeader)))
781  {
782  return Error("OpenRealGrpFile: Error reading header");
783  }
784  MemScramble((BYTE*)&Head, sizeof(C4GroupHeader));
785  p->EntryOffset += sizeof(C4GroupHeader);
786 
787  // Check Header
788  if (!SEqual(Head.Id, C4GroupFileID)
789  || (Head.Ver1 != C4GroupFileVer1)
790  || (Head.Ver2 > C4GroupFileVer2))
791  {
792  return Error("OpenRealGrpFile: Invalid header");
793  }
794 
795  // Read Entries
796  int file_entries = Head.Entries;
797  Head.Entries = 0; // Reset, will be recounted by AddEntry
798  C4GroupEntryCore corebuf;
799  for (int cnt = 0; cnt<file_entries; cnt++)
800  {
801  if (!p->StdFile.Read((BYTE*)&corebuf, sizeof(C4GroupEntryCore)))
802  {
803  return Error("OpenRealGrpFile: Error reading entries");
804  }
805  // New C4Groups have filenames in UTF-8
806  StdStrBuf entryname(corebuf.FileName);
807  entryname.EnsureUnicode();
808  // Prevent overwriting of user stuff by malicuous groups
809  C4InVal::ValidateFilename(const_cast<char *>(entryname.getData()),entryname.getLength());
810  p->EntryOffset+=sizeof(C4GroupEntryCore);
811  if (!AddEntry(C4GroupEntry::C4GRES_InGroup,
812  !!corebuf.ChildGroup,
813  corebuf.FileName,
814  corebuf.Size,
815  entryname.getData(),
816  nullptr,
817  false,
818  false,
819  !!corebuf.Executable))
820  {
821  return Error("OpenRealGrpFile: Cannot add entry");
822  }
823  }
824 
825  return true;
826 }
827 
828 bool C4Group::AddEntry(C4GroupEntry::EntryStatus status,
829  bool add_as_child,
830  const char *filename,
831  long size,
832  const char *entry_name,
833  BYTE *buffer,
834  bool delete_on_disk,
835  bool hold_buffer,
836  bool is_executable,
837  bool buffer_is_stdbuf)
838 {
839 
840  // Folder: add file to folder immediately
841  if (p->SourceType == P::ST_Unpacked)
842  {
843  // Close open StdFile
844  p->StdFile.Close();
845 
846  // Get path to target folder file
847  char target_folder_name[_MAX_FNAME];
848  SCopy(GetName(),target_folder_name, _MAX_FNAME);
849  AppendBackslash(target_folder_name);
850  if (entry_name)
851  {
852  SAppend(entry_name, target_folder_name);
853  }
854  else
855  {
856  SAppend(GetFilename(filename), target_folder_name);
857  }
858 
859  switch (status)
860  {
861 
862  case C4GroupEntry::C4GRES_OnDisk: // Copy/move file to folder
863  if (!CopyItem(filename, target_folder_name))
864  {
865  return false;
866  }
867  // Reset directory iterator to reflect new file
868  ResetSearch(true);
869  if (delete_on_disk && !EraseItem(filename))
870  {
871  return false;
872  }
873  return true;
874 
875  case C4GroupEntry::C4GRES_InMemory: { // Save buffer to file in folder
876  CStdFile file;
877  bool okay = false;
878  if (file.Create(target_folder_name, !!add_as_child))
879  {
880  okay = !!file.Write(buffer, size);
881  }
882  file.Close();
883  ResetSearch(true);
884 
885  if (hold_buffer)
886  {
887  if (buffer_is_stdbuf) StdBuf::DeletePointer(buffer);
888  else delete [] buffer;
889  }
890 
891  return okay;
892  }
893 
894  default: break; // InGrp & Deleted ignored
895  }
896 
897  return Error("Add to folder: Invalid request");
898  }
899 
900 
901  // Group file: add to virtual entry list
902 
903  C4GroupEntry *next;
904  C4GroupEntry *last;
905  C4GroupEntry *current;
906 
907  // Delete existing entries of same name
908  current = GetEntry(GetFilename(entry_name ? entry_name : filename));
909  if (current)
910  {
912  Head.Entries--;
913  }
914 
915  // Allocate memory for new entry
916  next = new C4GroupEntry;
917 
918  // Find end of list
919  for (last = p->FirstEntry; last && last->Next; last = last->Next) {}
920 
921  // Init entry core data
922  if (entry_name) SCopy(entry_name, next->FileName, _MAX_FNAME);
923  else SCopy(GetFilename(filename),next->FileName, _MAX_FNAME);
924 
925  next->Size = size;
926  next->ChildGroup = add_as_child;
927  next->Offset = 0;
928  next->Executable = is_executable;
929  next->DeleteOnDisk = delete_on_disk;
930  next->HoldBuffer = hold_buffer;
931  next->BufferIsStdbuf = buffer_is_stdbuf;
932  if (last)
933  {
934  next->Offset = last->Offset + last->Size;
935  }
936 
937  // Init list entry data
938  SCopy(filename, next->DiskPath, _MAX_FNAME);
939  next->Status = status;
940  next->MemoryBuffer = buffer;
941  next->Next = nullptr;
942  next->NoSort = p->NoSort;
943 
944  // Append entry to list
945  if (last) last->Next = next;
946  else p->FirstEntry = next;
947 
948  // Increase virtual file count of group
949  Head.Entries++;
950 
951  return true;
952 }
953 
954 C4GroupEntry* C4Group::GetEntry(const char *entry_name)
955 {
956  if (p->SourceType == P::ST_Unpacked)
957  {
958  return nullptr;
959  }
960  for (C4GroupEntry *entry = p->FirstEntry; entry; entry = entry->Next)
961  {
962  if (entry->Status != C4GroupEntry::C4GRES_Deleted
963  && WildcardMatch(entry_name, entry->FileName))
964  {
965  return entry;
966  }
967  }
968  return nullptr;
969 }
970 
972 {
973  bool rewrite = false;
974 
975  if (p->SourceType == P::ST_None)
976  {
977  return false;
978  }
979 
980  // Folder: just close
981  if (p->SourceType == P::ST_Unpacked)
982  {
983  CloseExclusiveMother();
984  Clear();
985  return true;
986  }
987 
988  // Rewrite check
989  for (C4GroupEntry *entry = p->FirstEntry; entry; entry = entry->Next)
990  {
991  if (entry->Status != C4GroupEntry::C4GRES_InGroup)
992  {
993  rewrite = true;
994  }
995  }
996  if (p->Modified)
997  {
998  rewrite = true;
999  }
1000 
1001  // No rewrite: just close
1002  if (!rewrite)
1003  {
1004  CloseExclusiveMother();
1005  Clear();
1006  return true;
1007  }
1008 
1009  if (p->LogToStdOutput)
1010  {
1011  printf("Writing group file...\n");
1012  }
1013 
1014  // Set new version
1017 
1018  // Automatic sort
1020 
1021  // Save group contents to disk
1022  bool success = Save(false);
1023 
1024  // Close files
1025  CloseExclusiveMother();
1026  Clear();
1027 
1028  return !!success;
1029 }
1030 
1031 bool C4Group::Save(bool reopen)
1032 {
1033  char temp_filename[_MAX_FNAME+1];
1034  char group_filename[_MAX_FNAME+1];
1035 
1036  // Create temporary core list with new actual offsets to be saved
1037  int32_t contents_size = 0;
1038  C4GroupEntryCore *save_core = new C4GroupEntryCore[Head.Entries];
1039  int core_index = 0;
1040  for (C4GroupEntry *entry = p->FirstEntry; entry; entry = entry->Next)
1041  {
1042  if (entry->Status != C4GroupEntry::C4GRES_Deleted)
1043  {
1044  save_core[core_index]=(C4GroupEntryCore)*entry;
1045  // Make actual offset
1046  save_core[core_index].Offset = contents_size;
1047  contents_size += entry->Size;
1048  core_index++;
1049  }
1050  }
1051 
1052  // Hold contents in memory?
1053  bool hold_in_memory = !reopen && p->Mother && contents_size < C4GroupSwapThreshold;
1054  if (!hold_in_memory)
1055  {
1056  // Create target temp file (in temp directory!)
1057  SCopy(GetName(), group_filename, _MAX_FNAME);
1058  if (C4Group_TempPath[0])
1059  {
1060  SCopy(C4Group_TempPath, temp_filename, _MAX_FNAME);
1061  SAppend(GetFilename(GetName()),temp_filename, _MAX_FNAME);
1062  }
1063  else
1064  {
1065  SCopy(GetName(), temp_filename, _MAX_FNAME);
1066  }
1067  MakeTempFilename(temp_filename);
1068  // (Temp file must not have the same name as the group.)
1069  if (SEqual(temp_filename, group_filename))
1070  {
1071  SAppend(".tmp", temp_filename); // Add a second temp extension
1072  MakeTempFilename(temp_filename);
1073  }
1074  }
1075 
1076  // Create the new (temp) group file
1077  CStdFile temp_file;
1078  if (!temp_file.Create(temp_filename, true, false, hold_in_memory))
1079  {
1080  delete [] save_core;
1081  return Error("Close: ...");
1082  }
1083 
1084  // Save header and core list
1085  C4GroupHeader header_buffer = Head;
1086  MemScramble((BYTE*)&header_buffer, sizeof(C4GroupHeader));
1087  if (!temp_file.Write((BYTE*)&header_buffer, sizeof(C4GroupHeader))
1088  || !temp_file.Write((BYTE*)save_core, Head.Entries*sizeof(C4GroupEntryCore)))
1089  {
1090  temp_file.Close();
1091  delete [] save_core;
1092  return Error("Close: ...");
1093  }
1094  delete [] save_core;
1095 
1096  // Save Entries to temp file
1097  int total_size = 0;
1098  for (C4GroupEntry *entry = p->FirstEntry; entry; entry = entry->Next)
1099  {
1100  total_size += entry->Size;
1101  }
1102  int size_done = 0;
1103  for (C4GroupEntry *entry = p->FirstEntry; entry; entry = entry->Next)
1104  {
1105  if (AppendEntry2StdFile(entry, temp_file))
1106  {
1107  size_done += entry->Size;
1108  if (total_size && p->ProcessCallback)
1109  {
1110  p->ProcessCallback(entry->FileName, 100 * size_done / total_size);
1111  }
1112  }
1113  else
1114  {
1115  temp_file.Close();
1116  return false;
1117  }
1118  }
1119 
1120  // Write
1121  StdBuf *buffer;
1122  temp_file.Close(hold_in_memory ? &buffer : nullptr);
1123 
1124  // Child: move temp file to mother
1125  if (p->Mother)
1126  {
1127  if (hold_in_memory)
1128  {
1129  if (!p->Mother->Add(GetFilename(GetName()), *buffer, true, true))
1130  {
1131  delete buffer;
1132  CloseExclusiveMother();
1133  Clear();
1134  return Error("Close: Cannot move rewritten child data to mother");
1135  }
1136  delete buffer;
1137  }
1138  else
1139  {
1140  if (!p->Mother->Move(temp_filename, GetFilename(GetName())))
1141  {
1142  CloseExclusiveMother();
1143  Clear();
1144  return Error("Close: Cannot move rewritten child temp file to mother");
1145  }
1146  }
1147  Clear();
1148  return true;
1149  }
1150 
1151  // Clear (close file)
1152  Clear();
1153 
1154  // Delete old group file, rename new file
1155  if (!EraseFile(group_filename))
1156  {
1157  return Error("Close: Cannot erase temp file");
1158  }
1159  if (!RenameFile(temp_filename, group_filename))
1160  {
1161  return Error("Close: Cannot rename group file");
1162  }
1163 
1164  // Should reopen the file?
1165  if (reopen)
1166  {
1167  OpenReal(group_filename);
1168  }
1169 
1170  return true;
1171 }
1172 
1174 {
1175  if (p)
1176  {
1177  // Delete entries
1178  C4GroupEntry *next;
1179  while (p->FirstEntry)
1180  {
1181  next = p->FirstEntry->Next;
1182  delete p->FirstEntry;
1183  p->FirstEntry = next;
1184  }
1185  // Close std file
1186  p->StdFile.Close();
1187  // Delete mother
1188  if (p->Mother && p->ExclusiveChild)
1189  {
1190  delete p->Mother;
1191  p->Mother = nullptr;
1192  }
1193  }
1194  // Reset
1195  Init();
1196 }
1197 
1198 bool C4Group::AppendEntry2StdFile(C4GroupEntry *entry, CStdFile &target)
1199 {
1200  CStdFile source;
1201  BYTE buffer;
1202 
1203  switch (entry->Status)
1204  {
1205 
1206  case C4GroupEntry::C4GRES_InGroup: // Copy from group to std file
1207  if (!SetFilePtr(entry->Offset))
1208  return Error("AE2S: Cannot set file pointer");
1209  for (long current_size = entry->Size; current_size > 0; current_size--)
1210  {
1211  if (!Read(&buffer, 1))
1212  {
1213  return Error("AE2S: Cannot read entry from group file");
1214  }
1215  if (!target.Write(&buffer, 1))
1216  {
1217  return Error("AE2S: Cannot write to target file");
1218  }
1219  }
1220  break;
1221 
1222  case C4GroupEntry::C4GRES_OnDisk: // Copy/move from disk item to std file
1223  {
1224  char file_source[_MAX_FNAME_LEN];
1225  SCopy(entry->DiskPath, file_source, _MAX_FNAME);
1226 
1227  // Disk item is a directory
1228  if (DirectoryExists(entry->DiskPath))
1229  {
1230  return Error("AE2S: Cannot add directory to group file");
1231  }
1232 
1233  // Resort group if neccessary
1234  // (The group might be renamed by adding, forcing a resort)
1235  bool has_temp_file = false;
1236  if (entry->ChildGroup
1237  && !entry->NoSort
1238  && !SEqual(GetFilename(file_source), entry->FileName))
1239  {
1240  // copy group
1241  MakeTempFilename(file_source);
1242  if (!CopyItem(entry->DiskPath, file_source))
1243  {
1244  return Error("AE2S: Cannot copy item");
1245  }
1246  // open group and resort
1247  C4Group SortGrp;
1248  if (!SortGrp.Open(file_source))
1249  {
1250  return Error("AE2S: Cannot open group");
1251  }
1252  if (!SortGrp.SortByList(C4Group_SortList, entry->FileName))
1253  {
1254  return Error("AE2S: Cannot resort group");
1255  }
1256  has_temp_file = true;
1257  // close group (won't be saved if the sort didn't change)
1258  SortGrp.Close();
1259  }
1260 
1261  // Append disk source to target file
1262  if (!source.Open(file_source, !!entry->ChildGroup))
1263  {
1264  return Error("AE2S: Cannot open on-disk file");
1265  }
1266  for (long current_size = entry->Size; current_size > 0; current_size--)
1267  {
1268  if (!source.Read(&buffer, 1))
1269  {
1270  source.Close();
1271  return Error("AE2S: Cannot read on-disk file");
1272  }
1273  if (!target.Write(&buffer, 1))
1274  {
1275  source.Close();
1276  return Error("AE2S: Cannot write to target file");
1277  }
1278  }
1279  source.Close();
1280 
1281  // Erase temp file
1282  if (has_temp_file)
1283  {
1284  EraseItem(file_source);
1285  }
1286  // Erase disk source if requested
1287  if (entry->DeleteOnDisk)
1288  {
1289  EraseItem(entry->DiskPath);
1290  }
1291 
1292  break;
1293  }
1294 
1295  case C4GroupEntry::C4GRES_InMemory: // Copy from mem to std file
1296  if (!entry->MemoryBuffer)
1297  {
1298  return Error("AE2S: no buffer");
1299  }
1300  if (!target.Write(entry->MemoryBuffer, entry->Size))
1301  {
1302  return Error("AE2S: writing error");
1303  }
1304  break;
1305 
1306  case C4GroupEntry::C4GRES_Deleted: // Don't save
1307  break;
1308 
1309  default: // Unknown file status
1310  return Error("AE2S: Unknown file status");
1311  }
1312 
1313  return true;
1314 }
1315 
1316 void C4Group::ResetSearch(bool reload_contents)
1317 {
1318  switch (p->SourceType)
1319  {
1320  case P::ST_Unpacked:
1321  p->SearchPtr = nullptr;
1322  p->FolderSearch.Reset(GetName(), reload_contents);
1323  if (*p->FolderSearch)
1324  {
1325  p->FolderSearchEntry.Set(p->FolderSearch, GetName());
1326  p->SearchPtr = &p->FolderSearchEntry;
1327  }
1328  break;
1329  case P::ST_Packed:
1330  p->SearchPtr = p->FirstEntry;
1331  break;
1332  default: break; // InGrp & Deleted ignored
1333  }
1334 }
1335 
1336 C4GroupEntry* C4Group::GetNextFolderEntry()
1337 {
1338  if (*++p->FolderSearch)
1339  {
1340  p->FolderSearchEntry.Set(p->FolderSearch, GetName());
1341  return &p->FolderSearchEntry;
1342  }
1343  else
1344  {
1345  return nullptr;
1346  }
1347 }
1348 
1349 C4GroupEntry* C4Group::SearchNextEntry(const char *entry_name)
1350 {
1351  // Wildcard "*.*" is expected to find all files: substitute correct wildcard "*"
1352  if (SEqual(entry_name, "*.*"))
1353  {
1354  entry_name = "*";
1355  }
1356  // Search by group type
1357  C4GroupEntry *pEntry;
1358  switch (p->SourceType)
1359  {
1360  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1361  case P::ST_Packed:
1362  for (pEntry = p->SearchPtr; pEntry; pEntry = pEntry->Next)
1363  if (pEntry->Status != C4GroupEntry::C4GRES_Deleted)
1364  if (WildcardMatch(entry_name, pEntry->FileName))
1365  {
1366  p->SearchPtr = pEntry->Next;
1367  return pEntry;
1368  }
1369  break;
1370  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1371  case P::ST_Unpacked:
1372  for (pEntry = p->SearchPtr; pEntry; pEntry = GetNextFolderEntry())
1373  if (WildcardMatch(entry_name, pEntry->FileName))
1374  if (!C4Group_TestIgnore(pEntry->FileName))
1375  {
1376  p->LastFolderSearchEntry=(*pEntry);
1377  pEntry=&p->LastFolderSearchEntry;
1378  p->SearchPtr = GetNextFolderEntry();
1379  return pEntry;
1380  }
1381  break;
1382  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1383  default: break; // InGrp & Deleted ignored
1384  }
1385  // No entry found: reset search pointer
1386  p->SearchPtr = nullptr;
1387  return nullptr;
1388 }
1389 
1390 bool C4Group::SetFilePtr(int offset)
1391 {
1392 
1393  if (p->SourceType == P::ST_Unpacked)
1394  return Error("SetFilePtr not implemented for Folders");
1395 
1396  // ensure mother is at correct pos
1397  if (p->Mother) p->Mother->EnsureChildFilePtr(this);
1398 
1399  // Rewind if necessary
1400  if (p->FilePtr>offset)
1401  if (!RewindFilePtr()) return false;
1402 
1403  // Advance to target pointer
1404  if (p->FilePtr<offset)
1405  if (!AdvanceFilePtr(offset- p->FilePtr)) return false;
1406 
1407  return true;
1408 }
1409 
1410 bool C4Group::Advance(int offset)
1411 {
1412  assert(offset >= 0);
1413  // cached advance
1414  if (p->pInMemEntry)
1415  {
1416  if (p->iInMemEntrySize < size_t(offset)) return false;
1417  p->iInMemEntrySize -= offset;
1418  p->pInMemEntry += offset;
1419  return true;
1420  }
1421  // uncached advance
1422  if (p->SourceType == P::ST_Unpacked) return !!p->StdFile.Advance(offset);
1423  // FIXME: reading the file one byte at a time sounds just slow.
1424  BYTE buf;
1425  for (; offset>0; offset--)
1426  if (!Read(&buf, 1)) return false;
1427  return true;
1428 }
1429 
1430 bool C4Group::Read(void *buffer, size_t size)
1431 {
1432  // Access cached entry from memory?
1433  if (p->pInMemEntry)
1434  {
1435  if (p->iInMemEntrySize < size) return Error("ReadCached:");
1436  memcpy(buffer, p->pInMemEntry, size);
1437  p->iInMemEntrySize -= size;
1438  p->pInMemEntry += size;
1439  return true;
1440  }
1441  // Not cached. Read from file.
1442  switch (p->SourceType)
1443  {
1444  case P::ST_Packed:
1445  // Child group: read from mother group
1446  if (p->Mother)
1447  {
1448  if (!p->Mother->Read(buffer, size))
1449  { RewindFilePtr(); return Error("Read:"); }
1450  }
1451  // Regular group: read from standard file
1452  else
1453  {
1454  if (!p->StdFile.Read(buffer, size))
1455  { RewindFilePtr(); return Error("Read:"); }
1456  }
1457  p->FilePtr+=size;
1458  break;
1459  case P::ST_Unpacked:
1460  if (!p->StdFile.Read(buffer, size)) return Error("Read: Error reading from folder contents");
1461  break;
1462  default: break; // InGrp & Deleted ignored
1463  }
1464 
1465  return true;
1466 }
1467 
1468 bool C4Group::AdvanceFilePtr(int offset)
1469 {
1470  // Child group file: pass command to mother
1471  if ((p->SourceType == P::ST_Packed) && p->Mother)
1472  {
1473 
1474  // Ensure mother file ptr for it may have been moved by foreign access to mother
1475  if (!p->Mother->EnsureChildFilePtr(this))
1476  return false;
1477 
1478  if (!p->Mother->AdvanceFilePtr(offset))
1479  return false;
1480 
1481  }
1482  // Regular group
1483  else if (p->SourceType == P::ST_Packed)
1484  {
1485  if (!p->StdFile.Advance(offset))
1486  return false;
1487  }
1488  // Open folder
1489  else
1490  {
1491  if (!p->StdFile.Advance(offset))
1492  return false;
1493  }
1494 
1495  // Advanced
1496  p->FilePtr+=offset;
1497 
1498  return true;
1499 }
1500 
1501 bool C4Group::RewindFilePtr()
1502 {
1503 
1504 #ifdef _DEBUG
1505  if (szCurrAccessedEntry && !iC4GroupRewindFilePtrNoWarn)
1506  {
1507  LogF("C4Group::RewindFilePtr() for %s (%s) after %s", szCurrAccessedEntry ? szCurrAccessedEntry : "???", GetName(), p->sPrevAccessedEntry.getLength() ? p->sPrevAccessedEntry.getData() : "???");
1508  szCurrAccessedEntry = nullptr;
1509  }
1510 #endif
1511 
1512  // Child group file: pass command to mother
1513  if ((p->SourceType == P::ST_Packed) && p->Mother)
1514  {
1515  if (!p->Mother->SetFilePtr2Entry(GetName(),true)) // Set to group file start
1516  return false;
1517  if (!p->Mother->AdvanceFilePtr(p->EntryOffset)) // Advance data offset
1518  return false;
1519  }
1520  // Regular group or open folder: rewind standard file
1521  else
1522  {
1523  if (!p->StdFile.Rewind()) // Set to group file start
1524  return false;
1525  if (!p->StdFile.Advance(p->EntryOffset)) // Advance data offset
1526  return false;
1527  }
1528 
1529  p->FilePtr = 0;
1530 
1531  return true;
1532 }
1533 
1534 bool C4Group::Merge(const char *folders)
1535 {
1536  bool move = true;
1537 
1538  if (p->LogToStdOutput) printf("%s...\n",move ? "Moving" : "Adding");
1539 
1540  // Add files & directories
1541  char szFileName[_MAX_FNAME_LEN];
1542  int iFileCount = 0;
1544 
1545  // Process segmented path & search wildcards
1546  char cSeparator = (SCharCount(';', folders) ? ';' : '|');
1547  for (int cseg = 0; SCopySegment(folders, cseg, szFileName, cSeparator); cseg++)
1548  {
1549  i.Reset(szFileName);
1550  while (*i)
1551  {
1552  // File count
1553  iFileCount++;
1554  // Process output & callback
1555  if (p->LogToStdOutput) printf("%s\n",GetFilename(*i));
1556  if (p->ProcessCallback)
1557  p->ProcessCallback(GetFilename(*i),0); // cbytes/tbytes
1558  // AddEntryOnDisk
1559  AddEntryOnDisk(*i, nullptr, move);
1560  ++i;
1561  }
1562  }
1563 
1564  if (p->LogToStdOutput) printf("%d file(s) %s.\n",iFileCount, move ? "moved" : "added");
1565 
1566  return true;
1567 }
1568 
1569 bool C4Group::AddEntryOnDisk(const char *filename,
1570  const char *entry_name,
1571  bool move)
1572 {
1573 
1574  // Do not process yourself
1575  if (ItemIdentical(filename, GetName())) return true;
1576 
1577  // File is a directory: copy to temp path, pack, and add packed file
1578  if (DirectoryExists(filename))
1579  {
1580  // Ignore
1581  if (C4Group_TestIgnore(filename)) return true;
1582  // Temp filename
1583  char temp_filename[_MAX_PATH_LEN];
1584  if (C4Group_TempPath[0]) { SCopy(C4Group_TempPath, temp_filename, _MAX_PATH); SAppend(GetFilename(filename),temp_filename, _MAX_PATH); }
1585  else SCopy(filename, temp_filename, _MAX_PATH);
1586  MakeTempFilename(temp_filename);
1587  // Copy or move item to temp file (moved items might be killed if later process fails)
1588  if (move) { if (!MoveItem(filename, temp_filename)) return Error("AddEntryOnDisk: Move failure"); }
1589  else { if (!CopyItem(filename, temp_filename)) return Error("AddEntryOnDisk: Copy failure"); }
1590  // Pack temp file
1591  if (!C4Group_PackDirectory(temp_filename)) return Error("AddEntryOnDisk: Pack directory failure");
1592  // Add temp file
1593  if (!entry_name) entry_name = GetFilename(filename);
1594  filename = temp_filename;
1595  move = true;
1596  }
1597 
1598  // Determine size
1599  bool fIsGroup = !!C4Group_IsGroup(filename);
1600  int size = fIsGroup ? UncompressedFileSize(filename) : FileSize(filename);
1601 
1602  // Determine executable bit (linux only)
1603  bool is_executable = false;
1604 #ifdef __linux__
1605  is_executable = (access(filename, X_OK) == 0);
1606 #endif
1607 
1608  // AddEntry
1609  return AddEntry(C4GroupEntry::C4GRES_OnDisk,
1610  fIsGroup,
1611  filename,
1612  size,
1613  entry_name,
1614  nullptr,
1615  move,
1616  false,
1617  is_executable);
1618 
1619 }
1620 
1621 bool C4Group::Add(const char *filename, const char *entry_name)
1622 {
1623  bool move = false;
1624 
1625  if (p->LogToStdOutput)
1626  {
1627  printf("%s %s as %s...\n", move ? "Moving" : "Adding", GetFilename(filename), entry_name);
1628  }
1629 
1630  return AddEntryOnDisk(filename, entry_name, move);
1631 }
1632 
1633 bool C4Group::Move(const char *filename, const char *entry_name)
1634 {
1635  bool move = true;
1636 
1637  if (p->LogToStdOutput)
1638  {
1639  printf("%s %s as %s...\n", move ? "Moving" : "Adding", GetFilename(filename), entry_name);
1640  }
1641 
1642  return AddEntryOnDisk(filename, entry_name, move);
1643 }
1644 
1645 bool C4Group::Delete(const char *files, bool recursive)
1646 {
1647  int fcount = 0;
1648  C4GroupEntry *tentry;
1649 
1650  // Segmented file specs
1651  if (SCharCount(';', files) || SCharCount('|', files))
1652  {
1653  char cSeparator = (SCharCount(';', files) ? ';' : '|');
1654  bool success = true;
1655  char filespec[_MAX_FNAME_LEN];
1656  for (int cseg = 0; SCopySegment(files, cseg, filespec, cSeparator, _MAX_FNAME); cseg++)
1657  if (!Delete(filespec, recursive))
1658  success = false;
1659  return success; // Would be nicer to return the file count and add up all counts from recursive actions...
1660  }
1661 
1662  // Delete all matching Entries
1663  ResetSearch();
1664  while ((tentry = SearchNextEntry(files)))
1665  {
1666  // StdOutput
1667  if (p->LogToStdOutput) printf("%s\n",tentry->FileName);
1668  if (!DeleteEntry(tentry->FileName))
1669  return Error("Delete: Could not delete entry");
1670  fcount++;
1671  }
1672 
1673  // Recursive: process sub groups
1674  if (recursive)
1675  {
1676  C4Group hChild;
1677  ResetSearch();
1678  while ((tentry = SearchNextEntry("*")))
1679  if (tentry->ChildGroup)
1680  if (hChild.OpenAsChild(this, tentry->FileName))
1681  {
1682  hChild.SetStdOutput(p->LogToStdOutput);
1683  hChild.Delete(files, recursive);
1684  hChild.Close();
1685  }
1686  }
1687 
1688  // StdOutput
1689  if (p->LogToStdOutput)
1690  printf("%d file(s) deleted.\n",fcount);
1691 
1692  return true; // Would be nicer to return the file count and add up all counts from recursive actions...
1693 }
1694 
1695 bool C4Group::DeleteEntry(const char *filename, bool do_recycle)
1696 {
1697  switch (p->SourceType)
1698  {
1699  case P::ST_Packed:
1700  // Get entry
1701  C4GroupEntry *pEntry;
1702  if (!(pEntry = GetEntry(filename))) return false;
1703  // Delete moved source files
1704  if (pEntry->Status == C4GroupEntry::C4GRES_OnDisk)
1705  if (pEntry->DeleteOnDisk)
1706  {
1707  EraseItem(pEntry->DiskPath);
1708  }
1709  // (moved buffers are deleted by ~C4GroupEntry)
1710  // Delete status and update virtual file count
1712  Head.Entries--;
1713  break;
1714  case P::ST_Unpacked:
1715  p->StdFile.Close();
1716  char path[_MAX_FNAME_LEN];
1717  sprintf(path,"%s%c%s", GetName(),DirectorySeparator, filename);
1718 
1719  if (do_recycle)
1720  {
1721  if (!EraseItemSafe(path)) return false;
1722  }
1723  else
1724  {
1725  if (!EraseItem(path)) return false;
1726  }
1727  // refresh file list
1728  ResetSearch(true);
1729  break;
1730  default: break; // InGrp & Deleted ignored
1731  }
1732  return true;
1733 }
1734 
1735 bool C4Group::Rename(const char *filename, const char *new_name)
1736 {
1737 
1738  if (p->LogToStdOutput)
1739  {
1740  printf("Renaming %s to %s...\n",filename, new_name);
1741  }
1742 
1743  switch (p->SourceType)
1744  {
1745  case P::ST_Packed:
1746  // Get entry
1747  C4GroupEntry *pEntry;
1748  if (!(pEntry = GetEntry(filename)))
1749  {
1750  return Error("Rename: File not found");
1751  }
1752  // Check double name
1753  if (GetEntry(new_name) && !SEqualNoCase(new_name, filename))
1754  {
1755  return Error("Rename: File exists already");
1756  }
1757  // Rename
1758  SCopy(new_name, pEntry->FileName, _MAX_FNAME);
1759  p->Modified = true;
1760  break;
1761  case P::ST_Unpacked:
1762  p->StdFile.Close();
1763 
1764  char path[_MAX_FNAME_LEN];
1765  SCopy(GetName(),path, _MAX_PATH-1);
1766  AppendBackslash(path);
1767  SAppend(filename, path, _MAX_PATH);
1768 
1769  char path2[_MAX_FNAME_LEN];
1770  SCopy(GetName(),path2, _MAX_PATH-1);
1771  AppendBackslash(path2);
1772  SAppend(new_name, path2, _MAX_PATH);
1773 
1774  if (!RenameFile(path, path2))
1775  {
1776  return Error("Rename: Failure");
1777  }
1778  // refresh file list
1779  ResetSearch(true);
1780  break;
1781  default: break; // InGrp & Deleted ignored
1782  }
1783 
1784  return true;
1785 }
1786 
1787 bool C4Group_IsExcluded(const char *filename, const char *exclude_list)
1788 {
1789  // No file or no exclude list
1790  if (!filename || !filename[0] || !exclude_list || !exclude_list[0])
1791  {
1792  return false;
1793  }
1794  // Process segmented exclude list
1795  char separator = (SCharCount(';', exclude_list) ? ';' : '|');
1796  char segment[_MAX_PATH_LEN];
1797  for (int i = 0; SCopySegment(exclude_list, i, segment, separator); i++)
1798  {
1799  if (WildcardMatch(segment, GetFilename(filename)))
1800  {
1801  return true;
1802  }
1803  }
1804  // No match
1805  return false;
1806 }
1807 
1808 bool C4Group::Extract(const char *files, const char *destination, const char *exclude)
1809 {
1810  // StdOutput
1811  if (p->LogToStdOutput)
1812  {
1813  printf("Extracting");
1814  if (destination)
1815  {
1816  printf(" to %s",destination);
1817  }
1818  printf("...\n");
1819  }
1820 
1821  int filecount = 0;
1822  int current_bytes = 0;
1823  int total_bytes = EntrySize();
1824  C4GroupEntry *entry;
1825 
1826 
1827  // Process segmented list
1828  char separator = (SCharCount(';', files) ? ';' : '|');
1829  char filename[_MAX_PATH_LEN];
1830  for (int segment_index = 0; SCopySegment(files, segment_index, filename, separator); segment_index++)
1831  {
1832  // Search all entries
1833  ResetSearch();
1834  while ((entry = SearchNextEntry(filename)))
1835  {
1836  // skip?
1837  if (C4Group_IsExcluded(entry->FileName, exclude))
1838  {
1839  continue;
1840  }
1841  // Process data & output
1842  if (p->LogToStdOutput)
1843  {
1844  printf("%s\n", entry->FileName);
1845  }
1846  current_bytes += entry->Size;
1847  if (p->ProcessCallback)
1848  {
1849  p->ProcessCallback(entry->FileName, 100 * current_bytes / std::max(total_bytes, 1));
1850  }
1851 
1852  // Extract
1853  if (!ExtractEntry(entry->FileName, destination))
1854  {
1855  return Error("Extract: Could not extract entry");
1856  }
1857 
1858  filecount++;
1859  }
1860  }
1861 
1862  if (p->LogToStdOutput)
1863  {
1864  printf("%d file(s) extracted.\n", filecount);
1865  }
1866 
1867  return true;
1868 }
1869 
1870 bool C4Group::ExtractEntry(const char *filename, const char *destination)
1871 {
1872  CStdFile temp_file;
1873  CStdFile dummy;
1874  char temp_file_name[_MAX_FNAME_LEN];
1875  char target_file_name[_MAX_FNAME_LEN];
1876 
1877  // Target file name
1878  if (destination)
1879  {
1880  SCopy(destination, target_file_name, _MAX_FNAME-1);
1881  if (DirectoryExists(target_file_name))
1882  {
1883  AppendBackslash(target_file_name);
1884  SAppend(filename, target_file_name, _MAX_FNAME);
1885  }
1886  }
1887  else
1888  {
1889  SCopy(filename, target_file_name, _MAX_FNAME);
1890  }
1891 
1892  // Extract
1893  switch (p->SourceType)
1894  {
1895  case P::ST_Packed: // Copy entry to target
1896  // Get entry
1897  C4GroupEntry *entry;
1898  if (!(entry = GetEntry(filename)))
1899  {
1900  return Error("Extract: Entry not found");
1901  }
1902 
1903  // Create dummy file to reserve target file name
1904  dummy.Create(target_file_name, false);
1905  dummy.Write("Dummy",5);
1906  dummy.Close();
1907 
1908  // Make temp target file name
1909  SCopy(target_file_name, temp_file_name, _MAX_FNAME);
1910  MakeTempFilename(temp_file_name);
1911  // Create temp target file
1912  if (!temp_file.Create(temp_file_name, !!entry->ChildGroup, !!entry->Executable))
1913  {
1914  return Error("Extract: Cannot create target file");
1915  }
1916  // Write entry file to temp target file
1917  if (!AppendEntry2StdFile(entry, temp_file))
1918  {
1919  // Failure: close and erase temp target file
1920  temp_file.Close();
1921  EraseItem(temp_file_name);
1922  // Also erase reservation target file
1923  EraseItem(target_file_name);
1924  // Failure
1925  return false;
1926  }
1927  // Close target file
1928  temp_file.Close();
1929  // Make temp file to original file
1930  if (!EraseItem(target_file_name))
1931  {
1932  return Error("Extract: Cannot erase temporary file");
1933  }
1934  if (!RenameItem(temp_file_name, target_file_name))
1935  {
1936  return Error("Extract: Cannot rename temporary file");
1937  }
1938  break;
1939  case P::ST_Unpacked: // Copy item from folder to target
1940  char path[_MAX_FNAME_LEN];
1941  sprintf(path,"%s%c%s", GetName(),DirectorySeparator, filename);
1942  if (!CopyItem(path, target_file_name))
1943  {
1944  return Error("ExtractEntry: Cannot copy item");
1945  }
1946  break;
1947  }
1948  return true;
1949 }
1950 
1951 
1952 bool C4Group::OpenAsChild(C4Group *mother, const char *entry_name, bool is_exclusive, bool do_create)
1953 {
1954 
1955  if (!mother) return Error("OpenAsChild: No mother specified");
1956 
1957  if (SCharCount('*',entry_name))
1958  {
1959  return Error("OpenAsChild: No wildcards allowed");
1960  }
1961 
1962  // Open nested child group check: If entry_name is a reference to
1963  // a nested group, open the first mother (in specified mode), then open the child
1964  // in exclusive mode
1965 
1966  if (SCharCount(DirectorySeparator, entry_name))
1967  {
1968  char mothername[_MAX_FNAME_LEN];
1969  SCopyUntil(entry_name, mothername, DirectorySeparator, _MAX_FNAME);
1970 
1971  C4Group *mother2;
1972  mother2 = new C4Group;
1973  mother2->SetStdOutput(p->LogToStdOutput);
1974  if (!mother2->OpenAsChild(mother, mothername, is_exclusive))
1975  {
1976  delete mother2;
1977  return Error("OpenAsChild: Cannot open mother");
1978  }
1979  return OpenAsChild(mother2, entry_name + SLen(mothername) + 1, true);
1980  }
1981 
1982  // Init
1983  Init();
1984  p->FileName = entry_name;
1985  p->Mother = mother;
1986  p->ExclusiveChild = is_exclusive;
1987 
1988  // Folder: Simply set status and return
1989  char path[_MAX_FNAME_LEN];
1990  SCopy( GetFullName().getData(), path, _MAX_FNAME);
1991  if (DirectoryExists(path))
1992  {
1993  p->FileName = path;
1994  p->SourceType = P::ST_Unpacked;
1995  ResetSearch();
1996  return true;
1997  }
1998 
1999  // Get original entry name
2000  C4GroupEntry *centry;
2001  if ((centry = p->Mother->GetEntry(GetName())))
2002  {
2003  p->FileName = centry->FileName;
2004  }
2005 
2006  // Access entry in mother group
2007  size_t size;
2008  if ((!p->Mother->AccessEntry(GetName(), &size, nullptr, true)))
2009  {
2010  if (!do_create)
2011  {
2012  CloseExclusiveMother();
2013  Clear();
2014  return Error("OpenAsChild: Entry not in mother group");
2015  }
2016  else
2017  {
2018  // Create - will be added to mother in Close()
2019  p->SourceType = P::ST_Packed;
2020  p->Modified = true;
2021  return true;
2022  }
2023  }
2024 
2025  // Child Group?
2026  if (centry && !centry->ChildGroup)
2027  {
2028  CloseExclusiveMother();
2029  Clear();
2030  return Error("OpenAsChild: Is not a child group");
2031  }
2032 
2033  // Read header
2034  // Do not do size checks for packed subgroups of unpacked groups (there will be no entry),
2035  // because that would be the PACKED size which can actually be smaller than sizeof(C4GroupHeader)!
2036  if (size < sizeof(C4GroupHeader) && centry)
2037  {
2038  CloseExclusiveMother();
2039  Clear();
2040  return Error("OpenAsChild: Entry too small");
2041  }
2042  if (!p->Mother->Read(&Head, sizeof(C4GroupHeader)))
2043  {
2044  CloseExclusiveMother();
2045  Clear();
2046  return Error("OpenAsChild: Entry reading error");
2047  }
2048  MemScramble((BYTE*)&Head, sizeof(C4GroupHeader));
2049  p->EntryOffset += sizeof(C4GroupHeader);
2050 
2051  // Check Header
2052  if (!SEqual(Head.Id, C4GroupFileID)
2053  || (Head.Ver1 != C4GroupFileVer1)
2054  || (Head.Ver2 > C4GroupFileVer2))
2055  {
2056  CloseExclusiveMother();
2057  Clear();
2058  return Error("OpenAsChild: Invalid Header");
2059  }
2060 
2061  // Read Entries
2062  C4GroupEntryCore buffer;
2063  int file_entries = Head.Entries;
2064  Head.Entries = 0; // Reset, will be recounted by AddEntry
2065  for (int cnt = 0; cnt < file_entries; cnt++)
2066  {
2067  if (!p->Mother->Read(&buffer, sizeof(C4GroupEntryCore)))
2068  {
2069  CloseExclusiveMother();
2070  Clear();
2071  return Error("OpenAsChild: Entry reading error");
2072  }
2073  p->EntryOffset += sizeof(C4GroupEntryCore);
2074  if (!AddEntry(C4GroupEntry::C4GRES_InGroup,
2075  !!buffer.ChildGroup,
2076  buffer.FileName,
2077  buffer.Size,
2078  nullptr,
2079  nullptr,
2080  false,
2081  false,
2082  !!buffer.Executable))
2083  {
2084  CloseExclusiveMother();
2085  Clear();
2086  return Error("OpenAsChild: Insufficient memory");
2087  }
2088  }
2089 
2090  ResetSearch();
2091 
2092  // File
2093  p->SourceType = P::ST_Packed;
2094 
2095  // save position in mother group
2096  if (centry)
2097  {
2098  p->MotherOffset = centry->Offset;
2099  }
2100 
2101  return true;
2102 }
2103 
2104 bool C4Group::AccessEntry(const char *wildcard,
2105  size_t *size,
2106  char *filename,
2107  bool needs_to_be_a_group)
2108 {
2109 #ifdef C4GROUP_DUMP_ACCESS
2110  LogF("Group access in %s: %s", GetFullName().getData(), wildcard);
2111 #endif
2112  StdStrBuf fname;
2113  if (!FindEntry(wildcard,&fname,&p->iCurrFileSize))
2114  {
2115  return false;
2116  }
2117 #ifdef _DEBUG
2118  szCurrAccessedEntry = fname.getMData();
2119 #endif
2120  bool okay = SetFilePtr2Entry(fname.getData(), needs_to_be_a_group);
2121 #ifdef _DEBUG
2122  p->sPrevAccessedEntry.Copy(szCurrAccessedEntry);
2123  szCurrAccessedEntry = nullptr;
2124 #endif
2125  if (!okay)
2126  {
2127  return false;
2128  }
2129  if (filename)
2130  {
2131  SCopy(fname.getData(),filename);
2132  }
2133  if (size)
2134  {
2135  *size = p->iCurrFileSize;
2136  }
2137  return true;
2138 }
2139 
2140 bool C4Group::AccessNextEntry(const char *wildcard,
2141  size_t *size,
2142  char *filename,
2143  bool start_at_filename)
2144 {
2145  char entry_name[_MAX_FNAME_LEN];
2146  if (!FindNextEntry(wildcard, entry_name, &p->iCurrFileSize, start_at_filename))
2147  {
2148  return false;
2149  }
2150 #ifdef _DEBUG
2151  szCurrAccessedEntry = filename;
2152 #endif
2153  bool okay = SetFilePtr2Entry(entry_name);
2154 #ifdef _DEBUG
2155  szCurrAccessedEntry = nullptr;
2156 #endif
2157  if (!okay)
2158  {
2159  return false;
2160  }
2161  if (filename)
2162  {
2163  SCopy(entry_name, filename);
2164  }
2165  if (size)
2166  {
2167  *size = p->iCurrFileSize;
2168  }
2169  return true;
2170 }
2171 
2172 bool C4Group::SetFilePtr2Entry(const char *entry_name, bool needs_to_be_a_group)
2173 {
2174  C4GroupEntry *entry = GetEntry(entry_name);
2175  // Read cached entries directly from memory (except child groups. that is not supported.)
2176  if (entry && entry->MemoryBuffer && !needs_to_be_a_group)
2177  {
2178  p->pInMemEntry = entry->MemoryBuffer;
2179  p->iInMemEntrySize = entry->Size;
2180  return true;
2181  }
2182  else
2183  {
2184  p->pInMemEntry = nullptr;
2185  }
2186 
2187  // Not cached. Access from disk.
2188  switch (p->SourceType)
2189  {
2190 
2191  case P::ST_Packed:
2192  if ((!entry) || (entry->Status != C4GroupEntry::C4GRES_InGroup))
2193  {
2194  return false;
2195  }
2196  return SetFilePtr(entry->Offset);
2197 
2198  case P::ST_Unpacked:
2199  p->StdFile.Close();
2200  char path[_MAX_FNAME_LEN];
2201  SCopy(GetName(),path, _MAX_FNAME);
2202  AppendBackslash(path);
2203  SAppend(entry_name, path);
2204  return p->StdFile.Open(path, needs_to_be_a_group);
2205 
2206  default: break; // InGrp & Deleted ignored
2207  }
2208  return false;
2209 }
2210 
2211 bool C4Group::FindEntry(const char *wildcard, StdStrBuf *filename, size_t *size)
2212 {
2213  ResetSearch();
2214  return FindNextEntry(wildcard, filename, size);
2215 }
2216 
2217 bool C4Group::FindNextEntry(const char *wildcard,
2218  StdStrBuf *filename,
2219  size_t *size,
2220  bool start_at_filename)
2221 {
2222  if (!wildcard)
2223  {
2224  return false;
2225  }
2226 
2227  // Reset search to specified position
2228  if (start_at_filename)
2229  {
2230  FindEntry(filename->getData());
2231  }
2232 
2233  C4GroupEntry *entry;
2234  if (!(entry = SearchNextEntry(wildcard)))
2235  {
2236  return false;
2237  }
2238  if (filename)
2239  {
2240  filename->Copy(entry->FileName);
2241  }
2242  if (size)
2243  {
2244  *size = entry->Size;
2245  }
2246  return true;
2247 }
2248 
2249 bool C4Group::Add(const char *entry_name, void *buffer, int size, bool add_as_child, bool hold_buffer, bool is_executable)
2250 {
2251  return AddEntry(C4GroupEntry::C4GRES_InMemory,
2252  add_as_child,
2253  entry_name,
2254  size,
2255  entry_name,
2256  (BYTE*) buffer,
2257  false,
2258  hold_buffer,
2259  is_executable);
2260 }
2261 
2262 bool C4Group::Add(const char *entry_name, StdBuf &buffer, bool add_as_child, bool hold_buffer, bool is_executable)
2263 {
2264  if (!AddEntry(C4GroupEntry::C4GRES_InMemory,
2265  add_as_child,
2266  entry_name,
2267  buffer.getSize(),
2268  entry_name,
2269  (BYTE*) buffer.getMData(),
2270  false,
2271  hold_buffer,
2272  is_executable,
2273  true))
2274  {
2275  return false;
2276  }
2277  // Pointer is now owned and released by C4Group!
2278  if (hold_buffer)
2279  {
2280  buffer.GrabPointer();
2281  }
2282  return true;
2283 }
2284 
2285 bool C4Group::Add(const char *entry_name, StdStrBuf &buffer, bool add_as_child, bool hold_buffer, bool is_executable)
2286 {
2287  if (!AddEntry(C4GroupEntry::C4GRES_InMemory,
2288  add_as_child,
2289  entry_name,
2290  buffer.getLength(),
2291  entry_name,
2292  (BYTE*) buffer.getMData(),
2293  false,
2294  hold_buffer,
2295  is_executable,
2296  true))
2297  {
2298  return false;
2299  }
2300  // Pointer is now owned and released by C4Group!
2301  if (hold_buffer)
2302  {
2303  buffer.GrabPointer();
2304  }
2305  return true;
2306 }
2307 
2308 
2309 const char* C4Group::GetName() const
2310 {
2311  return p->FileName.c_str();
2312 }
2313 
2314 int C4Group::EntryCount(const char *wildcard)
2315 {
2316  // All files if no wildcard
2317  if (!wildcard)
2318  {
2319  wildcard = "*";
2320  }
2321  // Match wildcard
2322  ResetSearch();
2323 
2324  int filecount = 0;
2325  C4GroupEntry *entry;
2326  while ((entry = SearchNextEntry(wildcard)))
2327  {
2328  filecount++;
2329  }
2330  return filecount;
2331 }
2332 
2333 size_t C4Group::EntrySize(const char *wildcard)
2334 {
2335  // All files if no wildcard
2336  if (!wildcard)
2337  {
2338  wildcard = "*";
2339  }
2340  // Match wildcard
2341  ResetSearch();
2342 
2343  int filesize = 0;
2344  C4GroupEntry *entry;
2345  while ((entry = SearchNextEntry(wildcard)))
2346  {
2347  filesize += entry->Size;
2348  }
2349  return filesize;
2350 }
2351 
2352 size_t C4Group::AccessedEntrySize() const { return p->iCurrFileSize; }
2353 
2354 unsigned int C4Group::EntryCRC32(const char *wildcard)
2355 {
2356  if (!wildcard)
2357  {
2358  wildcard = "*";
2359  }
2360  ResetSearch();
2361 
2362  // iterate thorugh child
2363  unsigned int CRC = 0;
2364  C4GroupEntry *entry;
2365  while ((entry = SearchNextEntry(wildcard)))
2366  {
2367  CRC ^= CalcCRC32(entry);
2368  }
2369  // return
2370  return CRC;
2371 }
2372 
2373 bool C4Group::IsOpen() const { return p->SourceType != P::ST_None; }
2374 
2375 bool C4Group::LoadEntry(const char *entry_name, char **buffer, size_t *size_info, int zeros_to_append)
2376 {
2377  size_t size;
2378 
2379  // Access entry, allocate buffer, read data
2380  (*buffer) = nullptr;
2381  if (size_info)
2382  {
2383  *size_info = 0;
2384  }
2385  if (!AccessEntry(entry_name, &size))
2386  {
2387  return Error("LoadEntry: Not found");
2388  }
2389  *buffer = new char[size + zeros_to_append];
2390  if (!Read(*buffer, size))
2391  {
2392  delete [] (*buffer);
2393  *buffer = nullptr;
2394  return Error("LoadEntry: Reading error");
2395  }
2396 
2397  if (size_info)
2398  {
2399  *size_info = size;
2400  }
2401 
2402  if (zeros_to_append)
2403  {
2404  ZeroMem( (*buffer)+size, zeros_to_append );
2405  }
2406 
2407  return true;
2408 }
2409 
2410 bool C4Group::LoadEntry(const char *entry_name, StdBuf * buffer)
2411 {
2412  size_t size;
2413  // Access entry, allocate buffer, read data
2414  if (!AccessEntry(entry_name, &size))
2415  {
2416  return Error("LoadEntry: Not found");
2417  }
2418  // Allocate memory
2419  buffer->New(size);
2420  // Load data
2421  if (!Read(buffer->getMData(), size))
2422  {
2423  buffer->Clear();
2424  return Error("LoadEntry: Reading error");
2425  }
2426  // ok
2427  return true;
2428 }
2429 
2430 bool C4Group::LoadEntryString(const char *entry_name, StdStrBuf *buffer)
2431 {
2432  size_t size;
2433  // Access entry, allocate buffer, read data
2434  if (!AccessEntry(entry_name, &size))
2435  {
2436  return Error("LoadEntry: Not found");
2437  }
2438  // Allocate memory
2439  buffer->SetLength(size);
2440  // other parts crash when they get a zero length buffer, so fail here
2441  if (!size)
2442  {
2443  return false;
2444  }
2445  // Load data
2446  if (!Read(buffer->getMData(), size))
2447  {
2448  buffer->Clear();
2449  return Error("LoadEntry: Reading error");
2450  }
2451  // ok
2452  return true;
2453 }
2454 
2455 int SortRank(const char *element, const char *sort_list)
2456 {
2457  char segment[_MAX_FNAME_LEN];
2458 
2459  for (int cnt = 0; SCopySegment(sort_list, cnt, segment,'|',_MAX_FNAME); cnt++)
2460  {
2461  if (WildcardMatch(segment, element))
2462  {
2463  return (SCharCount('|',sort_list) + 1) - cnt;
2464  }
2465  }
2466 
2467  return 0;
2468 }
2469 
2470 bool C4Group::Sort(const char *list)
2471 {
2472  bool bubble;
2473  C4GroupEntry *current;
2474  C4GroupEntry *prev;
2475  C4GroupEntry *next;
2476  C4GroupEntry *nextnext;
2477 
2478  if (!list || !list[0])
2479  {
2480  return false;
2481  }
2482 
2483  if (p->LogToStdOutput)
2484  {
2485  printf("Sorting...\n");
2486  }
2487 
2488  do
2489  {
2490  bubble = false;
2491 
2492  for (prev = nullptr, current = p->FirstEntry; current; prev = current, current = next)
2493  {
2494  if ((next = current->Next))
2495  {
2496  // primary sort by file list
2497  int rank_a = SortRank(current->FileName, list);
2498  int rank_b = SortRank(next->FileName, list);
2499  if (rank_a > rank_b)
2500  {
2501  continue;
2502  }
2503  // secondary sort by filename
2504  if (rank_a == rank_b)
2505  {
2506  if (stricmp(current->FileName, next->FileName) <= 0)
2507  {
2508  continue;
2509  }
2510  }
2511  // wrong order: Swap!
2512  nextnext = next->Next;
2513  if (prev)
2514  {
2515  prev->Next = next;
2516  }
2517  else
2518  {
2519  p->FirstEntry = next;
2520  }
2521  next->Next = current;
2522  current->Next = nextnext;
2523  next = nextnext;
2524 
2525  bubble = true;
2526  p->Modified = true;
2527  }
2528  }
2529 
2530  }
2531  while (bubble);
2532 
2533  return true;
2534 }
2535 
2537 {
2538  return p->Mother;
2539 }
2540 
2541 bool C4Group::IsPacked() const { return p->SourceType == P::ST_Packed; }
2542 
2544 {
2545  if (!p->Mother)
2546  {
2547  return false;
2548  }
2549  return p->Mother->IsPacked();
2550 }
2551 
2552 bool C4Group::SetNoSort(bool no_sorting) { p->NoSort = no_sorting; return true; }
2553 
2554 bool C4Group::CloseExclusiveMother()
2555 {
2556  if (p->Mother && p->ExclusiveChild)
2557  {
2558  p->Mother->Close();
2559  delete p->Mother;
2560  p->Mother = nullptr;
2561  return true;
2562  }
2563  return false;
2564 }
2565 
2566 bool C4Group::SortByList(const char **list, const char *filename)
2567 {
2568  // No sort list specified
2569  if (!list)
2570  {
2571  return false;
2572  }
2573  // No group name specified, use own
2574  if (!filename)
2575  {
2576  filename = GetName();
2577  }
2578  filename = GetFilename(filename);
2579  // Find matching filename entry in sort list
2580  const char **list_entry;
2581  for (list_entry = list; *list_entry; list_entry += 2)
2582  {
2583  if (WildcardMatch( *list_entry, filename ))
2584  {
2585  break;
2586  }
2587  }
2588  // Sort by sort list entry
2589  if (*list_entry && *(list_entry + 1))
2590  {
2591  Sort(*(list_entry + 1));
2592  }
2593  // Success
2594  return true;
2595 }
2596 
2597 bool C4Group::EnsureChildFilePtr(C4Group *child)
2598 {
2599 
2600  // group file
2601  if (p->SourceType == P::ST_Packed)
2602  {
2603  // check if FilePtr has to be moved
2604  if (p->FilePtr != child->p->MotherOffset + child->p->EntryOffset + child->p->FilePtr)
2605  {
2606  // move it to the position the child thinks it is
2607  if (!SetFilePtr(child->p->MotherOffset + child->p->EntryOffset + child->p->FilePtr))
2608  {
2609  return false;
2610  }
2611  }
2612  // ok
2613  return true;
2614  }
2615 
2616  // Open standard file is not the child file ...or StdFile ptr does not match child->FilePtr
2617  char child_path[_MAX_PATH_LEN];
2618  sprintf(child_path, "%s%c%s", GetName(), DirectorySeparator, GetFilename(child->GetName()));
2619  if (!ItemIdentical(p->StdFile.Name, child_path))
2620  {
2621  // Reopen correct child stdfile
2622  if (!SetFilePtr2Entry(GetFilename(child->GetName()), true))
2623  {
2624  return false;
2625  }
2626  // Advance to child's old file ptr
2627  if (!AdvanceFilePtr( child->p->EntryOffset + child->p->FilePtr))
2628  {
2629  return false;
2630  }
2631  }
2632 
2633  // Looks okay
2634  return true;
2635 
2636 }
2637 
2639 {
2640  char name[_MAX_PATH_LEN];
2641  *name='\0';
2642  char sep[] = "/";
2643  sep[0] = DirectorySeparator;
2644  for (const C4Group *pGroup = this; pGroup; pGroup = pGroup->p->Mother)
2645  {
2646  if (*name)
2647  {
2648  SInsert(name, sep, 0, _MAX_PATH);
2649  }
2650  // Avoid double slash
2651  if (pGroup == this || pGroup->p->FileName.length() > 1 || pGroup->p->FileName[0] != '/')
2652  {
2653  SInsert(name, pGroup->GetName(), 0, _MAX_PATH);
2654  }
2655  if (pGroup->p->SourceType == P::ST_Unpacked)
2656  {
2657  break; // Folder is assumed to have full path
2658  }
2659  }
2660  StdStrBuf result;
2661  result.Copy(name);
2662  return result;
2663 }
2664 
2665 uint32_t C4Group::CalcCRC32(C4GroupEntry *entry)
2666 {
2667  uint32_t CRC;
2668  // child group?
2669  if (entry->ChildGroup || (entry->Status == C4GroupEntry::C4GRES_OnDisk && (DirectoryExists(entry->DiskPath) || C4Group_IsGroup(entry->DiskPath))))
2670  {
2671  // open
2672  C4Group Child;
2673  switch (entry->Status)
2674  {
2676  if (!Child.OpenAsChild(this, entry->FileName))
2677  {
2678  return 0;
2679  }
2680  break;
2682  if (!Child.Open(entry->DiskPath))
2683  {
2684  return 0;
2685  }
2686  break;
2687  default:
2688  return 0;
2689  }
2690  // get checksum
2691  CRC = Child.EntryCRC32();
2692  }
2693  else if (!entry->Size)
2694  {
2695  CRC = 0;
2696  }
2697  else
2698  {
2699  BYTE *data = nullptr;
2700  bool own_data;
2701  CStdFile file;
2702  // get data
2703  switch (entry->Status)
2704  {
2706  // create buffer
2707  data = new BYTE [entry->Size];
2708  own_data = true;
2709  // go to entry
2710  if (!SetFilePtr2Entry(entry->FileName))
2711  {
2712  delete [] data;
2713  return false;
2714  }
2715  // read
2716  if (!Read(data, entry->Size))
2717  {
2718  delete [] data;
2719  return false;
2720  }
2721  break;
2723  // create buffer
2724  data = new BYTE [entry->Size];
2725  own_data = true;
2726  // open
2727  if (!file.Open(entry->DiskPath))
2728  {
2729  delete [] data;
2730  return false;
2731  }
2732  // read
2733  if (!file.Read(data, entry->Size))
2734  {
2735  delete [] data;
2736  return false;
2737  }
2738  break;
2740  // set
2741  data = entry->MemoryBuffer;
2742  own_data = false;
2743  break;
2744  default:
2745  return false;
2746  }
2747  if (!data)
2748  {
2749  return false;
2750  }
2751  // calc crc
2752  CRC = crc32(0, data, entry->Size);
2753  // discard buffer
2754  if (own_data)
2755  {
2756  delete [] data;
2757  }
2758  // add file name
2759  CRC = crc32(CRC, reinterpret_cast<BYTE *>(entry->FileName), SLen(entry->FileName));
2760  }
2761  // ok
2762  return CRC;
2763 }
2764 
2765 bool C4Group::OpenChild(const char* entry_name)
2766 {
2767  // hack: The seach-handle would be closed twice otherwise
2768  p->FolderSearch.Reset();
2769  // Create a memory copy of ourselves
2770  C4Group *ourselves = new C4Group;
2771  *ourselves->p = *p;
2772 
2773  // Open a child from the memory copy
2774  C4Group child;
2775  if (!child.OpenAsChild(ourselves, entry_name, false))
2776  {
2777  // Silently delete our memory copy
2778  ourselves->p.reset();
2779  delete ourselves;
2780  return false;
2781  }
2782 
2783  // hack: The seach-handle would be closed twice otherwise
2784  p->FolderSearch.Reset();
2785  child.p->FolderSearch.Reset();
2786 
2787  // We now become our own child
2788  *p = *child.p;
2789 
2790  // Make ourselves exclusive (until we hit our memory copy mother)
2791  for (C4Group *group = this; group != ourselves; group = group->p->Mother)
2792  {
2793  group->p->ExclusiveChild = true;
2794  }
2795 
2796  // Reset the temporary child variable so it doesn't delete anything
2797  child.p.reset();
2798 
2799  // Yeehaw
2800  return true;
2801 }
2802 
2804 {
2805  // This only works if we are an exclusive child
2806  if (!p->Mother || !p->ExclusiveChild)
2807  {
2808  return false;
2809  }
2810 
2811  // Store a pointer to our mother
2812  C4Group *mother = p->Mother;
2813 
2814  // Clear ourselves without deleting our mother
2815  p->ExclusiveChild = false;
2816  Clear();
2817 
2818  // hack: The seach-handle would be closed twice otherwise
2819  mother->p->FolderSearch.Reset();
2820  p->FolderSearch.Reset();
2821  // We now become our own mother (whoa!)
2822  *this = std::move(*mother);
2823 
2824  // Now silently delete our former mother
2825  delete mother;
2826 
2827  // Yeehaw
2828  return true;
2829 }
2830 
2831 int C4Group::PreCacheEntries(const char *search_pattern, bool cache_previous)
2832 {
2833  assert(search_pattern);
2834  int result = 0;
2835  // pre-load entries to memory. return number of loaded entries.
2836  for (C4GroupEntry * entry = p->FirstEntry; entry; entry = entry->Next)
2837  {
2838  // is this to be cached?
2839  if (!WildcardListMatch(search_pattern, entry->FileName))
2840  {
2841  continue;
2842  }
2843  // if desired, cache all entries up to that one to allow rewind in unpacked memory
2844  // (only makes sense for groups)
2845  if (cache_previous && p->SourceType == P::ST_Packed)
2846  {
2847  for (C4GroupEntry * e_pre = p->FirstEntry; e_pre != entry; e_pre = e_pre->Next)
2848  {
2849  if (e_pre->Offset >= p->FilePtr)
2850  {
2851  PreCacheEntry(e_pre);
2852  }
2853  }
2854  }
2855  // cache the given entry
2856  PreCacheEntry(entry);
2857  }
2858  return result;
2859 }
2860 
2861 const C4GroupHeader &C4Group::GetHeader() const { return Head; }
2862 
2863 const C4GroupEntry *C4Group::GetFirstEntry() const { return p->FirstEntry; }
2864 
2865 void C4Group::PreCacheEntry(C4GroupEntry * entry)
2866 {
2867  // skip some stuff that can not be cached or has already been cached
2868  if (entry->ChildGroup || entry->MemoryBuffer || !entry->Size)
2869  {
2870  return;
2871  }
2872  // now load it!
2873  StdBuf buffer;
2874  if (!this->LoadEntry(entry->FileName, &buffer))
2875  {
2876  return;
2877  }
2878  entry->HoldBuffer = true;
2879  entry->BufferIsStdbuf = true;
2880  entry->Size = buffer.getSize(); // update size in case group changed on disk between calls
2881  entry->MemoryBuffer = static_cast<BYTE *>(buffer.GrabPointer());
2882 }
#define C4FLS_Player
Definition: C4Components.h:194
#define C4CFN_Graphics
Definition: C4Components.h:28
#define C4FLS_Section
Definition: C4Components.h:190
#define C4FLS_Scenario
Definition: C4Components.h:189
#define C4FLS_Sound
Definition: C4Components.h:204
#define C4CFN_Music
Definition: C4Components.h:30
#define C4CFN_PlayerFiles
Definition: C4Components.h:168
#define C4FLS_Music
Definition: C4Components.h:205
#define C4CFN_System
Definition: C4Components.h:29
#define C4CFN_Material
Definition: C4Components.h:25
#define C4CFN_ScenarioSections
Definition: C4Components.h:38
#define C4CFN_Sound
Definition: C4Components.h:26
#define C4FLS_Material
Definition: C4Components.h:197
#define C4FLS_Def
Definition: C4Components.h:193
#define C4FLS_Folder
Definition: C4Components.h:196
#define C4CFN_ScenarioFiles
Definition: C4Components.h:175
#define C4FLS_Object
Definition: C4Components.h:195
#define C4FLS_System
Definition: C4Components.h:203
#define C4CFN_DefFiles
Definition: C4Components.h:166
#define C4FLS_Graphics
Definition: C4Components.h:198
#define C4CFN_ObjectInfoFiles
Definition: C4Components.h:170
#define C4CFN_FolderFiles
Definition: C4Components.h:176
void MemScramble(BYTE *buffer, int size)
Definition: C4Group.cpp:534
void C4Group_SetTempPath(const char *path)
Definition: C4Group.cpp:74
bool C4Group_ExplodeDirectory(const char *filename)
Definition: C4Group.cpp:470
bool C4Group_PackDirectoryTo(const char *filename, const char *to_filename)
Definition: C4Group.cpp:292
bool C4Group_PackDirectory(const char *filename)
Definition: C4Group.cpp:371
bool C4Group_CopyItem(const char *source, const char *target, bool no_sorting, bool reset_attributes)
Definition: C4Group.cpp:115
const char * C4CFN_FLS[]
Definition: C4Group.cpp:32
void C4Group_SetSortList(const char **sort_list)
Definition: C4Group.cpp:69
bool C4Group_MoveItem(const char *source, const char *target, bool no_sorting)
Definition: C4Group.cpp:182
int SortRank(const char *element, const char *sort_list)
Definition: C4Group.cpp:2455
bool C4Group_DeleteItem(const char *item_name, bool do_recycle)
Definition: C4Group.cpp:255
void C4Group_SetProcessCallback(bool(*callback)(const char *, int))
Definition: C4Group.cpp:64
bool C4Group_TestIgnore(const char *filename)
Definition: C4Group.cpp:92
bool C4Group_UnpackDirectory(const char *filename)
Definition: C4Group.cpp:401
bool C4Group_IsExcluded(const char *filename, const char *exclude_list)
Definition: C4Group.cpp:1787
const char ** C4Group_SortList
Definition: C4Group.cpp:61
bool C4Group_IsGroup(const char *filename)
Definition: C4Group.cpp:104
char C4Group_Ignore[_MAX_PATH_LEN]
Definition: C4Group.cpp:60
const char * C4Group_GetTempPath()
Definition: C4Group.cpp:87
bool C4Group_ReadFile(const char *filename, char **data, size_t *size)
Definition: C4Group.cpp:491
bool(* C4Group_ProcessCallback)(const char *, int)
Definition: C4Group.cpp:62
char C4Group_TempPath[_MAX_PATH_LEN]
Definition: C4Group.cpp:59
char Id[24+4]
Definition: C4Group.h:85
const int32_t C4GroupSwapThreshold
Definition: C4Group.h:60
const int C4GroupFileVer2
Definition: C4Group.h:56
const int C4GroupFileVer1
Definition: C4Group.h:55
#define C4GroupFileID
Definition: C4Group.h:62
int Entries
Definition: C4Group.h:88
bool EraseItemSafe(const char *szFilename)
Definition: C4GroupMain.cpp:42
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:262
int UncompressedFileSize(const char *szFilename)
Definition: CStdFile.cpp:322
#define _MAX_FNAME
#define DirectorySeparator
#define AltDirectorySeparator
int stricmp(const char *s1, const char *s2)
#define _MAX_PATH
#define _MAX_PATH_LEN
#define _MAX_FNAME_LEN
uint8_t BYTE
unsigned int SCharCount(char cTarget, const char *szInStr, const char *cpUntil)
Definition: Standard.cpp:326
void SReplaceChar(char *str, char fc, char tc)
Definition: Standard.cpp:354
bool SIsModule(const char *szList, const char *szString, int *ipIndex, bool fCaseSensitive)
Definition: Standard.cpp:547
bool SCopySegment(const char *szString, int iSegment, char *sTarget, char cSeparator, int iMaxL, bool fSkipWhitespace)
Definition: Standard.cpp:279
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:152
void SInsert(char *szString, const char *szInsert, int iPosition, int iMaxLen)
Definition: Standard.cpp:509
bool SEqualNoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:213
void SCopyUntil(const char *szSource, char *sTarget, char cUntil, int iMaxL, int iIndex)
Definition: Standard.cpp:172
void SAppend(const char *szSource, char *szTarget, int iMaxL)
Definition: Standard.cpp:263
#define sprintf
Definition: Standard.h:162
std::enable_if< std::is_pod< T >::value >::type ZeroMem(T *lpMem, size_t dwSize)
Definition: Standard.h:60
std::enable_if< std::is_nothrow_default_constructible< T >::value >::type InplaceReconstruct(T *obj)
Definition: Standard.h:35
bool SEqual(const char *szStr1, const char *szStr2)
Definition: Standard.h:93
size_t SLen(const char *sptr)
Definition: Standard.h:74
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270
bool EraseItem(const char *szItemName)
Definition: StdFile.cpp:833
bool CreateItem(const char *szItemname)
Definition: StdFile.cpp:839
bool TruncatePath(char *szPath)
Definition: StdFile.cpp:237
bool DirectoryExists(const char *szFilename)
Definition: StdFile.cpp:708
bool GetParentPath(const char *szFilename, char *szBuffer)
Definition: StdFile.cpp:186
bool WildcardMatch(const char *szWildcard, const char *szString)
Definition: StdFile.cpp:396
void MakeTempFilename(char *szFilename)
Definition: StdFile.cpp:320
bool EraseDirectory(const char *szDirName)
Definition: StdFile.cpp:785
bool ItemIdentical(const char *szFilename1, const char *szFilename2)
Definition: StdFile.cpp:879
char * GetFilename(char *szPath)
Definition: StdFile.cpp:42
void AppendBackslash(char *szFilename)
Definition: StdFile.cpp:254
bool WildcardListMatch(const char *szWildcardList, const char *szString)
Definition: StdFile.cpp:348
bool RenameItem(const char *szItemName, const char *szNewItemName)
Definition: StdFile.cpp:827
bool CreatePath(const std::string &path)
Definition: StdFile.cpp:656
bool CopyItem(const char *szSource, const char *szTarget, bool fResetAttributes)
Definition: StdFile.cpp:855
bool MoveItem(const char *szSource, const char *szTarget)
Definition: StdFile.cpp:873
int ForEachFile(const char *szDirName, bool(*fnCallback)(const char *))
Definition: StdFile.cpp:1068
bool FileExists(const char *szFileName)
bool ItemExists(const char *szItemName)
Definition: StdFile.h:75
bool EraseFile(const char *szFileName)
size_t FileSize(const char *fname)
bool RenameFile(const char *szFileName, const char *szNewFileName)
Definition: C4Group.h:110
bool DeleteOnDisk
Definition: C4Group.h:125
~C4GroupEntry()
Definition: C4Group.cpp:596
void Set(const DirectoryIterator &directories, const char *path)
Definition: C4Group.cpp:608
char DiskPath[_MAX_PATH_LEN]
Definition: C4Group.h:123
EntryStatus
Definition: C4Group.h:115
@ C4GRES_InGroup
Definition: C4Group.h:116
@ C4GRES_InMemory
Definition: C4Group.h:118
@ C4GRES_OnDisk
Definition: C4Group.h:117
@ C4GRES_Deleted
Definition: C4Group.h:119
bool NoSort
Definition: C4Group.h:128
EntryStatus Status
Definition: C4Group.h:124
bool BufferIsStdbuf
Definition: C4Group.h:127
BYTE * MemoryBuffer
Definition: C4Group.h:129
bool HoldBuffer
Definition: C4Group.h:126
C4GroupEntry * Next
Definition: C4Group.h:130
bool Rename(const char *filename, const char *new_name)
Definition: C4Group.cpp:1735
bool Read(void *buffer, size_t size) override
Definition: C4Group.cpp:1430
bool FindNextEntry(const char *wildcard, StdStrBuf *filename=nullptr, size_t *size=nullptr, bool start_at_filename=false)
Definition: C4Group.cpp:2217
const C4GroupEntry * GetFirstEntry() const
Definition: C4Group.cpp:2863
bool DeleteEntry(const char *filename, bool do_recycle=false)
Definition: C4Group.cpp:1695
bool HasPackedMother() const
Definition: C4Group.cpp:2543
bool Merge(const char *folders)
Definition: C4Group.cpp:1534
size_t EntrySize(const char *wildcard=nullptr)
Definition: C4Group.cpp:2333
const C4GroupHeader & GetHeader() const
Definition: C4Group.cpp:2861
bool IsPacked() const
Definition: C4Group.cpp:2541
bool Extract(const char *files, const char *destination=nullptr, const char *exclude=nullptr)
Definition: C4Group.cpp:1808
const char * GetName() const
Definition: C4Group.cpp:2309
void Clear()
Definition: C4Group.cpp:1173
size_t AccessedEntrySize() const override
Definition: C4Group.cpp:2352
bool AccessNextEntry(const char *wildcard, size_t *size=nullptr, char *filename=nullptr, bool start_at_filename=false)
Definition: C4Group.cpp:2140
bool AccessEntry(const char *wildcard, size_t *size=nullptr, char *filename=nullptr, bool needs_to_be_a_group=false)
Definition: C4Group.cpp:2104
int EntryCount(const char *wildcard=nullptr)
Definition: C4Group.cpp:2314
StdStrBuf GetFullName() const
Definition: C4Group.cpp:2638
int PreCacheEntries(const char *search_pattern, bool cache_previous=false)
Definition: C4Group.cpp:2831
bool ExtractEntry(const char *filename, const char *destination=nullptr)
Definition: C4Group.cpp:1870
bool Add(const char *filename, const char *entry_name)
Definition: C4Group.cpp:1621
unsigned int EntryCRC32(const char *wildcard=nullptr)
Definition: C4Group.cpp:2354
bool LoadEntry(const char *entry_name, char **buffer, size_t *size_info=nullptr, int zeros_to_append=0)
Definition: C4Group.cpp:2375
C4Group * GetMother()
Definition: C4Group.cpp:2536
bool Save(bool reopen)
Definition: C4Group.cpp:1031
bool OpenAsChild(C4Group *mother, const char *entry_name, bool is_exclusive=false, bool do_create=false)
Definition: C4Group.cpp:1952
bool SetNoSort(bool no_sorting)
Definition: C4Group.cpp:2552
C4GroupEntry * GetEntry(const char *entry_name)
Definition: C4Group.cpp:954
bool LoadEntryString(const char *entry_name, StdStrBuf *buffer)
Definition: C4Group.cpp:2430
bool IsOpen() const
Definition: C4Group.cpp:2373
C4Group()
Definition: C4Group.cpp:623
bool OpenChild(const char *entry_name)
Definition: C4Group.cpp:2765
const char * GetError()
Definition: C4Group.cpp:650
bool Sort(const char *list)
Definition: C4Group.cpp:2470
~C4Group() override
Definition: C4Group.cpp:639
void SetStdOutput(bool log_status)
Definition: C4Group.cpp:655
void ResetSearch(bool reload_contents=false)
Definition: C4Group.cpp:1316
bool Close()
Definition: C4Group.cpp:971
bool Move(const char *filename, const char *entry_name)
Definition: C4Group.cpp:1633
bool Delete(const char *files, bool recursive=false)
Definition: C4Group.cpp:1645
bool OpenMother()
Definition: C4Group.cpp:2803
C4GroupHeader Head
Definition: C4Group.h:147
bool SortByList(const char **list, const char *filename=nullptr)
Definition: C4Group.cpp:2566
bool FindEntry(const char *wildcard, StdStrBuf *filename=nullptr, size_t *size=nullptr)
Definition: C4Group.cpp:2211
bool Open(const char *group_name, bool do_create=false)
Definition: C4Group.cpp:660
bool Advance(int offset) override
Definition: C4Group.cpp:1410
bool Close(StdBuf **ppMemory=nullptr)
Definition: CStdFile.cpp:151
bool Create(const char *szFileName, bool fCompressed=false, bool fExecutable=false, bool fMemory=false)
Definition: CStdFile.cpp:49
bool Write(const void *pBuffer, int iSize)
Definition: CStdFile.cpp:240
bool Read(void *pBuffer, size_t iSize) override
Definition: CStdFile.h:60
bool Open(const char *szFileName, bool fCompressed=false)
Definition: CStdFile.cpp:95
size_t GetFileSize() const
Definition: StdFile.cpp:1059
void Reset(const char *dirname, bool force_reread=false)
Definition: StdFile.cpp:942
Definition: StdBuf.h:30
size_t getSize() const
Definition: StdBuf.h:101
void * GrabPointer()
Definition: StdBuf.h:133
void * getMData()
Definition: StdBuf.h:100
void New(size_t inSize)
Definition: StdBuf.h:146
void Clear()
Definition: StdBuf.h:190
static void DeletePointer(void *data)
Definition: StdBuf.h:196
void SetLength(size_t iLength)
Definition: StdBuf.h:509
const char * getData() const
Definition: StdBuf.h:442
char * getMData()
Definition: StdBuf.h:443
void Copy()
Definition: StdBuf.h:467
void Clear()
Definition: StdBuf.h:466
size_t getLength() const
Definition: StdBuf.h:445
char * GrabPointer()
Definition: StdBuf.h:459
bool ValidateFilename(char *szFilename, size_t iMaxSize=_MAX_PATH)
bool(* ProcessCallback)(const char *, int)
Definition: C4Group.cpp:590
C4GroupEntry * FirstEntry
Definition: C4Group.cpp:578
C4GroupEntry * SearchPtr
Definition: C4Group.cpp:570
BYTE * pInMemEntry
Definition: C4Group.cpp:579
int MotherOffset
Definition: C4Group.cpp:575
size_t iCurrFileSize
Definition: C4Group.cpp:572
std::string FileName
Definition: C4Group.cpp:565
DirectoryIterator FolderSearch
Definition: C4Group.cpp:585
bool Modified
Definition: C4Group.cpp:577
int FilePtr
Definition: C4Group.cpp:574
std::string ErrorString
Definition: C4Group.cpp:591
C4GroupEntry LastFolderSearchEntry
Definition: C4Group.cpp:587
bool ExclusiveChild
Definition: C4Group.cpp:568
C4Group * Mother
Definition: C4Group.cpp:567
bool NoSort
Definition: C4Group.cpp:593
bool LogToStdOutput
Definition: C4Group.cpp:589
C4GroupEntry FolderSearchEntry
Definition: C4Group.cpp:586
CStdFile StdFile
Definition: C4Group.cpp:571
size_t iInMemEntrySize
Definition: C4Group.cpp:580
int EntryOffset
Definition: C4Group.cpp:576
Definition: C4Group.h:93
char FileName[260]
Definition: C4Group.h:94
int32_t Size
Definition: C4Group.h:97
int32_t Packed
Definition: C4Group.h:95
char Executable
Definition: C4Group.h:103
int32_t ChildGroup
Definition: C4Group.h:96
int32_t Offset
Definition: C4Group.h:98