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