OpenClonk
C4Update.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 #include "C4Include.h"
17 #include "c4group/C4Update.h"
18 
19 #include "C4Version.h"
20 #include "c4group/C4Components.h"
21 #include "c4group/C4Group.h"
22 
24 
25 #ifdef _WIN32
27 #include <direct.h>
28 #endif
29 
30 // helper
31 bool C4Group_CopyEntry(C4Group *pFrom, C4Group *pTo, const char *strItemName)
32 {
33  // read entry
34  char *pData; size_t iSize;
35  if (!pFrom->LoadEntry(strItemName, &pData, &iSize))
36  return false;
37  if (!pTo->Add(strItemName, pData, iSize, false, true))
38  return false;
39  return true;
40 }
41 
42 bool C4Group_ApplyUpdate(C4Group &hGroup, unsigned long ParentProcessID)
43 {
44  // Wait for parent process to terminate (so we can safely replace the executable)
45 #ifdef _WIN32
46  if(ParentProcessID)
47  {
48  HANDLE ParentProcess = OpenProcess(SYNCHRONIZE, FALSE, ParentProcessID);
49  if(ParentProcess)
50  {
51  // If we couldn't find a handle then either
52  // a) the process terminated already, which is great.
53  // b) OpenProcess() failed, which is not so great. But let's still try to do
54  // the update.
55  printf("Waiting for parent process to terminate...");
56  DWORD res = WaitForSingleObject(ParentProcess, 10000);
57  if(res == WAIT_TIMEOUT)
58  fprintf(stderr, "Parent process did not terminate after 10 seconds. Continuing...");
59  }
60  }
61 #else
62  // We could use waitpid on Unix, but we don't need that functionality there anyway...
63 #endif
64 
65  // Process object update group (GRPUP_Entries.txt found)
66  C4UpdatePackage Upd;
67  if (hGroup.FindEntry(C4CFN_UpdateEntries))
68  if (Upd.Load(&hGroup))
69  {
70  // Do update check first (ensure packet has everything it needs in order to perfom the update)
71  int iRes = Upd.Check(&hGroup);
72  switch (iRes)
73  {
74  // Bad version - checks against version of the applying executable (major version must match, minor version must be equal or higher)
76  fprintf(stderr, "This update %s can only be applied using version %d.%d.%d.%d or higher.\n", Upd.Name, Upd.RequireVersion[0], Upd.RequireVersion[1], Upd.RequireVersion[2], Upd.RequireVersion[3]);
77  return false;
78  // Target not found: keep going
80  fprintf(stderr, "Target %s for update %s not found. Ignoring.\n", Upd.DestPath, Upd.Name);
81  return true;
82  // Target mismatch: abort updating
84  fprintf(stderr, "Target %s incorrect version for update %s. Ignoring.\n", Upd.DestPath, Upd.Name);
85  return true;
86  // Target already updated: keep going
88  fprintf(stderr,"Target %s already up-to-date at %s.\n", Upd.DestPath, Upd.Name);
89  return true;
90  // Ok to perform update
91  case C4UPD_CHK_OK:
92  printf("Updating %s to %s... ", Upd.DestPath, Upd.Name);
93  // Make sure the user sees the message while the work is in progress
94  fflush(stdout);
95  // Execute update
96  if (Upd.Execute(&hGroup))
97  {
98  printf("Ok\n");
99  return true;
100  }
101  else
102  {
103  printf("Failed\n");
104  return false;
105  }
106  // Unknown return value from update
107  default:
108  fprintf(stderr,"Unknown error while updating.\n");
109  return false;
110  }
111  }
112 
113  // Process binary update group (AutoUpdate.txt found, additional binary files found)
114  if (hGroup.EntryCount(C4CFN_UpdateCore))
115  if (hGroup.EntryCount() - hGroup.EntryCount(C4CFN_UpdateCore) - hGroup.EntryCount("*.ocu") > 0)
116  {
117  // Notice: AutoUpdate.txt is currently not processed...
118  char strEntry[_MAX_FNAME_LEN] = "";
119  StdStrBuf strList;
120  printf("Updating binaries...\n");
121  hGroup.ResetSearch();
122  // Look for binaries
123  while (hGroup.FindNextEntry("*", strEntry))
124  // Accept everything except *.ocu, AutoUpdate.txt, and c4group.exe (which is assumed not to work under Windows)
125  if (!WildcardMatch("*.ocu", strEntry) && !WildcardMatch(C4CFN_UpdateCore, strEntry) && !WildcardMatch("c4group.exe", strEntry))
126  { strList += strEntry; strList += ";"; }
127  // Extract binaries to current working directory
128  if (!hGroup.Extract(strList.getData()))
129  return false;
130  // If extracted file is a group, explode it (this is meant for Clonk.app on Mac)
131  for (int i = 0; SGetModule(strList.getData(), i, strEntry); i++)
132  if (C4Group_IsGroup(strEntry))
133  {
134  printf("Exploding: %s\n", strEntry);
135  if (!C4Group_ExplodeDirectory(strEntry))
136  return false;
137  }
138  }
139 
140  // Process any child updates (*.ocu)
141  if (hGroup.FindEntry("*.ocu"))
142  {
143  // Process all children
144  char strEntry[_MAX_FNAME_LEN] = "";
145  C4Group hChild;
146  hGroup.ResetSearch();
147  while (hGroup.FindNextEntry("*.ocu", strEntry))
148  if (hChild.OpenAsChild(&hGroup, strEntry))
149  {
150  bool ok = C4Group_ApplyUpdate(hChild, 0);
151  hChild.Close();
152  // Failure on child update
153  if (!ok) return false;
154  }
155  }
156 
157  // Success
158  return true;
159 }
160 
161 
162 
163 
164 // *** C4GroupEx
165 class C4GroupEx : public C4Group
166 {
167 public:
168 
169  // some funcs to alter internal values of groups.
170  // Needed to create byte-correct updated files
171 
172  void SetHead(C4Group &rByGrp)
173  {
174  // Cheat away the protection
175  C4GroupHeader *pHdr = &static_cast<C4GroupEx&>(rByGrp).Head;
176  // save Entries
177  int Entries = Head.Entries;
178  // copy
179  memcpy(&Head, pHdr, sizeof(Head));
180  // restore
181  Head.Entries = Entries;
182  }
183 
184  bool HeadIdentical(C4Group &rByGrp, bool fLax)
185  {
186  // Cheat away the protection while avoiding
187  // gcc strict aliasing violation warnings.
188  intptr_t iGroup = (intptr_t) &rByGrp;
189  C4GroupHeader *pHdr = &((C4GroupEx*) iGroup)->Head;
190  // overwrite entries field
191  int Entries = Head.Entries;
192  Head.Entries = pHdr->Entries;
193  // compare
194  bool fIdentical = !memcmp(&Head, pHdr, sizeof(C4GroupHeader));
195  // restore field values
196  Head.Entries = Entries;
197  // okay
198  return fIdentical;
199  }
200 
202  void SaveEntryCore(C4Group &rByGrp, const char *szEntry)
203  {
204  C4GroupEntryCore *pCore = ((C4GroupEx &)rByGrp).GetEntry(szEntry);
205  // copy core
207  }
208  void SetSavedEntryCore(const char *szEntry)
209  {
210  C4GroupEntryCore *pCore = GetEntry(szEntry);
211  // copy core
213  }
214 
215  void SetNoSort(const char *szEntry)
216  {
217  C4GroupEntry *pEntry = GetEntry(szEntry);
218  if (pEntry) pEntry->NoSort = true;
219  }
220 
221  // close without header update
222  bool Close(bool fHeaderUpdate)
223  {
224  if (fHeaderUpdate) return C4Group::Close(); else { bool fSuccess = Save(false); Clear(); return fSuccess; }
225  }
226 };
227 
228 // *** C4UpdatePackageCore
229 
231 {
232  pComp->Value(mkNamingAdapt(toC4CArr(RequireVersion), "RequireVersion"));
233  pComp->Value(mkNamingAdapt(toC4CStr(Name), "Name", ""));
234  pComp->Value(mkNamingAdapt(toC4CStr(DestPath), "DestPath", ""));
235  pComp->Value(mkNamingAdapt(GrpUpdate, "GrpUpdate", 0));
236  pComp->Value(mkNamingAdapt(UpGrpCnt, "TargetCount", 0));
237  pComp->Value(mkNamingAdapt(toC4CArrU(GrpChks1), "GrpChks1"));
238  pComp->Value(mkNamingAdapt(GrpChks2, "GrpChks2", 0u));
239 }
240 
242 {
243  // Load from group
244  StdStrBuf Source;
245  if (!hGroup.LoadEntryString(C4CFN_UpdateCore,&Source))
246  return false;
247  try
248  {
249  // Compile data
250  CompileFromBuf<StdCompilerINIRead>(mkNamingAdapt(*this, "Update"), Source);
251  }
252  catch (StdCompiler::Exception *pExc)
253  {
254  delete pExc;
255  return false;
256  }
257  return true;
258 }
259 
261 {
262  try
263  {
264  // decompile data
265  StdStrBuf Core = DecompileToBuf<StdCompilerINIWrite>(mkNamingAdapt(*this, "Update"));
266  char *stupid_buffer = new char[Core.getLength() + 1];
267  memcpy(stupid_buffer, Core.getMData(), Core.getLength() + 1);
268  // add to group
269  return hGroup.Add(C4CFN_UpdateCore, stupid_buffer, Core.getLength(), false, true);
270  }
271  catch (StdCompiler::Exception * pExc)
272  {
273  delete pExc;
274  return false;
275  }
276 }
277 
278 // *** C4UpdatePackage
279 
281 {
282  // read update core
283  StdStrBuf Source;
284  if (!pGroup->LoadEntryString(C4CFN_UpdateCore,&Source))
285  return false;
286  try
287  {
288  // Compile data
289  CompileFromBuf<StdCompilerINIRead>(mkNamingAdapt(*this, "Update"), Source);
290  }
291  catch (StdCompiler::Exception *pExc)
292  {
294  WriteLog("ERROR: %s (in %s)", pExc->Msg.getData(), Name.getData());
295  delete pExc;
296  return false;
297  }
298  return true;
299 }
300 
301 // #define UPDATE_DEBUG
302 
304 {
305 
306  // search target
307  C4GroupEx TargetGrp;
308  char strTarget[_MAX_PATH]; SCopy(DestPath, strTarget, _MAX_PATH);
309  char *p = strTarget, *lp = strTarget;
310  while ((p = strchr(p + 1, '\\')))
311  {
312  *p = 0;
313  if (!*(p + 1)) break;
314  if (!SEqual(lp, ".."))
315  {
316  if (TargetGrp.Open(strTarget))
317  {
318  // packed?
319  bool fPacked = TargetGrp.IsPacked();
320  // Close Group
321  TargetGrp.Close(true);
322  if (fPacked)
323  // Unpack
324  C4Group_UnpackDirectory(strTarget);
325  }
326  else
327  {
328  // GrpUpdate -> file must exist
329  if (GrpUpdate) return false;
330  // create dir
331  CreatePath(strTarget);
332  }
333  }
334  *p = '\\'; lp = p + 1;
335  }
336 
337  // try to open it
338  if (!TargetGrp.Open(strTarget, !GrpUpdate))
339  return false;
340 
341  // check if the update is allowed
342  if (GrpUpdate)
343  {
344  // check checksum
345  uint32_t iCRC32;
346  if (!GetFileCRC(TargetGrp.GetFullName().getData(), &iCRC32))
347  return false;
348  int i = 0;
349  for (; i < UpGrpCnt; i++)
350  if (iCRC32 == GrpChks1[i])
351  break;
352  if (i >= UpGrpCnt)
353  return false;
354  }
355  else
356  {
357  // only allow Extra.ocg-Updates
358  if (!SEqual2(DestPath, "Extra.ocg"))
359  return false;
360  }
361 
362  // update children
363  char ItemFileName[_MAX_PATH];
364  pGroup->ResetSearch();
365  while (pGroup->FindNextEntry("*", ItemFileName))
366  if (!SEqual(ItemFileName, C4CFN_UpdateCore) && !SEqual(ItemFileName, C4CFN_UpdateEntries))
367  DoUpdate(pGroup, &TargetGrp, ItemFileName);
368 
369  // do GrpUpdate
370  if (GrpUpdate)
371  DoGrpUpdate(pGroup, &TargetGrp);
372 
373  // close the group
374  TargetGrp.Close(false);
375 
376  if (GrpUpdate)
377  {
378  // check the result
379  uint32_t iResChks;
380  if (!GetFileCRC(strTarget, &iResChks))
381  return false;
382  if (iResChks != GrpChks2)
383  {
384 #ifdef UPDATE_DEBUG
385  char *pData; int iSize;
386  CStdFile MyFile; MyFile.Load(strTarget, (BYTE **)&pData, &iSize, 0, true);
387  MyFile.Create("DiesesDingIstMist.txt", false);
388  MyFile.Write(pData, iSize);
389  MyFile.Close();
390 #endif
391  return false;
392  }
393  }
394 
395  return true;
396 }
397 
398 bool C4UpdatePackage::Optimize(C4Group *pGroup, const char *strTarget)
399 {
400 
401  // Open target group
402  C4GroupEx TargetGrp;
403  if (!TargetGrp.Open(strTarget))
404  return false;
405 
406  // Both groups must be packed
407  if (!pGroup->IsPacked() || !TargetGrp.IsPacked())
408  {
409  TargetGrp.Close(false);
410  return false;
411  }
412 
413  // update children
414  char ItemFileName[_MAX_PATH];
415  pGroup->ResetSearch();
416  while (pGroup->FindNextEntry("*", ItemFileName))
417  if (!SEqual(ItemFileName, C4CFN_UpdateCore) && !SEqual(ItemFileName, C4CFN_UpdateEntries))
418  Optimize(pGroup, &TargetGrp, ItemFileName);
419 
420  // set header
421  if (TargetGrp.HeadIdentical(*pGroup, true))
422  TargetGrp.SetHead(*pGroup);
423 
424  // save
425  TargetGrp.Close(false);
426 
427  // okay
428  return true;
429 }
430 
432 {
433  // Version requirement is set
434  if (RequireVersion[0])
435  {
436  // Engine and game version must match (rest ignored)
437  if ((C4XVER1 != RequireVersion[0]) || (C4XVER2 != RequireVersion[1]))
438  return C4UPD_CHK_BAD_VERSION;
439  }
440 
441  // only group updates have any special needs
442  if (!GrpUpdate) return C4UPD_CHK_OK;
443 
444  // check source file
445  C4Group TargetGrp;
446  if (!TargetGrp.Open(DestPath))
447  return C4UPD_CHK_NO_SOURCE;
448  if (!TargetGrp.IsPacked())
449  return C4UPD_CHK_BAD_SOURCE;
450  TargetGrp.Close();
451 
452  // check source crc
453  uint32_t iCRC32;
454  if (!GetFileCRC(DestPath, &iCRC32))
455  return C4UPD_CHK_BAD_SOURCE;
456  // equal to destination group?
457  if (iCRC32 == GrpChks2)
458  // so there's nothing to do
460  // check if it's one of our registered sources
461  int i = 0;
462  for (; i < UpGrpCnt; i++)
463  if (iCRC32 == GrpChks1[i])
464  break;
465  if (i >= UpGrpCnt)
466  return C4UPD_CHK_BAD_SOURCE;
467 
468  // ok
469  return C4UPD_CHK_OK;
470 }
471 
472 bool C4UpdatePackage::DoUpdate(C4Group *pGrpFrom, C4GroupEx *pGrpTo, const char *strFileName)
473 {
474  // group file?
475  C4Group ItemGroupFrom;
476  if (ItemGroupFrom.OpenAsChild(pGrpFrom, strFileName))
477  {
478  // try to open target group
479  C4GroupEx ItemGroupTo;
480  char strTempGroup[_MAX_PATH_LEN]; strTempGroup[0] = 0;
481  if (!ItemGroupTo.OpenAsChild(pGrpTo, strFileName, false, true))
482  return false;
483  // update children
484  char ItemFileName[_MAX_PATH];
485  ItemGroupFrom.ResetSearch();
486  while (ItemGroupFrom.FindNextEntry("*", ItemFileName))
487  if (!SEqual(ItemFileName, C4CFN_UpdateCore) && !SEqual(ItemFileName, C4CFN_UpdateEntries))
488  DoUpdate(&ItemGroupFrom, &ItemGroupTo, ItemFileName);
489  if (GrpUpdate)
490  {
491  DoGrpUpdate(&ItemGroupFrom, &ItemGroupTo);
492  // write group (do not change any headers set by DoGrpUpdate!)
493  ItemGroupTo.Close(false);
494  // set core (C4Group::Save overwrites it)
495  pGrpTo->SaveEntryCore(*pGrpFrom, strFileName);
496  pGrpTo->SetSavedEntryCore(strFileName);
497  // flag as no-resort
498  pGrpTo->SetNoSort(strFileName);
499  }
500  else
501  {
502  // write group
503  ItemGroupTo.Close(true);
504  // temporary group?
505  if (strTempGroup[0])
506  if (!pGrpTo->Move(strTempGroup, strFileName))
507  return false;
508  }
509  }
510  else
511  {
512 #ifdef _WIN32
513  OutputDebugString(FormatString("updating %s\\%s\n", pGrpTo->GetFullName().getData(), strFileName).GetWideChar());
514 #elif defined(_DEBUG)
515  printf("updating %s\\%s\n", pGrpTo->GetFullName().getData(), strFileName);
516 #endif
517  if (!C4Group_CopyEntry(pGrpFrom, pGrpTo, strFileName))
518  return false;
519  // set core
520  pGrpTo->SaveEntryCore(*pGrpFrom, strFileName);
521  pGrpTo->SetSavedEntryCore(strFileName);
522  }
523  // ok
524  return true;
525 }
526 
527 bool C4UpdatePackage::DoGrpUpdate(C4Group *pUpdateData, C4GroupEx *pGrpTo)
528 {
529  char *pData;
530  // sort entries
531  if (pUpdateData->LoadEntry(C4CFN_UpdateEntries, &pData, nullptr, 1))
532  {
533  // delete all entries that do not appear in the entries list
534  char strItemName[_MAX_FNAME_LEN], strItemName2[_MAX_FNAME_LEN];
535  pGrpTo->ResetSearch();
536  while (pGrpTo->FindNextEntry("*", strItemName))
537  {
538  bool fGotIt = false;
539  for (int i = 0; (fGotIt = SCopySegment(pData, i, strItemName2, '|', _MAX_FNAME)); i++)
540  {
541  // remove separator
542  char *pSep = strchr(strItemName2, '=');
543  if (pSep) *pSep = '\0';
544  // in list?
545  if (SEqual(strItemName, strItemName2))
546  break;
547  }
548  if (!fGotIt)
549  pGrpTo->DeleteEntry(strItemName);
550  }
551  // set entry times, set sort list
552  char strSortList[32767] = "";
553  for (int i = 0; SCopySegment(pData, i, strItemName, '|', _MAX_FNAME); i++)
554  {
555  // strip checksum/time (if given)
556  char *pTime = strchr(strItemName, '=');
557  if (pTime) *pTime = '\0';
558  // copy to sort list
559  SAppend(strItemName, strSortList);
560  SAppendChar('|', strSortList);
561  }
562  // sort by list
563  pGrpTo->Sort(strSortList);
564  delete[] pData;
565  }
566  // copy header from update group
567  pGrpTo->SetHead(*pUpdateData);
568  // ok
569  return true;
570 }
571 
572 bool C4UpdatePackage::Optimize(C4Group *pGrpFrom, C4GroupEx *pGrpTo, const char *strFileName)
573 {
574  // group file?
575  C4Group ItemGroupFrom;
576  if (!ItemGroupFrom.OpenAsChild(pGrpFrom, strFileName))
577  return true;
578  // try to open target group
579  C4GroupEx ItemGroupTo;
580  if (!ItemGroupTo.OpenAsChild(pGrpTo, strFileName))
581  return true;
582  // update children
583  char ItemFileName[_MAX_PATH];
584  ItemGroupFrom.ResetSearch();
585  while (ItemGroupFrom.FindNextEntry("*", ItemFileName))
586  Optimize(&ItemGroupFrom, &ItemGroupTo, ItemFileName);
587  // set head
588  if (ItemGroupTo.HeadIdentical(ItemGroupFrom, true))
589  ItemGroupTo.SetHead(ItemGroupFrom);
590  // write group (do not change any headers set by DoGrpUpdate!)
591  ItemGroupTo.Close(false);
592  // set core (C4Group::Save overwrites it)
593  pGrpTo->SaveEntryCore(*pGrpFrom, strFileName);
594  pGrpTo->SetSavedEntryCore(strFileName);
595  return true;
596 }
597 
598 void MemScramble(BYTE *, int);
599 
600 bool C4UpdatePackage::MakeUpdate(const char *strFile1, const char *strFile2, const char *strUpdateFile, const char *strName)
601 {
602 #ifdef UPDATE_DEBUG
603  char *pData; int iSize;
604  CStdFile MyFile; MyFile.Load(strFile2, (BYTE **)&pData, &iSize, 0, true);
605  MyFile.Create("SoIstRichtig.txt", false);
606  MyFile.Write(pData, iSize);
607  MyFile.Close();
608  MemScramble((BYTE *)pData, iSize);
609  MyFile.Create("UndSoAuch.txt", false);
610  MyFile.Write(pData, iSize);
611  MyFile.Close();
612 #endif
613 
614  // open Log
615  if (!Log.Create("Update.log"))
616  return false;
617 
618  // begin message
619  WriteLog("Source: %s\nTarget: %s\nOutput: %s\n\n", strFile1, strFile2, strUpdateFile);
620 
621  // open both groups
622  C4Group Group1, Group2;
623  if (!Group1.Open(strFile1)) { WriteLog("Error: could not open %s!\n", strFile1); return false; }
624  if (!Group2.Open(strFile2)) { WriteLog("Error: could not open %s!\n", strFile2); return false; }
625 
626  // All groups to be compared need to be packed
627  if (!Group1.IsPacked()) { WriteLog("Error: source group %s not packed!\n", strFile1); return false; }
628  if (!Group2.IsPacked()) { WriteLog("Error: target group %s not packed!\n", strFile2); return false; }
629  if (Group1.HasPackedMother()) { WriteLog("Error: source group %s must not have a packed mother group!\n", strFile1); return false; }
630  if (Group2.HasPackedMother()) { WriteLog("Error: target group %s must not have a packed mother group!\n", strFile2); return false; }
631 
632  // create/open update-group
633  C4GroupEx UpGroup;
634  if (!UpGroup.Open(strUpdateFile, true)) { WriteLog("Error: could not open %s!\n", strUpdateFile); return false; }
635 
636  // may be continued update-file -> try to load core
637  UpGrpCnt = 0;
638  bool fContinued = C4UpdatePackageCore::Load(UpGroup);
639 
640  // save crc2 for later check
641  unsigned int iOldChks2 = GrpChks2;
642 
643  // create core info
644  if (strName)
645  SCopy(strName, Name, C4MaxName);
646  else
647  sprintf(Name, "%s Update", GetFilename(strFile1));
648  SCopy(strFile1, DestPath, _MAX_PATH);
649  GrpUpdate = true;
650  if (!GetFileCRC(strFile1, &GrpChks1[UpGrpCnt]))
651  { WriteLog("Error: could not calc checksum for %s!\n", strFile1); return false; }
652  if (!GetFileCRC(strFile2, &GrpChks2))
653  { WriteLog("Error: could not calc checksum for %s!\n", strFile2); return false; }
654  if (fContinued)
655  {
656  // continuation check: GrpChks2 matches?
657  if (GrpChks2 != iOldChks2)
658  // that would mess up the update result...
659  { WriteLog("Error: could not add to update package - target groups don't match (checksum error)\n"); return false; }
660  // already supported by this update?
661  int i = 0;
662  for (; i < UpGrpCnt; i++)
663  if (GrpChks1[UpGrpCnt] == GrpChks1[i])
664  break;
665  if (i < UpGrpCnt)
666  { WriteLog("This update already supports the version of the source file.\n"); return false; }
667  }
668 
669  UpGrpCnt++;
670 
671  // save core
672  if (!C4UpdatePackageCore::Save(UpGroup))
673  { WriteLog("Could not save update package core!\n"); return false; }
674 
675  // compare groups, create update
676  bool fModified = false;
677  bool fSuccess = MkUp(&Group1, &Group2, &UpGroup, &fModified);
678  // close (save) it
679  UpGroup.Close(false);
680  // error?
681  if (!fSuccess)
682  {
683  WriteLog("Update package not created.\n");
684  EraseItem(strUpdateFile);
685  return false;
686  }
687 
688  WriteLog("Update package created.\n");
689  return true;
690 }
691 
692 extern char C4Group_TempPath[_MAX_PATH_LEN];
693 
694 bool C4UpdatePackage::MkUp(C4Group *pGrp1, C4Group *pGrp2, C4GroupEx *pUpGrp, bool *fModified)
695 {
696  // (CAUTION: pGrp1 may be nullptr - that means that there is no counterpart for Grp2
697  // in the base group)
698 
699  // compare headers
700  if (!pGrp1 || pGrp1->EntryCRC32() != pGrp2->EntryCRC32())
701  *fModified = true;
702  // set header
703  pUpGrp->SetHead(*pGrp2);
704  // compare entries
705  char strItemName[_MAX_PATH], strItemName2[_MAX_PATH]; StdStrBuf EntryList;
706  strItemName[0] = strItemName2[0] = 0;
707  pGrp2->ResetSearch(); if (!*fModified) pGrp1->ResetSearch();
708  int iChangedEntries = 0;
709  while (pGrp2->FindNextEntry("*", strItemName, nullptr, !! strItemName[0]))
710  {
711  // add to entry list
712  if (!!EntryList) EntryList.AppendChar('|');
713  EntryList.AppendFormat("%s=%d", strItemName, pGrp2->EntryCRC32(strItemName));
714  // no modification detected yet? then check order
715  if (!*fModified)
716  {
717  if (!pGrp1->FindNextEntry("*", strItemName2, nullptr, !! strItemName2[0]))
718  *fModified = true;
719  else if (!SEqual(strItemName, strItemName2))
720  *fModified = true;
721  }
722 
723  // TODO: write DeleteEntries.txt
724 
725  // a child group?
726  C4GroupEx ChildGrp2;
727  if (ChildGrp2.OpenAsChild(pGrp2, strItemName))
728  {
729  // open in Grp1
730  C4Group *pChildGrp1 = new C4GroupEx();
731  if (!pGrp1 || !pChildGrp1->OpenAsChild(pGrp1, strItemName))
732  { delete pChildGrp1; pChildGrp1 = nullptr; }
733  // open group for update data
734  C4GroupEx UpdGroup; char strTempGroupName[_MAX_FNAME_LEN];
735  strTempGroupName[0] = 0;
736  if (!UpdGroup.OpenAsChild(pUpGrp, strItemName))
737  {
738  // create new group (may be temporary)
739  if (C4Group_TempPath[0]) { SCopy(C4Group_TempPath,strTempGroupName,_MAX_FNAME); SAppend("~upd",strTempGroupName,_MAX_FNAME); }
740  else SCopy("~upd",strTempGroupName,_MAX_FNAME);
741  MakeTempFilename(strTempGroupName);
742  if (!UpdGroup.Open(strTempGroupName, true)) { delete pChildGrp1; WriteLog("Error: could not create temp group\n"); return false; }
743  }
744  // do nested MkUp-search
745  bool Modified = false;
746  bool fSuccess = MkUp(pChildGrp1, &ChildGrp2, &UpdGroup, &Modified);
747  // sort & close
748  extern const char ** C4Group_SortList;
749  UpdGroup.SortByList(C4Group_SortList, ChildGrp2.GetName());
750  UpdGroup.Close(false);
751  // check entry crcs
752  if (!pGrp1 || (pGrp1->EntryCRC32(strItemName) != pGrp2->EntryCRC32(strItemName)))
753  Modified = true;
754  // add group (if modified)
755  if (fSuccess && Modified)
756  {
757  if (strTempGroupName[0])
758  if (!pUpGrp->Move(strTempGroupName, strItemName))
759  {
760  WriteLog("Error: could not add modified group\n");
761  return false;
762  }
763  // copy core
764  pUpGrp->SaveEntryCore(*pGrp2, strItemName);
765  pUpGrp->SetSavedEntryCore(strItemName);
766  // got a modification in a subgroup
767  *fModified = true;
768  iChangedEntries++;
769  }
770  else
771  // delete group (do not remove groups that existed before!)
772  if (strTempGroupName[0])
773  if (!EraseItem(strTempGroupName))
774  { WriteLog("Error: could not delete temporary directory\n"); return false; }
775  delete pChildGrp1;
776  }
777  else
778  {
779  // compare them (size & crc32)
780  if (!pGrp1 ||
781  pGrp1->EntrySize(strItemName) != pGrp2->EntrySize(strItemName) ||
782  pGrp1->EntryCRC32(strItemName) != pGrp2->EntryCRC32(strItemName))
783  {
784  bool fCopied = false;
785 
786  // save core (EntryCRC32 might set additional fields)
787  pUpGrp->SaveEntryCore(*pGrp2, strItemName);
788 
789  // already in update grp?
790  if (pUpGrp->EntrySize(strItemName) != pGrp2->EntrySize(strItemName) ||
791  pUpGrp->EntryCRC32(strItemName) != pGrp2->EntryCRC32(strItemName))
792  {
793  // copy it
794  if (!C4Group_CopyEntry(pGrp2, pUpGrp, strItemName))
795  {
796  WriteLog("Error: could not add changed entry to update group\n");
797  return false;
798  }
799  // set entry core
800  pUpGrp->SetSavedEntryCore(strItemName);
801  // modified...
802  *fModified = true;
803  fCopied = true;
804  }
805  iChangedEntries++;
806 
807  WriteLog("%s\\%s: update%s\n", pGrp2->GetFullName().getData(), strItemName, fCopied ? "" : " (already in group)");
808  }
809  }
810  }
811  // write entries list (always)
812  if (!pUpGrp->Add(C4CFN_UpdateEntries, EntryList, false, true))
813  {
814  WriteLog("Error: could not save entry list!");
815  return false;
816  }
817 
818  if (iChangedEntries > 0)
819  WriteLog("%s: %d/%d changed (%s)\n", pGrp2->GetFullName().getData(), iChangedEntries, pGrp2->EntryCount(), *fModified ? "update" : "skip");
820 
821  // success
822  return true;
823 }
824 
825 void C4UpdatePackage::WriteLog(const char *strMsg, ...)
826 {
827  va_list arglist; va_start(arglist, strMsg);
828  char strOutp[1024];
829  vsprintf(strOutp, strMsg, arglist);
830  Log.Write(strOutp, strlen(strOutp));
831  Log.Flush();
832  ::Log(strOutp);
833 }
#define C4CFN_UpdateEntries
Definition: C4Components.h:51
#define C4CFN_UpdateCore
Definition: C4Components.h:50
bool C4Group_ExplodeDirectory(const char *filename)
Definition: C4Group.cpp:470
bool C4Group_UnpackDirectory(const char *filename)
Definition: C4Group.cpp:401
const char ** C4Group_SortList
Definition: C4Group.cpp:61
bool C4Group_IsGroup(const char *filename)
Definition: C4Group.cpp:104
int Entries
Definition: C4Group.h:88
const unsigned int C4MaxName
bool C4Group_ApplyUpdate(C4Group &hGroup, unsigned long ParentProcessID)
Definition: C4Update.cpp:42
C4Config * GetCfg()
char C4Group_TempPath[_MAX_PATH_LEN]
Definition: C4Group.cpp:59
bool C4Group_CopyEntry(C4Group *pFrom, C4Group *pTo, const char *strItemName)
Definition: C4Update.cpp:31
void MemScramble(BYTE *, int)
Definition: C4Group.cpp:534
#define C4UPD_CHK_NO_SOURCE
Definition: C4Update.h:41
#define C4UPD_CHK_BAD_VERSION
Definition: C4Update.h:44
#define C4UPD_CHK_ALREADY_UPDATED
Definition: C4Update.h:43
#define C4UPD_CHK_OK
Definition: C4Update.h:40
#define C4UPD_CHK_BAD_SOURCE
Definition: C4Update.h:42
bool GetFileCRC(const char *szFilename, uint32_t *pCRC32)
Definition: CStdFile.cpp:355
#define _MAX_FNAME
#define _MAX_PATH
#define _MAX_PATH_LEN
#define DirSep
#define _MAX_FNAME_LEN
uint8_t BYTE
uint32_t DWORD
bool SEqual2(const char *szStr1, const char *szStr2)
Definition: Standard.cpp:204
bool SCopySegment(const char *szString, int iSegment, char *sTarget, char cSeparator, int iMaxL, bool fSkipWhitespace)
Definition: Standard.cpp:279
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:152
bool SGetModule(const char *szList, int iIndex, char *sTarget, int iSize)
Definition: Standard.cpp:539
void SAppendChar(char cChar, char *szStr)
Definition: Standard.cpp:271
void SAppend(const char *szSource, char *szTarget, int iMaxL)
Definition: Standard.cpp:263
#define sprintf
Definition: Standard.h:162
bool SEqual(const char *szStr1, const char *szStr2)
Definition: Standard.h:93
#define toC4CArr(rArr)
Definition: StdAdaptors.h:28
#define toC4CArrU(rArr)
Definition: StdAdaptors.h:29
StdNamingAdapt< T > mkNamingAdapt(T &&rValue, const char *szName)
Definition: StdAdaptors.h:92
#define toC4CStr(szString)
Definition: StdAdaptors.h:24
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270
bool EraseItem(const char *szItemName)
Definition: StdFile.cpp:833
bool WildcardMatch(const char *szWildcard, const char *szString)
Definition: StdFile.cpp:396
void MakeTempFilename(char *szFilename)
Definition: StdFile.cpp:320
char * GetFilename(char *szPath)
Definition: StdFile.cpp:42
bool CreatePath(const std::string &path)
Definition: StdFile.cpp:656
int iSize
Definition: TstC4NetIO.cpp:32
Definition: C4Group.h:110
bool NoSort
Definition: C4Group.h:128
void SetSavedEntryCore(const char *szEntry)
Definition: C4Update.cpp:208
void SetNoSort(const char *szEntry)
Definition: C4Update.cpp:215
void SetHead(C4Group &rByGrp)
Definition: C4Update.cpp:172
void SaveEntryCore(C4Group &rByGrp, const char *szEntry)
Definition: C4Update.cpp:202
C4GroupEntryCore SavedCore
Definition: C4Update.cpp:201
bool HeadIdentical(C4Group &rByGrp, bool fLax)
Definition: C4Update.cpp:184
bool Close(bool fHeaderUpdate)
Definition: C4Update.cpp:222
bool FindNextEntry(const char *wildcard, StdStrBuf *filename=nullptr, size_t *size=nullptr, bool start_at_filename=false)
Definition: C4Group.cpp:2217
bool DeleteEntry(const char *filename, bool do_recycle=false)
Definition: C4Group.cpp:1695
bool HasPackedMother() const
Definition: C4Group.cpp:2543
size_t EntrySize(const char *wildcard=nullptr)
Definition: C4Group.cpp:2333
bool IsPacked() const
Definition: C4Group.cpp:2541
bool Extract(const char *files, const char *destination=nullptr, const char *exclude=nullptr)
Definition: C4Group.cpp:1808
const char * GetName() const
Definition: C4Group.cpp:2309
void Clear()
Definition: C4Group.cpp:1173
int EntryCount(const char *wildcard=nullptr)
Definition: C4Group.cpp:2314
StdStrBuf GetFullName() const
Definition: C4Group.cpp:2638
bool Add(const char *filename, const char *entry_name)
Definition: C4Group.cpp:1621
unsigned int EntryCRC32(const char *wildcard=nullptr)
Definition: C4Group.cpp:2354
bool LoadEntry(const char *entry_name, char **buffer, size_t *size_info=nullptr, int zeros_to_append=0)
Definition: C4Group.cpp:2375
bool Save(bool reopen)
Definition: C4Group.cpp:1031
bool OpenAsChild(C4Group *mother, const char *entry_name, bool is_exclusive=false, bool do_create=false)
Definition: C4Group.cpp:1952
C4GroupEntry * GetEntry(const char *entry_name)
Definition: C4Group.cpp:954
bool LoadEntryString(const char *entry_name, StdStrBuf *buffer)
Definition: C4Group.cpp:2430
bool Sort(const char *list)
Definition: C4Group.cpp:2470
void ResetSearch(bool reload_contents=false)
Definition: C4Group.cpp:1316
bool Close()
Definition: C4Group.cpp:971
bool Move(const char *filename, const char *entry_name)
Definition: C4Group.cpp:1633
C4GroupHeader Head
Definition: C4Group.h:147
bool SortByList(const char **list, const char *filename=nullptr)
Definition: C4Group.cpp:2566
bool FindEntry(const char *wildcard, StdStrBuf *filename=nullptr, size_t *size=nullptr)
Definition: C4Group.cpp:2211
bool Open(const char *group_name, bool do_create=false)
Definition: C4Group.cpp:660
int32_t UpGrpCnt
Definition: C4Update.h:32
int32_t GrpUpdate
Definition: C4Update.h:31
int32_t RequireVersion[4]
Definition: C4Update.h:28
bool Load(C4Group &hGroup)
Definition: C4Update.cpp:241
char Name[C4MaxName+1]
Definition: C4Update.h:29
uint32_t GrpChks2
Definition: C4Update.h:33
bool Save(C4Group &hGroup)
Definition: C4Update.cpp:260
char DestPath[_MAX_PATH_LEN]
Definition: C4Update.h:30
void CompileFunc(StdCompiler *pComp)
Definition: C4Update.cpp:230
uint32_t GrpChks1[C4UP_MaxUpGrpCnt]
Definition: C4Update.h:33
int Check(C4Group *pGroup)
Definition: C4Update.cpp:431
bool Load(C4Group *pGroup)
Definition: C4Update.cpp:280
CStdFile Log
Definition: C4Update.h:65
bool DoUpdate(C4Group *pGrpFrom, class C4GroupEx *pGrpTo, const char *strFileName)
Definition: C4Update.cpp:472
bool MakeUpdate(const char *strFile1, const char *strFile2, const char *strUpdateFile, const char *strName=nullptr)
Definition: C4Update.cpp:600
static bool Optimize(C4Group *pGrpFrom, const char *strTarget)
Definition: C4Update.cpp:398
bool Execute(C4Group *pGroup)
Definition: C4Update.cpp:303
bool DoGrpUpdate(C4Group *pUpdateData, class C4GroupEx *pGrpTo)
Definition: C4Update.cpp:527
bool MkUp(C4Group *pGrp1, C4Group *pGrp2, C4GroupEx *pUpGr, bool *fModified)
Definition: C4Update.cpp:694
void WriteLog(const char *strMsg,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: C4Update.cpp:825
bool Close(StdBuf **ppMemory=nullptr)
Definition: CStdFile.cpp:151
bool Create(const char *szFileName, bool fCompressed=false, bool fExecutable=false, bool fMemory=false)
Definition: CStdFile.cpp:49
bool Write(const void *pBuffer, int iSize)
Definition: CStdFile.cpp:240
bool Flush()
Definition: CStdFile.h:70
void Value(const T &rStruct)
Definition: StdCompiler.h:161
void AppendFormat(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:190
const char * getData() const
Definition: StdBuf.h:442
char * getMData()
Definition: StdBuf.h:443
void AppendChar(char cChar)
Definition: StdBuf.h:588
size_t getLength() const
Definition: StdBuf.h:445
Definition: C4Group.h:93
char Executable
Definition: C4Group.h:103