summaryrefslogtreecommitdiffstats
path: root/main.c
diff options
context:
space:
mode:
authorMarkus Mittendrein <git@maxmitti.tk>2018-07-02 01:21:26 +0200
committerMarkus Mittendrein <git@maxmitti.tk>2018-07-04 22:44:12 +0200
commite1623f08ffda3602928fec65b1f02c70324188ce (patch)
tree1587c2e441eaa56204ad6e5248cac55aca863ed4 /main.c
parent20740c7acc81f34c49bca508c5d83357d02e48be (diff)
downloadcc4group-e1623f08ffda3602928fec65b1f02c70324188ce.tar.gz
cc4group-e1623f08ffda3602928fec65b1f02c70324188ce.zip
Very basic C4Group extracting implementation
Diffstat (limited to 'main.c')
-rw-r--r--main.c325
1 files changed, 325 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..0558d1b
--- /dev/null
+++ b/main.c
@@ -0,0 +1,325 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <zlib.h>
+
+#include "GenericList.h"
+
+#define C4GroupMaxMaker 30
+#define C4GroupMaxPassword 30
+
+typedef struct {
+ char id[25];
+ uint8_t Reserved1[3];
+ int32_t Ver1, Ver2;
+ int32_t Entries;
+ char Maker[C4GroupMaxMaker + 2];
+ char Password[C4GroupMaxPassword + 2];
+ int32_t Creation;
+ int32_t Original;
+ uint8_t Reserved2[92];
+} __attribute__((__packed__)) C4GroupHeader;
+
+typedef struct {
+ char FileName[257];
+ uint8_t Reserved1[3];
+ int32_t Packed;
+ int32_t Directory;
+ int32_t Size;
+ int32_t Reserved2;
+ int32_t Offset;
+ int32_t Modified;
+ uint8_t HasCRC;
+ uint32_t CRC;
+ uint8_t Executable;
+ uint8_t Reserved3[26];
+} __attribute__((__packed__)) C4GroupEntryCore;
+
+struct list_GroupEntryList;
+typedef struct {
+ C4GroupEntryCore core;
+ uint8_t* data;
+ struct list_GroupEntryList* children;
+} C4GroupEntryData;
+
+LIST_AUTO(C4GroupEntryData, GroupEntryList)
+#define ForeachGroupEntry(list) LIST_FOREACH(GroupEntryList, list, entry)
+
+__off_t fileSizeFd(int fd)
+{
+ struct stat st;
+
+ if(fstat(fd, &st) == -1)
+ {
+ return -1;
+ }
+
+ return st.st_size;
+}
+
+void memScrambleHeader(uint8_t* data)
+{
+ // XOR deface
+ for(size_t i = 0; i < sizeof(C4GroupHeader); i++)
+ data[i] ^= 237;
+ // byte swap
+ for(size_t i = 0; i + 2 < sizeof(C4GroupHeader); i += 3)
+ {
+ uint8_t temp = data[i];
+ data[i] = data[i + 2];
+ data[i + 2] = temp;
+ }
+}
+
+void buildChildren(C4GroupEntryData* entry)
+{
+ memScrambleHeader(entry->data);
+ C4GroupHeader* header = (C4GroupHeader*)entry->data;
+
+ entry->children = GroupEntryListNew();
+
+ uint8_t* data = entry->data + sizeof(C4GroupHeader);
+ uint8_t* childData = entry->data + sizeof(C4GroupHeader) + sizeof(C4GroupEntryCore) * header->Entries;
+
+ for(size_t i = 0; i < (size_t)header->Entries; ++i)
+ {
+ C4GroupEntryCore* core = (C4GroupEntryCore*)(data + sizeof(C4GroupEntryCore) * i);
+ C4GroupEntryData* childEntry = &GroupEntryListAppend(entry->children, (C4GroupEntryData){.core = *core, .data = childData + core->Offset, .children = NULL})->value;
+
+ if(core->Directory)
+ {
+ buildChildren(childEntry);
+ }
+ }
+}
+
+const char* formatTime(int32_t time)
+{
+ time_t tTime = time;
+ static char ret[128];
+ strftime(ret, sizeof(ret), "%c", localtime(&tTime));
+ return ret;
+}
+
+void handleChildren(GroupEntryList* entries, size_t indent, const char* path)
+{
+ size_t pathLen = strlen(path);
+ char* targetPath = malloc(pathLen + 260 + 1);
+
+ strcpy(targetPath, path);
+ strcpy(targetPath + pathLen++, "/");
+
+ if(mkdir(path, 0755) == -1)
+ {
+ fprintf(stderr, "ERROR: Creating target directory \"%s\": %s\n", path, strerror(errno));
+ goto ret;
+ }
+
+ ForeachGroupEntry(entries)
+ {
+ for(size_t i = 0; i < indent; ++i)
+ {
+ putchar('\t');
+ }
+
+ strcpy(targetPath + pathLen, entry->value.core.FileName);
+
+// printf("%s\t%s\t%d B\n", targetPath, formatTime(entry->value.core.Modified), entry->value.core.Size);
+
+ if(!entry->value.core.Directory)
+ {
+ int file = open(targetPath, O_WRONLY | O_CREAT, entry->value.core.Executable ? 0755 : 0644);
+ if(file == -1)
+ {
+ fprintf(stderr, "ERROR: Creating target file \"%s\": %s\n", targetPath, strerror(errno));
+ goto ret;
+ }
+ write(file, entry->value.data, entry->value.core.Size);
+
+ if(close(file) == -1)
+ {
+ fprintf(stderr, "ERROR: Closing file \"%s\": %s\n", targetPath, strerror(errno));
+ }
+
+ struct timeval tv[2] = {{.tv_usec = 0, .tv_sec = entry->value.core.Modified}, {.tv_usec = 0, .tv_sec = entry->value.core.Modified}};
+ if(utimes(targetPath, tv) == -1)
+ {
+ fprintf(stderr, "ERROR: Setting modification time for \"%s\": %s\n", targetPath, strerror(errno));
+ }
+ }
+
+ if(entry->value.core.Directory)
+ {
+ handleChildren(entry->value.children, indent + 1, targetPath);
+ }
+ }
+
+ret:
+ free(targetPath);
+}
+
+void deleteChildren(GroupEntryList* entries)
+{
+ ForeachGroupEntry(entries)
+ {
+ if(entry->value.core.Directory)
+ {
+ deleteChildren(entry->value.children);
+ }
+ }
+
+ GroupEntryListDestroy(entries);
+}
+
+int main(int argc, char* argv[])
+{
+ if(argc != 3)
+ {
+ fprintf(stderr, "USAGE: %s <group> <target>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ int file = open(argv[1], O_RDONLY);
+ if(file == -1)
+ {
+ fprintf(stderr, "ERROR: Opening file \"%s\": %s\n", argv[1], strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ __off_t size = fileSizeFd(file);
+ if(size == -1)
+ {
+ fprintf(stderr, "ERROR: Getting file size: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+ else
+ {
+ printf("Size: %ld\n", size);
+ }
+
+ uint8_t* mappedFile = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, file, 0);
+
+ if(mappedFile == MAP_FAILED)
+ {
+ fprintf(stderr, "ERROR: Mapping file: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ if(close(file) == -1)
+ {
+ fprintf(stderr, "ERROR: Closing file: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ if((mappedFile[0] != 0x1e && mappedFile[0] != 0x1f) || (mappedFile[1] != 0x8c && mappedFile[1] != 0x8b))
+ {
+ fprintf(stderr, "ERROR: The file is not a valid group file. Magic bytes don't match.\n");
+ return EXIT_FAILURE;
+ }
+
+ mappedFile[0] = 0x1f;
+ mappedFile[1] = 0x8b;
+
+ z_stream strm = {
+ .zalloc = NULL,
+ .zfree = NULL,
+ .opaque = NULL,
+ .next_in = mappedFile,
+ .avail_in = size
+ };
+
+ int ret = inflateInit2(&strm, 15 + 16); // window size 15 + automatic gzip
+
+ if(ret != Z_OK)
+ {
+ fprintf(stderr, "ERROR: inflateInit2: %s\n", zError(ret));
+ return EXIT_FAILURE;
+ }
+
+ C4GroupHeader header;
+
+ strm.next_out = (Bytef*)&header;
+ strm.avail_out = sizeof(header);
+
+ ret = inflate(&strm, Z_SYNC_FLUSH);
+ if(ret != Z_OK)
+ {
+ fprintf(stderr, "ERROR: inflating header: %s\n", zError(ret));
+ return EXIT_FAILURE;
+ }
+
+ memScrambleHeader((uint8_t*)&header);
+
+ printf("Version %d.%d\n", header.Ver1, header.Ver2);
+ printf("%d Entries\n", header.Entries);
+ printf("%sOriginal\n", header.Original == 1234567 ? "" : "Not ");
+ printf("Created %s\n", formatTime(header.Creation));
+ puts(header.id);
+ puts(header.Maker);
+
+ GroupEntryList* entries = GroupEntryListNew();
+
+ for(size_t i = 0; i < (size_t)header.Entries; ++i)
+ {
+ GroupEntryListEntry* listEntry = GroupEntryListAppend(entries, (C4GroupEntryData){.data = NULL, .children = NULL});
+ strm.next_out = (Bytef*)&listEntry->value.core;
+ strm.avail_out = sizeof(listEntry->value.core);
+
+ ret = inflate(&strm, Z_SYNC_FLUSH);
+ if(ret != Z_OK)
+ {
+ fprintf(stderr, "ERROR: inflating header: %s\n", zError(ret));
+ return EXIT_FAILURE;
+ }
+ }
+
+ GroupEntryListEntry* last = GroupEntryListLast(entries);
+ size_t uncompressedSize = last->value.core.Offset + last->value.core.Size;
+
+ uint8_t* data = malloc(uncompressedSize);
+
+ strm.next_out = data;
+ strm.avail_out = uncompressedSize;
+
+ ret = inflate(&strm, Z_SYNC_FLUSH);
+ if(ret != Z_STREAM_END)
+ {
+ fprintf(stderr, "ERROR: inflating header: %s\n", zError(ret));
+ return EXIT_FAILURE;
+ }
+
+ inflateEnd(&strm);
+
+ if(munmap(mappedFile, size) == -1)
+ {
+ fprintf(stderr, "ERROR: Unmapping file: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ ForeachGroupEntry(entries)
+ {
+ entry->value.data = data + entry->value.core.Offset;
+
+ if(entry->value.core.Directory)
+ {
+ buildChildren(&entry->value);
+ }
+ }
+
+ handleChildren(entries, 0, argv[2]);
+
+ deleteChildren(entries);
+
+ free(data);
+
+ return EXIT_SUCCESS;
+}