diff --git a/.gitignore b/.gitignore index 937bcf8..07f8ea1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ #ignore the following directories -.vs/ +.* +!/.gitignore +!.gitattributes Debug/ Release/ Debug (GUI)/ diff --git a/DecimaExplorer.vcxproj b/DecimaExplorer.vcxproj index e54f5ab..f561cda 100644 --- a/DecimaExplorer.vcxproj +++ b/DecimaExplorer.vcxproj @@ -62,18 +62,21 @@ true v142 MultiByte + DecimaExplorer Application true v142 MultiByte + DecimaExplorer-GUI Application true v142 MultiByte + DecimaExplorer-GUI-Repack Application @@ -81,6 +84,7 @@ v142 true MultiByte + DecimaExplorer Application @@ -88,6 +92,7 @@ v142 true MultiByte + DecimaExplorer-GUI Application @@ -95,24 +100,28 @@ v142 true MultiByte + DecimaExplorer-GUI-Repack Application true v142 MultiByte + DecimaExplorer Application true v142 MultiByte + DecimaExplorer-GUI Application true v142 MultiByte + DecimaExplorer-GUI-Repack Application @@ -120,6 +129,7 @@ v142 true MultiByte + DecimaExplorer Application @@ -127,6 +137,7 @@ v142 true MultiByte + DecimaExplorer-GUI Application @@ -134,6 +145,7 @@ v142 true MultiByte + DecimaExplorer-GUI-Repack diff --git a/README.md b/README.md index bcc7052..ab9ecde 100644 --- a/README.md +++ b/README.md @@ -27,17 +27,17 @@ Decima Explorer supports both repacking and packing. Please note there are some ## Usage -There are two flavours of Decima Explorer, one that can be run from the command line and one that runs as a Graphical User Interface. If the Ooz library fails to decompress a file you will need to use a version of the oodle dll. Repacking will require the oodle dll. +There are two flavours of Decima Explorer, one that can be run from the command line and one that runs as a Graphical User Interface. If the Ooz library fails to decompress a file you will need to use a version of the oodle dll (x64 as this is a 64-bit program). Repacking will require the oodle dll. ### GUI -With the GUI version you can select the initial data directory of the game and it will populate a file list based on the games cache prefetch. You can use the keyboard shortcut Ctrl+F to filter this list for the items you are interested in. You can select all the items with Ctrl+A or by Ctrl or shift clicking. With the items you wish to extract selected you can press the extract button and choose a directory in which to extract, when extracting multiple files with the GUI extraction will be multithreaded and should use all available cores. It is currently not possible to extract .mpk archives with the GUI. +With the GUI version you can select the initial data directory of the game and it will populate a file list based on the games cache prefetch (not complete!). You can use the keyboard shortcut Ctrl+F to filter this list for the items you are interested in. You can select all the items with Ctrl+A or by Ctrl or shift clicking. With the items you wish to extract selected you can press the extract button and choose a directory in which to extract, when extracting multiple files with the GUI extraction will be multithreaded and should use all available cores. It is currently not possible to extract .mpk archives with the GUI. There is also a separate GUI for packing and repacking files. I decided to separate this for now for a cleaner UX. When repacking you must first select a folder that contains the complete path for a file, this is because the directory is used when hashing. You can then select and output, if it is a bin file that already exists it will attempt to repack that file. If it is a file that doesnt exist it will pack the files into a new bin file. ### CLI -With the CLI version there are various commands that can be used, they are list, extract, pack and repack. List will dump all the strings from the game's cache prefetch. Extract can extract either with a directory as the input or by file. When extracting by file you can use the file ID to extract as well, this is useful as it doesn't require knowing the filename to extract a particular entry. This currently supports both .bin and .mpk archives. Repack can be used to repack core files to their original .bin file. A root directory should be chosen so that the path from the root directory will match the hashed file name. For example if you extract a file and keep its original filename and directory structure to C:\Files, you can repack by using C:\Files as the base path. Pack uses a base directory the same way as Repack but instread of an existing Bin as the input, it allows you to specify an output bin file to create. Packing and Repacking require oo2core_7_win64.dll to be present alongside Decima Explorer. Below are example instructions that can be used on the command line; +With the CLI version there are various commands that can be used, they are list, extract, pack and repack. List will dump all the strings from the game's cache prefetch (not complete!). Extract can extract either with a directory as the input or by file. When extracting by file you can use the file ID to extract as well, this is useful as it doesn't require knowing the filename to extract a particular entry. This currently supports both .bin and .mpk archives. Repack can be used to repack core files to their original .bin file. A root directory should be chosen so that the path from the root directory will match the hashed file name. For example if you extract a file and keep its original filename and directory structure to C:\Files, you can repack by using C:\Files as the base path. Pack uses a base directory the same way as Repack but instread of an existing Bin as the input, it allows you to specify an output bin file to create. Packing and Repacking require oo2core_7_win64.dll to be present alongside Decima Explorer. Below are example instructions that can be used on the command line; ``` DecimaExplorer.exe -list "G:\path\to\game\data\files" @@ -57,7 +57,12 @@ The same command can be used on movie files. ``` DecimaExplorer.exe -extract input.bin /file/name/to/extract output.bin ``` -The example above is simlar to the last however the file's name is used to chose which file to extract. Only the extract and list commands are implemented for now. +The example above is similar to the last however the file's name is used to chose which file to extract. Only the extract and list commands are implemented for now. + +``` +DecimaExplorer.exe -extract input.bin * +``` +Extracts all IDs in .bin, same as passing ID 0/1/2.../N manually (may print an error at the end, ignore). ``` DecimaExplorer.exe -extract "G:\path\to\game\data\files" /file/name/to/extract output.bin @@ -69,6 +74,11 @@ DecimaExplorer.exe -extract "G:\path\to\game\data\files" /file/name/to/extract ``` It is possible to omit the output file, in this case the input filename or fileID will be used as the file name. If it is a directory, the directory structure will be created. +``` +DecimaExplorer.exe -extract "G:\path\to\game\data\files" filelist.txt +``` +Rather than extracting one by one, you can pass a text list with all names you want to extract. Since not all names are listed in the prefetch list (-l) this is useful to get extra files in bulk. Names can be found in various .core files from the prefetch list. Non-existant names are ignored. + ``` DecimaExplorer.exe -pack "G:\path\to\files\to\pack" output.bin ``` diff --git a/decima/archive/DecimaArchive.h b/decima/archive/DecimaArchive.h index dec0d5e..72e9103 100644 --- a/decima/archive/DecimaArchive.h +++ b/decima/archive/DecimaArchive.h @@ -54,6 +54,6 @@ class DecimaArchive { std::string getFilename(); std::string getExtension(); void setMessageHandler(MessageHandler* messageHandler); - virtual int extractFile(uint32_t id, std::string output) = 0; - virtual int extractFile(std::string filename, std::string output, bool suppressError = 0) = 0; + virtual int extractFile(uint32_t id, const char* output) = 0; + virtual int extractFile(std::string filename, const char* output, bool suppressError = 0) = 0; }; \ No newline at end of file diff --git a/decima/archive/bin/ArchiveBin.cpp b/decima/archive/bin/ArchiveBin.cpp index c9b702e..ab9b15d 100644 --- a/decima/archive/bin/ArchiveBin.cpp +++ b/decima/archive/bin/ArchiveBin.cpp @@ -70,6 +70,7 @@ void ArchiveBin::parseFileTable(FILE* f, uint64_t fileTableCount) { fread(&fileEntry.key2, 4, 1, f); fileTable.push_back(fileEntry); + //hashFiles[fileEntry.hash] = &fileEntry; } } @@ -248,9 +249,46 @@ uint32_t ArchiveBin::getFileEntryIndex(const std::string& filename) { if (fileTable[i].hash == hash) return i; } + + //todo + /* + bool found = hashFiles.find(hash) != hashFiles.end(); + if (found) { + return hashFiles[hash]->entryNum; + } + */ + + return -1; +} + +//todo unify with interface +uint32_t ArchiveBin::getFileEntryIndexExt(std::string& filename) { + const int max_extensions = 6; + const char* extensions[max_extensions] = {"core", "stream", "core.stream", "coretext", "coredebug", "dep"}; + + // try default name first + std::string fname = filename; + uint32_t index = getFileEntryIndex(fname); + if (index != -1) { + return index; + } + + // try common extensions (files like .soundbank.core exists in prefetch list so extension check isn't good) + for (int i = 0; i < max_extensions; i++) { + fname = filename; + addExtension(fname, extensions[i]); + + index = getFileEntryIndex(fname); + if (index != -1) { + filename = fname; + return index; + } + } + return -1; } + BinFileEntry ArchiveBin::getFileEntry(int id) { for (int i = 0; i < fileTable.size(); i++) { if (fileTable[i].entryNum == id) @@ -399,6 +437,7 @@ DataBuffer ArchiveBin::createFileEntries(const std::string& basePath, const std: fileEntry.offset = pos; fileEntry.size = filesize; fileTable.push_back(fileEntry); + //hashFiles[fileEntry.hash] = &fileEntry; } int writePos = isUpdate ? pos - header.dataSize : pos; @@ -471,6 +510,7 @@ void ArchiveBin::swapEntries(const std::vector& swapMap) { std::string firstFile = swapMap[i].firstFile; std::string secondFile = swapMap[i].secondFile; + //todo fix extensions if (!hasExtension(firstFile)) addExtension(firstFile, "core"); if (!hasExtension(secondFile)) addExtension(secondFile, "core"); @@ -543,40 +583,41 @@ int ArchiveBin::update(const std::string& basePath, const std::vectormessageHandler->showError(FILEINDEXERROR); return 0; } + //if (!hasExtension(output)) addExtension(output, "core"); + DataBuffer data = extract(fileTable[i]); if (!writeDataToFile(data, output)) return 0; return 1; } -int ArchiveBin::extractFile(std::string filename, std::string output, bool suppressError) { - if (!hasExtension(filename)) addExtension(filename, "core"); - if (!hasExtension(output)) addExtension(output, "core"); - uint32_t i = getFileEntryIndex(filename); - +int ArchiveBin::extractFile(std::string filename, const char* output, bool suppressError) { + uint32_t i = getFileEntryIndexExt(filename); if (i == -1) { if (!suppressError) this->messageHandler->showError(FILENAMEERROR); return 0; } + std::string outname; + if (output == NULL) + outname = filename; + else + outname = output; + DataBuffer data = extract(fileTable[i]); - if (!writeDataToFile(data, output)) return 0; + if (!writeDataToFile(data, outname)) return 0; return 1; } DataBuffer ArchiveBin::extractFile(std::string filename) { DataBuffer data; - if (!hasExtension(filename)) addExtension(filename, "core"); - uint32_t i = getFileEntryIndex(filename); + uint32_t i = getFileEntryIndexExt(filename); if (i == -1) { this->messageHandler->showError(FILENAMEERROR); diff --git a/decima/archive/bin/ArchiveBin.h b/decima/archive/bin/ArchiveBin.h index 515c53b..624d7cc 100644 --- a/decima/archive/bin/ArchiveBin.h +++ b/decima/archive/bin/ArchiveBin.h @@ -1,6 +1,7 @@ #pragma once #include "../DecimaArchive.h" +//#include typedef struct BinHeader { uint32_t magic; @@ -37,6 +38,7 @@ class ArchiveBin : public DecimaArchive { BinHeader header = {0}; std::vector fileTable; std::vector chunkTable; + //std::unordered_map hashFiles; uint32_t murmurSalt[4] = { 0x0FA3A9443, 0x0F41CAB62, 0x0F376811C, 0x0D2A89E3E }; uint32_t murmurSalt2[4] = { 0x06C084A37, 0x07E159D95, 0x03D5AF7E8, 0x018AA7D3F }; @@ -69,6 +71,7 @@ class ArchiveBin : public DecimaArchive { DataBuffer getChunkData(BinChunkEntry chunkEntry); void decryptChunkData(int32_t id, DataBuffer* data); uint32_t getFileEntryIndex(const std::string& filename); + uint32_t getFileEntryIndexExt(std::string& filename); uint64_t calculateDataOffset(); @@ -100,10 +103,10 @@ class ArchiveBin : public DecimaArchive { int open() override; DataBuffer extractFile(std::string filename); - int extractFile(uint32_t id, std::string output); + int extractFile(uint32_t id, const char* output); void swapEntries(const std::vector& swapMap); void nukeHashes(const std::vector& fileList); - int extractFile(std::string filename, std::string output, bool suppressError = 0); + int extractFile(std::string filename, const char* output, bool suppressError = 0); int create(const std::string& basePath, const std::vector& fileList); int update(const std::string& basePath, const std::vector& fileList); const std::vector& getFileTable() { return fileTable; } diff --git a/decima/archive/mpk/ArchiveMoviePack.cpp b/decima/archive/mpk/ArchiveMoviePack.cpp index c68454a..d26fa07 100644 --- a/decima/archive/mpk/ArchiveMoviePack.cpp +++ b/decima/archive/mpk/ArchiveMoviePack.cpp @@ -34,7 +34,7 @@ uint32_t ArchiveMoviePack::getFileEntryIndex(std::string filename) { return -1; } -int ArchiveMoviePack::extractFile(uint32_t id, std::string output) { +int ArchiveMoviePack::extractFile(uint32_t id, const char* output) { if (id < 0 || id >= fileTable.size()) { this->messageHandler->showError(FILEINDEXERROR); return 0; @@ -45,7 +45,7 @@ int ArchiveMoviePack::extractFile(uint32_t id, std::string output) { return 1; } -int ArchiveMoviePack::extractFile(std::string filename, std::string output, bool suppressError) { +int ArchiveMoviePack::extractFile(std::string filename, const char* output, bool suppressError) { uint32_t i = getFileEntryIndex(filename); if (i == -1) { diff --git a/decima/archive/mpk/ArchiveMoviePack.h b/decima/archive/mpk/ArchiveMoviePack.h index a93d753..4319f99 100644 --- a/decima/archive/mpk/ArchiveMoviePack.h +++ b/decima/archive/mpk/ArchiveMoviePack.h @@ -40,6 +40,6 @@ class ArchiveMoviePack : public DecimaArchive { ArchiveMoviePack(std::string filename); ~ArchiveMoviePack(); int open() override; - int extractFile(uint32_t id, std::string output); - int extractFile(std::string filename, std::string output, bool suppressError = 0); + int extractFile(uint32_t id, const char* output); + int extractFile(std::string filename, const char* output, bool suppressError = 0); }; \ No newline at end of file diff --git a/interface/Interface.cpp b/interface/Interface.cpp index d47ed4e..50b0d73 100644 --- a/interface/Interface.cpp +++ b/interface/Interface.cpp @@ -28,7 +28,6 @@ void Interface::buildFileMap(const char* fileDirectory) { ArchiveBin decimaArchive(availableFiles[i].c_str()); decimaArchive.setMessageHandler(this); if (!decimaArchive.open()) continue; - std::vector fileTable = decimaArchive.getFileTable(); for (int j = 0; j < fileTable.size(); j++) { @@ -37,10 +36,128 @@ void Interface::buildFileMap(const char* fileDirectory) { } } -const char* Interface::getContainingBinFile(const char* filename) { +bool Interface::getFinalFilename(const char* filename, std::string& p_binName, std::string& p_fname) { + const int max_extensions = 6; + const char* extensions[max_extensions] = {"core", "stream", "core.stream", "coretext", "coredebug", "dep"}; + + // try default name first std::string fname = filename; - if (!hasExtension(fname)) addExtension(fname, "core"); - uint64_t hash = getFileHash(fname); + const char* binFile = getContainingBinFile(fname); + if (binFile != NULL) { + p_binName = binFile; + p_fname = fname; + return true; + } + + // try common extensions (files like .soundbank.core exists in prefetch list so extension check isn't good) + for (int i = 0; i < max_extensions; i++) { + fname = filename; + addExtension(fname, extensions[i]); + + binFile = getContainingBinFile(fname); + if (binFile != NULL) { + p_binName = binFile; + p_fname = fname; + return true; + } + } + + p_binName = ""; + p_fname = ""; + return false; +} + + +struct BinFileEntryNumSorter { + inline bool operator() (const BinFileEntry& s1, const BinFileEntry& s2) { + return (s1.entryNum < s2.entryNum); + } +}; + +bool Interface::loadHashNames(const char* fileDirectory, std::unordered_map& hashNames) { + const char* fileList = "filenames-all.txt"; + if (!checkFileExists(fileList)) + return false; + + buildFileMap(fileDirectory); + + std::ifstream infile(fileList); + + std::string binFile; + std::string fname; + std::string line; + while (std::getline(infile, line)) { + if (line.empty()) + continue; + if (line.rfind("#", 0) == 0) //skip comments + continue; + + if (!getFinalFilename(line.c_str(), binFile, fname)) + continue; + + uint64_t hash = getFileHash(fname); + hashNames[hash] = fname; + } + + return true; +} + +void Interface::extractFileMap(const char* fileDirectory) { + std::ofstream out("file_hash.txt", std::ios::binary); + const char* newLine = "\r\n"; + char buf[1024]; + + // get names if possible + std::unordered_map hashNames; + bool loadedNames = loadHashNames(fileDirectory, hashNames); + + std::vector availableFiles = getFilesFromDirectory(fileDirectory, ".bin"); + + int count = 0; + for (int i = 0; i < availableFiles.size(); i++) { + const char* str = availableFiles[i].c_str(); + uint32_t size = availableFiles[i].size(); + + out.write(str, size); + out.write(newLine, 2); + + ArchiveBin decimaArchive(str); + //decimaArchive.setMessageHandler(this); + if (!decimaArchive.open()) continue; + std::vector fileTable = decimaArchive.getFileTable(); + std::sort(fileTable.begin(), fileTable.end(), BinFileEntryNumSorter()); + + for (int j = 0; j < fileTable.size(); j++) { + uint32_t entryNum = fileTable[j].entryNum; + uint64_t hash = fileTable[j].hash; + + if (loadedNames) { + bool found = hashNames.find(hash) != hashNames.end(); + std::string fname = found ? hashNames[hash] : "?"; + //if (!found) { + snprintf(buf, sizeof(buf), "- %i: %08x%08x = %s", entryNum, (uint32_t)(hash >> 32), (uint32_t)(hash), fname.c_str()); + out.write(buf, strlen(buf)); + out.write(newLine, 2); + //} + } else { + snprintf(buf, sizeof(buf), "- %i: %08x%08x", entryNum, (uint32_t)(hash >> 32), (uint32_t)(hash)); + out.write(buf, strlen(buf)); + out.write(newLine, 2); + } + } + count += fileTable.size(); + out.write(newLine, 2); + } + + out.write(newLine, 2); + snprintf(buf, sizeof(buf), "total: %i", count); + out.write(buf, strlen(buf)); + out.write(newLine, 2); +} + + +const char* Interface::getContainingBinFile(const std::string& filename) { + uint64_t hash = getFileHash(filename); return fileMap[hash]; } @@ -85,19 +202,43 @@ void Interface::setupOutput(const std::string& output) { if (path != "") createDirectoriesFromPath(path); } -void Interface::directoryExtract(const char* filename, std::string output) { - const char* binFile = getContainingBinFile(filename); - if (binFile == NULL) return; +int Interface::directoryExtract(const char* filename, const char* output) { + std::string binFile; + std::string fname; + if (!getFinalFilename(filename, binFile, fname)) + return 0; + + if (output == NULL) + output = fname.c_str(); + setupOutput(output); - extract(binFile, filename, output.c_str()); + int done = extract(binFile.c_str(), fname.c_str(), output); + return done; +} + +int Interface::fileListExtract(const char* fileList) { + int count = 0; + + std::ifstream infile(fileList); + + std::string line; + while (std::getline(infile, line)) { + if (line.empty()) + continue; + if (line.rfind("#", 0) == 0) //skip comments + continue; + count += directoryExtract(line.c_str(), NULL); + } + + return count; } void Interface::batchExtract(const std::vector& filenames, std::string output, int batchSize, int batchOffset) { int step = batchSize < 10 ? 1 : batchSize / 10; for (int i = batchOffset; i < batchSize + batchOffset; i++) { - std::string newOutput = addFileToPath(filenames[i], output); - directoryExtract(filenames[i], newOutput); + std::string newOutput = addFileToPath(filenames[i], output); //todo fix output name + directoryExtract(filenames[i], newOutput.c_str()); if ((i - batchOffset) % step == 0) updateProgress(step); if (this->forceQuit) return; } @@ -119,8 +260,9 @@ void Interface::deinitPrefetch() { } DecimaArchive* archiveFactory(const char* archiveFile) { - const char* ext = getFileExtension(archiveFile).c_str(); + std::string ext = getFileExtension(archiveFile); if (ext == "mpk") { + std::cout << "archive movie pack\n"; return new ArchiveMoviePack(archiveFile); } @@ -128,7 +270,7 @@ DecimaArchive* archiveFactory(const char* archiveFile) { } void destroyArchive(DecimaArchive* archive, const char* archiveFile) { - const char* ext = getFileExtension(archiveFile).c_str(); + std::string ext = getFileExtension(archiveFile); if (ext == "mpk") { return delete (ArchiveMoviePack*)archive; } @@ -136,6 +278,32 @@ void destroyArchive(DecimaArchive* archive, const char* archiveFile) { return delete (ArchiveBin*)archive; } +int Interface::extractAllIds(const char* archiveFile) { + DecimaArchive* archive = archiveFactory(archiveFile); + archive->setMessageHandler(this); + if (!archive->open()) { + destroyArchive(archive, archiveFile); + return 0; + } + + std::string path = getBaseFile(archiveFile); + + createDirectoriesFromPath(path); + int id = 0; + char buf[1024]; + while (true) { + snprintf(buf, sizeof(buf), "%s/%06i", path.c_str(), id); + //std::cout << buf <<"\n"; + + int ret = archive->extractFile(id, buf); + if (ret <= 0) + break; + id++; + } + delete archive; + return id; +} + int Interface::extract(const char* archiveFile, int id, const char* output) { DecimaArchive* archive = archiveFactory(archiveFile); archive->setMessageHandler(this); @@ -143,9 +311,9 @@ int Interface::extract(const char* archiveFile, int id, const char* output) { destroyArchive(archive, archiveFile); return 0; } - archive->extractFile(id, output); + int ret = archive->extractFile(id, output); delete archive; - return 1; + return ret; } int Interface::extract(const char* archiveFile, const char* input, const char* output) { @@ -156,9 +324,9 @@ int Interface::extract(const char* archiveFile, const char* input, const char* o return 0; } - archive->extractFile(input, output); + int ret = archive->extractFile(input, output); destroyArchive(archive, archiveFile); - return 1; + return ret; } std::vector Interface::getFiles(const std::string& directory) { diff --git a/interface/Interface.h b/interface/Interface.h index ec0b495..e55ad00 100644 --- a/interface/Interface.h +++ b/interface/Interface.h @@ -37,10 +37,16 @@ class Interface : public MessageHandler { int initPrefetch(const char* binFile); void setupOutput(const std::string& output); void buildFileMap(const char* fileDirectory); + bool getFinalFilename(const char* filename, std::string& p_binName, std::string& p_fname); + + bool loadHashNames(const char* fileDirectory, std::unordered_map& hashNames); + void extractFileMap(const char* fileDirectory); void swap(const char* dataDir, const char* swapFile); - const char* getContainingBinFile(const char* filename); + const char* getContainingBinFile(const std::string& filename); std::vector getFiles(const std::string& directory); - void directoryExtract(const char* filename, std::string output); + int directoryExtract(const char* filename, const char* output); + int fileListExtract(const char* fileList); + int extractAllIds(const char* archiveFile); int extract(const char* archiveFile, int id, const char* output); int extract(const char* archiveFile, const char* input, const char* output); void parallelExtract(const std::string& directory, const std::vector& selectedStrings); diff --git a/interface/cli/CLI.cpp b/interface/cli/CLI.cpp index aeba359..688f4ec 100644 --- a/interface/cli/CLI.cpp +++ b/interface/cli/CLI.cpp @@ -46,39 +46,69 @@ void CLI::cliExtract() { } void CLI::fileExtract() { - std::string output = argc == 5 ? argv[4] : argv[3]; - setupOutput(output); + std::string outpath = argc == 5 ? argv[4] : argv[3]; + setupOutput(outpath); - if (isNumber(argv[3])) { + int done; + if (argv[3][0] == '*') { + done = extractAllIds(argv[2]); + } else if (isNumber(argv[3])) { int id = argToNumber(argv[3]); - int ret = extract(argv[2], id, output.c_str()); - if (!ret) return; + const char* output = argc == 5 ? argv[4] : argv[3]; + done = extract(argv[2], id, output); } else { - int ret = extract(argv[2], argv[3], output.c_str()); - if (!ret) return; + const char* output = argc == 5 ? argv[4] : NULL; + done = extract(argv[2], argv[3], output); } - std::string message = "finished extracting file " + output; - showMessage(message.c_str()); + if (done <= 0) + showError("extraction failed (id/name not found)"); + else { + showMessage("finished extracting file"); + } } void CLI::dirExtract() { buildFileMap(argv[2]); - if (isNumber(argv[3])) { + const char* name = argv[3]; + if (isNumber(name)) { showError("IDs cannot be used with directory extract"); return; } - std::string output = argc == 5 ? argv[4] : argv[3]; - directoryExtract(argv[3], output); - showMessage("extraction finished"); + if (checkFileExists(name)) { + std::string fileListName = name; + if (hasExtension(fileListName, "txt")) { + int done = fileListExtract(fileListName.c_str()); + + char buf[512] = {0}; + snprintf(buf, sizeof(buf), "finished extracting %s list, total: %d", fileListName.c_str(), done); + std::string message = buf; + + showMessage(message.c_str()); + return; + } + } + + const char* output = argc >= 5 ? argv[4] : NULL; + int done = directoryExtract(name, output); + if (done <= 0) + showError("extraction failed (name not found)"); + else { + std::string infile = name; + std::string message = "finished extracting file " + infile; + showMessage(message.c_str()); + } } void CLI::list() { initPrefetch(argv[2]); prefetchFile.extractFileTable(); showMessage("File table extracted to file_list.txt"); + + extractFileMap(argv[2]); + showMessage("Hash table extracted to file_hash.txt"); } bool CLI::checkInput() { @@ -119,13 +149,13 @@ argcRange CLI::getArgCount(CLI_COMMAND command) { } } -int CLI::argToNumber(char* arg) { +int CLI::argToNumber(const char* arg) { int num; sscanf(arg, "%d", &num); return num; } -CLI_COMMAND CLI::argToCommand(char* arg) { +CLI_COMMAND CLI::argToCommand(const char* arg) { if (!strcmp(arg, "-extract") || !strcmp(arg, "-e")) return EXTRACT; if (!strcmp(arg, "-list") || !strcmp(arg, "-l")) @@ -138,7 +168,7 @@ CLI_COMMAND CLI::argToCommand(char* arg) { return SWAP; } -bool CLI::isNumber(char* arg) { +bool CLI::isNumber(const char* arg) { for (int i = 0; arg[i] != 0; i++) { if (!isdigit(arg[i])) return false; @@ -146,7 +176,7 @@ bool CLI::isNumber(char* arg) { return true; } -bool CLI::isCommand(char* arg) { +bool CLI::isCommand(const char* arg) { return arg[0] == 0x2D; } diff --git a/interface/cli/CLI.h b/interface/cli/CLI.h index 8fa152c..2c3609b 100644 --- a/interface/cli/CLI.h +++ b/interface/cli/CLI.h @@ -31,6 +31,7 @@ class CLI : public Interface { "\t DecimaExplorer.exe [-e/-extract] inputfile filename outputfile\n" "\t DecimaExplorer.exe [-e/-extract] [directory containing data files] filename outputfile\n" "\t DecimaExplorer.exe [-e/-extract] [directory containing data files] filename\n" + "\t DecimaExplorer.exe [-e/-extract] [directory containing data files] [file list].txt\n" "\t DecimaExplorer.exe [-r/-repack] [bin file to repack] [directory containing directories of core files]\n" "\t DecimaExplorer.exe [-p/-pack] [directory containing directories of core files] outputfile\n" "\t DecimaExplorer.exe [-s/-swap] [directory containing data files] [swap text file]\n" @@ -63,10 +64,10 @@ class CLI : public Interface { void printUsage(); bool checkInput(); void fileExtract(); - bool isNumber(char* arg); - bool isCommand(char* arg); - int argToNumber(char* arg); - CLI_COMMAND argToCommand(char* arg); + bool isNumber(const char* arg); + bool isCommand(const char* arg); + int argToNumber(const char* arg); + CLI_COMMAND argToCommand(const char* arg); argcRange getArgCount(CLI_COMMAND command); void processCommand(CLI_COMMAND command, char* arg); void removeHashes(const std::vector& fileList, const char* dataFolder); diff --git a/lists/filenames-ds-pc.zip b/lists/filenames-ds-pc.zip new file mode 100644 index 0000000..9e2e171 Binary files /dev/null and b/lists/filenames-ds-pc.zip differ diff --git a/main.cpp b/main.cpp index cac6da2..304c95a 100644 --- a/main.cpp +++ b/main.cpp @@ -2,5 +2,5 @@ int main(int argc, char **argv) { CLI cli = CLI(argc, argv); - cli.run("Decima Explorer", "2.7"); + cli.run("Decima Explorer", "2.7b"); } \ No newline at end of file diff --git a/main_gui.cpp b/main_gui.cpp index c69bd61..50b3ec3 100644 --- a/main_gui.cpp +++ b/main_gui.cpp @@ -2,5 +2,5 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR args, int ncmdshow) { GUI gui = GUI(hInst); - gui.run("Decima Explorer", "2.7"); + gui.run("Decima Explorer", "2.7b"); } \ No newline at end of file diff --git a/utils/Fileutils.h b/utils/Fileutils.h index 79cfe10..ec8ddfa 100644 --- a/utils/Fileutils.h +++ b/utils/Fileutils.h @@ -122,6 +122,21 @@ std::string getFileExtension(const std::string& dirName) { return dirName.substr(dirName.find_last_of(".") + 1); } +inline +std::string getBaseFile(const std::string& pathName) { + std::string out = pathName; + int index = out.find_last_of("."); + if (index > 0) + out = out.substr(0, index); + index = out.find_last_of("\\"); + if (index > 0) + out = out.substr(index + 1); + index = out.find_last_of("/"); + if (index > 0) + out = out.substr(index + 1); + return out; +} + inline bool hasExtension(const std::string& dirName, const std::string& extension) { return getFileExtension(dirName) == extension;