make quest disassemble robust against invalid inputs

This commit is contained in:
Martin Michelsen
2023-06-27 00:27:04 -07:00
parent af56b6d2c6
commit 9acb542689
+60 -35
View File
@@ -12,6 +12,19 @@
using namespace std; 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 <size_t Size>
static string dasm_u16string(const parray<char16_t, Size>& data) {
return dasm_u16string(data.data(), data.size());
}
struct QuestScriptOpcodeDefinition { struct QuestScriptOpcodeDefinition {
struct Argument { struct Argument {
enum class Type { enum class Type {
@@ -30,7 +43,7 @@ struct QuestScriptOpcodeDefinition {
}; };
Type type; Type type;
size_t count = 0; size_t count;
const char* name; const char* name;
Argument(Type type, size_t count = 0, const char* name = nullptr) Argument(Type type, size_t count = 0, const char* name = nullptr)
@@ -47,12 +60,17 @@ struct QuestScriptOpcodeDefinition {
NUM_VERSIONS, NUM_VERSIONS,
}; };
enum Flag {
PRESERVE_ARG_STACK = 0x01,
};
uint16_t opcode; uint16_t opcode;
const char* name; const char* name;
std::vector<Argument> imm_args; std::vector<Argument> imm_args;
std::vector<Argument> stack_args; std::vector<Argument> stack_args;
Version first_version; Version first_version;
Version last_version; Version last_version;
uint8_t flags;
QuestScriptOpcodeDefinition( QuestScriptOpcodeDefinition(
uint16_t opcode, uint16_t opcode,
@@ -60,13 +78,15 @@ struct QuestScriptOpcodeDefinition {
std::vector<Argument> imm_args, std::vector<Argument> imm_args,
std::vector<Argument> stack_args, std::vector<Argument> stack_args,
Version first_version, Version first_version,
Version last_version) Version last_version,
uint8_t flags = 0)
: opcode(opcode), : opcode(opcode),
name(name), name(name),
imm_args(imm_args), imm_args(imm_args),
stack_args(stack_args), stack_args(stack_args),
first_version(first_version), first_version(first_version),
last_version(last_version) {} last_version(last_version),
flags(flags) {}
}; };
static constexpr auto V1 = QuestScriptOpcodeDefinition::Version::V1; 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 INT32 = QuestScriptOpcodeDefinition::Argument::Type::INT32;
static constexpr auto FLOAT32 = QuestScriptOpcodeDefinition::Argument::Type::FLOAT32; static constexpr auto FLOAT32 = QuestScriptOpcodeDefinition::Argument::Type::FLOAT32;
static constexpr auto CSTRING = QuestScriptOpcodeDefinition::Argument::Type::CSTRING; 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 CLIENT_ID(INT32, 0, "client_id");
static const QuestScriptOpcodeDefinition::Argument ITEM_ID(INT32, 0, "item_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}, {0x0043, "stack_pop", {REG}, {}, V3, V4},
{0x0044, "stack_pushm", {REG, INT32}, {}, V3, V4}, {0x0044, "stack_pushm", {REG, INT32}, {}, V3, V4},
{0x0045, "stack_popm", {REG, INT32}, {}, V3, V4}, {0x0045, "stack_popm", {REG, INT32}, {}, V3, V4},
{0x0048, "arg_pushr", {REG}, {}, V3, V4}, {0x0048, "arg_pushr", {REG}, {}, V3, V4, PRESERVE_ARG_STACK},
{0x0049, "arg_pushl", {INT32}, {}, V3, V4}, {0x0049, "arg_pushl", {INT32}, {}, V3, V4, PRESERVE_ARG_STACK},
{0x004A, "arg_pushb", {INT8}, {}, V3, V4}, {0x004A, "arg_pushb", {INT8}, {}, V3, V4, PRESERVE_ARG_STACK},
{0x004B, "arg_pushw", {INT16}, {}, V3, V4}, {0x004B, "arg_pushw", {INT16}, {}, V3, V4, PRESERVE_ARG_STACK},
{0x004C, "arg_pusha", {REG}, {}, V3, V4}, {0x004C, "arg_pusha", {REG}, {}, V3, V4, PRESERVE_ARG_STACK},
{0x004D, "arg_pusho", {FUNCTION16}, {}, V3, V4}, {0x004D, "arg_pusho", {FUNCTION16}, {}, V3, V4, PRESERVE_ARG_STACK},
{0x004E, "arg_pushs", {CSTRING}, {}, V3, V4}, {0x004E, "arg_pushs", {CSTRING}, {}, V3, V4, PRESERVE_ARG_STACK},
{0x0050, "message", {INT32, CSTRING}, {}, V1, V2}, {0x0050, "message", {INT32, CSTRING}, {}, V1, V2},
{0x0050, "message", {}, {INT32, CSTRING}, V3, V4}, {0x0050, "message", {}, {INT32, CSTRING}, V3, V4},
{0x0051, "list", {REG, CSTRING}, {}, V1, V2}, {0x0051, "list", {REG, CSTRING}, {}, V1, V2},
@@ -450,16 +471,16 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = {
{0xF83E, "delete_area_title", {}, {INT32}, V3, V4}, {0xF83E, "delete_area_title", {}, {INT32}, V3, V4},
{0xF840, "load_npc_data", {}, {}, V2, V4}, {0xF840, "load_npc_data", {}, {}, V2, V4},
{0xF841, "get_npc_data", {DATA_LABEL}, {}, V2, V4}, {0xF841, "get_npc_data", {DATA_LABEL}, {}, V2, V4},
{0xF848, "give_damage_score", {REG}, {}, V2, V4}, {0xF848, "give_damage_score", {{REG_SET_FIXED, 3}}, {}, V2, V4},
{0xF849, "take_damage_score", {REG}, {}, V2, V4}, {0xF849, "take_damage_score", {{REG_SET_FIXED, 3}}, {}, V2, V4},
{0xF84A, nullptr, {REG}, {}, V2, V4}, {0xF84A, nullptr, {{REG_SET_FIXED, 3}}, {}, V2, V4},
{0xF84B, nullptr, {REG}, {}, V2, V4}, {0xF84B, nullptr, {{REG_SET_FIXED, 3}}, {}, V2, V4},
{0xF84C, "kill_score", {REG}, {}, V2, V4}, {0xF84C, "kill_score", {{REG_SET_FIXED, 3}}, {}, V2, V4},
{0xF84D, "death_score", {REG}, {}, V2, V4}, {0xF84D, "death_score", {{REG_SET_FIXED, 3}}, {}, V2, V4},
{0xF84E, nullptr, {REG}, {}, V2, V4}, {0xF84E, nullptr, {{REG_SET_FIXED, 3}}, {}, V2, V4},
{0xF84F, "enemy_death_score", {REG}, {}, V2, V4}, {0xF84F, "enemy_death_score", {{REG_SET_FIXED, 3}}, {}, V2, V4},
{0xF850, "meseta_score", {REG}, {}, V2, V4}, {0xF850, "meseta_score", {{REG_SET_FIXED, 3}}, {}, V2, V4},
{0xF851, nullptr, {REG}, {}, V2, V4}, {0xF851, nullptr, {{REG_SET_FIXED, 2}}, {}, V2, V4},
{0xF852, nullptr, {INT32}, {}, V2, V2}, {0xF852, nullptr, {INT32}, {}, V2, V2},
{0xF852, nullptr, {}, {INT32}, V3, V4}, {0xF852, nullptr, {}, {INT32}, V3, V4},
{0xF853, "reverse_warps", {}, {}, V2, V4}, {0xF853, "reverse_warps", {}, {}, V2, V4},
@@ -522,7 +543,7 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = {
{0xF87D, nullptr, {REG}, {}, V2, V4}, {0xF87D, nullptr, {REG}, {}, V2, V4},
{0xF87E, nullptr, {REG}, {}, V2, V4}, {0xF87E, nullptr, {REG}, {}, V2, V4},
{0xF87F, "read_guildcard_flag", {REG, 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}, {0xF881, "get_pl_name", {REG}, {}, V2, V4},
{0xF882, "get_pl_job", {REG}, {}, V2, V4}, {0xF882, "get_pl_job", {REG}, {}, V2, V4},
{0xF883, nullptr, {REG, 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) { if (header.is_download) {
lines.emplace_back(string_printf(".is_download_quest")); lines.emplace_back(string_printf(".is_download_quest"));
} }
lines.emplace_back(".name " + format_data_string(encode_sjis(header.name))); lines.emplace_back(".name " + dasm_u16string(header.name));
lines.emplace_back(".short_desc " + format_data_string(encode_sjis(header.short_description))); lines.emplace_back(".short_desc " + dasm_u16string(header.short_description));
lines.emplace_back(".long_desc " + format_data_string(encode_sjis(header.long_description))); lines.emplace_back(".long_desc " + dasm_u16string(header.long_description));
break; break;
} }
case GameVersion::GC: case GameVersion::GC:
@@ -837,9 +858,9 @@ std::string disassemble_quest_script(const void* data, size_t size, GameVersion
if (header.joinable_in_progress) { if (header.joinable_in_progress) {
lines.emplace_back(".joinable_in_progress"); lines.emplace_back(".joinable_in_progress");
} }
lines.emplace_back(".name " + format_data_string(encode_sjis(header.name))); lines.emplace_back(".name " + dasm_u16string(header.name));
lines.emplace_back(".short_desc " + format_data_string(encode_sjis(header.short_description))); lines.emplace_back(".short_desc " + dasm_u16string(header.short_description));
lines.emplace_back(".long_desc " + format_data_string(encode_sjis(header.long_description))); lines.emplace_back(".long_desc " + dasm_u16string(header.long_description));
break; break;
} }
default: default:
@@ -851,7 +872,11 @@ std::string disassemble_quest_script(const void* data, size_t size, GameVersion
vector<le_uint32_t> function_table; vector<le_uint32_t> function_table;
StringReader function_table_r = r.sub(function_table_offset); StringReader function_table_r = r.sub(function_table_offset);
while (!function_table_r.eof()) { while (!function_table_r.eof()) {
try {
function_table.emplace_back(function_table_r.get_u32l()); function_table.emplace_back(function_table_r.get_u32l());
} catch (const out_of_range&) {
function_table_r.skip(function_table_r.remaining());
}
} }
map<size_t, string> dasm_lines; map<size_t, string> dasm_lines;
@@ -862,7 +887,8 @@ std::string disassemble_quest_script(const void* data, size_t size, GameVersion
StringReader cmd_r = r.sub(code_offset, function_table_offset - code_offset); StringReader cmd_r = r.sub(code_offset, function_table_offset - code_offset);
while (!cmd_r.eof()) { while (!cmd_r.eof()) {
size_t opcode_start_offset = cmd_r.where(); size_t opcode_start_offset = cmd_r.where();
string dasm_line;
try {
uint16_t opcode = cmd_r.get_u8(); uint16_t opcode = cmd_r.get_u8();
if ((opcode & 0xFE) == 0xF8) { if ((opcode & 0xFE) == 0xF8) {
opcode = (opcode << 8) | cmd_r.get_u8(); opcode = (opcode << 8) | cmd_r.get_u8();
@@ -874,7 +900,6 @@ std::string disassemble_quest_script(const void* data, size_t size, GameVersion
} catch (const out_of_range&) { } catch (const out_of_range&) {
} }
string dasm_line;
if (def == nullptr) { if (def == nullptr) {
dasm_line = string_printf(".unknown %04hX", opcode); dasm_line = string_printf(".unknown %04hX", opcode);
} else { } else {
@@ -887,7 +912,8 @@ std::string disassemble_quest_script(const void* data, size_t size, GameVersion
string dasm_arg; string dasm_arg;
switch (arg.type) { switch (arg.type) {
case Type::FUNCTION16: case Type::FUNCTION16:
case Type::FUNCTION32: { case Type::FUNCTION32:
case Type::DATA_LABEL: {
uint32_t function_id = (arg.type == Type::FUNCTION32) ? cmd_r.get_u32l() : cmd_r.get_u16l(); uint32_t function_id = (arg.type == Type::FUNCTION32) ? cmd_r.get_u32l() : cmd_r.get_u16l();
if (function_id >= function_table.size()) { 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);
@@ -946,11 +972,7 @@ std::string disassemble_quest_script(const void* data, size_t size, GameVersion
for (char16_t ch = cmd_r.get_u16l(); ch; ch = cmd_r.get_u16l()) { for (char16_t ch = cmd_r.get_u16l(); ch; ch = cmd_r.get_u16l()) {
s.push_back(ch); s.push_back(ch);
} }
try { dasm_arg = dasm_u16string(s.data(), s.size());
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 { } else {
dasm_arg = format_data_string(cmd_r.get_cstr()); dasm_arg = format_data_string(cmd_r.get_cstr());
} }
@@ -967,6 +989,9 @@ std::string disassemble_quest_script(const void* data, size_t size, GameVersion
} }
} }
} }
} 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); string hex_data = format_data_string(cmd_r.preadx(opcode_start_offset, cmd_r.where() - opcode_start_offset), nullptr, FormatDataFlags::HEX_ONLY);
if (hex_data.size() > 14) { if (hex_data.size() > 14) {