mirror of https://github.com/zeldaret/tmc.git
				
				
				
			
		
			
				
	
	
		
			800 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			800 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C++
		
	
	
	
| #include <fmt/format.h>
 | ||
| #include <fstream>
 | ||
| #include <nlohmann/json.hpp>
 | ||
| 
 | ||
| using namespace std::string_literals;
 | ||
| using json = nlohmann::json;
 | ||
| using u8   = std::uint8_t;
 | ||
| using u16  = std::uint16_t;
 | ||
| using u32  = std::uint32_t;
 | ||
| 
 | ||
| const u32 RomStartAddress = 0x8000000;
 | ||
| 
 | ||
| namespace {
 | ||
| 
 | ||
|     struct LanguageTable {
 | ||
|         const char *name;
 | ||
|         u32 address;
 | ||
|     };
 | ||
| 
 | ||
|     enum Color {
 | ||
|         Color_White,
 | ||
|         Color_Red,
 | ||
|         Color_Green,
 | ||
|         Color_Blue,
 | ||
|         Color_Yellow,
 | ||
| 
 | ||
|         Color_Count,
 | ||
|     };
 | ||
| 
 | ||
|     constexpr const char *const ColorStrings[] = {
 | ||
|         [Color_White]  = "White",
 | ||
|         [Color_Red]    = "Red",
 | ||
|         [Color_Green]  = "Green",
 | ||
|         [Color_Blue]   = "Blue",
 | ||
|         [Color_Yellow] = "Yellow",
 | ||
|     };
 | ||
| 
 | ||
|     enum Input {
 | ||
|         Input_A,
 | ||
|         Input_B,
 | ||
|         Input_Left,
 | ||
|         Input_Right,
 | ||
|         Input_DUp,
 | ||
|         Input_DDown,
 | ||
|         Input_DLeft,
 | ||
|         Input_DRight,
 | ||
|         Input_Dpad,
 | ||
|         Input_Select,
 | ||
|         Input_Start,
 | ||
| 
 | ||
|         Input_Count,
 | ||
|     };
 | ||
| 
 | ||
|     constexpr const char *const InputStrings[] = {
 | ||
|         [Input_A]      = "A",
 | ||
|         [Input_B]      = "B",
 | ||
|         [Input_Left]   = "Left",
 | ||
|         [Input_Right]  = "Right",
 | ||
|         [Input_DUp]    = "DUp",
 | ||
|         [Input_DDown]  = "DDown",
 | ||
|         [Input_DLeft]  = "DLeft",
 | ||
|         [Input_DRight] = "DRight",
 | ||
|         [Input_Dpad]   = "Dpad",
 | ||
|         [Input_Select] = "Select",
 | ||
|         [Input_Start]  = "Start",
 | ||
|     };
 | ||
| 
 | ||
|     const std::map<u8, std::string> CharConvertArray = {
 | ||
|         {0x0a, "\n"},
 | ||
|         {0x0d, "\r"},
 | ||
|         {0x20, " "},
 | ||
|         {0x21, "!"},
 | ||
|         {0x22, "\""},
 | ||
|         {0x23, "#"},
 | ||
|         {0x24, "$"},
 | ||
|         {0x25, "%"},
 | ||
|         {0x26, "&"},
 | ||
|         {0x27, "\'"},
 | ||
|         {0x28, "("},
 | ||
|         {0x29, ")"},
 | ||
|         {0x2a, "*"},
 | ||
|         {0x2b, "+"},
 | ||
|         {0x2c, ","},
 | ||
|         {0x2d, "-"},
 | ||
|         {0x2e, "."},
 | ||
|         {0x2f, "/"},
 | ||
|         {0x30, "0"},
 | ||
|         {0x31, "1"},
 | ||
|         {0x32, "2"},
 | ||
|         {0x33, "3"},
 | ||
|         {0x34, "4"},
 | ||
|         {0x35, "5"},
 | ||
|         {0x36, "6"},
 | ||
|         {0x37, "7"},
 | ||
|         {0x38, "8"},
 | ||
|         {0x39, "9"},
 | ||
|         {0x3a, ":"},
 | ||
|         {0x3b, ";"},
 | ||
|         {0x3c, "<"},
 | ||
|         {0x3d, "="},
 | ||
|         {0x3e, ">"},
 | ||
|         {0x3f, "?"},
 | ||
|         {0x40, "@"},
 | ||
|         {0x41, "A"},
 | ||
|         {0x42, "B"},
 | ||
|         {0x43, "C"},
 | ||
|         {0x44, "D"},
 | ||
|         {0x45, "E"},
 | ||
|         {0x46, "F"},
 | ||
|         {0x47, "G"},
 | ||
|         {0x48, "H"},
 | ||
|         {0x49, "I"},
 | ||
|         {0x4a, "J"},
 | ||
|         {0x4b, "K"},
 | ||
|         {0x4c, "L"},
 | ||
|         {0x4d, "M"},
 | ||
|         {0x4e, "N"},
 | ||
|         {0x4f, "O"},
 | ||
|         {0x50, "P"},
 | ||
|         {0x51, "Q"},
 | ||
|         {0x52, "R"},
 | ||
|         {0x53, "S"},
 | ||
|         {0x54, "T"},
 | ||
|         {0x55, "U"},
 | ||
|         {0x56, "V"},
 | ||
|         {0x57, "W"},
 | ||
|         {0x58, "X"},
 | ||
|         {0x59, "Y"},
 | ||
|         {0x5a, "Z"},
 | ||
|         {0x5b, "["},
 | ||
|         {0x5c, "\'"},
 | ||
|         {0x5d, "]"},
 | ||
|         {0x5e, "^"},
 | ||
|         {0x5f, "_"},
 | ||
|         {0x60, "`"},
 | ||
|         {0x61, "a"},
 | ||
|         {0x62, "b"},
 | ||
|         {0x63, "c"},
 | ||
|         {0x64, "d"},
 | ||
|         {0x65, "e"},
 | ||
|         {0x66, "f"},
 | ||
|         {0x67, "g"},
 | ||
|         {0x68, "h"},
 | ||
|         {0x69, "i"},
 | ||
|         {0x6a, "j"},
 | ||
|         {0x6b, "k"},
 | ||
|         {0x6c, "l"},
 | ||
|         {0x6d, "m"},
 | ||
|         {0x6e, "n"},
 | ||
|         {0x6f, "o"},
 | ||
|         {0x70, "p"},
 | ||
|         {0x71, "q"},
 | ||
|         {0x72, "r"},
 | ||
|         {0x73, "s"},
 | ||
|         {0x74, "t"},
 | ||
|         {0x75, "u"},
 | ||
|         {0x76, "v"},
 | ||
|         {0x77, "w"},
 | ||
|         {0x78, "x"},
 | ||
|         {0x79, "y"},
 | ||
|         {0x7a, "z"},
 | ||
|         {0x82, ","},
 | ||
|         {0x84, "„"},
 | ||
|         {0x85, "⋯"},
 | ||
|         {0x8A, "Š"},
 | ||
|         {0x8B, "‹"},
 | ||
|         {0x8C, "Œ"},
 | ||
|         {0x8E, "Ž"},
 | ||
|         {0x91, "‘"},
 | ||
|         {0x92, "’"},
 | ||
|         {0x93, "“"},
 | ||
|         {0x94, "”"},
 | ||
|         {0x95, "·"},
 | ||
|         {0x99, "™"},
 | ||
|         {0x9A, "š"},
 | ||
|         {0x9B, "›"},
 | ||
|         {0x9C, "œ"},
 | ||
|         {0x9E, "ž"},
 | ||
|         {0x9F, "Ÿ"},
 | ||
|         {0xA1, "¡"},
 | ||
|         {0xA3, "♪"},
 | ||
|         {0xAA, "ª"},
 | ||
|         {0xAB, "«"},
 | ||
|         {0xB0, "º"},
 | ||
|         {0xB4, "'"},
 | ||
|         {0xB7, "´"},
 | ||
|         {0xBA, "º"},
 | ||
|         {0xBB, "»"},
 | ||
|         {0xBF, "¿"},
 | ||
|         {0xC0, "À"},
 | ||
|         {0xC1, "Á"},
 | ||
|         {0xC2, "Â"},
 | ||
|         {0xC3, "Ã"},
 | ||
|         {0xC4, "Ä"},
 | ||
|         {0xC5, "Å"},
 | ||
|         {0xC6, "Æ"},
 | ||
|         {0xC7, "Ç"},
 | ||
|         {0xC8, "È"},
 | ||
|         {0xC9, "É"},
 | ||
|         {0xCA, "Ê"},
 | ||
|         {0xCB, "Ë"},
 | ||
|         {0xCC, "Ì"},
 | ||
|         {0xCD, "Í"},
 | ||
|         {0xCE, "Î"},
 | ||
|         {0xCF, "Ï"},
 | ||
|         {0xD0, "Đ"},
 | ||
|         {0xD1, "Ñ"},
 | ||
|         {0xD2, "Ò"},
 | ||
|         {0xD3, "Ó"},
 | ||
|         {0xD4, "Ô"},
 | ||
|         {0xD5, "Õ"},
 | ||
|         {0xD6, "Ö"},
 | ||
|         {0xD7, "×"},
 | ||
|         {0xD8, "Ø"},
 | ||
|         {0xD9, "Ù"},
 | ||
|         {0xDA, "Ú"},
 | ||
|         {0xDB, "Û"},
 | ||
|         {0xDC, "Ü"},
 | ||
|         {0xDD, "Ý"},
 | ||
|         {0xDE, "Þ"},
 | ||
|         {0xDF, "β"},
 | ||
|         {0xE0, "à"},
 | ||
|         {0xE1, "á"},
 | ||
|         {0xE2, "â"},
 | ||
|         {0xE3, "ã"},
 | ||
|         {0xE4, "ä"},
 | ||
|         {0xE5, "å"},
 | ||
|         {0xE6, "æ"},
 | ||
|         {0xE7, "ç"},
 | ||
|         {0xE8, "è"},
 | ||
|         {0xE9, "é"},
 | ||
|         {0xEA, "ê"},
 | ||
|         {0xEB, "ë"},
 | ||
|         {0xEC, "ì"},
 | ||
|         {0xED, "í"},
 | ||
|         {0xEE, "î"},
 | ||
|         {0xEF, "ï"},
 | ||
|         {0xF0, "ð"},
 | ||
|         {0xF1, "ñ"},
 | ||
|         {0xF2, "ò"},
 | ||
|         {0xF3, "ó"},
 | ||
|         {0xF4, "ô"},
 | ||
|         {0xF5, "õ"},
 | ||
|         {0xF6, "ö"},
 | ||
|         {0xF7, "÷"},
 | ||
|         {0xF8, "ø"},
 | ||
|         {0xF9, "ù"},
 | ||
|         {0xFA, "ú"},
 | ||
|         {0xFB, "û"},
 | ||
|         {0xFC, "ü"},
 | ||
|         {0xFD, "ý"},
 | ||
|         {0xFE, "þ"},
 | ||
|         {0xFF, "ÿ"},
 | ||
|     };
 | ||
| 
 | ||
|     using ConvertFunction = std::string (*const)(const char *&);
 | ||
| 
 | ||
|     std::string Unk1Handler(const char *&ptr) {
 | ||
|         u8 a = *ptr++;
 | ||
|         return fmt::format("{{01:{:02X}}}", a);
 | ||
|     }
 | ||
| 
 | ||
|     std::string ColorHandler(const char *&ptr) {
 | ||
|         u8 color = *ptr++;
 | ||
|         if (color >= Color_Count)
 | ||
|             throw std::runtime_error(ptr);
 | ||
|         return fmt::format("{{Color:{}}}", ColorStrings[color]);
 | ||
|     }
 | ||
| 
 | ||
|     std::string SoundHandler(const char *&ptr) {
 | ||
|         u8 a = *ptr++;
 | ||
|         u8 b = *ptr++;
 | ||
|         return fmt::format("{{Sound:{:02X}:{:02X}}}", a, b);
 | ||
|     }
 | ||
| 
 | ||
|     std::string Unk4Handler(const char *&ptr) {
 | ||
|         u8 a = *ptr++;
 | ||
|         if (a == 0x10) {
 | ||
|             u8 b = *ptr++;
 | ||
|             return fmt::format("{{04:{:02X}:{:02X}}}", a, b);
 | ||
|         } else {
 | ||
|             return fmt::format("{{04:{:02X}}}", a);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     std::string ChoiceHandler(const char *&ptr) {
 | ||
|         u8 category = *ptr++;
 | ||
|         if (category == 0xff) {
 | ||
|             return "{Choice:FF}";
 | ||
|         } else {
 | ||
|             u8 action = *ptr++;
 | ||
|             return fmt::format("{{Choice:{:02X}:{:02X}}}", category, action);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     std::string VariableHandler(const char *&ptr) {
 | ||
|         u8 idx = *ptr++;
 | ||
|         if (idx == 0) {
 | ||
|             return "{Player}";
 | ||
|         } else {
 | ||
|             return fmt::format("{{Var:{:X}}}", idx);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     std::string Unk7Handler(const char *&ptr) {
 | ||
|         u8 a = *ptr++;
 | ||
|         u8 b = *ptr++;
 | ||
|         return fmt::format("{{07:{:02X}:{:02X}}}", a, b);
 | ||
|     }
 | ||
| 
 | ||
|     std::string Unk8Handler(const char *&ptr) {
 | ||
|         u8 a = *ptr++;
 | ||
|         if (a != 0xff)
 | ||
|             throw std::runtime_error("unmatched unk8: "s + std::to_string(a));
 | ||
|         return "{08:FF}";
 | ||
|     }
 | ||
| 
 | ||
|     std::string Unk9Handler(const char *&ptr) {
 | ||
|         u8 a = *ptr++;
 | ||
|         if (a != 0x78 && a != 0x00)
 | ||
|             throw std::runtime_error("unmatched unk8: "s + std::to_string(a));
 | ||
|         return fmt::format("{{09:{:02X}}}", a);
 | ||
|     }
 | ||
| 
 | ||
|     std::string InputHandler(const char *&ptr) {
 | ||
|         u8 key = *ptr++;
 | ||
|         if (key > 8)
 | ||
|             throw std::runtime_error("unmatched key: "s + std::to_string(key));
 | ||
|         return fmt::format("{{Key:{}}}", InputStrings[key]);
 | ||
|     }
 | ||
| 
 | ||
|     std::string SymbolHandler(const char *&ptr) {
 | ||
|         u8 a = *ptr++;
 | ||
|         return fmt::format("{{Symbol:{:02X}}}", a);
 | ||
|     }
 | ||
| 
 | ||
|     const std::map<u8, ConvertFunction> FuncConvertArray = {
 | ||
|         {0x01, Unk1Handler},
 | ||
|         {0x02, ColorHandler},
 | ||
|         {0x03, SoundHandler},
 | ||
|         {0x04, Unk4Handler},
 | ||
|         {0x05, ChoiceHandler},
 | ||
|         {0x06, VariableHandler},
 | ||
|         {0x07, Unk7Handler},
 | ||
|         {0x08, Unk8Handler},
 | ||
|         {0x09, Unk9Handler},
 | ||
|         {0x0c, InputHandler},
 | ||
|         {0x0f, SymbolHandler},
 | ||
|     };
 | ||
| 
 | ||
|     std::string ParseTMCString(const char *ptr) {
 | ||
|         std::string ret;
 | ||
| 
 | ||
|         while (*ptr) {
 | ||
|             u8 c = *ptr++;
 | ||
| 
 | ||
|             /* Convert character. */
 | ||
|             {
 | ||
|                 const auto it = CharConvertArray.find(c);
 | ||
| 
 | ||
|                 if (it != std::end(CharConvertArray)) {
 | ||
|                     ret += it->second;
 | ||
|                     continue;
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             /* Convert function. */
 | ||
|             {
 | ||
|                 const auto it = FuncConvertArray.find(c);
 | ||
| 
 | ||
|                 if (it != std::end(FuncConvertArray)) {
 | ||
|                     ret += it->second(ptr);
 | ||
|                     continue;
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             throw std::runtime_error(fmt::format("Unknown characters: {}", ptr));
 | ||
|         }
 | ||
| 
 | ||
|         return ret;
 | ||
|     }
 | ||
| 
 | ||
|     using RevertFunction = void (*)(const char *&, char *&);
 | ||
| 
 | ||
|     void ColorRevert(const char *&src, char *&dst) {
 | ||
|         *dst++ = 0x02;
 | ||
|         src++; // ':'
 | ||
|         for (u32 i = 0; i < Color_Count; i++) {
 | ||
|             const char *const color     = ColorStrings[i];
 | ||
|             const std::size_t color_len = std::strlen(color);
 | ||
|             if (std::strncmp(src, color, color_len) == 0) {
 | ||
|                 *dst++ = i;
 | ||
|                 src += color_len;
 | ||
|                 return;
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         throw std::runtime_error(fmt::format("Color not found: {}", src));
 | ||
|     }
 | ||
| 
 | ||
|     void SoundRevert(const char *&src, char *&dst) {
 | ||
|         *dst++ = 0x03;
 | ||
|         *dst++ = std::strtoul(++src, nullptr, 0x10);
 | ||
|         src += 2;
 | ||
|         *dst++ = std::strtoul(++src, nullptr, 0x10);
 | ||
|         src += 2;
 | ||
|     }
 | ||
| 
 | ||
|     void ChoiceRevert(const char *&src, char *&dst) {
 | ||
|         *dst++    = 0x05;
 | ||
|         u8 choice = std::strtoul(++src, nullptr, 0x10);
 | ||
| 
 | ||
|         *dst++ = choice;
 | ||
|         src += 2;
 | ||
| 
 | ||
|         if (choice == 0xff)
 | ||
|             return;
 | ||
| 
 | ||
|         *dst++ = std::strtoul(++src, nullptr, 0x10);
 | ||
|         src += 2;
 | ||
|     }
 | ||
| 
 | ||
|     void PlayerRevert(const char *&src, char *&dst) {
 | ||
|         *dst++ = 0x06;
 | ||
|         *dst++ = 0x00;
 | ||
| 
 | ||
|         (void)src;
 | ||
|     }
 | ||
| 
 | ||
|     void VariableRevert(const char *&src, char *&dst) {
 | ||
|         *dst++ = 0x06;
 | ||
|         src++; // ':'
 | ||
| 
 | ||
|         *dst++ = std::strtoul(src, nullptr, 0x10);
 | ||
|         src++;
 | ||
|     }
 | ||
| 
 | ||
|     void KeyRevert(const char *&src, char *&dst) {
 | ||
|         *dst++ = 0x0c;
 | ||
|         src++; // ':'
 | ||
|         for (u32 i = 0; i < Input_Count; i++) {
 | ||
|             const char *const input     = InputStrings[i];
 | ||
|             const std::size_t input_len = std::strlen(input);
 | ||
|             if (std::strncmp(src, input, input_len) == 0) {
 | ||
|                 *dst++ = i;
 | ||
|                 src += input_len;
 | ||
|                 return;
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         throw std::runtime_error(fmt::format("Input not found: {}", src));
 | ||
|     }
 | ||
| 
 | ||
|     void SymbolRevert(const char *&src, char *&dst) {
 | ||
|         *dst++ = 0x0f;
 | ||
|         src++; // ':'
 | ||
| 
 | ||
|         *dst++ = std::strtoul(src, nullptr, 0x10);
 | ||
|         src += 2;
 | ||
|     }
 | ||
| 
 | ||
|     const std::pair<std::string, RevertFunction> FuncRevertArray[] = {
 | ||
|         {"Color", ColorRevert},
 | ||
|         {"Sound", SoundRevert},
 | ||
|         {"Choice", ChoiceRevert},
 | ||
|         {"Player", PlayerRevert},
 | ||
|         {"Var", VariableRevert},
 | ||
|         {"Key", KeyRevert},
 | ||
|         {"Symbol", SymbolRevert},
 | ||
|     };
 | ||
| 
 | ||
|     void WriteTMCString(char *&dst, const std::string &src) {
 | ||
|         const char *ptr = src.data();
 | ||
| 
 | ||
|         while (*ptr) {
 | ||
|             /* Parse special */
 | ||
|             {
 | ||
|                 if (*ptr == '{') {
 | ||
|                     ptr++;
 | ||
|                     const auto it = std::find_if(std::begin(FuncRevertArray), std::end(FuncRevertArray), [&](const auto &data) {
 | ||
|                         return std::strncmp(ptr, data.first.c_str(), data.first.size()) == 0;
 | ||
|                     });
 | ||
| 
 | ||
|                     if (it != std::end(FuncRevertArray)) {
 | ||
|                         ptr += it->first.size();
 | ||
|                         it->second(ptr, dst);
 | ||
|                     } else {
 | ||
|                         do {
 | ||
|                             *dst++ = std::strtoul(ptr, nullptr, 0x10);
 | ||
|                             ptr += 2;
 | ||
|                         } while (*ptr == ':' && (ptr++, true));
 | ||
|                     }
 | ||
| 
 | ||
|                     if (*ptr != '}')
 | ||
|                         throw std::runtime_error(fmt::format("unmatched characters: \"{}\"\n", ptr));
 | ||
| 
 | ||
|                     ptr++;
 | ||
|                     continue;
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             /* Convert character. */
 | ||
|             {
 | ||
|                 const auto it = std::find_if(std::begin(CharConvertArray), std::end(CharConvertArray), [ptr](const auto &data) {
 | ||
|                     return std::strncmp(ptr, data.second.c_str(), data.second.length()) == 0;
 | ||
|                 });
 | ||
| 
 | ||
|                 if (it != std::end(CharConvertArray)) {
 | ||
|                     ptr += it->second.size();
 | ||
|                     *dst++ = it->first;
 | ||
|                     continue;
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             throw std::runtime_error(fmt::format("unmatched characters: \"{}\"\n", ptr));
 | ||
|         }
 | ||
|         *dst++ = '\0';
 | ||
|     }
 | ||
| 
 | ||
| }
 | ||
| 
 | ||
| void ExtractStringTable(std::string &rom_path, const std::vector<LanguageTable> &tables) {
 | ||
|     const std::vector<char> rom = [&]() {
 | ||
|         std::vector<char> rom;
 | ||
| 
 | ||
|         /* Open ROM. */
 | ||
|         std::ifstream rom_file(rom_path, std::ios::in);
 | ||
| 
 | ||
|         if (!rom_file.good()) {
 | ||
|             fmt::print(stderr, "Couldn't open ROM {}\n", rom_path);
 | ||
|             exit(EXIT_FAILURE);
 | ||
|         }
 | ||
| 
 | ||
|         /* Get ROM size. */
 | ||
|         rom_file.seekg(0, std::ios::end);
 | ||
|         const std::size_t rom_size = rom_file.tellg();
 | ||
| 
 | ||
|         /* Read ROM to buffer. */
 | ||
|         rom.resize(rom_size);
 | ||
|         rom_file.seekg(0, std::ios::beg);
 | ||
|         rom_file.read(&rom[0], rom_size);
 | ||
| 
 | ||
|         return rom;
 | ||
|     }();
 | ||
| 
 | ||
|     auto ReadAbsolute = [&](u32 address) -> u32 {
 | ||
|         return *(u32 *)&rom[address - RomStartAddress];
 | ||
|     };
 | ||
| 
 | ||
|     for (auto &language : tables) {
 | ||
|         /* Get category table start. */
 | ||
|         const std::size_t table_start = language.address;
 | ||
| 
 | ||
|         /* Get category count. */
 | ||
|         const std::size_t category_count = ReadAbsolute(table_start) / sizeof(u32);
 | ||
| 
 | ||
|         /* Read category offsets. */
 | ||
|         std::vector<u32> category_table(category_count);
 | ||
|         std::memcpy(&category_table[0], &rom[table_start - RomStartAddress], category_count * sizeof(u32));
 | ||
| 
 | ||
|         json j = json::array();
 | ||
| 
 | ||
|         for (auto &category_offset : category_table) {
 | ||
|             /* Get string table start. */
 | ||
|             const std::size_t category_start = table_start + category_offset;
 | ||
| 
 | ||
|             /* Get string count. */
 | ||
|             const std::size_t string_count = ReadAbsolute(category_start) / sizeof(u32);
 | ||
| 
 | ||
|             /* Read string offsets. */
 | ||
|             std::vector<u32> string_table(string_count);
 | ||
|             std::memcpy(&string_table[0], &rom[category_start - RomStartAddress], string_count * sizeof(u32));
 | ||
| 
 | ||
|             auto &category = j.emplace_back();
 | ||
| 
 | ||
|             for (std::size_t l = 0; l < string_count; l++) {
 | ||
|                 /* Get string start. */
 | ||
|                 const std::size_t string_start = category_start + string_table[l];
 | ||
| 
 | ||
|                 /* Parse TMC. */
 | ||
|                 category[l] = ParseTMCString(&rom[string_start - RomStartAddress]);
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         const std::string out_path = language.name + ".json"s;
 | ||
|         std::ofstream ofs(out_path);
 | ||
|         ofs << j.dump(4);
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| void PackStringTable(const std::string &src_path, const std::string &dst_path, const std::size_t out_size) {
 | ||
|     const json j = [&]() -> json {
 | ||
|         std::ifstream ifs(src_path);
 | ||
| 
 | ||
|         if (!ifs.good()) {
 | ||
|             fmt::print(stderr, "Couldn't open JSON {}\n", src_path);
 | ||
|             exit(EXIT_FAILURE);
 | ||
|         }
 | ||
| 
 | ||
|         return json::parse(ifs);
 | ||
|     }();
 | ||
| 
 | ||
|     std::vector<char> buffer(0x100000);
 | ||
|     std::uintptr_t root_start = (std::uintptr_t)buffer.data();
 | ||
| 
 | ||
|     char *root_ptr = buffer.data();
 | ||
|     char *table    = buffer.data() + j.size() * sizeof(u32);
 | ||
| 
 | ||
|     for (auto &category : j) {
 | ||
|         char *table_ptr = table;
 | ||
|         char *str_start = table_ptr + category.size() * sizeof(u32);
 | ||
|         char *str_ptr   = str_start;
 | ||
|         for (auto &str_j : category) {
 | ||
|             /* Write string offset to table. */
 | ||
|             *(u32 *)table_ptr = (std::uintptr_t)str_ptr - (std::uintptr_t)table;
 | ||
|             table_ptr += sizeof(u32);
 | ||
| 
 | ||
|             auto str = str_j.get<std::string>();
 | ||
| 
 | ||
|             /* Copy string to table. */
 | ||
|             WriteTMCString(str_ptr, str);
 | ||
|         }
 | ||
| 
 | ||
|         /* Align string table size by 16 bytes. */
 | ||
|         while (static_cast<std::uintptr_t>(str_ptr - str_start) & 0xf) {
 | ||
|             *str_ptr = 0xff;
 | ||
|             str_ptr++;
 | ||
|         }
 | ||
| 
 | ||
|         /* Write category offset to root table. */
 | ||
|         *(u32 *)root_ptr = (std::uintptr_t)table - root_start;
 | ||
|         root_ptr += sizeof(u32);
 | ||
| 
 | ||
|         table = str_ptr;
 | ||
|     }
 | ||
| 
 | ||
|     /* Align table end to 0x10 bytes. */
 | ||
|     while ((std::uintptr_t)table & 0xf) {
 | ||
|         *table++ = 0xff;
 | ||
|     }
 | ||
| 
 | ||
|     std::size_t table_size = (uintptr_t)table - root_start;
 | ||
| 
 | ||
|     if (out_size) {
 | ||
|         /* Abort if string table is too big. */
 | ||
|         if (table_size > out_size) {
 | ||
|             fmt::print(stderr, "Table is too big. Should be {} but was {}", out_size, table_size);
 | ||
|             exit(EXIT_FAILURE);
 | ||
|         }
 | ||
| 
 | ||
|         /* Pad table if it's too small. */
 | ||
|         while (table_size < out_size) {
 | ||
|             *table++ = 0xff;
 | ||
|             table_size++;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     std::ofstream ofs(dst_path);
 | ||
|     ofs.write(buffer.data(), table_size);
 | ||
| }
 | ||
| 
 | ||
| // clang-format off
 | ||
| const std::vector<LanguageTable> LanguageTableUS = {
 | ||
|     {"USA",      0x89B1D90},
 | ||
| };
 | ||
| 
 | ||
| const std::vector<LanguageTable> LanguageTableEU = {
 | ||
|     {"English",  0x89AEB60},
 | ||
|     {"French",   0x89F7420},
 | ||
|     {"German",   0x8A3EEB0},
 | ||
|     {"Spanish",  0x8A81E70},
 | ||
|     {"Italian",  0x8AC37A0},
 | ||
| };
 | ||
| // clang-format on
 | ||
| 
 | ||
| #include <getopt.h>
 | ||
| 
 | ||
| const char *progname;
 | ||
| 
 | ||
| void usage() {
 | ||
|     fmt::print(stderr,
 | ||
|                "tmc_strings (c) Luis Scheurenbrand\n"
 | ||
|                "Built: " __TIME__ " " __DATE__ "\n"
 | ||
|                "\n"
 | ||
|                "Usage: {} [options...]\n"
 | ||
|                "Options:\n"
 | ||
|                "  -x, --extract     Extract string table from ROM and store it in json format. (Default)\n"
 | ||
|                "  -p, --pack        Pack a string table from json format.\n"
 | ||
|                "  --region          Specify ROM region. [USA, EU]\n"
 | ||
|                "  --source          Specify source (-x: ROM, -p: JSON)\n"
 | ||
|                "  --dest            Specify string table destination.\n"
 | ||
|                "  --size            Specify string table size.\n",
 | ||
|                progname);
 | ||
| }
 | ||
| 
 | ||
| // clang-format off
 | ||
| constexpr const struct option long_options[] = {
 | ||
|     {"extract", no_argument,        nullptr, 'x'},
 | ||
|     {"pack",    no_argument,        nullptr, 'p'},
 | ||
|     {"region",  required_argument,  nullptr, 0},
 | ||
|     {"source",  required_argument,  nullptr, 1},
 | ||
|     {"dest",    required_argument,  nullptr, 2},
 | ||
|     {"size",    required_argument,  nullptr, 3},
 | ||
|     {},
 | ||
| };
 | ||
| // clang-format on
 | ||
| 
 | ||
| int main(int argc, char **argv) {
 | ||
|     std::string src_path;
 | ||
|     std::string dst_path;
 | ||
|     std::size_t max_size = 0;
 | ||
| 
 | ||
|     progname = (argc < 1) ? "tmc_strings" : argv[0];
 | ||
| 
 | ||
|     enum {
 | ||
|         Mode_Extract,
 | ||
|         Mode_Pack,
 | ||
|     } mode = Mode_Extract;
 | ||
| 
 | ||
|     enum {
 | ||
|         Region_USA,
 | ||
|         Region_JAPAN,
 | ||
|         Region_EU,
 | ||
|     } region = Region_USA;
 | ||
| 
 | ||
|     while (true) {
 | ||
|         int opt_index;
 | ||
|         int c = getopt_long(argc, argv, "xp", long_options, &opt_index);
 | ||
|         if (c == -1)
 | ||
|             break;
 | ||
| 
 | ||
|         switch (c) {
 | ||
|             case 'x':
 | ||
|                 mode = Mode_Extract;
 | ||
|                 break;
 | ||
|             case 'p':
 | ||
|                 mode = Mode_Pack;
 | ||
|                 break;
 | ||
|             case 0:
 | ||
|                 if (strcmp(optarg, "USA") == 0) {
 | ||
|                     region = Region_USA;
 | ||
|                 } else if (strcmp(optarg, "EU") == 0) {
 | ||
|                     region = Region_EU;
 | ||
|                 } else if (strcmp(optarg, "JAPAN") == 0) {
 | ||
|                     region = Region_JAPAN;
 | ||
|                     fmt::print("Region unsupported\n\n");
 | ||
|                     usage();
 | ||
|                     exit(EXIT_FAILURE);
 | ||
|                 }
 | ||
|                 break;
 | ||
|             case 1:
 | ||
|                 src_path = optarg;
 | ||
|                 break;
 | ||
|             case 2:
 | ||
|                 dst_path = optarg;
 | ||
|                 break;
 | ||
|             case 3:
 | ||
|                 max_size = std::strtoul(optarg, nullptr, 0);
 | ||
|                 break;
 | ||
|             default:
 | ||
|                 usage();
 | ||
|                 exit(EXIT_FAILURE);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     switch (mode) {
 | ||
|         case Mode_Extract:
 | ||
|             if (src_path == "") {
 | ||
|                 fmt::print("ROM source path not supplied.\n\n");
 | ||
|                 usage();
 | ||
|                 exit(EXIT_FAILURE);
 | ||
|             }
 | ||
|             ExtractStringTable(src_path, [region]() {
 | ||
|                 switch (region) {
 | ||
|                     case Region_USA:
 | ||
|                         return LanguageTableUS;
 | ||
|                     case Region_EU:
 | ||
|                         return LanguageTableEU;
 | ||
|                     default:
 | ||
|                         fmt::print("Region unsupported\n\n");
 | ||
|                         usage();
 | ||
|                         exit(EXIT_FAILURE);
 | ||
|                 }
 | ||
|             }());
 | ||
|             break;
 | ||
|         case Mode_Pack:
 | ||
|             if (src_path == "" || dst_path == "") {
 | ||
|                 fmt::print("Source or destination path not supplied\n\n");
 | ||
|                 usage();
 | ||
|                 exit(EXIT_FAILURE);
 | ||
|             }
 | ||
|             if (max_size == 0) {
 | ||
|                 fmt::print("Max size not supplied. Assuming shiftable.\n");
 | ||
|             }
 | ||
|             PackStringTable(src_path, dst_path, max_size);
 | ||
|     }
 | ||
| 
 | ||
|     return EXIT_SUCCESS;
 | ||
| }
 |