From d178d062a89f6a68580632765f2796b3371e3074 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Tue, 4 Jun 2024 21:14:54 -0700 Subject: [PATCH] add named registers in quest assembler --- src/QuestScript.cc | 344 +++++++++++++++++++--- system/quests/retrieval/q058-gc-e.bin.txt | 141 +++++---- tests/assemble-quest.test.sh | 15 + tests/q058-gc-e.bin | Bin 0 -> 1362 bytes 4 files changed, 395 insertions(+), 105 deletions(-) create mode 100755 tests/assemble-quest.test.sh create mode 100755 tests/q058-gc-e.bin diff --git a/src/QuestScript.cc b/src/QuestScript.cc index 82a65742..28664207 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -1775,6 +1775,186 @@ Episode episode_for_quest_episode_number(uint8_t episode_number) { } } +struct RegisterAssigner { + struct Register { + string name; + int16_t number = -1; // -1 = unassigned (any number) + shared_ptr prev; + shared_ptr next; + unordered_set offsets; + }; + + ~RegisterAssigner() { + for (auto& it : this->named_regs) { + it.second->prev.reset(); + it.second->next.reset(); + } + for (auto& reg : this->numbered_regs) { + if (reg) { + reg->prev.reset(); + reg->next.reset(); + } + } + } + + shared_ptr get_or_create(const string& name, int16_t number) { + shared_ptr reg; + try { + if (!name.empty()) { + reg = this->named_regs.at(name); + } else if (number >= 0 && number < 0x100) { + reg = this->numbered_regs.at(number); + } else { + throw runtime_error("invalid register name or number"); + } + } catch (const out_of_range&) { + } + + if (!reg) { + reg = make_shared(); + } + + if (number >= 0) { + if (reg->number < 0) { + reg->number = number; + if (this->numbered_regs.at(reg->number)) { + throw runtime_error(string_printf("number %hd is already assigned to a different register", reg->number)); + } + this->numbered_regs.at(reg->number) = reg; + } else if (reg->number != number) { + throw runtime_error(string_printf("register %s is assigned multiple numbers", reg->name.c_str())); + } + } + + if (!name.empty()) { + if (reg->name.empty()) { + reg->name = name; + if (!this->named_regs.emplace(reg->name, reg).second) { + throw runtime_error(string_printf("name %s is already assigned to a different register", reg->name.c_str())); + } + } else if (reg->name != name) { + throw runtime_error(string_printf("register %hd is assigned multiple names", reg->number)); + } + } + + return reg; + } + + void assign_number(shared_ptr reg, uint8_t number) { + if (reg->number < 0) { + reg->number = number; + if (this->numbered_regs.at(reg->number)) { + throw logic_error(string_printf("register number %hd assigned multiple times", reg->number)); + } + this->numbered_regs.at(reg->number) = reg; + } else if (reg->number != static_cast(number)) { + throw runtime_error(string_printf("assigning different register number %hhu over existing register number %hd", number, reg->number)); + } + } + + void constrain(shared_ptr first_reg, shared_ptr second_reg) { + if (!first_reg->next) { + first_reg->next = second_reg; + } else if (first_reg->next != second_reg) { + throw runtime_error(string_printf("register %s must come after %s, but is already constrained to another register", second_reg->name.c_str(), first_reg->name.c_str())); + } + if (!second_reg->prev) { + second_reg->prev = first_reg; + } else if (second_reg->prev != first_reg) { + throw runtime_error(string_printf("register %s must come before %s, but is already constrained to another register", first_reg->name.c_str(), second_reg->name.c_str())); + } + if ((first_reg->number >= 0) && (second_reg->number >= 0) && (first_reg->number != ((second_reg->number - 1) & 0xFF))) { + throw runtime_error(string_printf("register %s must come before %s, but both registers already have non-consecutive numbers", first_reg->name.c_str(), second_reg->name.c_str())); + } + } + + void assign_all() { + // TODO: Technically, we should assign the biggest blocks first to minimize + // fragmentation. I am lazy and haven't implemented this yet. + unordered_set> unassigned; + for (auto it : this->named_regs) { + if (it.second->number < 0) { + unassigned.emplace(it.second); + } + } + + while (!unassigned.empty()) { + auto reg_it = unassigned.begin(); + auto reg = *reg_it; + unassigned.erase(reg_it); + + // If this register is already assigned, skip it + if (reg->number >= 0) { + continue; + } + + // If any next register is assigned, assign this register + size_t next_delta = 1; + for (auto next_reg = reg->next; next_reg; next_reg = next_reg->next, next_delta++) { + if (next_reg->number >= 0) { + this->assign_number(reg, (next_reg->number - next_delta) & 0xFF); + break; + } + } + if (reg->number >= 0) { + continue; + } + + // If any prev register is assigned, assign this register + size_t prev_delta = 1; + for (auto prev_reg = reg->prev; prev_reg; prev_reg = prev_reg->prev, prev_delta++) { + if (prev_reg->number >= 0) { + this->assign_number(reg, (prev_reg->number + prev_delta) & 0xFF); + break; + } + } + if (reg->number >= 0) { + continue; + } + + // No prev or next register is assigned; find an interval in the register + // number space that fits this block of registers. The total number of + // register numbers needed is (prev_delta - 1) + (next_delta - 1) + 1. + size_t num_regs = prev_delta + next_delta - 1; + this->assign_number(reg, (this->find_register_number_space(num_regs) + (prev_delta - 1)) & 0xFF); + + // We don't need to assign the prev and next registers; they should also + // be in the unassigned set and will be assigned by the above logic + } + + // At this point, all registers should be assigned + for (const auto& it : this->named_regs) { + if (it.second->number < 0) { + throw logic_error(string_printf("register %s was not assigned", it.second->name.c_str())); + } + } + for (size_t z = 0; z < 0x100; z++) { + auto reg = this->numbered_regs[z]; + if (reg && (reg->number != static_cast(z))) { + throw logic_error(string_printf("register %zu has incorrect number %hd", z, reg->number)); + } + } + } + + uint8_t find_register_number_space(size_t num_regs) const { + for (size_t candidate = 0; candidate < 0x100; candidate++) { + size_t z; + for (z = 0; z < num_regs; z++) { + if (this->numbered_regs[candidate + z]) { + break; + } + } + if (z == num_regs) { + return candidate; + } + } + throw runtime_error("not enough space to assign registers"); + } + + unordered_map> named_regs; + array, 0x100> numbered_regs; +}; + std::string assemble_quest_script(const std::string& text) { auto lines = split(text, '\n'); @@ -1901,6 +2081,88 @@ std::string assemble_quest_script(const std::string& text) { } } + // Prepare to collect named registers + RegisterAssigner reg_assigner; + auto parse_reg = [®_assigner](const string& arg, bool allow_unnumbered = true) -> shared_ptr { + if (arg.size() < 2) { + throw runtime_error("register argument is too short"); + } + if ((arg[0] != 'r') && (arg[0] != 'f')) { + throw runtime_error("a register is required"); + } + string name; + ssize_t number = -1; + if (arg[1] == ':') { + auto tokens = split(arg.substr(2), '@'); + if (tokens.size() == 1) { + name = std::move(tokens[0]); + } else if (tokens.size() == 2) { + name = std::move(tokens[0]); + number = stoull(tokens[1], nullptr, 0); + } else { + throw runtime_error("invalid register specification"); + } + } else { + number = stoull(arg.substr(1), nullptr, 0); + } + if (!allow_unnumbered && (number < 0)) { + throw runtime_error("a numbered register is required"); + } + if (number > 0xFF) { + throw runtime_error("invalid register number"); + } + return reg_assigner.get_or_create(name, number); + }; + auto parse_reg_set_fixed = [®_assigner, &parse_reg](const string& name, size_t expected_count) -> vector> { + if (expected_count == 0) { + throw logic_error("REG_SET_FIXED argument expects no registers"); + } + if (name.empty()) { + throw runtime_error("no register specified for REG_SET_FIXED argument"); + } + vector> regs; + if ((name[0] == '(') && (name.back() == ')')) { + auto tokens = split(name.substr(1, name.size() - 2), ','); + if (tokens.size() != expected_count) { + throw runtime_error("incorrect number of registers in REG_SET_FIXED"); + } + for (auto& token : tokens) { + strip_trailing_whitespace(token); + strip_leading_whitespace(token); + regs.emplace_back(parse_reg(token)); + if (regs.size() > 1) { + reg_assigner.constrain(regs.at(regs.size() - 2), regs.back()); + } + } + } else { + auto tokens = split(name, '-'); + if (tokens.size() == 1) { + regs.emplace_back(parse_reg(tokens[0], false)); + while (regs.size() < expected_count) { + regs.emplace_back(parse_reg("", (regs.back()->number + 1) & 0xFF)); + reg_assigner.constrain(regs.at(regs.size() - 2), regs.back()); + } + } else if (tokens.size() == 2) { + regs.emplace_back(parse_reg(tokens[0], false)); + while (regs.size() < expected_count - 1) { + regs.emplace_back(reg_assigner.get_or_create("", (regs.back()->number + 1) & 0xFF)); + reg_assigner.constrain(regs.at(regs.size() - 2), regs.back()); + } + regs.emplace_back(parse_reg(tokens[1], false)); + if (static_cast(regs.back()->number - regs.front()->number + 1) != expected_count) { + throw runtime_error("incorrect number of registers used"); + } + reg_assigner.constrain(regs.at(regs.size() - 2), regs.back()); + } else { + throw runtime_error("invalid fixed register set syntax"); + } + } + if (regs.empty() || regs.size() != expected_count) { + throw logic_error("incorrect register count in REG_SET_FIXED after parsing"); + } + return regs; + }; + // Assemble code segment bool version_has_args = F_HAS_ARGS & v_flag(quest_version); const auto& opcodes = opcodes_by_name_for_version(quest_version); @@ -1974,16 +2236,6 @@ std::string assemble_quest_script(const std::string& text) { 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"); - } - 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: @@ -2019,37 +2271,33 @@ std::string assemble_quest_script(const std::string& text) { } 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')) { + } else if ((arg[0] == 'r') || (arg[0] == 'f') || ((arg[0] == '(') && (arg.back() == ')'))) { // 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)); + auto reg = parse_reg(arg); + reg->offsets.emplace(code_w.size()); + code_w.put_u8(reg->number); } 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"); - } + auto regs = parse_reg_set_fixed(arg, arg_def.count); code_w.put_u8(0x4A); // arg_pushb - code_w.put_u8(start_reg); + regs[0]->offsets.emplace(code_w.size()); + code_w.put_u8(regs[0]->number); } else { code_w.put_u8(0x48); // arg_pushr - code_w.put_u8(parse_reg(arg)); + auto reg = parse_reg(arg); + reg->offsets.emplace(code_w.size()); + code_w.put_u8(reg->number); } } 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))); + auto reg = parse_reg(arg.substr(1)); + reg->offsets.emplace(code_w.size()); + code_w.put_u8(reg->number); } 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); @@ -2094,11 +2342,12 @@ std::string assemble_quest_script(const std::string& text) { code_w.put_u16(labels_by_name.at(name)->index); } }; - auto add_reg = [&](const string& name, bool is32) -> void { + auto add_reg = [&](shared_ptr reg, bool is32) -> void { + reg->offsets.emplace(code_w.size()); if (is32) { - code_w.put_u32l(parse_reg(name)); + code_w.put_u32l(reg->number & 0xFF); } else { - code_w.put_u8(parse_reg(name)); + code_w.put_u8(reg->number); } }; @@ -2130,30 +2379,21 @@ std::string assemble_quest_script(const std::string& text) { } case Type::REG: case Type::REG32: - add_reg(arg, arg_def.type == Type::REG32); + add_reg(parse_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"); - } + auto regs = parse_reg_set_fixed(arg, arg_def.count); + add_reg(regs[0], arg_def.type == Type::REG32_SET_FIXED); 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); + for (auto reg_arg : regs) { + strip_trailing_whitespace(reg_arg); + strip_leading_whitespace(reg_arg); + add_reg(parse_reg(reg_arg), false); } break; } @@ -2198,6 +2438,18 @@ std::string assemble_quest_script(const std::string& text) { code_w.put_u8(0); } + // Assign all register numbers and patch the code section if needed + reg_assigner.assign_all(); + for (size_t z = 0; z < 0x100; z++) { + auto reg = reg_assigner.numbered_regs[z]; + if (!reg) { + continue; + } + for (size_t offset : reg->offsets) { + code_w.pput_u8(offset, reg->number); + } + } + // Generate function table ssize_t function_table_size = labels_by_index.rbegin()->first + 1; vector function_table; diff --git a/system/quests/retrieval/q058-gc-e.bin.txt b/system/quests/retrieval/q058-gc-e.bin.txt index 1a76b536..67807098 100644 --- a/system/quests/retrieval/q058-gc-e.bin.txt +++ b/system/quests/retrieval/q058-gc-e.bin.txt @@ -59,15 +59,38 @@ // capability. // .joinable -// The quest script begins here. A quest script is a sequence of opcodes, and -// labels denoting positions within that sequence that can be jumped to or -// called like a function. All labels have names, and some have numbers. (In the -// compiled format, labels have only numbers and no names; during compilation, -// each label that doesn't have a number is assigned a number that isn't in use -// by another label.) To explicitly specify a label number (for example, if an -// object or NPC refers to a label by number), use an @ sign followed by the -// desired number. Note that numbers can be specified in decimal or hexadecimal; -// see on_talk_to_npc1 and on_talk_to_npc2 for examples. +// The quest script begins after the header directives. A quest script is a +// sequence of opcodes, and labels denoting positions within that sequence that +// can be jumped to or called like a function. All labels have names, and some +// have numbers. (In the compiled format, labels have only numbers and no names; +// during compilation, each label that doesn't have a number is assigned a +// number that isn't in use by another label.) To explicitly specify a label +// number (for example, if an object or NPC refers to a label by number), use an +// @ sign followed by the desired number. Note that numbers can be specified in +// decimal or hexadecimal; see on_talk_to_npc1 and on_talk_to_npc2 for examples. + +// Registers may be named as well as labels. (In the compiled script, registers +// do not have names, so disassembling a quest script always produces only +// numbered registers.) When compiling, all of the following are valid: +// r83 (explicitly numbered register) +// r:difficulty_level (the compiler will assign an unused register number) +// r:difficulty_level@83 (named and explicitly numbered) +// You don't always have to use the same form for each register; for example, +// if you use r:difficulty_level@83 anywhere in the quest script, you can also +// use r:difficulty_level and r83 in other places and they will all refer to the +// same register. (However, if you don't use r:difficulty_level@83 anywhere, but +// you do use r83 and r:difficulty_level, the compiler will assign these to two +// different registers since there is nothing linking the name to the number.) + +// Using opcodes that take a consecutive sequence of registers, such as +// map_designate which takes 4, introduces constraints on which registers may be +// assigned to which numbers. For example, before one of the map_designate +// opcodes after the start label, we explicitly assign one register's number, +// but leave the nearby registers' numbers unassigned. The compiler assigns +// those four registers to r60-r63, because they are used in a map_designate +// call. If we didn't explicitly number any of those registers, the compiler +// would instead choose a consecutive sequence of register numbers that aren't +// used anywhere else in the script. // This quest does not contain any examples of non-script data, but such data // can be included in the quest script using the .data directive, like this: @@ -80,63 +103,63 @@ // Every quest must have a start label; this is the main thread that starts when // the quest begins. The start label is always assigned number 0. start: - gget 0x0091, r252 + gget 0x0091, r:flag_0091_value@252 set_floor_handler 0, floor_handler_pioneer_2 set_floor_handler 1, floor_handler_forest_1 set_floor_handler 2, floor_handler_forest_2 set_floor_handler 11, floor_handler_dragon set_qt_success on_quest_success - get_difficulty_level_v2 r83 - leti r60, 0 // Pioneer 2 - leti r61, 0 - leti r62, 0 - leti r63, 0 - map_designate r60-r63 - leti r60, 1 // Forest 1 - leti r61, 0 - leti r62, 0 - leti r63, 0 - map_designate r60-r63 - leti r60, 2 // Forest 2 - leti r61, 0 - leti r62, 0 - leti r63, 0 - map_designate r60-r63 - leti r60, 11 // Dragon - leti r61, 0 - leti r62, 0 - leti r63, 0 - map_designate r60-r63 + get_difficulty_level_v2 r:difficulty_level@83 + leti r:op_arg1, 0 // Pioneer 2 + leti r:op_arg2, 0 + leti r:op_arg3@62, 0 // See comment above about register assignment + leti r:op_arg4, 0 + map_designate (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4) + leti r:op_arg1, 1 // Forest 1 + leti r:op_arg2, 0 + leti r:op_arg3, 0 + leti r:op_arg4, 0 + map_designate (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4) + leti r:op_arg1, 2 // Forest 2 + leti r:op_arg2, 0 + leti r:op_arg3, 0 + leti r:op_arg4, 0 + map_designate (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4) + leti r:op_arg1, 11 // Dragon + leti r:op_arg2, 0 + leti r:op_arg3, 0 + leti r:op_arg4, 0 + map_designate (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4) ret return_immediately: ret floor_handler_pioneer_2: - switch_jmp r0, [floor_handler_pioneer_2_first_time, floor_handler_pioneer_2_not_first_time] + switch_jmp r:has_talked_to_hopkins, [floor_handler_pioneer_2_first_time, floor_handler_pioneer_2_not_first_time] floor_handler_pioneer_2_first_time: set r50 set_mainwarp 1 - leti r60, 0x000000ED - leti r61, 0x00000000 - leti r62, 0x0000014D - leti r63, 0xFFFFFFF1 - p_setpos 0, r60-r63 - leti r60, 0x000000FF - leti r61, 0x00000000 - leti r62, 0x00000152 - leti r63, 0xFFFFFFD5 - p_setpos 1, r60-r63 - leti r60, 0x000000DE - leti r61, 0x00000000 - leti r62, 0x00000142 - leti r63, 0x00000019 - p_setpos 2, r60-r63 - leti r60, 0x000000F8 - leti r61, 0x00000000 - leti r62, 0x00000143 - leti r63, 0xFFFFFFEC - p_setpos 3, r60-r63 + leti r:op_arg1, 0x000000ED + leti r:op_arg2, 0x00000000 + leti r:op_arg3, 0x0000014D + leti r:op_arg4, 0xFFFFFFF1 + p_setpos 0, (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4) + leti r:op_arg1, 0x000000FF + leti r:op_arg2, 0x00000000 + leti r:op_arg3, 0x00000152 + leti r:op_arg4, 0xFFFFFFD5 + p_setpos 1, (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4) + leti r:op_arg1, 0x000000DE + leti r:op_arg2, 0x00000000 + leti r:op_arg3, 0x00000142 + leti r:op_arg4, 0x00000019 + p_setpos 2, (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4) + leti r:op_arg1, 0x000000F8 + leti r:op_arg2, 0x00000000 + leti r:op_arg3, 0x00000143 + leti r:op_arg4, 0xFFFFFFEC + p_setpos 3, (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4) call on_talk_to_hopkins ret floor_handler_pioneer_2_not_first_time: @@ -156,10 +179,10 @@ label00CB@0x00CB: ret on_quest_success: - jmpi_eq r83, 0, on_quest_success_normal - jmpi_eq r83, 1, on_quest_success_hard - jmpi_eq r83, 2, on_quest_success_very_hard - jmpi_eq r83, 3, on_quest_success_ultimate + jmpi_eq r:difficulty_level, 0, on_quest_success_normal + jmpi_eq r:difficulty_level, 1, on_quest_success_hard + jmpi_eq r:difficulty_level, 2, on_quest_success_very_hard + jmpi_eq r:difficulty_level, 3, on_quest_success_ultimate on_quest_success_normal: window_msg "You\'ve been awarded\n100 Meseta." bgm 1 @@ -241,7 +264,7 @@ play_dragon_killed_cutscene_when_ready_check_again: ret on_dragon_killed: - jmpi_eq r83, 3, on_dragon_killed_ultimate + jmpi_eq r:difficulty_level, 3, on_dragon_killed_ultimate window_msg "Dragon killed!" add_msg "Hopkins\'s HEAT SWORD found\nin Dragon\'s mouth!" se 1 @@ -281,7 +304,7 @@ show_mission_complete_if_needed_check_again: on_talk_to_hopkins@310: jmpi_eq r255, 1, on_talk_to_hopkins_complete_again jmpi_eq r254, 1, on_talk_to_hopkins_complete - jmpi_eq r0, 1, on_talk_to_hopkins_incomplete_again + jmpi_eq r:has_talked_to_hopkins, 1, on_talk_to_hopkins_incomplete_again call start_cutscene call wait_30_frames message 100, "Ca... can you help\nme? Please?" @@ -290,7 +313,7 @@ on_talk_to_hopkins@310: add_msg "My HEAT SWORD...\nMy dad gave HEAT SWORD\nto me..." add_msg "It\'s really important to\nme. I don\'t know how it\nwas taken from me." add_msg "I cannot do my job\nwithout it! Please\nget it back for me." - set r0 + set r:has_talked_to_hopkins mesend bgm 1 call end_cutscene diff --git a/tests/assemble-quest.test.sh b/tests/assemble-quest.test.sh new file mode 100755 index 00000000..68def40f --- /dev/null +++ b/tests/assemble-quest.test.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +set -e + +EXECUTABLE="$1" +if [ "$EXECUTABLE" == "" ]; then + EXECUTABLE="./newserv" +fi + +echo "... assemble system/quests/retrieval/q058-gc-e.bin.txt" +$EXECUTABLE assemble-quest-script system/quests/retrieval/q058-gc-e.bin.txt tests/q058-gc-e-test.bin +diff tests/q058-gc-e-test.bin tests/q058-gc-e.bin + +echo "... clean up" +rm tests/q058-gc-e-test.bin diff --git a/tests/q058-gc-e.bin b/tests/q058-gc-e.bin new file mode 100755 index 0000000000000000000000000000000000000000..a9a2bb96701ca9073151c913b478afc32dea46ff GIT binary patch literal 1362 zcmWNRdsI_(0LOoK^Fab4@!0}RH#2l~8;vjeIvMo05*u4mJIH72Y zK^)D)36j%8JepP}o>Qh~AtD_N5d`ERkZVS0+t^^RG46KbkMHMv&-wiS5sV-Jgn72p z862Qx%n9Iah9D_4V^2zc9h!Gwe{MQpY>aO%PH7bQVFpJrScPM1hA2l%HG~QZFf?6* z$@B|6U}K8Xl&1P}DgiYW66!LApqz|C<(0Gv*T9@gTuaVDG<&rMO#IYm1h&i4JHT7MtsK245Q8$CLWgSm|zCxigcs8n}}{E#tC<&>OxrJEjx9# zHIa5nr|)IQ>muLmsy)`pWk9DpK>Xskylt!7+DRlj&&|~X*go+KwgrX<>1=GWZAMHpk4xuaY)nk$JRfM^lNS^TtE5Kcnlo@mjPJKj->im!ky~QNx>lzj zH=@-U#EEhEw2T7)%I#J?_j;hWJlH zRzQoh&qX_+jkKvFApyAl70;YO{-INm-XBf}ROmy2NRTlD|Ktj}PEL0utJ*Dn$@7^& zKZBMjl*;3=yxk(>KiUXVJq6KHu4{R6z>5N%w@zhDqvV6{+c^0}h$v5d7ro}-k32l> z2s$+*J%mY78K#(+y?-CBQpBklK6o-Vm>O7W<66AkK(yb9cB{Ha@drsNww9bERO)KV zj7JMWK{GU>Rbink2A3%mx&EnhQAtQ7H4s~0dTJ7_5j@r4CAdP|V9~@ZhEMUsLb-uO z8*cUc2`f?$eOAxFde_ECXXC~T$AHPto@3c$5lTgAY>dzJe}t7^Ri9I;Vf8)%ii(2? zjC5LoSd@uk$^uo+&>qXK$L6N-7Ex+tBz0Hv&5G%aRRWfw0oHg}!yaZ*Y-~w$w(G*E zLglL_G!*4Wadnlu91F7=PmfLITD|z(Dcx zLtgU=l;JKWu{%~$)@^DDb8ovOT`lD9%SiWTJ7dW9d!~Sfu*ffD_kN*ZcOZ(#>U^<1wXDQ^`1qxo z_R4p=;e%=J#1GE4Fx&@Mo4lazLj8o{UY~SqhQ>yT~8gorz8OJ^N!G$ zh97B{z!q7PA^dacXwnSaL}6Wm;;2ozZHAVzy#r6K=8_NQA3uMs_JzSE0`{%&e;-o| z!AXq}aj;Adwx;!qjrGW}b8V>x(m;BwVIx_AcxGgWbUw(a-6_b}aQg=0qmOr|D|mFX zTLb+s-RVmo)D8^g&{z7ky(t4{>6Cby>n`atEjG=?^;g7N94-xm`14q=sixkJ-sc6u zQ*HZfDx@c@eJeY*$I{qq3IE<6KP7g}?^)~MMH|E1UR%h~xedsXmzRxe{}CIV-Yj(6 N+vVK+)A(9U{|CIZfAIhS literal 0 HcmV?d00001