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.
25 #include "prvm_cmds.h"
27 prvm_prog_t prvm_prog_list[PRVM_PROG_MAX];
29 int prvm_type_size[8] = {1,sizeof(string_t)/4,1,3,1,1,sizeof(func_t)/4,sizeof(void *)/4};
31 prvm_eval_t prvm_badvalue; // used only for error returns
33 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"};
34 // LadyHavoc: prints every opcode as it executes - warning: this is significant spew
35 cvar_t prvm_traceqc = {CF_CLIENT | CF_SERVER, "prvm_traceqc", "0", "prints every QuakeC statement as it is executed (only for really thorough debugging!)"};
36 // LadyHavoc: counts usage of each QuakeC statement
37 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)"};
38 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)"};
39 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)"};
40 cvar_t prvm_backtraceforwarnings = {CF_CLIENT | CF_SERVER, "prvm_backtraceforwarnings", "0", "print a backtrace for warnings too"};
41 cvar_t prvm_leaktest = {CF_CLIENT | CF_SERVER, "prvm_leaktest", "0", "try to detect memory leaks in strings or entities"};
42 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"};
43 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)"};
44 cvar_t prvm_errordump = {CF_CLIENT | CF_SERVER, "prvm_errordump", "0", "write a savegame on crash to crash-server.dmp"};
45 cvar_t prvm_breakpointdump = {CF_CLIENT | CF_SERVER, "prvm_breakpointdump", "0", "write a savegame on breakpoint to breakpoint-server.dmp"};
46 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)"};
47 cvar_t prvm_reuseedicts_neverinsameframe = {CF_CLIENT | CF_SERVER, "prvm_reuseedicts_neverinsameframe", "1", "never allows re-use of freed entity slots during same frame"};
48 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"};
49 cvar_t prvm_garbagecollection_notify = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_notify", "0", "print out a notification for each resource freed by garbage collection (set developer >= 1 to see these)"};
50 /// At 50k in Xonotic with 8 bots scans take about: 24s server, 25s menu, 9s client.
51 /// At 50k in Quake 1.5: 2.2s server, 0.14s client.
52 /// At 50k impact on high FPS benchmarks is negligible, at 100k impact is low but measurable.
53 cvar_t prvm_garbagecollection_scan_limit = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_scan_limit", "50000", "scan this many fields or resources per second to free up unreferenced resources"};
54 cvar_t prvm_garbagecollection_strings = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_strings", "1", "automatically call strunzone() on strings that are not referenced"};
55 cvar_t prvm_stringdebug = {CF_CLIENT | CF_SERVER, "prvm_stringdebug", "0", "Print debug and warning messages related to strings"};
57 static double prvm_reuseedicts_always_allow = 0;
58 qbool prvm_runawaycheck = true;
60 //============================================================================
68 static void PRVM_MEM_Alloc(prvm_prog_t *prog)
72 // reserve space for the null entity aka world
73 // check bound of max_edicts
74 prog->max_edicts = bound(1 + prog->reserved_edicts, prog->max_edicts, prog->limit_edicts);
75 prog->num_edicts = bound(1 + prog->reserved_edicts, prog->num_edicts, prog->max_edicts);
77 // edictprivate_size has to be min as big prvm_edict_private_t
78 prog->edictprivate_size = max(prog->edictprivate_size,(int)sizeof(prvm_edict_private_t));
81 prog->edicts = (prvm_edict_t *)Mem_Alloc(prog->progs_mempool,prog->limit_edicts * sizeof(prvm_edict_t));
83 // alloc edict private space
84 prog->edictprivate = Mem_Alloc(prog->progs_mempool, prog->max_edicts * prog->edictprivate_size);
87 prog->entityfieldsarea = prog->entityfields * prog->max_edicts;
88 prog->edictsfields.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, prog->entityfieldsarea * sizeof(prvm_vec_t));
91 for(i = 0; i < prog->max_edicts; i++)
93 prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size);
94 prog->edicts[i].fields.fp = prog->edictsfields.fp + i * prog->entityfields;
100 PRVM_MEM_IncreaseEdicts
103 void PRVM_MEM_IncreaseEdicts(prvm_prog_t *prog)
107 if(prog->max_edicts >= prog->limit_edicts)
110 prog->begin_increase_edicts(prog);
113 prog->max_edicts = min(prog->max_edicts + 256, prog->limit_edicts);
115 prog->entityfieldsarea = prog->entityfields * prog->max_edicts;
116 prog->edictsfields.fp = (prvm_vec_t*)Mem_Realloc(prog->progs_mempool, (void *)prog->edictsfields.fp, prog->entityfieldsarea * sizeof(prvm_vec_t));
117 prog->edictprivate = (void *)Mem_Realloc(prog->progs_mempool, (void *)prog->edictprivate, prog->max_edicts * prog->edictprivate_size);
119 //set e and v pointers
120 for(i = 0; i < prog->max_edicts; i++)
122 prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size);
123 prog->edicts[i].fields.fp = prog->edictsfields.fp + i * prog->entityfields;
126 prog->end_increase_edicts(prog);
129 //============================================================================
132 int PRVM_ED_FindFieldOffset(prvm_prog_t *prog, const char *field)
135 d = PRVM_ED_FindField(prog, field);
141 int PRVM_ED_FindGlobalOffset(prvm_prog_t *prog, const char *global)
144 d = PRVM_ED_FindGlobal(prog, global);
150 func_t PRVM_ED_FindFunctionOffset(prvm_prog_t *prog, const char *function)
153 f = PRVM_ED_FindFunction(prog, function);
156 return (func_t)(f - prog->functions);
164 prvm_prog_t *PRVM_ProgFromString(const char *str)
166 if (!strcmp(str, "server"))
168 if (!strcmp(str, "client"))
171 if (!strcmp(str, "menu"))
179 PRVM_FriendlyProgFromString
182 prvm_prog_t *PRVM_FriendlyProgFromString(const char *str)
184 prvm_prog_t *prog = PRVM_ProgFromString(str);
187 Con_Printf("%s: unknown program name\n", str);
192 Con_Printf("%s: program is not loaded\n", str);
202 Sets everything to NULL.
204 Nota bene: this also marks the entity as allocated if it has been previously
205 freed and sets the allocation origin.
208 void PRVM_ED_ClearEdict(prvm_prog_t *prog, prvm_edict_t *e)
210 memset(e->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
212 e->freetime = host.realtime;
213 if(e->priv.required->allocation_origin)
214 Mem_Free((char *)e->priv.required->allocation_origin);
215 e->priv.required->allocation_origin = PRVM_AllocationOrigin(prog);
217 // AK: Let the init_edict function determine if something needs to be initialized
218 prog->init_edict(prog, e);
221 const char *PRVM_AllocationOrigin(prvm_prog_t *prog)
224 if(prog->leaktest_active)
225 if(prog->depth > 0) // actually in QC code and not just parsing the entities block of a map/savegame
227 // bones_was_here: this is the smallest 64 multiple that avoids truncation in Xonotic (was 256)
228 buf = (char *)PRVM_Alloc(448);
229 PRVM_ShortStackTrace(prog, buf, 448);
238 Returns if this particular edict could get allocated by PRVM_ED_Alloc
241 qbool PRVM_ED_CanAlloc(prvm_prog_t *prog, prvm_edict_t *e)
245 if(prvm_reuseedicts_always_allow == host.realtime)
247 if(host.realtime <= e->freetime + 0.1 && prvm_reuseedicts_neverinsameframe.integer)
248 return false; // never allow reuse in same frame (causes networking trouble)
249 if(e->freetime < prog->starttime + prvm_reuseedicts_startuptime.value)
251 if(host.realtime > e->freetime + 1)
253 return false; // entity slot still blocked because the entity was freed less than one second ago
260 Either finds a free edict, or allocates a new one.
261 Try to avoid reusing an entity that was recently freed, because it
262 can cause the client to think the entity morphed into something else
263 instead of being removed and recreated, which can cause interpolated
264 angles and bad trails.
267 prvm_edict_t *PRVM_ED_Alloc(prvm_prog_t *prog)
272 // the client qc dont need maxclients
273 // thus it doesnt need to use svs.maxclients
274 // AK: changed i=svs.maxclients+1
275 // AK: changed so the edict 0 wont spawn -> used as reserved/world entity
276 // although the menu/client has no world
277 for (i = prog->reserved_edicts + 1;i < prog->num_edicts;i++)
279 e = PRVM_EDICT_NUM(i);
280 if(PRVM_ED_CanAlloc(prog, e))
282 PRVM_ED_ClearEdict (prog, e);
287 if (i == prog->limit_edicts)
288 prog->error_cmd("%s: PRVM_ED_Alloc: no free edicts", prog->name);
291 if (prog->num_edicts >= prog->max_edicts)
292 PRVM_MEM_IncreaseEdicts(prog);
294 e = PRVM_EDICT_NUM(i);
296 PRVM_ED_ClearEdict(prog, e);
304 Marks the edict as free
306 FIXME: walk all entities and NULL out references to this entity
307 bones_was_here: do not want, that would break chains immediately!
308 Currently chains aren't broken by removing an entity, at least with prvm_reuseedicts_neverinsameframe 1
309 which is very handy and some QC code will depend on it.
312 void PRVM_ED_Free(prvm_prog_t *prog, prvm_edict_t *ed)
314 // dont delete the null entity (world) or reserved edicts
315 if (ed - prog->edicts <= prog->reserved_edicts)
318 prog->free_edict(prog, ed);
321 ed->freetime = host.realtime;
322 if(ed->priv.required->allocation_origin)
324 Mem_Free((char *)ed->priv.required->allocation_origin);
325 ed->priv.required->allocation_origin = NULL;
329 //===========================================================================
336 static mdef_t *PRVM_ED_GlobalAtOfs (prvm_prog_t *prog, unsigned int ofs)
341 for (i = 0;i < prog->numglobaldefs;i++)
343 def = &prog->globaldefs[i];
355 mdef_t *PRVM_ED_FieldAtOfs (prvm_prog_t *prog, unsigned int ofs)
360 for (i = 0;i < prog->numfielddefs;i++)
362 def = &prog->fielddefs[i];
374 mdef_t *PRVM_ED_FindField (prvm_prog_t *prog, const char *name)
379 for (i = 0;i < prog->numfielddefs;i++)
381 def = &prog->fielddefs[i];
382 if (!strcmp(PRVM_GetString(prog, def->s_name), name))
393 mdef_t *PRVM_ED_FindGlobal (prvm_prog_t *prog, const char *name)
398 for (i = 0;i < prog->numglobaldefs;i++)
400 def = &prog->globaldefs[i];
401 if (!strcmp(PRVM_GetString(prog, def->s_name), name))
409 PRVM_ED_FindGlobalEval
412 prvm_eval_t *PRVM_ED_FindGlobalEval(prvm_prog_t *prog, const char *name)
414 mdef_t *def = PRVM_ED_FindGlobal(prog, name);
415 return def ? (prvm_eval_t *) &prog->globals.fp[def->ofs] : NULL;
423 mfunction_t *PRVM_ED_FindFunction (prvm_prog_t *prog, const char *name)
428 for (i = 0;i < prog->numfunctions;i++)
430 func = &prog->functions[i];
431 if (!strcmp(PRVM_GetString(prog, func->s_name), name))
442 Returns a string describing *data in a type specific manner
445 static char *PRVM_ValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength)
451 type = (etype_t)((int) type & ~DEF_SAVEGLOBAL);
456 dp_strlcpy (line, PRVM_GetString (prog, val->string), linelength);
460 if (n < 0 || n >= prog->max_edicts)
461 dpsnprintf (line, linelength, "entity %i (invalid!)", n);
463 dpsnprintf (line, linelength, "entity %i", n);
466 if ((unsigned int)val->function < (unsigned int)prog->progs_numfunctions)
468 f = prog->functions + val->function;
469 dpsnprintf (line, linelength, "%s()", PRVM_GetString(prog, f->s_name));
472 dpsnprintf (line, linelength, "function %" PRVM_PRIi "() (invalid!)", val->function);
475 def = PRVM_ED_FieldAtOfs ( prog, val->_int );
477 dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name));
479 dpsnprintf (line, linelength, "field %" PRVM_PRIi " (invalid!)", val->_int );
482 dpsnprintf (line, linelength, "void");
485 // LadyHavoc: changed from %5.1f to %10.4f
486 dpsnprintf (line, linelength, PRVM_FLOAT_LOSSLESS_FORMAT, val->_float);
489 // LadyHavoc: changed from %5.1f to %10.4f
490 dpsnprintf (line, linelength, "'" PRVM_VECTOR_LOSSLESS_FORMAT "'", val->vector[0], val->vector[1], val->vector[2]);
493 dpsnprintf (line, linelength, "pointer");
496 dpsnprintf (line, linelength, "bad type %i", (int) type);
507 Returns a string describing *data in a type specific manner
508 Easier to parse than PR_ValueString
511 char *PRVM_UglyValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength)
518 type = (etype_t)((int)type & ~DEF_SAVEGLOBAL);
523 // Parse the string a bit to turn special characters
524 // (like newline, specifically) into escape codes,
525 // this fixes saving games from various mods
526 s = PRVM_GetString (prog, val->string);
527 for (i = 0;i < (int)linelength - 2 && *s;)
557 dpsnprintf (line, linelength, "%i", i);
560 if ((unsigned int)val->function < (unsigned int)prog->progs_numfunctions)
562 f = prog->functions + val->function;
563 dp_strlcpy (line, PRVM_GetString (prog, f->s_name), linelength);
566 dpsnprintf (line, linelength, "bad function %" PRVM_PRIi " (invalid!)", val->function);
569 def = PRVM_ED_FieldAtOfs ( prog, val->_int );
571 dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name));
573 dpsnprintf (line, linelength, "field %" PRVM_PRIi "(invalid!)", val->_int );
576 dpsnprintf (line, linelength, "void");
579 dpsnprintf (line, linelength, PRVM_FLOAT_LOSSLESS_FORMAT, val->_float);
582 dpsnprintf (line, linelength, PRVM_VECTOR_LOSSLESS_FORMAT, val->vector[0], val->vector[1], val->vector[2]);
585 dpsnprintf (line, linelength, "bad type %i", type);
596 Returns a string with a description and the contents of a global,
597 padded to 20 field width
600 char *PRVM_GlobalString (prvm_prog_t *prog, int ofs, char *line, size_t linelength)
606 char valuebuf[MAX_INPUTLINE];
608 val = (prvm_eval_t *)&prog->globals.fp[ofs];
609 def = PRVM_ED_GlobalAtOfs(prog, ofs);
611 dpsnprintf (line, linelength, "GLOBAL%i", ofs);
614 s = PRVM_ValueString (prog, (etype_t)def->type, val, valuebuf, sizeof(valuebuf));
615 dpsnprintf (line, linelength, "%s (=%s)", PRVM_GetString(prog, def->s_name), s);
619 //for ( ; i<20 ; i++)
620 // strcat (line," ");
626 char *PRVM_GlobalStringNoContents (prvm_prog_t *prog, int ofs, char *line, size_t linelength)
631 def = PRVM_ED_GlobalAtOfs(prog, ofs);
633 dpsnprintf (line, linelength, "GLOBAL%i", ofs);
635 dpsnprintf (line, linelength, "%s", PRVM_GetString(prog, def->s_name));
638 //for ( ; i<20 ; i++)
639 // strcat (line," ");
653 // LadyHavoc: optimized this to print out much more quickly (tempstring)
654 // LadyHavoc: changed to print out every 4096 characters (incase there are a lot of fields to print)
655 void PRVM_ED_Print(prvm_prog_t *prog, prvm_edict_t *ed, const char *wildcard_fieldname)
663 char tempstring[MAX_INPUTLINE], tempstring2[260]; // temporary string buffers
664 char valuebuf[MAX_INPUTLINE];
668 Con_Printf("%s: FREE\n",prog->name);
673 dpsnprintf(tempstring, sizeof(tempstring), "\n%s EDICT %i:\n", prog->name, PRVM_NUM_FOR_EDICT(ed));
674 for (i = 1;i < prog->numfielddefs;i++)
676 d = &prog->fielddefs[i];
677 name = PRVM_GetString(prog, d->s_name);
678 if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
679 continue; // skip _x, _y, _z vars
681 // Check Field Name Wildcard
682 if(wildcard_fieldname)
683 if( !matchpattern(name, wildcard_fieldname, 1) )
684 // Didn't match; skip
687 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
689 // if the value is still all 0, skip the field
690 type = d->type & ~DEF_SAVEGLOBAL;
692 for (j=0 ; j<prvm_type_size[type] ; j++)
695 if (j == prvm_type_size[type])
698 if (strlen(name) > sizeof(tempstring2)-4)
700 memcpy (tempstring2, name, sizeof(tempstring2)-4);
701 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
702 tempstring2[sizeof(tempstring2)-1] = 0;
705 dp_strlcat(tempstring, name, sizeof(tempstring));
706 for (l = strlen(name);l < 14;l++)
707 dp_strlcat(tempstring, " ", sizeof(tempstring));
708 dp_strlcat(tempstring, " ", sizeof(tempstring));
710 name = PRVM_ValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf));
711 if (strlen(name) > sizeof(tempstring2)-4)
713 memcpy (tempstring2, name, sizeof(tempstring2)-4);
714 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
715 tempstring2[sizeof(tempstring2)-1] = 0;
718 dp_strlcat(tempstring, name, sizeof(tempstring));
719 dp_strlcat(tempstring, "\n", sizeof(tempstring));
720 if (strlen(tempstring) >= sizeof(tempstring)/2)
722 Con_Print(tempstring);
727 Con_Print(tempstring);
737 void PRVM_ED_Write (prvm_prog_t *prog, qfile_t *f, prvm_edict_t *ed)
745 char valuebuf[MAX_INPUTLINE];
755 for (i = 1;i < prog->numfielddefs;i++)
757 d = &prog->fielddefs[i];
758 name = PRVM_GetString(prog, d->s_name);
760 if(developer_entityparsing.integer)
761 Con_Printf("PRVM_ED_Write: at entity %d field %s\n", PRVM_NUM_FOR_EDICT(ed), name);
763 //if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
764 if(strlen(name) > 1 && name[strlen(name)-2] == '_')
765 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?)
767 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
769 // if the value is still all 0, skip the field
770 type = d->type & ~DEF_SAVEGLOBAL;
771 for (j=0 ; j<prvm_type_size[type] ; j++)
774 if (j == prvm_type_size[type])
777 FS_Printf(f,"\"%s\" ",name);
778 prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_Write, ent=%d, name=%s", i, name);
779 FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf)));
780 prog->statestring = NULL;
786 void PRVM_ED_PrintNum (prvm_prog_t *prog, int ent, const char *wildcard_fieldname)
788 PRVM_ED_Print(prog, PRVM_EDICT_NUM(ent), wildcard_fieldname);
793 PRVM_ED_PrintEdicts_f
795 For debugging, prints all the entities in the current server
798 void PRVM_ED_PrintEdicts_f(cmd_state_t *cmd)
802 const char *wildcard_fieldname;
804 if(Cmd_Argc(cmd) < 2 || Cmd_Argc(cmd) > 3)
806 Con_Print("prvm_edicts <program name> <optional field name wildcard>\n");
810 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
813 if( Cmd_Argc(cmd) == 3)
814 wildcard_fieldname = Cmd_Argv(cmd, 2);
816 wildcard_fieldname = NULL;
818 Con_Printf("%s: %i entities\n", prog->name, prog->num_edicts);
819 for (i=0 ; i<prog->num_edicts ; i++)
820 PRVM_ED_PrintNum (prog, i, wildcard_fieldname);
827 For debugging, prints a single edict
830 static void PRVM_ED_PrintEdict_f(cmd_state_t *cmd)
834 const char *wildcard_fieldname;
836 if(Cmd_Argc(cmd) < 3 || Cmd_Argc(cmd) > 4)
838 Con_Print("prvm_edict <program name> <edict number> <optional field name wildcard>\n");
842 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
845 i = atoi (Cmd_Argv(cmd, 2));
846 if (i >= prog->num_edicts)
848 Con_Print("Bad edict number\n");
851 if( Cmd_Argc(cmd) == 4)
852 // Optional Wildcard Provided
853 wildcard_fieldname = Cmd_Argv(cmd, 3);
856 wildcard_fieldname = NULL;
857 PRVM_ED_PrintNum (prog, i, wildcard_fieldname);
867 // 2 possibilities : 1. just displaying the active edict count
868 // 2. making a function pointer [x]
869 static void PRVM_ED_Count_f(cmd_state_t *cmd)
873 if(Cmd_Argc(cmd) != 2)
875 Con_Print("prvm_count <program name>\n");
879 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
882 prog->count_edicts(prog);
886 ==============================================================================
890 FIXME: need to tag constants, doesn't really work
891 ==============================================================================
899 void PRVM_ED_WriteGlobals (prvm_prog_t *prog, qfile_t *f)
906 char valuebuf[MAX_INPUTLINE];
909 for (i = 0;i < prog->numglobaldefs;i++)
911 def = &prog->globaldefs[i];
913 if ( !(def->type & DEF_SAVEGLOBAL) )
915 type &= ~DEF_SAVEGLOBAL;
917 if (type != ev_string && type != ev_float && type != ev_entity)
920 name = PRVM_GetString(prog, def->s_name);
922 if(developer_entityparsing.integer)
923 Con_Printf("PRVM_ED_WriteGlobals: at global %s\n", name);
925 prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_WriteGlobals, name=%s", name);
926 FS_Printf(f,"\"%s\" ", name);
927 FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)type, (prvm_eval_t *)&prog->globals.fp[def->ofs], valuebuf, sizeof(valuebuf)));
928 prog->statestring = NULL;
938 void PRVM_ED_ParseGlobals (prvm_prog_t *prog, const char *data)
940 char keyname[MAX_INPUTLINE];
946 if (!COM_ParseToken_Simple(&data, false, false, true))
947 prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace");
948 if (com_token[0] == '}')
951 if (developer_entityparsing.integer)
952 Con_Printf("Key: \"%s\"", com_token);
954 dp_strlcpy (keyname, com_token, sizeof(keyname));
957 if (!COM_ParseToken_Simple(&data, false, true, true))
958 prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace");
960 if (developer_entityparsing.integer)
961 Con_Printf(" \"%s\"\n", com_token);
963 if (com_token[0] == '}')
964 prog->error_cmd("PRVM_ED_ParseGlobals: closing brace without data");
966 key = PRVM_ED_FindGlobal (prog, keyname);
969 Con_DPrintf("'%s' is not a global on %s\n", keyname, prog->name);
973 if (!PRVM_ED_ParseEpair(prog, NULL, key, com_token, true))
974 prog->error_cmd("PRVM_ED_ParseGlobals: parse error");
978 //============================================================================
985 Can parse either fields or globals
986 returns false if error
989 qbool PRVM_ED_ParseEpair(prvm_prog_t *prog, prvm_edict_t *ent, mdef_t *key, const char *s, qbool parsebackslash)
998 val = (prvm_eval_t *)(ent->fields.fp + key->ofs);
1000 val = (prvm_eval_t *)(prog->globals.fp + key->ofs);
1001 switch (key->type & ~DEF_SAVEGLOBAL)
1004 l = (int)strlen(s) + 1;
1005 val->string = PRVM_AllocString(prog, l, &new_p);
1006 for (i = 0;i < l;i++)
1008 if (s[i] == '\\' && s[i+1] && parsebackslash)
1013 else if (s[i] == 'r')
1024 while (*s && ISWHITESPACE(*s))
1026 val->_float = atof(s);
1030 for (i = 0;i < 3;i++)
1032 while (*s && ISWHITESPACE(*s))
1036 val->vector[i] = atof(s);
1037 while (!ISWHITESPACE(*s))
1045 while (*s && ISWHITESPACE(*s))
1048 if (i >= prog->limit_edicts)
1049 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);
1050 while (i >= prog->max_edicts)
1051 PRVM_MEM_IncreaseEdicts(prog);
1052 // if IncreaseEdicts was called the base pointer needs to be updated
1054 val = (prvm_eval_t *)(ent->fields.fp + key->ofs);
1055 val->edict = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM((int)i));
1061 Con_DPrintf("PRVM_ED_ParseEpair: Bogus field name %s in %s\n", s, prog->name);
1064 def = PRVM_ED_FindField(prog, s + 1);
1067 Con_DPrintf("PRVM_ED_ParseEpair: Can't find field %s in %s\n", s, prog->name);
1070 val->_int = def->ofs;
1074 func = PRVM_ED_FindFunction(prog, s);
1077 Con_Printf("PRVM_ED_ParseEpair: Can't find function %s in %s\n", s, prog->name);
1080 val->function = func - prog->functions;
1084 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);
1094 Console command to send a string to QC function GameCommand of the
1098 sv_cmd adminmsg 3 "do not teamkill"
1099 cl_cmd someclientcommand
1100 menu_cmd somemenucommand
1102 All progs can support this extension; sg calls it in server QC, cg in client
1106 static void PRVM_GameCommand(cmd_state_t *cmd, const char *whichprogs, const char *whichcmd)
1109 if(Cmd_Argc(cmd) < 1)
1111 Con_Printf("%s text...\n", whichcmd);
1115 if (!(prog = PRVM_FriendlyProgFromString(whichprogs)))
1118 if(!PRVM_allfunction(GameCommand))
1120 Con_Printf("%s program do not support GameCommand!\n", whichprogs);
1124 int restorevm_tempstringsbuf_cursize;
1129 restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize;
1130 PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, s ? s : "", s ? strlen(s) : 0);
1131 prog->ExecuteProgram(prog, PRVM_allfunction(GameCommand), "QC function GameCommand is missing");
1132 prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
1135 static void PRVM_GameCommand_Server_f(cmd_state_t *cmd)
1137 PRVM_GameCommand(cmd, "server", "sv_cmd");
1139 static void PRVM_GameCommand_Client_f(cmd_state_t *cmd)
1141 PRVM_GameCommand(cmd, "client", "cl_cmd");
1143 static void PRVM_GameCommand_Menu_f(cmd_state_t *cmd)
1145 PRVM_GameCommand(cmd, "menu", "menu_cmd");
1152 Console command to load a field of a specified edict
1155 static void PRVM_ED_EdictGet_f(cmd_state_t *cmd)
1162 char valuebuf[MAX_INPUTLINE];
1164 if(Cmd_Argc(cmd) != 4 && Cmd_Argc(cmd) != 5)
1166 Con_Print("prvm_edictget <program name> <edict number> <field> [<cvar>]\n");
1170 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1173 ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(cmd, 2)));
1175 if((key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, 3))) == 0)
1177 Con_Printf("Key %s not found !\n", Cmd_Argv(cmd, 3));
1181 v = (prvm_eval_t *)(ed->fields.fp + key->ofs);
1182 s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf));
1183 if(Cmd_Argc(cmd) == 5)
1185 cvar_t *cvar = Cvar_FindVar(cmd->cvars, Cmd_Argv(cmd, 4), cmd->cvars_flagsmask);
1187 if(Cvar_Readonly(cvar, "prvm_edictget"))
1190 Cvar_Get(cmd->cvars, Cmd_Argv(cmd, 4), s, cmd->cvars_flagsmask, NULL);
1193 Con_Printf("%s\n", s);
1199 static void PRVM_ED_GlobalGet_f(cmd_state_t *cmd)
1205 char valuebuf[MAX_INPUTLINE];
1207 if(Cmd_Argc(cmd) != 3 && Cmd_Argc(cmd) != 4)
1209 Con_Print("prvm_globalget <program name> <global> [<cvar>]\n");
1213 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1216 key = PRVM_ED_FindGlobal(prog, Cmd_Argv(cmd, 2));
1219 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
1223 v = (prvm_eval_t *) &prog->globals.fp[key->ofs];
1224 s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf));
1225 if(Cmd_Argc(cmd) == 4)
1227 cvar_t *cvar = Cvar_FindVar(cmd->cvars, Cmd_Argv(cmd, 3), cmd->cvars_flagsmask);
1229 if(Cvar_Readonly(cvar, "prvm_globalget"))
1231 Cvar_Get(cmd->cvars, Cmd_Argv(cmd, 3), s, cmd->cvars_flagsmask, NULL);
1234 Con_Printf("%s\n", s);
1244 Console command to set a field of a specified edict
1247 static void PRVM_ED_EdictSet_f(cmd_state_t *cmd)
1253 if(Cmd_Argc(cmd) != 5)
1255 Con_Print("prvm_edictset <program name> <edict number> <field> <value>\n");
1259 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1262 ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(cmd, 2)));
1264 if((key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, 3))) == 0)
1265 Con_Printf("Key %s not found!\n", Cmd_Argv(cmd, 3));
1267 PRVM_ED_ParseEpair(prog, ed, key, Cmd_Argv(cmd, 4), true);
1271 ====================
1274 Parses an edict out of the given string, returning the new position
1275 ed should be a properly initialized empty edict.
1276 Used for initial level load and for savegames.
1277 ====================
1279 const char *PRVM_ED_ParseEdict (prvm_prog_t *prog, const char *data, prvm_edict_t *ent)
1289 // go through all the dictionary pairs
1293 if (!COM_ParseToken_Simple(&data, false, false, true))
1294 prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace");
1295 if (developer_entityparsing.integer)
1296 Con_Printf("Key: \"%s\"", com_token);
1297 if (com_token[0] == '}')
1300 // anglehack is to allow QuakeEd to write single scalar angles
1301 // and allow them to be turned into vectors. (FIXME...)
1302 if (!strcmp(com_token, "angle"))
1304 dp_strlcpy (com_token, "angles", sizeof(com_token));
1310 // FIXME: change light to _light to get rid of this hack
1311 if (!strcmp(com_token, "light"))
1312 dp_strlcpy (com_token, "light_lev", sizeof(com_token)); // hack for single light def
1314 dp_strlcpy (keyname, com_token, sizeof(keyname));
1316 // another hack to fix keynames with trailing spaces
1317 n = strlen(keyname);
1318 while (n && keyname[n-1] == ' ')
1325 if (!COM_ParseToken_Simple(&data, false, false, true))
1326 prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace");
1327 if (developer_entityparsing.integer)
1328 Con_Printf(" \"%s\"\n", com_token);
1330 if (com_token[0] == '}')
1331 prog->error_cmd("PRVM_ED_ParseEdict: closing brace without data");
1335 // ignore attempts to set key "" (this problem occurs in nehahra neh1m8.bsp)
1339 // keynames with a leading underscore are used for utility comments,
1340 // and are immediately discarded by quake
1341 if (keyname[0] == '_')
1344 key = PRVM_ED_FindField (prog, keyname);
1347 Con_DPrintf("%s: '%s' is not a field\n", prog->name, keyname);
1354 dp_strlcpy (temp, com_token, sizeof(temp));
1355 dpsnprintf (com_token, sizeof(com_token), "0 %s 0", temp);
1358 if (!PRVM_ED_ParseEpair(prog, ent, key, com_token, strcmp(keyname, "wad") != 0))
1359 prog->error_cmd("PRVM_ED_ParseEdict: parse error");
1364 ent->freetime = host.realtime;
1370 void PRVM_ED_CallPrespawnFunction(prvm_prog_t *prog, prvm_edict_t *ent)
1372 if (PRVM_serverfunction(SV_OnEntityPreSpawnFunction))
1375 PRVM_serverglobalfloat(time) = sv.time;
1376 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1377 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPreSpawnFunction), "QC function SV_OnEntityPreSpawnFunction is missing");
1381 qbool PRVM_ED_CallSpawnFunction(prvm_prog_t *prog, prvm_edict_t *ent, const char *data, const char *start)
1383 const char *funcname;
1385 prvm_eval_t *fulldata = NULL;
1389 // immediately call spawn function, but only if there is a self global and a classname
1393 if (!PRVM_alledictstring(ent, classname))
1395 Con_Print("No classname for:\n");
1396 PRVM_ED_Print(prog, ent, NULL);
1397 PRVM_ED_Free (prog, ent);
1401 * This is required for FTE compatibility (FreeCS).
1402 * It copies the key/value pairs themselves into a
1403 * global for QC to parse on its own.
1405 else if (data && start)
1407 if((fulldata = PRVM_ED_FindGlobalEval(prog, "__fullspawndata")))
1411 fulldata->string = PRVM_AllocString(prog, data - start + 1, &spawndata);
1412 for(in = start; in < data; )
1416 *spawndata++ = '\t';
1424 // look for the spawn function
1425 funcname = PRVM_GetString(prog, PRVM_alledictstring(ent, classname));
1426 func = PRVM_ED_FindFunction (prog, va(vabuf, sizeof(vabuf), "spawnfunc_%s", funcname));
1428 if(!PRVM_allglobalfloat(require_spawnfunc_prefix))
1429 func = PRVM_ED_FindFunction (prog, funcname);
1433 // check for OnEntityNoSpawnFunction
1434 if (PRVM_serverfunction(SV_OnEntityNoSpawnFunction))
1437 PRVM_serverglobalfloat(time) = sv.time;
1438 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1439 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityNoSpawnFunction), "QC function SV_OnEntityNoSpawnFunction is missing");
1444 Con_DPrint("No spawn function for:\n");
1445 if (developer.integer > 0) // don't confuse non-developers with errors
1446 PRVM_ED_Print(prog, ent, NULL);
1448 PRVM_ED_Free (prog, ent);
1449 return false; // not included in "inhibited" count
1455 PRVM_serverglobalfloat(time) = sv.time;
1456 PRVM_allglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1457 prog->ExecuteProgram(prog, func - prog->functions, "");
1461 PRVM_ED_Free(prog, ent);
1465 void PRVM_ED_CallPostspawnFunction (prvm_prog_t *prog, prvm_edict_t *ent)
1468 if (PRVM_serverfunction(SV_OnEntityPostSpawnFunction))
1471 PRVM_serverglobalfloat(time) = sv.time;
1472 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1473 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPostSpawnFunction), "QC function SV_OnEntityPostSpawnFunction is missing");
1479 PRVM_ED_LoadFromFile
1481 The entities are directly placed in the array, rather than allocated with
1482 PRVM_ED_Alloc, because otherwise an error loading the map would have entity
1483 number references out of order.
1485 Creates a server's entity / program execution context by
1486 parsing textual entity definitions out of an ent file.
1488 Used for both fresh maps and savegame loads. A fresh map would also need
1489 to call PRVM_ED_CallSpawnFunctions () to let the objects initialize themselves.
1492 void PRVM_ED_LoadFromFile (prvm_prog_t *prog, const char *data)
1496 int parsed, inhibited, spawned, died;
1503 prvm_reuseedicts_always_allow = host.realtime;
1510 // parse the opening brace
1511 if (!COM_ParseToken_Simple(&data, false, false, true))
1513 if (com_token[0] != '{')
1514 prog->error_cmd("PRVM_ED_LoadFromFile: %s: found %s when expecting {", prog->name, com_token);
1516 // CHANGED: this is not conform to PR_LoadFromFile
1517 if(prog->loadintoworld)
1519 prog->loadintoworld = false;
1520 ent = PRVM_EDICT_NUM(0);
1523 ent = PRVM_ED_Alloc(prog);
1526 if (ent != prog->edicts) // hack
1527 memset (ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
1529 data = PRVM_ED_ParseEdict (prog, data, ent);
1532 // remove the entity ?
1533 if(!prog->load_edict(prog, ent))
1535 PRVM_ED_Free(prog, ent);
1540 PRVM_ED_CallPrespawnFunction(prog, ent);
1550 if(!PRVM_ED_CallSpawnFunction(prog, ent, data, start))
1553 PRVM_ED_CallPostspawnFunction(prog, ent);
1560 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);
1562 prvm_reuseedicts_always_allow = 0;
1565 static void PRVM_FindOffsets(prvm_prog_t *prog)
1567 // field and global searches use -1 for NULL
1568 memset(&prog->fieldoffsets, -1, sizeof(prog->fieldoffsets));
1569 memset(&prog->globaloffsets, -1, sizeof(prog->globaloffsets));
1570 // function searches use 0 for NULL
1571 memset(&prog->funcoffsets, 0, sizeof(prog->funcoffsets));
1572 #define PRVM_DECLARE_serverglobalfloat(x)
1573 #define PRVM_DECLARE_serverglobalvector(x)
1574 #define PRVM_DECLARE_serverglobalstring(x)
1575 #define PRVM_DECLARE_serverglobaledict(x)
1576 #define PRVM_DECLARE_serverglobalfunction(x)
1577 #define PRVM_DECLARE_clientglobalfloat(x)
1578 #define PRVM_DECLARE_clientglobalvector(x)
1579 #define PRVM_DECLARE_clientglobalstring(x)
1580 #define PRVM_DECLARE_clientglobaledict(x)
1581 #define PRVM_DECLARE_clientglobalfunction(x)
1582 #define PRVM_DECLARE_menuglobalfloat(x)
1583 #define PRVM_DECLARE_menuglobalvector(x)
1584 #define PRVM_DECLARE_menuglobalstring(x)
1585 #define PRVM_DECLARE_menuglobaledict(x)
1586 #define PRVM_DECLARE_menuglobalfunction(x)
1587 #define PRVM_DECLARE_serverfieldfloat(x)
1588 #define PRVM_DECLARE_serverfieldvector(x)
1589 #define PRVM_DECLARE_serverfieldstring(x)
1590 #define PRVM_DECLARE_serverfieldedict(x)
1591 #define PRVM_DECLARE_serverfieldfunction(x)
1592 #define PRVM_DECLARE_clientfieldfloat(x)
1593 #define PRVM_DECLARE_clientfieldvector(x)
1594 #define PRVM_DECLARE_clientfieldstring(x)
1595 #define PRVM_DECLARE_clientfieldedict(x)
1596 #define PRVM_DECLARE_clientfieldfunction(x)
1597 #define PRVM_DECLARE_menufieldfloat(x)
1598 #define PRVM_DECLARE_menufieldvector(x)
1599 #define PRVM_DECLARE_menufieldstring(x)
1600 #define PRVM_DECLARE_menufieldedict(x)
1601 #define PRVM_DECLARE_menufieldfunction(x)
1602 #define PRVM_DECLARE_serverfunction(x)
1603 #define PRVM_DECLARE_clientfunction(x)
1604 #define PRVM_DECLARE_menufunction(x)
1605 #define PRVM_DECLARE_field(x) prog->fieldoffsets.x = PRVM_ED_FindFieldOffset(prog, #x);
1606 #define PRVM_DECLARE_global(x) prog->globaloffsets.x = PRVM_ED_FindGlobalOffset(prog, #x);
1607 #define PRVM_DECLARE_function(x) prog->funcoffsets.x = PRVM_ED_FindFunctionOffset(prog, #x);
1608 #include "prvm_offsets.h"
1609 #undef PRVM_DECLARE_serverglobalfloat
1610 #undef PRVM_DECLARE_serverglobalvector
1611 #undef PRVM_DECLARE_serverglobalstring
1612 #undef PRVM_DECLARE_serverglobaledict
1613 #undef PRVM_DECLARE_serverglobalfunction
1614 #undef PRVM_DECLARE_clientglobalfloat
1615 #undef PRVM_DECLARE_clientglobalvector
1616 #undef PRVM_DECLARE_clientglobalstring
1617 #undef PRVM_DECLARE_clientglobaledict
1618 #undef PRVM_DECLARE_clientglobalfunction
1619 #undef PRVM_DECLARE_menuglobalfloat
1620 #undef PRVM_DECLARE_menuglobalvector
1621 #undef PRVM_DECLARE_menuglobalstring
1622 #undef PRVM_DECLARE_menuglobaledict
1623 #undef PRVM_DECLARE_menuglobalfunction
1624 #undef PRVM_DECLARE_serverfieldfloat
1625 #undef PRVM_DECLARE_serverfieldvector
1626 #undef PRVM_DECLARE_serverfieldstring
1627 #undef PRVM_DECLARE_serverfieldedict
1628 #undef PRVM_DECLARE_serverfieldfunction
1629 #undef PRVM_DECLARE_clientfieldfloat
1630 #undef PRVM_DECLARE_clientfieldvector
1631 #undef PRVM_DECLARE_clientfieldstring
1632 #undef PRVM_DECLARE_clientfieldedict
1633 #undef PRVM_DECLARE_clientfieldfunction
1634 #undef PRVM_DECLARE_menufieldfloat
1635 #undef PRVM_DECLARE_menufieldvector
1636 #undef PRVM_DECLARE_menufieldstring
1637 #undef PRVM_DECLARE_menufieldedict
1638 #undef PRVM_DECLARE_menufieldfunction
1639 #undef PRVM_DECLARE_serverfunction
1640 #undef PRVM_DECLARE_clientfunction
1641 #undef PRVM_DECLARE_menufunction
1642 #undef PRVM_DECLARE_field
1643 #undef PRVM_DECLARE_global
1644 #undef PRVM_DECLARE_function
1649 typedef struct dpfield_s
1656 #define DPFIELDS (sizeof(dpfields) / sizeof(dpfield_t))
1658 dpfield_t dpfields[] =
1669 #define PO_HASHSIZE 16384
1670 typedef struct po_string_s
1673 struct po_string_s *nextonhashchain;
1678 po_string_t *hashtable[PO_HASHSIZE];
1681 static void PRVM_PO_UnparseString(char *out, const char *in, size_t outsize)
1690 case '\a': if(outsize >= 2) { *out++ = '\\'; *out++ = 'a'; outsize -= 2; } break;
1691 case '\b': if(outsize >= 2) { *out++ = '\\'; *out++ = 'b'; outsize -= 2; } break;
1692 case '\t': if(outsize >= 2) { *out++ = '\\'; *out++ = 't'; outsize -= 2; } break;
1693 case '\r': if(outsize >= 2) { *out++ = '\\'; *out++ = 'r'; outsize -= 2; } break;
1694 case '\n': if(outsize >= 2) { *out++ = '\\'; *out++ = 'n'; outsize -= 2; } break;
1695 case '\\': if(outsize >= 2) { *out++ = '\\'; *out++ = '\\'; outsize -= 2; } break;
1696 case '"': if(outsize >= 2) { *out++ = '\\'; *out++ = '"'; outsize -= 2; } break;
1698 if(*in >= 0 && *in <= 0x1F)
1703 *out++ = '0' + ((*in & 0700) >> 6);
1704 *out++ = '0' + ((*in & 0070) >> 3);
1705 *out++ = '0' + (*in & 0007) ;
1722 static void PRVM_PO_ParseString(char *out, const char *in, size_t outsize)
1735 case 'a': if(outsize > 0) { *out++ = '\a'; --outsize; } break;
1736 case 'b': if(outsize > 0) { *out++ = '\b'; --outsize; } break;
1737 case 't': if(outsize > 0) { *out++ = '\t'; --outsize; } break;
1738 case 'r': if(outsize > 0) { *out++ = '\r'; --outsize; } break;
1739 case 'n': if(outsize > 0) { *out++ = '\n'; --outsize; } break;
1740 case '\\': if(outsize > 0) { *out++ = '\\'; --outsize; } break;
1741 case '"': if(outsize > 0) { *out++ = '"'; --outsize; } break;
1742 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
1746 if(*in >= '0' && *in <= '7')
1749 *out = (*out << 3) | (*in - '0');
1752 if(*in >= '0' && *in <= '7')
1755 *out = (*out << 3) | (*in - '0');
1766 if(outsize > 0) { *out++ = *in; --outsize; }
1781 static po_t *PRVM_PO_Load(const char *filename, const char *filename2, mempool_t *pool)
1786 char inbuf[MAX_INPUTLINE];
1787 char decodedbuf[MAX_INPUTLINE];
1790 po_string_t thisstr;
1793 for (i = 0; i < 2; ++i)
1795 const char *buf = (const char *)
1796 FS_LoadFile((i > 0 ? filename : filename2), pool, true, NULL);
1797 // first read filename2, then read filename
1798 // so that progs.dat.de.po wins over common.de.po
1799 // and within file, last item wins
1806 po = (po_t *)Mem_Alloc(pool, sizeof(*po));
1807 memset(po, 0, sizeof(*po));
1810 memset(&thisstr, 0, sizeof(thisstr)); // hush compiler warning
1818 p = strchr(p, '\n');
1824 if(*p == '\r' || *p == '\n')
1829 if(!strncmp(p, "msgid \"", 7))
1834 else if(!strncmp(p, "msgstr \"", 8))
1841 p = strchr(p, '\n');
1851 q = strchr(p, '\n');
1858 if((size_t)(q - p) >= (size_t) sizeof(inbuf))
1860 memcpy(inbuf, p, q - p - 1);
1861 inbuf[q - p - 1] = '\0';
1862 PRVM_PO_ParseString(decodedbuf + decodedpos, inbuf, sizeof(decodedbuf) - decodedpos);
1863 decodedpos += strlen(decodedbuf + decodedpos);
1873 Mem_Free(thisstr.key);
1874 thisstr.key = (char *)Mem_Alloc(pool, decodedpos + 1);
1875 memcpy(thisstr.key, decodedbuf, decodedpos + 1);
1877 else if(decodedpos > 0 && thisstr.key) // skip empty translation results
1879 thisstr.value = (char *)Mem_Alloc(pool, decodedpos + 1);
1880 memcpy(thisstr.value, decodedbuf, decodedpos + 1);
1881 hashindex = CRC_Block((const unsigned char *) thisstr.key, strlen(thisstr.key)) % PO_HASHSIZE;
1882 thisstr.nextonhashchain = po->hashtable[hashindex];
1883 po->hashtable[hashindex] = (po_string_t *)Mem_Alloc(pool, sizeof(thisstr));
1884 memcpy(po->hashtable[hashindex], &thisstr, sizeof(thisstr));
1885 memset(&thisstr, 0, sizeof(thisstr));
1889 Mem_Free((char *) buf);
1894 static const char *PRVM_PO_Lookup(po_t *po, const char *str)
1896 int hashindex = CRC_Block((const unsigned char *) str, strlen(str)) % PO_HASHSIZE;
1897 po_string_t *p = po->hashtable[hashindex];
1900 if(!strcmp(str, p->key))
1902 p = p->nextonhashchain;
1906 static void PRVM_PO_Destroy(po_t *po)
1909 for(i = 0; i < PO_HASHSIZE; ++i)
1911 po_string_t *p = po->hashtable[i];
1915 p = p->nextonhashchain;
1924 void PRVM_LeakTest(prvm_prog_t *prog);
1925 void PRVM_Prog_Reset(prvm_prog_t *prog)
1929 if(prog->tempstringsbuf.cursize)
1930 Mem_Free(prog->tempstringsbuf.data);
1931 prog->tempstringsbuf.cursize = 0;
1932 PRVM_LeakTest(prog);
1933 prog->reset_cmd(prog);
1934 Mem_FreePool(&prog->progs_mempool);
1936 PRVM_PO_Destroy((po_t *) prog->po);
1938 memset(prog,0,sizeof(prvm_prog_t));
1939 prog->break_statement = -1;
1940 prog->watch_global_type = ev_void;
1941 prog->watch_field_type = ev_void;
1949 static void PRVM_LoadLNO( prvm_prog_t *prog, const char *progname ) {
1950 fs_offset_t filesize;
1952 unsigned int *header;
1955 FS_StripExtension( progname, filename, sizeof( filename ) );
1956 dp_strlcat( filename, ".lno", sizeof( filename ) );
1958 lno = FS_LoadFile( filename, tempmempool, false, &filesize );
1964 <Spike> SafeWrite (h, &lnotype, sizeof(int));
1965 <Spike> SafeWrite (h, &version, sizeof(int));
1966 <Spike> SafeWrite (h, &numglobaldefs, sizeof(int));
1967 <Spike> SafeWrite (h, &numpr_globals, sizeof(int));
1968 <Spike> SafeWrite (h, &numfielddefs, sizeof(int));
1969 <Spike> SafeWrite (h, &numstatements, sizeof(int));
1970 <Spike> SafeWrite (h, statement_linenums, numstatements*sizeof(int));
1972 if ((unsigned int)filesize < (6 + prog->progs_numstatements) * sizeof(int))
1978 header = (unsigned int *) lno;
1979 if (memcmp(lno, "LNOF", 4) == 0
1980 && LittleLong( header[ 1 ] ) == 1
1981 && (unsigned int)LittleLong( header[ 2 ] ) == (unsigned int)prog->progs_numglobaldefs
1982 && (unsigned int)LittleLong( header[ 3 ] ) == (unsigned int)prog->progs_numglobals
1983 && (unsigned int)LittleLong( header[ 4 ] ) == (unsigned int)prog->progs_numfielddefs
1984 && (unsigned int)LittleLong( header[ 5 ] ) == (unsigned int)prog->progs_numstatements)
1986 prog->statement_linenums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1987 memcpy( prog->statement_linenums, header + 6, prog->progs_numstatements * sizeof( int ) );
1989 /* gmqcc suports columnums */
1990 if ((unsigned int)filesize > ((6 + 2 * prog->progs_numstatements) * sizeof( int )))
1992 prog->statement_columnnums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1993 memcpy( prog->statement_columnnums, header + 6 + prog->progs_numstatements, prog->progs_numstatements * sizeof( int ) );
2004 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog);
2005 void PRVM_Prog_Load(prvm_prog_t *prog, const char *filename, unsigned char *data, fs_offset_t size, void CheckRequiredFuncs(prvm_prog_t *prog, const char *filename), int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global)
2008 dprograms_t *dprograms;
2010 dstatement16_t *instatements16;
2011 dstatement32_t *instatements32;
2012 ddef16_t *infielddefs16;
2013 ddef32_t *infielddefs32;
2014 ddef16_t *inglobaldefs16;
2015 ddef32_t *inglobaldefs32;
2018 dfunction_t *infunctions;
2020 fs_offset_t filesize;
2021 int requiredglobalspace;
2039 prog->error_cmd("%s: there is already a %s program loaded!", __func__, prog->name);
2041 Host_LockSession(); // all progs can use the session cvar
2042 Crypto_LoadKeys(); // all progs might use the keys at init time
2046 dprograms = (dprograms_t *) data;
2050 dprograms = (dprograms_t *)FS_LoadFile (filename, prog->progs_mempool, false, &filesize);
2051 if (dprograms == NULL || filesize < (fs_offset_t)sizeof(dprograms_t))
2052 prog->error_cmd("%s: couldn't load \"%s\" for %s", __func__, filename, prog->name);
2053 // TODO bounds check header fields (e.g. numstatements), they must never go behind end of file
2055 prog->profiletime = Sys_DirtyTime();
2056 prog->starttime = host.realtime;
2058 requiredglobalspace = 0;
2059 for (i = 0;i < numrequiredglobals;i++)
2060 requiredglobalspace += required_global[i].type == ev_vector ? 3 : 1;
2062 prog->filecrc = CRC_Block((unsigned char *)dprograms, filesize);
2064 // byte swap the header
2065 prog->progs_version = LittleLong(dprograms->version);
2066 prog->progs_crc = LittleLong(dprograms->crc);
2067 if (prog->progs_version == 7)
2069 dprograms_v7_t *v7 = (dprograms_v7_t*)dprograms;
2070 structtype = LittleLong(v7->secondaryversion);
2071 if (structtype == PROG_SECONDARYVERSION16 ||
2072 structtype == PROG_SECONDARYVERSION32) // barely supported
2073 Con_Printf(CON_WARN "WARNING: %s: %s targets FTEQW, for which support is incomplete. Proceed at your own risk.\n", prog->name, filename);
2075 prog->error_cmd("%s: %s targets unknown engine", prog->name, filename);
2077 if (v7->numbodylessfuncs != 0 || v7->numtypes != 0 || v7->blockscompressed != 0)
2078 prog->error_cmd("%s: %s uses unsupported features.", prog->name, filename);
2080 else if (prog->progs_version != PROG_VERSION)
2081 prog->error_cmd("%s: %s has wrong version number (%i should be %i)", prog->name, filename, prog->progs_version, PROG_VERSION);
2082 instatements16 = (dstatement16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_statements));
2083 instatements32 = (dstatement32_t *)instatements16;
2084 prog->progs_numstatements = LittleLong(dprograms->numstatements);
2085 inglobaldefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globaldefs));
2086 inglobaldefs32 = (ddef32_t *)inglobaldefs16;
2087 prog->progs_numglobaldefs = LittleLong(dprograms->numglobaldefs);
2088 infielddefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_fielddefs));
2089 infielddefs32 = (ddef32_t *)infielddefs16;
2090 prog->progs_numfielddefs = LittleLong(dprograms->numfielddefs);
2091 infunctions = (dfunction_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_functions));
2092 prog->progs_numfunctions = LittleLong(dprograms->numfunctions);
2093 instrings = (char *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_strings));
2094 prog->progs_numstrings = LittleLong(dprograms->numstrings);
2095 inglobals = (int *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globals));
2096 prog->progs_numglobals = LittleLong(dprograms->numglobals);
2097 prog->progs_entityfields = LittleLong(dprograms->entityfields);
2099 prog->numstatements = prog->progs_numstatements;
2100 prog->numglobaldefs = prog->progs_numglobaldefs;
2101 prog->numfielddefs = prog->progs_numfielddefs;
2102 prog->numfunctions = prog->progs_numfunctions;
2103 prog->numstrings = prog->progs_numstrings;
2104 prog->numglobals = prog->progs_numglobals;
2105 prog->entityfields = prog->progs_entityfields;
2107 if (LittleLong(dprograms->ofs_strings) + prog->progs_numstrings > (int)filesize)
2108 prog->error_cmd("%s: %s strings go past end of file", prog->name, filename);
2109 prog->strings = (char *)Mem_Alloc(prog->progs_mempool, prog->progs_numstrings);
2110 memcpy(prog->strings, instrings, prog->progs_numstrings);
2111 prog->stringssize = prog->progs_numstrings;
2113 prog->numknownstrings = 0;
2114 prog->maxknownstrings = 0;
2115 prog->knownstrings = NULL;
2116 prog->knownstrings_flags = NULL;
2118 Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64);
2120 // we need to expand the globaldefs and fielddefs to include engine defs
2121 prog->globaldefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobaldefs + numrequiredglobals) * sizeof(mdef_t));
2122 prog->globals.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobals + requiredglobalspace + 2) * sizeof(prvm_vec_t));
2123 // + 2 is because of an otherwise occurring overrun in RETURN instruction
2124 // when trying to return the last or second-last global
2125 // (RETURN always returns a vector, there is no RETURN_F instruction)
2126 prog->fielddefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numfielddefs + numrequiredfields) * sizeof(mdef_t));
2127 // we need to convert the statements to our memory format
2128 prog->statements = (mstatement_t *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(mstatement_t));
2129 // allocate space for profiling statement usage
2130 prog->statement_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2131 prog->explicit_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2132 // functions need to be converted to the memory format
2133 prog->functions = (mfunction_t *)Mem_Alloc(prog->progs_mempool, sizeof(mfunction_t) * prog->progs_numfunctions);
2135 for (i = 0;i < prog->progs_numfunctions;i++)
2137 prog->functions[i].first_statement = LittleLong(infunctions[i].first_statement);
2138 prog->functions[i].parm_start = LittleLong(infunctions[i].parm_start);
2139 prog->functions[i].s_name = LittleLong(infunctions[i].s_name);
2140 prog->functions[i].s_file = LittleLong(infunctions[i].s_file);
2141 prog->functions[i].numparms = LittleLong(infunctions[i].numparms);
2142 prog->functions[i].locals = LittleLong(infunctions[i].locals);
2143 memcpy(prog->functions[i].parm_size, infunctions[i].parm_size, sizeof(infunctions[i].parm_size));
2144 if(prog->functions[i].first_statement >= prog->numstatements)
2145 prog->error_cmd("%s: out of bounds function statement (function %d) in %s", __func__, i, prog->name);
2146 // TODO bounds check parm_start, s_name, s_file, numparms, locals, parm_size
2149 // copy the globaldefs to the new globaldefs list
2152 case PROG_SECONDARYVERSION32:
2153 for (i=0 ; i<prog->numglobaldefs ; i++)
2155 prog->globaldefs[i].type = LittleLong(inglobaldefs32[i].type);
2156 prog->globaldefs[i].ofs = LittleLong(inglobaldefs32[i].ofs);
2157 prog->globaldefs[i].s_name = LittleLong(inglobaldefs32[i].s_name);
2158 // TODO bounds check ofs, s_name
2162 for (i=0 ; i<prog->numglobaldefs ; i++)
2164 prog->globaldefs[i].type = (unsigned short)LittleShort(inglobaldefs16[i].type);
2165 prog->globaldefs[i].ofs = (unsigned short)LittleShort(inglobaldefs16[i].ofs);
2166 prog->globaldefs[i].s_name = LittleLong(inglobaldefs16[i].s_name);
2167 // TODO bounds check ofs, s_name
2172 // append the required globals
2173 for (i = 0;i < numrequiredglobals;i++)
2175 prog->globaldefs[prog->numglobaldefs].type = required_global[i].type;
2176 prog->globaldefs[prog->numglobaldefs].ofs = prog->numglobals;
2177 prog->globaldefs[prog->numglobaldefs].s_name = PRVM_SetEngineString(prog, required_global[i].name);
2178 if (prog->globaldefs[prog->numglobaldefs].type == ev_vector)
2179 prog->numglobals += 3;
2182 prog->numglobaldefs++;
2185 // copy the progs fields to the new fields list
2188 case PROG_SECONDARYVERSION32:
2189 for (i = 0;i < prog->numfielddefs;i++)
2191 prog->fielddefs[i].type = LittleLong(infielddefs32[i].type);
2192 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2193 prog->error_cmd("%s: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", __func__, prog->name);
2194 prog->fielddefs[i].ofs = LittleLong(infielddefs32[i].ofs);
2195 prog->fielddefs[i].s_name = LittleLong(infielddefs32[i].s_name);
2196 // TODO bounds check ofs, s_name
2200 for (i = 0;i < prog->numfielddefs;i++)
2202 prog->fielddefs[i].type = (unsigned short)LittleShort(infielddefs16[i].type);
2203 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2204 prog->error_cmd("%s: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", __func__, prog->name);
2205 prog->fielddefs[i].ofs = (unsigned short)LittleShort(infielddefs16[i].ofs);
2206 prog->fielddefs[i].s_name = LittleLong(infielddefs16[i].s_name);
2207 // TODO bounds check ofs, s_name
2212 // append the required fields
2213 for (i = 0;i < numrequiredfields;i++)
2215 prog->fielddefs[prog->numfielddefs].type = required_field[i].type;
2216 prog->fielddefs[prog->numfielddefs].ofs = prog->entityfields;
2217 prog->fielddefs[prog->numfielddefs].s_name = PRVM_SetEngineString(prog, required_field[i].name);
2218 if (prog->fielddefs[prog->numfielddefs].type == ev_vector)
2219 prog->entityfields += 3;
2221 prog->entityfields++;
2222 prog->numfielddefs++;
2225 // LadyHavoc: TODO: reorder globals to match engine struct
2226 // LadyHavoc: TODO: reorder fields to match engine struct
2227 #define remapglobal(index) (index)
2228 #define remapfield(index) (index)
2231 // FIXME: LadyHavoc: this uses a crude way to identify integer constants, rather than checking for matching globaldefs and checking their type
2232 for (i = 0;i < prog->progs_numglobals;i++)
2234 u.i = LittleLong(inglobals[i]);
2235 // most globals are 0, we only need to deal with the ones that are not
2238 d = u.i & 0xFF800000;
2239 if ((d == 0xFF800000) || (d == 0))
2241 // Looks like an integer (expand to int64)
2242 prog->globals.ip[remapglobal(i)] = u.i;
2246 // Looks like a float (expand to double)
2247 prog->globals.fp[remapglobal(i)] = u.f;
2252 // copy, remap globals in statements, bounds check
2253 for (i = 0;i < prog->progs_numstatements;i++)
2257 case PROG_SECONDARYVERSION32:
2258 op = (opcode_t)LittleLong(instatements32[i].op);
2259 a = (unsigned int)LittleLong(instatements32[i].a);
2260 b = (unsigned int)LittleLong(instatements32[i].b);
2261 c = (unsigned int)LittleLong(instatements32[i].c);
2264 op = (opcode_t)LittleShort(instatements16[i].op);
2265 a = (unsigned short)LittleShort(instatements16[i].a);
2266 b = (unsigned short)LittleShort(instatements16[i].b);
2267 c = (unsigned short)LittleShort(instatements16[i].c);
2275 if (a >= prog->progs_numglobals || b + i < 0 || b + i >= prog->progs_numstatements)
2276 prog->error_cmd("%s: out of bounds IF/IFNOT (statement %d) in %s", __func__, i, prog->name);
2277 prog->statements[i].op = op;
2278 prog->statements[i].operand[0] = remapglobal(a);
2279 prog->statements[i].operand[1] = -1;
2280 prog->statements[i].operand[2] = -1;
2281 prog->statements[i].jumpabsolute = i + b;
2285 if (a + i < 0 || a + i >= prog->progs_numstatements)
2286 prog->error_cmd("%s: out of bounds GOTO (statement %d) in %s", __func__, i, prog->name);
2287 prog->statements[i].op = op;
2288 prog->statements[i].operand[0] = -1;
2289 prog->statements[i].operand[1] = -1;
2290 prog->statements[i].operand[2] = -1;
2291 prog->statements[i].jumpabsolute = i + a;
2294 Con_DPrintf("%s: unknown opcode %d at statement %d in %s\n", __func__, (int)op, i, prog->name);
2296 //make sure its something well defined.
2297 prog->statements[i].op = OP_BOUNDCHECK;
2298 prog->statements[i].operand[0] = 0;
2299 prog->statements[i].operand[1] =
2300 prog->statements[i].operand[2] = op;
2301 prog->statements[i].jumpabsolute = -1;
2356 case OP_GSTOREP_ENT:
2357 case OP_GSTOREP_FLD:
2359 case OP_GSTOREP_FNC:
2361 // case OP_GADDRESS:
2371 // global global global
2406 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals || c >= prog->progs_numglobals)
2407 prog->error_cmd("%s: out of bounds global index (statement %d)", __func__, i);
2408 prog->statements[i].op = op;
2409 prog->statements[i].operand[0] = remapglobal(a);
2410 prog->statements[i].operand[1] = remapglobal(b);
2411 prog->statements[i].operand[2] = remapglobal(c);
2412 prog->statements[i].jumpabsolute = -1;
2414 // global none global
2420 if (a >= prog->progs_numglobals || c >= prog->progs_numglobals)
2421 prog->error_cmd("%s: out of bounds global index (statement %d) in %s", __func__, i, prog->name);
2422 prog->statements[i].op = op;
2423 prog->statements[i].operand[0] = remapglobal(a);
2424 prog->statements[i].operand[1] = -1;
2425 prog->statements[i].operand[2] = remapglobal(c);
2426 prog->statements[i].jumpabsolute = -1;
2434 if (c) //Spike -- DP is alergic to pointers in QC. Try to avoid too many nasty surprises.
2435 Con_DPrintf("%s: storep-with-offset is not permitted in %s\n", __func__, prog->name);
2445 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals)
2446 prog->error_cmd("%s: out of bounds global index (statement %d) in %s", __func__, i, prog->name);
2447 prog->statements[i].op = op;
2448 prog->statements[i].operand[0] = remapglobal(a);
2449 prog->statements[i].operand[1] = remapglobal(b);
2450 prog->statements[i].operand[2] = -1;
2451 prog->statements[i].jumpabsolute = -1;
2455 if ( a < prog->progs_numglobals)
2456 if ( prog->globals.ip[remapglobal(a)] >= 0 )
2457 if ( prog->globals.ip[remapglobal(a)] < prog->progs_numfunctions )
2458 if ( prog->functions[prog->globals.ip[remapglobal(a)]].first_statement == -642 )
2459 ++prog->numexplicitcoveragestatements;
2470 if ( a >= prog->progs_numglobals)
2471 prog->error_cmd("%s: out of bounds global index (statement %d) in %s", __func__, i, prog->name);
2472 if (b || c) //Spike -- added this check just as a diagnostic...
2473 Con_DPrintf("%s: unexpected offset on call opcode in %s. Hexen2 format is not supported\n", __func__, prog->name);
2474 prog->statements[i].op = op;
2475 prog->statements[i].operand[0] = remapglobal(a);
2476 prog->statements[i].operand[1] = -1;
2477 prog->statements[i].operand[2] = -1;
2478 prog->statements[i].jumpabsolute = -1;
2482 if(prog->numstatements < 1)
2484 prog->error_cmd("%s: empty program in %s", __func__, prog->name);
2486 else switch(prog->statements[prog->numstatements - 1].op)
2493 prog->error_cmd("%s: program may fall off the edge (does not end with RETURN, GOTO or DONE) in %s", __func__, prog->name);
2497 // we're done with the file now
2499 Mem_Free(dprograms);
2503 // expected to not return (call prog->error_cmd) if checks fail
2504 CheckRequiredFuncs(prog, filename);
2506 PRVM_LoadLNO(prog, filename);
2508 PRVM_Init_Exec(prog);
2510 if(*prvm_language.string)
2511 // in CSQC we really shouldn't be able to change how stuff works... sorry for now
2512 // later idea: include a list of authorized .po file checksums with the csprogs
2514 qbool deftrans = prog == CLVM_prog;
2515 const char *realfilename = (prog != CLVM_prog ? filename : csqc_progname.string);
2516 if(deftrans) // once we have dotranslate_ strings, ALWAYS use the opt-in method!
2518 for (i=0 ; i<prog->numglobaldefs ; i++)
2521 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2522 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2523 if(name && !strncmp(name, "dotranslate_", 12))
2530 if(!strcmp(prvm_language.string, "dump"))
2532 qfile_t *f = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.pot", realfilename), "w", false);
2533 Con_Printf("Dumping to %s.pot\n", realfilename);
2536 for (i=0 ; i<prog->numglobaldefs ; i++)
2539 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2540 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2541 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2543 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2544 const char *value = PRVM_GetString(prog, val->string);
2547 char buf[MAX_INPUTLINE];
2548 PRVM_PO_UnparseString(buf, value, sizeof(buf));
2549 FS_Printf(f, "msgid \"%s\"\nmsgstr \"\"\n\n", buf);
2558 po_t *po = PRVM_PO_Load(
2559 va(vabuf, sizeof(vabuf), "%s.%s.po", realfilename, prvm_language.string),
2560 va(vabuf2, sizeof(vabuf2), "common.%s.po", prvm_language.string),
2561 prog->progs_mempool);
2564 for (i=0 ; i<prog->numglobaldefs ; i++)
2567 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2568 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2569 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2571 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2572 const char *value = PRVM_GetString(prog, val->string);
2575 value = PRVM_PO_Lookup(po, value);
2577 val->string = PRVM_SetEngineString(prog, value);
2585 for (cvar = prog->console_cmd->cvars->vars; cvar; cvar = cvar->next)
2586 cvar->globaldefindex[prog - prvm_prog_list] = -1;
2588 for (i=0 ; i<prog->numglobaldefs ; i++)
2591 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2592 //Con_Printf("found var %s\n", name);
2594 && !strncmp(name, "autocvar_", 9)
2595 && !(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
2598 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2599 cvar = Cvar_FindVar(prog->console_cmd->cvars, name + 9, prog->console_cmd->cvars_flagsmask);
2600 //Con_Printf("%s: autocvar global %s in %s, processing...\n", __func__, name, prog->name);
2607 Con_DPrintf("%s: no cvar for autocvar global %s in %s, creating...\n", __func__, name, prog->name);
2608 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2611 if((float)((int)(val->_float)) == val->_float)
2612 dpsnprintf(buf, sizeof(buf), "%i", (int)(val->_float));
2617 for (int precision = 7; precision <= 9; ++precision) {
2618 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2619 if ((float)atof(buf) == f) {
2627 for (i = 0; i < 3; ++i)
2631 for (int precision = 7; precision <= 9; ++precision) {
2632 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2633 if ((float)atof(buf) == f) {
2634 prec[i] = precision;
2639 dpsnprintf(buf, sizeof(buf), "%.*g %.*g %.*g", prec[0], val->vector[0], prec[1], val->vector[1], prec[2], val->vector[2]);
2643 value = PRVM_GetString(prog, val->string);
2646 Con_Printf("%s: invalid type of autocvar global %s in %s\n", __func__, name, prog->name);
2649 cvar = Cvar_Get(prog->console_cmd->cvars, name + 9, value, prog->console_cmd->cvars_flagsmask, NULL);
2650 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2652 val->string = PRVM_SetEngineString(prog, cvar->string);
2653 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2656 prog->error_cmd("%s: could not create cvar for autocvar global %s in %s", __func__, name, prog->name);
2657 cvar->globaldefindex[prog - prvm_prog_list] = i;
2659 else if((cvar->flags & CF_PRIVATE) == 0)
2661 // MUST BE SYNCED WITH cvar.c Cvar_Set
2664 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2667 val->_float = cvar->value;
2671 VectorClear(val->vector);
2672 for (j = 0;j < 3;j++)
2674 while (*s && ISWHITESPACE(*s))
2678 val->vector[j] = atof(s);
2679 while (!ISWHITESPACE(*s))
2686 val->string = PRVM_SetEngineString(prog, cvar->string);
2687 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2690 Con_Printf("%s: invalid type of autocvar global %s in %s\n", __func__, name, prog->name);
2693 cvar->globaldefindex[prog - prvm_prog_list] = i;
2696 Con_Printf("%s: private cvar for autocvar global %s in %s\n", __func__, name, prog->name);
2702 prog->loaded = true;
2704 PRVM_UpdateBreakpoints(prog);
2706 // set flags & mdef_ts in prog
2708 PRVM_FindOffsets(prog);
2710 prog->init_cmd(prog);
2713 PRVM_MEM_Alloc(prog);
2715 Con_Printf("%s: program loaded (crc %i, size %iK)%s\n", prog->name, prog->filecrc, (int)(filesize/1024),
2716 prog == CLVM_prog ? (prog->flag & PRVM_CSQC_SIMPLE ? " CSQC_SIMPLE" : " EXT_CSQC") : "");
2718 // Inittime is at least the time when this function finished. However,
2719 // later events may bump it.
2720 prog->inittime = host.realtime;
2724 static void PRVM_Fields_f(cmd_state_t *cmd)
2727 int i, j, ednum, used, usedamount;
2729 char tempstring[MAX_INPUTLINE], tempstring2[260];
2739 Con_Print("no progs loaded\n");
2744 if(Cmd_Argc(cmd) != 2)
2746 Con_Print("prvm_fields <program name>\n");
2750 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2753 counts = (int *)Mem_Alloc(tempmempool, prog->numfielddefs * sizeof(int));
2754 for (ednum = 0;ednum < prog->max_edicts;ednum++)
2756 ed = PRVM_EDICT_NUM(ednum);
2759 for (i = 1;i < prog->numfielddefs;i++)
2761 d = &prog->fielddefs[i];
2762 name = PRVM_GetString(prog, d->s_name);
2763 if (name[strlen(name)-2] == '_')
2764 continue; // skip _x, _y, _z vars
2765 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
2766 // if the value is still all 0, skip the field
2767 for (j = 0;j < prvm_type_size[d->type & ~DEF_SAVEGLOBAL];j++)
2769 if (val->ivector[j])
2780 for (i = 0;i < prog->numfielddefs;i++)
2782 d = &prog->fielddefs[i];
2783 name = PRVM_GetString(prog, d->s_name);
2784 if (name[strlen(name)-2] == '_')
2785 continue; // skip _x, _y, _z vars
2786 switch(d->type & ~DEF_SAVEGLOBAL)
2789 dp_strlcat(tempstring, "string ", sizeof(tempstring));
2792 dp_strlcat(tempstring, "entity ", sizeof(tempstring));
2795 dp_strlcat(tempstring, "function ", sizeof(tempstring));
2798 dp_strlcat(tempstring, "field ", sizeof(tempstring));
2801 dp_strlcat(tempstring, "void ", sizeof(tempstring));
2804 dp_strlcat(tempstring, "float ", sizeof(tempstring));
2807 dp_strlcat(tempstring, "vector ", sizeof(tempstring));
2810 dp_strlcat(tempstring, "pointer ", sizeof(tempstring));
2813 dpsnprintf (tempstring2, sizeof(tempstring2), "bad type %i ", d->type & ~DEF_SAVEGLOBAL);
2814 dp_strlcat(tempstring, tempstring2, sizeof(tempstring));
2817 if (strlen(name) > sizeof(tempstring2)-4)
2819 memcpy (tempstring2, name, sizeof(tempstring2)-4);
2820 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
2821 tempstring2[sizeof(tempstring2)-1] = 0;
2824 dp_strlcat(tempstring, name, sizeof(tempstring));
2825 for (j = (int)strlen(name);j < 25;j++)
2826 dp_strlcat(tempstring, " ", sizeof(tempstring));
2827 dpsnprintf(tempstring2, sizeof(tempstring2), "%5d", counts[i]);
2828 dp_strlcat(tempstring, tempstring2, sizeof(tempstring));
2829 dp_strlcat(tempstring, "\n", sizeof(tempstring));
2830 if (strlen(tempstring) >= sizeof(tempstring)/2)
2832 Con_Print(tempstring);
2838 usedamount += prvm_type_size[d->type & ~DEF_SAVEGLOBAL];
2842 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);
2845 static void PRVM_Globals_f(cmd_state_t *cmd)
2849 const char *wildcard;
2855 Con_Print("no progs loaded\n");
2858 if(Cmd_Argc (cmd) < 2 || Cmd_Argc(cmd) > 3)
2860 Con_Print("prvm_globals <program name> <optional name wildcard>\n");
2864 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2867 if( Cmd_Argc(cmd) == 3)
2868 wildcard = Cmd_Argv(cmd, 2);
2872 Con_Printf("%s :", prog->name);
2874 for (i = 0;i < prog->numglobaldefs;i++)
2877 if( !matchpattern( PRVM_GetString(prog, prog->globaldefs[i].s_name), wildcard, 1) )
2882 Con_Printf("%s\n", PRVM_GetString(prog, prog->globaldefs[i].s_name));
2884 Con_Printf("%i global variables, %i culled, totalling %i bytes\n", prog->numglobals, numculled, prog->numglobals * 4);
2892 static void PRVM_Global_f(cmd_state_t *cmd)
2896 char valuebuf[MAX_INPUTLINE];
2897 if( Cmd_Argc(cmd) != 3 ) {
2898 Con_Printf( "prvm_global <program name> <global name>\n" );
2902 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2905 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2907 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2909 Con_Printf( "%s: %s\n", Cmd_Argv(cmd, 2), PRVM_ValueString( prog, (etype_t)global->type, PRVM_GLOBALFIELDVALUE(global->ofs), valuebuf, sizeof(valuebuf) ) );
2917 static void PRVM_GlobalSet_f(cmd_state_t *cmd)
2921 if( Cmd_Argc(cmd) != 4 ) {
2922 Con_Printf( "prvm_globalset <program name> <global name> <value>\n" );
2926 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2929 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2931 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2933 PRVM_ED_ParseEpair( prog, NULL, global, Cmd_Argv(cmd, 3), true );
2937 ======================
2938 Break- and Watchpoints
2939 ======================
2943 char break_statement[256];
2944 char watch_global[256];
2946 char watch_field[256];
2949 static debug_data_t debug_data[PRVM_PROG_MAX];
2951 void PRVM_Breakpoint(prvm_prog_t *prog, int stack_index, const char *text)
2954 Con_Printf("PRVM_Breakpoint: %s\n", text);
2955 PRVM_PrintState(prog, stack_index);
2956 if (prvm_breakpointdump.integer)
2957 SV_Savegame_to(prog, va(vabuf, sizeof(vabuf), "breakpoint-%s.dmp", prog->name));
2960 void PRVM_Watchpoint(prvm_prog_t *prog, int stack_index, const char *text, etype_t type, prvm_eval_t *o, prvm_eval_t *n)
2962 size_t sz = sizeof(prvm_vec_t) * ((type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
2963 if (memcmp(o, n, sz))
2966 char valuebuf_o[128];
2967 char valuebuf_n[128];
2968 PRVM_UglyValueString(prog, type, o, valuebuf_o, sizeof(valuebuf_o));
2969 PRVM_UglyValueString(prog, type, n, valuebuf_n, sizeof(valuebuf_n));
2970 dpsnprintf(buf, sizeof(buf), "%s: %s -> %s", text, valuebuf_o, valuebuf_n);
2971 PRVM_Breakpoint(prog, stack_index, buf);
2976 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog)
2978 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
2981 if (debug->break_statement[0])
2983 if (debug->break_statement[0] >= '0' && debug->break_statement[0] <= '9')
2985 prog->break_statement = atoi(debug->break_statement);
2986 prog->break_stack_index = 0;
2991 func = PRVM_ED_FindFunction (prog, debug->break_statement);
2994 Con_Printf("%s progs: no function or statement named %s to break on!\n", prog->name, debug->break_statement);
2995 prog->break_statement = -1;
2999 prog->break_statement = func->first_statement;
3000 prog->break_stack_index = 1;
3003 if (prog->break_statement >= -1)
3004 Con_Printf("%s progs: breakpoint is at statement %d\n", prog->name, prog->break_statement);
3007 prog->break_statement = -1;
3009 if (debug->watch_global[0])
3011 mdef_t *global = PRVM_ED_FindGlobal( prog, debug->watch_global );
3014 Con_Printf( "%s progs: no global named '%s' to watch!\n", prog->name, debug->watch_global );
3015 prog->watch_global_type = ev_void;
3019 size_t sz = sizeof(prvm_vec_t) * ((global->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3020 prog->watch_global = global->ofs;
3021 prog->watch_global_type = (etype_t)global->type;
3022 memcpy(&prog->watch_global_value, PRVM_GLOBALFIELDVALUE(prog->watch_global), sz);
3024 if (prog->watch_global_type != ev_void)
3025 Con_Printf("%s progs: global watchpoint is at global index %d\n", prog->name, prog->watch_global);
3028 prog->watch_global_type = ev_void;
3030 if (debug->watch_field[0])
3032 mdef_t *field = PRVM_ED_FindField( prog, debug->watch_field );
3035 Con_Printf( "%s progs: no field named '%s' to watch!\n", prog->name, debug->watch_field );
3036 prog->watch_field_type = ev_void;
3040 size_t sz = sizeof(prvm_vec_t) * ((field->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3041 prog->watch_edict = debug->watch_edict;
3042 prog->watch_field = field->ofs;
3043 prog->watch_field_type = (etype_t)field->type;
3044 if (prog->watch_edict < prog->num_edicts)
3045 memcpy(&prog->watch_edictfield_value, PRVM_EDICTFIELDVALUE(PRVM_EDICT_NUM(prog->watch_edict), prog->watch_field), sz);
3047 memset(&prog->watch_edictfield_value, 0, sz);
3049 if (prog->watch_edict != ev_void)
3050 Con_Printf("%s progs: edict field watchpoint is at edict %d field index %d\n", prog->name, prog->watch_edict, prog->watch_field);
3053 prog->watch_field_type = ev_void;
3056 static void PRVM_Breakpoint_f(cmd_state_t *cmd)
3060 if( Cmd_Argc(cmd) == 2 ) {
3061 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3064 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3065 debug->break_statement[0] = 0;
3067 PRVM_UpdateBreakpoints(prog);
3070 if( Cmd_Argc(cmd) != 3 ) {
3071 Con_Printf( "prvm_breakpoint <program name> <function name | statement>\n" );
3075 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3079 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3080 dp_strlcpy(debug->break_statement, Cmd_Argv(cmd, 2), sizeof(debug->break_statement));
3082 PRVM_UpdateBreakpoints(prog);
3085 static void PRVM_GlobalWatchpoint_f(cmd_state_t *cmd)
3089 if( Cmd_Argc(cmd) == 2 ) {
3090 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3093 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3094 debug->watch_global[0] = 0;
3096 PRVM_UpdateBreakpoints(prog);
3099 if( Cmd_Argc(cmd) != 3 ) {
3100 Con_Printf( "prvm_globalwatchpoint <program name> <global name>\n" );
3104 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3108 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3109 dp_strlcpy(debug->watch_global, Cmd_Argv(cmd, 2), sizeof(debug->watch_global));
3111 PRVM_UpdateBreakpoints(prog);
3114 static void PRVM_EdictWatchpoint_f(cmd_state_t *cmd)
3118 if( Cmd_Argc(cmd) == 2 ) {
3119 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3122 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3123 debug->watch_field[0] = 0;
3125 PRVM_UpdateBreakpoints(prog);
3128 if( Cmd_Argc(cmd) != 4 ) {
3129 Con_Printf( "prvm_edictwatchpoint <program name> <edict number> <field name>\n" );
3133 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3137 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3138 debug->watch_edict = atoi(Cmd_Argv(cmd, 2));
3139 dp_strlcpy(debug->watch_field, Cmd_Argv(cmd, 3), sizeof(debug->watch_field));
3141 PRVM_UpdateBreakpoints(prog);
3149 void PRVM_Init (void)
3153 Cmd_AddCommand(CF_SHARED, "prvm_edict", PRVM_ED_PrintEdict_f, "print all data about an entity number in the selected VM (server, client, menu)");
3154 Cmd_AddCommand(CF_SHARED, "prvm_edicts", PRVM_ED_PrintEdicts_f, "prints all data about all entities in the selected VM (server, client, menu)");
3155 Cmd_AddCommand(CF_SHARED, "prvm_edictcount", PRVM_ED_Count_f, "prints number of active entities in the selected VM (server, client, menu)");
3156 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)");
3157 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");
3158 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)");
3159 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)");
3160 Cmd_AddCommand(CF_SHARED, "prvm_globals", PRVM_Globals_f, "prints all global variables in the selected VM (server, client, menu)");
3161 Cmd_AddCommand(CF_SHARED, "prvm_global", PRVM_Global_f, "prints value of a specified global variable in the selected VM (server, client, menu)");
3162 Cmd_AddCommand(CF_SHARED, "prvm_globalset", PRVM_GlobalSet_f, "sets value of a specified global variable in the selected VM (server, client, menu)");
3163 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)");
3164 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");
3165 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");
3166 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)");
3167 Cmd_AddCommand(CF_SHARED, "cl_cmd", PRVM_GameCommand_Client_f, "calls the client QC function GameCommand with the supplied string as argument");
3168 Cmd_AddCommand(CF_SHARED, "menu_cmd", PRVM_GameCommand_Menu_f, "calls the menu QC function GameCommand with the supplied string as argument");
3169 Cmd_AddCommand(CF_SHARED, "sv_cmd", PRVM_GameCommand_Server_f, "calls the server QC function GameCommand with the supplied string as argument");
3170 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");
3171 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");
3172 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");
3174 Cvar_RegisterVariable (&prvm_language);
3175 Cvar_RegisterVariable (&prvm_traceqc);
3176 Cvar_RegisterVariable (&prvm_statementprofiling);
3177 Cvar_RegisterVariable (&prvm_timeprofiling);
3178 Cvar_RegisterVariable (&prvm_coverage);
3179 Cvar_RegisterVariable (&prvm_backtraceforwarnings);
3180 Cvar_RegisterVariable (&prvm_leaktest);
3181 Cvar_RegisterVariable (&prvm_leaktest_follow_targetname);
3182 Cvar_RegisterVariable (&prvm_leaktest_ignore_classnames);
3183 Cvar_RegisterVariable (&prvm_errordump);
3184 Cvar_RegisterVariable (&prvm_breakpointdump);
3185 Cvar_RegisterVariable (&prvm_reuseedicts_startuptime);
3186 Cvar_RegisterVariable (&prvm_reuseedicts_neverinsameframe);
3187 Cvar_RegisterVariable (&prvm_garbagecollection_enable);
3188 Cvar_RegisterVariable (&prvm_garbagecollection_notify);
3189 Cvar_RegisterVariable (&prvm_garbagecollection_scan_limit);
3190 Cvar_RegisterVariable (&prvm_garbagecollection_strings);
3191 Cvar_RegisterVariable (&prvm_stringdebug);
3193 // COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!)
3194 prvm_runawaycheck = !Sys_CheckParm("-norunaway");
3198 // LadyHavoc: report supported extensions
3199 Con_DPrintf("\nQuakeC extensions for server and client:");
3200 for (i = 0; vm_sv_extensions[i]; i++)
3201 Con_DPrintf(" %s", vm_sv_extensions[i]);
3204 Con_DPrintf("\nQuakeC extensions for menu:");
3205 for (i = 0; vm_m_extensions[i]; i++)
3206 Con_DPrintf(" %s", vm_m_extensions[i]);
3216 void PRVM_Prog_Init(prvm_prog_t *prog, cmd_state_t *cmd)
3218 PRVM_Prog_Reset(prog);
3219 prog->leaktest_active = prvm_leaktest.integer != 0;
3220 prog->console_cmd = cmd;
3223 // LadyHavoc: turned PRVM_EDICT_NUM into a #define for speed reasons
3224 unsigned int PRVM_EDICT_NUM_ERROR(prvm_prog_t *prog, unsigned int n, const char *filename, int fileline)
3226 prog->error_cmd("PRVM_EDICT_NUM: %s: bad number %i (called at %s:%i)", prog->name, n, filename, fileline);
3230 #define PRVM_KNOWNSTRINGBASE 0x40000000
3232 const char *PRVM_GetString(prvm_prog_t *prog, int num)
3237 if (prvm_stringdebug.integer)
3238 VM_Warning(prog, "PRVM_GetString: Invalid string offset (%i < 0)\n", num);
3241 else if (num < prog->stringssize)
3243 // constant string from progs.dat
3244 return prog->strings + num;
3246 else if (num <= prog->stringssize + prog->tempstringsbuf.maxsize)
3248 // tempstring returned by engine to QC (becomes invalid after returning to engine)
3249 num -= prog->stringssize;
3250 if (num < prog->tempstringsbuf.cursize)
3251 return (char *)prog->tempstringsbuf.data + num;
3254 if (prvm_stringdebug.integer)
3255 VM_Warning(prog, "PRVM_GetString: Invalid temp-string offset (%i >= %i prog->tempstringsbuf.cursize)\n", num, prog->tempstringsbuf.cursize);
3259 else if (num & PRVM_KNOWNSTRINGBASE)
3262 num = num - PRVM_KNOWNSTRINGBASE;
3263 if (num >= 0 && num < prog->numknownstrings)
3265 if (!prog->knownstrings[num])
3267 if (prvm_stringdebug.integer)
3268 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num);
3271 // refresh the garbage collection on the string - this guards
3272 // against a certain sort of repeated migration to earlier
3273 // points in the scan that could otherwise result in the string
3274 // being freed for being unused
3275 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] & ~KNOWNSTRINGFLAG_GCPRUNE) | KNOWNSTRINGFLAG_GCMARK;
3276 return prog->knownstrings[num];
3280 if (prvm_stringdebug.integer)
3281 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, prog->numknownstrings);
3287 // invalid string offset
3288 if (prvm_stringdebug.integer)
3289 VM_Warning(prog, "PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, prog->stringssize);
3294 const char *PRVM_ChangeEngineString(prvm_prog_t *prog, int i, const char *s)
3297 i = i - PRVM_KNOWNSTRINGBASE;
3298 if (i < 0 || i >= prog->numknownstrings)
3299 prog->error_cmd("PRVM_ChangeEngineString: string index %i is out of bounds", i);
3300 else if ((prog->knownstrings_flags[i] & KNOWNSTRINGFLAG_ENGINE) == 0)
3301 prog->error_cmd("PRVM_ChangeEngineString: string index %i is not an engine string", i);
3302 old = prog->knownstrings[i];
3303 prog->knownstrings[i] = s;
3307 static void PRVM_NewKnownString(prvm_prog_t *prog, int i, int flags, const char *s)
3309 if (i >= prog->numknownstrings)
3311 if (i >= prog->maxknownstrings)
3313 const char **oldstrings = prog->knownstrings;
3314 const unsigned char *oldstrings_flags = prog->knownstrings_flags;
3315 const char **oldstrings_origin = prog->knownstrings_origin;
3316 prog->maxknownstrings += 128;
3317 prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3318 prog->knownstrings_flags = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char));
3319 if (prog->leaktest_active)
3320 prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3321 if (prog->numknownstrings)
3323 memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *));
3324 memcpy((char **)prog->knownstrings_flags, oldstrings_flags, prog->numknownstrings * sizeof(unsigned char));
3325 if (prog->leaktest_active)
3326 memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *));
3329 prog->numknownstrings++;
3331 prog->firstfreeknownstring = i + 1;
3332 prog->knownstrings[i] = s;
3333 // it's in use right now, spare it until the next gc pass - that said, it is not freeable so this is probably moot
3334 prog->knownstrings_flags[i] = flags;
3335 if (prog->leaktest_active)
3336 prog->knownstrings_origin[i] = NULL;
3339 int PRVM_SetEngineString(prvm_prog_t *prog, const char *s)
3344 if (s >= prog->strings && s <= prog->strings + prog->stringssize)
3345 prog->error_cmd("PRVM_SetEngineString: s in prog->strings area");
3346 // if it's in the tempstrings area, use a reserved range
3347 // (otherwise we'd get millions of useless string offsets cluttering the database)
3348 if (s >= (char *)prog->tempstringsbuf.data && s < (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.maxsize)
3349 return prog->stringssize + (s - (char *)prog->tempstringsbuf.data);
3350 // see if it's a known string address
3351 for (i = 0;i < prog->numknownstrings;i++)
3352 if (prog->knownstrings[i] == s)
3353 return PRVM_KNOWNSTRINGBASE + i;
3354 // new unknown engine string
3355 if (developer_insane.integer)
3356 Con_DPrintf("new engine string %p = \"%s\"\n", (void *)s, s);
3357 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3358 if (!prog->knownstrings[i])
3360 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE, s);
3361 return PRVM_KNOWNSTRINGBASE + i;
3364 // temp string handling
3366 // all tempstrings go into this buffer consecutively, and it is reset
3367 // whenever PRVM_ExecuteProgram returns to the engine
3368 // (technically each PRVM_ExecuteProgram call saves the cursize value and
3369 // restores it on return, so multiple recursive calls can share the same
3371 // the buffer size is automatically grown as needed
3372 int PRVM_SetTempString(prvm_prog_t *prog, const char *s, size_t slen)
3377 if (!s || slen >= VM_TEMPSTRING_MAXSIZE)
3380 if (developer_insane.integer)
3381 Con_DPrintf("PRVM_SetTempString %s: cursize %i, new tempstring size %lu\n", prog->name, prog->tempstringsbuf.cursize, (unsigned long)size);
3382 if ((size_t)prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3384 sizebuf_t old = prog->tempstringsbuf;
3385 if (prog->tempstringsbuf.cursize + size >= 1<<28)
3386 prog->error_cmd("PRVM_SetTempString %s: ran out of tempstring memory! (refusing to grow tempstring buffer over 256MB, cursize %i, new tempstring size %lu)\n", prog->name, prog->tempstringsbuf.cursize, (unsigned long)size);
3387 prog->tempstringsbuf.maxsize = max(prog->tempstringsbuf.maxsize, 65536);
3388 while ((size_t)prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3389 prog->tempstringsbuf.maxsize *= 2;
3390 if (prog->tempstringsbuf.maxsize != old.maxsize || prog->tempstringsbuf.data == NULL)
3392 Con_DPrintf("PRVM_SetTempString %s: enlarging tempstrings buffer (%iKB -> %iKB)\n", prog->name, old.maxsize/1024, prog->tempstringsbuf.maxsize/1024);
3393 prog->tempstringsbuf.data = (unsigned char *) Mem_Alloc(prog->progs_mempool, prog->tempstringsbuf.maxsize);
3397 memcpy(prog->tempstringsbuf.data, old.data, old.cursize);
3402 t = (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.cursize;
3404 prog->tempstringsbuf.cursize += size;
3405 return PRVM_SetEngineString(prog, t);
3408 int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer)
3418 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3419 if (!prog->knownstrings[i])
3421 s = (char *)PRVM_Alloc(bufferlength);
3422 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK, s);
3423 if(prog->leaktest_active)
3424 prog->knownstrings_origin[i] = PRVM_AllocationOrigin(prog);
3426 *pointer = (char *)(prog->knownstrings[i]);
3427 return PRVM_KNOWNSTRINGBASE + i;
3430 void PRVM_FreeString(prvm_prog_t *prog, int num)
3433 prog->error_cmd("PRVM_FreeString %s: attempt to free a NULL string", prog->name);
3434 else if (num >= 0 && num < prog->stringssize)
3435 prog->error_cmd("PRVM_FreeString %s: attempt to free a constant string", prog->name);
3436 else if (num >= PRVM_KNOWNSTRINGBASE && num < PRVM_KNOWNSTRINGBASE + prog->numknownstrings)
3438 num = num - PRVM_KNOWNSTRINGBASE;
3439 if (!prog->knownstrings[num])
3440 prog->error_cmd("PRVM_FreeString %s: attempt to free a non-existent or already freed string", prog->name);
3441 if (!prog->knownstrings_flags[num])
3442 prog->error_cmd("PRVM_FreeString %s: attempt to free a string owned by the engine", prog->name);
3443 PRVM_Free((char *)prog->knownstrings[num]);
3444 if(prog->leaktest_active)
3445 if(prog->knownstrings_origin[num])
3446 PRVM_Free((char *)prog->knownstrings_origin[num]);
3447 prog->knownstrings[num] = NULL;
3448 prog->knownstrings_flags[num] = 0;
3449 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3452 prog->error_cmd("PRVM_FreeString %s: invalid string offset %i", prog->name, num);
3455 static qbool PRVM_IsStringReferenced(prvm_prog_t *prog, string_t string)
3459 for (i = 0;i < prog->numglobaldefs;i++)
3461 mdef_t *d = &prog->globaldefs[i];
3462 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3464 if(string == PRVM_GLOBALFIELDSTRING(d->ofs))
3468 for(j = 0; j < prog->num_edicts; ++j)
3470 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3473 for (i=0; i<prog->numfielddefs; ++i)
3475 mdef_t *d = &prog->fielddefs[i];
3476 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3478 if(string == PRVM_EDICTFIELDSTRING(ed, d->ofs))
3486 static qbool PRVM_IsEdictRelevant(prvm_prog_t *prog, prvm_edict_t *edict)
3490 if(PRVM_NUM_FOR_EDICT(edict) <= prog->reserved_edicts)
3491 return true; // world or clients
3492 if (edict->freetime <= prog->inittime)
3493 return true; // created during startup
3494 if (prog == SVVM_prog)
3496 if(PRVM_serveredictfloat(edict, solid)) // can block other stuff, or is a trigger?
3498 if(PRVM_serveredictfloat(edict, modelindex)) // visible ent?
3500 if(PRVM_serveredictfloat(edict, effects)) // particle effect?
3502 if(PRVM_serveredictfunction(edict, think)) // has a think function?
3503 if(PRVM_serveredictfloat(edict, nextthink) > 0) // that actually will eventually run?
3505 if(PRVM_serveredictfloat(edict, takedamage))
3507 if(*prvm_leaktest_ignore_classnames.string)
3509 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_serveredictstring(edict, classname)))))
3513 else if (prog == CLVM_prog)
3515 // TODO someone add more stuff here
3516 if(PRVM_clientedictfloat(edict, entnum)) // csqc networked
3518 if(PRVM_clientedictfloat(edict, modelindex)) // visible ent?
3520 if(PRVM_clientedictfloat(edict, effects)) // particle effect?
3522 if(PRVM_clientedictfunction(edict, think)) // has a think function?
3523 if(PRVM_clientedictfloat(edict, nextthink) > 0) // that actually will eventually run?
3525 if(*prvm_leaktest_ignore_classnames.string)
3527 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_clientedictstring(edict, classname)))))
3533 // menu prog does not have classnames
3538 static qbool PRVM_IsEdictReferenced(prvm_prog_t *prog, prvm_edict_t *edict, int mark)
3541 int edictnum = PRVM_NUM_FOR_EDICT(edict);
3542 const char *targetname = NULL;
3544 if (prog == SVVM_prog && prvm_leaktest_follow_targetname.integer)
3545 targetname = PRVM_GetString(prog, PRVM_serveredictstring(edict, targetname));
3548 if(!*targetname) // ""
3551 for(j = 0; j < prog->num_edicts; ++j)
3553 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3554 if (ed->priv.required->mark < mark)
3560 const char *target = PRVM_GetString(prog, PRVM_serveredictstring(ed, target));
3562 if(!strcmp(target, targetname))
3565 for (i=0; i<prog->numfielddefs; ++i)
3567 mdef_t *d = &prog->fielddefs[i];
3568 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3570 if(edictnum == PRVM_EDICTFIELDEDICT(ed, d->ofs))
3578 static void PRVM_MarkReferencedEdicts(prvm_prog_t *prog)
3584 // Stage 1: world, all entities that are relevant, and all entities that are referenced by globals.
3586 for(j = 0; j < prog->num_edicts; ++j)
3588 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3591 ed->priv.required->mark = PRVM_IsEdictRelevant(prog, ed) ? stage : 0;
3593 for (i = 0;i < prog->numglobaldefs;i++)
3595 mdef_t *d = &prog->globaldefs[i];
3597 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3599 j = PRVM_GLOBALFIELDEDICT(d->ofs);
3600 if (i < 0 || j >= prog->max_edicts) {
3601 Con_Printf("Invalid entity reference from global %s.\n", PRVM_GetString(prog, d->s_name));
3604 ed = PRVM_EDICT_NUM(j);;
3605 ed->priv.required->mark = stage;
3608 // Future stages: all entities that are referenced by an entity of the previous stage.
3612 for(j = 0; j < prog->num_edicts; ++j)
3614 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3617 if(ed->priv.required->mark)
3619 if(PRVM_IsEdictReferenced(prog, ed, stage))
3621 ed->priv.required->mark = stage + 1;
3628 Con_DPrintf("leak check used %d stages to find all references\n", stage);
3631 void PRVM_LeakTest(prvm_prog_t *prog)
3634 qbool leaked = false;
3636 if(!prog->leaktest_active)
3640 for (i = 0; i < prog->numknownstrings; ++i)
3642 if(prog->knownstrings[i])
3643 if(prog->knownstrings_flags[i])
3644 if(prog->knownstrings_origin[i])
3645 if(!PRVM_IsStringReferenced(prog, PRVM_KNOWNSTRINGBASE + i))
3647 Con_Printf("Unreferenced string found!\n Value: %s\n Origin: %s\n", prog->knownstrings[i], prog->knownstrings_origin[i]);
3653 PRVM_MarkReferencedEdicts(prog);
3654 for(j = 0; j < prog->num_edicts; ++j)
3656 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3659 if(!ed->priv.required->mark)
3660 if(ed->priv.required->allocation_origin)
3662 Con_Printf("Unreferenced edict found!\n Allocated at: %s\n", ed->priv.required->allocation_origin);
3663 PRVM_ED_Print(prog, ed, NULL);
3668 ed->priv.required->mark = 0; // clear marks again when done
3671 for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); ++i)
3673 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
3675 if(stringbuffer->origin)
3677 Con_Printf("Open string buffer handle found!\n Allocated at: %s\n", stringbuffer->origin);
3682 for(i = 0; i < PRVM_MAX_OPENFILES; ++i)
3684 if(prog->openfiles[i])
3685 if(prog->openfiles_origin[i])
3687 Con_Printf("Open file handle found!\n Allocated at: %s\n", prog->openfiles_origin[i]);
3692 for(i = 0; i < PRVM_MAX_OPENSEARCHES; ++i)
3694 if(prog->opensearches[i])
3695 if(prog->opensearches_origin[i])
3697 Con_Printf("Open search handle found!\n Allocated at: %s\n", prog->opensearches_origin[i]);
3703 Con_Printf("Congratulations. No leaks found.\n");
3706 void PRVM_GarbageCollection(prvm_prog_t *prog)
3708 int limit = prvm_garbagecollection_scan_limit.integer * (prog == SVVM_prog ? sv.frametime : cl.realframetime);
3709 prvm_prog_garbagecollection_state_t *gc = &prog->gc;
3710 if (!prvm_garbagecollection_enable.integer)
3713 // we like to limit how much scanning we do so it doesn't put a significant
3714 // burden on the cpu, so each of these are not complete scans, we also like
3715 // to have consistent cpu usage so we do a bit of work on each category of
3716 // leaked object every frame
3722 case PRVM_GC_GLOBALS_MARK:
3723 for (; gc->globals_mark_progress < prog->numglobaldefs && (limit--) > 0; gc->globals_mark_progress++)
3725 mdef_t *d = &prog->globaldefs[gc->globals_mark_progress];
3730 prvm_int_t s = prog->globals.ip[d->ofs];
3731 if (s & PRVM_KNOWNSTRINGBASE)
3733 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3734 if (!prog->knownstrings[num])
3737 Con_DPrintf("PRVM_GarbageCollection: Found bogus strzone reference in global %i (global name: \"%s\"), erasing reference", d->ofs, PRVM_GetString(prog, d->s_name));
3738 prog->globals.ip[d->ofs] = 0;
3741 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3749 if (gc->globals_mark_progress >= prog->numglobaldefs)
3752 case PRVM_GC_FIELDS_MARK:
3753 for (; gc->fields_mark_progress < prog->numfielddefs && limit > 0;)
3755 mdef_t *d = &prog->fielddefs[gc->fields_mark_progress];
3759 //for (gc-> entityindex = 0; entityindex < prog->num_edicts; entityindex++)
3760 for (;gc->fields_mark_progress_entity < prog->num_edicts && (limit--) > 0;gc->fields_mark_progress_entity++)
3762 int entityindex = gc->fields_mark_progress_entity;
3763 prvm_int_t s = prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs];
3764 if (s & PRVM_KNOWNSTRINGBASE)
3766 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3767 if (!prog->knownstrings[num])
3770 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));
3771 prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs] = 0;
3774 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3777 if (gc->fields_mark_progress_entity >= prog->num_edicts)
3779 gc->fields_mark_progress_entity = 0;
3780 gc->fields_mark_progress++;
3784 gc->fields_mark_progress_entity = 0;
3785 gc->fields_mark_progress++;
3789 if (gc->fields_mark_progress >= prog->numfielddefs)
3792 case PRVM_GC_KNOWNSTRINGS_SWEEP:
3793 // free any strzone'd strings that are not marked
3794 if (!prvm_garbagecollection_strings.integer)
3799 for (;gc->knownstrings_sweep_progress < prog->numknownstrings && (limit--) > 0;gc->knownstrings_sweep_progress++)
3801 int num = gc->knownstrings_sweep_progress;
3802 if (prog->knownstrings[num] && (prog->knownstrings_flags[num] & (KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE)) == 0)
3804 if (prog->knownstrings_flags[num] & KNOWNSTRINGFLAG_GCPRUNE)
3806 // string has been marked for pruning two passes in a row
3807 if (prvm_garbagecollection_notify.integer)
3808 Con_DPrintf("prvm_garbagecollection_notify: %s: freeing unreferenced string %i: \"%s\"\n", prog->name, num, prog->knownstrings[num]);
3809 Mem_Free((char *)prog->knownstrings[num]);
3810 prog->knownstrings[num] = NULL;
3811 prog->knownstrings_flags[num] = 0;
3812 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3816 // mark it for pruning next pass
3817 prog->knownstrings_flags[num] |= KNOWNSTRINGFLAG_GCPRUNE;
3821 if (gc->knownstrings_sweep_progress >= prog->numknownstrings)
3826 memset(gc, 0, sizeof(*gc));
3827 // Con_Printf("%s%s GC: reset @ %f frametime %f scan_limit per frame %i\n", prog == SVVM_prog ? "^6" : "^5", prog->name, host.realtime, prog == SVVM_prog ? sv.frametime : cl.realframetime, limit);