/* 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 #endif #ifdef HAVE_STRINGS_H # include #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 "QF/progs/pr_type.h" #include "compat.h" VISIBLE int pr_boundscheck; static cvar_t pr_boundscheck_cvar = { .name = "pr_boundscheck", .description = "Server progs bounds checking", .default_value = "0", .flags = CVAR_NONE, .value = { .type = &cexpr_int, .value = &pr_boundscheck }, }; int pr_deadbeef_ents; static cvar_t pr_deadbeef_ents_cvar = { .name = "pr_deadbeef_ents", .description = "set to clear unallocated memory to 0xdeadbeef", .default_value = "0", .flags = CVAR_NONE, .value = { .type = &cexpr_int, .value = &pr_deadbeef_ents }, }; int pr_deadbeef_locals; static cvar_t pr_deadbeef_locals_cvar = { .name = "pr_deadbeef_locals", .description = "set to clear uninitialized local vars to 0xdeadbeef", .default_value = "0", .flags = CVAR_NONE, .value = { .type = &cexpr_int, .value = &pr_deadbeef_locals }, }; int pr_faultchecks; static cvar_t pr_faultchecks_cvar = { .name = "pr_faultchecks", .description = "capture and handle division by 0 in progs", .default_value = "0", .flags = CVAR_NONE, .value = { .type = &cexpr_int, .value = &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->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 const char * type_get_key (const void *t, void *_pr) { progs_t *pr = (progs_t *) _pr; __auto_type type = (qfot_type_t *) t; return PR_GetString (pr, type->encoding); } 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 (0, size, pr->progs_name); } static void free_progs_mem (progs_t *pr, void *mem) { } static int align_size (int size, int align) { size += align - 1; size &= ~(align - 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; ddef_t *global_ddefs; ddef_t *field_ddefs; // absolute minimum alignment is 4 bytes int edict_alignment = __alignof__(pr_lvec4_t); 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_V6P_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); } } if (progs.version == PROG_VERSION) { // ensure SIMD types can be aligned (qfcc will align them within // the progs memory map, so the engine needs to ensure the progs memory // is aligned) edict_alignment = __alignof__(pr_lvec4_t); } // edict size is in words edict_alignment /= 4; // 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.strings.offset % 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, 64); pr->zone_size = align_size (pr->zone_size, 64); pr->stack_size = align_size (pr->stack_size, 64); // size of edict asked for by progs, but at least 1 pr->pr_edict_size = max (1, progs.entityfields); pr->pr_edict_size = align_size (pr->pr_edict_size, edict_alignment); pr->pr_edict_area_size = pr->max_edicts * pr->pr_edict_size; pr->pr_edict_area_size = align_size (pr->pr_edict_area_size, 64); mem_size = pr->pr_edict_area_size * sizeof (pr_type_t); mem_size += pr->progs_size + pr->zone_size + pr->stack_size; // space for return buffer mem_size += PR_MAX_RETURN * sizeof (pr_type_t); // +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->functions.offset); pr->pr_strings = (char *) base + pr->progs->strings.offset; pr->pr_stringsize = (heap - base) + pr->zone_size; global_ddefs = (ddef_t *) (base + pr->progs->globaldefs.offset); field_ddefs = (ddef_t *) (base + pr->progs->fielddefs.offset); pr->pr_statements = (dstatement_t *) (base + pr->progs->statements.offset); pr->pr_globals = (pr_type_t *) (base + pr->progs->globals.offset); 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->globals.stack && pr->stack_bottom) { *pr->globals.stack = pr->globals_size; } pr->pr_return_buffer = pr->pr_globals + pr->globals_size; if (pr->zone) { PR_Zone_Init (pr); } Hash_FlushTable (pr->function_hash); Hash_FlushTable (pr->global_hash); Hash_FlushTable (pr->field_hash); Hash_FlushTable (pr->type_hash); // byte swap the lumps for (i = 0; i < pr->progs->statements.count; 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 < pr->progs->functions.count; i++) { pr->pr_functions[i].first_statement = LittleLong (pr->pr_functions[i].first_statement); pr->pr_functions[i].params_start = LittleLong (pr->pr_functions[i].params_start); pr->pr_functions[i].name = LittleLong (pr->pr_functions[i].name); pr->pr_functions[i].file = LittleLong (pr->pr_functions[i].file); pr->pr_functions[i].numparams = LittleLong (pr->pr_functions[i].numparams); pr->pr_functions[i].locals = LittleLong (pr->pr_functions[i].locals); if (pr->pr_functions[i].name) Hash_Add (pr->function_hash, &pr->pr_functions[i]); } if (pr->pr_globaldefs) { free (pr->pr_globaldefs); } pr->pr_globaldefs = calloc (pr->progs->globaldefs.count, sizeof (pr_def_t)); for (i = 0; i < pr->progs->globaldefs.count; 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].name = LittleLong (global_ddefs[i].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].name; Hash_Add (pr->global_hash, &pr->pr_globaldefs[i]); } if (pr->pr_fielddefs) { free (pr->pr_fielddefs); } pr->pr_fielddefs = calloc (pr->progs->fielddefs.count, sizeof (pr_def_t)); for (i = 0; i < pr->progs->fielddefs.count; 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].name = LittleLong (field_ddefs[i].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].name; Hash_Add (pr->field_hash, &pr->pr_fielddefs[i]); } for (i = 0; i < pr->progs->globals.count; i++) ((int *) pr->pr_globals)[i] = LittleLong (((int *) pr->pr_globals)[i]); pr_def_t *types_def = PR_FindGlobal (pr, ".type_encodings"); if (types_def) { __auto_type encodings = &G_STRUCT (pr, qfot_type_encodings_t, types_def->ofs); pr->type_encodings = encodings->types; qfot_type_t *type; for (pr_ptr_t type_ptr = 4; type_ptr < encodings->size; type_ptr += type->size) { type = &G_STRUCT (pr, qfot_type_t, pr->type_encodings + type_ptr); Hash_Add (pr->type_hash, type); } } else { pr->type_encodings = 0; } pr_def_t *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->globaldefs.count; i++, xdef++, def++) { def->ofs = xdef->ofs; def->type_encoding = xdef->type; } for (def = pr->pr_fielddefs, i = 0; i < pr->progs->fielddefs.count; 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->globaldefs.count; i++, def++) { def->size = pr_type_size[def->type & ~DEF_SAVEGLOBAL]; } for (def = pr->pr_fielddefs, i = 0; i < pr->progs->fielddefs.count; i++, def++) { def->size = pr_type_size[def->type & ~DEF_SAVEGLOBAL]; } } 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->functions.count; fnum++) { func = pr->pr_functions + fnum; if (strequal (PR_GetString (pr, func->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) { Cvar_Register (&pr_boundscheck_cvar, 0, 0); Cvar_Register (&pr_deadbeef_ents_cvar, 0, 0); Cvar_Register (&pr_deadbeef_locals_cvar, 0, 0); Cvar_Register (&pr_faultchecks_cvar, 0, 0); PR_Debug_Init_Cvars (); } VISIBLE void PR_Init (progs_t *pr) { PR_Resources_Init (pr); PR_Strings_Init (pr); PR_Debug_Init (pr); pr->function_hash = Hash_NewTable (1021, function_get_key, 0, pr, pr->hashctx); pr->global_hash = Hash_NewTable (1021, var_get_key, 0, pr, pr->hashctx); pr->field_hash = Hash_NewTable (1021, var_get_key, 0, pr, pr->hashctx); pr->type_hash = Hash_NewTable (1021, type_get_key, 0, pr, pr->hashctx); } VISIBLE void PR_Shutdown (progs_t *pr) { PR_Resources_Shutdown (pr); PR_Builtins_Shutdown (pr); free (pr->load_funcs); free (pr->load_finish_funcs); pr->num_load_funcs = pr->max_load_funcs = 0; pr->num_load_finish_funcs = pr->max_load_finish_funcs = 0; Hash_DelTable (pr->function_hash); Hash_DelTable (pr->global_hash); Hash_DelTable (pr->field_hash); Hash_DelTable (pr->type_hash); pr->function_hash = 0; pr->global_hash = 0; pr->field_hash = 0; pr->type_hash = 0; } 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); }