2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 prvm_prog_t prvm_prog_list[PRVM_PROG_MAX];
28 int prvm_type_size[8] = {1,sizeof(string_t)/4,1,3,1,1,sizeof(func_t)/4,sizeof(void *)/4};
30 prvm_eval_t prvm_badvalue; // used only for error returns
32 cvar_t prvm_language = {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "prvm_language", "", "when set, loads PROGSFILE.LANGUAGENAME.po and common.LANGUAGENAME.po for string translations; when set to dump, PROGSFILE.pot is written from the strings in the progs"};
33 // LadyHavoc: prints every opcode as it executes - warning: this is significant spew
34 cvar_t prvm_traceqc = {CF_CLIENT | CF_SERVER, "prvm_traceqc", "0", "prints every QuakeC statement as it is executed (only for really thorough debugging!)"};
35 // LadyHavoc: counts usage of each QuakeC statement
36 cvar_t prvm_statementprofiling = {CF_CLIENT | CF_SERVER, "prvm_statementprofiling", "0", "counts how many times each QuakeC statement has been executed, these counts are displayed in prvm_printfunction output (if enabled)"};
37 cvar_t prvm_timeprofiling = {CF_CLIENT | CF_SERVER, "prvm_timeprofiling", "0", "counts how long each function has been executed, these counts are displayed in prvm_profile output (if enabled)"};
38 cvar_t prvm_coverage = {CF_CLIENT | CF_SERVER, "prvm_coverage", "0", "report and count coverage events (1: per-function, 2: coverage() builtin, 4: per-statement)"};
39 cvar_t prvm_backtraceforwarnings = {CF_CLIENT | CF_SERVER, "prvm_backtraceforwarnings", "0", "print a backtrace for warnings too"};
40 cvar_t prvm_leaktest = {CF_CLIENT | CF_SERVER, "prvm_leaktest", "0", "try to detect memory leaks in strings or entities"};
41 cvar_t prvm_leaktest_follow_targetname = {CF_CLIENT | CF_SERVER, "prvm_leaktest_follow_targetname", "0", "if set, target/targetname links are considered when leak testing; this should normally not be required, as entities created during startup - e.g. info_notnull - are never considered leaky"};
42 cvar_t prvm_leaktest_ignore_classnames = {CF_CLIENT | CF_SERVER, "prvm_leaktest_ignore_classnames", "", "classnames of entities to NOT leak check because they are found by find(world, classname, ...) but are actually spawned by QC code (NOT map entities)"};
43 cvar_t prvm_errordump = {CF_CLIENT | CF_SERVER, "prvm_errordump", "0", "write a savegame on crash to crash-server.dmp"};
44 cvar_t prvm_breakpointdump = {CF_CLIENT | CF_SERVER, "prvm_breakpointdump", "0", "write a savegame on breakpoint to breakpoint-server.dmp"};
45 cvar_t prvm_reuseedicts_startuptime = {CF_CLIENT | CF_SERVER, "prvm_reuseedicts_startuptime", "2", "allows immediate re-use of freed entity slots during start of new level (value in seconds)"};
46 cvar_t prvm_reuseedicts_neverinsameframe = {CF_CLIENT | CF_SERVER, "prvm_reuseedicts_neverinsameframe", "1", "never allows re-use of freed entity slots during same frame"};
47 cvar_t prvm_garbagecollection_enable = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_enable", "1", "automatically scan for and free resources that are not referenced by the code being executed in the VM"};
48 cvar_t prvm_garbagecollection_notify = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_notify", "0", "print out a notification for each resource freed by garbage collection"};
49 cvar_t prvm_garbagecollection_scan_limit = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_scan_limit", "10000", "scan this many fields or resources per frame to free up unreferenced resources"};
50 cvar_t prvm_garbagecollection_strings = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_strings", "1", "automatically call strunzone() on strings that are not referenced"};
51 cvar_t prvm_stringdebug = {CF_CLIENT | CF_SERVER, "prvm_stringdebug", "0", "Print debug and warning messages related to strings"};
53 static double prvm_reuseedicts_always_allow = 0;
54 qbool prvm_runawaycheck = true;
56 //============================================================================
64 static void PRVM_MEM_Alloc(prvm_prog_t *prog)
68 // reserve space for the null entity aka world
69 // check bound of max_edicts
70 prog->max_edicts = bound(1 + prog->reserved_edicts, prog->max_edicts, prog->limit_edicts);
71 prog->num_edicts = bound(1 + prog->reserved_edicts, prog->num_edicts, prog->max_edicts);
73 // edictprivate_size has to be min as big prvm_edict_private_t
74 prog->edictprivate_size = max(prog->edictprivate_size,(int)sizeof(prvm_edict_private_t));
77 prog->edicts = (prvm_edict_t *)Mem_Alloc(prog->progs_mempool,prog->limit_edicts * sizeof(prvm_edict_t));
79 // alloc edict private space
80 prog->edictprivate = Mem_Alloc(prog->progs_mempool, prog->max_edicts * prog->edictprivate_size);
83 prog->entityfieldsarea = prog->entityfields * prog->max_edicts;
84 prog->edictsfields.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, prog->entityfieldsarea * sizeof(prvm_vec_t));
87 for(i = 0; i < prog->max_edicts; i++)
89 prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size);
90 prog->edicts[i].fields.fp = prog->edictsfields.fp + i * prog->entityfields;
96 PRVM_MEM_IncreaseEdicts
99 void PRVM_MEM_IncreaseEdicts(prvm_prog_t *prog)
103 if(prog->max_edicts >= prog->limit_edicts)
106 prog->begin_increase_edicts(prog);
109 prog->max_edicts = min(prog->max_edicts + 256, prog->limit_edicts);
111 prog->entityfieldsarea = prog->entityfields * prog->max_edicts;
112 prog->edictsfields.fp = (prvm_vec_t*)Mem_Realloc(prog->progs_mempool, (void *)prog->edictsfields.fp, prog->entityfieldsarea * sizeof(prvm_vec_t));
113 prog->edictprivate = (void *)Mem_Realloc(prog->progs_mempool, (void *)prog->edictprivate, prog->max_edicts * prog->edictprivate_size);
115 //set e and v pointers
116 for(i = 0; i < prog->max_edicts; i++)
118 prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size);
119 prog->edicts[i].fields.fp = prog->edictsfields.fp + i * prog->entityfields;
122 prog->end_increase_edicts(prog);
125 //============================================================================
128 int PRVM_ED_FindFieldOffset(prvm_prog_t *prog, const char *field)
131 d = PRVM_ED_FindField(prog, field);
137 int PRVM_ED_FindGlobalOffset(prvm_prog_t *prog, const char *global)
140 d = PRVM_ED_FindGlobal(prog, global);
146 func_t PRVM_ED_FindFunctionOffset(prvm_prog_t *prog, const char *function)
149 f = PRVM_ED_FindFunction(prog, function);
152 return (func_t)(f - prog->functions);
160 prvm_prog_t *PRVM_ProgFromString(const char *str)
162 if (!strcmp(str, "server"))
164 if (!strcmp(str, "client"))
167 if (!strcmp(str, "menu"))
175 PRVM_FriendlyProgFromString
178 prvm_prog_t *PRVM_FriendlyProgFromString(const char *str)
180 prvm_prog_t *prog = PRVM_ProgFromString(str);
183 Con_Printf("%s: unknown program name\n", str);
188 Con_Printf("%s: program is not loaded\n", str);
198 Sets everything to NULL.
200 Nota bene: this also marks the entity as allocated if it has been previously
201 freed and sets the allocation origin.
204 void PRVM_ED_ClearEdict(prvm_prog_t *prog, prvm_edict_t *e)
206 memset(e->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
208 e->freetime = host.realtime;
209 if(e->priv.required->allocation_origin)
210 Mem_Free((char *)e->priv.required->allocation_origin);
211 e->priv.required->allocation_origin = PRVM_AllocationOrigin(prog);
213 // AK: Let the init_edict function determine if something needs to be initialized
214 prog->init_edict(prog, e);
217 const char *PRVM_AllocationOrigin(prvm_prog_t *prog)
220 if(prog->leaktest_active)
221 if(prog->depth > 0) // actually in QC code and not just parsing the entities block of a map/savegame
223 buf = (char *)PRVM_Alloc(256);
224 PRVM_ShortStackTrace(prog, buf, 256);
233 Returns if this particular edict could get allocated by PRVM_ED_Alloc
236 qbool PRVM_ED_CanAlloc(prvm_prog_t *prog, prvm_edict_t *e)
240 if(prvm_reuseedicts_always_allow == host.realtime)
242 if(host.realtime <= e->freetime + 0.1 && prvm_reuseedicts_neverinsameframe.integer)
243 return false; // never allow reuse in same frame (causes networking trouble)
244 if(e->freetime < prog->starttime + prvm_reuseedicts_startuptime.value)
246 if(host.realtime > e->freetime + 1)
248 return false; // entity slot still blocked because the entity was freed less than one second ago
255 Either finds a free edict, or allocates a new one.
256 Try to avoid reusing an entity that was recently freed, because it
257 can cause the client to think the entity morphed into something else
258 instead of being removed and recreated, which can cause interpolated
259 angles and bad trails.
262 prvm_edict_t *PRVM_ED_Alloc(prvm_prog_t *prog)
267 // the client qc dont need maxclients
268 // thus it doesnt need to use svs.maxclients
269 // AK: changed i=svs.maxclients+1
270 // AK: changed so the edict 0 wont spawn -> used as reserved/world entity
271 // although the menu/client has no world
272 for (i = prog->reserved_edicts + 1;i < prog->num_edicts;i++)
274 e = PRVM_EDICT_NUM(i);
275 if(PRVM_ED_CanAlloc(prog, e))
277 PRVM_ED_ClearEdict (prog, e);
282 if (i == prog->limit_edicts)
283 prog->error_cmd("%s: PRVM_ED_Alloc: no free edicts", prog->name);
286 if (prog->num_edicts >= prog->max_edicts)
287 PRVM_MEM_IncreaseEdicts(prog);
289 e = PRVM_EDICT_NUM(i);
291 PRVM_ED_ClearEdict(prog, e);
299 Marks the edict as free
301 FIXME: walk all entities and NULL out references to this entity
302 bones_was_here: do not want, that would break chains immediately!
303 Currently chains aren't broken by removing an entity, at least with prvm_reuseedicts_neverinsameframe 1
304 which is very handy and some QC code will depend on it.
307 void PRVM_ED_Free(prvm_prog_t *prog, prvm_edict_t *ed)
309 // dont delete the null entity (world) or reserved edicts
310 if (ed - prog->edicts <= prog->reserved_edicts)
313 prog->free_edict(prog, ed);
316 ed->freetime = host.realtime;
317 if(ed->priv.required->allocation_origin)
319 Mem_Free((char *)ed->priv.required->allocation_origin);
320 ed->priv.required->allocation_origin = NULL;
324 //===========================================================================
331 static mdef_t *PRVM_ED_GlobalAtOfs (prvm_prog_t *prog, unsigned int ofs)
336 for (i = 0;i < prog->numglobaldefs;i++)
338 def = &prog->globaldefs[i];
350 mdef_t *PRVM_ED_FieldAtOfs (prvm_prog_t *prog, unsigned int ofs)
355 for (i = 0;i < prog->numfielddefs;i++)
357 def = &prog->fielddefs[i];
369 mdef_t *PRVM_ED_FindField (prvm_prog_t *prog, const char *name)
374 for (i = 0;i < prog->numfielddefs;i++)
376 def = &prog->fielddefs[i];
377 if (!strcmp(PRVM_GetString(prog, def->s_name), name))
388 mdef_t *PRVM_ED_FindGlobal (prvm_prog_t *prog, const char *name)
393 for (i = 0;i < prog->numglobaldefs;i++)
395 def = &prog->globaldefs[i];
396 if (!strcmp(PRVM_GetString(prog, def->s_name), name))
404 PRVM_ED_FindGlobalEval
407 prvm_eval_t *PRVM_ED_FindGlobalEval(prvm_prog_t *prog, const char *name)
409 mdef_t *def = PRVM_ED_FindGlobal(prog, name);
410 return def ? (prvm_eval_t *) &prog->globals.fp[def->ofs] : NULL;
418 mfunction_t *PRVM_ED_FindFunction (prvm_prog_t *prog, const char *name)
423 for (i = 0;i < prog->numfunctions;i++)
425 func = &prog->functions[i];
426 if (!strcmp(PRVM_GetString(prog, func->s_name), name))
437 Returns a string describing *data in a type specific manner
440 static char *PRVM_ValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength)
446 type = (etype_t)((int) type & ~DEF_SAVEGLOBAL);
451 strlcpy (line, PRVM_GetString (prog, val->string), linelength);
455 if (n < 0 || n >= prog->max_edicts)
456 dpsnprintf (line, linelength, "entity %i (invalid!)", n);
458 dpsnprintf (line, linelength, "entity %i", n);
461 if ((unsigned int)val->function < (unsigned int)prog->progs_numfunctions)
463 f = prog->functions + val->function;
464 dpsnprintf (line, linelength, "%s()", PRVM_GetString(prog, f->s_name));
467 dpsnprintf (line, linelength, "function %" PRVM_PRIi "() (invalid!)", val->function);
470 def = PRVM_ED_FieldAtOfs ( prog, val->_int );
472 dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name));
474 dpsnprintf (line, linelength, "field %" PRVM_PRIi " (invalid!)", val->_int );
477 dpsnprintf (line, linelength, "void");
480 // LadyHavoc: changed from %5.1f to %10.4f
481 dpsnprintf (line, linelength, PRVM_FLOAT_LOSSLESS_FORMAT, val->_float);
484 // LadyHavoc: changed from %5.1f to %10.4f
485 dpsnprintf (line, linelength, "'" PRVM_VECTOR_LOSSLESS_FORMAT "'", val->vector[0], val->vector[1], val->vector[2]);
488 dpsnprintf (line, linelength, "pointer");
491 dpsnprintf (line, linelength, "bad type %i", (int) type);
502 Returns a string describing *data in a type specific manner
503 Easier to parse than PR_ValueString
506 char *PRVM_UglyValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength)
513 type = (etype_t)((int)type & ~DEF_SAVEGLOBAL);
518 // Parse the string a bit to turn special characters
519 // (like newline, specifically) into escape codes,
520 // this fixes saving games from various mods
521 s = PRVM_GetString (prog, val->string);
522 for (i = 0;i < (int)linelength - 2 && *s;)
552 dpsnprintf (line, linelength, "%i", i);
555 if ((unsigned int)val->function < (unsigned int)prog->progs_numfunctions)
557 f = prog->functions + val->function;
558 strlcpy (line, PRVM_GetString (prog, f->s_name), linelength);
561 dpsnprintf (line, linelength, "bad function %" PRVM_PRIi " (invalid!)", val->function);
564 def = PRVM_ED_FieldAtOfs ( prog, val->_int );
566 dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name));
568 dpsnprintf (line, linelength, "field %" PRVM_PRIi "(invalid!)", val->_int );
571 dpsnprintf (line, linelength, "void");
574 dpsnprintf (line, linelength, PRVM_FLOAT_LOSSLESS_FORMAT, val->_float);
577 dpsnprintf (line, linelength, PRVM_VECTOR_LOSSLESS_FORMAT, val->vector[0], val->vector[1], val->vector[2]);
580 dpsnprintf (line, linelength, "bad type %i", type);
591 Returns a string with a description and the contents of a global,
592 padded to 20 field width
595 char *PRVM_GlobalString (prvm_prog_t *prog, int ofs, char *line, size_t linelength)
601 char valuebuf[MAX_INPUTLINE];
603 val = (prvm_eval_t *)&prog->globals.fp[ofs];
604 def = PRVM_ED_GlobalAtOfs(prog, ofs);
606 dpsnprintf (line, linelength, "GLOBAL%i", ofs);
609 s = PRVM_ValueString (prog, (etype_t)def->type, val, valuebuf, sizeof(valuebuf));
610 dpsnprintf (line, linelength, "%s (=%s)", PRVM_GetString(prog, def->s_name), s);
614 //for ( ; i<20 ; i++)
615 // strcat (line," ");
621 char *PRVM_GlobalStringNoContents (prvm_prog_t *prog, int ofs, char *line, size_t linelength)
626 def = PRVM_ED_GlobalAtOfs(prog, ofs);
628 dpsnprintf (line, linelength, "GLOBAL%i", ofs);
630 dpsnprintf (line, linelength, "%s", PRVM_GetString(prog, def->s_name));
633 //for ( ; i<20 ; i++)
634 // strcat (line," ");
648 // LadyHavoc: optimized this to print out much more quickly (tempstring)
649 // LadyHavoc: changed to print out every 4096 characters (incase there are a lot of fields to print)
650 void PRVM_ED_Print(prvm_prog_t *prog, prvm_edict_t *ed, const char *wildcard_fieldname)
658 char tempstring[MAX_INPUTLINE], tempstring2[260]; // temporary string buffers
659 char valuebuf[MAX_INPUTLINE];
663 Con_Printf("%s: FREE\n",prog->name);
668 dpsnprintf(tempstring, sizeof(tempstring), "\n%s EDICT %i:\n", prog->name, PRVM_NUM_FOR_EDICT(ed));
669 for (i = 1;i < prog->numfielddefs;i++)
671 d = &prog->fielddefs[i];
672 name = PRVM_GetString(prog, d->s_name);
673 if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
674 continue; // skip _x, _y, _z vars
676 // Check Field Name Wildcard
677 if(wildcard_fieldname)
678 if( !matchpattern(name, wildcard_fieldname, 1) )
679 // Didn't match; skip
682 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
684 // if the value is still all 0, skip the field
685 type = d->type & ~DEF_SAVEGLOBAL;
687 for (j=0 ; j<prvm_type_size[type] ; j++)
690 if (j == prvm_type_size[type])
693 if (strlen(name) > sizeof(tempstring2)-4)
695 memcpy (tempstring2, name, sizeof(tempstring2)-4);
696 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
697 tempstring2[sizeof(tempstring2)-1] = 0;
700 strlcat(tempstring, name, sizeof(tempstring));
701 for (l = strlen(name);l < 14;l++)
702 strlcat(tempstring, " ", sizeof(tempstring));
703 strlcat(tempstring, " ", sizeof(tempstring));
705 name = PRVM_ValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf));
706 if (strlen(name) > sizeof(tempstring2)-4)
708 memcpy (tempstring2, name, sizeof(tempstring2)-4);
709 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
710 tempstring2[sizeof(tempstring2)-1] = 0;
713 strlcat(tempstring, name, sizeof(tempstring));
714 strlcat(tempstring, "\n", sizeof(tempstring));
715 if (strlen(tempstring) >= sizeof(tempstring)/2)
717 Con_Print(tempstring);
722 Con_Print(tempstring);
732 void PRVM_ED_Write (prvm_prog_t *prog, qfile_t *f, prvm_edict_t *ed)
740 char valuebuf[MAX_INPUTLINE];
750 for (i = 1;i < prog->numfielddefs;i++)
752 d = &prog->fielddefs[i];
753 name = PRVM_GetString(prog, d->s_name);
755 if(developer_entityparsing.integer)
756 Con_Printf("PRVM_ED_Write: at entity %d field %s\n", PRVM_NUM_FOR_EDICT(ed), name);
758 //if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
759 if(strlen(name) > 1 && name[strlen(name)-2] == '_')
760 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?)
762 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
764 // if the value is still all 0, skip the field
765 type = d->type & ~DEF_SAVEGLOBAL;
766 for (j=0 ; j<prvm_type_size[type] ; j++)
769 if (j == prvm_type_size[type])
772 FS_Printf(f,"\"%s\" ",name);
773 prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_Write, ent=%d, name=%s", i, name);
774 FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf)));
775 prog->statestring = NULL;
781 void PRVM_ED_PrintNum (prvm_prog_t *prog, int ent, const char *wildcard_fieldname)
783 PRVM_ED_Print(prog, PRVM_EDICT_NUM(ent), wildcard_fieldname);
788 PRVM_ED_PrintEdicts_f
790 For debugging, prints all the entities in the current server
793 void PRVM_ED_PrintEdicts_f(cmd_state_t *cmd)
797 const char *wildcard_fieldname;
799 if(Cmd_Argc(cmd) < 2 || Cmd_Argc(cmd) > 3)
801 Con_Print("prvm_edicts <program name> <optional field name wildcard>\n");
805 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
808 if( Cmd_Argc(cmd) == 3)
809 wildcard_fieldname = Cmd_Argv(cmd, 2);
811 wildcard_fieldname = NULL;
813 Con_Printf("%s: %i entities\n", prog->name, prog->num_edicts);
814 for (i=0 ; i<prog->num_edicts ; i++)
815 PRVM_ED_PrintNum (prog, i, wildcard_fieldname);
822 For debugging, prints a single edict
825 static void PRVM_ED_PrintEdict_f(cmd_state_t *cmd)
829 const char *wildcard_fieldname;
831 if(Cmd_Argc(cmd) < 3 || Cmd_Argc(cmd) > 4)
833 Con_Print("prvm_edict <program name> <edict number> <optional field name wildcard>\n");
837 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
840 i = atoi (Cmd_Argv(cmd, 2));
841 if (i >= prog->num_edicts)
843 Con_Print("Bad edict number\n");
846 if( Cmd_Argc(cmd) == 4)
847 // Optional Wildcard Provided
848 wildcard_fieldname = Cmd_Argv(cmd, 3);
851 wildcard_fieldname = NULL;
852 PRVM_ED_PrintNum (prog, i, wildcard_fieldname);
862 // 2 possibilities : 1. just displaying the active edict count
863 // 2. making a function pointer [x]
864 static void PRVM_ED_Count_f(cmd_state_t *cmd)
868 if(Cmd_Argc(cmd) != 2)
870 Con_Print("prvm_count <program name>\n");
874 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
877 prog->count_edicts(prog);
881 ==============================================================================
885 FIXME: need to tag constants, doesn't really work
886 ==============================================================================
894 void PRVM_ED_WriteGlobals (prvm_prog_t *prog, qfile_t *f)
901 char valuebuf[MAX_INPUTLINE];
904 for (i = 0;i < prog->numglobaldefs;i++)
906 def = &prog->globaldefs[i];
908 if ( !(def->type & DEF_SAVEGLOBAL) )
910 type &= ~DEF_SAVEGLOBAL;
912 if (type != ev_string && type != ev_float && type != ev_entity)
915 name = PRVM_GetString(prog, def->s_name);
917 if(developer_entityparsing.integer)
918 Con_Printf("PRVM_ED_WriteGlobals: at global %s\n", name);
920 prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_WriteGlobals, name=%s", name);
921 FS_Printf(f,"\"%s\" ", name);
922 FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)type, (prvm_eval_t *)&prog->globals.fp[def->ofs], valuebuf, sizeof(valuebuf)));
923 prog->statestring = NULL;
933 void PRVM_ED_ParseGlobals (prvm_prog_t *prog, const char *data)
935 char keyname[MAX_INPUTLINE];
941 if (!COM_ParseToken_Simple(&data, false, false, true))
942 prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace");
943 if (com_token[0] == '}')
946 if (developer_entityparsing.integer)
947 Con_Printf("Key: \"%s\"", com_token);
949 strlcpy (keyname, com_token, sizeof(keyname));
952 if (!COM_ParseToken_Simple(&data, false, true, true))
953 prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace");
955 if (developer_entityparsing.integer)
956 Con_Printf(" \"%s\"\n", com_token);
958 if (com_token[0] == '}')
959 prog->error_cmd("PRVM_ED_ParseGlobals: closing brace without data");
961 key = PRVM_ED_FindGlobal (prog, keyname);
964 Con_DPrintf("'%s' is not a global on %s\n", keyname, prog->name);
968 if (!PRVM_ED_ParseEpair(prog, NULL, key, com_token, true))
969 prog->error_cmd("PRVM_ED_ParseGlobals: parse error");
973 //============================================================================
980 Can parse either fields or globals
981 returns false if error
984 qbool PRVM_ED_ParseEpair(prvm_prog_t *prog, prvm_edict_t *ent, mdef_t *key, const char *s, qbool parsebackslash)
993 val = (prvm_eval_t *)(ent->fields.fp + key->ofs);
995 val = (prvm_eval_t *)(prog->globals.fp + key->ofs);
996 switch (key->type & ~DEF_SAVEGLOBAL)
999 l = (int)strlen(s) + 1;
1000 val->string = PRVM_AllocString(prog, l, &new_p);
1001 for (i = 0;i < l;i++)
1003 if (s[i] == '\\' && s[i+1] && parsebackslash)
1008 else if (s[i] == 'r')
1019 while (*s && ISWHITESPACE(*s))
1021 val->_float = atof(s);
1025 for (i = 0;i < 3;i++)
1027 while (*s && ISWHITESPACE(*s))
1031 val->vector[i] = atof(s);
1032 while (!ISWHITESPACE(*s))
1040 while (*s && ISWHITESPACE(*s))
1043 if (i >= prog->limit_edicts)
1044 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);
1045 while (i >= prog->max_edicts)
1046 PRVM_MEM_IncreaseEdicts(prog);
1047 // if IncreaseEdicts was called the base pointer needs to be updated
1049 val = (prvm_eval_t *)(ent->fields.fp + key->ofs);
1050 val->edict = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM((int)i));
1056 Con_DPrintf("PRVM_ED_ParseEpair: Bogus field name %s in %s\n", s, prog->name);
1059 def = PRVM_ED_FindField(prog, s + 1);
1062 Con_DPrintf("PRVM_ED_ParseEpair: Can't find field %s in %s\n", s, prog->name);
1065 val->_int = def->ofs;
1069 func = PRVM_ED_FindFunction(prog, s);
1072 Con_Printf("PRVM_ED_ParseEpair: Can't find function %s in %s\n", s, prog->name);
1075 val->function = func - prog->functions;
1079 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);
1089 Console command to send a string to QC function GameCommand of the
1093 sv_cmd adminmsg 3 "do not teamkill"
1094 cl_cmd someclientcommand
1095 menu_cmd somemenucommand
1097 All progs can support this extension; sg calls it in server QC, cg in client
1101 static void PRVM_GameCommand(cmd_state_t *cmd, const char *whichprogs, const char *whichcmd)
1104 if(Cmd_Argc(cmd) < 1)
1106 Con_Printf("%s text...\n", whichcmd);
1110 if (!(prog = PRVM_FriendlyProgFromString(whichprogs)))
1113 if(!PRVM_allfunction(GameCommand))
1115 Con_Printf("%s program do not support GameCommand!\n", whichprogs);
1119 int restorevm_tempstringsbuf_cursize;
1124 restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize;
1125 PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, s ? s : "");
1126 prog->ExecuteProgram(prog, PRVM_allfunction(GameCommand), "QC function GameCommand is missing");
1127 prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
1130 static void PRVM_GameCommand_Server_f(cmd_state_t *cmd)
1132 PRVM_GameCommand(cmd, "server", "sv_cmd");
1134 static void PRVM_GameCommand_Client_f(cmd_state_t *cmd)
1136 PRVM_GameCommand(cmd, "client", "cl_cmd");
1138 static void PRVM_GameCommand_Menu_f(cmd_state_t *cmd)
1140 PRVM_GameCommand(cmd, "menu", "menu_cmd");
1147 Console command to load a field of a specified edict
1150 static void PRVM_ED_EdictGet_f(cmd_state_t *cmd)
1157 char valuebuf[MAX_INPUTLINE];
1159 if(Cmd_Argc(cmd) != 4 && Cmd_Argc(cmd) != 5)
1161 Con_Print("prvm_edictget <program name> <edict number> <field> [<cvar>]\n");
1165 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1168 ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(cmd, 2)));
1170 if((key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, 3))) == 0)
1172 Con_Printf("Key %s not found !\n", Cmd_Argv(cmd, 3));
1176 v = (prvm_eval_t *)(ed->fields.fp + key->ofs);
1177 s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf));
1178 if(Cmd_Argc(cmd) == 5)
1180 cvar_t *cvar = Cvar_FindVar(cmd->cvars, Cmd_Argv(cmd, 4), cmd->cvars_flagsmask);
1182 if(Cvar_Readonly(cvar, "prvm_edictget"))
1185 Cvar_Get(cmd->cvars, Cmd_Argv(cmd, 4), s, cmd->cvars_flagsmask, NULL);
1188 Con_Printf("%s\n", s);
1194 static void PRVM_ED_GlobalGet_f(cmd_state_t *cmd)
1200 char valuebuf[MAX_INPUTLINE];
1202 if(Cmd_Argc(cmd) != 3 && Cmd_Argc(cmd) != 4)
1204 Con_Print("prvm_globalget <program name> <global> [<cvar>]\n");
1208 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1211 key = PRVM_ED_FindGlobal(prog, Cmd_Argv(cmd, 2));
1214 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
1218 v = (prvm_eval_t *) &prog->globals.fp[key->ofs];
1219 s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf));
1220 if(Cmd_Argc(cmd) == 4)
1222 cvar_t *cvar = Cvar_FindVar(cmd->cvars, Cmd_Argv(cmd, 3), cmd->cvars_flagsmask);
1224 if(Cvar_Readonly(cvar, "prvm_globalget"))
1226 Cvar_Get(cmd->cvars, Cmd_Argv(cmd, 3), s, cmd->cvars_flagsmask, NULL);
1229 Con_Printf("%s\n", s);
1239 Console command to set a field of a specified edict
1242 static void PRVM_ED_EdictSet_f(cmd_state_t *cmd)
1248 if(Cmd_Argc(cmd) != 5)
1250 Con_Print("prvm_edictset <program name> <edict number> <field> <value>\n");
1254 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1257 ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(cmd, 2)));
1259 if((key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, 3))) == 0)
1260 Con_Printf("Key %s not found!\n", Cmd_Argv(cmd, 3));
1262 PRVM_ED_ParseEpair(prog, ed, key, Cmd_Argv(cmd, 4), true);
1266 ====================
1269 Parses an edict out of the given string, returning the new position
1270 ed should be a properly initialized empty edict.
1271 Used for initial level load and for savegames.
1272 ====================
1274 const char *PRVM_ED_ParseEdict (prvm_prog_t *prog, const char *data, prvm_edict_t *ent)
1284 // go through all the dictionary pairs
1288 if (!COM_ParseToken_Simple(&data, false, false, true))
1289 prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace");
1290 if (developer_entityparsing.integer)
1291 Con_Printf("Key: \"%s\"", com_token);
1292 if (com_token[0] == '}')
1295 // anglehack is to allow QuakeEd to write single scalar angles
1296 // and allow them to be turned into vectors. (FIXME...)
1297 if (!strcmp(com_token, "angle"))
1299 strlcpy (com_token, "angles", sizeof(com_token));
1305 // FIXME: change light to _light to get rid of this hack
1306 if (!strcmp(com_token, "light"))
1307 strlcpy (com_token, "light_lev", sizeof(com_token)); // hack for single light def
1309 strlcpy (keyname, com_token, sizeof(keyname));
1311 // another hack to fix keynames with trailing spaces
1312 n = strlen(keyname);
1313 while (n && keyname[n-1] == ' ')
1320 if (!COM_ParseToken_Simple(&data, false, false, true))
1321 prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace");
1322 if (developer_entityparsing.integer)
1323 Con_Printf(" \"%s\"\n", com_token);
1325 if (com_token[0] == '}')
1326 prog->error_cmd("PRVM_ED_ParseEdict: closing brace without data");
1330 // ignore attempts to set key "" (this problem occurs in nehahra neh1m8.bsp)
1334 // keynames with a leading underscore are used for utility comments,
1335 // and are immediately discarded by quake
1336 if (keyname[0] == '_')
1339 key = PRVM_ED_FindField (prog, keyname);
1342 Con_DPrintf("%s: '%s' is not a field\n", prog->name, keyname);
1349 strlcpy (temp, com_token, sizeof(temp));
1350 dpsnprintf (com_token, sizeof(com_token), "0 %s 0", temp);
1353 if (!PRVM_ED_ParseEpair(prog, ent, key, com_token, strcmp(keyname, "wad") != 0))
1354 prog->error_cmd("PRVM_ED_ParseEdict: parse error");
1359 ent->freetime = host.realtime;
1365 void PRVM_ED_CallPrespawnFunction(prvm_prog_t *prog, prvm_edict_t *ent)
1367 if (PRVM_serverfunction(SV_OnEntityPreSpawnFunction))
1370 PRVM_serverglobalfloat(time) = sv.time;
1371 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1372 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPreSpawnFunction), "QC function SV_OnEntityPreSpawnFunction is missing");
1376 qbool PRVM_ED_CallSpawnFunction(prvm_prog_t *prog, prvm_edict_t *ent, const char *data, const char *start)
1378 const char *funcname;
1380 prvm_eval_t *fulldata = NULL;
1384 // immediately call spawn function, but only if there is a self global and a classname
1388 if (!PRVM_alledictstring(ent, classname))
1390 Con_Print("No classname for:\n");
1391 PRVM_ED_Print(prog, ent, NULL);
1392 PRVM_ED_Free (prog, ent);
1396 * This is required for FTE compatibility (FreeCS).
1397 * It copies the key/value pairs themselves into a
1398 * global for QC to parse on its own.
1400 else if (data && start)
1402 if((fulldata = PRVM_ED_FindGlobalEval(prog, "__fullspawndata")))
1406 fulldata->string = PRVM_AllocString(prog, data - start + 1, &spawndata);
1407 for(in = start; in < data; )
1411 *spawndata++ = '\t';
1419 // look for the spawn function
1420 funcname = PRVM_GetString(prog, PRVM_alledictstring(ent, classname));
1421 func = PRVM_ED_FindFunction (prog, va(vabuf, sizeof(vabuf), "spawnfunc_%s", funcname));
1423 if(!PRVM_allglobalfloat(require_spawnfunc_prefix))
1424 func = PRVM_ED_FindFunction (prog, funcname);
1428 // check for OnEntityNoSpawnFunction
1429 if (PRVM_serverfunction(SV_OnEntityNoSpawnFunction))
1432 PRVM_serverglobalfloat(time) = sv.time;
1433 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1434 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityNoSpawnFunction), "QC function SV_OnEntityNoSpawnFunction is missing");
1439 Con_DPrint("No spawn function for:\n");
1440 if (developer.integer > 0) // don't confuse non-developers with errors
1441 PRVM_ED_Print(prog, ent, NULL);
1443 PRVM_ED_Free (prog, ent);
1444 return false; // not included in "inhibited" count
1450 PRVM_serverglobalfloat(time) = sv.time;
1451 PRVM_allglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1452 prog->ExecuteProgram(prog, func - prog->functions, "");
1456 PRVM_ED_Free(prog, ent);
1460 void PRVM_ED_CallPostspawnFunction (prvm_prog_t *prog, prvm_edict_t *ent)
1463 if (PRVM_serverfunction(SV_OnEntityPostSpawnFunction))
1466 PRVM_serverglobalfloat(time) = sv.time;
1467 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1468 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPostSpawnFunction), "QC function SV_OnEntityPostSpawnFunction is missing");
1474 PRVM_ED_LoadFromFile
1476 The entities are directly placed in the array, rather than allocated with
1477 PRVM_ED_Alloc, because otherwise an error loading the map would have entity
1478 number references out of order.
1480 Creates a server's entity / program execution context by
1481 parsing textual entity definitions out of an ent file.
1483 Used for both fresh maps and savegame loads. A fresh map would also need
1484 to call PRVM_ED_CallSpawnFunctions () to let the objects initialize themselves.
1487 void PRVM_ED_LoadFromFile (prvm_prog_t *prog, const char *data)
1491 int parsed, inhibited, spawned, died;
1498 prvm_reuseedicts_always_allow = host.realtime;
1505 // parse the opening brace
1506 if (!COM_ParseToken_Simple(&data, false, false, true))
1508 if (com_token[0] != '{')
1509 prog->error_cmd("PRVM_ED_LoadFromFile: %s: found %s when expecting {", prog->name, com_token);
1511 // CHANGED: this is not conform to PR_LoadFromFile
1512 if(prog->loadintoworld)
1514 prog->loadintoworld = false;
1515 ent = PRVM_EDICT_NUM(0);
1518 ent = PRVM_ED_Alloc(prog);
1521 if (ent != prog->edicts) // hack
1522 memset (ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
1524 data = PRVM_ED_ParseEdict (prog, data, ent);
1527 // remove the entity ?
1528 if(!prog->load_edict(prog, ent))
1530 PRVM_ED_Free(prog, ent);
1535 PRVM_ED_CallPrespawnFunction(prog, ent);
1545 if(!PRVM_ED_CallSpawnFunction(prog, ent, data, start))
1548 PRVM_ED_CallPostspawnFunction(prog, ent);
1555 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);
1557 prvm_reuseedicts_always_allow = 0;
1560 static void PRVM_FindOffsets(prvm_prog_t *prog)
1562 // field and global searches use -1 for NULL
1563 memset(&prog->fieldoffsets, -1, sizeof(prog->fieldoffsets));
1564 memset(&prog->globaloffsets, -1, sizeof(prog->globaloffsets));
1565 // function searches use 0 for NULL
1566 memset(&prog->funcoffsets, 0, sizeof(prog->funcoffsets));
1567 #define PRVM_DECLARE_serverglobalfloat(x)
1568 #define PRVM_DECLARE_serverglobalvector(x)
1569 #define PRVM_DECLARE_serverglobalstring(x)
1570 #define PRVM_DECLARE_serverglobaledict(x)
1571 #define PRVM_DECLARE_serverglobalfunction(x)
1572 #define PRVM_DECLARE_clientglobalfloat(x)
1573 #define PRVM_DECLARE_clientglobalvector(x)
1574 #define PRVM_DECLARE_clientglobalstring(x)
1575 #define PRVM_DECLARE_clientglobaledict(x)
1576 #define PRVM_DECLARE_clientglobalfunction(x)
1577 #define PRVM_DECLARE_menuglobalfloat(x)
1578 #define PRVM_DECLARE_menuglobalvector(x)
1579 #define PRVM_DECLARE_menuglobalstring(x)
1580 #define PRVM_DECLARE_menuglobaledict(x)
1581 #define PRVM_DECLARE_menuglobalfunction(x)
1582 #define PRVM_DECLARE_serverfieldfloat(x)
1583 #define PRVM_DECLARE_serverfieldvector(x)
1584 #define PRVM_DECLARE_serverfieldstring(x)
1585 #define PRVM_DECLARE_serverfieldedict(x)
1586 #define PRVM_DECLARE_serverfieldfunction(x)
1587 #define PRVM_DECLARE_clientfieldfloat(x)
1588 #define PRVM_DECLARE_clientfieldvector(x)
1589 #define PRVM_DECLARE_clientfieldstring(x)
1590 #define PRVM_DECLARE_clientfieldedict(x)
1591 #define PRVM_DECLARE_clientfieldfunction(x)
1592 #define PRVM_DECLARE_menufieldfloat(x)
1593 #define PRVM_DECLARE_menufieldvector(x)
1594 #define PRVM_DECLARE_menufieldstring(x)
1595 #define PRVM_DECLARE_menufieldedict(x)
1596 #define PRVM_DECLARE_menufieldfunction(x)
1597 #define PRVM_DECLARE_serverfunction(x)
1598 #define PRVM_DECLARE_clientfunction(x)
1599 #define PRVM_DECLARE_menufunction(x)
1600 #define PRVM_DECLARE_field(x) prog->fieldoffsets.x = PRVM_ED_FindFieldOffset(prog, #x);
1601 #define PRVM_DECLARE_global(x) prog->globaloffsets.x = PRVM_ED_FindGlobalOffset(prog, #x);
1602 #define PRVM_DECLARE_function(x) prog->funcoffsets.x = PRVM_ED_FindFunctionOffset(prog, #x);
1603 #include "prvm_offsets.h"
1604 #undef PRVM_DECLARE_serverglobalfloat
1605 #undef PRVM_DECLARE_serverglobalvector
1606 #undef PRVM_DECLARE_serverglobalstring
1607 #undef PRVM_DECLARE_serverglobaledict
1608 #undef PRVM_DECLARE_serverglobalfunction
1609 #undef PRVM_DECLARE_clientglobalfloat
1610 #undef PRVM_DECLARE_clientglobalvector
1611 #undef PRVM_DECLARE_clientglobalstring
1612 #undef PRVM_DECLARE_clientglobaledict
1613 #undef PRVM_DECLARE_clientglobalfunction
1614 #undef PRVM_DECLARE_menuglobalfloat
1615 #undef PRVM_DECLARE_menuglobalvector
1616 #undef PRVM_DECLARE_menuglobalstring
1617 #undef PRVM_DECLARE_menuglobaledict
1618 #undef PRVM_DECLARE_menuglobalfunction
1619 #undef PRVM_DECLARE_serverfieldfloat
1620 #undef PRVM_DECLARE_serverfieldvector
1621 #undef PRVM_DECLARE_serverfieldstring
1622 #undef PRVM_DECLARE_serverfieldedict
1623 #undef PRVM_DECLARE_serverfieldfunction
1624 #undef PRVM_DECLARE_clientfieldfloat
1625 #undef PRVM_DECLARE_clientfieldvector
1626 #undef PRVM_DECLARE_clientfieldstring
1627 #undef PRVM_DECLARE_clientfieldedict
1628 #undef PRVM_DECLARE_clientfieldfunction
1629 #undef PRVM_DECLARE_menufieldfloat
1630 #undef PRVM_DECLARE_menufieldvector
1631 #undef PRVM_DECLARE_menufieldstring
1632 #undef PRVM_DECLARE_menufieldedict
1633 #undef PRVM_DECLARE_menufieldfunction
1634 #undef PRVM_DECLARE_serverfunction
1635 #undef PRVM_DECLARE_clientfunction
1636 #undef PRVM_DECLARE_menufunction
1637 #undef PRVM_DECLARE_field
1638 #undef PRVM_DECLARE_global
1639 #undef PRVM_DECLARE_function
1644 typedef struct dpfield_s
1651 #define DPFIELDS (sizeof(dpfields) / sizeof(dpfield_t))
1653 dpfield_t dpfields[] =
1664 #define PO_HASHSIZE 16384
1665 typedef struct po_string_s
1668 struct po_string_s *nextonhashchain;
1673 po_string_t *hashtable[PO_HASHSIZE];
1676 static void PRVM_PO_UnparseString(char *out, const char *in, size_t outsize)
1685 case '\a': if(outsize >= 2) { *out++ = '\\'; *out++ = 'a'; outsize -= 2; } break;
1686 case '\b': if(outsize >= 2) { *out++ = '\\'; *out++ = 'b'; outsize -= 2; } break;
1687 case '\t': if(outsize >= 2) { *out++ = '\\'; *out++ = 't'; outsize -= 2; } break;
1688 case '\r': if(outsize >= 2) { *out++ = '\\'; *out++ = 'r'; outsize -= 2; } break;
1689 case '\n': if(outsize >= 2) { *out++ = '\\'; *out++ = 'n'; outsize -= 2; } break;
1690 case '\\': if(outsize >= 2) { *out++ = '\\'; *out++ = '\\'; outsize -= 2; } break;
1691 case '"': if(outsize >= 2) { *out++ = '\\'; *out++ = '"'; outsize -= 2; } break;
1693 if(*in >= 0 && *in <= 0x1F)
1698 *out++ = '0' + ((*in & 0700) >> 6);
1699 *out++ = '0' + ((*in & 0070) >> 3);
1700 *out++ = '0' + (*in & 0007) ;
1717 static void PRVM_PO_ParseString(char *out, const char *in, size_t outsize)
1730 case 'a': if(outsize > 0) { *out++ = '\a'; --outsize; } break;
1731 case 'b': if(outsize > 0) { *out++ = '\b'; --outsize; } break;
1732 case 't': if(outsize > 0) { *out++ = '\t'; --outsize; } break;
1733 case 'r': if(outsize > 0) { *out++ = '\r'; --outsize; } break;
1734 case 'n': if(outsize > 0) { *out++ = '\n'; --outsize; } break;
1735 case '\\': if(outsize > 0) { *out++ = '\\'; --outsize; } break;
1736 case '"': if(outsize > 0) { *out++ = '"'; --outsize; } break;
1737 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
1741 if(*in >= '0' && *in <= '7')
1744 *out = (*out << 3) | (*in - '0');
1747 if(*in >= '0' && *in <= '7')
1750 *out = (*out << 3) | (*in - '0');
1761 if(outsize > 0) { *out++ = *in; --outsize; }
1776 static po_t *PRVM_PO_Load(const char *filename, const char *filename2, mempool_t *pool)
1781 char inbuf[MAX_INPUTLINE];
1782 char decodedbuf[MAX_INPUTLINE];
1785 po_string_t thisstr;
1788 for (i = 0; i < 2; ++i)
1790 const char *buf = (const char *)
1791 FS_LoadFile((i > 0 ? filename : filename2), pool, true, NULL);
1792 // first read filename2, then read filename
1793 // so that progs.dat.de.po wins over common.de.po
1794 // and within file, last item wins
1801 po = (po_t *)Mem_Alloc(pool, sizeof(*po));
1802 memset(po, 0, sizeof(*po));
1805 memset(&thisstr, 0, sizeof(thisstr)); // hush compiler warning
1813 p = strchr(p, '\n');
1819 if(*p == '\r' || *p == '\n')
1824 if(!strncmp(p, "msgid \"", 7))
1829 else if(!strncmp(p, "msgstr \"", 8))
1836 p = strchr(p, '\n');
1846 q = strchr(p, '\n');
1853 if((size_t)(q - p) >= (size_t) sizeof(inbuf))
1855 strlcpy(inbuf, p, q - p); // not - 1, because this adds a NUL
1856 PRVM_PO_ParseString(decodedbuf + decodedpos, inbuf, sizeof(decodedbuf) - decodedpos);
1857 decodedpos += strlen(decodedbuf + decodedpos);
1867 Mem_Free(thisstr.key);
1868 thisstr.key = (char *)Mem_Alloc(pool, decodedpos + 1);
1869 memcpy(thisstr.key, decodedbuf, decodedpos + 1);
1871 else if(decodedpos > 0 && thisstr.key) // skip empty translation results
1873 thisstr.value = (char *)Mem_Alloc(pool, decodedpos + 1);
1874 memcpy(thisstr.value, decodedbuf, decodedpos + 1);
1875 hashindex = CRC_Block((const unsigned char *) thisstr.key, strlen(thisstr.key)) % PO_HASHSIZE;
1876 thisstr.nextonhashchain = po->hashtable[hashindex];
1877 po->hashtable[hashindex] = (po_string_t *)Mem_Alloc(pool, sizeof(thisstr));
1878 memcpy(po->hashtable[hashindex], &thisstr, sizeof(thisstr));
1879 memset(&thisstr, 0, sizeof(thisstr));
1883 Mem_Free((char *) buf);
1888 static const char *PRVM_PO_Lookup(po_t *po, const char *str)
1890 int hashindex = CRC_Block((const unsigned char *) str, strlen(str)) % PO_HASHSIZE;
1891 po_string_t *p = po->hashtable[hashindex];
1894 if(!strcmp(str, p->key))
1896 p = p->nextonhashchain;
1900 static void PRVM_PO_Destroy(po_t *po)
1903 for(i = 0; i < PO_HASHSIZE; ++i)
1905 po_string_t *p = po->hashtable[i];
1909 p = p->nextonhashchain;
1918 void PRVM_LeakTest(prvm_prog_t *prog);
1919 void PRVM_Prog_Reset(prvm_prog_t *prog)
1923 if(prog->tempstringsbuf.cursize)
1924 Mem_Free(prog->tempstringsbuf.data);
1925 prog->tempstringsbuf.cursize = 0;
1926 PRVM_LeakTest(prog);
1927 prog->reset_cmd(prog);
1928 Mem_FreePool(&prog->progs_mempool);
1930 PRVM_PO_Destroy((po_t *) prog->po);
1932 memset(prog,0,sizeof(prvm_prog_t));
1933 prog->break_statement = -1;
1934 prog->watch_global_type = ev_void;
1935 prog->watch_field_type = ev_void;
1943 static void PRVM_LoadLNO( prvm_prog_t *prog, const char *progname ) {
1944 fs_offset_t filesize;
1946 unsigned int *header;
1949 FS_StripExtension( progname, filename, sizeof( filename ) );
1950 strlcat( filename, ".lno", sizeof( filename ) );
1952 lno = FS_LoadFile( filename, tempmempool, false, &filesize );
1958 <Spike> SafeWrite (h, &lnotype, sizeof(int));
1959 <Spike> SafeWrite (h, &version, sizeof(int));
1960 <Spike> SafeWrite (h, &numglobaldefs, sizeof(int));
1961 <Spike> SafeWrite (h, &numpr_globals, sizeof(int));
1962 <Spike> SafeWrite (h, &numfielddefs, sizeof(int));
1963 <Spike> SafeWrite (h, &numstatements, sizeof(int));
1964 <Spike> SafeWrite (h, statement_linenums, numstatements*sizeof(int));
1966 if ((unsigned int)filesize < (6 + prog->progs_numstatements) * sizeof(int))
1972 header = (unsigned int *) lno;
1973 if( header[ 0 ] == *(unsigned int *) "LNOF" &&
1974 LittleLong( header[ 1 ] ) == 1 &&
1975 (unsigned int)LittleLong( header[ 2 ] ) == (unsigned int)prog->progs_numglobaldefs &&
1976 (unsigned int)LittleLong( header[ 3 ] ) == (unsigned int)prog->progs_numglobals &&
1977 (unsigned int)LittleLong( header[ 4 ] ) == (unsigned int)prog->progs_numfielddefs &&
1978 (unsigned int)LittleLong( header[ 5 ] ) == (unsigned int)prog->progs_numstatements )
1980 prog->statement_linenums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1981 memcpy( prog->statement_linenums, header + 6, prog->progs_numstatements * sizeof( int ) );
1983 /* gmqcc suports columnums */
1984 if ((unsigned int)filesize > ((6 + 2 * prog->progs_numstatements) * sizeof( int )))
1986 prog->statement_columnnums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1987 memcpy( prog->statement_columnnums, header + 6 + prog->progs_numstatements, prog->progs_numstatements * sizeof( int ) );
1998 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog);
1999 void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * data, fs_offset_t size, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global)
2002 dprograms_t *dprograms;
2004 dstatement16_t *instatements16;
2005 dstatement32_t *instatements32;
2006 ddef16_t *infielddefs16;
2007 ddef32_t *infielddefs32;
2008 ddef16_t *inglobaldefs16;
2009 ddef32_t *inglobaldefs32;
2012 dfunction_t *infunctions;
2014 fs_offset_t filesize;
2015 int requiredglobalspace;
2033 prog->error_cmd("PRVM_LoadProgs: there is already a %s program loaded!", prog->name );
2035 Host_LockSession(); // all progs can use the session cvar
2036 Crypto_LoadKeys(); // all progs might use the keys at init time
2040 dprograms = (dprograms_t *) data;
2044 dprograms = (dprograms_t *)FS_LoadFile (filename, prog->progs_mempool, false, &filesize);
2045 if (dprograms == NULL || filesize < (fs_offset_t)sizeof(dprograms_t))
2046 prog->error_cmd("PRVM_LoadProgs: couldn't load %s for %s", filename, prog->name);
2047 // TODO bounds check header fields (e.g. numstatements), they must never go behind end of file
2049 prog->profiletime = Sys_DirtyTime();
2050 prog->starttime = host.realtime;
2052 requiredglobalspace = 0;
2053 for (i = 0;i < numrequiredglobals;i++)
2054 requiredglobalspace += required_global[i].type == ev_vector ? 3 : 1;
2056 prog->filecrc = CRC_Block((unsigned char *)dprograms, filesize);
2058 // byte swap the header
2059 prog->progs_version = LittleLong(dprograms->version);
2060 prog->progs_crc = LittleLong(dprograms->crc);
2061 if (prog->progs_version == 7)
2063 dprograms_v7_t *v7 = (dprograms_v7_t*)dprograms;
2064 structtype = LittleLong(v7->secondaryversion);
2065 if (structtype == PROG_SECONDARYVERSION16 ||
2066 structtype == PROG_SECONDARYVERSION32) // barely supported
2067 Con_Printf(CON_WARN "WARNING: %s: %s targets FTEQW, for which support is incomplete. Proceed at your own risk.\n", prog->name, filename);
2069 prog->error_cmd("%s: %s targets unknown engine", prog->name, filename);
2071 if (v7->numbodylessfuncs != 0 || v7->numtypes != 0 || v7->blockscompressed != 0)
2072 prog->error_cmd("%s: %s uses unsupported features.", prog->name, filename);
2074 else if (prog->progs_version != PROG_VERSION)
2075 prog->error_cmd("%s: %s has wrong version number (%i should be %i)", prog->name, filename, prog->progs_version, PROG_VERSION);
2076 instatements16 = (dstatement16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_statements));
2077 instatements32 = (dstatement32_t *)instatements16;
2078 prog->progs_numstatements = LittleLong(dprograms->numstatements);
2079 inglobaldefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globaldefs));
2080 inglobaldefs32 = (ddef32_t *)inglobaldefs16;
2081 prog->progs_numglobaldefs = LittleLong(dprograms->numglobaldefs);
2082 infielddefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_fielddefs));
2083 infielddefs32 = (ddef32_t *)infielddefs16;
2084 prog->progs_numfielddefs = LittleLong(dprograms->numfielddefs);
2085 infunctions = (dfunction_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_functions));
2086 prog->progs_numfunctions = LittleLong(dprograms->numfunctions);
2087 instrings = (char *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_strings));
2088 prog->progs_numstrings = LittleLong(dprograms->numstrings);
2089 inglobals = (int *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globals));
2090 prog->progs_numglobals = LittleLong(dprograms->numglobals);
2091 prog->progs_entityfields = LittleLong(dprograms->entityfields);
2093 prog->numstatements = prog->progs_numstatements;
2094 prog->numglobaldefs = prog->progs_numglobaldefs;
2095 prog->numfielddefs = prog->progs_numfielddefs;
2096 prog->numfunctions = prog->progs_numfunctions;
2097 prog->numstrings = prog->progs_numstrings;
2098 prog->numglobals = prog->progs_numglobals;
2099 prog->entityfields = prog->progs_entityfields;
2101 if (LittleLong(dprograms->ofs_strings) + prog->progs_numstrings > (int)filesize)
2102 prog->error_cmd("%s: %s strings go past end of file", prog->name, filename);
2103 prog->strings = (char *)Mem_Alloc(prog->progs_mempool, prog->progs_numstrings);
2104 memcpy(prog->strings, instrings, prog->progs_numstrings);
2105 prog->stringssize = prog->progs_numstrings;
2107 prog->numknownstrings = 0;
2108 prog->maxknownstrings = 0;
2109 prog->knownstrings = NULL;
2110 prog->knownstrings_flags = NULL;
2112 Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64);
2114 // we need to expand the globaldefs and fielddefs to include engine defs
2115 prog->globaldefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobaldefs + numrequiredglobals) * sizeof(mdef_t));
2116 prog->globals.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobals + requiredglobalspace + 2) * sizeof(prvm_vec_t));
2117 // + 2 is because of an otherwise occurring overrun in RETURN instruction
2118 // when trying to return the last or second-last global
2119 // (RETURN always returns a vector, there is no RETURN_F instruction)
2120 prog->fielddefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numfielddefs + numrequiredfields) * sizeof(mdef_t));
2121 // we need to convert the statements to our memory format
2122 prog->statements = (mstatement_t *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(mstatement_t));
2123 // allocate space for profiling statement usage
2124 prog->statement_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2125 prog->explicit_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2126 // functions need to be converted to the memory format
2127 prog->functions = (mfunction_t *)Mem_Alloc(prog->progs_mempool, sizeof(mfunction_t) * prog->progs_numfunctions);
2129 for (i = 0;i < prog->progs_numfunctions;i++)
2131 prog->functions[i].first_statement = LittleLong(infunctions[i].first_statement);
2132 prog->functions[i].parm_start = LittleLong(infunctions[i].parm_start);
2133 prog->functions[i].s_name = LittleLong(infunctions[i].s_name);
2134 prog->functions[i].s_file = LittleLong(infunctions[i].s_file);
2135 prog->functions[i].numparms = LittleLong(infunctions[i].numparms);
2136 prog->functions[i].locals = LittleLong(infunctions[i].locals);
2137 memcpy(prog->functions[i].parm_size, infunctions[i].parm_size, sizeof(infunctions[i].parm_size));
2138 if(prog->functions[i].first_statement >= prog->numstatements)
2139 prog->error_cmd("PRVM_LoadProgs: out of bounds function statement (function %d) in %s", i, prog->name);
2140 // TODO bounds check parm_start, s_name, s_file, numparms, locals, parm_size
2143 // copy the globaldefs to the new globaldefs list
2146 case PROG_SECONDARYVERSION32:
2147 for (i=0 ; i<prog->numglobaldefs ; i++)
2149 prog->globaldefs[i].type = LittleLong(inglobaldefs32[i].type);
2150 prog->globaldefs[i].ofs = LittleLong(inglobaldefs32[i].ofs);
2151 prog->globaldefs[i].s_name = LittleLong(inglobaldefs32[i].s_name);
2152 // TODO bounds check ofs, s_name
2156 for (i=0 ; i<prog->numglobaldefs ; i++)
2158 prog->globaldefs[i].type = (unsigned short)LittleShort(inglobaldefs16[i].type);
2159 prog->globaldefs[i].ofs = (unsigned short)LittleShort(inglobaldefs16[i].ofs);
2160 prog->globaldefs[i].s_name = LittleLong(inglobaldefs16[i].s_name);
2161 // TODO bounds check ofs, s_name
2166 // append the required globals
2167 for (i = 0;i < numrequiredglobals;i++)
2169 prog->globaldefs[prog->numglobaldefs].type = required_global[i].type;
2170 prog->globaldefs[prog->numglobaldefs].ofs = prog->numglobals;
2171 prog->globaldefs[prog->numglobaldefs].s_name = PRVM_SetEngineString(prog, required_global[i].name);
2172 if (prog->globaldefs[prog->numglobaldefs].type == ev_vector)
2173 prog->numglobals += 3;
2176 prog->numglobaldefs++;
2179 // copy the progs fields to the new fields list
2182 case PROG_SECONDARYVERSION32:
2183 for (i = 0;i < prog->numfielddefs;i++)
2185 prog->fielddefs[i].type = LittleLong(infielddefs32[i].type);
2186 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2187 prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name);
2188 prog->fielddefs[i].ofs = LittleLong(infielddefs32[i].ofs);
2189 prog->fielddefs[i].s_name = LittleLong(infielddefs32[i].s_name);
2190 // TODO bounds check ofs, s_name
2194 for (i = 0;i < prog->numfielddefs;i++)
2196 prog->fielddefs[i].type = (unsigned short)LittleShort(infielddefs16[i].type);
2197 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2198 prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name);
2199 prog->fielddefs[i].ofs = (unsigned short)LittleShort(infielddefs16[i].ofs);
2200 prog->fielddefs[i].s_name = LittleLong(infielddefs16[i].s_name);
2201 // TODO bounds check ofs, s_name
2206 // append the required fields
2207 for (i = 0;i < numrequiredfields;i++)
2209 prog->fielddefs[prog->numfielddefs].type = required_field[i].type;
2210 prog->fielddefs[prog->numfielddefs].ofs = prog->entityfields;
2211 prog->fielddefs[prog->numfielddefs].s_name = PRVM_SetEngineString(prog, required_field[i].name);
2212 if (prog->fielddefs[prog->numfielddefs].type == ev_vector)
2213 prog->entityfields += 3;
2215 prog->entityfields++;
2216 prog->numfielddefs++;
2219 // LadyHavoc: TODO: reorder globals to match engine struct
2220 // LadyHavoc: TODO: reorder fields to match engine struct
2221 #define remapglobal(index) (index)
2222 #define remapfield(index) (index)
2225 // FIXME: LadyHavoc: this uses a crude way to identify integer constants, rather than checking for matching globaldefs and checking their type
2226 for (i = 0;i < prog->progs_numglobals;i++)
2228 u.i = LittleLong(inglobals[i]);
2229 // most globals are 0, we only need to deal with the ones that are not
2232 d = u.i & 0xFF800000;
2233 if ((d == 0xFF800000) || (d == 0))
2235 // Looks like an integer (expand to int64)
2236 prog->globals.ip[remapglobal(i)] = u.i;
2240 // Looks like a float (expand to double)
2241 prog->globals.fp[remapglobal(i)] = u.f;
2246 // copy, remap globals in statements, bounds check
2247 for (i = 0;i < prog->progs_numstatements;i++)
2251 case PROG_SECONDARYVERSION32:
2252 op = (opcode_t)LittleLong(instatements32[i].op);
2253 a = (unsigned int)LittleLong(instatements32[i].a);
2254 b = (unsigned int)LittleLong(instatements32[i].b);
2255 c = (unsigned int)LittleLong(instatements32[i].c);
2258 op = (opcode_t)LittleShort(instatements16[i].op);
2259 a = (unsigned short)LittleShort(instatements16[i].a);
2260 b = (unsigned short)LittleShort(instatements16[i].b);
2261 c = (unsigned short)LittleShort(instatements16[i].c);
2269 if (a >= prog->progs_numglobals || b + i < 0 || b + i >= prog->progs_numstatements)
2270 prog->error_cmd("PRVM_LoadProgs: out of bounds IF/IFNOT (statement %d) in %s", i, prog->name);
2271 prog->statements[i].op = op;
2272 prog->statements[i].operand[0] = remapglobal(a);
2273 prog->statements[i].operand[1] = -1;
2274 prog->statements[i].operand[2] = -1;
2275 prog->statements[i].jumpabsolute = i + b;
2279 if (a + i < 0 || a + i >= prog->progs_numstatements)
2280 prog->error_cmd("PRVM_LoadProgs: out of bounds GOTO (statement %d) in %s", i, prog->name);
2281 prog->statements[i].op = op;
2282 prog->statements[i].operand[0] = -1;
2283 prog->statements[i].operand[1] = -1;
2284 prog->statements[i].operand[2] = -1;
2285 prog->statements[i].jumpabsolute = i + a;
2288 Con_DPrintf("PRVM_LoadProgs: unknown opcode %d at statement %d in %s\n", (int)op, i, prog->name);
2290 //make sure its something well defined.
2291 prog->statements[i].op = OP_BOUNDCHECK;
2292 prog->statements[i].operand[0] = 0;
2293 prog->statements[i].operand[1] =
2294 prog->statements[i].operand[2] = op;
2295 prog->statements[i].jumpabsolute = -1;
2350 case OP_GSTOREP_ENT:
2351 case OP_GSTOREP_FLD:
2353 case OP_GSTOREP_FNC:
2355 // case OP_GADDRESS:
2365 // global global global
2400 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals || c >= prog->progs_numglobals)
2401 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d)", i);
2402 prog->statements[i].op = op;
2403 prog->statements[i].operand[0] = remapglobal(a);
2404 prog->statements[i].operand[1] = remapglobal(b);
2405 prog->statements[i].operand[2] = remapglobal(c);
2406 prog->statements[i].jumpabsolute = -1;
2408 // global none global
2414 if (a >= prog->progs_numglobals || c >= prog->progs_numglobals)
2415 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2416 prog->statements[i].op = op;
2417 prog->statements[i].operand[0] = remapglobal(a);
2418 prog->statements[i].operand[1] = -1;
2419 prog->statements[i].operand[2] = remapglobal(c);
2420 prog->statements[i].jumpabsolute = -1;
2428 if (c) //Spike -- DP is alergic to pointers in QC. Try to avoid too many nasty surprises.
2429 Con_DPrintf("PRVM_LoadProgs: storep-with-offset is not permitted in %s\n", prog->name);
2439 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals)
2440 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2441 prog->statements[i].op = op;
2442 prog->statements[i].operand[0] = remapglobal(a);
2443 prog->statements[i].operand[1] = remapglobal(b);
2444 prog->statements[i].operand[2] = -1;
2445 prog->statements[i].jumpabsolute = -1;
2449 if ( a < prog->progs_numglobals)
2450 if ( prog->globals.ip[remapglobal(a)] >= 0 )
2451 if ( prog->globals.ip[remapglobal(a)] < prog->progs_numfunctions )
2452 if ( prog->functions[prog->globals.ip[remapglobal(a)]].first_statement == -642 )
2453 ++prog->numexplicitcoveragestatements;
2464 if ( a >= prog->progs_numglobals)
2465 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2466 if (b || c) //Spike -- added this check just as a diagnostic...
2467 Con_DPrintf("PRVM_LoadProgs: unexpected offset on call opcode in %s. Hexen2 format is not supported\n", prog->name);
2468 prog->statements[i].op = op;
2469 prog->statements[i].operand[0] = remapglobal(a);
2470 prog->statements[i].operand[1] = -1;
2471 prog->statements[i].operand[2] = -1;
2472 prog->statements[i].jumpabsolute = -1;
2476 if(prog->numstatements < 1)
2478 prog->error_cmd("PRVM_LoadProgs: empty program in %s", prog->name);
2480 else switch(prog->statements[prog->numstatements - 1].op)
2487 prog->error_cmd("PRVM_LoadProgs: program may fall off the edge (does not end with RETURN, GOTO or DONE) in %s", prog->name);
2491 // we're done with the file now
2493 Mem_Free(dprograms);
2496 // check required functions
2497 for(i=0 ; i < numrequiredfunc ; i++)
2498 if(PRVM_ED_FindFunction(prog, required_func[i]) == 0)
2499 prog->error_cmd("%s: %s not found in %s",prog->name, required_func[i], filename);
2501 PRVM_LoadLNO(prog, filename);
2503 PRVM_Init_Exec(prog);
2505 if(*prvm_language.string)
2506 // in CSQC we really shouldn't be able to change how stuff works... sorry for now
2507 // later idea: include a list of authorized .po file checksums with the csprogs
2509 qbool deftrans = prog == CLVM_prog;
2510 const char *realfilename = (prog != CLVM_prog ? filename : csqc_progname.string);
2511 if(deftrans) // once we have dotranslate_ strings, ALWAYS use the opt-in method!
2513 for (i=0 ; i<prog->numglobaldefs ; i++)
2516 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2517 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2518 if(name && !strncmp(name, "dotranslate_", 12))
2525 if(!strcmp(prvm_language.string, "dump"))
2527 qfile_t *f = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.pot", realfilename), "w", false);
2528 Con_Printf("Dumping to %s.pot\n", realfilename);
2531 for (i=0 ; i<prog->numglobaldefs ; i++)
2534 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2535 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2536 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2538 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2539 const char *value = PRVM_GetString(prog, val->string);
2542 char buf[MAX_INPUTLINE];
2543 PRVM_PO_UnparseString(buf, value, sizeof(buf));
2544 FS_Printf(f, "msgid \"%s\"\nmsgstr \"\"\n\n", buf);
2553 po_t *po = PRVM_PO_Load(
2554 va(vabuf, sizeof(vabuf), "%s.%s.po", realfilename, prvm_language.string),
2555 va(vabuf2, sizeof(vabuf2), "common.%s.po", prvm_language.string),
2556 prog->progs_mempool);
2559 for (i=0 ; i<prog->numglobaldefs ; i++)
2562 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2563 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2564 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2566 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2567 const char *value = PRVM_GetString(prog, val->string);
2570 value = PRVM_PO_Lookup(po, value);
2572 val->string = PRVM_SetEngineString(prog, value);
2580 for (cvar = prog->console_cmd->cvars->vars; cvar; cvar = cvar->next)
2581 cvar->globaldefindex[prog - prvm_prog_list] = -1;
2583 for (i=0 ; i<prog->numglobaldefs ; i++)
2586 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2587 //Con_Printf("found var %s\n", name);
2589 && !strncmp(name, "autocvar_", 9)
2590 && !(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
2593 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2594 cvar = Cvar_FindVar(prog->console_cmd->cvars, name + 9, prog->console_cmd->cvars_flagsmask);
2595 //Con_Printf("PRVM_LoadProgs: autocvar global %s in %s, processing...\n", name, prog->name);
2602 Con_DPrintf("PRVM_LoadProgs: no cvar for autocvar global %s in %s, creating...\n", name, prog->name);
2603 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2606 if((float)((int)(val->_float)) == val->_float)
2607 dpsnprintf(buf, sizeof(buf), "%i", (int)(val->_float));
2612 for (int precision = 7; precision <= 9; ++precision) {
2613 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2614 if ((float)atof(buf) == f) {
2622 for (i = 0; i < 3; ++i)
2626 for (int precision = 7; precision <= 9; ++precision) {
2627 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2628 if ((float)atof(buf) == f) {
2629 prec[i] = precision;
2634 dpsnprintf(buf, sizeof(buf), "%.*g %.*g %.*g", prec[0], val->vector[0], prec[1], val->vector[1], prec[2], val->vector[2]);
2638 value = PRVM_GetString(prog, val->string);
2641 Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name);
2644 cvar = Cvar_Get(prog->console_cmd->cvars, name + 9, value, prog->console_cmd->cvars_flagsmask, NULL);
2645 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2647 val->string = PRVM_SetEngineString(prog, cvar->string);
2648 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2651 prog->error_cmd("PRVM_LoadProgs: could not create cvar for autocvar global %s in %s", name, prog->name);
2652 cvar->globaldefindex[prog - prvm_prog_list] = i;
2654 else if((cvar->flags & CF_PRIVATE) == 0)
2656 // MUST BE SYNCED WITH cvar.c Cvar_Set
2659 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2662 val->_float = cvar->value;
2666 VectorClear(val->vector);
2667 for (j = 0;j < 3;j++)
2669 while (*s && ISWHITESPACE(*s))
2673 val->vector[j] = atof(s);
2674 while (!ISWHITESPACE(*s))
2681 val->string = PRVM_SetEngineString(prog, cvar->string);
2682 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2685 Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name);
2688 cvar->globaldefindex[prog - prvm_prog_list] = i;
2691 Con_Printf("PRVM_LoadProgs: private cvar for autocvar global %s in %s\n", name, prog->name);
2697 prog->loaded = true;
2699 PRVM_UpdateBreakpoints(prog);
2701 // 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 strlcat(tempstring, "string ", sizeof(tempstring));
2788 strlcat(tempstring, "entity ", sizeof(tempstring));
2791 strlcat(tempstring, "function ", sizeof(tempstring));
2794 strlcat(tempstring, "field ", sizeof(tempstring));
2797 strlcat(tempstring, "void ", sizeof(tempstring));
2800 strlcat(tempstring, "float ", sizeof(tempstring));
2803 strlcat(tempstring, "vector ", sizeof(tempstring));
2806 strlcat(tempstring, "pointer ", sizeof(tempstring));
2809 dpsnprintf (tempstring2, sizeof(tempstring2), "bad type %i ", d->type & ~DEF_SAVEGLOBAL);
2810 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 strlcat(tempstring, name, sizeof(tempstring));
2821 for (j = (int)strlen(name);j < 25;j++)
2822 strlcat(tempstring, " ", sizeof(tempstring));
2823 dpsnprintf(tempstring2, sizeof(tempstring2), "%5d", counts[i]);
2824 strlcat(tempstring, tempstring2, sizeof(tempstring));
2825 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 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 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 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
3369 int PRVM_SetTempString(prvm_prog_t *prog, const char *s)
3375 size = (int)strlen(s) + 1;
3376 if (developer_insane.integer)
3377 Con_DPrintf("PRVM_SetTempString: cursize %i, size %i\n", prog->tempstringsbuf.cursize, size);
3378 if (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: ran out of tempstring memory! (refusing to grow tempstring buffer over 256MB, cursize %i, size %i)\n", prog->tempstringsbuf.cursize, size);
3383 prog->tempstringsbuf.maxsize = max(prog->tempstringsbuf.maxsize, 65536);
3384 while (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: enlarging tempstrings buffer (%iKB -> %iKB)\n", 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: attempt to free a NULL string");
3430 else if (num >= 0 && num < prog->stringssize)
3431 prog->error_cmd("PRVM_FreeString: attempt to free a constant string");
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: attempt to free a non-existent or already freed string");
3437 if (!prog->knownstrings_flags[num])
3438 prog->error_cmd("PRVM_FreeString: attempt to free a string owned by the engine");
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: invalid string offset %i", 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));