OpenClonk
C4GroupMain.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 
17 /* C4Group command line executable */
18 
19 #include "C4Include.h"
20 
21 #include "c4group/C4Group.h"
22 #include "C4Version.h"
23 #include "c4group/C4Update.h"
24 #include "platform/StdRegistry.h"
25 #ifdef _WIN32
27 #endif
28 
30 char **globalArgV;
31 int iFirstCommand = 0;
32 
33 extern bool fQuiet;
34 bool fRecursive = false;
35 bool fRegisterShell = false;
36 bool fUnregisterShell = false;
37 char strExecuteAtEnd[_MAX_PATH + 1] = "";
38 
39 int iResult = 0;
40 
41 bool EraseItemSafe(const char *szFilename)
42 {
43  return false;
44 }
45 
46 void DisplayGroup(C4Group &grp, const char *filter = nullptr)
47 {
48  const C4GroupHeader &head = grp.GetHeader();
49 
50  printf("Version: %d.%d ", head.Ver1, head.Ver2);
51 
52  uint32_t crc = 0;
53  bool crc_valid = GetFileCRC(grp.GetFullName().getData(), &crc);
54  if (crc_valid)
55  printf("File CRC: %u (%X) ", crc, crc);
56  else
57  printf("File CRC: <error accessing file> ");
58 
59  uint32_t contents_crc = grp.EntryCRC32();
60  printf("Contents CRC: %u (%X)\n", contents_crc, contents_crc);
61 
62  // Find maximum file name length (matching filter)
63  size_t max_fn_len = 0;
64  for (const C4GroupEntry *entry = grp.GetFirstEntry(); entry != nullptr; entry = entry->Next)
65  {
66  if (filter == nullptr || WildcardMatch(filter, entry->FileName))
67  max_fn_len = std::max(max_fn_len, strlen(entry->FileName));
68  }
69 
70  // List files
71  size_t file_count = 0;
72  size_t byte_count = 0;
73  for (const C4GroupEntry *entry = grp.GetFirstEntry(); entry != nullptr; entry = entry->Next)
74  {
75  if (filter != nullptr && !WildcardMatch(filter, entry->FileName))
76  continue;
77 
78  printf("%*s %8u Bytes",
79  int(max_fn_len),
80  entry->FileName,
81  entry->Size);
82  if (entry->ChildGroup != 0)
83  printf(" (Group)");
84  if (entry->Executable != 0)
85  printf(" (Executable)");
86  printf("\n");
87 
88  ++file_count;
89  byte_count += entry->Size;
90  }
91  printf("%lu Entries, %lu Bytes\n", (unsigned long)file_count, (unsigned long)byte_count);
92 }
93 
94 void PrintGroupInternals(C4Group &grp, int indent_level = 0)
95 {
96  const C4GroupHeader &head = grp.GetHeader();
97  int indent = indent_level * 4;
98 
99  printf("%*sHead.id: '%s'\n", indent, "", head.id);
100  printf("%*sHead.Ver1: %d\n", indent, "", head.Ver1);
101  printf("%*sHead.Ver2: %d\n", indent, "", head.Ver2);
102  printf("%*sHead.Entries: %d\n", indent, "", head.Entries);
103  for (const C4GroupEntry * p = grp.GetFirstEntry(); p; p = p->Next)
104  {
105  printf("%*sEntry '%s':\n", indent, "", p->FileName);
106  printf("%*s Packed: %d\n", indent, "", p->Packed);
107  printf("%*s ChildGroup: %d\n", indent, "", p->ChildGroup);
108  printf("%*s Size: %d\n", indent, "", p->Size);
109  printf("%*s Offset: %d\n", indent, "", p->Offset);
110  printf("%*s Executable: %d\n", indent, "", p->Executable);
111  if (p->ChildGroup != 0)
112  {
113  C4Group hChildGroup;
114  if (hChildGroup.OpenAsChild(&grp, p->FileName))
115  PrintGroupInternals(hChildGroup, indent_level + 1);
116  }
117  }
118 }
119 
120 
121 bool ProcessGroup(const char *FilenamePar)
122 {
123  C4Group hGroup;
124  hGroup.SetStdOutput(!fQuiet);
125  bool fDeleteGroup = false;
126 
127  int argc = globalArgC;
128  char **argv = globalArgV;
129 
130  // Strip trailing slash
131  char * szFilename = strdup(FilenamePar);
132  size_t len = strlen(szFilename);
133  if (szFilename[len-1] == DirectorySeparator) szFilename[len-1] = 0;
134  // Current filename
135  LogF("Group: %s", szFilename);
136 
137  // Open group file
138  if (hGroup.Open(szFilename, true))
139  {
140  // No commands: display contents
141  if (iFirstCommand >= argc)
142  {
143  DisplayGroup(hGroup);
144  }
145  // Process commands
146  else
147  {
148  for (int iArg = iFirstCommand; iArg < argc; ++iArg)
149  {
150  // This argument is a command
151  if (argv[iArg][0] == '-')
152  {
153  // Handle commands
154  switch (argv[iArg][1])
155  {
156  // Sort
157  case 's':
158  // First sort parameter overrides default Clonk sort list
159  C4Group_SetSortList(nullptr);
160  // Missing argument
161  if ((iArg + 1 >= argc) || (argv[iArg + 1][0] == '-'))
162  {
163  fprintf(stderr, "Missing argument for sort command\n");
164  }
165  // Sort, advance to next argument
166  else
167  {
168  hGroup.Sort(argv[iArg + 1]);
169  iArg++;
170  }
171  break;
172  // View
173  case 'l':
174  if ((iArg + 1 >= argc) || (argv[iArg + 1][0] == '-'))
175  {
176  DisplayGroup(hGroup);
177  }
178  else
179  {
180  DisplayGroup(hGroup, argv[iArg + 1]);
181  iArg++;
182  }
183  break;
184  // Pack
185  case 'p':
186  Log("Packing...");
187  // Close
188  if (!hGroup.Close())
189  {
190  fprintf(stderr, "Closing failed: %s\n", hGroup.GetError());
191  }
192  // Pack
193  else if (!C4Group_PackDirectory(szFilename))
194  {
195  fprintf(stderr, "Pack failed\n");
196  }
197  // Reopen
198  else if (!hGroup.Open(szFilename))
199  {
200  fprintf(stderr, "Reopen failed: %s\n", hGroup.GetError());
201  }
202  break;
203  // Pack To
204  case 't':
205  if ((iArg + 1 >= argc))
206  {
207  fprintf(stderr, "Pack failed: too few arguments\n");
208  break;
209  }
210  ++iArg;
211  Log("Packing...");
212  // Close
213  if (!hGroup.Close())
214  {
215  fprintf(stderr, "Closing failed: %s\n", hGroup.GetError());
216  }
217  else if (!EraseItem(argv[iArg]))
218  {
219  fprintf(stderr, "Destination Clear failed\n");
220  break;
221  }
222  // Pack
223  else if (!C4Group_PackDirectoryTo(szFilename, argv[iArg]))
224  {
225  fprintf(stderr, "Pack failed\n");
226  break;
227  }
228  free(szFilename);
229  szFilename = strdup(argv[iArg]);
230  // Reopen
231  if (!hGroup.Open(szFilename))
232  {
233  fprintf(stderr, "Reopen failed: %s\n", hGroup.GetError());
234  }
235  break;
236  // Unpack
237  case 'u':
238  LogF("Unpacking...");
239  // Close
240  if (!hGroup.Close())
241  {
242  fprintf(stderr, "Closing failed: %s\n", hGroup.GetError());
243  }
244  // Pack
245  else if (!C4Group_UnpackDirectory(szFilename))
246  {
247  fprintf(stderr, "Unpack failed\n");
248  }
249  // Reopen
250  else if (!hGroup.Open(szFilename))
251  {
252  fprintf(stderr, "Reopen failed: %s\n", hGroup.GetError());
253  }
254  break;
255  // Unpack
256  case 'x':
257  Log("Exploding...");
258  // Close
259  if (!hGroup.Close())
260  {
261  fprintf(stderr, "Closing failed: %s\n", hGroup.GetError());
262  }
263  // Pack
264  else if (!C4Group_ExplodeDirectory(szFilename))
265  {
266  fprintf(stderr, "Unpack failed\n");
267  }
268  // Reopen
269  else if (!hGroup.Open(szFilename))
270  {
271  fprintf(stderr, "Reopen failed: %s\n", hGroup.GetError());
272  }
273  break;
274  // Generate update
275  case 'g':
276  if ((iArg + 3 >= argc) || (argv[iArg + 1][0] == '-')
277  || (argv[iArg + 2][0] == '-')
278  || (argv[iArg + 3][0] == '-'))
279  {
280  fprintf(stderr, "Update generation failed: too few arguments\n");
281  }
282  else
283  {
284  C4UpdatePackage Upd;
285  // Close
286  if (!hGroup.Close())
287  {
288  fprintf(stderr, "Closing failed: %s\n", hGroup.GetError());
289  }
290  // generate
291  else if (!Upd.MakeUpdate(argv[iArg + 1], argv[iArg + 2], szFilename, argv[iArg + 3]))
292  {
293  fprintf(stderr, "Update generation failed.\n");
294  }
295  // Reopen
296  else if (!hGroup.Open(szFilename))
297  {
298  fprintf(stderr, "Reopen failed: %s\n", hGroup.GetError());
299  }
300  iArg += 3;
301  }
302  break;
303 
304  // Apply an update
305  case 'y':
306  {
307  Log("Applying update...");
308  unsigned long pid = 0;
309  bool have_pid = false;
310 
311  if(iArg + 1 < argc)
312  {
313  errno = 0;
314  pid = strtoul(argv[iArg+1], nullptr, 10);
315  if(errno == 0)
316  have_pid = true;
317  else
318  pid = 0;
319  }
320 
321  if(C4Group_ApplyUpdate(hGroup, pid))
322  { if (argv[iArg][2]=='d') fDeleteGroup = true; }
323  else
324  fprintf(stderr,"Update failed.\n");
325 
326  if(have_pid) ++iArg;
327  }
328  break;
329  case 'z':
330  PrintGroupInternals(hGroup);
331  break;
332  // Undefined
333  default:
334  fprintf(stderr, "Unknown command: %s\n", argv[iArg]);
335  break;
336  }
337  }
338  else
339  {
340  fprintf(stderr, "Invalid parameter %s\n", argv[iArg]);
341  }
342 
343  }
344  }
345  // Error: output status
346  if (!SEqual(hGroup.GetError(), "No Error"))
347  {
348  fprintf(stderr, "Status: %s\n", hGroup.GetError());
349  }
350  // Close group file
351  if (!hGroup.Close())
352  {
353  fprintf(stderr, "Closing: %s\n", hGroup.GetError());
354  }
355  // Delete group file if desired (i.e. after apply update)
356  if (fDeleteGroup)
357  {
358  LogF("Deleting %s...\n", GetFilename(szFilename));
359  EraseItem(szFilename);
360  }
361  }
362  // Couldn't open group
363  else
364  {
365  fprintf(stderr, "Status: %s\n", hGroup.GetError());
366  }
367  free(szFilename);
368  // Done
369  return true;
370 }
371 
373 {
374 #ifdef _WIN32
375  wchar_t strModule[2048+1];
376  wchar_t strCommand[2048+1];
377  char strClass[128];
378  int i;
379  GetModuleFileNameW(nullptr, strModule, 2048);
380  // Groups
381  const char *strClasses =
382  "Clonk4.Definition;Clonk4.Folder;Clonk4.Group;Clonk4.Player;Clonk4.Scenario;Clonk4.Update;Clonk4.Weblink";
383  for (i = 0; SCopySegment(strClasses, i, strClass); i++)
384  {
385  // Unpack
386  _snwprintf(strCommand, 2048, L"\"%s\" \"%%1\" \"-u\"", strModule);
387  if (!SetRegShell(GetWideChar(strClass), L"MakeFolder", L"C4Group Unpack", strCommand))
388  return 0;
389  // Explode
390  _snwprintf(strCommand, 2048, L"\"%s\" \"%%1\" \"-x\"", strModule);
391  if (!SetRegShell(GetWideChar(strClass), L"ExplodeFolder", L"C4Group Explode", strCommand))
392  return 0;
393  }
394  // Directories
395  const char *strClasses2 = "Directory";
396  for (i = 0; SCopySegment(strClasses2, i, strClass); i++)
397  {
398  // Pack
399  _snwprintf(strCommand, 2048, L"\"%s\" \"%%1\" \"-p\"", strModule);
400  if (!SetRegShell(GetWideChar(strClass), L"MakeGroupFile", L"C4Group Pack", strCommand))
401  return 0;
402  }
403  // Done
404 #endif
405  return 1;
406 }
407 
409 {
410 #ifdef _WIN32
411  char strClass[128];
412  int i;
413  // Groups
414  const char *strClasses =
415  "Clonk4.Definition;Clonk4.Folder;Clonk4.Group;Clonk4.Player;Clonk4.Scenario;Clonk4.Update;Clonk4.Weblink";
416  for (i = 0; SCopySegment(strClasses, i, strClass); i++)
417  {
418  // Unpack
419  if (!RemoveRegShell(strClass, "MakeFolder"))
420  return 0;
421  // Explode
422  if (!RemoveRegShell(strClass, "ExplodeFolder"))
423  return 0;
424  }
425  // Directories
426  const char *strClasses2 = "Directory";
427  for (i = 0; SCopySegment(strClasses2, i, strClass); i++)
428  {
429  // Pack
430  if (!RemoveRegShell(strClass, "MakeGroupFile"))
431  return 0;
432  }
433  // Done
434 #endif
435  return 1;
436 }
437 
438 int main(int argc, char *argv[])
439 {
440 #ifndef WIN32
441  // Always line buffer mode, even if the output is not sent to a terminal
442  setvbuf(stdout, nullptr, _IOLBF, 0);
443 #endif
444  // Scan options
445  fQuiet = true;
446  int iFirstGroup = 0;
447  for (int i = 1; i < argc; ++i)
448  {
449  // Option encountered
450  if (argv[i][0] == '-')
451  {
452  switch (argv[i][1])
453  {
454  // Quiet mode
455  case 'v':
456  fQuiet = false;
457  break;
458  // Recursive mode
459  case 'r':
460  fRecursive = true;
461  break;
462  // Register shell
463  case 'i':
464  fRegisterShell = true;
465  break;
466  // Unregister shell
467  case 'u':
468  fUnregisterShell = true;
469  break;
470  // Execute at end
471  case 'x': SCopy(argv[i] + 3, strExecuteAtEnd, _MAX_PATH); break;
472  // Unknown
473  default:
474  fprintf(stderr, "Unknown option %s\n", argv[i]);
475  break;
476  }
477  }
478  else
479  {
480  // filename encountered: no more options expected
481  iFirstGroup = i;
482  break;
483  }
484  }
485  iFirstCommand = iFirstGroup;
486  while (iFirstCommand < argc && argv[iFirstCommand][0] != '-')
487  ++iFirstCommand;
488 
489  // Program info
490  LogF("OpenClonk C4Group %s", C4VERSION);
491 
492  // Init C4Group
494 
495  // Store command line parameters
496  globalArgC = argc;
497  globalArgV = argv;
498 
499  // Register shell
500  if (fRegisterShell)
501  {
503  printf("Shell extensions registered.\n");
504  else
505  printf("Error registering shell extensions.\n");
506  }
507  // Unregister shell
508  if (fUnregisterShell)
509  {
511  printf("Shell extensions removed.\n");
512  else
513  printf("Error removing shell extensions.\n");
514  }
515 
516  // At least one parameter (filename, not option or command): process file(s)
517  if (iFirstGroup)
518  {
519 #ifdef _WIN32
520  // Wildcard in filename: use file search
521  if (SCharCount('*', argv[1]))
522  ForEachFile(argv[1], &ProcessGroup);
523  // Only one file
524  else
525  ProcessGroup(argv[1]);
526 #else
527  for (int i = iFirstGroup; i < argc && argv[i][0] != '-'; ++i)
528  ProcessGroup(argv[i]);
529 #endif
530  }
531  // Too few parameters: output help (if we didn't register stuff)
532  else if (!fRegisterShell && !fUnregisterShell) {
533  printf("\n");
534  printf("Usage: c4group [options] group(s) command(s)\n\n");
535  printf("Commands: -l List\n");
536  printf(" -x Explode\n");
537  printf(" -u Unpack\n");
538  printf(" -p Pack\n");
539  printf(" -t [filename] Pack To\n");
540  printf(" -y [ppid] Apply update (waiting for ppid to terminate first)\n");
541  printf(" -g [source] [target] [title] Make update\n");
542  printf(" -s Sort\n");
543  printf("\n");
544  printf("Options: -v Verbose -r Recursive\n");
545  printf(" -i Register shell -u Unregister shell\n");
546  printf(" -x:<command> Execute shell command when done\n");
547  printf("\n");
548  printf("Examples: c4group pack.ocg -x\n");
549  printf(" c4group update.ocu -g ver1.ocf ver2.ocf New_Version\n");
550  printf(" c4group -i\n");
551  }
552 
553  // Execute when done
554  if (strExecuteAtEnd[0])
555  {
556  printf("Executing: %s\n", strExecuteAtEnd);
557 #ifdef _WIN32
558 
559  STARTUPINFOW startInfo;
560  ZeroMem(&startInfo, sizeof(startInfo));
561  startInfo.cb = sizeof(startInfo);
562 
563  PROCESS_INFORMATION procInfo;
564 
565  CreateProcessW(GetWideChar(strExecuteAtEnd), nullptr, nullptr, nullptr, false, 0, nullptr, nullptr, &startInfo, &procInfo);
566 #else
567  switch (fork())
568  {
569  // Error
570  case -1:
571  fprintf(stderr, "Error forking.\n");
572  break;
573  // Child process
574  case 0:
575  execl(strExecuteAtEnd, strExecuteAtEnd, static_cast<char *>(nullptr)); // currently no parameters are passed to the executed program
576  exit(1);
577  // Parent process
578  default:
579  break;
580  }
581 #endif
582  }
583  // Done
584  return iResult;
585 
586 }
char * GetFilename(char *szPath)
Definition: StdFile.cpp:45
bool C4Group_ApplyUpdate(C4Group &hGroup, unsigned long ParentProcessID)
Definition: C4Update.cpp:42
bool C4Group_ExplodeDirectory(const char *szFilename)
Definition: C4Group.cpp:351
bool fRegisterShell
Definition: C4GroupMain.cpp:35
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:152
StdStrBuf::wchar_t_holder GetWideChar(const char *utf8, bool double_null_terminate=false)
bool SCopySegment(const char *szString, int iSegment, char *sTarget, char cSeparator, int iMaxL, bool fSkipWhitespace)
Definition: Standard.cpp:273
int iFirstCommand
Definition: C4GroupMain.cpp:31
void C4Group_SetSortList(const char **ppSortList)
Definition: C4Group.cpp:69
bool fUnregisterShell
Definition: C4GroupMain.cpp:36
const char * C4CFN_FLS[]
Definition: C4Group.cpp:32
bool EraseItemSafe(const char *szFilename)
Definition: C4GroupMain.cpp:41
int RegisterShellExtensions()
bool C4Group_UnpackDirectory(const char *szFilename)
Definition: C4Group.cpp:306
char id[24+4]
Definition: C4Group.h:84
#define _MAX_PATH
char strExecuteAtEnd[_MAX_PATH+1]
Definition: C4GroupMain.cpp:37
bool SEqual(const char *szStr1, const char *szStr2)
Definition: Standard.h:93
void DisplayGroup(C4Group &grp, const char *filter=nullptr)
Definition: C4GroupMain.cpp:46
unsigned int SCharCount(char cTarget, const char *szInStr, const char *cpUntil)
Definition: Standard.cpp:320
C4GroupEntry * Next
Definition: C4Group.h:126
bool C4Group_PackDirectoryTo(const char *szFilename, const char *szFilenameTo)
Definition: C4Group.cpp:221
StdStrBuf GetFullName() const
Definition: C4Group.cpp:2078
int iResult
Definition: C4GroupMain.cpp:39
bool GetFileCRC(const char *szFilename, uint32_t *pCRC32)
Definition: CStdFile.cpp:355
int UnregisterShellExtensions()
bool Open(const char *szGroupName, bool fCreate=false)
Definition: C4Group.cpp:514
void SetStdOutput(bool fStatus)
Definition: C4Group.cpp:509
bool fQuiet
Definition: C4SimpleLog.cpp:25
unsigned int EntryCRC32(const char *szWildCard=nullptr)
Definition: C4Group.cpp:1877
bool fRecursive
Definition: C4GroupMain.cpp:34
bool Close()
Definition: C4Group.cpp:755
void PrintGroupInternals(C4Group &grp, int indent_level=0)
Definition: C4GroupMain.cpp:94
const char * getData() const
Definition: StdBuf.h:442
const char * GetError()
Definition: C4Group.cpp:504
const C4GroupHeader & GetHeader() const
Definition: C4Group.cpp:2245
int main(int argc, char *argv[])
bool EraseItem(const char *szItemName)
Definition: StdFile.cpp:809
bool WildcardMatch(const char *szWildcard, const char *szString)
Definition: StdFile.cpp:374
int ForEachFile(const char *szDirName, bool(*fnCallback)(const char *))
Definition: StdFile.cpp:1044
bool Log(const char *szMessage)
Definition: C4Log.cpp:202
bool MakeUpdate(const char *strFile1, const char *strFile2, const char *strUpdateFile, const char *strName=nullptr)
Definition: C4Update.cpp:600
bool Sort(const char *szSortList)
Definition: C4Group.cpp:1963
std::enable_if< std::is_pod< T >::value >::type ZeroMem(T *lpMem, size_t dwSize)
Definition: Standard.h:60
const C4GroupEntry * GetFirstEntry() const
Definition: C4Group.cpp:2247
#define DirectorySeparator
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:260
int Entries
Definition: C4Group.h:87
bool OpenAsChild(C4Group *pMother, const char *szEntryName, bool fExclusive=false, bool fCreate=false)
Definition: C4Group.cpp:1585
int globalArgC
Definition: C4GroupMain.cpp:29
bool ProcessGroup(const char *FilenamePar)
bool C4Group_PackDirectory(const char *szFilename)
Definition: C4Group.cpp:284
Definition: C4Group.h:105
char ** globalArgV
Definition: C4GroupMain.cpp:30