#include #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) { 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) { memScrambleHeader(childEntry->data); 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; } size_t currentSize = sizeof(C4GroupHeader); C4GroupHeader* header = malloc(currentSize); strm.next_out = (Bytef*)header; strm.avail_out = currentSize; 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); assert(header->Ver1 == 1); assert(header->Ver2 == 2); currentSize += sizeof(C4GroupEntryCore) * header->Entries; header = realloc(header, currentSize); C4GroupEntryCore* cores = (C4GroupEntryCore*)((void*)(header) + sizeof(C4GroupHeader)); GroupEntryList* entries = GroupEntryListNew(); strm.next_out = (Bytef*)cores; strm.avail_out = sizeof(C4GroupEntryCore) * header->Entries; ret = inflate(&strm, Z_SYNC_FLUSH); if(ret != Z_OK) { fprintf(stderr, "ERROR: inflating header: %s\n", zError(ret)); return EXIT_FAILURE; } C4GroupEntryCore* last = cores + header->Entries - 1; size_t uncompressedSize = last->Offset + last->Size; currentSize += uncompressedSize; #define TMP_FILE "cc4group.tmp" int tmpFile = open(TMP_FILE, O_CREAT | O_RDWR | O_TRUNC | O_EXCL, 0600); if(tmpFile == -1) { fprintf(stderr, "ERROR: Opening tmp file \"%s\": %s\n", "cc4group.tmp", strerror(errno)); return EXIT_FAILURE; } if(unlink(TMP_FILE) == -1) { fprintf(stderr, "ERROR: Deleting tmp file \"%s\". Manual deletion is required: %s\n", TMP_FILE, strerror(errno)); } #undef TMP_FILE // allocate file size // https://gist.github.com/marcetcheverry/991042 if(lseek(tmpFile, currentSize - 1, SEEK_SET) == -1) { fprintf(stderr, "ERROR: Seeking the tmp file to the end: %s\n", strerror(errno)); return EXIT_FAILURE; } if(write(tmpFile, "", 1) == -1) { fprintf(stderr, "ERROR: Writing to the tmp file's end: %s\n", strerror(errno)); return EXIT_FAILURE; } uint8_t* mmappedHeader = mmap(NULL, currentSize, PROT_READ | PROT_WRITE, MAP_SHARED, tmpFile, 0); if(mmappedHeader == MAP_FAILED) { fprintf(stderr, "ERROR: Mapping tmp file: %s\n", strerror(errno)); return EXIT_FAILURE; } if(close(tmpFile) == -1) { fprintf(stderr, "ERROR: Closing tmp file \"cc4group.tmp\": %s\n", strerror(errno)); return EXIT_FAILURE; } uint8_t* data = (void*)(mmappedHeader) + sizeof(C4GroupHeader) + sizeof(C4GroupEntryCore) * header->Entries; // write alredy decompressed header and cores into the file memcpy(mmappedHeader, header, data - mmappedHeader); 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; } C4GroupEntryData root = {.data = (uint8_t*)mmappedHeader, .children = NULL}; buildChildren(&root); handleChildren(root.children, 0, argv[2]); deleteChildren(entries); free(header); if(munmap(mmappedHeader, size) == -1) { fprintf(stderr, "ERROR: Unmapping tmp file: %s\n", strerror(errno)); return EXIT_FAILURE; } return EXIT_SUCCESS; }