From 9acb542689830b892585bc5811b540453cc16365 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Tue, 27 Jun 2023 00:27:04 -0700 Subject: [PATCH] make quest disassemble robust against invalid inputs --- src/QuestScript.cc | 267 +++++++++++++++++++++++++-------------------- 1 file changed, 146 insertions(+), 121 deletions(-) diff --git a/src/QuestScript.cc b/src/QuestScript.cc index 8e59d04e..64da146c 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -12,6 +12,19 @@ using namespace std; +static string dasm_u16string(const char16_t* data, size_t size) { + try { + return format_data_string(encode_sjis(data, size)); + } catch (const runtime_error& e) { + return "/* undecodable */ " + format_data_string(data, size * sizeof(char16_t)); + } +} + +template +static string dasm_u16string(const parray& data) { + return dasm_u16string(data.data(), data.size()); +} + struct QuestScriptOpcodeDefinition { struct Argument { enum class Type { @@ -30,7 +43,7 @@ struct QuestScriptOpcodeDefinition { }; Type type; - size_t count = 0; + size_t count; const char* name; Argument(Type type, size_t count = 0, const char* name = nullptr) @@ -47,12 +60,17 @@ struct QuestScriptOpcodeDefinition { NUM_VERSIONS, }; + enum Flag { + PRESERVE_ARG_STACK = 0x01, + }; + uint16_t opcode; const char* name; std::vector imm_args; std::vector stack_args; Version first_version; Version last_version; + uint8_t flags; QuestScriptOpcodeDefinition( uint16_t opcode, @@ -60,13 +78,15 @@ struct QuestScriptOpcodeDefinition { std::vector imm_args, std::vector stack_args, Version first_version, - Version last_version) + Version last_version, + uint8_t flags = 0) : opcode(opcode), name(name), imm_args(imm_args), stack_args(stack_args), first_version(first_version), - last_version(last_version) {} + last_version(last_version), + flags(flags) {} }; static constexpr auto V1 = QuestScriptOpcodeDefinition::Version::V1; @@ -86,6 +106,7 @@ static constexpr auto INT16 = QuestScriptOpcodeDefinition::Argument::Type::INT16 static constexpr auto INT32 = QuestScriptOpcodeDefinition::Argument::Type::INT32; static constexpr auto FLOAT32 = QuestScriptOpcodeDefinition::Argument::Type::FLOAT32; static constexpr auto CSTRING = QuestScriptOpcodeDefinition::Argument::Type::CSTRING; +static constexpr uint8_t PRESERVE_ARG_STACK = QuestScriptOpcodeDefinition::Flag::PRESERVE_ARG_STACK; static const QuestScriptOpcodeDefinition::Argument CLIENT_ID(INT32, 0, "client_id"); static const QuestScriptOpcodeDefinition::Argument ITEM_ID(INT32, 0, "item_id"); @@ -162,13 +183,13 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { {0x0043, "stack_pop", {REG}, {}, V3, V4}, {0x0044, "stack_pushm", {REG, INT32}, {}, V3, V4}, {0x0045, "stack_popm", {REG, INT32}, {}, V3, V4}, - {0x0048, "arg_pushr", {REG}, {}, V3, V4}, - {0x0049, "arg_pushl", {INT32}, {}, V3, V4}, - {0x004A, "arg_pushb", {INT8}, {}, V3, V4}, - {0x004B, "arg_pushw", {INT16}, {}, V3, V4}, - {0x004C, "arg_pusha", {REG}, {}, V3, V4}, - {0x004D, "arg_pusho", {FUNCTION16}, {}, V3, V4}, - {0x004E, "arg_pushs", {CSTRING}, {}, V3, V4}, + {0x0048, "arg_pushr", {REG}, {}, V3, V4, PRESERVE_ARG_STACK}, + {0x0049, "arg_pushl", {INT32}, {}, V3, V4, PRESERVE_ARG_STACK}, + {0x004A, "arg_pushb", {INT8}, {}, V3, V4, PRESERVE_ARG_STACK}, + {0x004B, "arg_pushw", {INT16}, {}, V3, V4, PRESERVE_ARG_STACK}, + {0x004C, "arg_pusha", {REG}, {}, V3, V4, PRESERVE_ARG_STACK}, + {0x004D, "arg_pusho", {FUNCTION16}, {}, V3, V4, PRESERVE_ARG_STACK}, + {0x004E, "arg_pushs", {CSTRING}, {}, V3, V4, PRESERVE_ARG_STACK}, {0x0050, "message", {INT32, CSTRING}, {}, V1, V2}, {0x0050, "message", {}, {INT32, CSTRING}, V3, V4}, {0x0051, "list", {REG, CSTRING}, {}, V1, V2}, @@ -450,16 +471,16 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { {0xF83E, "delete_area_title", {}, {INT32}, V3, V4}, {0xF840, "load_npc_data", {}, {}, V2, V4}, {0xF841, "get_npc_data", {DATA_LABEL}, {}, V2, V4}, - {0xF848, "give_damage_score", {REG}, {}, V2, V4}, - {0xF849, "take_damage_score", {REG}, {}, V2, V4}, - {0xF84A, nullptr, {REG}, {}, V2, V4}, - {0xF84B, nullptr, {REG}, {}, V2, V4}, - {0xF84C, "kill_score", {REG}, {}, V2, V4}, - {0xF84D, "death_score", {REG}, {}, V2, V4}, - {0xF84E, nullptr, {REG}, {}, V2, V4}, - {0xF84F, "enemy_death_score", {REG}, {}, V2, V4}, - {0xF850, "meseta_score", {REG}, {}, V2, V4}, - {0xF851, nullptr, {REG}, {}, V2, V4}, + {0xF848, "give_damage_score", {{REG_SET_FIXED, 3}}, {}, V2, V4}, + {0xF849, "take_damage_score", {{REG_SET_FIXED, 3}}, {}, V2, V4}, + {0xF84A, nullptr, {{REG_SET_FIXED, 3}}, {}, V2, V4}, + {0xF84B, nullptr, {{REG_SET_FIXED, 3}}, {}, V2, V4}, + {0xF84C, "kill_score", {{REG_SET_FIXED, 3}}, {}, V2, V4}, + {0xF84D, "death_score", {{REG_SET_FIXED, 3}}, {}, V2, V4}, + {0xF84E, nullptr, {{REG_SET_FIXED, 3}}, {}, V2, V4}, + {0xF84F, "enemy_death_score", {{REG_SET_FIXED, 3}}, {}, V2, V4}, + {0xF850, "meseta_score", {{REG_SET_FIXED, 3}}, {}, V2, V4}, + {0xF851, nullptr, {{REG_SET_FIXED, 2}}, {}, V2, V4}, {0xF852, nullptr, {INT32}, {}, V2, V2}, {0xF852, nullptr, {}, {INT32}, V3, V4}, {0xF853, "reverse_warps", {}, {}, V2, V4}, @@ -522,7 +543,7 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { {0xF87D, nullptr, {REG}, {}, V2, V4}, {0xF87E, nullptr, {REG}, {}, V2, V4}, {0xF87F, "read_guildcard_flag", {REG, REG}, {}, V2, V4}, - {0xF880, nullptr, {REG}, {}, V2, V4}, + {0xF880, nullptr, {{REG_SET_FIXED, 3}}, {}, V2, V4}, {0xF881, "get_pl_name", {REG}, {}, V2, V4}, {0xF882, "get_pl_job", {REG}, {}, V2, V4}, {0xF883, nullptr, {REG, REG}, {}, V2, V4}, @@ -804,9 +825,9 @@ std::string disassemble_quest_script(const void* data, size_t size, GameVersion if (header.is_download) { lines.emplace_back(string_printf(".is_download_quest")); } - lines.emplace_back(".name " + format_data_string(encode_sjis(header.name))); - lines.emplace_back(".short_desc " + format_data_string(encode_sjis(header.short_description))); - lines.emplace_back(".long_desc " + format_data_string(encode_sjis(header.long_description))); + lines.emplace_back(".name " + dasm_u16string(header.name)); + lines.emplace_back(".short_desc " + dasm_u16string(header.short_description)); + lines.emplace_back(".long_desc " + dasm_u16string(header.long_description)); break; } case GameVersion::GC: @@ -837,9 +858,9 @@ std::string disassemble_quest_script(const void* data, size_t size, GameVersion if (header.joinable_in_progress) { lines.emplace_back(".joinable_in_progress"); } - lines.emplace_back(".name " + format_data_string(encode_sjis(header.name))); - lines.emplace_back(".short_desc " + format_data_string(encode_sjis(header.short_description))); - lines.emplace_back(".long_desc " + format_data_string(encode_sjis(header.long_description))); + lines.emplace_back(".name " + dasm_u16string(header.name)); + lines.emplace_back(".short_desc " + dasm_u16string(header.short_description)); + lines.emplace_back(".long_desc " + dasm_u16string(header.long_description)); break; } default: @@ -851,7 +872,11 @@ std::string disassemble_quest_script(const void* data, size_t size, GameVersion vector function_table; StringReader function_table_r = r.sub(function_table_offset); while (!function_table_r.eof()) { - function_table.emplace_back(function_table_r.get_u32l()); + try { + function_table.emplace_back(function_table_r.get_u32l()); + } catch (const out_of_range&) { + function_table_r.skip(function_table_r.remaining()); + } } map dasm_lines; @@ -862,110 +887,110 @@ std::string disassemble_quest_script(const void* data, size_t size, GameVersion StringReader cmd_r = r.sub(code_offset, function_table_offset - code_offset); while (!cmd_r.eof()) { size_t opcode_start_offset = cmd_r.where(); - - uint16_t opcode = cmd_r.get_u8(); - if ((opcode & 0xFE) == 0xF8) { - opcode = (opcode << 8) | cmd_r.get_u8(); - } - - const QuestScriptOpcodeDefinition* def = nullptr; - try { - def = opcodes.at(opcode); - } catch (const out_of_range&) { - } - string dasm_line; - if (def == nullptr) { - dasm_line = string_printf(".unknown %04hX", opcode); - } else { - dasm_line = def->name ? def->name : string_printf("[%04hX]", opcode); - if (!def->imm_args.empty()) { - dasm_line.resize(0x20, ' '); - bool is_first_arg = true; - for (const auto& arg : def->imm_args) { - using Type = QuestScriptOpcodeDefinition::Argument::Type; - string dasm_arg; - switch (arg.type) { - case Type::FUNCTION16: - case Type::FUNCTION32: { - uint32_t function_id = (arg.type == Type::FUNCTION32) ? cmd_r.get_u32l() : cmd_r.get_u16l(); - if (function_id >= function_table.size()) { - dasm_arg = string_printf("function%04" PRIX32 " /* invalid */", function_id); - } else { - uint32_t label_offset = function_table.at(function_id); - dasm_arg = string_printf("function%04" PRIX32 " /* %04" PRIX32 " */", function_id, label_offset); - } - break; - } - case Type::FUNCTION16_SET: { - uint8_t num_functions = cmd_r.get_u8(); - for (size_t z = 0; z < num_functions; z++) { - dasm_arg += (dasm_arg.empty() ? "(" : ","); - uint32_t function_id = cmd_r.get_u16l(); + try { + uint16_t opcode = cmd_r.get_u8(); + if ((opcode & 0xFE) == 0xF8) { + opcode = (opcode << 8) | cmd_r.get_u8(); + } + + const QuestScriptOpcodeDefinition* def = nullptr; + try { + def = opcodes.at(opcode); + } catch (const out_of_range&) { + } + + if (def == nullptr) { + dasm_line = string_printf(".unknown %04hX", opcode); + } else { + dasm_line = def->name ? def->name : string_printf("[%04hX]", opcode); + if (!def->imm_args.empty()) { + dasm_line.resize(0x20, ' '); + bool is_first_arg = true; + for (const auto& arg : def->imm_args) { + using Type = QuestScriptOpcodeDefinition::Argument::Type; + string dasm_arg; + switch (arg.type) { + case Type::FUNCTION16: + case Type::FUNCTION32: + case Type::DATA_LABEL: { + uint32_t function_id = (arg.type == Type::FUNCTION32) ? cmd_r.get_u32l() : cmd_r.get_u16l(); if (function_id >= function_table.size()) { - dasm_arg += string_printf("function%04" PRIX32 " /* invalid */", function_id); + dasm_arg = string_printf("function%04" PRIX32 " /* invalid */", function_id); } else { uint32_t label_offset = function_table.at(function_id); - dasm_arg += string_printf("function%04" PRIX32 " /* %04" PRIX32 " */", function_id, label_offset); + dasm_arg = string_printf("function%04" PRIX32 " /* %04" PRIX32 " */", function_id, label_offset); } + break; } - dasm_arg += ")"; - break; - } - case Type::REG: - dasm_arg = string_printf("r%hhu", cmd_r.get_u8()); - break; - case Type::REG_SET: { - uint8_t num_regs = cmd_r.get_u8(); - for (size_t z = 0; z < num_regs; z++) { - dasm_arg += string_printf("%cr%hhu", (dasm_arg.empty() ? '(' : ','), cmd_r.get_u8()); - } - dasm_arg += ")"; - break; - } - case Type::REG_SET_FIXED: { - uint8_t first_reg = cmd_r.get_u8(); - dasm_arg = string_printf("r%hhu-r%hhu", first_reg, static_cast(first_reg + arg.count - 1)); - break; - } - case Type::INT8: - dasm_arg = string_printf("0x%02hhX", cmd_r.get_u8()); - break; - case Type::INT16: - dasm_arg = string_printf("0x%04hX", cmd_r.get_u16l()); - break; - case Type::INT32: - dasm_arg = string_printf("0x%08" PRIX32, cmd_r.get_u32l()); - break; - case Type::FLOAT32: - dasm_arg = string_printf("%g", cmd_r.get_f32l()); - break; - case Type::CSTRING: - if (use_wstrs) { - u16string s; - for (char16_t ch = cmd_r.get_u16l(); ch; ch = cmd_r.get_u16l()) { - s.push_back(ch); + case Type::FUNCTION16_SET: { + uint8_t num_functions = cmd_r.get_u8(); + for (size_t z = 0; z < num_functions; z++) { + dasm_arg += (dasm_arg.empty() ? "(" : ","); + uint32_t function_id = cmd_r.get_u16l(); + if (function_id >= function_table.size()) { + dasm_arg += string_printf("function%04" PRIX32 " /* invalid */", function_id); + } else { + uint32_t label_offset = function_table.at(function_id); + dasm_arg += string_printf("function%04" PRIX32 " /* %04" PRIX32 " */", function_id, label_offset); + } } - try { - dasm_arg = format_data_string(encode_sjis(s)); - } catch (const runtime_error& e) { - dasm_arg = "/* undecodable */ " + format_data_string(s.data(), s.size() * sizeof(char16_t)); - } - } else { - dasm_arg = format_data_string(cmd_r.get_cstr()); + dasm_arg += ")"; + break; } - break; - default: - throw logic_error("invalid argument type"); + case Type::REG: + dasm_arg = string_printf("r%hhu", cmd_r.get_u8()); + break; + case Type::REG_SET: { + uint8_t num_regs = cmd_r.get_u8(); + for (size_t z = 0; z < num_regs; z++) { + dasm_arg += string_printf("%cr%hhu", (dasm_arg.empty() ? '(' : ','), cmd_r.get_u8()); + } + dasm_arg += ")"; + break; + } + case Type::REG_SET_FIXED: { + uint8_t first_reg = cmd_r.get_u8(); + dasm_arg = string_printf("r%hhu-r%hhu", first_reg, static_cast(first_reg + arg.count - 1)); + break; + } + case Type::INT8: + dasm_arg = string_printf("0x%02hhX", cmd_r.get_u8()); + break; + case Type::INT16: + dasm_arg = string_printf("0x%04hX", cmd_r.get_u16l()); + break; + case Type::INT32: + dasm_arg = string_printf("0x%08" PRIX32, cmd_r.get_u32l()); + break; + case Type::FLOAT32: + dasm_arg = string_printf("%g", cmd_r.get_f32l()); + break; + case Type::CSTRING: + if (use_wstrs) { + u16string s; + for (char16_t ch = cmd_r.get_u16l(); ch; ch = cmd_r.get_u16l()) { + s.push_back(ch); + } + dasm_arg = dasm_u16string(s.data(), s.size()); + } else { + dasm_arg = format_data_string(cmd_r.get_cstr()); + } + break; + default: + throw logic_error("invalid argument type"); + } + if (!is_first_arg) { + dasm_line += ", "; + } else { + is_first_arg = false; + } + dasm_line += dasm_arg; } - if (!is_first_arg) { - dasm_line += ", "; - } else { - is_first_arg = false; - } - dasm_line += dasm_arg; } } + } catch (const exception& e) { + dasm_line = string_printf(".failed (%s)", e.what()); } string hex_data = format_data_string(cmd_r.preadx(opcode_start_offset, cmd_r.where() - opcode_start_offset), nullptr, FormatDataFlags::HEX_ONLY);