diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bbbb8a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vs/ +Debug/ +Release/ +X64/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab921f2 --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ + +# Gcx + + +Gcx is a tool to work with GCX files found in the game Metal Gear Solid. Gcx files in Metal Gear Solid games are binary script files. Currently this tool will decompile GCX binaries into their original Game Command Language script. Currently Metal Gear Games from Metal Gear Solid 3 onwards are supported. MGS1 and MGS2 are not supported in the tool as their are some minor differences I haven't accounted for yet. Currenlty only decompilation is supported although I am hoping to add a compile function eventually. By default the -MGS4 option is selected. by entering -MGS3 at the command line you can switch it to MGS3 support. + +### GCL Information +The Game Command Language was created in-house at Kojima Productions and is based on TCL. The GCX binary generally consists of GCL compiled bytecode and resources that may or may not be encrypted. when decompiling a GCX file you can also choose to export the resources using the -res option. Resources consist of procs, strings and general binary data. The GCL Command that calls it decides how the data is interpreted. + +Metal Gear Solid runs many of it strings against a custom 24 bit hash algorithm. I have represented these in hex format in the decompilation wrapped around square brackets. I have also included a dictionary that can be modified, this tries to help identify the original name of the hash. The dictionary should use EUC-JP encoding as GCL supported scripting in Japanese by running it through the hash algorithm. Commands are resolved to C functions in game. I have salvaged some commands that I know from various ELF files and have made a commands.txt which can be used to resolve them and added to if new ones are found. Using the Command and Dictionary text files is completely optional. + +The scripts for all games after they are compiled should be in EUC-JP if you wish to read the Japanese print commands left behind by the developers. MGS4 is an exception which uses UTF-8 Japanese characters, however as the hashed Japanese characters in the script use EUC-JP you will have to switch between the two for this game. + +There are some variable values to note for in the decompilation. + - **$strres** calls upon a resource. Resources can be exported using the -res option + - **$arg** is an argument that has been passed to the proc when it was called + - **$lclArg** is variable that only lives in the current proc's scope + - **$var** is a variable that is taken from a global gcl buffer the game creates, this buffer has memory allocated for two different regions. + -- linkvarbuf + -- varbuf + +there is also a localvarbuf region. + +### To Do + - Fix formatting for switch statements + - Make Game Command Language TMLanguage. + +## Usage + +By default the tool will be set to decompile in MGS4 mode and not export resources. Currenlty only decompile is supported. MGS3 and MGS4 are the only game options that can be set. MGS2 and MGS1 are not currently supported. Below is a list of games that are supported by both options; +``` +-MGS4 + MGS4, MGS3D, Peace Walker +``` +``` +-MGS3 + MGS3, Portable Ops, Acid 1 + 2 +``` + +If you wish to decompile an MGS4 GCX file. + +``` +Gcx.exe "path\to\scenerio.gcx" +``` +As Decompile and MGS4 are default options there are no need to set them + +``` +Gcx.exe -res "path\to\scenerio.gcx" +``` +If you want to export resources as well you can use the -res option. This will export numbered binary resources in a separate folder. If there are a lot of resources it can take a while. + +``` +Gcx.exe -mgs3 -res "path\to\scenerio.gcx" +``` +If you wish to export from mgs3 with resources you would use the above option. + +``` +Gcx.exe -decompile -mgs4 "path\to\scenerio.gcx" +``` +all of the options are optional, above is what the default would resolve to. + +If there are any problems or you believe some output is incorrect please raise it in the issues tab. + +[MIT](LICENSE.md) +This project falls under the MIT license. + + + diff --git a/decompiler/decompiler.cpp b/decompiler/decompiler.cpp new file mode 100644 index 0000000..79ffe5e --- /dev/null +++ b/decompiler/decompiler.cpp @@ -0,0 +1,505 @@ +#include "decompiler.h" + +Decompiler::Decompiler(const GcxProc& proc, std::string procName) { + this->procBuffer = proc; + this->procName = procName; +} + +Decompiler::~Decompiler() { +} + +//print util +template +void Decompiler::readAndPrint(T& t, int size) { + t = *(T*)&procBuffer[ptr]; + ptr += size; + std::cout << std::dec << +t; +} + +void Decompiler::printNewLine() { + if (!isInline) + std::cout << "\n"; +} + +void Decompiler::printProcSig() { + if (isFirstRun) { + printProcName(); + isFirstRun = false; + } +} + +void Decompiler::printProcName() { + std::cout << procName << " "; +} + +void Decompiler::openBrace() { + std::cout << "{"; + printNewLine(); + if (!isInline) { indentation.indent(); } +} + +void Decompiler::closeBrace() { + if (!isInline) { indentation.unindent(); } + if (!isInline) { indentation.printIndent(); } + std::cout << "}"; +} + +//read helpers +void Decompiler::readLetter() { + uint8_t l = procBuffer[ptr]; + ptr++; + std::cout << l; +} + +void Decompiler::readCmdHeader() { + int size = getShortSize(); + int start = ptr; + + processFor(start, size); + std::cout << " \\"; + printNewLine(); +} + +//read expr +void Decompiler::captureValues(uint8_t tag, std::vector* exprStack) { + std::streambuf* old = std::cout.rdbuf(); + std::stringstream value; + std::cout.rdbuf(value.rdbuf()); + process(); + std::cout.rdbuf(old); + exprStack->push_back(value.str()); +} + +void Decompiler::calcExpr(std::vector* exprStack) { + uint8_t op = procBuffer[ptr]; + ptr++; + + op &= 0x1F; + + if (op == 0) + return; + + std::string val1 = exprStack->back(); + exprStack->pop_back(); + + std::string val2 = exprStack->back(); + exprStack->pop_back(); + + std::streambuf* old = std::cout.rdbuf(); + std::stringstream value; + std::cout.rdbuf(value.rdbuf()); + + if (op == 0x16) { indentation.printIndent(); } + std::cout << "("; ProcessExpr(op, val1, val2); std::cout << ")"; + if (op == 0x16) { std::cout << "\n"; } + std::cout.rdbuf(old); + exprStack->push_back(value.str()); +} + + +//read types +void Decompiler::readUShort() { + uint16_t val; + readAndPrint(val, 2); +} + +void Decompiler::readByte() { + int8_t val; + readAndPrint(val, 1); +} + +void Decompiler::readUByte() { + uint8_t val; + readAndPrint(val, 1); +} + +uint32_t Decompiler::readStrCode(bool hasLetter) { + uint8_t l; + + if (hasLetter) { + l = procBuffer[ptr]; + ptr++; + } + + uint32_t strcode = *(uint32_t*)&procBuffer[ptr]; + ptr += 3; + strcode &= 0x00FFFFFF; + + if (commandMap[strcode] != "") { + std::cout << std::hex << commandMap[strcode]; + return strcode; + } + + + if (strcodeMap[strcode] != "") { + std::cout << strcodeMap[strcode]; + } + else { + if (hasLetter) std::cout << l; + std::cout << std::hex << "[" << strcode << "]"; + } + + return strcode; +} + +void Decompiler::readString() { + uint8_t length = procBuffer[ptr]; + ptr++; + std::string str; + str.resize(length); + memcpy(&str[0], &procBuffer[ptr], length); + ptr += length; + str.pop_back(); + //SetConsoleOutputCP(65001); 20932 + std::cout << "\"" << str << "\""; +} + +void Decompiler::readArray() { + readStrCode(0); + uint8_t ap = procBuffer[ptr]; + ptr++; + std::cout << std::dec << "[" << +ap << "]"; +} + +void Decompiler::readShort() { + int16_t val; + readAndPrint(val, 2); +} + +void Decompiler::readLong() { + int32_t val; + readAndPrint(val, 4); +} + +void Decompiler::readULong() { + uint32_t val; + readAndPrint(val, 4); +} + +void Decompiler::readStrRes() { + //TODO: if > 8000 get res a diff way + uint16_t val; + std::cout << "$strres:"; + readAndPrint(val, 2); +} + +void Decompiler::readEnd() { + +} + +//read special commands +void Decompiler::readIf(int start, int size, bool isParam, bool isElse) { + if (!isParam) { + size = getShortSize(); + start = ptr; + } + + if (!isElse) { + std::cout << " "; + readExpr(); + } + + std::cout << " "; + openBrace(); + processFor(start, size); + closeBrace(); +} + +//read tags + +//0xC0 +void Decompiler::readNum() { + uint8_t val = procBuffer[ptr]; + ptr++; + int output = (val & 0x3F) - 1; + + std::cout << std::to_string(output); +} + +//0x90 +void Decompiler::readLocal() { + uint8_t lclArg = procBuffer[ptr]; + ptr++; + + lclArg &= 0x0F; + std::cout << "$lclArg" << std::dec << +lclArg; +} + +//0x80 +void Decompiler::readProc() { + int size = getSize(); + int start = ptr; + + if (!isInline) { indentation.printIndent(); } + std::cout << "proc "; printProcSig(); + openBrace(); + + processFor(start, size); + + closeBrace(); + printNewLine(); +} + +//0x70 +void Decompiler::readEval() { + int size = getSize(); + int start = ptr; + + if (!isInline) { indentation.printIndent(); } + std::cout << "@proc"; + readShort(); + + processFor(start, size); + printNewLine(); +} + +//0x60 +void Decompiler::readCmd() { + int size = getSize(); + int start = ptr; + + if (!isInline) { indentation.printIndent(); } + uint32_t strcode = readStrCode(); + if (strcode == 0xD86) { + readIf(start, size); + } + else { + readCmdHeader(); + indentation.indent(); + processFor(start, size); + indentation.unindent(); + } + + printNewLine(); +} + +//0x50 +void Decompiler::mgs3param() { + int size = getSize(); + int start = ptr; + + readLetter(); + processFor(start, size); +} + +void Decompiler::mgs4param() { + int size = getSize(); + int start = ptr; + + uint32_t strcode = readStrCode(1); + if (strcode == 0x69 && (getTag() == 0x30)) { //-i isn't always if + readIf(start, size, 1); + } + else if (strcode == 0x65) { + readIf(start, size, 1, 1); + } + else { + processFor(start, size); + } +} + +void Decompiler::readParam() { + if (!isInline) { indentation.printIndent(); } + std::cout << "-"; + + GAME == MGS3 ? mgs3param() : mgs4param(); + + if (procBuffer[ptr] != 0x00) std::cout << " \\"; + printNewLine(); +} + +//0x40 +void Decompiler::readArgs() { + uint8_t argno = procBuffer[ptr]; + ptr++; + + argno &= 0x0F; + + if (argno == 0x0F) { + uint8_t next = procBuffer[ptr]; + ptr++; + argno += next; + } + + std::cout << "$arg" << std::dec << +argno; +} + +//0x30 +void Decompiler::readExpr() { + int size = getSize(); + int start = ptr; + int length = start + size; + std::vectorexprStack; + + isInline = true; + while (ptr < length) { + uint8_t type = procBuffer[ptr]; + ((type & 0xE0) != 0xA0) ? captureValues(type, &exprStack) : calcExpr(&exprStack); + } + isInline = false; + std::cout << exprStack[0]; +} + +//0x20 +void Decompiler::readVar() { + std::string bufp = "varbuf"; + uint32_t varcode = *(uint32_t*)&procBuffer[ptr]; + ptr += 4; + + varcode = _byteswap_ulong(varcode); + + uint8_t tag = (varcode & 0xF0000000) >> 24; + uint32_t region = (varcode & 0xF00000); + if (region == 0x800000) { bufp = "linkvarbuf"; } + if (region == 0x100000) { bufp = "localvarbuf"; } + + uint32_t offset = varcode & 0xFFFF; + + std::cout << "$var:" << bufp << "[" << std::hex << offset << "]"; + + if (tag == 0x20) { + std::cout << "["; process(); std::cout << "]"; + std::cout << "["; process(); std::cout << "]"; + } +} + +void Decompiler::ProcessExpr(const uint8_t& op, std::string value1, std::string value2) { + switch (op) { + case 0x00: break; + case 0x01: std::cout << "-" << value1; break; + case 0x02: std::cout << "!" << value1; break; + case 0x03: std::cout << "~" << value1; break; + case 0x04: std::cout << value2 << " + " << value1; break; + case 0x05: std::cout << value2 << " - " << value1; break; + case 0x06: std::cout << value2 << " * " << value1; break; + case 0x07: std::cout << value2 << " / " << value1; break; + case 0x08: std::cout << value2 << " % " << value1; break; + case 0x09: std::cout << value2 << " << " << value1; break; + case 0x0A: std::cout << value2 << " >> " << value1; break; + case 0x0B: std::cout << value2 << " == " << value1; break; + case 0x0C: std::cout << value2 << " != " << value1; break; + case 0x0D: std::cout << value2 << " < " << value1; break; + case 0x0E: std::cout << value2 << " <= " << value1; break; + case 0x0F: std::cout << value2 << " > " << value1; break; + case 0x10: std::cout << value2 << " >= " << value1; break; + case 0x11: std::cout << value2 << " | " << value1; break; + case 0x12: std::cout << value2 << " & " << value1; break; + case 0x13: std::cout << value2 << " ^ " << value1; break; + case 0x14: std::cout << value2 << " || " << value1; break; + case 0x15: std::cout << value2 << " && " << value1; break; + case 0x16: std::cout << value2 << " = " << value1; break; + } +} + +void Decompiler::processType() { + uint8_t type = procBuffer[ptr]; + ptr++; + + switch (type) { + case 0x01: readShort(); break; + case 0x02: readUByte(); break; + case 0x03: readUByte(); break; + case 0x04: readUByte(); break; + case 0x06: readStrCode(); break; + case 0x07: readString(); break; + case 0x08: readUShort(); break; + case 0x09: readLong(); break; + case 0x0A: readULong(); break; + case 0x0D: readArray(); break; + case 0x0E: readStrRes(); break; + case 0x00: readEnd(); break; + } +} + +void Decompiler::processTag(const uint8_t& tag) { + switch (tag) { + case 0xF0: + case 0xE0: + case 0xD0: + case 0xC0: readNum(); break; + case 0x90: readLocal(); break; + case 0x80: readProc(); break; + case 0x70: readEval(); break; + case 0x60: readCmd(); break; + case 0x50: readParam(); break; + case 0x40: readArgs(); break; + case 0x30: readExpr(); break; + case 0x20: readVar(); break; + case 0x10: readVar(); break; + default: break; + } +} + +void Decompiler::processSize(uint32_t& size) { + switch (size) { + case 0x0D: size = procBuffer[ptr]; ptr++; break; + case 0x0E: size = *(uint16_t*)&procBuffer[ptr]; ptr += 2; break; + default: break; + } +} + +void Decompiler::processFor(const int& start, const int& size) { + int length = start + size; + + while (ptr < length) { + std::cout << " "; + process(); + } +} + +int Decompiler::getShortSize() { + uint16_t size = procBuffer[ptr]; + ptr++; + size &= 0xFF; + + if (size > 0x7F) { + uint16_t size2 = procBuffer[ptr]; + ptr++; + size2 &= 0xFF; + + size = (size << 8) | (size2); + size &= 0x7FFF; + } + + return size; +} + +int Decompiler::getSize() { + uint32_t size = procBuffer[ptr]; + ptr++; + size &= 0x0F; + processSize(size); + return size; +} + +uint8_t Decompiler::getTag() { + return (procBuffer[ptr] & 0xF0); +} + +void Decompiler::process() { + uint8_t tag = getTag(); + tag ? processTag(tag) : processType(); +} + +void Decompiler::decompile() { + process(); + printNewLine(); +} + + +void Decompiler::processResource() { + while (procBuffer[ptr]) { + std::cout << " "; + process(); + } +} + +void Decompiler::decompileResource(int size) { + std::cout << "proc "; printProcSig(); + openBrace(); + processResource(); + printNewLine(); + closeBrace(); + printNewLine(); + printNewLine(); +} \ No newline at end of file diff --git a/decompiler/decompiler.h b/decompiler/decompiler.h new file mode 100644 index 0000000..e23f64d --- /dev/null +++ b/decompiler/decompiler.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include + +#include "../mgs/script/gcx.h" +#include "../mgs/common/strcode.h" +#include "../mgs/common/game/game.h" +#include "../dictionary/dictionary.h" +#include "../indentation/indentation.h" + +class Decompiler { +public: + Decompiler(const GcxProc& proc, std::string procName); + ~Decompiler(); + void decompile(); + void decompileResource(int size); + +private: + + int ptr = 0; + GcxProc procBuffer; + std::string procName; + IndentationManager indentation; + + bool isIf = false; + bool isInline = false; + bool isFirstRun = true; + + int getSize(); + int getShortSize(); + + void process(); + void processType(); + void processSize(uint32_t& size); + void ProcessExpr(const uint8_t& op, std::string value1, std::string value2); + void processTag(const uint8_t& tag); + void processFor(const int& start, const int& size); + + uint8_t getTag(); + + //print helpers + template + void readAndPrint(T& t, int size); + void openBrace(); + void closeBrace(); + void printNewLine(); + void printProcSig(); + void printProcName(); + + + //read helpers + void readLetter(); + void readCmdHeader(); + + //expr helpers + void calcExpr(std::vector* exprStack); + void captureValues(uint8_t tag, std::vector* exprStack); + + //special funcs + void mgs4param(); + void mgs3param(); + void processResource(); + void readIf(int start, int size, bool isParam = 0, bool isElse = 0); + + //tag funcs + void readCmd(); + void readNum(); + void readVar(); + void readProc(); + void readEval(); + void readArgs(); + void readExpr(); + void readLocal(); + void readParam(); + + //type funcs + void readEnd(); + void readByte(); + void readLong(); + void readArray(); + void readShort(); + void readString(); + void readStrRes(); + void readUShort(); + uint32_t readStrCode(bool hasLetter = 0); + + //extra + void readUByte(); + void readULong(); +}; diff --git a/dictionary/dictionary.cpp b/dictionary/dictionary.cpp new file mode 100644 index 0000000..002d104 --- /dev/null +++ b/dictionary/dictionary.cpp @@ -0,0 +1,16 @@ +#include "dictionary.h" +#include "../mgs/common/strcode.h" + +std::map strcodeMap; + +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()); + strcodeMap[hashed] = str; + } +} \ No newline at end of file diff --git a/dictionary/dictionary.h b/dictionary/dictionary.h new file mode 100644 index 0000000..44e139a --- /dev/null +++ b/dictionary/dictionary.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include +#include + +extern std::map strcodeMap; +extern void loadDictionary(const std::string& dictionary); \ No newline at end of file diff --git a/gcx.sln b/gcx.sln new file mode 100644 index 0000000..52a0dcd --- /dev/null +++ b/gcx.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}") = "gcx", "gcx.vcxproj", "{F2AE4237-AE63-41DE-92D7-B5F0EAC281CF}" +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 + {F2AE4237-AE63-41DE-92D7-B5F0EAC281CF}.Debug|x64.ActiveCfg = Debug|x64 + {F2AE4237-AE63-41DE-92D7-B5F0EAC281CF}.Debug|x64.Build.0 = Debug|x64 + {F2AE4237-AE63-41DE-92D7-B5F0EAC281CF}.Debug|x86.ActiveCfg = Debug|Win32 + {F2AE4237-AE63-41DE-92D7-B5F0EAC281CF}.Debug|x86.Build.0 = Debug|Win32 + {F2AE4237-AE63-41DE-92D7-B5F0EAC281CF}.Release|x64.ActiveCfg = Release|x64 + {F2AE4237-AE63-41DE-92D7-B5F0EAC281CF}.Release|x64.Build.0 = Release|x64 + {F2AE4237-AE63-41DE-92D7-B5F0EAC281CF}.Release|x86.ActiveCfg = Release|Win32 + {F2AE4237-AE63-41DE-92D7-B5F0EAC281CF}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E41D368E-6CFE-4BE2-BA0D-4649C66B57BE} + EndGlobalSection +EndGlobal diff --git a/gcx.vcxproj b/gcx.vcxproj new file mode 100644 index 0000000..2d46c23 --- /dev/null +++ b/gcx.vcxproj @@ -0,0 +1,170 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {f2ae4237-ae63-41de-92d7-b5f0eac281cf} + gcx + 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 + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gcx.vcxproj.filters b/gcx.vcxproj.filters new file mode 100644 index 0000000..238e1a1 --- /dev/null +++ b/gcx.vcxproj.filters @@ -0,0 +1,69 @@ + + + + + {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 + + + + + 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/gcx.vcxproj.user b/gcx.vcxproj.user new file mode 100644 index 0000000..bf739f6 --- /dev/null +++ b/gcx.vcxproj.user @@ -0,0 +1,10 @@ + + + + true + + + n018a.gcx + WindowsLocalDebugger + + \ No newline at end of file diff --git a/indentation/indentation.cpp b/indentation/indentation.cpp new file mode 100644 index 0000000..2bcab9b --- /dev/null +++ b/indentation/indentation.cpp @@ -0,0 +1,34 @@ +#include "indentation.h" +#include + +IndentationManager::IndentationManager(int indentation) { + m_indentation = indentation; +} + +IndentationManager::IndentationManager() { +} + +IndentationManager::~IndentationManager() { +} + + +void IndentationManager::indent() { + m_indentation++; +} + +void IndentationManager::printIndent() { + for (int i = 0; i < m_indentation; i++) + std::cout << "\t"; +} + +void IndentationManager::unindent() { + if (m_indentation > 0) m_indentation--; +} + +void IndentationManager::setIndentation(int indentation) { + m_indentation = indentation; +} + +void IndentationManager::resetIndentation() { + setIndentation(0); +} \ No newline at end of file diff --git a/indentation/indentation.h b/indentation/indentation.h new file mode 100644 index 0000000..ad61391 --- /dev/null +++ b/indentation/indentation.h @@ -0,0 +1,14 @@ +#pragma once + +class IndentationManager { +public: + int m_indentation = 0; + IndentationManager(int indentation); + IndentationManager(); + ~IndentationManager(); + void indent(); + void unindent(); + void printIndent(); + void resetIndentation(); + void setIndentation(int indentation); +}; \ No newline at end of file diff --git a/interface/cli/cli.cpp b/interface/cli/cli.cpp new file mode 100644 index 0000000..cc76d59 --- /dev/null +++ b/interface/cli/cli.cpp @@ -0,0 +1,161 @@ +#include "cli.h" + +CLI::CLI(int argc, char** argv) { + this->argc = argc; + this->argv = argv; +} + +CLI::~CLI() { + +} + +void CLI::processCommands() { + while (currentArg < 4 && isCommand(argv[currentArg])) { + setCommand(argv[currentArg]); + currentArg++; + } +} + +void CLI::setCommand(char* arg) { + + if (!strcmp(arg, "-MGS4") || !strcmp(arg, "-mgs4") || !strcmp(arg, "-4")) { + GAME = MGS4; + return; + } + + if (!strcmp(arg, "-MGS3") || !strcmp(arg, "-mgs3") || !strcmp(arg, "-3")) { + GAME = MGS3; + return; + } + + if (!strcmp(arg, "-COMPILE") || !strcmp(arg, "-compile") || !strcmp(arg, "-c") || !strcmp(arg, "-C")) { + command = CLI::COMPILE; + return; + } + + if (!strcmp(arg, "-DECOMPILE") || !strcmp(arg, "-decompile") || !strcmp(arg, "-d") || !strcmp(arg, "-D")) { + command = CLI::DECOMPILE; + return; + } + + if (!strcmp(arg, "-res") || !strcmp(arg, "-RES")) { + exportRes = true; + return; + } + + if (!strcmp(arg, "-nores") || !strcmp(arg, "-NORES")) { + exportRes = false; + return; + } + + printf("command not recognised\n"); +} + +void CLI::processArgs() { + processCommands(); + processFile(); +} + +std::string makeOutputFilename(const std::string& input) { + std::string extension = getExtension(input); + std::string filename = "output.gcl"; + + if (extension == ".gcx") { + filename = getExtensionlessName(input) + ".gcl"; + } + return filename; +} + +void CLI::compileFile() { + printf("compile is not currently supported\n"); + exit(); +} + +void CLI::decompileProcs(Gcx& gcx, const std::string& input, std::string& output) { + std::streambuf* cout_buff = std::cout.rdbuf(); + + std::stringstream decompiled; + std::cout.rdbuf(decompiled.rdbuf()); + + Decompiler main(gcx.getMainProc(), "main"); + main.decompile(); + + for (int i = 0; i < gcx.getNumProc(); i++) { + std::string name = "proc" + std::to_string(i + 1); + Decompiler decompiler(gcx.getProc(i), name); + decompiler.decompile(); + } + + std::cout.rdbuf(cout_buff); + + writeTextToFile(decompiled, makeOutputFilename(input), output); +} + +void CLI::exportResources(Gcx& gcx, const std::string& input, std::string& output) { + std::string foldername = getExtensionlessName(input) + "_strres"; + updateDir(foldername, output); + + for (int i = 0; i < gcx.getNumResource(); i++) { + std::string filename = std::to_string(i) + ".bin"; + writeDataToFile(gcx.getResource(i), gcx.getResourceSize(i), filename, output); + } + + resetDir(output); +} + +void CLI::decompileFile() { + std::string input = argv[currentArg]; + std::string output = ""; + initDictionaries(); + currentArg++; + + if (currentArg == argc - 1) output = argv[currentArg]; + + Gcx gcx = Gcx(input); + gcx.open(); + + if (exportRes) exportResources(gcx, input, output); + decompileProcs(gcx, input, output); +} + +void CLI::processFile() { + switch (command) { + case CLI::DECOMPILE: + decompileFile(); + break; + case CLI::COMPILE: + compileFile(); + break; + default: + decompileFile(); + } +} + +bool CLI::checkInput() { + if (argc > 1 && argc < 6) return true; + printUsage(); + return false; +} + +void CLI::initDictionaries() { + loadDictionary("dictionary.txt"); + loadCommands("commands.txt"); +} + +void CLI::run(std::string programName, std::string version) { + printf("Running %s v%s: Visit https://github.com/Jayveer/Gcx for updates:\n", programName.c_str(), version.c_str()); + 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..da9405f --- /dev/null +++ b/interface/cli/cli.h @@ -0,0 +1,42 @@ +#pragma once +#include "../../mgs/common/fileutil.h" +#include "../../decompiler/decompiler.h" + +class CLI { +public: + CLI(int argc, char** argv); + ~CLI(); + + void run(std::string programName, std::string version); + void exit(); + + enum COMMAND { + DECOMPILE, + COMPILE + }; + +private: + int argc; + char** argv; + int currentArg = 1; + CLI::COMMAND command; + bool exportRes = false; + + void printUsage(); + bool checkInput(); + void processArgs(); + void processFile(); + void compileFile(); + void decompileFile(); + void processCommands(); + void initDictionaries(); + bool isCommand(char* arg); + void setCommand(char* arg); + void decompileProcs(Gcx& gcx, const std::string& input, std::string& output); + void exportResources(Gcx& gcx, const std::string& input, std::string& output); + + const char* EXIT_MESSAGE = "Exiting\n"; + const char* USAGE_MESSAGE = "Usage:\t gcx.exe [-OPT] <*.gcx> \n" + "To decompile using the MGS3 version of Gcx use the -mgs3 option\n" + "If you wish to export resources as well use the -res option\n"; +}; \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..3d3ba4a --- /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("Gcx", "1.0"); +} \ No newline at end of file diff --git a/mgs/common/fileutil.h b/mgs/common/fileutil.h new file mode 100644 index 0000000..5862c9d --- /dev/null +++ b/mgs/common/fileutil.h @@ -0,0 +1,84 @@ +#pragma once +#include +#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(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 isDirectory(std::string& output) { + std::filesystem::path p{ output }; + return std::filesystem::is_directory(p); +} + +inline +bool fileExists(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); +} + +inline +void writeTextToFile(const std::stringstream& sstream, const std::string& filename, std::string& output) { + if (!std::filesystem::exists(output)) + std::filesystem::create_directories(output); + + updateDir(filename, output); + std::ofstream ofs(output); + ofs << sstream.str(); + 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..831e30d --- /dev/null +++ b/mgs/common/game/game.cpp @@ -0,0 +1,23 @@ +#include "game.h" + +std::map commandMap; + +_GAME GAME = MGS4; + +void loadCommands(const std::string& commands) { + std::string str; + std::ifstream file(commands); + + if (!file) return; + + while (std::getline(file, str)) { + const char* regex = "([^\\s]+)(\\s+)?->(\\s+)?([^\\s]+)"; + std::regex re(regex); + std::smatch match; + + if (std::regex_search(str, match, re)) { + int n = stoi(match[1], 0, 16); + commandMap[n] = match[4]; + } + } +} \ 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..5c45316 --- /dev/null +++ b/mgs/common/game/game.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include +#include +#include + +enum _GAME { + MGS3, + MGS4 +}; + +extern _GAME GAME; +extern std::map commandMap; +extern void loadCommands(const std::string& dictionary); \ No newline at end of file diff --git a/mgs/common/strcode.h b/mgs/common/strcode.h new file mode 100644 index 0000000..636e262 --- /dev/null +++ b/mgs/common/strcode.h @@ -0,0 +1,29 @@ +#pragma once + +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/script/gcx.cpp b/mgs/script/gcx.cpp new file mode 100644 index 0000000..8209915 --- /dev/null +++ b/mgs/script/gcx.cpp @@ -0,0 +1,106 @@ +#include "gcx.h" + +Gcx::Gcx(std::string filename) { + this->filename = filename; + initData(); +} + +void Gcx::initData() { + std::ifstream fs; + int size = std::filesystem::file_size(filename); + + fs.open(filename, std::ios::binary); + uint8_t* data = new uint8_t[size]; + fs.read((char*)data, size); + this->gcxData = data; + fs.close(); +} + +Gcx::~Gcx() { + delete[] gcxData; +} + +int Gcx::getNumProc() { + return numProc; +} + +void Gcx::setNumProc() { + int i = 0; + while (procTable[i] != -1) i++; + numProc = i; +} + +bool Gcx::isScriptResource(int idx) { + return (resourceTable[idx] & 0xFF000000) != 0x80000000; +} + +int Gcx::getResourceSize(int idx) { + int offset = resourceTable[idx] & 0x00FFFFFF; + int nextOffset = idx == numResource ? blockHeader->fontDataOffset : resourceTable[idx + 1] & 0x00FFFFFF; + + return nextOffset - offset; +} + +int Gcx::getResourcesSize() { + return blockHeader->fontDataOffset - blockHeader->stringResources; +} + +int Gcx::getNumResource() { + return numResource; +} + +void Gcx::setNumResource() { + numResource = (blockHeader->stringResources - blockHeader->resourceTableOffset) / 4; +} + +GcxProc Gcx::getResource(int idx) { + uint8_t* resources = &blockStart[blockHeader->stringResources]; + return &resources[resourceTable[idx] & 0x00FFFFFF]; +} + +void Gcx::decodeBuffer(uint32_t seed, uint8_t* src, int size) { + for (int i = 0; i < size; i++) { + seed = (seed * 0x7D2B89DD) + 0xCF9; + int c = (seed >> 0xF) & 0x00FF; + src[i] ^= c; + } +} + +void Gcx::decryptStringResources() { + if (!blockHeader->seed) return; + int size = blockHeader->fontDataOffset - blockHeader->stringResources; + uint8_t* stringTableData = &blockStart[blockHeader->stringResources]; + if (!size) return; + decodeBuffer(blockHeader->seed, stringTableData, size); + blockHeader->seed = 0; +} + +GcxProc Gcx::getMainProc() { + return mainProcStart; +} + +GcxProc Gcx::getProc(int idx) { + return &procStart[procTable[idx] & 0x00FFFFFF]; +} + +void Gcx::setProc() { + uint32_t mainProcOffset = *(uint32_t*)&blockStart[blockHeader->procOffset]; + + procStart = &blockStart[blockHeader->procOffset + 4]; + mainProcSize = (uint32_t*)&procStart[mainProcOffset]; + mainProcStart = &procStart[mainProcOffset + 4]; +} + +void Gcx::open() { + timestamp = (uint32_t*)gcxData; + procTable = (int32_t*)&gcxData[4]; + setNumProc(); + + blockStart = (uint8_t*)(&procTable[numProc + 1]); + blockHeader = (GcxBlockHeader*)blockStart; + + resourceTable = (uint32_t*)&blockStart[blockHeader->resourceTableOffset]; + setNumResource(); + decryptStringResources(); + setProc(); +} diff --git a/mgs/script/gcx.h b/mgs/script/gcx.h new file mode 100644 index 0000000..5140df3 --- /dev/null +++ b/mgs/script/gcx.h @@ -0,0 +1,52 @@ +#pragma once +#include +#include + +typedef uint8_t* GcxProc; + +struct GcxBlockHeader { + uint32_t procOffset; + uint32_t resourceTableOffset; + uint32_t stringResources; + uint32_t fontDataOffset; + uint32_t seed; +}; + +class Gcx { +public: + Gcx(std::string filename); + ~Gcx(); + + void open(); + int getNumProc(); + GcxProc getMainProc(); + int getNumResource(); + int getResourcesSize(); + GcxProc getProc(int idx); + int getResourceSize(int idx); + GcxProc getResource(int idx); + bool isScriptResource(int idx); +private: + std::string filename = "scenerio.gcx"; + + int numProc = 0; + uint8_t* gcxData; + uint8_t* procStart; + int32_t* procTable; + int numResource = 0; + uint32_t* timestamp; + uint8_t* blockStart; + uint32_t* mainProcSize; + uint8_t* mainProcStart; + uint32_t* resourceTable; + GcxBlockHeader* blockHeader; + + void setProc(); + void initData(); + void setNumProc(); + void setNumResource(); + + void decryptStringResources(); + void decodeBuffer(uint32_t seed, uint8_t* src, int size); +}; +