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