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, void CheckRequiredFuncs(prvm_prog_t *prog, const char *filename), int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global)
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("%s: there is already a %s program loaded!", __func__, 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("%s: couldn't load \"%s\" for %s", __func__, 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("%s: out of bounds function statement (function %d) in %s", __func__, 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("%s: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", __func__, 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("%s: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", __func__, 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("%s: out of bounds IF/IFNOT (statement %d) in %s", __func__, 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("%s: out of bounds GOTO (statement %d) in %s", __func__, 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("%s: unknown opcode %d at statement %d in %s\n", __func__, (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("%s: out of bounds global index (statement %d)", __func__, 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("%s: out of bounds global index (statement %d) in %s", __func__, 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("%s: storep-with-offset is not permitted in %s\n", __func__, prog->name);
2442 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals)
2443 prog->error_cmd("%s: out of bounds global index (statement %d) in %s", __func__, 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("%s: out of bounds global index (statement %d) in %s", __func__, i, prog->name);
2469 if (b || c) //Spike -- added this check just as a diagnostic...
2470 Con_DPrintf("%s: unexpected offset on call opcode in %s. Hexen2 format is not supported\n", __func__, 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("%s: empty program in %s", __func__, prog->name);
2483 else switch(prog->statements[prog->numstatements - 1].op)
2490 prog->error_cmd("%s: program may fall off the edge (does not end with RETURN, GOTO or DONE) in %s", __func__, prog->name);
2494 // we're done with the file now
2496 Mem_Free(dprograms);
2500 // expected to not return (call prog->error_cmd) if checks fail
2501 CheckRequiredFuncs(prog, filename);
2503 PRVM_LoadLNO(prog, filename);
2505 PRVM_Init_Exec(prog);
2507 if(*prvm_language.string)
2508 // in CSQC we really shouldn't be able to change how stuff works... sorry for now
2509 // later idea: include a list of authorized .po file checksums with the csprogs
2511 qbool deftrans = prog == CLVM_prog;
2512 const char *realfilename = (prog != CLVM_prog ? filename : csqc_progname.string);
2513 if(deftrans) // once we have dotranslate_ strings, ALWAYS use the opt-in method!
2515 for (i=0 ; i<prog->numglobaldefs ; i++)
2518 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2519 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2520 if(name && !strncmp(name, "dotranslate_", 12))
2527 if(!strcmp(prvm_language.string, "dump"))
2529 qfile_t *f = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.pot", realfilename), "w", false);
2530 Con_Printf("Dumping to %s.pot\n", realfilename);
2533 for (i=0 ; i<prog->numglobaldefs ; i++)
2536 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2537 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2538 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2540 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2541 const char *value = PRVM_GetString(prog, val->string);
2544 char buf[MAX_INPUTLINE];
2545 PRVM_PO_UnparseString(buf, value, sizeof(buf));
2546 FS_Printf(f, "msgid \"%s\"\nmsgstr \"\"\n\n", buf);
2555 po_t *po = PRVM_PO_Load(
2556 va(vabuf, sizeof(vabuf), "%s.%s.po", realfilename, prvm_language.string),
2557 va(vabuf2, sizeof(vabuf2), "common.%s.po", prvm_language.string),
2558 prog->progs_mempool);
2561 for (i=0 ; i<prog->numglobaldefs ; i++)
2564 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2565 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2566 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2568 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2569 const char *value = PRVM_GetString(prog, val->string);
2572 value = PRVM_PO_Lookup(po, value);
2574 val->string = PRVM_SetEngineString(prog, value);
2582 for (cvar = prog->console_cmd->cvars->vars; cvar; cvar = cvar->next)
2583 cvar->globaldefindex[prog - prvm_prog_list] = -1;
2585 for (i=0 ; i<prog->numglobaldefs ; i++)
2588 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2589 //Con_Printf("found var %s\n", name);
2591 && !strncmp(name, "autocvar_", 9)
2592 && !(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
2595 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2596 cvar = Cvar_FindVar(prog->console_cmd->cvars, name + 9, prog->console_cmd->cvars_flagsmask);
2597 //Con_Printf("%s: autocvar global %s in %s, processing...\n", __func__, name, prog->name);
2604 Con_DPrintf("%s: no cvar for autocvar global %s in %s, creating...\n", __func__, name, prog->name);
2605 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2608 if((float)((int)(val->_float)) == val->_float)
2609 dpsnprintf(buf, sizeof(buf), "%i", (int)(val->_float));
2614 for (int precision = 7; precision <= 9; ++precision) {
2615 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2616 if ((float)atof(buf) == f) {
2624 for (i = 0; i < 3; ++i)
2628 for (int precision = 7; precision <= 9; ++precision) {
2629 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2630 if ((float)atof(buf) == f) {
2631 prec[i] = precision;
2636 dpsnprintf(buf, sizeof(buf), "%.*g %.*g %.*g", prec[0], val->vector[0], prec[1], val->vector[1], prec[2], val->vector[2]);
2640 value = PRVM_GetString(prog, val->string);
2643 Con_Printf("%s: invalid type of autocvar global %s in %s\n", __func__, name, prog->name);
2646 cvar = Cvar_Get(prog->console_cmd->cvars, name + 9, value, prog->console_cmd->cvars_flagsmask, NULL);
2647 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2649 val->string = PRVM_SetEngineString(prog, cvar->string);
2650 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2653 prog->error_cmd("%s: could not create cvar for autocvar global %s in %s", __func__, name, prog->name);
2654 cvar->globaldefindex[prog - prvm_prog_list] = i;
2656 else if((cvar->flags & CF_PRIVATE) == 0)
2658 // MUST BE SYNCED WITH cvar.c Cvar_Set
2661 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2664 val->_float = cvar->value;
2668 VectorClear(val->vector);
2669 for (j = 0;j < 3;j++)
2671 while (*s && ISWHITESPACE(*s))
2675 val->vector[j] = atof(s);
2676 while (!ISWHITESPACE(*s))
2683 val->string = PRVM_SetEngineString(prog, cvar->string);
2684 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2687 Con_Printf("%s: invalid type of autocvar global %s in %s\n", __func__, name, prog->name);
2690 cvar->globaldefindex[prog - prvm_prog_list] = i;
2693 Con_Printf("%s: private cvar for autocvar global %s in %s\n", __func__, name, prog->name);
2699 prog->loaded = true;
2701 PRVM_UpdateBreakpoints(prog);
2703 // set flags & mdef_ts in prog
2705 PRVM_FindOffsets(prog);
2707 prog->init_cmd(prog);
2710 PRVM_MEM_Alloc(prog);
2712 Con_Printf("%s: program loaded (crc %i, size %iK)\n", prog->name, prog->filecrc, (int)(filesize/1024));
2714 // Inittime is at least the time when this function finished. However,
2715 // later events may bump it.
2716 prog->inittime = host.realtime;
2720 static void PRVM_Fields_f(cmd_state_t *cmd)
2723 int i, j, ednum, used, usedamount;
2725 char tempstring[MAX_INPUTLINE], tempstring2[260];
2735 Con_Print("no progs loaded\n");
2740 if(Cmd_Argc(cmd) != 2)
2742 Con_Print("prvm_fields <program name>\n");
2746 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2749 counts = (int *)Mem_Alloc(tempmempool, prog->numfielddefs * sizeof(int));
2750 for (ednum = 0;ednum < prog->max_edicts;ednum++)
2752 ed = PRVM_EDICT_NUM(ednum);
2755 for (i = 1;i < prog->numfielddefs;i++)
2757 d = &prog->fielddefs[i];
2758 name = PRVM_GetString(prog, d->s_name);
2759 if (name[strlen(name)-2] == '_')
2760 continue; // skip _x, _y, _z vars
2761 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
2762 // if the value is still all 0, skip the field
2763 for (j = 0;j < prvm_type_size[d->type & ~DEF_SAVEGLOBAL];j++)
2765 if (val->ivector[j])
2776 for (i = 0;i < prog->numfielddefs;i++)
2778 d = &prog->fielddefs[i];
2779 name = PRVM_GetString(prog, d->s_name);
2780 if (name[strlen(name)-2] == '_')
2781 continue; // skip _x, _y, _z vars
2782 switch(d->type & ~DEF_SAVEGLOBAL)
2785 dp_strlcat(tempstring, "string ", sizeof(tempstring));
2788 dp_strlcat(tempstring, "entity ", sizeof(tempstring));
2791 dp_strlcat(tempstring, "function ", sizeof(tempstring));
2794 dp_strlcat(tempstring, "field ", sizeof(tempstring));
2797 dp_strlcat(tempstring, "void ", sizeof(tempstring));
2800 dp_strlcat(tempstring, "float ", sizeof(tempstring));
2803 dp_strlcat(tempstring, "vector ", sizeof(tempstring));
2806 dp_strlcat(tempstring, "pointer ", sizeof(tempstring));
2809 dpsnprintf (tempstring2, sizeof(tempstring2), "bad type %i ", d->type & ~DEF_SAVEGLOBAL);
2810 dp_strlcat(tempstring, tempstring2, sizeof(tempstring));
2813 if (strlen(name) > sizeof(tempstring2)-4)
2815 memcpy (tempstring2, name, sizeof(tempstring2)-4);
2816 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
2817 tempstring2[sizeof(tempstring2)-1] = 0;
2820 dp_strlcat(tempstring, name, sizeof(tempstring));
2821 for (j = (int)strlen(name);j < 25;j++)
2822 dp_strlcat(tempstring, " ", sizeof(tempstring));
2823 dpsnprintf(tempstring2, sizeof(tempstring2), "%5d", counts[i]);
2824 dp_strlcat(tempstring, tempstring2, sizeof(tempstring));
2825 dp_strlcat(tempstring, "\n", sizeof(tempstring));
2826 if (strlen(tempstring) >= sizeof(tempstring)/2)
2828 Con_Print(tempstring);
2834 usedamount += prvm_type_size[d->type & ~DEF_SAVEGLOBAL];
2838 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);
2841 static void PRVM_Globals_f(cmd_state_t *cmd)
2845 const char *wildcard;
2851 Con_Print("no progs loaded\n");
2854 if(Cmd_Argc (cmd) < 2 || Cmd_Argc(cmd) > 3)
2856 Con_Print("prvm_globals <program name> <optional name wildcard>\n");
2860 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2863 if( Cmd_Argc(cmd) == 3)
2864 wildcard = Cmd_Argv(cmd, 2);
2868 Con_Printf("%s :", prog->name);
2870 for (i = 0;i < prog->numglobaldefs;i++)
2873 if( !matchpattern( PRVM_GetString(prog, prog->globaldefs[i].s_name), wildcard, 1) )
2878 Con_Printf("%s\n", PRVM_GetString(prog, prog->globaldefs[i].s_name));
2880 Con_Printf("%i global variables, %i culled, totalling %i bytes\n", prog->numglobals, numculled, prog->numglobals * 4);
2888 static void PRVM_Global_f(cmd_state_t *cmd)
2892 char valuebuf[MAX_INPUTLINE];
2893 if( Cmd_Argc(cmd) != 3 ) {
2894 Con_Printf( "prvm_global <program name> <global name>\n" );
2898 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2901 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2903 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2905 Con_Printf( "%s: %s\n", Cmd_Argv(cmd, 2), PRVM_ValueString( prog, (etype_t)global->type, PRVM_GLOBALFIELDVALUE(global->ofs), valuebuf, sizeof(valuebuf) ) );
2913 static void PRVM_GlobalSet_f(cmd_state_t *cmd)
2917 if( Cmd_Argc(cmd) != 4 ) {
2918 Con_Printf( "prvm_globalset <program name> <global name> <value>\n" );
2922 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2925 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2927 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2929 PRVM_ED_ParseEpair( prog, NULL, global, Cmd_Argv(cmd, 3), true );
2933 ======================
2934 Break- and Watchpoints
2935 ======================
2939 char break_statement[256];
2940 char watch_global[256];
2942 char watch_field[256];
2945 static debug_data_t debug_data[PRVM_PROG_MAX];
2947 void PRVM_Breakpoint(prvm_prog_t *prog, int stack_index, const char *text)
2950 Con_Printf("PRVM_Breakpoint: %s\n", text);
2951 PRVM_PrintState(prog, stack_index);
2952 if (prvm_breakpointdump.integer)
2953 SV_Savegame_to(prog, va(vabuf, sizeof(vabuf), "breakpoint-%s.dmp", prog->name));
2956 void PRVM_Watchpoint(prvm_prog_t *prog, int stack_index, const char *text, etype_t type, prvm_eval_t *o, prvm_eval_t *n)
2958 size_t sz = sizeof(prvm_vec_t) * ((type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
2959 if (memcmp(o, n, sz))
2962 char valuebuf_o[128];
2963 char valuebuf_n[128];
2964 PRVM_UglyValueString(prog, type, o, valuebuf_o, sizeof(valuebuf_o));
2965 PRVM_UglyValueString(prog, type, n, valuebuf_n, sizeof(valuebuf_n));
2966 dpsnprintf(buf, sizeof(buf), "%s: %s -> %s", text, valuebuf_o, valuebuf_n);
2967 PRVM_Breakpoint(prog, stack_index, buf);
2972 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog)
2974 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
2977 if (debug->break_statement[0])
2979 if (debug->break_statement[0] >= '0' && debug->break_statement[0] <= '9')
2981 prog->break_statement = atoi(debug->break_statement);
2982 prog->break_stack_index = 0;
2987 func = PRVM_ED_FindFunction (prog, debug->break_statement);
2990 Con_Printf("%s progs: no function or statement named %s to break on!\n", prog->name, debug->break_statement);
2991 prog->break_statement = -1;
2995 prog->break_statement = func->first_statement;
2996 prog->break_stack_index = 1;
2999 if (prog->break_statement >= -1)
3000 Con_Printf("%s progs: breakpoint is at statement %d\n", prog->name, prog->break_statement);
3003 prog->break_statement = -1;
3005 if (debug->watch_global[0])
3007 mdef_t *global = PRVM_ED_FindGlobal( prog, debug->watch_global );
3010 Con_Printf( "%s progs: no global named '%s' to watch!\n", prog->name, debug->watch_global );
3011 prog->watch_global_type = ev_void;
3015 size_t sz = sizeof(prvm_vec_t) * ((global->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3016 prog->watch_global = global->ofs;
3017 prog->watch_global_type = (etype_t)global->type;
3018 memcpy(&prog->watch_global_value, PRVM_GLOBALFIELDVALUE(prog->watch_global), sz);
3020 if (prog->watch_global_type != ev_void)
3021 Con_Printf("%s progs: global watchpoint is at global index %d\n", prog->name, prog->watch_global);
3024 prog->watch_global_type = ev_void;
3026 if (debug->watch_field[0])
3028 mdef_t *field = PRVM_ED_FindField( prog, debug->watch_field );
3031 Con_Printf( "%s progs: no field named '%s' to watch!\n", prog->name, debug->watch_field );
3032 prog->watch_field_type = ev_void;
3036 size_t sz = sizeof(prvm_vec_t) * ((field->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3037 prog->watch_edict = debug->watch_edict;
3038 prog->watch_field = field->ofs;
3039 prog->watch_field_type = (etype_t)field->type;
3040 if (prog->watch_edict < prog->num_edicts)
3041 memcpy(&prog->watch_edictfield_value, PRVM_EDICTFIELDVALUE(PRVM_EDICT_NUM(prog->watch_edict), prog->watch_field), sz);
3043 memset(&prog->watch_edictfield_value, 0, sz);
3045 if (prog->watch_edict != ev_void)
3046 Con_Printf("%s progs: edict field watchpoint is at edict %d field index %d\n", prog->name, prog->watch_edict, prog->watch_field);
3049 prog->watch_field_type = ev_void;
3052 static void PRVM_Breakpoint_f(cmd_state_t *cmd)
3056 if( Cmd_Argc(cmd) == 2 ) {
3057 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3060 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3061 debug->break_statement[0] = 0;
3063 PRVM_UpdateBreakpoints(prog);
3066 if( Cmd_Argc(cmd) != 3 ) {
3067 Con_Printf( "prvm_breakpoint <program name> <function name | statement>\n" );
3071 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3075 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3076 dp_strlcpy(debug->break_statement, Cmd_Argv(cmd, 2), sizeof(debug->break_statement));
3078 PRVM_UpdateBreakpoints(prog);
3081 static void PRVM_GlobalWatchpoint_f(cmd_state_t *cmd)
3085 if( Cmd_Argc(cmd) == 2 ) {
3086 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3089 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3090 debug->watch_global[0] = 0;
3092 PRVM_UpdateBreakpoints(prog);
3095 if( Cmd_Argc(cmd) != 3 ) {
3096 Con_Printf( "prvm_globalwatchpoint <program name> <global name>\n" );
3100 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3104 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3105 dp_strlcpy(debug->watch_global, Cmd_Argv(cmd, 2), sizeof(debug->watch_global));
3107 PRVM_UpdateBreakpoints(prog);
3110 static void PRVM_EdictWatchpoint_f(cmd_state_t *cmd)
3114 if( Cmd_Argc(cmd) == 2 ) {
3115 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3118 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3119 debug->watch_field[0] = 0;
3121 PRVM_UpdateBreakpoints(prog);
3124 if( Cmd_Argc(cmd) != 4 ) {
3125 Con_Printf( "prvm_edictwatchpoint <program name> <edict number> <field name>\n" );
3129 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3133 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3134 debug->watch_edict = atoi(Cmd_Argv(cmd, 2));
3135 dp_strlcpy(debug->watch_field, Cmd_Argv(cmd, 3), sizeof(debug->watch_field));
3137 PRVM_UpdateBreakpoints(prog);
3145 void PRVM_Init (void)
3149 Cmd_AddCommand(CF_SHARED, "prvm_edict", PRVM_ED_PrintEdict_f, "print all data about an entity number in the selected VM (server, client, menu)");
3150 Cmd_AddCommand(CF_SHARED, "prvm_edicts", PRVM_ED_PrintEdicts_f, "prints all data about all entities in the selected VM (server, client, menu)");
3151 Cmd_AddCommand(CF_SHARED, "prvm_edictcount", PRVM_ED_Count_f, "prints number of active entities in the selected VM (server, client, menu)");
3152 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)");
3153 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");
3154 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)");
3155 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)");
3156 Cmd_AddCommand(CF_SHARED, "prvm_globals", PRVM_Globals_f, "prints all global variables in the selected VM (server, client, menu)");
3157 Cmd_AddCommand(CF_SHARED, "prvm_global", PRVM_Global_f, "prints value of a specified global variable in the selected VM (server, client, menu)");
3158 Cmd_AddCommand(CF_SHARED, "prvm_globalset", PRVM_GlobalSet_f, "sets value of a specified global variable in the selected VM (server, client, menu)");
3159 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)");
3160 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");
3161 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");
3162 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)");
3163 Cmd_AddCommand(CF_SHARED, "cl_cmd", PRVM_GameCommand_Client_f, "calls the client QC function GameCommand with the supplied string as argument");
3164 Cmd_AddCommand(CF_SHARED, "menu_cmd", PRVM_GameCommand_Menu_f, "calls the menu QC function GameCommand with the supplied string as argument");
3165 Cmd_AddCommand(CF_SHARED, "sv_cmd", PRVM_GameCommand_Server_f, "calls the server QC function GameCommand with the supplied string as argument");
3166 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");
3167 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");
3168 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");
3170 Cvar_RegisterVariable (&prvm_language);
3171 Cvar_RegisterVariable (&prvm_traceqc);
3172 Cvar_RegisterVariable (&prvm_statementprofiling);
3173 Cvar_RegisterVariable (&prvm_timeprofiling);
3174 Cvar_RegisterVariable (&prvm_coverage);
3175 Cvar_RegisterVariable (&prvm_backtraceforwarnings);
3176 Cvar_RegisterVariable (&prvm_leaktest);
3177 Cvar_RegisterVariable (&prvm_leaktest_follow_targetname);
3178 Cvar_RegisterVariable (&prvm_leaktest_ignore_classnames);
3179 Cvar_RegisterVariable (&prvm_errordump);
3180 Cvar_RegisterVariable (&prvm_breakpointdump);
3181 Cvar_RegisterVariable (&prvm_reuseedicts_startuptime);
3182 Cvar_RegisterVariable (&prvm_reuseedicts_neverinsameframe);
3183 Cvar_RegisterVariable (&prvm_garbagecollection_enable);
3184 Cvar_RegisterVariable (&prvm_garbagecollection_notify);
3185 Cvar_RegisterVariable (&prvm_garbagecollection_scan_limit);
3186 Cvar_RegisterVariable (&prvm_garbagecollection_strings);
3187 Cvar_RegisterVariable (&prvm_stringdebug);
3189 // COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!)
3190 prvm_runawaycheck = !Sys_CheckParm("-norunaway");
3194 // LadyHavoc: report supported extensions
3195 Con_DPrintf("\nQuakeC extensions for server and client:");
3196 for (i = 0; vm_sv_extensions[i]; i++)
3197 Con_DPrintf(" %s", vm_sv_extensions[i]);
3200 Con_DPrintf("\nQuakeC extensions for menu:");
3201 for (i = 0; vm_m_extensions[i]; i++)
3202 Con_DPrintf(" %s", vm_m_extensions[i]);
3212 void PRVM_Prog_Init(prvm_prog_t *prog, cmd_state_t *cmd)
3214 PRVM_Prog_Reset(prog);
3215 prog->leaktest_active = prvm_leaktest.integer != 0;
3216 prog->console_cmd = cmd;
3219 // LadyHavoc: turned PRVM_EDICT_NUM into a #define for speed reasons
3220 unsigned int PRVM_EDICT_NUM_ERROR(prvm_prog_t *prog, unsigned int n, const char *filename, int fileline)
3222 prog->error_cmd("PRVM_EDICT_NUM: %s: bad number %i (called at %s:%i)", prog->name, n, filename, fileline);
3226 #define PRVM_KNOWNSTRINGBASE 0x40000000
3228 const char *PRVM_GetString(prvm_prog_t *prog, int num)
3233 if (prvm_stringdebug.integer)
3234 VM_Warning(prog, "PRVM_GetString: Invalid string offset (%i < 0)\n", num);
3237 else if (num < prog->stringssize)
3239 // constant string from progs.dat
3240 return prog->strings + num;
3242 else if (num <= prog->stringssize + prog->tempstringsbuf.maxsize)
3244 // tempstring returned by engine to QC (becomes invalid after returning to engine)
3245 num -= prog->stringssize;
3246 if (num < prog->tempstringsbuf.cursize)
3247 return (char *)prog->tempstringsbuf.data + num;
3250 if (prvm_stringdebug.integer)
3251 VM_Warning(prog, "PRVM_GetString: Invalid temp-string offset (%i >= %i prog->tempstringsbuf.cursize)\n", num, prog->tempstringsbuf.cursize);
3255 else if (num & PRVM_KNOWNSTRINGBASE)
3258 num = num - PRVM_KNOWNSTRINGBASE;
3259 if (num >= 0 && num < prog->numknownstrings)
3261 if (!prog->knownstrings[num])
3263 if (prvm_stringdebug.integer)
3264 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num);
3267 // refresh the garbage collection on the string - this guards
3268 // against a certain sort of repeated migration to earlier
3269 // points in the scan that could otherwise result in the string
3270 // being freed for being unused
3271 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] & ~KNOWNSTRINGFLAG_GCPRUNE) | KNOWNSTRINGFLAG_GCMARK;
3272 return prog->knownstrings[num];
3276 if (prvm_stringdebug.integer)
3277 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, prog->numknownstrings);
3283 // invalid string offset
3284 if (prvm_stringdebug.integer)
3285 VM_Warning(prog, "PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, prog->stringssize);
3290 const char *PRVM_ChangeEngineString(prvm_prog_t *prog, int i, const char *s)
3293 i = i - PRVM_KNOWNSTRINGBASE;
3294 if (i < 0 || i >= prog->numknownstrings)
3295 prog->error_cmd("PRVM_ChangeEngineString: string index %i is out of bounds", i);
3296 else if ((prog->knownstrings_flags[i] & KNOWNSTRINGFLAG_ENGINE) == 0)
3297 prog->error_cmd("PRVM_ChangeEngineString: string index %i is not an engine string", i);
3298 old = prog->knownstrings[i];
3299 prog->knownstrings[i] = s;
3303 static void PRVM_NewKnownString(prvm_prog_t *prog, int i, int flags, const char *s)
3305 if (i >= prog->numknownstrings)
3307 if (i >= prog->maxknownstrings)
3309 const char **oldstrings = prog->knownstrings;
3310 const unsigned char *oldstrings_flags = prog->knownstrings_flags;
3311 const char **oldstrings_origin = prog->knownstrings_origin;
3312 prog->maxknownstrings += 128;
3313 prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3314 prog->knownstrings_flags = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char));
3315 if (prog->leaktest_active)
3316 prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3317 if (prog->numknownstrings)
3319 memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *));
3320 memcpy((char **)prog->knownstrings_flags, oldstrings_flags, prog->numknownstrings * sizeof(unsigned char));
3321 if (prog->leaktest_active)
3322 memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *));
3325 prog->numknownstrings++;
3327 prog->firstfreeknownstring = i + 1;
3328 prog->knownstrings[i] = s;
3329 // it's in use right now, spare it until the next gc pass - that said, it is not freeable so this is probably moot
3330 prog->knownstrings_flags[i] = flags;
3331 if (prog->leaktest_active)
3332 prog->knownstrings_origin[i] = NULL;
3335 int PRVM_SetEngineString(prvm_prog_t *prog, const char *s)
3340 if (s >= prog->strings && s <= prog->strings + prog->stringssize)
3341 prog->error_cmd("PRVM_SetEngineString: s in prog->strings area");
3342 // if it's in the tempstrings area, use a reserved range
3343 // (otherwise we'd get millions of useless string offsets cluttering the database)
3344 if (s >= (char *)prog->tempstringsbuf.data && s < (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.maxsize)
3345 return prog->stringssize + (s - (char *)prog->tempstringsbuf.data);
3346 // see if it's a known string address
3347 for (i = 0;i < prog->numknownstrings;i++)
3348 if (prog->knownstrings[i] == s)
3349 return PRVM_KNOWNSTRINGBASE + i;
3350 // new unknown engine string
3351 if (developer_insane.integer)
3352 Con_DPrintf("new engine string %p = \"%s\"\n", (void *)s, s);
3353 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3354 if (!prog->knownstrings[i])
3356 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE, s);
3357 return PRVM_KNOWNSTRINGBASE + i;
3360 // temp string handling
3362 // all tempstrings go into this buffer consecutively, and it is reset
3363 // whenever PRVM_ExecuteProgram returns to the engine
3364 // (technically each PRVM_ExecuteProgram call saves the cursize value and
3365 // restores it on return, so multiple recursive calls can share the same
3367 // the buffer size is automatically grown as needed
3368 int PRVM_SetTempString(prvm_prog_t *prog, const char *s, size_t slen)
3373 if (!s || slen >= VM_TEMPSTRING_MAXSIZE)
3376 if (developer_insane.integer)
3377 Con_DPrintf("PRVM_SetTempString %s: cursize %i, new tempstring size %lu\n", prog->name, prog->tempstringsbuf.cursize, (unsigned long)size);
3378 if ((size_t)prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3380 sizebuf_t old = prog->tempstringsbuf;
3381 if (prog->tempstringsbuf.cursize + size >= 1<<28)
3382 prog->error_cmd("PRVM_SetTempString %s: ran out of tempstring memory! (refusing to grow tempstring buffer over 256MB, cursize %i, new tempstring size %lu)\n", prog->name, prog->tempstringsbuf.cursize, (unsigned long)size);
3383 prog->tempstringsbuf.maxsize = max(prog->tempstringsbuf.maxsize, 65536);
3384 while ((size_t)prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3385 prog->tempstringsbuf.maxsize *= 2;
3386 if (prog->tempstringsbuf.maxsize != old.maxsize || prog->tempstringsbuf.data == NULL)
3388 Con_DPrintf("PRVM_SetTempString %s: enlarging tempstrings buffer (%iKB -> %iKB)\n", prog->name, old.maxsize/1024, prog->tempstringsbuf.maxsize/1024);
3389 prog->tempstringsbuf.data = (unsigned char *) Mem_Alloc(prog->progs_mempool, prog->tempstringsbuf.maxsize);
3393 memcpy(prog->tempstringsbuf.data, old.data, old.cursize);
3398 t = (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.cursize;
3400 prog->tempstringsbuf.cursize += size;
3401 return PRVM_SetEngineString(prog, t);
3404 int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer)
3414 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3415 if (!prog->knownstrings[i])
3417 s = (char *)PRVM_Alloc(bufferlength);
3418 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK, s);
3419 if(prog->leaktest_active)
3420 prog->knownstrings_origin[i] = PRVM_AllocationOrigin(prog);
3422 *pointer = (char *)(prog->knownstrings[i]);
3423 return PRVM_KNOWNSTRINGBASE + i;
3426 void PRVM_FreeString(prvm_prog_t *prog, int num)
3429 prog->error_cmd("PRVM_FreeString %s: attempt to free a NULL string", prog->name);
3430 else if (num >= 0 && num < prog->stringssize)
3431 prog->error_cmd("PRVM_FreeString %s: attempt to free a constant string", prog->name);
3432 else if (num >= PRVM_KNOWNSTRINGBASE && num < PRVM_KNOWNSTRINGBASE + prog->numknownstrings)
3434 num = num - PRVM_KNOWNSTRINGBASE;
3435 if (!prog->knownstrings[num])
3436 prog->error_cmd("PRVM_FreeString %s: attempt to free a non-existent or already freed string", prog->name);
3437 if (!prog->knownstrings_flags[num])
3438 prog->error_cmd("PRVM_FreeString %s: attempt to free a string owned by the engine", prog->name);
3439 PRVM_Free((char *)prog->knownstrings[num]);
3440 if(prog->leaktest_active)
3441 if(prog->knownstrings_origin[num])
3442 PRVM_Free((char *)prog->knownstrings_origin[num]);
3443 prog->knownstrings[num] = NULL;
3444 prog->knownstrings_flags[num] = 0;
3445 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3448 prog->error_cmd("PRVM_FreeString %s: invalid string offset %i", prog->name, num);
3451 static qbool PRVM_IsStringReferenced(prvm_prog_t *prog, string_t string)
3455 for (i = 0;i < prog->numglobaldefs;i++)
3457 mdef_t *d = &prog->globaldefs[i];
3458 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3460 if(string == PRVM_GLOBALFIELDSTRING(d->ofs))
3464 for(j = 0; j < prog->num_edicts; ++j)
3466 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3469 for (i=0; i<prog->numfielddefs; ++i)
3471 mdef_t *d = &prog->fielddefs[i];
3472 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3474 if(string == PRVM_EDICTFIELDSTRING(ed, d->ofs))
3482 static qbool PRVM_IsEdictRelevant(prvm_prog_t *prog, prvm_edict_t *edict)
3486 if(PRVM_NUM_FOR_EDICT(edict) <= prog->reserved_edicts)
3487 return true; // world or clients
3488 if (edict->freetime <= prog->inittime)
3489 return true; // created during startup
3490 if (prog == SVVM_prog)
3492 if(PRVM_serveredictfloat(edict, solid)) // can block other stuff, or is a trigger?
3494 if(PRVM_serveredictfloat(edict, modelindex)) // visible ent?
3496 if(PRVM_serveredictfloat(edict, effects)) // particle effect?
3498 if(PRVM_serveredictfunction(edict, think)) // has a think function?
3499 if(PRVM_serveredictfloat(edict, nextthink) > 0) // that actually will eventually run?
3501 if(PRVM_serveredictfloat(edict, takedamage))
3503 if(*prvm_leaktest_ignore_classnames.string)
3505 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_serveredictstring(edict, classname)))))
3509 else if (prog == CLVM_prog)
3511 // TODO someone add more stuff here
3512 if(PRVM_clientedictfloat(edict, entnum)) // csqc networked
3514 if(PRVM_clientedictfloat(edict, modelindex)) // visible ent?
3516 if(PRVM_clientedictfloat(edict, effects)) // particle effect?
3518 if(PRVM_clientedictfunction(edict, think)) // has a think function?
3519 if(PRVM_clientedictfloat(edict, nextthink) > 0) // that actually will eventually run?
3521 if(*prvm_leaktest_ignore_classnames.string)
3523 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_clientedictstring(edict, classname)))))
3529 // menu prog does not have classnames
3534 static qbool PRVM_IsEdictReferenced(prvm_prog_t *prog, prvm_edict_t *edict, int mark)
3537 int edictnum = PRVM_NUM_FOR_EDICT(edict);
3538 const char *targetname = NULL;
3540 if (prog == SVVM_prog && prvm_leaktest_follow_targetname.integer)
3541 targetname = PRVM_GetString(prog, PRVM_serveredictstring(edict, targetname));
3544 if(!*targetname) // ""
3547 for(j = 0; j < prog->num_edicts; ++j)
3549 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3550 if (ed->priv.required->mark < mark)
3556 const char *target = PRVM_GetString(prog, PRVM_serveredictstring(ed, target));
3558 if(!strcmp(target, targetname))
3561 for (i=0; i<prog->numfielddefs; ++i)
3563 mdef_t *d = &prog->fielddefs[i];
3564 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3566 if(edictnum == PRVM_EDICTFIELDEDICT(ed, d->ofs))
3574 static void PRVM_MarkReferencedEdicts(prvm_prog_t *prog)
3580 // Stage 1: world, all entities that are relevant, and all entities that are referenced by globals.
3582 for(j = 0; j < prog->num_edicts; ++j)
3584 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3587 ed->priv.required->mark = PRVM_IsEdictRelevant(prog, ed) ? stage : 0;
3589 for (i = 0;i < prog->numglobaldefs;i++)
3591 mdef_t *d = &prog->globaldefs[i];
3593 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3595 j = PRVM_GLOBALFIELDEDICT(d->ofs);
3596 if (i < 0 || j >= prog->max_edicts) {
3597 Con_Printf("Invalid entity reference from global %s.\n", PRVM_GetString(prog, d->s_name));
3600 ed = PRVM_EDICT_NUM(j);;
3601 ed->priv.required->mark = stage;
3604 // Future stages: all entities that are referenced by an entity of the previous stage.
3608 for(j = 0; j < prog->num_edicts; ++j)
3610 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3613 if(ed->priv.required->mark)
3615 if(PRVM_IsEdictReferenced(prog, ed, stage))
3617 ed->priv.required->mark = stage + 1;
3624 Con_DPrintf("leak check used %d stages to find all references\n", stage);
3627 void PRVM_LeakTest(prvm_prog_t *prog)
3630 qbool leaked = false;
3632 if(!prog->leaktest_active)
3636 for (i = 0; i < prog->numknownstrings; ++i)
3638 if(prog->knownstrings[i])
3639 if(prog->knownstrings_flags[i])
3640 if(prog->knownstrings_origin[i])
3641 if(!PRVM_IsStringReferenced(prog, PRVM_KNOWNSTRINGBASE + i))
3643 Con_Printf("Unreferenced string found!\n Value: %s\n Origin: %s\n", prog->knownstrings[i], prog->knownstrings_origin[i]);
3649 PRVM_MarkReferencedEdicts(prog);
3650 for(j = 0; j < prog->num_edicts; ++j)
3652 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3655 if(!ed->priv.required->mark)
3656 if(ed->priv.required->allocation_origin)
3658 Con_Printf("Unreferenced edict found!\n Allocated at: %s\n", ed->priv.required->allocation_origin);
3659 PRVM_ED_Print(prog, ed, NULL);
3664 ed->priv.required->mark = 0; // clear marks again when done
3667 for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); ++i)
3669 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
3671 if(stringbuffer->origin)
3673 Con_Printf("Open string buffer handle found!\n Allocated at: %s\n", stringbuffer->origin);
3678 for(i = 0; i < PRVM_MAX_OPENFILES; ++i)
3680 if(prog->openfiles[i])
3681 if(prog->openfiles_origin[i])
3683 Con_Printf("Open file handle found!\n Allocated at: %s\n", prog->openfiles_origin[i]);
3688 for(i = 0; i < PRVM_MAX_OPENSEARCHES; ++i)
3690 if(prog->opensearches[i])
3691 if(prog->opensearches_origin[i])
3693 Con_Printf("Open search handle found!\n Allocated at: %s\n", prog->opensearches_origin[i]);
3699 Con_Printf("Congratulations. No leaks found.\n");
3702 void PRVM_GarbageCollection(prvm_prog_t *prog)
3704 int limit = prvm_garbagecollection_scan_limit.integer;
3705 prvm_prog_garbagecollection_state_t *gc = &prog->gc;
3706 if (!prvm_garbagecollection_enable.integer)
3709 // we like to limit how much scanning we do so it doesn't put a significant
3710 // burden on the cpu, so each of these are not complete scans, we also like
3711 // to have consistent cpu usage so we do a bit of work on each category of
3712 // leaked object every frame
3718 case PRVM_GC_GLOBALS_MARK:
3719 for (; gc->globals_mark_progress < prog->numglobaldefs && (limit--) > 0; gc->globals_mark_progress++)
3721 mdef_t *d = &prog->globaldefs[gc->globals_mark_progress];
3726 prvm_int_t s = prog->globals.ip[d->ofs];
3727 if (s & PRVM_KNOWNSTRINGBASE)
3729 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3730 if (!prog->knownstrings[num])
3733 Con_DPrintf("PRVM_GarbageCollection: Found bogus strzone reference in global %i (global name: \"%s\"), erasing reference", d->ofs, PRVM_GetString(prog, d->s_name));
3734 prog->globals.ip[d->ofs] = 0;
3737 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3745 if (gc->globals_mark_progress >= prog->numglobaldefs)
3748 case PRVM_GC_FIELDS_MARK:
3749 for (; gc->fields_mark_progress < prog->numfielddefs && limit > 0;)
3751 mdef_t *d = &prog->fielddefs[gc->fields_mark_progress];
3755 //for (gc-> entityindex = 0; entityindex < prog->num_edicts; entityindex++)
3756 for (;gc->fields_mark_progress_entity < prog->num_edicts && (limit--) > 0;gc->fields_mark_progress_entity++)
3758 int entityindex = gc->fields_mark_progress_entity;
3759 prvm_int_t s = prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs];
3760 if (s & PRVM_KNOWNSTRINGBASE)
3762 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3763 if (!prog->knownstrings[num])
3766 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));
3767 prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs] = 0;
3770 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3773 if (gc->fields_mark_progress_entity >= prog->num_edicts)
3775 gc->fields_mark_progress_entity = 0;
3776 gc->fields_mark_progress++;
3780 gc->fields_mark_progress_entity = 0;
3781 gc->fields_mark_progress++;
3785 if (gc->fields_mark_progress >= prog->numfielddefs)
3788 case PRVM_GC_KNOWNSTRINGS_SWEEP:
3789 // free any strzone'd strings that are not marked
3790 if (!prvm_garbagecollection_strings.integer)
3795 for (;gc->knownstrings_sweep_progress < prog->numknownstrings && (limit--) > 0;gc->knownstrings_sweep_progress++)
3797 int num = gc->knownstrings_sweep_progress;
3798 if (prog->knownstrings[num] && (prog->knownstrings_flags[num] & (KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE)) == 0)
3800 if (prog->knownstrings_flags[num] & KNOWNSTRINGFLAG_GCPRUNE)
3802 // string has been marked for pruning two passes in a row
3803 if (prvm_garbagecollection_notify.integer)
3804 Con_DPrintf("prvm_garbagecollection_notify: %s: freeing unreferenced string %i: \"%s\"\n", prog->name, num, prog->knownstrings[num]);
3805 Mem_Free((char *)prog->knownstrings[num]);
3806 prog->knownstrings[num] = NULL;
3807 prog->knownstrings_flags[num] = 0;
3808 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3812 // mark it for pruning next pass
3813 prog->knownstrings_flags[num] |= KNOWNSTRINGFLAG_GCPRUNE;
3817 if (gc->knownstrings_sweep_progress >= prog->numknownstrings)
3822 memset(gc, 0, sizeof(*gc));