diff options
Diffstat (limited to 'main.c')
| -rw-r--r-- | main.c | 325 |
1 files changed, 325 insertions, 0 deletions
@@ -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; +} |
