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 = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "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 = {CVAR_CLIENT | CVAR_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 = {CVAR_CLIENT | CVAR_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 = {CVAR_CLIENT | CVAR_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 = {CVAR_CLIENT | CVAR_SERVER, "prvm_coverage", "0", "report and count coverage events (1: per-function, 2: coverage() builtin, 4: per-statement)"};
39 cvar_t prvm_backtraceforwarnings = {CVAR_CLIENT | CVAR_SERVER, "prvm_backtraceforwarnings", "0", "print a backtrace for warnings too"};
40 cvar_t prvm_leaktest = {CVAR_CLIENT | CVAR_SERVER, "prvm_leaktest", "0", "try to detect memory leaks in strings or entities"};
41 cvar_t prvm_leaktest_follow_targetname = {CVAR_CLIENT | CVAR_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 = {CVAR_CLIENT | CVAR_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 = {CVAR_CLIENT | CVAR_SERVER, "prvm_errordump", "0", "write a savegame on crash to crash-server.dmp"};
44 cvar_t prvm_breakpointdump = {CVAR_CLIENT | CVAR_SERVER, "prvm_breakpointdump", "0", "write a savegame on breakpoint to breakpoint-server.dmp"};
45 cvar_t prvm_reuseedicts_startuptime = {CVAR_CLIENT | CVAR_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 = {CVAR_CLIENT | CVAR_SERVER, "prvm_reuseedicts_neverinsameframe", "1", "never allows re-use of freed entity slots during same frame"};
47 cvar_t prvm_garbagecollection_enable = {CVAR_CLIENT | CVAR_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 = {CVAR_CLIENT | CVAR_SERVER, "prvm_garbagecollection_notify", "0", "print out a notification for each resource freed by garbage collection"};
49 cvar_t prvm_garbagecollection_scan_limit = {CVAR_CLIENT | CVAR_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 = {CVAR_CLIENT | CVAR_SERVER, "prvm_garbagecollection_strings", "1", "automatically call strunzone() on strings that are not referenced"};
51 cvar_t prvm_stringdebug = {CVAR_CLIENT | CVAR_SERVER, "prvm_stringdebug", "0", "Print debug and warning messages related to strings"};
53 static double prvm_reuseedicts_always_allow = 0;
54 qboolean 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));
207 e->priv.required->free = false;
208 e->priv.required->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 qboolean PRVM_ED_CanAlloc(prvm_prog_t *prog, prvm_edict_t *e)
238 if(!e->priv.required->free)
240 if(prvm_reuseedicts_always_allow == host.realtime)
242 if(host.realtime <= e->priv.required->freetime + 0.1 && prvm_reuseedicts_neverinsameframe.integer)
243 return false; // never allow reuse in same frame (causes networking trouble)
244 if(e->priv.required->freetime < prog->starttime + prvm_reuseedicts_startuptime.value)
246 if(host.realtime > e->priv.required->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
300 FIXME: walk all entities and NULL out references to this entity
303 void PRVM_ED_Free(prvm_prog_t *prog, prvm_edict_t *ed)
305 // dont delete the null entity (world) or reserved edicts
306 if (ed - prog->edicts <= prog->reserved_edicts)
309 prog->free_edict(prog, ed);
311 ed->priv.required->free = true;
312 ed->priv.required->freetime = host.realtime;
313 if(ed->priv.required->allocation_origin)
315 Mem_Free((char *)ed->priv.required->allocation_origin);
316 ed->priv.required->allocation_origin = NULL;
320 //===========================================================================
327 static mdef_t *PRVM_ED_GlobalAtOfs (prvm_prog_t *prog, unsigned int ofs)
332 for (i = 0;i < prog->numglobaldefs;i++)
334 def = &prog->globaldefs[i];
346 mdef_t *PRVM_ED_FieldAtOfs (prvm_prog_t *prog, unsigned int ofs)
351 for (i = 0;i < prog->numfielddefs;i++)
353 def = &prog->fielddefs[i];
365 mdef_t *PRVM_ED_FindField (prvm_prog_t *prog, const char *name)
370 for (i = 0;i < prog->numfielddefs;i++)
372 def = &prog->fielddefs[i];
373 if (!strcmp(PRVM_GetString(prog, def->s_name), name))
384 mdef_t *PRVM_ED_FindGlobal (prvm_prog_t *prog, const char *name)
389 for (i = 0;i < prog->numglobaldefs;i++)
391 def = &prog->globaldefs[i];
392 if (!strcmp(PRVM_GetString(prog, def->s_name), name))
400 PRVM_ED_FindGlobalEval
403 prvm_eval_t *PRVM_ED_FindGlobalEval(prvm_prog_t *prog, const char *name)
405 mdef_t *def = PRVM_ED_FindGlobal(prog, name);
406 return def ? (prvm_eval_t *) &prog->globals.fp[def->ofs] : NULL;
414 mfunction_t *PRVM_ED_FindFunction (prvm_prog_t *prog, const char *name)
419 for (i = 0;i < prog->numfunctions;i++)
421 func = &prog->functions[i];
422 if (!strcmp(PRVM_GetString(prog, func->s_name), name))
433 Returns a string describing *data in a type specific manner
436 static char *PRVM_ValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength)
442 type = (etype_t)((int) type & ~DEF_SAVEGLOBAL);
447 strlcpy (line, PRVM_GetString (prog, val->string), linelength);
451 if (n < 0 || n >= prog->max_edicts)
452 dpsnprintf (line, linelength, "entity %i (invalid!)", n);
454 dpsnprintf (line, linelength, "entity %i", n);
457 if ((unsigned int)val->function < (unsigned int)prog->progs_numfunctions)
459 f = prog->functions + val->function;
460 dpsnprintf (line, linelength, "%s()", PRVM_GetString(prog, f->s_name));
463 dpsnprintf (line, linelength, "function %" PRVM_PRIi "() (invalid!)", val->function);
466 def = PRVM_ED_FieldAtOfs ( prog, val->_int );
468 dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name));
470 dpsnprintf (line, linelength, "field %" PRVM_PRIi " (invalid!)", val->_int );
473 dpsnprintf (line, linelength, "void");
476 // LadyHavoc: changed from %5.1f to %10.4f
477 dpsnprintf (line, linelength, FLOAT_LOSSLESS_FORMAT, val->_float);
480 // LadyHavoc: changed from %5.1f to %10.4f
481 dpsnprintf (line, linelength, "'" VECTOR_LOSSLESS_FORMAT "'", val->vector[0], val->vector[1], val->vector[2]);
484 dpsnprintf (line, linelength, "pointer");
487 dpsnprintf (line, linelength, "bad type %i", (int) type);
498 Returns a string describing *data in a type specific manner
499 Easier to parse than PR_ValueString
502 char *PRVM_UglyValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength)
509 type = (etype_t)((int)type & ~DEF_SAVEGLOBAL);
514 // Parse the string a bit to turn special characters
515 // (like newline, specifically) into escape codes,
516 // this fixes saving games from various mods
517 s = PRVM_GetString (prog, val->string);
518 for (i = 0;i < (int)linelength - 2 && *s;)
548 dpsnprintf (line, linelength, "%i", i);
551 if ((unsigned int)val->function < (unsigned int)prog->progs_numfunctions)
553 f = prog->functions + val->function;
554 strlcpy (line, PRVM_GetString (prog, f->s_name), linelength);
557 dpsnprintf (line, linelength, "bad function %" PRVM_PRIi " (invalid!)", val->function);
560 def = PRVM_ED_FieldAtOfs ( prog, val->_int );
562 dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name));
564 dpsnprintf (line, linelength, "field %" PRVM_PRIi "(invalid!)", val->_int );
567 dpsnprintf (line, linelength, "void");
570 dpsnprintf (line, linelength, FLOAT_LOSSLESS_FORMAT, val->_float);
573 dpsnprintf (line, linelength, VECTOR_LOSSLESS_FORMAT, val->vector[0], val->vector[1], val->vector[2]);
576 dpsnprintf (line, linelength, "bad type %i", type);
587 Returns a string with a description and the contents of a global,
588 padded to 20 field width
591 char *PRVM_GlobalString (prvm_prog_t *prog, int ofs, char *line, size_t linelength)
597 char valuebuf[MAX_INPUTLINE];
599 val = (prvm_eval_t *)&prog->globals.fp[ofs];
600 def = PRVM_ED_GlobalAtOfs(prog, ofs);
602 dpsnprintf (line, linelength, "GLOBAL%i", ofs);
605 s = PRVM_ValueString (prog, (etype_t)def->type, val, valuebuf, sizeof(valuebuf));
606 dpsnprintf (line, linelength, "%s (=%s)", PRVM_GetString(prog, def->s_name), s);
610 //for ( ; i<20 ; i++)
611 // strcat (line," ");
617 char *PRVM_GlobalStringNoContents (prvm_prog_t *prog, int ofs, char *line, size_t linelength)
622 def = PRVM_ED_GlobalAtOfs(prog, ofs);
624 dpsnprintf (line, linelength, "GLOBAL%i", ofs);
626 dpsnprintf (line, linelength, "%s", PRVM_GetString(prog, def->s_name));
629 //for ( ; i<20 ; i++)
630 // strcat (line," ");
644 // LadyHavoc: optimized this to print out much more quickly (tempstring)
645 // LadyHavoc: changed to print out every 4096 characters (incase there are a lot of fields to print)
646 void PRVM_ED_Print(prvm_prog_t *prog, prvm_edict_t *ed, const char *wildcard_fieldname)
654 char tempstring[MAX_INPUTLINE], tempstring2[260]; // temporary string buffers
655 char valuebuf[MAX_INPUTLINE];
657 if (ed->priv.required->free)
659 Con_Printf("%s: FREE\n",prog->name);
664 dpsnprintf(tempstring, sizeof(tempstring), "\n%s EDICT %i:\n", prog->name, PRVM_NUM_FOR_EDICT(ed));
665 for (i = 1;i < prog->numfielddefs;i++)
667 d = &prog->fielddefs[i];
668 name = PRVM_GetString(prog, d->s_name);
669 if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
670 continue; // skip _x, _y, _z vars
672 // Check Field Name Wildcard
673 if(wildcard_fieldname)
674 if( !matchpattern(name, wildcard_fieldname, 1) )
675 // Didn't match; skip
678 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
680 // if the value is still all 0, skip the field
681 type = d->type & ~DEF_SAVEGLOBAL;
683 for (j=0 ; j<prvm_type_size[type] ; j++)
686 if (j == prvm_type_size[type])
689 if (strlen(name) > sizeof(tempstring2)-4)
691 memcpy (tempstring2, name, sizeof(tempstring2)-4);
692 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
693 tempstring2[sizeof(tempstring2)-1] = 0;
696 strlcat(tempstring, name, sizeof(tempstring));
697 for (l = strlen(name);l < 14;l++)
698 strlcat(tempstring, " ", sizeof(tempstring));
699 strlcat(tempstring, " ", sizeof(tempstring));
701 name = PRVM_ValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf));
702 if (strlen(name) > sizeof(tempstring2)-4)
704 memcpy (tempstring2, name, sizeof(tempstring2)-4);
705 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
706 tempstring2[sizeof(tempstring2)-1] = 0;
709 strlcat(tempstring, name, sizeof(tempstring));
710 strlcat(tempstring, "\n", sizeof(tempstring));
711 if (strlen(tempstring) >= sizeof(tempstring)/2)
713 Con_Print(tempstring);
718 Con_Print(tempstring);
728 void PRVM_ED_Write (prvm_prog_t *prog, qfile_t *f, prvm_edict_t *ed)
736 char valuebuf[MAX_INPUTLINE];
740 if (ed->priv.required->free)
746 for (i = 1;i < prog->numfielddefs;i++)
748 d = &prog->fielddefs[i];
749 name = PRVM_GetString(prog, d->s_name);
751 if(developer_entityparsing.integer)
752 Con_Printf("PRVM_ED_Write: at entity %d field %s\n", PRVM_NUM_FOR_EDICT(ed), name);
754 //if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
755 if(strlen(name) > 1 && name[strlen(name)-2] == '_')
756 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?)
758 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
760 // if the value is still all 0, skip the field
761 type = d->type & ~DEF_SAVEGLOBAL;
762 for (j=0 ; j<prvm_type_size[type] ; j++)
765 if (j == prvm_type_size[type])
768 FS_Printf(f,"\"%s\" ",name);
769 prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_Write, ent=%d, name=%s", i, name);
770 FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf)));
771 prog->statestring = NULL;
777 void PRVM_ED_PrintNum (prvm_prog_t *prog, int ent, const char *wildcard_fieldname)
779 PRVM_ED_Print(prog, PRVM_EDICT_NUM(ent), wildcard_fieldname);
784 PRVM_ED_PrintEdicts_f
786 For debugging, prints all the entities in the current server
789 void PRVM_ED_PrintEdicts_f(cmd_state_t *cmd)
793 const char *wildcard_fieldname;
795 if(Cmd_Argc(cmd) < 2 || Cmd_Argc(cmd) > 3)
797 Con_Print("prvm_edicts <program name> <optional field name wildcard>\n");
801 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
804 if( Cmd_Argc(cmd) == 3)
805 wildcard_fieldname = Cmd_Argv(cmd, 2);
807 wildcard_fieldname = NULL;
809 Con_Printf("%s: %i entities\n", prog->name, prog->num_edicts);
810 for (i=0 ; i<prog->num_edicts ; i++)
811 PRVM_ED_PrintNum (prog, i, wildcard_fieldname);
818 For debugging, prints a single edict
821 static void PRVM_ED_PrintEdict_f(cmd_state_t *cmd)
825 const char *wildcard_fieldname;
827 if(Cmd_Argc(cmd) < 3 || Cmd_Argc(cmd) > 4)
829 Con_Print("prvm_edict <program name> <edict number> <optional field name wildcard>\n");
833 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
836 i = atoi (Cmd_Argv(cmd, 2));
837 if (i >= prog->num_edicts)
839 Con_Print("Bad edict number\n");
842 if( Cmd_Argc(cmd) == 4)
843 // Optional Wildcard Provided
844 wildcard_fieldname = Cmd_Argv(cmd, 3);
847 wildcard_fieldname = NULL;
848 PRVM_ED_PrintNum (prog, i, wildcard_fieldname);
858 // 2 possibilities : 1. just displaying the active edict count
859 // 2. making a function pointer [x]
860 static void PRVM_ED_Count_f(cmd_state_t *cmd)
864 if(Cmd_Argc(cmd) != 2)
866 Con_Print("prvm_count <program name>\n");
870 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
873 prog->count_edicts(prog);
877 ==============================================================================
881 FIXME: need to tag constants, doesn't really work
882 ==============================================================================
890 void PRVM_ED_WriteGlobals (prvm_prog_t *prog, qfile_t *f)
897 char valuebuf[MAX_INPUTLINE];
900 for (i = 0;i < prog->numglobaldefs;i++)
902 def = &prog->globaldefs[i];
904 if ( !(def->type & DEF_SAVEGLOBAL) )
906 type &= ~DEF_SAVEGLOBAL;
908 if (type != ev_string && type != ev_float && type != ev_entity)
911 name = PRVM_GetString(prog, def->s_name);
913 if(developer_entityparsing.integer)
914 Con_Printf("PRVM_ED_WriteGlobals: at global %s\n", name);
916 prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_WriteGlobals, name=%s", name);
917 FS_Printf(f,"\"%s\" ", name);
918 FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)type, (prvm_eval_t *)&prog->globals.fp[def->ofs], valuebuf, sizeof(valuebuf)));
919 prog->statestring = NULL;
929 void PRVM_ED_ParseGlobals (prvm_prog_t *prog, const char *data)
931 char keyname[MAX_INPUTLINE];
937 if (!COM_ParseToken_Simple(&data, false, false, true))
938 prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace");
939 if (com_token[0] == '}')
942 if (developer_entityparsing.integer)
943 Con_Printf("Key: \"%s\"", com_token);
945 strlcpy (keyname, com_token, sizeof(keyname));
948 if (!COM_ParseToken_Simple(&data, false, true, true))
949 prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace");
951 if (developer_entityparsing.integer)
952 Con_Printf(" \"%s\"\n", com_token);
954 if (com_token[0] == '}')
955 prog->error_cmd("PRVM_ED_ParseGlobals: closing brace without data");
957 key = PRVM_ED_FindGlobal (prog, keyname);
960 Con_DPrintf("'%s' is not a global on %s\n", keyname, prog->name);
964 if (!PRVM_ED_ParseEpair(prog, NULL, key, com_token, true))
965 prog->error_cmd("PRVM_ED_ParseGlobals: parse error");
969 //============================================================================
976 Can parse either fields or globals
977 returns false if error
980 qboolean PRVM_ED_ParseEpair(prvm_prog_t *prog, prvm_edict_t *ent, mdef_t *key, const char *s, qboolean parsebackslash)
989 val = (prvm_eval_t *)(ent->fields.fp + key->ofs);
991 val = (prvm_eval_t *)(prog->globals.fp + key->ofs);
992 switch (key->type & ~DEF_SAVEGLOBAL)
995 l = (int)strlen(s) + 1;
996 val->string = PRVM_AllocString(prog, l, &new_p);
997 for (i = 0;i < l;i++)
999 if (s[i] == '\\' && s[i+1] && parsebackslash)
1004 else if (s[i] == 'r')
1015 while (*s && ISWHITESPACE(*s))
1017 val->_float = atof(s);
1021 for (i = 0;i < 3;i++)
1023 while (*s && ISWHITESPACE(*s))
1027 val->vector[i] = atof(s);
1028 while (!ISWHITESPACE(*s))
1036 while (*s && ISWHITESPACE(*s))
1039 if (i >= prog->limit_edicts)
1040 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);
1041 while (i >= prog->max_edicts)
1042 PRVM_MEM_IncreaseEdicts(prog);
1043 // if IncreaseEdicts was called the base pointer needs to be updated
1045 val = (prvm_eval_t *)(ent->fields.fp + key->ofs);
1046 val->edict = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM((int)i));
1052 Con_DPrintf("PRVM_ED_ParseEpair: Bogus field name %s in %s\n", s, prog->name);
1055 def = PRVM_ED_FindField(prog, s + 1);
1058 Con_DPrintf("PRVM_ED_ParseEpair: Can't find field %s in %s\n", s, prog->name);
1061 val->_int = def->ofs;
1065 func = PRVM_ED_FindFunction(prog, s);
1068 Con_Printf("PRVM_ED_ParseEpair: Can't find function %s in %s\n", s, prog->name);
1071 val->function = func - prog->functions;
1075 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);
1085 Console command to send a string to QC function GameCommand of the
1089 sv_cmd adminmsg 3 "do not teamkill"
1090 cl_cmd someclientcommand
1091 menu_cmd somemenucommand
1093 All progs can support this extension; sg calls it in server QC, cg in client
1097 static void PRVM_GameCommand(cmd_state_t *cmd, const char *whichprogs, const char *whichcmd)
1100 if(Cmd_Argc(cmd) < 1)
1102 Con_Printf("%s text...\n", whichcmd);
1106 if (!(prog = PRVM_FriendlyProgFromString(whichprogs)))
1109 if(!PRVM_allfunction(GameCommand))
1111 Con_Printf("%s program do not support GameCommand!\n", whichprogs);
1115 int restorevm_tempstringsbuf_cursize;
1120 restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize;
1121 PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, s ? s : "");
1122 prog->ExecuteProgram(prog, PRVM_allfunction(GameCommand), "QC function GameCommand is missing");
1123 prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
1126 static void PRVM_GameCommand_Server_f(cmd_state_t *cmd)
1128 PRVM_GameCommand(cmd, "server", "sv_cmd");
1130 static void PRVM_GameCommand_Client_f(cmd_state_t *cmd)
1132 PRVM_GameCommand(cmd, "client", "cl_cmd");
1134 static void PRVM_GameCommand_Menu_f(cmd_state_t *cmd)
1136 PRVM_GameCommand(cmd, "menu", "menu_cmd");
1143 Console command to load a field of a specified edict
1146 static void PRVM_ED_EdictGet_f(cmd_state_t *cmd)
1153 char valuebuf[MAX_INPUTLINE];
1155 if(Cmd_Argc(cmd) != 4 && Cmd_Argc(cmd) != 5)
1157 Con_Print("prvm_edictget <program name> <edict number> <field> [<cvar>]\n");
1161 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1164 ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(cmd, 2)));
1166 if((key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, 3))) == 0)
1168 Con_Printf("Key %s not found !\n", Cmd_Argv(cmd, 3));
1172 v = (prvm_eval_t *)(ed->fields.fp + key->ofs);
1173 s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf));
1174 if(Cmd_Argc(cmd) == 5)
1176 cvar_t *cvar = Cvar_FindVar(cmd->cvars, Cmd_Argv(cmd, 4), cmd->cvars_flagsmask);
1178 if(Cvar_Readonly(cvar, "prvm_edictget"))
1181 Cvar_Get(cmd->cvars, Cmd_Argv(cmd, 4), s, cmd->cvars_flagsmask, NULL);
1184 Con_Printf("%s\n", s);
1190 static void PRVM_ED_GlobalGet_f(cmd_state_t *cmd)
1196 char valuebuf[MAX_INPUTLINE];
1198 if(Cmd_Argc(cmd) != 3 && Cmd_Argc(cmd) != 4)
1200 Con_Print("prvm_globalget <program name> <global> [<cvar>]\n");
1204 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1207 key = PRVM_ED_FindGlobal(prog, Cmd_Argv(cmd, 2));
1210 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
1214 v = (prvm_eval_t *) &prog->globals.fp[key->ofs];
1215 s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf));
1216 if(Cmd_Argc(cmd) == 4)
1218 cvar_t *cvar = Cvar_FindVar(cmd->cvars, Cmd_Argv(cmd, 3), cmd->cvars_flagsmask);
1220 if(Cvar_Readonly(cvar, "prvm_globalget"))
1222 Cvar_Get(cmd->cvars, Cmd_Argv(cmd, 3), s, cmd->cvars_flagsmask, NULL);
1225 Con_Printf("%s\n", s);
1235 Console command to set a field of a specified edict
1238 static void PRVM_ED_EdictSet_f(cmd_state_t *cmd)
1244 if(Cmd_Argc(cmd) != 5)
1246 Con_Print("prvm_edictset <program name> <edict number> <field> <value>\n");
1250 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1253 ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(cmd, 2)));
1255 if((key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, 3))) == 0)
1256 Con_Printf("Key %s not found!\n", Cmd_Argv(cmd, 3));
1258 PRVM_ED_ParseEpair(prog, ed, key, Cmd_Argv(cmd, 4), true);
1262 ====================
1265 Parses an edict out of the given string, returning the new position
1266 ed should be a properly initialized empty edict.
1267 Used for initial level load and for savegames.
1268 ====================
1270 const char *PRVM_ED_ParseEdict (prvm_prog_t *prog, const char *data, prvm_edict_t *ent)
1280 // go through all the dictionary pairs
1284 if (!COM_ParseToken_Simple(&data, false, false, true))
1285 prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace");
1286 if (developer_entityparsing.integer)
1287 Con_Printf("Key: \"%s\"", com_token);
1288 if (com_token[0] == '}')
1291 // anglehack is to allow QuakeEd to write single scalar angles
1292 // and allow them to be turned into vectors. (FIXME...)
1293 if (!strcmp(com_token, "angle"))
1295 strlcpy (com_token, "angles", sizeof(com_token));
1301 // FIXME: change light to _light to get rid of this hack
1302 if (!strcmp(com_token, "light"))
1303 strlcpy (com_token, "light_lev", sizeof(com_token)); // hack for single light def
1305 strlcpy (keyname, com_token, sizeof(keyname));
1307 // another hack to fix keynames with trailing spaces
1308 n = strlen(keyname);
1309 while (n && keyname[n-1] == ' ')
1316 if (!COM_ParseToken_Simple(&data, false, false, true))
1317 prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace");
1318 if (developer_entityparsing.integer)
1319 Con_Printf(" \"%s\"\n", com_token);
1321 if (com_token[0] == '}')
1322 prog->error_cmd("PRVM_ED_ParseEdict: closing brace without data");
1326 // ignore attempts to set key "" (this problem occurs in nehahra neh1m8.bsp)
1330 // keynames with a leading underscore are used for utility comments,
1331 // and are immediately discarded by quake
1332 if (keyname[0] == '_')
1335 key = PRVM_ED_FindField (prog, keyname);
1338 Con_DPrintf("%s: '%s' is not a field\n", prog->name, keyname);
1345 strlcpy (temp, com_token, sizeof(temp));
1346 dpsnprintf (com_token, sizeof(com_token), "0 %s 0", temp);
1349 if (!PRVM_ED_ParseEpair(prog, ent, key, com_token, strcmp(keyname, "wad") != 0))
1350 prog->error_cmd("PRVM_ED_ParseEdict: parse error");
1354 ent->priv.required->free = true;
1355 ent->priv.required->freetime = host.realtime;
1361 void PRVM_ED_CallPrespawnFunction(prvm_prog_t *prog, prvm_edict_t *ent)
1363 if (PRVM_serverfunction(SV_OnEntityPreSpawnFunction))
1366 PRVM_serverglobalfloat(time) = sv.time;
1367 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1368 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPreSpawnFunction), "QC function SV_OnEntityPreSpawnFunction is missing");
1372 qboolean PRVM_ED_CallSpawnFunction(prvm_prog_t *prog, prvm_edict_t *ent, const char *data, const char *start)
1374 const char *funcname;
1376 prvm_eval_t *fulldata = NULL;
1380 // immediately call spawn function, but only if there is a self global and a classname
1382 if (!ent->priv.required->free)
1384 if (!PRVM_alledictstring(ent, classname))
1386 Con_Print("No classname for:\n");
1387 PRVM_ED_Print(prog, ent, NULL);
1388 PRVM_ED_Free (prog, ent);
1392 * This is required for FTE compatibility (FreeCS).
1393 * It copies the key/value pairs themselves into a
1394 * global for QC to parse on its own.
1396 else if (data && start)
1398 if((fulldata = PRVM_ED_FindGlobalEval(prog, "__fullspawndata")))
1402 fulldata->string = PRVM_AllocString(prog, data - start + 1, &spawndata);
1403 for(in = start; in < data; )
1407 *spawndata++ = '\t';
1415 // look for the spawn function
1416 funcname = PRVM_GetString(prog, PRVM_alledictstring(ent, classname));
1417 func = PRVM_ED_FindFunction (prog, va(vabuf, sizeof(vabuf), "spawnfunc_%s", funcname));
1419 if(!PRVM_allglobalfloat(require_spawnfunc_prefix))
1420 func = PRVM_ED_FindFunction (prog, funcname);
1424 // check for OnEntityNoSpawnFunction
1425 if (PRVM_serverfunction(SV_OnEntityNoSpawnFunction))
1428 PRVM_serverglobalfloat(time) = sv.time;
1429 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1430 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityNoSpawnFunction), "QC function SV_OnEntityNoSpawnFunction is missing");
1435 Con_DPrint("No spawn function for:\n");
1436 if (developer.integer > 0) // don't confuse non-developers with errors
1437 PRVM_ED_Print(prog, ent, NULL);
1439 PRVM_ED_Free (prog, ent);
1440 return false; // not included in "inhibited" count
1446 PRVM_serverglobalfloat(time) = sv.time;
1447 PRVM_allglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1448 prog->ExecuteProgram(prog, func - prog->functions, "");
1452 PRVM_ED_Free(prog, ent);
1456 void PRVM_ED_CallPostspawnFunction (prvm_prog_t *prog, prvm_edict_t *ent)
1458 if(!ent->priv.required->free)
1459 if (PRVM_serverfunction(SV_OnEntityPostSpawnFunction))
1462 PRVM_serverglobalfloat(time) = sv.time;
1463 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1464 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPostSpawnFunction), "QC function SV_OnEntityPostSpawnFunction is missing");
1470 PRVM_ED_LoadFromFile
1472 The entities are directly placed in the array, rather than allocated with
1473 PRVM_ED_Alloc, because otherwise an error loading the map would have entity
1474 number references out of order.
1476 Creates a server's entity / program execution context by
1477 parsing textual entity definitions out of an ent file.
1479 Used for both fresh maps and savegame loads. A fresh map would also need
1480 to call PRVM_ED_CallSpawnFunctions () to let the objects initialize themselves.
1483 void PRVM_ED_LoadFromFile (prvm_prog_t *prog, const char *data)
1487 int parsed, inhibited, spawned, died;
1494 prvm_reuseedicts_always_allow = host.realtime;
1501 // parse the opening brace
1502 if (!COM_ParseToken_Simple(&data, false, false, true))
1504 if (com_token[0] != '{')
1505 prog->error_cmd("PRVM_ED_LoadFromFile: %s: found %s when expecting {", prog->name, com_token);
1507 // CHANGED: this is not conform to PR_LoadFromFile
1508 if(prog->loadintoworld)
1510 prog->loadintoworld = false;
1511 ent = PRVM_EDICT_NUM(0);
1514 ent = PRVM_ED_Alloc(prog);
1517 if (ent != prog->edicts) // hack
1518 memset (ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
1520 data = PRVM_ED_ParseEdict (prog, data, ent);
1523 // remove the entity ?
1524 if(!prog->load_edict(prog, ent))
1526 PRVM_ED_Free(prog, ent);
1531 PRVM_ED_CallPrespawnFunction(prog, ent);
1533 if(ent->priv.required->free)
1539 if(!PRVM_ED_CallSpawnFunction(prog, ent, data, start))
1542 PRVM_ED_CallPostspawnFunction(prog, ent);
1545 if (ent->priv.required->free)
1549 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);
1551 prvm_reuseedicts_always_allow = 0;
1554 static void PRVM_FindOffsets(prvm_prog_t *prog)
1556 // field and global searches use -1 for NULL
1557 memset(&prog->fieldoffsets, -1, sizeof(prog->fieldoffsets));
1558 memset(&prog->globaloffsets, -1, sizeof(prog->globaloffsets));
1559 // function searches use 0 for NULL
1560 memset(&prog->funcoffsets, 0, sizeof(prog->funcoffsets));
1561 #define PRVM_DECLARE_serverglobalfloat(x)
1562 #define PRVM_DECLARE_serverglobalvector(x)
1563 #define PRVM_DECLARE_serverglobalstring(x)
1564 #define PRVM_DECLARE_serverglobaledict(x)
1565 #define PRVM_DECLARE_serverglobalfunction(x)
1566 #define PRVM_DECLARE_clientglobalfloat(x)
1567 #define PRVM_DECLARE_clientglobalvector(x)
1568 #define PRVM_DECLARE_clientglobalstring(x)
1569 #define PRVM_DECLARE_clientglobaledict(x)
1570 #define PRVM_DECLARE_clientglobalfunction(x)
1571 #define PRVM_DECLARE_menuglobalfloat(x)
1572 #define PRVM_DECLARE_menuglobalvector(x)
1573 #define PRVM_DECLARE_menuglobalstring(x)
1574 #define PRVM_DECLARE_menuglobaledict(x)
1575 #define PRVM_DECLARE_menuglobalfunction(x)
1576 #define PRVM_DECLARE_serverfieldfloat(x)
1577 #define PRVM_DECLARE_serverfieldvector(x)
1578 #define PRVM_DECLARE_serverfieldstring(x)
1579 #define PRVM_DECLARE_serverfieldedict(x)
1580 #define PRVM_DECLARE_serverfieldfunction(x)
1581 #define PRVM_DECLARE_clientfieldfloat(x)
1582 #define PRVM_DECLARE_clientfieldvector(x)
1583 #define PRVM_DECLARE_clientfieldstring(x)
1584 #define PRVM_DECLARE_clientfieldedict(x)
1585 #define PRVM_DECLARE_clientfieldfunction(x)
1586 #define PRVM_DECLARE_menufieldfloat(x)
1587 #define PRVM_DECLARE_menufieldvector(x)
1588 #define PRVM_DECLARE_menufieldstring(x)
1589 #define PRVM_DECLARE_menufieldedict(x)
1590 #define PRVM_DECLARE_menufieldfunction(x)
1591 #define PRVM_DECLARE_serverfunction(x)
1592 #define PRVM_DECLARE_clientfunction(x)
1593 #define PRVM_DECLARE_menufunction(x)
1594 #define PRVM_DECLARE_field(x) prog->fieldoffsets.x = PRVM_ED_FindFieldOffset(prog, #x);
1595 #define PRVM_DECLARE_global(x) prog->globaloffsets.x = PRVM_ED_FindGlobalOffset(prog, #x);
1596 #define PRVM_DECLARE_function(x) prog->funcoffsets.x = PRVM_ED_FindFunctionOffset(prog, #x);
1597 #include "prvm_offsets.h"
1598 #undef PRVM_DECLARE_serverglobalfloat
1599 #undef PRVM_DECLARE_serverglobalvector
1600 #undef PRVM_DECLARE_serverglobalstring
1601 #undef PRVM_DECLARE_serverglobaledict
1602 #undef PRVM_DECLARE_serverglobalfunction
1603 #undef PRVM_DECLARE_clientglobalfloat
1604 #undef PRVM_DECLARE_clientglobalvector
1605 #undef PRVM_DECLARE_clientglobalstring
1606 #undef PRVM_DECLARE_clientglobaledict
1607 #undef PRVM_DECLARE_clientglobalfunction
1608 #undef PRVM_DECLARE_menuglobalfloat
1609 #undef PRVM_DECLARE_menuglobalvector
1610 #undef PRVM_DECLARE_menuglobalstring
1611 #undef PRVM_DECLARE_menuglobaledict
1612 #undef PRVM_DECLARE_menuglobalfunction
1613 #undef PRVM_DECLARE_serverfieldfloat
1614 #undef PRVM_DECLARE_serverfieldvector
1615 #undef PRVM_DECLARE_serverfieldstring
1616 #undef PRVM_DECLARE_serverfieldedict
1617 #undef PRVM_DECLARE_serverfieldfunction
1618 #undef PRVM_DECLARE_clientfieldfloat
1619 #undef PRVM_DECLARE_clientfieldvector
1620 #undef PRVM_DECLARE_clientfieldstring
1621 #undef PRVM_DECLARE_clientfieldedict
1622 #undef PRVM_DECLARE_clientfieldfunction
1623 #undef PRVM_DECLARE_menufieldfloat
1624 #undef PRVM_DECLARE_menufieldvector
1625 #undef PRVM_DECLARE_menufieldstring
1626 #undef PRVM_DECLARE_menufieldedict
1627 #undef PRVM_DECLARE_menufieldfunction
1628 #undef PRVM_DECLARE_serverfunction
1629 #undef PRVM_DECLARE_clientfunction
1630 #undef PRVM_DECLARE_menufunction
1631 #undef PRVM_DECLARE_field
1632 #undef PRVM_DECLARE_global
1633 #undef PRVM_DECLARE_function
1638 typedef struct dpfield_s
1645 #define DPFIELDS (sizeof(dpfields) / sizeof(dpfield_t))
1647 dpfield_t dpfields[] =
1658 #define PO_HASHSIZE 16384
1659 typedef struct po_string_s
1662 struct po_string_s *nextonhashchain;
1667 po_string_t *hashtable[PO_HASHSIZE];
1670 static void PRVM_PO_UnparseString(char *out, const char *in, size_t outsize)
1679 case '\a': if(outsize >= 2) { *out++ = '\\'; *out++ = 'a'; outsize -= 2; } break;
1680 case '\b': if(outsize >= 2) { *out++ = '\\'; *out++ = 'b'; outsize -= 2; } break;
1681 case '\t': if(outsize >= 2) { *out++ = '\\'; *out++ = 't'; outsize -= 2; } break;
1682 case '\r': if(outsize >= 2) { *out++ = '\\'; *out++ = 'r'; outsize -= 2; } break;
1683 case '\n': if(outsize >= 2) { *out++ = '\\'; *out++ = 'n'; outsize -= 2; } break;
1684 case '\\': if(outsize >= 2) { *out++ = '\\'; *out++ = '\\'; outsize -= 2; } break;
1685 case '"': if(outsize >= 2) { *out++ = '\\'; *out++ = '"'; outsize -= 2; } break;
1687 if(*in >= 0 && *in <= 0x1F)
1692 *out++ = '0' + ((*in & 0700) >> 6);
1693 *out++ = '0' + ((*in & 0070) >> 3);
1694 *out++ = '0' + (*in & 0007) ;
1711 static void PRVM_PO_ParseString(char *out, const char *in, size_t outsize)
1724 case 'a': if(outsize > 0) { *out++ = '\a'; --outsize; } break;
1725 case 'b': if(outsize > 0) { *out++ = '\b'; --outsize; } break;
1726 case 't': if(outsize > 0) { *out++ = '\t'; --outsize; } break;
1727 case 'r': if(outsize > 0) { *out++ = '\r'; --outsize; } break;
1728 case 'n': if(outsize > 0) { *out++ = '\n'; --outsize; } break;
1729 case '\\': if(outsize > 0) { *out++ = '\\'; --outsize; } break;
1730 case '"': if(outsize > 0) { *out++ = '"'; --outsize; } break;
1731 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
1735 if(*in >= '0' && *in <= '7')
1738 *out = (*out << 3) | (*in - '0');
1741 if(*in >= '0' && *in <= '7')
1744 *out = (*out << 3) | (*in - '0');
1755 if(outsize > 0) { *out++ = *in; --outsize; }
1770 static po_t *PRVM_PO_Load(const char *filename, const char *filename2, mempool_t *pool)
1775 char inbuf[MAX_INPUTLINE];
1776 char decodedbuf[MAX_INPUTLINE];
1779 po_string_t thisstr;
1782 for (i = 0; i < 2; ++i)
1784 const char *buf = (const char *)
1785 FS_LoadFile((i > 0 ? filename : filename2), pool, true, NULL);
1786 // first read filename2, then read filename
1787 // so that progs.dat.de.po wins over common.de.po
1788 // and within file, last item wins
1795 po = (po_t *)Mem_Alloc(pool, sizeof(*po));
1796 memset(po, 0, sizeof(*po));
1799 memset(&thisstr, 0, sizeof(thisstr)); // hush compiler warning
1807 p = strchr(p, '\n');
1813 if(*p == '\r' || *p == '\n')
1818 if(!strncmp(p, "msgid \"", 7))
1823 else if(!strncmp(p, "msgstr \"", 8))
1830 p = strchr(p, '\n');
1840 q = strchr(p, '\n');
1847 if((size_t)(q - p) >= (size_t) sizeof(inbuf))
1849 strlcpy(inbuf, p, q - p); // not - 1, because this adds a NUL
1850 PRVM_PO_ParseString(decodedbuf + decodedpos, inbuf, sizeof(decodedbuf) - decodedpos);
1851 decodedpos += strlen(decodedbuf + decodedpos);
1861 Mem_Free(thisstr.key);
1862 thisstr.key = (char *)Mem_Alloc(pool, decodedpos + 1);
1863 memcpy(thisstr.key, decodedbuf, decodedpos + 1);
1865 else if(decodedpos > 0 && thisstr.key) // skip empty translation results
1867 thisstr.value = (char *)Mem_Alloc(pool, decodedpos + 1);
1868 memcpy(thisstr.value, decodedbuf, decodedpos + 1);
1869 hashindex = CRC_Block((const unsigned char *) thisstr.key, strlen(thisstr.key)) % PO_HASHSIZE;
1870 thisstr.nextonhashchain = po->hashtable[hashindex];
1871 po->hashtable[hashindex] = (po_string_t *)Mem_Alloc(pool, sizeof(thisstr));
1872 memcpy(po->hashtable[hashindex], &thisstr, sizeof(thisstr));
1873 memset(&thisstr, 0, sizeof(thisstr));
1877 Mem_Free((char *) buf);
1882 static const char *PRVM_PO_Lookup(po_t *po, const char *str)
1884 int hashindex = CRC_Block((const unsigned char *) str, strlen(str)) % PO_HASHSIZE;
1885 po_string_t *p = po->hashtable[hashindex];
1888 if(!strcmp(str, p->key))
1890 p = p->nextonhashchain;
1894 static void PRVM_PO_Destroy(po_t *po)
1897 for(i = 0; i < PO_HASHSIZE; ++i)
1899 po_string_t *p = po->hashtable[i];
1903 p = p->nextonhashchain;
1912 void PRVM_LeakTest(prvm_prog_t *prog);
1913 void PRVM_Prog_Reset(prvm_prog_t *prog)
1917 if(prog->tempstringsbuf.cursize)
1918 Mem_Free(prog->tempstringsbuf.data);
1919 prog->tempstringsbuf.cursize = 0;
1920 PRVM_LeakTest(prog);
1921 prog->reset_cmd(prog);
1922 Mem_FreePool(&prog->progs_mempool);
1924 PRVM_PO_Destroy((po_t *) prog->po);
1926 memset(prog,0,sizeof(prvm_prog_t));
1927 prog->break_statement = -1;
1928 prog->watch_global_type = ev_void;
1929 prog->watch_field_type = ev_void;
1937 static void PRVM_LoadLNO( prvm_prog_t *prog, const char *progname ) {
1938 fs_offset_t filesize;
1940 unsigned int *header;
1943 FS_StripExtension( progname, filename, sizeof( filename ) );
1944 strlcat( filename, ".lno", sizeof( filename ) );
1946 lno = FS_LoadFile( filename, tempmempool, false, &filesize );
1952 <Spike> SafeWrite (h, &lnotype, sizeof(int));
1953 <Spike> SafeWrite (h, &version, sizeof(int));
1954 <Spike> SafeWrite (h, &numglobaldefs, sizeof(int));
1955 <Spike> SafeWrite (h, &numpr_globals, sizeof(int));
1956 <Spike> SafeWrite (h, &numfielddefs, sizeof(int));
1957 <Spike> SafeWrite (h, &numstatements, sizeof(int));
1958 <Spike> SafeWrite (h, statement_linenums, numstatements*sizeof(int));
1960 if ((unsigned int)filesize < (6 + prog->progs_numstatements) * sizeof(int))
1966 header = (unsigned int *) lno;
1967 if( header[ 0 ] == *(unsigned int *) "LNOF" &&
1968 LittleLong( header[ 1 ] ) == 1 &&
1969 (unsigned int)LittleLong( header[ 2 ] ) == (unsigned int)prog->progs_numglobaldefs &&
1970 (unsigned int)LittleLong( header[ 3 ] ) == (unsigned int)prog->progs_numglobals &&
1971 (unsigned int)LittleLong( header[ 4 ] ) == (unsigned int)prog->progs_numfielddefs &&
1972 (unsigned int)LittleLong( header[ 5 ] ) == (unsigned int)prog->progs_numstatements )
1974 prog->statement_linenums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1975 memcpy( prog->statement_linenums, header + 6, prog->progs_numstatements * sizeof( int ) );
1977 /* gmqcc suports columnums */
1978 if ((unsigned int)filesize > ((6 + 2 * prog->progs_numstatements) * sizeof( int )))
1980 prog->statement_columnnums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1981 memcpy( prog->statement_columnnums, header + 6 + prog->progs_numstatements, prog->progs_numstatements * sizeof( int ) );
1992 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog);
1993 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)
1996 dprograms_t *dprograms;
1998 dstatement16_t *instatements16;
1999 dstatement32_t *instatements32;
2000 ddef16_t *infielddefs16;
2001 ddef32_t *infielddefs32;
2002 ddef16_t *inglobaldefs16;
2003 ddef32_t *inglobaldefs32;
2006 dfunction_t *infunctions;
2008 fs_offset_t filesize;
2009 int requiredglobalspace;
2027 prog->error_cmd("PRVM_LoadProgs: there is already a %s program loaded!", prog->name );
2029 Host_LockSession(); // all progs can use the session cvar
2030 Crypto_LoadKeys(); // all progs might use the keys at init time
2034 dprograms = (dprograms_t *) data;
2038 dprograms = (dprograms_t *)FS_LoadFile (filename, prog->progs_mempool, false, &filesize);
2039 if (dprograms == NULL || filesize < (fs_offset_t)sizeof(dprograms_t))
2040 prog->error_cmd("PRVM_LoadProgs: couldn't load %s for %s", filename, prog->name);
2041 // TODO bounds check header fields (e.g. numstatements), they must never go behind end of file
2043 prog->profiletime = Sys_DirtyTime();
2044 prog->starttime = host.realtime;
2046 requiredglobalspace = 0;
2047 for (i = 0;i < numrequiredglobals;i++)
2048 requiredglobalspace += required_global[i].type == ev_vector ? 3 : 1;
2050 prog->filecrc = CRC_Block((unsigned char *)dprograms, filesize);
2052 // byte swap the header
2053 prog->progs_version = LittleLong(dprograms->version);
2054 prog->progs_crc = LittleLong(dprograms->crc);
2055 if (prog->progs_version == 7)
2057 dprograms_v7_t *v7 = (dprograms_v7_t*)dprograms;
2058 structtype = LittleLong(v7->secondaryversion);
2059 if (structtype == PROG_SECONDARYVERSION16 ||
2060 structtype == PROG_SECONDARYVERSION32)
2063 prog->error_cmd("%s: %s targets unknown engine", prog->name, filename);
2065 if (v7->numbodylessfuncs != 0 || v7->numtypes != 0 || v7->blockscompressed != 0)
2066 prog->error_cmd("%s: %s uses unsupported features.", prog->name, filename);
2068 else if (prog->progs_version != PROG_VERSION)
2069 prog->error_cmd("%s: %s has wrong version number (%i should be %i)", prog->name, filename, prog->progs_version, PROG_VERSION);
2070 instatements16 = (dstatement16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_statements));
2071 instatements32 = (dstatement32_t *)instatements16;
2072 prog->progs_numstatements = LittleLong(dprograms->numstatements);
2073 inglobaldefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globaldefs));
2074 inglobaldefs32 = (ddef32_t *)inglobaldefs16;
2075 prog->progs_numglobaldefs = LittleLong(dprograms->numglobaldefs);
2076 infielddefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_fielddefs));
2077 infielddefs32 = (ddef32_t *)infielddefs16;
2078 prog->progs_numfielddefs = LittleLong(dprograms->numfielddefs);
2079 infunctions = (dfunction_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_functions));
2080 prog->progs_numfunctions = LittleLong(dprograms->numfunctions);
2081 instrings = (char *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_strings));
2082 prog->progs_numstrings = LittleLong(dprograms->numstrings);
2083 inglobals = (int *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globals));
2084 prog->progs_numglobals = LittleLong(dprograms->numglobals);
2085 prog->progs_entityfields = LittleLong(dprograms->entityfields);
2087 prog->numstatements = prog->progs_numstatements;
2088 prog->numglobaldefs = prog->progs_numglobaldefs;
2089 prog->numfielddefs = prog->progs_numfielddefs;
2090 prog->numfunctions = prog->progs_numfunctions;
2091 prog->numstrings = prog->progs_numstrings;
2092 prog->numglobals = prog->progs_numglobals;
2093 prog->entityfields = prog->progs_entityfields;
2095 if (LittleLong(dprograms->ofs_strings) + prog->progs_numstrings > (int)filesize)
2096 prog->error_cmd("%s: %s strings go past end of file", prog->name, filename);
2097 prog->strings = (char *)Mem_Alloc(prog->progs_mempool, prog->progs_numstrings);
2098 memcpy(prog->strings, instrings, prog->progs_numstrings);
2099 prog->stringssize = prog->progs_numstrings;
2101 prog->numknownstrings = 0;
2102 prog->maxknownstrings = 0;
2103 prog->knownstrings = NULL;
2104 prog->knownstrings_flags = NULL;
2106 Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64);
2108 // we need to expand the globaldefs and fielddefs to include engine defs
2109 prog->globaldefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobaldefs + numrequiredglobals) * sizeof(mdef_t));
2110 prog->globals.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobals + requiredglobalspace + 2) * sizeof(prvm_vec_t));
2111 // + 2 is because of an otherwise occurring overrun in RETURN instruction
2112 // when trying to return the last or second-last global
2113 // (RETURN always returns a vector, there is no RETURN_F instruction)
2114 prog->fielddefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numfielddefs + numrequiredfields) * sizeof(mdef_t));
2115 // we need to convert the statements to our memory format
2116 prog->statements = (mstatement_t *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(mstatement_t));
2117 // allocate space for profiling statement usage
2118 prog->statement_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2119 prog->explicit_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2120 // functions need to be converted to the memory format
2121 prog->functions = (mfunction_t *)Mem_Alloc(prog->progs_mempool, sizeof(mfunction_t) * prog->progs_numfunctions);
2123 for (i = 0;i < prog->progs_numfunctions;i++)
2125 prog->functions[i].first_statement = LittleLong(infunctions[i].first_statement);
2126 prog->functions[i].parm_start = LittleLong(infunctions[i].parm_start);
2127 prog->functions[i].s_name = LittleLong(infunctions[i].s_name);
2128 prog->functions[i].s_file = LittleLong(infunctions[i].s_file);
2129 prog->functions[i].numparms = LittleLong(infunctions[i].numparms);
2130 prog->functions[i].locals = LittleLong(infunctions[i].locals);
2131 memcpy(prog->functions[i].parm_size, infunctions[i].parm_size, sizeof(infunctions[i].parm_size));
2132 if(prog->functions[i].first_statement >= prog->numstatements)
2133 prog->error_cmd("PRVM_LoadProgs: out of bounds function statement (function %d) in %s", i, prog->name);
2134 // TODO bounds check parm_start, s_name, s_file, numparms, locals, parm_size
2137 // copy the globaldefs to the new globaldefs list
2140 case PROG_SECONDARYVERSION32:
2141 for (i=0 ; i<prog->numglobaldefs ; i++)
2143 prog->globaldefs[i].type = LittleLong(inglobaldefs32[i].type);
2144 prog->globaldefs[i].ofs = LittleLong(inglobaldefs32[i].ofs);
2145 prog->globaldefs[i].s_name = LittleLong(inglobaldefs32[i].s_name);
2146 // TODO bounds check ofs, s_name
2150 for (i=0 ; i<prog->numglobaldefs ; i++)
2152 prog->globaldefs[i].type = (unsigned short)LittleShort(inglobaldefs16[i].type);
2153 prog->globaldefs[i].ofs = (unsigned short)LittleShort(inglobaldefs16[i].ofs);
2154 prog->globaldefs[i].s_name = LittleLong(inglobaldefs16[i].s_name);
2155 // TODO bounds check ofs, s_name
2160 // append the required globals
2161 for (i = 0;i < numrequiredglobals;i++)
2163 prog->globaldefs[prog->numglobaldefs].type = required_global[i].type;
2164 prog->globaldefs[prog->numglobaldefs].ofs = prog->numglobals;
2165 prog->globaldefs[prog->numglobaldefs].s_name = PRVM_SetEngineString(prog, required_global[i].name);
2166 if (prog->globaldefs[prog->numglobaldefs].type == ev_vector)
2167 prog->numglobals += 3;
2170 prog->numglobaldefs++;
2173 // copy the progs fields to the new fields list
2176 case PROG_SECONDARYVERSION32:
2177 for (i = 0;i < prog->numfielddefs;i++)
2179 prog->fielddefs[i].type = LittleLong(infielddefs32[i].type);
2180 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2181 prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name);
2182 prog->fielddefs[i].ofs = LittleLong(infielddefs32[i].ofs);
2183 prog->fielddefs[i].s_name = LittleLong(infielddefs32[i].s_name);
2184 // TODO bounds check ofs, s_name
2188 for (i = 0;i < prog->numfielddefs;i++)
2190 prog->fielddefs[i].type = (unsigned short)LittleShort(infielddefs16[i].type);
2191 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2192 prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name);
2193 prog->fielddefs[i].ofs = (unsigned short)LittleShort(infielddefs16[i].ofs);
2194 prog->fielddefs[i].s_name = LittleLong(infielddefs16[i].s_name);
2195 // TODO bounds check ofs, s_name
2200 // append the required fields
2201 for (i = 0;i < numrequiredfields;i++)
2203 prog->fielddefs[prog->numfielddefs].type = required_field[i].type;
2204 prog->fielddefs[prog->numfielddefs].ofs = prog->entityfields;
2205 prog->fielddefs[prog->numfielddefs].s_name = PRVM_SetEngineString(prog, required_field[i].name);
2206 if (prog->fielddefs[prog->numfielddefs].type == ev_vector)
2207 prog->entityfields += 3;
2209 prog->entityfields++;
2210 prog->numfielddefs++;
2213 // LadyHavoc: TODO: reorder globals to match engine struct
2214 // LadyHavoc: TODO: reorder fields to match engine struct
2215 #define remapglobal(index) (index)
2216 #define remapfield(index) (index)
2219 // FIXME: LadyHavoc: this uses a crude way to identify integer constants, rather than checking for matching globaldefs and checking their type
2220 for (i = 0;i < prog->progs_numglobals;i++)
2222 u.i = LittleLong(inglobals[i]);
2223 // most globals are 0, we only need to deal with the ones that are not
2226 d = u.i & 0xFF800000;
2227 if ((d == 0xFF800000) || (d == 0))
2229 // Looks like an integer (expand to int64)
2230 prog->globals.ip[remapglobal(i)] = u.i;
2234 // Looks like a float (expand to double)
2235 prog->globals.fp[remapglobal(i)] = u.f;
2240 // copy, remap globals in statements, bounds check
2241 for (i = 0;i < prog->progs_numstatements;i++)
2245 case PROG_SECONDARYVERSION32:
2246 op = (opcode_t)LittleLong(instatements32[i].op);
2247 a = (unsigned int)LittleLong(instatements32[i].a);
2248 b = (unsigned int)LittleLong(instatements32[i].b);
2249 c = (unsigned int)LittleLong(instatements32[i].c);
2252 op = (opcode_t)LittleShort(instatements16[i].op);
2253 a = (unsigned short)LittleShort(instatements16[i].a);
2254 b = (unsigned short)LittleShort(instatements16[i].b);
2255 c = (unsigned short)LittleShort(instatements16[i].c);
2263 if (a >= prog->progs_numglobals || b + i < 0 || b + i >= prog->progs_numstatements)
2264 prog->error_cmd("PRVM_LoadProgs: out of bounds IF/IFNOT (statement %d) in %s", i, prog->name);
2265 prog->statements[i].op = op;
2266 prog->statements[i].operand[0] = remapglobal(a);
2267 prog->statements[i].operand[1] = -1;
2268 prog->statements[i].operand[2] = -1;
2269 prog->statements[i].jumpabsolute = i + b;
2273 if (a + i < 0 || a + i >= prog->progs_numstatements)
2274 prog->error_cmd("PRVM_LoadProgs: out of bounds GOTO (statement %d) in %s", i, prog->name);
2275 prog->statements[i].op = op;
2276 prog->statements[i].operand[0] = -1;
2277 prog->statements[i].operand[1] = -1;
2278 prog->statements[i].operand[2] = -1;
2279 prog->statements[i].jumpabsolute = i + a;
2282 Con_DPrintf("PRVM_LoadProgs: unknown opcode %d at statement %d in %s\n", (int)op, i, prog->name);
2284 //make sure its something well defined.
2285 prog->statements[i].op = OP_BOUNDCHECK;
2286 prog->statements[i].operand[0] = 0;
2287 prog->statements[i].operand[1] =
2288 prog->statements[i].operand[2] = op;
2289 prog->statements[i].jumpabsolute = -1;
2344 case OP_GSTOREP_ENT:
2345 case OP_GSTOREP_FLD:
2347 case OP_GSTOREP_FNC:
2349 // case OP_GADDRESS:
2359 // global global global
2394 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals || c >= prog->progs_numglobals)
2395 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d)", i);
2396 prog->statements[i].op = op;
2397 prog->statements[i].operand[0] = remapglobal(a);
2398 prog->statements[i].operand[1] = remapglobal(b);
2399 prog->statements[i].operand[2] = remapglobal(c);
2400 prog->statements[i].jumpabsolute = -1;
2402 // global none global
2408 if (a >= prog->progs_numglobals || c >= prog->progs_numglobals)
2409 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2410 prog->statements[i].op = op;
2411 prog->statements[i].operand[0] = remapglobal(a);
2412 prog->statements[i].operand[1] = -1;
2413 prog->statements[i].operand[2] = remapglobal(c);
2414 prog->statements[i].jumpabsolute = -1;
2422 if (c) //Spike -- DP is alergic to pointers in QC. Try to avoid too many nasty surprises.
2423 Con_DPrintf("PRVM_LoadProgs: storep-with-offset is not permitted in %s\n", prog->name);
2433 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals)
2434 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2435 prog->statements[i].op = op;
2436 prog->statements[i].operand[0] = remapglobal(a);
2437 prog->statements[i].operand[1] = remapglobal(b);
2438 prog->statements[i].operand[2] = -1;
2439 prog->statements[i].jumpabsolute = -1;
2443 if ( a < prog->progs_numglobals)
2444 if ( prog->globals.ip[remapglobal(a)] >= 0 )
2445 if ( prog->globals.ip[remapglobal(a)] < prog->progs_numfunctions )
2446 if ( prog->functions[prog->globals.ip[remapglobal(a)]].first_statement == -642 )
2447 ++prog->numexplicitcoveragestatements;
2458 if ( a >= prog->progs_numglobals)
2459 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2460 if (b || c) //Spike -- added this check just as a diagnostic...
2461 Con_DPrintf("PRVM_LoadProgs: unxpected offset on call opcode in %s. Hexen2 format is not supported\n", prog->name);
2462 prog->statements[i].op = op;
2463 prog->statements[i].operand[0] = remapglobal(a);
2464 prog->statements[i].operand[1] = -1;
2465 prog->statements[i].operand[2] = -1;
2466 prog->statements[i].jumpabsolute = -1;
2470 if(prog->numstatements < 1)
2472 prog->error_cmd("PRVM_LoadProgs: empty program in %s", prog->name);
2474 else switch(prog->statements[prog->numstatements - 1].op)
2481 prog->error_cmd("PRVM_LoadProgs: program may fall off the edge (does not end with RETURN, GOTO or DONE) in %s", prog->name);
2485 // we're done with the file now
2487 Mem_Free(dprograms);
2490 // check required functions
2491 for(i=0 ; i < numrequiredfunc ; i++)
2492 if(PRVM_ED_FindFunction(prog, required_func[i]) == 0)
2493 prog->error_cmd("%s: %s not found in %s",prog->name, required_func[i], filename);
2495 PRVM_LoadLNO(prog, filename);
2497 PRVM_Init_Exec(prog);
2499 if(*prvm_language.string)
2500 // in CSQC we really shouldn't be able to change how stuff works... sorry for now
2501 // later idea: include a list of authorized .po file checksums with the csprogs
2503 qboolean deftrans = prog == CLVM_prog;
2504 const char *realfilename = (prog != CLVM_prog ? filename : csqc_progname.string);
2505 if(deftrans) // once we have dotranslate_ strings, ALWAYS use the opt-in method!
2507 for (i=0 ; i<prog->numglobaldefs ; i++)
2510 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2511 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2512 if(name && !strncmp(name, "dotranslate_", 12))
2519 if(!strcmp(prvm_language.string, "dump"))
2521 qfile_t *f = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.pot", realfilename), "w", false);
2522 Con_Printf("Dumping to %s.pot\n", realfilename);
2525 for (i=0 ; i<prog->numglobaldefs ; i++)
2528 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2529 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2530 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2532 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2533 const char *value = PRVM_GetString(prog, val->string);
2536 char buf[MAX_INPUTLINE];
2537 PRVM_PO_UnparseString(buf, value, sizeof(buf));
2538 FS_Printf(f, "msgid \"%s\"\nmsgstr \"\"\n\n", buf);
2547 po_t *po = PRVM_PO_Load(
2548 va(vabuf, sizeof(vabuf), "%s.%s.po", realfilename, prvm_language.string),
2549 va(vabuf2, sizeof(vabuf2), "common.%s.po", prvm_language.string),
2550 prog->progs_mempool);
2553 for (i=0 ; i<prog->numglobaldefs ; i++)
2556 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2557 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2558 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2560 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2561 const char *value = PRVM_GetString(prog, val->string);
2564 value = PRVM_PO_Lookup(po, value);
2566 val->string = PRVM_SetEngineString(prog, value);
2574 for (cvar = prog->console_cmd->cvars->vars; cvar; cvar = cvar->next)
2575 cvar->globaldefindex[prog - prvm_prog_list] = -1;
2577 for (i=0 ; i<prog->numglobaldefs ; i++)
2580 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2581 //Con_Printf("found var %s\n", name);
2583 && !strncmp(name, "autocvar_", 9)
2584 && !(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
2587 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2588 cvar = Cvar_FindVar(prog->console_cmd->cvars, name + 9, prog->console_cmd->cvars_flagsmask);
2589 //Con_Printf("PRVM_LoadProgs: autocvar global %s in %s, processing...\n", name, prog->name);
2596 Con_DPrintf("PRVM_LoadProgs: no cvar for autocvar global %s in %s, creating...\n", name, prog->name);
2597 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2600 if((float)((int)(val->_float)) == val->_float)
2601 dpsnprintf(buf, sizeof(buf), "%i", (int)(val->_float));
2606 for (int precision = 7; precision <= 9; ++precision) {
2607 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2608 if ((float)atof(buf) == f) {
2616 for (i = 0; i < 3; ++i)
2620 for (int precision = 7; precision <= 9; ++precision) {
2621 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2622 if ((float)atof(buf) == f) {
2623 prec[i] = precision;
2628 dpsnprintf(buf, sizeof(buf), "%.*g %.*g %.*g", prec[0], val->vector[0], prec[1], val->vector[1], prec[2], val->vector[2]);
2632 value = PRVM_GetString(prog, val->string);
2635 Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name);
2638 cvar = Cvar_Get(prog->console_cmd->cvars, name + 9, value, prog->console_cmd->cvars_flagsmask, NULL);
2639 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2641 val->string = PRVM_SetEngineString(prog, cvar->string);
2642 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2645 prog->error_cmd("PRVM_LoadProgs: could not create cvar for autocvar global %s in %s", name, prog->name);
2646 cvar->globaldefindex[prog - prvm_prog_list] = i;
2648 else if((cvar->flags & CVAR_PRIVATE) == 0)
2650 // MUST BE SYNCED WITH cvar.c Cvar_Set
2653 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2656 val->_float = cvar->value;
2660 VectorClear(val->vector);
2661 for (j = 0;j < 3;j++)
2663 while (*s && ISWHITESPACE(*s))
2667 val->vector[j] = atof(s);
2668 while (!ISWHITESPACE(*s))
2675 val->string = PRVM_SetEngineString(prog, cvar->string);
2676 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2679 Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name);
2682 cvar->globaldefindex[prog - prvm_prog_list] = i;
2685 Con_Printf("PRVM_LoadProgs: private cvar for autocvar global %s in %s\n", name, prog->name);
2691 prog->loaded = true;
2693 PRVM_UpdateBreakpoints(prog);
2695 // set flags & mdef_ts in prog
2699 PRVM_FindOffsets(prog);
2701 prog->init_cmd(prog);
2704 PRVM_MEM_Alloc(prog);
2706 Con_Printf("%s: program loaded (crc %i, size %iK)\n", prog->name, prog->filecrc, (int)(filesize/1024));
2708 // Inittime is at least the time when this function finished. However,
2709 // later events may bump it.
2710 prog->inittime = host.realtime;
2714 static void PRVM_Fields_f(cmd_state_t *cmd)
2717 int i, j, ednum, used, usedamount;
2719 char tempstring[MAX_INPUTLINE], tempstring2[260];
2729 Con_Print("no progs loaded\n");
2734 if(Cmd_Argc(cmd) != 2)
2736 Con_Print("prvm_fields <program name>\n");
2740 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2743 counts = (int *)Mem_Alloc(tempmempool, prog->numfielddefs * sizeof(int));
2744 for (ednum = 0;ednum < prog->max_edicts;ednum++)
2746 ed = PRVM_EDICT_NUM(ednum);
2747 if (ed->priv.required->free)
2749 for (i = 1;i < prog->numfielddefs;i++)
2751 d = &prog->fielddefs[i];
2752 name = PRVM_GetString(prog, d->s_name);
2753 if (name[strlen(name)-2] == '_')
2754 continue; // skip _x, _y, _z vars
2755 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
2756 // if the value is still all 0, skip the field
2757 for (j = 0;j < prvm_type_size[d->type & ~DEF_SAVEGLOBAL];j++)
2759 if (val->ivector[j])
2770 for (i = 0;i < prog->numfielddefs;i++)
2772 d = &prog->fielddefs[i];
2773 name = PRVM_GetString(prog, d->s_name);
2774 if (name[strlen(name)-2] == '_')
2775 continue; // skip _x, _y, _z vars
2776 switch(d->type & ~DEF_SAVEGLOBAL)
2779 strlcat(tempstring, "string ", sizeof(tempstring));
2782 strlcat(tempstring, "entity ", sizeof(tempstring));
2785 strlcat(tempstring, "function ", sizeof(tempstring));
2788 strlcat(tempstring, "field ", sizeof(tempstring));
2791 strlcat(tempstring, "void ", sizeof(tempstring));
2794 strlcat(tempstring, "float ", sizeof(tempstring));
2797 strlcat(tempstring, "vector ", sizeof(tempstring));
2800 strlcat(tempstring, "pointer ", sizeof(tempstring));
2803 dpsnprintf (tempstring2, sizeof(tempstring2), "bad type %i ", d->type & ~DEF_SAVEGLOBAL);
2804 strlcat(tempstring, tempstring2, sizeof(tempstring));
2807 if (strlen(name) > sizeof(tempstring2)-4)
2809 memcpy (tempstring2, name, sizeof(tempstring2)-4);
2810 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
2811 tempstring2[sizeof(tempstring2)-1] = 0;
2814 strlcat(tempstring, name, sizeof(tempstring));
2815 for (j = (int)strlen(name);j < 25;j++)
2816 strlcat(tempstring, " ", sizeof(tempstring));
2817 dpsnprintf(tempstring2, sizeof(tempstring2), "%5d", counts[i]);
2818 strlcat(tempstring, tempstring2, sizeof(tempstring));
2819 strlcat(tempstring, "\n", sizeof(tempstring));
2820 if (strlen(tempstring) >= sizeof(tempstring)/2)
2822 Con_Print(tempstring);
2828 usedamount += prvm_type_size[d->type & ~DEF_SAVEGLOBAL];
2832 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);
2835 static void PRVM_Globals_f(cmd_state_t *cmd)
2839 const char *wildcard;
2845 Con_Print("no progs loaded\n");
2848 if(Cmd_Argc (cmd) < 2 || Cmd_Argc(cmd) > 3)
2850 Con_Print("prvm_globals <program name> <optional name wildcard>\n");
2854 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2857 if( Cmd_Argc(cmd) == 3)
2858 wildcard = Cmd_Argv(cmd, 2);
2862 Con_Printf("%s :", prog->name);
2864 for (i = 0;i < prog->numglobaldefs;i++)
2867 if( !matchpattern( PRVM_GetString(prog, prog->globaldefs[i].s_name), wildcard, 1) )
2872 Con_Printf("%s\n", PRVM_GetString(prog, prog->globaldefs[i].s_name));
2874 Con_Printf("%i global variables, %i culled, totalling %i bytes\n", prog->numglobals, numculled, prog->numglobals * 4);
2882 static void PRVM_Global_f(cmd_state_t *cmd)
2886 char valuebuf[MAX_INPUTLINE];
2887 if( Cmd_Argc(cmd) != 3 ) {
2888 Con_Printf( "prvm_global <program name> <global name>\n" );
2892 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2895 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2897 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2899 Con_Printf( "%s: %s\n", Cmd_Argv(cmd, 2), PRVM_ValueString( prog, (etype_t)global->type, PRVM_GLOBALFIELDVALUE(global->ofs), valuebuf, sizeof(valuebuf) ) );
2907 static void PRVM_GlobalSet_f(cmd_state_t *cmd)
2911 if( Cmd_Argc(cmd) != 4 ) {
2912 Con_Printf( "prvm_globalset <program name> <global name> <value>\n" );
2916 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2919 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2921 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2923 PRVM_ED_ParseEpair( prog, NULL, global, Cmd_Argv(cmd, 3), true );
2927 ======================
2928 Break- and Watchpoints
2929 ======================
2933 char break_statement[256];
2934 char watch_global[256];
2936 char watch_field[256];
2939 static debug_data_t debug_data[PRVM_PROG_MAX];
2941 void PRVM_Breakpoint(prvm_prog_t *prog, int stack_index, const char *text)
2944 Con_Printf("PRVM_Breakpoint: %s\n", text);
2945 PRVM_PrintState(prog, stack_index);
2946 if (prvm_breakpointdump.integer)
2947 SV_Savegame_to(prog, va(vabuf, sizeof(vabuf), "breakpoint-%s.dmp", prog->name));
2950 void PRVM_Watchpoint(prvm_prog_t *prog, int stack_index, const char *text, etype_t type, prvm_eval_t *o, prvm_eval_t *n)
2952 size_t sz = sizeof(prvm_vec_t) * ((type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
2953 if (memcmp(o, n, sz))
2956 char valuebuf_o[128];
2957 char valuebuf_n[128];
2958 PRVM_UglyValueString(prog, type, o, valuebuf_o, sizeof(valuebuf_o));
2959 PRVM_UglyValueString(prog, type, n, valuebuf_n, sizeof(valuebuf_n));
2960 dpsnprintf(buf, sizeof(buf), "%s: %s -> %s", text, valuebuf_o, valuebuf_n);
2961 PRVM_Breakpoint(prog, stack_index, buf);
2966 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog)
2968 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
2971 if (debug->break_statement[0])
2973 if (debug->break_statement[0] >= '0' && debug->break_statement[0] <= '9')
2975 prog->break_statement = atoi(debug->break_statement);
2976 prog->break_stack_index = 0;
2981 func = PRVM_ED_FindFunction (prog, debug->break_statement);
2984 Con_Printf("%s progs: no function or statement named %s to break on!\n", prog->name, debug->break_statement);
2985 prog->break_statement = -1;
2989 prog->break_statement = func->first_statement;
2990 prog->break_stack_index = 1;
2993 if (prog->break_statement >= -1)
2994 Con_Printf("%s progs: breakpoint is at statement %d\n", prog->name, prog->break_statement);
2997 prog->break_statement = -1;
2999 if (debug->watch_global[0])
3001 mdef_t *global = PRVM_ED_FindGlobal( prog, debug->watch_global );
3004 Con_Printf( "%s progs: no global named '%s' to watch!\n", prog->name, debug->watch_global );
3005 prog->watch_global_type = ev_void;
3009 size_t sz = sizeof(prvm_vec_t) * ((global->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3010 prog->watch_global = global->ofs;
3011 prog->watch_global_type = (etype_t)global->type;
3012 memcpy(&prog->watch_global_value, PRVM_GLOBALFIELDVALUE(prog->watch_global), sz);
3014 if (prog->watch_global_type != ev_void)
3015 Con_Printf("%s progs: global watchpoint is at global index %d\n", prog->name, prog->watch_global);
3018 prog->watch_global_type = ev_void;
3020 if (debug->watch_field[0])
3022 mdef_t *field = PRVM_ED_FindField( prog, debug->watch_field );
3025 Con_Printf( "%s progs: no field named '%s' to watch!\n", prog->name, debug->watch_field );
3026 prog->watch_field_type = ev_void;
3030 size_t sz = sizeof(prvm_vec_t) * ((field->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3031 prog->watch_edict = debug->watch_edict;
3032 prog->watch_field = field->ofs;
3033 prog->watch_field_type = (etype_t)field->type;
3034 if (prog->watch_edict < prog->num_edicts)
3035 memcpy(&prog->watch_edictfield_value, PRVM_EDICTFIELDVALUE(PRVM_EDICT_NUM(prog->watch_edict), prog->watch_field), sz);
3037 memset(&prog->watch_edictfield_value, 0, sz);
3039 if (prog->watch_edict != ev_void)
3040 Con_Printf("%s progs: edict field watchpoint is at edict %d field index %d\n", prog->name, prog->watch_edict, prog->watch_field);
3043 prog->watch_field_type = ev_void;
3046 static void PRVM_Breakpoint_f(cmd_state_t *cmd)
3050 if( Cmd_Argc(cmd) == 2 ) {
3051 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3054 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3055 debug->break_statement[0] = 0;
3057 PRVM_UpdateBreakpoints(prog);
3060 if( Cmd_Argc(cmd) != 3 ) {
3061 Con_Printf( "prvm_breakpoint <program name> <function name | statement>\n" );
3065 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3069 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3070 strlcpy(debug->break_statement, Cmd_Argv(cmd, 2), sizeof(debug->break_statement));
3072 PRVM_UpdateBreakpoints(prog);
3075 static void PRVM_GlobalWatchpoint_f(cmd_state_t *cmd)
3079 if( Cmd_Argc(cmd) == 2 ) {
3080 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3083 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3084 debug->watch_global[0] = 0;
3086 PRVM_UpdateBreakpoints(prog);
3089 if( Cmd_Argc(cmd) != 3 ) {
3090 Con_Printf( "prvm_globalwatchpoint <program name> <global name>\n" );
3094 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3098 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3099 strlcpy(debug->watch_global, Cmd_Argv(cmd, 2), sizeof(debug->watch_global));
3101 PRVM_UpdateBreakpoints(prog);
3104 static void PRVM_EdictWatchpoint_f(cmd_state_t *cmd)
3108 if( Cmd_Argc(cmd) == 2 ) {
3109 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3112 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3113 debug->watch_field[0] = 0;
3115 PRVM_UpdateBreakpoints(prog);
3118 if( Cmd_Argc(cmd) != 4 ) {
3119 Con_Printf( "prvm_edictwatchpoint <program name> <edict number> <field name>\n" );
3123 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3127 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3128 debug->watch_edict = atoi(Cmd_Argv(cmd, 2));
3129 strlcpy(debug->watch_field, Cmd_Argv(cmd, 3), sizeof(debug->watch_field));
3131 PRVM_UpdateBreakpoints(prog);
3139 void PRVM_Init (void)
3141 Cmd_AddCommand(CMD_SHARED, "prvm_edict", PRVM_ED_PrintEdict_f, "print all data about an entity number in the selected VM (server, client, menu)");
3142 Cmd_AddCommand(CMD_SHARED, "prvm_edicts", PRVM_ED_PrintEdicts_f, "prints all data about all entities in the selected VM (server, client, menu)");
3143 Cmd_AddCommand(CMD_SHARED, "prvm_edictcount", PRVM_ED_Count_f, "prints number of active entities in the selected VM (server, client, menu)");
3144 Cmd_AddCommand(CMD_SHARED, "prvm_profile", PRVM_Profile_f, "prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu)");
3145 Cmd_AddCommand(CMD_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");
3146 Cmd_AddCommand(CMD_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)");
3147 Cmd_AddCommand(CMD_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)");
3148 Cmd_AddCommand(CMD_SHARED, "prvm_globals", PRVM_Globals_f, "prints all global variables in the selected VM (server, client, menu)");
3149 Cmd_AddCommand(CMD_SHARED, "prvm_global", PRVM_Global_f, "prints value of a specified global variable in the selected VM (server, client, menu)");
3150 Cmd_AddCommand(CMD_SHARED, "prvm_globalset", PRVM_GlobalSet_f, "sets value of a specified global variable in the selected VM (server, client, menu)");
3151 Cmd_AddCommand(CMD_SHARED, "prvm_edictset", PRVM_ED_EdictSet_f, "changes value of a specified property of a specified entity in the selected VM (server, client, menu)");
3152 Cmd_AddCommand(CMD_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");
3153 Cmd_AddCommand(CMD_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");
3154 Cmd_AddCommand(CMD_SHARED, "prvm_printfunction", PRVM_PrintFunction_f, "prints a disassembly (QuakeC instructions) of the specified function in the selected VM (server, client, menu)");
3155 Cmd_AddCommand(CMD_SHARED, "cl_cmd", PRVM_GameCommand_Client_f, "calls the client QC function GameCommand with the supplied string as argument");
3156 Cmd_AddCommand(CMD_SHARED, "menu_cmd", PRVM_GameCommand_Menu_f, "calls the menu QC function GameCommand with the supplied string as argument");
3157 Cmd_AddCommand(CMD_SHARED, "sv_cmd", PRVM_GameCommand_Server_f, "calls the server QC function GameCommand with the supplied string as argument");
3158 Cmd_AddCommand(CMD_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");
3159 Cmd_AddCommand(CMD_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");
3160 Cmd_AddCommand(CMD_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");
3162 Cvar_RegisterVariable (&prvm_language);
3163 Cvar_RegisterVariable (&prvm_traceqc);
3164 Cvar_RegisterVariable (&prvm_statementprofiling);
3165 Cvar_RegisterVariable (&prvm_timeprofiling);
3166 Cvar_RegisterVariable (&prvm_coverage);
3167 Cvar_RegisterVariable (&prvm_backtraceforwarnings);
3168 Cvar_RegisterVariable (&prvm_leaktest);
3169 Cvar_RegisterVariable (&prvm_leaktest_follow_targetname);
3170 Cvar_RegisterVariable (&prvm_leaktest_ignore_classnames);
3171 Cvar_RegisterVariable (&prvm_errordump);
3172 Cvar_RegisterVariable (&prvm_breakpointdump);
3173 Cvar_RegisterVariable (&prvm_reuseedicts_startuptime);
3174 Cvar_RegisterVariable (&prvm_reuseedicts_neverinsameframe);
3175 Cvar_RegisterVariable (&prvm_garbagecollection_enable);
3176 Cvar_RegisterVariable (&prvm_garbagecollection_notify);
3177 Cvar_RegisterVariable (&prvm_garbagecollection_scan_limit);
3178 Cvar_RegisterVariable (&prvm_garbagecollection_strings);
3179 Cvar_RegisterVariable (&prvm_stringdebug);
3181 // COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!)
3182 prvm_runawaycheck = !Sys_CheckParm("-norunaway");
3192 void PRVM_Prog_Init(prvm_prog_t *prog, cmd_state_t *cmd)
3194 PRVM_Prog_Reset(prog);
3195 prog->leaktest_active = prvm_leaktest.integer != 0;
3196 prog->console_cmd = cmd;
3199 // LadyHavoc: turned PRVM_EDICT_NUM into a #define for speed reasons
3200 unsigned int PRVM_EDICT_NUM_ERROR(prvm_prog_t *prog, unsigned int n, const char *filename, int fileline)
3202 prog->error_cmd("PRVM_EDICT_NUM: %s: bad number %i (called at %s:%i)", prog->name, n, filename, fileline);
3206 #define PRVM_KNOWNSTRINGBASE 0x40000000
3208 const char *PRVM_GetString(prvm_prog_t *prog, int num)
3213 if (prvm_stringdebug.integer)
3214 VM_Warning(prog, "PRVM_GetString: Invalid string offset (%i < 0)\n", num);
3217 else if (num < prog->stringssize)
3219 // constant string from progs.dat
3220 return prog->strings + num;
3222 else if (num <= prog->stringssize + prog->tempstringsbuf.maxsize)
3224 // tempstring returned by engine to QC (becomes invalid after returning to engine)
3225 num -= prog->stringssize;
3226 if (num < prog->tempstringsbuf.cursize)
3227 return (char *)prog->tempstringsbuf.data + num;
3230 if (prvm_stringdebug.integer)
3231 VM_Warning(prog, "PRVM_GetString: Invalid temp-string offset (%i >= %i prog->tempstringsbuf.cursize)\n", num, prog->tempstringsbuf.cursize);
3235 else if (num & PRVM_KNOWNSTRINGBASE)
3238 num = num - PRVM_KNOWNSTRINGBASE;
3239 if (num >= 0 && num < prog->numknownstrings)
3241 if (!prog->knownstrings[num])
3243 if (prvm_stringdebug.integer)
3244 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num);
3247 // refresh the garbage collection on the string - this guards
3248 // against a certain sort of repeated migration to earlier
3249 // points in the scan that could otherwise result in the string
3250 // being freed for being unused
3251 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] & ~KNOWNSTRINGFLAG_GCPRUNE) | KNOWNSTRINGFLAG_GCMARK;
3252 return prog->knownstrings[num];
3256 if (prvm_stringdebug.integer)
3257 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, prog->numknownstrings);
3263 // invalid string offset
3264 if (prvm_stringdebug.integer)
3265 VM_Warning(prog, "PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, prog->stringssize);
3270 const char *PRVM_ChangeEngineString(prvm_prog_t *prog, int i, const char *s)
3273 i = i - PRVM_KNOWNSTRINGBASE;
3274 if (i < 0 || i >= prog->numknownstrings)
3275 prog->error_cmd("PRVM_ChangeEngineString: string index %i is out of bounds", i);
3276 else if ((prog->knownstrings_flags[i] & KNOWNSTRINGFLAG_ENGINE) == 0)
3277 prog->error_cmd("PRVM_ChangeEngineString: string index %i is not an engine string", i);
3278 old = prog->knownstrings[i];
3279 prog->knownstrings[i] = s;
3283 static void PRVM_NewKnownString(prvm_prog_t *prog, int i, int flags, const char *s)
3285 if (i >= prog->numknownstrings)
3287 if (i >= prog->maxknownstrings)
3289 const char **oldstrings = prog->knownstrings;
3290 const unsigned char *oldstrings_flags = prog->knownstrings_flags;
3291 const char **oldstrings_origin = prog->knownstrings_origin;
3292 prog->maxknownstrings += 128;
3293 prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3294 prog->knownstrings_flags = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char));
3295 if (prog->leaktest_active)
3296 prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3297 if (prog->numknownstrings)
3299 memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *));
3300 memcpy((char **)prog->knownstrings_flags, oldstrings_flags, prog->numknownstrings * sizeof(unsigned char));
3301 if (prog->leaktest_active)
3302 memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *));
3305 prog->numknownstrings++;
3307 prog->firstfreeknownstring = i + 1;
3308 prog->knownstrings[i] = s;
3309 // it's in use right now, spare it until the next gc pass - that said, it is not freeable so this is probably moot
3310 prog->knownstrings_flags[i] = flags;
3311 if (prog->leaktest_active)
3312 prog->knownstrings_origin[i] = NULL;
3315 int PRVM_SetEngineString(prvm_prog_t *prog, const char *s)
3320 if (s >= prog->strings && s <= prog->strings + prog->stringssize)
3321 prog->error_cmd("PRVM_SetEngineString: s in prog->strings area");
3322 // if it's in the tempstrings area, use a reserved range
3323 // (otherwise we'd get millions of useless string offsets cluttering the database)
3324 if (s >= (char *)prog->tempstringsbuf.data && s < (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.maxsize)
3325 return prog->stringssize + (s - (char *)prog->tempstringsbuf.data);
3326 // see if it's a known string address
3327 for (i = 0;i < prog->numknownstrings;i++)
3328 if (prog->knownstrings[i] == s)
3329 return PRVM_KNOWNSTRINGBASE + i;
3330 // new unknown engine string
3331 if (developer_insane.integer)
3332 Con_DPrintf("new engine string %p = \"%s\"\n", (void *)s, s);
3333 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3334 if (!prog->knownstrings[i])
3336 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE, s);
3337 return PRVM_KNOWNSTRINGBASE + i;
3340 // temp string handling
3342 // all tempstrings go into this buffer consecutively, and it is reset
3343 // whenever PRVM_ExecuteProgram returns to the engine
3344 // (technically each PRVM_ExecuteProgram call saves the cursize value and
3345 // restores it on return, so multiple recursive calls can share the same
3347 // the buffer size is automatically grown as needed
3349 int PRVM_SetTempString(prvm_prog_t *prog, const char *s)
3355 size = (int)strlen(s) + 1;
3356 if (developer_insane.integer)
3357 Con_DPrintf("PRVM_SetTempString: cursize %i, size %i\n", prog->tempstringsbuf.cursize, size);
3358 if (prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3360 sizebuf_t old = prog->tempstringsbuf;
3361 if (prog->tempstringsbuf.cursize + size >= 1<<28)
3362 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);
3363 prog->tempstringsbuf.maxsize = max(prog->tempstringsbuf.maxsize, 65536);
3364 while (prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3365 prog->tempstringsbuf.maxsize *= 2;
3366 if (prog->tempstringsbuf.maxsize != old.maxsize || prog->tempstringsbuf.data == NULL)
3368 Con_DPrintf("PRVM_SetTempString: enlarging tempstrings buffer (%iKB -> %iKB)\n", old.maxsize/1024, prog->tempstringsbuf.maxsize/1024);
3369 prog->tempstringsbuf.data = (unsigned char *) Mem_Alloc(prog->progs_mempool, prog->tempstringsbuf.maxsize);
3373 memcpy(prog->tempstringsbuf.data, old.data, old.cursize);
3378 t = (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.cursize;
3380 prog->tempstringsbuf.cursize += size;
3381 return PRVM_SetEngineString(prog, t);
3384 int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer)
3394 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3395 if (!prog->knownstrings[i])
3397 s = (char *)PRVM_Alloc(bufferlength);
3398 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK, s);
3399 if(prog->leaktest_active)
3400 prog->knownstrings_origin[i] = PRVM_AllocationOrigin(prog);
3402 *pointer = (char *)(prog->knownstrings[i]);
3403 return PRVM_KNOWNSTRINGBASE + i;
3406 void PRVM_FreeString(prvm_prog_t *prog, int num)
3409 prog->error_cmd("PRVM_FreeString: attempt to free a NULL string");
3410 else if (num >= 0 && num < prog->stringssize)
3411 prog->error_cmd("PRVM_FreeString: attempt to free a constant string");
3412 else if (num >= PRVM_KNOWNSTRINGBASE && num < PRVM_KNOWNSTRINGBASE + prog->numknownstrings)
3414 num = num - PRVM_KNOWNSTRINGBASE;
3415 if (!prog->knownstrings[num])
3416 prog->error_cmd("PRVM_FreeString: attempt to free a non-existent or already freed string");
3417 if (!prog->knownstrings_flags[num])
3418 prog->error_cmd("PRVM_FreeString: attempt to free a string owned by the engine");
3419 PRVM_Free((char *)prog->knownstrings[num]);
3420 if(prog->leaktest_active)
3421 if(prog->knownstrings_origin[num])
3422 PRVM_Free((char *)prog->knownstrings_origin[num]);
3423 prog->knownstrings[num] = NULL;
3424 prog->knownstrings_flags[num] = 0;
3425 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3428 prog->error_cmd("PRVM_FreeString: invalid string offset %i", num);
3431 static qboolean PRVM_IsStringReferenced(prvm_prog_t *prog, string_t string)
3435 for (i = 0;i < prog->numglobaldefs;i++)
3437 mdef_t *d = &prog->globaldefs[i];
3438 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3440 if(string == PRVM_GLOBALFIELDSTRING(d->ofs))
3444 for(j = 0; j < prog->num_edicts; ++j)
3446 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3447 if (ed->priv.required->free)
3449 for (i=0; i<prog->numfielddefs; ++i)
3451 mdef_t *d = &prog->fielddefs[i];
3452 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3454 if(string == PRVM_EDICTFIELDSTRING(ed, d->ofs))
3462 static qboolean PRVM_IsEdictRelevant(prvm_prog_t *prog, prvm_edict_t *edict)
3466 if(PRVM_NUM_FOR_EDICT(edict) <= prog->reserved_edicts)
3467 return true; // world or clients
3468 if (edict->priv.required->freetime <= prog->inittime)
3469 return true; // created during startup
3470 if (prog == SVVM_prog)
3472 if(PRVM_serveredictfloat(edict, solid)) // can block other stuff, or is a trigger?
3474 if(PRVM_serveredictfloat(edict, modelindex)) // visible ent?
3476 if(PRVM_serveredictfloat(edict, effects)) // particle effect?
3478 if(PRVM_serveredictfunction(edict, think)) // has a think function?
3479 if(PRVM_serveredictfloat(edict, nextthink) > 0) // that actually will eventually run?
3481 if(PRVM_serveredictfloat(edict, takedamage))
3483 if(*prvm_leaktest_ignore_classnames.string)
3485 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_serveredictstring(edict, classname)))))
3489 else if (prog == CLVM_prog)
3491 // TODO someone add more stuff here
3492 if(PRVM_clientedictfloat(edict, entnum)) // csqc networked
3494 if(PRVM_clientedictfloat(edict, modelindex)) // visible ent?
3496 if(PRVM_clientedictfloat(edict, effects)) // particle effect?
3498 if(PRVM_clientedictfunction(edict, think)) // has a think function?
3499 if(PRVM_clientedictfloat(edict, nextthink) > 0) // that actually will eventually run?
3501 if(*prvm_leaktest_ignore_classnames.string)
3503 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_clientedictstring(edict, classname)))))
3509 // menu prog does not have classnames
3514 static qboolean PRVM_IsEdictReferenced(prvm_prog_t *prog, prvm_edict_t *edict, int mark)
3517 int edictnum = PRVM_NUM_FOR_EDICT(edict);
3518 const char *targetname = NULL;
3520 if (prog == SVVM_prog && prvm_leaktest_follow_targetname.integer)
3521 targetname = PRVM_GetString(prog, PRVM_serveredictstring(edict, targetname));
3524 if(!*targetname) // ""
3527 for(j = 0; j < prog->num_edicts; ++j)
3529 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3530 if (ed->priv.required->mark < mark)
3536 const char *target = PRVM_GetString(prog, PRVM_serveredictstring(ed, target));
3538 if(!strcmp(target, targetname))
3541 for (i=0; i<prog->numfielddefs; ++i)
3543 mdef_t *d = &prog->fielddefs[i];
3544 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3546 if(edictnum == PRVM_EDICTFIELDEDICT(ed, d->ofs))
3554 static void PRVM_MarkReferencedEdicts(prvm_prog_t *prog)
3560 // Stage 1: world, all entities that are relevant, and all entities that are referenced by globals.
3562 for(j = 0; j < prog->num_edicts; ++j)
3564 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3565 if(ed->priv.required->free)
3567 ed->priv.required->mark = PRVM_IsEdictRelevant(prog, ed) ? stage : 0;
3569 for (i = 0;i < prog->numglobaldefs;i++)
3571 mdef_t *d = &prog->globaldefs[i];
3573 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3575 j = PRVM_GLOBALFIELDEDICT(d->ofs);
3576 if (i < 0 || j >= prog->max_edicts) {
3577 Con_Printf("Invalid entity reference from global %s.\n", PRVM_GetString(prog, d->s_name));
3580 ed = PRVM_EDICT_NUM(j);;
3581 ed->priv.required->mark = stage;
3584 // Future stages: all entities that are referenced by an entity of the previous stage.
3588 for(j = 0; j < prog->num_edicts; ++j)
3590 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3591 if(ed->priv.required->free)
3593 if(ed->priv.required->mark)
3595 if(PRVM_IsEdictReferenced(prog, ed, stage))
3597 ed->priv.required->mark = stage + 1;
3604 Con_DPrintf("leak check used %d stages to find all references\n", stage);
3607 void PRVM_LeakTest(prvm_prog_t *prog)
3610 qboolean leaked = false;
3612 if(!prog->leaktest_active)
3616 for (i = 0; i < prog->numknownstrings; ++i)
3618 if(prog->knownstrings[i])
3619 if(prog->knownstrings_flags[i])
3620 if(prog->knownstrings_origin[i])
3621 if(!PRVM_IsStringReferenced(prog, PRVM_KNOWNSTRINGBASE + i))
3623 Con_Printf("Unreferenced string found!\n Value: %s\n Origin: %s\n", prog->knownstrings[i], prog->knownstrings_origin[i]);
3629 PRVM_MarkReferencedEdicts(prog);
3630 for(j = 0; j < prog->num_edicts; ++j)
3632 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3633 if(ed->priv.required->free)
3635 if(!ed->priv.required->mark)
3636 if(ed->priv.required->allocation_origin)
3638 Con_Printf("Unreferenced edict found!\n Allocated at: %s\n", ed->priv.required->allocation_origin);
3639 PRVM_ED_Print(prog, ed, NULL);
3644 ed->priv.required->mark = 0; // clear marks again when done
3647 for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); ++i)
3649 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
3651 if(stringbuffer->origin)
3653 Con_Printf("Open string buffer handle found!\n Allocated at: %s\n", stringbuffer->origin);
3658 for(i = 0; i < PRVM_MAX_OPENFILES; ++i)
3660 if(prog->openfiles[i])
3661 if(prog->openfiles_origin[i])
3663 Con_Printf("Open file handle found!\n Allocated at: %s\n", prog->openfiles_origin[i]);
3668 for(i = 0; i < PRVM_MAX_OPENSEARCHES; ++i)
3670 if(prog->opensearches[i])
3671 if(prog->opensearches_origin[i])
3673 Con_Printf("Open search handle found!\n Allocated at: %s\n", prog->opensearches_origin[i]);
3679 Con_Printf("Congratulations. No leaks found.\n");
3682 void PRVM_GarbageCollection(prvm_prog_t *prog)
3684 int limit = prvm_garbagecollection_scan_limit.integer;
3685 prvm_prog_garbagecollection_state_t *gc = &prog->gc;
3686 if (!prvm_garbagecollection_enable.integer)
3689 // we like to limit how much scanning we do so it doesn't put a significant
3690 // burden on the cpu, so each of these are not complete scans, we also like
3691 // to have consistent cpu usage so we do a bit of work on each category of
3692 // leaked object every frame
3698 case PRVM_GC_GLOBALS_MARK:
3699 for (; gc->globals_mark_progress < prog->numglobaldefs && (limit--) > 0; gc->globals_mark_progress++)
3701 mdef_t *d = &prog->globaldefs[gc->globals_mark_progress];
3706 prvm_int_t s = prog->globals.ip[d->ofs];
3707 if (s & PRVM_KNOWNSTRINGBASE)
3709 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3710 if (!prog->knownstrings[num])
3713 Con_DPrintf("PRVM_GarbageCollection: Found bogus strzone reference in global %i (global name: \"%s\"), erasing reference", d->ofs, PRVM_GetString(prog, d->s_name));
3714 prog->globals.ip[d->ofs] = 0;
3717 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3725 if (gc->globals_mark_progress >= prog->numglobaldefs)
3728 case PRVM_GC_FIELDS_MARK:
3729 for (; gc->fields_mark_progress < prog->numfielddefs && limit > 0;)
3731 mdef_t *d = &prog->fielddefs[gc->fields_mark_progress];
3735 //for (gc-> entityindex = 0; entityindex < prog->num_edicts; entityindex++)
3736 for (;gc->fields_mark_progress_entity < prog->num_edicts && (limit--) > 0;gc->fields_mark_progress_entity++)
3738 int entityindex = gc->fields_mark_progress_entity;
3739 prvm_int_t s = prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs];
3740 if (s & PRVM_KNOWNSTRINGBASE)
3742 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3743 if (!prog->knownstrings[num])
3746 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));
3747 prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs] = 0;
3750 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3753 if (gc->fields_mark_progress_entity >= prog->num_edicts)
3755 gc->fields_mark_progress_entity = 0;
3756 gc->fields_mark_progress++;
3760 gc->fields_mark_progress_entity = 0;
3761 gc->fields_mark_progress++;
3765 if (gc->fields_mark_progress >= prog->numfielddefs)
3768 case PRVM_GC_KNOWNSTRINGS_SWEEP:
3769 // free any strzone'd strings that are not marked
3770 if (!prvm_garbagecollection_strings.integer)
3775 for (;gc->knownstrings_sweep_progress < prog->numknownstrings && (limit--) > 0;gc->knownstrings_sweep_progress++)
3777 int num = gc->knownstrings_sweep_progress;
3778 if (prog->knownstrings[num] && (prog->knownstrings_flags[num] & (KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE)) == 0)
3780 if (prog->knownstrings_flags[num] & KNOWNSTRINGFLAG_GCPRUNE)
3782 // string has been marked for pruning two passes in a row
3783 if (prvm_garbagecollection_notify.integer)
3784 Con_DPrintf("prvm_garbagecollection_notify: %s: freeing unreferenced string %i: \"%s\"\n", prog->name, num, prog->knownstrings[num]);
3785 Mem_Free((char *)prog->knownstrings[num]);
3786 prog->knownstrings[num] = NULL;
3787 prog->knownstrings_flags[num] = 0;
3788 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3792 // mark it for pruning next pass
3793 prog->knownstrings_flags[num] |= KNOWNSTRINGFLAG_GCPRUNE;
3797 if (gc->knownstrings_sweep_progress >= prog->numknownstrings)
3802 memset(gc, 0, sizeof(*gc));