summaryrefslogtreecommitdiffstats
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
parent20740c7acc81f34c49bca508c5d83357d02e48be (diff)
downloadcc4group-e1623f08ffda3602928fec65b1f02c70324188ce.tar.gz
cc4group-e1623f08ffda3602928fec65b1f02c70324188ce.zip
Very basic C4Group extracting implementation
-rw-r--r--CMakeLists.txt13
-rw-r--r--GenericList.h142
-rw-r--r--main.c325
-rw-r--r--main.cpp6
4 files changed, 478 insertions, 8 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d8a6458..b7bb5cc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,15 @@
cmake_minimum_required(VERSION 2.6)
project(cc4group)
-add_executable(cc4group main.cpp)
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_C_STANDARD_REQUIRED ON)
-install(TARGETS cc4group RUNTIME DESTINATION bin)
+if(${CMAKE_C_COMPILER_ID} MATCHES GNU)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wcast-align -Werror")
+endif()
+
+find_package(ZLIB REQUIRED)
+
+add_executable(cc4group main.c)
+target_link_libraries(cc4group PRIVATE ZLIB::ZLIB)
+target_include_directories(cc4group PRIVATE ZLIB::ZLIB)
diff --git a/GenericList.h b/GenericList.h
new file mode 100644
index 0000000..0679bb7
--- /dev/null
+++ b/GenericList.h
@@ -0,0 +1,142 @@
+#pragma once
+
+/**
+ *
+ * A collection of macros to create a doubly linked list of an arbitrary type.
+ *
+ * For normal usage just use LIST_AUTO(type, listTypeName) where type is the type to be stored and listTypeName is the desired typeName of the list container.
+ * List nodes get the type listTypeName##Entry.
+ *
+ * LIST_AUTO generates a list container struct of the name listTypeName,
+ * a list entry struct of the name listTypeName##Entry and
+ * functions of the name listTypeName##function for each function of
+ * New, Destroy, Empty (checks if the list is empty), Size, First, Last, Insert and Remove.
+ *
+ * For a simple foreach loop over a list use LIST_FOREACH(listTypeName, list, loopVariable),
+ * where listTypeName is the same as for LIST_AUTO, list is the list to be looped over and loopVariable is the name of the currently iterated list entry.
+ *
+ * (c) Markus Mittendrein - 2017-2018
+ *
+ **/
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+#define LIST_ENTRY_STRUCT(typeName, type) struct list_entry_##typeName {\
+ type value;\
+ struct list_entry_##typeName* next;\
+ struct list_entry_##typeName* prev;\
+}
+
+#define LIST_STRUCT(type) struct list_##type\
+{\
+ struct list_entry_##type* head;\
+ struct list_entry_##type* tail;\
+ size_t size;\
+}
+
+#define LIST_NEW(type, name) struct list_##type* name(void)\
+{\
+ struct list_##type* ret = malloc(sizeof(*ret));\
+ if(ret != NULL)\
+ {\
+ ret->head = NULL;\
+ ret->tail = NULL;\
+ ret->size = 0;\
+ }\
+ return ret;\
+}
+
+#define LIST_DESTROY(type, name) void name(struct list_##type* list)\
+{\
+ for(struct list_entry_##type* entry = list->head; entry != NULL; )\
+ {\
+ struct list_entry_##type* tmp = entry;\
+ entry = entry->next; free(tmp);\
+ }\
+ free(list);\
+}
+
+#define LIST_EMPTY(type, name) bool name(struct list_##type* list) { return list->size == 0; }
+
+#define LIST_SIZE(type, name) size_t name(struct list_##type* list) { return list->size; }
+
+#define LIST_FIRST(type, name) struct list_entry_##type* name(struct list_##type* list) { return list->head; }
+
+#define LIST_LAST(type, name) struct list_entry_##type* name(struct list_##type* list) { return list->tail; }
+
+#define LIST_INSERT(typeName, type, name) struct list_entry_##typeName* name(struct list_##typeName* list, struct list_entry_##typeName* after, type value)\
+{\
+ struct list_entry_##typeName* newEntry = malloc(sizeof(*newEntry));\
+ if(newEntry == NULL)\
+ {\
+ return NULL;\
+ }\
+\
+newEntry->value = value;\
+ newEntry->prev = after;\
+\
+ if(after != NULL)\
+ {\
+ newEntry->next = after->next;\
+ after->next = newEntry;\
+ }\
+ else\
+ {\
+ newEntry->next = list->head;\
+ list->head = newEntry;\
+ }\
+\
+ if(newEntry->next != NULL)\
+ {\
+ newEntry->next->prev = newEntry;\
+ }\
+\
+ if(after == list->tail)\
+ {\
+ list->tail = newEntry;\
+ }\
+\
+ ++list->size;\
+ return newEntry;\
+}
+
+#define LIST_REMOVE(type, name) void name(struct list_##type* list, struct list_entry_##type* entry)\
+{\
+ if(entry == list->head)\
+ {\
+ list->head = entry->next;\
+ }\
+ if(entry == list->tail)\
+ {\
+ list->tail = entry->prev;\
+ }\
+\
+ if(entry->next != NULL)\
+ {\
+ entry->next->prev = entry->prev;\
+ }\
+\
+ if(entry->prev != NULL)\
+ {\
+ entry->prev->next = entry->next;\
+ }\
+\
+ free(entry);\
+ --list->size;\
+}
+
+#define LIST_AUTO(type, listTypeName) typedef LIST_ENTRY_STRUCT(listTypeName, type) listTypeName##Entry;\
+typedef LIST_STRUCT(listTypeName) listTypeName;\
+LIST_NEW(listTypeName, listTypeName##New)\
+LIST_DESTROY(listTypeName, listTypeName##Destroy)\
+LIST_EMPTY(listTypeName, listTypeName##Empty)\
+LIST_SIZE(listTypeName, listTypeName##Size)\
+LIST_FIRST(listTypeName, listTypeName##First)\
+LIST_LAST(listTypeName, listTypeName##Last)\
+LIST_INSERT(listTypeName, type, listTypeName##Insert)\
+LIST_REMOVE(listTypeName, listTypeName##Remove)\
+struct list_entry_##listTypeName* listTypeName##Append(struct list_##listTypeName* list, type value) { return listTypeName##Insert(list, list->tail, value); }\
+struct list_entry_##listTypeName* listTypeName##Prepend(struct list_##listTypeName* list, type value) { return listTypeName##Insert(list, NULL, value); }
+
+#define LIST_FOREACH(listTypeName, list, var) for(listTypeName##Entry* var = listTypeName##First(list); var != NULL; var = var->next)
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;
+}
diff --git a/main.cpp b/main.cpp
deleted file mode 100644
index 8bb47f1..0000000
--- a/main.cpp
+++ /dev/null
@@ -1,6 +0,0 @@
-#include <iostream>
-
-int main(int argc, char **argv) {
- std::cout << "Hello, world!" << std::endl;
- return 0;
-}