/**
* This file is part of Hercules.
* http://herc.ws - http://github.com/HerculesWS/Hercules
*
* Copyright (C) 2012-2018 Hercules Dev Team
* Copyright (C) Athena Dev Teams
*
* Hercules 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 .
*/
#define HERCULES_CORE
#include "config/core.h" // RENEWAL, RENEWAL_ASPD, RENEWAL_CAST, RENEWAL_DROP, RENEWAL_EDP, RENEWAL_EXP, RENEWAL_LVDMG, SCRIPT_CALLFUNC_CHECK, SECURE_NPCTIMEOUT, SECURE_NPCTIMEOUT_INTERVAL
#include "script.h"
#include "map/atcommand.h"
#include "map/battle.h"
#include "map/battleground.h"
#include "map/channel.h"
#include "map/chat.h"
#include "map/chrif.h"
#include "map/clan.h"
#include "map/clif.h"
#include "map/date.h"
#include "map/elemental.h"
#include "map/guild.h"
#include "map/homunculus.h"
#include "map/instance.h"
#include "map/intif.h"
#include "map/itemdb.h"
#include "map/log.h"
#include "map/mail.h"
#include "map/map.h"
#include "map/mapreg.h"
#include "map/mercenary.h"
#include "map/messages.h"
#include "map/mob.h"
#include "map/npc.h"
#include "map/party.h"
#include "map/path.h"
#include "map/pc.h"
#include "map/pet.h"
#include "map/pet.h"
#include "map/quest.h"
#include "map/skill.h"
#include "map/status.h"
#include "map/status.h"
#include "map/storage.h"
#include "map/unit.h"
#include "common/cbasetypes.h"
#include "common/conf.h"
#include "common/db.h"
#include "common/memmgr.h"
#include "common/md5calc.h"
#include "common/mmo.h" // NEW_CARTS
#include "common/nullpo.h"
#include "common/random.h"
#include "common/showmsg.h"
#include "common/socket.h" // usage: getcharip
#include "common/sql.h"
#include "common/strlib.h"
#include "common/sysinfo.h"
#include "common/timer.h"
#include "common/utils.h"
#include "common/HPM.h"
#include
#include
#include
#include
#include
#ifndef WIN32
#include
#endif
static struct script_interface script_s;
struct script_interface *script;
static inline int GETVALUE(const struct script_buf *buf, int i) __attribute__((nonnull (1)));
static inline int GETVALUE(const struct script_buf *buf, int i)
{
Assert_ret(VECTOR_LENGTH(*buf) > i + 2);
return (int)MakeDWord(MakeWord(VECTOR_INDEX(*buf, i), VECTOR_INDEX(*buf, i+1)),
MakeWord(VECTOR_INDEX(*buf, i+2), 0));
}
static inline void SETVALUE(struct script_buf *buf, int i, int n) __attribute__((nonnull (1)));
static inline void SETVALUE(struct script_buf *buf, int i, int n)
{
Assert_retv(VECTOR_LENGTH(*buf) > i + 2);
VECTOR_INDEX(*buf, i) = GetByte(n, 0);
VECTOR_INDEX(*buf, i+1) = GetByte(n, 1);
VECTOR_INDEX(*buf, i+2) = GetByte(n, 2);
}
const char *script_op2name(int op)
{
#define RETURN_OP_NAME(type) case type: return #type
switch( op ) {
RETURN_OP_NAME(C_NOP);
RETURN_OP_NAME(C_POS);
RETURN_OP_NAME(C_INT);
RETURN_OP_NAME(C_PARAM);
RETURN_OP_NAME(C_FUNC);
RETURN_OP_NAME(C_STR);
RETURN_OP_NAME(C_CONSTSTR);
RETURN_OP_NAME(C_ARG);
RETURN_OP_NAME(C_NAME);
RETURN_OP_NAME(C_EOL);
RETURN_OP_NAME(C_RETINFO);
RETURN_OP_NAME(C_USERFUNC);
RETURN_OP_NAME(C_USERFUNC_POS);
RETURN_OP_NAME(C_REF);
RETURN_OP_NAME(C_LSTR);
// operators
RETURN_OP_NAME(C_OP3);
RETURN_OP_NAME(C_LOR);
RETURN_OP_NAME(C_LAND);
RETURN_OP_NAME(C_LE);
RETURN_OP_NAME(C_LT);
RETURN_OP_NAME(C_GE);
RETURN_OP_NAME(C_GT);
RETURN_OP_NAME(C_EQ);
RETURN_OP_NAME(C_NE);
RETURN_OP_NAME(C_XOR);
RETURN_OP_NAME(C_OR);
RETURN_OP_NAME(C_AND);
RETURN_OP_NAME(C_ADD);
RETURN_OP_NAME(C_SUB);
RETURN_OP_NAME(C_MUL);
RETURN_OP_NAME(C_POW);
RETURN_OP_NAME(C_DIV);
RETURN_OP_NAME(C_MOD);
RETURN_OP_NAME(C_NEG);
RETURN_OP_NAME(C_LNOT);
RETURN_OP_NAME(C_NOT);
RETURN_OP_NAME(C_R_SHIFT);
RETURN_OP_NAME(C_L_SHIFT);
RETURN_OP_NAME(C_ADD_POST);
RETURN_OP_NAME(C_SUB_POST);
RETURN_OP_NAME(C_ADD_PRE);
RETURN_OP_NAME(C_SUB_PRE);
RETURN_OP_NAME(C_RE_EQ);
RETURN_OP_NAME(C_RE_NE);
default:
ShowDebug("script_op2name: unexpected op=%d\n", op);
return "???";
}
#undef RETURN_OP_NAME
}
#ifdef SCRIPT_DEBUG_DUMP_STACK
static void script_dump_stack(struct script_state *st)
{
int i;
nullpo_retv(st);
ShowMessage("\tstart = %d\n", st->start);
ShowMessage("\tend = %d\n", st->end);
ShowMessage("\tdefsp = %d\n", st->stack->defsp);
ShowMessage("\tsp = %d\n", st->stack->sp);
for( i = 0; i < st->stack->sp; ++i )
{
struct script_data* data = &st->stack->stack_data[i];
ShowMessage("\t[%d] %s", i, script->op2name(data->type));
switch( data->type )
{
case C_INT:
case C_POS:
ShowMessage(" %d\n", data->u.num);
break;
case C_STR:
case C_CONSTSTR:
ShowMessage(" \"%s\"\n", data->u.str);
break;
case C_NAME:
ShowMessage(" \"%s\" (id=%d ref=%p subtype=%s)\n", reference_getname(data), data->u.num, data->ref, script->op2name(script->str_data[data->u.num].type));
break;
case C_RETINFO:
{
struct script_retinfo* ri = data->u.ri;
ShowMessage(" %p {scope.vars=%p, scope.arrays=%p, script=%p, pos=%d, nargs=%d, defsp=%d}\n", ri, ri->scope.vars, ri->scope.arrays, ri->script, ri->pos, ri->nargs, ri->defsp);
}
break;
default:
ShowMessage("\n");
break;
}
}
}
#endif
/// Reports on the console the src of a script error.
static void script_reportsrc(struct script_state *st)
{
struct block_list* bl;
nullpo_retv(st);
if( st->oid == 0 )
return; //Can't report source.
bl = map->id2bl(st->oid);
if( bl == NULL )
return;
switch( bl->type ) {
case BL_NPC:
{
const struct npc_data *nd = BL_UCCAST(BL_NPC, bl);
if (bl->m >= 0)
ShowDebug("Source (NPC): %s at %s (%d,%d)\n", nd->name, map->list[bl->m].name, bl->x, bl->y);
else
ShowDebug("Source (NPC): %s (invisible/not on a map)\n", nd->name);
}
break;
default:
if( bl->m >= 0 )
ShowDebug("Source (Non-NPC type %u): name %s at %s (%d,%d)\n", bl->type, clif->get_bl_name(bl), map->list[bl->m].name, bl->x, bl->y);
else
ShowDebug("Source (Non-NPC type %u): name %s (invisible/not on a map)\n", bl->type, clif->get_bl_name(bl));
break;
}
}
/// Reports on the console information about the script data.
static void script_reportdata(struct script_data *data)
{
if( data == NULL )
return;
switch( data->type ) {
case C_NOP:// no value
ShowDebug("Data: nothing (nil)\n");
break;
case C_INT:// number
ShowDebug("Data: number value=%"PRId64"\n", data->u.num);
break;
case C_STR:
case C_CONSTSTR:// string
if( data->u.str ) {
ShowDebug("Data: string value=\"%s\"\n", data->u.str);
} else {
ShowDebug("Data: string value=NULL\n");
}
break;
case C_NAME:// reference
if( reference_tovariable(data) ) {// variable
const char* name = reference_getname(data);
ShowDebug("Data: variable name='%s' index=%u\n", name, reference_getindex(data));
} else if( reference_toconstant(data) ) {// constant
ShowDebug("Data: constant name='%s' value=%d\n", reference_getname(data), reference_getconstant(data));
} else if( reference_toparam(data) ) {// param
ShowDebug("Data: param name='%s' type=%d\n", reference_getname(data), reference_getparamtype(data));
} else {// ???
ShowDebug("Data: reference name='%s' type=%s\n", reference_getname(data), script->op2name(data->type));
ShowDebug("Please report this!!! - script->str_data.type=%s\n", script->op2name(script->str_data[reference_getid(data)].type));
}
break;
case C_POS:// label
ShowDebug("Data: label pos=%"PRId64"\n", data->u.num);
break;
default:
ShowDebug("Data: %s\n", script->op2name(data->type));
break;
}
}
/// Reports on the console information about the current built-in function.
static void script_reportfunc(struct script_state *st)
{
int params, id;
struct script_data* data;
if (!script_hasdata(st,0)) {
// no stack
return;
}
data = script_getdata(st,0);
if (!data_isreference(data) || script->str_data[reference_getid(data)].type != C_FUNC) {
// script currently not executing a built-in function or corrupt stack
return;
}
id = reference_getid(data);
params = script_lastdata(st)-1;
if (params > 0) {
int i;
ShowDebug("Function: %s (%d parameter%s):\n", script->get_str(id), params, ( params == 1 ) ? "" : "s");
for (i = 2; i <= script_lastdata(st); i++) {
script->reportdata(script_getdata(st,i));
}
} else {
ShowDebug("Function: %s (no parameters)\n", script->get_str(id));
}
}
/*==========================================
* Output error message
*------------------------------------------*/
static void disp_error_message2(const char *mes, const char *pos, int report) __attribute__((nonnull (1))) analyzer_noreturn;
static void disp_error_message2(const char *mes, const char *pos, int report)
{
script->error_msg = aStrdup(mes);
script->error_pos = pos;
script->error_report = report;
longjmp( script->error_jump, 1 );
}
#define disp_error_message(mes,pos) (disp_error_message2((mes),(pos),1))
static void disp_warning_message(const char *mes, const char *pos)
{
script->warning(script->parser_current_src,script->parser_current_file,script->parser_current_line,mes,pos);
}
/// Checks event parameter validity
static void check_event(struct script_state *st, const char *evt)
{
if( evt && evt[0] && !stristr(evt, "::On") )
{
ShowWarning("NPC event parameter deprecated! Please use 'NPCNAME::OnEVENT' instead of '%s'.\n", evt);
script->reportsrc(st);
}
}
/*==========================================
* Hashes the input string
*------------------------------------------*/
static unsigned int calc_hash(const char *p)
{
unsigned int h;
nullpo_ret(p);
#if defined(SCRIPT_HASH_DJB2)
h = 5381;
while( *p ) // hash*33 + c
h = ( h << 5 ) + h + ((unsigned char)(*p++));
#elif defined(SCRIPT_HASH_SDBM)
h = 0;
while( *p ) // hash*65599 + c
h = ( h << 6 ) + ( h << 16 ) - h + ((unsigned char)(*p++));
#elif defined(SCRIPT_HASH_ELF) // UNIX ELF hash
h = 0;
while( *p ) {
unsigned int g;
h = ( h << 4 ) + ((unsigned char)(*p++));
g = h & 0xF0000000;
if( g ) {
h ^= g >> 24;
h &= ~g;
}
}
#else // athena hash
h = 0;
while( *p )
h = ( h << 1 ) + ( h >> 3 ) + ( h >> 5 ) + ( h >> 8 ) + (unsigned char)(*p++);
#endif
return h % SCRIPT_HASH_SIZE;
}
/*==========================================
* Hashes the input string in a case insensitive way
*------------------------------------------*/
static unsigned int calc_hash_ci(const char *p)
{
unsigned int h = 0;
#ifdef ENABLE_CASE_CHECK
nullpo_ret(p);
#if defined(SCRIPT_HASH_DJB2)
h = 5381;
while( *p ) // hash*33 + c
h = ( h << 5 ) + h + ((unsigned char)TOLOWER(*p++));
#elif defined(SCRIPT_HASH_SDBM)
h = 0;
while( *p ) // hash*65599 + c
h = ( h << 6 ) + ( h << 16 ) - h + ((unsigned char)TOLOWER(*p++));
#elif defined(SCRIPT_HASH_ELF) // UNIX ELF hash
h = 0;
while( *p ) {
unsigned int g;
h = ( h << 4 ) + ((unsigned char)TOLOWER(*p++));
g = h & 0xF0000000;
if( g ) {
h ^= g >> 24;
h &= ~g;
}
}
#else // athena hash
h = 0;
while( *p )
h = ( h << 1 ) + ( h >> 3 ) + ( h >> 5 ) + ( h >> 8 ) + (unsigned char)TOLOWER(*p++);
#endif
#endif // ENABLE_CASE_CHECK
return h % SCRIPT_HASH_SIZE;
}
/*==========================================
* script->str_data manipulation functions
*------------------------------------------*/
/// Looks up string using the provided id.
static const char *script_get_str(int id)
{
Assert_retr(NULL, id >= LABEL_START && id < script->str_size);
return script->str_buf+script->str_data[id].str;
}
/// Returns the uid of the string, or -1.
static int script_search_str(const char *p)
{
int i;
for( i = script->str_hash[script->calc_hash(p)]; i != 0; i = script->str_data[i].next ) {
if( strcmp(script->get_str(i),p) == 0 ) {
return i;
}
}
return -1;
}
static void script_casecheck_clear_sub(struct casecheck_data *ccd)
{
#ifdef ENABLE_CASE_CHECK
nullpo_retv(ccd);
if (ccd->str_data) {
aFree(ccd->str_data);
ccd->str_data = NULL;
}
ccd->str_data_size = 0;
ccd->str_num = 1;
if (ccd->str_buf) {
aFree(ccd->str_buf);
ccd->str_buf = NULL;
}
ccd->str_pos = 0;
ccd->str_size = 0;
memset(ccd->str_hash, 0, sizeof(ccd->str_hash));
#endif // ENABLE_CASE_CHECK
}
static void script_global_casecheck_clear(void)
{
script_casecheck_clear_sub(&script->global_casecheck);
}
static void script_local_casecheck_clear(void)
{
script_casecheck_clear_sub(&script->local_casecheck);
}
static const char *script_casecheck_add_str_sub(struct casecheck_data *ccd, const char *p)
{
#ifdef ENABLE_CASE_CHECK
int len;
int h = script->calc_hash_ci(p);
nullpo_retr(NULL, ccd);
if (ccd->str_hash[h] == 0) {
//empty bucket, add new node here
ccd->str_hash[h] = ccd->str_num;
} else {
int i;
for (i = ccd->str_hash[h]; ; i = ccd->str_data[i].next) {
const char *s = NULL;
Assert_retb(i >= 0 && i < ccd->str_size);
s = ccd->str_buf+ccd->str_data[i].str;
if (strcasecmp(s,p) == 0) {
return s; // string already in list
}
if (ccd->str_data[i].next == 0)
break; // reached the end
}
// append node to end of list
ccd->str_data[i].next = ccd->str_num;
}
// grow list if neccessary
if( ccd->str_num >= ccd->str_data_size ) {
ccd->str_data_size += 1280;
RECREATE(ccd->str_data,struct str_data_struct,ccd->str_data_size);
memset(ccd->str_data + (ccd->str_data_size - 1280), '\0', 1280);
}
len=(int)strlen(p);
// grow string buffer if neccessary
while( ccd->str_pos+len+1 >= ccd->str_size ) {
ccd->str_size += 10240;
RECREATE(ccd->str_buf,char,ccd->str_size);
memset(ccd->str_buf + (ccd->str_size - 10240), '\0', 10240);
}
safestrncpy(ccd->str_buf+ccd->str_pos, p, len+1);
ccd->str_data[ccd->str_num].type = C_NOP;
ccd->str_data[ccd->str_num].str = ccd->str_pos;
ccd->str_data[ccd->str_num].val = 0;
ccd->str_data[ccd->str_num].next = 0;
ccd->str_data[ccd->str_num].func = NULL;
ccd->str_data[ccd->str_num].backpatch = -1;
ccd->str_data[ccd->str_num].label = -1;
ccd->str_pos += len+1;
ccd->str_num++;
#endif // ENABLE_CASE_CHECK
return NULL;
}
static const char *script_global_casecheck_add_str(const char *p)
{
return script_casecheck_add_str_sub(&script->global_casecheck, p);
}
static const char *script_local_casecheck_add_str(const char *p)
{
return script_casecheck_add_str_sub(&script->local_casecheck, p);
}
/// Stores a copy of the string and returns its id.
/// If an identical string is already present, returns its id instead.
static int script_add_str(const char *p)
{
int len, h = script->calc_hash(p);
#ifdef ENABLE_CASE_CHECK
const char *existingentry = NULL;
#endif // ENABLE_CASE_CHECK
if (script->str_hash[h] == 0) {
// empty bucket, add new node here
script->str_hash[h] = script->str_num;
} else {
// scan for end of list, or occurence of identical string
int i;
for (i = script->str_hash[h]; ; i = script->str_data[i].next) {
if( strcmp(script->get_str(i),p) == 0 ) {
return i; // string already in list
}
if( script->str_data[i].next == 0 )
break; // reached the end
}
// append node to end of list
script->str_data[i].next = script->str_num;
}
#ifdef ENABLE_CASE_CHECK
if( (strncmp(p, ".@", 2) == 0) ) {
// Local scope vars are checked separately to decrease false positives
existingentry = script->local_casecheck.add_str(p);
} else {
existingentry = script->global_casecheck.add_str(p);
if( existingentry ) {
if( strcasecmp(p, "disguise") == 0 || strcasecmp(p, "Poison_Spore") == 0
|| strcasecmp(p, "PecoPeco_Egg") == 0 || strcasecmp(p, "Soccer_Ball") == 0
|| strcasecmp(p, "Horn") == 0 || strcasecmp(p, "Treasure_Box_") == 0
|| strcasecmp(p, "Lord_of_Death") == 0
) // Known duplicates, don't bother warning the user
existingentry = NULL;
}
}
if( existingentry ) {
DeprecationCaseWarning("script_add_str", p, existingentry, script->parser_current_file); // TODO
}
#endif // ENABLE_CASE_CHECK
// grow list if neccessary
if( script->str_num >= script->str_data_size ) {
script->str_data_size += 1280;
RECREATE(script->str_data,struct str_data_struct,script->str_data_size);
memset(script->str_data + (script->str_data_size - 1280), '\0', 1280);
}
len=(int)strlen(p);
// grow string buffer if neccessary
while( script->str_pos+len+1 >= script->str_size ) {
script->str_size += 10240;
RECREATE(script->str_buf,char,script->str_size);
memset(script->str_buf + (script->str_size - 10240), '\0', 10240);
}
safestrncpy(script->str_buf+script->str_pos, p, len+1);
script->str_data[script->str_num].type = C_NOP;
script->str_data[script->str_num].str = script->str_pos;
script->str_data[script->str_num].val = 0;
script->str_data[script->str_num].next = 0;
script->str_data[script->str_num].func = NULL;
script->str_data[script->str_num].backpatch = -1;
script->str_data[script->str_num].label = -1;
script->str_pos += len+1;
return script->str_num++;
}
/**
* Appends 1 byte to the script buffer.
*
* @param a The byte to append.
*/
static void add_scriptb(int a)
{
VECTOR_ENSURE(script->buf, 1, SCRIPT_BLOCK_SIZE);
VECTOR_PUSH(script->buf, (uint8)a);
}
/**
* Appends a c_op value to the script buffer.
*
* The value is variable-length encoded into 8-bit blocks.
* The encoding scheme is ( 01?????? )* 00??????, LSB first.
* All blocks but the last hold 7 bits of data, topmost bit is always 1 (carries).
*
* @param a The value to append.
*/
static void add_scriptc(int a)
{
while( a >= 0x40 )
{
script->addb((a&0x3f)|0x40);
a = (a - 0x40) >> 6;
}
script->addb(a);
}
/**
* Appends an integer value to the script buffer.
*
* The value is variable-length encoded into 8-bit blocks.
* The encoding scheme is ( 11?????? )* 10??????, LSB first.
* All blocks but the last hold 7 bits of data, topmost bit is always 1 (carries).
*
* @param a The value to append.
*/
static void add_scripti(int a)
{
while( a >= 0x40 )
{
script->addb((a&0x3f)|0xc0);
a = (a - 0x40) >> 6;
}
script->addb(a|0x80);
}
/**
* Appends a script->str_data object (label/function/variable/integer) to the script buffer.
*
* @param l The id of the script->str_data entry (Maximum up to 16M)
*/
static void add_scriptl(int l)
{
int backpatch = script->str_data[l].backpatch;
switch(script->str_data[l].type) {
case C_POS:
case C_USERFUNC_POS:
script->addc(C_POS);
script->addb(script->str_data[l].label);
script->addb(script->str_data[l].label>>8);
script->addb(script->str_data[l].label>>16);
break;
case C_NOP:
case C_USERFUNC:
// Embedded data backpatch there is a possibility of label
script->addc(C_NAME);
script->str_data[l].backpatch = VECTOR_LENGTH(script->buf);
script->addb(backpatch);
script->addb(backpatch>>8);
script->addb(backpatch>>16);
break;
case C_INT:
script->addi(abs(script->str_data[l].val));
if( script->str_data[l].val < 0 ) //Notice that this is negative, from jA (Rayce)
script->addc(C_NEG);
break;
default: // assume C_NAME
script->addc(C_NAME);
script->addb(l);
script->addb(l>>8);
script->addb(l>>16);
break;
}
}
/*==========================================
* Resolve the label
*------------------------------------------*/
static void set_label(int l, int pos, const char *script_pos)
{
int i;
if(script->str_data[l].type==C_INT || script->str_data[l].type==C_PARAM || script->str_data[l].type==C_FUNC) {
//Prevent overwriting constants values, parameters and built-in functions [Skotlex]
disp_error_message("set_label: invalid label name",script_pos);
return;
}
if(script->str_data[l].label!=-1) {
disp_error_message("set_label: dup label ",script_pos);
return;
}
script->str_data[l].type=(script->str_data[l].type == C_USERFUNC ? C_USERFUNC_POS : C_POS);
script->str_data[l].label=pos;
for (i = script->str_data[l].backpatch; i >= 0 && i != 0x00ffffff; ) {
int next = GETVALUE(&script->buf, i);
VECTOR_INDEX(script->buf, i-1) = (script->str_data[l].type == C_USERFUNC ? C_USERFUNC_POS : C_POS);
SETVALUE(&script->buf, i, pos);
i = next;
}
}
/// Skips spaces and/or comments.
static const char *script_skip_space(const char *p)
{
if( p == NULL )
return NULL;
for(;;)
{
while( ISSPACE(*p) )
++p;
if( *p == '/' && p[1] == '/' )
{// line comment
while(*p && *p!='\n')
++p;
}
else if( *p == '/' && p[1] == '*' )
{// block comment
p += 2;
for(;;)
{
if( *p == '\0' ) {
script->disp_warning_message("script:script->skip_space: end of file while parsing block comment. expected "CL_BOLD"*/"CL_NORM, p);
return p;
}
if( *p == '*' && p[1] == '/' )
{// end of block comment
p += 2;
break;
}
++p;
}
}
else
break;
}
return p;
}
/// Skips a word.
/// A word consists of undercores and/or alphanumeric characters,
/// and valid variable prefixes/postfixes.
static const char *skip_word(const char *p)
{
nullpo_retr(NULL, p);
// prefix
switch( *p ) {
case '@':// temporary char variable
++p; break;
case '#':// account variable
p += ( p[1] == '#' ? 2 : 1 ); break;
case '\'':// instance variable
++p; break;
case '.':// npc variable
p += ( p[1] == '@' ? 2 : 1 ); break;
case '$':// global variable
p += ( p[1] == '@' ? 2 : 1 ); break;
}
while (ISALNUM(*p) || *p == '_')
++p;
// postfix
if( *p == '$' )// string
p++;
return p;
}
/// Adds a word to script->str_data.
/// @see skip_word
/// @see script->add_str
static int add_word(const char *p)
{
size_t len;
int i;
nullpo_retr(0, p);
// Check for a word
len = script->skip_word(p) - p;
if( len == 0 )
disp_error_message("script:add_word: invalid word. A word consists of undercores and/or alphanumeric characters, and valid variable prefixes/postfixes.", p);
// Duplicate the word
if( len+1 > script->word_size )
RECREATE(script->word_buf, char, (script->word_size = (len+1)));
memcpy(script->word_buf, p, len);
script->word_buf[len] = 0;
// add the word
i = script->add_str(script->word_buf);
return i;
}
/// Parses a function call.
/// The argument list can have parenthesis or not.
/// The number of arguments is checked.
static const char *parse_callfunc(const char *p, int require_paren, int is_custom)
{
const char *p2;
char *arg = NULL;
char null_arg = '\0';
int func;
bool macro = false;
nullpo_retr(NULL, p);
// is need add check for arg null pointer below?
func = script->add_word(p);
if (script->str_data[func].type == C_FUNC) {
script->syntax.nested_call++;
if (script->syntax.last_func != -1) {
if (script->str_data[func].val == script->buildin_lang_macro_offset) {
script->syntax.lang_macro_active = true;
macro = true;
} else if (script->str_data[func].val == script->buildin_lang_macro_fmtstring_offset) {
script->syntax.lang_macro_fmtstring_active = true;
macro = true;
}
}
if( !macro ) {
// buildin function
script->syntax.last_func = script->str_data[func].val;
script->addl(func);
script->addc(C_ARG);
}
arg = script->buildin[script->str_data[func].val];
if (script->str_data[func].deprecated)
DeprecationWarning(p);
if( !arg ) arg = &null_arg; // Use a dummy, null string
} else if( script->str_data[func].type == C_USERFUNC || script->str_data[func].type == C_USERFUNC_POS ) {
// script defined function
script->addl(script->buildin_callsub_ref);
script->addc(C_ARG);
script->addl(func);
arg = script->buildin[script->str_data[script->buildin_callsub_ref].val];
if( *arg == 0 )
disp_error_message("parse_callfunc: callsub has no arguments, please review its definition",p);
if( *arg != '*' )
++arg; // count func as argument
} else {
#ifdef SCRIPT_CALLFUNC_CHECK
const char* name = script->get_str(func);
if( !is_custom && strdb_get(script->userfunc_db, name) == NULL ) {
#endif
disp_error_message("parse_line: expect command, missing function name or calling undeclared function",p);
#ifdef SCRIPT_CALLFUNC_CHECK
} else {;
script->addl(script->buildin_callfunc_ref);
script->addc(C_ARG);
script->addc(C_STR);
while( *name ) script->addb(*name ++);
script->addb(0);
arg = script->buildin[script->str_data[script->buildin_callfunc_ref].val];
if( *arg != '*' ) ++ arg;
}
#endif
}
p = script->skip_word(p);
p = script->skip_space(p);
script->syntax.curly[script->syntax.curly_count].type = TYPE_ARGLIST;
script->syntax.curly[script->syntax.curly_count].count = 0;
if( *p == ';' )
{// ';'
script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_NO_PAREN;
} else if( *p == '(' && *(p2=script->skip_space(p+1)) == ')' )
{// '(' ')'
script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_PAREN;
p = p2;
/*
} else if( 0 && require_paren && *p != '(' )
{//
script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_NO_PAREN;
*/
} else {//
if( require_paren ) {
if( *p != '(' )
disp_error_message("need '('",p);
++p; // skip '('
script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_PAREN;
} else if( *p == '(' ) {
script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_UNDEFINED;
} else {
script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_NO_PAREN;
}
++script->syntax.curly_count;
while( *arg ) {
p2=script->parse_subexpr(p,-1);
if( p == p2 )
break; // not an argument
if( *arg != '*' )
++arg; // next argument
p=script->skip_space(p2);
if( *arg == 0 || *p != ',' )
break; // no more arguments
++p; // skip comma
}
--script->syntax.curly_count;
}
if( arg && *arg && *arg != '?' && *arg != '*' )
disp_error_message2("parse_callfunc: not enough arguments, expected ','", p, script->config.warn_func_mismatch_paramnum);
if( script->syntax.curly[script->syntax.curly_count].type != TYPE_ARGLIST )
disp_error_message("parse_callfunc: DEBUG last curly is not an argument list",p);
if( script->syntax.curly[script->syntax.curly_count].flag == ARGLIST_PAREN ) {
if( *p != ')' )
disp_error_message("parse_callfunc: expected ')' to close argument list",p);
++p;
if (script->str_data[func].val == script->buildin_lang_macro_offset)
script->syntax.lang_macro_active = false;
else if (script->str_data[func].val == script->buildin_lang_macro_fmtstring_offset)
script->syntax.lang_macro_fmtstring_active = false;
}
if (!macro) {
if (0 == --script->syntax.nested_call)
script->syntax.last_func = -1;
script->addc(C_FUNC);
}
return p;
}
/// Processes end of logical script line.
/// @param first When true, only fix up scheduling data is initialized
/// @param p Script position for error reporting in set_label
static void parse_nextline(bool first, const char *p)
{
if( !first )
{
script->addc(C_EOL); // mark end of line for stack cleanup
script->set_label(LABEL_NEXTLINE, VECTOR_LENGTH(script->buf), p); // fix up '-' labels
}
// initialize data for new '-' label fix up scheduling
script->str_data[LABEL_NEXTLINE].type = C_NOP;
script->str_data[LABEL_NEXTLINE].backpatch = -1;
script->str_data[LABEL_NEXTLINE].label = -1;
}
/**
* Pushes a variable into stack, processing its array index if needed.
* @see parse_variable
*/
static void parse_variable_sub_push(int word, const char *p2)
{
if( p2 ) {
const char* p3 = NULL;
// process the variable index
// push the getelementofarray method into the stack
script->addl(script->buildin_getelementofarray_ref);
script->addc(C_ARG);
script->addl(word);
// process the sub-expression for this assignment
p3 = script->parse_subexpr(p2 + 1, 1);
p3 = script->skip_space(p3);
if( *p3 != ']' ) {// closing parenthesis is required for this script
disp_error_message("Missing closing ']' parenthesis for the variable assignment.", p3);
}
// push the closing function stack operator onto the stack
script->addc(C_FUNC);
p3++;
} else {
// No array index, simply push the variable or value onto the stack
script->addl(word);
}
}
/// Parse a variable assignment using the direct equals operator
/// @param p script position where the function should run from
/// @return NULL if not a variable assignment, the new position otherwise
static const char *parse_variable(const char *p)
{
int word;
c_op type = C_NOP;
const char *p2 = NULL;
const char *var = p;
nullpo_retr(NULL, p);
if( ( p[0] == '+' && p[1] == '+' && (type = C_ADD_PRE, true) ) // pre ++
|| ( p[0] == '-' && p[1] == '-' && (type = C_SUB_PRE, true) ) // pre --
) {
var = p = script->skip_space(&p[2]);
}
// skip the variable where applicable
p = script->skip_word(p);
p = script->skip_space(p);
if( p == NULL ) {
// end of the line or invalid buffer
return NULL;
}
if (*p == '[') {
int i, j;
// array variable so process the array as appropriate
for (p2 = p, i = 0, j = 1; p; ++ i) {
if( *p ++ == ']' && --(j) == 0 ) break;
if( *p == '[' ) ++ j;
}
if( !(p = script->skip_space(p)) ) {
// end of line or invalid characters remaining
disp_error_message("Missing right expression or closing bracket for variable.", p);
}
}
if( type == C_NOP &&
!( ( p[0] == '=' && p[1] != '=' && (type = C_EQ, true) ) // =
|| ( p[0] == '+' && p[1] == '=' && (type = C_ADD, true) ) // +=
|| ( p[0] == '-' && p[1] == '=' && (type = C_SUB, true) ) // -=
|| ( p[0] == '^' && p[1] == '=' && (type = C_XOR, true) ) // ^=
|| ( p[0] == '|' && p[1] == '=' && (type = C_OR, true) ) // |=
|| ( p[0] == '&' && p[1] == '=' && (type = C_AND, true) ) // &=
|| ( p[0] == '*' && p[1] == '=' && (type = C_MUL, true) ) // *=
|| ( p[0] == '*' && p[1] == '*' && p[2] == '=' && (type = C_POW, true) ) // **=
|| ( p[0] == '/' && p[1] == '=' && (type = C_DIV, true) ) // /=
|| ( p[0] == '%' && p[1] == '=' && (type = C_MOD, true) ) // %=
|| ( p[0] == '+' && p[1] == '+' && (type = C_ADD_POST, true) ) // post ++
|| ( p[0] == '-' && p[1] == '-' && (type = C_SUB_POST, true) ) // post --
|| ( p[0] == '<' && p[1] == '<' && p[2] == '=' && (type = C_L_SHIFT, true) ) // <<=
|| ( p[0] == '>' && p[1] == '>' && p[2] == '=' && (type = C_R_SHIFT, true) ) // >>=
) )
{// failed to find a matching operator combination so invalid
return NULL;
}
switch( type ) {
case C_ADD_PRE: // pre ++
case C_SUB_PRE: // pre --
// (nothing more to skip)
break;
case C_EQ: // =
p = script->skip_space( &p[1] );
break;
case C_L_SHIFT: // <<=
case C_R_SHIFT: // >>=
case C_POW: // **=
p = script->skip_space( &p[3] );
break;
default: // everything else
p = script->skip_space( &p[2] );
}
if( p == NULL ) {
// end of line or invalid buffer
return NULL;
}
// push the set function onto the stack
script->syntax.nested_call++;
script->syntax.last_func = script->str_data[script->buildin_set_ref].val;
script->addl(script->buildin_set_ref);
script->addc(C_ARG);
// always append parenthesis to avoid errors
script->syntax.curly[script->syntax.curly_count].type = TYPE_ARGLIST;
script->syntax.curly[script->syntax.curly_count].count = 0;
script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_PAREN;
// increment the total curly count for the position in the script
++script->syntax.curly_count;
// parse the variable currently being modified
word = script->add_word(var);
if( script->str_data[word].type == C_FUNC
|| script->str_data[word].type == C_USERFUNC
|| script->str_data[word].type == C_USERFUNC_POS
) {
// cannot assign a variable which exists as a function or label
disp_error_message("Cannot modify a variable which has the same name as a function or label.", p);
}
parse_variable_sub_push(word, p2); // Push variable onto the stack
if( type != C_EQ ) {
parse_variable_sub_push(word, p2); // Push variable onto the stack once again (first argument of setr)
}
if( type == C_ADD_POST || type == C_SUB_POST ) { // post ++ / --
script->addi(1);
script->addc(type == C_ADD_POST ? C_ADD : C_SUB);
parse_variable_sub_push(word, p2); // Push variable onto the stack (third argument of setr)
} else if( type == C_ADD_PRE || type == C_SUB_PRE ) { // pre ++ / --
script->addi(1);
script->addc(type == C_ADD_PRE ? C_ADD : C_SUB);
} else {
// process the value as an expression
p = script->parse_subexpr(p, -1);
if( type != C_EQ ) {
// push the type of modifier onto the stack
script->addc(type);
}
}
// decrement the curly count for the position within the script
--script->syntax.curly_count;
// close the script by appending the function operator
script->addc(C_FUNC);
if (--script->syntax.nested_call == 0)
script->syntax.last_func = -1;
// push the buffer from the method
return p;
}
/*
* Checks whether the gives string is a number literal
*
* Mainly necessary to differentiate between number literals and NPC name
* constants, since several of those start with a digit.
*
* All this does is to check if the string begins with an optional + or - sign,
* followed by a hexadecimal or decimal number literal literal and is NOT
* followed by a underscore or letter.
*
* @param p Pointer to the string to check
* @return Whether the string is a number literal
*/
static bool is_number(const char *p)
{
const char *np;
if (!p)
return false;
if (*p == '-' || *p == '+')
p++;
np = p;
if (*p == '0' && p[1] == 'x') {
p+=2;
np = p;
// Hexadecimal
while (ISXDIGIT(*np))
np++;
} else {
// Decimal
while (ISDIGIT(*np))
np++;
}
if (p != np && *np != '_' && !ISALPHA(*np)) // At least one digit, and next isn't a letter or _
return true;
return false;
}
/**
* Duplicates a script string into the script string list.
*
* Grows the script string list as needed.
*
* @param str The string to insert.
* @return the string position in the script string list.
*/
static int script_string_dup(char *str)
{
int len;
int pos = script->string_list_pos;
nullpo_retr(pos, str);
len = (int)strlen(str);
while (pos+len+1 >= script->string_list_size) {
script->string_list_size += (1024*1024)/2;
RECREATE(script->string_list,char,script->string_list_size);
}
safestrncpy(script->string_list+pos, str, len+1);
script->string_list_pos += len+1;
return pos;
}
/*==========================================
* Analysis section
*------------------------------------------*/
static const char *parse_simpleexpr(const char *p)
{
p=script->skip_space(p);
nullpo_retr(NULL, p);
if (*p == ';' || *p == ',')
disp_error_message("parse_simpleexpr: unexpected end of expression",p);
if (*p == '(') {
return script->parse_simpleexpr_paren(p);
} else if (is_number(p)) {
return script->parse_simpleexpr_number(p);
} else if(*p == '"') {
return script->parse_simpleexpr_string(p);
} else {
return script->parse_simpleexpr_name(p);
}
}
static const char *parse_simpleexpr_paren(const char *p)
{
int i = script->syntax.curly_count - 1;
nullpo_retr(NULL, p);
if (i >= 0 && script->syntax.curly[i].type == TYPE_ARGLIST)
++script->syntax.curly[i].count;
p = script->parse_subexpr(p + 1, -1);
p = script->skip_space(p);
if ((i = script->syntax.curly_count - 1) >= 0
&& script->syntax.curly[i].type == TYPE_ARGLIST
&& script->syntax.curly[i].flag == ARGLIST_UNDEFINED
&& --script->syntax.curly[i].count == 0
) {
if (*p == ',') {
script->syntax.curly[i].flag = ARGLIST_PAREN;
return p;
} else {
script->syntax.curly[i].flag = ARGLIST_NO_PAREN;
}
}
if (*p != ')')
disp_error_message("parse_simpleexpr: unmatched ')'", p);
return p + 1;
}
static const char *parse_simpleexpr_number(const char *p)
{
char *np = NULL;
long long lli;
nullpo_retr(NULL, p);
while (*p == '0' && ISDIGIT(p[1]))
p++; // Skip leading zeros, we don't support octal literals
lli = strtoll(p, &np, 0);
if (lli < INT_MIN) {
lli = INT_MIN;
script->disp_warning_message("parse_simpleexpr: underflow detected, capping value to INT_MIN", p);
} else if (lli > INT_MAX) {
lli = INT_MAX;
script->disp_warning_message("parse_simpleexpr: overflow detected, capping value to INT_MAX", p);
}
script->addi((int)lli); // Cast is safe, as it's already been checked for overflows
return np;
}
static const char *parse_simpleexpr_string(const char *p)
{
const char *start_point = p;
nullpo_retr(NULL, p);
do {
p++;
while (*p != '\0' && *p != '"') {
if ((unsigned char)p[-1] <= 0x7e && *p == '\\') {
char buf[8];
size_t len = sv->skip_escaped_c(p) - p;
size_t n = sv->unescape_c(buf, p, len);
if (n != 1)
ShowDebug("parse_simpleexpr: unexpected length %d after unescape (\"%.*s\" -> %.*s)\n", (int)n, (int)len, p, (int)n, buf);
p += len;
VECTOR_ENSURE(script->parse_simpleexpr_strbuf, 1, 512);
VECTOR_PUSH(script->parse_simpleexpr_strbuf, buf[0]);
continue;
}
if (*p == '\n') {
disp_error_message("parse_simpleexpr: unexpected newline @ string", p);
}
VECTOR_ENSURE(script->parse_simpleexpr_strbuf, 1, 512);
VECTOR_PUSH(script->parse_simpleexpr_strbuf, *p++);
}
if (*p == '\0')
disp_error_message("parse_simpleexpr: unexpected end of file @ string", p);
p++; //'"'
p = script->skip_space(p);
} while (*p != '\0' && *p == '"');
VECTOR_ENSURE(script->parse_simpleexpr_strbuf, 1, 512);
VECTOR_PUSH(script->parse_simpleexpr_strbuf, '\0');
script->add_translatable_string(&script->parse_simpleexpr_strbuf, start_point);
VECTOR_TRUNCATE(script->parse_simpleexpr_strbuf);
return p;
}
static const char *parse_simpleexpr_name(const char *p)
{
int l;
const char *pv = NULL;
// label , register , function etc
if (script->skip_word(p) == p)
disp_error_message("parse_simpleexpr: unexpected character", p);
l = script->add_word(p);
if (script->str_data[l].type == C_FUNC || script->str_data[l].type == C_USERFUNC || script->str_data[l].type == C_USERFUNC_POS) {
return script->parse_callfunc(p,1,0);
#ifdef SCRIPT_CALLFUNC_CHECK
} else {
const char *name = script->get_str(l);
if (strdb_get(script->userfunc_db,name) != NULL) {
return script->parse_callfunc(p, 1, 1);
}
#endif
}
if ((pv = script->parse_variable(p)) != NULL) {
// successfully processed a variable assignment
return pv;
}
if (script->str_data[l].type == C_INT && script->str_data[l].deprecated) {
disp_warning_message("This constant is deprecated and it will be removed in a future version. Please see the script documentation and constants.conf for an alternative.\n", p);
}
p = script->skip_word(p);
if (*p == '[') {
// array(name[i] => getelementofarray(name,i) )
script->addl(script->buildin_getelementofarray_ref);
script->addc(C_ARG);
script->addl(l);
p = script->parse_subexpr(p + 1, -1);
p = script->skip_space(p);
if (*p != ']')
disp_error_message("parse_simpleexpr: unmatched ']'", p);
++p;
script->addc(C_FUNC);
} else {
script->addl(l);
}
return p;
}
static void script_add_translatable_string(const struct script_string_buf *string, const char *start_point)
{
struct string_translation *st = NULL;
nullpo_retv(string);
if (script->syntax.translation_db == NULL
|| (st = strdb_get(script->syntax.translation_db, VECTOR_DATA(*string))) == NULL) {
script->addc(C_STR);
VECTOR_ENSURE(script->buf, VECTOR_LENGTH(*string), SCRIPT_BLOCK_SIZE);
VECTOR_PUSHARRAY(script->buf, VECTOR_DATA(*string), VECTOR_LENGTH(*string));
} else {
unsigned char u;
int st_cursor = 0;
script->addc(C_LSTR);
VECTOR_ENSURE(script->buf, (int)(sizeof(st->string_id) + sizeof(st->translations)), SCRIPT_BLOCK_SIZE);
VECTOR_PUSHARRAY(script->buf, (void *)&st->string_id, sizeof(st->string_id));
VECTOR_PUSHARRAY(script->buf, (void *)&st->translations, sizeof(st->translations));
for (u = 0; u != st->translations; u++) {
struct string_translation_entry *entry = (void *)(st->buf+st_cursor);
char *stringptr = &entry->string[0];
st_cursor += sizeof(*entry);
VECTOR_ENSURE(script->buf, (int)(sizeof(entry->lang_id) + sizeof(char *)), SCRIPT_BLOCK_SIZE);
VECTOR_PUSHARRAY(script->buf, (void *)&entry->lang_id, sizeof(entry->lang_id));
VECTOR_PUSHARRAY(script->buf, (void *)&stringptr, sizeof(stringptr));
st_cursor += sizeof(uint8); // FIXME: What are we skipping here?
while (st->buf[st_cursor++] != 0)
(void)0; // Skip string
st_cursor += sizeof(uint8); // FIXME: What are we skipping here?
}
}
}
/*==========================================
* Analysis of the expression
*------------------------------------------*/
static const char *script_parse_subexpr(const char *p, int limit)
{
int op,opl,len;
nullpo_retr(NULL, p);
p=script->skip_space(p);
if( *p == '-' ) {
const char *tmpp = script->skip_space(p+1);
if( *tmpp == ';' || *tmpp == ',' ) {
script->addl(LABEL_NEXTLINE);
p++;
return p;
}
}
if( (p[0]=='+' && p[1]=='+') /* C_ADD_PRE */ || (p[0]=='-'&&p[1]=='-') /* C_SUB_PRE */ ) { // Pre ++ -- operators
p=script->parse_variable(p);
} else if( (op=C_NEG,*p=='-') || (op=C_LNOT,*p=='!') || (op=C_NOT,*p=='~') ) { // Unary - ! ~ operators
p=script->parse_subexpr(p+1,11);
script->addc(op);
} else {
p=script->parse_simpleexpr(p);
}
p=script->skip_space(p);
while((
(op=C_OP3, opl=0, len=1,*p=='?') // ?:
|| (op=C_ADD, opl=9, len=1,*p=='+' && p[1]!='+') // +
|| (op=C_SUB, opl=9, len=1,*p=='-' && p[1]!='-') // -
|| (op=C_POW, opl=11,len=2,*p=='*' && p[1]=='*') // **
|| (op=C_MUL, opl=10,len=1,*p=='*') // *
|| (op=C_DIV, opl=10,len=1,*p=='/') // /
|| (op=C_MOD, opl=10,len=1,*p=='%') // %
|| (op=C_LAND, opl=2, len=2,*p=='&' && p[1]=='&') // &&
|| (op=C_AND, opl=5, len=1,*p=='&') // &
|| (op=C_LOR, opl=1, len=2,*p=='|' && p[1]=='|') // ||
|| (op=C_OR, opl=3, len=1,*p=='|') // |
|| (op=C_XOR, opl=4, len=1,*p=='^') // ^
|| (op=C_EQ, opl=6, len=2,*p=='=' && p[1]=='=') // ==
|| (op=C_NE, opl=6, len=2,*p=='!' && p[1]=='=') // !=
|| (op=C_RE_EQ, opl=6, len=2,*p=='~' && p[1]=='=') // ~=
|| (op=C_RE_NE, opl=6, len=2,*p=='~' && p[1]=='!') // ~!
|| (op=C_R_SHIFT,opl=8, len=2,*p=='>' && p[1]=='>') // >>
|| (op=C_GE, opl=7, len=2,*p=='>' && p[1]=='=') // >=
|| (op=C_GT, opl=7, len=1,*p=='>') // >
|| (op=C_L_SHIFT,opl=8, len=2,*p=='<' && p[1]=='<') // <<
|| (op=C_LE, opl=7, len=2,*p=='<' && p[1]=='=') // <=
|| (op=C_LT, opl=7, len=1,*p=='<') // <
) && opl>limit) {
p+=len;
if(op == C_OP3) {
p=script->parse_subexpr(p,-1);
p=script->skip_space(p);
if( *(p++) != ':')
disp_error_message("parse_subexpr: need ':'", p-1);
p=script->parse_subexpr(p,-1);
} else {
p=script->parse_subexpr(p,opl);
}
script->addc(op);
p=script->skip_space(p);
}
return p; /* return first untreated operator */
}
/*==========================================
* Evaluation of the expression
*------------------------------------------*/
static const char *parse_expr(const char *p)
{
nullpo_retr(NULL, p);
switch(*p) {
case ')': case ';': case ':': case '[': case ']':
case '}':
disp_error_message("parse_expr: unexpected char",p);
}
p=script->parse_subexpr(p,-1);
return p;
}
/*==========================================
* Analysis of the line
*------------------------------------------*/
static const char *parse_line(const char *p)
{
const char* p2;
nullpo_retr(NULL, p);
p=script->skip_space(p);
if(*p==';') {
//Close decision for if(); for(); while();
p = script->parse_syntax_close(p + 1);
return p;
}
if(*p==')' && script->parse_syntax_for_flag)
return p+1;
p = script->skip_space(p);
if(p[0] == '{') {
script->syntax.curly[script->syntax.curly_count].type = TYPE_NULL;
script->syntax.curly[script->syntax.curly_count].count = -1;
script->syntax.curly[script->syntax.curly_count].index = -1;
script->syntax.curly_count++;
return p + 1;
} else if(p[0] == '}') {
return script->parse_curly_close(p);
}
// Syntax-related processing
p2 = script->parse_syntax(p);
if(p2 != NULL)
return p2;
// attempt to process a variable assignment
p2 = script->parse_variable(p);
if (p2 != NULL) {
// variable assignment processed so leave the method
if (script->parse_syntax_for_flag) {
if (*p2 != ')')
disp_error_message("parse_line: need ')'", p2);
} else {
if (*p2 != ';')
disp_error_message("parse_line: need ';'", p2);
}
return script->parse_syntax_close(p2 + 1);
}
p = script->parse_callfunc(p,0,0);
p = script->skip_space(p);
if(script->parse_syntax_for_flag) {
if( *p != ')' )
disp_error_message("parse_line: need ')'",p);
} else {
if( *p != ';' )
disp_error_message("parse_line: need ';'",p);
}
//Binding decision for if(), for(), while()
p = script->parse_syntax_close(p+1);
return p;
}
// { ... } Closing process
static const char *parse_curly_close(const char *p)
{
nullpo_retr(NULL, p);
if(script->syntax.curly_count <= 0) {
disp_error_message("parse_curly_close: unexpected string",p);
return p + 1;
} else if(script->syntax.curly[script->syntax.curly_count-1].type == TYPE_NULL) {
script->syntax.curly_count--;
//Close decision if, for , while
p = script->parse_syntax_close(p + 1);
return p;
} else if(script->syntax.curly[script->syntax.curly_count-1].type == TYPE_SWITCH) {
//Closing switch()
int pos = script->syntax.curly_count-1;
char label[256];
int l;
// Remove temporary variables
sprintf(label, "__setr $@__SW%x_VAL,0;", (unsigned int)script->syntax.curly[pos].index);
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
// Go to the end pointer unconditionally
sprintf(label,"goto __SW%x_FIN;", (unsigned int)script->syntax.curly[pos].index);
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
// You are here labeled
sprintf(label,"__SW%x_%x", (unsigned int)script->syntax.curly[pos].index, (unsigned int)script->syntax.curly[pos].count);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
if(script->syntax.curly[pos].flag) {
//Exists default
sprintf(label,"goto __SW%x_DEF;", (unsigned int)script->syntax.curly[pos].index);
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
}
// Label end
sprintf(label,"__SW%x_FIN", (unsigned int)script->syntax.curly[pos].index);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
linkdb_final(&script->syntax.curly[pos].case_label); // free the list of case label
script->syntax.curly_count--;
//Closing decision if, for , while
p = script->parse_syntax_close(p + 1);
return p;
} else {
disp_error_message("parse_curly_close: unexpected string",p);
return p + 1;
}
}
// Syntax-related processing
// break, case, continue, default, do, for, function,
// if, switch, while ? will handle this internally.
static const char *parse_syntax(const char *p)
{
const char *p2 = script->skip_word(p);
nullpo_retr(NULL, p);
switch(*p) {
case 'B':
case 'b':
if( p2 - p == 5 && strncmp(p,"break",5) == 0 ) {
// break Processing
char label[256];
int pos = script->syntax.curly_count - 1;
while(pos >= 0) {
if(script->syntax.curly[pos].type == TYPE_DO) {
sprintf(label, "goto __DO%x_FIN;", (unsigned int)script->syntax.curly[pos].index);
break;
} else if(script->syntax.curly[pos].type == TYPE_FOR) {
sprintf(label, "goto __FR%x_FIN;", (unsigned int)script->syntax.curly[pos].index);
break;
} else if(script->syntax.curly[pos].type == TYPE_WHILE) {
sprintf(label, "goto __WL%x_FIN;", (unsigned int)script->syntax.curly[pos].index);
break;
} else if(script->syntax.curly[pos].type == TYPE_SWITCH) {
sprintf(label, "goto __SW%x_FIN;", (unsigned int)script->syntax.curly[pos].index);
break;
}
pos--;
}
if(pos < 0) {
disp_error_message("parse_syntax: unexpected 'break'",p);
} else {
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
}
p = script->skip_space(p2);
if(*p != ';')
disp_error_message("parse_syntax: need ';'",p);
// Closing decision if, for , while
p = script->parse_syntax_close(p + 1);
return p;
}
break;
case 'c':
case 'C':
if( p2 - p == 4 && strncmp(p, "case", 4) == 0 ) {
//Processing case
int pos = script->syntax.curly_count-1;
if(pos < 0 || script->syntax.curly[pos].type != TYPE_SWITCH) {
disp_error_message("parse_syntax: unexpected 'case' ",p);
return p+1;
} else {
char label[256];
int l,v;
char *np;
if(script->syntax.curly[pos].count != 1) {
//Jump for FALLTHRU
sprintf(label,"goto __SW%x_%xJ;", (unsigned int)script->syntax.curly[pos].index, (unsigned int)script->syntax.curly[pos].count);
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
// You are here labeled
sprintf(label,"__SW%x_%x", (unsigned int)script->syntax.curly[pos].index, (unsigned int)script->syntax.curly[pos].count);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
}
//Decision statement switch
p = script->skip_space(p2);
if(p == p2) {
disp_error_message("parse_syntax: expect space ' '",p);
}
// check whether case label is integer or not
if(is_number(p)) {
//Numeric value
v = (int)strtol(p,&np,0);
if((*p == '-' || *p == '+') && ISDIGIT(p[1])) // pre-skip because '-' can not skip_word
p++;
p = script->skip_word(p);
if(np != p)
disp_error_message("parse_syntax: 'case' label is not an integer",np);
} else {
//Check for constants
p2 = script->skip_word(p);
v = (int)(size_t) (p2-p); // length of word at p2
memcpy(label,p,v);
label[v]='\0';
if( !script->get_constant(label, &v) )
disp_error_message("parse_syntax: 'case' label is not an integer",p);
p = script->skip_word(p);
}
p = script->skip_space(p);
if(*p != ':')
disp_error_message("parse_syntax: expect ':'",p);
sprintf(label,"if(%d != $@__SW%x_VAL) goto __SW%x_%x;",
v, (unsigned int)script->syntax.curly[pos].index, (unsigned int)script->syntax.curly[pos].index, (unsigned int)script->syntax.curly[pos].count+1);
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
// Bad I do not parse twice
p2 = script->parse_line(label);
script->parse_line(p2);
script->syntax.curly_count--;
if(script->syntax.curly[pos].count != 1) {
// Label after the completion of FALLTHRU
sprintf(label, "__SW%x_%xJ", (unsigned int)script->syntax.curly[pos].index, (unsigned int)script->syntax.curly[pos].count);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
}
// check duplication of case label [Rayce]
if(linkdb_search(&script->syntax.curly[pos].case_label, (void*)h64BPTRSIZE(v)) != NULL)
disp_error_message("parse_syntax: dup 'case'",p);
linkdb_insert(&script->syntax.curly[pos].case_label, (void*)h64BPTRSIZE(v), (void*)1);
sprintf(label, "__setr $@__SW%x_VAL,0;", (unsigned int)script->syntax.curly[pos].index);
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
script->syntax.curly[pos].count++;
}
return p + 1;
} else if( p2 - p == 8 && strncmp(p, "continue", 8) == 0 ) {
// Processing continue
char label[256];
int pos = script->syntax.curly_count - 1;
while(pos >= 0) {
if(script->syntax.curly[pos].type == TYPE_DO) {
sprintf(label, "goto __DO%x_NXT;", (unsigned int)script->syntax.curly[pos].index);
script->syntax.curly[pos].flag = 1; //Flag put the link for continue
break;
} else if(script->syntax.curly[pos].type == TYPE_FOR) {
sprintf(label, "goto __FR%x_NXT;", (unsigned int)script->syntax.curly[pos].index);
break;
} else if(script->syntax.curly[pos].type == TYPE_WHILE) {
sprintf(label, "goto __WL%x_NXT;", (unsigned int)script->syntax.curly[pos].index);
break;
}
pos--;
}
if(pos < 0) {
disp_error_message("parse_syntax: unexpected 'continue'",p);
} else {
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
}
p = script->skip_space(p2);
if(*p != ';')
disp_error_message("parse_syntax: need ';'",p);
//Closing decision if, for , while
p = script->parse_syntax_close(p + 1);
return p;
}
break;
case 'd':
case 'D':
if( p2 - p == 7 && strncmp(p, "default", 7) == 0 ) {
// Switch - default processing
int pos = script->syntax.curly_count-1;
if(pos < 0 || script->syntax.curly[pos].type != TYPE_SWITCH) {
disp_error_message("parse_syntax: unexpected 'default'",p);
} else if(script->syntax.curly[pos].flag) {
disp_error_message("parse_syntax: dup 'default'",p);
} else {
char label[256];
int l;
// Put the label location
p = script->skip_space(p2);
if(*p != ':') {
disp_error_message("parse_syntax: need ':'",p);
}
sprintf(label, "__SW%x_%x", (unsigned int)script->syntax.curly[pos].index, (unsigned int)script->syntax.curly[pos].count);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
// Skip to the next link w/o condition
sprintf(label, "goto __SW%x_%x;", (unsigned int)script->syntax.curly[pos].index, (unsigned int)script->syntax.curly[pos].count + 1);
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
// The default label
sprintf(label, "__SW%x_DEF", (unsigned int)script->syntax.curly[pos].index);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
script->syntax.curly[script->syntax.curly_count - 1].flag = 1;
script->syntax.curly[pos].count++;
}
return p + 1;
} else if( p2 - p == 2 && strncmp(p, "do", 2) == 0 ) {
int l;
char label[256];
p=script->skip_space(p2);
script->syntax.curly[script->syntax.curly_count].type = TYPE_DO;
script->syntax.curly[script->syntax.curly_count].count = 1;
script->syntax.curly[script->syntax.curly_count].index = script->syntax.index++;
script->syntax.curly[script->syntax.curly_count].flag = 0;
// Label of the (do) form here
sprintf(label, "__DO%x_BGN", (unsigned int)script->syntax.curly[script->syntax.curly_count].index);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
script->syntax.curly_count++;
return p;
}
break;
case 'f':
case 'F':
if( p2 - p == 3 && strncmp(p, "for", 3) == 0 ) {
int l;
char label[256];
int pos = script->syntax.curly_count;
script->syntax.curly[script->syntax.curly_count].type = TYPE_FOR;
script->syntax.curly[script->syntax.curly_count].count = 1;
script->syntax.curly[script->syntax.curly_count].index = script->syntax.index++;
script->syntax.curly[script->syntax.curly_count].flag = 0;
script->syntax.curly_count++;
p=script->skip_space(p2);
if(*p != '(')
disp_error_message("parse_syntax: need '('",p);
p++;
// Execute the initialization statement
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
p=script->parse_line(p);
script->syntax.curly_count--;
// Form the start of label decision
sprintf(label, "__FR%x_J", (unsigned int)script->syntax.curly[pos].index);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
p=script->skip_space(p);
if(*p == ';') {
// For (; Because the pattern of always true ;)
;
} else {
// Skip to the end point if the condition is false
sprintf(label, "__FR%x_FIN", (unsigned int)script->syntax.curly[pos].index);
script->addl(script->add_str("__jump_zero"));
script->addc(C_ARG);
p=script->parse_expr(p);
p=script->skip_space(p);
script->addl(script->add_str(label));
script->addc(C_FUNC);
}
if(*p != ';')
disp_error_message("parse_syntax: need ';'",p);
p++;
// Skip to the beginning of the loop
sprintf(label, "goto __FR%x_BGN;", (unsigned int)script->syntax.curly[pos].index);
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
// Labels to form the next loop
sprintf(label, "__FR%x_NXT", (unsigned int)script->syntax.curly[pos].index);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
// Process the next time you enter the loop
// A ')' last for; flag to be treated as'
script->parse_syntax_for_flag = 1;
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
p=script->parse_line(p);
script->syntax.curly_count--;
script->parse_syntax_for_flag = 0;
// Skip to the determination process conditions
sprintf(label, "goto __FR%x_J;", (unsigned int)script->syntax.curly[pos].index);
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
// Loop start labeling
sprintf(label, "__FR%x_BGN", (unsigned int)script->syntax.curly[pos].index);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
return p;
} else if( p2 - p == 8 && strncmp(p, "function", 8) == 0 ) {
// internal script function
const char *func_name;
func_name = script->skip_space(p2);
p = script->skip_word(func_name);
if( p == func_name )
disp_error_message("parse_syntax:function: function name is missing or invalid", p);
p2 = script->skip_space(p);
if( *p2 == ';' )
{// function ;
// function declaration - just register the name
int l;
l = script->add_word(func_name);
if( script->str_data[l].type == C_NOP )// register only, if the name was not used by something else
script->str_data[l].type = C_USERFUNC;
else if( script->str_data[l].type == C_USERFUNC )
; // already registered
else
disp_error_message("parse_syntax:function: function name is invalid", func_name);
// Close condition of if, for, while
p = script->parse_syntax_close(p2 + 1);
return p;
}
else if(*p2 == '{')
{// function
char label[256];
int l;
script->syntax.curly[script->syntax.curly_count].type = TYPE_USERFUNC;
script->syntax.curly[script->syntax.curly_count].count = 1;
script->syntax.curly[script->syntax.curly_count].index = script->syntax.index++;
script->syntax.curly[script->syntax.curly_count].flag = 0;
++script->syntax.curly_count;
// Jump over the function code
sprintf(label, "goto __FN%x_FIN;", (unsigned int)script->syntax.curly[script->syntax.curly_count-1].index);
script->syntax.curly[script->syntax.curly_count].type = TYPE_NULL;
++script->syntax.curly_count;
script->parse_line(label);
--script->syntax.curly_count;
// Set the position of the function (label)
l=script->add_word(func_name);
if( script->str_data[l].type == C_NOP || script->str_data[l].type == C_USERFUNC )// register only, if the name was not used by something else
{
script->str_data[l].type = C_USERFUNC;
script->set_label(l, VECTOR_LENGTH(script->buf), p);
if( script->parse_options&SCRIPT_USE_LABEL_DB )
script->label_add(l, VECTOR_LENGTH(script->buf));
}
else
disp_error_message("parse_syntax:function: function name is invalid", func_name);
return script->skip_space(p);
}
else
{
disp_error_message("expect ';' or '{' at function syntax",p);
}
}
break;
case 'i':
case 'I':
if( p2 - p == 2 && strncmp(p, "if", 2) == 0 ) {
// If process
char label[256];
p=script->skip_space(p2);
if(*p != '(') { //Prevent if this {} non-c script->syntax. from Rayce (jA)
disp_error_message("need '('",p);
}
script->syntax.curly[script->syntax.curly_count].type = TYPE_IF;
script->syntax.curly[script->syntax.curly_count].count = 1;
script->syntax.curly[script->syntax.curly_count].index = script->syntax.index++;
script->syntax.curly[script->syntax.curly_count].flag = 0;
sprintf(label, "__IF%x_%x", (unsigned int)script->syntax.curly[script->syntax.curly_count].index, (unsigned int)script->syntax.curly[script->syntax.curly_count].count);
script->syntax.curly_count++;
script->addl(script->add_str("__jump_zero"));
script->addc(C_ARG);
p=script->parse_expr(p);
p=script->skip_space(p);
script->addl(script->add_str(label));
script->addc(C_FUNC);
return p;
}
break;
case 's':
case 'S':
if( p2 - p == 6 && strncmp(p, "switch", 6) == 0 ) {
// Processing of switch ()
char label[256];
p=script->skip_space(p2);
if(*p != '(') {
disp_error_message("need '('",p);
}
script->syntax.curly[script->syntax.curly_count].type = TYPE_SWITCH;
script->syntax.curly[script->syntax.curly_count].count = 1;
script->syntax.curly[script->syntax.curly_count].index = script->syntax.index++;
script->syntax.curly[script->syntax.curly_count].flag = 0;
sprintf(label, "$@__SW%x_VAL", (unsigned int)script->syntax.curly[script->syntax.curly_count].index);
script->syntax.curly_count++;
script->addl(script->add_str("__setr"));
script->addc(C_ARG);
script->addl(script->add_str(label));
p=script->parse_expr(p);
p=script->skip_space(p);
if(*p != '{') {
disp_error_message("parse_syntax: need '{'",p);
}
script->addc(C_FUNC);
return p + 1;
}
break;
case 'w':
case 'W':
if( p2 - p == 5 && strncmp(p, "while", 5) == 0 ) {
int l;
char label[256];
p=script->skip_space(p2);
if(*p != '(') {
disp_error_message("need '('",p);
}
script->syntax.curly[script->syntax.curly_count].type = TYPE_WHILE;
script->syntax.curly[script->syntax.curly_count].count = 1;
script->syntax.curly[script->syntax.curly_count].index = script->syntax.index++;
script->syntax.curly[script->syntax.curly_count].flag = 0;
// Form the start of label decision
sprintf(label, "__WL%x_NXT", (unsigned int)script->syntax.curly[script->syntax.curly_count].index);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
// Skip to the end point if the condition is false
sprintf(label, "__WL%x_FIN", (unsigned int)script->syntax.curly[script->syntax.curly_count].index);
script->syntax.curly_count++;
script->addl(script->add_str("__jump_zero"));
script->addc(C_ARG);
p=script->parse_expr(p);
p=script->skip_space(p);
script->addl(script->add_str(label));
script->addc(C_FUNC);
return p;
}
break;
}
return NULL;
}
static const char *parse_syntax_close(const char *p)
{
// If (...) for (...) hoge (); as to make sure closed closed once again
int flag;
nullpo_retr(NULL, p);
do {
p = script->parse_syntax_close_sub(p,&flag);
} while(flag);
return p;
}
// Close judgment if, for, while, of do
// flag == 1 : closed
// flag == 0 : not closed
static const char *parse_syntax_close_sub(const char *p, int *flag)
{
char label[256];
int pos = script->syntax.curly_count - 1;
int l;
*flag = 1;
if(script->syntax.curly_count <= 0) {
*flag = 0;
return p;
} else if(script->syntax.curly[pos].type == TYPE_IF) {
const char *bp = p;
const char *p2;
// if-block and else-block end is a new line
script->parse_nextline(false, p);
// Skip to the last location if
sprintf(label, "goto __IF%x_FIN;", (unsigned int)script->syntax.curly[pos].index);
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
// Put the label of the location
sprintf(label, "__IF%x_%x", (unsigned int)script->syntax.curly[pos].index, (unsigned int)script->syntax.curly[pos].count);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
script->syntax.curly[pos].count++;
p = script->skip_space(p);
p2 = script->skip_word(p);
if( !script->syntax.curly[pos].flag && p2 - p == 4 && strncmp(p, "else", 4) == 0 ) {
// else or else - if
p = script->skip_space(p2);
p2 = script->skip_word(p);
if( p2 - p == 2 && strncmp(p, "if", 2) == 0 ) {
// else - if
p=script->skip_space(p2);
if(*p != '(') {
disp_error_message("need '('",p);
}
sprintf(label, "__IF%x_%x", (unsigned int)script->syntax.curly[pos].index, (unsigned int)script->syntax.curly[pos].count);
script->addl(script->add_str("__jump_zero"));
script->addc(C_ARG);
p=script->parse_expr(p);
p=script->skip_space(p);
script->addl(script->add_str(label));
script->addc(C_FUNC);
*flag = 0;
return p;
} else {
// else
if(!script->syntax.curly[pos].flag) {
script->syntax.curly[pos].flag = 1;
*flag = 0;
return p;
}
}
}
// Close if
script->syntax.curly_count--;
// Put the label of the final location
sprintf(label, "__IF%x_FIN", (unsigned int)script->syntax.curly[pos].index);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
if(script->syntax.curly[pos].flag == 1) {
// Because the position of the pointer is the same if not else for this
return bp;
}
return p;
} else if(script->syntax.curly[pos].type == TYPE_DO) {
const char *p2;
if(script->syntax.curly[pos].flag) {
// (Come here continue) to form the label here
sprintf(label, "__DO%x_NXT", (unsigned int)script->syntax.curly[pos].index);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
}
// Skip to the end point if the condition is false
p = script->skip_space(p);
p2 = script->skip_word(p);
if( p2 - p != 5 || strncmp(p, "while", 5) != 0 ) {
disp_error_message("parse_syntax: need 'while'",p);
}
p = script->skip_space(p2);
if(*p != '(') {
disp_error_message("need '('",p);
}
// do-block end is a new line
script->parse_nextline(false, p);
sprintf(label, "__DO%x_FIN", (unsigned int)script->syntax.curly[pos].index);
script->addl(script->add_str("__jump_zero"));
script->addc(C_ARG);
p=script->parse_expr(p);
p=script->skip_space(p);
script->addl(script->add_str(label));
script->addc(C_FUNC);
// Skip to the starting point
sprintf(label, "goto __DO%x_BGN;", (unsigned int)script->syntax.curly[pos].index);
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
// Form label of the end point conditions
sprintf(label, "__DO%x_FIN", (unsigned int)script->syntax.curly[pos].index);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
p = script->skip_space(p);
if(*p != ';') {
disp_error_message("parse_syntax: need ';'",p);
return p+1;
}
p++;
script->syntax.curly_count--;
return p;
} else if(script->syntax.curly[pos].type == TYPE_FOR) {
// for-block end is a new line
script->parse_nextline(false, p);
// Skip to the next loop
sprintf(label, "goto __FR%x_NXT;", (unsigned int)script->syntax.curly[pos].index);
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
// End for labeling
sprintf(label, "__FR%x_FIN", (unsigned int)script->syntax.curly[pos].index);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
script->syntax.curly_count--;
return p;
} else if(script->syntax.curly[pos].type == TYPE_WHILE) {
// while-block end is a new line
script->parse_nextline(false, p);
// Skip to the decision while
sprintf(label, "goto __WL%x_NXT;", (unsigned int)script->syntax.curly[pos].index);
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
// End while labeling
sprintf(label, "__WL%x_FIN", (unsigned int)script->syntax.curly[pos].index);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
script->syntax.curly_count--;
return p;
} else if(script->syntax.curly[pos].type == TYPE_USERFUNC) {
// Back
sprintf(label,"return;");
script->syntax.curly[script->syntax.curly_count++].type = TYPE_NULL;
script->parse_line(label);
script->syntax.curly_count--;
// Put the label of the location
sprintf(label, "__FN%x_FIN", (unsigned int)script->syntax.curly[pos].index);
l=script->add_str(label);
script->set_label(l, VECTOR_LENGTH(script->buf), p);
script->syntax.curly_count--;
return p;
} else {
*flag = 0;
return p;
}
}
/// Retrieves the value of a constant.
static bool script_get_constant(const char *name, int *value)
{
int n = script->search_str(name);
nullpo_retr(false, value);
if( n == -1 || script->str_data[n].type != C_INT )
{// not found or not a constant
return false;
}
value[0] = script->str_data[n].val;
if (script->str_data[n].deprecated) {
ShowWarning("The constant '%s' is deprecated and it will be removed in a future version. Please see the script documentation and constants.conf for an alternative.\n", name);
}
return true;
}
/// Creates new constant or parameter with given value.
static void script_set_constant(const char *name, int value, bool is_parameter, bool is_deprecated)
{
int n = script->add_str(name);
if (script->str_data[n].type == C_NOP) {
script->str_data[n].type = is_parameter ? C_PARAM : C_INT;
script->str_data[n].val = value;
script->str_data[n].deprecated = is_deprecated ? 1 : 0;
} else if( script->str_data[n].type == C_PARAM || script->str_data[n].type == C_INT ) {// existing parameter or constant
ShowError("script_set_constant: Attempted to overwrite existing %s '%s' (old value=%d, new value=%d).\n", ( script->str_data[n].type == C_PARAM ) ? "parameter" : "constant", name, script->str_data[n].val, value);
} else {// existing name
ShowError("script_set_constant: Invalid name for %s '%s' (already defined as %s).\n", is_parameter ? "parameter" : "constant", name, script->op2name(script->str_data[n].type));
}
}
/* adds data to a existent constant in the database, inserted normally via parse */
static void script_set_constant2(const char *name, int value, bool is_parameter, bool is_deprecated)
{
int n = script->add_str(name);
if( script->str_data[n].type == C_PARAM ) {
ShowError("script_set_constant2: Attempted to overwrite existing parameter '%s' with a constant (value=%d).\n", name, value);
return;
}
if( script->str_data[n].type == C_NAME && script->str_data[n].val ) {
ShowWarning("script_set_constant2: Attempted to overwrite existing variable '%s' with a constant (value=%d).\n", name, value);
return;
}
if( script->str_data[n].type == C_INT && value && value != script->str_data[n].val ) { // existing constant
ShowWarning("script_set_constant2: Attempted to overwrite existing constant '%s' (old value=%d, new value=%d).\n", name, script->str_data[n].val, value);
return;
}
if( script->str_data[n].type != C_NOP ) {
script->str_data[n].func = NULL;
script->str_data[n].backpatch = -1;
script->str_data[n].label = -1;
}
script->str_data[n].type = is_parameter ? C_PARAM : C_INT;
script->str_data[n].val = value;
script->str_data[n].deprecated = is_deprecated ? 1 : 0;
}
/**
* Loads the constants database from constants.conf
*/
static void read_constdb(bool reload)
{
struct config_t constants_conf;
char filepath[256];
struct config_setting_t *cdb;
struct config_setting_t *t;
int i = 0;
safesnprintf(filepath, 256, "%s/constants.conf", map->db_path);
if (!libconfig->load_file(&constants_conf, filepath))
return;
if ((cdb = libconfig->setting_get_member(constants_conf.root, "constants_db")) == NULL) {
ShowError("can't read %s\n", filepath);
return;
}
while ((t = libconfig->setting_get_elem(cdb, i++))) {
bool is_deprecated = false;
int value = 0;
const char *name = config_setting_name(t);
const char *p = name;
while (*p != '\0') {
if (!ISALNUM(*p) && *p != '_')
break;
p++;
}
if (*p != '\0') {
ShowWarning("read_constdb: Invalid constant name %s. Skipping.\n", name);
continue;
}
if (strcmp(name, "comment__") == 0) {
const char *comment = libconfig->setting_get_string(t);
if (comment == NULL)
continue;
if (*comment == '\0')
comment = NULL;
script->constdb_comment(comment);
continue;
}
if (config_setting_is_aggregate(t)) {
int i32;
if (!libconfig->setting_lookup_int(t, "Value", &i32)) {
ShowWarning("read_constdb: Invalid entry for %s. Skipping.\n", name);
continue;
}
value = i32;
if (libconfig->setting_lookup_bool(t, "Deprecated", &i32)) {
if (i32 != 0)
is_deprecated = true;
}
} else {
value = libconfig->setting_get_int(t);
}
if (reload) {
int n = script->add_str(name);
script->str_data[n].type = C_NOP; // ensures it will be overwritten
}
script->set_constant(name, value, false, is_deprecated);
}
script->constdb_comment(NULL);
libconfig->destroy(&constants_conf);
}
/**
* Sets the current constdb comment.
*
* This function does nothing (used by plugins only)
*
* @param comment The comment to set (NULL to unset)
*/
static void script_constdb_comment(const char *comment)
{
(void)comment;
}
static void script_load_parameters(void)
{
int i = 0;
struct {
char *name;
enum status_point_types type;
} parameters[] = {
{"BaseExp", SP_BASEEXP},
{"JobExp", SP_JOBEXP},
{"Karma", SP_KARMA},
{"Manner", SP_MANNER},
{"Hp", SP_HP},
{"MaxHp", SP_MAXHP},
{"Sp", SP_SP},
{"MaxSp", SP_MAXSP},
{"StatusPoint", SP_STATUSPOINT},
{"BaseLevel", SP_BASELEVEL},
{"SkillPoint", SP_SKILLPOINT},
{"Class", SP_CLASS},
{"Zeny", SP_ZENY},
{"BankVault", SP_BANKVAULT},
{"Sex", SP_SEX},
{"NextBaseExp", SP_NEXTBASEEXP},
{"NextJobExp", SP_NEXTJOBEXP},
{"Weight", SP_WEIGHT},
{"MaxWeight", SP_MAXWEIGHT},
{"JobLevel", SP_JOBLEVEL},
{"Upper", SP_UPPER},
{"BaseJob", SP_BASEJOB},
{"BaseClass", SP_BASECLASS},
{"killerrid", SP_KILLERRID},
{"killedrid", SP_KILLEDRID},
{"SlotChange", SP_SLOTCHANGE},
{"CharRename", SP_CHARRENAME},
{"ModExp", SP_MOD_EXP},
{"ModDrop", SP_MOD_DROP},
{"ModDeath", SP_MOD_DEATH},
};
script->constdb_comment("Parameters");
for (i=0; i < ARRAYLENGTH(parameters); ++i)
script->set_constant(parameters[i].name, parameters[i].type, true, false);
script->constdb_comment(NULL);
}
// Standard UNIX tab size is 8
#define TAB_SIZE 8
#define update_tabstop(tabstop,chars) \
do { \
(tabstop) -= (chars); \
while ((tabstop) <= 0) (tabstop) += TAB_SIZE; \
} while (false)
/*==========================================
* Display emplacement line of script
*------------------------------------------*/
static const char *script_print_line(StringBuf *buf, const char *p, const char *mark, int line)
{
int i, mark_pos = 0, tabstop = TAB_SIZE;
if( p == NULL || !p[0] ) return NULL;
if( line < 0 )
StrBuf->Printf(buf, "*%5d: ", -line); // len = 8
else
StrBuf->Printf(buf, " %5d: ", line); // len = 8
update_tabstop(tabstop,8); // len = 8
for( i=0; p[i] && p[i] != '\n'; i++ ) {
char c = p[i];
int w = 1;
// Like Clang does, let's print the code with tabs expanded to spaces to ensure that the marker will be under the right character
if( c == '\t' ) {
c = ' ';
w = tabstop;
}
update_tabstop(tabstop, w);
if( p + i < mark)
mark_pos += w;
if( p + i != mark)
StrBuf->Printf(buf, "%*c", w, c);
else
StrBuf->Printf(buf, CL_BT_RED"%*c"CL_RESET, w, c);
}
StrBuf->AppendStr(buf, "\n");
if( mark ) {
StrBuf->AppendStr(buf, " "CL_BT_CYAN); // len = 8
for( ; mark_pos > 0; mark_pos-- ) {
StrBuf->AppendStr(buf, "~");
}
StrBuf->AppendStr(buf, CL_RESET CL_BT_GREEN"^"CL_RESET"\n");
}
return p+i+(p[i] == '\n' ? 1 : 0);
}
#undef TAB_SIZE
#undef update_tabstop
#define CONTEXTLINES 3
static void script_errorwarning_sub(StringBuf *buf, const char *src, const char *file, int start_line, const char *error_msg, const char *error_pos)
{
// Find the line where the error occurred
int j;
int line = start_line;
const char *p, *error_linepos;
const char *linestart[CONTEXTLINES] = { NULL };
for(p=src;p && *p;line++) {
const char *lineend=strchr(p,'\n');
if(lineend==NULL || error_pos= 0 )
StrBuf->Printf(buf, "script error in file '%s' line %d column %"PRIdPTR"\n", file, line, error_pos-error_linepos+1);
else
StrBuf->Printf(buf, "script error in file '%s' item ID %d\n", file, -line);
StrBuf->Printf(buf, " %s\n", error_msg);
for(j = 0; j < CONTEXTLINES; j++ ) {
script->print_line(buf, linestart[j], NULL, line + j - CONTEXTLINES);
}
p = script->print_line(buf, p, error_pos, -line);
for(j = 0; j < CONTEXTLINES; j++) {
p = script->print_line(buf, p, NULL, line + j + 1 );
}
}
#undef CONTEXTLINES
static void script_error(const char *src, const char *file, int start_line, const char *error_msg, const char *error_pos)
{
StringBuf buf;
StrBuf->Init(&buf);
StrBuf->AppendStr(&buf, "\a");
script->errorwarning_sub(&buf, src, file, start_line, error_msg, error_pos);
ShowError("%s", StrBuf->Value(&buf));
StrBuf->Destroy(&buf);
}
static void script_warning(const char *src, const char *file, int start_line, const char *error_msg, const char *error_pos)
{
StringBuf buf;
StrBuf->Init(&buf);
script->errorwarning_sub(&buf, src, file, start_line, error_msg, error_pos);
ShowWarning("%s", StrBuf->Value(&buf));
StrBuf->Destroy(&buf);
}
/*==========================================
* Analysis of the script
*------------------------------------------*/
static struct script_code *parse_script(const char *src, const char *file, int line, int options, int *retval)
{
const char *p,*tmpp;
int i;
struct script_code* code = NULL;
char end;
bool unresolved_names = false;
script->parser_current_src = src;
script->parser_current_file = file;
script->parser_current_line = line;
if( src == NULL )
return NULL;// empty script
if( script->parse_cleanup_timer_id == INVALID_TIMER ) {
script->parse_cleanup_timer_id = timer->add(timer->gettick() + 10, script->parse_cleanup_timer, 0, 0);
}
memset(&script->syntax,0,sizeof(script->syntax));
script->syntax.last_func = -1;/* as valid values are >= 0 */
if( script->parser_current_npc_name ) {
if( !script->translation_db )
script->load_translations();
if( script->translation_db )
script->syntax.translation_db = strdb_get(script->translation_db, script->parser_current_npc_name);
}
VECTOR_TRUNCATE(script->buf);
script->parse_nextline(true, NULL);
// who called parse_script is responsible for clearing the database after using it, but just in case... lets clear it here
if( options&SCRIPT_USE_LABEL_DB )
script->label_count = 0;
script->parse_options = options;
if( setjmp( script->error_jump ) != 0 ) {
//Restore program state when script has problems. [from jA]
const int size = ARRAYLENGTH(script->syntax.curly);
if( script->error_report )
script->error(src,file,line,script->error_msg,script->error_pos);
aFree( script->error_msg );
VECTOR_TRUNCATE(script->buf);
for(i=LABEL_START;istr_num;i++)
if(script->str_data[i].type == C_NOP) script->str_data[i].type = C_NAME;
for(i=0; isyntax.curly[i].case_label);
#ifdef ENABLE_CASE_CHECK
script->local_casecheck.clear();
script->parser_current_src = NULL;
script->parser_current_file = NULL;
script->parser_current_line = 0;
#endif // ENABLE_CASE_CHECK
if (retval) *retval = EXIT_FAILURE;
return NULL;
}
script->parse_syntax_for_flag=0;
p=src;
p=script->skip_space(p);
if( options&SCRIPT_IGNORE_EXTERNAL_BRACKETS )
{// does not require brackets around the script
if (*p == '\0' && !(options&SCRIPT_RETURN_EMPTY_SCRIPT)) {
// empty script and can return NULL
VECTOR_TRUNCATE(script->buf);
#ifdef ENABLE_CASE_CHECK
script->local_casecheck.clear();
script->parser_current_src = NULL;
script->parser_current_file = NULL;
script->parser_current_line = 0;
#endif // ENABLE_CASE_CHECK
return NULL;
}
end = '\0';
}
else
{// requires brackets around the script
if( *p != '{' ) {
disp_error_message("not found '{'",p);
if (retval) *retval = EXIT_FAILURE;
}
p = script->skip_space(p+1);
if (*p == '}' && !(options&SCRIPT_RETURN_EMPTY_SCRIPT)) {
// empty script and can return NULL
VECTOR_TRUNCATE(script->buf);
#ifdef ENABLE_CASE_CHECK
script->local_casecheck.clear();
script->parser_current_src = NULL;
script->parser_current_file = NULL;
script->parser_current_line = 0;
#endif // ENABLE_CASE_CHECK
return NULL;
}
end = '}';
}
// clear references of labels, variables and internal functions
for(i=LABEL_START;istr_num;i++) {
if(
script->str_data[i].type==C_POS || script->str_data[i].type==C_NAME ||
script->str_data[i].type==C_USERFUNC || script->str_data[i].type == C_USERFUNC_POS
) {
script->str_data[i].type=C_NOP;
script->str_data[i].backpatch=-1;
script->str_data[i].label=-1;
}
}
while( script->syntax.curly_count != 0 || *p != end )
{
if( *p == '\0' )
disp_error_message("unexpected end of script",p);
// Special handling only label
tmpp=script->skip_space(script->skip_word(p));
if(*tmpp==':' && !(strncmp(p,"default:",8) == 0 && p + 7 == tmpp)) {
i=script->add_word(p);
script->set_label(i, VECTOR_LENGTH(script->buf), p);
if( script->parse_options&SCRIPT_USE_LABEL_DB )
script->label_add(i, VECTOR_LENGTH(script->buf));
p=tmpp+1;
p=script->skip_space(p);
continue;
}
// All other lumped
p=script->parse_line(p);
p=script->skip_space(p);
script->parse_nextline(false, p);
}
script->addc(C_NOP);
// default unknown references to variables
for (i = LABEL_START; i < script->str_num; i++) {
if (script->str_data[i].type == C_NOP) {
int j;
script->str_data[i].type=C_NAME;
script->str_data[i].label=i;
for (j = script->str_data[i].backpatch; j >= 0 && j != 0x00ffffff; ) {
int next = GETVALUE(&script->buf, j);
SETVALUE(&script->buf, j, i);
j = next;
}
} else if(script->str_data[i].type == C_USERFUNC) {
// 'function name;' without follow-up code
ShowError("parse_script: function '%s' declared but not defined.\n", script->str_buf+script->str_data[i].str);
if (retval) *retval = EXIT_FAILURE;
unresolved_names = true;
}
}
if( unresolved_names ) {
disp_error_message("parse_script: unresolved function references", p);
if (retval) *retval = EXIT_FAILURE;
}
#ifdef SCRIPT_DEBUG_DISP
for (i = 0; i < VECTOR_LENGTH(script->buf); i++) {
if ((i&15) == 0)
ShowMessage("%04x : ",i);
ShowMessage("%02x ", VECTOR_INDEX(script->buf, i));
if ((i&15) == 15)
ShowMessage("\n");
}
ShowMessage("\n");
#endif
#ifdef SCRIPT_DEBUG_DISASM
i = 0;
while (i < VECTOR_LENGTH(script->buf)) {
c_op op = script->get_com(&script->buf, &i);
int j = i; // Note: i is modified in the line above.
ShowMessage("%06x %s", i, script->op2name(op));
switch (op) {
case C_INT:
ShowMessage(" %d", script->get_num(&script->buf, &i));
break;
case C_POS:
ShowMessage(" 0x%06x", *(int*)(&VECTOR_INDEX(script->buf, i))&0xffffff);
i += 3;
break;
case C_NAME:
j = (*(int*)(&VECTOR_INDEX(script->buf, i))&0xffffff);
ShowMessage(" %s", ( j == 0xffffff ) ? "?? unknown ??" : script->get_str(j));
i += 3;
break;
case C_STR:
j = (int)strlen((char*)&VECTOR_INDEX(script->buf, i));
ShowMessage(" %s", &VECTOR_INDEX(script->buf, i));
i += j+1;
break;
}
ShowMessage(CL_CLL"\n");
}
#endif
CREATE(code,struct script_code,1);
VECTOR_INIT(code->script_buf);
VECTOR_ENSURE(code->script_buf, VECTOR_LENGTH(script->buf), 1);
VECTOR_PUSHARRAY(code->script_buf, VECTOR_DATA(script->buf), VECTOR_LENGTH(script->buf));
code->local.vars = NULL;
code->local.arrays = NULL;
#ifdef ENABLE_CASE_CHECK
script->local_casecheck.clear();
script->parser_current_src = NULL;
script->parser_current_file = NULL;
script->parser_current_line = 0;
#endif // ENABLE_CASE_CHECK
return code;
}
/// Returns the player attached to this script, identified by the rid.
/// If there is no player attached, the script is terminated.
static struct map_session_data *script_rid2sd(struct script_state *st)
{
struct map_session_data *sd;
nullpo_retr(NULL, st);
if( !( sd = map->id2sd(st->rid) ) ) {
ShowError("script_rid2sd: fatal error ! player not attached!\n");
script->reportfunc(st);
script->reportsrc(st);
st->state = END;
}
return sd;
}
static struct map_session_data *script_id2sd(struct script_state *st, int account_id)
{
struct map_session_data *sd;
if ((sd = map->id2sd(account_id)) == NULL) {
ShowWarning("script_id2sd: Player with account ID '%d' not found!\n", account_id);
script->reportfunc(st);
script->reportsrc(st);
}
return sd;
}
static struct map_session_data *script_charid2sd(struct script_state *st, int char_id)
{
struct map_session_data *sd;
if ((sd = map->charid2sd(char_id)) == NULL) {
ShowWarning("script_charid2sd: Player with char ID '%d' not found!\n", char_id);
script->reportfunc(st);
script->reportsrc(st);
}
return sd;
}
static struct map_session_data *script_nick2sd(struct script_state *st, const char *name)
{
struct map_session_data *sd;
if ((sd = map->nick2sd(name)) == NULL) {
ShowWarning("script_nick2sd: Player name '%s' not found!\n", name);
script->reportfunc(st);
script->reportsrc(st);
}
return sd;
}
static char *get_val_npcscope_str(struct script_state *st, struct reg_db *n, struct script_data *data)
{
if (n)
return (char*)i64db_get(n->vars, reference_getuid(data));
else
return NULL;
}
static char *get_val_pc_ref_str(struct script_state *st, struct reg_db *n, struct script_data *data)
{
struct script_reg_str *p = NULL;
nullpo_retr(NULL, n);
p = i64db_get(n->vars, reference_getuid(data));
return p ? p->value : NULL;
}
static char *get_val_instance_str(struct script_state *st, const char *name, struct script_data *data)
{
nullpo_retr(NULL, st);
if (st->instance_id >= 0) {
return (char*)i64db_get(instance->list[st->instance_id].regs.vars, reference_getuid(data));
} else {
ShowWarning("script_get_val: cannot access instance variable '%s', defaulting to \"\"\n", name);
return NULL;
}
}
static int get_val_npcscope_num(struct script_state *st, struct reg_db *n, struct script_data *data)
{
if (n)
return (int)i64db_iget(n->vars, reference_getuid(data));
else
return 0;
}
static int get_val_pc_ref_num(struct script_state *st, struct reg_db *n, struct script_data *data)
{
struct script_reg_num *p = NULL;
nullpo_retr(0, n);
p = i64db_get(n->vars, reference_getuid(data));
return p ? p->value : 0;
}
static int get_val_instance_num(struct script_state *st, const char *name, struct script_data *data)
{
if (st->instance_id >= 0)
return (int)i64db_iget(instance->list[st->instance_id].regs.vars, reference_getuid(data));
else {
ShowWarning("script_get_val: cannot access instance variable '%s', defaulting to 0\n", name);
return 0;
}
}
/**
* Dereferences a variable/constant, replacing it with a copy of the value.
*
* @param st[in] script state.
* @param data[in,out] variable/constant.
* @return pointer to data, for convenience.
*/
static struct script_data *get_val(struct script_state *st, struct script_data *data)
{
const char* name;
char prefix;
char postfix;
struct map_session_data *sd = NULL;
if (!data_isreference(data))
return data;// not a variable/constant
name = reference_getname(data);
prefix = name[0];
postfix = name[strlen(name) - 1];
if (strlen(name) > SCRIPT_VARNAME_LENGTH) {
ShowError("script_get_val: variable name too long. '%s'\n", name);
script->reportsrc(st);
st->state = END;
return data;
}
//##TODO use reference_tovariable(data) when it's confirmed that it works [FlavioJS]
if (!reference_toconstant(data) && not_server_variable(prefix) && reference_getref(data) == NULL) {
sd = script->rid2sd(st);
if (sd == NULL) {// needs player attached
if (postfix == '$') {// string variable
ShowWarning("script_get_val: cannot access player variable '%s', defaulting to \"\"\n", name);
data->type = C_CONSTSTR;
data->u.str = "";
} else {// integer variable
ShowWarning("script_get_val: cannot access player variable '%s', defaulting to 0\n", name);
data->type = C_INT;
data->u.num = 0;
}
return data;
}
}
if (postfix == '$') {
// string variable
const char *str = NULL;
switch (prefix) {
case '@':
if (data->ref) {
str = script->get_val_ref_str(st, data->ref, data);
} else {
str = pc->readregstr(sd, data->u.num);
}
break;
case '$':
str = mapreg->readregstr(data->u.num);
break;
case '#':
if (data->ref) {
str = script->get_val_pc_ref_str(st, data->ref, data);
} else if (name[1] == '#') {
str = pc_readaccountreg2str(sd, data->u.num);// global
} else {
str = pc_readaccountregstr(sd, data->u.num);// local
}
break;
case '.':
if (data->ref) {
str = script->get_val_ref_str(st, data->ref, data);
} else if (name[1] == '@') {
str = script->get_val_scope_str(st, &st->stack->scope, data);
} else {
str = script->get_val_npc_str(st, &st->script->local, data);
}
break;
case '\'':
str = script->get_val_instance_str(st, name, data);
break;
default:
if (data->ref) {
str = script->get_val_pc_ref_str(st, data->ref, data);
} else {
str = pc_readglobalreg_str(sd, data->u.num);
}
break;
}
if (str == NULL || str[0] == '\0') {
// empty string
data->type = C_CONSTSTR;
data->u.str = "";
} else {// duplicate string
data->type = C_STR;
data->u.mutstr = aStrdup(str);
}
} else {// integer variable
data->type = C_INT;
if( reference_toconstant(data) ) {
data->u.num = reference_getconstant(data);
} else if( reference_toparam(data) ) {
data->u.num = pc->readparam(sd, reference_getparamtype(data));
} else {
switch (prefix) {
case '@':
if (data->ref) {
data->u.num = script->get_val_ref_num(st, data->ref, data);
} else {
data->u.num = pc->readreg(sd, data->u.num);
}
break;
case '$':
data->u.num = mapreg->readreg(data->u.num);
break;
case '#':
if (data->ref) {
data->u.num = script->get_val_pc_ref_num(st, data->ref, data);
} else if (name[1] == '#') {
data->u.num = pc_readaccountreg2(sd, data->u.num);// global
} else {
data->u.num = pc_readaccountreg(sd, data->u.num);// local
}
break;
case '.':
if (data->ref) {
data->u.num = script->get_val_ref_num(st, data->ref, data);
} else if (name[1] == '@') {
data->u.num = script->get_val_scope_num(st, &st->stack->scope, data);
} else {
data->u.num = script->get_val_npc_num(st, &st->script->local, data);
}
break;
case '\'':
data->u.num = script->get_val_instance_num(st, name, data);
break;
default:
if (data->ref) {
data->u.num = script->get_val_pc_ref_num(st, data->ref, data);
} else {
data->u.num = pc_readglobalreg(sd, data->u.num);
}
break;
}
}
}
data->ref = NULL;
return data;
}
/**
* Retrieves the value of a reference identified by uid (variable, constant, param)
*
* The value is left in the top of the stack and needs to be removed manually.
*
* @param st[in] script state.
* @param uid[in] reference identifier.
* @param ref[in] the container to look up the reference into.
* @return the retrieved value of the reference.
*/
static const void *get_val2(struct script_state *st, int64 uid, struct reg_db *ref)
{
struct script_data* data;
nullpo_retr(NULL, st);
script->push_val(st->stack, C_NAME, uid, ref);
data = script_getdatatop(st, -1);
script->get_val(st, data);
if (data->type == C_INT) // u.num is int32 because it comes from script->get_val
return (const void *)h64BPTRSIZE((int32)data->u.num);
else
return (const void *)h64BPTRSIZE(data->u.str);
}
/**
* Because, currently, array members with key 0 are indifferenciable from normal variables, we should ensure its actually in
* Will be gone as soon as undefined var feature is implemented
**/
static void script_array_ensure_zero(struct script_state *st, struct map_session_data *sd, int64 uid, struct reg_db *ref)
{
const char *name = script->get_str(script_getvarid(uid));
struct reg_db *src = NULL;
bool insert = false;
if (st == NULL) {
// Special case with no st available, only sd
nullpo_retv(sd);
src = script->array_src(NULL, sd, name, ref);
insert = true;
} else {
if (sd == NULL && st->rid != 0)
sd = map->id2sd(st->rid); // Retrieve the missing sd
src = script->array_src(st, sd, name, ref);
if( is_string_variable(name) ) {
const char *str = script->get_val2(st, uid, ref);
if (str != NULL && *str != '\0')
insert = true;
script_removetop(st, -1, 0);
} else {
int32 num = (int32)h64BPTRSIZE(script->get_val2(st, uid, ref));
if( num )
insert = true;
script_removetop(st, -1, 0);
}
}
if (src && src->arrays) {
struct script_array *sa = idb_get(src->arrays, script_getvarid(uid));
if (sa) {
unsigned int i;
ARR_FIND(0, sa->size, i, sa->members[i] == 0);
if( i != sa->size ) {
if( !insert )
script->array_remove_member(src,sa,i);
return;
}
script->array_add_member(sa,0);
} else if (insert) {
script->array_update(src,reference_uid(script_getvarid(uid), 0),false);
}
}
}
/**
* Returns array size by ID
**/
static unsigned int script_array_size(struct script_state *st, struct map_session_data *sd, const char *name, struct reg_db *ref)
{
struct script_array *sa = NULL;
struct reg_db *src = script->array_src(st, sd, name, ref);
if( src && src->arrays )
sa = idb_get(src->arrays, script->search_str(name));
return sa ? sa->size : 0;
}
/**
* Returns array's highest key (for that awful getarraysize implementation that doesn't really gets the array size)
**/
static unsigned int script_array_highest_key(struct script_state *st, struct map_session_data *sd, const char *name, struct reg_db *ref)
{
struct script_array *sa = NULL;
struct reg_db *src = script->array_src(st, sd, name, ref);
if( src && src->arrays ) {
int key = script->add_word(name);
script->array_ensure_zero(st,sd,reference_uid(key, 0),ref);
if( ( sa = idb_get(src->arrays, key) ) ) {
unsigned int i, highest_key = 0;
for(i = 0; i < sa->size; i++) {
if( sa->members[i] > highest_key )
highest_key = sa->members[i];
}
return sa->size ? highest_key + 1 : 0;
}
}
return 0;
}
static int script_free_array_db(union DBKey key, struct DBData *data, va_list ap)
{
struct script_array *sa = DB->data2ptr(data);
aFree(sa->members);
ers_free(script->array_ers, sa);
return 0;
}
/**
* Clears script_array and removes it from script->array_db
**/
static void script_array_delete(struct reg_db *src, struct script_array *sa)
{
nullpo_retv(src);
nullpo_retv(sa);
aFree(sa->members);
idb_remove(src->arrays, sa->id);
ers_free(script->array_ers, sa);
}
/**
* Removes a member from a script_array list
*
* @param idx the index of the member in script_array struct list, not of the actual array member
**/
static void script_array_remove_member(struct reg_db *src, struct script_array *sa, unsigned int idx)
{
unsigned int i, cursor;
nullpo_retv(sa);
/* its the only member left, no need to do anything other than delete the array data */
if( sa->size == 1 ) {
script->array_delete(src,sa);
return;
}
sa->members[idx] = UINT_MAX;
for(i = 0, cursor = 0; i < sa->size; i++) {
if( sa->members[i] == UINT_MAX )
continue;
if( i != cursor )
sa->members[cursor] = sa->members[i];
cursor++;
}
sa->size = cursor;
}
/**
* Appends a new array index to the list in script_array
*
* @param idx the index of the array member being inserted
**/
static void script_array_add_member(struct script_array *sa, unsigned int idx)
{
nullpo_retv(sa);
RECREATE(sa->members, unsigned int, ++sa->size);
sa->members[sa->size - 1] = idx;
}
/**
* Obtains the source of the array database for this type and scenario
* Initializes such database when not yet initialized.
**/
static struct reg_db *script_array_src(struct script_state *st, struct map_session_data *sd, const char *name, struct reg_db *ref)
{
struct reg_db *src = NULL;
nullpo_retr(NULL, name);
switch (name[0]) {
/* from player */
default: /* char reg */
case '@':/* temp char reg */
case '#':/* account reg */
if (ref != NULL) {
src = ref;
} else {
nullpo_retr(NULL, sd);
src = &sd->regs;
}
break;
case '$':/* map reg */
src = &mapreg->regs;
break;
case '.':/* npc/script */
if (ref != NULL) {
src = ref;
} else {
nullpo_retr(NULL, st);
src = (name[1] == '@') ? &st->stack->scope : &st->script->local;
}
break;
case '\'':/* instance */
nullpo_retr(NULL, st);
if (st->instance_id >= 0) {
src = &instance->list[st->instance_id].regs;
}
break;
}
if (src) {
if (!src->arrays) {
src->arrays = idb_alloc(DB_OPT_BASE);
}
return src;
}
return NULL;
}
/**
* Processes a array member modification, and update data accordingly
*
* @param src[in,out] Variable source database. If the array database doesn't exist, it is created.
* @param num[in] Variable ID
* @param empty[in] Whether the modified member is empty (needs to be removed)
**/
static void script_array_update(struct reg_db *src, int64 num, bool empty)
{
struct script_array *sa = NULL;
int id = script_getvarid(num);
unsigned int index = script_getvaridx(num);
nullpo_retv(src);
if (!src->arrays) {
src->arrays = idb_alloc(DB_OPT_BASE);
} else {
sa = idb_get(src->arrays, id);
}
if( sa ) {
unsigned int i;
/* search */
for(i = 0; i < sa->size; i++) {
if( sa->members[i] == index )
break;
}
/* if existent */
if( i != sa->size ) {
/* if empty, we gotta remove it */
if( empty ) {
script->array_remove_member(src, sa, i);
}
} else if( !empty ) { /* new entry */
script->array_add_member(sa,index);
/* we do nothing if its empty, no point in modifying array data for a new empty member */
}
} else if ( !empty ) {/* we only move to create if not empty */
sa = ers_alloc(script->array_ers, struct script_array);
sa->id = id;
sa->members = NULL;
sa->size = 0;
script->array_add_member(sa,index);
idb_put(src->arrays, id, sa);
}
}
static void set_reg_npcscope_str(struct script_state *st, struct reg_db *n, int64 num, const char *name, const char *str)
{
if (n)
{
nullpo_retv(str);
if (str[0]) {
i64db_put(n->vars, num, aStrdup(str));
if (script_getvaridx(num))
script->array_update(n, num, false);
} else {
i64db_remove(n->vars, num);
if (script_getvaridx(num))
script->array_update(n, num, true);
}
}
}
static void set_reg_pc_ref_str(struct script_state *st, struct reg_db *n, int64 num, const char *name, const char *str)
{
struct script_reg_str *p = NULL;
unsigned int index = script_getvaridx(num);
nullpo_retv(n);
if ((p = i64db_get(n->vars, num)) != NULL) {
if (str[0]) {
if (p->value) {
aFree(p->value);
} else if (index) {
script->array_update(n, num, false);
}
p->value = aStrdup(str);
} else {
p->value = NULL;
if (index) {
script->array_update(n, num, true);
}
}
if (!pc->reg_load) {
p->flag.update = 1;
}
} else if (str[0]) {
struct DBData prev;
if (index) {
script->array_update(n, num, false);
}
p = ers_alloc(pc->str_reg_ers, struct script_reg_str);
p->value = aStrdup(str);
if (!pc->reg_load) {
p->flag.update = 1;
}
p->flag.type = 1;
if(n->vars->put(n->vars, DB->i642key(num), DB->ptr2data(p), &prev)) {
p = DB->data2ptr(&prev);
if (p->value) {
aFree(p->value);
}
ers_free(pc->str_reg_ers, p);
}
}
}
static void set_reg_pc_ref_num(struct script_state *st, struct reg_db *n, int64 num, const char *name, int val)
{
struct script_reg_num *p = NULL;
unsigned int index = script_getvaridx(num);
nullpo_retv(n);
if ((p = i64db_get(n->vars, num)) != NULL) {
if (val) {
if (!p->value && index) {
script->array_update(n, num, false);
}
p->value = val;
} else {
p->value = 0;
if (index) {
script->array_update(n, num, true);
}
}
if (!pc->reg_load) {
p->flag.update = 1;
}
} else if (val) {
struct DBData prev;
if (index) {
script->array_update(n, num, false);
}
p = ers_alloc(pc->num_reg_ers, struct script_reg_num);
p->value = val;
if (!pc->reg_load) {
p->flag.update = 1;
}
p->flag.type = 1;
if(n->vars->put(n->vars, DB->i642key(num), DB->ptr2data(p), &prev)) {
p = DB->data2ptr(&prev);
ers_free(pc->num_reg_ers, p);
}
}
}
static void set_reg_npcscope_num(struct script_state *st, struct reg_db *n, int64 num, const char *name, int val)
{
if (n) {
if (val != 0) {
i64db_iput(n->vars, num, val);
if (script_getvaridx(num))
script->array_update(n, num, false);
} else {
i64db_remove(n->vars, num);
if (script_getvaridx(num))
script->array_update(n, num, true);
}
}
}
static void set_reg_instance_str(struct script_state *st, int64 num, const char *name, const char *str)
{
nullpo_retv(st);
if (st->instance_id >= 0) {
if (str[0]) {
i64db_put(instance->list[st->instance_id].regs.vars, num, aStrdup(str));
if (script_getvaridx(num))
script->array_update(&instance->list[st->instance_id].regs, num, false);
} else {
i64db_remove(instance->list[st->instance_id].regs.vars, num);
if (script_getvaridx(num))
script->array_update(&instance->list[st->instance_id].regs, num, true);
}
} else {
ShowError("script_set_reg: cannot write instance variable '%s', NPC not in a instance!\n", name);
script->reportsrc(st);
}
}
static void set_reg_instance_num(struct script_state *st, int64 num, const char *name, int val)
{
nullpo_retv(st);
if (st->instance_id >= 0) {
if (val != 0) {
i64db_iput(instance->list[st->instance_id].regs.vars, num, val);
if (script_getvaridx(num))
script->array_update(&instance->list[st->instance_id].regs, num, false);
} else {
i64db_remove(instance->list[st->instance_id].regs.vars, num);
if (script_getvaridx(num))
script->array_update(&instance->list[st->instance_id].regs, num, true);
}
} else {
ShowError("script_set_reg: cannot write instance variable '%s', NPC not in a instance!\n", name);
script->reportsrc(st);
}
}
/**
* Stores the value of a script variable
*
* @param st current script state.
* @param sd current character data.
* @param num variable identifier.
* @param name variable name.
* @param value new value.
* @param ref variable container, in case of a npc/scope variable reference outside the current scope.
* @retval 0 failure.
* @retval 1 success.
*
* TODO: return values are screwed up, have been for some time (reaad: years), e.g. some functions return 1 failure and success.
*------------------------------------------*/
static int set_reg(struct script_state *st, struct map_session_data *sd, int64 num, const char *name, const void *value, struct reg_db *ref)
{
char prefix;
nullpo_ret(name);
prefix = name[0];
if (strlen(name) > SCRIPT_VARNAME_LENGTH) {
ShowError("script:set_reg: variable name too long. '%s'\n", name);
if (st) {
script->reportsrc(st);
st->state = END;
}
return 0;
}
if (is_string_variable(name)) {// string variable
const char *str = (const char*)value;
switch (prefix) {
case '@':
if (ref) {
script->set_reg_ref_str(st, ref, num, name, str);
} else {
pc->setregstr(sd, num, str);
}
return 1;
case '$':
mapreg->setregstr(num, str);
return 1;
case '#':
if (ref) {
script->set_reg_pc_ref_str(st, ref, num, name, str);
} else if (name[1] == '#') {
pc_setaccountreg2str(sd, num, str);
} else {
pc_setaccountregstr(sd, num, str);
}
return 1;
case '.':
if (ref) {
script->set_reg_ref_str(st, ref, num, name, str);
} else if (name[1] == '@') {
script->set_reg_scope_str(st, &st->stack->scope, num, name, str);
} else {
script->set_reg_npc_str(st, &st->script->local, num, name, str);
}
return 1;
case '\'':
set_reg_instance_str(st, num, name, str);
return 1;
default:
if (ref) {
script->set_reg_pc_ref_str(st, ref, num, name, str);
} else {
pc_setglobalreg_str(sd, num, str);
}
return 1;
}
} else {// integer variable
// FIXME: This isn't safe, in 32bits systems we're converting a 64bit pointer
// to a 32bit int, this will lead to overflows! [Panikon]
int val = (int)h64BPTRSIZE(value);
if (script->str_data[script_getvarid(num)].type == C_PARAM) {
if (pc->setparam(sd, script->str_data[script_getvarid(num)].val, val) == 0) {
if (st != NULL) {
ShowError("script:set_reg: failed to set param '%s' to %d.\n", name, val);
script->reportsrc(st);
// Instead of just stop the script execution we let the character close
// the window if it was open.
st->state = (sd->state.dialog) ? CLOSE : END;
if(st->state == CLOSE) {
clif->scriptclose(sd, st->oid);
}
}
return 0;
}
return 1;
}
switch (prefix) {
case '@':
if (ref) {
script->set_reg_ref_num(st, ref, num, name, val);
} else {
pc->setreg(sd, num, val);
}
return 1;
case '$':
mapreg->setreg(num, val);
return 1;
case '#':
if (ref) {
script->set_reg_pc_ref_num(st, ref, num, name, val);
} else if (name[1] == '#') {
pc_setaccountreg2(sd, num, val);
} else {
pc_setaccountreg(sd, num, val);
}
return 1;
case '.':
if (ref) {
script->set_reg_ref_num(st, ref, num, name, val);
} else if (name[1] == '@') {
script->set_reg_scope_num(st, &st->stack->scope, num, name, val);
} else {
script->set_reg_npc_num(st, &st->script->local, num, name, val);
}
return 1;
case '\'':
set_reg_instance_num(st, num, name, val);
return 1;
default:
if (ref) {
script->set_reg_pc_ref_num(st, ref, num, name, val);
} else {
pc_setglobalreg(sd, num, val);
}
return 1;
}
}
}
static int set_var(struct map_session_data *sd, char *name, void *val)
{
return script->set_reg(NULL, sd, reference_uid(script->add_str(name),0), name, val, NULL);
}
static void setd_sub(struct script_state *st, struct map_session_data *sd, const char *varname, int elem, const void *value, struct reg_db *ref)
{
script->set_reg(st, sd, reference_uid(script->add_str(varname),elem), varname, value, ref);
}
/// Converts the data to a string
static const char *conv_str(struct script_state *st, struct script_data *data)
{
script->get_val(st, data);
if (data_isstring(data)) {
// nothing to convert
return data->u.str;
}
if (data_isint(data)) {
// int -> string
char *p;
CREATE(p, char, ITEM_NAME_LENGTH);
snprintf(p, ITEM_NAME_LENGTH, "%"PRId64"", data->u.num);
p[ITEM_NAME_LENGTH-1] = '\0';
data->type = C_STR;
data->u.mutstr = p;
return data->u.mutstr;
}
if (data_isreference(data)) {
// reference -> string
//##TODO when does this happen (check script->get_val) [FlavioJS]
data->type = C_CONSTSTR;
data->u.str = reference_getname(data);
return data->u.str;
}
// unsupported data type
ShowError("script:conv_str: cannot convert to string, defaulting to \"\"\n");
script->reportdata(data);
script->reportsrc(st);
data->type = C_CONSTSTR;
data->u.str = "";
return data->u.str;
}
/// Converts the data to an int
static int conv_num(struct script_state *st, struct script_data *data)
{
long num;
script->get_val(st, data);
if (data_isint(data)) {
// nothing to convert
return (int)data->u.num;
}
if (data_isstring(data)) {
// string -> int
// the result does not overflow or underflow, it is capped instead
// ex: 999999999999 is capped to INT_MAX (2147483647)
errno = 0;
num = strtol(data->u.str, NULL, 10);// change radix to 0 to support octal numbers "o377" and hex numbers "0xFF"
if( errno == ERANGE
#if LONG_MAX > INT_MAX
|| num < INT_MIN || num > INT_MAX
#endif
)
{
if( num <= INT_MIN )
{
num = INT_MIN;
ShowError("script:conv_num: underflow detected, capping to %ld\n", num);
}
else//if( num >= INT_MAX )
{
num = INT_MAX;
ShowError("script:conv_num: overflow detected, capping to %ld\n", num);
}
script->reportdata(data);
script->reportsrc(st);
}
if (data->type == C_STR)
aFree(data->u.mutstr);
data->type = C_INT;
data->u.num = (int)num;
return (int)data->u.num;
}
#if 0
// unsupported data type
// FIXME this function is being used to retrieve the position of labels and
// probably other stuff [FlavioJS]
ShowError("script:conv_num: cannot convert to number, defaulting to 0\n");
script->reportdata(data);
script->reportsrc(st);
data->type = C_INT;
data->u.num = 0;
#endif
return (int)data->u.num;
}
//
// Stack operations
//
/// Increases the size of the stack
static void stack_expand(struct script_stack *stack)
{
nullpo_retv(stack);
stack->sp_max += 64;
stack->stack_data = (struct script_data*)aRealloc(stack->stack_data,
stack->sp_max * sizeof(stack->stack_data[0]) );
memset(stack->stack_data + (stack->sp_max - 64), 0,
64 * sizeof(stack->stack_data[0]) );
}
/// Pushes a value into the stack (with reference)
static struct script_data *push_val(struct script_stack *stack, enum c_op type, int64 val, struct reg_db *ref)
{
nullpo_retr(NULL, stack);
if( stack->sp >= stack->sp_max )
script->stack_expand(stack);
stack->stack_data[stack->sp].type = type;
stack->stack_data[stack->sp].u.num = val;
stack->stack_data[stack->sp].ref = ref;
stack->sp++;
return &stack->stack_data[stack->sp-1];
}
/// Pushes a string into the stack
static struct script_data *push_str(struct script_stack *stack, char *str)
{
nullpo_retr(NULL, stack);
if( stack->sp >= stack->sp_max )
script->stack_expand(stack);
stack->stack_data[stack->sp].type = C_STR;
stack->stack_data[stack->sp].u.mutstr = str;
stack->stack_data[stack->sp].ref = NULL;
stack->sp++;
return &stack->stack_data[stack->sp-1];
}
/// Pushes a constant string into the stack
static struct script_data *push_conststr(struct script_stack *stack, const char *str)
{
nullpo_retr(NULL, stack);
if( stack->sp >= stack->sp_max )
script->stack_expand(stack);
stack->stack_data[stack->sp].type = C_CONSTSTR;
stack->stack_data[stack->sp].u.str = str;
stack->stack_data[stack->sp].ref = NULL;
stack->sp++;
return &stack->stack_data[stack->sp-1];
}
/// Pushes a retinfo into the stack
static struct script_data *push_retinfo(struct script_stack *stack, struct script_retinfo *ri, struct reg_db *ref)
{
nullpo_retr(NULL, stack);
if( stack->sp >= stack->sp_max )
script->stack_expand(stack);
stack->stack_data[stack->sp].type = C_RETINFO;
stack->stack_data[stack->sp].u.ri = ri;
stack->stack_data[stack->sp].ref = ref;
stack->sp++;
return &stack->stack_data[stack->sp-1];
}
/// Pushes a copy of the target position into the stack
static struct script_data *push_copy(struct script_stack *stack, int pos)
{
nullpo_retr(NULL, stack);
switch( stack->stack_data[pos].type ) {
case C_CONSTSTR:
return script->push_conststr(stack, stack->stack_data[pos].u.str);
break;
case C_STR:
return script->push_str(stack, aStrdup(stack->stack_data[pos].u.mutstr));
break;
case C_RETINFO:
ShowFatalError("script:push_copy: can't create copies of C_RETINFO. Exiting...\n");
exit(1);
break;
default:
return script->push_val(
stack,stack->stack_data[pos].type,
stack->stack_data[pos].u.num,
stack->stack_data[pos].ref
);
break;
}
}
/// Removes the values in indexes [start,end[ from the stack.
/// Adjusts all stack pointers.
static void pop_stack(struct script_state *st, int start, int end)
{
struct script_stack* stack;
struct script_data* data;
int i;
nullpo_retv(st);
stack = st->stack;
if( start < 0 )
start = 0;
if( end > stack->sp )
end = stack->sp;
if( start >= end )
return;// nothing to pop
// free stack elements
for( i = start; i < end; i++ )
{
data = &stack->stack_data[i];
if (data->type == C_STR)
aFree(data->u.mutstr);
if( data->type == C_RETINFO )
{
struct script_retinfo* ri = data->u.ri;
if( ri->scope.vars ) {
// Note: This is necessary evern if we're also doing it in run_func
// (in the RETFUNC block) because not all functions return. If a
// function (or a sub) has an 'end' or a 'close', it'll reach this
// block with its scope vars still to be freed.
script->free_vars(ri->scope.vars);
ri->scope.vars = NULL;
}
if( ri->scope.arrays ) {
ri->scope.arrays->destroy(ri->scope.arrays,script->array_free_db);
ri->scope.arrays = NULL;
}
if( data->ref )
aFree(data->ref);
aFree(ri);
}
data->type = C_NOP;
}
// move the rest of the elements
if( stack->sp > end )
{
memmove(&stack->stack_data[start], &stack->stack_data[end], sizeof(stack->stack_data[0])*(stack->sp - end));
for( i = start + stack->sp - end; i < stack->sp; ++i )
stack->stack_data[i].type = C_NOP;
}
// adjust stack pointers
if( st->start > end )
st->start -= end - start;
else if( st->start > start )
st->start = start;
if( st->end > end )
st->end -= end - start;
else if( st->end > start )
st->end = start;
if( stack->defsp > end )
stack->defsp -= end - start;
else if( stack->defsp > start )
stack->defsp = start;
stack->sp -= end - start;
}
///
///
///
/*==========================================
* Release script dependent variable, dependent variable of function
*------------------------------------------*/
static void script_free_vars(struct DBMap *var_storage)
{
if( var_storage ) {
// destroy the storage construct containing the variables
db_destroy(var_storage);
}
}
static void script_free_code(struct script_code *code)
{
nullpo_retv(code);
if (code->instances)
script->stop_instances(code);
script->free_vars(code->local.vars);
if (code->local.arrays)
code->local.arrays->destroy(code->local.arrays,script->array_free_db);
VECTOR_CLEAR(code->script_buf);
aFree(code);
}
/// Creates a new script state.
///
/// @param script Script code
/// @param pos Position in the code
/// @param rid Who is running the script (attached player)
/// @param oid Where the code is being run (npc 'object')
/// @return Script state
static struct script_state *script_alloc_state(struct script_code *rootscript, int pos, int rid, int oid)
{
struct script_state* st;
st = ers_alloc(script->st_ers, struct script_state);
st->stack = ers_alloc(script->stack_ers, struct script_stack);
st->pending_refs = NULL;
st->pending_ref_count = 0;
st->stack->sp = 0;
st->stack->sp_max = 64;
CREATE(st->stack->stack_data, struct script_data, st->stack->sp_max);
st->stack->defsp = st->stack->sp;
st->stack->scope.vars = i64db_alloc(DB_OPT_RELEASE_DATA);
st->stack->scope.arrays = NULL;
st->state = RUN;
st->script = rootscript;
st->pos = pos;
st->rid = rid;
st->oid = oid;
st->sleep.timer = INVALID_TIMER;
st->npc_item_flag = battle_config.item_enabled_npc;
if( st->script->instances != USHRT_MAX )
st->script->instances++;
else {
struct npc_data *nd = map->id2nd(oid);
ShowError("over 65k instances of '%s' script are being run\n",nd ? nd->name : "unknown");
}
if( !st->script->local.vars )
st->script->local.vars = i64db_alloc(DB_OPT_RELEASE_DATA);
st->id = script->next_id++;
script->active_scripts++;
idb_put(script->st_db, st->id, st);
return st;
}
/// Frees a script state.
///
/// @param st Script state
static void script_free_state(struct script_state *st)
{
nullpo_retv(st);
if( idb_exists(script->st_db,st->id) ) {
struct map_session_data *sd = st->rid ? map->id2sd(st->rid) : NULL;
if(st->bk_st) {// backup was not restored
ShowDebug("script_free_state: Previous script state lost (rid=%d, oid=%d, state=%u, bk_npcid=%d).\n", st->bk_st->rid, st->bk_st->oid, st->bk_st->state, st->bk_npcid);
}
if(sd && sd->st == st) { //Current script is aborted.
if(sd->state.using_fake_npc){
clif->clearunit_single(sd->npc_id, CLR_OUTSIGHT, sd->fd);
sd->state.using_fake_npc = 0;
}
sd->st = NULL;
sd->npc_id = 0;
}
if( st->sleep.timer != INVALID_TIMER )
timer->delete(st->sleep.timer, script->run_timer);
if( st->stack ) {
script->free_vars(st->stack->scope.vars);
if( st->stack->scope.arrays )
st->stack->scope.arrays->destroy(st->stack->scope.arrays,script->array_free_db);
script->pop_stack(st, 0, st->stack->sp);
aFree(st->stack->stack_data);
ers_free(script->stack_ers, st->stack);
st->stack = NULL;
}
if( st->script && st->script->instances != USHRT_MAX && --st->script->instances == 0 ) {
if( st->script->local.vars && !db_size(st->script->local.vars) ) {
script->free_vars(st->script->local.vars);
st->script->local.vars = NULL;
}
if( st->script->local.arrays && !db_size(st->script->local.arrays) ) {
st->script->local.arrays->destroy(st->script->local.arrays,script->array_free_db);
st->script->local.arrays = NULL;
}
}
st->pos = -1;
if (st->pending_ref_count > 0) {
while (st->pending_ref_count > 0)
aFree(st->pending_refs[--st->pending_ref_count]);
aFree(st->pending_refs);
st->pending_refs = NULL;
}
idb_remove(script->st_db, st->id);
ers_free(script->st_ers, st);
if( --script->active_scripts == 0 ) {
script->next_id = 0;
}
}
}
/**
* Adds a pending reference entry to the current script.
*
* @see struct script_state::pending_refs
*
* @param st[in] Script state.
* @param ref[in] Reference to be added.
*/
static void script_add_pending_ref(struct script_state *st, struct reg_db *ref)
{
nullpo_retv(st);
RECREATE(st->pending_refs, struct reg_db*, ++st->pending_ref_count);
st->pending_refs[st->pending_ref_count-1] = ref;
}
//
// Main execution unit
//
/*==========================================
* Read command
*------------------------------------------*/
static c_op get_com(const struct script_buf *scriptbuf, int *pos)
{
int i = 0, j = 0;
if (VECTOR_INDEX(*scriptbuf, *pos) >= 0x80) {
return C_INT;
}
while (VECTOR_INDEX(*scriptbuf, *pos) >= 0x40) {
i = VECTOR_INDEX(*scriptbuf, (*pos)++) << j;
j+=6;
}
return (c_op)(i+(VECTOR_INDEX(*scriptbuf, (*pos)++)<= 0xc0) {
i+= (VECTOR_INDEX(*scriptbuf, (*pos)++)&0x7f)<get_val(st, data);
if (data_isstring(data)) {
flag = data->u.str[0]; // "" -> false
} else if (data_isint(data)) {
flag = data->u.num == 0 ? 0 : 1;// 0 -> false
} else {
ShowError("script:op_3: invalid data for the ternary operator test\n");
script->reportdata(data);
script->reportsrc(st);
script_removetop(st, -3, 0);
script_pushnil(st);
return;
}
if( flag )
script_pushcopytop(st, -2);
else
script_pushcopytop(st, -1);
script_removetop(st, -4, -1);
}
/// Binary string operators
/// s1 EQ s2 -> i
/// s1 NE s2 -> i
/// s1 GT s2 -> i
/// s1 GE s2 -> i
/// s1 LT s2 -> i
/// s1 LE s2 -> i
/// s1 RE_EQ s2 -> i
/// s1 RE_NE s2 -> i
/// s1 ADD s2 -> s
static void op_2str(struct script_state *st, int op, const char *s1, const char *s2)
{
int a = 0;
switch(op) {
case C_EQ: a = (strcmp(s1,s2) == 0); break;
case C_NE: a = (strcmp(s1,s2) != 0); break;
case C_GT: a = (strcmp(s1,s2) > 0); break;
case C_GE: a = (strcmp(s1,s2) >= 0); break;
case C_LT: a = (strcmp(s1,s2) < 0); break;
case C_LE: a = (strcmp(s1,s2) <= 0); break;
case C_RE_EQ:
case C_RE_NE:
{
int inputlen = (int)strlen(s1);
pcre *compiled_regex;
pcre_extra *extra_regex;
const char *pcre_error, *pcre_match;
int pcre_erroroffset, offsetcount;
int offsets[256*3]; // (max_capturing_groups+1)*3
compiled_regex = libpcre->compile(s2, 0, &pcre_error, &pcre_erroroffset, NULL);
if( compiled_regex == NULL ) {
ShowError("script:op2_str: Invalid regex '%s'.\n", s2);
script->reportsrc(st);
script_pushnil(st);
st->state = END;
return;
}
extra_regex = libpcre->study(compiled_regex, 0, &pcre_error);
if( pcre_error != NULL ) {
libpcre->free(compiled_regex);
ShowError("script:op2_str: Unable to optimize the regex '%s': %s\n", s2, pcre_error);
script->reportsrc(st);
script_pushnil(st);
st->state = END;
return;
}
offsetcount = libpcre->exec(compiled_regex, extra_regex, s1, inputlen, 0, 0, offsets, 256*3);
if( offsetcount == 0 ) {
offsetcount = 256;
} else if( offsetcount == PCRE_ERROR_NOMATCH ) {
offsetcount = 0;
} else if( offsetcount < 0 ) {
libpcre->free(compiled_regex);
if( extra_regex != NULL )
libpcre->free(extra_regex);
ShowWarning("script:op2_str: Unable to process the regex '%s'.\n", s2);
script->reportsrc(st);
script_pushnil(st);
st->state = END;
return;
}
if (op == C_RE_EQ) {
int i;
for (i = 0; i < offsetcount; i++) {
libpcre->get_substring(s1, offsets, offsetcount, i, &pcre_match);
mapreg->setregstr(reference_uid(script->add_str("$@regexmatch$"), i), pcre_match);
libpcre->free_substring(pcre_match);
}
mapreg->setreg(script->add_str("$@regexmatchcount"), i);
a = offsetcount;
} else { // C_RE_NE
a = (offsetcount == 0);
}
libpcre->free(compiled_regex);
if( extra_regex != NULL )
libpcre->free(extra_regex);
}
break;
case C_ADD:
{
char* buf = (char *)aMalloc((strlen(s1)+strlen(s2)+1)*sizeof(char));
strcpy(buf, s1);
strcat(buf, s2);
script_pushstr(st, buf);
return;
}
default:
ShowError("script:op2_str: unexpected string operator %s\n", script->op2name(op));
script->reportsrc(st);
script_pushnil(st);
st->state = END;
return;
}
script_pushint(st,a);
}
/// Binary number operators
/// i OP i -> i
static void op_2num(struct script_state *st, int op, int i1, int i2)
{
int ret;
int64 ret64;
switch( op ) {
case C_AND: ret = i1 & i2; break;
case C_OR: ret = i1 | i2; break;
case C_XOR: ret = i1 ^ i2; break;
case C_LAND: ret = (i1 && i2); break;
case C_LOR: ret = (i1 || i2); break;
case C_EQ: ret = (i1 == i2); break;
case C_NE: ret = (i1 != i2); break;
case C_GT: ret = (i1 > i2); break;
case C_GE: ret = (i1 >= i2); break;
case C_LT: ret = (i1 < i2); break;
case C_LE: ret = (i1 <= i2); break;
case C_R_SHIFT: ret = i1>>i2; break;
case C_L_SHIFT: ret = i1<op2name(op), i1, i2);
script->reportsrc(st);
script_pushnil(st);
st->state = END;
return;
}
else if( op == C_DIV )
ret = i1 / i2;
else//if( op == C_MOD )
ret = i1 % i2;
break;
default:
switch (op) { // operators that can overflow/underflow
case C_ADD: ret = i1 + i2; ret64 = (int64)i1 + i2; break;
case C_SUB: ret = i1 - i2; ret64 = (int64)i1 - i2; break;
case C_MUL: ret = i1 * i2; ret64 = (int64)i1 * i2; break;
case C_POW: ret = (int)pow((double)i1, (double)i2); ret64 = (int64)pow((double)i1, (double)i2); break;
default:
ShowError("script:op_2num: unexpected number operator %s i1=%d i2=%d\n", script->op2name(op), i1, i2);
script->reportsrc(st);
script_pushnil(st);
return;
}
if (ret64 < INT_MIN) {
ShowWarning("script:op_2num: underflow detected op=%s i1=%d i2=%d\n", script->op2name(op), i1, i2);
script->reportsrc(st);
ret = INT_MIN;
} else if (ret64 > INT_MAX) {
ShowWarning("script:op_2num: overflow detected op=%s i1=%d i2=%d\n", script->op2name(op), i1, i2);
script->reportsrc(st);
ret = INT_MAX;
}
}
script_pushint(st, ret);
}
/// Binary operators
static void op_2(struct script_state *st, int op)
{
struct script_data* left, leftref;
struct script_data* right;
leftref.type = C_NOP;
left = script_getdatatop(st, -2);
right = script_getdatatop(st, -1);
if (st->op2ref) {
if (data_isreference(left)) {
leftref = *left;
}
st->op2ref = 0;
}
script->get_val(st, left);
script->get_val(st, right);
// automatic conversions
switch( op )
{
case C_ADD:
if( data_isint(left) && data_isstring(right) )
{// convert int-string to string-string
script->conv_str(st, left);
}
else if( data_isstring(left) && data_isint(right) )
{// convert string-int to string-string
script->conv_str(st, right);
}
break;
}
if( data_isstring(left) && data_isstring(right) )
{// ss => op_2str
script->op_2str(st, op, left->u.str, right->u.str);
script_removetop(st, leftref.type == C_NOP ? -3 : -2, -1);// pop the two values before the top one
if (leftref.type != C_NOP) {
if (left->type == C_STR) // don't free C_CONSTSTR
aFree(left->u.mutstr);
*left = leftref;
}
}
else if( data_isint(left) && data_isint(right) )
{// ii => op_2num
int i1 = (int)left->u.num;
int i2 = (int)right->u.num;
script_removetop(st, leftref.type == C_NOP ? -2 : -1, 0);
script->op_2num(st, op, i1, i2);
if (leftref.type != C_NOP)
*left = leftref;
}
else
{// invalid argument
ShowError("script:op_2: invalid data for operator %s\n", script->op2name(op));
script->reportdata(left);
script->reportdata(right);
script->reportsrc(st);
script_removetop(st, -2, 0);
script_pushnil(st);
st->state = END;
}
}
/// Unary operators
/// NEG i -> i
/// NOT i -> i
/// LNOT i -> i
static void op_1(struct script_state *st, int op)
{
struct script_data* data;
int i1;
data = script_getdatatop(st, -1);
script->get_val(st, data);
if( !data_isint(data) )
{// not a number
ShowError("script:op_1: argument is not a number (op=%s)\n", script->op2name(op));
script->reportdata(data);
script->reportsrc(st);
script_pushnil(st);
st->state = END;
return;
}
i1 = (int)data->u.num;
script_removetop(st, -1, 0);
switch( op ) {
case C_NEG: i1 = -i1; break;
case C_NOT: i1 = ~i1; break;
case C_LNOT: i1 = !i1; break;
default:
ShowError("script:op_1: unexpected operator %s i1=%d\n", script->op2name(op), i1);
script->reportsrc(st);
script_pushnil(st);
st->state = END;
return;
}
script_pushint(st, i1);
}
/// Checks the type of all arguments passed to a built-in function.
///
/// @param st Script state whose stack arguments should be inspected.
/// @param func Built-in function for which the arguments are intended.
static bool script_check_buildin_argtype(struct script_state *st, int func)
{
int idx, invalid = 0;
char* sf;
if (script->str_data[func].val < 0 || script->str_data[func].val >= script->buildin_count) {
ShowDebug("Function: %s\n", script->get_str(func));
ShowError("Script data corruption detected!\n");
script->reportsrc(st);
return false;
}
sf = script->buildin[script->str_data[func].val];
for (idx = 2; script_hasdata(st, idx); idx++) {
struct script_data* data = script_getdata(st, idx);
char type = sf[idx-2];
const char* name = NULL;
if (type == '?' || type == '*') {
// optional argument or unknown number of optional parameters ( no types are after this )
break;
}
if (type == 0) {
// more arguments than necessary ( should not happen, as it is checked before )
ShowWarning("Found more arguments than necessary. unexpected arg type %s\n",script->op2name(data->type));
invalid++;
break;
}
if (data_isreference(data)) {
// get name for variables to determine the type they refer to
name = reference_getname(data);
}
switch (type) {
case 'v':
if (!data_isstring(data) && !data_isint(data) && !data_isreference(data)) {
// variant
ShowWarning("Unexpected type for argument %d. Expected string, number or variable.\n", idx-1);
script->reportdata(data);
invalid++;
}
break;
case 's':
if (!data_isstring(data) && !(data_isreference(data) && is_string_variable(name))) {
// string
ShowWarning("Unexpected type for argument %d. Expected string.\n", idx-1);
script->reportdata(data);
invalid++;
}
break;
case 'i':
if (!data_isint(data) && !(data_isreference(data) && (reference_toparam(data) || reference_toconstant(data) || !is_string_variable(name)))) {
// int ( params and constants are always int )
ShowWarning("Unexpected type for argument %d. Expected number.\n", idx-1);
script->reportdata(data);
invalid++;
}
break;
case 'r':
if (!data_isreference(data) || reference_toconstant(data)) {
// variables
ShowWarning("Unexpected type for argument %d. Expected variable, got %s.\n", idx-1,script->op2name(data->type));
script->reportdata(data);
invalid++;
}
break;
case 'l':
if (!data_islabel(data) && !data_isfunclabel(data)) {
// label
ShowWarning("Unexpected type for argument %d. Expected label, got %s\n", idx-1,script->op2name(data->type));
script->reportdata(data);
invalid++;
}
break;
}
}
if (invalid) {
ShowDebug("Function: %s\n", script->get_str(func));
script->reportsrc(st);
}
return true;
}
/// Executes a buildin command.
/// Stack: C_NAME() C_ARG ...
static int run_func(struct script_state *st)
{
struct script_data* data;
int i,start_sp,end_sp,func;
nullpo_retr(1, st);
end_sp = st->stack->sp;// position after the last argument
for( i = end_sp-1; i > 0 ; --i )
if( st->stack->stack_data[i].type == C_ARG )
break;
if( i == 0 )
{
ShowError("script:run_func: C_ARG not found. please report this!!!\n");
st->state = END;
script->reportsrc(st);
return 1;
}
start_sp = i-1;// C_NAME of the command
st->start = start_sp;
st->end = end_sp;
data = &st->stack->stack_data[st->start];
if( data->type == C_NAME && script->str_data[data->u.num].type == C_FUNC )
func = (int)data->u.num;
else
{
ShowError("script:run_func: not a buildin command.\n");
script->reportdata(data);
script->reportsrc(st);
st->state = END;
return 1;
}
if( script->config.warn_func_mismatch_argtypes ) {
if (script->check_buildin_argtype(st, func) == false)
{
st->state = END;
return 1;
}
}
if(script->str_data[func].func) {
if (!(script->str_data[func].func(st))) //Report error
script->reportsrc(st);
} else {
ShowError("script:run_func: '%s' (id=%d type=%s) has no C function. please report this!!!\n",
script->get_str(func), func, script->op2name(script->str_data[func].type));
script->reportsrc(st);
st->state = END;
}
// Stack's datum are used when re-running functions [Eoe]
if( st->state == RERUNLINE )
return 0;
script->pop_stack(st, st->start, st->end);
if( st->state == RETFUNC )
{// return from a user-defined function
struct script_retinfo* ri;
int olddefsp = st->stack->defsp;
int nargs;
script->pop_stack(st, st->stack->defsp, st->start);// pop distractions from the stack
if( st->stack->defsp < 1 || st->stack->stack_data[st->stack->defsp-1].type != C_RETINFO )
{
ShowWarning("script:run_func: return without callfunc or callsub!\n");
script->reportsrc(st);
st->state = END;
return 1;
}
script->free_vars(st->stack->scope.vars);
st->stack->scope.arrays->destroy(st->stack->scope.arrays,script->array_free_db);
ri = st->stack->stack_data[st->stack->defsp-1].u.ri;
nargs = ri->nargs;
st->pos = ri->pos;
st->script = ri->script;
st->stack->scope.vars = ri->scope.vars;
st->stack->scope.arrays = ri->scope.arrays;
st->stack->defsp = ri->defsp;
memset(ri, 0, sizeof(struct script_retinfo));
script->pop_stack(st, olddefsp-nargs-1, olddefsp);// pop arguments and retinfo
st->state = GOTO;
}
return 0;
}
/*==========================================
* script execution
*------------------------------------------*/
static void run_script(struct script_code *rootscript, int pos, int rid, int oid)
{
struct script_state *st;
if( rootscript == NULL || pos < 0 )
return;
// TODO In jAthena, this function can take over the pending script in the player. [FlavioJS]
// It is unclear how that can be triggered, so it needs the be traced/checked in more detail.
// NOTE At the time of this change, this function wasn't capable of taking over the script state because st->scriptroot was never set.
st = script->alloc_state(rootscript, pos, rid, oid);
script->run_main(st);
}
static void script_stop_instances(struct script_code *code)
{
struct DBIterator *iter;
struct script_state* st;
if( !script->active_scripts )
return;//dont even bother.
iter = db_iterator(script->st_db);
for( st = dbi_first(iter); dbi_exists(iter); st = dbi_next(iter) ) {
if( st->script == code ) {
script->free_state(st);
}
}
dbi_destroy(iter);
}
/*==========================================
* Timer function for sleep
*------------------------------------------*/
static int run_script_timer(int tid, int64 tick, int id, intptr_t data)
{
struct script_state *st = idb_get(script->st_db,(int)data);
if( st ) {
struct map_session_data *sd = map->id2sd(st->rid);
if((sd && sd->status.char_id != id) || (st->rid && !sd)) { //Character mismatch. Cancel execution.
st->rid = 0;
st->state = END;
}
st->sleep.timer = INVALID_TIMER;
if(st->state != RERUNLINE)
st->sleep.tick = 0;
script->run_main(st);
}
return 0;
}
/// Detaches script state from possibly attached character and restores it's previous script if any.
///
/// @param st Script state to detach.
/// @param dequeue_event Whether to schedule any queued events, when there was no previous script.
static void script_detach_state(struct script_state *st, bool dequeue_event)
{
struct map_session_data* sd;
nullpo_retv(st);
if(st->rid && (sd = map->id2sd(st->rid))!=NULL) {
sd->st = st->bk_st;
sd->npc_id = st->bk_npcid;
if(st->bk_st) {
//Remove tag for removal.
st->bk_st = NULL;
st->bk_npcid = 0;
} else if(dequeue_event) {
// For the Secure NPC Timeout option (check config/Secure.h) [RR]
#ifdef SECURE_NPCTIMEOUT
// We're done with this NPC session, so we cancel the timer (if existent) and move on
if( sd->npc_idle_timer != INVALID_TIMER ) {
timer->delete(sd->npc_idle_timer,npc->secure_timeout_timer);
sd->npc_idle_timer = INVALID_TIMER;
}
#endif
npc->event_dequeue(sd);
}
} else if(st->bk_st) { // rid was set to 0, before detaching the script state
ShowError("script_detach_state: Found previous script state without attached player (rid=%d, oid=%d, state=%u, bk_npcid=%d)\n", st->bk_st->rid, st->bk_st->oid, st->bk_st->state, st->bk_npcid);
script->reportsrc(st->bk_st);
script->free_state(st->bk_st);
st->bk_st = NULL;
}
}
/// Attaches script state to possibly attached character and backups it's previous script, if any.
///
/// @param st Script state to attach.
static void script_attach_state(struct script_state *st)
{
struct map_session_data* sd;
nullpo_retv(st);
if(st->rid && (sd = map->id2sd(st->rid))!=NULL)
{
if(st!=sd->st)
{
if(st->bk_st)
{// there is already a backup
ShowDebug("script_free_state: Previous script state lost (rid=%d, oid=%d, state=%u, bk_npcid=%d).\n", st->bk_st->rid, st->bk_st->oid, st->bk_st->state, st->bk_npcid);
}
st->bk_st = sd->st;
st->bk_npcid = sd->npc_id;
}
sd->st = st;
sd->npc_id = st->oid;
sd->npc_item_flag = st->npc_item_flag; // load default.
/**
* For the Secure NPC Timeout option (check config/Secure.h) [RR]
**/
#ifdef SECURE_NPCTIMEOUT
if( sd->npc_idle_timer == INVALID_TIMER )
sd->npc_idle_timer = timer->add(timer->gettick() + (SECURE_NPCTIMEOUT_INTERVAL*1000),npc->secure_timeout_timer,sd->bl.id,0);
sd->npc_idle_tick = timer->gettick();
#endif
}
}
/*==========================================
* The main part of the script execution
*------------------------------------------*/
static void run_script_main(struct script_state *st)
{
int cmdcount = script->config.check_cmdcount;
int gotocount = script->config.check_gotocount;
struct map_session_data *sd;
struct script_stack *stack = st->stack;
struct npc_data *nd;
nullpo_retv(st);
script->attach_state(st);
nd = map->id2nd(st->oid);
if( nd && nd->bl.m >= 0 )
st->instance_id = map->list[nd->bl.m].instance_id;
else
st->instance_id = -1;
if(st->state == RERUNLINE) {
script->run_func(st);
if(st->state == GOTO)
st->state = RUN;
} else if(st->state != END)
st->state = RUN;
while( st->state == RUN ) {
enum c_op c = script->get_com(&st->script->script_buf, &st->pos);
switch(c) {
case C_EOL:
if( stack->defsp > stack->sp )
ShowError("script:run_script_main: unexpected stack position (defsp=%d sp=%d). please report this!!!\n", stack->defsp, stack->sp);
else
script->pop_stack(st, stack->defsp, stack->sp);// pop unused stack data. (unused return value)
break;
case C_INT:
script->push_val(stack,C_INT,script->get_num(&st->script->script_buf, &st->pos), NULL);
break;
case C_POS:
case C_NAME:
script->push_val(stack,c,GETVALUE(&st->script->script_buf, st->pos), NULL);
st->pos+=3;
break;
case C_ARG:
script->push_val(stack,c,0,NULL);
break;
case C_STR:
script->push_conststr(stack, (const char *)&VECTOR_INDEX(st->script->script_buf, st->pos));
while (VECTOR_INDEX(st->script->script_buf, st->pos++) != 0)
(void)0; // Skip string
break;
case C_LSTR:
{
struct map_session_data *lsd = NULL;
uint8 translations = 0;
int string_id = *((int *)(&VECTOR_INDEX(st->script->script_buf, st->pos)));
st->pos += sizeof(string_id);
translations = *((uint8 *)(&VECTOR_INDEX(st->script->script_buf, st->pos)));
st->pos += sizeof(translations);
if( (!st->rid || !(lsd = map->id2sd(st->rid)) || !lsd->lang_id) && !map->default_lang_id )
script->push_conststr(stack, script->string_list+string_id);
else {
uint8 k, wlang_id = lsd ? lsd->lang_id : map->default_lang_id;
int offset = st->pos;
for(k = 0; k < translations; k++) {
uint8 lang_id = *(uint8 *)(&VECTOR_INDEX(st->script->script_buf, offset));
offset += sizeof(uint8);
if( lang_id == wlang_id )
break;
offset += sizeof(char*);
}
if (k == translations)
script->push_conststr(stack, script->string_list+string_id);
else
script->push_conststr(stack, *(const char**)(&VECTOR_INDEX(st->script->script_buf, offset)));
}
st->pos += ( ( sizeof(char*) + sizeof(uint8) ) * translations );
}
break;
case C_FUNC:
script->run_func(st);
if(st->state==GOTO) {
st->state = RUN;
if( !st->freeloop && gotocount>0 && (--gotocount)<=0 ) {
ShowError("run_script: infinity loop !\n");
script->reportsrc(st);
st->state=END;
}
}
break;
case C_REF:
st->op2ref = 1;
break;
case C_NEG:
case C_NOT:
case C_LNOT:
script->op_1(st ,c);
break;
case C_ADD:
case C_SUB:
case C_MUL:
case C_POW:
case C_DIV:
case C_MOD:
case C_EQ:
case C_NE:
case C_GT:
case C_GE:
case C_LT:
case C_LE:
case C_AND:
case C_OR:
case C_XOR:
case C_LAND:
case C_LOR:
case C_R_SHIFT:
case C_L_SHIFT:
case C_RE_EQ:
case C_RE_NE:
script->op_2(st, c);
break;
case C_OP3:
script->op_3(st, c);
break;
case C_NOP:
st->state=END;
break;
default:
ShowError("unknown command : %u @ %d\n", c, st->pos);
st->state=END;
break;
}
if( !st->freeloop && cmdcount>0 && (--cmdcount)<=0 ) {
ShowError("run_script: too many opeartions being processed non-stop !\n");
script->reportsrc(st);
st->state=END;
}
}
if(st->sleep.tick > 0) {
//Restore previous script
script->detach_state(st, false);
//Delay execution
sd = map->id2sd(st->rid); // Get sd since script might have attached someone while running. [Inkfish]
st->sleep.charid = sd?sd->status.char_id:0;
st->sleep.timer = timer->add(timer->gettick()+st->sleep.tick,
script->run_timer, st->sleep.charid, (intptr_t)st->id);
} else if(st->state != END && st->rid) {
//Resume later (st is already attached to player).
if(st->bk_st) {
ShowWarning("Unable to restore stack! Double continuation!\n");
//Report BOTH scripts to see if that can help somehow.
ShowDebug("Previous script (lost):\n");
script->reportsrc(st->bk_st);
ShowDebug("Current script:\n");
script->reportsrc(st);
script->free_state(st->bk_st);
st->bk_st = NULL;
}
} else {
//Dispose of script.
if ((sd = map->id2sd(st->rid))!=NULL) { //Restore previous stack and save char.
if(sd->state.using_fake_npc) {
clif->clearunit_single(sd->npc_id, CLR_OUTSIGHT, sd->fd);
sd->state.using_fake_npc = 0;
}
//Restore previous script if any.
script->detach_state(st, true);
if (sd->vars_dirty)
intif->saveregistry(sd);
}
script->free_state(st);
st = NULL;
}
}
/**
* Reads 'script_configuration' and initializes required variables.
*
* @param filename Path to configuration file.
* @param imported Whether the current config is imported from another file.
*
* @retval false in case of error.
*/
static bool script_config_read(const char *filename, bool imported)
{
struct config_t config;
struct config_setting_t * setting = NULL;
const char *import = NULL;
bool retval = true;
nullpo_retr(false, filename);
if (!libconfig->load_file(&config, filename))
return false;
if ((setting = libconfig->lookup(&config, "script_configuration")) == NULL) {
libconfig->destroy(&config);
if (imported)
return true;
ShowError("script_config_read: script_configuration was not found in %s!\n", filename);
return false;
}
libconfig->setting_lookup_bool_real(setting, "warn_func_mismatch_paramnum", &script->config.warn_func_mismatch_paramnum);
libconfig->setting_lookup_bool_real(setting, "warn_func_mismatch_argtypes", &script->config.warn_func_mismatch_argtypes);
libconfig->setting_lookup_int(setting, "check_cmdcount", &script->config.check_cmdcount);
libconfig->setting_lookup_int(setting, "check_gotocount", &script->config.check_gotocount);
libconfig->setting_lookup_int(setting, "input_min_value", &script->config.input_min_value);
libconfig->setting_lookup_int(setting, "input_max_value", &script->config.input_max_value);
if (!HPM->parse_conf(&config, filename, HPCT_SCRIPT, imported))
retval = false;
// import should overwrite any previous configuration, so it should be called last
if (libconfig->lookup_string(&config, "import", &import) == CONFIG_TRUE) {
if (strcmp(import, filename) == 0 || strcmp(import, map->SCRIPT_CONF_NAME) == 0) {
ShowWarning("script_config_read: Loop detected! Skipping 'import'...\n");
} else {
if (!script->config_read(import, true))
retval = false;
}
}
libconfig->destroy(&config);
return retval;
}
/**
* @see DBApply
*/
static int db_script_free_code_sub(union DBKey key, struct DBData *data, va_list ap)
{
struct script_code *code = DB->data2ptr(data);
if (code)
script->free_code(code);
return 0;
}
static void script_run_autobonus(const char *autobonus, int id, int pos)
{
struct script_code *scriptroot = (struct script_code *)strdb_get(script->autobonus_db, autobonus);
if( scriptroot ) {
status->current_equip_item_index = pos;
script->run(scriptroot,0,id,0);
}
}
static void script_add_autobonus(const char *autobonus)
{
if( strdb_get(script->autobonus_db, autobonus) == NULL ) {
struct script_code *scriptroot = script->parse(autobonus, "autobonus", 0, 0, NULL);
if( scriptroot )
strdb_put(script->autobonus_db, autobonus, scriptroot);
}
}
/// resets a temporary character array variable to given value
static void script_cleararray_pc(struct map_session_data *sd, const char *varname, void *value)
{
struct script_array *sa = NULL;
struct reg_db *src = NULL;
unsigned int i, *list = NULL, size = 0;
int key;
key = script->add_str(varname);
if( !(src = script->array_src(NULL,sd,varname,NULL) ) )
return;
if( value )
script->array_ensure_zero(NULL,sd,reference_uid(key,0),NULL);
if( !(sa = idb_get(src->arrays, key)) ) /* non-existent array, nothing to empty */
return;
size = sa->size;
list = script->array_cpy_list(sa);
for(i = 0; i < size; i++) {
script->set_reg(NULL,sd,reference_uid(key, list[i]),varname,value,NULL);
}
}
/// sets a temporary character array variable element idx to given value
/// @param refcache Pointer to an int variable, which keeps a copy of the reference to varname and must be initialized to 0. Can be NULL if only one element is set.
static void script_setarray_pc(struct map_session_data *sd, const char *varname, uint32 idx, void *value, int *refcache)
{
int key;
if (idx > SCRIPT_MAX_ARRAYSIZE) {
ShowError("script_setarray_pc: Variable '%s' has invalid index '%u' (char_id=%d).\n", varname, idx, sd->status.char_id);
return;
}
key = ( refcache && refcache[0] ) ? refcache[0] : script->add_str(varname);
script->set_reg(NULL,sd,reference_uid(key, idx),varname,value,NULL);
if( refcache )
{// save to avoid repeated script->add_str calls
refcache[0] = key;
}
}
/**
* Clears persistent variables from memory
**/
static int script_reg_destroy(union DBKey key, struct DBData *data, va_list ap)
{
struct script_reg_state *src;
nullpo_ret(data);
if( data->type != DB_DATA_PTR )/* got no need for those! */
return 0;
src = DB->data2ptr(data);
if( src->type ) {
struct script_reg_str *p = (struct script_reg_str *)src;
if( p->value )
aFree(p->value);
ers_free(pc->str_reg_ers,p);
} else {
ers_free(pc->num_reg_ers,(struct script_reg_num*)src);
}
return 0;
}
/**
* Clears a single persistent variable
**/
static void script_reg_destroy_single(struct map_session_data *sd, int64 reg, struct script_reg_state *data)
{
nullpo_retv(sd);
nullpo_retv(data);
i64db_remove(sd->regs.vars, reg);
if( data->type ) {
struct script_reg_str *p = (struct script_reg_str*)data;
if( p->value )
aFree(p->value);
ers_free(pc->str_reg_ers,p);
} else {
ers_free(pc->num_reg_ers,(struct script_reg_num*)data);
}
}
static unsigned int *script_array_cpy_list(struct script_array *sa)
{
nullpo_retr(NULL, sa);
if( sa->size > script->generic_ui_array_size )
script->generic_ui_array_expand(sa->size);
memcpy(script->generic_ui_array, sa->members, sizeof(unsigned int)*sa->size);
return script->generic_ui_array;
}
static void script_generic_ui_array_expand(unsigned int plus)
{
script->generic_ui_array_size += plus + 100;
RECREATE(script->generic_ui_array, unsigned int, script->generic_ui_array_size);
}
/*==========================================
* Destructor
*------------------------------------------*/
static void do_final_script(void)
{
int i;
struct DBIterator *iter;
struct script_state *st;
#ifdef SCRIPT_DEBUG_HASH
if (battle_config.etc_log)
{
FILE *fp = fopen("hash_dump.txt","wt");
if(fp) {
int count[SCRIPT_HASH_SIZE];
int count2[SCRIPT_HASH_SIZE]; // number of buckets with a certain number of items
int n=0;
int min=INT_MAX,max=0,zero=0;
double mean=0.0f;
double median=0.0f;
ShowNotice("Dumping script str hash information to hash_dump.txt\n");
memset(count, 0, sizeof(count));
fprintf(fp,"num : hash : data_name\n");
fprintf(fp,"---------------------------------------------------------------\n");
for(i=LABEL_START; istr_num; i++) {
unsigned int h = script->calc_hash(script->get_str(i));
fprintf(fp,"%04d : %4u : %s\n",i,h, script->get_str(i));
++count[h];
}
fprintf(fp,"--------------------\n\n");
memset(count2, 0, sizeof(count2));
for(i=0; i count[i])
min = count[i]; // minimun count of collision
if(max < count[i])
max = count[i]; // maximun count of collision
if(count[i] == 0)
zero++;
++count2[count[i]];
}
fprintf(fp,"\n--------------------\n items : buckets\n--------------------\n");
for( i=min; i <= max; ++i ) {
fprintf(fp," %5d : %7d\n",i,count2[i]);
mean += 1.0f*i*count2[i]/SCRIPT_HASH_SIZE; // Note: this will always result in /
}
for( i=min; i <= max; ++i ) {
n += count2[i];
if( n*2 >= SCRIPT_HASH_SIZE )
{
if( SCRIPT_HASH_SIZE%2 == 0 && SCRIPT_HASH_SIZE/2 == n )
median = (i+i+1)/2.0f;
else
median = i;
break;
}
}
fprintf(fp,"--------------------\n min = %d, max = %d, zero = %d\n mean = %lf, median = %lf\n",min,max,zero,mean,median);
fclose(fp);
}
}
#endif
iter = db_iterator(script->st_db);
for( st = dbi_first(iter); dbi_exists(iter); st = dbi_next(iter) ) {
script->free_state(st);
}
dbi_destroy(iter);
mapreg->final();
script->userfunc_db->destroy(script->userfunc_db, script->db_free_code_sub);
script->autobonus_db->destroy(script->autobonus_db, script->db_free_code_sub);
if (script->str_data)
aFree(script->str_data);
if (script->str_buf)
aFree(script->str_buf);
for( i = 0; i < atcommand->binding_count; i++ ) {
aFree(atcommand->binding[i]->at_groups);
aFree(atcommand->binding[i]->char_groups);
aFree(atcommand->binding[i]);
}
if( atcommand->binding_count != 0 )
aFree(atcommand->binding);
for( i = 0; i < script->buildin_count; i++) {
if( script->buildin[i] ) {
aFree(script->buildin[i]);
script->buildin[i] = NULL;
}
}
aFree(script->buildin);
for (i = 0; i < VECTOR_LENGTH(script->hq); i++) {
VECTOR_CLEAR(VECTOR_INDEX(script->hq, i).entries);
}
VECTOR_CLEAR(script->hq);
for (i = 0; i < VECTOR_LENGTH(script->hqi); i++) {
VECTOR_CLEAR(VECTOR_INDEX(script->hqi, i).entries);
}
VECTOR_CLEAR(script->hqi);
if( script->word_buf != NULL )
aFree(script->word_buf);
#ifdef ENABLE_CASE_CHECK
script->global_casecheck.clear();
script->local_casecheck.clear();
#endif // ENABLE_CASE_CHECK
ers_destroy(script->st_ers);
ers_destroy(script->stack_ers);
db_destroy(script->st_db);
if( script->labels != NULL )
aFree(script->labels);
ers_destroy(script->array_ers);
if( script->generic_ui_array )
aFree(script->generic_ui_array);
script->clear_translations(false);
script->parser_clean_leftovers();
}
/**
*
**/
static uint8 script_add_language(const char *name)
{
uint8 lang_id = script->max_lang_id;
nullpo_ret(name);
RECREATE(script->languages, char *, ++script->max_lang_id);
script->languages[lang_id] = aStrdup(name);
return lang_id;
}
/**
* Goes thru db/translations.conf file
**/
static void script_load_translations(void)
{
struct config_t translations_conf;
const char *config_filename = "db/translations.conf"; // FIXME hardcoded name
struct config_setting_t *translations = NULL;
int i, size;
int total = 0;
uint8 lang_id = 0, k;
if (map->minimal) // No translations in minimal mode
return;
script->translation_db = strdb_alloc(DB_OPT_DUP_KEY, NAME_LENGTH*2+1);
if( script->languages ) {
for(i = 0; i < script->max_lang_id; i++)
aFree(script->languages[i]);
aFree(script->languages);
}
script->languages = NULL;
script->max_lang_id = 0;
script->add_language("English");/* 0 is default, which is whatever is in the npc files hardcoded (in our case, English) */
if (!libconfig->load_file(&translations_conf, config_filename))
return;
if ((translations = libconfig->lookup(&translations_conf, "translations")) == NULL) {
ShowError("load_translations: invalid format on '%s'\n",config_filename);
return;
}
if( script->string_list )
aFree(script->string_list);
script->string_list = NULL;
script->string_list_pos = 0;
script->string_list_size = 0;
size = libconfig->setting_length(translations);
for(i = 0; i < size; i++) {
const char *translation_file = libconfig->setting_get_string_elem(translations, i);
total += script->load_translation(translation_file, ++lang_id);
}
libconfig->destroy(&translations_conf);
if (total != 0) {
struct DBIterator *main_iter;
struct DBMap *string_db;
struct string_translation *st = NULL;
VECTOR_ENSURE(script->translation_buf, total, 1);
main_iter = db_iterator(script->translation_db);
for (string_db = dbi_first(main_iter); dbi_exists(main_iter); string_db = dbi_next(main_iter)) {
struct DBIterator *sub_iter = db_iterator(string_db);
for (st = dbi_first(sub_iter); dbi_exists(sub_iter); st = dbi_next(sub_iter)) {
VECTOR_PUSH(script->translation_buf, st->buf);
}
dbi_destroy(sub_iter);
}
dbi_destroy(main_iter);
}
for(k = 0; k < script->max_lang_id; k++) {
if( !strcmpi(script->languages[k],map->default_lang_str) ) {
break;
}
}
if( k == script->max_lang_id ) {
ShowError("load_translations: map server default_language setting '%s' is not a loaded language\n",map->default_lang_str);
map->default_lang_id = 0;
} else {
map->default_lang_id = k;
}
}
/**
* Generates a language name from a translation filename.
*
* @param file The filename.
* @return The corresponding translation name.
*/
static const char *script_get_translation_file_name(const char *file)
{
const char *basename = NULL, *last_dot = NULL;
nullpo_retr("Unknown", file);
basename = strrchr(file, '/');;
#ifdef WIN32
{
const char *basename_windows = strrchr(file, '\\');
if (basename_windows > basename)
basename = basename_windows;
}
#endif // WIN32
if (basename == NULL)
basename = file;
else
basename++; // Skip slash
Assert_retr("Unknown", *basename != '\0');
last_dot = strrchr(basename, '.');
if (last_dot != NULL) {
static char file_name[200];
if (last_dot == basename)
return basename + 1;
safestrncpy(file_name, basename, last_dot - basename + 1);
return file_name;
}
return basename;
}
/**
* Parses and adds a translated string to the translations database.
*
* @param file Translations file being parsed (for error messages).
* @param lang_id Language ID being parsed.
* @param msgctxt Message context (i.e. NPC name)
* @param msgid Message ID (source string)
* @param msgstr Translated message
* @return success state
* @retval true if a new string was added.
*/
static bool script_load_translation_addstring(const char *file, uint8 lang_id, const char *msgctxt, const struct script_string_buf *msgid, const struct script_string_buf *msgstr)
{
nullpo_retr(false, file);
nullpo_retr(false, msgctxt);
nullpo_retr(false, msgid);
nullpo_retr(false, msgstr);
if (VECTOR_LENGTH(*msgid) <= 1) {
// Empty ID (i.e. header) to be ignored
return false;
}
if (VECTOR_LENGTH(*msgstr) <= 1) {
// Empty (untranslated) string to be ignored
return false;
}
if (msgctxt[0] == '\0') {
// Missing context
ShowWarning("script_load_translation: Missing context for msgid '%s' in '%s'. Skipping.\n",
VECTOR_DATA(*msgid), file);
return false;
}
if (strcasecmp(msgctxt, "messages.conf") == 0) {
int i;
for (i = 0; i < MAX_MSG; i++) {
if (atcommand->msg_table[0][i] != NULL && strcmpi(atcommand->msg_table[0][i], VECTOR_DATA(*msgid)) == 0) {
if (atcommand->msg_table[lang_id][i] != NULL)
aFree(atcommand->msg_table[lang_id][i]);
atcommand->msg_table[lang_id][i] = aStrdup(VECTOR_DATA(*msgstr));
break;
}
}
} else {
int msgstr_len = VECTOR_LENGTH(*msgstr);
int inner_len = 1 + msgstr_len + 1; //uint8 lang_id + msgstr_len + '\0'
struct string_translation *st = NULL;
struct DBMap *string_db;
if ((string_db = strdb_get(script->translation_db, msgctxt)) == NULL) {
string_db = strdb_alloc(DB_OPT_DUP_KEY, 0);
strdb_put(script->translation_db, msgctxt, string_db);
}
if ((st = strdb_get(string_db, VECTOR_DATA(*msgid))) == NULL) {
CREATE(st, struct string_translation, 1);
st->string_id = script->string_dup(VECTOR_DATA(*msgid));
strdb_put(string_db, VECTOR_DATA(*msgid), st);
}
RECREATE(st->buf, uint8, st->len + inner_len);
WBUFB(st->buf, st->len) = lang_id;
safestrncpy(WBUFP(st->buf, st->len + 1), VECTOR_DATA(*msgstr), msgstr_len + 1);
st->translations++;
st->len += inner_len;
}
return true;
}
/**
* Parses an individual translation file.
*
* @param file The filename to parse.
* @param lang_id The language identifier.
* @return The amount of strings loaded.
*/
static int script_load_translation(const char *file, uint8 lang_id)
{
int translations = 0;
char line[1024];
char msgctxt[NAME_LENGTH*2+1] = { 0 };
FILE *fp;
int lineno = 0;
struct script_string_buf msgid, msgstr;
nullpo_ret(file);
if ((fp = fopen(file,"rb")) == NULL) {
ShowError("load_translation: failed to open '%s' for reading\n",file);
return 0;
}
VECTOR_INIT(msgid);
VECTOR_INIT(msgstr);
script->add_language(script->get_translation_file_name(file));
if (lang_id >= atcommand->max_message_table)
atcommand->expand_message_table();
while (fgets(line, sizeof(line), fp) != NULL) {
int len = (int)strlen(line);
int i;
lineno++;
if(len <= 1)
continue;
if (line[0] == '#')
continue;
if (VECTOR_LENGTH(msgid) > 0 && VECTOR_LENGTH(msgstr) > 0) {
if (line[0] == '"') {
// Continuation line
(void)VECTOR_POP(msgstr); // Pop final '\0'
for (i = 8; i < len - 2; i++) {
VECTOR_ENSURE(msgstr, 1, 512);
if (line[i] == '\\' && line[i+1] == '"') {
VECTOR_PUSH(msgstr, '"');
i++;
} else {
VECTOR_PUSH(msgstr, line[i]);
}
}
VECTOR_ENSURE(msgstr, 1, 512);
VECTOR_PUSH(msgstr, '\0');
continue;
}
// Add string
if (script->load_translation_addstring(file, lang_id, msgctxt, &msgid, &msgstr))
translations++;
msgctxt[0] = '\0';
VECTOR_TRUNCATE(msgid);
VECTOR_TRUNCATE(msgstr);
}
if (strncasecmp(line,"msgctxt \"", 9) == 0) {
int cursor = 0;
msgctxt[0] = '\0';
for (i = 9; i < len - 2; i++) {
if (line[i] == '\\' && line[i+1] == '"') {
msgctxt[cursor] = '"';
i++;
} else {
msgctxt[cursor] = line[i];
}
if (++cursor >= (int)sizeof(msgctxt) - 1)
break;
}
msgctxt[cursor] = '\0';
// New context, reset everything
VECTOR_TRUNCATE(msgid);
VECTOR_TRUNCATE(msgstr);
continue;
}
if (strncasecmp(line, "msgid \"", 7) == 0) {
VECTOR_TRUNCATE(msgid);
for (i = 7; i < len - 2; i++) {
VECTOR_ENSURE(msgid, 1, 512);
if (line[i] == '\\' && line[i+1] == '"') {
VECTOR_PUSH(msgid, '"');
i++;
} else {
VECTOR_PUSH(msgid, line[i]);
}
}
VECTOR_ENSURE(msgid, 1, 512);
VECTOR_PUSH(msgid, '\0');
// New id, reset string if any
VECTOR_TRUNCATE(msgstr);
continue;
}
if (VECTOR_LENGTH(msgid) > 0 && strncasecmp(line, "msgstr \"", 8) == 0) {
VECTOR_TRUNCATE(msgstr);
for (i = 8; i < len - 2; i++) {
VECTOR_ENSURE(msgstr, 1, 512);
if (line[i] == '\\' && line[i+1] == '"') {
VECTOR_PUSH(msgstr, '"');
i++;
} else {
VECTOR_PUSH(msgstr, line[i]);
}
}
VECTOR_ENSURE(msgstr, 1, 512);
VECTOR_PUSH(msgstr, '\0');
continue;
}
ShowWarning("script_load_translation: Unexpected input at '%s' in file '%s' line %d. Skipping.\n",
line, file, lineno);
}
// Add last string
if (VECTOR_LENGTH(msgid) > 0 && VECTOR_LENGTH(msgstr) > 0) {
if (script->load_translation_addstring(file, lang_id, msgctxt, &msgid, &msgstr))
translations++;
}
fclose(fp);
VECTOR_CLEAR(msgid);
VECTOR_CLEAR(msgstr);
ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' translations in '"CL_WHITE"%s"CL_RESET"'.\n", translations, file);
return translations;
}
/**
*
**/
static void script_clear_translations(bool reload)
{
uint32 i;
if( script->string_list )
aFree(script->string_list);
script->string_list = NULL;
script->string_list_pos = 0;
script->string_list_size = 0;
while (VECTOR_LENGTH(script->translation_buf) > 0) {
aFree(VECTOR_POP(script->translation_buf));
}
VECTOR_CLEAR(script->translation_buf);
if( script->languages ) {
for(i = 0; i < script->max_lang_id; i++)
aFree(script->languages[i]);
aFree(script->languages);
}
script->languages = NULL;
script->max_lang_id = 0;
if( script->translation_db ) {
script->translation_db->clear(script->translation_db,script->translation_db_destroyer);
}
if( reload )
script->load_translations();
}
/**
*
**/
static int script_translation_db_destroyer(union DBKey key, struct DBData *data, va_list ap)
{
struct DBMap *string_db = DB->data2ptr(data);
if( db_size(string_db) ) {
struct string_translation *st = NULL;
struct DBIterator *iter = db_iterator(string_db);
for( st = dbi_first(iter); dbi_exists(iter); st = dbi_next(iter) ) {
aFree(st);
}
dbi_destroy(iter);
}
db_destroy(string_db);
return 0;
}
/**
*
**/
static void script_parser_clean_leftovers(void)
{
VECTOR_CLEAR(script->buf);
if( script->translation_db ) {
script->translation_db->destroy(script->translation_db,script->translation_db_destroyer);
script->translation_db = NULL;
}
VECTOR_CLEAR(script->parse_simpleexpr_strbuf);
}
/**
* Performs cleanup after all parsing is processed
**/
static int script_parse_cleanup_timer(int tid, int64 tick, int id, intptr_t data)
{
script->parser_clean_leftovers();
script->parse_cleanup_timer_id = INVALID_TIMER;
return 0;
}
/*==========================================
* Initialization
*------------------------------------------*/
static void do_init_script(bool minimal)
{
script->parse_cleanup_timer_id = INVALID_TIMER;
VECTOR_INIT(script->parse_simpleexpr_strbuf);
script->st_db = idb_alloc(DB_OPT_BASE);
script->userfunc_db = strdb_alloc(DB_OPT_DUP_KEY,0);
script->autobonus_db = strdb_alloc(DB_OPT_DUP_KEY,0);
script->st_ers = ers_new(sizeof(struct script_state), "script.c::st_ers", ERS_OPT_CLEAN|ERS_OPT_FLEX_CHUNK);
script->stack_ers = ers_new(sizeof(struct script_stack), "script.c::script_stack", ERS_OPT_NONE|ERS_OPT_FLEX_CHUNK);
script->array_ers = ers_new(sizeof(struct script_array), "script.c::array_ers", ERS_OPT_CLEAN|ERS_OPT_CLEAR);
ers_chunk_size(script->st_ers, 10);
ers_chunk_size(script->stack_ers, 10);
VECTOR_INIT(script->hq);
VECTOR_INIT(script->hqi);
script->parse_builtin();
script->read_constdb(false);
script->load_parameters();
script->hardcoded_constants();
if (minimal)
return;
mapreg->init();
script->load_translations();
}
static int script_reload(void)
{
int i;
struct DBIterator *iter;
struct script_state *st;
#ifdef ENABLE_CASE_CHECK
script->global_casecheck.clear();
#endif // ENABLE_CASE_CHECK
iter = db_iterator(script->st_db);
for( st = dbi_first(iter); dbi_exists(iter); st = dbi_next(iter) ) {
script->free_state(st);
}
dbi_destroy(iter);
script->userfunc_db->clear(script->userfunc_db, script->db_free_code_sub);
script->label_count = 0;
for( i = 0; i < atcommand->binding_count; i++ ) {
aFree(atcommand->binding[i]->at_groups);
aFree(atcommand->binding[i]->char_groups);
aFree(atcommand->binding[i]);
}
if( atcommand->binding_count != 0 )
aFree(atcommand->binding);
atcommand->binding_count = 0;
db_clear(script->st_db);
script->clear_translations(true);
if( script->parse_cleanup_timer_id != INVALID_TIMER ) {
timer->delete(script->parse_cleanup_timer_id,script->parse_cleanup_timer);
script->parse_cleanup_timer_id = INVALID_TIMER;
}
script->read_constdb(true);
itemdb->name_constants();
clan->set_constants();
mapreg->reload();
sysinfo->vcsrevision_reload();
return 0;
}
/* returns name of current function being run, from within the stack [Ind/Hercules] */
static const char *script_getfuncname(struct script_state *st)
{
struct script_data *data;
nullpo_retr(NULL, st);
data = &st->stack->stack_data[st->start];
if( data->type == C_NAME && script->str_data[data->u.num].type == C_FUNC )
return script->get_str(script_getvarid(data->u.num));
return NULL;
}
/**
* Writes a string to a StringBuf by combining a format string and a set of
* arguments taken from the current script state (caller script function
* arguments).
*
* @param[in] st Script state (must have at least a string at index
* 'start').
* @param[in] start Index of the format string argument.
* @param[out] out Output string buffer (managed by the caller, must be
* already initialized)
* @retval false if an error occurs.
*/
static bool script_sprintf_helper(struct script_state *st, int start, struct StringBuf *out)
{
const char *format = NULL;
const char *p = NULL, *np = NULL;
char *buf = NULL;
int buf_len = 0;
int lastarg = start;
int argc = script_lastdata(st) + 1;
nullpo_retr(-1, out);
Assert_retr(-1, start >= 2 && start <= argc);
Assert_retr(-1, script_hasdata(st, start));
p = format = script_getstr(st, start);
/*
* format-string = "" / *(text / placeholder)
* placeholder = "%%" / "%n" / std-placeholder
* std-placeholder = "%" [pos-parameter] [flags] [width] [precision] [length] type
* pos-parameter = number "$"
* flags = *("-" / "+" / "0" / SP)
* width = number / ("*" [pos-parameter])
* precision = "." (number / ("*" [pos-parameter]))
* length = "hh" / "h" / "l" / "ll" / "L" / "z" / "j" / "t"
* type = "d" / "i" / "u" / "f" / "F" / "e" / "E" / "g" / "G" / "x" / "X" / "o" / "s" / "c" / "p" / "a" / "A"
* number = digit-nonzero *DIGIT
* digit-nonzero = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9"
*/
while ((np = strchr(p, '%')) != NULL) {
bool flag_plus = false, flag_minus = false, flag_zero = false, flag_space = false;
bool positional_arg = false;
int width = 0, nextarg = lastarg + 1, thisarg = nextarg;
if (p != np) {
int len = (int)(np - p + 1);
if (buf_len < len) {
RECREATE(buf, char, len);
buf_len = len;
}
safestrncpy(buf, p, len);
StrBuf->AppendStr(out, buf);
}
np++;
// placeholder = "%%" ; (special case)
if (*np == '%') {
StrBuf->AppendStr(out, "%");
p = np + 1;
continue;
}
// placeholder = "%n" ; (ignored)
if (*np == 'n') {
ShowWarning("script_sprintf_helper: Format %%n not supported! Skipping...\n");
script->reportsrc(st);
lastarg = nextarg;
p = np + 1;
continue;
}
// std-placeholder = "%" [pos-parameter] [flags] [width] [precision] [length] type
// pos-parameter = number "$"
if (ISDIGIT(*np) && *np != '0') {
const char *pp = np;
while (ISDIGIT(*pp))
pp++;
if (*pp == '$') {
thisarg = atoi(np) + start;
positional_arg = true;
np = pp + 1;
}
}
if (thisarg >= argc) {
ShowError("buildin_sprintf: Not enough arguments passed!\n");
if (buf != NULL)
aFree(buf);
return false;
}
// flags = *("-" / "+" / "0" / SP)
while (true) {
if (*np == '-') {
flag_minus = true;
} else if (*np == '+') {
flag_plus = true;
} else if (*np == ' ') {
flag_space = true;
} else if (*np == '0') {
flag_zero = true;
} else {
break;
}
np++;
}
// width = number / ("*" [pos-parameter])
if (ISDIGIT(*np)) {
width = atoi(np);
while (ISDIGIT(*np))
np++;
} else if (*np == '*') {
bool positional_widtharg = false;
int width_arg;
np++;
// pos-parameter = number "$"
if (ISDIGIT(*np) && *np != '0') {
const char *pp = np;
while (ISDIGIT(*pp))
pp++;
if (*pp == '$') {
width_arg = atoi(np) + start;
positional_widtharg = true;
np = pp + 1;
}
}
if (!positional_widtharg) {
width_arg = nextarg;
nextarg++;
if (!positional_arg)
thisarg++;
}
if (width_arg >= argc || thisarg >= argc) {
ShowError("buildin_sprintf: Not enough arguments passed!\n");
if (buf != NULL)
aFree(buf);
return false;
}
width = script_getnum(st, width_arg);
}
// precision = "." (number / ("*" [pos-parameter])) ; (not needed/implemented)
// length = "hh" / "h" / "l" / "ll" / "L" / "z" / "j" / "t" ; (not needed/implemented)
// type = "d" / "i" / "u" / "f" / "F" / "e" / "E" / "g" / "G" / "x" / "X" / "o" / "s" / "c" / "p" / "a" / "A"
if (buf_len < 16) {
RECREATE(buf, char, 16);
buf_len = 16;
}
{
int i = 0;
memset(buf, '\0', buf_len);
buf[i++] = '%';
if (flag_minus)
buf[i++] = '-';
if (flag_plus)
buf[i++] = '+';
else if (flag_space) // ignored if '+' is specified
buf[i++] = ' ';
if (flag_zero)
buf[i++] = '0';
if (width > 0)
safesnprintf(buf + i, buf_len - i - 1, "%d", width);
}
buf[(int)strlen(buf)] = *np;
switch (*np) {
case 'd':
case 'i':
case 'u':
case 'x':
case 'X':
case 'o':
// Piggyback printf
StrBuf->Printf(out, buf, script_getnum(st, thisarg));
break;
case 's':
// Piggyback printf
StrBuf->Printf(out, buf, script_getstr(st, thisarg));
break;
case 'c':
{
const char *str = script_getstr(st, thisarg);
// Piggyback printf
StrBuf->Printf(out, buf, str[0]);
}
break;
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
case 'p':
case 'a':
case 'A':
ShowWarning("buildin_sprintf: Format %%%c not supported! Skipping...\n", *np);
script->reportsrc(st);
lastarg = nextarg;
p = np + 1;
continue;
default:
ShowError("buildin_sprintf: Invalid format string.\n");
if (buf != NULL)
aFree(buf);
return false;
}
lastarg = nextarg;
p = np + 1;
}
// Append the remaining part
if (p != NULL)
StrBuf->AppendStr(out, p);
if (buf != NULL)
aFree(buf);
return true;
}
//-----------------------------------------------------------------------------
// buildin functions
//
/////////////////////////////////////////////////////////////////////
// NPC interaction
//
/// Appends a message to the npc dialog.
/// If a dialog doesn't exist yet, one is created.
///
/// mes "";
static BUILDIN(mes)
{
struct map_session_data *sd = script->rid2sd(st);
if (sd == NULL)
return true;
clif->scriptmes(sd, st->oid, script_getstr(st, 2));
return true;
}
/**
* Appends a message to the npc dialog, applying format string conversions (see
* sprintf).
*
* If a dialog doesn't exist yet, one is created.
*
* @code
* mes "";
* @endcode
*/
static BUILDIN(mesf)
{
struct map_session_data *sd = script->rid2sd(st);
struct StringBuf buf;
if (sd == NULL)
return true;
StrBuf->Init(&buf);
if (!script->sprintf_helper(st, 2, &buf)) {
StrBuf->Destroy(&buf);
return false;
}
clif->scriptmes(sd, st->oid, StrBuf->Value(&buf));
StrBuf->Destroy(&buf);
return true;
}
/// Displays the button 'next' in the npc dialog.
/// The dialog text is cleared and the script continues when the button is pressed.
///
/// next;
static BUILDIN(next)
{
struct map_session_data *sd = script->rid2sd(st);
if (sd == NULL)
return true;
#ifdef SECURE_NPCTIMEOUT
sd->npc_idle_type = NPCT_WAIT;
#endif
st->state = STOP;
clif->scriptnext(sd, st->oid);
return true;
}
/// Ends the script and displays the button 'close' on the npc dialog.
/// The dialog is closed when the button is pressed.
///
/// close;
static BUILDIN(close)
{
struct map_session_data *sd = script->rid2sd(st);
if (sd == NULL)
return true;
st->state = sd->state.dialog == 1 ? CLOSE : END;
clif->scriptclose(sd, st->oid);
return true;
}
/// Displays the button 'close' on the npc dialog.
/// The dialog is closed and the script continues when the button is pressed.
///
/// close2;
static BUILDIN(close2)
{
struct map_session_data *sd = script->rid2sd(st);
if (sd == NULL)
return true;
if( sd->state.dialog == 1 )
st->state = STOP;
else {
ShowWarning("misuse of 'close2'! trying to use it without prior dialog! skipping...\n");
script->reportsrc(st);
}
clif->scriptclose(sd, st->oid);
return true;
}
/// Counts the number of valid and total number of options in 'str'
/// If max_count > 0 the counting stops when that valid option is reached
/// total is incremented for each option (NULL is supported)
static int menu_countoptions(const char *str, int max_count, int *total)
{
int count = 0;
int bogus_total;
nullpo_ret(str);
if( total == NULL )
total = &bogus_total;
++(*total);
// initial empty options
while( *str == ':' )
{
++str;
++(*total);
}
// count menu options
while( *str != '\0' )
{
++count;
--max_count;
if( max_count == 0 )
break;
while( *str != ':' && *str != '\0' )
++str;
while( *str == ':' )
{
++str;
++(*total);
}
}
return count;
}
/// Displays a menu with options and goes to the target label.
/// The script is stopped if cancel is pressed.
/// Options with no text are not displayed in the client.
///
/// Options can be grouped together, separated by the character ':' in the text:
/// ex: menu "A:B:C",L_target;
/// All these options go to the specified target label.
///
/// The index of the selected option is put in the variable @menu.
/// Indexes start with 1 and are consistent with grouped and empty options.
/// ex: menu "A::B",-,"",L_Impossible,"C",-;
/// // displays "A", "B" and "C", corresponding to indexes 1, 3 and 5
///
/// NOTE: the client closes the npc dialog when cancel is pressed
///
/// menu "",{,"",,...};
static BUILDIN(menu)
{
int i;
const char* text;
struct map_session_data *sd = script->rid2sd(st);
if (sd == NULL)
return true;
#ifdef SECURE_NPCTIMEOUT
sd->npc_idle_type = NPCT_MENU;
#endif
// TODO detect multiple scripts waiting for input at the same time, and what to do when that happens
if (sd->state.menu_or_input == 0) {
struct StringBuf buf;
if (script_lastdata(st) % 2 == 0) {
// argument count is not even (1st argument is at index 2)
ShowError("script:menu: illegal number of arguments (%d).\n", (script_lastdata(st) - 1));
st->state = END;
return false;
}
StrBuf->Init(&buf);
sd->npc_menu = 0;
for (i = 2; i < script_lastdata(st); i += 2) {
// target label
struct script_data* data = script_getdata(st, i+1);
// menu options
text = script_getstr(st, i);
if( !data_islabel(data) )
{// not a label
StrBuf->Destroy(&buf);
ShowError("script:menu: argument #%d (from 1) is not a label or label not found.\n", i);
script->reportdata(data);
st->state = END;
return false;
}
// append option(s)
if( text[0] == '\0' )
continue;// empty string, ignore
if( sd->npc_menu > 0 )
StrBuf->AppendStr(&buf, ":");
StrBuf->AppendStr(&buf, text);
sd->npc_menu += script->menu_countoptions(text, 0, NULL);
}
st->state = RERUNLINE;
sd->state.menu_or_input = 1;
/* menus beyond this length crash the client (see bugreport:6402) */
if( StrBuf->Length(&buf) >= 2047 ) {
struct npc_data * nd = map->id2nd(st->oid);
char* menu;
CREATE(menu, char, 2048);
safestrncpy(menu, StrBuf->Value(&buf), 2047);
ShowWarning("NPC Menu too long! (source:%s / length:%d)\n",nd?nd->name:"Unknown",StrBuf->Length(&buf));
clif->scriptmenu(sd, st->oid, menu);
aFree(menu);
} else
clif->scriptmenu(sd, st->oid, StrBuf->Value(&buf));
StrBuf->Destroy(&buf);
if( sd->npc_menu >= 0xff )
{// client supports only up to 254 entries; 0 is not used and 255 is reserved for cancel; excess entries are displayed but cause 'uint8' overflow
ShowWarning("buildin_menu: Too many options specified (current=%d, max=254).\n", sd->npc_menu);
script->reportsrc(st);
}
}
else if( sd->npc_menu == 0xff )
{// Cancel was pressed
sd->state.menu_or_input = 0;
st->state = END;
}
else
{// goto target label
int menu = 0;
sd->state.menu_or_input = 0;
if( sd->npc_menu <= 0 )
{
ShowDebug("script:menu: unexpected selection (%d)\n", sd->npc_menu);
st->state = END;
return false;
}
// get target label
for( i = 2; i < script_lastdata(st); i += 2 )
{
text = script_getstr(st, i);
sd->npc_menu -= script->menu_countoptions(text, sd->npc_menu, &menu);
if( sd->npc_menu <= 0 )
break;// entry found
}
if( sd->npc_menu > 0 )
{// Invalid selection
ShowDebug("script:menu: selection is out of range (%d pairs are missing?) - please report this\n", sd->npc_menu);
st->state = END;
return false;
}
if( !data_islabel(script_getdata(st, i + 1)) )
{// TODO remove this temporary crash-prevention code (fallback for multiple scripts requesting user input)
ShowError("script:menu: unexpected data in label argument\n");
script->reportdata(script_getdata(st, i + 1));
st->state = END;
return false;
}
pc->setreg(sd, script->add_str("@menu"), menu);
st->pos = script_getnum(st, i + 1);
st->state = GOTO;
}
return true;
}
/// Displays a menu with options and returns the selected option.
/// Behaves like 'menu' without the target labels.
///
/// select({,,...}) ->
///
/// @see menu
static BUILDIN(select)
{
int i;
const char* text;
struct map_session_data *sd = script->rid2sd(st);
if (sd == NULL)
return true;
#ifdef SECURE_NPCTIMEOUT
sd->npc_idle_type = NPCT_MENU;
#endif
if( sd->state.menu_or_input == 0 ) {
struct StringBuf buf;
StrBuf->Init(&buf);
sd->npc_menu = 0;
for( i = 2; i <= script_lastdata(st); ++i ) {
text = script_getstr(st, i);
if( sd->npc_menu > 0 )
StrBuf->AppendStr(&buf, ":");
StrBuf->AppendStr(&buf, text);
sd->npc_menu += script->menu_countoptions(text, 0, NULL);
}
st->state = RERUNLINE;
sd->state.menu_or_input = 1;
/* menus beyond this length crash the client (see bugreport:6402) */
if( StrBuf->Length(&buf) >= 2047 ) {
struct npc_data * nd = map->id2nd(st->oid);
char* menu;
CREATE(menu, char, 2048);
safestrncpy(menu, StrBuf->Value(&buf), 2047);
ShowWarning("NPC Menu too long! (source:%s / length:%d)\n",nd?nd->name:"Unknown",StrBuf->Length(&buf));
clif->scriptmenu(sd, st->oid, menu);
aFree(menu);
} else
clif->scriptmenu(sd, st->oid, StrBuf->Value(&buf));
StrBuf->Destroy(&buf);
if( sd->npc_menu >= 0xff ) {
ShowWarning("buildin_select: Too many options specified (current=%d, max=254).\n", sd->npc_menu);
script->reportsrc(st);
}
} else if( sd->npc_menu == 0xff ) {// Cancel was pressed
sd->state.menu_or_input = 0;
st->state = END;
} else {// return selected option
int menu = 0;
sd->state.menu_or_input = 0;
for( i = 2; i <= script_lastdata(st); ++i ) {
text = script_getstr(st, i);
sd->npc_menu -= script->menu_countoptions(text, sd->npc_menu, &menu);
if( sd->npc_menu <= 0 )
break;// entry found
}
pc->setreg(sd, script->add_str("@menu"), menu);
script_pushint(st, menu);
st->state = RUN;
}
return true;
}
/// Displays a menu with options and returns the selected option.
/// Behaves like 'menu' without the target labels, except when cancel is
/// pressed.
/// When cancel is pressed, the script continues and 255 is returned.
///
/// prompt({,,...}) ->
///
/// @see menu
static BUILDIN(prompt)
{
int i;
const char *text;
struct map_session_data *sd = script->rid2sd(st);
if (sd == NULL)
return true;
#ifdef SECURE_NPCTIMEOUT
sd->npc_idle_type = NPCT_MENU;
#endif
if( sd->state.menu_or_input == 0 )
{
struct StringBuf buf;
StrBuf->Init(&buf);
sd->npc_menu = 0;
for( i = 2; i <= script_lastdata(st); ++i )
{
text = script_getstr(st, i);
if( sd->npc_menu > 0 )
StrBuf->AppendStr(&buf, ":");
StrBuf->AppendStr(&buf, text);
sd->npc_menu += script->menu_countoptions(text, 0, NULL);
}
st->state = RERUNLINE;
sd->state.menu_or_input = 1;
/* menus beyond this length crash the client (see bugreport:6402) */
if( StrBuf->Length(&buf) >= 2047 ) {
struct npc_data * nd = map->id2nd(st->oid);
char* menu;
CREATE(menu, char, 2048);
safestrncpy(menu, StrBuf->Value(&buf), 2047);
ShowWarning("NPC Menu too long! (source:%s / length:%d)\n",nd?nd->name:"Unknown",StrBuf->Length(&buf));
clif->scriptmenu(sd, st->oid, menu);
aFree(menu);
} else
clif->scriptmenu(sd, st->oid, StrBuf->Value(&buf));
StrBuf->Destroy(&buf);
if( sd->npc_menu >= 0xff )
{
ShowWarning("buildin_prompt: Too many options specified (current=%d, max=254).\n", sd->npc_menu);
script->reportsrc(st);
}
}
else if( sd->npc_menu == 0xff )
{// Cancel was pressed
sd->state.menu_or_input = 0;
pc->setreg(sd, script->add_str("@menu"), 0xff);
script_pushint(st, 0xff);
st->state = RUN;
}
else
{// return selected option
int menu = 0;
sd->state.menu_or_input = 0;
for( i = 2; i <= script_lastdata(st); ++i )
{
text = script_getstr(st, i);
sd->npc_menu -= script->menu_countoptions(text, sd->npc_menu, &menu);
if( sd->npc_menu <= 0 )
break;// entry found
}
pc->setreg(sd, script->add_str("@menu"), menu);
script_pushint(st, menu);
st->state = RUN;
}
return true;
}
/////////////////////////////////////////////////////////////////////
// ...
//
/// Jumps to the target script label.
///
/// goto