2059 lines
55 KiB
C
2059 lines
55 KiB
C
//=======================================================================
|
|
// Copyright XashXT Group 2007 ©
|
|
// pr_edict.c - vm runtime base
|
|
//=======================================================================
|
|
|
|
#include "vprogs.h"
|
|
|
|
static prvm_prog_t prog_list[PRVM_MAXPROGS];
|
|
sizebuf_t vm_tempstringsbuf;
|
|
|
|
int prvm_type_size[8] = {1, sizeof(string_t)/4,1,3,1,1, sizeof(func_t)/4, sizeof(void *)/4};
|
|
|
|
ddef_t *PRVM_ED_FieldAtOfs(int ofs);
|
|
bool PRVM_ED_ParseEpair(pr_edict_t *ent, ddef_t *key, const char *s);
|
|
|
|
//============================================================================
|
|
// mempool handling
|
|
|
|
/*
|
|
===============
|
|
PRVM_MEM_Alloc
|
|
===============
|
|
*/
|
|
void PRVM_MEM_Alloc(void)
|
|
{
|
|
int i;
|
|
|
|
// reserve space for the null entity aka world
|
|
// check bound of max_edicts
|
|
vm.prog->max_edicts = bound(1 + vm.prog->reserved_edicts, vm.prog->max_edicts, vm.prog->limit_edicts);
|
|
vm.prog->num_edicts = bound(1 + vm.prog->reserved_edicts, vm.prog->num_edicts, vm.prog->max_edicts);
|
|
|
|
// edictprivate_size has to be min as big prvm_edict_private_t
|
|
vm.prog->edictprivate_size = max(vm.prog->edictprivate_size, (int)sizeof(vm_edict_t));
|
|
|
|
// alloc edicts
|
|
vm.prog->edicts = (pr_edict_t *)Mem_Alloc(vm.prog->progs_mempool, vm.prog->limit_edicts * sizeof(pr_edict_t));
|
|
|
|
// alloc edict private space
|
|
vm.prog->edictprivate = Mem_Alloc(vm.prog->progs_mempool, vm.prog->max_edicts * vm.prog->edictprivate_size);
|
|
|
|
// alloc edict fields
|
|
vm.prog->edictsfields = Mem_Alloc(vm.prog->progs_mempool, vm.prog->max_edicts * vm.prog->edict_size);
|
|
|
|
// set edict pointers
|
|
for(i = 0; i < vm.prog->max_edicts; i++)
|
|
{
|
|
vm.prog->edicts[i].priv.ed = (vm_edict_t *)((byte *)vm.prog->edictprivate + i * vm.prog->edictprivate_size);
|
|
vm.prog->edicts[i].progs.vp = (void*)((byte *)vm.prog->edictsfields + i * vm.prog->edict_size);
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
PRVM_MEM_IncreaseEdicts
|
|
===============
|
|
*/
|
|
void PRVM_MEM_IncreaseEdicts( void )
|
|
{
|
|
int i;
|
|
int oldmaxedicts = vm.prog->max_edicts;
|
|
void *oldedictsfields = vm.prog->edictsfields;
|
|
void *oldedictprivate = vm.prog->edictprivate;
|
|
|
|
if(vm.prog->max_edicts >= vm.prog->limit_edicts)
|
|
return;
|
|
|
|
PRVM_GCALL(begin_increase_edicts)();
|
|
|
|
// increase edicts
|
|
vm.prog->max_edicts = min(vm.prog->max_edicts + 256, vm.prog->limit_edicts);
|
|
|
|
vm.prog->edictsfields = Mem_Alloc(vm.prog->progs_mempool, vm.prog->max_edicts * vm.prog->edict_size);
|
|
vm.prog->edictprivate = Mem_Alloc(vm.prog->progs_mempool, vm.prog->max_edicts * vm.prog->edictprivate_size);
|
|
|
|
Mem_Copy(vm.prog->edictsfields, oldedictsfields, oldmaxedicts * vm.prog->edict_size);
|
|
Mem_Copy(vm.prog->edictprivate, oldedictprivate, oldmaxedicts * vm.prog->edictprivate_size);
|
|
|
|
// set e and v pointers
|
|
for(i = 0; i < vm.prog->max_edicts; i++)
|
|
{
|
|
vm.prog->edicts[i].priv.ed = (vm_edict_t *)((byte *)vm.prog->edictprivate + i * vm.prog->edictprivate_size);
|
|
vm.prog->edicts[i].progs.vp = (void*)((byte *)vm.prog->edictsfields + i * vm.prog->edict_size);
|
|
}
|
|
|
|
PRVM_GCALL(end_increase_edicts)();
|
|
|
|
Mem_Free(oldedictsfields);
|
|
Mem_Free(oldedictprivate);
|
|
}
|
|
|
|
//============================================================================
|
|
// normal prvm
|
|
|
|
int PRVM_ED_FindFieldOffset(const char *field)
|
|
{
|
|
ddef_t *d;
|
|
d = PRVM_ED_FindField(field);
|
|
if (!d) return 0;
|
|
return d->ofs * 4;
|
|
}
|
|
|
|
int PRVM_ED_FindGlobalOffset(const char *global)
|
|
{
|
|
ddef_t *d;
|
|
d = PRVM_ED_FindGlobal(global);
|
|
if (!d) return 0;
|
|
return d->ofs * 4;
|
|
}
|
|
|
|
func_t PRVM_ED_FindFunctionOffset(const char *function)
|
|
{
|
|
mfunction_t *f;
|
|
f = PRVM_ED_FindFunction(function);
|
|
if (!f)
|
|
return 0;
|
|
return (func_t)(f - vm.prog->functions);
|
|
}
|
|
|
|
bool PRVM_ProgLoaded( int prognr )
|
|
{
|
|
if(prognr < 0 || prognr >= PRVM_MAXPROGS)
|
|
return false;
|
|
|
|
return (prog_list[prognr].loaded ? true : false);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PRVM_SetProgFromString
|
|
=================
|
|
*/
|
|
// perhaps add a return value when the str doesnt exist
|
|
bool PRVM_SetProgFromString(const char *str)
|
|
{
|
|
int i = 0;
|
|
for(; i < PRVM_MAXPROGS ; i++)
|
|
if(prog_list[i].name && !com.strcmp(prog_list[i].name,str))
|
|
{
|
|
if(prog_list[i].loaded)
|
|
{
|
|
vm.prog = &prog_list[i];
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Msg("%s not loaded !\n", PRVM_NAME);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Msg("Invalid program name %s !\n", str);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PRVM_SetProg
|
|
=================
|
|
*/
|
|
void PRVM_SetProg(int prognr)
|
|
{
|
|
if(0 <= prognr && prognr < PRVM_MAXPROGS)
|
|
{
|
|
if(prog_list[prognr].loaded)
|
|
vm.prog = &prog_list[prognr];
|
|
else PRVM_ERROR("%i not loaded!\n", prognr);
|
|
return;
|
|
}
|
|
PRVM_ERROR("Invalid program number %i", prognr);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PRVM_ED_ClearEdict
|
|
|
|
Sets everything to NULL
|
|
=================
|
|
*/
|
|
void PRVM_ED_ClearEdict (pr_edict_t *e)
|
|
{
|
|
Mem_Set (e->progs.vp, 0, vm.prog->progs->entityfields * 4);
|
|
e->priv.ed->free = false;
|
|
|
|
// AK: Let the init_edict function determine if something needs to be initialized
|
|
PRVM_GCALL(init_edict)(e);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PRVM_ED_Alloc
|
|
|
|
Either finds a free edict, or allocates a new one.
|
|
Try to avoid reusing an entity that was recently freed, because it
|
|
can cause the client to think the entity morphed into something else
|
|
instead of being removed and recreated, which can cause interpolated
|
|
angles and bad trails.
|
|
=================
|
|
*/
|
|
pr_edict_t *PRVM_ED_Alloc (void)
|
|
{
|
|
int i;
|
|
pr_edict_t *e;
|
|
|
|
// the client qc dont need maxclients
|
|
// thus it doesnt need to use svs.maxclients
|
|
// AK: changed i=svs.maxclients+1
|
|
// AK: changed so the edict 0 wont spawn -> used as reserved/world entity
|
|
// although the menu/client has no world
|
|
for (i = vm.prog->reserved_edicts + 1; i < vm.prog->num_edicts; i++)
|
|
{
|
|
e = PRVM_EDICT_NUM(i);
|
|
// the first couple seconds of server time can involve a lot of
|
|
// freeing and allocating, so relax the replacement policy
|
|
if (e->priv.ed->free && ( e->priv.ed->freetime < 2 || (*vm.prog->time - e->priv.ed->freetime) > 0.5 ) )
|
|
{
|
|
PRVM_ED_ClearEdict (e);
|
|
return e;
|
|
}
|
|
}
|
|
|
|
if (i == vm.prog->limit_edicts)
|
|
PRVM_ERROR ("%s: PRVM_ED_Alloc: no free edicts",PRVM_NAME);
|
|
|
|
vm.prog->num_edicts++;
|
|
if (vm.prog->num_edicts >= vm.prog->max_edicts)
|
|
PRVM_MEM_IncreaseEdicts();
|
|
|
|
e = PRVM_EDICT_NUM(i);
|
|
PRVM_ED_ClearEdict (e);
|
|
|
|
return e;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PRVM_ED_Free
|
|
|
|
Marks the edict as free
|
|
FIXME: walk all entities and NULL out references to this entity
|
|
=================
|
|
*/
|
|
void PRVM_ED_Free (pr_edict_t *ed)
|
|
{
|
|
// dont delete the null entity (world) or reserved edicts
|
|
if(PRVM_NUM_FOR_EDICT(ed) <= vm.prog->reserved_edicts )
|
|
return;
|
|
|
|
PRVM_GCALL(free_edict)(ed);
|
|
|
|
ed->priv.ed->free = true;
|
|
ed->priv.ed->freetime = *vm.prog->time;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
/*
|
|
============
|
|
PRVM_ED_GlobalAtOfs
|
|
============
|
|
*/
|
|
ddef_t *PRVM_ED_GlobalAtOfs( int ofs )
|
|
{
|
|
ddef_t *def;
|
|
int i;
|
|
|
|
for( i = 0; i < vm.prog->progs->numglobaldefs; i++ )
|
|
{
|
|
def = &vm.prog->globaldefs[i];
|
|
if( def->ofs == ofs )
|
|
return def;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
============
|
|
PRVM_ED_FieldAtOfs
|
|
============
|
|
*/
|
|
ddef_t *PRVM_ED_FieldAtOfs (int ofs)
|
|
{
|
|
ddef_t *def;
|
|
int i;
|
|
|
|
for (i=0 ; i<vm.prog->progs->numfielddefs ; i++)
|
|
{
|
|
def = &vm.prog->fielddefs[i];
|
|
if (def->ofs == ofs)
|
|
return def;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
============
|
|
PRVM_ED_FindField
|
|
============
|
|
*/
|
|
ddef_t *PRVM_ED_FindField( const char *name )
|
|
{
|
|
ddef_t *def;
|
|
int i;
|
|
|
|
for (i=0 ; i<vm.prog->progs->numfielddefs ; i++)
|
|
{
|
|
def = &vm.prog->fielddefs[i];
|
|
if (!com.strcmp(PRVM_GetString(def->s_name), name))
|
|
return def;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
============
|
|
PRVM_ED_FindGlobal
|
|
============
|
|
*/
|
|
ddef_t *PRVM_ED_FindGlobal (const char *name)
|
|
{
|
|
ddef_t *def;
|
|
int i;
|
|
|
|
for (i=0 ; i<vm.prog->progs->numglobaldefs ; i++)
|
|
{
|
|
def = &vm.prog->globaldefs[i];
|
|
if (!com.strcmp(PRVM_GetString(def->s_name), name))
|
|
return def;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
PRVM_ED_FindFunction
|
|
============
|
|
*/
|
|
mfunction_t *PRVM_ED_FindFunction (const char *name)
|
|
{
|
|
mfunction_t *func;
|
|
int i;
|
|
|
|
for (i=0 ; i<vm.prog->progs->numfunctions ; i++)
|
|
{
|
|
func = &vm.prog->functions[i];
|
|
if (!com.strcmp(PRVM_GetString(func->s_name), name))
|
|
return func;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
PRVM_ValueString
|
|
|
|
Returns a string describing *data in a type specific manner
|
|
=============
|
|
*/
|
|
char *PRVM_ValueString( etype_t type, prvm_eval_t *val )
|
|
{
|
|
static char line[MAX_MSGLEN];
|
|
ddef_t *def;
|
|
mfunction_t *f;
|
|
int n;
|
|
|
|
type = (etype_t)type & ~DEF_SAVEGLOBAL;
|
|
|
|
switch( type )
|
|
{
|
|
case ev_struct:
|
|
com.strncpy ( line, "struct", sizeof (line));
|
|
break;
|
|
case ev_union:
|
|
com.strncpy ( line, "union", sizeof (line));
|
|
break;
|
|
case ev_string:
|
|
com.strncpy (line, PRVM_GetString (val->string), sizeof (line));
|
|
break;
|
|
case ev_entity:
|
|
n = val->edict;
|
|
if (n < 0 || n >= vm.prog->limit_edicts)
|
|
com.sprintf (line, "entity %i (invalid!)", n);
|
|
else com.sprintf (line, "entity %i", n);
|
|
break;
|
|
case ev_function:
|
|
f = vm.prog->functions + val->function;
|
|
com.sprintf (line, "%s()", PRVM_GetString(f->s_name));
|
|
break;
|
|
case ev_field:
|
|
def = PRVM_ED_FieldAtOfs ( val->_int );
|
|
if(!def) com.sprintf (line, ".???", val->_int );
|
|
else com.sprintf (line, ".%s", PRVM_GetString(def->s_name));
|
|
break;
|
|
case ev_void:
|
|
com.sprintf (line, "void");
|
|
break;
|
|
case ev_float:
|
|
com.sprintf (line, "%10.4f", val->_float);
|
|
break;
|
|
case ev_integer:
|
|
com.sprintf (line, "%i", val->_int);
|
|
break;
|
|
case ev_vector:
|
|
com.sprintf (line, "'%10.4f %10.4f %10.4f'", val->vector[0], val->vector[1], val->vector[2]);
|
|
break;
|
|
case ev_pointer:
|
|
com.sprintf (line, "pointer");
|
|
break;
|
|
default:
|
|
com.sprintf (line, "bad type %i", (int) type);
|
|
break;
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
/*
|
|
============
|
|
PRVM_UglyValueString
|
|
|
|
Returns a string describing *data in a type specific manner
|
|
Easier to parse than PR_ValueString
|
|
=============
|
|
*/
|
|
char *PRVM_UglyValueString (etype_t type, prvm_eval_t *val)
|
|
{
|
|
static char line[MAX_MSGLEN];
|
|
int i;
|
|
const char *s;
|
|
ddef_t *def;
|
|
mfunction_t *f;
|
|
|
|
type = (etype_t)type & ~DEF_SAVEGLOBAL;
|
|
|
|
switch( type )
|
|
{
|
|
case ev_string:
|
|
// Parse the string a bit to turn special characters
|
|
// (like newline, specifically) into escape codes,
|
|
// this fixes saving games from various mods
|
|
s = PRVM_GetString ( val->string );
|
|
for( i = 0;i < (int)sizeof(line) - 2 && *s; )
|
|
{
|
|
if( *s == '\n' )
|
|
{
|
|
line[i++] = '\\';
|
|
line[i++] = 'n';
|
|
}
|
|
else if( *s == '\r' )
|
|
{
|
|
line[i++] = '\\';
|
|
line[i++] = 'r';
|
|
}
|
|
else line[i++] = *s;
|
|
s++;
|
|
}
|
|
line[i] = '\0';
|
|
break;
|
|
case ev_entity:
|
|
com.sprintf( line, "%i", PRVM_NUM_FOR_EDICT(PRVM_PROG_TO_EDICT(val->edict)));
|
|
break;
|
|
case ev_function:
|
|
f = vm.prog->functions + val->function;
|
|
com.strncpy (line, PRVM_GetString (f->s_name), sizeof (line));
|
|
break;
|
|
case ev_field:
|
|
def = PRVM_ED_FieldAtOfs ( val->_int );
|
|
com.sprintf (line, ".%s", PRVM_GetString(def->s_name));
|
|
break;
|
|
case ev_void:
|
|
com.sprintf (line, "void");
|
|
break;
|
|
case ev_float:
|
|
com.sprintf (line, "%f", val->_float);
|
|
break;
|
|
case ev_vector:
|
|
com.sprintf (line, "%f %f %f", val->vector[0], val->vector[1], val->vector[2]);
|
|
break;
|
|
case ev_integer:
|
|
com.sprintf (line, "%i", val->_int);
|
|
break;
|
|
case ev_pointer:
|
|
case ev_struct:
|
|
case ev_union:
|
|
com.sprintf (line, "skip new type %i", type);
|
|
break;
|
|
default:
|
|
com.sprintf (line, "bad type %i", type);
|
|
break;
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
/*
|
|
============
|
|
PRVM_GlobalString
|
|
|
|
Returns a string with a description and the contents of a global,
|
|
padded to 20 field width
|
|
============
|
|
*/
|
|
char *PRVM_GlobalString (int ofs)
|
|
{
|
|
char *s;
|
|
ddef_t *def;
|
|
void *val;
|
|
static char line[128];
|
|
|
|
val = (void *)&vm.prog->globals.gp[ofs];
|
|
def = PRVM_ED_GlobalAtOfs(ofs);
|
|
if( !def ) com.sprintf (line,"GLOBAL%i", ofs);
|
|
else
|
|
{
|
|
s = PRVM_ValueString((etype_t)def->type, (prvm_eval_t *)val);
|
|
com.sprintf (line,"%s (=%s)", PRVM_GetString(def->s_name), s);
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
char *PRVM_GlobalStringNoContents (int ofs)
|
|
{
|
|
//size_t i;
|
|
ddef_t *def;
|
|
static char line[128];
|
|
|
|
def = PRVM_ED_GlobalAtOfs(ofs);
|
|
if (!def)
|
|
com.sprintf (line,"GLOBAL%i", ofs);
|
|
else
|
|
com.sprintf (line,"%s", PRVM_GetString(def->s_name));
|
|
|
|
return line;
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
PRVM_ED_Print
|
|
|
|
For debugging
|
|
=============
|
|
*/
|
|
// LordHavoc: optimized this to print out much more quickly (tempstring)
|
|
// LordHavoc: changed to print out every 4096 characters (incase there are a lot of fields to print)
|
|
void PRVM_ED_Print(pr_edict_t *ed)
|
|
{
|
|
size_t l;
|
|
ddef_t *d;
|
|
int *v;
|
|
int i, j;
|
|
const char *name;
|
|
int type;
|
|
char tempstring[MAX_MSGLEN], tempstring2[260]; // temporary string buffers
|
|
|
|
if( ed->priv.ed->free )
|
|
{
|
|
Msg( "%s: FREE\n", PRVM_NAME );
|
|
return;
|
|
}
|
|
|
|
tempstring[0] = 0;
|
|
com.sprintf(tempstring, "\n%s EDICT %i:\n", PRVM_NAME, PRVM_NUM_FOR_EDICT(ed));
|
|
for( i = 1; i < vm.prog->progs->numfielddefs; i++ )
|
|
{
|
|
d = &vm.prog->fielddefs[i];
|
|
name = PRVM_GetString(d->s_name);
|
|
if(name[com.strlen(name)-2] == '_')
|
|
continue; // skip _x, _y, _z vars
|
|
|
|
v = (int *)((char *)ed->progs.vp + d->ofs*4);
|
|
|
|
// if the value is still all 0, skip the field
|
|
type = d->type & ~DEF_SAVEGLOBAL;
|
|
|
|
for (j=0 ; j<prvm_type_size[type] ; j++)
|
|
if (v[j])
|
|
break;
|
|
if (j == prvm_type_size[type])
|
|
continue;
|
|
|
|
if (com.strlen(name) > sizeof(tempstring2)-4)
|
|
{
|
|
Mem_Copy (tempstring2, name, sizeof(tempstring2)-4);
|
|
tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
|
|
tempstring2[sizeof(tempstring2)-1] = 0;
|
|
name = tempstring2;
|
|
}
|
|
com.strncat(tempstring, name, sizeof(tempstring));
|
|
for (l = com.strlen(name);l < 14;l++)
|
|
com.strncat(tempstring, " ", sizeof(tempstring));
|
|
com.strncat(tempstring, " ", sizeof(tempstring));
|
|
|
|
name = PRVM_ValueString((etype_t)d->type, (prvm_eval_t *)v);
|
|
if (com.strlen(name) > sizeof(tempstring2)-4)
|
|
{
|
|
Mem_Copy (tempstring2, name, sizeof(tempstring2)-4);
|
|
tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
|
|
tempstring2[sizeof(tempstring2)-1] = 0;
|
|
name = tempstring2;
|
|
}
|
|
com.strncat(tempstring, name, sizeof(tempstring));
|
|
com.strncat(tempstring, "\n", sizeof(tempstring));
|
|
if (com.strlen(tempstring) >= sizeof(tempstring)/2)
|
|
{
|
|
Msg(tempstring);
|
|
tempstring[0] = 0;
|
|
}
|
|
}
|
|
if (tempstring[0]) Msg(tempstring);
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PRVM_ED_Write
|
|
|
|
For savegames
|
|
=============
|
|
*/
|
|
void PRVM_ED_Write( pr_edict_t *ed, void *buffer, void *numpairs, setpair_t callback )
|
|
{
|
|
ddef_t *d;
|
|
int *v;
|
|
int i, j;
|
|
const char *name, *value;
|
|
int type;
|
|
|
|
if( !callback ) return;
|
|
if( ed->priv.ed->free )
|
|
{
|
|
// freed entity too has serialnumber!
|
|
callback( NULL, NULL, buffer, numpairs );
|
|
return;
|
|
}
|
|
|
|
for( i = 1; i < vm.prog->progs->numfielddefs; i++ )
|
|
{
|
|
d = &vm.prog->fielddefs[i];
|
|
name = PRVM_GetString( d->s_name );
|
|
if(name[com.strlen(name) - 2] == '_')
|
|
continue; // skip _x, _y, _z vars
|
|
|
|
v = (int *)((char *)ed->progs.vp + d->ofs * 4);
|
|
// if the value is still all 0, skip the field
|
|
type = d->type & ~DEF_SAVEGLOBAL;
|
|
for( j = 0; j < prvm_type_size[type]; j++ )
|
|
if( v[j] ) break;
|
|
if( j == prvm_type_size[type] ) continue;
|
|
|
|
value = PRVM_UglyValueString((etype_t)d->type, (prvm_eval_t *)v);
|
|
callback( name, value, buffer, numpairs );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PRVM_ED_Read
|
|
|
|
For savegames
|
|
=============
|
|
*/
|
|
void PRVM_ED_Read( int s_table, int entnum, dkeyvalue_t *fields, int numpairs )
|
|
{
|
|
const char *keyname, *value;
|
|
ddef_t *key;
|
|
pr_edict_t *ent;
|
|
int i;
|
|
|
|
if( entnum >= vm.prog->limit_edicts ) Host_Error( "PRVM_ED_Read: too many edicts in save file\n" );
|
|
while( entnum >= vm.prog->max_edicts) PRVM_MEM_IncreaseEdicts(); // increase edict numbers
|
|
ent = PRVM_EDICT_NUM( entnum );
|
|
PRVM_ED_ClearEdict( ent );
|
|
|
|
if( !numpairs )
|
|
{
|
|
// freed edict
|
|
ent->priv.ed->free = true;
|
|
return;
|
|
}
|
|
|
|
// go through all the dictionary pairs
|
|
for( i = 0; i < numpairs; i++ )
|
|
{
|
|
keyname = StringTable_GetString( s_table, fields[i].epair[DENT_KEY] );
|
|
value = StringTable_GetString( s_table, fields[i].epair[DENT_VAL] );
|
|
|
|
key = PRVM_ED_FindField( keyname );
|
|
if( !key )
|
|
{
|
|
PRVM_GCALL(keyvalue_edict)( ent, keyname, value );
|
|
else MsgDev( D_WARN, "%s: unknown field '%s'\n", PRVM_NAME, keyname);
|
|
continue;
|
|
}
|
|
|
|
// simple huh ?
|
|
if(!PRVM_ED_ParseEpair( ent, key, value )) PRVM_ERROR( "PRVM_ED_ParseEdict: parse error" );
|
|
}
|
|
|
|
// all done, restore physics interaction links or somelike
|
|
PRVM_GCALL(restore_edict)(ent);
|
|
}
|
|
|
|
void PRVM_ED_PrintNum (int ent)
|
|
{
|
|
PRVM_ED_Print(PRVM_EDICT_NUM(ent));
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PRVM_ED_PrintEdicts_f
|
|
|
|
For debugging, prints all the entities in the current server
|
|
=============
|
|
*/
|
|
void PRVM_ED_PrintEdicts_f (void)
|
|
{
|
|
int i;
|
|
|
|
if(Cmd_Argc() != 2)
|
|
{
|
|
Msg("prvm_edicts <program name>\n");
|
|
return;
|
|
}
|
|
|
|
|
|
if(!PRVM_SetProgFromString(Cmd_Argv(1)))
|
|
return;
|
|
|
|
Msg("%s: %i entities\n", PRVM_NAME, vm.prog->num_edicts);
|
|
for (i=0 ; i<vm.prog->num_edicts ; i++)
|
|
PRVM_ED_PrintNum (i);
|
|
|
|
vm.prog = NULL;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PRVM_ED_PrintEdict_f
|
|
|
|
For debugging, prints a single edict
|
|
=============
|
|
*/
|
|
void PRVM_ED_PrintEdict_f( void )
|
|
{
|
|
int i;
|
|
|
|
if( Cmd_Argc() != 3 )
|
|
{
|
|
Msg("prvm_edict <program name> <edict number>\n");
|
|
return;
|
|
}
|
|
|
|
if(!PRVM_SetProgFromString(Cmd_Argv( 1 ))) return;
|
|
|
|
i = com.atoi (Cmd_Argv(2));
|
|
if( i >= vm.prog->num_edicts )
|
|
{
|
|
Msg( "bad edict number\n" );
|
|
vm.prog = NULL;
|
|
return;
|
|
}
|
|
|
|
PRVM_ED_PrintNum( i );
|
|
vm.prog = NULL;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PRVM_ED_Count
|
|
|
|
For debugging
|
|
=============
|
|
*/
|
|
void PRVM_ED_Count_f( void )
|
|
{
|
|
int i;
|
|
pr_edict_t *ent;
|
|
int active;
|
|
|
|
if(Cmd_Argc() != 2)
|
|
{
|
|
Msg( "prvm_count <program name>\n" );
|
|
return;
|
|
}
|
|
|
|
|
|
if(!PRVM_SetProgFromString(Cmd_Argv( 1 )))
|
|
return;
|
|
|
|
if( vm.prog->count_edicts ) vm.prog->count_edicts();
|
|
else
|
|
{
|
|
active = 0;
|
|
for( i = 0; i < vm.prog->num_edicts; i++ )
|
|
{
|
|
ent = PRVM_EDICT_NUM( i );
|
|
if( ent->priv.ed->free )
|
|
continue;
|
|
active++;
|
|
}
|
|
|
|
Msg( "num_edicts:%3i\n", vm.prog->num_edicts );
|
|
Msg( "active :%3i\n", active );
|
|
}
|
|
vm.prog = NULL;
|
|
}
|
|
|
|
/*
|
|
==============================================================================
|
|
ARCHIVING GLOBALS
|
|
==============================================================================
|
|
*/
|
|
/*
|
|
=============
|
|
PRVM_ED_WriteGlobals
|
|
=============
|
|
*/
|
|
void PRVM_ED_WriteGlobals( void *buffer, void *numpairs, setpair_t callback )
|
|
{
|
|
ddef_t *def;
|
|
const char *name;
|
|
const char *value;
|
|
int i, type;
|
|
|
|
// nothing to process ?
|
|
if( !callback ) return;
|
|
|
|
for( i = 0; i < vm.prog->progs->numglobaldefs; i++ )
|
|
{
|
|
def = &vm.prog->globaldefs[i];
|
|
type = def->type;
|
|
if(!(def->type & DEF_SAVEGLOBAL))
|
|
continue;
|
|
type &= ~DEF_SAVEGLOBAL;
|
|
|
|
if( type != ev_string && type != ev_float && type != ev_entity )
|
|
continue;
|
|
|
|
name = PRVM_GetString(def->s_name);
|
|
value = PRVM_UglyValueString((etype_t)type, (prvm_eval_t *)&vm.prog->globals.gp[def->ofs]);
|
|
callback( name, value, buffer, numpairs );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PRVM_ED_ReadGlobals
|
|
=============
|
|
*/
|
|
void PRVM_ED_ReadGlobals( int s_table, dkeyvalue_t *globals, int numpairs )
|
|
{
|
|
const char *keyname;
|
|
const char *value;
|
|
ddef_t *key;
|
|
int i;
|
|
|
|
for( i = 0; i < numpairs; i++ )
|
|
{
|
|
keyname = StringTable_GetString( s_table, globals[i].epair[DENT_KEY] );
|
|
value = StringTable_GetString( s_table, globals[i].epair[DENT_VAL]);
|
|
|
|
key = PRVM_ED_FindGlobal( keyname );
|
|
if( !key )
|
|
{
|
|
MsgDev( D_INFO, "'%s' is not a global on %s\n", keyname, PRVM_NAME );
|
|
continue;
|
|
}
|
|
if( !PRVM_ED_ParseEpair( NULL, key, value )) PRVM_ERROR( "PRVM_ED_ReadGlobals: parse error\n" );
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
|
|
/*
|
|
=============
|
|
PRVM_ED_ParseEval
|
|
|
|
Can parse either fields or globals
|
|
returns false if error
|
|
=============
|
|
*/
|
|
bool PRVM_ED_ParseEpair( pr_edict_t *ent, ddef_t *key, const char *s )
|
|
{
|
|
int i, l;
|
|
char *new_p;
|
|
ddef_t *def;
|
|
prvm_eval_t *val;
|
|
mfunction_t *func;
|
|
|
|
if( ent ) val = (prvm_eval_t *)((int *)ent->progs.vp + key->ofs);
|
|
else val = (prvm_eval_t *)((int *)vm.prog->globals.gp + key->ofs);
|
|
|
|
switch( key->type & ~DEF_SAVEGLOBAL )
|
|
{
|
|
case ev_string:
|
|
l = (int)com.strlen(s) + 1;
|
|
val->string = PRVM_AllocString(l, &new_p);
|
|
for (i = 0;i < l;i++)
|
|
{
|
|
if (s[i] == '\\' && i < l-1)
|
|
{
|
|
i++;
|
|
if (s[i] == 'n')
|
|
*new_p++ = '\n';
|
|
else if (s[i] == 'r')
|
|
*new_p++ = '\r';
|
|
else
|
|
*new_p++ = s[i];
|
|
}
|
|
else
|
|
*new_p++ = s[i];
|
|
}
|
|
break;
|
|
case ev_float:
|
|
while(*s && *s <= ' ') s++;
|
|
val->_float = com.atof( s );
|
|
break;
|
|
case ev_vector:
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
while(*s && *s <= ' ') s++;
|
|
if( !*s ) break;
|
|
val->vector[i] = com.atof( s );
|
|
while( *s > ' ' ) s++;
|
|
if (!*s) break;
|
|
}
|
|
break;
|
|
case ev_entity:
|
|
while( *s && *s <= ' ' ) s++;
|
|
i = com.atoi( s );
|
|
if (i >= vm.prog->limit_edicts)
|
|
MsgDev( D_WARN, "PRVM_ED_ParseEpair: ev_entity reference too large (edict %u >= limit_edicts %u) on %s\n", (uint)i, (uint)vm.prog->limit_edicts, PRVM_NAME);
|
|
while( i >= vm.prog->max_edicts ) PRVM_MEM_IncreaseEdicts();
|
|
// if IncreaseEdicts was called the base pointer needs to be updated
|
|
if( ent ) val = (prvm_eval_t *)((int *)ent->progs.vp + key->ofs);
|
|
val->edict = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM((int)i));
|
|
break;
|
|
|
|
case ev_field:
|
|
def = PRVM_ED_FindField(s);
|
|
if( !def )
|
|
{
|
|
MsgDev( D_WARN, "PRVM_ED_ParseEpair: Can't find field %s in %s\n", s, PRVM_NAME );
|
|
return false;
|
|
}
|
|
val->_int = def->ofs;
|
|
break;
|
|
|
|
case ev_function:
|
|
func = PRVM_ED_FindFunction(s);
|
|
if( !func )
|
|
{
|
|
MsgDev( D_WARN, "PRVM_ED_ParseEpair: Can't find function %s in %s\n", s, PRVM_NAME );
|
|
return false;
|
|
}
|
|
val->function = func - vm.prog->functions;
|
|
break;
|
|
|
|
default:
|
|
MsgDev( D_WARN, "PRVM_ED_ParseEpair: Unknown key->type %i for key \"%s\" on %s\n", key->type, PRVM_GetString(key->s_name), PRVM_NAME );
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PRVM_ED_EdictSet_f
|
|
|
|
Console command to set a field of a specified edict
|
|
=============
|
|
*/
|
|
void PRVM_ED_EdictSet_f(void)
|
|
{
|
|
pr_edict_t *ed;
|
|
ddef_t *key;
|
|
|
|
if(Cmd_Argc() != 5)
|
|
{
|
|
Msg("prvm_edictset <program name> <edict number> <field> <value>\n");
|
|
return;
|
|
}
|
|
|
|
|
|
if(!PRVM_SetProgFromString(Cmd_Argv(1)))
|
|
{
|
|
Msg("Wrong program name %s !\n", Cmd_Argv(1));
|
|
return;
|
|
}
|
|
|
|
ed = PRVM_EDICT_NUM(com.atoi(Cmd_Argv(2)));
|
|
|
|
if((key = PRVM_ED_FindField(Cmd_Argv(3))) == 0)
|
|
MsgDev( D_WARN, "Key %s not found !\n", Cmd_Argv(3));
|
|
else PRVM_ED_ParseEpair(ed, key, Cmd_Argv(4));
|
|
|
|
vm.prog = NULL;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
PRVM_ED_ParseEdict
|
|
|
|
Parses an edict out of the given string, returning the new position
|
|
ed should be a properly initialized empty edict.
|
|
Used for initial level load and for savegames.
|
|
====================
|
|
*/
|
|
const char *PRVM_ED_ParseEdict( const char *data, pr_edict_t *ent )
|
|
{
|
|
ddef_t *key;
|
|
bool init, newline, anglehack;
|
|
char keyname[256];
|
|
size_t n;
|
|
|
|
init = false;
|
|
|
|
// go through all the dictionary pairs
|
|
MsgDev( D_NOTE, "{\n" );
|
|
while( 1 )
|
|
{
|
|
// parse key
|
|
PR_ParseToken( &data, true );
|
|
|
|
if( pr_token[0] == '\0')
|
|
PRVM_ERROR ("PRVM_ED_ParseEdict: EOF without closing brace");
|
|
|
|
// just format console messages
|
|
newline = (pr_token[0] == '}') ? true : false;
|
|
if(!newline) MsgDev(D_NOTE, "\"%s\"", pr_token);
|
|
else
|
|
{
|
|
MsgDev( D_NOTE, "}\n");
|
|
break;
|
|
}
|
|
|
|
// anglehack is to allow QuakeEd to write single scalar angles
|
|
// and allow them to be turned into vectors. (FIXME...)
|
|
if( !com.strcmp( pr_token, "angle" ))
|
|
{
|
|
com.strncpy( keyname, "angles", sizeof(keyname));
|
|
anglehack = true;
|
|
}
|
|
else
|
|
{
|
|
anglehack = false;
|
|
com.strncpy( keyname, pr_token, sizeof(keyname));
|
|
}
|
|
// another hack to fix keynames with trailing spaces
|
|
n = com.strlen( keyname );
|
|
while(n && keyname[n-1] == ' ')
|
|
{
|
|
keyname[n-1] = 0;
|
|
n--;
|
|
}
|
|
|
|
// parse value
|
|
if (!PR_ParseToken( &data, true ))
|
|
PRVM_ERROR ("PRVM_ED_ParseEdict: EOF without closing brace");
|
|
MsgDev(D_NOTE, " \"%s\"\n", pr_token);
|
|
if( pr_token[0] == '}' ) PRVM_ERROR( "PRVM_ED_ParseEdict: closing brace without data" );
|
|
init = true;
|
|
|
|
// ignore attempts to set key "" (this problem occurs in nehahra neh1m8.bsp)
|
|
if (!keyname[0]) continue;
|
|
|
|
// keynames with a leading underscore are used for utility comments,
|
|
// and are immediately discarded by quake
|
|
if(!stricmp( keyname, "light" )) continue; // ignore lightvalue
|
|
if( keyname[0] == '_' ) continue;
|
|
|
|
key = PRVM_ED_FindField( keyname );
|
|
if( !key )
|
|
{
|
|
PRVM_GCALL(keyvalue_edict)( ent, keyname, pr_token );
|
|
else MsgDev( D_WARN, "%s: unknown field '%s'\n", PRVM_NAME, keyname);
|
|
continue;
|
|
}
|
|
|
|
if( anglehack )
|
|
{
|
|
char temp[32];
|
|
|
|
com.strncpy( temp, pr_token, sizeof(temp));
|
|
com.sprintf( pr_token, "0 %s 0", temp );
|
|
}
|
|
if(!PRVM_ED_ParseEpair( ent, key, pr_token )) PRVM_ERROR ("PRVM_ED_ParseEdict: parse error");
|
|
}
|
|
if( !init ) ent->priv.ed->free = true;
|
|
|
|
return data;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
PRVM_ED_LoadFromFile
|
|
|
|
The entities are directly placed in the array, rather than allocated with
|
|
PRVM_ED_Alloc, because otherwise an error loading the map would have entity
|
|
number references out of order.
|
|
|
|
Creates a server's entity / program execution context by
|
|
parsing textual entity definitions out of an ent file.
|
|
|
|
Used for both fresh maps and savegame loads. A fresh map would also need
|
|
to call PRVM_ED_CallSpawnFunctions () to let the objects initialize themselves.
|
|
================
|
|
*/
|
|
void PRVM_ED_LoadFromFile( const char *data )
|
|
{
|
|
pr_edict_t *ent;
|
|
int parsed, inhibited, spawned, died;
|
|
mfunction_t *func;
|
|
|
|
parsed = 0;
|
|
inhibited = 0;
|
|
spawned = 0;
|
|
died = 0;
|
|
|
|
// parse ents
|
|
while( 1 )
|
|
{
|
|
// parse the opening brace
|
|
PR_ParseToken( &data, true );
|
|
|
|
if( pr_token[0] == '\0') break;
|
|
if( pr_token[0] != '{' )
|
|
PRVM_ERROR ("PRVM_ED_LoadFromFile: %s: found %s when expecting {", PRVM_NAME, pr_token );
|
|
|
|
// CHANGED: this is not conform to PR_LoadFromFile
|
|
if(vm.prog->loadintoworld)
|
|
{
|
|
vm.prog->loadintoworld = false;
|
|
ent = PRVM_EDICT_NUM(0);
|
|
}
|
|
else ent = PRVM_ED_Alloc();
|
|
|
|
// HACKHACK: clear it
|
|
if( ent != vm.prog->edicts ) Mem_Set( ent->progs.vp, 0, vm.prog->progs->entityfields * 4 );
|
|
|
|
data = PRVM_ED_ParseEdict( data, ent );
|
|
parsed++;
|
|
|
|
// remove the entity ?
|
|
if(vm.prog->load_edict && !vm.prog->load_edict(ent))
|
|
{
|
|
PRVM_ED_Free(ent);
|
|
inhibited++;
|
|
continue;
|
|
}
|
|
|
|
// immediately call spawn function, but only if there is a pev global and a classname
|
|
if(vm.prog->pev && vm.prog->flag & PRVM_FE_CLASSNAME)
|
|
{
|
|
string_t handle = *(string_t*)&((byte*)ent->progs.vp)[PRVM_ED_FindFieldOffset("classname")];
|
|
if( !handle )
|
|
{
|
|
if(prvm_developer >= D_NOTE)
|
|
{
|
|
MsgDev( D_ERROR, "No classname for:\n");
|
|
PRVM_ED_Print(ent);
|
|
}
|
|
PRVM_ED_Free (ent);
|
|
continue;
|
|
}
|
|
|
|
// look for the spawn function
|
|
func = PRVM_ED_FindFunction (PRVM_GetString(handle));
|
|
|
|
if( !func )
|
|
{
|
|
if( prvm_developer >= D_ERROR )
|
|
{
|
|
MsgDev( D_ERROR, "No spawn function for:\n" );
|
|
PRVM_ED_Print( ent );
|
|
}
|
|
PRVM_ED_Free( ent );
|
|
continue;
|
|
}
|
|
|
|
// pev = ent
|
|
PRVM_G_INT(vm.prog->pev->ofs) = PRVM_EDICT_TO_PROG(ent);
|
|
PRVM_ExecuteProgram( func - vm.prog->functions, "", __FILE__, __LINE__ );
|
|
}
|
|
|
|
spawned++;
|
|
if (ent->priv.ed->free) died++;
|
|
}
|
|
MsgDev(D_NOTE, "%s: %i new entities parsed, %i new inhibited, %i (%i new) spawned (whereas %i removed self, %i stayed)\n", PRVM_NAME, parsed, inhibited, vm.prog->num_edicts, spawned, died, spawned - died);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
PRVM_ResetProg
|
|
===============
|
|
*/
|
|
|
|
void PRVM_ResetProg( void )
|
|
{
|
|
PRVM_GCALL(reset_cmd)();
|
|
Mem_FreePool(&vm.prog->progs_mempool);
|
|
Mem_Set(vm.prog, 0, sizeof(prvm_prog_t));
|
|
}
|
|
|
|
/*
|
|
===============
|
|
PRVM_LoadProgs
|
|
===============
|
|
*/
|
|
void PRVM_LoadProgs( const char *filename )
|
|
{
|
|
dstatement_t *st;
|
|
ddef_t *infielddefs;
|
|
dfunction_t *dfunctions;
|
|
fs_offset_t filesize;
|
|
int i, len, complen;
|
|
byte *s;
|
|
|
|
if( vm.prog->loaded )
|
|
{
|
|
PRVM_ERROR ("PRVM_LoadProgs: there is already a %s program loaded!", PRVM_NAME );
|
|
}
|
|
|
|
if( vm.prog->progs ) Mem_Free( vm.prog->progs ); // release progs file
|
|
vm.prog->progs = (dprograms_t *)FS_LoadFile(va("%s", filename ), &filesize);
|
|
|
|
if( vm.prog->progs == NULL || filesize < (fs_offset_t)sizeof(dprograms_t))
|
|
PRVM_ERROR("PRVM_LoadProgs: couldn't load %s for %s\n", filename, PRVM_NAME);
|
|
|
|
MsgDev(D_NOTE, "%s programs occupy %iK.\n", PRVM_NAME, filesize/1024);
|
|
SwapBlock((int *)vm.prog->progs, sizeof(*vm.prog->progs)); // byte swap the header
|
|
|
|
if( vm.prog->progs->version != VPROGS_VERSION)
|
|
PRVM_ERROR( "%s: %s has wrong version number (%i should be %i)", PRVM_NAME, filename, vm.prog->progs->version, VPROGS_VERSION );
|
|
|
|
// try to recognize progs.dat by crc
|
|
if( PRVM_GetProgNr() == PRVM_DECOMPILED )
|
|
{
|
|
MsgDev(D_INFO, "Prepare %s [CRC %d]\n", filename, vm.prog->progs->crc );
|
|
}
|
|
else if( vm.prog->progs->crc != vm.prog->filecrc )
|
|
{
|
|
PRVM_ERROR("%s: %s system vars have been modified, progdefs.h is out of date %d\n", PRVM_NAME, filename, vm.prog->filecrc );
|
|
}
|
|
else MsgDev(D_LOAD, "%s [^2CRC %d^7]\n", filename, vm.prog->progs->crc );
|
|
|
|
// set initial pointers
|
|
vm.prog->statements = (dstatement_t *)((byte *)vm.prog->progs + vm.prog->progs->ofs_statements);
|
|
vm.prog->globaldefs = (ddef_t *)((byte *)vm.prog->progs + vm.prog->progs->ofs_globaldefs);
|
|
infielddefs = (ddef_t *)((byte *)vm.prog->progs + vm.prog->progs->ofs_fielddefs);
|
|
dfunctions = (dfunction_t *)((byte *)vm.prog->progs + vm.prog->progs->ofs_functions);
|
|
vm.prog->strings = (char *)vm.prog->progs + vm.prog->progs->ofs_strings;
|
|
vm.prog->globals.gp = (float *)((byte *)vm.prog->progs + vm.prog->progs->ofs_globals);
|
|
|
|
// debug info
|
|
if( vm.prog->progs->ofssources ) vm.prog->sources = (dsource_t*)((byte *)vm.prog->progs + vm.prog->progs->ofssources);
|
|
if( vm.prog->progs->ofslinenums ) vm.prog->linenums = (int *)((byte *)vm.prog->progs + vm.prog->progs->ofslinenums);
|
|
if( vm.prog->progs->ofs_types ) vm.prog->types = (type_t *)((byte *)vm.prog->progs + vm.prog->progs->ofs_types);
|
|
|
|
// decompress progs if needed
|
|
if( vm.prog->progs->flags & COMP_STATEMENTS )
|
|
{
|
|
len = sizeof(dstatement_t) * vm.prog->progs->numstatements;
|
|
complen = LittleLong(*(int*)vm.prog->statements);
|
|
|
|
MsgDev(D_NOTE, "Unpacked statements: len %d, comp len %d\n", len, complen );
|
|
s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block
|
|
VFS_Unpack((char *)(((int *)vm.prog->statements)+1), complen, &s, len );
|
|
vm.prog->statements = (dstatement_t *)s;
|
|
filesize += len - complen - sizeof(int); //merge filesize
|
|
}
|
|
|
|
if( vm.prog->progs->flags & COMP_DEFS )
|
|
{
|
|
len = sizeof(ddef_t) * vm.prog->progs->numglobaldefs;
|
|
complen = LittleLong(*(int*)vm.prog->globaldefs);
|
|
|
|
MsgDev(D_NOTE, "Unpacked defs: len %d, comp len %d\n", len, complen);
|
|
s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block
|
|
VFS_Unpack((char *)(((int *)vm.prog->globaldefs)+1), complen, &s, len );
|
|
vm.prog->globaldefs = (ddef_t *)s;
|
|
filesize += len - complen - sizeof(int); //merge filesize
|
|
}
|
|
|
|
if( vm.prog->progs->flags & COMP_FIELDS )
|
|
{
|
|
len = sizeof(ddef_t) * vm.prog->progs->numfielddefs;
|
|
complen = LittleLong(*(int*)infielddefs);
|
|
|
|
MsgDev(D_NOTE, "Unpacked fields: len %d, comp len %d\n", len, complen );
|
|
s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block
|
|
VFS_Unpack((char *)(((int *)infielddefs)+1), complen, &s, len );
|
|
infielddefs = (ddef_t *)s;
|
|
filesize += len - complen - sizeof(int); //merge filesize
|
|
}
|
|
|
|
if( vm.prog->progs->flags & COMP_FUNCTIONS )
|
|
{
|
|
len = sizeof(dfunction_t) * vm.prog->progs->numfunctions;
|
|
complen = LittleLong(*(int*)dfunctions);
|
|
|
|
MsgDev(D_NOTE, "Unpacked functions: len %d, comp len %d\n", len, complen );
|
|
s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block
|
|
VFS_Unpack((char *)(((int *)dfunctions)+1), complen, &s, len );
|
|
dfunctions = (dfunction_t *)s;
|
|
filesize += len - complen - sizeof(int); //merge filesize
|
|
}
|
|
|
|
if( vm.prog->progs->flags & COMP_STRINGS )
|
|
{
|
|
len = sizeof(char) * vm.prog->progs->numstrings;
|
|
complen = LittleLong(*(int*)vm.prog->strings);
|
|
|
|
MsgDev(D_NOTE, "Unpacked strings: count %d, len %d, comp len %d\n", vm.prog->progs->numstrings, len, complen );
|
|
s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block
|
|
VFS_Unpack((char *)(((int *)vm.prog->strings)+1), complen, &s, len );
|
|
vm.prog->strings = (char *)s;
|
|
|
|
vm.prog->progs->ofs_strings += 4;
|
|
filesize += len - complen - sizeof(int); //merge filesize
|
|
}
|
|
|
|
if( vm.prog->progs->flags & COMP_GLOBALS )
|
|
{
|
|
len = sizeof(float) * vm.prog->progs->numglobals;
|
|
complen = LittleLong(*(int*)vm.prog->globals.gp);
|
|
|
|
MsgDev(D_NOTE, "Unpacked globals: len %d, comp len %d\n", len, complen );
|
|
s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block
|
|
VFS_Unpack((char *)(((int *)vm.prog->globals.gp)+1), complen, &s, len );
|
|
vm.prog->globals.gp = (float *)s;
|
|
filesize += len - complen - sizeof(int); //merge filesize
|
|
}
|
|
|
|
if( vm.prog->linenums && vm.prog->progs->flags & COMP_LINENUMS )
|
|
{
|
|
len = sizeof(int) * vm.prog->progs->numstatements;
|
|
complen = LittleLong(*(int*)vm.prog->linenums);
|
|
|
|
MsgDev(D_NOTE, "Unpacked linenums: len %d, comp len %d\n", len, complen );
|
|
s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block
|
|
VFS_Unpack((char *)(((int *)vm.prog->linenums)+1), complen, &s, len );
|
|
vm.prog->linenums = (int *)s;
|
|
filesize += len - complen - sizeof(int); //merge filesize
|
|
}
|
|
|
|
if( vm.prog->types && vm.prog->progs->flags & COMP_TYPES )
|
|
{
|
|
len = sizeof(type_t) * vm.prog->progs->numtypes;
|
|
complen = LittleLong(*(int*)vm.prog->types);
|
|
|
|
MsgDev(D_NOTE, "Unpacked types: len %d, comp len %d\n", len, complen );
|
|
s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block
|
|
VFS_Unpack((char *)(((int *)vm.prog->types)+1), complen, &s, len );
|
|
vm.prog->types = (type_t *)s;
|
|
filesize += len - complen - sizeof(int); //merge filesize
|
|
}
|
|
|
|
vm.prog->stringssize = 0;
|
|
|
|
for( i = 0; vm.prog->stringssize < vm.prog->progs->numstrings; i++ )
|
|
{
|
|
if( vm.prog->progs->ofs_strings + vm.prog->stringssize >= (int)filesize )
|
|
PRVM_ERROR ("%s: %s strings go past end of file", PRVM_NAME, filename);
|
|
vm.prog->stringssize += (int)com.strlen(vm.prog->strings + vm.prog->stringssize) + 1;
|
|
}
|
|
|
|
vm.prog->numknownstrings = 0;
|
|
vm.prog->maxknownstrings = 0;
|
|
vm.prog->knownstrings = NULL;
|
|
vm.prog->knownstrings_freeable = NULL;
|
|
|
|
// we need to expand the fielddefs list to include all the engine fields,
|
|
// so allocate a new place for it ( + DPFIELDS )
|
|
vm.prog->fielddefs = (ddef_t *)Mem_Alloc(vm.prog->progs_mempool, (vm.prog->progs->numfielddefs) * sizeof(ddef_t));
|
|
vm.prog->statement_profile = (double *)Mem_Alloc(vm.prog->progs_mempool, vm.prog->progs->numstatements * sizeof(*vm.prog->statement_profile));
|
|
|
|
// byte swap the lumps
|
|
for (i = 0; i < vm.prog->progs->numstatements; i++)
|
|
{
|
|
vm.prog->statements[i].op = LittleLong(vm.prog->statements[i].op);
|
|
vm.prog->statements[i].a = LittleLong(vm.prog->statements[i].a);
|
|
vm.prog->statements[i].b = LittleLong(vm.prog->statements[i].b);
|
|
vm.prog->statements[i].c = LittleLong(vm.prog->statements[i].c);
|
|
}
|
|
vm.prog->functions = (mfunction_t *)Mem_Alloc(vm.prog->progs_mempool, sizeof(mfunction_t) * vm.prog->progs->numfunctions);
|
|
for (i = 0; i < vm.prog->progs->numfunctions; i++)
|
|
{
|
|
vm.prog->functions[i].first_statement = LittleLong (dfunctions[i].first_statement);
|
|
vm.prog->functions[i].parm_start = LittleLong (dfunctions[i].parm_start);
|
|
vm.prog->functions[i].s_name = LittleLong (dfunctions[i].s_name);
|
|
vm.prog->functions[i].s_file = LittleLong (dfunctions[i].s_file);
|
|
vm.prog->functions[i].numparms = LittleLong (dfunctions[i].numparms);
|
|
vm.prog->functions[i].locals = LittleLong (dfunctions[i].locals);
|
|
Mem_Copy(vm.prog->functions[i].parm_size, dfunctions[i].parm_size, sizeof(dfunctions[i].parm_size));
|
|
}
|
|
|
|
for (i = 0; i < vm.prog->progs->numglobaldefs; i++)
|
|
{
|
|
vm.prog->globaldefs[i].type = LittleLong (vm.prog->globaldefs[i].type);
|
|
vm.prog->globaldefs[i].ofs = LittleLong (vm.prog->globaldefs[i].ofs);
|
|
vm.prog->globaldefs[i].s_name = LittleLong (vm.prog->globaldefs[i].s_name);
|
|
}
|
|
|
|
// copy the progs fields to the new fields list
|
|
for (i = 0; i < vm.prog->progs->numfielddefs; i++)
|
|
{
|
|
vm.prog->fielddefs[i].type = LittleLong (infielddefs[i].type);
|
|
if (vm.prog->fielddefs[i].type & DEF_SAVEGLOBAL)
|
|
PRVM_ERROR ("PRVM_LoadProgs: vm.prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", PRVM_NAME);
|
|
vm.prog->fielddefs[i].ofs = LittleLong (infielddefs[i].ofs);
|
|
vm.prog->fielddefs[i].s_name = LittleLong (infielddefs[i].s_name);
|
|
}
|
|
|
|
for( i = 0; i < vm.prog->progs->numglobals; i++ )
|
|
((int *)vm.prog->globals.gp)[i] = LittleLong (((int *)vm.prog->globals.gp)[i]);
|
|
|
|
// moved edict_size calculation down here, below field adding code
|
|
// LordHavoc: this no longer includes the pr_edict_t header
|
|
vm.prog->edict_size = vm.prog->progs->entityfields * 4;
|
|
vm.prog->edictareasize = vm.prog->edict_size * vm.prog->limit_edicts;
|
|
|
|
// LordHavoc: bounds check anything static
|
|
for (i = 0, st = vm.prog->statements; i < vm.prog->progs->numstatements; i++, st++)
|
|
{
|
|
switch (st->op)
|
|
{
|
|
case OP_IF:
|
|
case OP_IFNOT:
|
|
if((dword)st->a >= vm.prog->progs->numglobals || st->b + i < 0 || st->b + i >= vm.prog->progs->numstatements)
|
|
PRVM_ERROR("PRVM_LoadProgs: out of bounds IF/IFNOT (statement %d) in %s", i, PRVM_NAME);
|
|
break;
|
|
case OP_IFNOTS:
|
|
// FIXME: make work
|
|
break;
|
|
case OP_GOTO:
|
|
if(st->a + i < 0 || st->a + i >= vm.prog->progs->numstatements)
|
|
PRVM_ERROR("PRVM_LoadProgs: out of bounds GOTO (statement %d) in %s", i, PRVM_NAME);
|
|
break;
|
|
// global global global
|
|
case OP_ADD_F:
|
|
case OP_ADD_V:
|
|
case OP_SUB_F:
|
|
case OP_SUB_V:
|
|
case OP_MUL_F:
|
|
case OP_MUL_V:
|
|
case OP_MUL_FV:
|
|
case OP_MUL_VF:
|
|
case OP_DIV_F:
|
|
case OP_BITAND:
|
|
case OP_BITOR:
|
|
case OP_BITSET:
|
|
case OP_BITSETP:
|
|
case OP_BITCLR:
|
|
case OP_BITCLRP:
|
|
case OP_GE:
|
|
case OP_LE:
|
|
case OP_GT:
|
|
case OP_LT:
|
|
case OP_AND:
|
|
case OP_OR:
|
|
case OP_EQ_F:
|
|
case OP_EQ_V:
|
|
case OP_EQ_S:
|
|
case OP_EQ_E:
|
|
case OP_EQ_FNC:
|
|
case OP_NE_F:
|
|
case OP_NE_V:
|
|
case OP_NE_S:
|
|
case OP_NE_E:
|
|
case OP_NE_FNC:
|
|
case OP_ADDRESS:
|
|
case OP_LOAD_F:
|
|
case OP_LOAD_FLD:
|
|
case OP_LOAD_ENT:
|
|
case OP_LOAD_S:
|
|
case OP_LOAD_FNC:
|
|
case OP_LOAD_V:
|
|
case OP_LOADA_F:
|
|
case OP_LOADA_V:
|
|
case OP_LOADA_S:
|
|
case OP_LOADA_ENT:
|
|
case OP_LOADA_FLD:
|
|
case OP_LOADA_FNC:
|
|
case OP_LOADA_I:
|
|
case OP_LE_I:
|
|
case OP_GE_I:
|
|
case OP_LT_I:
|
|
case OP_GT_I:
|
|
case OP_LE_IF:
|
|
case OP_GE_IF:
|
|
case OP_LT_IF:
|
|
case OP_GT_IF:
|
|
case OP_LE_FI:
|
|
case OP_GE_FI:
|
|
case OP_LT_FI:
|
|
case OP_GT_FI:
|
|
case OP_EQ_IF:
|
|
case OP_EQ_FI:
|
|
case OP_CONV_ITOF:
|
|
case OP_CONV_FTOI:
|
|
case OP_CP_ITOF:
|
|
case OP_CP_FTOI:
|
|
case OP_GLOBAL_ADD:
|
|
case OP_POINTER_ADD:
|
|
if((dword) st->a >= vm.prog->progs->numglobals || (dword) st->b >= vm.prog->progs->numglobals || (dword)st->c >= vm.prog->progs->numglobals)
|
|
PRVM_ERROR("PRVM_LoadProgs: out of bounds global index (statement %d)", i);
|
|
break;
|
|
// global none global
|
|
case OP_NOT_F:
|
|
case OP_NOT_V:
|
|
case OP_NOT_S:
|
|
case OP_NOT_FNC:
|
|
case OP_NOT_ENT:
|
|
if((dword) st->a >= vm.prog->progs->numglobals || (dword) st->c >= vm.prog->progs->numglobals)
|
|
PRVM_ERROR("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, PRVM_NAME);
|
|
break;
|
|
// 2 globals
|
|
case OP_STOREP_F:
|
|
case OP_STOREP_ENT:
|
|
case OP_STOREP_FLD:
|
|
case OP_STOREP_S:
|
|
case OP_STOREP_FNC:
|
|
case OP_STORE_F:
|
|
case OP_STORE_ENT:
|
|
case OP_STORE_FLD:
|
|
case OP_STORE_S:
|
|
case OP_STORE_FNC:
|
|
case OP_STATE:
|
|
case OP_STOREP_V:
|
|
case OP_STORE_V:
|
|
case OP_MULSTORE_F:
|
|
case OP_MULSTORE_V:
|
|
case OP_MULSTOREP_F:
|
|
case OP_MULSTOREP_V:
|
|
case OP_DIVSTORE_F:
|
|
case OP_DIVSTOREP_F:
|
|
case OP_ADDSTORE_F:
|
|
case OP_ADDSTORE_V:
|
|
case OP_ADDSTOREP_F:
|
|
case OP_ADDSTOREP_V:
|
|
case OP_SUBSTORE_F:
|
|
case OP_SUBSTORE_V:
|
|
case OP_SUBSTOREP_F:
|
|
case OP_SUBSTOREP_V:
|
|
if ((dword) st->a >= vm.prog->progs->numglobals || (dword) st->b >= vm.prog->progs->numglobals)
|
|
Host_Error("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, PRVM_NAME);
|
|
break;
|
|
// 1 global
|
|
case OP_CALL0:
|
|
case OP_CALL1:
|
|
case OP_CALL2:
|
|
case OP_CALL3:
|
|
case OP_CALL4:
|
|
case OP_CALL5:
|
|
case OP_CALL6:
|
|
case OP_CALL7:
|
|
case OP_CALL8:
|
|
case OP_CALL9:
|
|
case OP_DONE:
|
|
case OP_RETURN:
|
|
if ((dword) st->a >= vm.prog->progs->numglobals)
|
|
Host_Error("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, PRVM_NAME);
|
|
break;
|
|
default:
|
|
MsgDev( D_NOTE, "PRVM_LoadProgs: unknown opcode OP_%s at statement %d in %s\n", pr_opcodes[st->op].opname, i, PRVM_NAME);
|
|
break;
|
|
}
|
|
}
|
|
|
|
PRVM_Init_Exec();
|
|
vm.prog->loaded = true;
|
|
|
|
// set flags & ddef_ts in prog
|
|
vm.prog->flag = 0;
|
|
vm.prog->pev = PRVM_ED_FindGlobal( "pev" ); // critical stuff
|
|
|
|
if( PRVM_ED_FindGlobal("time") && PRVM_ED_FindGlobal("time")->type & ev_float )
|
|
vm.prog->time = &PRVM_G_FLOAT(PRVM_ED_FindGlobal("time")->ofs);
|
|
|
|
if(PRVM_ED_FindField("chain")) vm.prog->flag |= PRVM_FE_CHAIN;
|
|
if(PRVM_ED_FindField("classname")) vm.prog->flag |= PRVM_FE_CLASSNAME;
|
|
if(PRVM_ED_FindField("nextthink") && PRVM_ED_FindField ("frame") && PRVM_ED_FindField ("think") && vm.prog->flag && vm.prog->pev )
|
|
vm.prog->flag |= PRVM_OP_STATE;
|
|
if(PRVM_ED_FindField ("nextthink") && vm.prog->flag && vm.prog->pev )
|
|
vm.prog->flag |= PRVM_OP_THINKTIME;
|
|
PRVM_GCALL(init_cmd)();
|
|
|
|
// init mempools
|
|
PRVM_MEM_Alloc();
|
|
}
|
|
|
|
|
|
void PRVM_Fields_f (void)
|
|
{
|
|
int i, j, ednum, used, usedamount;
|
|
int *counts;
|
|
char tempstring[MAX_MSGLEN], tempstring2[260];
|
|
const char *name;
|
|
pr_edict_t *ed;
|
|
ddef_t *d;
|
|
int *v;
|
|
|
|
// TODO
|
|
/*
|
|
if (!sv.active)
|
|
{
|
|
Msg("no progs loaded\n");
|
|
return;
|
|
}
|
|
*/
|
|
|
|
if(Cmd_Argc() != 2)
|
|
{
|
|
Msg("prvm_fields <program name>\n");
|
|
return;
|
|
}
|
|
|
|
|
|
if(!PRVM_SetProgFromString(Cmd_Argv(1)))
|
|
return;
|
|
|
|
counts = (int *)Qalloc(vm.prog->progs->numfielddefs * sizeof(int));
|
|
for (ednum = 0; ednum < vm.prog->max_edicts; ednum++)
|
|
{
|
|
ed = PRVM_EDICT_NUM(ednum);
|
|
if (ed->priv.ed->free)
|
|
continue;
|
|
for (i = 1;i < vm.prog->progs->numfielddefs;i++)
|
|
{
|
|
d = &vm.prog->fielddefs[i];
|
|
name = PRVM_GetString(d->s_name);
|
|
if (name[com.strlen(name)-2] == '_')
|
|
continue; // skip _x, _y, _z vars
|
|
v = (int *)((char *)ed->progs.vp + d->ofs*4);
|
|
// if the value is still all 0, skip the field
|
|
for (j = 0;j < prvm_type_size[d->type & ~DEF_SAVEGLOBAL];j++)
|
|
{
|
|
if (v[j])
|
|
{
|
|
counts[i]++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
used = 0;
|
|
usedamount = 0;
|
|
tempstring[0] = 0;
|
|
for (i = 0;i < vm.prog->progs->numfielddefs;i++)
|
|
{
|
|
d = &vm.prog->fielddefs[i];
|
|
name = PRVM_GetString(d->s_name);
|
|
if (name[com.strlen(name)-2] == '_')
|
|
continue; // skip _x, _y, _z vars
|
|
switch(d->type & ~DEF_SAVEGLOBAL)
|
|
{
|
|
case ev_string:
|
|
com.strncat(tempstring, "string ", sizeof(tempstring));
|
|
break;
|
|
case ev_entity:
|
|
com.strncat(tempstring, "entity ", sizeof(tempstring));
|
|
break;
|
|
case ev_function:
|
|
com.strncat(tempstring, "function ", sizeof(tempstring));
|
|
break;
|
|
case ev_field:
|
|
com.strncat(tempstring, "field ", sizeof(tempstring));
|
|
break;
|
|
case ev_void:
|
|
com.strncat(tempstring, "void ", sizeof(tempstring));
|
|
break;
|
|
case ev_float:
|
|
com.strncat(tempstring, "float ", sizeof(tempstring));
|
|
break;
|
|
case ev_vector:
|
|
com.strncat(tempstring, "vector ", sizeof(tempstring));
|
|
break;
|
|
case ev_pointer:
|
|
com.strncat(tempstring, "pointer ", sizeof(tempstring));
|
|
break;
|
|
default:
|
|
com.sprintf (tempstring2, "bad type %i ", d->type & ~DEF_SAVEGLOBAL);
|
|
com.strncat(tempstring, tempstring2, sizeof(tempstring));
|
|
break;
|
|
}
|
|
if (com.strlen(name) > sizeof(tempstring2)-4)
|
|
{
|
|
Mem_Copy (tempstring2, name, sizeof(tempstring2)-4);
|
|
tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
|
|
tempstring2[sizeof(tempstring2)-1] = 0;
|
|
name = tempstring2;
|
|
}
|
|
com.strncat(tempstring, name, sizeof(tempstring));
|
|
for (j = (int)com.strlen(name);j < 25;j++)
|
|
com.strncat(tempstring, " ", sizeof(tempstring));
|
|
com.sprintf(tempstring2, "%5d", counts[i]);
|
|
com.strncat(tempstring, tempstring2, sizeof(tempstring));
|
|
com.strncat(tempstring, "\n", sizeof(tempstring));
|
|
if (com.strlen(tempstring) >= sizeof(tempstring)/2)
|
|
{
|
|
Msg(tempstring);
|
|
tempstring[0] = 0;
|
|
}
|
|
if (counts[i])
|
|
{
|
|
used++;
|
|
usedamount += prvm_type_size[d->type & ~DEF_SAVEGLOBAL];
|
|
}
|
|
}
|
|
Mem_Free(counts);
|
|
Msg("%s: %i entity fields (%i in use), totalling %i bytes per edict (%i in use), %i edicts allocated, %i bytes total spent on edict fields (%i needed)\n", PRVM_NAME, vm.prog->progs->entityfields, used, vm.prog->progs->entityfields * 4, usedamount * 4, vm.prog->max_edicts, vm.prog->progs->entityfields * 4 * vm.prog->max_edicts, usedamount * 4 * vm.prog->max_edicts);
|
|
|
|
vm.prog = NULL;
|
|
}
|
|
|
|
void PRVM_Globals_f (void)
|
|
{
|
|
int i;
|
|
// TODO
|
|
/*if (!sv.active)
|
|
{
|
|
Msg("no progs loaded\n");
|
|
return;
|
|
}*/
|
|
if(Cmd_Argc () != 2)
|
|
{
|
|
Msg("prvm_globals <program name>\n");
|
|
return;
|
|
}
|
|
|
|
|
|
if(!PRVM_SetProgFromString (Cmd_Argv (1)))
|
|
return;
|
|
|
|
Msg("%s :", PRVM_NAME);
|
|
|
|
for (i = 0;i < vm.prog->progs->numglobaldefs;i++)
|
|
Msg("%s\n", PRVM_GetString(vm.prog->globaldefs[i].s_name));
|
|
Msg("%i global variables, totalling %i bytes\n", vm.prog->progs->numglobals, vm.prog->progs->numglobals * 4);
|
|
|
|
vm.prog = NULL;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
PRVM_Global
|
|
===============
|
|
*/
|
|
void PRVM_Global_f(void)
|
|
{
|
|
ddef_t *global;
|
|
if( Cmd_Argc() != 3 )
|
|
{
|
|
Msg( "prvm_global <program name> <global name>\n" );
|
|
return;
|
|
}
|
|
|
|
|
|
if( !PRVM_SetProgFromString( Cmd_Argv(1) ) )
|
|
return;
|
|
|
|
global = PRVM_ED_FindGlobal( Cmd_Argv(2) );
|
|
if( !global )
|
|
Msg( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) );
|
|
else
|
|
Msg( "%s: %s\n", Cmd_Argv(2), PRVM_ValueString( (etype_t)global->type, (prvm_eval_t *) &vm.prog->globals.gp[ global->ofs ] ) );
|
|
vm.prog = NULL;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
PRVM_GlobalSet
|
|
===============
|
|
*/
|
|
void PRVM_GlobalSet_f(void)
|
|
{
|
|
ddef_t *global;
|
|
if( Cmd_Argc() != 4 ) {
|
|
Msg( "prvm_globalset <program name> <global name> <value>\n" );
|
|
return;
|
|
}
|
|
|
|
|
|
if( !PRVM_SetProgFromString( Cmd_Argv(1) ) )
|
|
return;
|
|
|
|
global = PRVM_ED_FindGlobal( Cmd_Argv(2) );
|
|
if( !global )
|
|
Msg( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) );
|
|
else
|
|
PRVM_ED_ParseEpair( NULL, global, Cmd_Argv(3) );
|
|
vm.prog = NULL;
|
|
}
|
|
|
|
// LordHavoc: changed this to NOT use a return statement, so that it can be used in functions that must return a value
|
|
void VM_Warning( const char *fmt, ... )
|
|
{
|
|
va_list argptr;
|
|
static char msg[MAX_MSGLEN];
|
|
|
|
va_start( argptr, fmt );
|
|
com.vsnprintf( msg, sizeof(msg), fmt, argptr );
|
|
va_end( argptr );
|
|
|
|
Msg( msg );
|
|
// TODO: either add a cvar/cmd to control the state dumping or replace some of the calls with Msgf [9/13/2006 Black]
|
|
//PRVM_PrintState();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
VM_error
|
|
|
|
Abort the server with a game error
|
|
===============
|
|
*/
|
|
void VM_Error( const char *fmt, ... )
|
|
{
|
|
char msg[1024];
|
|
va_list argptr;
|
|
|
|
va_start (argptr, fmt);
|
|
com.vsprintf (msg, fmt, argptr);
|
|
va_end (argptr);
|
|
|
|
Host_Error("Prvm error: %s", msg);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
PRVM_InitProg
|
|
===============
|
|
*/
|
|
void PRVM_InitProg( int prognr )
|
|
{
|
|
if(prognr < 0 || prognr >= PRVM_MAXPROGS)
|
|
Host_Error("PRVM_InitProg: Invalid program number %i",prognr);
|
|
|
|
vm.prog = &prog_list[prognr];
|
|
|
|
if(vm.prog->loaded) PRVM_ResetProg();
|
|
|
|
Mem_Set(vm.prog, 0, sizeof(prvm_prog_t));
|
|
|
|
vm.prog->time = &vm.prog->_time;
|
|
vm.prog->error_cmd = VM_Error;
|
|
}
|
|
|
|
int PRVM_GetProgNr()
|
|
{
|
|
return vm.prog - prog_list;
|
|
}
|
|
|
|
void *_PRVM_Alloc(size_t buffersize, const char *filename, int fileline)
|
|
{
|
|
return com.malloc(vm.prog->progs_mempool, buffersize, filename, fileline);
|
|
}
|
|
|
|
void _PRVM_Free(void *buffer, const char *filename, int fileline)
|
|
{
|
|
com.free(buffer, filename, fileline);
|
|
}
|
|
|
|
void _PRVM_FreeAll(const char *filename, int fileline)
|
|
{
|
|
vm.prog->progs = NULL;
|
|
vm.prog->fielddefs = NULL;
|
|
vm.prog->functions = NULL;
|
|
com.clearpool( vm.prog->progs_mempool, filename, fileline);
|
|
}
|
|
|
|
// LordHavoc: turned PRVM_EDICT_NUM into a #define for speed reasons
|
|
pr_edict_t *PRVM_EDICT_NUM_ERROR(int n, char *filename, int fileline)
|
|
{
|
|
PRVM_ERROR ("PRVM_EDICT_NUM: %s: bad number %i (called at %s:%i)", PRVM_NAME, n, filename, fileline);
|
|
return NULL;
|
|
}
|
|
|
|
const char *PRVM_GetString(int num)
|
|
{
|
|
if( num >= 0 )
|
|
{
|
|
if( num < vm.prog->stringssize ) return vm.prog->strings + num;
|
|
else if( num <= vm.prog->stringssize + vm_tempstringsbuf.maxsize)
|
|
{
|
|
num -= vm.prog->stringssize;
|
|
if( num < vm_tempstringsbuf.cursize )
|
|
return (char *)vm_tempstringsbuf.data + num;
|
|
else
|
|
{
|
|
VM_Warning("PRVM_GetString: Invalid temp-string offset (%i >= %i vm_tempstringsbuf.cursize)\n", num, vm_tempstringsbuf.cursize);
|
|
return "";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VM_Warning("PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, vm.prog->stringssize);
|
|
return "";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
num = -1 - num;
|
|
if (num < vm.prog->numknownstrings)
|
|
{
|
|
if (!vm.prog->knownstrings[num])
|
|
VM_Warning("PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num);
|
|
return vm.prog->knownstrings[num];
|
|
}
|
|
else
|
|
{
|
|
VM_Warning("PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, vm.prog->numknownstrings);
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
|
|
int PRVM_SetEngineString( const char *s )
|
|
{
|
|
int i;
|
|
if (!s)
|
|
return 0;
|
|
if (s >= vm.prog->strings && s <= vm.prog->strings + vm.prog->stringssize)
|
|
PRVM_ERROR("PRVM_SetEngineString: s in vm.prog->strings area");
|
|
// if it's in the tempstrings area, use a reserved range
|
|
// (otherwise we'd get millions of useless string offsets cluttering the database)
|
|
if (s >= (char *)vm_tempstringsbuf.data && s < (char *)vm_tempstringsbuf.data + vm_tempstringsbuf.maxsize)
|
|
return vm.prog->stringssize + (s - (char *)vm_tempstringsbuf.data);
|
|
// see if it's a known string address
|
|
for (i = 0;i < vm.prog->numknownstrings;i++)
|
|
if (vm.prog->knownstrings[i] == s)
|
|
return -1 - i;
|
|
// new unknown engine string
|
|
MsgDev(D_STRING, "new engine string %p\n", s );
|
|
for (i = vm.prog->firstfreeknownstring;i < vm.prog->numknownstrings;i++)
|
|
if (!vm.prog->knownstrings[i])
|
|
break;
|
|
if (i >= vm.prog->numknownstrings)
|
|
{
|
|
if (i >= vm.prog->maxknownstrings)
|
|
{
|
|
const char **oldstrings = vm.prog->knownstrings;
|
|
const byte *oldstrings_freeable = vm.prog->knownstrings_freeable;
|
|
vm.prog->maxknownstrings += 128;
|
|
vm.prog->knownstrings = (const char **)PRVM_Alloc(vm.prog->maxknownstrings * sizeof(char *));
|
|
vm.prog->knownstrings_freeable = (byte *)PRVM_Alloc(vm.prog->maxknownstrings * sizeof(byte));
|
|
if (vm.prog->numknownstrings)
|
|
{
|
|
Mem_Copy((char **)vm.prog->knownstrings, oldstrings, vm.prog->numknownstrings * sizeof(char *));
|
|
Mem_Copy((char **)vm.prog->knownstrings_freeable, oldstrings_freeable, vm.prog->numknownstrings * sizeof(byte));
|
|
}
|
|
}
|
|
vm.prog->numknownstrings++;
|
|
}
|
|
vm.prog->firstfreeknownstring = i + 1;
|
|
vm.prog->knownstrings[i] = s;
|
|
return -1 - i;
|
|
}
|
|
|
|
// temp string handling
|
|
|
|
// all tempstrings go into this buffer consecutively, and it is reset
|
|
// whenever PRVM_ExecuteProgram returns to the engine
|
|
// (technically each PRVM_ExecuteProgram call saves the cursize value and
|
|
// restores it on return, so multiple recursive calls can share the same
|
|
// buffer)
|
|
// the buffer size is automatically grown as needed
|
|
|
|
int PRVM_SetTempString( const char *s )
|
|
{
|
|
int size;
|
|
char *t;
|
|
|
|
if (!s) return 0;
|
|
|
|
size = (int)com.strlen(s) + 1;
|
|
MsgDev( D_STRING, "PRVM_SetTempString: cursize %i, size %i\n", vm_tempstringsbuf.cursize, size);
|
|
if (vm_tempstringsbuf.maxsize < vm_tempstringsbuf.cursize + size)
|
|
{
|
|
size_t old_maxsize = vm_tempstringsbuf.maxsize;
|
|
if( vm_tempstringsbuf.cursize + size >= 1<<28 )
|
|
PRVM_ERROR("PRVM_SetTempString: ran out of tempstring memory! (refusing to grow tempstring buffer over 256MB, cursize %i, size %i)\n", vm_tempstringsbuf.cursize, size);
|
|
vm_tempstringsbuf.maxsize = max( vm_tempstringsbuf.maxsize, 65536 );
|
|
while( vm_tempstringsbuf.maxsize < vm_tempstringsbuf.cursize + size )
|
|
vm_tempstringsbuf.maxsize *= 2;
|
|
if (vm_tempstringsbuf.maxsize != old_maxsize || vm_tempstringsbuf.data == NULL)
|
|
{
|
|
MsgDev( D_NOTE, "PRVM_SetTempString: enlarging tempstrings buffer (%iKB -> %iKB)\n", old_maxsize/1024, vm_tempstringsbuf.maxsize/1024);
|
|
vm_tempstringsbuf.data = Mem_Realloc( vm.prog->progs_mempool, vm_tempstringsbuf.data, vm_tempstringsbuf.maxsize );
|
|
}
|
|
}
|
|
t = (char *)vm_tempstringsbuf.data + vm_tempstringsbuf.cursize;
|
|
Mem_Copy( t, s, size );
|
|
vm_tempstringsbuf.cursize += size;
|
|
return PRVM_SetEngineString( t );
|
|
}
|
|
|
|
int PRVM_AllocString(size_t bufferlength, char **pointer)
|
|
{
|
|
int i;
|
|
if (!bufferlength)
|
|
return 0;
|
|
for (i = vm.prog->firstfreeknownstring;i < vm.prog->numknownstrings;i++)
|
|
if (!vm.prog->knownstrings[i])
|
|
break;
|
|
if (i >= vm.prog->numknownstrings)
|
|
{
|
|
if (i >= vm.prog->maxknownstrings)
|
|
{
|
|
const char **oldstrings = vm.prog->knownstrings;
|
|
const byte *oldstrings_freeable = vm.prog->knownstrings_freeable;
|
|
vm.prog->maxknownstrings += 128;
|
|
vm.prog->knownstrings = (const char **)PRVM_Alloc(vm.prog->maxknownstrings * sizeof(char *));
|
|
vm.prog->knownstrings_freeable = (byte *)PRVM_Alloc(vm.prog->maxknownstrings * sizeof(byte));
|
|
if (vm.prog->numknownstrings)
|
|
{
|
|
Mem_Copy((char **)vm.prog->knownstrings, oldstrings, vm.prog->numknownstrings * sizeof(char *));
|
|
Mem_Copy((char **)vm.prog->knownstrings_freeable, oldstrings_freeable, vm.prog->numknownstrings * sizeof(byte));
|
|
}
|
|
}
|
|
vm.prog->numknownstrings++;
|
|
}
|
|
vm.prog->firstfreeknownstring = i + 1;
|
|
vm.prog->knownstrings[i] = (char *)PRVM_Alloc(bufferlength);
|
|
vm.prog->knownstrings_freeable[i] = true;
|
|
if (pointer)
|
|
*pointer = (char *)(vm.prog->knownstrings[i]);
|
|
return -1 - i;
|
|
}
|
|
|
|
void PRVM_FreeString(int num)
|
|
{
|
|
if (num == 0)
|
|
PRVM_ERROR("PRVM_FreeString: attempt to free a NULL string");
|
|
else if (num >= 0 && num < vm.prog->stringssize)
|
|
PRVM_ERROR("PRVM_FreeString: attempt to free a constant string");
|
|
else if (num < 0 && num >= -vm.prog->numknownstrings)
|
|
{
|
|
num = -1 - num;
|
|
if (!vm.prog->knownstrings[num])
|
|
PRVM_ERROR("PRVM_FreeString: attempt to free a non-existent or already freed string");
|
|
if (!vm.prog->knownstrings[num])
|
|
PRVM_ERROR("PRVM_FreeString: attempt to free a string owned by the engine");
|
|
PRVM_Free((char *)vm.prog->knownstrings[num]);
|
|
vm.prog->knownstrings[num] = NULL;
|
|
vm.prog->knownstrings_freeable[num] = false;
|
|
vm.prog->firstfreeknownstring = min(vm.prog->firstfreeknownstring, num);
|
|
}
|
|
else
|
|
PRVM_ERROR("PRVM_FreeString: invalid string offset %i", num);
|
|
}
|
|
|