1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084 |
- /* This file is part of DiDiExtract.
- *
- * DiDiExtract is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * DiDiExtract is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- #include <stdbool.h>
- #include <stdio.h>
- #include <stdint.h>
- #include <string.h>
- #include <time.h>
- #include <cerrno>
- #include <getopt.h>
- #include <libgen.h> // dirname
- #include <sys/stat.h> // mkdir
- // PATH_MAX, NAME_MAX
- #ifdef __linux__
- #include <linux/limits.h>
- #else
- #include <limits.h> // *BSD and MSYS2
- #endif
- #include <assert.h>
- #include <zlib.h>
- #include "pefile.h"
- #include "print.h"
- #include "reader.h"
- #include "errors.h"
- #include "version.h"
- #define INFLATE_BUF_SIZE 0x4000
- #define SIZE_KiB 1024
- #define SIZE_MiB 1048576 // 1024^2
- #define SIZE_GiB 1073741824 // 1024^3
- #define WIN_PATH_MAX 260
- #define MAX_OUTPUT_PATH (PATH_MAX - WIN_PATH_MAX) - 2
- static char OutputPath[MAX_OUTPUT_PATH]; // should be absolute and end with a '/'
- #define DIDI_MAX_CHUNK_COUNT 1024
- // https://www.jazz2online.com/wiki/zlib/
- // https://www.rfc-editor.org/rfc/rfc1950
- // https://www.rfc-editor.org/rfc/rfc1951
- enum Operation {
- OP_NONE = 0,
- OP_EXTRACT = 1,
- OP_EXTRACT_DOOFUS = 2,
- OP_LIST = 3,
- OP_VERIFY = 4,
- OP_HELP = 5
- };
- // This is the header found at the overlay offset. DiDi probably stands for
- // Digital Dimensions, the creator of the installer.
- struct DiDiHeader {
- unsigned char magic[4]; // 0 DiDi
- uint32_t signature; // 4 0xDEADBABE
- uint32_t unknown_1; // 8
- uint32_t unknown_2; // 12
- uint32_t filesize; // 16
- uint32_t unknown_3; // 20
- uint32_t uncompressed_size; // 24
- uint32_t unknown_4; // 28 either a size or offset
- uint32_t filecount; // 32
- uint32_t doofus_offset; // 36 this + overlayoffset = offset to -DOOFUS-
- };
- // Contains deflated data for the installer, like translated strings and icons.
- // NOTE: Did not found a matching CRC32
- struct DoofusHeader {
- unsigned char magic[8]; // 0 -DOOFUS-
- uint32_t uncompressed_size; // 8
- uint32_t data_size; // 12
- uint32_t crc32; // 16 TODO it doesn't match
- };
- // File header
- struct DiDiFile {
- unsigned char magic[4]; // 0 FILE
- uint32_t uncompressed_size; // 4 uncompressed size
- uint32_t data_size; // 8 compressed size
- unsigned char datedata[7]; // 12
- unsigned char unknown_8[8]; // 19
- uint8_t group; // -- set from 'unknown_8' for now
- uint8_t language; // 27
- uint8_t filename_size; // 28
- unsigned char padding; // 29 padding/separator
- // Size of the filename depends on 'filename_size', it is not \0 terminated.
- // The maximum size is 255 since it is limited by a uint8_t, we add +1 so
- // it becomes 256 for the \0 terminator.
- unsigned char filename[256];
- };
- DIDIEErr readDiDiHeader(FILE * fp, DiDiHeader * dest) {
- DIDIEErr status;
- status = readBytesInto(fp, dest->magic, 4);
- if (status != DIDIE_OK) {
- return status;
- }
- // Verify that the magic is 'DiDi'
- if (*((uint32_t*)dest->magic) != 1766091076) {
- printError("Expected 'DiDi' magic.\n");
- return DIDIE_ERR_DIDI;
- }
- status = readUInt32(fp, &dest->signature);
- if (status != DIDIE_OK) {
- return status;
- }
- // Verify that the signature is 0xDEADBABE
- if (dest->signature != 0xDEADBABE) {
- printError("Expected 0xDEADBABE signature.\n");
- return DIDIE_ERR_DIDI;
- }
- status = readUInt32(fp, &dest->unknown_1);
- if (status != DIDIE_OK) {
- return status;
- }
- status = readUInt32(fp, &dest->unknown_2);
- if (status != DIDIE_OK) {
- return status;
- }
- status = readUInt32(fp, &dest->filesize);
- if (status != DIDIE_OK) {
- return status;
- }
- status = readUInt32(fp, &dest->unknown_3);
- if (status != DIDIE_OK) {
- return status;
- }
- status = readUInt32(fp, &dest->uncompressed_size);
- if (status != DIDIE_OK) {
- return status;
- }
- status = readUInt32(fp, &dest->unknown_4);
- if (status != DIDIE_OK) {
- return status;
- }
- status = readUInt32(fp, &dest->filecount);
- if (status != DIDIE_OK) {
- return status;
- }
- status = readUInt32(fp, &dest->doofus_offset);
- if (status != DIDIE_OK) {
- return status;
- }
- return DIDIE_OK;
- }
- DIDIEErr readDoofusHeader(FILE * fp, DoofusHeader * dest) {
- DIDIEErr status;
- status = readBytesInto(fp, dest->magic, 8);
- if (status != DIDIE_OK) {
- return status;
- }
- // Verify that the magic is '-DOOFUS-'
- if (strncmp((const char*)dest->magic, "-DOOFUS-", 8) != 0) {
- printError("Expected '-DOOFUS-' magic.\n");
- return DIDIE_ERR_DIDI;
- }
- status = readUInt32(fp, &dest->uncompressed_size);
- if (status != DIDIE_OK) {
- return status;
- }
- status = readUInt32(fp, &dest->data_size);
- if (status != DIDIE_OK) {
- return status;
- }
- status = readUInt32(fp, &dest->crc32);
- if (status != DIDIE_OK) {
- return status;
- }
- return DIDIE_OK;
- }
- DIDIEErr readDiDiFile(FILE * fp, DiDiFile * dest) {
- DIDIEErr status;
- // init struct
- memset(dest->filename, 0, 256);
- // Read magic
- status = readBytesInto(fp, dest->magic, 4);
- if (status != DIDIE_OK) {
- return status;
- }
- // Verify that the magic is 'FILE'
- if (*((uint32_t*)dest->magic) != 1162627398) {
- printError("Expected 'FILE' magic.\n");
- return DIDIE_ERR_DIDI;
- }
- // Read unknown
- status = readUInt32(fp, &dest->uncompressed_size);
- if (status != DIDIE_OK) {
- return status;
- }
- // Read data_size
- status = readUInt32(fp, &dest->data_size);
- if (status != DIDIE_OK) {
- return status;
- }
- // read date bytes
- status = readBytesInto(fp, dest->datedata, 7);
- if (status != DIDIE_OK) {
- return status;
- }
- // Read unknown 8 bytes
- status = readBytesInto(fp, dest->unknown_8, 8);
- if (status != DIDIE_OK) {
- return status;
- }
- dest->group = dest->unknown_8[5];
- status = readBytesInto(fp, &dest->language, 1);
- if (status != DIDIE_OK) {
- return status;
- }
- // HACKS
- // There might be a extra byte before the filename-size, we don't know.
- // What we know is that after the filename-size there should be a zero byte,
- // probably padding or something. So we use that to check if we should skip
- // a byte or not. TODO detect this once and not per file!
- status = readBytesInto(fp, &dest->filename_size, 1);
- if (status != DIDIE_OK) {
- return status;
- }
- status = readBytesInto(fp, &dest->padding, 1);
- if (status != DIDIE_OK) {
- return status;
- }
- if (dest->padding != 0) {
- //printf("Padding != 0!\n");
- dest->filename_size = dest->padding;
- status = readBytesInto(fp, &dest->padding, 1);
- if (status != DIDIE_OK) {
- return status;
- }
- }
- // Read filename
- status = readBytesInto(fp, dest->filename, dest->filename_size);
- if (status != DIDIE_OK) {
- return status;
- }
- return DIDIE_OK;
- }
- // Returns DIDIE_OK on success
- // Returns DIDIE_ERR_CRC32 on CRC32 mismatch
- // Returns DIDIE_ERR_INFLATE when something else gone wrong.
- DIDIEErr inflateFile(FILE *in_file, FILE *out_file, uint32_t available, z_stream *strm, uint32_t filecrc) {
- int ret;
- unsigned long newcrc;
- unsigned int have;
- unsigned char in_buf[INFLATE_BUF_SIZE];
- unsigned char out_buf[INFLATE_BUF_SIZE];
- newcrc = crc32(0, NULL, 0);
- uint32_t done = 0;
- uint32_t next_available = INFLATE_BUF_SIZE;
- if (next_available > available) {
- next_available = available;
- }
- do {
- // Set how many bytes we want to read next
- if ((done + next_available) > available) {
- next_available = available - done;
- }
- // Reached the end of what we wanted to read.
- if (!next_available) {
- break;
- }
- // Read data
- strm->avail_in = fread(in_buf, 1, next_available, in_file);
- if (ferror(in_file)) {
- printError("Failed to read from input file.\n");
- return DIDIE_ERR_INFLATE;
- }
- // This should never happen because the above check on '!next_available',
- // but just in case.
- if (strm->avail_in == 0) {
- printError("Failed to read, got 0 bytes, this should never happen.\n");
- return DIDIE_ERR_INFLATE;
- }
- strm->next_in = in_buf;
- /* run inflate() on input until output buffer not full */
- do {
- // Inflate
- strm->avail_out = INFLATE_BUF_SIZE;
- strm->next_out = out_buf;
- ret = inflate(strm, Z_NO_FLUSH);
- assert(ret != Z_STREAM_ERROR); /* state not clobbered */
- // Inflate failed
- if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
- if (strm->msg != NULL) {
- printError("zlib: %s\n", strm->msg);
- }
- else {
- printError("zlib error: %d\n", ret);
- }
- return DIDIE_ERR_INFLATE;
- }
- have = INFLATE_BUF_SIZE - strm->avail_out;
- // Write to output file
- if (out_file != NULL) {
- if (fwrite(out_buf, 1, have, out_file) != have || ferror(out_file)) {
- printError("Failed to write, have: %d, strm.avail_out: %d, "
- "next_available: %d\n", have, strm->avail_out,
- next_available);
- return DIDIE_ERR_INFLATE;
- }
- }
- // Update the CRC32
- if (have) {
- newcrc = crc32(newcrc, out_buf, have);
- }
- } while (strm->avail_out == 0);
- done += next_available;
- } while (ret != Z_STREAM_END);
- if (filecrc != newcrc) {
- printError("CRC Mismatch! Expected: %08X Got: %08X\n", filecrc, newcrc);
- return DIDIE_ERR_CRC32;
- }
- return DIDIE_OK;
- }
- void printHelp(void) {
- printf("==============================================================\n");
- printf(" Welcome to DiDi Extract version %s\n", DIDIE_VERSION_STR);
- printf("==============================================================\n\n");
- printf(" Usage: didiextract [OPERATION] [OPTIONS] INPUT_FILE\n\n");
- printf(" OPERATIONS\n");
- printf(" -x --extract OUTPUT_PATH Extract files.\n");
- printf(" -y --extract-doofus OUTPUT_PATH Extract doofus.bin, which "
- "contains, installer-strings, "
- "installer-icons/images and "
- "probably some instructions for "
- "the installer.\n");
- printf(" -l --list List files.\n");
- printf(" -V --verify Run extract without actually "
- "outputting files, crc32s will be "
- "checked.\n");
- printf(" -v --version Print version and exit.\n");
- printf(" -h --help Display this HELP.\n");
- printf("\n");
- printf(" OPTIONS\n");
- printf(" -g --group GROUP_NO List, verify or extract files "
- "inside this group-number. Use "
- "-l --list to see all files with "
- "their group numbers.\n");
- printf(" -d --debug Print debug info.\n");
- printf(" -s --silent Be silent, don't print anything.\n");
- printf("\n");
- printf(" NOTES\n");
- printf(" - Path to directory OUTPUT_PATH should exist and be writable.\n");
- printf(" - All files DiDi Extract does output will be overwritten when "
- "they exist!\n");
- }
- void printPrettySize(size_t size) {
- if (size > SIZE_GiB) {
- printInfo("%.2f GiB", (float)size / SIZE_GiB);
- }
- else
- if (size > SIZE_MiB) {
- printInfo("%.2f MiB", (float)size / SIZE_MiB);
- }
- else
- if (size > SIZE_KiB) {
- printInfo("%.2f KiB", (float)size / SIZE_KiB);
- }
- else {
- printInfo("%zu bytes", size);
- }
- }
- bool setPath(const char * optarg, char * dest) {
- // Resolve absolute path
- char * outputPath = realpath(optarg, dest);
- if (outputPath == NULL) {
- printError("Invalid PATH given, could not resolve absolute path for "
- "'%s'. Errno: %s\n", optarg, strerror(errno));
- return false;
- }
- size_t outputPathLen = strlen(outputPath);
- // -2 for the potential '/' we may add
- if (outputPathLen >= (MAX_OUTPUT_PATH - 1)) {
- printError("Absolute path of PATH is to large.\n");
- return false;
- }
- // Make sure the path ends with a '/'
- if (dest[outputPathLen - 1] != '/') {
- strcat(dest, "/");
- }
- // Make sure the path exists
- if (access(dest, F_OK) != 0) {
- // dest exists but is not a directory
- if (errno == ENOTDIR) {
- printError("'%s' is not a directory.\n", dest);
- return false;
- }
- }
- return true;
- }
- // Must be a valid pointer to a \0 terminated string.
- char * parsePath(char *path, char *dest) {
- char * pathCopy;
- char * section;
- char * returnPath;
- uint32_t strSize = 0;
- // Basic verification that this string may be a valid path
- do {
- unsigned char ch = path[strSize];
- if (ch == 0x00) {
- break;
- }
- // It contains a illegal character
- if (ch < 0x20 || ch > 0x7E || ch == '/') {
- printError("parsePath: path contains an illegal character "
- "0x%02X\n", ch);
- return NULL;
- }
- strSize++;
- // Path is to long
- if (strSize > WIN_PATH_MAX) {
- printError("parsePath: path is larger then WIN_PATH_MAX\n");
- return NULL;
- }
- } while (1);
- // Duplicate the path for the use with strtok
- pathCopy = strdup(path);
- if (pathCopy == NULL) {
- printError("parsePath: errno: %s\n", strerror(errno));
- return NULL;
- }
- dest[0] = 0x00;
- section = strtok(pathCopy, "\\");
- do {
- size_t sectionLen = strlen(section);
- if (sectionLen > NAME_MAX) {
- printError("parsePath: path component name exceeds NAME_MAX\n");
- free(pathCopy);
- return NULL;
- }
- // Don't allow a path section to start with '..'
- if (sectionLen >= 2) {
- if (section[0] == '.' && section[1] == '.') {
- printError("parsePath: path component starts with '..'."
- " Symbolic paths are not allowed! Path: '%s'\n", path);
- free(pathCopy);
- return NULL;
- }
- }
- strcat(dest, "/");
- strcat(dest, section);
- } while ((section = strtok(NULL, "\\")));
- free(pathCopy);
- returnPath = strdup(dest + 1); // +1 to remove the first character '/'
- if (returnPath == NULL) {
- printError("parsePath: errno: %s\n", strerror(errno));
- return NULL;
- }
- return returnPath;
- }
- bool preparePath(char * basePath, char * subPath, char * dest) {
- // Join paths
- if ((strlen(basePath) + strlen(subPath) + 1) > PATH_MAX) {
- printError("Overflow of final path > PATH_MAX\n");
- return false;
- }
- strcpy(dest, basePath);
- strcat(dest, subPath);
- // Try to create directories as needed
- char * outputFilePath;
- char * currentSubPath;
- char * separator;
- // make a copy which strchr may manipulate.
- outputFilePath = strdup(dest);
- if (outputFilePath == NULL) {
- printError("Errno: %s\n", strerror(errno));
- return false;
- }
- // get the path without filename
- currentSubPath = dirname(outputFilePath);
- // get the path its root (string until first '/')
- separator = strchr(currentSubPath, '/');
- // This should not happen because the given path by the user should exist.
- if (separator == NULL) {
- printError("This should not happen, please report if it does! (1)\n");
- return false;
- }
- // iterate through all sub-directories from root
- while (separator != NULL) {
- // terminate the dirName string on next occurance of '/'
- separator[0] = 0x00;
- // do not create root
- if (currentSubPath[0] != 0x00) {
- // stat currentSubPath
- if (access(currentSubPath, F_OK) != 0) {
- // currentSubPath exists but is not a directory
- if (errno == ENOTDIR) {
- printError("Extract subpath '%s' exists but is not a directory!\n",
- currentSubPath);
- free(outputFilePath);
- return false;
- }
- // currentSubPath does not exist, try to create a new directory
- if (errno == ENOENT) {
- errno = 0;
- if (mkdir(currentSubPath, 0777) != 0) {
- printError("Failed to create subpath (1): '%s'\n", currentSubPath);
- printError("Errno: %s\n", strerror(errno));
- free(outputFilePath);
- return false;
- }
- }
- }
- }
- // reset the previous set terminator
- separator[0] = '/';
- // set separator to next occurrence of '/' (will be set to NULL when
- // there are no more occurrences of '/'.
- separator = strchr(separator + 1, '/');
- }
- // last subdir
- if (access(currentSubPath, F_OK) != 0) {
- // currentSubPath exists but is not a directory
- if (errno == ENOTDIR) {
- printError("Extract path '%s' exists but is not a directory!\n",
- currentSubPath);
- free(outputFilePath);
- return false;
- }
- // currentSubPath does not exist, try to create a new directory
- if (errno == ENOENT) {
- if (mkdir(currentSubPath, 0777) != 0) {
- printError("Failed to create subpath (2): '%s'\n", currentSubPath);
- printError("Errno: %s\n", strerror(errno));
- free(outputFilePath);
- return false;
- }
- }
- }
- // cleanup
- free(outputFilePath);
- return true;
- }
- DIDIEErr listFiles(FILE *fp, uint32_t count, uint8_t group) {
- DiDiFile didiFile;
- DIDIEErr status;
- size_t totalsize = 0;
- uint32_t totalfiles = 0;
- printInfo("GRP SIZE NAME\n");
- printInfo("--- ---------- ------------\n");
- for (uint32_t i = 0; i < count; i++) {
- status = readDiDiFile(fp, &didiFile);
- if (status != DIDIE_OK) {
- fclose(fp);
- printError("Failed to read DiDiFile\n");
- return status;
- }
- fseek(fp, didiFile.data_size, SEEK_CUR);
- // skip (filtered out by given argument)
- if (group != 0xff && group != didiFile.group) {
- continue;
- }
- totalsize += didiFile.uncompressed_size;
- totalfiles++;
- printInfo("%3d %10u %s\n", didiFile.group, didiFile.uncompressed_size, didiFile.filename);
- }
- printInfo("---------------------------\n");
- printInfo("%d files (%ul bytes aka ", totalfiles, totalsize);
- printPrettySize(totalsize);
- printInfo(")\n");
- return DIDIE_OK;
- }
- DIDIEErr extractFiles(FILE *fp, uint32_t count, uint8_t group, bool onlycheck) {
- DiDiFile didiFile;
- uint8_t curgroup; // Current file group
- DIDIEErr status = DIDIE_OK;
- int zstatus;
- z_stream strm;
- char filepath[WIN_PATH_MAX];
- char finalFilepath[PATH_MAX];
- // Largest group number I've seen is 10 (0x0A), so we reserve 0xff for
- // when the group is unset by us.
- curgroup = 0xff;
- for (uint32_t i = 0; i < count; i++) {
- status = readDiDiFile(fp, &didiFile);
- if (status != DIDIE_OK) {
- printError("Failed to read DiDiFile\n");
- break;
- }
- // handle file groups
- if (didiFile.group != curgroup) {
- (void)inflateEnd(&strm);
- // allocate inflate state
- strm.zalloc = Z_NULL;
- strm.zfree = Z_NULL;
- strm.opaque = Z_NULL;
- strm.avail_in = 0;
- strm.next_in = Z_NULL;
- zstatus = inflateInit(&strm);
- if (zstatus != Z_OK) {
- status = DIDIE_ERR_OMEM;
- break;
- }
- curgroup = didiFile.group;
- }
- // skip (filtered out by given arguments)
- if (group != 0xff && group != didiFile.group) {
- fseek(fp, didiFile.data_size, SEEK_CUR);
- continue;
- }
- printInfo("%s\n", didiFile.filename);
- // When outputFile == NULL, it will not output any files.
- FILE *outputFile = NULL;
- // Do output files
- if (!onlycheck) {
- // Parse WIN32 filepath
- if (parsePath((char *)didiFile.filename, filepath) == NULL) {
- printError("Failed to parse win32 filepath.\n");
- status = DIDIE_ERR_PATH;
- break;
- }
- // Create 'finalFilepath' and make sure directories for that exist,
- // create them when needed to.
- if (!preparePath(OutputPath, filepath, finalFilepath)) {
- status = DIDIE_ERR_PATH;
- break;
- }
- outputFile = fopen(finalFilepath, "w");
- }
- // Read data chunk sizes
- uint16_t chunkcount = 0;
- uint32_t chunkstotal = 0;
- uint16_t chunksizes[DIDI_MAX_CHUNK_COUNT];
- while (chunkstotal < didiFile.data_size) {
- status = readUInt16(fp, &chunksizes[chunkcount]);
- if (status != DIDIE_OK) {
- break;
- }
- chunkstotal += chunksizes[chunkcount] + 2;
- chunkcount++;
- if (chunkcount > DIDI_MAX_CHUNK_COUNT) {
- printError("Overflow: chunkcount > %d\n", DIDI_MAX_CHUNK_COUNT);
- status = DIDIE_ERR_DIDI;
- break;
- }
- }
- // Reading data chunk sizes failed.
- if (status != DIDIE_OK) {
- break;
- }
- for (uint32_t i = 0; i < chunkcount; ++i) {
- // read CRC32
- uint32_t filecrc32;
- status = readUInt32(fp, &filecrc32);
- if (status != DIDIE_OK) {
- break;
- }
- // Inflate
- // NOTE: -4 for the CRC32
- status = inflateFile(fp, outputFile, chunksizes[i] - 4, &strm, filecrc32);
- if (status != DIDIE_OK) {
- // Errors should be printed.
- break;
- }
- }
- // Close output file
- if (outputFile != NULL) {
- fclose(outputFile);
- }
- // Inflate failed or CRC32 mismatch.
- if (status != DIDIE_OK) {
- break;
- }
- }
- if (curgroup != 0xff) {
- (void)inflateEnd(&strm);
- }
- return status;
- }
- DIDIEErr extractDoofus(FILE *fp) {
- DoofusHeader doofus;
- DIDIEErr status;
- FILE *outputFile;
- int zstatus;
- z_stream strm;
- char finalFilepath[PATH_MAX];
- status = readDoofusHeader(fp, &doofus);
- if (status != DIDIE_OK) {
- printError("Failed to read DoofusHeader\n");
- return DIDIE_ERR_DIDI;
- }
- printDebug("-DOOFUS- Header\n");
- printDebug("---------------------------------------------------\n");
- printDebug("Magic : %.8s\n", doofus.magic);
- printDebug("uncompressed size: %d\n", doofus.uncompressed_size);
- printDebug("data_size : %d\n", doofus.data_size);
- printDebug("CRC32 : %08X\n", doofus.crc32);
- printDebug("CRC32 : %08X\n", ~doofus.crc32);
- // allocate inflate state
- strm.zalloc = Z_NULL;
- strm.zfree = Z_NULL;
- strm.opaque = Z_NULL;
- strm.avail_in = 0;
- strm.next_in = Z_NULL;
- zstatus = inflateInit(&strm);
- if (zstatus != Z_OK) {
- return DIDIE_ERR_OMEM;
- }
- if (!preparePath(OutputPath, (char*)"doofus.bin", finalFilepath)) {
- return DIDIE_ERR_PATH;
- }
- outputFile = fopen(finalFilepath, "w");
- status = inflateFile(fp, outputFile, doofus.data_size, &strm, doofus.crc32);
- fclose(outputFile);
- return status;
- }
- int main(int argc, char *argv[]) {
- char inputFile[PATH_MAX];
- long overlayOffset;
- FILE * fp;
- uint8_t group = 0xff;
- enum Operation operation = OP_NONE;
- inputFile[0] = 0x00;
- // https://www.gnu.org/software/libc/manual/html_node/Getopt-Long-Options.html
- // https://www.gnu.org/software/libc/manual/html_node/Getopt-Long-Option-Example.html
- struct option long_options[] = {
- // OPERATIONS
- {"extract" , required_argument, NULL, 'x'},
- {"extract-doofus", required_argument, NULL, 'y'},
- {"list" , no_argument , NULL, 'l'},
- {"verify" , no_argument , NULL, 'V'},
- {"version" , no_argument , NULL, 'v'},
- {"help" , no_argument , NULL, 'h'},
- // OPTIONS
- {"group" , required_argument, NULL, 'g'},
- {"debug" , no_argument , NULL, 'd'},
- {"silent" , no_argument , NULL, 's'},
- {NULL , 0 , NULL, 0}
- };
- int option_index = 0;
- for (;;) {
- int opt = getopt_long(argc, argv, "x:y:g:lhdsVv",
- long_options, &option_index);
- if (opt == -1) {
- break;
- }
- switch (opt) {
- // OPERATIONS
- case 'x':
- {
- if (operation != OP_NONE) {
- printError("More then one operation is set! Do set only one.\n");
- return 1;
- }
- operation = OP_EXTRACT;
- if (setPath(optarg, OutputPath) == false) {
- return 1;
- }
- }
- break;
- case 'y':
- if (operation != OP_NONE) {
- printError("More then one operation is set! Do set only one.\n");
- return 1;
- }
- operation = OP_EXTRACT_DOOFUS;
- if (setPath(optarg, OutputPath) == false) {
- return 1;
- }
- break;
- case 'l':
- if (operation != OP_NONE) {
- printError("More then one operation is set! Do set only one.\n");
- return 1;
- }
- operation = OP_LIST;
- break;
- case 'V':
- if (operation != OP_NONE) {
- printError("More then one operation is set! Do set only one.\n");
- return 1;
- }
- operation = OP_VERIFY;
- break;
- case 'v':
- printf("DiDi Extract v%s\n", DIDIE_VERSION_STR);
- return 0;
- case 'h':
- printHelp();
- return 0;
- // OPTIONS
- case 'g':
- {
- char *str;
- unsigned long val = strtoul(optarg, &str, 10);
- if (val > 0xfe) {
- printError("Invalid group: number to large\n");
- return 1;
- }
- if (strlen(str)) {
- printError("Invalid group: string not allowed\n");
- return 1;
- }
- group = (uint8_t)val;
- }
- break;
- case 'd':
- setPrintFlag(PRINT_DEBUG);
- break;
- case 's':
- setPrintFlags(PRINT_SILENT);
- break;
- case '?':
- // invalid option
- printError("Invalid operation or option\n");
- return 1;
- default:
- printError("default\n");
- break;
- }
- }
- if ((argc - 1 ) < optind) {
- printError("Please supply a input file\n");
- return 1;
- }
- if ((argc - 1 ) > optind) {
- printError("Please supply only one input file\n");
- return 1;
- }
- if (strlen(argv[optind]) > (PATH_MAX - 1)) {
- printError("What are you trying to do? INPUT_FILE is larger then PATH_MAX\n");
- return 1;
- }
- strcpy(inputFile, argv[optind]);
- if (operation == OP_NONE) {
- printError("Please specify a operation.\n");
- return 1;
- }
- // Check if input file exists
- if (access(inputFile, F_OK) != 0) {
- printError("InputFile '%s' not found. Errno: %s\n", inputFile,
- strerror(errno));
- return 1;
- }
- // Get offset to overlay data
- overlayOffset = pefileGetOverlayOffset(inputFile);
- if (overlayOffset == -1) {
- printError("Failed to find overlay offset.\n", inputFile);
- return 1;
- }
- // Open inputFile
- fp = fopen(inputFile, "rb");
- if (fp == NULL) {
- printError("Failed to open inputFile '%s'\n", inputFile);
- printError("Errno: %s\n", strerror(errno));
- return 1;
- };
- // Seek to overlayData
- if (fseek(fp, overlayOffset, SEEK_SET) != 0) {
- printError("Failed to seek to overlayData. Offset: 0x%08X\n", overlayOffset);
- printError("Errno: %s\n", strerror(errno));
- fclose(fp);
- return 1;
- }
- DiDiHeader didiHeader;
- DIDIEErr status;
- status = readDiDiHeader(fp, &didiHeader);
- if (status != DIDIE_OK) {
- fclose(fp);
- printError("Failed to read DiDiHeader\n");
- return status;
- }
- printDebug("Overlay offset : %d\n", overlayOffset);
- printDebug("DIDI Header\n");
- printDebug("---------------------------------------------------\n");
- printDebug("Magic : %.4s\n", didiHeader.magic);
- printDebug("Signature : 0x%08X\n", didiHeader.signature);
- printDebug("Filecount : %d\n", didiHeader.filecount);
- printDebug("Unknown 1 : %08X %d\n", didiHeader.unknown_1, didiHeader.unknown_1);
- printDebug("Unknown 2 : %08X %d\n", didiHeader.unknown_2, didiHeader.unknown_2);
- printDebug("filesize : %08X %d\n", didiHeader.filesize, didiHeader.filesize);
- printDebug("Unknown 3 : %08X %d\n", didiHeader.unknown_3, didiHeader.unknown_3);
- printDebug("Uncompressed size: %08X %d\n", didiHeader.uncompressed_size, didiHeader.uncompressed_size);
- printDebug("Unknown 4 : %08X %d\n", didiHeader.unknown_4, didiHeader.unknown_4);
- printDebug("DOOFUS offset : %08X %d\n", didiHeader.doofus_offset, didiHeader.doofus_offset);
- if (operation == OP_LIST) {
- status = listFiles(fp, didiHeader.filecount, group);
- }
- else
- if (operation == OP_EXTRACT) {
- status = extractFiles(fp, didiHeader.filecount, group, false);
- }
- else
- if (operation == OP_VERIFY) {
- status = extractFiles(fp, didiHeader.filecount, group, true);
- }
- else
- if (operation == OP_EXTRACT_DOOFUS) {
- fseek(fp, overlayOffset + didiHeader.doofus_offset, SEEK_SET);
- status = extractDoofus(fp);
- }
- fclose(fp);
- return status;
- }
|