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 memcpy(inbuf, p, q - p - 1);
1858 inbuf[q - p - 1] = '\0';
1859 PRVM_PO_ParseString(decodedbuf + decodedpos, inbuf, sizeof(decodedbuf) - decodedpos);
1860 decodedpos += strlen(decodedbuf + decodedpos);
1870 Mem_Free(thisstr.key);
1871 thisstr.key = (char *)Mem_Alloc(pool, decodedpos + 1);
1872 memcpy(thisstr.key, decodedbuf, decodedpos + 1);
1874 else if(decodedpos > 0 && thisstr.key) // skip empty translation results
1876 thisstr.value = (char *)Mem_Alloc(pool, decodedpos + 1);
1877 memcpy(thisstr.value, decodedbuf, decodedpos + 1);
1878 hashindex = CRC_Block((const unsigned char *) thisstr.key, strlen(thisstr.key)) % PO_HASHSIZE;
1879 thisstr.nextonhashchain = po->hashtable[hashindex];
1880 po->hashtable[hashindex] = (po_string_t *)Mem_Alloc(pool, sizeof(thisstr));
1881 memcpy(po->hashtable[hashindex], &thisstr, sizeof(thisstr));
1882 memset(&thisstr, 0, sizeof(thisstr));
1886 Mem_Free((char *) buf);
1891 static const char *PRVM_PO_Lookup(po_t *po, const char *str)
1893 int hashindex = CRC_Block((const unsigned char *) str, strlen(str)) % PO_HASHSIZE;
1894 po_string_t *p = po->hashtable[hashindex];
1897 if(!strcmp(str, p->key))
1899 p = p->nextonhashchain;
1903 static void PRVM_PO_Destroy(po_t *po)
1906 for(i = 0; i < PO_HASHSIZE; ++i)
1908 po_string_t *p = po->hashtable[i];
1912 p = p->nextonhashchain;
1921 void PRVM_LeakTest(prvm_prog_t *prog);
1922 void PRVM_Prog_Reset(prvm_prog_t *prog)
1926 if(prog->tempstringsbuf.cursize)
1927 Mem_Free(prog->tempstringsbuf.data);
1928 prog->tempstringsbuf.cursize = 0;
1929 PRVM_LeakTest(prog);
1930 prog->reset_cmd(prog);
1931 Mem_FreePool(&prog->progs_mempool);
1933 PRVM_PO_Destroy((po_t *) prog->po);
1935 memset(prog,0,sizeof(prvm_prog_t));
1936 prog->break_statement = -1;
1937 prog->watch_global_type = ev_void;
1938 prog->watch_field_type = ev_void;
1946 static void PRVM_LoadLNO( prvm_prog_t *prog, const char *progname ) {
1947 fs_offset_t filesize;
1949 unsigned int *header;
1952 FS_StripExtension( progname, filename, sizeof( filename ) );
1953 dp_strlcat( filename, ".lno", sizeof( filename ) );
1955 lno = FS_LoadFile( filename, tempmempool, false, &filesize );
1961 <Spike> SafeWrite (h, &lnotype, sizeof(int));
1962 <Spike> SafeWrite (h, &version, sizeof(int));
1963 <Spike> SafeWrite (h, &numglobaldefs, sizeof(int));
1964 <Spike> SafeWrite (h, &numpr_globals, sizeof(int));
1965 <Spike> SafeWrite (h, &numfielddefs, sizeof(int));
1966 <Spike> SafeWrite (h, &numstatements, sizeof(int));
1967 <Spike> SafeWrite (h, statement_linenums, numstatements*sizeof(int));
1969 if ((unsigned int)filesize < (6 + prog->progs_numstatements) * sizeof(int))
1975 header = (unsigned int *) lno;
1976 if (memcmp(lno, "LNOF", 4) == 0
1977 && LittleLong( header[ 1 ] ) == 1
1978 && (unsigned int)LittleLong( header[ 2 ] ) == (unsigned int)prog->progs_numglobaldefs
1979 && (unsigned int)LittleLong( header[ 3 ] ) == (unsigned int)prog->progs_numglobals
1980 && (unsigned int)LittleLong( header[ 4 ] ) == (unsigned int)prog->progs_numfielddefs
1981 && (unsigned int)LittleLong( header[ 5 ] ) == (unsigned int)prog->progs_numstatements)
1983 prog->statement_linenums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1984 memcpy( prog->statement_linenums, header + 6, prog->progs_numstatements * sizeof( int ) );
1986 /* gmqcc suports columnums */
1987 if ((unsigned int)filesize > ((6 + 2 * prog->progs_numstatements) * sizeof( int )))
1989 prog->statement_columnnums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1990 memcpy( prog->statement_columnnums, header + 6 + prog->progs_numstatements, prog->progs_numstatements * sizeof( int ) );
2001 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog);
2002 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)
2005 dprograms_t *dprograms;
2007 dstatement16_t *instatements16;
2008 dstatement32_t *instatements32;
2009 ddef16_t *infielddefs16;
2010 ddef32_t *infielddefs32;
2011 ddef16_t *inglobaldefs16;
2012 ddef32_t *inglobaldefs32;
2015 dfunction_t *infunctions;
2017 fs_offset_t filesize;
2018 int requiredglobalspace;
2036 prog->error_cmd("PRVM_LoadProgs: there is already a %s program loaded!", prog->name );
2038 Host_LockSession(); // all progs can use the session cvar
2039 Crypto_LoadKeys(); // all progs might use the keys at init time
2043 dprograms = (dprograms_t *) data;
2047 dprograms = (dprograms_t *)FS_LoadFile (filename, prog->progs_mempool, false, &filesize);
2048 if (dprograms == NULL || filesize < (fs_offset_t)sizeof(dprograms_t))
2049 prog->error_cmd("PRVM_LoadProgs: couldn't load %s for %s", filename, prog->name);
2050 // TODO bounds check header fields (e.g. numstatements), they must never go behind end of file
2052 prog->profiletime = Sys_DirtyTime();
2053 prog->starttime = host.realtime;
2055 requiredglobalspace = 0;
2056 for (i = 0;i < numrequiredglobals;i++)
2057 requiredglobalspace += required_global[i].type == ev_vector ? 3 : 1;
2059 prog->filecrc = CRC_Block((unsigned char *)dprograms, filesize);
2061 // byte swap the header
2062 prog->progs_version = LittleLong(dprograms->version);
2063 prog->progs_crc = LittleLong(dprograms->crc);
2064 if (prog->progs_version == 7)
2066 dprograms_v7_t *v7 = (dprograms_v7_t*)dprograms;
2067 structtype = LittleLong(v7->secondaryversion);
2068 if (structtype == PROG_SECONDARYVERSION16 ||
2069 structtype == PROG_SECONDARYVERSION32) // barely supported
2070 Con_Printf(CON_WARN "WARNING: %s: %s targets FTEQW, for which support is incomplete. Proceed at your own risk.\n", prog->name, filename);
2072 prog->error_cmd("%s: %s targets unknown engine", prog->name, filename);
2074 if (v7->numbodylessfuncs != 0 || v7->numtypes != 0 || v7->blockscompressed != 0)
2075 prog->error_cmd("%s: %s uses unsupported features.", prog->name, filename);
2077 else if (prog->progs_version != PROG_VERSION)
2078 prog->error_cmd("%s: %s has wrong version number (%i should be %i)", prog->name, filename, prog->progs_version, PROG_VERSION);
2079 instatements16 = (dstatement16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_statements));
2080 instatements32 = (dstatement32_t *)instatements16;
2081 prog->progs_numstatements = LittleLong(dprograms->numstatements);
2082 inglobaldefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globaldefs));
2083 inglobaldefs32 = (ddef32_t *)inglobaldefs16;
2084 prog->progs_numglobaldefs = LittleLong(dprograms->numglobaldefs);
2085 infielddefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_fielddefs));
2086 infielddefs32 = (ddef32_t *)infielddefs16;
2087 prog->progs_numfielddefs = LittleLong(dprograms->numfielddefs);
2088 infunctions = (dfunction_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_functions));
2089 prog->progs_numfunctions = LittleLong(dprograms->numfunctions);
2090 instrings = (char *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_strings));
2091 prog->progs_numstrings = LittleLong(dprograms->numstrings);
2092 inglobals = (int *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globals));
2093 prog->progs_numglobals = LittleLong(dprograms->numglobals);
2094 prog->progs_entityfields = LittleLong(dprograms->entityfields);
2096 prog->numstatements = prog->progs_numstatements;
2097 prog->numglobaldefs = prog->progs_numglobaldefs;
2098 prog->numfielddefs = prog->progs_numfielddefs;
2099 prog->numfunctions = prog->progs_numfunctions;
2100 prog->numstrings = prog->progs_numstrings;
2101 prog->numglobals = prog->progs_numglobals;
2102 prog->entityfields = prog->progs_entityfields;
2104 if (LittleLong(dprograms->ofs_strings) + prog->progs_numstrings > (int)filesize)
2105 prog->error_cmd("%s: %s strings go past end of file", prog->name, filename);
2106 prog->strings = (char *)Mem_Alloc(prog->progs_mempool, prog->progs_numstrings);
2107 memcpy(prog->strings, instrings, prog->progs_numstrings);
2108 prog->stringssize = prog->progs_numstrings;
2110 prog->numknownstrings = 0;
2111 prog->maxknownstrings = 0;
2112 prog->knownstrings = NULL;
2113 prog->knownstrings_flags = NULL;
2115 Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64);
2117 // we need to expand the globaldefs and fielddefs to include engine defs
2118 prog->globaldefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobaldefs + numrequiredglobals) * sizeof(mdef_t));
2119 prog->globals.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobals + requiredglobalspace + 2) * sizeof(prvm_vec_t));
2120 // + 2 is because of an otherwise occurring overrun in RETURN instruction
2121 // when trying to return the last or second-last global
2122 // (RETURN always returns a vector, there is no RETURN_F instruction)
2123 prog->fielddefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numfielddefs + numrequiredfields) * sizeof(mdef_t));
2124 // we need to convert the statements to our memory format
2125 prog->statements = (mstatement_t *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(mstatement_t));
2126 // allocate space for profiling statement usage
2127 prog->statement_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2128 prog->explicit_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2129 // functions need to be converted to the memory format
2130 prog->functions = (mfunction_t *)Mem_Alloc(prog->progs_mempool, sizeof(mfunction_t) * prog->progs_numfunctions);
2132 for (i = 0;i < prog->progs_numfunctions;i++)
2134 prog->functions[i].first_statement = LittleLong(infunctions[i].first_statement);
2135 prog->functions[i].parm_start = LittleLong(infunctions[i].parm_start);
2136 prog->functions[i].s_name = LittleLong(infunctions[i].s_name);
2137 prog->functions[i].s_file = LittleLong(infunctions[i].s_file);
2138 prog->functions[i].numparms = LittleLong(infunctions[i].numparms);
2139 prog->functions[i].locals = LittleLong(infunctions[i].locals);
2140 memcpy(prog->functions[i].parm_size, infunctions[i].parm_size, sizeof(infunctions[i].parm_size));
2141 if(prog->functions[i].first_statement >= prog->numstatements)
2142 prog->error_cmd("PRVM_LoadProgs: out of bounds function statement (function %d) in %s", i, prog->name);
2143 // TODO bounds check parm_start, s_name, s_file, numparms, locals, parm_size
2146 // copy the globaldefs to the new globaldefs list
2149 case PROG_SECONDARYVERSION32:
2150 for (i=0 ; i<prog->numglobaldefs ; i++)
2152 prog->globaldefs[i].type = LittleLong(inglobaldefs32[i].type);
2153 prog->globaldefs[i].ofs = LittleLong(inglobaldefs32[i].ofs);
2154 prog->globaldefs[i].s_name = LittleLong(inglobaldefs32[i].s_name);
2155 // TODO bounds check ofs, s_name
2159 for (i=0 ; i<prog->numglobaldefs ; i++)
2161 prog->globaldefs[i].type = (unsigned short)LittleShort(inglobaldefs16[i].type);
2162 prog->globaldefs[i].ofs = (unsigned short)LittleShort(inglobaldefs16[i].ofs);
2163 prog->globaldefs[i].s_name = LittleLong(inglobaldefs16[i].s_name);
2164 // TODO bounds check ofs, s_name
2169 // append the required globals
2170 for (i = 0;i < numrequiredglobals;i++)
2172 prog->globaldefs[prog->numglobaldefs].type = required_global[i].type;
2173 prog->globaldefs[prog->numglobaldefs].ofs = prog->numglobals;
2174 prog->globaldefs[prog->numglobaldefs].s_name = PRVM_SetEngineString(prog, required_global[i].name);
2175 if (prog->globaldefs[prog->numglobaldefs].type == ev_vector)
2176 prog->numglobals += 3;
2179 prog->numglobaldefs++;
2182 // copy the progs fields to the new fields list
2185 case PROG_SECONDARYVERSION32:
2186 for (i = 0;i < prog->numfielddefs;i++)
2188 prog->fielddefs[i].type = LittleLong(infielddefs32[i].type);
2189 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2190 prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name);
2191 prog->fielddefs[i].ofs = LittleLong(infielddefs32[i].ofs);
2192 prog->fielddefs[i].s_name = LittleLong(infielddefs32[i].s_name);
2193 // TODO bounds check ofs, s_name
2197 for (i = 0;i < prog->numfielddefs;i++)
2199 prog->fielddefs[i].type = (unsigned short)LittleShort(infielddefs16[i].type);
2200 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2201 prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name);
2202 prog->fielddefs[i].ofs = (unsigned short)LittleShort(infielddefs16[i].ofs);
2203 prog->fielddefs[i].s_name = LittleLong(infielddefs16[i].s_name);
2204 // TODO bounds check ofs, s_name
2209 // append the required fields
2210 for (i = 0;i < numrequiredfields;i++)
2212 prog->fielddefs[prog->numfielddefs].type = required_field[i].type;
2213 prog->fielddefs[prog->numfielddefs].ofs = prog->entityfields;
2214 prog->fielddefs[prog->numfielddefs].s_name = PRVM_SetEngineString(prog, required_field[i].name);
2215 if (prog->fielddefs[prog->numfielddefs].type == ev_vector)
2216 prog->entityfields += 3;
2218 prog->entityfields++;
2219 prog->numfielddefs++;
2222 // LadyHavoc: TODO: reorder globals to match engine struct
2223 // LadyHavoc: TODO: reorder fields to match engine struct
2224 #define remapglobal(index) (index)
2225 #define remapfield(index) (index)
2228 // FIXME: LadyHavoc: this uses a crude way to identify integer constants, rather than checking for matching globaldefs and checking their type
2229 for (i = 0;i < prog->progs_numglobals;i++)
2231 u.i = LittleLong(inglobals[i]);
2232 // most globals are 0, we only need to deal with the ones that are not
2235 d = u.i & 0xFF800000;
2236 if ((d == 0xFF800000) || (d == 0))
2238 // Looks like an integer (expand to int64)
2239 prog->globals.ip[remapglobal(i)] = u.i;
2243 // Looks like a float (expand to double)
2244 prog->globals.fp[remapglobal(i)] = u.f;
2249 // copy, remap globals in statements, bounds check
2250 for (i = 0;i < prog->progs_numstatements;i++)
2254 case PROG_SECONDARYVERSION32:
2255 op = (opcode_t)LittleLong(instatements32[i].op);
2256 a = (unsigned int)LittleLong(instatements32[i].a);
2257 b = (unsigned int)LittleLong(instatements32[i].b);
2258 c = (unsigned int)LittleLong(instatements32[i].c);
2261 op = (opcode_t)LittleShort(instatements16[i].op);
2262 a = (unsigned short)LittleShort(instatements16[i].a);
2263 b = (unsigned short)LittleShort(instatements16[i].b);
2264 c = (unsigned short)LittleShort(instatements16[i].c);
2272 if (a >= prog->progs_numglobals || b + i < 0 || b + i >= prog->progs_numstatements)
2273 prog->error_cmd("PRVM_LoadProgs: out of bounds IF/IFNOT (statement %d) in %s", i, prog->name);
2274 prog->statements[i].op = op;
2275 prog->statements[i].operand[0] = remapglobal(a);
2276 prog->statements[i].operand[1] = -1;
2277 prog->statements[i].operand[2] = -1;
2278 prog->statements[i].jumpabsolute = i + b;
2282 if (a + i < 0 || a + i >= prog->progs_numstatements)
2283 prog->error_cmd("PRVM_LoadProgs: out of bounds GOTO (statement %d) in %s", i, prog->name);
2284 prog->statements[i].op = op;
2285 prog->statements[i].operand[0] = -1;
2286 prog->statements[i].operand[1] = -1;
2287 prog->statements[i].operand[2] = -1;
2288 prog->statements[i].jumpabsolute = i + a;
2291 Con_DPrintf("PRVM_LoadProgs: unknown opcode %d at statement %d in %s\n", (int)op, i, prog->name);
2293 //make sure its something well defined.
2294 prog->statements[i].op = OP_BOUNDCHECK;
2295 prog->statements[i].operand[0] = 0;
2296 prog->statements[i].operand[1] =
2297 prog->statements[i].operand[2] = op;
2298 prog->statements[i].jumpabsolute = -1;
2353 case OP_GSTOREP_ENT:
2354 case OP_GSTOREP_FLD:
2356 case OP_GSTOREP_FNC:
2358 // case OP_GADDRESS:
2368 // global global global
2403 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals || c >= prog->progs_numglobals)
2404 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d)", i);
2405 prog->statements[i].op = op;
2406 prog->statements[i].operand[0] = remapglobal(a);
2407 prog->statements[i].operand[1] = remapglobal(b);
2408 prog->statements[i].operand[2] = remapglobal(c);
2409 prog->statements[i].jumpabsolute = -1;
2411 // global none global
2417 if (a >= prog->progs_numglobals || c >= prog->progs_numglobals)
2418 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2419 prog->statements[i].op = op;
2420 prog->statements[i].operand[0] = remapglobal(a);
2421 prog->statements[i].operand[1] = -1;
2422 prog->statements[i].operand[2] = remapglobal(c);
2423 prog->statements[i].jumpabsolute = -1;
2431 if (c) //Spike -- DP is alergic to pointers in QC. Try to avoid too many nasty surprises.
2432 Con_DPrintf("PRVM_LoadProgs: storep-with-offset is not permitted in %s\n", prog->name);
2442 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals)
2443 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2444 prog->statements[i].op = op;
2445 prog->statements[i].operand[0] = remapglobal(a);
2446 prog->statements[i].operand[1] = remapglobal(b);
2447 prog->statements[i].operand[2] = -1;
2448 prog->statements[i].jumpabsolute = -1;
2452 if ( a < prog->progs_numglobals)
2453 if ( prog->globals.ip[remapglobal(a)] >= 0 )
2454 if ( prog->globals.ip[remapglobal(a)] < prog->progs_numfunctions )
2455 if ( prog->functions[prog->globals.ip[remapglobal(a)]].first_statement == -642 )
2456 ++prog->numexplicitcoveragestatements;
2467 if ( a >= prog->progs_numglobals)
2468 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2469 if (b || c) //Spike -- added this check just as a diagnostic...
2470 Con_DPrintf("PRVM_LoadProgs: unexpected offset on call opcode in %s. Hexen2 format is not supported\n", prog->name);
2471 prog->statements[i].op = op;
2472 prog->statements[i].operand[0] = remapglobal(a);
2473 prog->statements[i].operand[1] = -1;
2474 prog->statements[i].operand[2] = -1;
2475 prog->statements[i].jumpabsolute = -1;
2479 if(prog->numstatements < 1)
2481 prog->error_cmd("PRVM_LoadProgs: empty program in %s", prog->name);
2483 else switch(prog->statements[prog->numstatements - 1].op)
2490 prog->error_cmd("PRVM_LoadProgs: program may fall off the edge (does not end with RETURN, GOTO or DONE) in %s", prog->name);
2494 // we're done with the file now
2496 Mem_Free(dprograms);
2499 // check required functions
2500 for(i=0 ; i < numrequiredfunc ; i++)
2501 if(PRVM_ED_FindFunction(prog, required_func[i]) == 0)
2502 prog->error_cmd("%s: %s not found in %s",prog->name, required_func[i], filename);
2504 PRVM_LoadLNO(prog, filename);
2506 PRVM_Init_Exec(prog);
2508 if(*prvm_language.string)
2509 // in CSQC we really shouldn't be able to change how stuff works... sorry for now
2510 // later idea: include a list of authorized .po file checksums with the csprogs
2512 qbool deftrans = prog == CLVM_prog;
2513 const char *realfilename = (prog != CLVM_prog ? filename : csqc_progname.string);
2514 if(deftrans) // once we have dotranslate_ strings, ALWAYS use the opt-in method!
2516 for (i=0 ; i<prog->numglobaldefs ; i++)
2519 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2520 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2521 if(name && !strncmp(name, "dotranslate_", 12))
2528 if(!strcmp(prvm_language.string, "dump"))
2530 qfile_t *f = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.pot", realfilename), "w", false);
2531 Con_Printf("Dumping to %s.pot\n", realfilename);
2534 for (i=0 ; i<prog->numglobaldefs ; i++)
2537 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2538 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2539 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2541 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2542 const char *value = PRVM_GetString(prog, val->string);
2545 char buf[MAX_INPUTLINE];
2546 PRVM_PO_UnparseString(buf, value, sizeof(buf));
2547 FS_Printf(f, "msgid \"%s\"\nmsgstr \"\"\n\n", buf);
2556 po_t *po = PRVM_PO_Load(
2557 va(vabuf, sizeof(vabuf), "%s.%s.po", realfilename, prvm_language.string),
2558 va(vabuf2, sizeof(vabuf2), "common.%s.po", prvm_language.string),
2559 prog->progs_mempool);
2562 for (i=0 ; i<prog->numglobaldefs ; i++)
2565 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2566 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2567 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2569 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2570 const char *value = PRVM_GetString(prog, val->string);
2573 value = PRVM_PO_Lookup(po, value);
2575 val->string = PRVM_SetEngineString(prog, value);
2583 for (cvar = prog->console_cmd->cvars->vars; cvar; cvar = cvar->next)
2584 cvar->globaldefindex[prog - prvm_prog_list] = -1;
2586 for (i=0 ; i<prog->numglobaldefs ; i++)
2589 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2590 //Con_Printf("found var %s\n", name);
2592 && !strncmp(name, "autocvar_", 9)
2593 && !(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
2596 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2597 cvar = Cvar_FindVar(prog->console_cmd->cvars, name + 9, prog->console_cmd->cvars_flagsmask);
2598 //Con_Printf("PRVM_LoadProgs: autocvar global %s in %s, processing...\n", name, prog->name);
2605 Con_DPrintf("PRVM_LoadProgs: no cvar for autocvar global %s in %s, creating...\n", name, prog->name);
2606 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2609 if((float)((int)(val->_float)) == val->_float)
2610 dpsnprintf(buf, sizeof(buf), "%i", (int)(val->_float));
2615 for (int precision = 7; precision <= 9; ++precision) {
2616 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2617 if ((float)atof(buf) == f) {
2625 for (i = 0; i < 3; ++i)
2629 for (int precision = 7; precision <= 9; ++precision) {
2630 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2631 if ((float)atof(buf) == f) {
2632 prec[i] = precision;
2637 dpsnprintf(buf, sizeof(buf), "%.*g %.*g %.*g", prec[0], val->vector[0], prec[1], val->vector[1], prec[2], val->vector[2]);
2641 value = PRVM_GetString(prog, val->string);
2644 Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name);
2647 cvar = Cvar_Get(prog->console_cmd->cvars, name + 9, value, prog->console_cmd->cvars_flagsmask, NULL);
2648 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2650 val->string = PRVM_SetEngineString(prog, cvar->string);
2651 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2654 prog->error_cmd("PRVM_LoadProgs: could not create cvar for autocvar global %s in %s", name, prog->name);
2655 cvar->globaldefindex[prog - prvm_prog_list] = i;
2657 else if((cvar->flags & CF_PRIVATE) == 0)
2659 // MUST BE SYNCED WITH cvar.c Cvar_Set
2662 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2665 val->_float = cvar->value;
2669 VectorClear(val->vector);
2670 for (j = 0;j < 3;j++)
2672 while (*s && ISWHITESPACE(*s))
2676 val->vector[j] = atof(s);
2677 while (!ISWHITESPACE(*s))
2684 val->string = PRVM_SetEngineString(prog, cvar->string);
2685 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2688 Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name);
2691 cvar->globaldefindex[prog - prvm_prog_list] = i;
2694 Con_Printf("PRVM_LoadProgs: private cvar for autocvar global %s in %s\n", name, prog->name);
2700 prog->loaded = true;
2702 PRVM_UpdateBreakpoints(prog);
2704 // set flags & mdef_ts in prog
2708 PRVM_FindOffsets(prog);
2710 prog->init_cmd(prog);
2713 PRVM_MEM_Alloc(prog);
2715 Con_Printf("%s: program loaded (crc %i, size %iK)\n", prog->name, prog->filecrc, (int)(filesize/1024));
2717 // Inittime is at least the time when this function finished. However,
2718 // later events may bump it.
2719 prog->inittime = host.realtime;
2723 static void PRVM_Fields_f(cmd_state_t *cmd)
2726 int i, j, ednum, used, usedamount;
2728 char tempstring[MAX_INPUTLINE], tempstring2[260];
2738 Con_Print("no progs loaded\n");
2743 if(Cmd_Argc(cmd) != 2)
2745 Con_Print("prvm_fields <program name>\n");
2749 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2752 counts = (int *)Mem_Alloc(tempmempool, prog->numfielddefs * sizeof(int));
2753 for (ednum = 0;ednum < prog->max_edicts;ednum++)
2755 ed = PRVM_EDICT_NUM(ednum);
2758 for (i = 1;i < prog->numfielddefs;i++)
2760 d = &prog->fielddefs[i];
2761 name = PRVM_GetString(prog, d->s_name);
2762 if (name[strlen(name)-2] == '_')
2763 continue; // skip _x, _y, _z vars
2764 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
2765 // if the value is still all 0, skip the field
2766 for (j = 0;j < prvm_type_size[d->type & ~DEF_SAVEGLOBAL];j++)
2768 if (val->ivector[j])
2779 for (i = 0;i < prog->numfielddefs;i++)
2781 d = &prog->fielddefs[i];
2782 name = PRVM_GetString(prog, d->s_name);
2783 if (name[strlen(name)-2] == '_')
2784 continue; // skip _x, _y, _z vars
2785 switch(d->type & ~DEF_SAVEGLOBAL)
2788 dp_strlcat(tempstring, "string ", sizeof(tempstring));
2791 dp_strlcat(tempstring, "entity ", sizeof(tempstring));
2794 dp_strlcat(tempstring, "function ", sizeof(tempstring));
2797 dp_strlcat(tempstring, "field ", sizeof(tempstring));
2800 dp_strlcat(tempstring, "void ", sizeof(tempstring));
2803 dp_strlcat(tempstring, "float ", sizeof(tempstring));
2806 dp_strlcat(tempstring, "vector ", sizeof(tempstring));
2809 dp_strlcat(tempstring, "pointer ", sizeof(tempstring));
2812 dpsnprintf (tempstring2, sizeof(tempstring2), "bad type %i ", d->type & ~DEF_SAVEGLOBAL);
2813 dp_strlcat(tempstring, tempstring2, sizeof(tempstring));
2816 if (strlen(name) > sizeof(tempstring2)-4)
2818 memcpy (tempstring2, name, sizeof(tempstring2)-4);
2819 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
2820 tempstring2[sizeof(tempstring2)-1] = 0;
2823 dp_strlcat(tempstring, name, sizeof(tempstring));
2824 for (j = (int)strlen(name);j < 25;j++)
2825 dp_strlcat(tempstring, " ", sizeof(tempstring));
2826 dpsnprintf(tempstring2, sizeof(tempstring2), "%5d", counts[i]);
2827 dp_strlcat(tempstring, tempstring2, sizeof(tempstring));
2828 dp_strlcat(tempstring, "\n", sizeof(tempstring));
2829 if (strlen(tempstring) >= sizeof(tempstring)/2)
2831 Con_Print(tempstring);
2837 usedamount += prvm_type_size[d->type & ~DEF_SAVEGLOBAL];
2841 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);
2844 static void PRVM_Globals_f(cmd_state_t *cmd)
2848 const char *wildcard;
2854 Con_Print("no progs loaded\n");
2857 if(Cmd_Argc (cmd) < 2 || Cmd_Argc(cmd) > 3)
2859 Con_Print("prvm_globals <program name> <optional name wildcard>\n");
2863 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2866 if( Cmd_Argc(cmd) == 3)
2867 wildcard = Cmd_Argv(cmd, 2);
2871 Con_Printf("%s :", prog->name);
2873 for (i = 0;i < prog->numglobaldefs;i++)
2876 if( !matchpattern( PRVM_GetString(prog, prog->globaldefs[i].s_name), wildcard, 1) )
2881 Con_Printf("%s\n", PRVM_GetString(prog, prog->globaldefs[i].s_name));
2883 Con_Printf("%i global variables, %i culled, totalling %i bytes\n", prog->numglobals, numculled, prog->numglobals * 4);
2891 static void PRVM_Global_f(cmd_state_t *cmd)
2895 char valuebuf[MAX_INPUTLINE];
2896 if( Cmd_Argc(cmd) != 3 ) {
2897 Con_Printf( "prvm_global <program name> <global name>\n" );
2901 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2904 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2906 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2908 Con_Printf( "%s: %s\n", Cmd_Argv(cmd, 2), PRVM_ValueString( prog, (etype_t)global->type, PRVM_GLOBALFIELDVALUE(global->ofs), valuebuf, sizeof(valuebuf) ) );
2916 static void PRVM_GlobalSet_f(cmd_state_t *cmd)
2920 if( Cmd_Argc(cmd) != 4 ) {
2921 Con_Printf( "prvm_globalset <program name> <global name> <value>\n" );
2925 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2928 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2930 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2932 PRVM_ED_ParseEpair( prog, NULL, global, Cmd_Argv(cmd, 3), true );
2936 ======================
2937 Break- and Watchpoints
2938 ======================
2942 char break_statement[256];
2943 char watch_global[256];
2945 char watch_field[256];
2948 static debug_data_t debug_data[PRVM_PROG_MAX];
2950 void PRVM_Breakpoint(prvm_prog_t *prog, int stack_index, const char *text)
2953 Con_Printf("PRVM_Breakpoint: %s\n", text);
2954 PRVM_PrintState(prog, stack_index);
2955 if (prvm_breakpointdump.integer)
2956 SV_Savegame_to(prog, va(vabuf, sizeof(vabuf), "breakpoint-%s.dmp", prog->name));
2959 void PRVM_Watchpoint(prvm_prog_t *prog, int stack_index, const char *text, etype_t type, prvm_eval_t *o, prvm_eval_t *n)
2961 size_t sz = sizeof(prvm_vec_t) * ((type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
2962 if (memcmp(o, n, sz))
2965 char valuebuf_o[128];
2966 char valuebuf_n[128];
2967 PRVM_UglyValueString(prog, type, o, valuebuf_o, sizeof(valuebuf_o));
2968 PRVM_UglyValueString(prog, type, n, valuebuf_n, sizeof(valuebuf_n));
2969 dpsnprintf(buf, sizeof(buf), "%s: %s -> %s", text, valuebuf_o, valuebuf_n);
2970 PRVM_Breakpoint(prog, stack_index, buf);
2975 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog)
2977 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
2980 if (debug->break_statement[0])
2982 if (debug->break_statement[0] >= '0' && debug->break_statement[0] <= '9')
2984 prog->break_statement = atoi(debug->break_statement);
2985 prog->break_stack_index = 0;
2990 func = PRVM_ED_FindFunction (prog, debug->break_statement);
2993 Con_Printf("%s progs: no function or statement named %s to break on!\n", prog->name, debug->break_statement);
2994 prog->break_statement = -1;
2998 prog->break_statement = func->first_statement;
2999 prog->break_stack_index = 1;
3002 if (prog->break_statement >= -1)
3003 Con_Printf("%s progs: breakpoint is at statement %d\n", prog->name, prog->break_statement);
3006 prog->break_statement = -1;
3008 if (debug->watch_global[0])
3010 mdef_t *global = PRVM_ED_FindGlobal( prog, debug->watch_global );
3013 Con_Printf( "%s progs: no global named '%s' to watch!\n", prog->name, debug->watch_global );
3014 prog->watch_global_type = ev_void;
3018 size_t sz = sizeof(prvm_vec_t) * ((global->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3019 prog->watch_global = global->ofs;
3020 prog->watch_global_type = (etype_t)global->type;
3021 memcpy(&prog->watch_global_value, PRVM_GLOBALFIELDVALUE(prog->watch_global), sz);
3023 if (prog->watch_global_type != ev_void)
3024 Con_Printf("%s progs: global watchpoint is at global index %d\n", prog->name, prog->watch_global);
3027 prog->watch_global_type = ev_void;
3029 if (debug->watch_field[0])
3031 mdef_t *field = PRVM_ED_FindField( prog, debug->watch_field );
3034 Con_Printf( "%s progs: no field named '%s' to watch!\n", prog->name, debug->watch_field );
3035 prog->watch_field_type = ev_void;
3039 size_t sz = sizeof(prvm_vec_t) * ((field->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3040 prog->watch_edict = debug->watch_edict;
3041 prog->watch_field = field->ofs;
3042 prog->watch_field_type = (etype_t)field->type;
3043 if (prog->watch_edict < prog->num_edicts)
3044 memcpy(&prog->watch_edictfield_value, PRVM_EDICTFIELDVALUE(PRVM_EDICT_NUM(prog->watch_edict), prog->watch_field), sz);
3046 memset(&prog->watch_edictfield_value, 0, sz);
3048 if (prog->watch_edict != ev_void)
3049 Con_Printf("%s progs: edict field watchpoint is at edict %d field index %d\n", prog->name, prog->watch_edict, prog->watch_field);
3052 prog->watch_field_type = ev_void;
3055 static void PRVM_Breakpoint_f(cmd_state_t *cmd)
3059 if( Cmd_Argc(cmd) == 2 ) {
3060 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3063 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3064 debug->break_statement[0] = 0;
3066 PRVM_UpdateBreakpoints(prog);
3069 if( Cmd_Argc(cmd) != 3 ) {
3070 Con_Printf( "prvm_breakpoint <program name> <function name | statement>\n" );
3074 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3078 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3079 dp_strlcpy(debug->break_statement, Cmd_Argv(cmd, 2), sizeof(debug->break_statement));
3081 PRVM_UpdateBreakpoints(prog);
3084 static void PRVM_GlobalWatchpoint_f(cmd_state_t *cmd)
3088 if( Cmd_Argc(cmd) == 2 ) {
3089 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3092 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3093 debug->watch_global[0] = 0;
3095 PRVM_UpdateBreakpoints(prog);
3098 if( Cmd_Argc(cmd) != 3 ) {
3099 Con_Printf( "prvm_globalwatchpoint <program name> <global name>\n" );
3103 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3107 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3108 dp_strlcpy(debug->watch_global, Cmd_Argv(cmd, 2), sizeof(debug->watch_global));
3110 PRVM_UpdateBreakpoints(prog);
3113 static void PRVM_EdictWatchpoint_f(cmd_state_t *cmd)
3117 if( Cmd_Argc(cmd) == 2 ) {
3118 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3121 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3122 debug->watch_field[0] = 0;
3124 PRVM_UpdateBreakpoints(prog);
3127 if( Cmd_Argc(cmd) != 4 ) {
3128 Con_Printf( "prvm_edictwatchpoint <program name> <edict number> <field name>\n" );
3132 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3136 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3137 debug->watch_edict = atoi(Cmd_Argv(cmd, 2));
3138 dp_strlcpy(debug->watch_field, Cmd_Argv(cmd, 3), sizeof(debug->watch_field));
3140 PRVM_UpdateBreakpoints(prog);
3148 void PRVM_Init (void)
3152 Cmd_AddCommand(CF_SHARED, "prvm_edict", PRVM_ED_PrintEdict_f, "print all data about an entity number in the selected VM (server, client, menu)");
3153 Cmd_AddCommand(CF_SHARED, "prvm_edicts", PRVM_ED_PrintEdicts_f, "prints all data about all entities in the selected VM (server, client, menu)");
3154 Cmd_AddCommand(CF_SHARED, "prvm_edictcount", PRVM_ED_Count_f, "prints number of active entities in the selected VM (server, client, menu)");
3155 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)");
3156 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");
3157 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)");
3158 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)");
3159 Cmd_AddCommand(CF_SHARED, "prvm_globals", PRVM_Globals_f, "prints all global variables in the selected VM (server, client, menu)");
3160 Cmd_AddCommand(CF_SHARED, "prvm_global", PRVM_Global_f, "prints value of a specified global variable in the selected VM (server, client, menu)");
3161 Cmd_AddCommand(CF_SHARED, "prvm_globalset", PRVM_GlobalSet_f, "sets value of a specified global variable in the selected VM (server, client, menu)");
3162 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)");
3163 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");
3164 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");
3165 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)");
3166 Cmd_AddCommand(CF_SHARED, "cl_cmd", PRVM_GameCommand_Client_f, "calls the client QC function GameCommand with the supplied string as argument");
3167 Cmd_AddCommand(CF_SHARED, "menu_cmd", PRVM_GameCommand_Menu_f, "calls the menu QC function GameCommand with the supplied string as argument");
3168 Cmd_AddCommand(CF_SHARED, "sv_cmd", PRVM_GameCommand_Server_f, "calls the server QC function GameCommand with the supplied string as argument");
3169 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");
3170 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");
3171 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");
3173 Cvar_RegisterVariable (&prvm_language);
3174 Cvar_RegisterVariable (&prvm_traceqc);
3175 Cvar_RegisterVariable (&prvm_statementprofiling);
3176 Cvar_RegisterVariable (&prvm_timeprofiling);
3177 Cvar_RegisterVariable (&prvm_coverage);
3178 Cvar_RegisterVariable (&prvm_backtraceforwarnings);
3179 Cvar_RegisterVariable (&prvm_leaktest);
3180 Cvar_RegisterVariable (&prvm_leaktest_follow_targetname);
3181 Cvar_RegisterVariable (&prvm_leaktest_ignore_classnames);
3182 Cvar_RegisterVariable (&prvm_errordump);
3183 Cvar_RegisterVariable (&prvm_breakpointdump);
3184 Cvar_RegisterVariable (&prvm_reuseedicts_startuptime);
3185 Cvar_RegisterVariable (&prvm_reuseedicts_neverinsameframe);
3186 Cvar_RegisterVariable (&prvm_garbagecollection_enable);
3187 Cvar_RegisterVariable (&prvm_garbagecollection_notify);
3188 Cvar_RegisterVariable (&prvm_garbagecollection_scan_limit);
3189 Cvar_RegisterVariable (&prvm_garbagecollection_strings);
3190 Cvar_RegisterVariable (&prvm_stringdebug);
3192 // COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!)
3193 prvm_runawaycheck = !Sys_CheckParm("-norunaway");
3197 // LadyHavoc: report supported extensions
3198 Con_DPrintf("\nQuakeC extensions for server and client:");
3199 for (i = 0; vm_sv_extensions[i]; i++)
3200 Con_DPrintf(" %s", vm_sv_extensions[i]);
3203 Con_DPrintf("\nQuakeC extensions for menu:");
3204 for (i = 0; vm_m_extensions[i]; i++)
3205 Con_DPrintf(" %s", vm_m_extensions[i]);
3215 void PRVM_Prog_Init(prvm_prog_t *prog, cmd_state_t *cmd)
3217 PRVM_Prog_Reset(prog);
3218 prog->leaktest_active = prvm_leaktest.integer != 0;
3219 prog->console_cmd = cmd;
3222 // LadyHavoc: turned PRVM_EDICT_NUM into a #define for speed reasons
3223 unsigned int PRVM_EDICT_NUM_ERROR(prvm_prog_t *prog, unsigned int n, const char *filename, int fileline)
3225 prog->error_cmd("PRVM_EDICT_NUM: %s: bad number %i (called at %s:%i)", prog->name, n, filename, fileline);
3229 #define PRVM_KNOWNSTRINGBASE 0x40000000
3231 const char *PRVM_GetString(prvm_prog_t *prog, int num)
3236 if (prvm_stringdebug.integer)
3237 VM_Warning(prog, "PRVM_GetString: Invalid string offset (%i < 0)\n", num);
3240 else if (num < prog->stringssize)
3242 // constant string from progs.dat
3243 return prog->strings + num;
3245 else if (num <= prog->stringssize + prog->tempstringsbuf.maxsize)
3247 // tempstring returned by engine to QC (becomes invalid after returning to engine)
3248 num -= prog->stringssize;
3249 if (num < prog->tempstringsbuf.cursize)
3250 return (char *)prog->tempstringsbuf.data + num;
3253 if (prvm_stringdebug.integer)
3254 VM_Warning(prog, "PRVM_GetString: Invalid temp-string offset (%i >= %i prog->tempstringsbuf.cursize)\n", num, prog->tempstringsbuf.cursize);
3258 else if (num & PRVM_KNOWNSTRINGBASE)
3261 num = num - PRVM_KNOWNSTRINGBASE;
3262 if (num >= 0 && num < prog->numknownstrings)
3264 if (!prog->knownstrings[num])
3266 if (prvm_stringdebug.integer)
3267 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num);
3270 // refresh the garbage collection on the string - this guards
3271 // against a certain sort of repeated migration to earlier
3272 // points in the scan that could otherwise result in the string
3273 // being freed for being unused
3274 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] & ~KNOWNSTRINGFLAG_GCPRUNE) | KNOWNSTRINGFLAG_GCMARK;
3275 return prog->knownstrings[num];
3279 if (prvm_stringdebug.integer)
3280 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, prog->numknownstrings);
3286 // invalid string offset
3287 if (prvm_stringdebug.integer)
3288 VM_Warning(prog, "PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, prog->stringssize);
3293 const char *PRVM_ChangeEngineString(prvm_prog_t *prog, int i, const char *s)
3296 i = i - PRVM_KNOWNSTRINGBASE;
3297 if (i < 0 || i >= prog->numknownstrings)
3298 prog->error_cmd("PRVM_ChangeEngineString: string index %i is out of bounds", i);
3299 else if ((prog->knownstrings_flags[i] & KNOWNSTRINGFLAG_ENGINE) == 0)
3300 prog->error_cmd("PRVM_ChangeEngineString: string index %i is not an engine string", i);
3301 old = prog->knownstrings[i];
3302 prog->knownstrings[i] = s;
3306 static void PRVM_NewKnownString(prvm_prog_t *prog, int i, int flags, const char *s)
3308 if (i >= prog->numknownstrings)
3310 if (i >= prog->maxknownstrings)
3312 const char **oldstrings = prog->knownstrings;
3313 const unsigned char *oldstrings_flags = prog->knownstrings_flags;
3314 const char **oldstrings_origin = prog->knownstrings_origin;
3315 prog->maxknownstrings += 128;
3316 prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3317 prog->knownstrings_flags = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char));
3318 if (prog->leaktest_active)
3319 prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3320 if (prog->numknownstrings)
3322 memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *));
3323 memcpy((char **)prog->knownstrings_flags, oldstrings_flags, prog->numknownstrings * sizeof(unsigned char));
3324 if (prog->leaktest_active)
3325 memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *));
3328 prog->numknownstrings++;
3330 prog->firstfreeknownstring = i + 1;
3331 prog->knownstrings[i] = s;
3332 // it's in use right now, spare it until the next gc pass - that said, it is not freeable so this is probably moot
3333 prog->knownstrings_flags[i] = flags;
3334 if (prog->leaktest_active)
3335 prog->knownstrings_origin[i] = NULL;
3338 int PRVM_SetEngineString(prvm_prog_t *prog, const char *s)
3343 if (s >= prog->strings && s <= prog->strings + prog->stringssize)
3344 prog->error_cmd("PRVM_SetEngineString: s in prog->strings area");
3345 // if it's in the tempstrings area, use a reserved range
3346 // (otherwise we'd get millions of useless string offsets cluttering the database)
3347 if (s >= (char *)prog->tempstringsbuf.data && s < (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.maxsize)
3348 return prog->stringssize + (s - (char *)prog->tempstringsbuf.data);
3349 // see if it's a known string address
3350 for (i = 0;i < prog->numknownstrings;i++)
3351 if (prog->knownstrings[i] == s)
3352 return PRVM_KNOWNSTRINGBASE + i;
3353 // new unknown engine string
3354 if (developer_insane.integer)
3355 Con_DPrintf("new engine string %p = \"%s\"\n", (void *)s, s);
3356 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3357 if (!prog->knownstrings[i])
3359 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE, s);
3360 return PRVM_KNOWNSTRINGBASE + i;
3363 // temp string handling
3365 // all tempstrings go into this buffer consecutively, and it is reset
3366 // whenever PRVM_ExecuteProgram returns to the engine
3367 // (technically each PRVM_ExecuteProgram call saves the cursize value and
3368 // restores it on return, so multiple recursive calls can share the same
3370 // the buffer size is automatically grown as needed
3371 int PRVM_SetTempString(prvm_prog_t *prog, const char *s, size_t slen)
3376 if (!s || slen >= VM_TEMPSTRING_MAXSIZE)
3379 if (developer_insane.integer)
3380 Con_DPrintf("PRVM_SetTempString %s: cursize %i, size %zu\n", prog->name, prog->tempstringsbuf.cursize, size);
3381 if ((size_t)prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3383 sizebuf_t old = prog->tempstringsbuf;
3384 if (prog->tempstringsbuf.cursize + size >= 1<<28)
3385 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);
3386 prog->tempstringsbuf.maxsize = max(prog->tempstringsbuf.maxsize, 65536);
3387 while ((size_t)prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3388 prog->tempstringsbuf.maxsize *= 2;
3389 if (prog->tempstringsbuf.maxsize != old.maxsize || prog->tempstringsbuf.data == NULL)
3391 Con_DPrintf("PRVM_SetTempString %s: enlarging tempstrings buffer (%iKB -> %iKB)\n", prog->name, old.maxsize/1024, prog->tempstringsbuf.maxsize/1024);
3392 prog->tempstringsbuf.data = (unsigned char *) Mem_Alloc(prog->progs_mempool, prog->tempstringsbuf.maxsize);
3396 memcpy(prog->tempstringsbuf.data, old.data, old.cursize);
3401 t = (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.cursize;
3403 prog->tempstringsbuf.cursize += size;
3404 return PRVM_SetEngineString(prog, t);
3407 int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer)
3417 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3418 if (!prog->knownstrings[i])
3420 s = (char *)PRVM_Alloc(bufferlength);
3421 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK, s);
3422 if(prog->leaktest_active)
3423 prog->knownstrings_origin[i] = PRVM_AllocationOrigin(prog);
3425 *pointer = (char *)(prog->knownstrings[i]);
3426 return PRVM_KNOWNSTRINGBASE + i;
3429 void PRVM_FreeString(prvm_prog_t *prog, int num)
3432 prog->error_cmd("PRVM_FreeString %s: attempt to free a NULL string", prog->name);
3433 else if (num >= 0 && num < prog->stringssize)
3434 prog->error_cmd("PRVM_FreeString %s: attempt to free a constant string", prog->name);
3435 else if (num >= PRVM_KNOWNSTRINGBASE && num < PRVM_KNOWNSTRINGBASE + prog->numknownstrings)
3437 num = num - PRVM_KNOWNSTRINGBASE;
3438 if (!prog->knownstrings[num])
3439 prog->error_cmd("PRVM_FreeString %s: attempt to free a non-existent or already freed string", prog->name);
3440 if (!prog->knownstrings_flags[num])
3441 prog->error_cmd("PRVM_FreeString %s: attempt to free a string owned by the engine", prog->name);
3442 PRVM_Free((char *)prog->knownstrings[num]);
3443 if(prog->leaktest_active)
3444 if(prog->knownstrings_origin[num])
3445 PRVM_Free((char *)prog->knownstrings_origin[num]);
3446 prog->knownstrings[num] = NULL;
3447 prog->knownstrings_flags[num] = 0;
3448 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3451 prog->error_cmd("PRVM_FreeString %s: invalid string offset %i", prog->name, num);
3454 static qbool PRVM_IsStringReferenced(prvm_prog_t *prog, string_t string)
3458 for (i = 0;i < prog->numglobaldefs;i++)
3460 mdef_t *d = &prog->globaldefs[i];
3461 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3463 if(string == PRVM_GLOBALFIELDSTRING(d->ofs))
3467 for(j = 0; j < prog->num_edicts; ++j)
3469 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3472 for (i=0; i<prog->numfielddefs; ++i)
3474 mdef_t *d = &prog->fielddefs[i];
3475 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3477 if(string == PRVM_EDICTFIELDSTRING(ed, d->ofs))
3485 static qbool PRVM_IsEdictRelevant(prvm_prog_t *prog, prvm_edict_t *edict)
3489 if(PRVM_NUM_FOR_EDICT(edict) <= prog->reserved_edicts)
3490 return true; // world or clients
3491 if (edict->freetime <= prog->inittime)
3492 return true; // created during startup
3493 if (prog == SVVM_prog)
3495 if(PRVM_serveredictfloat(edict, solid)) // can block other stuff, or is a trigger?
3497 if(PRVM_serveredictfloat(edict, modelindex)) // visible ent?
3499 if(PRVM_serveredictfloat(edict, effects)) // particle effect?
3501 if(PRVM_serveredictfunction(edict, think)) // has a think function?
3502 if(PRVM_serveredictfloat(edict, nextthink) > 0) // that actually will eventually run?
3504 if(PRVM_serveredictfloat(edict, takedamage))
3506 if(*prvm_leaktest_ignore_classnames.string)
3508 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_serveredictstring(edict, classname)))))
3512 else if (prog == CLVM_prog)
3514 // TODO someone add more stuff here
3515 if(PRVM_clientedictfloat(edict, entnum)) // csqc networked
3517 if(PRVM_clientedictfloat(edict, modelindex)) // visible ent?
3519 if(PRVM_clientedictfloat(edict, effects)) // particle effect?
3521 if(PRVM_clientedictfunction(edict, think)) // has a think function?
3522 if(PRVM_clientedictfloat(edict, nextthink) > 0) // that actually will eventually run?
3524 if(*prvm_leaktest_ignore_classnames.string)
3526 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_clientedictstring(edict, classname)))))
3532 // menu prog does not have classnames
3537 static qbool PRVM_IsEdictReferenced(prvm_prog_t *prog, prvm_edict_t *edict, int mark)
3540 int edictnum = PRVM_NUM_FOR_EDICT(edict);
3541 const char *targetname = NULL;
3543 if (prog == SVVM_prog && prvm_leaktest_follow_targetname.integer)
3544 targetname = PRVM_GetString(prog, PRVM_serveredictstring(edict, targetname));
3547 if(!*targetname) // ""
3550 for(j = 0; j < prog->num_edicts; ++j)
3552 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3553 if (ed->priv.required->mark < mark)
3559 const char *target = PRVM_GetString(prog, PRVM_serveredictstring(ed, target));
3561 if(!strcmp(target, targetname))
3564 for (i=0; i<prog->numfielddefs; ++i)
3566 mdef_t *d = &prog->fielddefs[i];
3567 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3569 if(edictnum == PRVM_EDICTFIELDEDICT(ed, d->ofs))
3577 static void PRVM_MarkReferencedEdicts(prvm_prog_t *prog)
3583 // Stage 1: world, all entities that are relevant, and all entities that are referenced by globals.
3585 for(j = 0; j < prog->num_edicts; ++j)
3587 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3590 ed->priv.required->mark = PRVM_IsEdictRelevant(prog, ed) ? stage : 0;
3592 for (i = 0;i < prog->numglobaldefs;i++)
3594 mdef_t *d = &prog->globaldefs[i];
3596 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3598 j = PRVM_GLOBALFIELDEDICT(d->ofs);
3599 if (i < 0 || j >= prog->max_edicts) {
3600 Con_Printf("Invalid entity reference from global %s.\n", PRVM_GetString(prog, d->s_name));
3603 ed = PRVM_EDICT_NUM(j);;
3604 ed->priv.required->mark = stage;
3607 // Future stages: all entities that are referenced by an entity of the previous stage.
3611 for(j = 0; j < prog->num_edicts; ++j)
3613 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3616 if(ed->priv.required->mark)
3618 if(PRVM_IsEdictReferenced(prog, ed, stage))
3620 ed->priv.required->mark = stage + 1;
3627 Con_DPrintf("leak check used %d stages to find all references\n", stage);
3630 void PRVM_LeakTest(prvm_prog_t *prog)
3633 qbool leaked = false;
3635 if(!prog->leaktest_active)
3639 for (i = 0; i < prog->numknownstrings; ++i)
3641 if(prog->knownstrings[i])
3642 if(prog->knownstrings_flags[i])
3643 if(prog->knownstrings_origin[i])
3644 if(!PRVM_IsStringReferenced(prog, PRVM_KNOWNSTRINGBASE + i))
3646 Con_Printf("Unreferenced string found!\n Value: %s\n Origin: %s\n", prog->knownstrings[i], prog->knownstrings_origin[i]);
3652 PRVM_MarkReferencedEdicts(prog);
3653 for(j = 0; j < prog->num_edicts; ++j)
3655 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3658 if(!ed->priv.required->mark)
3659 if(ed->priv.required->allocation_origin)
3661 Con_Printf("Unreferenced edict found!\n Allocated at: %s\n", ed->priv.required->allocation_origin);
3662 PRVM_ED_Print(prog, ed, NULL);
3667 ed->priv.required->mark = 0; // clear marks again when done
3670 for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); ++i)
3672 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
3674 if(stringbuffer->origin)
3676 Con_Printf("Open string buffer handle found!\n Allocated at: %s\n", stringbuffer->origin);
3681 for(i = 0; i < PRVM_MAX_OPENFILES; ++i)
3683 if(prog->openfiles[i])
3684 if(prog->openfiles_origin[i])
3686 Con_Printf("Open file handle found!\n Allocated at: %s\n", prog->openfiles_origin[i]);
3691 for(i = 0; i < PRVM_MAX_OPENSEARCHES; ++i)
3693 if(prog->opensearches[i])
3694 if(prog->opensearches_origin[i])
3696 Con_Printf("Open search handle found!\n Allocated at: %s\n", prog->opensearches_origin[i]);
3702 Con_Printf("Congratulations. No leaks found.\n");
3705 void PRVM_GarbageCollection(prvm_prog_t *prog)
3707 int limit = prvm_garbagecollection_scan_limit.integer;
3708 prvm_prog_garbagecollection_state_t *gc = &prog->gc;
3709 if (!prvm_garbagecollection_enable.integer)
3712 // we like to limit how much scanning we do so it doesn't put a significant
3713 // burden on the cpu, so each of these are not complete scans, we also like
3714 // to have consistent cpu usage so we do a bit of work on each category of
3715 // leaked object every frame
3721 case PRVM_GC_GLOBALS_MARK:
3722 for (; gc->globals_mark_progress < prog->numglobaldefs && (limit--) > 0; gc->globals_mark_progress++)
3724 mdef_t *d = &prog->globaldefs[gc->globals_mark_progress];
3729 prvm_int_t s = prog->globals.ip[d->ofs];
3730 if (s & PRVM_KNOWNSTRINGBASE)
3732 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3733 if (!prog->knownstrings[num])
3736 Con_DPrintf("PRVM_GarbageCollection: Found bogus strzone reference in global %i (global name: \"%s\"), erasing reference", d->ofs, PRVM_GetString(prog, d->s_name));
3737 prog->globals.ip[d->ofs] = 0;
3740 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3748 if (gc->globals_mark_progress >= prog->numglobaldefs)
3751 case PRVM_GC_FIELDS_MARK:
3752 for (; gc->fields_mark_progress < prog->numfielddefs && limit > 0;)
3754 mdef_t *d = &prog->fielddefs[gc->fields_mark_progress];
3758 //for (gc-> entityindex = 0; entityindex < prog->num_edicts; entityindex++)
3759 for (;gc->fields_mark_progress_entity < prog->num_edicts && (limit--) > 0;gc->fields_mark_progress_entity++)
3761 int entityindex = gc->fields_mark_progress_entity;
3762 prvm_int_t s = prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs];
3763 if (s & PRVM_KNOWNSTRINGBASE)
3765 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3766 if (!prog->knownstrings[num])
3769 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));
3770 prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs] = 0;
3773 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3776 if (gc->fields_mark_progress_entity >= prog->num_edicts)
3778 gc->fields_mark_progress_entity = 0;
3779 gc->fields_mark_progress++;
3783 gc->fields_mark_progress_entity = 0;
3784 gc->fields_mark_progress++;
3788 if (gc->fields_mark_progress >= prog->numfielddefs)
3791 case PRVM_GC_KNOWNSTRINGS_SWEEP:
3792 // free any strzone'd strings that are not marked
3793 if (!prvm_garbagecollection_strings.integer)
3798 for (;gc->knownstrings_sweep_progress < prog->numknownstrings && (limit--) > 0;gc->knownstrings_sweep_progress++)
3800 int num = gc->knownstrings_sweep_progress;
3801 if (prog->knownstrings[num] && (prog->knownstrings_flags[num] & (KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE)) == 0)
3803 if (prog->knownstrings_flags[num] & KNOWNSTRINGFLAG_GCPRUNE)
3805 // string has been marked for pruning two passes in a row
3806 if (prvm_garbagecollection_notify.integer)
3807 Con_DPrintf("prvm_garbagecollection_notify: %s: freeing unreferenced string %i: \"%s\"\n", prog->name, num, prog->knownstrings[num]);
3808 Mem_Free((char *)prog->knownstrings[num]);
3809 prog->knownstrings[num] = NULL;
3810 prog->knownstrings_flags[num] = 0;
3811 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3815 // mark it for pruning next pass
3816 prog->knownstrings_flags[num] |= KNOWNSTRINGFLAG_GCPRUNE;
3820 if (gc->knownstrings_sweep_progress >= prog->numknownstrings)
3825 memset(gc, 0, sizeof(*gc));