diff options
| author | Markus Mittendrein <git@maxmitti.tk> | 2018-07-02 01:21:26 +0200 |
|---|---|---|
| committer | Markus Mittendrein <git@maxmitti.tk> | 2018-07-04 22:44:12 +0200 |
| commit | e1623f08ffda3602928fec65b1f02c70324188ce (patch) | |
| tree | 1587c2e441eaa56204ad6e5248cac55aca863ed4 | |
| parent | 20740c7acc81f34c49bca508c5d83357d02e48be (diff) | |
| download | cc4group-e1623f08ffda3602928fec65b1f02c70324188ce.tar.gz cc4group-e1623f08ffda3602928fec65b1f02c70324188ce.zip | |
Very basic C4Group extracting implementation
| -rw-r--r-- | CMakeLists.txt | 13 | ||||
| -rw-r--r-- | GenericList.h | 142 | ||||
| -rw-r--r-- | main.c | 325 | ||||
| -rw-r--r-- | main.cpp | 6 |
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) @@ -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; -} |
