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 {
34 C4CFN_System, C4FLS_System,
35 C4CFN_Material, C4FLS_Material,
36 C4CFN_Graphics, C4FLS_Graphics,
37 C4CFN_DefFiles, C4FLS_Def,
38 C4CFN_PlayerFiles, C4FLS_Player,
39 C4CFN_ObjectInfoFiles, C4FLS_Object,
40 C4CFN_ScenarioFiles, C4FLS_Scenario,
41 C4CFN_FolderFiles, C4FLS_Folder,
42 C4CFN_ScenarioSections, C4FLS_Section,
43 C4CFN_Sound, C4FLS_Sound,
44 C4CFN_Music, C4FLS_Music,
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
59 char C4Group_TempPath[_MAX_PATH+1]="";
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
C4Group_SetProcessCallback(bool (* fnCallback)(const char *,int))64 void C4Group_SetProcessCallback(bool (*fnCallback)(const char *, int))
65 {
66 C4Group_ProcessCallback = fnCallback;
67 }
68
C4Group_SetSortList(const char ** ppSortList)69 void C4Group_SetSortList(const char **ppSortList)
70 {
71 C4Group_SortList = ppSortList;
72 }
73
C4Group_SetTempPath(const char * szPath)74 void C4Group_SetTempPath(const char *szPath)
75 {
76 if (!szPath || !szPath[0]) C4Group_TempPath[0]=0;
77 else { SCopy(szPath,C4Group_TempPath,_MAX_PATH); AppendBackslash(C4Group_TempPath); }
78 }
79
C4Group_GetTempPath()80 const char *C4Group_GetTempPath()
81 {
82 return C4Group_TempPath;
83 }
84
C4Group_TestIgnore(const char * szFilename)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
C4Group_IsGroup(const char * szFilename)94 bool C4Group_IsGroup(const char *szFilename)
95 {
96 C4Group hGroup; if (hGroup.Open(szFilename)) { hGroup.Close(); return true; }
97 return false;
98 }
99
C4Group_CopyItem(const char * szSource,const char * szTarget1,bool fNoSort,bool fResetAttributes)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
C4Group_MoveItem(const char * szSource,const char * szTarget1,bool fNoSort)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
C4Group_DeleteItem(const char * szItem,bool fRecycle)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
C4Group_PackDirectoryTo(const char * szFilename,const char * szFilenameTo)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
231 if (C4Group_ProcessCallback)
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
C4Group_PackDirectory(const char * szFilename)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
C4Group_UnpackDirectory(const char * szFilename)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
323 if (C4Group_ProcessCallback)
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
C4Group_ExplodeDirectory(const char * szFilename)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
360 ForEachFile(szFilename,C4Group_ExplodeDirectory);
361
362 // Success
363 return true;
364 }
365
C4Group_ReadFile(const char * szFile,char ** pData,size_t * iSize)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
MemScramble(BYTE * bypBuffer,int iSize)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
407 struct C4Group::P
408 {
409 enum SourceType
410 {
411 // No source; C4Group inactive
412 ST_None,
413 // C4Group backed by archive file
414 ST_Packed,
415 // C4Group backed by raw file system
416 ST_Unpacked
417 };
418
419 SourceType SourceType = ST_None;
420 std::string FileName;
421 // Parent status
422 C4Group *Mother = nullptr;
423 bool ExclusiveChild = false;
424 // File & Folder
425 C4GroupEntry *SearchPtr = nullptr;
426 CStdFile StdFile;
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;
433 C4GroupEntry *FirstEntry = nullptr;
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
439 DirectoryIterator FolderSearch;
440 C4GroupEntry FolderSearchEntry;
441 C4GroupEntry LastFolderSearchEntry;
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
~C4GroupEntry()450 C4GroupEntry::~C4GroupEntry()
451 {
452 if (HoldBuffer)
453 if (bpMemBuf)
454 {
455 if (BufferIsStdbuf)
456 StdBuf::DeletePointer(bpMemBuf);
457 else
458 delete [] bpMemBuf;
459 }
460 }
461
Set(const DirectoryIterator & iter,const char * path)462 void C4GroupEntry::Set(const DirectoryIterator &iter, const char * path)
463 {
464 InplaceReconstruct(this);
465
466 SCopy(GetFilename(*iter),FileName,_MAX_FNAME);
467 SCopy(*iter, DiskPath, _MAX_PATH-1);
468 Size = iter.GetFileSize();
469 Status=C4GRES_OnDisk;
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
C4Group()477 C4Group::C4Group()
478 : p(new P)
479 {}
480
Init()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
489 InplaceReconstruct(&Head);
490 p = std::move(new_p);
491 }
492
~C4Group()493 C4Group::~C4Group()
494 {
495 Clear();
496 }
497
Error(const char * szStatus)498 bool C4Group::Error(const char *szStatus)
499 {
500 p->ErrorString = szStatus;
501 return false;
502 }
503
GetError()504 const char *C4Group::GetError()
505 {
506 return p->ErrorString.c_str();
507 }
508
SetStdOutput(bool fStatus)509 void C4Group::SetStdOutput(bool fStatus)
510 {
511 p->StdOutput=fStatus;
512 }
513
Open(const char * szGroupName,bool fCreate)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(R"(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
OpenReal(const char * szFilename)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
OpenRealGrpFile()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
616 if (!SEqual(Head.id,C4GroupFileID)
617 || (Head.Ver1!=C4GroupFileVer1) || (Head.Ver2>C4GroupFileVer2))
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
AddEntry(C4GroupEntry::EntryStatus status,bool childgroup,const char * fname,long size,const char * entryname,BYTE * membuf,bool fDeleteOnDisk,bool fHoldBuffer,bool fExecutable,bool fBufferIsStdbuf)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
GetEntry(const char * szName)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
Close()755 bool C4Group::Close()
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
779 Head.Ver1=C4GroupFileVer1;
780 Head.Ver2=C4GroupFileVer2;
781
782 // Automatic sort
783 SortByList(C4Group_SortList);
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
Save(bool fReOpen)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
Clear()898 void C4Group::Clear()
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
AppendEntry2StdFile(C4GroupEntry * centry,CStdFile & hTarget)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
ResetSearch(bool reload_contents)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
GetNextFolderEntry()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
SearchNextEntry(const char * szName)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
SetFilePtr(int iOffset)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
Advance(int iOffset)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
Read(void * pBuffer,size_t iSize)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
AdvanceFilePtr(int iOffset)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
RewindFilePtr()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
Merge(const char * szFolders)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;
1238 DirectoryIterator i;
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
AddEntryOnDisk(const char * szFilename,const char * szAddAs,bool fMove)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
Add(const char * szFile,const char * szAddAs)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
Move(const char * szFile,const char * szAddAs)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
Delete(const char * szFiles,bool fRecursive)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
DeleteEntry(const char * szFilename,bool fRecycle)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
1400 pEntry->Status = C4GroupEntry::C4GRES_Deleted;
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
Rename(const char * szFile,const char * szNewName)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
C4Group_IsExcluded(const char * szFile,const char * szExcludeList)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
Extract(const char * szFiles,const char * szExtractTo,const char * szExclude)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
ExtractEntry(const char * szFilename,const char * szExtractTo)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
OpenAsChild(C4Group * pMother,const char * szEntryName,bool fExclusive,bool fCreate)1585 bool C4Group::OpenAsChild(C4Group *pMother,
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)
1665 || (Head.Ver1!=C4GroupFileVer1) || (Head.Ver2>C4GroupFileVer2))
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
AccessEntry(const char * szWildCard,size_t * iSize,char * sFileName,bool NeedsToBeAGroup)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
AccessNextEntry(const char * szWildCard,size_t * iSize,char * sFileName,bool fStartAtFilename)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
SetFilePtr2Entry(const char * szName,bool NeedsToBeAGroup)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
FindEntry(const char * szWildCard,StdStrBuf * sFileName,size_t * iSize)1774 bool C4Group::FindEntry(const char *szWildCard, StdStrBuf *sFileName, size_t *iSize)
1775 {
1776 ResetSearch();
1777 return FindNextEntry(szWildCard,sFileName,iSize);
1778 }
1779
FindNextEntry(const char * szWildCard,StdStrBuf * sFileName,size_t * iSize,bool fStartAtFilename)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
Add(const char * szName,void * pBuffer,int iSize,bool fChild,bool fHoldBuffer,bool fExecutable)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
Add(const char * szName,StdBuf & pBuffer,bool fChild,bool fHoldBuffer,bool fExecutable)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
Add(const char * szName,StdStrBuf & pBuffer,bool fChild,bool fHoldBuffer,bool fExecutable)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
GetName() const1845 const char* C4Group::GetName() const
1846 {
1847 return p->FileName.c_str();
1848 }
1849
EntryCount(const char * szWildCard)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
EntrySize(const char * szWildCard)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
AccessedEntrySize() const1875 size_t C4Group::AccessedEntrySize() const { return p->iCurrFileSize; }
1876
EntryCRC32(const char * szWildCard)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
IsOpen() const1891 bool C4Group::IsOpen() const { return p->SourceType != P::ST_None; }
1892
LoadEntry(const char * szEntryName,char ** lpbpBuf,size_t * ipSize,int iAppendZeros)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
LoadEntry(const char * szEntryName,StdBuf * Buf)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
LoadEntryString(const char * szEntryName,StdStrBuf * Buf)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
SortRank(const char * szElement,const char * szSortList)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
Sort(const char * szSortList)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
GetMother()2004 C4Group* C4Group::GetMother()
2005 {
2006 return p->Mother;
2007 }
2008
IsPacked() const2009 bool C4Group::IsPacked() const { return p->SourceType == P::ST_Packed; }
2010
HasPackedMother() const2011 bool C4Group::HasPackedMother() const { if (!p->Mother) return false; return p->Mother->IsPacked(); }
2012
SetNoSort(bool fNoSort)2013 bool C4Group::SetNoSort(bool fNoSort) { p->NoSort = fNoSort; return true; }
2014
CloseExclusiveMother()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
SortByList(const char ** ppSortList,const char * szFilename)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
EnsureChildFilePtr(C4Group * pChild)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
GetFullName() const2078 StdStrBuf C4Group::GetFullName() const
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
CalcCRC32(C4GroupEntry * pEntry)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 {
2104 case C4GroupEntry::C4GRES_InGroup:
2105 if (!Child.OpenAsChild(this, pEntry->FileName))
2106 return 0;
2107 break;
2108 case C4GroupEntry::C4GRES_OnDisk:
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 {
2126 case C4GroupEntry::C4GRES_InGroup:
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;
2134 case C4GroupEntry::C4GRES_OnDisk:
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;
2142 case C4GroupEntry::C4GRES_InMemory:
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
OpenChild(const char * strEntry)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
OpenMother()2197 bool C4Group::OpenMother()
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
PreCacheEntries(const char * szSearchPattern,bool cache_previous)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
GetHeader() const2245 const C4GroupHeader &C4Group::GetHeader() const { return Head; }
2246
GetFirstEntry() const2247 const C4GroupEntry *C4Group::GetFirstEntry() const { return p->FirstEntry; }
2248
PreCacheEntry(C4GroupEntry * e)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 }
2261