2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 prvm_prog_t prvm_prog_list[PRVM_PROG_MAX];
28 int prvm_type_size[8] = {1,sizeof(string_t)/4,1,3,1,1,sizeof(func_t)/4,sizeof(void *)/4};
30 prvm_eval_t prvm_badvalue; // used only for error returns
32 cvar_t prvm_language = {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "prvm_language", "", "when set, loads PROGSFILE.LANGUAGENAME.po and common.LANGUAGENAME.po for string translations; when set to dump, PROGSFILE.pot is written from the strings in the progs"};
33 // LadyHavoc: prints every opcode as it executes - warning: this is significant spew
34 cvar_t prvm_traceqc = {CF_CLIENT | CF_SERVER, "prvm_traceqc", "0", "prints every QuakeC statement as it is executed (only for really thorough debugging!)"};
35 // LadyHavoc: counts usage of each QuakeC statement
36 cvar_t prvm_statementprofiling = {CF_CLIENT | CF_SERVER, "prvm_statementprofiling", "0", "counts how many times each QuakeC statement has been executed, these counts are displayed in prvm_printfunction output (if enabled)"};
37 cvar_t prvm_timeprofiling = {CF_CLIENT | CF_SERVER, "prvm_timeprofiling", "0", "counts how long each function has been executed, these counts are displayed in prvm_profile output (if enabled)"};
38 cvar_t prvm_coverage = {CF_CLIENT | CF_SERVER, "prvm_coverage", "0", "report and count coverage events (1: per-function, 2: coverage() builtin, 4: per-statement)"};
39 cvar_t prvm_backtraceforwarnings = {CF_CLIENT | CF_SERVER, "prvm_backtraceforwarnings", "0", "print a backtrace for warnings too"};
40 cvar_t prvm_leaktest = {CF_CLIENT | CF_SERVER, "prvm_leaktest", "0", "try to detect memory leaks in strings or entities"};
41 cvar_t prvm_leaktest_follow_targetname = {CF_CLIENT | CF_SERVER, "prvm_leaktest_follow_targetname", "0", "if set, target/targetname links are considered when leak testing; this should normally not be required, as entities created during startup - e.g. info_notnull - are never considered leaky"};
42 cvar_t prvm_leaktest_ignore_classnames = {CF_CLIENT | CF_SERVER, "prvm_leaktest_ignore_classnames", "", "classnames of entities to NOT leak check because they are found by find(world, classname, ...) but are actually spawned by QC code (NOT map entities)"};
43 cvar_t prvm_errordump = {CF_CLIENT | CF_SERVER, "prvm_errordump", "0", "write a savegame on crash to crash-server.dmp"};
44 cvar_t prvm_breakpointdump = {CF_CLIENT | CF_SERVER, "prvm_breakpointdump", "0", "write a savegame on breakpoint to breakpoint-server.dmp"};
45 cvar_t prvm_reuseedicts_startuptime = {CF_CLIENT | CF_SERVER, "prvm_reuseedicts_startuptime", "2", "allows immediate re-use of freed entity slots during start of new level (value in seconds)"};
46 cvar_t prvm_reuseedicts_neverinsameframe = {CF_CLIENT | CF_SERVER, "prvm_reuseedicts_neverinsameframe", "1", "never allows re-use of freed entity slots during same frame"};
47 cvar_t prvm_garbagecollection_enable = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_enable", "1", "automatically scan for and free resources that are not referenced by the code being executed in the VM"};
48 cvar_t prvm_garbagecollection_notify = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_notify", "0", "print out a notification for each resource freed by garbage collection"};
49 cvar_t prvm_garbagecollection_scan_limit = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_scan_limit", "10000", "scan this many fields or resources per frame to free up unreferenced resources"};
50 cvar_t prvm_garbagecollection_strings = {CF_CLIENT | CF_SERVER, "prvm_garbagecollection_strings", "1", "automatically call strunzone() on strings that are not referenced"};
51 cvar_t prvm_stringdebug = {CF_CLIENT | CF_SERVER, "prvm_stringdebug", "0", "Print debug and warning messages related to strings"};
53 static double prvm_reuseedicts_always_allow = 0;
54 qbool prvm_runawaycheck = true;
56 //============================================================================
64 static void PRVM_MEM_Alloc(prvm_prog_t *prog)
68 // reserve space for the null entity aka world
69 // check bound of max_edicts
70 prog->max_edicts = bound(1 + prog->reserved_edicts, prog->max_edicts, prog->limit_edicts);
71 prog->num_edicts = bound(1 + prog->reserved_edicts, prog->num_edicts, prog->max_edicts);
73 // edictprivate_size has to be min as big prvm_edict_private_t
74 prog->edictprivate_size = max(prog->edictprivate_size,(int)sizeof(prvm_edict_private_t));
77 prog->edicts = (prvm_edict_t *)Mem_Alloc(prog->progs_mempool,prog->limit_edicts * sizeof(prvm_edict_t));
79 // alloc edict private space
80 prog->edictprivate = Mem_Alloc(prog->progs_mempool, prog->max_edicts * prog->edictprivate_size);
83 prog->entityfieldsarea = prog->entityfields * prog->max_edicts;
84 prog->edictsfields.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, prog->entityfieldsarea * sizeof(prvm_vec_t));
87 for(i = 0; i < prog->max_edicts; i++)
89 prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size);
90 prog->edicts[i].fields.fp = prog->edictsfields.fp + i * prog->entityfields;
96 PRVM_MEM_IncreaseEdicts
99 void PRVM_MEM_IncreaseEdicts(prvm_prog_t *prog)
103 if(prog->max_edicts >= prog->limit_edicts)
106 prog->begin_increase_edicts(prog);
109 prog->max_edicts = min(prog->max_edicts + 256, prog->limit_edicts);
111 prog->entityfieldsarea = prog->entityfields * prog->max_edicts;
112 prog->edictsfields.fp = (prvm_vec_t*)Mem_Realloc(prog->progs_mempool, (void *)prog->edictsfields.fp, prog->entityfieldsarea * sizeof(prvm_vec_t));
113 prog->edictprivate = (void *)Mem_Realloc(prog->progs_mempool, (void *)prog->edictprivate, prog->max_edicts * prog->edictprivate_size);
115 //set e and v pointers
116 for(i = 0; i < prog->max_edicts; i++)
118 prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size);
119 prog->edicts[i].fields.fp = prog->edictsfields.fp + i * prog->entityfields;
122 prog->end_increase_edicts(prog);
125 //============================================================================
128 int PRVM_ED_FindFieldOffset(prvm_prog_t *prog, const char *field)
131 d = PRVM_ED_FindField(prog, field);
137 int PRVM_ED_FindGlobalOffset(prvm_prog_t *prog, const char *global)
140 d = PRVM_ED_FindGlobal(prog, global);
146 func_t PRVM_ED_FindFunctionOffset(prvm_prog_t *prog, const char *function)
149 f = PRVM_ED_FindFunction(prog, function);
152 return (func_t)(f - prog->functions);
160 prvm_prog_t *PRVM_ProgFromString(const char *str)
162 if (!strcmp(str, "server"))
164 if (!strcmp(str, "client"))
167 if (!strcmp(str, "menu"))
175 PRVM_FriendlyProgFromString
178 prvm_prog_t *PRVM_FriendlyProgFromString(const char *str)
180 prvm_prog_t *prog = PRVM_ProgFromString(str);
183 Con_Printf("%s: unknown program name\n", str);
188 Con_Printf("%s: program is not loaded\n", str);
198 Sets everything to NULL.
200 Nota bene: this also marks the entity as allocated if it has been previously
201 freed and sets the allocation origin.
204 void PRVM_ED_ClearEdict(prvm_prog_t *prog, prvm_edict_t *e)
206 memset(e->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
208 e->freetime = host.realtime;
209 if(e->priv.required->allocation_origin)
210 Mem_Free((char *)e->priv.required->allocation_origin);
211 e->priv.required->allocation_origin = PRVM_AllocationOrigin(prog);
213 // AK: Let the init_edict function determine if something needs to be initialized
214 prog->init_edict(prog, e);
217 const char *PRVM_AllocationOrigin(prvm_prog_t *prog)
220 if(prog->leaktest_active)
221 if(prog->depth > 0) // actually in QC code and not just parsing the entities block of a map/savegame
223 // bones_was_here: this is the smallest 64 multiple that avoids truncation in Xonotic (was 256)
224 buf = (char *)PRVM_Alloc(448);
225 PRVM_ShortStackTrace(prog, buf, 448);
234 Returns if this particular edict could get allocated by PRVM_ED_Alloc
237 qbool PRVM_ED_CanAlloc(prvm_prog_t *prog, prvm_edict_t *e)
241 if(prvm_reuseedicts_always_allow == host.realtime)
243 if(host.realtime <= e->freetime + 0.1 && prvm_reuseedicts_neverinsameframe.integer)
244 return false; // never allow reuse in same frame (causes networking trouble)
245 if(e->freetime < prog->starttime + prvm_reuseedicts_startuptime.value)
247 if(host.realtime > e->freetime + 1)
249 return false; // entity slot still blocked because the entity was freed less than one second ago
256 Either finds a free edict, or allocates a new one.
257 Try to avoid reusing an entity that was recently freed, because it
258 can cause the client to think the entity morphed into something else
259 instead of being removed and recreated, which can cause interpolated
260 angles and bad trails.
263 prvm_edict_t *PRVM_ED_Alloc(prvm_prog_t *prog)
268 // the client qc dont need maxclients
269 // thus it doesnt need to use svs.maxclients
270 // AK: changed i=svs.maxclients+1
271 // AK: changed so the edict 0 wont spawn -> used as reserved/world entity
272 // although the menu/client has no world
273 for (i = prog->reserved_edicts + 1;i < prog->num_edicts;i++)
275 e = PRVM_EDICT_NUM(i);
276 if(PRVM_ED_CanAlloc(prog, e))
278 PRVM_ED_ClearEdict (prog, e);
283 if (i == prog->limit_edicts)
284 prog->error_cmd("%s: PRVM_ED_Alloc: no free edicts", prog->name);
287 if (prog->num_edicts >= prog->max_edicts)
288 PRVM_MEM_IncreaseEdicts(prog);
290 e = PRVM_EDICT_NUM(i);
292 PRVM_ED_ClearEdict(prog, e);
300 Marks the edict as free
302 FIXME: walk all entities and NULL out references to this entity
303 bones_was_here: do not want, that would break chains immediately!
304 Currently chains aren't broken by removing an entity, at least with prvm_reuseedicts_neverinsameframe 1
305 which is very handy and some QC code will depend on it.
308 void PRVM_ED_Free(prvm_prog_t *prog, prvm_edict_t *ed)
310 // dont delete the null entity (world) or reserved edicts
311 if (ed - prog->edicts <= prog->reserved_edicts)
314 prog->free_edict(prog, ed);
317 ed->freetime = host.realtime;
318 if(ed->priv.required->allocation_origin)
320 Mem_Free((char *)ed->priv.required->allocation_origin);
321 ed->priv.required->allocation_origin = NULL;
325 //===========================================================================
332 static mdef_t *PRVM_ED_GlobalAtOfs (prvm_prog_t *prog, unsigned int ofs)
337 for (i = 0;i < prog->numglobaldefs;i++)
339 def = &prog->globaldefs[i];
351 mdef_t *PRVM_ED_FieldAtOfs (prvm_prog_t *prog, unsigned int ofs)
356 for (i = 0;i < prog->numfielddefs;i++)
358 def = &prog->fielddefs[i];
370 mdef_t *PRVM_ED_FindField (prvm_prog_t *prog, const char *name)
375 for (i = 0;i < prog->numfielddefs;i++)
377 def = &prog->fielddefs[i];
378 if (!strcmp(PRVM_GetString(prog, def->s_name), name))
389 mdef_t *PRVM_ED_FindGlobal (prvm_prog_t *prog, const char *name)
394 for (i = 0;i < prog->numglobaldefs;i++)
396 def = &prog->globaldefs[i];
397 if (!strcmp(PRVM_GetString(prog, def->s_name), name))
405 PRVM_ED_FindGlobalEval
408 prvm_eval_t *PRVM_ED_FindGlobalEval(prvm_prog_t *prog, const char *name)
410 mdef_t *def = PRVM_ED_FindGlobal(prog, name);
411 return def ? (prvm_eval_t *) &prog->globals.fp[def->ofs] : NULL;
419 mfunction_t *PRVM_ED_FindFunction (prvm_prog_t *prog, const char *name)
424 for (i = 0;i < prog->numfunctions;i++)
426 func = &prog->functions[i];
427 if (!strcmp(PRVM_GetString(prog, func->s_name), name))
438 Returns a string describing *data in a type specific manner
441 static char *PRVM_ValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength)
447 type = (etype_t)((int) type & ~DEF_SAVEGLOBAL);
452 dp_strlcpy (line, PRVM_GetString (prog, val->string), linelength);
456 if (n < 0 || n >= prog->max_edicts)
457 dpsnprintf (line, linelength, "entity %i (invalid!)", n);
459 dpsnprintf (line, linelength, "entity %i", n);
462 if ((unsigned int)val->function < (unsigned int)prog->progs_numfunctions)
464 f = prog->functions + val->function;
465 dpsnprintf (line, linelength, "%s()", PRVM_GetString(prog, f->s_name));
468 dpsnprintf (line, linelength, "function %" PRVM_PRIi "() (invalid!)", val->function);
471 def = PRVM_ED_FieldAtOfs ( prog, val->_int );
473 dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name));
475 dpsnprintf (line, linelength, "field %" PRVM_PRIi " (invalid!)", val->_int );
478 dpsnprintf (line, linelength, "void");
481 // LadyHavoc: changed from %5.1f to %10.4f
482 dpsnprintf (line, linelength, PRVM_FLOAT_LOSSLESS_FORMAT, val->_float);
485 // LadyHavoc: changed from %5.1f to %10.4f
486 dpsnprintf (line, linelength, "'" PRVM_VECTOR_LOSSLESS_FORMAT "'", val->vector[0], val->vector[1], val->vector[2]);
489 dpsnprintf (line, linelength, "pointer");
492 dpsnprintf (line, linelength, "bad type %i", (int) type);
503 Returns a string describing *data in a type specific manner
504 Easier to parse than PR_ValueString
507 char *PRVM_UglyValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength)
514 type = (etype_t)((int)type & ~DEF_SAVEGLOBAL);
519 // Parse the string a bit to turn special characters
520 // (like newline, specifically) into escape codes,
521 // this fixes saving games from various mods
522 s = PRVM_GetString (prog, val->string);
523 for (i = 0;i < (int)linelength - 2 && *s;)
553 dpsnprintf (line, linelength, "%i", i);
556 if ((unsigned int)val->function < (unsigned int)prog->progs_numfunctions)
558 f = prog->functions + val->function;
559 dp_strlcpy (line, PRVM_GetString (prog, f->s_name), linelength);
562 dpsnprintf (line, linelength, "bad function %" PRVM_PRIi " (invalid!)", val->function);
565 def = PRVM_ED_FieldAtOfs ( prog, val->_int );
567 dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name));
569 dpsnprintf (line, linelength, "field %" PRVM_PRIi "(invalid!)", val->_int );
572 dpsnprintf (line, linelength, "void");
575 dpsnprintf (line, linelength, PRVM_FLOAT_LOSSLESS_FORMAT, val->_float);
578 dpsnprintf (line, linelength, PRVM_VECTOR_LOSSLESS_FORMAT, val->vector[0], val->vector[1], val->vector[2]);
581 dpsnprintf (line, linelength, "bad type %i", type);
592 Returns a string with a description and the contents of a global,
593 padded to 20 field width
596 char *PRVM_GlobalString (prvm_prog_t *prog, int ofs, char *line, size_t linelength)
602 char valuebuf[MAX_INPUTLINE];
604 val = (prvm_eval_t *)&prog->globals.fp[ofs];
605 def = PRVM_ED_GlobalAtOfs(prog, ofs);
607 dpsnprintf (line, linelength, "GLOBAL%i", ofs);
610 s = PRVM_ValueString (prog, (etype_t)def->type, val, valuebuf, sizeof(valuebuf));
611 dpsnprintf (line, linelength, "%s (=%s)", PRVM_GetString(prog, def->s_name), s);
615 //for ( ; i<20 ; i++)
616 // strcat (line," ");
622 char *PRVM_GlobalStringNoContents (prvm_prog_t *prog, int ofs, char *line, size_t linelength)
627 def = PRVM_ED_GlobalAtOfs(prog, ofs);
629 dpsnprintf (line, linelength, "GLOBAL%i", ofs);
631 dpsnprintf (line, linelength, "%s", PRVM_GetString(prog, def->s_name));
634 //for ( ; i<20 ; i++)
635 // strcat (line," ");
649 // LadyHavoc: optimized this to print out much more quickly (tempstring)
650 // LadyHavoc: changed to print out every 4096 characters (incase there are a lot of fields to print)
651 void PRVM_ED_Print(prvm_prog_t *prog, prvm_edict_t *ed, const char *wildcard_fieldname)
659 char tempstring[MAX_INPUTLINE], tempstring2[260]; // temporary string buffers
660 char valuebuf[MAX_INPUTLINE];
664 Con_Printf("%s: FREE\n",prog->name);
669 dpsnprintf(tempstring, sizeof(tempstring), "\n%s EDICT %i:\n", prog->name, PRVM_NUM_FOR_EDICT(ed));
670 for (i = 1;i < prog->numfielddefs;i++)
672 d = &prog->fielddefs[i];
673 name = PRVM_GetString(prog, d->s_name);
674 if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
675 continue; // skip _x, _y, _z vars
677 // Check Field Name Wildcard
678 if(wildcard_fieldname)
679 if( !matchpattern(name, wildcard_fieldname, 1) )
680 // Didn't match; skip
683 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
685 // if the value is still all 0, skip the field
686 type = d->type & ~DEF_SAVEGLOBAL;
688 for (j=0 ; j<prvm_type_size[type] ; j++)
691 if (j == prvm_type_size[type])
694 if (strlen(name) > sizeof(tempstring2)-4)
696 memcpy (tempstring2, name, sizeof(tempstring2)-4);
697 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
698 tempstring2[sizeof(tempstring2)-1] = 0;
701 dp_strlcat(tempstring, name, sizeof(tempstring));
702 for (l = strlen(name);l < 14;l++)
703 dp_strlcat(tempstring, " ", sizeof(tempstring));
704 dp_strlcat(tempstring, " ", sizeof(tempstring));
706 name = PRVM_ValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf));
707 if (strlen(name) > sizeof(tempstring2)-4)
709 memcpy (tempstring2, name, sizeof(tempstring2)-4);
710 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
711 tempstring2[sizeof(tempstring2)-1] = 0;
714 dp_strlcat(tempstring, name, sizeof(tempstring));
715 dp_strlcat(tempstring, "\n", sizeof(tempstring));
716 if (strlen(tempstring) >= sizeof(tempstring)/2)
718 Con_Print(tempstring);
723 Con_Print(tempstring);
733 void PRVM_ED_Write (prvm_prog_t *prog, qfile_t *f, prvm_edict_t *ed)
741 char valuebuf[MAX_INPUTLINE];
751 for (i = 1;i < prog->numfielddefs;i++)
753 d = &prog->fielddefs[i];
754 name = PRVM_GetString(prog, d->s_name);
756 if(developer_entityparsing.integer)
757 Con_Printf("PRVM_ED_Write: at entity %d field %s\n", PRVM_NUM_FOR_EDICT(ed), name);
759 //if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
760 if(strlen(name) > 1 && name[strlen(name)-2] == '_')
761 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?)
763 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
765 // if the value is still all 0, skip the field
766 type = d->type & ~DEF_SAVEGLOBAL;
767 for (j=0 ; j<prvm_type_size[type] ; j++)
770 if (j == prvm_type_size[type])
773 FS_Printf(f,"\"%s\" ",name);
774 prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_Write, ent=%d, name=%s", i, name);
775 FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf)));
776 prog->statestring = NULL;
782 void PRVM_ED_PrintNum (prvm_prog_t *prog, int ent, const char *wildcard_fieldname)
784 PRVM_ED_Print(prog, PRVM_EDICT_NUM(ent), wildcard_fieldname);
789 PRVM_ED_PrintEdicts_f
791 For debugging, prints all the entities in the current server
794 void PRVM_ED_PrintEdicts_f(cmd_state_t *cmd)
798 const char *wildcard_fieldname;
800 if(Cmd_Argc(cmd) < 2 || Cmd_Argc(cmd) > 3)
802 Con_Print("prvm_edicts <program name> <optional field name wildcard>\n");
806 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
809 if( Cmd_Argc(cmd) == 3)
810 wildcard_fieldname = Cmd_Argv(cmd, 2);
812 wildcard_fieldname = NULL;
814 Con_Printf("%s: %i entities\n", prog->name, prog->num_edicts);
815 for (i=0 ; i<prog->num_edicts ; i++)
816 PRVM_ED_PrintNum (prog, i, wildcard_fieldname);
823 For debugging, prints a single edict
826 static void PRVM_ED_PrintEdict_f(cmd_state_t *cmd)
830 const char *wildcard_fieldname;
832 if(Cmd_Argc(cmd) < 3 || Cmd_Argc(cmd) > 4)
834 Con_Print("prvm_edict <program name> <edict number> <optional field name wildcard>\n");
838 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
841 i = atoi (Cmd_Argv(cmd, 2));
842 if (i >= prog->num_edicts)
844 Con_Print("Bad edict number\n");
847 if( Cmd_Argc(cmd) == 4)
848 // Optional Wildcard Provided
849 wildcard_fieldname = Cmd_Argv(cmd, 3);
852 wildcard_fieldname = NULL;
853 PRVM_ED_PrintNum (prog, i, wildcard_fieldname);
863 // 2 possibilities : 1. just displaying the active edict count
864 // 2. making a function pointer [x]
865 static void PRVM_ED_Count_f(cmd_state_t *cmd)
869 if(Cmd_Argc(cmd) != 2)
871 Con_Print("prvm_count <program name>\n");
875 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
878 prog->count_edicts(prog);
882 ==============================================================================
886 FIXME: need to tag constants, doesn't really work
887 ==============================================================================
895 void PRVM_ED_WriteGlobals (prvm_prog_t *prog, qfile_t *f)
902 char valuebuf[MAX_INPUTLINE];
905 for (i = 0;i < prog->numglobaldefs;i++)
907 def = &prog->globaldefs[i];
909 if ( !(def->type & DEF_SAVEGLOBAL) )
911 type &= ~DEF_SAVEGLOBAL;
913 if (type != ev_string && type != ev_float && type != ev_entity)
916 name = PRVM_GetString(prog, def->s_name);
918 if(developer_entityparsing.integer)
919 Con_Printf("PRVM_ED_WriteGlobals: at global %s\n", name);
921 prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_WriteGlobals, name=%s", name);
922 FS_Printf(f,"\"%s\" ", name);
923 FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)type, (prvm_eval_t *)&prog->globals.fp[def->ofs], valuebuf, sizeof(valuebuf)));
924 prog->statestring = NULL;
934 void PRVM_ED_ParseGlobals (prvm_prog_t *prog, const char *data)
936 char keyname[MAX_INPUTLINE];
942 if (!COM_ParseToken_Simple(&data, false, false, true))
943 prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace");
944 if (com_token[0] == '}')
947 if (developer_entityparsing.integer)
948 Con_Printf("Key: \"%s\"", com_token);
950 dp_strlcpy (keyname, com_token, sizeof(keyname));
953 if (!COM_ParseToken_Simple(&data, false, true, true))
954 prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace");
956 if (developer_entityparsing.integer)
957 Con_Printf(" \"%s\"\n", com_token);
959 if (com_token[0] == '}')
960 prog->error_cmd("PRVM_ED_ParseGlobals: closing brace without data");
962 key = PRVM_ED_FindGlobal (prog, keyname);
965 Con_DPrintf("'%s' is not a global on %s\n", keyname, prog->name);
969 if (!PRVM_ED_ParseEpair(prog, NULL, key, com_token, true))
970 prog->error_cmd("PRVM_ED_ParseGlobals: parse error");
974 //============================================================================
981 Can parse either fields or globals
982 returns false if error
985 qbool PRVM_ED_ParseEpair(prvm_prog_t *prog, prvm_edict_t *ent, mdef_t *key, const char *s, qbool parsebackslash)
994 val = (prvm_eval_t *)(ent->fields.fp + key->ofs);
996 val = (prvm_eval_t *)(prog->globals.fp + key->ofs);
997 switch (key->type & ~DEF_SAVEGLOBAL)
1000 l = (int)strlen(s) + 1;
1001 val->string = PRVM_AllocString(prog, l, &new_p);
1002 for (i = 0;i < l;i++)
1004 if (s[i] == '\\' && s[i+1] && parsebackslash)
1009 else if (s[i] == 'r')
1020 while (*s && ISWHITESPACE(*s))
1022 val->_float = atof(s);
1026 for (i = 0;i < 3;i++)
1028 while (*s && ISWHITESPACE(*s))
1032 val->vector[i] = atof(s);
1033 while (!ISWHITESPACE(*s))
1041 while (*s && ISWHITESPACE(*s))
1044 if (i >= prog->limit_edicts)
1045 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);
1046 while (i >= prog->max_edicts)
1047 PRVM_MEM_IncreaseEdicts(prog);
1048 // if IncreaseEdicts was called the base pointer needs to be updated
1050 val = (prvm_eval_t *)(ent->fields.fp + key->ofs);
1051 val->edict = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM((int)i));
1057 Con_DPrintf("PRVM_ED_ParseEpair: Bogus field name %s in %s\n", s, prog->name);
1060 def = PRVM_ED_FindField(prog, s + 1);
1063 Con_DPrintf("PRVM_ED_ParseEpair: Can't find field %s in %s\n", s, prog->name);
1066 val->_int = def->ofs;
1070 func = PRVM_ED_FindFunction(prog, s);
1073 Con_Printf("PRVM_ED_ParseEpair: Can't find function %s in %s\n", s, prog->name);
1076 val->function = func - prog->functions;
1080 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);
1090 Console command to send a string to QC function GameCommand of the
1094 sv_cmd adminmsg 3 "do not teamkill"
1095 cl_cmd someclientcommand
1096 menu_cmd somemenucommand
1098 All progs can support this extension; sg calls it in server QC, cg in client
1102 static void PRVM_GameCommand(cmd_state_t *cmd, const char *whichprogs, const char *whichcmd)
1105 if(Cmd_Argc(cmd) < 1)
1107 Con_Printf("%s text...\n", whichcmd);
1111 if (!(prog = PRVM_FriendlyProgFromString(whichprogs)))
1114 if(!PRVM_allfunction(GameCommand))
1116 Con_Printf("%s program do not support GameCommand!\n", whichprogs);
1120 int restorevm_tempstringsbuf_cursize;
1125 restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize;
1126 PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, s ? s : "");
1127 prog->ExecuteProgram(prog, PRVM_allfunction(GameCommand), "QC function GameCommand is missing");
1128 prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
1131 static void PRVM_GameCommand_Server_f(cmd_state_t *cmd)
1133 PRVM_GameCommand(cmd, "server", "sv_cmd");
1135 static void PRVM_GameCommand_Client_f(cmd_state_t *cmd)
1137 PRVM_GameCommand(cmd, "client", "cl_cmd");
1139 static void PRVM_GameCommand_Menu_f(cmd_state_t *cmd)
1141 PRVM_GameCommand(cmd, "menu", "menu_cmd");
1148 Console command to load a field of a specified edict
1151 static void PRVM_ED_EdictGet_f(cmd_state_t *cmd)
1158 char valuebuf[MAX_INPUTLINE];
1160 if(Cmd_Argc(cmd) != 4 && Cmd_Argc(cmd) != 5)
1162 Con_Print("prvm_edictget <program name> <edict number> <field> [<cvar>]\n");
1166 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1169 ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(cmd, 2)));
1171 if((key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, 3))) == 0)
1173 Con_Printf("Key %s not found !\n", Cmd_Argv(cmd, 3));
1177 v = (prvm_eval_t *)(ed->fields.fp + key->ofs);
1178 s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf));
1179 if(Cmd_Argc(cmd) == 5)
1181 cvar_t *cvar = Cvar_FindVar(cmd->cvars, Cmd_Argv(cmd, 4), cmd->cvars_flagsmask);
1183 if(Cvar_Readonly(cvar, "prvm_edictget"))
1186 Cvar_Get(cmd->cvars, Cmd_Argv(cmd, 4), s, cmd->cvars_flagsmask, NULL);
1189 Con_Printf("%s\n", s);
1195 static void PRVM_ED_GlobalGet_f(cmd_state_t *cmd)
1201 char valuebuf[MAX_INPUTLINE];
1203 if(Cmd_Argc(cmd) != 3 && Cmd_Argc(cmd) != 4)
1205 Con_Print("prvm_globalget <program name> <global> [<cvar>]\n");
1209 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1212 key = PRVM_ED_FindGlobal(prog, Cmd_Argv(cmd, 2));
1215 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
1219 v = (prvm_eval_t *) &prog->globals.fp[key->ofs];
1220 s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf));
1221 if(Cmd_Argc(cmd) == 4)
1223 cvar_t *cvar = Cvar_FindVar(cmd->cvars, Cmd_Argv(cmd, 3), cmd->cvars_flagsmask);
1225 if(Cvar_Readonly(cvar, "prvm_globalget"))
1227 Cvar_Get(cmd->cvars, Cmd_Argv(cmd, 3), s, cmd->cvars_flagsmask, NULL);
1230 Con_Printf("%s\n", s);
1240 Console command to set a field of a specified edict
1243 static void PRVM_ED_EdictSet_f(cmd_state_t *cmd)
1249 if(Cmd_Argc(cmd) != 5)
1251 Con_Print("prvm_edictset <program name> <edict number> <field> <value>\n");
1255 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
1258 ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(cmd, 2)));
1260 if((key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, 3))) == 0)
1261 Con_Printf("Key %s not found!\n", Cmd_Argv(cmd, 3));
1263 PRVM_ED_ParseEpair(prog, ed, key, Cmd_Argv(cmd, 4), true);
1267 ====================
1270 Parses an edict out of the given string, returning the new position
1271 ed should be a properly initialized empty edict.
1272 Used for initial level load and for savegames.
1273 ====================
1275 const char *PRVM_ED_ParseEdict (prvm_prog_t *prog, const char *data, prvm_edict_t *ent)
1285 // go through all the dictionary pairs
1289 if (!COM_ParseToken_Simple(&data, false, false, true))
1290 prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace");
1291 if (developer_entityparsing.integer)
1292 Con_Printf("Key: \"%s\"", com_token);
1293 if (com_token[0] == '}')
1296 // anglehack is to allow QuakeEd to write single scalar angles
1297 // and allow them to be turned into vectors. (FIXME...)
1298 if (!strcmp(com_token, "angle"))
1300 dp_strlcpy (com_token, "angles", sizeof(com_token));
1306 // FIXME: change light to _light to get rid of this hack
1307 if (!strcmp(com_token, "light"))
1308 dp_strlcpy (com_token, "light_lev", sizeof(com_token)); // hack for single light def
1310 dp_strlcpy (keyname, com_token, sizeof(keyname));
1312 // another hack to fix keynames with trailing spaces
1313 n = strlen(keyname);
1314 while (n && keyname[n-1] == ' ')
1321 if (!COM_ParseToken_Simple(&data, false, false, true))
1322 prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace");
1323 if (developer_entityparsing.integer)
1324 Con_Printf(" \"%s\"\n", com_token);
1326 if (com_token[0] == '}')
1327 prog->error_cmd("PRVM_ED_ParseEdict: closing brace without data");
1331 // ignore attempts to set key "" (this problem occurs in nehahra neh1m8.bsp)
1335 // keynames with a leading underscore are used for utility comments,
1336 // and are immediately discarded by quake
1337 if (keyname[0] == '_')
1340 key = PRVM_ED_FindField (prog, keyname);
1343 Con_DPrintf("%s: '%s' is not a field\n", prog->name, keyname);
1350 dp_strlcpy (temp, com_token, sizeof(temp));
1351 dpsnprintf (com_token, sizeof(com_token), "0 %s 0", temp);
1354 if (!PRVM_ED_ParseEpair(prog, ent, key, com_token, strcmp(keyname, "wad") != 0))
1355 prog->error_cmd("PRVM_ED_ParseEdict: parse error");
1360 ent->freetime = host.realtime;
1366 void PRVM_ED_CallPrespawnFunction(prvm_prog_t *prog, prvm_edict_t *ent)
1368 if (PRVM_serverfunction(SV_OnEntityPreSpawnFunction))
1371 PRVM_serverglobalfloat(time) = sv.time;
1372 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1373 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPreSpawnFunction), "QC function SV_OnEntityPreSpawnFunction is missing");
1377 qbool PRVM_ED_CallSpawnFunction(prvm_prog_t *prog, prvm_edict_t *ent, const char *data, const char *start)
1379 const char *funcname;
1381 prvm_eval_t *fulldata = NULL;
1385 // immediately call spawn function, but only if there is a self global and a classname
1389 if (!PRVM_alledictstring(ent, classname))
1391 Con_Print("No classname for:\n");
1392 PRVM_ED_Print(prog, ent, NULL);
1393 PRVM_ED_Free (prog, ent);
1397 * This is required for FTE compatibility (FreeCS).
1398 * It copies the key/value pairs themselves into a
1399 * global for QC to parse on its own.
1401 else if (data && start)
1403 if((fulldata = PRVM_ED_FindGlobalEval(prog, "__fullspawndata")))
1407 fulldata->string = PRVM_AllocString(prog, data - start + 1, &spawndata);
1408 for(in = start; in < data; )
1412 *spawndata++ = '\t';
1420 // look for the spawn function
1421 funcname = PRVM_GetString(prog, PRVM_alledictstring(ent, classname));
1422 func = PRVM_ED_FindFunction (prog, va(vabuf, sizeof(vabuf), "spawnfunc_%s", funcname));
1424 if(!PRVM_allglobalfloat(require_spawnfunc_prefix))
1425 func = PRVM_ED_FindFunction (prog, funcname);
1429 // check for OnEntityNoSpawnFunction
1430 if (PRVM_serverfunction(SV_OnEntityNoSpawnFunction))
1433 PRVM_serverglobalfloat(time) = sv.time;
1434 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1435 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityNoSpawnFunction), "QC function SV_OnEntityNoSpawnFunction is missing");
1440 Con_DPrint("No spawn function for:\n");
1441 if (developer.integer > 0) // don't confuse non-developers with errors
1442 PRVM_ED_Print(prog, ent, NULL);
1444 PRVM_ED_Free (prog, ent);
1445 return false; // not included in "inhibited" count
1451 PRVM_serverglobalfloat(time) = sv.time;
1452 PRVM_allglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1453 prog->ExecuteProgram(prog, func - prog->functions, "");
1457 PRVM_ED_Free(prog, ent);
1461 void PRVM_ED_CallPostspawnFunction (prvm_prog_t *prog, prvm_edict_t *ent)
1464 if (PRVM_serverfunction(SV_OnEntityPostSpawnFunction))
1467 PRVM_serverglobalfloat(time) = sv.time;
1468 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
1469 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPostSpawnFunction), "QC function SV_OnEntityPostSpawnFunction is missing");
1475 PRVM_ED_LoadFromFile
1477 The entities are directly placed in the array, rather than allocated with
1478 PRVM_ED_Alloc, because otherwise an error loading the map would have entity
1479 number references out of order.
1481 Creates a server's entity / program execution context by
1482 parsing textual entity definitions out of an ent file.
1484 Used for both fresh maps and savegame loads. A fresh map would also need
1485 to call PRVM_ED_CallSpawnFunctions () to let the objects initialize themselves.
1488 void PRVM_ED_LoadFromFile (prvm_prog_t *prog, const char *data)
1492 int parsed, inhibited, spawned, died;
1499 prvm_reuseedicts_always_allow = host.realtime;
1506 // parse the opening brace
1507 if (!COM_ParseToken_Simple(&data, false, false, true))
1509 if (com_token[0] != '{')
1510 prog->error_cmd("PRVM_ED_LoadFromFile: %s: found %s when expecting {", prog->name, com_token);
1512 // CHANGED: this is not conform to PR_LoadFromFile
1513 if(prog->loadintoworld)
1515 prog->loadintoworld = false;
1516 ent = PRVM_EDICT_NUM(0);
1519 ent = PRVM_ED_Alloc(prog);
1522 if (ent != prog->edicts) // hack
1523 memset (ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
1525 data = PRVM_ED_ParseEdict (prog, data, ent);
1528 // remove the entity ?
1529 if(!prog->load_edict(prog, ent))
1531 PRVM_ED_Free(prog, ent);
1536 PRVM_ED_CallPrespawnFunction(prog, ent);
1546 if(!PRVM_ED_CallSpawnFunction(prog, ent, data, start))
1549 PRVM_ED_CallPostspawnFunction(prog, ent);
1556 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);
1558 prvm_reuseedicts_always_allow = 0;
1561 static void PRVM_FindOffsets(prvm_prog_t *prog)
1563 // field and global searches use -1 for NULL
1564 memset(&prog->fieldoffsets, -1, sizeof(prog->fieldoffsets));
1565 memset(&prog->globaloffsets, -1, sizeof(prog->globaloffsets));
1566 // function searches use 0 for NULL
1567 memset(&prog->funcoffsets, 0, sizeof(prog->funcoffsets));
1568 #define PRVM_DECLARE_serverglobalfloat(x)
1569 #define PRVM_DECLARE_serverglobalvector(x)
1570 #define PRVM_DECLARE_serverglobalstring(x)
1571 #define PRVM_DECLARE_serverglobaledict(x)
1572 #define PRVM_DECLARE_serverglobalfunction(x)
1573 #define PRVM_DECLARE_clientglobalfloat(x)
1574 #define PRVM_DECLARE_clientglobalvector(x)
1575 #define PRVM_DECLARE_clientglobalstring(x)
1576 #define PRVM_DECLARE_clientglobaledict(x)
1577 #define PRVM_DECLARE_clientglobalfunction(x)
1578 #define PRVM_DECLARE_menuglobalfloat(x)
1579 #define PRVM_DECLARE_menuglobalvector(x)
1580 #define PRVM_DECLARE_menuglobalstring(x)
1581 #define PRVM_DECLARE_menuglobaledict(x)
1582 #define PRVM_DECLARE_menuglobalfunction(x)
1583 #define PRVM_DECLARE_serverfieldfloat(x)
1584 #define PRVM_DECLARE_serverfieldvector(x)
1585 #define PRVM_DECLARE_serverfieldstring(x)
1586 #define PRVM_DECLARE_serverfieldedict(x)
1587 #define PRVM_DECLARE_serverfieldfunction(x)
1588 #define PRVM_DECLARE_clientfieldfloat(x)
1589 #define PRVM_DECLARE_clientfieldvector(x)
1590 #define PRVM_DECLARE_clientfieldstring(x)
1591 #define PRVM_DECLARE_clientfieldedict(x)
1592 #define PRVM_DECLARE_clientfieldfunction(x)
1593 #define PRVM_DECLARE_menufieldfloat(x)
1594 #define PRVM_DECLARE_menufieldvector(x)
1595 #define PRVM_DECLARE_menufieldstring(x)
1596 #define PRVM_DECLARE_menufieldedict(x)
1597 #define PRVM_DECLARE_menufieldfunction(x)
1598 #define PRVM_DECLARE_serverfunction(x)
1599 #define PRVM_DECLARE_clientfunction(x)
1600 #define PRVM_DECLARE_menufunction(x)
1601 #define PRVM_DECLARE_field(x) prog->fieldoffsets.x = PRVM_ED_FindFieldOffset(prog, #x);
1602 #define PRVM_DECLARE_global(x) prog->globaloffsets.x = PRVM_ED_FindGlobalOffset(prog, #x);
1603 #define PRVM_DECLARE_function(x) prog->funcoffsets.x = PRVM_ED_FindFunctionOffset(prog, #x);
1604 #include "prvm_offsets.h"
1605 #undef PRVM_DECLARE_serverglobalfloat
1606 #undef PRVM_DECLARE_serverglobalvector
1607 #undef PRVM_DECLARE_serverglobalstring
1608 #undef PRVM_DECLARE_serverglobaledict
1609 #undef PRVM_DECLARE_serverglobalfunction
1610 #undef PRVM_DECLARE_clientglobalfloat
1611 #undef PRVM_DECLARE_clientglobalvector
1612 #undef PRVM_DECLARE_clientglobalstring
1613 #undef PRVM_DECLARE_clientglobaledict
1614 #undef PRVM_DECLARE_clientglobalfunction
1615 #undef PRVM_DECLARE_menuglobalfloat
1616 #undef PRVM_DECLARE_menuglobalvector
1617 #undef PRVM_DECLARE_menuglobalstring
1618 #undef PRVM_DECLARE_menuglobaledict
1619 #undef PRVM_DECLARE_menuglobalfunction
1620 #undef PRVM_DECLARE_serverfieldfloat
1621 #undef PRVM_DECLARE_serverfieldvector
1622 #undef PRVM_DECLARE_serverfieldstring
1623 #undef PRVM_DECLARE_serverfieldedict
1624 #undef PRVM_DECLARE_serverfieldfunction
1625 #undef PRVM_DECLARE_clientfieldfloat
1626 #undef PRVM_DECLARE_clientfieldvector
1627 #undef PRVM_DECLARE_clientfieldstring
1628 #undef PRVM_DECLARE_clientfieldedict
1629 #undef PRVM_DECLARE_clientfieldfunction
1630 #undef PRVM_DECLARE_menufieldfloat
1631 #undef PRVM_DECLARE_menufieldvector
1632 #undef PRVM_DECLARE_menufieldstring
1633 #undef PRVM_DECLARE_menufieldedict
1634 #undef PRVM_DECLARE_menufieldfunction
1635 #undef PRVM_DECLARE_serverfunction
1636 #undef PRVM_DECLARE_clientfunction
1637 #undef PRVM_DECLARE_menufunction
1638 #undef PRVM_DECLARE_field
1639 #undef PRVM_DECLARE_global
1640 #undef PRVM_DECLARE_function
1645 typedef struct dpfield_s
1652 #define DPFIELDS (sizeof(dpfields) / sizeof(dpfield_t))
1654 dpfield_t dpfields[] =
1665 #define PO_HASHSIZE 16384
1666 typedef struct po_string_s
1669 struct po_string_s *nextonhashchain;
1674 po_string_t *hashtable[PO_HASHSIZE];
1677 static void PRVM_PO_UnparseString(char *out, const char *in, size_t outsize)
1686 case '\a': if(outsize >= 2) { *out++ = '\\'; *out++ = 'a'; outsize -= 2; } break;
1687 case '\b': if(outsize >= 2) { *out++ = '\\'; *out++ = 'b'; outsize -= 2; } break;
1688 case '\t': if(outsize >= 2) { *out++ = '\\'; *out++ = 't'; outsize -= 2; } break;
1689 case '\r': if(outsize >= 2) { *out++ = '\\'; *out++ = 'r'; outsize -= 2; } break;
1690 case '\n': if(outsize >= 2) { *out++ = '\\'; *out++ = 'n'; outsize -= 2; } break;
1691 case '\\': if(outsize >= 2) { *out++ = '\\'; *out++ = '\\'; outsize -= 2; } break;
1692 case '"': if(outsize >= 2) { *out++ = '\\'; *out++ = '"'; outsize -= 2; } break;
1694 if(*in >= 0 && *in <= 0x1F)
1699 *out++ = '0' + ((*in & 0700) >> 6);
1700 *out++ = '0' + ((*in & 0070) >> 3);
1701 *out++ = '0' + (*in & 0007) ;
1718 static void PRVM_PO_ParseString(char *out, const char *in, size_t outsize)
1731 case 'a': if(outsize > 0) { *out++ = '\a'; --outsize; } break;
1732 case 'b': if(outsize > 0) { *out++ = '\b'; --outsize; } break;
1733 case 't': if(outsize > 0) { *out++ = '\t'; --outsize; } break;
1734 case 'r': if(outsize > 0) { *out++ = '\r'; --outsize; } break;
1735 case 'n': if(outsize > 0) { *out++ = '\n'; --outsize; } break;
1736 case '\\': if(outsize > 0) { *out++ = '\\'; --outsize; } break;
1737 case '"': if(outsize > 0) { *out++ = '"'; --outsize; } break;
1738 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
1742 if(*in >= '0' && *in <= '7')
1745 *out = (*out << 3) | (*in - '0');
1748 if(*in >= '0' && *in <= '7')
1751 *out = (*out << 3) | (*in - '0');
1762 if(outsize > 0) { *out++ = *in; --outsize; }
1777 static po_t *PRVM_PO_Load(const char *filename, const char *filename2, mempool_t *pool)
1782 char inbuf[MAX_INPUTLINE];
1783 char decodedbuf[MAX_INPUTLINE];
1786 po_string_t thisstr;
1789 for (i = 0; i < 2; ++i)
1791 const char *buf = (const char *)
1792 FS_LoadFile((i > 0 ? filename : filename2), pool, true, NULL);
1793 // first read filename2, then read filename
1794 // so that progs.dat.de.po wins over common.de.po
1795 // and within file, last item wins
1802 po = (po_t *)Mem_Alloc(pool, sizeof(*po));
1803 memset(po, 0, sizeof(*po));
1806 memset(&thisstr, 0, sizeof(thisstr)); // hush compiler warning
1814 p = strchr(p, '\n');
1820 if(*p == '\r' || *p == '\n')
1825 if(!strncmp(p, "msgid \"", 7))
1830 else if(!strncmp(p, "msgstr \"", 8))
1837 p = strchr(p, '\n');
1847 q = strchr(p, '\n');
1854 if((size_t)(q - p) >= (size_t) sizeof(inbuf))
1856 dp_strlcpy(inbuf, p, q - p); // not - 1, because this adds a NUL
1857 PRVM_PO_ParseString(decodedbuf + decodedpos, inbuf, sizeof(decodedbuf) - decodedpos);
1858 decodedpos += strlen(decodedbuf + decodedpos);
1868 Mem_Free(thisstr.key);
1869 thisstr.key = (char *)Mem_Alloc(pool, decodedpos + 1);
1870 memcpy(thisstr.key, decodedbuf, decodedpos + 1);
1872 else if(decodedpos > 0 && thisstr.key) // skip empty translation results
1874 thisstr.value = (char *)Mem_Alloc(pool, decodedpos + 1);
1875 memcpy(thisstr.value, decodedbuf, decodedpos + 1);
1876 hashindex = CRC_Block((const unsigned char *) thisstr.key, strlen(thisstr.key)) % PO_HASHSIZE;
1877 thisstr.nextonhashchain = po->hashtable[hashindex];
1878 po->hashtable[hashindex] = (po_string_t *)Mem_Alloc(pool, sizeof(thisstr));
1879 memcpy(po->hashtable[hashindex], &thisstr, sizeof(thisstr));
1880 memset(&thisstr, 0, sizeof(thisstr));
1884 Mem_Free((char *) buf);
1889 static const char *PRVM_PO_Lookup(po_t *po, const char *str)
1891 int hashindex = CRC_Block((const unsigned char *) str, strlen(str)) % PO_HASHSIZE;
1892 po_string_t *p = po->hashtable[hashindex];
1895 if(!strcmp(str, p->key))
1897 p = p->nextonhashchain;
1901 static void PRVM_PO_Destroy(po_t *po)
1904 for(i = 0; i < PO_HASHSIZE; ++i)
1906 po_string_t *p = po->hashtable[i];
1910 p = p->nextonhashchain;
1919 void PRVM_LeakTest(prvm_prog_t *prog);
1920 void PRVM_Prog_Reset(prvm_prog_t *prog)
1924 if(prog->tempstringsbuf.cursize)
1925 Mem_Free(prog->tempstringsbuf.data);
1926 prog->tempstringsbuf.cursize = 0;
1927 PRVM_LeakTest(prog);
1928 prog->reset_cmd(prog);
1929 Mem_FreePool(&prog->progs_mempool);
1931 PRVM_PO_Destroy((po_t *) prog->po);
1933 memset(prog,0,sizeof(prvm_prog_t));
1934 prog->break_statement = -1;
1935 prog->watch_global_type = ev_void;
1936 prog->watch_field_type = ev_void;
1944 static void PRVM_LoadLNO( prvm_prog_t *prog, const char *progname ) {
1945 fs_offset_t filesize;
1947 unsigned int *header;
1950 FS_StripExtension( progname, filename, sizeof( filename ) );
1951 dp_strlcat( filename, ".lno", sizeof( filename ) );
1953 lno = FS_LoadFile( filename, tempmempool, false, &filesize );
1959 <Spike> SafeWrite (h, &lnotype, sizeof(int));
1960 <Spike> SafeWrite (h, &version, sizeof(int));
1961 <Spike> SafeWrite (h, &numglobaldefs, sizeof(int));
1962 <Spike> SafeWrite (h, &numpr_globals, sizeof(int));
1963 <Spike> SafeWrite (h, &numfielddefs, sizeof(int));
1964 <Spike> SafeWrite (h, &numstatements, sizeof(int));
1965 <Spike> SafeWrite (h, statement_linenums, numstatements*sizeof(int));
1967 if ((unsigned int)filesize < (6 + prog->progs_numstatements) * sizeof(int))
1973 header = (unsigned int *) lno;
1974 if( header[ 0 ] == *(unsigned int *) "LNOF" &&
1975 LittleLong( header[ 1 ] ) == 1 &&
1976 (unsigned int)LittleLong( header[ 2 ] ) == (unsigned int)prog->progs_numglobaldefs &&
1977 (unsigned int)LittleLong( header[ 3 ] ) == (unsigned int)prog->progs_numglobals &&
1978 (unsigned int)LittleLong( header[ 4 ] ) == (unsigned int)prog->progs_numfielddefs &&
1979 (unsigned int)LittleLong( header[ 5 ] ) == (unsigned int)prog->progs_numstatements )
1981 prog->statement_linenums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1982 memcpy( prog->statement_linenums, header + 6, prog->progs_numstatements * sizeof( int ) );
1984 /* gmqcc suports columnums */
1985 if ((unsigned int)filesize > ((6 + 2 * prog->progs_numstatements) * sizeof( int )))
1987 prog->statement_columnnums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) );
1988 memcpy( prog->statement_columnnums, header + 6 + prog->progs_numstatements, prog->progs_numstatements * sizeof( int ) );
1999 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog);
2000 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)
2003 dprograms_t *dprograms;
2005 dstatement16_t *instatements16;
2006 dstatement32_t *instatements32;
2007 ddef16_t *infielddefs16;
2008 ddef32_t *infielddefs32;
2009 ddef16_t *inglobaldefs16;
2010 ddef32_t *inglobaldefs32;
2013 dfunction_t *infunctions;
2015 fs_offset_t filesize;
2016 int requiredglobalspace;
2034 prog->error_cmd("PRVM_LoadProgs: there is already a %s program loaded!", prog->name );
2036 Host_LockSession(); // all progs can use the session cvar
2037 Crypto_LoadKeys(); // all progs might use the keys at init time
2041 dprograms = (dprograms_t *) data;
2045 dprograms = (dprograms_t *)FS_LoadFile (filename, prog->progs_mempool, false, &filesize);
2046 if (dprograms == NULL || filesize < (fs_offset_t)sizeof(dprograms_t))
2047 prog->error_cmd("PRVM_LoadProgs: couldn't load %s for %s", filename, prog->name);
2048 // TODO bounds check header fields (e.g. numstatements), they must never go behind end of file
2050 prog->profiletime = Sys_DirtyTime();
2051 prog->starttime = host.realtime;
2053 requiredglobalspace = 0;
2054 for (i = 0;i < numrequiredglobals;i++)
2055 requiredglobalspace += required_global[i].type == ev_vector ? 3 : 1;
2057 prog->filecrc = CRC_Block((unsigned char *)dprograms, filesize);
2059 // byte swap the header
2060 prog->progs_version = LittleLong(dprograms->version);
2061 prog->progs_crc = LittleLong(dprograms->crc);
2062 if (prog->progs_version == 7)
2064 dprograms_v7_t *v7 = (dprograms_v7_t*)dprograms;
2065 structtype = LittleLong(v7->secondaryversion);
2066 if (structtype == PROG_SECONDARYVERSION16 ||
2067 structtype == PROG_SECONDARYVERSION32) // barely supported
2068 Con_Printf(CON_WARN "WARNING: %s: %s targets FTEQW, for which support is incomplete. Proceed at your own risk.\n", prog->name, filename);
2070 prog->error_cmd("%s: %s targets unknown engine", prog->name, filename);
2072 if (v7->numbodylessfuncs != 0 || v7->numtypes != 0 || v7->blockscompressed != 0)
2073 prog->error_cmd("%s: %s uses unsupported features.", prog->name, filename);
2075 else if (prog->progs_version != PROG_VERSION)
2076 prog->error_cmd("%s: %s has wrong version number (%i should be %i)", prog->name, filename, prog->progs_version, PROG_VERSION);
2077 instatements16 = (dstatement16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_statements));
2078 instatements32 = (dstatement32_t *)instatements16;
2079 prog->progs_numstatements = LittleLong(dprograms->numstatements);
2080 inglobaldefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globaldefs));
2081 inglobaldefs32 = (ddef32_t *)inglobaldefs16;
2082 prog->progs_numglobaldefs = LittleLong(dprograms->numglobaldefs);
2083 infielddefs16 = (ddef16_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_fielddefs));
2084 infielddefs32 = (ddef32_t *)infielddefs16;
2085 prog->progs_numfielddefs = LittleLong(dprograms->numfielddefs);
2086 infunctions = (dfunction_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_functions));
2087 prog->progs_numfunctions = LittleLong(dprograms->numfunctions);
2088 instrings = (char *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_strings));
2089 prog->progs_numstrings = LittleLong(dprograms->numstrings);
2090 inglobals = (int *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globals));
2091 prog->progs_numglobals = LittleLong(dprograms->numglobals);
2092 prog->progs_entityfields = LittleLong(dprograms->entityfields);
2094 prog->numstatements = prog->progs_numstatements;
2095 prog->numglobaldefs = prog->progs_numglobaldefs;
2096 prog->numfielddefs = prog->progs_numfielddefs;
2097 prog->numfunctions = prog->progs_numfunctions;
2098 prog->numstrings = prog->progs_numstrings;
2099 prog->numglobals = prog->progs_numglobals;
2100 prog->entityfields = prog->progs_entityfields;
2102 if (LittleLong(dprograms->ofs_strings) + prog->progs_numstrings > (int)filesize)
2103 prog->error_cmd("%s: %s strings go past end of file", prog->name, filename);
2104 prog->strings = (char *)Mem_Alloc(prog->progs_mempool, prog->progs_numstrings);
2105 memcpy(prog->strings, instrings, prog->progs_numstrings);
2106 prog->stringssize = prog->progs_numstrings;
2108 prog->numknownstrings = 0;
2109 prog->maxknownstrings = 0;
2110 prog->knownstrings = NULL;
2111 prog->knownstrings_flags = NULL;
2113 Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64);
2115 // we need to expand the globaldefs and fielddefs to include engine defs
2116 prog->globaldefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobaldefs + numrequiredglobals) * sizeof(mdef_t));
2117 prog->globals.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobals + requiredglobalspace + 2) * sizeof(prvm_vec_t));
2118 // + 2 is because of an otherwise occurring overrun in RETURN instruction
2119 // when trying to return the last or second-last global
2120 // (RETURN always returns a vector, there is no RETURN_F instruction)
2121 prog->fielddefs = (mdef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numfielddefs + numrequiredfields) * sizeof(mdef_t));
2122 // we need to convert the statements to our memory format
2123 prog->statements = (mstatement_t *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(mstatement_t));
2124 // allocate space for profiling statement usage
2125 prog->statement_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2126 prog->explicit_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile));
2127 // functions need to be converted to the memory format
2128 prog->functions = (mfunction_t *)Mem_Alloc(prog->progs_mempool, sizeof(mfunction_t) * prog->progs_numfunctions);
2130 for (i = 0;i < prog->progs_numfunctions;i++)
2132 prog->functions[i].first_statement = LittleLong(infunctions[i].first_statement);
2133 prog->functions[i].parm_start = LittleLong(infunctions[i].parm_start);
2134 prog->functions[i].s_name = LittleLong(infunctions[i].s_name);
2135 prog->functions[i].s_file = LittleLong(infunctions[i].s_file);
2136 prog->functions[i].numparms = LittleLong(infunctions[i].numparms);
2137 prog->functions[i].locals = LittleLong(infunctions[i].locals);
2138 memcpy(prog->functions[i].parm_size, infunctions[i].parm_size, sizeof(infunctions[i].parm_size));
2139 if(prog->functions[i].first_statement >= prog->numstatements)
2140 prog->error_cmd("PRVM_LoadProgs: out of bounds function statement (function %d) in %s", i, prog->name);
2141 // TODO bounds check parm_start, s_name, s_file, numparms, locals, parm_size
2144 // copy the globaldefs to the new globaldefs list
2147 case PROG_SECONDARYVERSION32:
2148 for (i=0 ; i<prog->numglobaldefs ; i++)
2150 prog->globaldefs[i].type = LittleLong(inglobaldefs32[i].type);
2151 prog->globaldefs[i].ofs = LittleLong(inglobaldefs32[i].ofs);
2152 prog->globaldefs[i].s_name = LittleLong(inglobaldefs32[i].s_name);
2153 // TODO bounds check ofs, s_name
2157 for (i=0 ; i<prog->numglobaldefs ; i++)
2159 prog->globaldefs[i].type = (unsigned short)LittleShort(inglobaldefs16[i].type);
2160 prog->globaldefs[i].ofs = (unsigned short)LittleShort(inglobaldefs16[i].ofs);
2161 prog->globaldefs[i].s_name = LittleLong(inglobaldefs16[i].s_name);
2162 // TODO bounds check ofs, s_name
2167 // append the required globals
2168 for (i = 0;i < numrequiredglobals;i++)
2170 prog->globaldefs[prog->numglobaldefs].type = required_global[i].type;
2171 prog->globaldefs[prog->numglobaldefs].ofs = prog->numglobals;
2172 prog->globaldefs[prog->numglobaldefs].s_name = PRVM_SetEngineString(prog, required_global[i].name);
2173 if (prog->globaldefs[prog->numglobaldefs].type == ev_vector)
2174 prog->numglobals += 3;
2177 prog->numglobaldefs++;
2180 // copy the progs fields to the new fields list
2183 case PROG_SECONDARYVERSION32:
2184 for (i = 0;i < prog->numfielddefs;i++)
2186 prog->fielddefs[i].type = LittleLong(infielddefs32[i].type);
2187 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2188 prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name);
2189 prog->fielddefs[i].ofs = LittleLong(infielddefs32[i].ofs);
2190 prog->fielddefs[i].s_name = LittleLong(infielddefs32[i].s_name);
2191 // TODO bounds check ofs, s_name
2195 for (i = 0;i < prog->numfielddefs;i++)
2197 prog->fielddefs[i].type = (unsigned short)LittleShort(infielddefs16[i].type);
2198 if (prog->fielddefs[i].type & DEF_SAVEGLOBAL)
2199 prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name);
2200 prog->fielddefs[i].ofs = (unsigned short)LittleShort(infielddefs16[i].ofs);
2201 prog->fielddefs[i].s_name = LittleLong(infielddefs16[i].s_name);
2202 // TODO bounds check ofs, s_name
2207 // append the required fields
2208 for (i = 0;i < numrequiredfields;i++)
2210 prog->fielddefs[prog->numfielddefs].type = required_field[i].type;
2211 prog->fielddefs[prog->numfielddefs].ofs = prog->entityfields;
2212 prog->fielddefs[prog->numfielddefs].s_name = PRVM_SetEngineString(prog, required_field[i].name);
2213 if (prog->fielddefs[prog->numfielddefs].type == ev_vector)
2214 prog->entityfields += 3;
2216 prog->entityfields++;
2217 prog->numfielddefs++;
2220 // LadyHavoc: TODO: reorder globals to match engine struct
2221 // LadyHavoc: TODO: reorder fields to match engine struct
2222 #define remapglobal(index) (index)
2223 #define remapfield(index) (index)
2226 // FIXME: LadyHavoc: this uses a crude way to identify integer constants, rather than checking for matching globaldefs and checking their type
2227 for (i = 0;i < prog->progs_numglobals;i++)
2229 u.i = LittleLong(inglobals[i]);
2230 // most globals are 0, we only need to deal with the ones that are not
2233 d = u.i & 0xFF800000;
2234 if ((d == 0xFF800000) || (d == 0))
2236 // Looks like an integer (expand to int64)
2237 prog->globals.ip[remapglobal(i)] = u.i;
2241 // Looks like a float (expand to double)
2242 prog->globals.fp[remapglobal(i)] = u.f;
2247 // copy, remap globals in statements, bounds check
2248 for (i = 0;i < prog->progs_numstatements;i++)
2252 case PROG_SECONDARYVERSION32:
2253 op = (opcode_t)LittleLong(instatements32[i].op);
2254 a = (unsigned int)LittleLong(instatements32[i].a);
2255 b = (unsigned int)LittleLong(instatements32[i].b);
2256 c = (unsigned int)LittleLong(instatements32[i].c);
2259 op = (opcode_t)LittleShort(instatements16[i].op);
2260 a = (unsigned short)LittleShort(instatements16[i].a);
2261 b = (unsigned short)LittleShort(instatements16[i].b);
2262 c = (unsigned short)LittleShort(instatements16[i].c);
2270 if (a >= prog->progs_numglobals || b + i < 0 || b + i >= prog->progs_numstatements)
2271 prog->error_cmd("PRVM_LoadProgs: out of bounds IF/IFNOT (statement %d) in %s", i, prog->name);
2272 prog->statements[i].op = op;
2273 prog->statements[i].operand[0] = remapglobal(a);
2274 prog->statements[i].operand[1] = -1;
2275 prog->statements[i].operand[2] = -1;
2276 prog->statements[i].jumpabsolute = i + b;
2280 if (a + i < 0 || a + i >= prog->progs_numstatements)
2281 prog->error_cmd("PRVM_LoadProgs: out of bounds GOTO (statement %d) in %s", i, prog->name);
2282 prog->statements[i].op = op;
2283 prog->statements[i].operand[0] = -1;
2284 prog->statements[i].operand[1] = -1;
2285 prog->statements[i].operand[2] = -1;
2286 prog->statements[i].jumpabsolute = i + a;
2289 Con_DPrintf("PRVM_LoadProgs: unknown opcode %d at statement %d in %s\n", (int)op, i, prog->name);
2291 //make sure its something well defined.
2292 prog->statements[i].op = OP_BOUNDCHECK;
2293 prog->statements[i].operand[0] = 0;
2294 prog->statements[i].operand[1] =
2295 prog->statements[i].operand[2] = op;
2296 prog->statements[i].jumpabsolute = -1;
2351 case OP_GSTOREP_ENT:
2352 case OP_GSTOREP_FLD:
2354 case OP_GSTOREP_FNC:
2356 // case OP_GADDRESS:
2366 // global global global
2401 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals || c >= prog->progs_numglobals)
2402 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d)", i);
2403 prog->statements[i].op = op;
2404 prog->statements[i].operand[0] = remapglobal(a);
2405 prog->statements[i].operand[1] = remapglobal(b);
2406 prog->statements[i].operand[2] = remapglobal(c);
2407 prog->statements[i].jumpabsolute = -1;
2409 // global none global
2415 if (a >= prog->progs_numglobals || c >= prog->progs_numglobals)
2416 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2417 prog->statements[i].op = op;
2418 prog->statements[i].operand[0] = remapglobal(a);
2419 prog->statements[i].operand[1] = -1;
2420 prog->statements[i].operand[2] = remapglobal(c);
2421 prog->statements[i].jumpabsolute = -1;
2429 if (c) //Spike -- DP is alergic to pointers in QC. Try to avoid too many nasty surprises.
2430 Con_DPrintf("PRVM_LoadProgs: storep-with-offset is not permitted in %s\n", prog->name);
2440 if (a >= prog->progs_numglobals || b >= prog->progs_numglobals)
2441 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2442 prog->statements[i].op = op;
2443 prog->statements[i].operand[0] = remapglobal(a);
2444 prog->statements[i].operand[1] = remapglobal(b);
2445 prog->statements[i].operand[2] = -1;
2446 prog->statements[i].jumpabsolute = -1;
2450 if ( a < prog->progs_numglobals)
2451 if ( prog->globals.ip[remapglobal(a)] >= 0 )
2452 if ( prog->globals.ip[remapglobal(a)] < prog->progs_numfunctions )
2453 if ( prog->functions[prog->globals.ip[remapglobal(a)]].first_statement == -642 )
2454 ++prog->numexplicitcoveragestatements;
2465 if ( a >= prog->progs_numglobals)
2466 prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name);
2467 if (b || c) //Spike -- added this check just as a diagnostic...
2468 Con_DPrintf("PRVM_LoadProgs: unexpected offset on call opcode in %s. Hexen2 format is not supported\n", prog->name);
2469 prog->statements[i].op = op;
2470 prog->statements[i].operand[0] = remapglobal(a);
2471 prog->statements[i].operand[1] = -1;
2472 prog->statements[i].operand[2] = -1;
2473 prog->statements[i].jumpabsolute = -1;
2477 if(prog->numstatements < 1)
2479 prog->error_cmd("PRVM_LoadProgs: empty program in %s", prog->name);
2481 else switch(prog->statements[prog->numstatements - 1].op)
2488 prog->error_cmd("PRVM_LoadProgs: program may fall off the edge (does not end with RETURN, GOTO or DONE) in %s", prog->name);
2492 // we're done with the file now
2494 Mem_Free(dprograms);
2497 // check required functions
2498 for(i=0 ; i < numrequiredfunc ; i++)
2499 if(PRVM_ED_FindFunction(prog, required_func[i]) == 0)
2500 prog->error_cmd("%s: %s not found in %s",prog->name, required_func[i], filename);
2502 PRVM_LoadLNO(prog, filename);
2504 PRVM_Init_Exec(prog);
2506 if(*prvm_language.string)
2507 // in CSQC we really shouldn't be able to change how stuff works... sorry for now
2508 // later idea: include a list of authorized .po file checksums with the csprogs
2510 qbool deftrans = prog == CLVM_prog;
2511 const char *realfilename = (prog != CLVM_prog ? filename : csqc_progname.string);
2512 if(deftrans) // once we have dotranslate_ strings, ALWAYS use the opt-in method!
2514 for (i=0 ; i<prog->numglobaldefs ; i++)
2517 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2518 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2519 if(name && !strncmp(name, "dotranslate_", 12))
2526 if(!strcmp(prvm_language.string, "dump"))
2528 qfile_t *f = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.pot", realfilename), "w", false);
2529 Con_Printf("Dumping to %s.pot\n", realfilename);
2532 for (i=0 ; i<prog->numglobaldefs ; i++)
2535 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2536 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2537 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2539 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2540 const char *value = PRVM_GetString(prog, val->string);
2543 char buf[MAX_INPUTLINE];
2544 PRVM_PO_UnparseString(buf, value, sizeof(buf));
2545 FS_Printf(f, "msgid \"%s\"\nmsgstr \"\"\n\n", buf);
2554 po_t *po = PRVM_PO_Load(
2555 va(vabuf, sizeof(vabuf), "%s.%s.po", realfilename, prvm_language.string),
2556 va(vabuf2, sizeof(vabuf2), "common.%s.po", prvm_language.string),
2557 prog->progs_mempool);
2560 for (i=0 ; i<prog->numglobaldefs ; i++)
2563 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2564 if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12)))
2565 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2567 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2568 const char *value = PRVM_GetString(prog, val->string);
2571 value = PRVM_PO_Lookup(po, value);
2573 val->string = PRVM_SetEngineString(prog, value);
2581 for (cvar = prog->console_cmd->cvars->vars; cvar; cvar = cvar->next)
2582 cvar->globaldefindex[prog - prvm_prog_list] = -1;
2584 for (i=0 ; i<prog->numglobaldefs ; i++)
2587 name = PRVM_GetString(prog, prog->globaldefs[i].s_name);
2588 //Con_Printf("found var %s\n", name);
2590 && !strncmp(name, "autocvar_", 9)
2591 && !(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z'))
2594 prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs);
2595 cvar = Cvar_FindVar(prog->console_cmd->cvars, name + 9, prog->console_cmd->cvars_flagsmask);
2596 //Con_Printf("PRVM_LoadProgs: autocvar global %s in %s, processing...\n", name, prog->name);
2603 Con_DPrintf("PRVM_LoadProgs: no cvar for autocvar global %s in %s, creating...\n", name, prog->name);
2604 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2607 if((float)((int)(val->_float)) == val->_float)
2608 dpsnprintf(buf, sizeof(buf), "%i", (int)(val->_float));
2613 for (int precision = 7; precision <= 9; ++precision) {
2614 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2615 if ((float)atof(buf) == f) {
2623 for (i = 0; i < 3; ++i)
2627 for (int precision = 7; precision <= 9; ++precision) {
2628 dpsnprintf(buf, sizeof(buf), "%.*g", precision, f);
2629 if ((float)atof(buf) == f) {
2630 prec[i] = precision;
2635 dpsnprintf(buf, sizeof(buf), "%.*g %.*g %.*g", prec[0], val->vector[0], prec[1], val->vector[1], prec[2], val->vector[2]);
2639 value = PRVM_GetString(prog, val->string);
2642 Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name);
2645 cvar = Cvar_Get(prog->console_cmd->cvars, name + 9, value, prog->console_cmd->cvars_flagsmask, NULL);
2646 if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string)
2648 val->string = PRVM_SetEngineString(prog, cvar->string);
2649 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2652 prog->error_cmd("PRVM_LoadProgs: could not create cvar for autocvar global %s in %s", name, prog->name);
2653 cvar->globaldefindex[prog - prvm_prog_list] = i;
2655 else if((cvar->flags & CF_PRIVATE) == 0)
2657 // MUST BE SYNCED WITH cvar.c Cvar_Set
2660 switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL)
2663 val->_float = cvar->value;
2667 VectorClear(val->vector);
2668 for (j = 0;j < 3;j++)
2670 while (*s && ISWHITESPACE(*s))
2674 val->vector[j] = atof(s);
2675 while (!ISWHITESPACE(*s))
2682 val->string = PRVM_SetEngineString(prog, cvar->string);
2683 cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string;
2686 Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name);
2689 cvar->globaldefindex[prog - prvm_prog_list] = i;
2692 Con_Printf("PRVM_LoadProgs: private cvar for autocvar global %s in %s\n", name, prog->name);
2698 prog->loaded = true;
2700 PRVM_UpdateBreakpoints(prog);
2702 // set flags & mdef_ts in prog
2706 PRVM_FindOffsets(prog);
2708 prog->init_cmd(prog);
2711 PRVM_MEM_Alloc(prog);
2713 Con_Printf("%s: program loaded (crc %i, size %iK)\n", prog->name, prog->filecrc, (int)(filesize/1024));
2715 // Inittime is at least the time when this function finished. However,
2716 // later events may bump it.
2717 prog->inittime = host.realtime;
2721 static void PRVM_Fields_f(cmd_state_t *cmd)
2724 int i, j, ednum, used, usedamount;
2726 char tempstring[MAX_INPUTLINE], tempstring2[260];
2736 Con_Print("no progs loaded\n");
2741 if(Cmd_Argc(cmd) != 2)
2743 Con_Print("prvm_fields <program name>\n");
2747 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2750 counts = (int *)Mem_Alloc(tempmempool, prog->numfielddefs * sizeof(int));
2751 for (ednum = 0;ednum < prog->max_edicts;ednum++)
2753 ed = PRVM_EDICT_NUM(ednum);
2756 for (i = 1;i < prog->numfielddefs;i++)
2758 d = &prog->fielddefs[i];
2759 name = PRVM_GetString(prog, d->s_name);
2760 if (name[strlen(name)-2] == '_')
2761 continue; // skip _x, _y, _z vars
2762 val = (prvm_eval_t *)(ed->fields.fp + d->ofs);
2763 // if the value is still all 0, skip the field
2764 for (j = 0;j < prvm_type_size[d->type & ~DEF_SAVEGLOBAL];j++)
2766 if (val->ivector[j])
2777 for (i = 0;i < prog->numfielddefs;i++)
2779 d = &prog->fielddefs[i];
2780 name = PRVM_GetString(prog, d->s_name);
2781 if (name[strlen(name)-2] == '_')
2782 continue; // skip _x, _y, _z vars
2783 switch(d->type & ~DEF_SAVEGLOBAL)
2786 dp_strlcat(tempstring, "string ", sizeof(tempstring));
2789 dp_strlcat(tempstring, "entity ", sizeof(tempstring));
2792 dp_strlcat(tempstring, "function ", sizeof(tempstring));
2795 dp_strlcat(tempstring, "field ", sizeof(tempstring));
2798 dp_strlcat(tempstring, "void ", sizeof(tempstring));
2801 dp_strlcat(tempstring, "float ", sizeof(tempstring));
2804 dp_strlcat(tempstring, "vector ", sizeof(tempstring));
2807 dp_strlcat(tempstring, "pointer ", sizeof(tempstring));
2810 dpsnprintf (tempstring2, sizeof(tempstring2), "bad type %i ", d->type & ~DEF_SAVEGLOBAL);
2811 dp_strlcat(tempstring, tempstring2, sizeof(tempstring));
2814 if (strlen(name) > sizeof(tempstring2)-4)
2816 memcpy (tempstring2, name, sizeof(tempstring2)-4);
2817 tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.';
2818 tempstring2[sizeof(tempstring2)-1] = 0;
2821 dp_strlcat(tempstring, name, sizeof(tempstring));
2822 for (j = (int)strlen(name);j < 25;j++)
2823 dp_strlcat(tempstring, " ", sizeof(tempstring));
2824 dpsnprintf(tempstring2, sizeof(tempstring2), "%5d", counts[i]);
2825 dp_strlcat(tempstring, tempstring2, sizeof(tempstring));
2826 dp_strlcat(tempstring, "\n", sizeof(tempstring));
2827 if (strlen(tempstring) >= sizeof(tempstring)/2)
2829 Con_Print(tempstring);
2835 usedamount += prvm_type_size[d->type & ~DEF_SAVEGLOBAL];
2839 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);
2842 static void PRVM_Globals_f(cmd_state_t *cmd)
2846 const char *wildcard;
2852 Con_Print("no progs loaded\n");
2855 if(Cmd_Argc (cmd) < 2 || Cmd_Argc(cmd) > 3)
2857 Con_Print("prvm_globals <program name> <optional name wildcard>\n");
2861 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2864 if( Cmd_Argc(cmd) == 3)
2865 wildcard = Cmd_Argv(cmd, 2);
2869 Con_Printf("%s :", prog->name);
2871 for (i = 0;i < prog->numglobaldefs;i++)
2874 if( !matchpattern( PRVM_GetString(prog, prog->globaldefs[i].s_name), wildcard, 1) )
2879 Con_Printf("%s\n", PRVM_GetString(prog, prog->globaldefs[i].s_name));
2881 Con_Printf("%i global variables, %i culled, totalling %i bytes\n", prog->numglobals, numculled, prog->numglobals * 4);
2889 static void PRVM_Global_f(cmd_state_t *cmd)
2893 char valuebuf[MAX_INPUTLINE];
2894 if( Cmd_Argc(cmd) != 3 ) {
2895 Con_Printf( "prvm_global <program name> <global name>\n" );
2899 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2902 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2904 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2906 Con_Printf( "%s: %s\n", Cmd_Argv(cmd, 2), PRVM_ValueString( prog, (etype_t)global->type, PRVM_GLOBALFIELDVALUE(global->ofs), valuebuf, sizeof(valuebuf) ) );
2914 static void PRVM_GlobalSet_f(cmd_state_t *cmd)
2918 if( Cmd_Argc(cmd) != 4 ) {
2919 Con_Printf( "prvm_globalset <program name> <global name> <value>\n" );
2923 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
2926 global = PRVM_ED_FindGlobal( prog, Cmd_Argv(cmd, 2) );
2928 Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(cmd, 2), Cmd_Argv(cmd, 1) );
2930 PRVM_ED_ParseEpair( prog, NULL, global, Cmd_Argv(cmd, 3), true );
2934 ======================
2935 Break- and Watchpoints
2936 ======================
2940 char break_statement[256];
2941 char watch_global[256];
2943 char watch_field[256];
2946 static debug_data_t debug_data[PRVM_PROG_MAX];
2948 void PRVM_Breakpoint(prvm_prog_t *prog, int stack_index, const char *text)
2951 Con_Printf("PRVM_Breakpoint: %s\n", text);
2952 PRVM_PrintState(prog, stack_index);
2953 if (prvm_breakpointdump.integer)
2954 SV_Savegame_to(prog, va(vabuf, sizeof(vabuf), "breakpoint-%s.dmp", prog->name));
2957 void PRVM_Watchpoint(prvm_prog_t *prog, int stack_index, const char *text, etype_t type, prvm_eval_t *o, prvm_eval_t *n)
2959 size_t sz = sizeof(prvm_vec_t) * ((type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
2960 if (memcmp(o, n, sz))
2963 char valuebuf_o[128];
2964 char valuebuf_n[128];
2965 PRVM_UglyValueString(prog, type, o, valuebuf_o, sizeof(valuebuf_o));
2966 PRVM_UglyValueString(prog, type, n, valuebuf_n, sizeof(valuebuf_n));
2967 dpsnprintf(buf, sizeof(buf), "%s: %s -> %s", text, valuebuf_o, valuebuf_n);
2968 PRVM_Breakpoint(prog, stack_index, buf);
2973 static void PRVM_UpdateBreakpoints(prvm_prog_t *prog)
2975 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
2978 if (debug->break_statement[0])
2980 if (debug->break_statement[0] >= '0' && debug->break_statement[0] <= '9')
2982 prog->break_statement = atoi(debug->break_statement);
2983 prog->break_stack_index = 0;
2988 func = PRVM_ED_FindFunction (prog, debug->break_statement);
2991 Con_Printf("%s progs: no function or statement named %s to break on!\n", prog->name, debug->break_statement);
2992 prog->break_statement = -1;
2996 prog->break_statement = func->first_statement;
2997 prog->break_stack_index = 1;
3000 if (prog->break_statement >= -1)
3001 Con_Printf("%s progs: breakpoint is at statement %d\n", prog->name, prog->break_statement);
3004 prog->break_statement = -1;
3006 if (debug->watch_global[0])
3008 mdef_t *global = PRVM_ED_FindGlobal( prog, debug->watch_global );
3011 Con_Printf( "%s progs: no global named '%s' to watch!\n", prog->name, debug->watch_global );
3012 prog->watch_global_type = ev_void;
3016 size_t sz = sizeof(prvm_vec_t) * ((global->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3017 prog->watch_global = global->ofs;
3018 prog->watch_global_type = (etype_t)global->type;
3019 memcpy(&prog->watch_global_value, PRVM_GLOBALFIELDVALUE(prog->watch_global), sz);
3021 if (prog->watch_global_type != ev_void)
3022 Con_Printf("%s progs: global watchpoint is at global index %d\n", prog->name, prog->watch_global);
3025 prog->watch_global_type = ev_void;
3027 if (debug->watch_field[0])
3029 mdef_t *field = PRVM_ED_FindField( prog, debug->watch_field );
3032 Con_Printf( "%s progs: no field named '%s' to watch!\n", prog->name, debug->watch_field );
3033 prog->watch_field_type = ev_void;
3037 size_t sz = sizeof(prvm_vec_t) * ((field->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
3038 prog->watch_edict = debug->watch_edict;
3039 prog->watch_field = field->ofs;
3040 prog->watch_field_type = (etype_t)field->type;
3041 if (prog->watch_edict < prog->num_edicts)
3042 memcpy(&prog->watch_edictfield_value, PRVM_EDICTFIELDVALUE(PRVM_EDICT_NUM(prog->watch_edict), prog->watch_field), sz);
3044 memset(&prog->watch_edictfield_value, 0, sz);
3046 if (prog->watch_edict != ev_void)
3047 Con_Printf("%s progs: edict field watchpoint is at edict %d field index %d\n", prog->name, prog->watch_edict, prog->watch_field);
3050 prog->watch_field_type = ev_void;
3053 static void PRVM_Breakpoint_f(cmd_state_t *cmd)
3057 if( Cmd_Argc(cmd) == 2 ) {
3058 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3061 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3062 debug->break_statement[0] = 0;
3064 PRVM_UpdateBreakpoints(prog);
3067 if( Cmd_Argc(cmd) != 3 ) {
3068 Con_Printf( "prvm_breakpoint <program name> <function name | statement>\n" );
3072 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3076 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3077 dp_strlcpy(debug->break_statement, Cmd_Argv(cmd, 2), sizeof(debug->break_statement));
3079 PRVM_UpdateBreakpoints(prog);
3082 static void PRVM_GlobalWatchpoint_f(cmd_state_t *cmd)
3086 if( Cmd_Argc(cmd) == 2 ) {
3087 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3090 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3091 debug->watch_global[0] = 0;
3093 PRVM_UpdateBreakpoints(prog);
3096 if( Cmd_Argc(cmd) != 3 ) {
3097 Con_Printf( "prvm_globalwatchpoint <program name> <global name>\n" );
3101 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3105 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3106 dp_strlcpy(debug->watch_global, Cmd_Argv(cmd, 2), sizeof(debug->watch_global));
3108 PRVM_UpdateBreakpoints(prog);
3111 static void PRVM_EdictWatchpoint_f(cmd_state_t *cmd)
3115 if( Cmd_Argc(cmd) == 2 ) {
3116 if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(cmd, 1))))
3119 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3120 debug->watch_field[0] = 0;
3122 PRVM_UpdateBreakpoints(prog);
3125 if( Cmd_Argc(cmd) != 4 ) {
3126 Con_Printf( "prvm_edictwatchpoint <program name> <edict number> <field name>\n" );
3130 if (!(prog = PRVM_ProgFromString(Cmd_Argv(cmd, 1))))
3134 debug_data_t *debug = &debug_data[prog - prvm_prog_list];
3135 debug->watch_edict = atoi(Cmd_Argv(cmd, 2));
3136 dp_strlcpy(debug->watch_field, Cmd_Argv(cmd, 3), sizeof(debug->watch_field));
3138 PRVM_UpdateBreakpoints(prog);
3146 void PRVM_Init (void)
3150 Cmd_AddCommand(CF_SHARED, "prvm_edict", PRVM_ED_PrintEdict_f, "print all data about an entity number in the selected VM (server, client, menu)");
3151 Cmd_AddCommand(CF_SHARED, "prvm_edicts", PRVM_ED_PrintEdicts_f, "prints all data about all entities in the selected VM (server, client, menu)");
3152 Cmd_AddCommand(CF_SHARED, "prvm_edictcount", PRVM_ED_Count_f, "prints number of active entities in the selected VM (server, client, menu)");
3153 Cmd_AddCommand(CF_SHARED, "prvm_profile", PRVM_Profile_f, "prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu)");
3154 Cmd_AddCommand(CF_SHARED, "prvm_childprofile", PRVM_ChildProfile_f, "prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu), sorted by time taken in function with child calls");
3155 Cmd_AddCommand(CF_SHARED, "prvm_callprofile", PRVM_CallProfile_f, "prints execution statistics about the most time consuming QuakeC calls from the engine in the selected VM (server, client, menu)");
3156 Cmd_AddCommand(CF_SHARED, "prvm_fields", PRVM_Fields_f, "prints usage statistics on properties (how many entities have non-zero values) in the selected VM (server, client, menu)");
3157 Cmd_AddCommand(CF_SHARED, "prvm_globals", PRVM_Globals_f, "prints all global variables in the selected VM (server, client, menu)");
3158 Cmd_AddCommand(CF_SHARED, "prvm_global", PRVM_Global_f, "prints value of a specified global variable in the selected VM (server, client, menu)");
3159 Cmd_AddCommand(CF_SHARED, "prvm_globalset", PRVM_GlobalSet_f, "sets value of a specified global variable in the selected VM (server, client, menu)");
3160 Cmd_AddCommand(CF_SHARED, "prvm_edictset", PRVM_ED_EdictSet_f, "changes value of a specified property of a specified entity in the selected VM (server, client, menu)");
3161 Cmd_AddCommand(CF_SHARED, "prvm_edictget", PRVM_ED_EdictGet_f, "retrieves the value of a specified property of a specified entity in the selected VM (server, client menu) into a cvar or to the console");
3162 Cmd_AddCommand(CF_SHARED, "prvm_globalget", PRVM_ED_GlobalGet_f, "retrieves the value of a specified global variable in the selected VM (server, client menu) into a cvar or to the console");
3163 Cmd_AddCommand(CF_SHARED, "prvm_printfunction", PRVM_PrintFunction_f, "prints a disassembly (QuakeC instructions) of the specified function in the selected VM (server, client, menu)");
3164 Cmd_AddCommand(CF_SHARED, "cl_cmd", PRVM_GameCommand_Client_f, "calls the client QC function GameCommand with the supplied string as argument");
3165 Cmd_AddCommand(CF_SHARED, "menu_cmd", PRVM_GameCommand_Menu_f, "calls the menu QC function GameCommand with the supplied string as argument");
3166 Cmd_AddCommand(CF_SHARED, "sv_cmd", PRVM_GameCommand_Server_f, "calls the server QC function GameCommand with the supplied string as argument");
3167 Cmd_AddCommand(CF_SHARED, "prvm_breakpoint", PRVM_Breakpoint_f, "marks a statement or function as breakpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear breakpoint");
3168 Cmd_AddCommand(CF_SHARED, "prvm_globalwatchpoint", PRVM_GlobalWatchpoint_f, "marks a global as watchpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear watchpoint");
3169 Cmd_AddCommand(CF_SHARED, "prvm_edictwatchpoint", PRVM_EdictWatchpoint_f, "marks an entity field as watchpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear watchpoint");
3171 Cvar_RegisterVariable (&prvm_language);
3172 Cvar_RegisterVariable (&prvm_traceqc);
3173 Cvar_RegisterVariable (&prvm_statementprofiling);
3174 Cvar_RegisterVariable (&prvm_timeprofiling);
3175 Cvar_RegisterVariable (&prvm_coverage);
3176 Cvar_RegisterVariable (&prvm_backtraceforwarnings);
3177 Cvar_RegisterVariable (&prvm_leaktest);
3178 Cvar_RegisterVariable (&prvm_leaktest_follow_targetname);
3179 Cvar_RegisterVariable (&prvm_leaktest_ignore_classnames);
3180 Cvar_RegisterVariable (&prvm_errordump);
3181 Cvar_RegisterVariable (&prvm_breakpointdump);
3182 Cvar_RegisterVariable (&prvm_reuseedicts_startuptime);
3183 Cvar_RegisterVariable (&prvm_reuseedicts_neverinsameframe);
3184 Cvar_RegisterVariable (&prvm_garbagecollection_enable);
3185 Cvar_RegisterVariable (&prvm_garbagecollection_notify);
3186 Cvar_RegisterVariable (&prvm_garbagecollection_scan_limit);
3187 Cvar_RegisterVariable (&prvm_garbagecollection_strings);
3188 Cvar_RegisterVariable (&prvm_stringdebug);
3190 // COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!)
3191 prvm_runawaycheck = !Sys_CheckParm("-norunaway");
3195 // LadyHavoc: report supported extensions
3196 Con_DPrintf("\nQuakeC extensions for server and client:");
3197 for (i = 0; vm_sv_extensions[i]; i++)
3198 Con_DPrintf(" %s", vm_sv_extensions[i]);
3201 Con_DPrintf("\nQuakeC extensions for menu:");
3202 for (i = 0; vm_m_extensions[i]; i++)
3203 Con_DPrintf(" %s", vm_m_extensions[i]);
3213 void PRVM_Prog_Init(prvm_prog_t *prog, cmd_state_t *cmd)
3215 PRVM_Prog_Reset(prog);
3216 prog->leaktest_active = prvm_leaktest.integer != 0;
3217 prog->console_cmd = cmd;
3220 // LadyHavoc: turned PRVM_EDICT_NUM into a #define for speed reasons
3221 unsigned int PRVM_EDICT_NUM_ERROR(prvm_prog_t *prog, unsigned int n, const char *filename, int fileline)
3223 prog->error_cmd("PRVM_EDICT_NUM: %s: bad number %i (called at %s:%i)", prog->name, n, filename, fileline);
3227 #define PRVM_KNOWNSTRINGBASE 0x40000000
3229 const char *PRVM_GetString(prvm_prog_t *prog, int num)
3234 if (prvm_stringdebug.integer)
3235 VM_Warning(prog, "PRVM_GetString: Invalid string offset (%i < 0)\n", num);
3238 else if (num < prog->stringssize)
3240 // constant string from progs.dat
3241 return prog->strings + num;
3243 else if (num <= prog->stringssize + prog->tempstringsbuf.maxsize)
3245 // tempstring returned by engine to QC (becomes invalid after returning to engine)
3246 num -= prog->stringssize;
3247 if (num < prog->tempstringsbuf.cursize)
3248 return (char *)prog->tempstringsbuf.data + num;
3251 if (prvm_stringdebug.integer)
3252 VM_Warning(prog, "PRVM_GetString: Invalid temp-string offset (%i >= %i prog->tempstringsbuf.cursize)\n", num, prog->tempstringsbuf.cursize);
3256 else if (num & PRVM_KNOWNSTRINGBASE)
3259 num = num - PRVM_KNOWNSTRINGBASE;
3260 if (num >= 0 && num < prog->numknownstrings)
3262 if (!prog->knownstrings[num])
3264 if (prvm_stringdebug.integer)
3265 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num);
3268 // refresh the garbage collection on the string - this guards
3269 // against a certain sort of repeated migration to earlier
3270 // points in the scan that could otherwise result in the string
3271 // being freed for being unused
3272 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] & ~KNOWNSTRINGFLAG_GCPRUNE) | KNOWNSTRINGFLAG_GCMARK;
3273 return prog->knownstrings[num];
3277 if (prvm_stringdebug.integer)
3278 VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, prog->numknownstrings);
3284 // invalid string offset
3285 if (prvm_stringdebug.integer)
3286 VM_Warning(prog, "PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, prog->stringssize);
3291 const char *PRVM_ChangeEngineString(prvm_prog_t *prog, int i, const char *s)
3294 i = i - PRVM_KNOWNSTRINGBASE;
3295 if (i < 0 || i >= prog->numknownstrings)
3296 prog->error_cmd("PRVM_ChangeEngineString: string index %i is out of bounds", i);
3297 else if ((prog->knownstrings_flags[i] & KNOWNSTRINGFLAG_ENGINE) == 0)
3298 prog->error_cmd("PRVM_ChangeEngineString: string index %i is not an engine string", i);
3299 old = prog->knownstrings[i];
3300 prog->knownstrings[i] = s;
3304 static void PRVM_NewKnownString(prvm_prog_t *prog, int i, int flags, const char *s)
3306 if (i >= prog->numknownstrings)
3308 if (i >= prog->maxknownstrings)
3310 const char **oldstrings = prog->knownstrings;
3311 const unsigned char *oldstrings_flags = prog->knownstrings_flags;
3312 const char **oldstrings_origin = prog->knownstrings_origin;
3313 prog->maxknownstrings += 128;
3314 prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3315 prog->knownstrings_flags = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char));
3316 if (prog->leaktest_active)
3317 prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *));
3318 if (prog->numknownstrings)
3320 memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *));
3321 memcpy((char **)prog->knownstrings_flags, oldstrings_flags, prog->numknownstrings * sizeof(unsigned char));
3322 if (prog->leaktest_active)
3323 memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *));
3326 prog->numknownstrings++;
3328 prog->firstfreeknownstring = i + 1;
3329 prog->knownstrings[i] = s;
3330 // it's in use right now, spare it until the next gc pass - that said, it is not freeable so this is probably moot
3331 prog->knownstrings_flags[i] = flags;
3332 if (prog->leaktest_active)
3333 prog->knownstrings_origin[i] = NULL;
3336 int PRVM_SetEngineString(prvm_prog_t *prog, const char *s)
3341 if (s >= prog->strings && s <= prog->strings + prog->stringssize)
3342 prog->error_cmd("PRVM_SetEngineString: s in prog->strings area");
3343 // if it's in the tempstrings area, use a reserved range
3344 // (otherwise we'd get millions of useless string offsets cluttering the database)
3345 if (s >= (char *)prog->tempstringsbuf.data && s < (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.maxsize)
3346 return prog->stringssize + (s - (char *)prog->tempstringsbuf.data);
3347 // see if it's a known string address
3348 for (i = 0;i < prog->numknownstrings;i++)
3349 if (prog->knownstrings[i] == s)
3350 return PRVM_KNOWNSTRINGBASE + i;
3351 // new unknown engine string
3352 if (developer_insane.integer)
3353 Con_DPrintf("new engine string %p = \"%s\"\n", (void *)s, s);
3354 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3355 if (!prog->knownstrings[i])
3357 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE, s);
3358 return PRVM_KNOWNSTRINGBASE + i;
3361 // temp string handling
3363 // all tempstrings go into this buffer consecutively, and it is reset
3364 // whenever PRVM_ExecuteProgram returns to the engine
3365 // (technically each PRVM_ExecuteProgram call saves the cursize value and
3366 // restores it on return, so multiple recursive calls can share the same
3368 // the buffer size is automatically grown as needed
3370 int PRVM_SetTempString(prvm_prog_t *prog, const char *s)
3376 size = (int)strlen(s) + 1;
3377 if (developer_insane.integer)
3378 Con_DPrintf("PRVM_SetTempString %s: cursize %i, size %i\n", prog->name, prog->tempstringsbuf.cursize, size);
3379 if (prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3381 sizebuf_t old = prog->tempstringsbuf;
3382 if (prog->tempstringsbuf.cursize + size >= 1<<28)
3383 prog->error_cmd("PRVM_SetTempString %s: ran out of tempstring memory! (refusing to grow tempstring buffer over 256MB, cursize %i, size %i)\n", prog->name, prog->tempstringsbuf.cursize, size);
3384 prog->tempstringsbuf.maxsize = max(prog->tempstringsbuf.maxsize, 65536);
3385 while (prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size)
3386 prog->tempstringsbuf.maxsize *= 2;
3387 if (prog->tempstringsbuf.maxsize != old.maxsize || prog->tempstringsbuf.data == NULL)
3389 Con_DPrintf("PRVM_SetTempString %s: enlarging tempstrings buffer (%iKB -> %iKB)\n", prog->name, old.maxsize/1024, prog->tempstringsbuf.maxsize/1024);
3390 prog->tempstringsbuf.data = (unsigned char *) Mem_Alloc(prog->progs_mempool, prog->tempstringsbuf.maxsize);
3394 memcpy(prog->tempstringsbuf.data, old.data, old.cursize);
3399 t = (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.cursize;
3401 prog->tempstringsbuf.cursize += size;
3402 return PRVM_SetEngineString(prog, t);
3405 int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer)
3415 for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++)
3416 if (!prog->knownstrings[i])
3418 s = (char *)PRVM_Alloc(bufferlength);
3419 PRVM_NewKnownString(prog, i, KNOWNSTRINGFLAG_GCMARK, s);
3420 if(prog->leaktest_active)
3421 prog->knownstrings_origin[i] = PRVM_AllocationOrigin(prog);
3423 *pointer = (char *)(prog->knownstrings[i]);
3424 return PRVM_KNOWNSTRINGBASE + i;
3427 void PRVM_FreeString(prvm_prog_t *prog, int num)
3430 prog->error_cmd("PRVM_FreeString %s: attempt to free a NULL string", prog->name);
3431 else if (num >= 0 && num < prog->stringssize)
3432 prog->error_cmd("PRVM_FreeString %s: attempt to free a constant string", prog->name);
3433 else if (num >= PRVM_KNOWNSTRINGBASE && num < PRVM_KNOWNSTRINGBASE + prog->numknownstrings)
3435 num = num - PRVM_KNOWNSTRINGBASE;
3436 if (!prog->knownstrings[num])
3437 prog->error_cmd("PRVM_FreeString %s: attempt to free a non-existent or already freed string", prog->name);
3438 if (!prog->knownstrings_flags[num])
3439 prog->error_cmd("PRVM_FreeString %s: attempt to free a string owned by the engine", prog->name);
3440 PRVM_Free((char *)prog->knownstrings[num]);
3441 if(prog->leaktest_active)
3442 if(prog->knownstrings_origin[num])
3443 PRVM_Free((char *)prog->knownstrings_origin[num]);
3444 prog->knownstrings[num] = NULL;
3445 prog->knownstrings_flags[num] = 0;
3446 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3449 prog->error_cmd("PRVM_FreeString %s: invalid string offset %i", prog->name, num);
3452 static qbool PRVM_IsStringReferenced(prvm_prog_t *prog, string_t string)
3456 for (i = 0;i < prog->numglobaldefs;i++)
3458 mdef_t *d = &prog->globaldefs[i];
3459 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3461 if(string == PRVM_GLOBALFIELDSTRING(d->ofs))
3465 for(j = 0; j < prog->num_edicts; ++j)
3467 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3470 for (i=0; i<prog->numfielddefs; ++i)
3472 mdef_t *d = &prog->fielddefs[i];
3473 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string)
3475 if(string == PRVM_EDICTFIELDSTRING(ed, d->ofs))
3483 static qbool PRVM_IsEdictRelevant(prvm_prog_t *prog, prvm_edict_t *edict)
3487 if(PRVM_NUM_FOR_EDICT(edict) <= prog->reserved_edicts)
3488 return true; // world or clients
3489 if (edict->freetime <= prog->inittime)
3490 return true; // created during startup
3491 if (prog == SVVM_prog)
3493 if(PRVM_serveredictfloat(edict, solid)) // can block other stuff, or is a trigger?
3495 if(PRVM_serveredictfloat(edict, modelindex)) // visible ent?
3497 if(PRVM_serveredictfloat(edict, effects)) // particle effect?
3499 if(PRVM_serveredictfunction(edict, think)) // has a think function?
3500 if(PRVM_serveredictfloat(edict, nextthink) > 0) // that actually will eventually run?
3502 if(PRVM_serveredictfloat(edict, takedamage))
3504 if(*prvm_leaktest_ignore_classnames.string)
3506 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_serveredictstring(edict, classname)))))
3510 else if (prog == CLVM_prog)
3512 // TODO someone add more stuff here
3513 if(PRVM_clientedictfloat(edict, entnum)) // csqc networked
3515 if(PRVM_clientedictfloat(edict, modelindex)) // visible ent?
3517 if(PRVM_clientedictfloat(edict, effects)) // particle effect?
3519 if(PRVM_clientedictfunction(edict, think)) // has a think function?
3520 if(PRVM_clientedictfloat(edict, nextthink) > 0) // that actually will eventually run?
3522 if(*prvm_leaktest_ignore_classnames.string)
3524 if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_clientedictstring(edict, classname)))))
3530 // menu prog does not have classnames
3535 static qbool PRVM_IsEdictReferenced(prvm_prog_t *prog, prvm_edict_t *edict, int mark)
3538 int edictnum = PRVM_NUM_FOR_EDICT(edict);
3539 const char *targetname = NULL;
3541 if (prog == SVVM_prog && prvm_leaktest_follow_targetname.integer)
3542 targetname = PRVM_GetString(prog, PRVM_serveredictstring(edict, targetname));
3545 if(!*targetname) // ""
3548 for(j = 0; j < prog->num_edicts; ++j)
3550 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3551 if (ed->priv.required->mark < mark)
3557 const char *target = PRVM_GetString(prog, PRVM_serveredictstring(ed, target));
3559 if(!strcmp(target, targetname))
3562 for (i=0; i<prog->numfielddefs; ++i)
3564 mdef_t *d = &prog->fielddefs[i];
3565 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3567 if(edictnum == PRVM_EDICTFIELDEDICT(ed, d->ofs))
3575 static void PRVM_MarkReferencedEdicts(prvm_prog_t *prog)
3581 // Stage 1: world, all entities that are relevant, and all entities that are referenced by globals.
3583 for(j = 0; j < prog->num_edicts; ++j)
3585 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3588 ed->priv.required->mark = PRVM_IsEdictRelevant(prog, ed) ? stage : 0;
3590 for (i = 0;i < prog->numglobaldefs;i++)
3592 mdef_t *d = &prog->globaldefs[i];
3594 if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity)
3596 j = PRVM_GLOBALFIELDEDICT(d->ofs);
3597 if (i < 0 || j >= prog->max_edicts) {
3598 Con_Printf("Invalid entity reference from global %s.\n", PRVM_GetString(prog, d->s_name));
3601 ed = PRVM_EDICT_NUM(j);;
3602 ed->priv.required->mark = stage;
3605 // Future stages: all entities that are referenced by an entity of the previous stage.
3609 for(j = 0; j < prog->num_edicts; ++j)
3611 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3614 if(ed->priv.required->mark)
3616 if(PRVM_IsEdictReferenced(prog, ed, stage))
3618 ed->priv.required->mark = stage + 1;
3625 Con_DPrintf("leak check used %d stages to find all references\n", stage);
3628 void PRVM_LeakTest(prvm_prog_t *prog)
3631 qbool leaked = false;
3633 if(!prog->leaktest_active)
3637 for (i = 0; i < prog->numknownstrings; ++i)
3639 if(prog->knownstrings[i])
3640 if(prog->knownstrings_flags[i])
3641 if(prog->knownstrings_origin[i])
3642 if(!PRVM_IsStringReferenced(prog, PRVM_KNOWNSTRINGBASE + i))
3644 Con_Printf("Unreferenced string found!\n Value: %s\n Origin: %s\n", prog->knownstrings[i], prog->knownstrings_origin[i]);
3650 PRVM_MarkReferencedEdicts(prog);
3651 for(j = 0; j < prog->num_edicts; ++j)
3653 prvm_edict_t *ed = PRVM_EDICT_NUM(j);
3656 if(!ed->priv.required->mark)
3657 if(ed->priv.required->allocation_origin)
3659 Con_Printf("Unreferenced edict found!\n Allocated at: %s\n", ed->priv.required->allocation_origin);
3660 PRVM_ED_Print(prog, ed, NULL);
3665 ed->priv.required->mark = 0; // clear marks again when done
3668 for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); ++i)
3670 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
3672 if(stringbuffer->origin)
3674 Con_Printf("Open string buffer handle found!\n Allocated at: %s\n", stringbuffer->origin);
3679 for(i = 0; i < PRVM_MAX_OPENFILES; ++i)
3681 if(prog->openfiles[i])
3682 if(prog->openfiles_origin[i])
3684 Con_Printf("Open file handle found!\n Allocated at: %s\n", prog->openfiles_origin[i]);
3689 for(i = 0; i < PRVM_MAX_OPENSEARCHES; ++i)
3691 if(prog->opensearches[i])
3692 if(prog->opensearches_origin[i])
3694 Con_Printf("Open search handle found!\n Allocated at: %s\n", prog->opensearches_origin[i]);
3700 Con_Printf("Congratulations. No leaks found.\n");
3703 void PRVM_GarbageCollection(prvm_prog_t *prog)
3705 int limit = prvm_garbagecollection_scan_limit.integer;
3706 prvm_prog_garbagecollection_state_t *gc = &prog->gc;
3707 if (!prvm_garbagecollection_enable.integer)
3710 // we like to limit how much scanning we do so it doesn't put a significant
3711 // burden on the cpu, so each of these are not complete scans, we also like
3712 // to have consistent cpu usage so we do a bit of work on each category of
3713 // leaked object every frame
3719 case PRVM_GC_GLOBALS_MARK:
3720 for (; gc->globals_mark_progress < prog->numglobaldefs && (limit--) > 0; gc->globals_mark_progress++)
3722 mdef_t *d = &prog->globaldefs[gc->globals_mark_progress];
3727 prvm_int_t s = prog->globals.ip[d->ofs];
3728 if (s & PRVM_KNOWNSTRINGBASE)
3730 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3731 if (!prog->knownstrings[num])
3734 Con_DPrintf("PRVM_GarbageCollection: Found bogus strzone reference in global %i (global name: \"%s\"), erasing reference", d->ofs, PRVM_GetString(prog, d->s_name));
3735 prog->globals.ip[d->ofs] = 0;
3738 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3746 if (gc->globals_mark_progress >= prog->numglobaldefs)
3749 case PRVM_GC_FIELDS_MARK:
3750 for (; gc->fields_mark_progress < prog->numfielddefs && limit > 0;)
3752 mdef_t *d = &prog->fielddefs[gc->fields_mark_progress];
3756 //for (gc-> entityindex = 0; entityindex < prog->num_edicts; entityindex++)
3757 for (;gc->fields_mark_progress_entity < prog->num_edicts && (limit--) > 0;gc->fields_mark_progress_entity++)
3759 int entityindex = gc->fields_mark_progress_entity;
3760 prvm_int_t s = prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs];
3761 if (s & PRVM_KNOWNSTRINGBASE)
3763 prvm_int_t num = s - PRVM_KNOWNSTRINGBASE;
3764 if (!prog->knownstrings[num])
3767 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));
3768 prog->edictsfields.ip[entityindex * prog->entityfields + d->ofs] = 0;
3771 prog->knownstrings_flags[num] = (prog->knownstrings_flags[num] | KNOWNSTRINGFLAG_GCMARK) & ~KNOWNSTRINGFLAG_GCPRUNE;
3774 if (gc->fields_mark_progress_entity >= prog->num_edicts)
3776 gc->fields_mark_progress_entity = 0;
3777 gc->fields_mark_progress++;
3781 gc->fields_mark_progress_entity = 0;
3782 gc->fields_mark_progress++;
3786 if (gc->fields_mark_progress >= prog->numfielddefs)
3789 case PRVM_GC_KNOWNSTRINGS_SWEEP:
3790 // free any strzone'd strings that are not marked
3791 if (!prvm_garbagecollection_strings.integer)
3796 for (;gc->knownstrings_sweep_progress < prog->numknownstrings && (limit--) > 0;gc->knownstrings_sweep_progress++)
3798 int num = gc->knownstrings_sweep_progress;
3799 if (prog->knownstrings[num] && (prog->knownstrings_flags[num] & (KNOWNSTRINGFLAG_GCMARK | KNOWNSTRINGFLAG_ENGINE)) == 0)
3801 if (prog->knownstrings_flags[num] & KNOWNSTRINGFLAG_GCPRUNE)
3803 // string has been marked for pruning two passes in a row
3804 if (prvm_garbagecollection_notify.integer)
3805 Con_DPrintf("prvm_garbagecollection_notify: %s: freeing unreferenced string %i: \"%s\"\n", prog->name, num, prog->knownstrings[num]);
3806 Mem_Free((char *)prog->knownstrings[num]);
3807 prog->knownstrings[num] = NULL;
3808 prog->knownstrings_flags[num] = 0;
3809 prog->firstfreeknownstring = min(prog->firstfreeknownstring, num);
3813 // mark it for pruning next pass
3814 prog->knownstrings_flags[num] |= KNOWNSTRINGFLAG_GCPRUNE;
3818 if (gc->knownstrings_sweep_progress >= prog->numknownstrings)
3823 memset(gc, 0, sizeof(*gc));