#include #include #include #include #include #include #include #include #include #include #include #include #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 \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; }