2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 prvm_prog_t prvm_prog_list[PRVM_PROG_MAX];
28 int prvm_type_size[8] = {1,sizeof(string_t)/4,1,3,1,1,sizeof(func_t)/4,sizeof(void *)/4};
30 prvm_eval_t prvm_badvalue; // used only for error returns
32 cvar_t prvm_language = {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "prvm_language", "", "when set, loads PROGSFILE.LANGUAGENAME.po and common.LANGUAGENAME.po for string translations; when set to dump, PROGSFILE.pot is written from the strings in the progs"};
33 // LadyHavoc: prints every opcode as it executes - warning: this is significant spew
34 cvar_t prvm_traceqc = {CF_CLIENT | CF_SERVER, "prvm_traceqc", "0", "prints every QuakeC statement as it is executed (only for really thorough debugging!)"};
35 // LadyHavoc: counts usage of each QuakeC statement
36 cvar_t prvm_statementprofiling = {CF_CLIENT | CF_SERVER, "prvm_statementprofiling", "0", "counts how many times each QuakeC statement has been executed, these counts are displayed in prvm_printfunction output (if enabled)"};
37 cvar_t prvm_timeprofiling = {CF_CLIENT | CF_SERVER, "prvm_timeprofiling", "0", "counts how long each function has been executed, these counts are displayed in prvm_profile output (if enabled)"};
38 cvar_t prvm_coverage = {CF_CLIENT | CF_SERVER, "prvm_coverage", "0", "report and count coverage events (1: per-function, 2: coverage() builtin, 4: per-statement)"};
39 cvar_t prvm_backtraceforwarnings = {CF_CLIENT | CF_SERVER, "prvm_backtraceforwarnings", "0", "print a backtrace for warnings too"};
40 cvar_t prvm_leaktest = {CF_CLIENT | CF_SERVER, "prvm_leaktest", "0", "try to detect memory leaks in strings or entities"};
41 cvar_t prvm_leaktest_follow_targetname = {CF_CLIENT | CF_SERVER, "prvm_leaktest_follow_targetname", "0", "if set, target/targetname links are considered when leak testing; this should normally not be required, as entities created during startup - e.g. info_notnull - are never considered leaky"};
42 cvar_t prvm_leaktest_ignore_classnames = {CF_CLIENT | CF_SERVER, "prvm_leaktest_ignore_classnames", "", "classnames of entities to NOT leak check because they are found by find(world, classname, ...) but are actually spawned by QC code (NOT map entities)"};
43 cvar_t prvm_errordump = {CF_CLIENT | CF_SERVER, "prvm_errordump", "0", "write a savegame on crash to crash-server.dmp"};
44 cvar_t prvm_breakpointdump = {CF_CLIENT | CF_SERVER, "prvm_breakpointdump", "0", "write a savegame on breakpoint to breakpoint-server.dmp"};
45 cvar_t prvm_reuseedicts_startuptime = {CF_CLIENT | CF_SERVER, "prvm_reuseedicts_startuptime", "2", "allows immediate re-use of freed entity slots during start of new level (value in seconds)"};
46 cvar_t prvm_reuseedicts_neverinsameframe = {CF_CLIENT | CF_SERVER, "prvm_reuseedicts_neverinsameframe", "1", "never allows re-use of freed entity slots during same frame"};
47 cvar_t prvm_garbagecollection_enable = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_enable", "1", "automatically scan for and free resources that are not referenced by the code being executed in the VM"};
48 cvar_t prvm_garbagecollection_notify = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_notify", "0", "print out a notification for each resource freed by garbage collection"};
49 cvar_t prvm_garbagecollection_scan_limit = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_scan_limit", "10000", "scan this many fields or resources per frame to free up unreferenced resources"};
50 cvar_t prvm_garbagecollection_strings = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_strings", "1", "automatically call strunzone() on strings that are not referenced"};
51 cvar_t prvm_stringdebug = {CF_CLIENT | CF_SERVER, "prvm_stringdebug", "0", "Print debug and warning messages related to strings"};
53 static double prvm_reuseedicts_always_allow = 0;
54 qbool prvm_runawaycheck = true;
56 //============================================================================
64 static void PRVM_MEM_Alloc(prvm_prog_t *prog)
68 // reserve space for the null entity aka world
69 // check bound of max_edicts
70 prog->max_edicts = bound(1 + prog->reserved_edicts, prog->max_edicts, prog->limit_edicts);
71 prog->num_edicts = bound(1 + prog->reserved_edicts, prog->num_edicts, prog->max_edicts);
73 // edictprivate_size has to be min as big prvm_edict_private_t
74 prog->edictprivate_size = max(prog->edictprivate_size,(int)sizeof(prvm_edict_private_t));
77 prog->edicts = (prvm_edict_t *)Mem_Alloc(prog->progs_mempool,prog->limit_edicts * sizeof(prvm_edict_t));
79 // alloc edict private space
80 prog->edictprivate = Mem_Alloc(prog->progs_mempool, prog->max_edicts * prog->edictprivate_size);
83 prog->entityfieldsarea = prog->entityfields * prog->max_edicts;
84 prog->edictsfields.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, prog->entityfieldsarea * sizeof(prvm_vec_t));
87 for(i = 0; i < prog->max_edicts; i++)
89 prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size);
90 prog->edicts[i].fields.fp = prog->edictsfields.fp + i * prog->entityfields;
96 PRVM_MEM_IncreaseEdicts
99 void PRVM_MEM_IncreaseEdicts(prvm_prog_t *prog)
103 if(prog->max_edicts >= prog->limit_edicts)
106 prog->begin_increase_edicts(prog);
109 prog->max_edicts = min(prog->max_edicts + 256, prog->limit_edicts);
111 prog->entityfieldsarea = prog->entityfields * prog->max_edicts;
112 prog->edictsfields.fp = (prvm_vec_t*)Mem_Realloc(prog->progs_mempool, (void *)prog->edictsfields.fp, prog->entityfieldsarea * sizeof(prvm_vec_t));
113 prog->edictprivate = (void *)Mem_Realloc(prog->progs_mempool, (void *)prog->edictprivate, prog->max_edicts * prog->edictprivate_size);
115 //set e and v pointers
116 for(i = 0; i < prog->max_edicts; i++)
118 prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size);
119 prog->edicts[i].fields.fp = prog->edictsfields.fp + i * prog->entityfields;
122 prog->end_increase_edicts(prog);
125 //============================================================================
128 int PRVM_ED_FindFieldOffset(prvm_prog_t *prog, const char *field)
131 d = PRVM_ED_FindField(prog, field);
137 int PRVM_ED_FindGlobalOffset(prvm_prog_t *prog, const char *global)
140 d = PRVM_ED_FindGlobal(prog, global);
146 func_t PRVM_ED_FindFunctionOffset(prvm_prog_t *prog, const char *function)
149 f = PRVM_ED_FindFunction(prog, function);
152 return (func_t)(f - prog->functions);
160 prvm_prog_t *PRVM_ProgFromString(const char *str)
162 if (!strcmp(str, "server"))
164 if (!strcmp(str, "client"))
167 if (!strcmp(str, "menu"))
175 PRVM_FriendlyProgFromString
178 prvm_prog_t *PRVM_FriendlyProgFromString(const char *str)
180 prvm_prog_t *prog = PRVM_ProgFromString(str);
183 Con_Printf("%s: unknown program name\n", str);
188 Con_Printf("%s: program is not loaded\n", str);
198 Sets everything to NULL.
200 Nota bene: this also marks the entity as allocated if it has been previously
201 freed and sets the allocation origin.
204 void PRVM_ED_ClearEdict(prvm_prog_t *prog, prvm_edict_t *e)
206 memset(e->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
207 e->priv.required->free = false;
208 e->priv.required->freetime = host.realtime;
209 if(e->priv.required->allocation_origin)
210 Mem_Free((char *)e->priv.required->allocation_origin);
211 e->priv.required->allocation_origin = PRVM_AllocationOrigin(prog);
213 // AK: Let the init_edict function determine if something needs to be initialized
214 prog->init_edict(prog, e);
217 const char *PRVM_AllocationOrigin(prvm_prog_t *prog)
220 if(prog->leaktest_active)
221 if(prog->depth > 0) // actually in QC code and not just parsing the entities block of a map/savegame
223 buf = (char *)PRVM_Alloc(256);
224 PRVM_ShortStackTrace(prog, buf, 256);
233 Returns if this particular edict could get allocated by PRVM_ED_Alloc
236 qbool PRVM_ED_CanAlloc(prvm_prog_t *prog, prvm_edict_t *e)
238 if(!e->priv.required->free)
240 if(prvm_reuseedicts_always_allow == host.realtime)
242 if(host.realtime <= e->priv.required->freetime + 0.1 && prvm_reuseedicts_neverinsameframe.integer)
243 return false; // never allow reuse in same frame (causes networking trouble)
244 if(e->priv.required->freetime < prog->starttime + prvm_reuseedicts_startuptime.value)
246 if(host.realtime > e->priv.required->freetime + 1)
248 return false; // entity slot still blocked because the entity was freed less than one second ago
255 Either finds a free edict, or allocates a new one.
256 Try to avoid reusing an entity that was recently freed, because it
257 can cause the client to think the entity morphed into something else
258 instead of being removed and recreated, which can cause interpolated
259 angles and bad trails.
262 prvm_edict_t *PRVM_ED_Alloc(prvm_prog_t *prog)
267 // the client qc dont need maxclients
268 // thus it doesnt need to use svs.maxclients
269 // AK: changed i=svs.maxclients+1
270 // AK: changed so the edict 0 wont spawn -> used as reserved/world entity
271 // although the menu/client has no world
272 for (i = prog->reserved_edicts + 1;i < prog->num_edicts;i++)
274 e = PRVM_EDICT_NUM(i);
275 if(PRVM_ED_CanAlloc(prog, e))
277 PRVM_ED_ClearEdict (prog, e);
282 if (i == prog->limit_edicts)
283 prog->error_cmd("%s: PRVM_ED_Alloc: no free edicts", prog->name);
286 if (prog->num_edicts >= prog->max_edicts)
287 PRVM_MEM_IncreaseEdicts(prog);
289 e = PRVM_EDICT_NUM(i);
291 PRVM_ED_ClearEdict(prog, e);
299 Marks the edict as free
300 FIXME: walk all entities and NULL out references to this entity
303 void PRVM_ED_Free(prvm_prog_t *prog, prvm_edict_t *ed)
305 // dont delete the null entity (world) or reserved edicts
306 if (ed - prog->edicts <= prog->reserved_edicts)
309 prog->free_edict(prog, ed);
311 ed->priv.required->free = true;
312 ed->priv.required->freetime = host.realtime;
313 if(ed->priv.required->allocation_origin)
315 Mem_Free((char *)ed->priv.required->allocation_origin);
316 ed->priv.required->allocation_origin = NULL;
320 //===========================================================================
327 static mdef_t *PRVM_ED_GlobalAtOfs (prvm_prog_t *prog, unsigned int ofs)
332 for (i = 0;i < prog->numglobaldefs;i++)
334 def = &prog->globaldefs[i];
346 mdef_t *PRVM_ED_FieldAtOfs (prvm_prog_t *prog, unsigned int ofs)
351 for (i = 0;i < prog->numfielddefs;i++)
353 def = &prog->fielddefs[i];
365 mdef_t *PRVM_ED_FindField (prvm_prog_t *prog, const char *name)
370 for (i = 0;i < prog->numfielddefs;i++)
372 def = &prog->fielddefs[i];
373 if (!strcmp(PRVM_GetString(prog, def->s_name), name))
384 mdef_t *PRVM_ED_FindGlobal (prvm_prog_t *prog, const char *name)
389 for (i = 0;i < prog->numglobaldefs;i++)
391 def = &prog->globaldefs[i];
392 if (!strcmp(PRVM_GetString(prog, def->s_name), name))
400 PRVM_ED_FindGlobalEval
403 prvm_eval_t *PRVM_ED_FindGlobalEval(prvm_prog_t *prog, const char *name)
405 mdef_t *def = PRVM_ED_FindGlobal(prog, name);
406 return def ? (prvm_eval_t *) &prog->globals.fp[def->ofs] : NULL;
414 mfunction_t *PRVM_ED_FindFunction (prvm_prog_t *prog, const char *name)
419 for (i = 0;i < prog->numfunctions;i++)
421 func = &prog->functions[i];
422 if (!strcmp(PRVM_GetString(prog, func->s_name), name))
433 Returns a string describing *data in a type specific manner
436 static char *PRVM_ValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength)
442 type = (etype_t)((int) type & ~DEF_SAVEGLOBAL);
447 strlcpy (line, PRVM_GetString (prog, val->string), linelength);
451 if (n < 0 || n >= prog->max_edicts)
452 dpsnprintf (line, linelength, "entity %i (invalid!)", n);
454 dpsnprintf (line, linelength, "entity %i", n);
457 if ((unsigned int)val->function < (unsigned int)prog->progs_numfunctions)
459 f = prog->functions + val->function;
460 dpsnprintf (line, linelength, "%s()", PRVM_GetString(prog, f->s_name));
463 dpsnprintf (line, linelength, "function %" PRVM_PRIi "() (invalid!)", val->function);
466 def = PRVM_ED_FieldAtOfs ( prog, val->_int );
468 dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name));
470 dpsnprintf (line, linelength, "field %" PRVM_PRIi " (invalid!)", val->_int );
473 dpsnprintf (line, linelength, "void");
476 // LadyHavoc: changed from %5.1f to %10.4f
477 dpsnprintf (line, linelength, FLOAT_LOSSLESS_FORMAT, val->_float);
480 // LadyHavoc: changed from %5.1f to %10.4f
481 dpsnprintf (line, linelength, "'" VECTOR_LOSSLESS_FORMAT "'", val->vector[0], val->vector[1], val->vector[2]);
484 dpsnprintf (line, linelength, "pointer");
487 dpsnprintf (line, linelength, "bad type %i", (int) type);
498 Returns a string describing *data in a type specific manner
499 Easier to parse than PR_ValueString
502 char *PRVM_UglyValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength)
509 type = (etype_t)((int)type & ~DEF_SAVEGLOBAL);
514 // Parse the string a bit to turn special characters
515 // (like newline, specifically) into escape codes,
516 // this fixes saving games from various mods
517 s = PRVM_GetString (prog, val->string);
518 for (i = 0;i < (int)linelength - 2 && *s;)
548 dpsnprintf (line, linelength, "%i", i);
551 if ((unsigned int)val->function < (unsigned int)prog->progs_numfunctions)
553 f = prog->functions + val->function;
554 strlcpy (line, PRVM_GetString (prog, f->s_name), linelength);
557 dpsnprintf (line, linelength, "bad function %" PRVM_PRIi " (invalid!)", val->function);
560 def = PRVM_ED_FieldAtOfs ( prog, val->_int );
562 dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name));
564 dpsnprintf (line, linelength, "field %" PRVM_PRIi "(invalid!)", val->_int );
567 dpsnprintf (line, linelength, "void");
570 dpsnprintf (line, linelength, FLOAT_LOSSLESS_FORMAT, val->_float);
573 dpsnprintf (line, linelength, VECTOR_LOSSLESS_FORMAT, val->vector[0], val->vector[1], val->vector[2]);
576 dpsnprintf (line, linelength, "bad type %i", type);
587 Returns a string with a description and the contents of a global,
588 padded to 20 field width
591 char *PRVM_GlobalString (prvm_prog_t *prog, int ofs, char *line, size_t linelength)
597 char valuebuf[MAX_INPUTLINE];
599 val = (prvm_eval_t *)&prog->globals.fp[ofs];
600 def = PRVM_ED_GlobalAtOfs(prog, ofs);
602 dpsnprintf (line, linelength, "GLOBAL%i", ofs);
605 s = PRVM_ValueString (prog, (etype_t)def->type, val, valuebuf, sizeof(valuebuf));
606 dpsnprintf (line, linelength, "%s (=%s)", PRVM_GetString(prog, def->s_name), s);
610 //for ( ; i<20 ; i++)
611 // strcat (line," ");
617 char *PRVM_GlobalStringNoContents (prvm_prog_t *prog, int ofs, char *line, size_t linelength)
622 def = PRVM_ED_GlobalAtOfs(prog, ofs);
624 dpsnprintf (line, linelength, "GLOBAL%i", ofs);
626 dpsnprintf (line, linelength, "%s", PRVM_GetString(prog, def->s_name));
629 //for ( ; i<20 ; i++)
630 // strcat (line," ");
644 // LadyHavoc: optimized this to print out much more quickly (tempstring)
645 // LadyHavoc: changed to print out every 4096 characters (incase there are a lot of fields to print)
646 void PRVM_ED_Print(prvm_prog_t *prog, prvm_edict_t *ed, const char *wildcard_fieldname)
654 char tempstring[MAX_INPUTLINE], tempstring2[260]; // temporary string buffers
655 char valuebuf[MAX_INPUTLINE];
657 if (ed->priv.required->free)
659 Con_Printf("%s: FREE\n",prog->name);
664 dpsnprintf(tempstring, sizeof(tempstring), "\n%s EDICT %i:\n", prog->name, PRVM_NUM_FOR_EDICT(ed));
665 for (i = 1;i < prog->numfielddefs;i++)
667 d = &prog->fielddefs[i];
668 name = PRVM_GetString(prog, d->s_name);
669 if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
670 continue; // skip _x, _y, _z vars
672 // Check Field Name Wildcard
673 if(wildcard_fieldname)
674 if( !matchpattern(name, wildcard_fieldname, 1) )
675 // Didn't match; skip
678 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
680 // if the value is still all 0, skip the field
681 type = d->type & ~DEF_SAVEGLOBAL;
683 for (j=0 ; j<prvm_type_size[type] ; j++)
686 if (j == prvm_type_size[type])
689 if (strlen(name) > sizeof(tempstring2)-4)
691 memcpy (tempstring2, name, sizeof(tempstring2)-4);
692 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
693 tempstring2[sizeof(tempstring2)-1] = 0;
696 strlcat(tempstring, name, sizeof(tempstring));
697 for (l = strlen(name);l < 14;l++)
698 strlcat(tempstring, " ", sizeof(tempstring));
699 strlcat(tempstring, " ", sizeof(tempstring));
701 name = PRVM_ValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf));
702 if (strlen(name) > sizeof(tempstring2)-4)
704 memcpy (tempstring2, name, sizeof(tempstring2)-4);
705 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
706 tempstring2[sizeof(tempstring2)-1] = 0;
709 strlcat(tempstring, name, sizeof(tempstring));
710 strlcat(tempstring, "\n", sizeof(tempstring));
711 if (strlen(tempstring) >= sizeof(tempstring)/2)
713 Con_Print(tempstring);
718 Con_Print(tempstring);
728 void PRVM_ED_Write (prvm_prog_t *prog, qfile_t *f, prvm_edict_t *ed)
736 char valuebuf[MAX_INPUTLINE];
740 if (ed->priv.required->free)
746 for (i = 1;i < prog->numfielddefs;i++)
748 d = &prog->fielddefs[i];
749 name = PRVM_GetString(prog, d->s_name);
751 if(developer_entityparsing.integer)
752 Con_Printf("PRVM_ED_Write: at entity %d field %s\n", PRVM_NUM_FOR_EDICT(ed), name);
754 //if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
755 if(strlen(name) > 1 && name[strlen(name)-2] == '_')
756 continue; // skip _x, _y, _z vars, and ALSO other _? vars as some mods expect them to be never saved (TODO: a gameplayfix for using the "more precise" condition above?)
758 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
760 // if the value is still all 0, skip the field
761 type = d->type & ~DEF_SAVEGLOBAL;
762 for (j=0 ; j<prvm_type_size[type] ; j++)
765 if (j == prvm_type_size[type])
768 FS_Printf(f,"\"%s\" ",name);
769 prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_Write, ent=%d, name=%s", i, name);
770 FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf)));
771 prog->statestring = NULL;
777 void PRVM_ED_PrintNum (prvm_prog_t *prog, int ent, const char *wildcard_fieldname)
779 PRVM_ED_Print(prog, PRVM_EDICT_NUM(ent), wildcard_fieldname);
784 PRVM_ED_PrintEdicts_f
786 For debugging, prints all the entities in the current server
789 void PRVM_ED_PrintEdicts_f(cmd_state_t *cmd)
793 const char *wildcard_fieldname;
795 if(Cmd_Argc(cmd) < 2 || Cmd_Argc(cmd) > 3)
797 Con_Print("prvm_edicts <program name> <optional field name wildcard>\n");
801 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
804 if( Cmd_Argc(cmd) == 3)
805 wildcard_fieldname = Cmd_Argv(cmd, 2);
807 wildcard_fieldname = NULL;
809 Con_Printf("%s: %i entities\n", prog->name, prog->num_edicts);
810 for (i=0 ; i<prog->num_edicts ; i++)
811 PRVM_ED_PrintNum (prog, i, wildcard_fieldname);
818 For debugging, prints a single edict
821 static void PRVM_ED_PrintEdict_f(cmd_state_t *cmd)
825 const char *wildcard_fieldname;
827 if(Cmd_Argc(cmd) < 3 || Cmd_Argc(cmd) > 4)
829 Con_Print("prvm_edict <program name> <edict number> <optional field name wildcard>\n");
833 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
836 i = atoi (Cmd_Argv(cmd, 2));
837 if (i >= prog->num_edicts)
839 Con_Print("Bad edict number\n");
842 if( Cmd_Argc(cmd) == 4)
843 // Optional Wildcard Provided
844 wildcard_fieldname = Cmd_Argv(cmd, 3);
847 wildcard_fieldname = NULL;
848 PRVM_ED_PrintNum (prog, i, wildcard_fieldname);
858 // 2 possibilities : 1. just displaying the active edict count
859 // 2. making a function pointer [x]
860 static void PRVM_ED_Count_f(cmd_state_t *cmd)
864 if(Cmd_Argc(cmd) != 2)
866 Con_Print("prvm_count <program name>\n");
870 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
873 prog->count_edicts(prog);
877 ==============================================================================
881 FIXME: need to tag constants, doesn't really work
882 ==============================================================================
890 void PRVM_ED_WriteGlobals (prvm_prog_t *prog, qfile_t *f)
897 char valuebuf[MAX_INPUTLINE];
900 for (i = 0;i < prog->numglobaldefs;i++)
902 def = &prog->globaldefs[i];
904 if ( !(def->type & DEF_SAVEGLOBAL) )
906 type &= ~DEF_SAVEGLOBAL;
908 if (type != ev_string && type != ev_float && type != ev_entity)
911 name = PRVM_GetString(prog, def->s_name);
913 if(developer_entityparsing.integer)
914 Con_Printf("PRVM_ED_WriteGlobals: at global %s\n", name);
916 prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_WriteGlobals, name=%s", name);
917 FS_Printf(f,"\"%s\" ", name);
918 FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)type, (prvm_eval_t *)&prog->globals.fp[def->ofs], valuebuf, sizeof(valuebuf)));
919 prog->statestring = NULL;
929 void PRVM_ED_ParseGlobals (prvm_prog_t *prog, const char *data)
931 char keyname[MAX_INPUTLINE];
937 if (!COM_ParseToken_Simple(&data, false, false, true))
938 prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace");
939 if (com_token[0] == '}')
942 if (developer_entityparsing.integer)
943 Con_Printf("Key: \"%s\"", com_token);
945 strlcpy (keyname, com_token, sizeof(keyname));
948 if (!COM_ParseToken_Simple(&data, false, true, true))
949 prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace");
951 if (developer_entityparsing.integer)
952 Con_Printf(" \"%s\"\n", com_token);
954 if (com_token[0] == '}')
955 prog->error_cmd("PRVM_ED_ParseGlobals: closing brace without data");
957 key = PRVM_ED_FindGlobal (prog, keyname);
960 Con_DPrintf("'%s' is not a global on %s\n", keyname, prog->name);
964 if (!PRVM_ED_ParseEpair(prog, NULL, key, com_token, true))
965 prog->error_cmd("PRVM_ED_ParseGlobals: parse error");
969 //============================================================================
976 Can parse either fields or globals
977 returns false if error
980 qbool PRVM_ED_ParseEpair(prvm_prog_t *prog, prvm_edict_t *ent, mdef_t *key, const char *s, qbool parsebackslash)
989 val = (prvm_eval_t *)(ent->fields.fp + key->ofs);
991 val = (prvm_eval_t *)(prog->globals.fp + key->ofs);
992 switch (key->type & ~DEF_SAVEGLOBAL)
995 l = (int)strlen(s) + 1;
996 val->string = PRVM_AllocString(prog, l, &new_p);
997 for (i = 0;i < l;i++)
999 if (s[i] == '\\' && s[i+1] && parsebackslash)
1004 else if (s[i] == 'r')
1015 while (*s && ISWHITESPACE(*s))
1017 val->_float = atof(s);
1021 for (i = 0;i < 3;i++)
1023 while (*s && ISWHITESPACE(*s))
1027 val->vector[i] = atof(s);
1028 while (!ISWHITESPACE(*s))
1036 while (*s && ISWHITESPACE(*s))
1039 if (i >= prog->limit_edicts)
1040 Con_Printf("PRVM_ED_ParseEpair: ev_entity reference too large (edict %u >= MAX_EDICTS %u) on %s\n", (unsigned int)i, prog->limit_edicts, prog->name);
1041 while (i >= prog->max_edicts)
1042 PRVM_MEM_IncreaseEdicts(prog);
1043 // if IncreaseEdicts was called the base pointer needs to be updated
1045 val = (prvm_eval_t *)(ent->fields.fp + key->ofs);
1046 val->edict = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM((int)i));
1052 Con_DPrintf("PRVM_ED_ParseEpair: Bogus field name %s in %s\n", s, prog->name);
1055 def = PRVM_ED_FindField(prog, s + 1);
1058 Con_DPrintf("PRVM_ED_ParseEpair: Can't find field %s in %s\n", s, prog->name);
1061 val->_int = def->ofs;
1065 func = PRVM_ED_FindFunction(prog, s);
1068 Con_Printf("PRVM_ED_ParseEpair: Can't find function %s in %s\n", s, prog->name);
1071 val->function = func - prog->functions;
1075 Con_Printf("PRVM_ED_ParseEpair: Unknown key->type %i for key \"%s\" on %s\n", key->type, PRVM_GetString(prog, key->s_name), prog->name);
1085 Console command to send a string to QC function GameCommand of the
1089 sv_cmd adminmsg 3 "do not teamkill"
1090 cl_cmd someclientcommand
1091 menu_cmd somemenucommand
1093 All progs can support this extension; sg calls it in server QC, cg in client
1097 static void PRVM_GameCommand(cmd_state_t *cmd, const char *whichprogs, const char *whichcmd)
1100 if(Cmd_Argc(cmd) < 1)
1102 Con_Printf("%s text...\n", whichcmd);
1106 if (!(prog = PRVM_FriendlyProgFromString(whichprogs)))
1109 if(!PRVM_allfunction(GameCommand))
1111 Con_Printf("%s program do not support GameCommand!\n", whichprogs);
1115 int restorevm_tempstringsbuf_cursize;
1120 restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize;
1121 PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, s ? s : "");
1122 prog->ExecuteProgram(prog, PRVM_allfunction(GameCommand), "QC function GameCommand is missing");
1123 prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
1126 static void PRVM_GameCommand_Server_f(cmd_state_t *cmd)
1128 PRVM_GameCommand(cmd, "server", "sv_cmd");
1130 static void PRVM_GameCommand_Client_f(cmd_state_t *cmd)
1132 PRVM_GameCommand(cmd, "client", "cl_cmd");
1134 static void PRVM_GameCommand_Menu_f(cmd_state_t *cmd)
1136 PRVM_GameCommand(cmd, "menu", "menu_cmd");
1143 Console command to load a field of a specified edict
1146 static void PRVM_ED_EdictGet_f(cmd_state_t *cmd)
1153 char valuebuf[MAX_INPUTLINE];
1155 if(Cmd_Argc(cmd) != 4 && Cmd_Argc(cmd) != 5)
1157 Con_Print("prvm_edictget <program name> <edict number> <field> [<cvar>]\n");
1161 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1164 ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(cmd, 2)));
1166 if((key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, 3))) == 0)
1168 Con_Printf("Key %s not found !\n", Cmd_Argv(cmd, 3));
1172 v = (prvm_eval_t *)(ed->fields.fp + key->ofs);
1173 s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf));
1174 if(Cmd_Argc(cmd) == 5)
1176 cvar_t *cvar = Cvar_FindVar(cmd->cvars, Cmd_Argv(cmd, 4), cmd->cvars_flagsmask);
1178 if(Cvar_Readonly(cvar, "prvm_edictget"))
1181 Cvar_Get(cmd->cvars, Cmd_Argv(cmd, 4), s, cmd->cvars_flagsmask, NULL);
1184 Con_Printf("%s\n", s);
1190 static void PRVM_ED_GlobalGet_f(cmd_state_t *cmd)
1196 char valuebuf[MAX_INPUTLINE];
1198 if(Cmd_Argc(cmd) != 3 && Cmd_Argc(cmd) != 4)
1200 Con_Print("prvm_globalget <program name> <global> [<cvar>]\n");
1204 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1207 key = PRVM_ED_FindGlobal(prog, Cmd_Argv(cmd, 2));
1210 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
1214 v = (prvm_eval_t *) &prog->globals.fp[key->ofs];
1215 s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf));
1216 if(Cmd_Argc(cmd) == 4)
1218 cvar_t *cvar = Cvar_FindVar(cmd->cvars, Cmd_Argv(cmd, 3), cmd->cvars_flagsmask);
1220 if(Cvar_Readonly(cvar, "prvm_globalget"))
1222 Cvar_Get(cmd->cvars, Cmd_Argv(cmd, 3), s, cmd->cvars_flagsmask, NULL);
1225 Con_Printf("%s\n", s);
1235 Console command to set a field of a specified edict
1238 static void PRVM_ED_EdictSet_f(cmd_state_t *cmd)
1244 if(Cmd_Argc(cmd) != 5)
1246 Con_Print("prvm_edictset <program name> <edict number> <field> <value>\n");
1250 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1253 ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(cmd, 2)));
1255 if((key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, 3))) == 0)
1256 Con_Printf("Key %s not found!\n", Cmd_Argv(cmd, 3));
1258 PRVM_ED_ParseEpair(prog, ed, key, Cmd_Argv(cmd, 4), true);
1262 ====================
1265 Parses an edict out of the given string, returning the new position
1266 ed should be a properly initialized empty edict.
1267 Used for initial level load and for savegames.
1268 ====================
1270 const char *PRVM_ED_ParseEdict (prvm_prog_t *prog, const char *data, prvm_edict_t *ent)
1280 // go through all the dictionary pairs
1284 if (!COM_ParseToken_Simple(&data, false, false, true))
1285 prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace");
1286 if (developer_entityparsing.integer)
1287 Con_Printf("Key: \"%s\"", com_token);
1288 if (com_token[0] == '}')
1291 // anglehack is to allow QuakeEd to write single scalar angles
1292 // and allow them to be turned into vectors. (FIXME...)
1293 if (!strcmp(com_token, "angle"))
1295 strlcpy (com_token, "angles", sizeof(com_token));
1301 // FIXME: change light to _light to get rid of this hack
1302 if (!strcmp(com_token, "light"))
1303 strlcpy (com_token, "light_lev", sizeof(com_token)); // hack for single light def
1305 strlcpy (keyname, com_token, sizeof(keyname));
1307 // another hack to fix keynames with trailing spaces
1308 n = strlen(keyname);
1309 while (n && keyname[n-1] == ' ')
1316 if (!COM_ParseToken_Simple(&data, false, false, true))
1317 prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace");
1318 if (developer_entityparsing.integer)
1319 Con_Printf(" \"%s\"\n", com_token);
1321 if (com_token[0] == '}')
1322 prog->error_cmd("PRVM_ED_ParseEdict: closing brace without data");
1326 // ignore attempts to set key "" (this problem occurs in nehahra neh1m8.bsp)
1330 // keynames with a leading underscore are used for utility comments,
1331 // and are immediately discarded by quake
1332 if (keyname[0] == '_')
1335 key = PRVM_ED_FindField (prog, keyname);
1338 Con_DPrintf("%s: '%s' is not a field\n", prog->name, keyname);
1345 strlcpy (temp, com_token, sizeof(temp));
1346 dpsnprintf (com_token, sizeof(com_token), "0 %s 0", temp);
1349 if (!PRVM_ED_ParseEpair(prog, ent, key, com_token, strcmp(keyname, "wad") != 0))
1350 prog->error_cmd("PRVM_ED_ParseEdict: parse error");
1354 ent->priv.required->free = true;
1355 ent->priv.required->freetime = host.realtime;
1361 void PRVM_ED_CallPrespawnFunction(prvm_prog_t *prog, prvm_edict_t *ent)
1363 if (PRVM_serverfunction(SV_OnEntityPreSpawnFunction))
1366 PRVM_serverglobalfloat(time) = sv.time;
1367 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1368 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPreSpawnFunction), "QC function SV_OnEntityPreSpawnFunction is missing");
1372 qbool PRVM_ED_CallSpawnFunction(prvm_prog_t *prog, prvm_edict_t *ent, const char *data, const char *start)
1374 const char *funcname;
1376 prvm_eval_t *fulldata = NULL;
1380 // immediately call spawn function, but only if there is a self global and a classname
1382 if (!ent->priv.required->free)
1384 if (!PRVM_alledictstring(ent, classname))
1386 Con_Print("No classname for:\n");
1387 PRVM_ED_Print(prog, ent, NULL);
1388 PRVM_ED_Free (prog, ent);
1392 * This is required for FTE compatibility (FreeCS).
1393 * It copies the key/value pairs themselves into a
1394 * global for QC to parse on its own.
1396 else if (data && start)
1398 if((fulldata = PRVM_ED_FindGlobalEval(prog, "__fullspawndata")))
1402 fulldata->string = PRVM_AllocString(prog, data - start + 1, &spawndata);
1403 for(in = start; in < data; )
1407 *spawndata++ = '\t';
1415 // look for the spawn function
1416 funcname = PRVM_GetString(prog, PRVM_alledictstring(ent, classname));
1417 func = PRVM_ED_FindFunction (prog, va(vabuf, sizeof(vabuf), "spawnfunc_%s", funcname));
1419 if(!PRVM_allglobalfloat(require_spawnfunc_prefix))
1420 func = PRVM_ED_FindFunction (prog, funcname);
1424 // check for OnEntityNoSpawnFunction
1425 if (PRVM_serverfunction(SV_OnEntityNoSpawnFunction))
1428 PRVM_serverglobalfloat(time) = sv.time;
1429 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1430 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityNoSpawnFunction), "QC function SV_OnEntityNoSpawnFunction is missing");
1435 Con_DPrint("No spawn function for:\n");
1436 if (developer.integer > 0) // don't confuse non-developers with errors
1437 PRVM_ED_Print(prog, ent, NULL);
1439 PRVM_ED_Free (prog, ent);
1440 return false; // not included in "inhibited" count
1446 PRVM_serverglobalfloat(time) = sv.time;
1447 PRVM_allglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1448 prog->ExecuteProgram(prog, func - prog->functions, "");
1452 PRVM_ED_Free(prog, ent);
1456 void PRVM_ED_CallPostspawnFunction (prvm_prog_t *prog, prvm_edict_t *ent)
1458 if(!ent->priv.required->free)
1459 if (PRVM_serverfunction(SV_OnEntityPostSpawnFunction))
1462 PRVM_serverglobalfloat(time) = sv.time;
1463 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1464 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPostSpawnFunction), "QC function SV_OnEntityPostSpawnFunction is missing");
1470 PRVM_ED_LoadFromFile
1472 The entities are directly placed in the array, rather than allocated with
1473 PRVM_ED_Alloc, because otherwise an error loading the map would have entity
1474 number references out of order.
1476 Creates a server's entity / program execution context by
1477 parsing textual entity definitions out of an ent file.
1479 Used for both fresh maps and savegame loads. A fresh map would also need
1480 to call PRVM_ED_CallSpawnFunctions () to let the objects initialize themselves.
1483 void PRVM_ED_LoadFromFile (prvm_prog_t *prog, const char *data)
1487 int parsed, inhibited, spawned, died;
1494 prvm_reuseedicts_always_allow = host.realtime;
1501 // parse the opening brace
1502 if (!COM_ParseToken_Simple(&data, false, false, true))
1504 if (com_token[0] != '{')
1505 prog->error_cmd("PRVM_ED_LoadFromFile: %s: found %s when expecting {", prog->name, com_token);
1507 // CHANGED: this is not conform to PR_LoadFromFile
1508 if(prog->loadintoworld)
1510 prog->loadintoworld = false;
1511 ent = PRVM_EDICT_NUM(0);
1514 ent = PRVM_ED_Alloc(prog);
1517 if (ent != prog->edicts) // hack
1518 memset (ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
1520 data = PRVM_ED_ParseEdict (prog, data, ent);
1523 // remove the entity ?
1524 if(!prog->load_edict(prog, ent))
1526 PRVM_ED_Free(prog, ent);
1531 PRVM_ED_CallPrespawnFunction(prog, ent);
1533 if(ent->priv.required->free)
1541 if(!PRVM_ED_CallSpawnFunction(prog, ent, data, start))
1544 PRVM_ED_CallPostspawnFunction(prog, ent);
1547 if (ent->priv.required->free)
1551 Con_DPrintf("%s: %i new entities parsed, %i new inhibited, %i (%i new) spawned (whereas %i removed self, %i stayed)\n", prog->name, parsed, inhibited, prog->num_edicts, spawned, died, spawned - died);
1553 prvm_reuseedicts_always_allow = 0;
1556 static void PRVM_FindOffsets(prvm_prog_t *prog)
1558 // field and global searches use -1 for NULL
1559 memset(&prog->fieldoffsets, -1, sizeof(prog->fieldoffsets));
1560 memset(&prog->globaloffsets, -1, sizeof(prog->globaloffsets));
1561 // function searches use 0 for NULL
1562 memset(&prog->funcoffsets, 0, sizeof(prog->funcoffsets));
1563 #define PRVM_DECLARE_serverglobalfloat(x)
1564 #define PRVM_DECLARE_serverglobalvector(x)
1565 #define PRVM_DECLARE_serverglobalstring(x)
1566 #define PRVM_DECLARE_serverglobaledict(x)
1567 #define PRVM_DECLARE_serverglobalfunction(x)
1568 #define PRVM_DECLARE_clientglobalfloat(x)
1569 #define PRVM_DECLARE_clientglobalvector(x)
1570 #define PRVM_DECLARE_clientglobalstring(x)
1571 #define PRVM_DECLARE_clientglobaledict(x)
1572 #define PRVM_DECLARE_clientglobalfunction(x)
1573 #define PRVM_DECLARE_menuglobalfloat(x)
1574 #define PRVM_DECLARE_menuglobalvector(x)
1575 #define PRVM_DECLARE_menuglobalstring(x)
1576 #define PRVM_DECLARE_menuglobaledict(x)
1577 #define PRVM_DECLARE_menuglobalfunction(x)
1578 #define PRVM_DECLARE_serverfieldfloat(x)
1579 #define PRVM_DECLARE_serverfieldvector(x)
1580 #define PRVM_DECLARE_serverfieldstring(x)
1581 #define PRVM_DECLARE_serverfieldedict(x)
1582 #define PRVM_DECLARE_serverfieldfunction(x)
1583 #define PRVM_DECLARE_clientfieldfloat(x)
1584 #define PRVM_DECLARE_clientfieldvector(x)
1585 #define PRVM_DECLARE_clientfieldstring(x)
1586 #define PRVM_DECLARE_clientfieldedict(x)
1587 #define PRVM_DECLARE_clientfieldfunction(x)
1588 #define PRVM_DECLARE_menufieldfloat(x)
1589 #define PRVM_DECLARE_menufieldvector(x)
1590 #define PRVM_DECLARE_menufieldstring(x)
1591 #define PRVM_DECLARE_menufieldedict(x)
1592 #define PRVM_DECLARE_menufieldfunction(x)
1593 #define PRVM_DECLARE_serverfunction(x)
1594 #define PRVM_DECLARE_clientfunction(x)
1595 #define PRVM_DECLARE_menufunction(x)
1596 #define PRVM_DECLARE_field(x) prog->fieldoffsets.x = PRVM_ED_FindFieldOffset(prog, #x);
1597 #define PRVM_DECLARE_global(x) prog->globaloffsets.x = PRVM_ED_FindGlobalOffset(prog, #x);
1598 #define PRVM_DECLARE_function(x) prog->funcoffsets.x = PRVM_ED_FindFunctionOffset(prog, #x);
1599 #include "prvm_offsets.h"
1600 #undef PRVM_DECLARE_serverglobalfloat
1601 #undef PRVM_DECLARE_serverglobalvector
1602 #undef PRVM_DECLARE_serverglobalstring
1603 #undef PRVM_DECLARE_serverglobaledict
1604 #undef PRVM_DECLARE_serverglobalfunction
1605 #undef PRVM_DECLARE_clientglobalfloat
1606 #undef PRVM_DECLARE_clientglobalvector
1607 #undef PRVM_DECLARE_clientglobalstring
1608 #undef PRVM_DECLARE_clientglobaledict
1609 #undef PRVM_DECLARE_clientglobalfunction
1610 #undef PRVM_DECLARE_menuglobalfloat
1611 #undef PRVM_DECLARE_menuglobalvector
1612 #undef PRVM_DECLARE_menuglobalstring
1613 #undef PRVM_DECLARE_menuglobaledict
1614 #undef PRVM_DECLARE_menuglobalfunction
1615 #undef PRVM_DECLARE_serverfieldfloat
1616 #undef PRVM_DECLARE_serverfieldvector
1617 #undef PRVM_DECLARE_serverfieldstring
1618 #undef PRVM_DECLARE_serverfieldedict
1619 #undef PRVM_DECLARE_serverfieldfunction
1620 #undef PRVM_DECLARE_clientfieldfloat
1621 #undef PRVM_DECLARE_clientfieldvector
1622 #undef PRVM_DECLARE_clientfieldstring
1623 #undef PRVM_DECLARE_clientfieldedict
1624 #undef PRVM_DECLARE_clientfieldfunction
1625 #undef PRVM_DECLARE_menufieldfloat
1626 #undef PRVM_DECLARE_menufieldvector
1627 #undef PRVM_DECLARE_menufieldstring
1628 #undef PRVM_DECLARE_menufieldedict
1629 #undef PRVM_DECLARE_menufieldfunction
1630 #undef PRVM_DECLARE_serverfunction
1631 #undef PRVM_DECLARE_clientfunction
1632 #undef PRVM_DECLARE_menufunction
1633 #undef PRVM_DECLARE_field
1634 #undef PRVM_DECLARE_global
1635 #undef PRVM_DECLARE_function
1640 typedef struct dpfield_s
1647 #define DPFIELDS (sizeof(dpfields) / sizeof(dpfield_t))
1649 dpfield_t dpfields[] =
1660 #define PO_HASHSIZE 16384
1661 typedef struct po_string_s
1664 struct po_string_s *nextonhashchain;
1669 po_string_t *hashtable[PO_HASHSIZE];
1672 static void PRVM_PO_UnparseString(char *out, const char *in, size_t outsize)
1681 case '\a': if(outsize >= 2) { *out++ = '\\'; *out++ = 'a'; outsize -= 2; } break;
1682 case '\b': if(outsize >= 2) { *out++ = '\\'; *out++ = 'b'; outsize -= 2; } break;
1683 case '\t': if(outsize >= 2) { *out++ = '\\'; *out++ = 't'; outsize -= 2; } break;
1684 case '\r': if(outsize >= 2) { *out++ = '\\'; *out++ = 'r'; outsize -= 2; } break;
1685 case '\n': if(outsize >= 2) { *out++ = '\\'; *out++ = 'n'; outsize -= 2; } break;
1686 case '\\': if(outsize >= 2) { *out++ = '\\'; *out++ = '\\'; outsize -= 2; } break;
1687 case '"': if(outsize >= 2) { *out++ = '\\'; *out++ = '"'; outsize -= 2; } break;
1689 if(*in >= 0 && *in <= 0x1F)
1694 *out++ = '0' + ((*in & 0700) >> 6);
1695 *out++ = '0' + ((*in & 0070) >> 3);
1696 *out++ = '0' + (*in & 0007) ;
1713 static void PRVM_PO_ParseString(char *out, const char *in, size_t outsize)
1726 case 'a': if(outsize > 0) { *out++ = '\a'; --outsize; } break;
1727 case 'b': if(outsize > 0) { *out++ = '\b'; --outsize; } break;
1728 case 't': if(outsize > 0) { *out++ = '\t'; --outsize; } break;
1729 case 'r': if(outsize > 0) { *out++ = '\r'; --outsize; } break;
1730 case 'n': if(outsize > 0) { *out++ = '\n'; --outsize; } break;
1731 case '\\': if(outsize > 0) { *out++ = '\\'; --outsize; } break;
1732 case '"': if(outsize > 0) { *out++ = '"'; --outsize; } break;
1733 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
1737 if(*in >= '0' && *in <= '7')
1740 *out = (*out << 3) | (*in - '0');
1743 if(*in >= '0' && *in <= '7')
1746 *out = (*out << 3) | (*in - '0');
1757 if(outsize > 0) { *out++ = *in; --outsize; }
1772 static po_t *PRVM_PO_Load(const char *filename, const char *filename2, mempool_t *pool)
1777 char inbuf[MAX_INPUTLINE];
1778 char decodedbuf[MAX_INPUTLINE];
1781 po_string_t thisstr;
1784 for (i = 0; i < 2; ++i)
1786 const char *buf = (const char *)
1787 FS_LoadFile((i > 0 ? filename : filename2), pool, true, NULL);
1788 // first read filename2, then read filename
1789 // so that progs.dat.de.po wins over common.de.po
1790 // and within file, last item wins
1797 po = (po_t *)Mem_Alloc(pool, sizeof(*po));
1798 memset(po, 0, sizeof(*po));
1801 memset(&thisstr, 0, sizeof(thisstr)); // hush compiler warning
1809 p = strchr(p, '\n');
1815 if(*p == '\r' || *p == '\n')
1820 if(!strncmp(p, "msgid \"", 7))
1825 else if(!strncmp(p, "msgstr \"", 8))
1832 p = strchr(p, '\n');
1842 q = strchr(p, '\n');
1849 if((size_t)(q - p) >= (size_t) sizeof(inbuf))
1851 strlcpy(inbuf, p, q - p); // not - 1, because this adds a NUL
1852 PRVM_PO_ParseString(decodedbuf + decodedpos, inbuf, sizeof(decodedbuf) - decodedpos);
1853 decodedpos += strlen(decodedbuf + decodedpos);
1863 Mem_Free(thisstr.key);
1864 thisstr.key = (char *)Mem_Alloc(pool, decodedpos + 1);
1865 memcpy(thisstr.key, decodedbuf, decodedpos + 1);
1867 else if(decodedpos > 0 && thisstr.key) // skip empty translation results
1869 thisstr.value = (char *)Mem_Alloc(pool, decodedpos + 1);
1870 memcpy(thisstr.value, decodedbuf, decodedpos + 1);
1871 hashindex = CRC_Block((const unsigned char *) thisstr.key, strlen(thisstr.key)) % PO_HASHSIZE;
1872 thisstr.nextonhashchain = po->hashtable[hashindex];
1873 po->hashtable[hashindex] = (po_string_t *)Mem_Alloc(pool, sizeof(thisstr));
1874 memcpy(po->hashtable[hashindex], &thisstr, sizeof(thisstr));
1875 memset(&thisstr, 0, sizeof(thisstr));
1879 Mem_Free((char *) buf);
1884 static const char *PRVM_PO_Lookup(po_t *po, const char *str)
1886 int hashindex = CRC_Block((const unsigned char *) str, strlen(str)) % PO_HASHSIZE;
1887 po_string_t *p = po->hashtable[hashindex];
1890 if(!strcmp(str, p->key))
1892 p = p->nextonhashchain;
1896 static void PRVM_PO_Destroy(po_t *po)
1899 for(i = 0; i < PO_HASHSIZE; ++i)
1901 po_string_t *p = po->hashtable[i];
1905 p = p->nextonhashchain;
1914 void PRVM_LeakTest(prvm_prog_t *prog);
1915 void PRVM_Prog_Reset(prvm_prog_t *prog)
1919 if(prog->tempstringsbuf.cursize)
1920 Mem_Free(prog->tempstringsbuf.data);
1921 prog->tempstringsbuf.cursize = 0;
1922 PRVM_LeakTest(prog);
1923 prog->reset_cmd(prog);
1924 Mem_FreePool(&prog->progs_mempool);
1926 PRVM_PO_Destroy((po_t *) prog->po);
1928 memset(prog,0,sizeof(prvm_prog_t));
1929 prog->break_statement = -1;
1930 prog->watch_global_type = ev_void;
1931 prog->watch_field_type = ev_void;
1939 static void PRVM_LoadLNO( prvm_prog_t *prog, const char *progname ) {
1940 fs_offset_t filesize;
1942 unsigned int *header;
1945 FS_StripExtension( progname, filename, sizeof( filename ) );
1946 strlcat( filename, ".lno", sizeof( filename ) );
1948 lno = FS_LoadFile( filename, tempmempool, false, &filesize );
1954 <Spike> SafeWrite (h, &lnotype, sizeof(int));
1955 <Spike> SafeWrite (h, &version, sizeof(int));
1956 <Spike> SafeWrite (h, &numglobaldefs, sizeof(int));
1957 <Spike> SafeWrite (h, &numpr_globals, sizeof(int));
1958 <Spike> SafeWrite (h, &numfielddefs, sizeof(int));
1959 <Spike> SafeWrite (h, &numstatements, sizeof(int));
1960 <Spike> SafeWrite (h, statement_linenums, numstatements*sizeof(int));
1962 if ((unsigned int)filesize < (6 + prog->progs_numstatements) * sizeof(int))
1968 header = (unsigned int *) lno;
1969 if( header[ 0 ] == *(unsigned int *) "LNOF" &&
1970 LittleLong( header[ 1 ] ) == 1 &&
1971 (unsigned int)LittleLong( header[ 2 ] ) == (unsigned int)prog->progs_numglobaldefs &&
1972 (unsigned int)LittleLong( header[ 3 ] ) == (unsigned int)prog->progs_numglobals &&
1973 (unsigned int)LittleLong( header[ 4 ] ) == (unsigned int)prog->progs_numfielddefs &&
1974 (unsigned int)LittleLong( header[ 5 ] ) == (unsigned int)prog->progs_numstatements )
1976 prog->statement_linenums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1977 memcpy( prog->statement_linenums, header + 6, prog->progs_numstatements * sizeof( int ) );
1979 /* gmqcc suports columnums */
1980 if ((unsigned int)filesize > ((6 + 2 * prog->progs_numstatements) * sizeof( int )))
1982 prog->statement_columnnums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1983 memcpy( prog->statement_columnnums, header + 6 + prog->progs_numstatements, prog->progs_numstatements * sizeof( int ) );
1994 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog);
1995 void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * data, fs_offset_t size, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global)
1998 dprograms_t *dprograms;
2000 dstatement16_t *instatements16;
2001 dstatement32_t *instatements32;
2002 ddef16_t *infielddefs16;
2003 ddef32_t *infielddefs32;
2004 ddef16_t *inglobaldefs16;
2005 ddef32_t *inglobaldefs32;
2008 dfunction_t *infunctions;
2010 fs_offset_t filesize;
2011 int requiredglobalspace;
2029 prog->error_cmd("PRVM_LoadProgs: there is already a %s program loaded!", prog->name );
2031 Host_LockSession(); // all progs can use the session cvar
2032 Crypto_LoadKeys(); // all progs might use the keys at init time
2036 dprograms = (dprograms_t *) data;
2040 dprograms = (dprograms_t *)FS_LoadFile (filename, prog->progs_mempool, false, &filesize);
2041 if (dprograms == NULL || filesize < (fs_offset_t)sizeof(dprograms_t))
2042 prog->error_cmd("PRVM_LoadProgs: couldn't load %s for %s", filename, prog->name);
2043 // TODO bounds check header fields (e.g. numstatements), they must never go behind end of file
2045 prog->profiletime = Sys_DirtyTime();
2046 prog->starttime = host.realtime;
2048 requiredglobalspace = 0;
2049 for (i = 0;i < numrequiredglobals;i++)
2050 requiredglobalspace += required_global[i].type == ev_vector ? 3 : 1;
2052 prog->filecrc = CRC_Block((unsigned char *)dprograms, filesize);
2054 // byte swap the header
2055 prog->progs_version = LittleLong(dprograms->version);
2056 prog->progs_crc = LittleLong(dprograms->crc);
2057 if (prog->progs_version == 7)
2059 dprograms_v7_t *v7 = (dprograms_v7_t*)dprograms;
2060 structtype = LittleLong(v7->secondaryversion);
2061 if (structtype == PROG_SECONDARYVERSION16 ||
2062 structtype == PROG_SECONDARYVERSION32) // barely supported
2063 Con_Printf(CON_WARN "WARNING: %s: %s targets FTEQW, for which support is incomplete. Proceed at your own risk.\n", prog->name, filename);
2065 prog->error_cmd("%s: %s targets unknown engine", prog->name, filename);
2067 if (v7->numbodylessfuncs != 0 || v7->numtypes != 0 || v7->blockscompressed != 0)
2068 prog->error_cmd("%s: %s uses unsupported features.", prog->name, filename);
2070 else if (prog->progs_version != PROG_VERSION)
2071 prog->error_cmd("%s: %s has wrong version number (%i should be %i)", prog->name, filename, prog->progs_version, PROG_VERSION);
2072 instatements16 = (dstatement16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_statements));
2073 instatements32 = (dstatement32_t *)instatements16;
2074 prog->progs_numstatements = LittleLong(dprograms->numstatements);
2075 inglobaldefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globaldefs));
2076 inglobaldefs32 = (ddef32_t *)inglobaldefs16;
2077 prog->progs_numglobaldefs = LittleLong(dprograms->numglobaldefs);
2078 infielddefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_fielddefs));
2079 infielddefs32 = (ddef32_t *)infielddefs16;
2080 prog->progs_numfielddefs = LittleLong(dprograms->numfielddefs);
2081 infunctions = (dfunction_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_functions));
2082 prog->progs_numfunctions = LittleLong(dprograms->numfunctions);
2083 instrings = (char *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_strings));
2084 prog->progs_numstrings = LittleLong(dprograms->numstrings);
2085 inglobals = (int *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globals));
2086 prog->progs_numglobals = LittleLong(dprograms->numglobals);
2087 prog->progs_entityfields = LittleLong(dprograms->entityfields);
2089 prog->numstatements = prog->progs_numstatements;
2090 prog->numglobaldefs = prog->progs_numglobaldefs;
2091 prog->numfielddefs = prog->progs_numfielddefs;
2092 prog->numfunctions = prog->progs_numfunctions;
2093 prog->numstrings = prog->progs_numstrings;
2094 prog->numglobals = prog->progs_numglobals;
2095 prog->entityfields = prog->progs_entityfields;
2097 if (LittleLong(dprograms->ofs_strings) + prog->progs_numstrings > (int)filesize)
2098 prog->error_cmd("%s: %s strings go past end of file", prog->name, filename);
2099 prog->strings = (char *)Mem_Alloc(prog->progs_mempool, prog->progs_numstrings);
2100 memcpy(prog->strings, instrings, prog->progs_numstrings);
2101 prog->stringssize = prog->progs_numstrings;
2103 prog->numknownstrings = 0;
2104 prog->maxknownstrings = 0;
2105 prog->knownstrings = NULL;
2106 prog->knownstrings_flags = NULL;
2108 Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64);
2110 // we need to expand the globaldefs and fielddefs to include engine defs
2111 prog->globaldefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobaldefs + numrequiredglobals) * sizeof(mdef_t));
2112 prog->globals.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobals + requiredglobalspace + 2) * sizeof(prvm_vec_t));
2113 // + 2 is because of an otherwise occurring overrun in RETURN instruction
2114 // when trying to return the last or second-last global
2115 // (RETURN always returns a vector, there is no RETURN_F instruction)
2116 prog->fielddefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numfielddefs + numrequiredfields) * sizeof(mdef_t));
2117 // we need to convert the statements to our memory format
2118 prog->statements = (mstatement_t *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(mstatement_t));
2119 // allocate space for profiling statement usage
2120 prog->statement_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2121 prog->explicit_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2122 // functions need to be converted to the memory format
2123 prog->functions = (mfunction_t *)Mem_Alloc(prog->progs_mempool, sizeof(mfunction_t) * prog->progs_numfunctions);
2125 for (i = 0;i < prog->progs_numfunctions;i++)
2127 prog->functions[i].first_statement = LittleLong(infunctions[i].first_statement);
2128 prog->functions[i].parm_start = LittleLong(infunctions[i].parm_start);
2129 prog->functions[i].s_name = LittleLong(infunctions[i].s_name);
2130 prog->functions[i].s_file = LittleLong(infunctions[i].s_file);
2131 prog->functions[i].numparms = LittleLong(infunctions[i].numparms);
2132 prog->functions[i].locals = LittleLong(infunctions[i].locals);
2133 memcpy(prog->functions[i].parm_size, infunctions[i].parm_size, sizeof(infunctions[i].parm_size));
2134 if(prog->functions[i].first_statement >= prog->numstatements)
2135 prog->error_cmd("PRVM_LoadProgs: out of bounds function statement (function %d) in %s", i, prog->name);
2136 // TODO bounds check parm_start, s_name, s_file, numparms, locals, parm_size
2139 // copy the globaldefs to the new globaldefs list
2142 case PROG_SECONDARYVERSION32:
2143 for (i=0 ; i<prog->numglobaldefs ; i++)
2145 prog->globaldefs[i].type = LittleLong(inglobaldefs32[i].type);
2146 prog->globaldefs[i].ofs = LittleLong(inglobaldefs32[i].ofs);
2147 prog->globaldefs[i].s_name = LittleLong(inglobaldefs32[i].s_name);
2148 // TODO bounds check ofs, s_name
2152 for (i=0 ; i<prog->numglobaldefs ; i++)
2154 prog->globaldefs[i].type = (unsigned short)LittleShort(inglobaldefs16[i].type);
2155 prog->globaldefs[i].ofs = (unsigned short)LittleShort(inglobaldefs16[i].ofs);
2156 prog->globaldefs[i].s_name = LittleLong(inglobaldefs16[i].s_name);
2157 // TODO bounds check ofs, s_name
2162 // append the required globals
2163 for (i = 0;i < numrequiredglobals;i++)
2165 prog->globaldefs[prog->numglobaldefs].type = required_global[i].type;
2166 prog->globaldefs[prog->numglobaldefs].ofs = prog->numglobals;
2167 prog->globaldefs[prog->numglobaldefs].s_name = PRVM_SetEngineString(prog, required_global[i].name);
2168 if (prog->globaldefs[prog->numglobaldefs].type == ev_vector)
2169 prog->numglobals += 3;
2172 prog->numglobaldefs++;
2175 // copy the progs fields to the new fields list
2178 case PROG_SECONDARYVERSION32:
2179 for (i = 0;i < prog->numfielddefs;i++)
2181 prog->fielddefs[i].type = LittleLong(infielddefs32[i].type);
2182 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2183 prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name);
2184 prog->fielddefs[i].ofs = LittleLong(infielddefs32[i].ofs);
2185 prog->fielddefs[i].s_name = LittleLong(infielddefs32[i].s_name);
2186 // TODO bounds check ofs, s_name
2190 for (i = 0;i < prog->numfielddefs;i++)
2192 prog->fielddefs[i].type = (unsigned short)LittleShort(infielddefs16[i].type);
2193 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2194 prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name);
2195 prog->fielddefs[i].ofs = (unsigned short)LittleShort(infielddefs16[i].ofs);
2196 prog->fielddefs[i].s_name = LittleLong(infielddefs16[i].s_name);
2197 // TODO bounds check ofs, s_name
2202 // append the required fields
2203 for (i = 0;i < numrequiredfields;i++)
2205 prog->fielddefs[prog->numfielddefs].type = required_field[i].type;
2206 prog->fielddefs[prog->numfielddefs].ofs = prog->entityfields;
2207 prog->fielddefs[prog->numfielddefs].s_name = PRVM_SetEngineString(prog, required_field[i].name);
2208 if (prog->fielddefs[prog->numfielddefs].type == ev_vector)
2209 prog->entityfields += 3;
2211 prog->entityfields++;
2212 prog->numfielddefs++;
2215 // LadyHavoc: TODO: reorder globals to match engine struct
2216 // LadyHavoc: TODO: reorder fields to match engine struct
2217 #define remapglobal(index) (index)
2218 #define remapfield(index) (index)
2221 // FIXME: LadyHavoc: this uses a crude way to identify integer constants, rather than checking for matching globaldefs and checking their type
2222 for (i = 0;i < prog->progs_numglobals;i++)
2224 u.i = LittleLong(inglobals[i]);
2225 // most globals are 0, we only need to deal with the ones that are not
2228 d = u.i & 0xFF800000;
2229 if ((d == 0xFF800000) || (d == 0))
2231 // Looks like an integer (expand to int64)
2232 prog->globals.ip[remapglobal(i)] = u.i;
2236 // Looks like a float (expand to double)
2237 prog->globals.fp[remapglobal(i)] = u.f;
2242 // copy, remap globals in statements, bounds check
2243 for (i = 0;i < prog->progs_numstatements;i++)
2247 case PROG_SECONDARYVERSION32:
2248 op = (opcode_t)LittleLong(instatements32[i].op);
2249 a = (unsigned int)LittleLong(instatements32[i].a);
2250 b = (unsigned int)LittleLong(instatements32[i].b);
2251 c = (unsigned int)LittleLong(instatements32[i].c);
2254 op = (opcode_t)LittleShort(instatements16[i].op);
2255 a = (unsigned short)LittleShort(instatements16[i].a);
2256 b = (unsigned short)LittleShort(instatements16[i].b);
2257 c = (unsigned short)LittleShort(instatements16[i].c);
2265 if (a >= prog->progs_numglobals || b + i < 0 || b + i >= prog->progs_numstatements)
2266 prog->error_cmd("PRVM_LoadProgs: out of bounds IF/IFNOT (statement %d) in %s", i, prog->name);
2267 prog->statements[i].op = op;
2268 prog->statements[i].operand[0] = remapglobal(a);
2269 prog->statements[i].operand[1] = -1;
2270 prog->statements[i].operand[2] = -1;
2271 prog->statements[i].jumpabsolute = i + b;
2275 if (a + i < 0 || a + i >= prog->progs_numstatements)
2276 prog->error_cmd("PRVM_LoadProgs: out of bounds GOTO (statement %d) in %s", i, prog->name);
2277 prog->statements[i].op = op;
2278 prog->statements[i].operand[0] = -1;
2279 prog->statements[i].operand[1] = -1;
2280 prog->statements[i].operand[2] = -1;
2281 prog->statements[i].jumpabsolute = i + a;
2284 Con_DPrintf("PRVM_LoadProgs: unknown opcode %d at statement %d in %s\n", (int)op, i, prog->name);
2286 //make sure its something well defined.
2287 prog->statements[i].op = OP_BOUNDCHECK;
2288 prog->statements[i].operand[0] = 0;
2289 prog->statements[i].operand[1] =
2290 prog->statements[i].operand[2] = op;
2291 prog->statements[i].jumpabsolute = -1;
2346 case OP_GSTOREP_ENT:
2347 case OP_GSTOREP_FLD:
2349 case OP_GSTOREP_FNC:
2351 // case OP_GADDRESS:
2361 // global global global
2396 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals || c >= prog->progs_numglobals)
2397 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d)", i);
2398 prog->statements[i].op = op;
2399 prog->statements[i].operand[0] = remapglobal(a);
2400 prog->statements[i].operand[1] = remapglobal(b);
2401 prog->statements[i].operand[2] = remapglobal(c);
2402 prog->statements[i].jumpabsolute = -1;
2404 // global none global
2410 if (a >= prog->progs_numglobals || c >= prog->progs_numglobals)
2411 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2412 prog->statements[i].op = op;
2413 prog->statements[i].operand[0] = remapglobal(a);
2414 prog->statements[i].operand[1] = -1;
2415 prog->statements[i].operand[2] = remapglobal(c);
2416 prog->statements[i].jumpabsolute = -1;
2424 if (c) //Spike -- DP is alergic to pointers in QC. Try to avoid too many nasty surprises.
2425 Con_DPrintf("PRVM_LoadProgs: storep-with-offset is not permitted in %s\n", prog->name);
2435 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals)
2436 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2437 prog->statements[i].op = op;
2438 prog->statements[i].operand[0] = remapglobal(a);
2439 prog->statements[i].operand[1] = remapglobal(b);
2440 prog->statements[i].operand[2] = -1;
2441 prog->statements[i].jumpabsolute = -1;
2445 if ( a < prog->progs_numglobals)
2446 if ( prog->globals.ip[remapglobal(a)] >= 0 )
2447 if ( prog->globals.ip[remapglobal(a)] < prog->progs_numfunctions )
2448 if ( prog->functions[prog->globals.ip[remapglobal(a)]].first_statement == -642 )
2449 ++prog->numexplicitcoveragestatements;
2460 if ( a >= prog->progs_numglobals)
2461 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2462 if (b || c) //Spike -- added this check just as a diagnostic...
2463 Con_DPrintf("PRVM_LoadProgs: unexpected offset on call opcode in %s. Hexen2 format is not supported\n", prog->name);
2464 prog->statements[i].op = op;
2465 prog->statements[i].operand[0] = remapglobal(a);
2466 prog->statements[i].operand[1] = -1;
2467 prog->statements[i].operand[2] = -1;
2468 prog->statements[i].jumpabsolute = -1;
2472 if(prog->numstatements < 1)
2474 prog->error_cmd("PRVM_LoadProgs: empty program in %s", prog->name);
2476 else switch(prog->statements[prog->numstatements - 1].op)
2483 prog->error_cmd("PRVM_LoadProgs: program may fall off the edge (does not end with RETURN, GOTO or DONE) in %s", prog->name);
2487 // we're done with the file now
2489 Mem_Free(dprograms);
2492 // check required functions
2493 for(i=0 ; i < numrequiredfunc ; i++)
2494 if(PRVM_ED_FindFunction(prog, required_func[i]) == 0)
2495 prog->error_cmd("%s: %s not found in %s",prog->name, required_func[i], filename);
2497 PRVM_LoadLNO(prog, filename);
2499 PRVM_Init_Exec(prog);
2501 if(*prvm_language.string)
2502 // in CSQC we really shouldn't be able to change how stuff works... sorry for now
2503 // later idea: include a list of authorized .po file checksums with the csprogs
2505 qbool deftrans = prog == CLVM_prog;
2506 const char *realfilename = (prog != CLVM_prog ? filename : csqc_progname.string);
2507 if(deftrans) // once we have dotranslate_ strings, ALWAYS use the opt-in method!
2509 for (i=0 ; i<prog->numglobaldefs ; i++)
2512 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2513 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2514 if(name && !strncmp(name, "dotranslate_", 12))
2521 if(!strcmp(prvm_language.string, "dump"))
2523 qfile_t *f = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.pot", realfilename), "w", false);
2524 Con_Printf("Dumping to %s.pot\n", realfilename);
2527 for (i=0 ; i<prog->numglobaldefs ; i++)
2530 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2531 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2532 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2534 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2535 const char *value = PRVM_GetString(prog, val->string);
2538 char buf[MAX_INPUTLINE];
2539 PRVM_PO_UnparseString(buf, value, sizeof(buf));
2540 FS_Printf(f, "msgid \"%s\"\nmsgstr \"\"\n\n", buf);
2549 po_t *po = PRVM_PO_Load(
2550 va(vabuf, sizeof(vabuf), "%s.%s.po", realfilename, prvm_language.string),
2551 va(vabuf2, sizeof(vabuf2), "common.%s.po", prvm_language.string),
2552 prog->progs_mempool);
2555 for (i=0 ; i<prog->numglobaldefs ; i++)
2558 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2559 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2560 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2562 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2563 const char *value = PRVM_GetString(prog, val->string);
2566 value = PRVM_PO_Lookup(po, value);
2568 val->string = PRVM_SetEngineString(prog, value);
2576 for (cvar = prog->console_cmd->cvars->vars; cvar; cvar = cvar->next)
2577 cvar->globaldefindex[prog - prvm_prog_list] = -1;
2579 for (i=0 ; i<prog->numglobaldefs ; i++)
2582 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2583 //Con_Printf("found var %s\n", name);
2585 && !strncmp(name, "autocvar_", 9)
2586 && !(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
2589 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2590 cvar = Cvar_FindVar(prog->console_cmd->cvars, name + 9, prog->console_cmd->cvars_flagsmask);
2591 //Con_Printf("PRVM_LoadProgs: autocvar global %s in %s, processing...\n", name, prog->name);
2598 Con_DPrintf("PRVM_LoadProgs: no cvar for autocvar global %s in %s, creating...\n", name, prog->name);
2599 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2602 if((float)((int)(val->_float)) == val->_float)
2603 dpsnprintf(buf, sizeof(buf), "%i", (int)(val->_float));
2608 for (int precision = 7; precision <= 9; ++precision) {
2609 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2610 if ((float)atof(buf) == f) {
2618 for (i = 0; i < 3; ++i)
2622 for (int precision = 7; precision <= 9; ++precision) {
2623 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2624 if ((float)atof(buf) == f) {
2625 prec[i] = precision;
2630 dpsnprintf(buf, sizeof(buf), "%.*g %.*g %.*g", prec[0], val->vector[0], prec[1], val->vector[1], prec[2], val->vector[2]);
2634 value = PRVM_GetString(prog, val->string);
2637 Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name);
2640 cvar = Cvar_Get(prog->console_cmd->cvars, name + 9, value, prog->console_cmd->cvars_flagsmask, NULL);
2641 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2643 val->string = PRVM_SetEngineString(prog, cvar->string);
2644 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2647 prog->error_cmd("PRVM_LoadProgs: could not create cvar for autocvar global %s in %s", name, prog->name);
2648 cvar->globaldefindex[prog - prvm_prog_list] = i;
2650 else if((cvar->flags & CF_PRIVATE) == 0)
2652 // MUST BE SYNCED WITH cvar.c Cvar_Set
2655 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2658 val->_float = cvar->value;
2662 VectorClear(val->vector);
2663 for (j = 0;j < 3;j++)
2665 while (*s && ISWHITESPACE(*s))
2669 val->vector[j] = atof(s);
2670 while (!ISWHITESPACE(*s))
2677 val->string = PRVM_SetEngineString(prog, cvar->string);
2678 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2681 Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name);
2684 cvar->globaldefindex[prog - prvm_prog_list] = i;
2687 Con_Printf("PRVM_LoadProgs: private cvar for autocvar global %s in %s\n", name, prog->name);
2693 prog->loaded = true;
2695 PRVM_UpdateBreakpoints(prog);
2697 // set flags & mdef_ts in prog
2701 PRVM_FindOffsets(prog);
2703 prog->init_cmd(prog);
2706 PRVM_MEM_Alloc(prog);
2708 Con_Printf("%s: program loaded (crc %i, size %iK)\n", prog->name, prog->filecrc, (int)(filesize/1024));
2710 // Inittime is at least the time when this function finished. However,
2711 // later events may bump it.
2712 prog->inittime = host.realtime;
2716 static void PRVM_Fields_f(cmd_state_t *cmd)
2719 int i, j, ednum, used, usedamount;
2721 char tempstring[MAX_INPUTLINE], tempstring2[260];
2731 Con_Print("no progs loaded\n");
2736 if(Cmd_Argc(cmd) != 2)
2738 Con_Print("prvm_fields <program name>\n");
2742 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2745 counts = (int *)Mem_Alloc(tempmempool, prog->numfielddefs * sizeof(int));
2746 for (ednum = 0;ednum < prog->max_edicts;ednum++)
2748 ed = PRVM_EDICT_NUM(ednum);
2749 if (ed->priv.required->free)
2751 for (i = 1;i < prog->numfielddefs;i++)
2753 d = &prog->fielddefs[i];
2754 name = PRVM_GetString(prog, d->s_name);
2755 if (name[strlen(name)-2] == '_')
2756 continue; // skip _x, _y, _z vars
2757 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
2758 // if the value is still all 0, skip the field
2759 for (j = 0;j < prvm_type_size[d->type & ~DEF_SAVEGLOBAL];j++)
2761 if (val->ivector[j])
2772 for (i = 0;i < prog->numfielddefs;i++)
2774 d = &prog->fielddefs[i];
2775 name = PRVM_GetString(prog, d->s_name);
2776 if (name[strlen(name)-2] == '_')
2777 continue; // skip _x, _y, _z vars
2778 switch(d->type & ~DEF_SAVEGLOBAL)
2781 strlcat(tempstring, "string ", sizeof(tempstring));
2784 strlcat(tempstring, "entity ", sizeof(tempstring));
2787 strlcat(tempstring, "function ", sizeof(tempstring));
2790 strlcat(tempstring, "field ", sizeof(tempstring));
2793 strlcat(tempstring, "void ", sizeof(tempstring));
2796 strlcat(tempstring, "float ", sizeof(tempstring));
2799 strlcat(tempstring, "vector ", sizeof(tempstring));
2802 strlcat(tempstring, "pointer ", sizeof(tempstring));
2805 dpsnprintf (tempstring2, sizeof(tempstring2), "bad type %i ", d->type & ~DEF_SAVEGLOBAL);
2806 strlcat(tempstring, tempstring2, sizeof(tempstring));
2809 if (strlen(name) > sizeof(tempstring2)-4)
2811 memcpy (tempstring2, name, sizeof(tempstring2)-4);
2812 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
2813 tempstring2[sizeof(tempstring2)-1] = 0;
2816 strlcat(tempstring, name, sizeof(tempstring));
2817 for (j = (int)strlen(name);j < 25;j++)
2818 strlcat(tempstring, " ", sizeof(tempstring));
2819 dpsnprintf(tempstring2, sizeof(tempstring2), "%5d", counts[i]);
2820 strlcat(tempstring, tempstring2, sizeof(tempstring));
2821 strlcat(tempstring, "\n", sizeof(tempstring));
2822 if (strlen(tempstring) >= sizeof(tempstring)/2)
2824 Con_Print(tempstring);
2830 usedamount += prvm_type_size[d->type & ~DEF_SAVEGLOBAL];
2834 Con_Printf("%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", prog->name, prog->entityfields, used, prog->entityfields * 4, usedamount * 4, prog->max_edicts, prog->entityfields * 4 * prog->max_edicts, usedamount * 4 * prog->max_edicts);
2837 static void PRVM_Globals_f(cmd_state_t *cmd)
2841 const char *wildcard;
2847 Con_Print("no progs loaded\n");
2850 if(Cmd_Argc (cmd) < 2 || Cmd_Argc(cmd) > 3)
2852 Con_Print("prvm_globals <program name> <optional name wildcard>\n");
2856 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2859 if( Cmd_Argc(cmd) == 3)
2860 wildcard = Cmd_Argv(cmd, 2);
2864 Con_Printf("%s :", prog->name);
2866 for (i = 0;i < prog->numglobaldefs;i++)
2869 if( !matchpattern( PRVM_GetString(prog, prog->globaldefs[i].s_name), wildcard, 1) )
2874 Con_Printf("%s\n", PRVM_GetString(prog, prog->globaldefs[i].s_name));
2876 Con_Printf("%i global variables, %i culled, totalling %i bytes\n", prog->numglobals, numculled, prog->numglobals * 4);
2884 static void PRVM_Global_f(cmd_state_t *cmd)
2888 char valuebuf[MAX_INPUTLINE];
2889 if( Cmd_Argc(cmd) != 3 ) {
2890 Con_Printf( "prvm_global <program name> <global name>\n" );
2894 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2897 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2899 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2901 Con_Printf( "%s: %s\n", Cmd_Argv(cmd, 2), PRVM_ValueString( prog, (etype_t)global->type, PRVM_GLOBALFIELDVALUE(global->ofs), valuebuf, sizeof(valuebuf) ) );
2909 static void PRVM_GlobalSet_f(cmd_state_t *cmd)
2913 if( Cmd_Argc(cmd) != 4 ) {
2914 Con_Printf( "prvm_globalset <program name> <global name> <value>\n" );
2918 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2921 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2923 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2925 PRVM_ED_ParseEpair( prog, NULL, global, Cmd_Argv(cmd, 3), true );
2929 ======================
2930 Break- and Watchpoints
2931 ======================
2935 char break_statement[256];
2936 char watch_global[256];
2938 char watch_field[256];
2941 static debug_data_t debug_data[PRVM_PROG_MAX];
2943 void PRVM_Breakpoint(prvm_prog_t *prog, int stack_index, const char *text)
2946 Con_Printf("PRVM_Breakpoint: %s\n", text);
2947 PRVM_PrintState(prog, stack_index);
2948 if (prvm_breakpointdump.integer)
2949 SV_Savegame_to(prog, va(vabuf, sizeof(vabuf), "breakpoint-%s.dmp", prog->name));
2952 void PRVM_Watchpoint(prvm_prog_t *prog, int stack_index, const char *text, etype_t type, prvm_eval_t *o, prvm_eval_t *n)
2954 size_t sz = sizeof(prvm_vec_t) * ((type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
2955 if (memcmp(o, n, sz))
2958 char valuebuf_o[128];
2959 char valuebuf_n[128];
2960 PRVM_UglyValueString(prog, type, o, valuebuf_o, sizeof(valuebuf_o));
2961 PRVM_UglyValueString(prog, type, n, valuebuf_n, sizeof(valuebuf_n));
2962 dpsnprintf(buf, sizeof(buf), "%s: %s -> %s", text, valuebuf_o, valuebuf_n);
2963 PRVM_Breakpoint(prog, stack_index, buf);
2968 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog)
2970 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
2973 if (debug->break_statement[0])
2975 if (debug->break_statement[0] >= '0' && debug->break_statement[0] <= '9')
2977 prog->break_statement = atoi(debug->break_statement);
2978 prog->break_stack_index = 0;
2983 func = PRVM_ED_FindFunction (prog, debug->break_statement);
2986 Con_Printf("%s progs: no function or statement named %s to break on!\n", prog->name, debug->break_statement);
2987 prog->break_statement = -1;
2991 prog->break_statement = func->first_statement;
2992 prog->break_stack_index = 1;
2995 if (prog->break_statement >= -1)
2996 Con_Printf("%s progs: breakpoint is at statement %d\n", prog->name, prog->break_statement);
2999 prog->break_statement = -1;
3001 if (debug->watch_global[0])
3003 mdef_t *global = PRVM_ED_FindGlobal( prog, debug->watch_global );
3006 Con_Printf( "%s progs: no global named '%s' to watch!\n", prog->name, debug->watch_global );
3007 prog->watch_global_type = ev_void;
3011 size_t sz = sizeof(prvm_vec_t) * ((global->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3012 prog->watch_global = global->ofs;
3013 prog->watch_global_type = (etype_t)global->type;
3014 memcpy(&prog->watch_global_value, PRVM_GLOBALFIELDVALUE(prog->watch_global), sz);
3016 if (prog->watch_global_type != ev_void)
3017 Con_Printf("%s progs: global watchpoint is at global index %d\n", prog->name, prog->watch_global);
3020 prog->watch_global_type = ev_void;
3022 if (debug->watch_field[0])
3024 mdef_t *field = PRVM_ED_FindField( prog, debug->watch_field );
3027 Con_Printf( "%s progs: no field named '%s' to watch!\n", prog->name, debug->watch_field );
3028 prog->watch_field_type = ev_void;
3032 size_t sz = sizeof(prvm_vec_t) * ((field->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3033 prog->watch_edict = debug->watch_edict;
3034 prog->watch_field = field->ofs;
3035 prog->watch_field_type = (etype_t)field->type;
3036 if (prog->watch_edict < prog->num_edicts)
3037 memcpy(&prog->watch_edictfield_value, PRVM_EDICTFIELDVALUE(PRVM_EDICT_NUM(prog->watch_edict), prog->watch_field), sz);
3039 memset(&prog->watch_edictfield_value, 0, sz);
3041 if (prog->watch_edict != ev_void)
3042 Con_Printf("%s progs: edict field watchpoint is at edict %d field index %d\n", prog->name, prog->watch_edict, prog->watch_field);
3045 prog->watch_field_type = ev_void;
3048 static void PRVM_Breakpoint_f(cmd_state_t *cmd)
3052 if( Cmd_Argc(cmd) == 2 ) {
3053 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3056 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3057 debug->break_statement[0] = 0;
3059 PRVM_UpdateBreakpoints(prog);
3062 if( Cmd_Argc(cmd) != 3 ) {
3063 Con_Printf( "prvm_breakpoint <program name> <function name | statement>\n" );
3067 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3071 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3072 strlcpy(debug->break_statement, Cmd_Argv(cmd, 2), sizeof(debug->break_statement));
3074 PRVM_UpdateBreakpoints(prog);
3077 static void PRVM_GlobalWatchpoint_f(cmd_state_t *cmd)
3081 if( Cmd_Argc(cmd) == 2 ) {
3082 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3085 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3086 debug->watch_global[0] = 0;
3088 PRVM_UpdateBreakpoints(prog);
3091 if( Cmd_Argc(cmd) != 3 ) {
3092 Con_Printf( "prvm_globalwatchpoint <program name> <global name>\n" );
3096 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3100 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3101 strlcpy(debug->watch_global, Cmd_Argv(cmd, 2), sizeof(debug->watch_global));
3103 PRVM_UpdateBreakpoints(prog);
3106 static void PRVM_EdictWatchpoint_f(cmd_state_t *cmd)
3110 if( Cmd_Argc(cmd) == 2 ) {
3111 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3114 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3115 debug->watch_field[0] = 0;
3117 PRVM_UpdateBreakpoints(prog);
3120 if( Cmd_Argc(cmd) != 4 ) {
3121 Con_Printf( "prvm_edictwatchpoint <program name> <edict number> <field name>\n" );
3125 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3129 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3130 debug->watch_edict = atoi(Cmd_Argv(cmd, 2));
3131 strlcpy(debug->watch_field, Cmd_Argv(cmd, 3), sizeof(debug->watch_field));
3133 PRVM_UpdateBreakpoints(prog);
3141 void PRVM_Init (void)
3143 Cmd_AddCommand(CF_SHARED, "prvm_edict", PRVM_ED_PrintEdict_f, "print all data about an entity number in the selected VM (server, client, menu)");
3144 Cmd_AddCommand(CF_SHARED, "prvm_edicts", PRVM_ED_PrintEdicts_f, "prints all data about all entities in the selected VM (server, client, menu)");
3145 Cmd_AddCommand(CF_SHARED, "prvm_edictcount", PRVM_ED_Count_f, "prints number of active entities in the selected VM (server, client, menu)");
3146 Cmd_AddCommand(CF_SHARED, "prvm_profile", PRVM_Profile_f, "prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu)");
3147 Cmd_AddCommand(CF_SHARED, "prvm_childprofile", PRVM_ChildProfile_f, "prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu), sorted by time taken in function with child calls");
3148 Cmd_AddCommand(CF_SHARED, "prvm_callprofile", PRVM_CallProfile_f, "prints execution statistics about the most time consuming QuakeC calls from the engine in the selected VM (server, client, menu)");
3149 Cmd_AddCommand(CF_SHARED, "prvm_fields", PRVM_Fields_f, "prints usage statistics on properties (how many entities have non-zero values) in the selected VM (server, client, menu)");
3150 Cmd_AddCommand(CF_SHARED, "prvm_globals", PRVM_Globals_f, "prints all global variables in the selected VM (server, client, menu)");
3151 Cmd_AddCommand(CF_SHARED, "prvm_global", PRVM_Global_f, "prints value of a specified global variable in the selected VM (server, client, menu)");
3152 Cmd_AddCommand(CF_SHARED, "prvm_globalset", PRVM_GlobalSet_f, "sets value of a specified global variable in the selected VM (server, client, menu)");
3153 Cmd_AddCommand(CF_SHARED, "prvm_edictset", PRVM_ED_EdictSet_f, "changes value of a specified property of a specified entity in the selected VM (server, client, menu)");
3154 Cmd_AddCommand(CF_SHARED, "prvm_edictget", PRVM_ED_EdictGet_f, "retrieves the value of a specified property of a specified entity in the selected VM (server, client menu) into a cvar or to the console");
3155 Cmd_AddCommand(CF_SHARED, "prvm_globalget", PRVM_ED_GlobalGet_f, "retrieves the value of a specified global variable in the selected VM (server, client menu) into a cvar or to the console");
3156 Cmd_AddCommand(CF_SHARED, "prvm_printfunction", PRVM_PrintFunction_f, "prints a disassembly (QuakeC instructions) of the specified function in the selected VM (server, client, menu)");
3157 Cmd_AddCommand(CF_SHARED, "cl_cmd", PRVM_GameCommand_Client_f, "calls the client QC function GameCommand with the supplied string as argument");
3158 Cmd_AddCommand(CF_SHARED, "menu_cmd", PRVM_GameCommand_Menu_f, "calls the menu QC function GameCommand with the supplied string as argument");
3159 Cmd_AddCommand(CF_SHARED, "sv_cmd", PRVM_GameCommand_Server_f, "calls the server QC function GameCommand with the supplied string as argument");
3160 Cmd_AddCommand(CF_SHARED, "prvm_breakpoint", PRVM_Breakpoint_f, "marks a statement or function as breakpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear breakpoint");
3161 Cmd_AddCommand(CF_SHARED, "prvm_globalwatchpoint", PRVM_GlobalWatchpoint_f, "marks a global as watchpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear watchpoint");
3162 Cmd_AddCommand(CF_SHARED, "prvm_edictwatchpoint", PRVM_EdictWatchpoint_f, "marks an entity field as watchpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear watchpoint");
3164 Cvar_RegisterVariable (&prvm_language);
3165 Cvar_RegisterVariable (&prvm_traceqc);
3166 Cvar_RegisterVariable (&prvm_statementprofiling);
3167 Cvar_RegisterVariable (&prvm_timeprofiling);
3168 Cvar_RegisterVariable (&prvm_coverage);
3169 Cvar_RegisterVariable (&prvm_backtraceforwarnings);
3170 Cvar_RegisterVariable (&prvm_leaktest);
3171 Cvar_RegisterVariable (&prvm_leaktest_follow_targetname);
3172 Cvar_RegisterVariable (&prvm_leaktest_ignore_classnames);
3173 Cvar_RegisterVariable (&prvm_errordump);
3174 Cvar_RegisterVariable (&prvm_breakpointdump);
3175 Cvar_RegisterVariable (&prvm_reuseedicts_startuptime);
3176 Cvar_RegisterVariable (&prvm_reuseedicts_neverinsameframe);
3177 Cvar_RegisterVariable (&prvm_garbagecollection_enable);
3178 Cvar_RegisterVariable (&prvm_garbagecollection_notify);
3179 Cvar_RegisterVariable (&prvm_garbagecollection_scan_limit);
3180 Cvar_RegisterVariable (&prvm_garbagecollection_strings);
3181 Cvar_RegisterVariable (&prvm_stringdebug);
3183 // COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!)
3184 prvm_runawaycheck = !Sys_CheckParm("-norunaway");
3194 void PRVM_Prog_Init(prvm_prog_t *prog, cmd_state_t *cmd)
3196 PRVM_Prog_Reset(prog);
3197 prog->leaktest_active = prvm_leaktest.integer != 0;
3198 prog->console_cmd = cmd;
3201 // LadyHavoc: turned PRVM_EDICT_NUM into a #define for speed reasons
3202 unsigned int PRVM_EDICT_NUM_ERROR(prvm_prog_t *prog, unsigned int n, const char *filename, int fileline)
3204 prog->error_cmd("PRVM_EDICT_NUM: %s: bad number %i (called at %s:%i)", prog->name, n, filename, fileline);
3208 #define PRVM_KNOWNSTRINGBASE 0x40000000
3210 const char *PRVM_GetString(prvm_prog_t *prog, int num)
3215 if (prvm_stringdebug.integer)
3216 VM_Warning(prog, "PRVM_GetString: Invalid string offset (%i < 0)\n", num);
3219 else if (num < prog->stringssize)
3221 // constant string from progs.dat
3222 return prog->strings + num;
3224 else if (num <= prog->stringssize + prog->tempstringsbuf.maxsize)
3226 // tempstring returned by engine to QC (becomes invalid after returning to engine)
3227 num -= prog->stringssize;
3228 if (num < prog->tempstringsbuf.cursize)
3229 return (char *)prog->tempstringsbuf.data + num;
3232 if (prvm_stringdebug.integer)
3233 VM_Warning(prog, "PRVM_GetString: Invalid temp-string offset (%i >= %i prog->tempstringsbuf.cursize)\n", num, prog->tempstringsbuf.cursize);
3237 else if (num & PRVM_KNOWNSTRINGBASE)
3240 num = num - PRVM_KNOWNSTRINGBASE;
3241 if (num >= 0 && num < prog->numknownstrings)
3243 if (!prog->knownstrings[num])
3245 if (prvm_stringdebug.integer)
3246 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num);
3249 // refresh the garbage collection on the string - this guards
3250 // against a certain sort of repeated migration to earlier
3251 // points in the scan that could otherwise result in the string
3252 // being freed for being unused
3253 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] & ~KNOWNSTRINGFLAG_GCPRUNE) | KNOWNSTRINGFLAG_GCMARK;
3254 return prog->knownstrings[num];
3258 if (prvm_stringdebug.integer)
3259 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, prog->numknownstrings);
3265 // invalid string offset
3266 if (prvm_stringdebug.integer)
3267 VM_Warning(prog, "PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, prog->stringssize);
3272 const char *PRVM_ChangeEngineString(prvm_prog_t *prog, int i, const char *s)
3275 i = i - PRVM_KNOWNSTRINGBASE;
3276 if (i < 0 || i >= prog->numknownstrings)
3277 prog->error_cmd("PRVM_ChangeEngineString: string index %i is out of bounds", i);
3278 else if ((prog->knownstrings_flags[i] & KNOWNSTRINGFLAG_ENGINE) == 0)
3279 prog->error_cmd("PRVM_ChangeEngineString: string index %i is not an engine string", i);
3280 old = prog->knownstrings[i];
3281 prog->knownstrings[i] = s;
3285 static void PRVM_NewKnownString(prvm_prog_t *prog, int i, int flags, const char *s)
3287 if (i >= prog->numknownstrings)
3289 if (i >= prog->maxknownstrings)
3291 const char **oldstrings = prog->knownstrings;
3292 const unsigned char *oldstrings_flags = prog->knownstrings_flags;
3293 const char **oldstrings_origin = prog->knownstrings_origin;
3294 prog->maxknownstrings += 128;
3295 prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3296 prog->knownstrings_flags = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char));
3297 if (prog->leaktest_active)
3298 prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3299 if (prog->numknownstrings)
3301 memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *));
3302 memcpy((char **)prog->knownstrings_flags, oldstrings_flags, prog->numknownstrings * sizeof(unsigned char));
3303 if (prog->leaktest_active)
3304 memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *));
3307 prog->numknownstrings++;
3309 prog->firstfreeknownstring = i + 1;
3310 prog->knownstrings[i] = s;
3311 // it's in use right now, spare it until the next gc pass - that said, it is not freeable so this is probably moot
3312 prog->knownstrings_flags[i] = flags;
3313 if (prog->leaktest_active)
3314 prog->knownstrings_origin[i] = NULL;
3317 int PRVM_SetEngineString(prvm_prog_t *prog, const char *s)
3322 if (s >= prog->strings && s <= prog->strings + prog->stringssize)
3323 prog->error_cmd("PRVM_SetEngineString: s in prog->strings area");
3324 // if it's in the tempstrings area, use a reserved range
3325 // (otherwise we'd get millions of useless string offsets cluttering the database)
3326 if (s >= (char *)prog->tempstringsbuf.data && s < (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.maxsize)
3327 return prog->stringssize + (s - (char *)prog->tempstringsbuf.data);
3328 // see if it's a known string address
3329 for (i = 0;i < prog->numknownstrings;i++)
3330 if (prog->knownstrings[i] == s)
3331 return PRVM_KNOWNSTRINGBASE + i;
3332 // new unknown engine string
3333 if (developer_insane.integer)
3334 Con_DPrintf("new engine string %p = \"%s\"\n", (void *)s, s);
3335 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3336 if (!prog->knownstrings[i])
3338 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE, s);
3339 return PRVM_KNOWNSTRINGBASE + i;
3342 // temp string handling
3344 // all tempstrings go into this buffer consecutively, and it is reset
3345 // whenever PRVM_ExecuteProgram returns to the engine
3346 // (technically each PRVM_ExecuteProgram call saves the cursize value and
3347 // restores it on return, so multiple recursive calls can share the same
3349 // the buffer size is automatically grown as needed
3351 int PRVM_SetTempString(prvm_prog_t *prog, const char *s)
3357 size = (int)strlen(s) + 1;
3358 if (developer_insane.integer)
3359 Con_DPrintf("PRVM_SetTempString: cursize %i, size %i\n", prog->tempstringsbuf.cursize, size);
3360 if (prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3362 sizebuf_t old = prog->tempstringsbuf;
3363 if (prog->tempstringsbuf.cursize + size >= 1<<28)
3364 prog->error_cmd("PRVM_SetTempString: ran out of tempstring memory! (refusing to grow tempstring buffer over 256MB, cursize %i, size %i)\n", prog->tempstringsbuf.cursize, size);
3365 prog->tempstringsbuf.maxsize = max(prog->tempstringsbuf.maxsize, 65536);
3366 while (prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3367 prog->tempstringsbuf.maxsize *= 2;
3368 if (prog->tempstringsbuf.maxsize != old.maxsize || prog->tempstringsbuf.data == NULL)
3370 Con_DPrintf("PRVM_SetTempString: enlarging tempstrings buffer (%iKB -> %iKB)\n", old.maxsize/1024, prog->tempstringsbuf.maxsize/1024);
3371 prog->tempstringsbuf.data = (unsigned char *) Mem_Alloc(prog->progs_mempool, prog->tempstringsbuf.maxsize);
3375 memcpy(prog->tempstringsbuf.data, old.data, old.cursize);
3380 t = (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.cursize;
3382 prog->tempstringsbuf.cursize += size;
3383 return PRVM_SetEngineString(prog, t);
3386 int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer)
3396 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3397 if (!prog->knownstrings[i])
3399 s = (char *)PRVM_Alloc(bufferlength);
3400 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK, s);
3401 if(prog->leaktest_active)
3402 prog->knownstrings_origin[i] = PRVM_AllocationOrigin(prog);
3404 *pointer = (char *)(prog->knownstrings[i]);
3405 return PRVM_KNOWNSTRINGBASE + i;
3408 void PRVM_FreeString(prvm_prog_t *prog, int num)
3411 prog->error_cmd("PRVM_FreeString: attempt to free a NULL string");
3412 else if (num >= 0 && num < prog->stringssize)
3413 prog->error_cmd("PRVM_FreeString: attempt to free a constant string");
3414 else if (num >= PRVM_KNOWNSTRINGBASE && num < PRVM_KNOWNSTRINGBASE + prog->numknownstrings)
3416 num = num - PRVM_KNOWNSTRINGBASE;
3417 if (!prog->knownstrings[num])
3418 prog->error_cmd("PRVM_FreeString: attempt to free a non-existent or already freed string");
3419 if (!prog->knownstrings_flags[num])
3420 prog->error_cmd("PRVM_FreeString: attempt to free a string owned by the engine");
3421 PRVM_Free((char *)prog->knownstrings[num]);
3422 if(prog->leaktest_active)
3423 if(prog->knownstrings_origin[num])
3424 PRVM_Free((char *)prog->knownstrings_origin[num]);
3425 prog->knownstrings[num] = NULL;
3426 prog->knownstrings_flags[num] = 0;
3427 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3430 prog->error_cmd("PRVM_FreeString: invalid string offset %i", num);
3433 static qbool PRVM_IsStringReferenced(prvm_prog_t *prog, string_t string)
3437 for (i = 0;i < prog->numglobaldefs;i++)
3439 mdef_t *d = &prog->globaldefs[i];
3440 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3442 if(string == PRVM_GLOBALFIELDSTRING(d->ofs))
3446 for(j = 0; j < prog->num_edicts; ++j)
3448 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3449 if (ed->priv.required->free)
3451 for (i=0; i<prog->numfielddefs; ++i)
3453 mdef_t *d = &prog->fielddefs[i];
3454 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3456 if(string == PRVM_EDICTFIELDSTRING(ed, d->ofs))
3464 static qbool PRVM_IsEdictRelevant(prvm_prog_t *prog, prvm_edict_t *edict)
3468 if(PRVM_NUM_FOR_EDICT(edict) <= prog->reserved_edicts)
3469 return true; // world or clients
3470 if (edict->priv.required->freetime <= prog->inittime)
3471 return true; // created during startup
3472 if (prog == SVVM_prog)
3474 if(PRVM_serveredictfloat(edict, solid)) // can block other stuff, or is a trigger?
3476 if(PRVM_serveredictfloat(edict, modelindex)) // visible ent?
3478 if(PRVM_serveredictfloat(edict, effects)) // particle effect?
3480 if(PRVM_serveredictfunction(edict, think)) // has a think function?
3481 if(PRVM_serveredictfloat(edict, nextthink) > 0) // that actually will eventually run?
3483 if(PRVM_serveredictfloat(edict, takedamage))
3485 if(*prvm_leaktest_ignore_classnames.string)
3487 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_serveredictstring(edict, classname)))))
3491 else if (prog == CLVM_prog)
3493 // TODO someone add more stuff here
3494 if(PRVM_clientedictfloat(edict, entnum)) // csqc networked
3496 if(PRVM_clientedictfloat(edict, modelindex)) // visible ent?
3498 if(PRVM_clientedictfloat(edict, effects)) // particle effect?
3500 if(PRVM_clientedictfunction(edict, think)) // has a think function?
3501 if(PRVM_clientedictfloat(edict, nextthink) > 0) // that actually will eventually run?
3503 if(*prvm_leaktest_ignore_classnames.string)
3505 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_clientedictstring(edict, classname)))))
3511 // menu prog does not have classnames
3516 static qbool PRVM_IsEdictReferenced(prvm_prog_t *prog, prvm_edict_t *edict, int mark)
3519 int edictnum = PRVM_NUM_FOR_EDICT(edict);
3520 const char *targetname = NULL;
3522 if (prog == SVVM_prog && prvm_leaktest_follow_targetname.integer)
3523 targetname = PRVM_GetString(prog, PRVM_serveredictstring(edict, targetname));
3526 if(!*targetname) // ""
3529 for(j = 0; j < prog->num_edicts; ++j)
3531 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3532 if (ed->priv.required->mark < mark)
3538 const char *target = PRVM_GetString(prog, PRVM_serveredictstring(ed, target));
3540 if(!strcmp(target, targetname))
3543 for (i=0; i<prog->numfielddefs; ++i)
3545 mdef_t *d = &prog->fielddefs[i];
3546 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3548 if(edictnum == PRVM_EDICTFIELDEDICT(ed, d->ofs))
3556 static void PRVM_MarkReferencedEdicts(prvm_prog_t *prog)
3562 // Stage 1: world, all entities that are relevant, and all entities that are referenced by globals.
3564 for(j = 0; j < prog->num_edicts; ++j)
3566 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3567 if(ed->priv.required->free)
3569 ed->priv.required->mark = PRVM_IsEdictRelevant(prog, ed) ? stage : 0;
3571 for (i = 0;i < prog->numglobaldefs;i++)
3573 mdef_t *d = &prog->globaldefs[i];
3575 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3577 j = PRVM_GLOBALFIELDEDICT(d->ofs);
3578 if (i < 0 || j >= prog->max_edicts) {
3579 Con_Printf("Invalid entity reference from global %s.\n", PRVM_GetString(prog, d->s_name));
3582 ed = PRVM_EDICT_NUM(j);;
3583 ed->priv.required->mark = stage;
3586 // Future stages: all entities that are referenced by an entity of the previous stage.
3590 for(j = 0; j < prog->num_edicts; ++j)
3592 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3593 if(ed->priv.required->free)
3595 if(ed->priv.required->mark)
3597 if(PRVM_IsEdictReferenced(prog, ed, stage))
3599 ed->priv.required->mark = stage + 1;
3606 Con_DPrintf("leak check used %d stages to find all references\n", stage);
3609 void PRVM_LeakTest(prvm_prog_t *prog)
3612 qbool leaked = false;
3614 if(!prog->leaktest_active)
3618 for (i = 0; i < prog->numknownstrings; ++i)
3620 if(prog->knownstrings[i])
3621 if(prog->knownstrings_flags[i])
3622 if(prog->knownstrings_origin[i])
3623 if(!PRVM_IsStringReferenced(prog, PRVM_KNOWNSTRINGBASE + i))
3625 Con_Printf("Unreferenced string found!\n Value: %s\n Origin: %s\n", prog->knownstrings[i], prog->knownstrings_origin[i]);
3631 PRVM_MarkReferencedEdicts(prog);
3632 for(j = 0; j < prog->num_edicts; ++j)
3634 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3635 if(ed->priv.required->free)
3637 if(!ed->priv.required->mark)
3638 if(ed->priv.required->allocation_origin)
3640 Con_Printf("Unreferenced edict found!\n Allocated at: %s\n", ed->priv.required->allocation_origin);
3641 PRVM_ED_Print(prog, ed, NULL);
3646 ed->priv.required->mark = 0; // clear marks again when done
3649 for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); ++i)
3651 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
3653 if(stringbuffer->origin)
3655 Con_Printf("Open string buffer handle found!\n Allocated at: %s\n", stringbuffer->origin);
3660 for(i = 0; i < PRVM_MAX_OPENFILES; ++i)
3662 if(prog->openfiles[i])
3663 if(prog->openfiles_origin[i])
3665 Con_Printf("Open file handle found!\n Allocated at: %s\n", prog->openfiles_origin[i]);
3670 for(i = 0; i < PRVM_MAX_OPENSEARCHES; ++i)
3672 if(prog->opensearches[i])
3673 if(prog->opensearches_origin[i])
3675 Con_Printf("Open search handle found!\n Allocated at: %s\n", prog->opensearches_origin[i]);
3681 Con_Printf("Congratulations. No leaks found.\n");
3684 void PRVM_GarbageCollection(prvm_prog_t *prog)
3686 int limit = prvm_garbagecollection_scan_limit.integer;
3687 prvm_prog_garbagecollection_state_t *gc = &prog->gc;
3688 if (!prvm_garbagecollection_enable.integer)
3691 // we like to limit how much scanning we do so it doesn't put a significant
3692 // burden on the cpu, so each of these are not complete scans, we also like
3693 // to have consistent cpu usage so we do a bit of work on each category of
3694 // leaked object every frame
3700 case PRVM_GC_GLOBALS_MARK:
3701 for (; gc->globals_mark_progress < prog->numglobaldefs && (limit--) > 0; gc->globals_mark_progress++)
3703 mdef_t *d = &prog->globaldefs[gc->globals_mark_progress];
3708 prvm_int_t s = prog->globals.ip[d->ofs];
3709 if (s & PRVM_KNOWNSTRINGBASE)
3711 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3712 if (!prog->knownstrings[num])
3715 Con_DPrintf("PRVM_GarbageCollection: Found bogus strzone reference in global %i (global name: \"%s\"), erasing reference", d->ofs, PRVM_GetString(prog, d->s_name));
3716 prog->globals.ip[d->ofs] = 0;
3719 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3727 if (gc->globals_mark_progress >= prog->numglobaldefs)
3730 case PRVM_GC_FIELDS_MARK:
3731 for (; gc->fields_mark_progress < prog->numfielddefs && limit > 0;)
3733 mdef_t *d = &prog->fielddefs[gc->fields_mark_progress];
3737 //for (gc-> entityindex = 0; entityindex < prog->num_edicts; entityindex++)
3738 for (;gc->fields_mark_progress_entity < prog->num_edicts && (limit--) > 0;gc->fields_mark_progress_entity++)
3740 int entityindex = gc->fields_mark_progress_entity;
3741 prvm_int_t s = prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs];
3742 if (s & PRVM_KNOWNSTRINGBASE)
3744 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3745 if (!prog->knownstrings[num])
3748 Con_DPrintf("PRVM_GarbageCollection: Found bogus strzone reference in edict %i field %i (field name: \"%s\"), erasing reference", entityindex, d->ofs, PRVM_GetString(prog, d->s_name));
3749 prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs] = 0;
3752 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3755 if (gc->fields_mark_progress_entity >= prog->num_edicts)
3757 gc->fields_mark_progress_entity = 0;
3758 gc->fields_mark_progress++;
3762 gc->fields_mark_progress_entity = 0;
3763 gc->fields_mark_progress++;
3767 if (gc->fields_mark_progress >= prog->numfielddefs)
3770 case PRVM_GC_KNOWNSTRINGS_SWEEP:
3771 // free any strzone'd strings that are not marked
3772 if (!prvm_garbagecollection_strings.integer)
3777 for (;gc->knownstrings_sweep_progress < prog->numknownstrings && (limit--) > 0;gc->knownstrings_sweep_progress++)
3779 int num = gc->knownstrings_sweep_progress;
3780 if (prog->knownstrings[num] && (prog->knownstrings_flags[num] & (KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE)) == 0)
3782 if (prog->knownstrings_flags[num] & KNOWNSTRINGFLAG_GCPRUNE)
3784 // string has been marked for pruning two passes in a row
3785 if (prvm_garbagecollection_notify.integer)
3786 Con_DPrintf("prvm_garbagecollection_notify: %s: freeing unreferenced string %i: \"%s\"\n", prog->name, num, prog->knownstrings[num]);
3787 Mem_Free((char *)prog->knownstrings[num]);
3788 prog->knownstrings[num] = NULL;
3789 prog->knownstrings_flags[num] = 0;
3790 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3794 // mark it for pruning next pass
3795 prog->knownstrings_flags[num] |= KNOWNSTRINGFLAG_GCPRUNE;
3799 if (gc->knownstrings_sweep_progress >= prog->numknownstrings)
3804 memset(gc, 0, sizeof(*gc));