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