add named registers in quest assembler
This commit is contained in:
+298
-46
@@ -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<Register> prev;
|
||||||
|
shared_ptr<Register> next;
|
||||||
|
unordered_set<size_t> 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<Register> get_or_create(const string& name, int16_t number) {
|
||||||
|
shared_ptr<Register> 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<Register>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Register> 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<int16_t>(number)) {
|
||||||
|
throw runtime_error(string_printf("assigning different register number %hhu over existing register number %hd", number, reg->number));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void constrain(shared_ptr<Register> first_reg, shared_ptr<Register> 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<shared_ptr<Register>> 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<int16_t>(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<string, shared_ptr<Register>> named_regs;
|
||||||
|
array<shared_ptr<Register>, 0x100> numbered_regs;
|
||||||
|
};
|
||||||
|
|
||||||
std::string assemble_quest_script(const std::string& text) {
|
std::string assemble_quest_script(const std::string& text) {
|
||||||
auto lines = split(text, '\n');
|
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<RegisterAssigner::Register> {
|
||||||
|
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<shared_ptr<RegisterAssigner::Register>> {
|
||||||
|
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<shared_ptr<RegisterAssigner::Register>> 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<size_t>(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
|
// Assemble code segment
|
||||||
bool version_has_args = F_HAS_ARGS & v_flag(quest_version);
|
bool version_has_args = F_HAS_ARGS & v_flag(quest_version);
|
||||||
const auto& opcodes = opcodes_by_name_for_version(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);
|
strip_leading_whitespace(arg);
|
||||||
|
|
||||||
try {
|
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 {
|
auto add_cstr = [&](const string& text) -> void {
|
||||||
switch (quest_version) {
|
switch (quest_version) {
|
||||||
case Version::DC_NTE:
|
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()) {
|
} else if (label_it != labels_by_name.end()) {
|
||||||
code_w.put_u8(0x4B); // arg_pushw
|
code_w.put_u8(0x4B); // arg_pushw
|
||||||
code_w.put_u16l(label_it->second->index);
|
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
|
// If the corresponding argument is a REG or REG_SET_FIXED, push
|
||||||
// the register number, not the register's value, since it's an
|
// the register number, not the register's value, since it's an
|
||||||
// out-param
|
// out-param
|
||||||
if ((arg_def.type == Type::REG) || (arg_def.type == Type::REG32)) {
|
if ((arg_def.type == Type::REG) || (arg_def.type == Type::REG32)) {
|
||||||
code_w.put_u8(0x4A); // arg_pushb
|
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 (
|
} else if (
|
||||||
(arg_def.type == Type::REG_SET_FIXED) ||
|
(arg_def.type == Type::REG_SET_FIXED) ||
|
||||||
(arg_def.type == Type::REG32_SET_FIXED)) {
|
(arg_def.type == Type::REG32_SET_FIXED)) {
|
||||||
auto tokens = split(arg, '-');
|
auto regs = parse_reg_set_fixed(arg, arg_def.count);
|
||||||
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<size_t>(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(0x4A); // arg_pushb
|
||||||
code_w.put_u8(start_reg);
|
regs[0]->offsets.emplace(code_w.size());
|
||||||
|
code_w.put_u8(regs[0]->number);
|
||||||
} else {
|
} else {
|
||||||
code_w.put_u8(0x48); // arg_pushr
|
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'))) {
|
} else if ((arg[0] == '@') && ((arg[1] == 'r') || (arg[1] == 'f'))) {
|
||||||
code_w.put_u8(0x4C); // arg_pusha
|
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))) {
|
} else if ((arg[0] == '@') && labels_by_name.count(arg.substr(1))) {
|
||||||
code_w.put_u8(0x4D); // arg_pusho
|
code_w.put_u8(0x4D); // arg_pusho
|
||||||
code_w.put_u16(labels_by_name.at(arg.substr(1))->index);
|
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);
|
code_w.put_u16(labels_by_name.at(name)->index);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
auto add_reg = [&](const string& name, bool is32) -> void {
|
auto add_reg = [&](shared_ptr<RegisterAssigner::Register> reg, bool is32) -> void {
|
||||||
|
reg->offsets.emplace(code_w.size());
|
||||||
if (is32) {
|
if (is32) {
|
||||||
code_w.put_u32l(parse_reg(name));
|
code_w.put_u32l(reg->number & 0xFF);
|
||||||
} else {
|
} 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::REG:
|
||||||
case Type::REG32:
|
case Type::REG32:
|
||||||
add_reg(arg, arg_def.type == Type::REG32);
|
add_reg(parse_reg(arg), arg_def.type == Type::REG32);
|
||||||
break;
|
break;
|
||||||
case Type::REG_SET_FIXED:
|
case Type::REG_SET_FIXED:
|
||||||
case Type::REG32_SET_FIXED: {
|
case Type::REG32_SET_FIXED: {
|
||||||
auto tokens = split(arg, '-');
|
auto regs = parse_reg_set_fixed(arg, arg_def.count);
|
||||||
if (tokens.size() == 1) {
|
add_reg(regs[0], arg_def.type == Type::REG32_SET_FIXED);
|
||||||
add_reg(tokens[0], arg_def.type == Type::REG32_SET_FIXED);
|
|
||||||
} else if (tokens.size() == 2) {
|
|
||||||
if (static_cast<size_t>(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;
|
break;
|
||||||
}
|
}
|
||||||
case Type::REG_SET: {
|
case Type::REG_SET: {
|
||||||
auto regs = split_set(arg);
|
auto regs = split_set(arg);
|
||||||
code_w.put_u8(regs.size());
|
code_w.put_u8(regs.size());
|
||||||
for (auto reg : regs) {
|
for (auto reg_arg : regs) {
|
||||||
strip_trailing_whitespace(reg);
|
strip_trailing_whitespace(reg_arg);
|
||||||
strip_leading_whitespace(reg);
|
strip_leading_whitespace(reg_arg);
|
||||||
add_reg(reg, false);
|
add_reg(parse_reg(reg_arg), false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2198,6 +2438,18 @@ std::string assemble_quest_script(const std::string& text) {
|
|||||||
code_w.put_u8(0);
|
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
|
// Generate function table
|
||||||
ssize_t function_table_size = labels_by_index.rbegin()->first + 1;
|
ssize_t function_table_size = labels_by_index.rbegin()->first + 1;
|
||||||
vector<le_uint32_t> function_table;
|
vector<le_uint32_t> function_table;
|
||||||
|
|||||||
@@ -59,15 +59,38 @@
|
|||||||
// capability.
|
// capability.
|
||||||
// .joinable
|
// .joinable
|
||||||
|
|
||||||
// The quest script begins here. A quest script is a sequence of opcodes, and
|
// The quest script begins after the header directives. A quest script is a
|
||||||
// labels denoting positions within that sequence that can be jumped to or
|
// sequence of opcodes, and labels denoting positions within that sequence that
|
||||||
// called like a function. All labels have names, and some have numbers. (In the
|
// can be jumped to or called like a function. All labels have names, and some
|
||||||
// compiled format, labels have only numbers and no names; during compilation,
|
// have numbers. (In the compiled format, labels have only numbers and no names;
|
||||||
// each label that doesn't have a number is assigned a number that isn't in use
|
// during compilation, each label that doesn't have a number is assigned a
|
||||||
// by another label.) To explicitly specify a label number (for example, if an
|
// number that isn't in use by another label.) To explicitly specify a label
|
||||||
// object or NPC refers to a label by number), use an @ sign followed by the
|
// number (for example, if an object or NPC refers to a label by number), use an
|
||||||
// desired number. Note that numbers can be specified in decimal or hexadecimal;
|
// @ sign followed by the desired number. Note that numbers can be specified in
|
||||||
// see on_talk_to_npc1 and on_talk_to_npc2 for examples.
|
// 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
|
// 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:
|
// 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
|
// 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.
|
// the quest begins. The start label is always assigned number 0.
|
||||||
start:
|
start:
|
||||||
gget 0x0091, r252
|
gget 0x0091, r:flag_0091_value@252
|
||||||
set_floor_handler 0, floor_handler_pioneer_2
|
set_floor_handler 0, floor_handler_pioneer_2
|
||||||
set_floor_handler 1, floor_handler_forest_1
|
set_floor_handler 1, floor_handler_forest_1
|
||||||
set_floor_handler 2, floor_handler_forest_2
|
set_floor_handler 2, floor_handler_forest_2
|
||||||
set_floor_handler 11, floor_handler_dragon
|
set_floor_handler 11, floor_handler_dragon
|
||||||
set_qt_success on_quest_success
|
set_qt_success on_quest_success
|
||||||
get_difficulty_level_v2 r83
|
get_difficulty_level_v2 r:difficulty_level@83
|
||||||
leti r60, 0 // Pioneer 2
|
leti r:op_arg1, 0 // Pioneer 2
|
||||||
leti r61, 0
|
leti r:op_arg2, 0
|
||||||
leti r62, 0
|
leti r:op_arg3@62, 0 // See comment above about register assignment
|
||||||
leti r63, 0
|
leti r:op_arg4, 0
|
||||||
map_designate r60-r63
|
map_designate (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||||
leti r60, 1 // Forest 1
|
leti r:op_arg1, 1 // Forest 1
|
||||||
leti r61, 0
|
leti r:op_arg2, 0
|
||||||
leti r62, 0
|
leti r:op_arg3, 0
|
||||||
leti r63, 0
|
leti r:op_arg4, 0
|
||||||
map_designate r60-r63
|
map_designate (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||||
leti r60, 2 // Forest 2
|
leti r:op_arg1, 2 // Forest 2
|
||||||
leti r61, 0
|
leti r:op_arg2, 0
|
||||||
leti r62, 0
|
leti r:op_arg3, 0
|
||||||
leti r63, 0
|
leti r:op_arg4, 0
|
||||||
map_designate r60-r63
|
map_designate (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||||
leti r60, 11 // Dragon
|
leti r:op_arg1, 11 // Dragon
|
||||||
leti r61, 0
|
leti r:op_arg2, 0
|
||||||
leti r62, 0
|
leti r:op_arg3, 0
|
||||||
leti r63, 0
|
leti r:op_arg4, 0
|
||||||
map_designate r60-r63
|
map_designate (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||||
ret
|
ret
|
||||||
|
|
||||||
return_immediately:
|
return_immediately:
|
||||||
ret
|
ret
|
||||||
|
|
||||||
floor_handler_pioneer_2:
|
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:
|
floor_handler_pioneer_2_first_time:
|
||||||
set r50
|
set r50
|
||||||
set_mainwarp 1
|
set_mainwarp 1
|
||||||
leti r60, 0x000000ED
|
leti r:op_arg1, 0x000000ED
|
||||||
leti r61, 0x00000000
|
leti r:op_arg2, 0x00000000
|
||||||
leti r62, 0x0000014D
|
leti r:op_arg3, 0x0000014D
|
||||||
leti r63, 0xFFFFFFF1
|
leti r:op_arg4, 0xFFFFFFF1
|
||||||
p_setpos 0, r60-r63
|
p_setpos 0, (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||||
leti r60, 0x000000FF
|
leti r:op_arg1, 0x000000FF
|
||||||
leti r61, 0x00000000
|
leti r:op_arg2, 0x00000000
|
||||||
leti r62, 0x00000152
|
leti r:op_arg3, 0x00000152
|
||||||
leti r63, 0xFFFFFFD5
|
leti r:op_arg4, 0xFFFFFFD5
|
||||||
p_setpos 1, r60-r63
|
p_setpos 1, (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||||
leti r60, 0x000000DE
|
leti r:op_arg1, 0x000000DE
|
||||||
leti r61, 0x00000000
|
leti r:op_arg2, 0x00000000
|
||||||
leti r62, 0x00000142
|
leti r:op_arg3, 0x00000142
|
||||||
leti r63, 0x00000019
|
leti r:op_arg4, 0x00000019
|
||||||
p_setpos 2, r60-r63
|
p_setpos 2, (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||||
leti r60, 0x000000F8
|
leti r:op_arg1, 0x000000F8
|
||||||
leti r61, 0x00000000
|
leti r:op_arg2, 0x00000000
|
||||||
leti r62, 0x00000143
|
leti r:op_arg3, 0x00000143
|
||||||
leti r63, 0xFFFFFFEC
|
leti r:op_arg4, 0xFFFFFFEC
|
||||||
p_setpos 3, r60-r63
|
p_setpos 3, (r:op_arg1, r:op_arg2, r:op_arg3, r:op_arg4)
|
||||||
call on_talk_to_hopkins
|
call on_talk_to_hopkins
|
||||||
ret
|
ret
|
||||||
floor_handler_pioneer_2_not_first_time:
|
floor_handler_pioneer_2_not_first_time:
|
||||||
@@ -156,10 +179,10 @@ label00CB@0x00CB:
|
|||||||
ret
|
ret
|
||||||
|
|
||||||
on_quest_success:
|
on_quest_success:
|
||||||
jmpi_eq r83, 0, on_quest_success_normal
|
jmpi_eq r:difficulty_level, 0, on_quest_success_normal
|
||||||
jmpi_eq r83, 1, on_quest_success_hard
|
jmpi_eq r:difficulty_level, 1, on_quest_success_hard
|
||||||
jmpi_eq r83, 2, on_quest_success_very_hard
|
jmpi_eq r:difficulty_level, 2, on_quest_success_very_hard
|
||||||
jmpi_eq r83, 3, on_quest_success_ultimate
|
jmpi_eq r:difficulty_level, 3, on_quest_success_ultimate
|
||||||
on_quest_success_normal:
|
on_quest_success_normal:
|
||||||
window_msg "You\'ve been awarded\n100 Meseta."
|
window_msg "You\'ve been awarded\n100 Meseta."
|
||||||
bgm 1
|
bgm 1
|
||||||
@@ -241,7 +264,7 @@ play_dragon_killed_cutscene_when_ready_check_again:
|
|||||||
ret
|
ret
|
||||||
|
|
||||||
on_dragon_killed:
|
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!"
|
window_msg "Dragon killed!"
|
||||||
add_msg "Hopkins\'s HEAT SWORD found\nin Dragon\'s mouth!"
|
add_msg "Hopkins\'s HEAT SWORD found\nin Dragon\'s mouth!"
|
||||||
se 1
|
se 1
|
||||||
@@ -281,7 +304,7 @@ show_mission_complete_if_needed_check_again:
|
|||||||
on_talk_to_hopkins@310:
|
on_talk_to_hopkins@310:
|
||||||
jmpi_eq r255, 1, on_talk_to_hopkins_complete_again
|
jmpi_eq r255, 1, on_talk_to_hopkins_complete_again
|
||||||
jmpi_eq r254, 1, on_talk_to_hopkins_complete
|
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 start_cutscene
|
||||||
call wait_30_frames
|
call wait_30_frames
|
||||||
message 100, "Ca... can you help\nme? Please?"
|
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 "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 "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."
|
add_msg "I cannot do my job\nwithout it! Please\nget it back for me."
|
||||||
set r0
|
set r:has_talked_to_hopkins
|
||||||
mesend
|
mesend
|
||||||
bgm 1
|
bgm 1
|
||||||
call end_cutscene
|
call end_cutscene
|
||||||
|
|||||||
Executable
+15
@@ -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
|
||||||
Executable
BIN
Binary file not shown.
Reference in New Issue
Block a user