#include "script-call-internal.hpp" // script-call.cpp - EAthena script frontend, engine, and library. // // Copyright © ????-2004 Athena Dev Teams // Copyright © 2004-2011 The Mana World Development Team // Copyright © 2011 Chuck Miller // Copyright © 2011-2014 Ben Longbons // Copyright © 2013 wushin // // This file is part of The Mana World (Athena server) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "../generic/intern-pool.hpp" #include "../io/cxxstdio.hpp" #include "../mmo/cxxstdio_enums.hpp" #include "battle.hpp" #include "map.hpp" #include "npc.hpp" #include "pc.hpp" #include "script-fun.hpp" #include "script-parse-internal.hpp" #include "script-persist.hpp" #include "script-startup-internal.hpp" #include "../poison.hpp" namespace tmwa { constexpr bool DEBUG_RUN = false; static struct ScriptConfigRun { static const int check_cmdcount = 8192; static const int check_gotocount = 512; } script_config; /*========================================== * ridからsdへの解決 *------------------------------------------ */ dumb_ptr script_rid2sd(ScriptState *st) { dumb_ptr sd = map_id2sd(st->rid); if (!sd) { PRINTF("script_rid2sd: fatal error ! player not attached!\n"_fmt); } return sd; } /*========================================== * 変数の読み取り *------------------------------------------ */ void get_val(dumb_ptr sd, struct script_data *data) { MATCH (*data) { CASE (const ScriptDataParam&, u) { if (sd == nullptr) PRINTF("get_val error param SP::%d\n"_fmt, u.reg.sp()); int numi = 0; if (sd) numi = pc_readparam(sd, u.reg.sp()); *data = ScriptDataInt{numi}; } CASE (const ScriptDataVariable&, u) { ZString name_ = variable_names.outtern(u.reg.base()); VarName name = stringish(name_); char prefix = name.front(); char postfix = name.back(); if (prefix != '$') { if (sd == nullptr) PRINTF("get_val error name?:%s\n"_fmt, name); } if (postfix == '$') { RString str; if (prefix == '@') { if (sd) str = pc_readregstr(sd, u.reg); } else if (prefix == '$') { Option> s_ = mapregstr_db.search(u.reg); if OPTION_IS_SOME(s, s_) str = *s; } else { PRINTF("script: get_val: illegal scope string variable.\n"_fmt); str = "!!ERROR!!"_s; } *data = ScriptDataStr{str}; } else { int numi = 0; if (prefix == '@') { if (sd) numi = pc_readreg(sd, u.reg); } else if (prefix == '$') { numi = mapreg_db.get(u.reg); } else if (prefix == '#') { if (name[1] == '#') { if (sd) numi = pc_readaccountreg2(sd, name); } else { if (sd) numi = pc_readaccountreg(sd, name); } } else { if (sd) numi = pc_readglobalreg(sd, name); } *data = ScriptDataInt{numi}; } } } } void get_val(ScriptState *st, struct script_data *data) { dumb_ptr sd = st->rid ? map_id2sd(st->rid) : nullptr; get_val(sd, data); } /*========================================== * 変数の読み取り2 *------------------------------------------ */ struct script_data get_val2(ScriptState *st, SIR reg) { struct script_data dat = ScriptDataVariable{reg}; get_val(st, &dat); return dat; } /*========================================== * 変数設定用 *------------------------------------------ */ void set_reg(dumb_ptr sd, VariableCode type, SIR reg, struct script_data vd) { if (type == VariableCode::PARAM) { int val = vd.get_if()->numi; pc_setparam(sd, reg.sp(), val); return; } assert (type == VariableCode::VARIABLE); ZString name_ = variable_names.outtern(reg.base()); VarName name = stringish(name_); char prefix = name.front(); char postfix = name.back(); if (postfix == '$') { RString str = vd.get_if()->str; if (prefix == '@') { pc_setregstr(sd, reg, str); } else if (prefix == '$') { mapreg_setregstr(reg, str); } else { PRINTF("script: set_reg: illegal scope string variable !"_fmt); } } else { int val = vd.get_if()->numi; if (prefix == '@') { pc_setreg(sd, reg, val); } else if (prefix == '$') { mapreg_setreg(reg, val); } else if (prefix == '#') { if (name[1] == '#') pc_setaccountreg2(sd, name, val); else pc_setaccountreg(sd, name, val); } else { pc_setglobalreg(sd, name, val); } } } void set_reg(dumb_ptr sd, VariableCode type, SIR reg, int id) { struct script_data vd = ScriptDataInt{id}; set_reg(sd, type, reg, vd); } void set_reg(dumb_ptr sd, VariableCode type, SIR reg, RString zd) { struct script_data vd = ScriptDataStr{zd}; set_reg(sd, type, reg, vd); } /*========================================== * 文字列への変換 *------------------------------------------ */ RString conv_str(ScriptState *st, struct script_data *data) { get_val(st, data); assert (!data->is()); if (auto *u = data->get_if()) { AString buf = STRPRINTF("%d"_fmt, u->numi); *data = ScriptDataStr{buf}; } return data->get_if()->str; } /*========================================== * 数値へ変換 *------------------------------------------ */ int conv_num(ScriptState *st, struct script_data *data) { int rv = 0; get_val(st, data); assert (!data->is()); MATCH (*data) { default: abort(); CASE (const ScriptDataStr&, u) { RString p = u.str; rv = atoi(p.c_str()); } CASE (const ScriptDataInt&, u) { return u.numi; } CASE (const ScriptDataPos&, u) { return u.numi; } } *data = ScriptDataInt{rv}; return rv; } Borrowed conv_script(ScriptState *st, struct script_data *data) { get_val(st, data); return data->get_if()->script; } void push_copy(struct script_stack *stack, int pos_) { script_data csd = stack->stack_datav[pos_]; stack->stack_datav.push_back(csd); } void pop_stack(struct script_stack *stack, int start, int end) { auto it = stack->stack_datav.begin(); stack->stack_datav.erase(it + start, it + end); } // // 実行部main // /*========================================== * コマンドの読み取り *------------------------------------------ */ static ByteCode get_com(ScriptPointer *script) { if (static_cast(script->peek()) >= 0x80) { // synthetic! Does not advance pos yet. return ByteCode::INT; } return script->pop(); } /*========================================== * 数値の所得 *------------------------------------------ */ static int get_num(ScriptPointer *scr) { int i = 0; int j = 0; uint8_t val; do { val = static_cast(scr->pop()); i += (val & 0x7f) << j; j += 6; } while (val >= 0xc0); return i; } /*========================================== * スタックから値を取り出す *------------------------------------------ */ static int pop_val(ScriptState *st) { if (st->stack->stack_datav.empty()) return 0; script_data& back = st->stack->stack_datav.back(); get_val(st, &back); int rv = 0; if (auto *u = back.get_if()) rv = u->numi; st->stack->stack_datav.pop_back(); return rv; } static bool isstr(struct script_data& c) { return c.is(); } /*========================================== * 加算演算子 *------------------------------------------ */ static void op_add(ScriptState *st) { get_val(st, &st->stack->stack_datav.back()); script_data back = st->stack->stack_datav.back(); st->stack->stack_datav.pop_back(); script_data& back1 = st->stack->stack_datav.back(); get_val(st, &back1); if (!(isstr(back) || isstr(back1))) { back1.get_if()->numi += back.get_if()->numi; } else { RString sb = conv_str(st, &back); RString sb1 = conv_str(st, &back1); MString buf; buf += sb1; buf += sb; back1 = ScriptDataStr{.str= AString(buf)}; } } /*========================================== * 二項演算子(文字列) *------------------------------------------ */ static void op_2str(ScriptState *st, ByteCode op, ZString s1, ZString s2) { int a = 0; switch (op) { case ByteCode::EQ: a = s1 == s2; break; case ByteCode::NE: a = s1 != s2; break; case ByteCode::GT: a = s1 > s2; break; case ByteCode::GE: a = s1 >= s2; break; case ByteCode::LT: a = s1 < s2; break; case ByteCode::LE: a = s1 <= s2; break; default: PRINTF("illegal string operater\n"_fmt); break; } push_int(st->stack, a); } /*========================================== * 二項演算子(数値) *------------------------------------------ */ static void op_2num(ScriptState *st, ByteCode op, int i1, int i2) { switch (op) { case ByteCode::SUB: i1 -= i2; break; case ByteCode::MUL: i1 *= i2; break; case ByteCode::DIV: i1 /= i2; break; case ByteCode::MOD: i1 %= i2; break; case ByteCode::AND: i1 &= i2; break; case ByteCode::OR: i1 |= i2; break; case ByteCode::XOR: i1 ^= i2; break; case ByteCode::LAND: i1 = i1 && i2; break; case ByteCode::LOR: i1 = i1 || i2; break; case ByteCode::EQ: i1 = i1 == i2; break; case ByteCode::NE: i1 = i1 != i2; break; case ByteCode::GT: i1 = i1 > i2; break; case ByteCode::GE: i1 = i1 >= i2; break; case ByteCode::LT: i1 = i1 < i2; break; case ByteCode::LE: i1 = i1 <= i2; break; case ByteCode::R_SHIFT: i1 = i1 >> i2; break; case ByteCode::L_SHIFT: i1 = i1 << i2; break; } push_int(st->stack, i1); } /*========================================== * 二項演算子 *------------------------------------------ */ static void op_2(ScriptState *st, ByteCode op) { // pop_val has unfortunate implications here script_data d2 = st->stack->stack_datav.back(); st->stack->stack_datav.pop_back(); get_val(st, &d2); script_data d1 = st->stack->stack_datav.back(); st->stack->stack_datav.pop_back(); get_val(st, &d1); if (isstr(d1) && isstr(d2)) { // ss => op_2str op_2str(st, op, d1.get_if()->str, d2.get_if()->str); } else if (!(isstr(d1) || isstr(d2))) { // ii => op_2num op_2num(st, op, d1.get_if()->numi, d2.get_if()->numi); } else { // si,is => error PRINTF("script: op_2: int&str, str&int not allow.\n"_fmt); push_int(st->stack, 0); } } /*========================================== * 単項演算子 *------------------------------------------ */ static void op_1num(ScriptState *st, ByteCode op) { int i1; i1 = pop_val(st); switch (op) { case ByteCode::NEG: i1 = -i1; break; case ByteCode::NOT: i1 = ~i1; break; case ByteCode::LNOT: i1 = !i1; break; } push_int(st->stack, i1); } /*========================================== * 関数の実行 *------------------------------------------ */ void run_func(ScriptState *st) { size_t end_sp = st->stack->stack_datav.size(); size_t start_sp = end_sp - 1; while (!st->stack->stack_datav[start_sp].is()) { start_sp--; if (start_sp == 0) { if (battle_config.error_log) PRINTF("function not found\n"_fmt); st->state = ScriptEndState::END; return; } } // the func is before the arg start_sp--; st->start = start_sp; st->end = end_sp; if (!st->stack->stack_datav[st->start].is()) { PRINTF("run_func: not function and command! \n"_fmt); st->state = ScriptEndState::END; return; } size_t func = st->stack->stack_datav[st->start].get_if()->numi; if (DEBUG_RUN && battle_config.etc_log) { PRINTF("run_func : %s\n"_fmt, builtin_functions[func].name); PRINTF("stack dump :"_fmt); for (script_data& d : st->stack->stack_datav) { MATCH (d) { CASE (const ScriptDataInt&, u) { PRINTF(" int(%d)"_fmt, u.numi); } CASE (const ScriptDataRetInfo&, u) { PRINTF(" retinfo(%p)"_fmt, static_cast(&*u.script)); } CASE (const ScriptDataParam&, u) { PRINTF(" param(%d)"_fmt, u.reg.sp()); } CASE (const ScriptDataVariable&, u) { PRINTF(" name(%s)[%d]"_fmt, variable_names.outtern(u.reg.base()), u.reg.index()); } CASE (const ScriptDataArg&, u) { (void)u; PRINTF(" arg"_fmt); } CASE (const ScriptDataPos&, u) { (void)u; PRINTF(" pos(%d)"_fmt, u.numi); } CASE (const ScriptDataStr&, u) { (void)u; PRINTF(" str(%s)"_fmt, u.str); } CASE (const ScriptDataFuncRef&, u) { (void)u; PRINTF(" func(%s)"_fmt, builtin_functions[u.numi].name); } } } PRINTF("\n"_fmt); } builtin_functions[func].func(st); pop_stack(st->stack, start_sp, end_sp); if (st->state == ScriptEndState::RETFUNC) { // ユーザー定義関数からの復帰 int olddefsp = st->defsp; pop_stack(st->stack, st->defsp, start_sp); // 復帰に邪魔なスタック削除 if (st->defsp < 4 || !st->stack->stack_datav[st->defsp - 1].is()) { PRINTF("script:run_func (return) return without callfunc or callsub!\n"_fmt); st->state = ScriptEndState::END; return; } assert (olddefsp == st->defsp); // pretty sure it hasn't changed yet st->scriptp.code = Some(conv_script(st, &st->stack->stack_datav[olddefsp - 1])); // スクリプトを復元 st->scriptp.pos = conv_num(st, &st->stack->stack_datav[olddefsp - 2]); // スクリプト位置の復元 st->defsp = conv_num(st, &st->stack->stack_datav[olddefsp - 3]); // 基準スタックポインタを復元 // Number of arguments. int i = conv_num(st, &st->stack->stack_datav[olddefsp - 4]); // 引数の数所得 assert (i == 0); pop_stack(st->stack, olddefsp - 4 - i, olddefsp); // 要らなくなったスタック(引数と復帰用データ)削除 st->state = ScriptEndState::GOTO; } } // pretend it's external so this can be called in the debugger void dump_script(Borrowed script); void dump_script(Borrowed script) { ScriptPointer scriptp(script, 0); while (scriptp.pos < reinterpret_cast *>(&*script)->size()) { PRINTF("%6zu: "_fmt, scriptp.pos); switch (ByteCode c = get_com(&scriptp)) { case ByteCode::EOL: PRINTF("EOL\n"_fmt); // extra newline between functions break; case ByteCode::INT: // synthesized! PRINTF("INT %d"_fmt, get_num(&scriptp)); break; case ByteCode::POS: case ByteCode::VARIABLE: case ByteCode::FUNC_REF: case ByteCode::PARAM: { int arg = 0; arg |= static_cast(scriptp.pop()) << 0; arg |= static_cast(scriptp.pop()) << 8; arg |= static_cast(scriptp.pop()) << 16; switch(c) { case ByteCode::POS: PRINTF("POS %d"_fmt, arg); break; case ByteCode::VARIABLE: PRINTF("VARIABLE %s"_fmt, variable_names.outtern(arg)); break; case ByteCode::FUNC_REF: PRINTF("FUNC_REF %s"_fmt, builtin_functions[arg].name); break; case ByteCode::PARAM: PRINTF("PARAM SP::#%d (sorry)"_fmt, arg); break; } } break; case ByteCode::ARG: PRINTF("ARG"_fmt); break; case ByteCode::STR: PRINTF("STR \"%s\""_fmt, scriptp.pops()); break; case ByteCode::FUNC: PRINTF("FUNC"_fmt); break; case ByteCode::ADD: PRINTF("ADD"_fmt); break; case ByteCode::SUB: PRINTF("SUB"_fmt); break; case ByteCode::MUL: PRINTF("MUL"_fmt); break; case ByteCode::DIV: PRINTF("DIV"_fmt); break; case ByteCode::MOD: PRINTF("MOD"_fmt); break; case ByteCode::EQ: PRINTF("EQ"_fmt); break; case ByteCode::NE: PRINTF("NE"_fmt); break; case ByteCode::GT: PRINTF("GT"_fmt); break; case ByteCode::GE: PRINTF("GE"_fmt); break; case ByteCode::LT: PRINTF("LT"_fmt); break; case ByteCode::LE: PRINTF("LE"_fmt); break; case ByteCode::AND: PRINTF("AND"_fmt); break; case ByteCode::OR: PRINTF("OR"_fmt); break; case ByteCode::XOR: PRINTF("XOR"_fmt); break; case ByteCode::LAND: PRINTF("LAND"_fmt); break; case ByteCode::LOR: PRINTF("LOR"_fmt); break; case ByteCode::R_SHIFT: PRINTF("R_SHIFT"_fmt); break; case ByteCode::L_SHIFT: PRINTF("L_SHIFT"_fmt); break; case ByteCode::NEG: PRINTF("NEG"_fmt); break; case ByteCode::NOT: PRINTF("NOT"_fmt); break; case ByteCode::LNOT: PRINTF("LNOT"_fmt); break; case ByteCode::NOP: PRINTF("NOP"_fmt); break; default: PRINTF("??? %d"_fmt, c); break; } PRINTF("\n"_fmt); } } /*========================================== * スクリプトの実行メイン部分 *------------------------------------------ */ static void run_script_main(ScriptState *st, Borrowed rootscript) { int cmdcount = script_config.check_cmdcount; int gotocount = script_config.check_gotocount; struct script_stack *stack = st->stack; st->defsp = stack->stack_datav.size(); int rerun_pos = st->scriptp.pos; st->state = ScriptEndState::ZERO; while (st->state == ScriptEndState::ZERO) { switch (ByteCode c = get_com(&st->scriptp)) { case ByteCode::EOL: if (stack->stack_datav.size() != st->defsp) { if (true) PRINTF("stack.sp (%zu) != default (%d)\n"_fmt, stack->stack_datav.size(), st->defsp); abort(); } rerun_pos = st->scriptp.pos; break; case ByteCode::INT: // synthesized! push_int(stack, get_num(&st->scriptp)); break; case ByteCode::POS: case ByteCode::VARIABLE: case ByteCode::FUNC_REF: case ByteCode::PARAM: // Note that these 3 have *very* different meanings, // despite being encoded similarly. { int arg = 0; arg |= static_cast(st->scriptp.pop()) << 0; arg |= static_cast(st->scriptp.pop()) << 8; arg |= static_cast(st->scriptp.pop()) << 16; switch(c) { case ByteCode::POS: push_int(stack, arg); break; case ByteCode::VARIABLE: push_reg(stack, SIR::from(arg)); break; case ByteCode::FUNC_REF: push_int(stack, arg); break; case ByteCode::PARAM: SP arg_sp = static_cast(arg); push_reg(stack, SIR::from(arg_sp)); break; } } break; case ByteCode::ARG: push_int(stack, 0); break; case ByteCode::STR: push_str(stack, st->scriptp.pops()); break; case ByteCode::FUNC: run_func(st); if (st->state == ScriptEndState::GOTO) { rerun_pos = st->scriptp.pos; st->state = ScriptEndState::ZERO; if (gotocount > 0 && (--gotocount) <= 0) { PRINTF("run_script: infinity loop !\n"_fmt); st->state = ScriptEndState::END; } } break; case ByteCode::ADD: op_add(st); break; case ByteCode::SUB: case ByteCode::MUL: case ByteCode::DIV: case ByteCode::MOD: case ByteCode::EQ: case ByteCode::NE: case ByteCode::GT: case ByteCode::GE: case ByteCode::LT: case ByteCode::LE: case ByteCode::AND: case ByteCode::OR: case ByteCode::XOR: case ByteCode::LAND: case ByteCode::LOR: case ByteCode::R_SHIFT: case ByteCode::L_SHIFT: op_2(st, c); break; case ByteCode::NEG: case ByteCode::NOT: case ByteCode::LNOT: op_1num(st, c); break; case ByteCode::NOP: st->state = ScriptEndState::END; break; default: if (battle_config.error_log) PRINTF("unknown command : %d @ %zu\n"_fmt, c, st->scriptp.pos); st->state = ScriptEndState::END; break; } if (cmdcount > 0 && (--cmdcount) <= 0) { PRINTF("run_script: infinity loop !\n"_fmt); st->state = ScriptEndState::END; } } switch (st->state) { case ScriptEndState::STOP: break; case ScriptEndState::END: { dumb_ptr sd = map_id2sd(st->rid); st->scriptp.code = None; st->scriptp.pos = -1; if (sd && sd->npc_id == st->oid) npc_event_dequeue(sd); } break; case ScriptEndState::RERUNLINE: st->scriptp.pos = rerun_pos; break; } if (st->state != ScriptEndState::END) { // 再開するためにスタック情報を保存 dumb_ptr sd = map_id2sd(st->rid); if (sd) { sd->npc_stackbuf = stack->stack_datav; sd->npc_script = st->scriptp.code; // sd->npc_pos is set later ... ??? sd->npc_scriptroot = Some(rootscript); } } } /*========================================== * スクリプトの実行 *------------------------------------------ */ int run_script(ScriptPointer sp, BlockId rid, BlockId oid) { return run_script_l(sp, rid, oid, nullptr); } int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid, Slice args) { struct script_stack stack; ScriptState st; dumb_ptr sd = map_id2sd(rid); P rootscript = TRY_UNWRAP(sp.code, return -1); int i; if (sp.pos >> 24) return -1; if (sd && !sd->npc_stackbuf.empty() && sd->npc_scriptroot == Some(rootscript)) { // 前回のスタックを復帰 sp.code = sd->npc_script; stack.stack_datav = std::move(sd->npc_stackbuf); } st.stack = &stack; st.scriptp = sp; st.rid = rid; st.oid = oid; for (i = 0; i < args.size(); i++) { if (args[i].name.back() == '$') pc_setregstr(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.s); else pc_setreg(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.i); } run_script_main(&st, rootscript); stack.stack_datav.clear(); return st.scriptp.pos; } void set_script_var_i(dumb_ptr sd, VarName var, int e, int val) { size_t k = variable_names.intern(var); SIR reg = SIR::from(k, e); set_reg(sd, VariableCode::VARIABLE, reg, val); } void set_script_var_s(dumb_ptr sd, VarName var, int e, XString val) { size_t k = variable_names.intern(var); SIR reg = SIR::from(k, e); set_reg(sd, VariableCode::VARIABLE, reg, val); } int get_script_var_i(dumb_ptr sd, VarName var, int e) { size_t k = variable_names.intern(var); SIR reg = SIR::from(k, e); struct script_data dat = ScriptDataVariable{.reg= reg}; get_val(sd, &dat); if (auto *u = dat.get_if()) return u->numi; PRINTF("Warning: you lied about the type and I'm too lazy to fix it!"_fmt); return 0; } ZString get_script_var_s(dumb_ptr sd, VarName var, int e) { size_t k = variable_names.intern(var); SIR reg = SIR::from(k, e); struct script_data dat = ScriptDataVariable{.reg= reg}; get_val(sd, &dat); if (auto *u = dat.get_if()) // this is almost certainly a memory leak after CONSTSTR removal return u->str; PRINTF("Warning: you lied about the type and I can't fix it!"_fmt); return ZString(); } } // namespace tmwa