+// #229 float(string s1, string s2) strcasecmp (FTE_STRINGS)
+// #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS)
+void VM_strncasecmp (prvm_prog_t *prog)
+{
+ const char *s1, *s2;
+ VM_SAFEPARMCOUNTRANGE(2, 3, VM_strncasecmp);
+ s1 = PRVM_G_STRING(OFS_PARM0);
+ s2 = PRVM_G_STRING(OFS_PARM1);
+ if (prog->argc > 2)
+ {
+ PRVM_G_FLOAT(OFS_RETURN) = strncasecmp(s1, s2, (size_t)PRVM_G_FLOAT(OFS_PARM2));
+ }
+ else
+ {
+ PRVM_G_FLOAT(OFS_RETURN) = strcasecmp(s1, s2);
+ }
+}
+
+// #494 float(float caseinsensitive, string s, ...) crc16
+void VM_crc16(prvm_prog_t *prog)
+{
+ float insensitive;
+ char s[VM_STRINGTEMP_LENGTH];
+ VM_SAFEPARMCOUNTRANGE(2, 8, VM_crc16);
+ insensitive = PRVM_G_FLOAT(OFS_PARM0);
+ VM_VarString(prog, 1, s, sizeof(s));
+ PRVM_G_FLOAT(OFS_RETURN) = (unsigned short) ((insensitive ? CRC_Block_CaseInsensitive : CRC_Block) ((unsigned char *) s, strlen(s)));
+}
+
+// #639 float(string digest, string data, ...) digest_hex
+void VM_digest_hex(prvm_prog_t *prog)
+{
+ const char *digest;
+
+ char out[32];
+ char outhex[65];
+ int outlen;
+
+ char s[VM_STRINGTEMP_LENGTH];
+ int len;
+
+ VM_SAFEPARMCOUNTRANGE(2, 8, VM_digest_hex);
+ digest = PRVM_G_STRING(OFS_PARM0);
+ if(!digest)
+ digest = "";
+ VM_VarString(prog, 1, s, sizeof(s));
+ len = (int)strlen(s);
+
+ outlen = 0;
+
+ if(!strcmp(digest, "MD4"))
+ {
+ outlen = 16;
+ mdfour((unsigned char *) out, (unsigned char *) s, len);
+ }
+ else if(!strcmp(digest, "SHA256") && Crypto_Available())
+ {
+ outlen = 32;
+ sha256((unsigned char *) out, (unsigned char *) s, len);
+ }
+ // no warning needed on mismatch - we return string_null to QC
+
+ if(outlen)
+ {
+ int i;
+ static const char *hexmap = "0123456789abcdef";
+ for(i = 0; i < outlen; ++i)
+ {
+ outhex[2*i] = hexmap[(out[i] >> 4) & 15];
+ outhex[2*i+1] = hexmap[(out[i] >> 0) & 15];
+ }
+ outhex[2*i] = 0;
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, outhex);
+ }
+ else
+ PRVM_G_INT(OFS_RETURN) = 0;
+}
+
+void VM_wasfreed (prvm_prog_t *prog)
+{
+ VM_SAFEPARMCOUNT(1, VM_wasfreed);
+ PRVM_G_FLOAT(OFS_RETURN) = PRVM_G_EDICT(OFS_PARM0)->free;
+}
+
+void VM_SetTraceGlobals(prvm_prog_t *prog, const trace_t *trace)
+{
+ PRVM_gameglobalfloat(trace_allsolid) = trace->allsolid;
+ PRVM_gameglobalfloat(trace_startsolid) = trace->startsolid;
+ PRVM_gameglobalfloat(trace_fraction) = trace->fraction;
+ PRVM_gameglobalfloat(trace_inwater) = trace->inwater;
+ PRVM_gameglobalfloat(trace_inopen) = trace->inopen;
+ VectorCopy(trace->endpos, PRVM_gameglobalvector(trace_endpos));
+ VectorCopy(trace->plane.normal, PRVM_gameglobalvector(trace_plane_normal));
+ PRVM_gameglobalfloat(trace_plane_dist) = trace->plane.dist;
+ PRVM_gameglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(trace->ent ? trace->ent : prog->edicts);
+ PRVM_gameglobalfloat(trace_dpstartcontents) = trace->startsupercontents;
+ PRVM_gameglobalfloat(trace_dphitcontents) = trace->hitsupercontents;
+ PRVM_gameglobalfloat(trace_dphitq3surfaceflags) = trace->hitq3surfaceflags;
+ PRVM_gameglobalstring(trace_dphittexturename) = trace->hittexture ? PRVM_SetTempString(prog, trace->hittexture->name) : 0;
+}
+
+void VM_ClearTraceGlobals(prvm_prog_t *prog)
+{
+ // clean up all trace globals when leaving the VM (anti-triggerbot safeguard)
+ PRVM_gameglobalfloat(trace_allsolid) = 0;
+ PRVM_gameglobalfloat(trace_startsolid) = 0;
+ PRVM_gameglobalfloat(trace_fraction) = 0;
+ PRVM_gameglobalfloat(trace_inwater) = 0;
+ PRVM_gameglobalfloat(trace_inopen) = 0;
+ VectorClear(PRVM_gameglobalvector(trace_endpos));
+ VectorClear(PRVM_gameglobalvector(trace_plane_normal));
+ PRVM_gameglobalfloat(trace_plane_dist) = 0;
+ PRVM_gameglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(prog->edicts);
+ PRVM_gameglobalfloat(trace_dpstartcontents) = 0;
+ PRVM_gameglobalfloat(trace_dphitcontents) = 0;
+ PRVM_gameglobalfloat(trace_dphitq3surfaceflags) = 0;
+ PRVM_gameglobalstring(trace_dphittexturename) = 0;
+}
+
+//=============
+
+void VM_Cmd_Init(prvm_prog_t *prog)
+{
+ // only init the stuff for the current prog
+ VM_Files_Init(prog);
+ VM_Search_Init(prog);
+}
+
+static void animatemodel_reset(prvm_prog_t *prog);
+
+void VM_Cmd_Reset(prvm_prog_t *prog)
+{
+ CL_PurgeOwner( MENUOWNER );
+ VM_Search_Reset(prog);
+ VM_Files_CloseAll(prog);
+ animatemodel_reset(prog);
+}
+
+// #510 string(string input, ...) uri_escape (DP_QC_URI_ESCAPE)
+// does URI escaping on a string (replace evil stuff by %AB escapes)
+void VM_uri_escape (prvm_prog_t *prog)
+{
+ char src[VM_STRINGTEMP_LENGTH];
+ char dest[VM_STRINGTEMP_LENGTH];
+ char *p, *q;
+ static const char *hex = "0123456789ABCDEF";
+
+ VM_SAFEPARMCOUNTRANGE(1, 8, VM_uri_escape);
+ VM_VarString(prog, 0, src, sizeof(src));
+
+ for(p = src, q = dest; *p && q < dest + sizeof(dest) - 3; ++p)
+ {
+ if((*p >= 'A' && *p <= 'Z')
+ || (*p >= 'a' && *p <= 'z')
+ || (*p >= '0' && *p <= '9')
+ || (*p == '-') || (*p == '_') || (*p == '.')
+ || (*p == '!') || (*p == '~')
+ || (*p == '\'') || (*p == '(') || (*p == ')'))
+ *q++ = *p;
+ else
+ {
+ *q++ = '%';
+ *q++ = hex[(*(unsigned char *)p >> 4) & 0xF];
+ *q++ = hex[ *(unsigned char *)p & 0xF];
+ }
+ }
+ *q++ = 0;
+
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, dest);
+}
+
+// #510 string(string input, ...) uri_unescape (DP_QC_URI_ESCAPE)
+// does URI unescaping on a string (get back the evil stuff)
+void VM_uri_unescape (prvm_prog_t *prog)
+{
+ char src[VM_STRINGTEMP_LENGTH];
+ char dest[VM_STRINGTEMP_LENGTH];
+ char *p, *q;
+ int hi, lo;
+
+ VM_SAFEPARMCOUNTRANGE(1, 8, VM_uri_unescape);
+ VM_VarString(prog, 0, src, sizeof(src));
+
+ for(p = src, q = dest; *p; ) // no need to check size, because unescape can't expand
+ {
+ if(*p == '%')
+ {
+ if(p[1] >= '0' && p[1] <= '9')
+ hi = p[1] - '0';
+ else if(p[1] >= 'a' && p[1] <= 'f')
+ hi = p[1] - 'a' + 10;
+ else if(p[1] >= 'A' && p[1] <= 'F')
+ hi = p[1] - 'A' + 10;
+ else
+ goto nohex;
+ if(p[2] >= '0' && p[2] <= '9')
+ lo = p[2] - '0';
+ else if(p[2] >= 'a' && p[2] <= 'f')
+ lo = p[2] - 'a' + 10;
+ else if(p[2] >= 'A' && p[2] <= 'F')
+ lo = p[2] - 'A' + 10;
+ else
+ goto nohex;
+ if(hi != 0 || lo != 0) // don't unescape NUL bytes
+ *q++ = (char) (hi * 0x10 + lo);
+ p += 3;
+ continue;
+ }
+
+nohex:
+ // otherwise:
+ *q++ = *p++;
+ }
+ *q++ = 0;
+
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, dest);
+}
+
+// #502 string(string filename) whichpack (DP_QC_WHICHPACK)
+// returns the name of the pack containing a file, or "" if it is not in any pack (but local or non-existant)
+void VM_whichpack (prvm_prog_t *prog)
+{
+ const char *fn, *pack;
+
+ VM_SAFEPARMCOUNT(1, VM_whichpack);
+ fn = PRVM_G_STRING(OFS_PARM0);
+ pack = FS_WhichPack(fn);
+
+ PRVM_G_INT(OFS_RETURN) = pack ? PRVM_SetTempString(prog, pack) : 0;
+}
+
+typedef struct
+{
+ prvm_prog_t *prog;
+ double starttime;
+ float id;
+ char buffer[MAX_INPUTLINE];
+ char posttype[128];
+ unsigned char *postdata; // free when uri_to_prog_t is freed
+ size_t postlen;
+ char *sigdata; // free when uri_to_prog_t is freed
+ size_t siglen;
+}
+uri_to_prog_t;
+
+static void uri_to_string_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
+{
+ prvm_prog_t *prog;
+ uri_to_prog_t *handle = (uri_to_prog_t *) cbdata;
+
+ prog = handle->prog;
+ if(!prog->loaded)
+ {
+ // curl reply came too late... so just drop it
+ if(handle->postdata)
+ Z_Free(handle->postdata);
+ if(handle->sigdata)
+ Z_Free(handle->sigdata);
+ Z_Free(handle);
+ return;
+ }
+
+ if((prog->starttime == handle->starttime) && (PRVM_allfunction(URI_Get_Callback)))
+ {
+ if(length_received >= sizeof(handle->buffer))
+ length_received = sizeof(handle->buffer) - 1;
+ handle->buffer[length_received] = 0;
+
+ PRVM_G_FLOAT(OFS_PARM0) = handle->id;
+ PRVM_G_FLOAT(OFS_PARM1) = status;
+ PRVM_G_INT(OFS_PARM2) = PRVM_SetTempString(prog, handle->buffer);
+ prog->ExecuteProgram(prog, PRVM_allfunction(URI_Get_Callback), "QC function URI_Get_Callback is missing");
+ }
+
+ if(handle->postdata)
+ Z_Free(handle->postdata);
+ if(handle->sigdata)
+ Z_Free(handle->sigdata);
+ Z_Free(handle);
+}
+
+// uri_get() gets content from an URL and calls a callback "uri_get_callback" with it set as string; an unique ID of the transfer is returned
+// returns 1 on success, and then calls the callback with the ID, 0 or the HTTP status code, and the received data in a string
+void VM_uri_get (prvm_prog_t *prog)
+{
+ const char *url;
+ float id;
+ qbool ret;
+ uri_to_prog_t *handle;
+ const char *posttype = NULL;
+ const char *postseparator = NULL;
+ int poststringbuffer = -1;
+ int postkeyid = -1;
+ const char *query_string = NULL;
+ size_t lq;
+
+ if(!PRVM_allfunction(URI_Get_Callback))
+ prog->error_cmd("uri_get called by %s without URI_Get_Callback defined", prog->name);
+
+ VM_SAFEPARMCOUNTRANGE(2, 6, VM_uri_get);
+
+ url = PRVM_G_STRING(OFS_PARM0);
+ id = PRVM_G_FLOAT(OFS_PARM1);
+ if(prog->argc >= 3)
+ posttype = PRVM_G_STRING(OFS_PARM2);
+ if(prog->argc >= 4)
+ postseparator = PRVM_G_STRING(OFS_PARM3);
+ if(prog->argc >= 5)
+ poststringbuffer = PRVM_G_FLOAT(OFS_PARM4);
+ if(prog->argc >= 6)
+ postkeyid = PRVM_G_FLOAT(OFS_PARM5);
+ handle = (uri_to_prog_t *) Z_Malloc(sizeof(*handle)); // this can't be the prog's mem pool, as curl may call the callback later!
+
+ query_string = strchr(url, '?');
+ if(query_string)
+ ++query_string;
+ lq = query_string ? strlen(query_string) : 0;
+
+ handle->prog = prog;
+ handle->starttime = prog->starttime;
+ handle->id = id;
+ if(postseparator && posttype && *posttype)
+ {
+ size_t l = strlen(postseparator);
+ if(poststringbuffer >= 0)
+ {
+ size_t ltotal;
+ int i;
+ // "implode"
+ prvm_stringbuffer_t *stringbuffer;
+ stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, poststringbuffer);
+ if(!stringbuffer)
+ {
+ VM_Warning(prog, "uri_get: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name);
+ return;
+ }
+ ltotal = 0;
+ for(i = 0;i < stringbuffer->num_strings;i++)
+ {
+ if(i > 0)
+ ltotal += l;
+ if(stringbuffer->strings[i])
+ ltotal += strlen(stringbuffer->strings[i]);
+ }
+ handle->postdata = (unsigned char *)Z_Malloc(ltotal + 1 + lq);
+ handle->postlen = ltotal;
+ ltotal = 0;
+ for(i = 0;i < stringbuffer->num_strings;i++)
+ {
+ if(i > 0)
+ {
+ memcpy(handle->postdata + ltotal, postseparator, l);
+ ltotal += l;
+ }
+ if(stringbuffer->strings[i])
+ {
+ memcpy(handle->postdata + ltotal, stringbuffer->strings[i], strlen(stringbuffer->strings[i]));
+ ltotal += strlen(stringbuffer->strings[i]);
+ }
+ }
+ if(ltotal != handle->postlen)
+ prog->error_cmd("%s: string buffer content size mismatch, possible overrun", prog->name);
+ }
+ else
+ {
+ handle->postdata = (unsigned char *)Z_Malloc(l + 1 + lq);
+ handle->postlen = l;
+ memcpy(handle->postdata, postseparator, l);
+ }
+ handle->postdata[handle->postlen] = 0;
+ if(query_string)
+ memcpy(handle->postdata + handle->postlen + 1, query_string, lq);
+ if(postkeyid >= 0)
+ {
+ // POST: we sign postdata \0 query string
+ size_t ll;
+ handle->sigdata = (char *)Z_Malloc(8192);
+ strlcpy(handle->sigdata, "X-D0-Blind-ID-Detached-Signature: ", 8192);
+ l = strlen(handle->sigdata);
+ handle->siglen = Crypto_SignDataDetached(handle->postdata, handle->postlen + 1 + lq, postkeyid, handle->sigdata + l, 8192 - l);
+ if(!handle->siglen)
+ {
+ Z_Free(handle->sigdata);
+ handle->sigdata = NULL;
+ goto out1;
+ }
+ ll = base64_encode((unsigned char *) (handle->sigdata + l), handle->siglen, 8192 - l - 1);
+ if(!ll)
+ {
+ Z_Free(handle->sigdata);
+ handle->sigdata = NULL;
+ goto out1;
+ }
+ handle->siglen = l + ll;
+ handle->sigdata[handle->siglen] = 0;
+ }
+out1:
+ strlcpy(handle->posttype, posttype, sizeof(handle->posttype));
+ ret = Curl_Begin_ToMemory_POST(url, handle->sigdata, 0, handle->posttype, handle->postdata, handle->postlen, (unsigned char *) handle->buffer, sizeof(handle->buffer), uri_to_string_callback, handle);
+ }
+ else
+ {
+ if(postkeyid >= 0 && query_string)
+ {
+ // GET: we sign JUST the query string
+ size_t l, ll;
+ handle->sigdata = (char *)Z_Malloc(8192);
+ strlcpy(handle->sigdata, "X-D0-Blind-ID-Detached-Signature: ", 8192);
+ l = strlen(handle->sigdata);
+ handle->siglen = Crypto_SignDataDetached(query_string, lq, postkeyid, handle->sigdata + l, 8192 - l);
+ if(!handle->siglen)
+ {
+ Z_Free(handle->sigdata);
+ handle->sigdata = NULL;
+ goto out2;
+ }
+ ll = base64_encode((unsigned char *) (handle->sigdata + l), handle->siglen, 8192 - l - 1);
+ if(!ll)
+ {
+ Z_Free(handle->sigdata);
+ handle->sigdata = NULL;
+ goto out2;
+ }
+ handle->siglen = l + ll;
+ handle->sigdata[handle->siglen] = 0;
+ }
+out2:
+ handle->postdata = NULL;
+ handle->postlen = 0;
+ ret = Curl_Begin_ToMemory_POST(url, handle->sigdata, 0, NULL, NULL, 0, (unsigned char *) handle->buffer, sizeof(handle->buffer), uri_to_string_callback, handle);
+ }
+ if(ret)
+ {
+ PRVM_G_INT(OFS_RETURN) = 1;
+ }
+ else
+ {
+ if(handle->postdata)
+ Z_Free(handle->postdata);
+ if(handle->sigdata)
+ Z_Free(handle->sigdata);
+ Z_Free(handle);
+ PRVM_G_INT(OFS_RETURN) = 0;
+ }
+}
+
+void VM_netaddress_resolve (prvm_prog_t *prog)
+{
+ const char *ip;
+ char normalized[128];
+ int port;
+ lhnetaddress_t addr;
+
+ VM_SAFEPARMCOUNTRANGE(1, 2, VM_netaddress_resolve);
+
+ ip = PRVM_G_STRING(OFS_PARM0);
+ port = 0;
+ if(prog->argc > 1)
+ port = (int) PRVM_G_FLOAT(OFS_PARM1);
+
+ if(LHNETADDRESS_FromString(&addr, ip, port) && LHNETADDRESS_ToString(&addr, normalized, sizeof(normalized), prog->argc > 1))
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, normalized);
+ else
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, "");
+}
+
+//string(prvm_prog_t *prog) getextresponse = #624; // returns the next extResponse packet that was sent to this client
+void VM_CL_getextresponse (prvm_prog_t *prog)
+{
+ VM_SAFEPARMCOUNT(0,VM_argv);
+
+ if (cl_net_extresponse_count <= 0)
+ PRVM_G_INT(OFS_RETURN) = OFS_NULL;
+ else
+ {
+ int first;
+ --cl_net_extresponse_count;
+ first = (cl_net_extresponse_last + NET_EXTRESPONSE_MAX - cl_net_extresponse_count) % NET_EXTRESPONSE_MAX;
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, cl_net_extresponse[first]);
+ }
+}
+
+void VM_SV_getextresponse (prvm_prog_t *prog)
+{
+ VM_SAFEPARMCOUNT(0,VM_argv);
+
+ if (sv_net_extresponse_count <= 0)
+ PRVM_G_INT(OFS_RETURN) = OFS_NULL;
+ else
+ {
+ int first;
+ --sv_net_extresponse_count;
+ first = (sv_net_extresponse_last + NET_EXTRESPONSE_MAX - sv_net_extresponse_count) % NET_EXTRESPONSE_MAX;
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, sv_net_extresponse[first]);
+ }
+}
+
+/*
+=========
+Common functions between menu.dat and clsprogs
+=========
+*/
+
+//#349 float() isdemo
+void VM_CL_isdemo (prvm_prog_t *prog)
+{
+ VM_SAFEPARMCOUNT(0, VM_CL_isdemo);
+ PRVM_G_FLOAT(OFS_RETURN) = cls.demoplayback;
+}
+
+//#355 float() videoplaying
+void VM_CL_videoplaying (prvm_prog_t *prog)
+{
+ VM_SAFEPARMCOUNT(0, VM_CL_videoplaying);
+ PRVM_G_FLOAT(OFS_RETURN) = cl_videoplaying;
+}
+
+/*
+=========
+VM_M_callfunction
+
+ callfunction(...,string function_name)
+Extension: pass
+=========
+*/
+void VM_callfunction(prvm_prog_t *prog)
+{
+ mfunction_t *func;
+ const char *s;
+
+ VM_SAFEPARMCOUNTRANGE(1, 8, VM_callfunction);
+
+ s = PRVM_G_STRING(OFS_PARM0+(prog->argc - 1)*3);
+
+ VM_CheckEmptyString(prog, s);
+
+ func = PRVM_ED_FindFunction(prog, s);
+
+ if(!func)
+ prog->error_cmd("VM_callfunction: function %s not found !", s);
+ else if (func->first_statement < 0)
+ {
+ // negative statements are built in functions
+ int builtinnumber = -func->first_statement;
+ prog->xfunction->builtinsprofile++;
+ if (builtinnumber < prog->numbuiltins && prog->builtins[builtinnumber])
+ prog->builtins[builtinnumber](prog);
+ else
+ prog->error_cmd("No such builtin #%i in %s; most likely cause: outdated engine build. Try updating!", builtinnumber, prog->name);
+ }
+ else if(func - prog->functions > 0)
+ {
+ prog->argc--;
+ prog->ExecuteProgram(prog, func - prog->functions,"");
+ prog->argc++;
+ }
+}
+
+/*
+=========
+VM_isfunction
+
+float isfunction(string function_name)
+=========
+*/
+void VM_isfunction(prvm_prog_t *prog)
+{
+ mfunction_t *func;
+ const char *s;
+
+ VM_SAFEPARMCOUNT(1, VM_isfunction);
+
+ s = PRVM_G_STRING(OFS_PARM0);
+
+ VM_CheckEmptyString(prog, s);
+
+ func = PRVM_ED_FindFunction(prog, s);
+
+ if(!func)
+ PRVM_G_FLOAT(OFS_RETURN) = false;
+ else
+ PRVM_G_FLOAT(OFS_RETURN) = true;
+}
+
+/*
+=========
+VM_sprintf
+
+string sprintf(string format, ...)
+=========
+*/
+
+void VM_sprintf(prvm_prog_t *prog)
+{
+ const char *s, *s0;
+ char outbuf[MAX_INPUTLINE];
+ char *o = outbuf, *end = outbuf + sizeof(outbuf), *err;
+ const char *p;
+ int argpos = 1;
+ int width, precision, thisarg, flags;
+ char formatbuf[16];
+ char *f;
+ int isfloat;
+ static prvm_int_t dummyivec[3] = {0, 0, 0};
+ static prvm_vec_t dummyvec[3] = {0, 0, 0};
+ char vabuf[1024];
+
+#define PRINTF_ALTERNATE 1
+#define PRINTF_ZEROPAD 2
+#define PRINTF_LEFT 4
+#define PRINTF_SPACEPOSITIVE 8
+#define PRINTF_SIGNPOSITIVE 16
+
+ formatbuf[0] = '%';
+
+ s = PRVM_G_STRING(OFS_PARM0);
+
+#define GETARG_FLOAT(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_FLOAT(OFS_PARM0 + 3 * (a))) : 0)
+#define GETARG_VECTOR(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyvec)
+#define GETARG_INT(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_INT(OFS_PARM0 + 3 * (a))) : 0)
+#define GETARG_INTVECTOR(a) (((a)>=1 && (a)<prog->argc) ? ((prvm_int_t*) PRVM_G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyivec)
+#define GETARG_STRING(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_STRING(OFS_PARM0 + 3 * (a))) : "")
+
+ for(;;)
+ {
+ s0 = s;
+ switch(*s)
+ {
+ case 0:
+ goto finished;
+ case '%':
+ ++s;
+
+ if(*s == '%')
+ goto verbatim;
+
+ // complete directive format:
+ // %3$*1$.*2$ld
+
+ width = -1;
+ precision = -1;
+ thisarg = -1;
+ flags = 0;
+ isfloat = -1;
+
+ // is number following?
+ if(*s >= '0' && *s <= '9')
+ {
+ width = strtol(s, &err, 10);
+ if(!err)
+ {
+ VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0);
+ goto finished;
+ }
+ if(*err == '$')
+ {
+ thisarg = width;
+ width = -1;
+ s = err + 1;
+ }
+ else
+ {
+ if(*s == '0')
+ {
+ flags |= PRINTF_ZEROPAD;
+ if(width == 0)
+ width = -1; // it was just a flag
+ }
+ s = err;
+ }
+ }
+
+ if(width < 0)
+ {
+ for(;;)
+ {
+ switch(*s)
+ {
+ case '#': flags |= PRINTF_ALTERNATE; break;
+ case '0': flags |= PRINTF_ZEROPAD; break;
+ case '-': flags |= PRINTF_LEFT; break;
+ case ' ': flags |= PRINTF_SPACEPOSITIVE; break;
+ case '+': flags |= PRINTF_SIGNPOSITIVE; break;
+ default:
+ goto noflags;
+ }
+ ++s;
+ }
+noflags:
+ if(*s == '*')
+ {
+ ++s;
+ if(*s >= '0' && *s <= '9')
+ {
+ width = strtol(s, &err, 10);
+ if(!err || *err != '$')
+ {
+ VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0);
+ goto finished;
+ }
+ s = err + 1;
+ }
+ else
+ width = argpos++;
+ width = GETARG_FLOAT(width);
+ if(width < 0)
+ {
+ flags |= PRINTF_LEFT;
+ width = -width;
+ }
+ }
+ else if(*s >= '0' && *s <= '9')
+ {
+ width = strtol(s, &err, 10);
+ if(!err)
+ {
+ VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0);
+ goto finished;
+ }
+ s = err;
+ if(width < 0)
+ {
+ flags |= PRINTF_LEFT;
+ width = -width;
+ }
+ }
+ // otherwise width stays -1
+ }
+
+ if(*s == '.')
+ {
+ ++s;
+ if(*s == '*')
+ {
+ ++s;
+ if(*s >= '0' && *s <= '9')
+ {
+ precision = strtol(s, &err, 10);
+ if(!err || *err != '$')
+ {
+ VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0);
+ goto finished;
+ }
+ s = err + 1;
+ }
+ else
+ precision = argpos++;
+ precision = GETARG_FLOAT(precision);
+ }
+ else if(*s >= '0' && *s <= '9')
+ {
+ precision = strtol(s, &err, 10);
+ if(!err)
+ {
+ VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0);
+ goto finished;
+ }
+ s = err;
+ }
+ else
+ {
+ VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0);
+ goto finished;
+ }
+ }