diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8bb671c --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.vs/ +Debug/ +Release/ +X64/ +mgs/3rdparty/zlib/zconf.h +mgs/3rdparty/zlib/zlib.h +mgs/3rdparty/zlib/zlib.lib diff --git a/Arsenal.sln b/Arsenal.sln new file mode 100644 index 0000000..2b34614 --- /dev/null +++ b/Arsenal.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30907.101 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Arsenal", "Arsenal.vcxproj", "{8C944FD4-90E3-4FC2-9143-CDA2A21FF96D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8C944FD4-90E3-4FC2-9143-CDA2A21FF96D}.Debug|x64.ActiveCfg = Debug|x64 + {8C944FD4-90E3-4FC2-9143-CDA2A21FF96D}.Debug|x64.Build.0 = Debug|x64 + {8C944FD4-90E3-4FC2-9143-CDA2A21FF96D}.Debug|x86.ActiveCfg = Debug|Win32 + {8C944FD4-90E3-4FC2-9143-CDA2A21FF96D}.Debug|x86.Build.0 = Debug|Win32 + {8C944FD4-90E3-4FC2-9143-CDA2A21FF96D}.Release|x64.ActiveCfg = Release|x64 + {8C944FD4-90E3-4FC2-9143-CDA2A21FF96D}.Release|x64.Build.0 = Release|x64 + {8C944FD4-90E3-4FC2-9143-CDA2A21FF96D}.Release|x86.ActiveCfg = Release|Win32 + {8C944FD4-90E3-4FC2-9143-CDA2A21FF96D}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {48227280-F7AF-4A74-A5C7-1CD24EB09673} + EndGlobalSection +EndGlobal diff --git a/Arsenal.vcxproj b/Arsenal.vcxproj new file mode 100644 index 0000000..6c29f48 --- /dev/null +++ b/Arsenal.vcxproj @@ -0,0 +1,182 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {8c944fd4-90e3-4fc2-9143-cda2a21ff96d} + Arsenal + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + "mgs/3rdparty/zlib/zlib.lib";%(AdditionalDependencies) + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + true + true + "mgs/3rdparty/zlib/zlib.lib";%(AdditionalDependencies) + false + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + "mgs/3rdparty/zlib/zlib.lib";%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + true + true + "mgs/3rdparty/zlib/zlib.lib";%(AdditionalDependencies) + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Arsenal.vcxproj.filters b/Arsenal.vcxproj.filters new file mode 100644 index 0000000..2018c19 --- /dev/null +++ b/Arsenal.vcxproj.filters @@ -0,0 +1,93 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/Arsenal.vcxproj.user b/Arsenal.vcxproj.user new file mode 100644 index 0000000..a8b9ca7 --- /dev/null +++ b/Arsenal.vcxproj.user @@ -0,0 +1,10 @@ + + + + true + + + "STAGE2.DAT" + WindowsLocalDebugger + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e6b0405 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ + +# Arsenal + + +Arsenal is a free open source tool designed to be used with the PS2 version of the game Metal Gear Solid 2. It allows the user to extract Face and Stage archive files packaged with the game. The dictionary provided is thanks to [GirianSeed](https://github.com/GirianSeed) you can check out his mgs dictionary project [here](https://github.com/Joy-Division/JoyDict) + +This project uses [Zlib](https://github.com/madler/zlib) by [Mark Adler](https://github.com/madler) + +### To Do + - Add multithreaded extract + - Add error handling + - Create GUI variant + - Clean up the code + +## Usage + +Currently only a CLI version of the application exists. An optional output directory can also be added. it is also possible to just drag the file you wish to extract on the executable. A dictionary file is also included in order to resolve a lot of the hashes. + +If you wish to use the dictionary to resolve filenames place it alonside Arsenal.exe, please note this program is currently only designed to work with the PS2 version of MGS2. + +``` +Arsenal.exe "path\to\STAGE.DAT" +``` +The above instruction will extract all files from STAGE.DAT to the current directory. + +``` +Arsenal.exe -sub "path\to\STAGE.DAT" +``` +The above instruction will extract all files from STAGE.DAT for the subsistence version of the game. + +``` +Arsenal.exe "path\to\FACE.DAT" "path\to\output" +``` +The same can be used for Face files. An optional output path can be added at the end, if it is not included then it will extract to the directory of the file being extracted. + +## License +[MIT](LICENSE.md) +This project falls under the MIT license. \ No newline at end of file diff --git a/dictionary/dictionary.cpp b/dictionary/dictionary.cpp new file mode 100644 index 0000000..88b909a --- /dev/null +++ b/dictionary/dictionary.cpp @@ -0,0 +1,16 @@ +#include "dictionary.h" +#include "../mgs/common/strcode.h" + +std::map FileNames; + +void loadDictionary(const std::string& dictionary) { + std::string str; + std::ifstream file(dictionary); + + if (!file) return; + + while (std::getline(file, str)) { + int hashed = strcode(str.c_str()); + FileNames[hashed] = str; + } +} \ No newline at end of file diff --git a/dictionary/dictionary.h b/dictionary/dictionary.h new file mode 100644 index 0000000..0ed12ec --- /dev/null +++ b/dictionary/dictionary.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include +#include + +extern std::map FileNames; +extern void loadDictionary(const std::string& dictionary); \ No newline at end of file diff --git a/interface/cli/cli.cpp b/interface/cli/cli.cpp new file mode 100644 index 0000000..eb101ec --- /dev/null +++ b/interface/cli/cli.cpp @@ -0,0 +1,103 @@ +#include "cli.h" + +CLI::CLI(int argc, char** argv) { + this->argc = argc; + this->argv = argv; +} + +CLI::~CLI() { + +} + +bool CLI::isFace(const std::string& filepath) { + if (filenameContainsString(filepath, "FACE") || filenameContainsString(filepath, "face")) + return true; + + return false; +} +bool CLI::isStage(const std::string& filepath) { + if (filenameContainsString(filepath, "STAGE") || filenameContainsString(filepath, "stage")) + return true; + + return false; +} + +bool CLI::isDat(const std::string& filepath) { + if (getExtension(filepath) == ".DAT" || getExtension(filepath) == ".dat") + return true; + + printf("Unsupported file type\n"); + exit(); + return false; +} + +void CLI::extractArchive(Archive& archive) { + std::string output = ""; + if (currentArg == argc - 1) output = argv[currentArg]; + + archive.open(); + archive.extractAll(output); +} + +void CLI::processCommands() { + while (currentArg < 2 && isCommand(argv[currentArg])) { + setCommand(argv[currentArg]); + currentArg++; + } +} + +void CLI::setCommand(char* arg) { + + printf("command not recognised\n"); +} + +void CLI::processArgs() { + processFile(); +} + +void CLI::processFile() { + std::string input = argv[currentArg]; + currentArg++; + + if (!isDat(input)) return; + + if (isFace(input)) { + Face face = Face(input); + extractArchive(face); + return; + } + + if (isStage(input)) { + Stage stage = Stage(input); + extractArchive(stage); + return; + } + + printf("Unsupported Archive\n"); + exit(); +} + +bool CLI::checkInput() { + if (argc > 1 && argc < 3) return true; + printUsage(); + return false; +} + +void CLI::run(std::string programName, std::string version) { + printf("Running %s v%s: Visit https://github.com/Jayveer/Arsenal for updates:\n", programName.c_str(), version.c_str()); + loadDictionary("dictionary.txt"); + if (!checkInput()) return; + processArgs(); +} + +bool CLI::isCommand(char* arg) { + return arg[0] == 0x2D; +} + +void CLI::printUsage() { + printf(this->USAGE_MESSAGE); +} + +void CLI::exit() { + printf(this->EXIT_MESSAGE); +} \ No newline at end of file diff --git a/interface/cli/cli.h b/interface/cli/cli.h new file mode 100644 index 0000000..62ab99a --- /dev/null +++ b/interface/cli/cli.h @@ -0,0 +1,34 @@ +#pragma once +#include "../../mgs/archive/face/face.h" +#include "../../mgs/archive/face/stage/stage.h" + +class CLI { +public: + CLI(int argc, char** argv); + ~CLI(); + + void run(std::string programName, std::string version); + void exit(); +private: + int argc; + char** argv; + int currentArg = 1; + + void printUsage(); + bool checkInput(); + void processArgs(); + void processFile(); + void processCommands(); + bool isCommand(char* arg); + void setCommand(char* arg); + void extractArchive(Archive& archive); + + + bool isDat(const std::string& filepath); + bool isFace(const std::string& filepath); + bool isStage(const std::string& filepath); + + + const char* EXIT_MESSAGE = "Exiting\n"; + const char* USAGE_MESSAGE = "Usage:\t Arsenal.exe [OUTPUTDIRECTORY] \n"; +}; \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..028e545 --- /dev/null +++ b/main.cpp @@ -0,0 +1,6 @@ +#include "interface/cli/cli.h" + +int main(int argc, char** argv) { + CLI cli = CLI(argc, argv); + cli.run("Arsenal", "1.0"); +} \ No newline at end of file diff --git a/mgs/3rdparty/zlib/wrapper/ZlibWrapper.cpp b/mgs/3rdparty/zlib/wrapper/ZlibWrapper.cpp new file mode 100644 index 0000000..b490c92 --- /dev/null +++ b/mgs/3rdparty/zlib/wrapper/ZlibWrapper.cpp @@ -0,0 +1,150 @@ +#include +#include +#include +#include +#include +#include +#include "ZlibWrapper.h" + + +#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) +# include +# include +# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) +#else +# define SET_BINARY_MODE(file) +#endif + + +//#define CHUNK 16271376 +#define CHUNK 22917472 +#ifdef WIN32 +#define ZLIB_WINAPI +#endif + +/* report a zlib or i/o error */ +void ZlibWrapper::zerr(int ret) +{ + fputs("zpipe: ", stderr); + switch (ret) { + case Z_ERRNO: + if (ferror(stdin)) + fputs("error reading stdin\n", stderr); + if (ferror(stdout)) + fputs("error writing stdout\n", stderr); + break; + case Z_STREAM_ERROR: + fputs("invalid compression level\n", stderr); + break; + case Z_DATA_ERROR: + fputs("invalid or incomplete deflate data\n", stderr); + break; + case Z_MEM_ERROR: + fputs("out of memory\n", stderr); + break; + case Z_VERSION_ERROR: + fputs("zlib version mismatch!\n", stderr); + } +} + +/* Compress from file source to file dest until EOF on source. +def() returns Z_OK on success, Z_MEM_ERROR if memory could not be +allocated for processing, Z_STREAM_ERROR if an invalid compression +level is supplied, Z_VERSION_ERROR if the version of zlib.h and the +version of the library linked do not match, or Z_ERRNO if there is +an error reading or writing the files. */ +int ZlibWrapper::zlibDeflate(char *src, char *dst, int bytes_to_compress, int *bytes_after_compressed) +{ + z_stream strm; + + int ret; + unsigned have; + + /* allocate deflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + //ret = deflateInit(&strm, Z_DEFAULT_COMPRESSION); + ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 9, Z_DEFAULT_STRATEGY); + + if (ret != Z_OK) + return ret; + + /* compress */ + strm.avail_in = bytes_to_compress; + strm.avail_out = bytes_to_compress; + + strm.next_in = (Bytef *)src; + strm.next_out = (Bytef *)dst; + + ret = deflate(&strm, Z_FINISH); + + have = bytes_to_compress - strm.avail_out; + + *bytes_after_compressed = have; + + assert(ret == Z_STREAM_END); /* stream will be complete */ + + /* clean up and return */ + (void)deflateEnd(&strm); + + return Z_OK; +} + +/* Decompress from file source to file dest until stream ends or EOF. +inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be +allocated for processing, Z_DATA_ERROR if the deflate data is +invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and +the version of the library linked do not match, or Z_ERRNO if there +is an error reading or writing the files. */ +int ZlibWrapper::zlibInflate(char *src, char *dst, int bytes_to_decompress, int maximum_after_decompress, int* outbytes, int type) +{ + z_stream strm; + + int ret = 0; + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + if (type == 1) + ret = inflateInit(&strm); + else + ret = inflateInit2(&strm, -15); + if (ret != Z_OK) return ret; + + /* decompress */ + strm.avail_in = bytes_to_decompress; + strm.next_in = (Bytef *)src; + + strm.next_out = (Bytef *)dst; + strm.avail_out = maximum_after_decompress; + + ret = inflate(&strm, Z_NO_FLUSH); + + assert(ret != Z_STREAM_ERROR); /* state not clobbered */ + + switch (ret) + { + + case Z_NEED_DICT: + ret = Z_DATA_ERROR; /* and fall through */ + + case Z_DATA_ERROR: + + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + return ret; + } + + assert(strm.avail_in == 0); /* all input will be used */ + + if (outbytes != NULL) *outbytes = strm.total_out; + + /* clean up and return */ + (void)inflateEnd(&strm); + + return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; +} \ No newline at end of file diff --git a/mgs/3rdparty/zlib/wrapper/ZlibWrapper.h b/mgs/3rdparty/zlib/wrapper/ZlibWrapper.h new file mode 100644 index 0000000..01d4ef4 --- /dev/null +++ b/mgs/3rdparty/zlib/wrapper/ZlibWrapper.h @@ -0,0 +1,9 @@ +#pragma once +#include "../zlib.h" + +class ZlibWrapper { +public: + void zerr(int ret); + int zlibDeflate(char *src, char *dst, int bytes_to_compress, int *bytes_after_compressed); + int zlibInflate(char *src, char *dst, int bytes_to_decompress, int maximum_after_decompress, int* outbytes, int type); +}; \ No newline at end of file diff --git a/mgs/archive/archive.h b/mgs/archive/archive.h new file mode 100644 index 0000000..d60b2ce --- /dev/null +++ b/mgs/archive/archive.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "../common/fileutil.h" + +class Archive { +public: + virtual void open() = 0; + virtual void extractAll(std::string output = "") = 0; +}; \ No newline at end of file diff --git a/mgs/archive/face/face.cpp b/mgs/archive/face/face.cpp new file mode 100644 index 0000000..1f74da4 --- /dev/null +++ b/mgs/archive/face/face.cpp @@ -0,0 +1,143 @@ +#include "face.h" + +Face::Face(std::string filename) { + this->filename = filename; +} + +Face::Face(std::string filename, uint32_t sector) { + this->filename = filename; + this->sector = sector; +} + +Face::~Face() {} + +std::string Face::getFilename() { + return this->filename; +} + +std::string Face::getCreatedDate() { + std::string createdDate; + createdDate.resize(20); + time_t rawtime = (time_t)this->header.timestamp; + tm ptm; + gmtime_s(&ptm, &rawtime); + + sprintf_s(&createdDate[0], + 20, + "%02d/%02d/%04d %02d:%02d:%02d", + ptm.tm_mday, + ptm.tm_mon, + (1900 + ptm.tm_year), + ptm.tm_hour, + ptm.tm_min, + ptm.tm_sec); + + return createdDate; +} + +void Face::open() { + std::ifstream faceDat; + faceDat.open(filename, std::ios::binary); + faceDat.read((char*)&this->header, sizeof(FaceHeader)); + + table.resize(header.numPages); + int size = sizeof(FaceTable) * header.numPages; + faceDat.read((char*)&this->table[0], size); + faceDat.close(); +} + +uint8_t* Face::decompressPage(uint8_t* compressedPage, int64_t compressedSize, int64_t decompressedSize) { + int size; + ZlibWrapper zlib; + uint8_t* decompressedPage = new uint8_t[decompressedSize]; + + zlib.zlibInflate((char*)(compressedPage), (char*)decompressedPage, compressedSize, decompressedSize, &size, 1); + + return decompressedPage; +} + +void Face::decryptPage(uint8_t* src, int64_t size) { + uint16_t key = *(uint16_t*)src; + + decryptor.decodePage(key, 0, size, src); + src[0] = 0x78; src[1] = 0x9C; +} + +void Face::decryptPageCNF(const std::string& stageName, uint8_t* src) { + uint32_t keyA = strcode(stageName.c_str()); + uint32_t keyB = header.timestamp; + + int32_t numTags = *(uint32_t*)src; + decryptor.decodeCNF(keyA, keyB, 0, 4, (uint8_t*)&numTags); + + int64_t cnfHeaderSize = sizeof(DataCNF) + sizeof(DataCNFTag) * numTags; + decryptor.decodeCNF(keyA, keyB, 0, cnfHeaderSize, src); +} + +void Face::extractFiles(int64_t size, int64_t offset, std::string& output, bool isEncrypted) { + std::ifstream fileDat; + fileDat.open(filename, std::ios::binary); + fileDat.seekg(offset); + + uint8_t* pageData = new uint8_t[size]; + fileDat.read((char*)pageData, size); + fileDat.close(); + + if (isEncrypted) decryptPageCNF(getCurrentDir(output), pageData); + + DataConfig cnf = DataConfig(pageData, size); + cnf.setHandler(this); + cnf.setWorkDir(output); + cnf.read(BINARY); + + delete[] pageData; +} + +void Face::extract(uint16_t pageID, std::string output, std::string dirName, bool isEncrypted) { + if (pageID > header.numPages) return; + + updateDir(dirName, output); + updateDir(table[pageID].name, output); + + int64_t nextOffset = getNextPageOffset(pageID); + int64_t offset = table[pageID].offset * sector; + + int64_t size = nextOffset - offset; + extractFiles(size, offset, output, isEncrypted); +} + +void Face::extractAll(std::string output) { + for (int i = 0; i < header.numPages; i++) { + extract(i, output, "face", false); + } +} + +int64_t Face::getNextPageOffset(uint16_t pageID) { + return (pageID == header.numPages - 1) ? getFileSize(filename) : table[pageID + 1].offset * sector; +} + +/* CNF */ +int64_t Face::getNextSectorOffset(int64_t currentOffset) { + return getAlignment(currentOffset, sector); +} + +int32_t Face::getSectorSize() { + return this->sector; +} + +void Face::processQar(std::string filename, std::string region, std::string* workDir) {} + +void Face::processFace(std::string filename, int16_t page, int64_t offset, std::string region, std::string* workDir) {} + +void Face::processVram(std::string filename, std::string region, std::string* workDir) {} + +void Face::processAfp(std::string filename, std::string region, std::string* workDir) { } + +void Face::processFile(std::string filename, std::string region, std::string* workDir, uint8_t* fileData, int size) { + writeDataToFile(fileData, size, filename, *workDir); +} + +void Face::processEnc(std::string region, uint8_t** section, int64_t size, int64_t sectionSize) { + decryptPage(*section, size); + *section = decompressPage(*section, size, sectionSize); +} \ No newline at end of file diff --git a/mgs/archive/face/face.h b/mgs/archive/face/face.h new file mode 100644 index 0000000..a479cfa --- /dev/null +++ b/mgs/archive/face/face.h @@ -0,0 +1,64 @@ +#pragma once +#include + +#include "../archive.h" +#include "../../config/cnf.h" +#include "../../common/game/game.h" +#include "../../common/decryptor/decryptor.h" +#include "../../3rdparty/zlib/wrapper/ZlibWrapper.h" + +struct FaceHeader { + uint32_t timestamp; //interchangable with strcode + uint16_t version; + uint16_t pageSize; //multiplied by sector size + uint16_t numPages; + uint16_t unknownA; + uint32_t unknownB; +}; + +#pragma pack(push, 4) +struct FaceTable { + char name[8]; + int32_t offset; +}; +#pragma pop + +class Face : public CNFHandler, public Archive { +protected: + FaceHeader header; + Decryptor decryptor; + std::string filename; + uint32_t sector = 0x800; + std::vector table; + + int64_t getNextPageOffset(uint16_t pageID); + void decryptPage(uint8_t* src, int64_t size); + void decryptPageCNF(const std::string& stageName, uint8_t* src); + void extractFiles(int64_t size, int64_t offset, std::string& output, bool isEncrypted = 0); + + int32_t getSectorSize(); + int64_t getNextSectorOffset(int64_t currentOffset); + void processQar(std::string filename, std::string region, std::string* workDir); + void processAfp(std::string filename, std::string region, std::string* workDir); + void processVram(std::string filename, std::string region, std::string* workDir); + void processEnc(std::string region, uint8_t** section, int64_t size, int64_t sectionSize); + void processFile(std::string filename, std::string region, std::string* workDir, uint8_t* fileData, int size); + void processFace(std::string filename, int16_t page, int64_t offset, std::string region, std::string* workDir); +private: + int64_t getMaxPageSize(int64_t offset); + void initPageInfo(std::ifstream& faceDat); + int64_t getMaxPageSize(std::ifstream& faceDat); + uint8_t* decompressPage(uint8_t* compressedPage, int64_t compressedSize, int64_t decompressedSize); +public: + ~Face(); + Face(std::string filename); + Face(std::string filename, uint32_t sector); + + std::string getFilename(); + std::string getCreatedDate(); + + void open(); + void extractAll(std::string output = ""); + void extract(uint16_t pageID, std::string output, std::string dirName, bool isEncrypted); + +}; \ No newline at end of file diff --git a/mgs/archive/face/stage/stage.cpp b/mgs/archive/face/stage/stage.cpp new file mode 100644 index 0000000..dc4ad0a --- /dev/null +++ b/mgs/archive/face/stage/stage.cpp @@ -0,0 +1,42 @@ +#include "stage.h" + +Stage::Stage(std::string filename) : Face(filename) {} + +Stage::Stage(std::string filename, uint32_t sector) : Face(filename, sector) {} + +Stage::~Stage() {} + +void Stage::open() { + std::ifstream stageDat; + stageDat.open(filename, std::ios::binary); + + int size = sizeof(FaceHeader); + stageDat.read((char*)&this->header, size); + uint32_t keyA = header.timestamp; + uint32_t keyB = keyA ^ 0xF0F0; + keyA = decryptor.decodeBuffer(keyA, keyB, 0, 0x0C, (uint8_t*)&header.version); + + table.resize(header.numPages); + size = sizeof(FaceTable) * header.numPages; + stageDat.read((char*)&table[0], size); + keyA = decryptor.decodeBuffer(keyA, keyB, 0, size, (uint8_t*)&table[0]); +} +/* +void Stage::extract(uint16_t pageID, std::string output) { + if (pageID > header.numPages) return; + + updateDir("stage", output); + updateDir(table[pageID].name, output); + + int64_t nextOffset = getNextPageOffset(pageID); + int64_t offset = table[pageID].offset * sector; + + int64_t size = nextOffset - offset; + extractFiles(size, offset, output, true); +}*/ + +void Stage::extractAll(std::string output) { + for (int i = 0; i < header.numPages; i++) { + extract(i, output, "stage", true); + } +} \ No newline at end of file diff --git a/mgs/archive/face/stage/stage.h b/mgs/archive/face/stage/stage.h new file mode 100644 index 0000000..ed5eb5b --- /dev/null +++ b/mgs/archive/face/stage/stage.h @@ -0,0 +1,13 @@ +#pragma once +#include "../face.h" + +class Stage : public Face { +public: + Stage(std::string filename); + Stage(std::string filename, uint32_t sector); + ~Stage(); + + void open(); + void extractAll(std::string output = ""); + //void extract(uint16_t pageID, std::string output = ""); +}; \ No newline at end of file diff --git a/mgs/common/decryptor/decryptor.cpp b/mgs/common/decryptor/decryptor.cpp new file mode 100644 index 0000000..d98bcaf --- /dev/null +++ b/mgs/common/decryptor/decryptor.cpp @@ -0,0 +1,48 @@ +#include "decryptor.h" + +uint32_t Decryptor::makeCNFKey(uint32_t saltA, uint32_t saltB) { + return (saltB << 0x07) + saltB + saltA; +} + +uint32_t Decryptor::makeCNFKeyA(uint32_t saltA, uint32_t saltB) { + return makeCNFKey(0xA78925D9, saltA) + saltB; +} + +uint32_t Decryptor::makeCNFKeyB(uint32_t salt) { + return makeCNFKey(0x7A88FB59, salt); +} + +uint32_t Decryptor::makePageKeyA(uint32_t salt) { + uint32_t pageKeyA = salt ^ 0x6576; + pageKeyA <<= 0x10; + return pageKeyA |= salt; +} + +uint32_t Decryptor::makePageKeyB(uint32_t salt) { + return salt * 0x0116; +} + +uint32_t Decryptor::decodePage(uint16_t keyA, int offset, int size, uint8_t* src) { + uint32_t pageKey = keyA ^ 0x9385; + uint32_t pageKeyA = makePageKeyA(pageKey); + uint32_t pageKeyB = makePageKeyB(pageKey); + return decodeBuffer(pageKeyA, pageKeyB, offset, size, src); +} + +uint32_t Decryptor::decodeCNF(uint32_t keyA, uint32_t keyB, int offset, int size, uint8_t* src) { + uint32_t cnfKeyA = makeCNFKeyA(keyA, keyB); + uint32_t cnfKeyB = makeCNFKeyB(keyA); + return decodeBuffer(cnfKeyA, cnfKeyB, offset, size, src); +} + +uint32_t Decryptor::decodeBuffer(uint32_t keyA, uint32_t keyB, unsigned int offset, unsigned int size, uint8_t* src) { + uint32_t* srcP = (uint32_t*)src; + size /= 4; + + for (int i = offset; i < size; i++) { + uint32_t interval = keyA * KEY; + srcP[i] ^= keyA; + keyA = interval + keyB; + } + return keyA; +} \ No newline at end of file diff --git a/mgs/common/decryptor/decryptor.h b/mgs/common/decryptor/decryptor.h new file mode 100644 index 0000000..004b60d --- /dev/null +++ b/mgs/common/decryptor/decryptor.h @@ -0,0 +1,16 @@ +#pragma once +#include + +class Decryptor { +private: + const uint32_t KEY = 0x02E90EDD; + uint32_t makePageKeyB(uint32_t salt); + uint32_t makePageKeyA(uint32_t salt); + uint32_t makeCNFKeyB(uint32_t salt); + uint32_t makeCNFKeyA(uint32_t saltA, uint32_t saltB); + uint32_t makeCNFKey(uint32_t saltA, uint32_t saltB); +public: + uint32_t decodePage(uint16_t keyA, int offset, int size, uint8_t* src); + uint32_t decodeCNF(uint32_t keyA, uint32_t keyB, int offset, int size, uint8_t* src); + uint32_t decodeBuffer(uint32_t keyA, uint32_t keyB, unsigned int offset, unsigned int size, uint8_t* src); +}; \ No newline at end of file diff --git a/mgs/common/ext_table.h b/mgs/common/ext_table.h new file mode 100644 index 0000000..4fa5334 --- /dev/null +++ b/mgs/common/ext_table.h @@ -0,0 +1,43 @@ +#pragma once +#include + +struct EXT_TABLE { + const char* name; + int id; +}; + +const EXT_TABLE ext_table[15] = { + {"bin", 0x01 }, + {"cv2", 0x02 }, + {"evm", 0x04 }, + {"far", 0x05 }, + {"gcx", 0x06 }, + {"hzx", 0x07 }, + {"kms", 0x0A }, + {"lt2", 0x0B }, + {"mar", 0x0C }, + {"o2d", 0x0E }, + {"row", 0x11 }, + {"sar", 0x12 }, + {"tri", 0x13 }, + {"var", 0x15 }, + {"zms", 0x19 }, +}; + +inline +const char* getExtForID(uint8_t id) { + for (int i = 0; i < 15; i++) { + if (ext_table[i].id == id) + return ext_table[i].name; + } + return ""; +} + +inline +uint8_t getIDforExt(const char* ext) { + for (int i = 0; i < 15; i++) { + if (!strcmp(ext_table[i].name, ext)) + return ext_table[i].id; + } + return -1; +} \ No newline at end of file diff --git a/mgs/common/fileutil.h b/mgs/common/fileutil.h new file mode 100644 index 0000000..7dbaa8b --- /dev/null +++ b/mgs/common/fileutil.h @@ -0,0 +1,81 @@ +#pragma once +#include +#include + +inline +void updateDir(const std::string& path, std::string& output) { + std::filesystem::path p{ output }; + p.append(path); + output = p.u8string(); +} + +inline +void resetDir(std::string& output) { + std::filesystem::path p{ output }; + output = p.parent_path().u8string(); +} + +inline +std::string getCurrentDir(const std::string& output) { + std::filesystem::path p{ output }; + return p.filename().u8string(); +} + +inline +std::string getExtension(const std::string& output) { + std::filesystem::path p{ output }; + return p.extension().u8string(); +} + +inline +std::string getExtensionlessName(const std::string& output) { + std::filesystem::path p{ output }; + return p.stem().u8string(); +} + +inline +bool filenameContainsString(const std::string& output, const std::string& string) { + std::string filename = getExtensionlessName(output); + + if (filename.find(string) != std::string::npos) + return true; + + return false; +} + +inline +bool isDirectory(const std::string& output) { + std::filesystem::path p{ output }; + return std::filesystem::is_directory(p); +} + +inline +bool fileExists(const std::string& output) { + std::filesystem::path p{ output }; + return std::filesystem::exists(p); +} + +inline +int64_t getFileSize(const std::string& input) { + return std::filesystem::file_size(input); +} + +inline +int64_t getAlignment(int64_t currentOffset, int64_t alignSize) { + uint64_t step = (alignSize - (currentOffset % alignSize)); + if (step != alignSize) + return step; + return 0; +} + +inline +void writeDataToFile(uint8_t* data, int size, const std::string& filename, std::string& output) { + if (!std::filesystem::exists(output)) + std::filesystem::create_directories(output); + + updateDir(filename, output); + std::ofstream ofs(output, std::ofstream::binary); + ofs.write((char*)data, size); + ofs.close(); + resetDir(output); +} \ No newline at end of file diff --git a/mgs/common/game/game.cpp b/mgs/common/game/game.cpp new file mode 100644 index 0000000..fbbf460 --- /dev/null +++ b/mgs/common/game/game.cpp @@ -0,0 +1,4 @@ +#include "game.h" + +_GAME GAME = SNAKE_EATER; +_PLATFORM PLATFORM = PS2; \ No newline at end of file diff --git a/mgs/common/game/game.h b/mgs/common/game/game.h new file mode 100644 index 0000000..0ccce63 --- /dev/null +++ b/mgs/common/game/game.h @@ -0,0 +1,14 @@ +#pragma once + +enum _PLATFORM { + PS2, + NDS +}; + +enum _GAME { + SNAKE_EATER, + SUBSISTENCE +}; + +extern _GAME GAME; +extern _PLATFORM PLATFORM; \ No newline at end of file diff --git a/mgs/common/strcode.h b/mgs/common/strcode.h new file mode 100644 index 0000000..65cdd56 --- /dev/null +++ b/mgs/common/strcode.h @@ -0,0 +1,30 @@ +#pragma once +#include "../common/ext_table.h" + +inline +unsigned int strcode(const char* string) { + unsigned char c; + unsigned char* p = (unsigned char*)string; + unsigned int id, mask = 0x00FFFFFF; + + for (id = 0; c = *p; p++) { + id = ((id >> 19) | (id << 5)); + id += c; + id &= mask; + } + + return (!id) ? 1 : id; +} + +inline +unsigned int strcode32(const char* string) { + unsigned int c; + signed int n = 0; + unsigned int id = 0; + + while ((c = *string++)) { + id += ((id << (c & 0x0F)) | ((id >> 3) + (c << (n & 0x0F)) + c)); + n++; + } + return id; +} \ No newline at end of file diff --git a/mgs/common/util.h b/mgs/common/util.h new file mode 100644 index 0000000..b1c0c39 --- /dev/null +++ b/mgs/common/util.h @@ -0,0 +1,23 @@ +#pragma once +#include + +inline +std::string formatStrcode(std::string strcode) { + uint8_t length = strcode.size(); + uint8_t pad = 6 - length; + std::string prefix; + + for (int i = 0; i < pad; i++) { + prefix += "0"; + } + + return (prefix + strcode); + +} + +inline +std::string intToHexString(int value) { + std::stringstream ss; + ss << std::hex << value; + return formatStrcode(ss.str()); +} \ No newline at end of file diff --git a/mgs/config/cnf.cpp b/mgs/config/cnf.cpp new file mode 100644 index 0000000..07ba1f3 --- /dev/null +++ b/mgs/config/cnf.cpp @@ -0,0 +1,167 @@ +#include "cnf.h" + +DataConfig::DataConfig(std::string filename) { + this->filename = filename; +} + +DataConfig::DataConfig(uint8_t* data, int size) { + this->data = data; + this->dataSize = size; +} + +DataConfig::~DataConfig() { +} + +void DataConfig::setWorkDir(std::string& workdir) { + this->workdir = &workdir; +} + +void DataConfig::setHandler(CNFHandler* cnfHandler) { + this->cnfHandler = cnfHandler; +} + +const char* DataConfig::getRegionForID(uint32_t id) { + for (int i = 0; i < 7; i++) { + if (cnfRegion[i].id == id) + return cnfRegion[i].name; + } + return ""; +} + +void DataConfig::readVram(std::vector tokens) { + for (int i = 1; i < tokens.size(); i++) { + if (cnfHandler) cnfHandler->processVram(tokens[i], currentRegion, workdir); + } +} + +uint32_t DataConfig::getCNFSize() { + return sizeof(DataCNF) + sizeof(DataCNFTag) * cnf->numTags; +} + +void DataConfig::readFace(std::vector tokens) {} + +void DataConfig::readFlag(std::string line) { + std::stringstream lineStream(line); + std::vector tokens = { std::istream_iterator{lineStream}, std::istream_iterator{} }; + std::string flagName = tokens[0]; + + if (flagName == "face") readFace(tokens); else + if (flagName == "vram") readVram(tokens); else + currentRegion = flagName; +} + +void DataConfig::readFile(DataCNFTag tag, DataCNFTag nextTag) { + uint32_t id = tag.id; + int64_t offset = tag.offset; + int64_t nextOffset = nextTag.offset; + + int size = nextOffset < offset ? 0 : nextOffset - offset; + uint32_t fileext = id >> 24; + uint32_t filehash = id & 0xFFFFFF; + std::string filename = (FileNames[filehash] != "") ? FileNames[filehash] : intToHexString(filehash); + filename = filename + "." + getExtForID(fileext); + + if (cnfHandler) cnfHandler->processFile(filename, currentRegion, workdir, §ion[offset], size); +} + +void DataConfig::readEnc(DataCNFTag tag) { + currentEncSize = tag.id & 0xFFFFFF; + int64_t offset = tag.offset; + + if (cnfHandler) cnfHandler->processEnc(currentRegion, §ion, currentEncSize, currentSectionSize); +} + +void DataConfig::readFace(DataCNFTag tag, DataCNFTag nextTag) {} + +void DataConfig::startSection(DataCNFTag tag) { + uint32_t flagID = tag.id & 0xFFFFFF; + + currentRegion = getRegionForID(flagID); + currentSectionSize = tag.offset; + dataPtr += cnfHandler->getNextSectorOffset(dataPtr); + section = &data[dataPtr]; +} + +void DataConfig::endSection(DataCNFTag tag) { + if (currentEncSize) { + dataPtr += currentEncSize; + currentEncSize = 0; + delete[] section; + } + else { + dataPtr += tag.offset; + } +} + +void DataConfig::readSection(DataCNFTag tag) { + uint32_t flagID = tag.id & 0xFFFFFF; + + flagID ? startSection(tag) : endSection(tag); +} + +void DataConfig::readFlag(DataCNFTag tag) {} + +void DataConfig::readLine(DataCNFTag tag, DataCNFTag nextTag) { + switch (tag.id >> 24) { + case 0x7F: + readSection(tag); + break; + case 0x7E: + readEnc(tag); + break; + case 0x7D: + readFace(tag, nextTag); + break; + case 0x00: + break; + default: + readFile(tag, nextTag); + } +} + +void DataConfig::readLine(std::string line) { + switch (line.at(0)) { + case 0x2E: + readFlag(&line[1]); + break; + case 0x3F: + if (cnfHandler) cnfHandler->processAfp(&line[1], currentRegion, workdir); + break; + case 0x40: + if (cnfHandler) cnfHandler->processQar(&line[1], currentRegion, workdir); + break; + default: + if (cnfHandler) cnfHandler->processFile(line, currentRegion, workdir, NULL, 0); + } +} + +void DataConfig::initBinaryStream() { + cnf = (DataCNF*)data; + dataPtr += getCNFSize(); +} + +void DataConfig::initTextStream() { + std::string cnfTxt = std::string((char*)data, dataSize); + textStream.str(cnfTxt); +} + +void DataConfig::readBinary() { + initBinaryStream(); + + for (int i = 0; i < cnf->numTags - 1; i++) { + readLine(cnf->tags[i], cnf->tags[i + 1]); + } +} + +void DataConfig::readASCII() { + initTextStream(); + + std::string line; + while (std::getline(textStream, line)) { + readLine(line); + } +} + +void DataConfig::read(CNFTYPE type) { + type == BINARY ? readBinary() : readASCII(); +} \ No newline at end of file diff --git a/mgs/config/cnf.h b/mgs/config/cnf.h new file mode 100644 index 0000000..23c6968 --- /dev/null +++ b/mgs/config/cnf.h @@ -0,0 +1,92 @@ +#pragma once +#include + +#include "../common/util.h" +#include "../common/strcode.h" +#include "../../dictionary/dictionary.h" + +struct DataCNFTag { + uint32_t id; + int32_t offset; +}; + +struct DataCNF { + uint32_t numTags; + DataCNFTag tags[]; +}; + +struct CNFREGION { + const char* name; + int id; +}; + +enum CNFTYPE { + ASCII, + BINARY +}; + +class CNFHandler { +public: + virtual int32_t getSectorSize() = 0; + virtual int64_t getNextSectorOffset(int64_t currentOffset) = 0; + virtual void processQar(std::string filename, std::string region, std::string* workDir) = 0; + virtual void processAfp(std::string filename, std::string region, std::string* workDir) = 0; + virtual void processVram(std::string filename, std::string region, std::string* workDir) = 0; + virtual void processEnc(std::string region, uint8_t** section, int64_t size, int64_t sectionSize) = 0; + virtual void processFile(std::string filename, std::string region, std::string* workDir, uint8_t* fileData, int size) = 0; + virtual void processFace(std::string filename, int16_t page, int64_t offset, std::string region, std::string* workDir) = 0; +}; + +class DataConfig { +public: + DataConfig(std::string filename); + DataConfig(uint8_t* data, int size); + + ~DataConfig(); + void read(CNFTYPE type = ASCII); + void setWorkDir(std::string& workdir); + void setHandler(CNFHandler* cnfHandler); + const char* getRegionForID(uint32_t id); +private: + int dataSize = 0; + DataCNF* cnf = {}; + uint8_t* data = {}; + std::string filename; + uint8_t* section = {}; + uint64_t dataPtr = 0; + std::string* workdir = {}; + CNFHandler* cnfHandler = {}; + std::string currentRegion; + int64_t currentEncSize = 0; + std::stringstream textStream; + int64_t currentSectionSize = 0; + + void readASCII(); + void readBinary(); + uint32_t getCNFSize(); + void initTextStream(); + void initBinaryStream(); + void readEnc(DataCNFTag tag); + void readFlag(DataCNFTag tag); + void readLine(std::string line); + void readFlag(std::string line); + void readSection(DataCNFTag tag); + void readVram(std::vector tokens); + void readFace(std::vector tokens); + void readFace(DataCNFTag tag, DataCNFTag nextTag); + void readLine(DataCNFTag tag, DataCNFTag nextTag); + void readFile(DataCNFTag tag, DataCNFTag nextTag); + + void endSection(DataCNFTag tag); + void startSection(DataCNFTag tag); + + const CNFREGION cnfRegion[7] = { + { "end", 0x00000000 }, + { "nocache", 0x00010000 }, + { "cache", 0x00000002 }, + { "resident", 0x00000003 }, + { "delayload", 0x00000004 }, + { "delayload_w", 0x00000005 }, + { "sound", 0x00000010 } + }; +}; \ No newline at end of file