From d9cdf9804f3bb0802edf8161f70a64895c29103e Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Mon, 11 Dec 2023 13:56:00 -0800 Subject: [PATCH] fix ... in F_ARGS opcode assembly --- src/QuestScript.cc | 420 +++++++++++++++++++++++---------------------- 1 file changed, 214 insertions(+), 206 deletions(-) diff --git a/src/QuestScript.cc b/src/QuestScript.cc index 9fd6d6c2..3af47413 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -1895,225 +1895,233 @@ std::string assemble_quest_script(const std::string& text) { if (line_tokens.size() < 2) { throw runtime_error(string_printf("(line %zu) arguments required for %s", line_num, opcode_def->name)); } - auto args = split_context(line_tokens[1], ','); - if (args.size() != opcode_def->args.size()) { - throw runtime_error(string_printf("(line %zu) incorrect argument count for %s", line_num, opcode_def->name)); - } + strip_trailing_whitespace(line_tokens[1]); + strip_leading_whitespace(line_tokens[1]); - for (size_t z = 0; z < args.size(); z++) { - using Type = QuestScriptOpcodeDefinition::Argument::Type; + if (starts_with(line_tokens[1], "...")) { + if (!(opcode_def->flags & F_ARGS)) { + throw runtime_error(string_printf("(line %zu) \'...\' can only be used with F_ARGS opcodes", line_num)); + } - string& arg = args[z]; - const auto& arg_def = opcode_def->args[z]; - strip_trailing_whitespace(arg); - strip_leading_whitespace(arg); + } else { // Not "..." + auto args = split_context(line_tokens[1], ','); + if (args.size() != opcode_def->args.size()) { + throw runtime_error(string_printf("(line %zu) incorrect argument count for %s", line_num, opcode_def->name)); + } - try { - auto parse_reg = +[](const string& name) -> uint8_t { - if ((name[0] != 'r') && (name[0] != 'f')) { - throw runtime_error("a register is required"); - } - size_t reg_num = stoull(name.substr(1), nullptr, 0); - if (reg_num > 0xFF) { - throw runtime_error("invalid register number"); - } - return reg_num; - }; - auto add_cstr = [&](const string& text) -> void { - switch (quest_version) { - case Version::DC_NTE: - code_w.write(tt_utf8_to_sjis(text)); - code_w.put_u8(0); - break; - case Version::DC_V1_11_2000_PROTOTYPE: - case Version::DC_V1: - case Version::DC_V2: - case Version::GC_NTE: - case Version::GC_V3: - case Version::GC_EP3_TRIAL_EDITION: - case Version::GC_EP3: - case Version::XB_V3: - code_w.write(quest_language ? tt_utf8_to_8859(text) : tt_utf8_to_sjis(text)); - code_w.put_u8(0); - break; - case Version::PC_V2: - case Version::BB_V4: - code_w.write(tt_utf8_to_utf16(text)); - code_w.put_u16(0); - break; - default: - throw logic_error("invalid game version"); - } - }; + for (size_t z = 0; z < args.size(); z++) { + using Type = QuestScriptOpcodeDefinition::Argument::Type; - if (opcode_def->flags & F_ARGS) { - auto label_it = labels_by_name.find(arg); - if (starts_with(line_tokens[1], "...")) { - // Args were specified by preceding arg_push calls; nothing to do here - } else if (arg.empty()) { - throw runtime_error("argument is empty"); - } else if (label_it != labels_by_name.end()) { - code_w.put_u8(0x4B); // arg_pushw - code_w.put_u16l(label_it->second->index); - } else if ((arg[0] == 'r') || (arg[0] == 'f')) { - // If the corresponding argument is a REG or REG_SET_FIXED, push - // the register number, not the register's value, since it's an - // out-param - if ((arg_def.type == Type::REG) || (arg_def.type == Type::REG32)) { - code_w.put_u8(0x4A); // arg_pushb - code_w.put_u8(parse_reg(arg)); - } else if ( - (arg_def.type == Type::REG_SET_FIXED) || - (arg_def.type == Type::REG32_SET_FIXED)) { - auto tokens = split(arg, '-'); - uint8_t start_reg; - if (tokens.size() == 1) { - start_reg = parse_reg(tokens[0]); - } else if (tokens.size() == 2) { - start_reg = parse_reg(tokens[0]); - if (static_cast(parse_reg(tokens[1]) - start_reg + 1) != arg_def.count) { - throw runtime_error("incorrect number of registers used"); - } - } else { - throw runtime_error("invalid fixed register set syntax"); - } - code_w.put_u8(0x4A); // arg_pushb - code_w.put_u8(start_reg); - } else { - code_w.put_u8(0x48); // arg_pushr - code_w.put_u8(parse_reg(arg)); + string& arg = args[z]; + const auto& arg_def = opcode_def->args[z]; + strip_trailing_whitespace(arg); + strip_leading_whitespace(arg); + + try { + auto parse_reg = +[](const string& name) -> uint8_t { + if ((name[0] != 'r') && (name[0] != 'f')) { + throw runtime_error("a register is required"); } - } else if ((arg[0] == '@') && ((arg[1] == 'r') || (arg[1] == 'f'))) { - code_w.put_u8(0x4C); // arg_pusha - code_w.put_u8(parse_reg(arg.substr(1))); - } else if ((arg[0] == '@') && labels_by_name.count(arg.substr(1))) { - code_w.put_u8(0x4D); // arg_pusho - code_w.put_u16(labels_by_name.at(arg.substr(1))->index); - } else { - bool write_as_str = false; - try { - size_t end_offset; - uint64_t value = stoll(arg, &end_offset, 0); - if (end_offset != arg.size()) { - write_as_str = true; - } else if (value > 0xFFFF) { - code_w.put_u8(0x49); // arg_pushl - code_w.put_u32l(value); - } else if (value > 0xFF) { - code_w.put_u8(0x4B); // arg_pushw - code_w.put_u16l(value); - } else { + size_t reg_num = stoull(name.substr(1), nullptr, 0); + if (reg_num > 0xFF) { + throw runtime_error("invalid register number"); + } + return reg_num; + }; + auto add_cstr = [&](const string& text) -> void { + switch (quest_version) { + case Version::DC_NTE: + code_w.write(tt_utf8_to_sjis(text)); + code_w.put_u8(0); + break; + case Version::DC_V1_11_2000_PROTOTYPE: + case Version::DC_V1: + case Version::DC_V2: + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_TRIAL_EDITION: + case Version::GC_EP3: + case Version::XB_V3: + code_w.write(quest_language ? tt_utf8_to_8859(text) : tt_utf8_to_sjis(text)); + code_w.put_u8(0); + break; + case Version::PC_V2: + case Version::BB_V4: + code_w.write(tt_utf8_to_utf16(text)); + code_w.put_u16(0); + break; + default: + throw logic_error("invalid game version"); + } + }; + + if (opcode_def->flags & F_ARGS) { + auto label_it = labels_by_name.find(arg); + if (arg.empty()) { + throw runtime_error("argument is empty"); + } else if (label_it != labels_by_name.end()) { + code_w.put_u8(0x4B); // arg_pushw + code_w.put_u16l(label_it->second->index); + } else if ((arg[0] == 'r') || (arg[0] == 'f')) { + // If the corresponding argument is a REG or REG_SET_FIXED, push + // the register number, not the register's value, since it's an + // out-param + if ((arg_def.type == Type::REG) || (arg_def.type == Type::REG32)) { code_w.put_u8(0x4A); // arg_pushb - code_w.put_u8(value); - } - } catch (const exception&) { - write_as_str = true; - } - if (write_as_str) { - if (arg[0] == '\"') { - code_w.put_u8(0x4E); // arg_pushs - add_cstr(parse_data_string(arg)); - } else { - throw runtime_error("invalid argument syntax"); - } - } - } - - } else { // Not F_ARGS - auto add_label = [&](const string& name, bool is32) -> void { - if (!labels_by_name.count(name)) { - throw runtime_error("label not defined: " + name); - } - if (is32) { - code_w.put_u32(labels_by_name.at(name)->index); - } else { - code_w.put_u16(labels_by_name.at(name)->index); - } - }; - auto add_reg = [&](const string& name, bool is32) -> void { - if (is32) { - code_w.put_u32l(parse_reg(name)); - } else { - code_w.put_u8(parse_reg(name)); - } - }; - - auto split_set = [&](const string& text) -> vector { - if (!starts_with(text, "[") || !ends_with(text, "]")) { - throw runtime_error("incorrect syntax for set-valued argument"); - } - auto values = split(text.substr(1, text.size() - 2), ','); - if (values.size() > 0xFF) { - throw runtime_error("too many labels in set-valued argument"); - } - return values; - }; - - switch (arg_def.type) { - case Type::LABEL16: - case Type::LABEL32: - add_label(arg, arg_def.type == Type::LABEL32); - break; - case Type::LABEL16_SET: { - auto label_names = split_set(arg); - code_w.put_u8(label_names.size()); - for (auto name : label_names) { - strip_trailing_whitespace(name); - strip_leading_whitespace(name); - add_label(name, false); - } - break; - } - case Type::REG: - case Type::REG32: - add_reg(arg, arg_def.type == Type::REG32); - break; - case Type::REG_SET_FIXED: - case Type::REG32_SET_FIXED: { - auto tokens = split(arg, '-'); - if (tokens.size() == 1) { - add_reg(tokens[0], arg_def.type == Type::REG32_SET_FIXED); - } else if (tokens.size() == 2) { - if (static_cast(parse_reg(tokens[1]) - parse_reg(tokens[0]) + 1) != arg_def.count) { - throw runtime_error("incorrect number of registers used"); + code_w.put_u8(parse_reg(arg)); + } else if ( + (arg_def.type == Type::REG_SET_FIXED) || + (arg_def.type == Type::REG32_SET_FIXED)) { + auto tokens = split(arg, '-'); + uint8_t start_reg; + if (tokens.size() == 1) { + start_reg = parse_reg(tokens[0]); + } else if (tokens.size() == 2) { + start_reg = parse_reg(tokens[0]); + if (static_cast(parse_reg(tokens[1]) - start_reg + 1) != arg_def.count) { + throw runtime_error("incorrect number of registers used"); + } + } else { + throw runtime_error("invalid fixed register set syntax"); } - add_reg(tokens[0], arg_def.type == Type::REG32_SET_FIXED); + code_w.put_u8(0x4A); // arg_pushb + code_w.put_u8(start_reg); } else { - throw runtime_error("invalid fixed register set syntax"); + code_w.put_u8(0x48); // arg_pushr + code_w.put_u8(parse_reg(arg)); } - break; - } - case Type::REG_SET: { - auto regs = split_set(arg); - code_w.put_u8(regs.size()); - for (auto reg : regs) { - strip_trailing_whitespace(reg); - strip_leading_whitespace(reg); - add_reg(reg, false); + } else if ((arg[0] == '@') && ((arg[1] == 'r') || (arg[1] == 'f'))) { + code_w.put_u8(0x4C); // arg_pusha + code_w.put_u8(parse_reg(arg.substr(1))); + } else if ((arg[0] == '@') && labels_by_name.count(arg.substr(1))) { + code_w.put_u8(0x4D); // arg_pusho + code_w.put_u16(labels_by_name.at(arg.substr(1))->index); + } else { + bool write_as_str = false; + try { + size_t end_offset; + uint64_t value = stoll(arg, &end_offset, 0); + if (end_offset != arg.size()) { + write_as_str = true; + } else if (value > 0xFFFF) { + code_w.put_u8(0x49); // arg_pushl + code_w.put_u32l(value); + } else if (value > 0xFF) { + code_w.put_u8(0x4B); // arg_pushw + code_w.put_u16l(value); + } else { + code_w.put_u8(0x4A); // arg_pushb + code_w.put_u8(value); + } + } catch (const exception&) { + write_as_str = true; + } + if (write_as_str) { + if (arg[0] == '\"') { + code_w.put_u8(0x4E); // arg_pushs + add_cstr(parse_data_string(arg)); + } else { + throw runtime_error("invalid argument syntax"); + } } - break; } - case Type::INT8: - code_w.put_u8(stol(arg, nullptr, 0)); - break; - case Type::INT16: - code_w.put_u16l(stol(arg, nullptr, 0)); - break; - case Type::INT32: - code_w.put_u32l(stol(arg, nullptr, 0)); - break; - case Type::FLOAT32: - code_w.put_u32l(stof(arg, nullptr)); - break; - case Type::CSTRING: - add_cstr(parse_data_string(arg)); - break; - default: - throw logic_error("unknown argument type"); + + } else { // Not F_ARGS + auto add_label = [&](const string& name, bool is32) -> void { + if (!labels_by_name.count(name)) { + throw runtime_error("label not defined: " + name); + } + if (is32) { + code_w.put_u32(labels_by_name.at(name)->index); + } else { + code_w.put_u16(labels_by_name.at(name)->index); + } + }; + auto add_reg = [&](const string& name, bool is32) -> void { + if (is32) { + code_w.put_u32l(parse_reg(name)); + } else { + code_w.put_u8(parse_reg(name)); + } + }; + + auto split_set = [&](const string& text) -> vector { + if (!starts_with(text, "[") || !ends_with(text, "]")) { + throw runtime_error("incorrect syntax for set-valued argument"); + } + auto values = split(text.substr(1, text.size() - 2), ','); + if (values.size() > 0xFF) { + throw runtime_error("too many labels in set-valued argument"); + } + return values; + }; + + switch (arg_def.type) { + case Type::LABEL16: + case Type::LABEL32: + add_label(arg, arg_def.type == Type::LABEL32); + break; + case Type::LABEL16_SET: { + auto label_names = split_set(arg); + code_w.put_u8(label_names.size()); + for (auto name : label_names) { + strip_trailing_whitespace(name); + strip_leading_whitespace(name); + add_label(name, false); + } + break; + } + case Type::REG: + case Type::REG32: + add_reg(arg, arg_def.type == Type::REG32); + break; + case Type::REG_SET_FIXED: + case Type::REG32_SET_FIXED: { + auto tokens = split(arg, '-'); + if (tokens.size() == 1) { + add_reg(tokens[0], arg_def.type == Type::REG32_SET_FIXED); + } else if (tokens.size() == 2) { + if (static_cast(parse_reg(tokens[1]) - parse_reg(tokens[0]) + 1) != arg_def.count) { + throw runtime_error("incorrect number of registers used"); + } + add_reg(tokens[0], arg_def.type == Type::REG32_SET_FIXED); + } else { + throw runtime_error("invalid fixed register set syntax"); + } + break; + } + case Type::REG_SET: { + auto regs = split_set(arg); + code_w.put_u8(regs.size()); + for (auto reg : regs) { + strip_trailing_whitespace(reg); + strip_leading_whitespace(reg); + add_reg(reg, false); + } + break; + } + case Type::INT8: + code_w.put_u8(stol(arg, nullptr, 0)); + break; + case Type::INT16: + code_w.put_u16l(stol(arg, nullptr, 0)); + break; + case Type::INT32: + code_w.put_u32l(stol(arg, nullptr, 0)); + break; + case Type::FLOAT32: + code_w.put_u32l(stof(arg, nullptr)); + break; + case Type::CSTRING: + add_cstr(parse_data_string(arg)); + break; + default: + throw logic_error("unknown argument type"); + } } + } catch (const exception& e) { + throw runtime_error(string_printf("(arg %zu) %s", z + 1, e.what())); } - } catch (const exception& e) { - throw runtime_error(string_printf("(arg %zu) %s", z + 1, e.what())); } }