ref: d687b4ee0c7ac58d9a9e5e50fb1813f47f17681d
dir: /src/rewise.c/
/* This file is part of REWise. * * REWise 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. * * REWise 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 <stdio.h> #include <string.h> #include <unistd.h> #include <linux/limits.h> // PATH_MAX, NAME_MAX #include <stdlib.h> #include <getopt.h> #include <time.h> #include <libgen.h> // dirname #include <errno.h> #include <sys/stat.h> // mkdir #include <utime.h> #include <sys/statvfs.h> #include "print.h" #include "reader.h" #include "inflate.h" #include "pefile.h" #include "errors.h" #include "wiseoverlay.h" #include "wisescript.h" #include "version.h" #ifndef REWISE_DEFAULT_TMP_PATH #define REWISE_DEFAULT_TMP_PATH "/tmp/" #endif #define SIZE_KiB 1024 #define SIZE_MiB 1048576 // 1024^2 #define SIZE_GiB 1073741824 // 1024^3 enum Operation { OP_NONE = 0, OP_EXTRACT = 1, OP_EXTRACT_RAW = 2, OP_LIST = 3, OP_VERIFY = 4, OP_HELP = 5, OP_SCRIPT_DEBUG = 6 }; void printPrettySize(size_t size) { if (size > SIZE_GiB) { printf("%.2f GiB", (float)size / SIZE_GiB); } else if (size > SIZE_MiB) { printf("%.2f MiB", (float)size / SIZE_MiB); } else if (size > SIZE_KiB) { printf("%.2f KiB", (float)size / SIZE_KiB); } else { printf("%zu bytes", size); } } unsigned long getFreeDiskSpace(char * path) { struct statvfs fsStats; if (statvfs((const char *)path, &fsStats) != 0) { printError("Failed to determine free disk space for '%s'. Errno: %s\n", strerror(errno)); return 0; } return fsStats.f_bsize * fsStats.f_bavail; } void convertMsDosTime(struct tm * destTime, uint16_t date, uint16_t time) { destTime->tm_year = (int)((date >> 9) + 80); destTime->tm_mon = (int)((date >> 5) & 0b0000000000001111); destTime->tm_mday = (int)(date & 0b0000000000011111); destTime->tm_hour = (int)(time >> 11); destTime->tm_min = (int)((time >> 5) & 0b0000000000111111); destTime->tm_sec = (int)(time & 0b0000000000011111) * 2; } static InflateObject * InflateObjPtr; static long ScriptDeflateOffset; #define MAX_OUTPUT_PATH (PATH_MAX - WIN_PATH_MAX) - 2 static char OutputPath[MAX_OUTPUT_PATH]; // should be absolute and end with a '/' static char TempPath[MAX_OUTPUT_PATH] = REWISE_DEFAULT_TMP_PATH; static char PreserveTmp = 0; static char NoExtract = 0; void printHelp(void) { printf("==============================================================\n"); printf(" Welcome to REWise version %s\n", REWISE_VERSION_STR); printf("==============================================================\n\n"); printf(" Usage: rewise [OPERATION] [OPTIONS] INPUT_FILE\n\n"); printf(" OPERATIONS\n"); printf(" -x --extract OUTPUT_PATH Extract files.\n"); printf(" -r --raw OUTPUT_PATH Extract all files in the overlay " "data. This does not move/rename " "files!\n"); printf(" -l --list List files.\n"); printf(" -V --verify Run extract without actually " "outputting files, crc32s will be " "checked.\n"); printf(" -z --script-debug Print parsed WiseScript.bin\n"); printf(" -v --version Print version and exit.\n"); printf(" -h --help Display this HELP.\n"); printf("\n"); printf(" OPTIONS\n"); printf(" -p --preserve Don't delete TMP files.\n"); printf(" -t --tmp-path TMP_PATH Set temporary path, default: %s\n", REWISE_DEFAULT_TMP_PATH); printf(" -d --debug Print debug info.\n"); printf(" -s --silent Be silent, don't print anything.\n"); printf(" -n --no-extract Don't extract anything. This will " "be ignored with -x or -r. It also " "will not try to remove TMP files, " "so -p won't do anything.\n"); printf("\n"); printf(" NOTES\n"); printf(" - Path to directory OUTPUT_PATH and TMP_PATH should exist and " "be writable.\n"); } void printFile(WiseScriptFileHeader * data) { struct tm fileDatetime; convertMsDosTime(&fileDatetime, data->date, data->time); printf("% 12u %02d-%02d-%04d %02d:%02d:%02d '%s'\n", data->inflatedSize, fileDatetime.tm_mday, fileDatetime.tm_mon, fileDatetime.tm_year + 1900, fileDatetime.tm_hour, fileDatetime.tm_min, fileDatetime.tm_sec, data->destFile); } /* preparePath() - Joins the two given paths to dest and tries to create the * directories that don't exist yet. * param subPath: Rest of the filepath (including file) from WiseScript.bin * Should not be larger then (WIN_PATH_MAX + 1) * param dest : A pre-allocated char buffer with size PATH_MAX */ 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; } void extractFile(WiseScriptFileHeader * data) { bool result; char outputFilePath[PATH_MAX]; // Create the final absolute filepath and make sure the path exists (will be // created when it doesn't exist). if (preparePath(OutputPath, data->destFile, outputFilePath) == false) { printError("preparePath failed.\n"); stopWiseScriptParse(); return; } // Seek to deflated file start if (fseek(InflateObjPtr->inputFile, ((long)data->deflateStart) + ScriptDeflateOffset, SEEK_SET) != 0) { printError("Failed seek to file offset 0x%08X\n", data->deflateStart); printError("Errno: %s\n", strerror(errno)); stopWiseScriptParse(); return; } // Inflate/extract the file result = inflateExtractNextFile(InflateObjPtr, outputFilePath); if (result == false) { printError("Failed to extract '%s'\n", outputFilePath); stopWiseScriptParse(); return; } // Set file access/modification datetime struct tm fileCreation; time_t creationSeconds; convertMsDosTime(&fileCreation, data->date, data->time); creationSeconds = mktime(&fileCreation); const struct utimbuf times = { .actime = creationSeconds, .modtime = creationSeconds }; if (utime(outputFilePath, ×) != 0) { printWarning("Failed to set access and modification datetime for file " "'%s'\n", outputFilePath); } printInfo("Extracted %s\n", data->destFile); } void noExtractFile(WiseScriptFileHeader * data) { // Inflate/extract the file bool result = inflateExtractNextFile(InflateObjPtr, NULL); if (result == false) { printError("Failed to no-extract '%s'\n", data->destFile); stopWiseScriptParse(); return; } printInfo("CRC32 success for '%s'\n", data->destFile); } 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; } // NOTE: realpath would have failed when the directory does not exist. // dest does not exist /*if (errno == ENOENT) { printError("'%s' does not exist.\n", dest); return false; }*/ } return true; } int main(int argc, char *argv[]) { char inputFile[PATH_MAX]; long overlayOffset; FILE * fp; REWError status; 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'}, {"raw" , required_argument, NULL, 'r'}, {"list" , no_argument , NULL, 'l'}, {"verify" , no_argument , NULL, 'V'}, {"script-debug", no_argument , NULL, 'z'}, {"version" , no_argument , NULL, 'v'}, {"help" , no_argument , NULL, 'h'}, // OPTIONS {"temp" , required_argument, NULL, 't'}, {"debug" , no_argument , NULL, 'd'}, {"preserve" , no_argument , NULL, 'p'}, {"silent" , no_argument , NULL, 's'}, {"no-extract" , no_argument , NULL, 'n'}, {NULL , 0 , NULL, 0} }; int option_index = 0; for (;;) { int opt = getopt_long(argc, argv, "x:r:t:lhdspznVv", 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 'r': if (operation != OP_NONE) { printError("More then one operation is set! Do set only one.\n"); return 1; } operation = OP_EXTRACT_RAW; 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 'z': if (operation != OP_NONE) { printError("More then one operation is set! Do set only one.\n"); return 1; } operation = OP_SCRIPT_DEBUG; break; case 'v': printf("REWise v%s\n", REWISE_VERSION_STR); return 0; case 'h': printHelp(); return 0; // OPTIONS case 'd': setPrintFlag(PRINT_DEBUG); break; case 's': setPrintFlags(PRINT_SILENT); break; case 't': if (setPath(optarg, TempPath) == false) { printError("Invalid TMP_PATH given.\n"); return 1; } break; case 'p': PreserveTmp = 1; break; case 'n': NoExtract = 1; 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; } printDebug("InputFile: %s\n", inputFile); printDebug("OverlayOffset: %ld\n", overlayOffset); /* 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; } // Read Wise overlay header WiseOverlayHeader overlayHeader; if ((status = readWiseOverlayHeader(fp, &overlayHeader)) != REWERR_OK) { printError("Failed to read WiseOverlayHeader.\n"); fclose(fp); return 1; } freeWiseOverlayHeader(&overlayHeader); // Here we arrived at the delated data, each entry followed by a CRC32 // https://en.wikipedia.org/wiki/DEFLATE if (huffmanInitFixedTrees() == false) { printError("Failed to huffmanInitFixedTrees, out of mem?\n"); fclose(fp); return 1; } // Initial check on free disk space (TMP_PATH) unsigned long tmpFreeDiskSpace = getFreeDiskSpace(TempPath); if (tmpFreeDiskSpace == 0) { // failed to determine free disk space fclose(fp); return 1; } // make sure at-least 1 MiB is available at the TMP path if (tmpFreeDiskSpace < SIZE_MiB) { printError("At-least 1 MiB of free space is required in the TMP_PATH.\n"); fclose(fp); return 1; } bool result; InflateObject inflateObj; inflateInit(&inflateObj, fp); InflateObjPtr = &inflateObj; // Raw extract if (operation == OP_EXTRACT_RAW) { uint32_t extractCount = 0; char extractFilePath[PATH_MAX]; // Start inflating and outputting files while (ftell(fp) < inflateObj.inputFileSize) { char fileName[21]; if (snprintf(fileName, 20, "EXTRACTED_%09u", extractCount) > 20) { // truncated printError("Failed to format filename, it truncated.\n"); fclose(fp); huffmanFreeFixedTrees(); return 1; } if (preparePath(OutputPath, fileName, extractFilePath) == false) { printError("Failed to create directories for '%s'.\n", fileName); fclose(fp); huffmanFreeFixedTrees(); return 1; } result = inflateExtractNextFile(&inflateObj, (const char *)extractFilePath); if (result == false) { printError("Failed to extract '%s'.\n", extractFilePath); fclose(fp); huffmanFreeFixedTrees(); return 1; } printInfo("Extracted '%s'\n", extractFilePath); extractCount++; } printInfo("Extracted %d files.\n", extractCount); } else { char tmpFileScript[PATH_MAX]; // Skip WiseColors.dib if (NoExtract == 0) { result = inflateExtractNextFile(&inflateObj, NULL); if (result == false) { printError("Failed to extract 'WiseColors.dib'.\n"); fclose(fp); huffmanFreeFixedTrees(); return 1; } } // Create filepath for WiseScript.bin if (preparePath(TempPath, "WiseScript.bin", tmpFileScript) == false) { fclose(fp); huffmanFreeFixedTrees(); printf("Failed to create filepath for WiseScript.bin.\n"); return 1; } // Extract WiseScript.bin if (NoExtract == 0) { result = inflateExtractNextFile(&inflateObj, tmpFileScript); if (result == false) { printError("Failed to extract '%s'.\n", tmpFileScript); fclose(fp); huffmanFreeFixedTrees(); return 1; } } // Determine the inflate data offset inside WiseScript.bin (this needs to // be added to the inflateStart we got for files from WiseScript to get to // the real inflateStart offset in the PE file.) WiseScriptParsedInfo * parsedInfo = wiseScriptGetParsedInfo(tmpFileScript); ScriptDeflateOffset = inflateObj.inputFileSize - parsedInfo->inflateStartOffset; printDebug("scriptDeflateOffset: %ld (0x%08X).\n", parsedInfo->inflateStartOffset); WiseScriptCallbacks callbacks; initWiseScriptCallbacks(&callbacks); // LIST if (operation == OP_LIST) { callbacks.cb_0x00 = &printFile; printf(" FILESIZE FILEDATE FILETIME FILEPATH\n"); printf("------------ ---------- -------- ----------------------------\n"); status = parseWiseScript(tmpFileScript, &callbacks); if (status != REWERR_OK) { printError("Parsing WiseScript failed.\n"); } printf("------------ ---------- -------- ----------------------------\n"); printf("Total size: "); printPrettySize(parsedInfo->inflatedSize0x00); printf(" (%zu bytes)\n", parsedInfo->inflatedSize0x00); } // EXTRACT else if (operation == OP_EXTRACT) { // Check if there is enough free disk space unsigned long outputFreeDiskSpace = getFreeDiskSpace(OutputPath); if (outputFreeDiskSpace == 0) { // failed to determine free disk space fclose(fp); return 1; } if (outputFreeDiskSpace <= parsedInfo->inflatedSize0x00) { printError("Not enough free disk space at '%s'. Required: %ld Left: " "%ld\n", OutputPath, parsedInfo->inflatedSize0x00, outputFreeDiskSpace); fclose(fp); return 1; } // Start inflating and outputting files callbacks.cb_0x00 = &extractFile; status = parseWiseScript(tmpFileScript, &callbacks); // Something went wrong if (status != REWERR_OK) { printError("Parsing WiseScript failed.\n"); } } // SCRIPT_DEBUG else if (operation == OP_SCRIPT_DEBUG) { status = wiseScriptDebugPrint(tmpFileScript); if (status != REWERR_OK) { printError("Debug print WiseScript failed.\n"); } } else if (operation == OP_VERIFY) { callbacks.cb_0x00 = &noExtractFile; status = parseWiseScript(tmpFileScript, &callbacks); if (status != REWERR_OK) { printError("Parsing WiseScript failed.\n"); } printInfo("All looks good!\n"); } // remove tmp files if (PreserveTmp == 0 && NoExtract == 0) { if (remove(tmpFileScript) != 0) { printError("Failed to remove '%s'. Errno: %s\n", tmpFileScript, strerror(errno)); } } } // Cleanup huffmanFreeFixedTrees(); fclose(fp); return status; }