mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-23 12:52:46 +00:00
d5454faeb7
Legacy progs do not have the extended defs data (and usually won't have anything more complicated than a vector), so use the basic type size for the def size. Fixes broken edict prints.
517 lines
14 KiB
C
517 lines
14 KiB
C
/*
|
|
pr_edict.c
|
|
|
|
entity dictionary
|
|
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
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, write to:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
#endif
|
|
|
|
#include "QF/crc.h"
|
|
#include "QF/cvar.h"
|
|
#include "QF/dstring.h"
|
|
#include "QF/hash.h"
|
|
#include "QF/mathlib.h"
|
|
#include "QF/progs.h"
|
|
#include "QF/qendian.h"
|
|
#include "QF/quakefs.h"
|
|
#include "QF/sys.h"
|
|
#include "QF/zone.h"
|
|
|
|
#include "compat.h"
|
|
|
|
VISIBLE cvar_t *pr_boundscheck;
|
|
cvar_t *pr_deadbeef_ents;
|
|
cvar_t *pr_deadbeef_locals;
|
|
cvar_t *pr_faultchecks;
|
|
|
|
static const char *
|
|
function_get_key (const void *f, void *_pr)
|
|
{
|
|
progs_t *pr = (progs_t*)_pr;
|
|
dfunction_t *func = (dfunction_t*)f;
|
|
return PR_GetString (pr, func->s_name);
|
|
}
|
|
|
|
static const char *
|
|
var_get_key (const void *d, void *_pr)
|
|
{
|
|
progs_t *pr = (progs_t*)_pr;
|
|
pr_def_t *def = (pr_def_t*)d;
|
|
return PR_GetString (pr, def->name);
|
|
}
|
|
|
|
static void
|
|
file_error (progs_t *pr, const char *path)
|
|
{
|
|
Sys_Printf ("failed to load %s\n", path);
|
|
}
|
|
|
|
static void *
|
|
load_file (progs_t *pr, const char *path, off_t *size)
|
|
{
|
|
void *data = QFS_LoadHunkFile (QFS_FOpenFile (path));
|
|
*size = qfs_filesize;
|
|
return data;
|
|
}
|
|
|
|
static void *
|
|
allocate_progs_mem (progs_t *pr, int size)
|
|
{
|
|
return Hunk_AllocName (size, pr->progs_name);
|
|
}
|
|
|
|
static void
|
|
free_progs_mem (progs_t *pr, void *mem)
|
|
{
|
|
}
|
|
|
|
static int
|
|
align_size (int size)
|
|
{
|
|
// round off to next highest whole word address (esp for Alpha)
|
|
// this ensures that pointers in the engine data area are always
|
|
// properly aligned
|
|
size += sizeof (void*) - 1;
|
|
size &= ~(sizeof (void*) - 1);
|
|
return size;
|
|
}
|
|
|
|
VISIBLE void
|
|
PR_LoadProgsFile (progs_t *pr, QFile *file, int size)
|
|
{
|
|
size_t i;
|
|
int mem_size;
|
|
int offset_tweak;
|
|
dprograms_t progs;
|
|
byte *base;
|
|
byte *heap;
|
|
pr_def_t *xdefs_def = 0;
|
|
ddef_t *global_ddefs;
|
|
ddef_t *field_ddefs;
|
|
|
|
if (!pr->file_error)
|
|
pr->file_error = file_error;
|
|
if (!pr->load_file)
|
|
pr->load_file = load_file;
|
|
if (!pr->allocate_progs_mem)
|
|
pr->allocate_progs_mem = allocate_progs_mem;
|
|
if (!pr->free_progs_mem)
|
|
pr->free_progs_mem = free_progs_mem;
|
|
|
|
PR_Resources_Clear (pr);
|
|
if (pr->progs)
|
|
pr->free_progs_mem (pr, pr->progs);
|
|
pr->progs = 0;
|
|
|
|
if (Qread (file, &progs, sizeof (progs)) != sizeof (progs))
|
|
PR_Error (pr, "error reading header");
|
|
|
|
// store prog crc
|
|
pr->crc = CRC_Block ((byte*)&progs, sizeof (progs));
|
|
|
|
pr->denorm_found = 0;
|
|
|
|
// byte swap the header
|
|
for (i = 0; i < sizeof (progs) / 4; i++)
|
|
((int *) &progs)[i] = LittleLong (((int *) &progs)[i]);
|
|
|
|
if (progs.version != PROG_VERSION
|
|
&& progs.version != PROG_ID_VERSION) {
|
|
if (progs.version < 0x00fff000) {
|
|
PR_Error (pr, "%s has unrecognised version number (%u)",
|
|
pr->progs_name, progs.version);
|
|
} else {
|
|
PR_Error (pr,
|
|
"%s has unrecognised version number (%02x.%03x.%03x)"
|
|
" [%02x.%03x.%03x expected]",
|
|
pr->progs_name,
|
|
progs.version >> 24,
|
|
(progs.version >> 12) & 0xfff,
|
|
progs.version & 0xfff,
|
|
PROG_VERSION >> 24,
|
|
(PROG_VERSION >> 12) & 0xfff,
|
|
PROG_VERSION & 0xfff);
|
|
}
|
|
}
|
|
|
|
// Some compilers (eg, FTE) put extra data between the header and the
|
|
// strings section. What's worse, they de-align the data.
|
|
offset_tweak = progs.ofs_strings % sizeof (pr_int_t);
|
|
offset_tweak = (sizeof (pr_int_t) - offset_tweak) % sizeof (pr_int_t);
|
|
|
|
// size of progs themselves
|
|
pr->progs_size = size + offset_tweak;
|
|
Sys_MaskPrintf (SYS_dev, "Programs occupy %iK.\n", size / 1024);
|
|
|
|
pr->progs_size = align_size (pr->progs_size);
|
|
pr->zone_size = align_size (pr->zone_size);
|
|
pr->stack_size = align_size (pr->stack_size);
|
|
|
|
// size of edict asked for by progs, but at least 1
|
|
pr->pr_edict_size = max (1, progs.entityfields);
|
|
// round off to next highest multiple of 4 words
|
|
// this ensures that progs that try to align vectors and quaternions
|
|
// get what they want
|
|
pr->pr_edict_size += (4 - 1);
|
|
pr->pr_edict_size &= ~(4 - 1);
|
|
pr->pr_edict_area_size = pr->max_edicts * pr->pr_edict_size;
|
|
|
|
mem_size = pr->pr_edict_area_size * sizeof (pr_type_t);
|
|
mem_size += pr->progs_size + pr->zone_size + pr->stack_size;
|
|
|
|
// +1 for a nul terminator
|
|
pr->progs = pr->allocate_progs_mem (pr, mem_size + 1);
|
|
if (!pr->progs)
|
|
return;
|
|
// Place a nul at the end of progs memory to ensure any unterminated
|
|
// strings within progs memory don't run off the end.
|
|
((byte *) pr->progs)[mem_size] = 0;
|
|
|
|
memcpy (pr->progs, &progs, sizeof (progs));
|
|
base = (byte *) (pr->progs + 1) + offset_tweak;
|
|
Qread (file, base, size - sizeof (progs));
|
|
CRC_ProcessBlock (base, &pr->crc, size - sizeof (progs));
|
|
|
|
pr->pr_edict_area = (pr_type_t *)((byte *) pr->progs + pr->progs_size);
|
|
|
|
base -= sizeof (progs); // offsets are from file start
|
|
heap = (byte *) &pr->pr_edict_area[pr->pr_edict_area_size];
|
|
|
|
pr->zone = 0;
|
|
if (pr->zone_size) {
|
|
//FIXME zone_size needs to be at least as big as memzone_t, but
|
|
//memzone_t is opaque so its size is unknown
|
|
pr->zone = (memzone_t *) heap;
|
|
}
|
|
|
|
pr->pr_functions = (dfunction_t *) (base + pr->progs->ofs_functions);
|
|
pr->pr_strings = (char *) base + pr->progs->ofs_strings;
|
|
pr->pr_stringsize = (heap - base) + pr->zone_size;
|
|
global_ddefs = (ddef_t *) (base + pr->progs->ofs_globaldefs);
|
|
field_ddefs = (ddef_t *) (base + pr->progs->ofs_fielddefs);
|
|
pr->pr_statements = (dstatement_t *) (base + pr->progs->ofs_statements);
|
|
|
|
pr->pr_globals = (pr_type_t *) (base + pr->progs->ofs_globals);
|
|
pr->stack = (pr_type_t *) ((byte *) pr->zone + pr->zone_size);
|
|
pr->stack_bottom = pr->stack - pr->pr_globals;
|
|
pr->globals_size = (pr_type_t *) ((byte *) pr->stack + pr->stack_size)
|
|
- pr->pr_globals;
|
|
|
|
if (pr->zone) {
|
|
PR_Zone_Init (pr);
|
|
}
|
|
|
|
if (pr->function_hash) {
|
|
Hash_FlushTable (pr->function_hash);
|
|
} else {
|
|
pr->function_hash = Hash_NewTable (1021, function_get_key, 0, pr,
|
|
pr->hashlink_freelist);
|
|
}
|
|
if (pr->global_hash) {
|
|
Hash_FlushTable (pr->global_hash);
|
|
} else {
|
|
pr->global_hash = Hash_NewTable (1021, var_get_key, 0, pr,
|
|
pr->hashlink_freelist);
|
|
}
|
|
if (pr->field_hash) {
|
|
Hash_FlushTable (pr->field_hash);
|
|
} else {
|
|
pr->field_hash = Hash_NewTable (1021, var_get_key, 0, pr,
|
|
pr->hashlink_freelist);
|
|
}
|
|
|
|
// byte swap the lumps
|
|
for (i = 0; i < pr->progs->numstatements; i++) {
|
|
pr->pr_statements[i].op = LittleShort (pr->pr_statements[i].op);
|
|
pr->pr_statements[i].a = LittleShort (pr->pr_statements[i].a);
|
|
pr->pr_statements[i].b = LittleShort (pr->pr_statements[i].b);
|
|
pr->pr_statements[i].c = LittleShort (pr->pr_statements[i].c);
|
|
}
|
|
|
|
for (i = 0; i < (size_t) pr->progs->numfunctions; i++) {
|
|
pr->pr_functions[i].first_statement =
|
|
LittleLong (pr->pr_functions[i].first_statement);
|
|
pr->pr_functions[i].parm_start =
|
|
LittleLong (pr->pr_functions[i].parm_start);
|
|
pr->pr_functions[i].s_name = LittleLong (pr->pr_functions[i].s_name);
|
|
pr->pr_functions[i].s_file = LittleLong (pr->pr_functions[i].s_file);
|
|
pr->pr_functions[i].numparms =
|
|
LittleLong (pr->pr_functions[i].numparms);
|
|
pr->pr_functions[i].locals = LittleLong (pr->pr_functions[i].locals);
|
|
if (pr->pr_functions[i].s_name)
|
|
Hash_Add (pr->function_hash, &pr->pr_functions[i]);
|
|
}
|
|
|
|
if (pr->pr_globaldefs) {
|
|
free (pr->pr_globaldefs);
|
|
}
|
|
pr->pr_globaldefs = calloc (pr->progs->numglobaldefs, sizeof (pr_def_t));
|
|
|
|
for (i = 0; i < pr->progs->numglobaldefs; i++) {
|
|
pr_ushort_t safe_type = global_ddefs[i].type & ~DEF_SAVEGLOBAL;
|
|
global_ddefs[i].type = LittleShort (global_ddefs[i].type);
|
|
global_ddefs[i].ofs = LittleShort (global_ddefs[i].ofs);
|
|
global_ddefs[i].s_name = LittleLong (global_ddefs[i].s_name);
|
|
|
|
pr->pr_globaldefs[i].type = global_ddefs[i].type;
|
|
pr->pr_globaldefs[i].size = pr_type_size[safe_type];
|
|
pr->pr_globaldefs[i].ofs = global_ddefs[i].ofs;
|
|
pr->pr_globaldefs[i].name = global_ddefs[i].s_name;
|
|
Hash_Add (pr->global_hash, &pr->pr_globaldefs[i]);
|
|
}
|
|
|
|
if (pr->pr_fielddefs) {
|
|
free (pr->pr_fielddefs);
|
|
}
|
|
pr->pr_fielddefs = calloc (pr->progs->numfielddefs, sizeof (pr_def_t));
|
|
for (i = 0; i < pr->progs->numfielddefs; i++) {
|
|
field_ddefs[i].type = LittleShort (field_ddefs[i].type);
|
|
if (field_ddefs[i].type & DEF_SAVEGLOBAL)
|
|
PR_Error (pr, "PR_LoadProgs: DEF_SAVEGLOBAL on field def %zd", i);
|
|
field_ddefs[i].ofs = LittleShort (field_ddefs[i].ofs);
|
|
field_ddefs[i].s_name = LittleLong (field_ddefs[i].s_name);
|
|
|
|
pr->pr_fielddefs[i].type = field_ddefs[i].type;
|
|
pr->pr_fielddefs[i].ofs = field_ddefs[i].ofs;
|
|
pr->pr_fielddefs[i].name = field_ddefs[i].s_name;
|
|
Hash_Add (pr->field_hash, &pr->pr_fielddefs[i]);
|
|
}
|
|
|
|
for (i = 0; i < pr->progs->numglobals; i++)
|
|
((int *) pr->pr_globals)[i] = LittleLong (((int *) pr->pr_globals)[i]);
|
|
|
|
xdefs_def = PR_FindGlobal (pr, ".xdefs");
|
|
if (xdefs_def) {
|
|
pr_xdefs_t *xdefs = &G_STRUCT (pr, pr_xdefs_t, xdefs_def->ofs);
|
|
xdef_t *xdef = &G_STRUCT (pr, xdef_t, xdefs->xdefs);
|
|
pr_def_t *def;
|
|
for (def = pr->pr_globaldefs, i = 0; i < pr->progs->numglobaldefs;
|
|
i++, xdef++, def++) {
|
|
def->ofs = xdef->ofs;
|
|
def->type_encoding = xdef->type;
|
|
}
|
|
for (def = pr->pr_fielddefs, i = 0; i < pr->progs->numfielddefs;
|
|
i++, xdef++, def++) {
|
|
def->ofs = xdef->ofs;
|
|
def->type_encoding = xdef->type;
|
|
}
|
|
} else {
|
|
pr_def_t *def;
|
|
for (def = pr->pr_globaldefs, i = 0; i < pr->progs->numglobaldefs;
|
|
i++, def++) {
|
|
def->size = pr_type_size[def->type];
|
|
}
|
|
for (def = pr->pr_fielddefs, i = 0; i < pr->progs->numfielddefs;
|
|
i++, def++) {
|
|
def->size = pr_type_size[def->type];
|
|
}
|
|
}
|
|
pr->pr_trace = 0;
|
|
pr->pr_trace_depth = 0;
|
|
pr->pr_xfunction = 0;
|
|
pr->pr_xstatement = 0;
|
|
pr->pr_depth = 0;
|
|
pr->localstack_used = 0;
|
|
pr->pr_argc = 0;
|
|
}
|
|
|
|
VISIBLE void
|
|
PR_AddLoadFunc (progs_t *pr, int (*func)(progs_t *))
|
|
{
|
|
if (pr->num_load_funcs == pr->max_load_funcs) {
|
|
int n;
|
|
pr->max_load_funcs += 8;
|
|
n = pr->max_load_funcs;
|
|
pr->load_funcs = realloc (pr->load_funcs,
|
|
n * sizeof (pr_load_func_t *));
|
|
SYS_CHECKMEM (pr->load_funcs);
|
|
}
|
|
pr->load_funcs[pr->num_load_funcs++] = func;
|
|
}
|
|
|
|
VISIBLE void
|
|
PR_AddLoadFinishFunc (progs_t *pr, int (*func)(progs_t *))
|
|
{
|
|
if (pr->num_load_finish_funcs == pr->max_load_finish_funcs) {
|
|
int n;
|
|
pr->max_load_finish_funcs += 8;
|
|
n = pr->max_load_finish_funcs;
|
|
pr->load_finish_funcs = realloc (pr->load_finish_funcs,
|
|
n * sizeof (pr_load_func_t *));
|
|
SYS_CHECKMEM (pr->load_finish_funcs);
|
|
}
|
|
pr->load_finish_funcs[pr->num_load_finish_funcs++] = func;
|
|
}
|
|
|
|
static int
|
|
pr_run_ctors (progs_t *pr)
|
|
{
|
|
pr_uint_t fnum;
|
|
dfunction_t *func;
|
|
|
|
for (fnum = 0; fnum < pr->progs->numfunctions; fnum++) {
|
|
func = pr->pr_functions + fnum;
|
|
if (strequal (PR_GetString (pr, func->s_name), ".ctor"))
|
|
PR_ExecuteProgram (pr, fnum);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int (*load_funcs_1[])(progs_t *) = {
|
|
PR_LoadStrings,
|
|
PR_RelocateBuiltins,
|
|
PR_LoadDebug,
|
|
0,
|
|
};
|
|
|
|
static int (*load_funcs_2[])(progs_t *) = {
|
|
PR_ResolveGlobals,
|
|
PR_Check_Opcodes,
|
|
0,
|
|
};
|
|
|
|
static int
|
|
run_load_funcs (progs_t *pr, int (**load_funcs)(progs_t *))
|
|
{
|
|
int (**lf)(progs_t *);
|
|
|
|
for (lf = load_funcs; *lf; lf++)
|
|
if (!(*lf)(pr))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
VISIBLE int
|
|
PR_RunLoadFuncs (progs_t *pr)
|
|
{
|
|
int i;
|
|
|
|
memset (&pr->globals, 0, sizeof (pr->globals));
|
|
pr->fields.nextthink = -1;
|
|
pr->fields.frame = -1;
|
|
pr->fields.think = -1;
|
|
pr->fields.this = -1;
|
|
|
|
if (!run_load_funcs(pr, load_funcs_1))
|
|
return 0;
|
|
if (pr->resolve)
|
|
if (!pr->resolve (pr))
|
|
return 0;
|
|
if (!run_load_funcs(pr, load_funcs_2))
|
|
return 0;
|
|
|
|
for (i = 0; i < pr->num_load_funcs; i++)
|
|
if (!pr->load_funcs[i] (pr))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
VISIBLE int
|
|
PR_RunPostLoadFuncs (progs_t *pr)
|
|
{
|
|
if (!pr_run_ctors (pr))
|
|
return 0;
|
|
|
|
while (pr->num_load_finish_funcs)
|
|
if (!pr->load_finish_funcs[--pr->num_load_finish_funcs] (pr))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
PR_LoadProgs
|
|
*/
|
|
VISIBLE void
|
|
PR_LoadProgs (progs_t *pr, const char *progsname)
|
|
{
|
|
QFile *file;
|
|
file = QFS_FOpenFile (progsname);
|
|
|
|
pr->progs_name = progsname;
|
|
if (file) {
|
|
PR_LoadProgsFile (pr, file, qfs_filesize);
|
|
Qclose (file);
|
|
}
|
|
if (!pr->progs)
|
|
return;
|
|
|
|
if (!PR_RunLoadFuncs (pr)) {
|
|
PR_Error (pr, "unable to load %s", progsname);
|
|
}
|
|
if (!pr->debug_handler && !PR_RunPostLoadFuncs (pr)) {
|
|
PR_Error (pr, "unable to load %s", progsname);
|
|
}
|
|
}
|
|
|
|
VISIBLE void
|
|
PR_Init_Cvars (void)
|
|
{
|
|
pr_boundscheck =
|
|
Cvar_Get ("pr_boundscheck", "0", CVAR_NONE, NULL,
|
|
"Server progs bounds checking");
|
|
pr_deadbeef_ents = Cvar_Get ("pr_deadbeef_ents", "0", CVAR_NONE, NULL,
|
|
"set to clear unallocated memory to 0xdeadbeef");
|
|
pr_deadbeef_locals = Cvar_Get ("pr_deadbeef_locals", "0", CVAR_NONE, NULL,
|
|
"set to clear uninitialized local vars to "
|
|
"0xdeadbeef");
|
|
pr_faultchecks = Cvar_Get ("pr_faultchecks", "0", CVAR_NONE, NULL,
|
|
"capture and handle division by 0 in progs");
|
|
PR_Debug_Init_Cvars ();
|
|
}
|
|
|
|
VISIBLE void
|
|
PR_Init (progs_t *pr)
|
|
{
|
|
PR_Opcode_Init (); // idempotent
|
|
PR_Resources_Init (pr);
|
|
PR_Strings_Init (pr);
|
|
PR_Debug_Init (pr);
|
|
}
|
|
|
|
VISIBLE void
|
|
PR_Error (progs_t *pr, const char *error, ...)
|
|
{
|
|
va_list argptr;
|
|
dstring_t *string = dstring_new ();//FIXME leaks when debugging
|
|
|
|
va_start (argptr, error);
|
|
dvsprintf (string, error, argptr);
|
|
va_end (argptr);
|
|
|
|
if (pr->debug_handler) {
|
|
pr->debug_handler (prd_error, string->str, pr->debug_data);
|
|
// not expected to return, but if so, behave as if there was no handler
|
|
}
|
|
Sys_Error ("%s: %s", pr->progs_name, string->str);
|
|
}
|