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"};
50 cvar_t prvm_garbagecollection_scan_limit = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_scan_limit", "10000", "scan this many fields or resources per frame to free up unreferenced resources"};
51 cvar_t prvm_garbagecollection_strings = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_strings", "1", "automatically call strunzone() on strings that are not referenced"};
52 cvar_t prvm_stringdebug = {CF_CLIENT | CF_SERVER, "prvm_stringdebug", "0", "Print debug and warning messages related to strings"};
54 static double prvm_reuseedicts_always_allow = 0;
55 qbool prvm_runawaycheck = true;
57 //============================================================================
65 static void PRVM_MEM_Alloc(prvm_prog_t *prog)
69 // reserve space for the null entity aka world
70 // check bound of max_edicts
71 prog->max_edicts = bound(1 + prog->reserved_edicts, prog->max_edicts, prog->limit_edicts);
72 prog->num_edicts = bound(1 + prog->reserved_edicts, prog->num_edicts, prog->max_edicts);
74 // edictprivate_size has to be min as big prvm_edict_private_t
75 prog->edictprivate_size = max(prog->edictprivate_size,(int)sizeof(prvm_edict_private_t));
78 prog->edicts = (prvm_edict_t *)Mem_Alloc(prog->progs_mempool,prog->limit_edicts * sizeof(prvm_edict_t));
80 // alloc edict private space
81 prog->edictprivate = Mem_Alloc(prog->progs_mempool, prog->max_edicts * prog->edictprivate_size);
84 prog->entityfieldsarea = prog->entityfields * prog->max_edicts;
85 prog->edictsfields.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, prog->entityfieldsarea * sizeof(prvm_vec_t));
88 for(i = 0; i < prog->max_edicts; i++)
90 prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size);
91 prog->edicts[i].fields.fp = prog->edictsfields.fp + i * prog->entityfields;
97 PRVM_MEM_IncreaseEdicts
100 void PRVM_MEM_IncreaseEdicts(prvm_prog_t *prog)
104 if(prog->max_edicts >= prog->limit_edicts)
107 prog->begin_increase_edicts(prog);
110 prog->max_edicts = min(prog->max_edicts + 256, prog->limit_edicts);
112 prog->entityfieldsarea = prog->entityfields * prog->max_edicts;
113 prog->edictsfields.fp = (prvm_vec_t*)Mem_Realloc(prog->progs_mempool, (void *)prog->edictsfields.fp, prog->entityfieldsarea * sizeof(prvm_vec_t));
114 prog->edictprivate = (void *)Mem_Realloc(prog->progs_mempool, (void *)prog->edictprivate, prog->max_edicts * prog->edictprivate_size);
116 //set e and v pointers
117 for(i = 0; i < prog->max_edicts; i++)
119 prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size);
120 prog->edicts[i].fields.fp = prog->edictsfields.fp + i * prog->entityfields;
123 prog->end_increase_edicts(prog);
126 //============================================================================
129 int PRVM_ED_FindFieldOffset(prvm_prog_t *prog, const char *field)
132 d = PRVM_ED_FindField(prog, field);
138 int PRVM_ED_FindGlobalOffset(prvm_prog_t *prog, const char *global)
141 d = PRVM_ED_FindGlobal(prog, global);
147 func_t PRVM_ED_FindFunctionOffset(prvm_prog_t *prog, const char *function)
150 f = PRVM_ED_FindFunction(prog, function);
153 return (func_t)(f - prog->functions);
161 prvm_prog_t *PRVM_ProgFromString(const char *str)
163 if (!strcmp(str, "server"))
165 if (!strcmp(str, "client"))
168 if (!strcmp(str, "menu"))
176 PRVM_FriendlyProgFromString
179 prvm_prog_t *PRVM_FriendlyProgFromString(const char *str)
181 prvm_prog_t *prog = PRVM_ProgFromString(str);
184 Con_Printf("%s: unknown program name\n", str);
189 Con_Printf("%s: program is not loaded\n", str);
199 Sets everything to NULL.
201 Nota bene: this also marks the entity as allocated if it has been previously
202 freed and sets the allocation origin.
205 void PRVM_ED_ClearEdict(prvm_prog_t *prog, prvm_edict_t *e)
207 memset(e->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
209 e->freetime = host.realtime;
210 if(e->priv.required->allocation_origin)
211 Mem_Free((char *)e->priv.required->allocation_origin);
212 e->priv.required->allocation_origin = PRVM_AllocationOrigin(prog);
214 // AK: Let the init_edict function determine if something needs to be initialized
215 prog->init_edict(prog, e);
218 const char *PRVM_AllocationOrigin(prvm_prog_t *prog)
221 if(prog->leaktest_active)
222 if(prog->depth > 0) // actually in QC code and not just parsing the entities block of a map/savegame
224 // bones_was_here: this is the smallest 64 multiple that avoids truncation in Xonotic (was 256)
225 buf = (char *)PRVM_Alloc(448);
226 PRVM_ShortStackTrace(prog, buf, 448);
235 Returns if this particular edict could get allocated by PRVM_ED_Alloc
238 qbool PRVM_ED_CanAlloc(prvm_prog_t *prog, prvm_edict_t *e)
242 if(prvm_reuseedicts_always_allow == host.realtime)
244 if(host.realtime <= e->freetime + 0.1 && prvm_reuseedicts_neverinsameframe.integer)
245 return false; // never allow reuse in same frame (causes networking trouble)
246 if(e->freetime < prog->starttime + prvm_reuseedicts_startuptime.value)
248 if(host.realtime > e->freetime + 1)
250 return false; // entity slot still blocked because the entity was freed less than one second ago
257 Either finds a free edict, or allocates a new one.
258 Try to avoid reusing an entity that was recently freed, because it
259 can cause the client to think the entity morphed into something else
260 instead of being removed and recreated, which can cause interpolated
261 angles and bad trails.
264 prvm_edict_t *PRVM_ED_Alloc(prvm_prog_t *prog)
269 // the client qc dont need maxclients
270 // thus it doesnt need to use svs.maxclients
271 // AK: changed i=svs.maxclients+1
272 // AK: changed so the edict 0 wont spawn -> used as reserved/world entity
273 // although the menu/client has no world
274 for (i = prog->reserved_edicts + 1;i < prog->num_edicts;i++)
276 e = PRVM_EDICT_NUM(i);
277 if(PRVM_ED_CanAlloc(prog, e))
279 PRVM_ED_ClearEdict (prog, e);
284 if (i == prog->limit_edicts)
285 prog->error_cmd("%s: PRVM_ED_Alloc: no free edicts", prog->name);
288 if (prog->num_edicts >= prog->max_edicts)
289 PRVM_MEM_IncreaseEdicts(prog);
291 e = PRVM_EDICT_NUM(i);
293 PRVM_ED_ClearEdict(prog, e);
301 Marks the edict as free
303 FIXME: walk all entities and NULL out references to this entity
304 bones_was_here: do not want, that would break chains immediately!
305 Currently chains aren't broken by removing an entity, at least with prvm_reuseedicts_neverinsameframe 1
306 which is very handy and some QC code will depend on it.
309 void PRVM_ED_Free(prvm_prog_t *prog, prvm_edict_t *ed)
311 // dont delete the null entity (world) or reserved edicts
312 if (ed - prog->edicts <= prog->reserved_edicts)
315 prog->free_edict(prog, ed);
318 ed->freetime = host.realtime;
319 if(ed->priv.required->allocation_origin)
321 Mem_Free((char *)ed->priv.required->allocation_origin);
322 ed->priv.required->allocation_origin = NULL;
326 //===========================================================================
333 static mdef_t *PRVM_ED_GlobalAtOfs (prvm_prog_t *prog, unsigned int ofs)
338 for (i = 0;i < prog->numglobaldefs;i++)
340 def = &prog->globaldefs[i];
352 mdef_t *PRVM_ED_FieldAtOfs (prvm_prog_t *prog, unsigned int ofs)
357 for (i = 0;i < prog->numfielddefs;i++)
359 def = &prog->fielddefs[i];
371 mdef_t *PRVM_ED_FindField (prvm_prog_t *prog, const char *name)
376 for (i = 0;i < prog->numfielddefs;i++)
378 def = &prog->fielddefs[i];
379 if (!strcmp(PRVM_GetString(prog, def->s_name), name))
390 mdef_t *PRVM_ED_FindGlobal (prvm_prog_t *prog, const char *name)
395 for (i = 0;i < prog->numglobaldefs;i++)
397 def = &prog->globaldefs[i];
398 if (!strcmp(PRVM_GetString(prog, def->s_name), name))
406 PRVM_ED_FindGlobalEval
409 prvm_eval_t *PRVM_ED_FindGlobalEval(prvm_prog_t *prog, const char *name)
411 mdef_t *def = PRVM_ED_FindGlobal(prog, name);
412 return def ? (prvm_eval_t *) &prog->globals.fp[def->ofs] : NULL;
420 mfunction_t *PRVM_ED_FindFunction (prvm_prog_t *prog, const char *name)
425 for (i = 0;i < prog->numfunctions;i++)
427 func = &prog->functions[i];
428 if (!strcmp(PRVM_GetString(prog, func->s_name), name))
439 Returns a string describing *data in a type specific manner
442 static char *PRVM_ValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength)
448 type = (etype_t)((int) type & ~DEF_SAVEGLOBAL);
453 dp_strlcpy (line, PRVM_GetString (prog, val->string), linelength);
457 if (n < 0 || n >= prog->max_edicts)
458 dpsnprintf (line, linelength, "entity %i (invalid!)", n);
460 dpsnprintf (line, linelength, "entity %i", n);
463 if ((unsigned int)val->function < (unsigned int)prog->progs_numfunctions)
465 f = prog->functions + val->function;
466 dpsnprintf (line, linelength, "%s()", PRVM_GetString(prog, f->s_name));
469 dpsnprintf (line, linelength, "function %" PRVM_PRIi "() (invalid!)", val->function);
472 def = PRVM_ED_FieldAtOfs ( prog, val->_int );
474 dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name));
476 dpsnprintf (line, linelength, "field %" PRVM_PRIi " (invalid!)", val->_int );
479 dpsnprintf (line, linelength, "void");
482 // LadyHavoc: changed from %5.1f to %10.4f
483 dpsnprintf (line, linelength, PRVM_FLOAT_LOSSLESS_FORMAT, val->_float);
486 // LadyHavoc: changed from %5.1f to %10.4f
487 dpsnprintf (line, linelength, "'" PRVM_VECTOR_LOSSLESS_FORMAT "'", val->vector[0], val->vector[1], val->vector[2]);
490 dpsnprintf (line, linelength, "pointer");
493 dpsnprintf (line, linelength, "bad type %i", (int) type);
504 Returns a string describing *data in a type specific manner
505 Easier to parse than PR_ValueString
508 char *PRVM_UglyValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength)
515 type = (etype_t)((int)type & ~DEF_SAVEGLOBAL);
520 // Parse the string a bit to turn special characters
521 // (like newline, specifically) into escape codes,
522 // this fixes saving games from various mods
523 s = PRVM_GetString (prog, val->string);
524 for (i = 0;i < (int)linelength - 2 && *s;)
554 dpsnprintf (line, linelength, "%i", i);
557 if ((unsigned int)val->function < (unsigned int)prog->progs_numfunctions)
559 f = prog->functions + val->function;
560 dp_strlcpy (line, PRVM_GetString (prog, f->s_name), linelength);
563 dpsnprintf (line, linelength, "bad function %" PRVM_PRIi " (invalid!)", val->function);
566 def = PRVM_ED_FieldAtOfs ( prog, val->_int );
568 dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name));
570 dpsnprintf (line, linelength, "field %" PRVM_PRIi "(invalid!)", val->_int );
573 dpsnprintf (line, linelength, "void");
576 dpsnprintf (line, linelength, PRVM_FLOAT_LOSSLESS_FORMAT, val->_float);
579 dpsnprintf (line, linelength, PRVM_VECTOR_LOSSLESS_FORMAT, val->vector[0], val->vector[1], val->vector[2]);
582 dpsnprintf (line, linelength, "bad type %i", type);
593 Returns a string with a description and the contents of a global,
594 padded to 20 field width
597 char *PRVM_GlobalString (prvm_prog_t *prog, int ofs, char *line, size_t linelength)
603 char valuebuf[MAX_INPUTLINE];
605 val = (prvm_eval_t *)&prog->globals.fp[ofs];
606 def = PRVM_ED_GlobalAtOfs(prog, ofs);
608 dpsnprintf (line, linelength, "GLOBAL%i", ofs);
611 s = PRVM_ValueString (prog, (etype_t)def->type, val, valuebuf, sizeof(valuebuf));
612 dpsnprintf (line, linelength, "%s (=%s)", PRVM_GetString(prog, def->s_name), s);
616 //for ( ; i<20 ; i++)
617 // strcat (line," ");
623 char *PRVM_GlobalStringNoContents (prvm_prog_t *prog, int ofs, char *line, size_t linelength)
628 def = PRVM_ED_GlobalAtOfs(prog, ofs);
630 dpsnprintf (line, linelength, "GLOBAL%i", ofs);
632 dpsnprintf (line, linelength, "%s", PRVM_GetString(prog, def->s_name));
635 //for ( ; i<20 ; i++)
636 // strcat (line," ");
650 // LadyHavoc: optimized this to print out much more quickly (tempstring)
651 // LadyHavoc: changed to print out every 4096 characters (incase there are a lot of fields to print)
652 void PRVM_ED_Print(prvm_prog_t *prog, prvm_edict_t *ed, const char *wildcard_fieldname)
660 char tempstring[MAX_INPUTLINE], tempstring2[260]; // temporary string buffers
661 char valuebuf[MAX_INPUTLINE];
665 Con_Printf("%s: FREE\n",prog->name);
670 dpsnprintf(tempstring, sizeof(tempstring), "\n%s EDICT %i:\n", prog->name, PRVM_NUM_FOR_EDICT(ed));
671 for (i = 1;i < prog->numfielddefs;i++)
673 d = &prog->fielddefs[i];
674 name = PRVM_GetString(prog, d->s_name);
675 if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
676 continue; // skip _x, _y, _z vars
678 // Check Field Name Wildcard
679 if(wildcard_fieldname)
680 if( !matchpattern(name, wildcard_fieldname, 1) )
681 // Didn't match; skip
684 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
686 // if the value is still all 0, skip the field
687 type = d->type & ~DEF_SAVEGLOBAL;
689 for (j=0 ; j<prvm_type_size[type] ; j++)
692 if (j == prvm_type_size[type])
695 if (strlen(name) > sizeof(tempstring2)-4)
697 memcpy (tempstring2, name, sizeof(tempstring2)-4);
698 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
699 tempstring2[sizeof(tempstring2)-1] = 0;
702 dp_strlcat(tempstring, name, sizeof(tempstring));
703 for (l = strlen(name);l < 14;l++)
704 dp_strlcat(tempstring, " ", sizeof(tempstring));
705 dp_strlcat(tempstring, " ", sizeof(tempstring));
707 name = PRVM_ValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf));
708 if (strlen(name) > sizeof(tempstring2)-4)
710 memcpy (tempstring2, name, sizeof(tempstring2)-4);
711 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
712 tempstring2[sizeof(tempstring2)-1] = 0;
715 dp_strlcat(tempstring, name, sizeof(tempstring));
716 dp_strlcat(tempstring, "\n", sizeof(tempstring));
717 if (strlen(tempstring) >= sizeof(tempstring)/2)
719 Con_Print(tempstring);
724 Con_Print(tempstring);
734 void PRVM_ED_Write (prvm_prog_t *prog, qfile_t *f, prvm_edict_t *ed)
742 char valuebuf[MAX_INPUTLINE];
752 for (i = 1;i < prog->numfielddefs;i++)
754 d = &prog->fielddefs[i];
755 name = PRVM_GetString(prog, d->s_name);
757 if(developer_entityparsing.integer)
758 Con_Printf("PRVM_ED_Write: at entity %d field %s\n", PRVM_NUM_FOR_EDICT(ed), name);
760 //if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
761 if(strlen(name) > 1 && name[strlen(name)-2] == '_')
762 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?)
764 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
766 // if the value is still all 0, skip the field
767 type = d->type & ~DEF_SAVEGLOBAL;
768 for (j=0 ; j<prvm_type_size[type] ; j++)
771 if (j == prvm_type_size[type])
774 FS_Printf(f,"\"%s\" ",name);
775 prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_Write, ent=%d, name=%s", i, name);
776 FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf)));
777 prog->statestring = NULL;
783 void PRVM_ED_PrintNum (prvm_prog_t *prog, int ent, const char *wildcard_fieldname)
785 PRVM_ED_Print(prog, PRVM_EDICT_NUM(ent), wildcard_fieldname);
790 PRVM_ED_PrintEdicts_f
792 For debugging, prints all the entities in the current server
795 void PRVM_ED_PrintEdicts_f(cmd_state_t *cmd)
799 const char *wildcard_fieldname;
801 if(Cmd_Argc(cmd) < 2 || Cmd_Argc(cmd) > 3)
803 Con_Print("prvm_edicts <program name> <optional field name wildcard>\n");
807 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
810 if( Cmd_Argc(cmd) == 3)
811 wildcard_fieldname = Cmd_Argv(cmd, 2);
813 wildcard_fieldname = NULL;
815 Con_Printf("%s: %i entities\n", prog->name, prog->num_edicts);
816 for (i=0 ; i<prog->num_edicts ; i++)
817 PRVM_ED_PrintNum (prog, i, wildcard_fieldname);
824 For debugging, prints a single edict
827 static void PRVM_ED_PrintEdict_f(cmd_state_t *cmd)
831 const char *wildcard_fieldname;
833 if(Cmd_Argc(cmd) < 3 || Cmd_Argc(cmd) > 4)
835 Con_Print("prvm_edict <program name> <edict number> <optional field name wildcard>\n");
839 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
842 i = atoi (Cmd_Argv(cmd, 2));
843 if (i >= prog->num_edicts)
845 Con_Print("Bad edict number\n");
848 if( Cmd_Argc(cmd) == 4)
849 // Optional Wildcard Provided
850 wildcard_fieldname = Cmd_Argv(cmd, 3);
853 wildcard_fieldname = NULL;
854 PRVM_ED_PrintNum (prog, i, wildcard_fieldname);
864 // 2 possibilities : 1. just displaying the active edict count
865 // 2. making a function pointer [x]
866 static void PRVM_ED_Count_f(cmd_state_t *cmd)
870 if(Cmd_Argc(cmd) != 2)
872 Con_Print("prvm_count <program name>\n");
876 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
879 prog->count_edicts(prog);
883 ==============================================================================
887 FIXME: need to tag constants, doesn't really work
888 ==============================================================================
896 void PRVM_ED_WriteGlobals (prvm_prog_t *prog, qfile_t *f)
903 char valuebuf[MAX_INPUTLINE];
906 for (i = 0;i < prog->numglobaldefs;i++)
908 def = &prog->globaldefs[i];
910 if ( !(def->type & DEF_SAVEGLOBAL) )
912 type &= ~DEF_SAVEGLOBAL;
914 if (type != ev_string && type != ev_float && type != ev_entity)
917 name = PRVM_GetString(prog, def->s_name);
919 if(developer_entityparsing.integer)
920 Con_Printf("PRVM_ED_WriteGlobals: at global %s\n", name);
922 prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_WriteGlobals, name=%s", name);
923 FS_Printf(f,"\"%s\" ", name);
924 FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)type, (prvm_eval_t *)&prog->globals.fp[def->ofs], valuebuf, sizeof(valuebuf)));
925 prog->statestring = NULL;
935 void PRVM_ED_ParseGlobals (prvm_prog_t *prog, const char *data)
937 char keyname[MAX_INPUTLINE];
943 if (!COM_ParseToken_Simple(&data, false, false, true))
944 prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace");
945 if (com_token[0] == '}')
948 if (developer_entityparsing.integer)
949 Con_Printf("Key: \"%s\"", com_token);
951 dp_strlcpy (keyname, com_token, sizeof(keyname));
954 if (!COM_ParseToken_Simple(&data, false, true, true))
955 prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace");
957 if (developer_entityparsing.integer)
958 Con_Printf(" \"%s\"\n", com_token);
960 if (com_token[0] == '}')
961 prog->error_cmd("PRVM_ED_ParseGlobals: closing brace without data");
963 key = PRVM_ED_FindGlobal (prog, keyname);
966 Con_DPrintf("'%s' is not a global on %s\n", keyname, prog->name);
970 if (!PRVM_ED_ParseEpair(prog, NULL, key, com_token, true))
971 prog->error_cmd("PRVM_ED_ParseGlobals: parse error");
975 //============================================================================
982 Can parse either fields or globals
983 returns false if error
986 qbool PRVM_ED_ParseEpair(prvm_prog_t *prog, prvm_edict_t *ent, mdef_t *key, const char *s, qbool parsebackslash)
995 val = (prvm_eval_t *)(ent->fields.fp + key->ofs);
997 val = (prvm_eval_t *)(prog->globals.fp + key->ofs);
998 switch (key->type & ~DEF_SAVEGLOBAL)
1001 l = (int)strlen(s) + 1;
1002 val->string = PRVM_AllocString(prog, l, &new_p);
1003 for (i = 0;i < l;i++)
1005 if (s[i] == '\\' && s[i+1] && parsebackslash)
1010 else if (s[i] == 'r')
1021 while (*s && ISWHITESPACE(*s))
1023 val->_float = atof(s);
1027 for (i = 0;i < 3;i++)
1029 while (*s && ISWHITESPACE(*s))
1033 val->vector[i] = atof(s);
1034 while (!ISWHITESPACE(*s))
1042 while (*s && ISWHITESPACE(*s))
1045 if (i >= prog->limit_edicts)
1046 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);
1047 while (i >= prog->max_edicts)
1048 PRVM_MEM_IncreaseEdicts(prog);
1049 // if IncreaseEdicts was called the base pointer needs to be updated
1051 val = (prvm_eval_t *)(ent->fields.fp + key->ofs);
1052 val->edict = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM((int)i));
1058 Con_DPrintf("PRVM_ED_ParseEpair: Bogus field name %s in %s\n", s, prog->name);
1061 def = PRVM_ED_FindField(prog, s + 1);
1064 Con_DPrintf("PRVM_ED_ParseEpair: Can't find field %s in %s\n", s, prog->name);
1067 val->_int = def->ofs;
1071 func = PRVM_ED_FindFunction(prog, s);
1074 Con_Printf("PRVM_ED_ParseEpair: Can't find function %s in %s\n", s, prog->name);
1077 val->function = func - prog->functions;
1081 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);
1091 Console command to send a string to QC function GameCommand of the
1095 sv_cmd adminmsg 3 "do not teamkill"
1096 cl_cmd someclientcommand
1097 menu_cmd somemenucommand
1099 All progs can support this extension; sg calls it in server QC, cg in client
1103 static void PRVM_GameCommand(cmd_state_t *cmd, const char *whichprogs, const char *whichcmd)
1106 if(Cmd_Argc(cmd) < 1)
1108 Con_Printf("%s text...\n", whichcmd);
1112 if (!(prog = PRVM_FriendlyProgFromString(whichprogs)))
1115 if(!PRVM_allfunction(GameCommand))
1117 Con_Printf("%s program do not support GameCommand!\n", whichprogs);
1121 int restorevm_tempstringsbuf_cursize;
1126 restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize;
1127 PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, s ? s : "", s ? strlen(s) : 0);
1128 prog->ExecuteProgram(prog, PRVM_allfunction(GameCommand), "QC function GameCommand is missing");
1129 prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
1132 static void PRVM_GameCommand_Server_f(cmd_state_t *cmd)
1134 PRVM_GameCommand(cmd, "server", "sv_cmd");
1136 static void PRVM_GameCommand_Client_f(cmd_state_t *cmd)
1138 PRVM_GameCommand(cmd, "client", "cl_cmd");
1140 static void PRVM_GameCommand_Menu_f(cmd_state_t *cmd)
1142 PRVM_GameCommand(cmd, "menu", "menu_cmd");
1149 Console command to load a field of a specified edict
1152 static void PRVM_ED_EdictGet_f(cmd_state_t *cmd)
1159 char valuebuf[MAX_INPUTLINE];
1161 if(Cmd_Argc(cmd) != 4 && Cmd_Argc(cmd) != 5)
1163 Con_Print("prvm_edictget <program name> <edict number> <field> [<cvar>]\n");
1167 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1170 ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(cmd, 2)));
1172 if((key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, 3))) == 0)
1174 Con_Printf("Key %s not found !\n", Cmd_Argv(cmd, 3));
1178 v = (prvm_eval_t *)(ed->fields.fp + key->ofs);
1179 s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf));
1180 if(Cmd_Argc(cmd) == 5)
1182 cvar_t *cvar = Cvar_FindVar(cmd->cvars, Cmd_Argv(cmd, 4), cmd->cvars_flagsmask);
1184 if(Cvar_Readonly(cvar, "prvm_edictget"))
1187 Cvar_Get(cmd->cvars, Cmd_Argv(cmd, 4), s, cmd->cvars_flagsmask, NULL);
1190 Con_Printf("%s\n", s);
1196 static void PRVM_ED_GlobalGet_f(cmd_state_t *cmd)
1202 char valuebuf[MAX_INPUTLINE];
1204 if(Cmd_Argc(cmd) != 3 && Cmd_Argc(cmd) != 4)
1206 Con_Print("prvm_globalget <program name> <global> [<cvar>]\n");
1210 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1213 key = PRVM_ED_FindGlobal(prog, Cmd_Argv(cmd, 2));
1216 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
1220 v = (prvm_eval_t *) &prog->globals.fp[key->ofs];
1221 s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf));
1222 if(Cmd_Argc(cmd) == 4)
1224 cvar_t *cvar = Cvar_FindVar(cmd->cvars, Cmd_Argv(cmd, 3), cmd->cvars_flagsmask);
1226 if(Cvar_Readonly(cvar, "prvm_globalget"))
1228 Cvar_Get(cmd->cvars, Cmd_Argv(cmd, 3), s, cmd->cvars_flagsmask, NULL);
1231 Con_Printf("%s\n", s);
1241 Console command to set a field of a specified edict
1244 static void PRVM_ED_EdictSet_f(cmd_state_t *cmd)
1250 if(Cmd_Argc(cmd) != 5)
1252 Con_Print("prvm_edictset <program name> <edict number> <field> <value>\n");
1256 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1259 ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(cmd, 2)));
1261 if((key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, 3))) == 0)
1262 Con_Printf("Key %s not found!\n", Cmd_Argv(cmd, 3));
1264 PRVM_ED_ParseEpair(prog, ed, key, Cmd_Argv(cmd, 4), true);
1268 ====================
1271 Parses an edict out of the given string, returning the new position
1272 ed should be a properly initialized empty edict.
1273 Used for initial level load and for savegames.
1274 ====================
1276 const char *PRVM_ED_ParseEdict (prvm_prog_t *prog, const char *data, prvm_edict_t *ent)
1286 // go through all the dictionary pairs
1290 if (!COM_ParseToken_Simple(&data, false, false, true))
1291 prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace");
1292 if (developer_entityparsing.integer)
1293 Con_Printf("Key: \"%s\"", com_token);
1294 if (com_token[0] == '}')
1297 // anglehack is to allow QuakeEd to write single scalar angles
1298 // and allow them to be turned into vectors. (FIXME...)
1299 if (!strcmp(com_token, "angle"))
1301 dp_strlcpy (com_token, "angles", sizeof(com_token));
1307 // FIXME: change light to _light to get rid of this hack
1308 if (!strcmp(com_token, "light"))
1309 dp_strlcpy (com_token, "light_lev", sizeof(com_token)); // hack for single light def
1311 dp_strlcpy (keyname, com_token, sizeof(keyname));
1313 // another hack to fix keynames with trailing spaces
1314 n = strlen(keyname);
1315 while (n && keyname[n-1] == ' ')
1322 if (!COM_ParseToken_Simple(&data, false, false, true))
1323 prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace");
1324 if (developer_entityparsing.integer)
1325 Con_Printf(" \"%s\"\n", com_token);
1327 if (com_token[0] == '}')
1328 prog->error_cmd("PRVM_ED_ParseEdict: closing brace without data");
1332 // ignore attempts to set key "" (this problem occurs in nehahra neh1m8.bsp)
1336 // keynames with a leading underscore are used for utility comments,
1337 // and are immediately discarded by quake
1338 if (keyname[0] == '_')
1341 key = PRVM_ED_FindField (prog, keyname);
1344 Con_DPrintf("%s: '%s' is not a field\n", prog->name, keyname);
1351 dp_strlcpy (temp, com_token, sizeof(temp));
1352 dpsnprintf (com_token, sizeof(com_token), "0 %s 0", temp);
1355 if (!PRVM_ED_ParseEpair(prog, ent, key, com_token, strcmp(keyname, "wad") != 0))
1356 prog->error_cmd("PRVM_ED_ParseEdict: parse error");
1361 ent->freetime = host.realtime;
1367 void PRVM_ED_CallPrespawnFunction(prvm_prog_t *prog, prvm_edict_t *ent)
1369 if (PRVM_serverfunction(SV_OnEntityPreSpawnFunction))
1372 PRVM_serverglobalfloat(time) = sv.time;
1373 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1374 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPreSpawnFunction), "QC function SV_OnEntityPreSpawnFunction is missing");
1378 qbool PRVM_ED_CallSpawnFunction(prvm_prog_t *prog, prvm_edict_t *ent, const char *data, const char *start)
1380 const char *funcname;
1382 prvm_eval_t *fulldata = NULL;
1386 // immediately call spawn function, but only if there is a self global and a classname
1390 if (!PRVM_alledictstring(ent, classname))
1392 Con_Print("No classname for:\n");
1393 PRVM_ED_Print(prog, ent, NULL);
1394 PRVM_ED_Free (prog, ent);
1398 * This is required for FTE compatibility (FreeCS).
1399 * It copies the key/value pairs themselves into a
1400 * global for QC to parse on its own.
1402 else if (data && start)
1404 if((fulldata = PRVM_ED_FindGlobalEval(prog, "__fullspawndata")))
1408 fulldata->string = PRVM_AllocString(prog, data - start + 1, &spawndata);
1409 for(in = start; in < data; )
1413 *spawndata++ = '\t';
1421 // look for the spawn function
1422 funcname = PRVM_GetString(prog, PRVM_alledictstring(ent, classname));
1423 func = PRVM_ED_FindFunction (prog, va(vabuf, sizeof(vabuf), "spawnfunc_%s", funcname));
1425 if(!PRVM_allglobalfloat(require_spawnfunc_prefix))
1426 func = PRVM_ED_FindFunction (prog, funcname);
1430 // check for OnEntityNoSpawnFunction
1431 if (PRVM_serverfunction(SV_OnEntityNoSpawnFunction))
1434 PRVM_serverglobalfloat(time) = sv.time;
1435 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1436 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityNoSpawnFunction), "QC function SV_OnEntityNoSpawnFunction is missing");
1441 Con_DPrint("No spawn function for:\n");
1442 if (developer.integer > 0) // don't confuse non-developers with errors
1443 PRVM_ED_Print(prog, ent, NULL);
1445 PRVM_ED_Free (prog, ent);
1446 return false; // not included in "inhibited" count
1452 PRVM_serverglobalfloat(time) = sv.time;
1453 PRVM_allglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1454 prog->ExecuteProgram(prog, func - prog->functions, "");
1458 PRVM_ED_Free(prog, ent);
1462 void PRVM_ED_CallPostspawnFunction (prvm_prog_t *prog, prvm_edict_t *ent)
1465 if (PRVM_serverfunction(SV_OnEntityPostSpawnFunction))
1468 PRVM_serverglobalfloat(time) = sv.time;
1469 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1470 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPostSpawnFunction), "QC function SV_OnEntityPostSpawnFunction is missing");
1476 PRVM_ED_LoadFromFile
1478 The entities are directly placed in the array, rather than allocated with
1479 PRVM_ED_Alloc, because otherwise an error loading the map would have entity
1480 number references out of order.
1482 Creates a server's entity / program execution context by
1483 parsing textual entity definitions out of an ent file.
1485 Used for both fresh maps and savegame loads. A fresh map would also need
1486 to call PRVM_ED_CallSpawnFunctions () to let the objects initialize themselves.
1489 void PRVM_ED_LoadFromFile (prvm_prog_t *prog, const char *data)
1493 int parsed, inhibited, spawned, died;
1500 prvm_reuseedicts_always_allow = host.realtime;
1507 // parse the opening brace
1508 if (!COM_ParseToken_Simple(&data, false, false, true))
1510 if (com_token[0] != '{')
1511 prog->error_cmd("PRVM_ED_LoadFromFile: %s: found %s when expecting {", prog->name, com_token);
1513 // CHANGED: this is not conform to PR_LoadFromFile
1514 if(prog->loadintoworld)
1516 prog->loadintoworld = false;
1517 ent = PRVM_EDICT_NUM(0);
1520 ent = PRVM_ED_Alloc(prog);
1523 if (ent != prog->edicts) // hack
1524 memset (ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
1526 data = PRVM_ED_ParseEdict (prog, data, ent);
1529 // remove the entity ?
1530 if(!prog->load_edict(prog, ent))
1532 PRVM_ED_Free(prog, ent);
1537 PRVM_ED_CallPrespawnFunction(prog, ent);
1547 if(!PRVM_ED_CallSpawnFunction(prog, ent, data, start))
1550 PRVM_ED_CallPostspawnFunction(prog, ent);
1557 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);
1559 prvm_reuseedicts_always_allow = 0;
1562 static void PRVM_FindOffsets(prvm_prog_t *prog)
1564 // field and global searches use -1 for NULL
1565 memset(&prog->fieldoffsets, -1, sizeof(prog->fieldoffsets));
1566 memset(&prog->globaloffsets, -1, sizeof(prog->globaloffsets));
1567 // function searches use 0 for NULL
1568 memset(&prog->funcoffsets, 0, sizeof(prog->funcoffsets));
1569 #define PRVM_DECLARE_serverglobalfloat(x)
1570 #define PRVM_DECLARE_serverglobalvector(x)
1571 #define PRVM_DECLARE_serverglobalstring(x)
1572 #define PRVM_DECLARE_serverglobaledict(x)
1573 #define PRVM_DECLARE_serverglobalfunction(x)
1574 #define PRVM_DECLARE_clientglobalfloat(x)
1575 #define PRVM_DECLARE_clientglobalvector(x)
1576 #define PRVM_DECLARE_clientglobalstring(x)
1577 #define PRVM_DECLARE_clientglobaledict(x)
1578 #define PRVM_DECLARE_clientglobalfunction(x)
1579 #define PRVM_DECLARE_menuglobalfloat(x)
1580 #define PRVM_DECLARE_menuglobalvector(x)
1581 #define PRVM_DECLARE_menuglobalstring(x)
1582 #define PRVM_DECLARE_menuglobaledict(x)
1583 #define PRVM_DECLARE_menuglobalfunction(x)
1584 #define PRVM_DECLARE_serverfieldfloat(x)
1585 #define PRVM_DECLARE_serverfieldvector(x)
1586 #define PRVM_DECLARE_serverfieldstring(x)
1587 #define PRVM_DECLARE_serverfieldedict(x)
1588 #define PRVM_DECLARE_serverfieldfunction(x)
1589 #define PRVM_DECLARE_clientfieldfloat(x)
1590 #define PRVM_DECLARE_clientfieldvector(x)
1591 #define PRVM_DECLARE_clientfieldstring(x)
1592 #define PRVM_DECLARE_clientfieldedict(x)
1593 #define PRVM_DECLARE_clientfieldfunction(x)
1594 #define PRVM_DECLARE_menufieldfloat(x)
1595 #define PRVM_DECLARE_menufieldvector(x)
1596 #define PRVM_DECLARE_menufieldstring(x)
1597 #define PRVM_DECLARE_menufieldedict(x)
1598 #define PRVM_DECLARE_menufieldfunction(x)
1599 #define PRVM_DECLARE_serverfunction(x)
1600 #define PRVM_DECLARE_clientfunction(x)
1601 #define PRVM_DECLARE_menufunction(x)
1602 #define PRVM_DECLARE_field(x) prog->fieldoffsets.x = PRVM_ED_FindFieldOffset(prog, #x);
1603 #define PRVM_DECLARE_global(x) prog->globaloffsets.x = PRVM_ED_FindGlobalOffset(prog, #x);
1604 #define PRVM_DECLARE_function(x) prog->funcoffsets.x = PRVM_ED_FindFunctionOffset(prog, #x);
1605 #include "prvm_offsets.h"
1606 #undef PRVM_DECLARE_serverglobalfloat
1607 #undef PRVM_DECLARE_serverglobalvector
1608 #undef PRVM_DECLARE_serverglobalstring
1609 #undef PRVM_DECLARE_serverglobaledict
1610 #undef PRVM_DECLARE_serverglobalfunction
1611 #undef PRVM_DECLARE_clientglobalfloat
1612 #undef PRVM_DECLARE_clientglobalvector
1613 #undef PRVM_DECLARE_clientglobalstring
1614 #undef PRVM_DECLARE_clientglobaledict
1615 #undef PRVM_DECLARE_clientglobalfunction
1616 #undef PRVM_DECLARE_menuglobalfloat
1617 #undef PRVM_DECLARE_menuglobalvector
1618 #undef PRVM_DECLARE_menuglobalstring
1619 #undef PRVM_DECLARE_menuglobaledict
1620 #undef PRVM_DECLARE_menuglobalfunction
1621 #undef PRVM_DECLARE_serverfieldfloat
1622 #undef PRVM_DECLARE_serverfieldvector
1623 #undef PRVM_DECLARE_serverfieldstring
1624 #undef PRVM_DECLARE_serverfieldedict
1625 #undef PRVM_DECLARE_serverfieldfunction
1626 #undef PRVM_DECLARE_clientfieldfloat
1627 #undef PRVM_DECLARE_clientfieldvector
1628 #undef PRVM_DECLARE_clientfieldstring
1629 #undef PRVM_DECLARE_clientfieldedict
1630 #undef PRVM_DECLARE_clientfieldfunction
1631 #undef PRVM_DECLARE_menufieldfloat
1632 #undef PRVM_DECLARE_menufieldvector
1633 #undef PRVM_DECLARE_menufieldstring
1634 #undef PRVM_DECLARE_menufieldedict
1635 #undef PRVM_DECLARE_menufieldfunction
1636 #undef PRVM_DECLARE_serverfunction
1637 #undef PRVM_DECLARE_clientfunction
1638 #undef PRVM_DECLARE_menufunction
1639 #undef PRVM_DECLARE_field
1640 #undef PRVM_DECLARE_global
1641 #undef PRVM_DECLARE_function
1646 typedef struct dpfield_s
1653 #define DPFIELDS (sizeof(dpfields) / sizeof(dpfield_t))
1655 dpfield_t dpfields[] =
1666 #define PO_HASHSIZE 16384
1667 typedef struct po_string_s
1670 struct po_string_s *nextonhashchain;
1675 po_string_t *hashtable[PO_HASHSIZE];
1678 static void PRVM_PO_UnparseString(char *out, const char *in, size_t outsize)
1687 case '\a': if(outsize >= 2) { *out++ = '\\'; *out++ = 'a'; outsize -= 2; } break;
1688 case '\b': if(outsize >= 2) { *out++ = '\\'; *out++ = 'b'; outsize -= 2; } break;
1689 case '\t': if(outsize >= 2) { *out++ = '\\'; *out++ = 't'; outsize -= 2; } break;
1690 case '\r': if(outsize >= 2) { *out++ = '\\'; *out++ = 'r'; outsize -= 2; } break;
1691 case '\n': if(outsize >= 2) { *out++ = '\\'; *out++ = 'n'; outsize -= 2; } break;
1692 case '\\': if(outsize >= 2) { *out++ = '\\'; *out++ = '\\'; outsize -= 2; } break;
1693 case '"': if(outsize >= 2) { *out++ = '\\'; *out++ = '"'; outsize -= 2; } break;
1695 if(*in >= 0 && *in <= 0x1F)
1700 *out++ = '0' + ((*in & 0700) >> 6);
1701 *out++ = '0' + ((*in & 0070) >> 3);
1702 *out++ = '0' + (*in & 0007) ;
1719 static void PRVM_PO_ParseString(char *out, const char *in, size_t outsize)
1732 case 'a': if(outsize > 0) { *out++ = '\a'; --outsize; } break;
1733 case 'b': if(outsize > 0) { *out++ = '\b'; --outsize; } break;
1734 case 't': if(outsize > 0) { *out++ = '\t'; --outsize; } break;
1735 case 'r': if(outsize > 0) { *out++ = '\r'; --outsize; } break;
1736 case 'n': if(outsize > 0) { *out++ = '\n'; --outsize; } break;
1737 case '\\': if(outsize > 0) { *out++ = '\\'; --outsize; } break;
1738 case '"': if(outsize > 0) { *out++ = '"'; --outsize; } break;
1739 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
1743 if(*in >= '0' && *in <= '7')
1746 *out = (*out << 3) | (*in - '0');
1749 if(*in >= '0' && *in <= '7')
1752 *out = (*out << 3) | (*in - '0');
1763 if(outsize > 0) { *out++ = *in; --outsize; }
1778 static po_t *PRVM_PO_Load(const char *filename, const char *filename2, mempool_t *pool)
1783 char inbuf[MAX_INPUTLINE];
1784 char decodedbuf[MAX_INPUTLINE];
1787 po_string_t thisstr;
1790 for (i = 0; i < 2; ++i)
1792 const char *buf = (const char *)
1793 FS_LoadFile((i > 0 ? filename : filename2), pool, true, NULL);
1794 // first read filename2, then read filename
1795 // so that progs.dat.de.po wins over common.de.po
1796 // and within file, last item wins
1803 po = (po_t *)Mem_Alloc(pool, sizeof(*po));
1804 memset(po, 0, sizeof(*po));
1807 memset(&thisstr, 0, sizeof(thisstr)); // hush compiler warning
1815 p = strchr(p, '\n');
1821 if(*p == '\r' || *p == '\n')
1826 if(!strncmp(p, "msgid \"", 7))
1831 else if(!strncmp(p, "msgstr \"", 8))
1838 p = strchr(p, '\n');
1848 q = strchr(p, '\n');
1855 if((size_t)(q - p) >= (size_t) sizeof(inbuf))
1857 dp_strlcpy(inbuf, p, q - p); // not - 1, because this adds a NUL
1858 PRVM_PO_ParseString(decodedbuf + decodedpos, inbuf, sizeof(decodedbuf) - decodedpos);
1859 decodedpos += strlen(decodedbuf + decodedpos);
1869 Mem_Free(thisstr.key);
1870 thisstr.key = (char *)Mem_Alloc(pool, decodedpos + 1);
1871 memcpy(thisstr.key, decodedbuf, decodedpos + 1);
1873 else if(decodedpos > 0 && thisstr.key) // skip empty translation results
1875 thisstr.value = (char *)Mem_Alloc(pool, decodedpos + 1);
1876 memcpy(thisstr.value, decodedbuf, decodedpos + 1);
1877 hashindex = CRC_Block((const unsigned char *) thisstr.key, strlen(thisstr.key)) % PO_HASHSIZE;
1878 thisstr.nextonhashchain = po->hashtable[hashindex];
1879 po->hashtable[hashindex] = (po_string_t *)Mem_Alloc(pool, sizeof(thisstr));
1880 memcpy(po->hashtable[hashindex], &thisstr, sizeof(thisstr));
1881 memset(&thisstr, 0, sizeof(thisstr));
1885 Mem_Free((char *) buf);
1890 static const char *PRVM_PO_Lookup(po_t *po, const char *str)
1892 int hashindex = CRC_Block((const unsigned char *) str, strlen(str)) % PO_HASHSIZE;
1893 po_string_t *p = po->hashtable[hashindex];
1896 if(!strcmp(str, p->key))
1898 p = p->nextonhashchain;
1902 static void PRVM_PO_Destroy(po_t *po)
1905 for(i = 0; i < PO_HASHSIZE; ++i)
1907 po_string_t *p = po->hashtable[i];
1911 p = p->nextonhashchain;
1920 void PRVM_LeakTest(prvm_prog_t *prog);
1921 void PRVM_Prog_Reset(prvm_prog_t *prog)
1925 if(prog->tempstringsbuf.cursize)
1926 Mem_Free(prog->tempstringsbuf.data);
1927 prog->tempstringsbuf.cursize = 0;
1928 PRVM_LeakTest(prog);
1929 prog->reset_cmd(prog);
1930 Mem_FreePool(&prog->progs_mempool);
1932 PRVM_PO_Destroy((po_t *) prog->po);
1934 memset(prog,0,sizeof(prvm_prog_t));
1935 prog->break_statement = -1;
1936 prog->watch_global_type = ev_void;
1937 prog->watch_field_type = ev_void;
1945 static void PRVM_LoadLNO( prvm_prog_t *prog, const char *progname ) {
1946 fs_offset_t filesize;
1948 unsigned int *header;
1951 FS_StripExtension( progname, filename, sizeof( filename ) );
1952 dp_strlcat( filename, ".lno", sizeof( filename ) );
1954 lno = FS_LoadFile( filename, tempmempool, false, &filesize );
1960 <Spike> SafeWrite (h, &lnotype, sizeof(int));
1961 <Spike> SafeWrite (h, &version, sizeof(int));
1962 <Spike> SafeWrite (h, &numglobaldefs, sizeof(int));
1963 <Spike> SafeWrite (h, &numpr_globals, sizeof(int));
1964 <Spike> SafeWrite (h, &numfielddefs, sizeof(int));
1965 <Spike> SafeWrite (h, &numstatements, sizeof(int));
1966 <Spike> SafeWrite (h, statement_linenums, numstatements*sizeof(int));
1968 if ((unsigned int)filesize < (6 + prog->progs_numstatements) * sizeof(int))
1974 header = (unsigned int *) lno;
1975 if( header[ 0 ] == *(unsigned int *) "LNOF" &&
1976 LittleLong( header[ 1 ] ) == 1 &&
1977 (unsigned int)LittleLong( header[ 2 ] ) == (unsigned int)prog->progs_numglobaldefs &&
1978 (unsigned int)LittleLong( header[ 3 ] ) == (unsigned int)prog->progs_numglobals &&
1979 (unsigned int)LittleLong( header[ 4 ] ) == (unsigned int)prog->progs_numfielddefs &&
1980 (unsigned int)LittleLong( header[ 5 ] ) == (unsigned int)prog->progs_numstatements )
1982 prog->statement_linenums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1983 memcpy( prog->statement_linenums, header + 6, prog->progs_numstatements * sizeof( int ) );
1985 /* gmqcc suports columnums */
1986 if ((unsigned int)filesize > ((6 + 2 * prog->progs_numstatements) * sizeof( int )))
1988 prog->statement_columnnums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1989 memcpy( prog->statement_columnnums, header + 6 + prog->progs_numstatements, prog->progs_numstatements * sizeof( int ) );
2000 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog);
2001 void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * data, fs_offset_t size, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global)
2004 dprograms_t *dprograms;
2006 dstatement16_t *instatements16;
2007 dstatement32_t *instatements32;
2008 ddef16_t *infielddefs16;
2009 ddef32_t *infielddefs32;
2010 ddef16_t *inglobaldefs16;
2011 ddef32_t *inglobaldefs32;
2014 dfunction_t *infunctions;
2016 fs_offset_t filesize;
2017 int requiredglobalspace;
2035 prog->error_cmd("PRVM_LoadProgs: there is already a %s program loaded!", prog->name );
2037 Host_LockSession(); // all progs can use the session cvar
2038 Crypto_LoadKeys(); // all progs might use the keys at init time
2042 dprograms = (dprograms_t *) data;
2046 dprograms = (dprograms_t *)FS_LoadFile (filename, prog->progs_mempool, false, &filesize);
2047 if (dprograms == NULL || filesize < (fs_offset_t)sizeof(dprograms_t))
2048 prog->error_cmd("PRVM_LoadProgs: couldn't load %s for %s", filename, prog->name);
2049 // TODO bounds check header fields (e.g. numstatements), they must never go behind end of file
2051 prog->profiletime = Sys_DirtyTime();
2052 prog->starttime = host.realtime;
2054 requiredglobalspace = 0;
2055 for (i = 0;i < numrequiredglobals;i++)
2056 requiredglobalspace += required_global[i].type == ev_vector ? 3 : 1;
2058 prog->filecrc = CRC_Block((unsigned char *)dprograms, filesize);
2060 // byte swap the header
2061 prog->progs_version = LittleLong(dprograms->version);
2062 prog->progs_crc = LittleLong(dprograms->crc);
2063 if (prog->progs_version == 7)
2065 dprograms_v7_t *v7 = (dprograms_v7_t*)dprograms;
2066 structtype = LittleLong(v7->secondaryversion);
2067 if (structtype == PROG_SECONDARYVERSION16 ||
2068 structtype == PROG_SECONDARYVERSION32) // barely supported
2069 Con_Printf(CON_WARN "WARNING: %s: %s targets FTEQW, for which support is incomplete. Proceed at your own risk.\n", prog->name, filename);
2071 prog->error_cmd("%s: %s targets unknown engine", prog->name, filename);
2073 if (v7->numbodylessfuncs != 0 || v7->numtypes != 0 || v7->blockscompressed != 0)
2074 prog->error_cmd("%s: %s uses unsupported features.", prog->name, filename);
2076 else if (prog->progs_version != PROG_VERSION)
2077 prog->error_cmd("%s: %s has wrong version number (%i should be %i)", prog->name, filename, prog->progs_version, PROG_VERSION);
2078 instatements16 = (dstatement16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_statements));
2079 instatements32 = (dstatement32_t *)instatements16;
2080 prog->progs_numstatements = LittleLong(dprograms->numstatements);
2081 inglobaldefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globaldefs));
2082 inglobaldefs32 = (ddef32_t *)inglobaldefs16;
2083 prog->progs_numglobaldefs = LittleLong(dprograms->numglobaldefs);
2084 infielddefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_fielddefs));
2085 infielddefs32 = (ddef32_t *)infielddefs16;
2086 prog->progs_numfielddefs = LittleLong(dprograms->numfielddefs);
2087 infunctions = (dfunction_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_functions));
2088 prog->progs_numfunctions = LittleLong(dprograms->numfunctions);
2089 instrings = (char *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_strings));
2090 prog->progs_numstrings = LittleLong(dprograms->numstrings);
2091 inglobals = (int *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globals));
2092 prog->progs_numglobals = LittleLong(dprograms->numglobals);
2093 prog->progs_entityfields = LittleLong(dprograms->entityfields);
2095 prog->numstatements = prog->progs_numstatements;
2096 prog->numglobaldefs = prog->progs_numglobaldefs;
2097 prog->numfielddefs = prog->progs_numfielddefs;
2098 prog->numfunctions = prog->progs_numfunctions;
2099 prog->numstrings = prog->progs_numstrings;
2100 prog->numglobals = prog->progs_numglobals;
2101 prog->entityfields = prog->progs_entityfields;
2103 if (LittleLong(dprograms->ofs_strings) + prog->progs_numstrings > (int)filesize)
2104 prog->error_cmd("%s: %s strings go past end of file", prog->name, filename);
2105 prog->strings = (char *)Mem_Alloc(prog->progs_mempool, prog->progs_numstrings);
2106 memcpy(prog->strings, instrings, prog->progs_numstrings);
2107 prog->stringssize = prog->progs_numstrings;
2109 prog->numknownstrings = 0;
2110 prog->maxknownstrings = 0;
2111 prog->knownstrings = NULL;
2112 prog->knownstrings_flags = NULL;
2114 Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64);
2116 // we need to expand the globaldefs and fielddefs to include engine defs
2117 prog->globaldefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobaldefs + numrequiredglobals) * sizeof(mdef_t));
2118 prog->globals.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobals + requiredglobalspace + 2) * sizeof(prvm_vec_t));
2119 // + 2 is because of an otherwise occurring overrun in RETURN instruction
2120 // when trying to return the last or second-last global
2121 // (RETURN always returns a vector, there is no RETURN_F instruction)
2122 prog->fielddefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numfielddefs + numrequiredfields) * sizeof(mdef_t));
2123 // we need to convert the statements to our memory format
2124 prog->statements = (mstatement_t *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(mstatement_t));
2125 // allocate space for profiling statement usage
2126 prog->statement_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2127 prog->explicit_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2128 // functions need to be converted to the memory format
2129 prog->functions = (mfunction_t *)Mem_Alloc(prog->progs_mempool, sizeof(mfunction_t) * prog->progs_numfunctions);
2131 for (i = 0;i < prog->progs_numfunctions;i++)
2133 prog->functions[i].first_statement = LittleLong(infunctions[i].first_statement);
2134 prog->functions[i].parm_start = LittleLong(infunctions[i].parm_start);
2135 prog->functions[i].s_name = LittleLong(infunctions[i].s_name);
2136 prog->functions[i].s_file = LittleLong(infunctions[i].s_file);
2137 prog->functions[i].numparms = LittleLong(infunctions[i].numparms);
2138 prog->functions[i].locals = LittleLong(infunctions[i].locals);
2139 memcpy(prog->functions[i].parm_size, infunctions[i].parm_size, sizeof(infunctions[i].parm_size));
2140 if(prog->functions[i].first_statement >= prog->numstatements)
2141 prog->error_cmd("PRVM_LoadProgs: out of bounds function statement (function %d) in %s", i, prog->name);
2142 // TODO bounds check parm_start, s_name, s_file, numparms, locals, parm_size
2145 // copy the globaldefs to the new globaldefs list
2148 case PROG_SECONDARYVERSION32:
2149 for (i=0 ; i<prog->numglobaldefs ; i++)
2151 prog->globaldefs[i].type = LittleLong(inglobaldefs32[i].type);
2152 prog->globaldefs[i].ofs = LittleLong(inglobaldefs32[i].ofs);
2153 prog->globaldefs[i].s_name = LittleLong(inglobaldefs32[i].s_name);
2154 // TODO bounds check ofs, s_name
2158 for (i=0 ; i<prog->numglobaldefs ; i++)
2160 prog->globaldefs[i].type = (unsigned short)LittleShort(inglobaldefs16[i].type);
2161 prog->globaldefs[i].ofs = (unsigned short)LittleShort(inglobaldefs16[i].ofs);
2162 prog->globaldefs[i].s_name = LittleLong(inglobaldefs16[i].s_name);
2163 // TODO bounds check ofs, s_name
2168 // append the required globals
2169 for (i = 0;i < numrequiredglobals;i++)
2171 prog->globaldefs[prog->numglobaldefs].type = required_global[i].type;
2172 prog->globaldefs[prog->numglobaldefs].ofs = prog->numglobals;
2173 prog->globaldefs[prog->numglobaldefs].s_name = PRVM_SetEngineString(prog, required_global[i].name);
2174 if (prog->globaldefs[prog->numglobaldefs].type == ev_vector)
2175 prog->numglobals += 3;
2178 prog->numglobaldefs++;
2181 // copy the progs fields to the new fields list
2184 case PROG_SECONDARYVERSION32:
2185 for (i = 0;i < prog->numfielddefs;i++)
2187 prog->fielddefs[i].type = LittleLong(infielddefs32[i].type);
2188 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2189 prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name);
2190 prog->fielddefs[i].ofs = LittleLong(infielddefs32[i].ofs);
2191 prog->fielddefs[i].s_name = LittleLong(infielddefs32[i].s_name);
2192 // TODO bounds check ofs, s_name
2196 for (i = 0;i < prog->numfielddefs;i++)
2198 prog->fielddefs[i].type = (unsigned short)LittleShort(infielddefs16[i].type);
2199 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2200 prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name);
2201 prog->fielddefs[i].ofs = (unsigned short)LittleShort(infielddefs16[i].ofs);
2202 prog->fielddefs[i].s_name = LittleLong(infielddefs16[i].s_name);
2203 // TODO bounds check ofs, s_name
2208 // append the required fields
2209 for (i = 0;i < numrequiredfields;i++)
2211 prog->fielddefs[prog->numfielddefs].type = required_field[i].type;
2212 prog->fielddefs[prog->numfielddefs].ofs = prog->entityfields;
2213 prog->fielddefs[prog->numfielddefs].s_name = PRVM_SetEngineString(prog, required_field[i].name);
2214 if (prog->fielddefs[prog->numfielddefs].type == ev_vector)
2215 prog->entityfields += 3;
2217 prog->entityfields++;
2218 prog->numfielddefs++;
2221 // LadyHavoc: TODO: reorder globals to match engine struct
2222 // LadyHavoc: TODO: reorder fields to match engine struct
2223 #define remapglobal(index) (index)
2224 #define remapfield(index) (index)
2227 // FIXME: LadyHavoc: this uses a crude way to identify integer constants, rather than checking for matching globaldefs and checking their type
2228 for (i = 0;i < prog->progs_numglobals;i++)
2230 u.i = LittleLong(inglobals[i]);
2231 // most globals are 0, we only need to deal with the ones that are not
2234 d = u.i & 0xFF800000;
2235 if ((d == 0xFF800000) || (d == 0))
2237 // Looks like an integer (expand to int64)
2238 prog->globals.ip[remapglobal(i)] = u.i;
2242 // Looks like a float (expand to double)
2243 prog->globals.fp[remapglobal(i)] = u.f;
2248 // copy, remap globals in statements, bounds check
2249 for (i = 0;i < prog->progs_numstatements;i++)
2253 case PROG_SECONDARYVERSION32:
2254 op = (opcode_t)LittleLong(instatements32[i].op);
2255 a = (unsigned int)LittleLong(instatements32[i].a);
2256 b = (unsigned int)LittleLong(instatements32[i].b);
2257 c = (unsigned int)LittleLong(instatements32[i].c);
2260 op = (opcode_t)LittleShort(instatements16[i].op);
2261 a = (unsigned short)LittleShort(instatements16[i].a);
2262 b = (unsigned short)LittleShort(instatements16[i].b);
2263 c = (unsigned short)LittleShort(instatements16[i].c);
2271 if (a >= prog->progs_numglobals || b + i < 0 || b + i >= prog->progs_numstatements)
2272 prog->error_cmd("PRVM_LoadProgs: out of bounds IF/IFNOT (statement %d) in %s", i, prog->name);
2273 prog->statements[i].op = op;
2274 prog->statements[i].operand[0] = remapglobal(a);
2275 prog->statements[i].operand[1] = -1;
2276 prog->statements[i].operand[2] = -1;
2277 prog->statements[i].jumpabsolute = i + b;
2281 if (a + i < 0 || a + i >= prog->progs_numstatements)
2282 prog->error_cmd("PRVM_LoadProgs: out of bounds GOTO (statement %d) in %s", i, prog->name);
2283 prog->statements[i].op = op;
2284 prog->statements[i].operand[0] = -1;
2285 prog->statements[i].operand[1] = -1;
2286 prog->statements[i].operand[2] = -1;
2287 prog->statements[i].jumpabsolute = i + a;
2290 Con_DPrintf("PRVM_LoadProgs: unknown opcode %d at statement %d in %s\n", (int)op, i, prog->name);
2292 //make sure its something well defined.
2293 prog->statements[i].op = OP_BOUNDCHECK;
2294 prog->statements[i].operand[0] = 0;
2295 prog->statements[i].operand[1] =
2296 prog->statements[i].operand[2] = op;
2297 prog->statements[i].jumpabsolute = -1;
2352 case OP_GSTOREP_ENT:
2353 case OP_GSTOREP_FLD:
2355 case OP_GSTOREP_FNC:
2357 // case OP_GADDRESS:
2367 // global global global
2402 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals || c >= prog->progs_numglobals)
2403 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d)", i);
2404 prog->statements[i].op = op;
2405 prog->statements[i].operand[0] = remapglobal(a);
2406 prog->statements[i].operand[1] = remapglobal(b);
2407 prog->statements[i].operand[2] = remapglobal(c);
2408 prog->statements[i].jumpabsolute = -1;
2410 // global none global
2416 if (a >= prog->progs_numglobals || c >= prog->progs_numglobals)
2417 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2418 prog->statements[i].op = op;
2419 prog->statements[i].operand[0] = remapglobal(a);
2420 prog->statements[i].operand[1] = -1;
2421 prog->statements[i].operand[2] = remapglobal(c);
2422 prog->statements[i].jumpabsolute = -1;
2430 if (c) //Spike -- DP is alergic to pointers in QC. Try to avoid too many nasty surprises.
2431 Con_DPrintf("PRVM_LoadProgs: storep-with-offset is not permitted in %s\n", prog->name);
2441 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals)
2442 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2443 prog->statements[i].op = op;
2444 prog->statements[i].operand[0] = remapglobal(a);
2445 prog->statements[i].operand[1] = remapglobal(b);
2446 prog->statements[i].operand[2] = -1;
2447 prog->statements[i].jumpabsolute = -1;
2451 if ( a < prog->progs_numglobals)
2452 if ( prog->globals.ip[remapglobal(a)] >= 0 )
2453 if ( prog->globals.ip[remapglobal(a)] < prog->progs_numfunctions )
2454 if ( prog->functions[prog->globals.ip[remapglobal(a)]].first_statement == -642 )
2455 ++prog->numexplicitcoveragestatements;
2466 if ( a >= prog->progs_numglobals)
2467 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2468 if (b || c) //Spike -- added this check just as a diagnostic...
2469 Con_DPrintf("PRVM_LoadProgs: unexpected offset on call opcode in %s. Hexen2 format is not supported\n", prog->name);
2470 prog->statements[i].op = op;
2471 prog->statements[i].operand[0] = remapglobal(a);
2472 prog->statements[i].operand[1] = -1;
2473 prog->statements[i].operand[2] = -1;
2474 prog->statements[i].jumpabsolute = -1;
2478 if(prog->numstatements < 1)
2480 prog->error_cmd("PRVM_LoadProgs: empty program in %s", prog->name);
2482 else switch(prog->statements[prog->numstatements - 1].op)
2489 prog->error_cmd("PRVM_LoadProgs: program may fall off the edge (does not end with RETURN, GOTO or DONE) in %s", prog->name);
2493 // we're done with the file now
2495 Mem_Free(dprograms);
2498 // check required functions
2499 for(i=0 ; i < numrequiredfunc ; i++)
2500 if(PRVM_ED_FindFunction(prog, required_func[i]) == 0)
2501 prog->error_cmd("%s: %s not found in %s",prog->name, required_func[i], filename);
2503 PRVM_LoadLNO(prog, filename);
2505 PRVM_Init_Exec(prog);
2507 if(*prvm_language.string)
2508 // in CSQC we really shouldn't be able to change how stuff works... sorry for now
2509 // later idea: include a list of authorized .po file checksums with the csprogs
2511 qbool deftrans = prog == CLVM_prog;
2512 const char *realfilename = (prog != CLVM_prog ? filename : csqc_progname.string);
2513 if(deftrans) // once we have dotranslate_ strings, ALWAYS use the opt-in method!
2515 for (i=0 ; i<prog->numglobaldefs ; i++)
2518 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2519 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2520 if(name && !strncmp(name, "dotranslate_", 12))
2527 if(!strcmp(prvm_language.string, "dump"))
2529 qfile_t *f = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.pot", realfilename), "w", false);
2530 Con_Printf("Dumping to %s.pot\n", realfilename);
2533 for (i=0 ; i<prog->numglobaldefs ; i++)
2536 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2537 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2538 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2540 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2541 const char *value = PRVM_GetString(prog, val->string);
2544 char buf[MAX_INPUTLINE];
2545 PRVM_PO_UnparseString(buf, value, sizeof(buf));
2546 FS_Printf(f, "msgid \"%s\"\nmsgstr \"\"\n\n", buf);
2555 po_t *po = PRVM_PO_Load(
2556 va(vabuf, sizeof(vabuf), "%s.%s.po", realfilename, prvm_language.string),
2557 va(vabuf2, sizeof(vabuf2), "common.%s.po", prvm_language.string),
2558 prog->progs_mempool);
2561 for (i=0 ; i<prog->numglobaldefs ; i++)
2564 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2565 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2566 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2568 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2569 const char *value = PRVM_GetString(prog, val->string);
2572 value = PRVM_PO_Lookup(po, value);
2574 val->string = PRVM_SetEngineString(prog, value);
2582 for (cvar = prog->console_cmd->cvars->vars; cvar; cvar = cvar->next)
2583 cvar->globaldefindex[prog - prvm_prog_list] = -1;
2585 for (i=0 ; i<prog->numglobaldefs ; i++)
2588 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2589 //Con_Printf("found var %s\n", name);
2591 && !strncmp(name, "autocvar_", 9)
2592 && !(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
2595 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2596 cvar = Cvar_FindVar(prog->console_cmd->cvars, name + 9, prog->console_cmd->cvars_flagsmask);
2597 //Con_Printf("PRVM_LoadProgs: autocvar global %s in %s, processing...\n", name, prog->name);
2604 Con_DPrintf("PRVM_LoadProgs: no cvar for autocvar global %s in %s, creating...\n", name, prog->name);
2605 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2608 if((float)((int)(val->_float)) == val->_float)
2609 dpsnprintf(buf, sizeof(buf), "%i", (int)(val->_float));
2614 for (int precision = 7; precision <= 9; ++precision) {
2615 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2616 if ((float)atof(buf) == f) {
2624 for (i = 0; i < 3; ++i)
2628 for (int precision = 7; precision <= 9; ++precision) {
2629 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2630 if ((float)atof(buf) == f) {
2631 prec[i] = precision;
2636 dpsnprintf(buf, sizeof(buf), "%.*g %.*g %.*g", prec[0], val->vector[0], prec[1], val->vector[1], prec[2], val->vector[2]);
2640 value = PRVM_GetString(prog, val->string);
2643 Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name);
2646 cvar = Cvar_Get(prog->console_cmd->cvars, name + 9, value, prog->console_cmd->cvars_flagsmask, NULL);
2647 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2649 val->string = PRVM_SetEngineString(prog, cvar->string);
2650 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2653 prog->error_cmd("PRVM_LoadProgs: could not create cvar for autocvar global %s in %s", name, prog->name);
2654 cvar->globaldefindex[prog - prvm_prog_list] = i;
2656 else if((cvar->flags & CF_PRIVATE) == 0)
2658 // MUST BE SYNCED WITH cvar.c Cvar_Set
2661 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2664 val->_float = cvar->value;
2668 VectorClear(val->vector);
2669 for (j = 0;j < 3;j++)
2671 while (*s && ISWHITESPACE(*s))
2675 val->vector[j] = atof(s);
2676 while (!ISWHITESPACE(*s))
2683 val->string = PRVM_SetEngineString(prog, cvar->string);
2684 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2687 Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name);
2690 cvar->globaldefindex[prog - prvm_prog_list] = i;
2693 Con_Printf("PRVM_LoadProgs: private cvar for autocvar global %s in %s\n", name, prog->name);
2699 prog->loaded = true;
2701 PRVM_UpdateBreakpoints(prog);
2703 // set flags & mdef_ts in prog
2707 PRVM_FindOffsets(prog);
2709 prog->init_cmd(prog);
2712 PRVM_MEM_Alloc(prog);
2714 Con_Printf("%s: program loaded (crc %i, size %iK)\n", prog->name, prog->filecrc, (int)(filesize/1024));
2716 // Inittime is at least the time when this function finished. However,
2717 // later events may bump it.
2718 prog->inittime = host.realtime;
2722 static void PRVM_Fields_f(cmd_state_t *cmd)
2725 int i, j, ednum, used, usedamount;
2727 char tempstring[MAX_INPUTLINE], tempstring2[260];
2737 Con_Print("no progs loaded\n");
2742 if(Cmd_Argc(cmd) != 2)
2744 Con_Print("prvm_fields <program name>\n");
2748 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2751 counts = (int *)Mem_Alloc(tempmempool, prog->numfielddefs * sizeof(int));
2752 for (ednum = 0;ednum < prog->max_edicts;ednum++)
2754 ed = PRVM_EDICT_NUM(ednum);
2757 for (i = 1;i < prog->numfielddefs;i++)
2759 d = &prog->fielddefs[i];
2760 name = PRVM_GetString(prog, d->s_name);
2761 if (name[strlen(name)-2] == '_')
2762 continue; // skip _x, _y, _z vars
2763 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
2764 // if the value is still all 0, skip the field
2765 for (j = 0;j < prvm_type_size[d->type & ~DEF_SAVEGLOBAL];j++)
2767 if (val->ivector[j])
2778 for (i = 0;i < prog->numfielddefs;i++)
2780 d = &prog->fielddefs[i];
2781 name = PRVM_GetString(prog, d->s_name);
2782 if (name[strlen(name)-2] == '_')
2783 continue; // skip _x, _y, _z vars
2784 switch(d->type & ~DEF_SAVEGLOBAL)
2787 dp_strlcat(tempstring, "string ", sizeof(tempstring));
2790 dp_strlcat(tempstring, "entity ", sizeof(tempstring));
2793 dp_strlcat(tempstring, "function ", sizeof(tempstring));
2796 dp_strlcat(tempstring, "field ", sizeof(tempstring));
2799 dp_strlcat(tempstring, "void ", sizeof(tempstring));
2802 dp_strlcat(tempstring, "float ", sizeof(tempstring));
2805 dp_strlcat(tempstring, "vector ", sizeof(tempstring));
2808 dp_strlcat(tempstring, "pointer ", sizeof(tempstring));
2811 dpsnprintf (tempstring2, sizeof(tempstring2), "bad type %i ", d->type & ~DEF_SAVEGLOBAL);
2812 dp_strlcat(tempstring, tempstring2, sizeof(tempstring));
2815 if (strlen(name) > sizeof(tempstring2)-4)
2817 memcpy (tempstring2, name, sizeof(tempstring2)-4);
2818 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
2819 tempstring2[sizeof(tempstring2)-1] = 0;
2822 dp_strlcat(tempstring, name, sizeof(tempstring));
2823 for (j = (int)strlen(name);j < 25;j++)
2824 dp_strlcat(tempstring, " ", sizeof(tempstring));
2825 dpsnprintf(tempstring2, sizeof(tempstring2), "%5d", counts[i]);
2826 dp_strlcat(tempstring, tempstring2, sizeof(tempstring));
2827 dp_strlcat(tempstring, "\n", sizeof(tempstring));
2828 if (strlen(tempstring) >= sizeof(tempstring)/2)
2830 Con_Print(tempstring);
2836 usedamount += prvm_type_size[d->type & ~DEF_SAVEGLOBAL];
2840 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);
2843 static void PRVM_Globals_f(cmd_state_t *cmd)
2847 const char *wildcard;
2853 Con_Print("no progs loaded\n");
2856 if(Cmd_Argc (cmd) < 2 || Cmd_Argc(cmd) > 3)
2858 Con_Print("prvm_globals <program name> <optional name wildcard>\n");
2862 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2865 if( Cmd_Argc(cmd) == 3)
2866 wildcard = Cmd_Argv(cmd, 2);
2870 Con_Printf("%s :", prog->name);
2872 for (i = 0;i < prog->numglobaldefs;i++)
2875 if( !matchpattern( PRVM_GetString(prog, prog->globaldefs[i].s_name), wildcard, 1) )
2880 Con_Printf("%s\n", PRVM_GetString(prog, prog->globaldefs[i].s_name));
2882 Con_Printf("%i global variables, %i culled, totalling %i bytes\n", prog->numglobals, numculled, prog->numglobals * 4);
2890 static void PRVM_Global_f(cmd_state_t *cmd)
2894 char valuebuf[MAX_INPUTLINE];
2895 if( Cmd_Argc(cmd) != 3 ) {
2896 Con_Printf( "prvm_global <program name> <global name>\n" );
2900 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2903 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2905 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2907 Con_Printf( "%s: %s\n", Cmd_Argv(cmd, 2), PRVM_ValueString( prog, (etype_t)global->type, PRVM_GLOBALFIELDVALUE(global->ofs), valuebuf, sizeof(valuebuf) ) );
2915 static void PRVM_GlobalSet_f(cmd_state_t *cmd)
2919 if( Cmd_Argc(cmd) != 4 ) {
2920 Con_Printf( "prvm_globalset <program name> <global name> <value>\n" );
2924 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2927 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2929 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2931 PRVM_ED_ParseEpair( prog, NULL, global, Cmd_Argv(cmd, 3), true );
2935 ======================
2936 Break- and Watchpoints
2937 ======================
2941 char break_statement[256];
2942 char watch_global[256];
2944 char watch_field[256];
2947 static debug_data_t debug_data[PRVM_PROG_MAX];
2949 void PRVM_Breakpoint(prvm_prog_t *prog, int stack_index, const char *text)
2952 Con_Printf("PRVM_Breakpoint: %s\n", text);
2953 PRVM_PrintState(prog, stack_index);
2954 if (prvm_breakpointdump.integer)
2955 SV_Savegame_to(prog, va(vabuf, sizeof(vabuf), "breakpoint-%s.dmp", prog->name));
2958 void PRVM_Watchpoint(prvm_prog_t *prog, int stack_index, const char *text, etype_t type, prvm_eval_t *o, prvm_eval_t *n)
2960 size_t sz = sizeof(prvm_vec_t) * ((type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
2961 if (memcmp(o, n, sz))
2964 char valuebuf_o[128];
2965 char valuebuf_n[128];
2966 PRVM_UglyValueString(prog, type, o, valuebuf_o, sizeof(valuebuf_o));
2967 PRVM_UglyValueString(prog, type, n, valuebuf_n, sizeof(valuebuf_n));
2968 dpsnprintf(buf, sizeof(buf), "%s: %s -> %s", text, valuebuf_o, valuebuf_n);
2969 PRVM_Breakpoint(prog, stack_index, buf);
2974 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog)
2976 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
2979 if (debug->break_statement[0])
2981 if (debug->break_statement[0] >= '0' && debug->break_statement[0] <= '9')
2983 prog->break_statement = atoi(debug->break_statement);
2984 prog->break_stack_index = 0;
2989 func = PRVM_ED_FindFunction (prog, debug->break_statement);
2992 Con_Printf("%s progs: no function or statement named %s to break on!\n", prog->name, debug->break_statement);
2993 prog->break_statement = -1;
2997 prog->break_statement = func->first_statement;
2998 prog->break_stack_index = 1;
3001 if (prog->break_statement >= -1)
3002 Con_Printf("%s progs: breakpoint is at statement %d\n", prog->name, prog->break_statement);
3005 prog->break_statement = -1;
3007 if (debug->watch_global[0])
3009 mdef_t *global = PRVM_ED_FindGlobal( prog, debug->watch_global );
3012 Con_Printf( "%s progs: no global named '%s' to watch!\n", prog->name, debug->watch_global );
3013 prog->watch_global_type = ev_void;
3017 size_t sz = sizeof(prvm_vec_t) * ((global->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3018 prog->watch_global = global->ofs;
3019 prog->watch_global_type = (etype_t)global->type;
3020 memcpy(&prog->watch_global_value, PRVM_GLOBALFIELDVALUE(prog->watch_global), sz);
3022 if (prog->watch_global_type != ev_void)
3023 Con_Printf("%s progs: global watchpoint is at global index %d\n", prog->name, prog->watch_global);
3026 prog->watch_global_type = ev_void;
3028 if (debug->watch_field[0])
3030 mdef_t *field = PRVM_ED_FindField( prog, debug->watch_field );
3033 Con_Printf( "%s progs: no field named '%s' to watch!\n", prog->name, debug->watch_field );
3034 prog->watch_field_type = ev_void;
3038 size_t sz = sizeof(prvm_vec_t) * ((field->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3039 prog->watch_edict = debug->watch_edict;
3040 prog->watch_field = field->ofs;
3041 prog->watch_field_type = (etype_t)field->type;
3042 if (prog->watch_edict < prog->num_edicts)
3043 memcpy(&prog->watch_edictfield_value, PRVM_EDICTFIELDVALUE(PRVM_EDICT_NUM(prog->watch_edict), prog->watch_field), sz);
3045 memset(&prog->watch_edictfield_value, 0, sz);
3047 if (prog->watch_edict != ev_void)
3048 Con_Printf("%s progs: edict field watchpoint is at edict %d field index %d\n", prog->name, prog->watch_edict, prog->watch_field);
3051 prog->watch_field_type = ev_void;
3054 static void PRVM_Breakpoint_f(cmd_state_t *cmd)
3058 if( Cmd_Argc(cmd) == 2 ) {
3059 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3062 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3063 debug->break_statement[0] = 0;
3065 PRVM_UpdateBreakpoints(prog);
3068 if( Cmd_Argc(cmd) != 3 ) {
3069 Con_Printf( "prvm_breakpoint <program name> <function name | statement>\n" );
3073 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3077 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3078 dp_strlcpy(debug->break_statement, Cmd_Argv(cmd, 2), sizeof(debug->break_statement));
3080 PRVM_UpdateBreakpoints(prog);
3083 static void PRVM_GlobalWatchpoint_f(cmd_state_t *cmd)
3087 if( Cmd_Argc(cmd) == 2 ) {
3088 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3091 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3092 debug->watch_global[0] = 0;
3094 PRVM_UpdateBreakpoints(prog);
3097 if( Cmd_Argc(cmd) != 3 ) {
3098 Con_Printf( "prvm_globalwatchpoint <program name> <global name>\n" );
3102 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3106 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3107 dp_strlcpy(debug->watch_global, Cmd_Argv(cmd, 2), sizeof(debug->watch_global));
3109 PRVM_UpdateBreakpoints(prog);
3112 static void PRVM_EdictWatchpoint_f(cmd_state_t *cmd)
3116 if( Cmd_Argc(cmd) == 2 ) {
3117 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3120 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3121 debug->watch_field[0] = 0;
3123 PRVM_UpdateBreakpoints(prog);
3126 if( Cmd_Argc(cmd) != 4 ) {
3127 Con_Printf( "prvm_edictwatchpoint <program name> <edict number> <field name>\n" );
3131 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3135 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3136 debug->watch_edict = atoi(Cmd_Argv(cmd, 2));
3137 dp_strlcpy(debug->watch_field, Cmd_Argv(cmd, 3), sizeof(debug->watch_field));
3139 PRVM_UpdateBreakpoints(prog);
3147 void PRVM_Init (void)
3151 Cmd_AddCommand(CF_SHARED, "prvm_edict", PRVM_ED_PrintEdict_f, "print all data about an entity number in the selected VM (server, client, menu)");
3152 Cmd_AddCommand(CF_SHARED, "prvm_edicts", PRVM_ED_PrintEdicts_f, "prints all data about all entities in the selected VM (server, client, menu)");
3153 Cmd_AddCommand(CF_SHARED, "prvm_edictcount", PRVM_ED_Count_f, "prints number of active entities in the selected VM (server, client, menu)");
3154 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)");
3155 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");
3156 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)");
3157 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)");
3158 Cmd_AddCommand(CF_SHARED, "prvm_globals", PRVM_Globals_f, "prints all global variables in the selected VM (server, client, menu)");
3159 Cmd_AddCommand(CF_SHARED, "prvm_global", PRVM_Global_f, "prints value of a specified global variable in the selected VM (server, client, menu)");
3160 Cmd_AddCommand(CF_SHARED, "prvm_globalset", PRVM_GlobalSet_f, "sets value of a specified global variable in the selected VM (server, client, menu)");
3161 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)");
3162 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");
3163 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");
3164 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)");
3165 Cmd_AddCommand(CF_SHARED, "cl_cmd", PRVM_GameCommand_Client_f, "calls the client QC function GameCommand with the supplied string as argument");
3166 Cmd_AddCommand(CF_SHARED, "menu_cmd", PRVM_GameCommand_Menu_f, "calls the menu QC function GameCommand with the supplied string as argument");
3167 Cmd_AddCommand(CF_SHARED, "sv_cmd", PRVM_GameCommand_Server_f, "calls the server QC function GameCommand with the supplied string as argument");
3168 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");
3169 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");
3170 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");
3172 Cvar_RegisterVariable (&prvm_language);
3173 Cvar_RegisterVariable (&prvm_traceqc);
3174 Cvar_RegisterVariable (&prvm_statementprofiling);
3175 Cvar_RegisterVariable (&prvm_timeprofiling);
3176 Cvar_RegisterVariable (&prvm_coverage);
3177 Cvar_RegisterVariable (&prvm_backtraceforwarnings);
3178 Cvar_RegisterVariable (&prvm_leaktest);
3179 Cvar_RegisterVariable (&prvm_leaktest_follow_targetname);
3180 Cvar_RegisterVariable (&prvm_leaktest_ignore_classnames);
3181 Cvar_RegisterVariable (&prvm_errordump);
3182 Cvar_RegisterVariable (&prvm_breakpointdump);
3183 Cvar_RegisterVariable (&prvm_reuseedicts_startuptime);
3184 Cvar_RegisterVariable (&prvm_reuseedicts_neverinsameframe);
3185 Cvar_RegisterVariable (&prvm_garbagecollection_enable);
3186 Cvar_RegisterVariable (&prvm_garbagecollection_notify);
3187 Cvar_RegisterVariable (&prvm_garbagecollection_scan_limit);
3188 Cvar_RegisterVariable (&prvm_garbagecollection_strings);
3189 Cvar_RegisterVariable (&prvm_stringdebug);
3191 // COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!)
3192 prvm_runawaycheck = !Sys_CheckParm("-norunaway");
3196 // LadyHavoc: report supported extensions
3197 Con_DPrintf("\nQuakeC extensions for server and client:");
3198 for (i = 0; vm_sv_extensions[i]; i++)
3199 Con_DPrintf(" %s", vm_sv_extensions[i]);
3202 Con_DPrintf("\nQuakeC extensions for menu:");
3203 for (i = 0; vm_m_extensions[i]; i++)
3204 Con_DPrintf(" %s", vm_m_extensions[i]);
3214 void PRVM_Prog_Init(prvm_prog_t *prog, cmd_state_t *cmd)
3216 PRVM_Prog_Reset(prog);
3217 prog->leaktest_active = prvm_leaktest.integer != 0;
3218 prog->console_cmd = cmd;
3221 // LadyHavoc: turned PRVM_EDICT_NUM into a #define for speed reasons
3222 unsigned int PRVM_EDICT_NUM_ERROR(prvm_prog_t *prog, unsigned int n, const char *filename, int fileline)
3224 prog->error_cmd("PRVM_EDICT_NUM: %s: bad number %i (called at %s:%i)", prog->name, n, filename, fileline);
3228 #define PRVM_KNOWNSTRINGBASE 0x40000000
3230 const char *PRVM_GetString(prvm_prog_t *prog, int num)
3235 if (prvm_stringdebug.integer)
3236 VM_Warning(prog, "PRVM_GetString: Invalid string offset (%i < 0)\n", num);
3239 else if (num < prog->stringssize)
3241 // constant string from progs.dat
3242 return prog->strings + num;
3244 else if (num <= prog->stringssize + prog->tempstringsbuf.maxsize)
3246 // tempstring returned by engine to QC (becomes invalid after returning to engine)
3247 num -= prog->stringssize;
3248 if (num < prog->tempstringsbuf.cursize)
3249 return (char *)prog->tempstringsbuf.data + num;
3252 if (prvm_stringdebug.integer)
3253 VM_Warning(prog, "PRVM_GetString: Invalid temp-string offset (%i >= %i prog->tempstringsbuf.cursize)\n", num, prog->tempstringsbuf.cursize);
3257 else if (num & PRVM_KNOWNSTRINGBASE)
3260 num = num - PRVM_KNOWNSTRINGBASE;
3261 if (num >= 0 && num < prog->numknownstrings)
3263 if (!prog->knownstrings[num])
3265 if (prvm_stringdebug.integer)
3266 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num);
3269 // refresh the garbage collection on the string - this guards
3270 // against a certain sort of repeated migration to earlier
3271 // points in the scan that could otherwise result in the string
3272 // being freed for being unused
3273 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] & ~KNOWNSTRINGFLAG_GCPRUNE) | KNOWNSTRINGFLAG_GCMARK;
3274 return prog->knownstrings[num];
3278 if (prvm_stringdebug.integer)
3279 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, prog->numknownstrings);
3285 // invalid string offset
3286 if (prvm_stringdebug.integer)
3287 VM_Warning(prog, "PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, prog->stringssize);
3292 const char *PRVM_ChangeEngineString(prvm_prog_t *prog, int i, const char *s)
3295 i = i - PRVM_KNOWNSTRINGBASE;
3296 if (i < 0 || i >= prog->numknownstrings)
3297 prog->error_cmd("PRVM_ChangeEngineString: string index %i is out of bounds", i);
3298 else if ((prog->knownstrings_flags[i] & KNOWNSTRINGFLAG_ENGINE) == 0)
3299 prog->error_cmd("PRVM_ChangeEngineString: string index %i is not an engine string", i);
3300 old = prog->knownstrings[i];
3301 prog->knownstrings[i] = s;
3305 static void PRVM_NewKnownString(prvm_prog_t *prog, int i, int flags, const char *s)
3307 if (i >= prog->numknownstrings)
3309 if (i >= prog->maxknownstrings)
3311 const char **oldstrings = prog->knownstrings;
3312 const unsigned char *oldstrings_flags = prog->knownstrings_flags;
3313 const char **oldstrings_origin = prog->knownstrings_origin;
3314 prog->maxknownstrings += 128;
3315 prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3316 prog->knownstrings_flags = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char));
3317 if (prog->leaktest_active)
3318 prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3319 if (prog->numknownstrings)
3321 memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *));
3322 memcpy((char **)prog->knownstrings_flags, oldstrings_flags, prog->numknownstrings * sizeof(unsigned char));
3323 if (prog->leaktest_active)
3324 memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *));
3327 prog->numknownstrings++;
3329 prog->firstfreeknownstring = i + 1;
3330 prog->knownstrings[i] = s;
3331 // it's in use right now, spare it until the next gc pass - that said, it is not freeable so this is probably moot
3332 prog->knownstrings_flags[i] = flags;
3333 if (prog->leaktest_active)
3334 prog->knownstrings_origin[i] = NULL;
3337 int PRVM_SetEngineString(prvm_prog_t *prog, const char *s)
3342 if (s >= prog->strings && s <= prog->strings + prog->stringssize)
3343 prog->error_cmd("PRVM_SetEngineString: s in prog->strings area");
3344 // if it's in the tempstrings area, use a reserved range
3345 // (otherwise we'd get millions of useless string offsets cluttering the database)
3346 if (s >= (char *)prog->tempstringsbuf.data && s < (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.maxsize)
3347 return prog->stringssize + (s - (char *)prog->tempstringsbuf.data);
3348 // see if it's a known string address
3349 for (i = 0;i < prog->numknownstrings;i++)
3350 if (prog->knownstrings[i] == s)
3351 return PRVM_KNOWNSTRINGBASE + i;
3352 // new unknown engine string
3353 if (developer_insane.integer)
3354 Con_DPrintf("new engine string %p = \"%s\"\n", (void *)s, s);
3355 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3356 if (!prog->knownstrings[i])
3358 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE, s);
3359 return PRVM_KNOWNSTRINGBASE + i;
3362 // temp string handling
3364 // all tempstrings go into this buffer consecutively, and it is reset
3365 // whenever PRVM_ExecuteProgram returns to the engine
3366 // (technically each PRVM_ExecuteProgram call saves the cursize value and
3367 // restores it on return, so multiple recursive calls can share the same
3369 // the buffer size is automatically grown as needed
3370 int PRVM_SetTempString(prvm_prog_t *prog, const char *s, size_t slen)
3375 if (!s || slen >= VM_TEMPSTRING_MAXSIZE)
3378 if (developer_insane.integer)
3379 Con_DPrintf("PRVM_SetTempString %s: cursize %i, size %zu\n", prog->name, prog->tempstringsbuf.cursize, size);
3380 if ((size_t)prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3382 sizebuf_t old = prog->tempstringsbuf;
3383 if (prog->tempstringsbuf.cursize + size >= 1<<28)
3384 prog->error_cmd("PRVM_SetTempString %s: ran out of tempstring memory! (refusing to grow tempstring buffer over 256MB, cursize %i, size %zu)\n", prog->name, prog->tempstringsbuf.cursize, size);
3385 prog->tempstringsbuf.maxsize = max(prog->tempstringsbuf.maxsize, 65536);
3386 while ((size_t)prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3387 prog->tempstringsbuf.maxsize *= 2;
3388 if (prog->tempstringsbuf.maxsize != old.maxsize || prog->tempstringsbuf.data == NULL)
3390 Con_DPrintf("PRVM_SetTempString %s: enlarging tempstrings buffer (%iKB -> %iKB)\n", prog->name, old.maxsize/1024, prog->tempstringsbuf.maxsize/1024);
3391 prog->tempstringsbuf.data = (unsigned char *) Mem_Alloc(prog->progs_mempool, prog->tempstringsbuf.maxsize);
3395 memcpy(prog->tempstringsbuf.data, old.data, old.cursize);
3400 t = (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.cursize;
3402 prog->tempstringsbuf.cursize += size;
3403 return PRVM_SetEngineString(prog, t);
3406 int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer)
3416 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3417 if (!prog->knownstrings[i])
3419 s = (char *)PRVM_Alloc(bufferlength);
3420 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK, s);
3421 if(prog->leaktest_active)
3422 prog->knownstrings_origin[i] = PRVM_AllocationOrigin(prog);
3424 *pointer = (char *)(prog->knownstrings[i]);
3425 return PRVM_KNOWNSTRINGBASE + i;
3428 void PRVM_FreeString(prvm_prog_t *prog, int num)
3431 prog->error_cmd("PRVM_FreeString %s: attempt to free a NULL string", prog->name);
3432 else if (num >= 0 && num < prog->stringssize)
3433 prog->error_cmd("PRVM_FreeString %s: attempt to free a constant string", prog->name);
3434 else if (num >= PRVM_KNOWNSTRINGBASE && num < PRVM_KNOWNSTRINGBASE + prog->numknownstrings)
3436 num = num - PRVM_KNOWNSTRINGBASE;
3437 if (!prog->knownstrings[num])
3438 prog->error_cmd("PRVM_FreeString %s: attempt to free a non-existent or already freed string", prog->name);
3439 if (!prog->knownstrings_flags[num])
3440 prog->error_cmd("PRVM_FreeString %s: attempt to free a string owned by the engine", prog->name);
3441 PRVM_Free((char *)prog->knownstrings[num]);
3442 if(prog->leaktest_active)
3443 if(prog->knownstrings_origin[num])
3444 PRVM_Free((char *)prog->knownstrings_origin[num]);
3445 prog->knownstrings[num] = NULL;
3446 prog->knownstrings_flags[num] = 0;
3447 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3450 prog->error_cmd("PRVM_FreeString %s: invalid string offset %i", prog->name, num);
3453 static qbool PRVM_IsStringReferenced(prvm_prog_t *prog, string_t string)
3457 for (i = 0;i < prog->numglobaldefs;i++)
3459 mdef_t *d = &prog->globaldefs[i];
3460 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3462 if(string == PRVM_GLOBALFIELDSTRING(d->ofs))
3466 for(j = 0; j < prog->num_edicts; ++j)
3468 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3471 for (i=0; i<prog->numfielddefs; ++i)
3473 mdef_t *d = &prog->fielddefs[i];
3474 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3476 if(string == PRVM_EDICTFIELDSTRING(ed, d->ofs))
3484 static qbool PRVM_IsEdictRelevant(prvm_prog_t *prog, prvm_edict_t *edict)
3488 if(PRVM_NUM_FOR_EDICT(edict) <= prog->reserved_edicts)
3489 return true; // world or clients
3490 if (edict->freetime <= prog->inittime)
3491 return true; // created during startup
3492 if (prog == SVVM_prog)
3494 if(PRVM_serveredictfloat(edict, solid)) // can block other stuff, or is a trigger?
3496 if(PRVM_serveredictfloat(edict, modelindex)) // visible ent?
3498 if(PRVM_serveredictfloat(edict, effects)) // particle effect?
3500 if(PRVM_serveredictfunction(edict, think)) // has a think function?
3501 if(PRVM_serveredictfloat(edict, nextthink) > 0) // that actually will eventually run?
3503 if(PRVM_serveredictfloat(edict, takedamage))
3505 if(*prvm_leaktest_ignore_classnames.string)
3507 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_serveredictstring(edict, classname)))))
3511 else if (prog == CLVM_prog)
3513 // TODO someone add more stuff here
3514 if(PRVM_clientedictfloat(edict, entnum)) // csqc networked
3516 if(PRVM_clientedictfloat(edict, modelindex)) // visible ent?
3518 if(PRVM_clientedictfloat(edict, effects)) // particle effect?
3520 if(PRVM_clientedictfunction(edict, think)) // has a think function?
3521 if(PRVM_clientedictfloat(edict, nextthink) > 0) // that actually will eventually run?
3523 if(*prvm_leaktest_ignore_classnames.string)
3525 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_clientedictstring(edict, classname)))))
3531 // menu prog does not have classnames
3536 static qbool PRVM_IsEdictReferenced(prvm_prog_t *prog, prvm_edict_t *edict, int mark)
3539 int edictnum = PRVM_NUM_FOR_EDICT(edict);
3540 const char *targetname = NULL;
3542 if (prog == SVVM_prog && prvm_leaktest_follow_targetname.integer)
3543 targetname = PRVM_GetString(prog, PRVM_serveredictstring(edict, targetname));
3546 if(!*targetname) // ""
3549 for(j = 0; j < prog->num_edicts; ++j)
3551 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3552 if (ed->priv.required->mark < mark)
3558 const char *target = PRVM_GetString(prog, PRVM_serveredictstring(ed, target));
3560 if(!strcmp(target, targetname))
3563 for (i=0; i<prog->numfielddefs; ++i)
3565 mdef_t *d = &prog->fielddefs[i];
3566 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3568 if(edictnum == PRVM_EDICTFIELDEDICT(ed, d->ofs))
3576 static void PRVM_MarkReferencedEdicts(prvm_prog_t *prog)
3582 // Stage 1: world, all entities that are relevant, and all entities that are referenced by globals.
3584 for(j = 0; j < prog->num_edicts; ++j)
3586 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3589 ed->priv.required->mark = PRVM_IsEdictRelevant(prog, ed) ? stage : 0;
3591 for (i = 0;i < prog->numglobaldefs;i++)
3593 mdef_t *d = &prog->globaldefs[i];
3595 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3597 j = PRVM_GLOBALFIELDEDICT(d->ofs);
3598 if (i < 0 || j >= prog->max_edicts) {
3599 Con_Printf("Invalid entity reference from global %s.\n", PRVM_GetString(prog, d->s_name));
3602 ed = PRVM_EDICT_NUM(j);;
3603 ed->priv.required->mark = stage;
3606 // Future stages: all entities that are referenced by an entity of the previous stage.
3610 for(j = 0; j < prog->num_edicts; ++j)
3612 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3615 if(ed->priv.required->mark)
3617 if(PRVM_IsEdictReferenced(prog, ed, stage))
3619 ed->priv.required->mark = stage + 1;
3626 Con_DPrintf("leak check used %d stages to find all references\n", stage);
3629 void PRVM_LeakTest(prvm_prog_t *prog)
3632 qbool leaked = false;
3634 if(!prog->leaktest_active)
3638 for (i = 0; i < prog->numknownstrings; ++i)
3640 if(prog->knownstrings[i])
3641 if(prog->knownstrings_flags[i])
3642 if(prog->knownstrings_origin[i])
3643 if(!PRVM_IsStringReferenced(prog, PRVM_KNOWNSTRINGBASE + i))
3645 Con_Printf("Unreferenced string found!\n Value: %s\n Origin: %s\n", prog->knownstrings[i], prog->knownstrings_origin[i]);
3651 PRVM_MarkReferencedEdicts(prog);
3652 for(j = 0; j < prog->num_edicts; ++j)
3654 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3657 if(!ed->priv.required->mark)
3658 if(ed->priv.required->allocation_origin)
3660 Con_Printf("Unreferenced edict found!\n Allocated at: %s\n", ed->priv.required->allocation_origin);
3661 PRVM_ED_Print(prog, ed, NULL);
3666 ed->priv.required->mark = 0; // clear marks again when done
3669 for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); ++i)
3671 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
3673 if(stringbuffer->origin)
3675 Con_Printf("Open string buffer handle found!\n Allocated at: %s\n", stringbuffer->origin);
3680 for(i = 0; i < PRVM_MAX_OPENFILES; ++i)
3682 if(prog->openfiles[i])
3683 if(prog->openfiles_origin[i])
3685 Con_Printf("Open file handle found!\n Allocated at: %s\n", prog->openfiles_origin[i]);
3690 for(i = 0; i < PRVM_MAX_OPENSEARCHES; ++i)
3692 if(prog->opensearches[i])
3693 if(prog->opensearches_origin[i])
3695 Con_Printf("Open search handle found!\n Allocated at: %s\n", prog->opensearches_origin[i]);
3701 Con_Printf("Congratulations. No leaks found.\n");
3704 void PRVM_GarbageCollection(prvm_prog_t *prog)
3706 int limit = prvm_garbagecollection_scan_limit.integer;
3707 prvm_prog_garbagecollection_state_t *gc = &prog->gc;
3708 if (!prvm_garbagecollection_enable.integer)
3711 // we like to limit how much scanning we do so it doesn't put a significant
3712 // burden on the cpu, so each of these are not complete scans, we also like
3713 // to have consistent cpu usage so we do a bit of work on each category of
3714 // leaked object every frame
3720 case PRVM_GC_GLOBALS_MARK:
3721 for (; gc->globals_mark_progress < prog->numglobaldefs && (limit--) > 0; gc->globals_mark_progress++)
3723 mdef_t *d = &prog->globaldefs[gc->globals_mark_progress];
3728 prvm_int_t s = prog->globals.ip[d->ofs];
3729 if (s & PRVM_KNOWNSTRINGBASE)
3731 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3732 if (!prog->knownstrings[num])
3735 Con_DPrintf("PRVM_GarbageCollection: Found bogus strzone reference in global %i (global name: \"%s\"), erasing reference", d->ofs, PRVM_GetString(prog, d->s_name));
3736 prog->globals.ip[d->ofs] = 0;
3739 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3747 if (gc->globals_mark_progress >= prog->numglobaldefs)
3750 case PRVM_GC_FIELDS_MARK:
3751 for (; gc->fields_mark_progress < prog->numfielddefs && limit > 0;)
3753 mdef_t *d = &prog->fielddefs[gc->fields_mark_progress];
3757 //for (gc-> entityindex = 0; entityindex < prog->num_edicts; entityindex++)
3758 for (;gc->fields_mark_progress_entity < prog->num_edicts && (limit--) > 0;gc->fields_mark_progress_entity++)
3760 int entityindex = gc->fields_mark_progress_entity;
3761 prvm_int_t s = prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs];
3762 if (s & PRVM_KNOWNSTRINGBASE)
3764 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3765 if (!prog->knownstrings[num])
3768 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));
3769 prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs] = 0;
3772 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3775 if (gc->fields_mark_progress_entity >= prog->num_edicts)
3777 gc->fields_mark_progress_entity = 0;
3778 gc->fields_mark_progress++;
3782 gc->fields_mark_progress_entity = 0;
3783 gc->fields_mark_progress++;
3787 if (gc->fields_mark_progress >= prog->numfielddefs)
3790 case PRVM_GC_KNOWNSTRINGS_SWEEP:
3791 // free any strzone'd strings that are not marked
3792 if (!prvm_garbagecollection_strings.integer)
3797 for (;gc->knownstrings_sweep_progress < prog->numknownstrings && (limit--) > 0;gc->knownstrings_sweep_progress++)
3799 int num = gc->knownstrings_sweep_progress;
3800 if (prog->knownstrings[num] && (prog->knownstrings_flags[num] & (KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE)) == 0)
3802 if (prog->knownstrings_flags[num] & KNOWNSTRINGFLAG_GCPRUNE)
3804 // string has been marked for pruning two passes in a row
3805 if (prvm_garbagecollection_notify.integer)
3806 Con_DPrintf("prvm_garbagecollection_notify: %s: freeing unreferenced string %i: \"%s\"\n", prog->name, num, prog->knownstrings[num]);
3807 Mem_Free((char *)prog->knownstrings[num]);
3808 prog->knownstrings[num] = NULL;
3809 prog->knownstrings_flags[num] = 0;
3810 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3814 // mark it for pruning next pass
3815 prog->knownstrings_flags[num] |= KNOWNSTRINGFLAG_GCPRUNE;
3819 if (gc->knownstrings_sweep_progress >= prog->numknownstrings)
3824 memset(gc, 0, sizeof(*gc));