fteqcc-20251105/./0000755000200200001440000000000015233070110012724 5ustar twolifeusersfteqcc-20251105/./pr_multi.c0000644000200200001440000003613515233070110014733 0ustar twolifeusers#define PROGSUSED #include "progsint.h" //#define MAPPING_DEBUG //#define MAPPING_PARANOID //may actually break unions, so beware. /* progstate_t *pr_progstate; progsnum_t pr_typecurrent; int maxprogs; progstate_t *current_progstate; int numshares; sharedvar_t *shares; //shared globals, not including parms int maxshares; */ //switches progs without preserving parms/ret/shared pbool PR_SwitchProgs(progfuncs_t *progfuncs, progsnum_t type) { if ((unsigned)type >= prinst.maxprogs) { if (type == -1) { prinst.pr_typecurrent = -1; current_progstate = NULL; return true; } PR_RunError(&progfuncs->funcs, "QCLIB: Bad prog type - %"pPRIi, type); // Sys_Error("Bad prog type - %i", type); } if (pr_progstate[(unsigned)type].progs == NULL) //we havn't loaded it yet, for some reason return false; current_progstate = &pr_progstate[(unsigned)type]; prinst.pr_typecurrent = type; return true; } //switch to new progs, preserving all arguments. oldpr should be 'pr_typecurrent' pbool PR_SwitchProgsParms(progfuncs_t *progfuncs, progsnum_t newpr) //from 2 to 1 { unsigned int a; progstate_t *np; progstate_t *op; progsnum_t oldpr = prinst.pr_typecurrent; if (newpr == oldpr) { //don't bother coping variables to themselves... return true; } np = &pr_progstate[(int)newpr]; op = &pr_progstate[(int)oldpr]; if ((unsigned)newpr >= prinst.maxprogs || !np->globals) { externs->Printf("QCLIB: Bad prog type - %"pPRIi, newpr); return false; } if ((unsigned)oldpr >= prinst.maxprogs || !op->globals) //startup? return PR_SwitchProgs(progfuncs, newpr); //copy parms. for (a = 0; a < MAX_PARMS;a++) { *(int *)&np->globals[OFS_PARM0+3*a ] = *(int *)&op->globals[OFS_PARM0+3*a ]; *(int *)&np->globals[OFS_PARM0+3*a+1] = *(int *)&op->globals[OFS_PARM0+3*a+1]; *(int *)&np->globals[OFS_PARM0+3*a+2] = *(int *)&op->globals[OFS_PARM0+3*a+2]; } np->globals[OFS_RETURN] = op->globals[OFS_RETURN]; np->globals[OFS_RETURN+1] = op->globals[OFS_RETURN+1]; np->globals[OFS_RETURN+2] = op->globals[OFS_RETURN+2]; //move the vars defined as shared. for (a = 0; a < prinst.numshares; a++)//fixme: make offset per progs { memmove(&((int *)np->globals)[prinst.shares[a].varofs], &((int *)op->globals)[prinst.shares[a].varofs], prinst.shares[a].size*4); /* ((int *)p1->globals)[shares[a].varofs] = ((int *)p2->globals)[shares[a].varofs]; if (shares[a].size > 1) { ((int *)p1->globals)[shares[a].varofs+1] = ((int *)p2->globals)[shares[a].varofs+1]; if (shares[a].size > 2) ((int *)p1->globals)[shares[a].varofs+2] = ((int *)p2->globals)[shares[a].varofs+2]; } */ } return PR_SwitchProgs(progfuncs, newpr); } progsnum_t PDECL PR_LoadProgs(pubprogfuncs_t *ppf, const char *s) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; unsigned int a; progsnum_t oldtype; oldtype = prinst.pr_typecurrent; for (a = 0; a < prinst.maxprogs; a++) { if (pr_progstate[a].progs == NULL) { prinst.pr_typecurrent = a; current_progstate = &pr_progstate[a]; if (PR_ReallyLoadProgs(progfuncs, s, &pr_progstate[a], false)) //try and load it { if (a <= progfuncs->funcs.numprogs) progfuncs->funcs.numprogs = a+1; #ifdef QCJIT current_progstate->jit = PR_GenerateJit(progfuncs); #endif if (oldtype != -1) PR_SwitchProgs(progfuncs, oldtype); return a; //we could load it. Yay! } PR_SwitchProgs(progfuncs, oldtype); return -1; // loading failed. } } PR_SwitchProgs(progfuncs, oldtype); return -1; } void PR_ShiftParms(progfuncs_t *progfuncs, int amount) { int a; for (a = 0; a < MAX_PARMS - amount;a++) *(int *)&pr_globals[OFS_PARM0+3*a] = *(int *)&pr_globals[OFS_PARM0+3*(amount+a)]; } //forget a progs void PR_Clear(progfuncs_t *progfuncs) { unsigned int a; for (a = 0; a < prinst.maxprogs; a++) { #ifdef QCJIT if (pr_progstate[a].jit) PR_CloseJit(pr_progstate[a].jit); #endif pr_progstate[a].progs = NULL; } } void QC_StartShares(progfuncs_t *progfuncs) { prinst.numshares = 0; prinst.maxshares = 32; if (prinst.shares) externs->memfree(prinst.shares); prinst.shares = externs->memalloc(sizeof(sharedvar_t)*prinst.maxshares); } void PDECL QC_AddSharedVar(pubprogfuncs_t *ppf, int start, int size) //fixme: make offset per progs and optional { progfuncs_t *progfuncs = (progfuncs_t*)ppf; int ofs; unsigned int a; if (prinst.numshares >= prinst.maxshares) { void *buf; buf = prinst.shares; prinst.maxshares += 16; prinst.shares = externs->memalloc(sizeof(sharedvar_t)*prinst.maxshares); memcpy(prinst.shares, buf, sizeof(sharedvar_t)*prinst.numshares); externs->memfree(buf); } ofs = start; for (a = 0; a < prinst.numshares; a++) { if (prinst.shares[a].varofs+prinst.shares[a].size == ofs) { prinst.shares[a].size += size; //expand size. return; } if (prinst.shares[a].varofs == start) return; } prinst.shares[prinst.numshares].varofs = start; prinst.shares[prinst.numshares].size = size; prinst.numshares++; } //void ShowWatch(void); void QC_InitShares(progfuncs_t *progfuncs) { // ShowWatch(); if (!prinst.field) //don't make it so we will just need to remalloc everything { prinst.maxfields = 64; prinst.field = externs->memalloc(sizeof(fdef_t) * prinst.maxfields); } prinst.numfields = 0; progfuncs->funcs.fieldadjust = 0; } void QC_FlushProgsOffsets(progfuncs_t *progfuncs) { //sets the fields up for loading a new progs. //fields are matched by name to other progs //not by offset unsigned int i; for (i = 0; i < prinst.numfields; i++) prinst.field[i].progsofs = -1; } //called if a global is defined as a field //returns offset. //vectors must be added before any of their corresponding _x/y/z vars //in this way, even screwed up progs work. //requestedpos is the offset the engine WILL put it at. //origionaloffs is used to track matching field offsets. fields with the same progs offset overlap //note: we probably suffer from progs with renamed system globals. int PDECL QC_RegisterFieldVar(pubprogfuncs_t *ppf, unsigned int type, const char *name, signed long engineofs, signed long progsofs) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; // progstate_t *p; // int pnum; unsigned int i; int namelen; int ofs; int fnum; if (!name) //engine can use this to offset all progs fields { //which fixes constant field offsets (some ktpro arrays) if (engineofs == 2) prinst.reorganisefields = 2; else if (engineofs) { progfuncs->funcs.fieldadjust = prinst.fields_size/sizeof(pvec_t); #ifdef MAPPING_DEBUG externs->Printf("FIELD ADJUST: %i %i %i\n", progfuncs->funcs.fieldadjust, prinst.fields_size, (int)prinst.fields_size/4); #endif } return 0; } else if (!prinst.reorganisefields) prinst.reorganisefields = true; //look for an existing match for (i = 0; i < prinst.numfields; i++) { if (!strcmp(name, prinst.field[i].name)) { if (prinst.field[i].type != type) { /*Hexen2/DP compat hack: if the new type is a float and the original type is a vector, make the new def alias to the engine's _x field this 'works around' the unused .vector color field used for rtlight colours vs the .float color used for particle colours (the float initialisers in map files will expand into the x slot safely). qc/hc can work around this by just using .vector color/color_x instead, which is the same as this hack, but would resolve defs to allow rtlight colours. */ if (prinst.field[i].type != ev_vector || type != ev_float) { if (prinst.field[i].type == ev_string && type == ev_float && !strcmp(name, "message")) ; //hexen2 uses floats here instead of strings. else externs->Printf("Field type mismatch on \"%s\". %i != %i\n", name, prinst.field[i].type, type); continue; } } if (!progfuncs->funcs.fieldadjust && engineofs>=0) if ((unsigned)engineofs/4 != prinst.field[i].ofs) externs->Sys_Error("Field %s at wrong offset", name); if (prinst.field[i].progsofs == -1) prinst.field[i].progsofs = progsofs; #ifdef MAPPING_DEBUG externs->Printf("Dupfield %s %i -> %i\n", name, prinst.field[i].progsofs,prinst.field[i].ofs); #endif return prinst.field[i].ofs-progfuncs->funcs.fieldadjust; //got a match } } if (prinst.numfields+1>prinst.maxfields) { fdef_t *nf; i = prinst.maxfields; prinst.maxfields += 32; nf = externs->memalloc(sizeof(fdef_t) * prinst.maxfields); memcpy(nf, prinst.field, sizeof(fdef_t) * i); externs->memfree(prinst.field); prinst.field = nf; } //try to add a new one fnum = prinst.numfields; prinst.numfields++; prinst.field[fnum].name = name; prinst.field[fnum].type = type; prinst.field[fnum].progsofs = progsofs; if (engineofs >= 0) { //the engine is setting up a list of required field indexes. //paranoid checking of the offset. #if 0//def MAPPING_PARANOID for (i = 0; i < numfields-1; i++) { if (field[i].ofs == ((unsigned)engineofs)/4) { if (type == ev_float && field[i].type == ev_vector) //check names { if (strncmp(field[i].name, name, strlen(field[i].name))) Sys_Error("Duplicated offset"); } else Sys_Error("Duplicated offset"); } } #endif if (engineofs&3) externs->Sys_Error("field %s is %i&3", name, (int)engineofs); prinst.field[fnum].ofs = ofs = engineofs/4; } else { //we just found a new fieldname inside a progs prinst.field[fnum].ofs = ofs = prinst.fields_size/sizeof(pvec_t); //add on the end //if the progs field offset matches another offset in the same progs, make it match up with the earlier one. if (progsofs>=0) { unsigned otherofs; for (i = 0; i < prinst.numfields-1; i++) { otherofs = prinst.field[i].progsofs; if (otherofs == -1) //qc unions work purely by progs offsets, not by engine offsets. continue; //this is because there really is no reliable mapping between progs and engine, so don't get confused. if (otherofs == (unsigned)progsofs) { #ifdef MAPPING_DEBUG externs->Printf("union(%s) ", prinst.field[i].name); #endif prinst.field[fnum].ofs = ofs = prinst.field[i].ofs; break; } if (prinst.field[i].type == ev_vector && otherofs+1 == (unsigned)progsofs) { #ifdef MAPPING_DEBUG externs->Printf("union(%s) ", prinst.field[i].name); #endif prinst.field[fnum].ofs = ofs = prinst.field[i].ofs+1; break; } if (prinst.field[i].type == ev_vector && otherofs+2 == (unsigned)progsofs) { #ifdef MAPPING_DEBUG externs->Printf("union(%s) ", prinst.field[i].name); #endif prinst.field[fnum].ofs = ofs = prinst.field[i].ofs+2; break; } } } } // if (type != ev_vector) if (prinst.fields_size < (ofs+type_size[type])*sizeof(pvec_t)) { prinst.fields_size = (ofs+type_size[type])*sizeof(pvec_t); progfuncs->funcs.activefieldslots = prinst.fields_size/sizeof(pvec_t); } if (prinst.max_fields_size && prinst.fields_size > prinst.max_fields_size) externs->Sys_Error("Allocated too many additional fields after ents were inited."); #ifdef MAPPING_DEBUG externs->Printf("Field %s %i -> %i\n", name, prinst.field[fnum].progsofs,prinst.field[fnum].ofs); #endif if (type == ev_vector) { //vectors define float fields too. this avoids issues if the mod has (pointless) anti-decompile field reordering/stripping that works thanks to decompilers expecting things to be ordered exactly.. char *n; namelen = strlen(name)+5; n=PRHunkAlloc(progfuncs, namelen, "str"); sprintf(n, "%s_x", name); ofs = QC_RegisterFieldVar(&progfuncs->funcs, ev_float, n, engineofs, progsofs); n=PRHunkAlloc(progfuncs, namelen, "str"); sprintf(n, "%s_y", name); QC_RegisterFieldVar(&progfuncs->funcs, ev_float, n, (engineofs==-1)?-1:(engineofs+4), (progsofs==-1)?-1:progsofs+1); n=PRHunkAlloc(progfuncs, namelen, "str"); sprintf(n, "%s_z", name); QC_RegisterFieldVar(&progfuncs->funcs, ev_float, n, (engineofs==-1)?-1:(engineofs+8), (progsofs==-1)?-1:progsofs+2); } //we've finished setting the structure return ofs - progfuncs->funcs.fieldadjust; } //called for each global defined as a field void PDECL QC_AddSharedFieldVar(pubprogfuncs_t *ppf, int num, char *stringtable) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; // progstate_t *p; // int pnum; unsigned int i, o; //look for an existing match not needed, cos we look a little later too. /* for (i = 0; i < numfields; i++) { if (!strcmp(pr_globaldefs[num].s_name, field[i].s_name)) { //really we should look for a field def *(int *)&pr_globals[pr_globaldefs[num].ofs] = field[i].ofs; //got a match return; } } */ switch(current_progstate->structtype) { case PST_KKQWSV: case PST_DEFAULT: { ddef16_t *gd = pr_globaldefs16; ddef16_t *fld = pr_fielddefs16; char *gname = gd[num].s_name+stringtable; int *eval = (int*)&pr_globals[gd[num].ofs]; if (*gname == '.') gname++; for (i=1 ; inumfielddefs; i++) { if (!strcmp(fld[i].s_name+stringtable, gname)) { #ifdef MAPPING_DEBUG int old = *eval; #endif *eval = QC_RegisterFieldVar(&progfuncs->funcs, fld[i].type, gname, -1, *eval); #ifdef MAPPING_DEBUG externs->Printf("Field=%s global %i -> %i\n", gd[num].s_name+stringtable, old, *eval); #endif return; } } for (i = 0; i < prinst.numfields; i++) { o = prinst.field[i].progsofs; if (o == *eval) { #ifdef MAPPING_DEBUG int old = *eval; #endif *eval = prinst.field[i].ofs-progfuncs->funcs.fieldadjust; #ifdef MAPPING_DEBUG externs->Printf("Field global=%s %i -> %i\n", gname, old, *eval); #endif return; } } //oh well, must be a parameter. // if (*(int *)&pr_globals[pr_globaldefs16[num].ofs]) // Sys_Error("QCLIB: Global field var with no matching field \"%s\", from offset %i", pr_globaldefs16[num].s_name+stringtable, *(int *)&pr_globals[pr_globaldefs16[num].ofs]); } return; case PST_FTE32: case PST_QTEST: case PST_UHEXEN2: { ddef32_t *gd = pr_globaldefs32; ddef32_t *fld = pr_fielddefs32; for (i=1 ; inumfielddefs; i++) { if (!strcmp(fld[i].s_name+stringtable, gd[num].s_name+stringtable)) { *(int *)&pr_globals[gd[num].ofs] = QC_RegisterFieldVar(&progfuncs->funcs, fld[i].type, gd[num].s_name+stringtable, -1, *(int *)&pr_globals[gd[num].ofs]); return; } } for (i = 0; i < prinst.numfields; i++) { o = prinst.field[i].progsofs; if (o == *(unsigned int *)&pr_globals[gd[num].ofs]) { *(int *)&pr_globals[gd[num].ofs] = prinst.field[i].ofs-progfuncs->funcs.fieldadjust; return; } } //oh well, must be a parameter. if (*(int *)&pr_globals[gd[num].ofs]) externs->Sys_Error("QCLIB: Global field var with no matching field \"%s\", from offset %i", gd[num].s_name+stringtable, *(int *)&pr_globals[gd[num].ofs]); } return; default: externs->Sys_Error("Bad bits"); break; } externs->Sys_Error("Should be unreachable"); } void QC_AddFieldGlobal(pubprogfuncs_t *ppf, int *globdata) { unsigned int i; int o; progfuncs_t *progfuncs = (progfuncs_t*)ppf; for (i = 0; i < prinst.numfields; i++) { o = prinst.field[i].progsofs; if (o == *globdata) { #ifdef MAPPING_DEBUG int old = *globdata; #endif *globdata = prinst.field[i].ofs-progfuncs->funcs.fieldadjust; #ifdef MAPPING_DEBUG externs->Printf("Field global %i -> %i\n", old, *globdata); #endif return; } } externs->Printf("Unable to map fieldglobal\n"); } fteqcc-20251105/./qcd.h0000644000200200001440000000166615233070110013655 0ustar twolifeuserspbool QC_decodeMethodSupported(int method); char *QC_decode(progfuncs_t *progfuncs, int complen, int len, int method, const void *info, char *buffer); int QC_encode(progfuncs_t *progfuncs, int len, int method, const char *in, int handle); int QC_EnumerateFilesFromBlob(const void *blob, size_t blobsize, void (*cb)(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize)); int QC_encodecrc(int len, char *in); char *PDECL filefromprogs(pubprogfuncs_t *progfuncs, progsnum_t prnum, const char *fname, size_t *size, char *buffer); char *filefromnewprogs(pubprogfuncs_t *progfuncs, const char *prname, const char *fname, size_t *size, char *buffer);//fixme - remove parm 1 void DecompileProgsDat(const char *name, void *buf, size_t bufsize); char *ReadProgsCopyright(char *buf, size_t bufsize); int GUIprintf(const char *msg, ...); void compilecb(void); void AddSourceFile(const char *parentpath, const char *filename); fteqcc-20251105/./execloop.h0000644000200200001440000016443115233070110014724 0ustar twolifeusers//qc execution code. //we have two conditions. //one allows us to debug and trace through our code, the other doesn't. //hopefully, the compiler will do a great job at optimising this code for us, where required. //if it dosn't, then bum. //the general overhead should be reduced significantly, and I would be supprised if it did run slower. //run away loops are checked for ONLY on gotos and function calls. This might give a poorer check, but it will run faster overall. //Appears to work fine. #if INTSIZE == 16 #define reeval reeval16 #define pr_statements pr_statements16 #define fakeop fakeop16 #define dstatement_t dstatement16_t #define sofs signed short #elif INTSIZE == 32 #define reeval reeval32 #define pr_statements pr_statements32 #define fakeop fakeop32 #define dstatement_t dstatement32_t #define sofs signed int #elif INTSIZE == 24 #error INTSIZE should be set to 32. #else #error Bad cont size #endif #define ENGINEPOINTER(p) ((char*)(p) - progfuncs->funcs.stringtable) #define QCPOINTER(p) (eval_t *)(p->_int+progfuncs->funcs.stringtable) #define QCPOINTERM(p) (eval_t *)((p)+progfuncs->funcs.stringtable) #define QCPOINTERWRITEFAIL(p,sz) ((unsigned int)(p)-1 >= prinst.addressableused-1-(sz)) //disallows null writes #define QCPOINTERREADFAIL(p,sz) ((unsigned int)(p) >= prinst.addressableused-(sz)) //permits null reads #define QCFAULT return (prinst.pr_xstatement=(st-pr_statements)-1),PR_HandleFault #define EVAL_FLOATISTRUE(ev) ((ev)->_int & 0x7fffffff) //mask away sign bit. This avoids using denormalized floats. #define A_RSHIFT_I(x,y) ((x < 0) ? ~(~(x) >> (y)) : ((x) >> (y))) //C leaves it undefined whether signed rshift is arithmatic or logical. gcc should be smart enough to fold this to the proper signed instruction at least on x86. #ifdef __GNUC__ #define errorif(x) if(__builtin_expect(x,0)) #else #define errorif(x) if(x) #endif //rely upon just st { #ifdef DEBUGABLE s = st-pr_statements; s+=1; errorif (prinst.watch_ptr && prinst.watch_ptr->_int != prinst.watch_old._int) { //this will fire on the next instruction after the variable got changed. prinst.pr_xstatement = s; if (current_progstate->linenums) externs->Printf("^b^3Watch point hit in %s:%u, \"%s\" changed", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), current_progstate->linenums[s-1], prinst.watch_name); else externs->Printf("^b^3Watch point hit in %s, \"%s\" changed", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), prinst.watch_name); switch(prinst.watch_type) { case ev_float: externs->Printf(" from %g to %g", prinst.watch_old._float, prinst.watch_ptr->_float); break; case ev_vector: externs->Printf(" from '%g %g %g' to '%g %g %g'", prinst.watch_old._vector[0], prinst.watch_old._vector[1], prinst.watch_old._vector[2], prinst.watch_ptr->_vector[0], prinst.watch_ptr->_vector[1], prinst.watch_ptr->_vector[2]); break; default: externs->Printf(" from %i to %i", prinst.watch_old._int, prinst.watch_ptr->_int); break; case ev_entity: externs->Printf(" from %i(%s) to %i(%s)", prinst.watch_old._int, PR_GetEdictClassname(progfuncs, prinst.watch_old._int), prinst.watch_ptr->_int, PR_GetEdictClassname(progfuncs, prinst.watch_ptr->_int)); break; case ev_function: case ev_string: externs->Printf(", now set to %s", PR_ValueString(progfuncs, prinst.watch_type, prinst.watch_ptr, false)); break; } externs->Printf(".\n"); prinst.watch_old = *prinst.watch_ptr; // prinst.watch_ptr = NULL; progfuncs->funcs.debug_trace=DEBUG_TRACE_INTO; //this is what it's for s=ShowStep(progfuncs, s, "Watchpoint hit", false); } else if (progfuncs->funcs.debug_trace) s=ShowStep(progfuncs, s, NULL, false); st = pr_statements + s; prinst.pr_xfunction->profile+=1; op = (progfuncs->funcs.debug_trace?(st->op & ~0x8000):st->op); reeval: #else st++; op = st->op; #endif safeswitch ((enum qcop_e)op) { case OP_ADD_F: OPC->_float = OPA->_float + OPB->_float; break; case OP_ADD_V: OPC->_vector[0] = OPA->_vector[0] + OPB->_vector[0]; OPC->_vector[1] = OPA->_vector[1] + OPB->_vector[1]; OPC->_vector[2] = OPA->_vector[2] + OPB->_vector[2]; break; case OP_SUB_F: OPC->_float = OPA->_float - OPB->_float; break; case OP_SUB_V: OPC->_vector[0] = OPA->_vector[0] - OPB->_vector[0]; OPC->_vector[1] = OPA->_vector[1] - OPB->_vector[1]; OPC->_vector[2] = OPA->_vector[2] - OPB->_vector[2]; break; case OP_MUL_F: OPC->_float = OPA->_float * OPB->_float; break; case OP_MUL_V: OPC->_float = OPA->_vector[0]*OPB->_vector[0] + OPA->_vector[1]*OPB->_vector[1] + OPA->_vector[2]*OPB->_vector[2]; break; case OP_MUL_FV: tmpf = OPA->_float; OPC->_vector[0] = tmpf * OPB->_vector[0]; OPC->_vector[1] = tmpf * OPB->_vector[1]; OPC->_vector[2] = tmpf * OPB->_vector[2]; break; case OP_MUL_VF: tmpf = OPB->_float; OPC->_vector[0] = tmpf * OPA->_vector[0]; OPC->_vector[1] = tmpf * OPA->_vector[1]; OPC->_vector[2] = tmpf * OPA->_vector[2]; break; case OP_DIV_F: /* errorif (OPB->_float == 0) { prinst.pr_xstatement = st-pr_statements; externs->Printf ("Division by 0 in %s\n", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name)); PR_StackTrace (&progfuncs->funcs, 1); OPC->_float = 0.0; } else */ OPC->_float = OPA->_float / OPB->_float; break; case OP_DIV_VF: tmpf = OPB->_float; /* errorif (!tmpf) { prinst.pr_xstatement = st-pr_statements; externs->Printf ("Division by 0 in %s\n", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name)); PR_StackTrace (&progfuncs->funcs, 1); } */ OPC->_vector[0] = OPA->_vector[0] / tmpf; OPC->_vector[1] = OPA->_vector[1] / tmpf; OPC->_vector[2] = OPA->_vector[2] / tmpf; break; case OP_BITAND_F: OPC->_float = (float)((int)OPA->_float & (int)OPB->_float); break; case OP_BITOR_F: OPC->_float = (float)((int)OPA->_float | (int)OPB->_float); break; case OP_GE_F: OPC->_float = (float)(OPA->_float >= OPB->_float); break; case OP_GE_I: OPC->_int = (int)(OPA->_int >= OPB->_int); break; case OP_GE_IF: OPC->_int = (int)(OPA->_int >= OPB->_float); break; case OP_GE_FI: OPC->_int = (int)(OPA->_float >= OPB->_int); break; case OP_LE_F: OPC->_float = (float)(OPA->_float <= OPB->_float); break; case OP_LE_I: OPC->_int = (int)(OPA->_int <= OPB->_int); break; case OP_LE_IF: OPC->_int = (int)(OPA->_int <= OPB->_float); break; case OP_LE_FI: OPC->_int = (int)(OPA->_float <= OPB->_int); break; case OP_LE_U: OPC->_int = (int)(OPA->_uint <= OPB->_uint); break; case OP_GT_F: OPC->_float = (float)(OPA->_float > OPB->_float); break; case OP_GT_I: OPC->_int = (int)(OPA->_int > OPB->_int); break; case OP_GT_IF: OPC->_int = (int)(OPA->_int > OPB->_float); break; case OP_GT_FI: OPC->_int = (int)(OPA->_float > OPB->_int); break; case OP_LT_F: OPC->_float = (float)(OPA->_float < OPB->_float); break; case OP_LT_I: OPC->_int = (int)(OPA->_int < OPB->_int); break; case OP_LT_IF: OPC->_int = (int)(OPA->_int < OPB->_float); break; case OP_LT_FI: OPC->_int = (int)(OPA->_float < OPB->_int); break; case OP_LT_U: OPC->_int = (OPA->_uint < OPB->_uint); break; case OP_AND_F: //original logic //OPC->_float = (float)(OPA->_float && OPB->_float); //deal with denormalized floats by ensuring that they're not 0 (ignoring sign bit). //this avoids issues where the fpu treats denormalised floats as 0, or fpus that don't support denormals. OPC->_float = (float)(EVAL_FLOATISTRUE(OPA) && EVAL_FLOATISTRUE(OPB)); break; case OP_OR_F: OPC->_float = (float)(EVAL_FLOATISTRUE(OPA) || EVAL_FLOATISTRUE(OPB)); break; case OP_NOT_F: OPC->_float = (float)(!EVAL_FLOATISTRUE(OPA)); break; case OP_NOT_V: OPC->_float = (float)(!OPA->_vector[0] && !OPA->_vector[1] && !OPA->_vector[2]); break; case OP_NOT_S: OPC->_float = (float)(!(OPA->string) || !*PR_StringToNative(&progfuncs->funcs, OPA->string)); break; case OP_NOT_FNC: OPC->_float = (float)(!(OPA->function & ~0xff000000)); break; case OP_NOT_ENT: OPC->_float = (float)(!(OPA->edict));//(PROG_TO_EDICT(progfuncs, OPA->edict) == (edictrun_t *)sv_edicts); break; case OP_NOT_I: OPC->_int = !OPA->_int; break; case OP_EQ_F: OPC->_float = (float)(OPA->_float == OPB->_float); break; case OP_EQ_IF: OPC->_int = (float)(OPA->_int == OPB->_float); break; case OP_EQ_FI: OPC->_int = (float)(OPA->_float == OPB->_int); break; case OP_EQ_V: OPC->_float = (float)((OPA->_vector[0] == OPB->_vector[0]) && (OPA->_vector[1] == OPB->_vector[1]) && (OPA->_vector[2] == OPB->_vector[2])); break; case OP_EQ_S: if (OPA->string==OPB->string) OPC->_float = true; else if (!OPA->string) { if (!OPB->string || !*PR_StringToNative(&progfuncs->funcs, OPB->string)) OPC->_float = true; else OPC->_float = false; } else if (!OPB->string) { if (!OPA->string || !*PR_StringToNative(&progfuncs->funcs, OPA->string)) OPC->_float = true; else OPC->_float = false; } else OPC->_float = (float)(!strcmp(PR_StringToNative(&progfuncs->funcs, OPA->string),PR_StringToNative(&progfuncs->funcs, OPB->string))); break; case OP_EQ_E: OPC->_float = (float)(OPA->_int == OPB->_int); break; case OP_EQ_FNC: OPC->_float = (float)(OPA->function == OPB->function); break; case OP_NE_F: OPC->_float = (float)(OPA->_float != OPB->_float); break; case OP_NE_V: OPC->_float = (float)((OPA->_vector[0] != OPB->_vector[0]) || (OPA->_vector[1] != OPB->_vector[1]) || (OPA->_vector[2] != OPB->_vector[2])); break; case OP_NE_S: if (OPA->string==OPB->string) OPC->_float = false; else if (!OPA->string) { if (!OPB->string || !*(PR_StringToNative(&progfuncs->funcs, OPB->string))) OPC->_float = false; else OPC->_float = true; } else if (!OPB->string) { if (!OPA->string || !*PR_StringToNative(&progfuncs->funcs, OPA->string)) OPC->_float = false; else OPC->_float = true; } else OPC->_float = (float)(strcmp(PR_StringToNative(&progfuncs->funcs, OPA->string),PR_StringToNative(&progfuncs->funcs, OPB->string))); break; case OP_NE_E: OPC->_float = (float)(OPA->_int != OPB->_int); break; case OP_NE_FNC: OPC->_float = (float)(OPA->function != OPB->function); break; //================== case OP_STORE_IF: OPB->_float = (float)OPA->_int; break; case OP_STORE_FI: OPB->_int = (int)OPA->_float; break; case OP_STORE_F: case OP_STORE_ENT: case OP_STORE_FLD: // integers case OP_STORE_S: case OP_STORE_I: case OP_STORE_FNC: // pointers case OP_STORE_P: OPB->_int = OPA->_int; break; case OP_STORE_V: OPB->_vector[0] = OPA->_vector[0]; OPB->_vector[1] = OPA->_vector[1]; OPB->_vector[2] = OPA->_vector[2]; break; //store a value to a pointer case OP_STOREP_IF: i = OPB->_int + OPC->_int*sizeof(ptr->_float); errorif (QCPOINTERWRITEFAIL(i, sizeof(float))) { if (!(ptr=PR_GetWriteTempStringPtr(progfuncs, OPB->_int, OPC->_int*sizeof(ptr->_float), sizeof(ptr->_float)))) { if (i == -1) break; QCFAULT(&progfuncs->funcs, "bad pointer write in %s", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name)); } } else ptr = QCPOINTERM(i); ptr->_float = (float)OPA->_int; break; case OP_STOREP_FI: i = OPB->_int + OPC->_int*sizeof(ptr->_int); errorif (QCPOINTERWRITEFAIL(i, sizeof(int))) { if (!(ptr=PR_GetWriteTempStringPtr(progfuncs, OPB->_int, OPC->_int*sizeof(ptr->_int), sizeof(ptr->_int)))) { if (i == -1) break; QCFAULT(&progfuncs->funcs, "bad pointer write in %s", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name)); } } else ptr = QCPOINTERM(i); ptr->_int = (int)OPA->_float; break; case OP_STOREP_I: case OP_STOREP_F: case OP_STOREP_ENT: case OP_STOREP_FLD: // integers case OP_STOREP_S: case OP_STOREP_FNC: // pointers i = OPB->_int + OPC->_int*sizeof(ptr->_int); errorif (QCPOINTERWRITEFAIL(i, sizeof(ptr->_int))) { if (!(ptr=PR_GetWriteTempStringPtr(progfuncs, OPB->_int, OPC->_int*sizeof(ptr->_int), sizeof(ptr->_int)))) { if (i == -1) break; if (i == 0) QCFAULT(&progfuncs->funcs, "bad pointer write in %s (null pointer)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name)); else QCFAULT(&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, prinst.addressableused); } } else ptr = QCPOINTERM(i); ptr->_int = OPA->_int; break; case OP_STOREP_I64: // 64bit i = OPB->_int + OPC->_int*sizeof(ptr->_int); errorif (QCPOINTERWRITEFAIL(i, sizeof(ptr->i64))) { if (!(ptr=PR_GetWriteTempStringPtr(progfuncs, OPB->_int, OPC->_int*sizeof(ptr->_int), sizeof(ptr->i64)))) { if (i == -1) break; if (i == 0) QCFAULT(&progfuncs->funcs, "bad pointer write in %s (null pointer)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name)); else QCFAULT(&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, prinst.addressableused); } } else ptr = QCPOINTERM(i); ptr->i64 = OPA->i64; break; case OP_STOREP_V: i = OPB->_int + (OPC->_int*sizeof(ptr->_int)); errorif (QCPOINTERWRITEFAIL(i, sizeof(pvec3_t))) { if (!(ptr=PR_GetWriteTempStringPtr(progfuncs, OPB->_int, OPC->_int*sizeof(ptr->_int), sizeof(pvec3_t)))) { if (i == -1) break; QCFAULT(&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, prinst.addressableused); } } else ptr = QCPOINTERM(i); ptr->_vector[0] = OPA->_vector[0]; ptr->_vector[1] = OPA->_vector[1]; ptr->_vector[2] = OPA->_vector[2]; break; case OP_STOREP_C: //store (float) character in a string i = OPB->_int + (OPC->_int)*sizeof(char); errorif (QCPOINTERWRITEFAIL(i, sizeof(char))) { if (!(ptr=PR_GetWriteTempStringPtr(progfuncs, OPB->_int, OPC->_int*sizeof(char), sizeof(char)))) { if (i == -1) break; QCFAULT(&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, prinst.addressableused); } } else ptr = QCPOINTERM(i); *(unsigned char *)ptr = (char)OPA->_float; break; case OP_STOREP_I8: //store (byte) character in a string i = OPB->_int + (OPC->_int)*sizeof(pbyte); errorif (QCPOINTERWRITEFAIL(i, sizeof(pbyte))) { if (!(ptr=PR_GetWriteTempStringPtr(progfuncs, OPB->_int, OPC->_int*sizeof(pbyte), sizeof(pbyte)))) { if (i == -1) break; QCFAULT(&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, prinst.addressableused); } } else ptr = QCPOINTERM(i); *(pbyte *)ptr = (pbyte)OPA->_int; break; case OP_STOREP_I16: //store short to a pointer i = OPB->_int + (OPC->_int)*sizeof(short); errorif (QCPOINTERWRITEFAIL(i, sizeof(short))) { if (!(ptr=PR_GetWriteTempStringPtr(progfuncs, OPB->_int, OPC->_int*sizeof(short), sizeof(short)))) { if (i == -1) break; QCFAULT(&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, prinst.addressableused); } } else ptr = QCPOINTERM(i); *(short *)ptr = (short)OPA->_int; break; case OP_STOREF_F: case OP_STOREF_I: case OP_STOREF_S: errorif ((unsigned)OPA->edict >= (unsigned)num_edicts) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_STOREF_? references invalid entity in %s\n", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; break; } ed = PROG_TO_EDICT_PB(progfuncs, OPA->edict); errorif (!ed || ed->readonly) { //boot it over to the debugger #if INTSIZE == 16 ddef16_t *d = ED_GlobalAtOfs16(progfuncs, st->a); #else ddef32_t *d = ED_GlobalAtOfs32(progfuncs, st->a); #endif fdef_t *f = ED_FieldAtOfs(progfuncs, OPB->_int + progfuncs->funcs.fieldadjust); if (PR_ExecRunWarning(&progfuncs->funcs, st-pr_statements, "assignment to read-only entity %i in %s (%s.%s)\n", OPA->edict, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), d?PR_StringToNative(&progfuncs->funcs, d->s_name):"??", f?f->name:"??")) return prinst.pr_xstatement; break; } //Whilst the next block would technically be correct, we don't use it as it breaks too many quake mods. #ifdef NOLEGACY errorif (ed->ereftype == ER_FREE) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "assignment to free entity in %s", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; break; } #endif i = OPB->_int + progfuncs->funcs.fieldadjust; errorif ((unsigned int)i*4 >= ed->fieldsize) //FIXME:lazy size check { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_STOREF_? references invalid field %i in %s\n", OPB->_int, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; break; } ptr = (eval_t *)(((int *)edvars(ed)) + i); ptr->_int = OPC->_int; break; case OP_STOREF_I64: errorif ((unsigned)OPA->edict >= (unsigned)num_edicts) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_STOREF_? references invalid entity in %s\n", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; break; } ed = PROG_TO_EDICT_PB(progfuncs, OPA->edict); errorif (!ed || ed->readonly) { //boot it over to the debugger #if INTSIZE == 16 ddef16_t *d = ED_GlobalAtOfs16(progfuncs, st->a); #else ddef32_t *d = ED_GlobalAtOfs32(progfuncs, st->a); #endif fdef_t *f = ED_FieldAtOfs(progfuncs, OPB->_int + progfuncs->funcs.fieldadjust); if (PR_ExecRunWarning(&progfuncs->funcs, st-pr_statements, "assignment to read-only entity %i in %s (%s.%s)\n", OPA->edict, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), d?PR_StringToNative(&progfuncs->funcs, d->s_name):"??", f?f->name:"??")) return prinst.pr_xstatement; break; } //Whilst the next block would technically be correct, we don't use it as it breaks too many quake mods. #ifdef NOLEGACY errorif (ed->ereftype == ER_FREE) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "assignment to free entity in %s", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; break; } #endif i = OPB->_int + progfuncs->funcs.fieldadjust; errorif ((unsigned int)i*4 >= ed->fieldsize) //FIXME:lazy size check { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_STOREF_? references invalid field %i in %s\n", OPB->_int, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; break; } ptr = (eval_t *)(((int *)edvars(ed)) + i); ptr->i64 = OPC->i64; break; case OP_STOREF_V: errorif ((unsigned)OPA->edict >= (unsigned)num_edicts) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_STOREF_? references invalid entity in %s\n", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; break; } ed = PROG_TO_EDICT_PB(progfuncs, OPA->edict); errorif (!ed || ed->readonly) { //boot it over to the debugger #if INTSIZE == 16 ddef16_t *d = ED_GlobalAtOfs16(progfuncs, st->a); #else ddef32_t *d = ED_GlobalAtOfs32(progfuncs, st->a); #endif fdef_t *f = ED_FieldAtOfs(progfuncs, OPB->_int + progfuncs->funcs.fieldadjust); if (PR_ExecRunWarning(&progfuncs->funcs, st-pr_statements, "assignment to read-only entity %i in %s (%s.%s)\n", OPA->edict, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), d?PR_StringToNative(&progfuncs->funcs, d->s_name):"??", f?f->name:"??")) return prinst.pr_xstatement; break; } //Whilst the next block would technically be correct, we don't use it as it breaks too many quake mods. #ifdef NOLEGACY errorif (ed->ereftype == ER_FREE) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "assignment to free entity in %s", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; break; } #endif i = OPB->_int + progfuncs->funcs.fieldadjust; errorif ((unsigned int)i*4 >= ed->fieldsize) //FIXME:lazy size check { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_STOREF_? references invalid field %i in %s\n", OPB->_int, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; break; } ptr = (eval_t *)(((int *)edvars(ed)) + i); ptr->_vector[0] = OPC->_vector[0]; ptr->_vector[1] = OPC->_vector[1]; ptr->_vector[2] = OPC->_vector[2]; break; //get a pointer to a field var case OP_ADDRESS: errorif ((unsigned)OPA->edict >= (unsigned)num_edicts) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_ADDRESS references invalid entity in %s\n", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; break; } ed = PROG_TO_EDICT_PB(progfuncs, OPA->edict); #ifdef PARANOID NUM_FOR_EDICT(ed); // make sure it's in range #endif errorif (!ed || ed->readonly) { //boot it over to the debugger { #if INTSIZE == 16 ddef16_t *d = ED_GlobalAtOfs16(progfuncs, st->a); #else ddef32_t *d = ED_GlobalAtOfs32(progfuncs, st->a); #endif fdef_t *f = ED_FieldAtOfs(progfuncs, OPB->_int + progfuncs->funcs.fieldadjust); if (PR_ExecRunWarning(&progfuncs->funcs, st-pr_statements, "assignment to read-only entity %i in %s (%s.%s)\n", OPA->edict, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), d?PR_StringToNative(&progfuncs->funcs, d->s_name):"??", f?f->name:"??")) return prinst.pr_xstatement; OPC->_int = ~0; break; } } //Whilst the next block would technically be correct, we don't use it as it breaks too many quake mods. #ifdef NOLEGACY errorif (ed->ereftype == ER_FREE) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "assignment to free entity in %s", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; break; } #endif i = OPB->_int + progfuncs->funcs.fieldadjust; #ifdef PARANOID errorif ((unsigned int)i*4 >= ed->fieldsize) //FIXME:lazy size check { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_ADDRESS references invalid field %i in %s\n", OPB->_int, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; OPC->_int = 0; break; } #endif OPC->_int = ENGINEPOINTER((((int *)edvars(ed)) + i)); break; //load a field to a value case OP_LOAD_P: case OP_LOAD_I: case OP_LOAD_F: case OP_LOAD_FLD: case OP_LOAD_ENT: case OP_LOAD_S: case OP_LOAD_FNC: errorif ((unsigned)OPA->edict >= (unsigned)num_edicts) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_LOAD references invalid entity %i in %s\n", OPA->edict, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; OPC->_int = 0; break; } ed = PROG_TO_EDICT_PB(progfuncs, OPA->edict); #ifdef PARANOID NUM_FOR_EDICT(ed); // make sure it's in range #endif #ifdef NOLEGACY if (ed->ereftype == ER_FREE) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_LOAD references free entity %i in %s\n", OPA->edict, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; OPC->_int = 0; } else #endif { i = OPB->_int + progfuncs->funcs.fieldadjust; errorif ((unsigned int)(i+1)*4 > ed->fieldsize) //FIXME:lazy size check { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_LOAD references invalid field %i in %s\n", OPB->_int, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; OPC->_int = 0; break; } ptr = (eval_t *)(((int *)edvars(ed)) + i); OPC->_int = ptr->_int; } break; case OP_LOAD_I64: errorif ((unsigned)OPA->edict >= (unsigned)num_edicts) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_LOAD_V references invalid entity %i in %s\n", OPA->edict, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; OPC->_vector[0] = 0; OPC->_vector[1] = 0; OPC->_vector[2] = 0; break; } ed = PROG_TO_EDICT_PB(progfuncs, OPA->edict); #ifdef PARANOID NUM_FOR_EDICT(ed); // make sure it's in range #endif #ifdef NOLEGACY if (ed->ereftype == ER_FREE) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_LOAD references free entity %i in %s\n", OPA->edict, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; OPC->_vector[0] = 0; OPC->_vector[1] = 0; OPC->_vector[2] = 0; } else #endif { i = OPB->_int + progfuncs->funcs.fieldadjust; errorif ((unsigned int)(i+2)*4 > ed->fieldsize) //FIXME:lazy size check { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_LOAD references invalid field %i in %s\n", OPB->_int, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; OPC->_int = 0; break; } ptr = (eval_t *)(((int *)edvars(ed)) + i); OPC->i64 = ptr->i64; } break; case OP_LOAD_V: errorif ((unsigned)OPA->edict >= (unsigned)num_edicts) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_LOAD_V references invalid entity %i in %s\n", OPA->edict, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; OPC->_vector[0] = 0; OPC->_vector[1] = 0; OPC->_vector[2] = 0; break; } ed = PROG_TO_EDICT_PB(progfuncs, OPA->edict); #ifdef PARANOID NUM_FOR_EDICT(ed); // make sure it's in range #endif #ifdef NOLEGACY if (ed->ereftype == ER_FREE) { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_LOAD references free entity %i in %s\n", OPA->edict, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; OPC->_vector[0] = 0; OPC->_vector[1] = 0; OPC->_vector[2] = 0; } else #endif { i = OPB->_int + progfuncs->funcs.fieldadjust; errorif ((unsigned int)(i+3)*4 > ed->fieldsize) //FIXME:lazy size check { if (PR_ExecRunWarning (&progfuncs->funcs, st-pr_statements, "OP_LOAD references invalid field %i in %s\n", OPB->_int, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name))) return prinst.pr_xstatement; OPC->_int = 0; break; } ptr = (eval_t *)(((int *)edvars(ed)) + i); OPC->_vector[0] = ptr->_vector[0]; OPC->_vector[1] = ptr->_vector[1]; OPC->_vector[2] = ptr->_vector[2]; } break; //================== case OP_IFNOT_S: RUNAWAYCHECK(); if (!OPA->string || !PR_StringToNative(&progfuncs->funcs, OPA->string)) st += (sofs)st->b - 1; // offset the s++ break; case OP_IFNOT_F: RUNAWAYCHECK(); if (!EVAL_FLOATISTRUE(OPA)) st += (sofs)st->b - 1; // offset the s++ break; //WARNING: vanilla uses this for floats too, which results in a discrepancy with -0 case OP_IFNOT_I: RUNAWAYCHECK(); if (!OPA->_int) st += (sofs)st->b - 1; // offset the s++ break; case OP_IF_S: RUNAWAYCHECK(); if (OPA->string && PR_StringToNative(&progfuncs->funcs, OPA->string)) st += (sofs)st->b - 1; // offset the s++ break; case OP_IF_F: RUNAWAYCHECK(); if (EVAL_FLOATISTRUE(OPA)) st += (sofs)st->b - 1; // offset the s++ break; //WARNING: vanilla uses this for floats too, which results in a discrepancy with -0 case OP_IF_I: RUNAWAYCHECK(); if (OPA->_int) st += (sofs)st->b - 1; // offset the s++ break; case OP_GOTO: RUNAWAYCHECK(); st += (sofs)st->a - 1; // offset the s++ break; case OP_CALL8H: case OP_CALL7H: case OP_CALL6H: case OP_CALL5H: case OP_CALL4H: case OP_CALL3H: case OP_CALL2H: G_VECTOR(OFS_PARM1)[0] = OPC->_vector[0]; G_VECTOR(OFS_PARM1)[1] = OPC->_vector[1]; G_VECTOR(OFS_PARM1)[2] = OPC->_vector[2]; case OP_CALL1H: G_VECTOR(OFS_PARM0)[0] = OPB->_vector[0]; G_VECTOR(OFS_PARM0)[1] = OPB->_vector[1]; G_VECTOR(OFS_PARM0)[2] = OPB->_vector[2]; case OP_CALL8: case OP_CALL7: case OP_CALL6: case OP_CALL5: case OP_CALL4: case OP_CALL3: case OP_CALL2: case OP_CALL1: case OP_CALL0: { int newpr; unsigned int fnum; RUNAWAYCHECK(); prinst.pr_xstatement = st-pr_statements; if (op > OP_CALL8) progfuncs->funcs.callargc = op - (OP_CALL1H-1); else progfuncs->funcs.callargc = op - OP_CALL0; fnum = OPA->function; glob = NULL; //try to derestrict it. progfuncs->funcs.callprogs=prinst.pr_typecurrent; //so we can revert to the right caller. newpr = (fnum & 0xff000000)>>24; //this is the progs index of the callee fnum &= ~0xff000000; //the callee's function index. //if it's an external call, switch now (before any function pointers are used) errorif (!PR_SwitchProgsParms(progfuncs, newpr) || !fnum || fnum > pr_progs->numfunctions) { char *msg = fnum?"OP_CALL references invalid function in %s\n":"NULL function from qc (inside %s).\n"; PR_SwitchProgsParms(progfuncs, progfuncs->funcs.callprogs); glob = pr_globals; if (!progfuncs->funcs.debug_trace) QCFAULT(&progfuncs->funcs, msg, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name)); //skip the instruction if they just try stepping over it anyway. PR_StackTrace(&progfuncs->funcs, 0); externs->Printf(msg, PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name)); pr_globals[OFS_RETURN] = 0; pr_globals[OFS_RETURN+1] = 0; pr_globals[OFS_RETURN+2] = 0; break; } newf = &pr_cp_functions[fnum & ~0xff000000]; if (newf->first_statement <= 0) { // negative statements are built in functions /*calling a builtin in another progs may affect that other progs' globals instead, is the theory anyway, so args and stuff need to move over*/ if (prinst.pr_typecurrent != 0) { //builtins quite hackily refer to only a single global. //for builtins to affect the globals of other progs, we need to first switch to the progs that it will affect, so they'll be correct when we switch back PR_SwitchProgsParms(progfuncs, 0); } i = -newf->first_statement; if (i < externs->numglobalbuiltins) { #ifndef QCGC prinst.numtempstringsstack = prinst.numtempstrings; #endif (*externs->globalbuiltins[i]) (&progfuncs->funcs, (struct globalvars_s *)current_progstate->globals); //in case ed_alloc was called num_edicts = sv_num_edicts; } else PR_RunError (&progfuncs->funcs, "Bad builtin call number - %i", -newf->first_statement); PR_SwitchProgsParms(progfuncs, (progsnum_t)progfuncs->funcs.callprogs); //decide weather non debugger wants to start debugging. return prinst.pr_xstatement; } s = PR_EnterFunction (progfuncs, newf, progfuncs->funcs.callprogs); st = &pr_statements[s]; } //resume at the new statement, which might be in a different progs return s; case OP_DONE: case OP_RETURN: RUNAWAYCHECK(); glob[OFS_RETURN] = glob[st->a]; glob[OFS_RETURN+1] = glob[st->a+1]; glob[OFS_RETURN+2] = glob[st->a+2]; s = PR_LeaveFunction (progfuncs); st = &pr_statements[s]; if (prinst.pr_depth == prinst.exitdepth) { prinst.pr_xstatement = s; return -1; // all done } return s; // break; case OP_STATE: externs->stateop(&progfuncs->funcs, OPA->_float, OPB->function); break; case OP_ADD_I: OPC->_int = OPA->_int + OPB->_int; break; case OP_ADD_FI: OPC->_float = OPA->_float + (float)OPB->_int; break; case OP_ADD_IF: OPC->_float = (float)OPA->_int + OPB->_float; break; case OP_SUB_I: OPC->_int = OPA->_int - OPB->_int; break; case OP_SUB_FI: OPC->_float = OPA->_float - (float)OPB->_int; break; case OP_SUB_IF: OPC->_float = (float)OPA->_int - OPB->_float; break; case OP_CONV_ITOF: OPC->_float = (float)OPA->_int; break; case OP_CONV_FTOI: OPC->_int = (int)OPA->_float; break; case OP_LOADP_ITOF: i = OPA->_int; errorif (QCPOINTERREADFAIL(i, sizeof(char))) { QCFAULT(&progfuncs->funcs, "bad pointer read in %s (%#x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), OPA->_int); } ptr = QCPOINTERM(i); OPC->_float = (float)ptr->_int; break; case OP_LOADP_FTOI: i = OPA->_int; errorif (QCPOINTERREADFAIL(i, sizeof(char))) { QCFAULT(&progfuncs->funcs, "bad pointer read in %s (%#x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), OPA->_int); } ptr = QCPOINTERM(i); OPC->_int = (int)ptr->_float; break; case OP_BITAND_I: OPC->_int = (OPA->_int & OPB->_int); break; case OP_BITOR_I: OPC->_int = (OPA->_int | OPB->_int); break; case OP_MUL_I: OPC->_int = OPA->_int * OPB->_int; break; case OP_DIV_I: if (OPB->_int == 0) //no division by zero allowed... OPC->_int = 0; else if (OPB->_int == -1 && OPA->_int==(int)0x80000000) OPC->_int = 0x7fffffff; else OPC->_int = OPA->_int / OPB->_int; break; case OP_DIV_U: if (OPB->_uint == 0) //no division by zero allowed... OPC->_uint = 0; else OPC->_uint = OPA->_uint / OPB->_uint; break; case OP_EQ_I: OPC->_int = (OPA->_int == OPB->_int); break; case OP_NE_I: OPC->_int = (OPA->_int != OPB->_int); break; //array/structure reading/writing. case OP_GLOBALADDRESS: OPC->_int = ENGINEPOINTER(&OPA->_int + OPB->_int); /*pointer arithmatic*/ break; case OP_ADD_PIW: //pointer to 32 bit (remember to *3 for vectors) OPC->_int = OPA->_int + OPB->_int*sizeof(float); break; case OP_LOADA_I: case OP_LOADA_F: case OP_LOADA_FLD: case OP_LOADA_ENT: case OP_LOADA_S: case OP_LOADA_FNC: i = st->a + OPB->_int; if ((size_t)i >= (size_t)(current_progstate->globals_bytes>>2)) { QCFAULT(&progfuncs->funcs, "bad array read in %s (index %i)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), OPB->_int); } else OPC->_int = ((eval_t *)&glob[i])->_int; break; case OP_LOADA_I64: i = st->a + OPB->_int; if ((size_t)i >= (size_t)(current_progstate->globals_bytes>>2)-1u) { QCFAULT(&progfuncs->funcs, "bad array read in %s (index %i)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), OPB->_int); } else OPC->i64 = ((eval_t *)&glob[i])->i64; break; case OP_LOADA_V: i = st->a + OPB->_int; if ((size_t)(i) >= (size_t)(current_progstate->globals_bytes>>2)-2u) { QCFAULT(&progfuncs->funcs, "bad array read in %s (index %i)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), OPB->_int); } else { OPC->_vector[0] = ((eval_t *)&glob[i])->_vector[0]; OPC->_vector[1] = ((eval_t *)&glob[i])->_vector[1]; OPC->_vector[2] = ((eval_t *)&glob[i])->_vector[2]; } break; case OP_ADD_SF: //(char*)c = (char*)a + (float)b OPC->_int = OPA->_int + (int)OPB->_float; break; case OP_SUB_S: //(float)c = (char*)a - (char*)b OPC->_int = OPA->_int - OPB->_int; break; case OP_LOADP_C: //load character from a string/pointer i = (unsigned int)OPA->_int + (int)OPB->_float; errorif (QCPOINTERREADFAIL(i, sizeof(char))) { if (!(ptr=PR_GetReadTempStringPtr(progfuncs, OPA->_int, OPB->_float, sizeof(char)))) { if (i == -1) { OPC->_float = 0; break; } QCFAULT(&progfuncs->funcs, "bad pointer read in %s (%i bytes into %s)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, ptr); } } else ptr = QCPOINTERM(i); OPC->_float = *(unsigned char *)ptr; break; case OP_LOADP_U8: //load character from a string/pointer i = (unsigned int)OPA->_int + (int)OPB->_int; errorif (QCPOINTERREADFAIL(i, sizeof(pbyte))) { if (!(ptr=PR_GetReadTempStringPtr(progfuncs, OPA->_int, OPB->_int, sizeof(pbyte)))) { if (i == -1) { OPC->_int = 0; break; } QCFAULT(&progfuncs->funcs, "bad pointer read in %s (%i bytes into %s)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, ptr); } } else ptr = QCPOINTERM(i); OPC->_int = *(pbyte *)ptr; break; case OP_LOADP_I8: //load character from a string/pointer i = (unsigned int)OPA->_int + (int)OPB->_int; errorif (QCPOINTERREADFAIL(i, sizeof(pbyte))) { if (!(ptr=PR_GetReadTempStringPtr(progfuncs, OPA->_int, OPB->_int, sizeof(pbyte)))) { if (i == -1) { OPC->_int = 0; break; } QCFAULT(&progfuncs->funcs, "bad pointer read in %s (%i bytes into %s)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, ptr); } } else ptr = QCPOINTERM(i); OPC->_int = *(char *)ptr; break; case OP_LOADP_U16: //load character from a string/pointer i = (unsigned int)OPA->_int + (int)OPB->_int*2; errorif (QCPOINTERREADFAIL(i, sizeof(short))) { if (!(ptr=PR_GetReadTempStringPtr(progfuncs, OPA->_int, OPB->_int*2, sizeof(short)))) { if (i == -1) { OPC->_int = 0; break; } QCFAULT(&progfuncs->funcs, "bad pointer read in %s (%i bytes into %s)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, ptr); } } else ptr = QCPOINTERM(i); OPC->_int = *(unsigned short *)ptr; break; case OP_LOADP_I16: //load character from a string/pointer i = (unsigned int)OPA->_int + (int)OPB->_int*2; errorif (QCPOINTERREADFAIL(i, sizeof(short))) { if (!(ptr=PR_GetReadTempStringPtr(progfuncs, OPA->_int, OPB->_int*2, sizeof(short)))) { if (i == -1) { OPC->_int = 0; break; } QCFAULT(&progfuncs->funcs, "bad pointer read in %s (%i bytes into %s)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, ptr); } } else ptr = QCPOINTERM(i); OPC->_int = *(short *)ptr; break; case OP_LOADP_I: case OP_LOADP_F: case OP_LOADP_FLD: case OP_LOADP_ENT: case OP_LOADP_S: case OP_LOADP_FNC: i = OPA->_int + OPB->_int*4; errorif (QCPOINTERREADFAIL(i, sizeof(int))) { if (!(ptr=PR_GetReadTempStringPtr(progfuncs, OPA->_int, OPB->_int*4, sizeof(int)))) { if (i == -1) { OPC->_int = 0; break; } QCFAULT(&progfuncs->funcs, "bad pointer read in %s (from %#x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i); } } else ptr = QCPOINTERM(i); OPC->_int = ptr->_int; break; case OP_LOADP_I64: i = OPA->_int + OPB->_int*4; errorif (QCPOINTERREADFAIL(i, sizeof(pint64_t))) { if (!(ptr=PR_GetReadTempStringPtr(progfuncs, OPA->_int, OPB->_int*4, sizeof(pint64_t)))) { if (i == -1) { OPC->i64 = 0; break; } QCFAULT(&progfuncs->funcs, "bad pointer read in %s (from %#x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i); } } else ptr = QCPOINTERM(i); OPC->i64 = ptr->i64; break; case OP_LOADP_V: i = OPA->_int + OPB->_int*4; //NOTE: inconsistant, but a bit more practical for the qcc when structs etc are involved errorif (QCPOINTERREADFAIL(i, sizeof(pvec3_t))) { if (!(ptr=PR_GetReadTempStringPtr(progfuncs, OPA->_int, OPB->_int*4, sizeof(pvec3_t)))) { if (i == -1) { OPC->_vector[0] = 0; OPC->_vector[1] = 0; OPC->_vector[2] = 0; break; } QCFAULT(&progfuncs->funcs, "bad pointer read in %s (from %#x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i); } } else ptr = QCPOINTERM(i); OPC->_vector[0] = ptr->_vector[0]; OPC->_vector[1] = ptr->_vector[1]; OPC->_vector[2] = ptr->_vector[2]; break; case OP_BITXOR_I: OPC->_int = OPA->_int ^ OPB->_int; break; case OP_RSHIFT_I: OPC->_int = A_RSHIFT_I(OPA->_int, OPB->_int); break; case OP_RSHIFT_U: OPC->_uint = OPA->_uint >> OPB->_uint; break; case OP_LSHIFT_I: OPC->_int = OPA->_int << OPB->_int; break; //hexen2 arrays contain a prefix global set to (arraysize-1) inserted before the actual array data //for vectors, this prefix is the number of vectors rather than the number of globals. this can cause issues with using OP_FETCH_GBL_V within structs. case OP_FETCH_GBL_F: case OP_FETCH_GBL_S: case OP_FETCH_GBL_E: case OP_FETCH_GBL_FNC: i = OPB->_float; errorif((unsigned)i > (unsigned)((eval_t *)&glob[st->a-1])->_int) { prinst.pr_xstatement = st-pr_statements; PR_RunError(&progfuncs->funcs, "array index out of bounds: %s[%d] (max %d)", PR_GlobalStringNoContents(progfuncs, st->a), i, ((eval_t *)&glob[st->a-1])->_int); } OPC->_int = ((eval_t *)&glob[st->a + i])->_int; break; case OP_FETCH_GBL_V: i = OPB->_float; errorif((unsigned)i > (unsigned)((eval_t *)&glob[st->a-1])->_int) { prinst.pr_xstatement = st-pr_statements; PR_RunError(&progfuncs->funcs, "array index out of bounds: %s[%d]", PR_GlobalStringNoContents(progfuncs, st->a), i); } ptr = (eval_t *)&glob[st->a + i*3]; OPC->_vector[0] = ptr->_vector[0]; OPC->_vector[1] = ptr->_vector[1]; OPC->_vector[2] = ptr->_vector[2]; break; case OP_CSTATE: externs->cstateop(&progfuncs->funcs, OPA->_float, OPB->_float, prinst.pr_xfunction - pr_cp_functions); break; case OP_CWSTATE: externs->cwstateop(&progfuncs->funcs, OPA->_float, OPB->_float, prinst.pr_xfunction - pr_cp_functions); break; case OP_THINKTIME: externs->thinktimeop(&progfuncs->funcs, (struct edict_s *)PROG_TO_EDICT_UB(progfuncs, OPA->edict), OPB->_float); break; case OP_MULSTORE_F: /*OPC->_float = */OPB->_float *= OPA->_float; break; case OP_MULSTORE_VF: tmpf = OPA->_float; //don't break on vec*=vec_x; /*OPC->_vector[0] = */OPB->_vector[0] *= tmpf; /*OPC->_vector[1] = */OPB->_vector[1] *= tmpf; /*OPC->_vector[2] = */OPB->_vector[2] *= tmpf; break; case OP_MULSTOREP_F: i = OPB->_int; errorif (QCPOINTERWRITEFAIL(i, sizeof(float))) { prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, (unsigned)prinst.addressableused); } ptr = QCPOINTERM(i); OPC->_float = ptr->_float *= OPA->_float; break; case OP_MULSTOREP_VF: i = OPB->_int; errorif (QCPOINTERWRITEFAIL(i, sizeof(pvec3_t))) { prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, (unsigned)prinst.addressableused); } ptr = QCPOINTERM(i); tmpf = OPA->_float; //don't break on vec*=vec_x; OPC->_vector[0] = ptr->_vector[0] *= tmpf; OPC->_vector[1] = ptr->_vector[1] *= tmpf; OPC->_vector[2] = ptr->_vector[2] *= tmpf; break; case OP_DIVSTORE_F: /*OPC->_float = */OPB->_float /= OPA->_float; break; case OP_DIVSTOREP_F: i = OPB->_int; errorif (QCPOINTERWRITEFAIL(i, sizeof(float))) { prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, (unsigned)prinst.addressableused); } ptr = QCPOINTERM(i); OPC->_float = ptr->_float /= OPA->_float; break; case OP_ADDSTORE_F: /*OPC->_float = */OPB->_float += OPA->_float; break; case OP_ADDSTORE_V: /*OPC->_vector[0] =*/ OPB->_vector[0] += OPA->_vector[0]; /*OPC->_vector[1] =*/ OPB->_vector[1] += OPA->_vector[1]; /*OPC->_vector[2] =*/ OPB->_vector[2] += OPA->_vector[2]; break; case OP_ADDSTOREP_F: i = OPB->_int; errorif (QCPOINTERWRITEFAIL(i, sizeof(float))) { prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, (unsigned)prinst.addressableused); } ptr = QCPOINTERM(i); OPC->_float = ptr->_float += OPA->_float; break; case OP_ADDSTOREP_V: i = OPB->_int; errorif (QCPOINTERWRITEFAIL(i, sizeof(pvec3_t))) { prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, (unsigned)prinst.addressableused); } ptr = QCPOINTERM(i); OPC->_vector[0] = ptr->_vector[0] += OPA->_vector[0]; OPC->_vector[1] = ptr->_vector[1] += OPA->_vector[1]; OPC->_vector[2] = ptr->_vector[2] += OPA->_vector[2]; break; case OP_SUBSTORE_F: /*OPC->_float = */OPB->_float -= OPA->_float; break; case OP_SUBSTORE_V: /*OPC->_vector[0] = */OPB->_vector[0] -= OPA->_vector[0]; /*OPC->_vector[1] = */OPB->_vector[1] -= OPA->_vector[1]; /*OPC->_vector[2] = */OPB->_vector[2] -= OPA->_vector[2]; break; case OP_SUBSTOREP_F: i = OPB->_int; errorif (QCPOINTERWRITEFAIL(i, sizeof(float))) { prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, (unsigned)prinst.addressableused); } ptr = QCPOINTERM(i); OPC->_float = ptr->_float -= OPA->_float; break; case OP_SUBSTOREP_V: i = OPB->_int; errorif (QCPOINTERWRITEFAIL(i, sizeof(pvec3_t))) { prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, (unsigned)prinst.addressableused); } ptr = QCPOINTERM(i); OPC->_vector[0] = ptr->_vector[0] -= OPA->_vector[0]; OPC->_vector[1] = ptr->_vector[1] -= OPA->_vector[1]; OPC->_vector[2] = ptr->_vector[2] -= OPA->_vector[2]; break; case OP_BITSETSTORE_F: OPB->_float = (int)OPB->_float | (int)OPA->_float; break; case OP_BITSETSTOREP_F: i = OPB->_int; errorif (QCPOINTERWRITEFAIL(i, sizeof(float))) { prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, (unsigned)prinst.addressableused); } ptr = QCPOINTERM(i); ptr->_float = (int)ptr->_float | (int)OPA->_float; break; case OP_BITCLRSTORE_F: OPB->_float = (int)OPB->_float & ~(int)OPA->_float; break; case OP_BITCLRSTOREP_F: i = OPB->_int; errorif (QCPOINTERWRITEFAIL(i, sizeof(float))) { prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "bad pointer write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), i, (unsigned)prinst.addressableused); } ptr = QCPOINTERM(i); ptr->_float = (int)ptr->_float & ~(int)OPA->_float; break; //for scaler randoms, prevent the random value from ever reaching 1 //this avoids issues when array[random()*array.length] case OP_RAND0: OPC->_float = (rand ()&0x7fff) / ((float)0x8000); break; case OP_RAND1: OPC->_float = (rand ()&0x7fff) / ((float)0x8000)*OPA->_float; break; case OP_RAND2: //backwards range shouldn't matter (except that it is b that is never reached, rather than the higher of the two) OPC->_float = OPA->_float + (rand ()&0x7fff) / ((float)0x8000)*(OPB->_float-OPA->_float); break; //random vectors DO result in 0 to 1 inclusive, to try to ensure a more balanced range case OP_RANDV0: OPC->_vector[0] = (rand ()&0x7fff) / ((float)0x7fff); OPC->_vector[1] = (rand ()&0x7fff) / ((float)0x7fff); OPC->_vector[2] = (rand ()&0x7fff) / ((float)0x7fff); break; case OP_RANDV1: OPC->_vector[0] = (rand ()&0x7fff) / ((float)0x7fff)*OPA->_vector[0]; OPC->_vector[1] = (rand ()&0x7fff) / ((float)0x7fff)*OPA->_vector[1]; OPC->_vector[2] = (rand ()&0x7fff) / ((float)0x7fff)*OPA->_vector[2]; break; case OP_RANDV2: //backwards range shouldn't matter OPC->_vector[0] = OPA->_vector[0] + (rand ()&0x7fff) / ((float)0x7fff)*(OPB->_vector[0]-OPA->_vector[0]); OPC->_vector[1] = OPA->_vector[1] + (rand ()&0x7fff) / ((float)0x7fff)*(OPB->_vector[1]-OPA->_vector[1]); OPC->_vector[2] = OPA->_vector[2] + (rand ()&0x7fff) / ((float)0x7fff)*(OPB->_vector[2]-OPA->_vector[2]); break; case OP_SWITCH_F: case OP_SWITCH_V: case OP_SWITCH_S: case OP_SWITCH_E: case OP_SWITCH_FNC: //the case opcodes depend upon the preceding switch. //otherwise the switch itself is much like a goto //don't embed the case/caserange checks directly into the switch so that custom caseranges can be potentially be implemented with hybrid emulation. switchcomparison = op - OP_SWITCH_F; switchref = OPA; RUNAWAYCHECK(); st += (sofs)st->b - 1; // offset the s++ break; case OP_SWITCH_I: //the case opcodes depend upon the preceding switch. //otherwise the switch itself is much like a goto //don't embed the case/caserange checks directly into the switch so that custom caseranges can be potentially be implemented with hybrid emulation. switchcomparison = OP_SWITCH_E - OP_SWITCH_F; switchref = OPA; RUNAWAYCHECK(); st += (sofs)st->b - 1; // offset the s++ break; case OP_CASE: //if the comparison is true, jump (back up) to the relevent code block if (casecmp[switchcomparison](progfuncs, switchref, OPA)) { RUNAWAYCHECK(); st += (sofs)st->b-1; // -1 to offset the s++ } break; case OP_CASERANGE: //if the comparison is true, jump (back up) to the relevent code block if (casecmprange[switchcomparison](progfuncs, switchref, OPA, OPB)) { RUNAWAYCHECK(); st += (sofs)st->c-1; // -1 to offset the s++ } break; case OP_BITAND_IF: OPC->_int = (OPA->_int & (int)OPB->_float); break; case OP_BITOR_IF: OPC->_int = (OPA->_int | (int)OPB->_float); break; case OP_BITAND_FI: OPC->_int = ((int)OPA->_float & OPB->_int); break; case OP_BITOR_FI: OPC->_int = ((int)OPA->_float | OPB->_int); break; case OP_MUL_IF: OPC->_float = (OPA->_int * OPB->_float); break; case OP_MUL_FI: OPC->_float = (OPA->_float * OPB->_int); break; case OP_MUL_VI: tmpi = OPB->_int; OPC->_vector[0] = OPA->_vector[0] * tmpi; OPC->_vector[1] = OPA->_vector[1] * tmpi; OPC->_vector[2] = OPA->_vector[2] * tmpi; break; case OP_MUL_IV: tmpi = OPA->_int; OPC->_vector[0] = tmpi * OPB->_vector[0]; OPC->_vector[1] = tmpi * OPB->_vector[1]; OPC->_vector[2] = tmpi * OPB->_vector[2]; break; case OP_DIV_IF: OPC->_float = (OPA->_int / OPB->_float); break; case OP_DIV_FI: OPC->_float = (OPA->_float / OPB->_int); break; /*case OP_MOD_I: OPC->_int = (OPA->_int % OPB->_int); break; case OP_MOD_U: OPC->_uint = (OPA->_uint % OPB->_uint); break; case OP_MOD_F: OPC->_float = OPA->_float - OPB->_float*(int)(OPA->_float/OPB->_float); break; case OP_MOD_V: OPC->_vector[0] = OPA->_vector[0] - OPB->_vector[0]*(int)(OPA->_vector[0]/OPB->_vector[0]); OPC->_vector[1] = OPA->_vector[1] - OPB->_vector[1]*(int)(OPA->_vector[1]/OPB->_vector[1]); OPC->_vector[2] = OPA->_vector[2] - OPB->_vector[2]*(int)(OPA->_vector[2]/OPB->_vector[2]); break;*/ case OP_AND_I: OPC->_int = (OPA->_int && OPB->_int); break; case OP_OR_I: OPC->_int = (OPA->_int || OPB->_int); break; case OP_AND_IF: OPC->_int = (OPA->_int && OPB->_float); break; case OP_OR_IF: OPC->_int = (OPA->_int || OPB->_float); break; case OP_AND_FI: OPC->_int = (OPA->_float && OPB->_int); break; case OP_OR_FI: OPC->_int = (OPA->_float || OPB->_int); break; case OP_NE_IF: OPC->_int = (OPA->_int != OPB->_float); break; case OP_NE_FI: OPC->_int = (OPA->_float != OPB->_int); break; case OP_GADDRESS: //return glob[aint+bfloat] //this instruction is not implemented due to the weirdness of it. //its theoretically a more powerful load... but untyped? //or is it meant to be an LEA instruction (that could simply be switched with OP_GLOAD_I) prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "OP_GADDRESS not implemented (found in %s)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name)); break; case OP_GLOAD_I: case OP_GLOAD_F: case OP_GLOAD_FLD: case OP_GLOAD_ENT: case OP_GLOAD_S: case OP_GLOAD_FNC: errorif (OPA->_int < 0 || OPA->_int >= (current_progstate->globals_bytes>>2)) { prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "bad indexed global read in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), OPA->_int, current_progstate->globals_bytes>>2); } ptr = ((eval_t *)&glob[OPA->_int]); OPC->_int = ptr->_int; break; case OP_GLOAD_V: errorif (OPA->_int < 0 || OPA->_int >= (current_progstate->globals_bytes>>2)-2u) { prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "bad indexed global read in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), OPA->_int, current_progstate->globals_bytes>>2); } ptr = ((eval_t *)&glob[OPA->_int]); OPC->_vector[0] = ptr->_vector[0]; OPC->_vector[1] = ptr->_vector[1]; OPC->_vector[2] = ptr->_vector[2]; break; case OP_GSTOREP_I: case OP_GSTOREP_F: case OP_GSTOREP_ENT: case OP_GSTOREP_FLD: case OP_GSTOREP_S: case OP_GSTOREP_FNC: errorif (OPB->_int < 0 || OPB->_int >= (current_progstate->globals_bytes>>2)) { prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "bad indexed global write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), OPB->_int, current_progstate->globals_bytes>>2); } ptr = ((eval_t *)&glob[OPB->_int]); ptr->_int = OPA->_int; break; case OP_GSTOREP_V: errorif (OPB->_int < 0 || OPB->_int >= (current_progstate->globals_bytes>>2)-2u) { prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "bad indexed global write in %s (%x >= %x)", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name), OPB->_int, current_progstate->globals_bytes>>2); } ptr = ((eval_t *)&glob[OPB->_int]); ptr->_vector[0] = OPA->_vector[0]; ptr->_vector[1] = OPA->_vector[1]; ptr->_vector[2] = OPA->_vector[2]; break; case OP_BOUNDCHECK: errorif ((unsigned int)OPA->_int < (unsigned int)st->c || (unsigned int)OPA->_int >= (unsigned int)st->b) { externs->Printf("Progs boundcheck failed. Value is %i. Must be %u<=value<%u\n", OPA->_int, st->c, st->b); QCFAULT(&progfuncs->funcs, "Progs boundcheck failed. Value is %i. Must be %u<=value<%u\n", OPA->_int, st->c, st->b); /* s=ShowStepf(progfuncs, st - pr_statements, "Progs boundcheck failed. Value is %i. Must be between %u and %u\n", OPA->_int, st->c, st->b); if (st == pr_statements + s) PR_RunError(&progfuncs->funcs, "unable to resume boundcheck"); st = pr_statements + s; return s; */ } break; case OP_PUSH: //note: OPA is words, not bytes. OPC->_int = ENGINEPOINTER(&prinst.localstack[prinst.localstack_used+prinst.spushed]); prinst.spushed += OPA->_uint; if (prinst.spushed + prinst.localstack_used >= LOCALSTACK_SIZE) { i = prinst.spushed; prinst.spushed = 0; prinst.pr_xstatement = st-pr_statements; PR_RunError(&progfuncs->funcs, "Progs pushed too much (%i bytes, %i parents, max %i)", i, prinst.localstack_used, LOCALSTACK_SIZE); } break; /* case OP_POP: pr_spushed -= OPA->_uint; if (pr_spushed < 0) { pr_spushed = 0; prinst.pr_xstatement = st-pr_statements; PR_RunError(progfuncs, "Progs poped more than it pushed"); } break; */ //[u]int64+double opcodes case OP_ADD_I64: OPC->i64 = OPA->i64 + OPB->i64; break; case OP_SUB_I64: OPC->i64 = OPA->i64 - OPB->i64; break; case OP_MUL_I64: OPC->i64 = OPA->i64 * OPB->i64; break; case OP_DIV_I64: OPC->i64 = OPA->i64 / OPB->i64; break; case OP_BITAND_I64: OPC->i64 = OPA->i64 & OPB->i64; break; case OP_BITOR_I64: OPC->i64 = OPA->i64 | OPB->i64; break; case OP_BITXOR_I64: OPC->i64 = OPA->i64 ^ OPB->i64; break; case OP_LSHIFT_I64I: OPC->i64 = OPA->i64 << OPB->_int; break; case OP_RSHIFT_I64I: OPC->i64 = A_RSHIFT_I(OPA->i64, OPB->_int); break; case OP_LT_I64: OPC->_int = OPA->i64 < OPB->i64; break; case OP_LE_I64: OPC->_int = OPA->i64 <= OPB->i64; break; case OP_EQ_I64: OPC->_int = OPA->i64 == OPB->i64; break; case OP_NE_I64: OPC->_int = OPA->i64 != OPB->i64; break; case OP_LT_U64: OPC->_int = OPA->u64 < OPB->u64; break; case OP_LE_U64: OPC->_int = OPA->u64 <= OPB->u64; break; case OP_DIV_U64: OPC->u64 = OPA->u64 / OPB->u64; break; case OP_RSHIFT_U64I: OPC->u64 = OPA->u64 >> OPB->_int; break; case OP_STORE_I64: OPB->i64 = OPA->i64; break; case OP_CONV_UI64: OPC->i64 = OPA->_uint; break; case OP_CONV_II64: OPC->i64 = OPA->_int; break; case OP_CONV_I64I: OPC->_int = OPA->i64; break; case OP_CONV_FD: OPC->_double = OPA->_float; break; case OP_CONV_DF: OPC->_float = OPA->_double; break; case OP_CONV_I64F: OPC->_float = OPA->i64; break; case OP_CONV_FI64: OPC->i64 = OPA->_float; break; case OP_CONV_I64D: OPC->_double = OPA->i64; break; case OP_CONV_DI64: OPC->i64 = OPA->_double; break; case OP_CONV_U64D: OPC->_double = OPA->u64; break; case OP_CONV_DU64: OPC->u64 = OPA->_double; break; case OP_CONV_U64F: OPC->_float = OPA->u64; break; case OP_CONV_FU64: OPC->u64 = OPA->_float; break; case OP_ADD_D: OPC->_double = OPA->_double + OPB->_double; break; case OP_SUB_D: OPC->_double = OPA->_double - OPB->_double; break; case OP_MUL_D: OPC->_double = OPA->_double * OPB->_double; break; case OP_DIV_D: OPC->_double = OPA->_double / OPB->_double; break; case OP_LT_D: OPC->_int = OPA->_double < OPB->_double; break; case OP_LE_D: OPC->_int = OPA->_double <= OPB->_double; break; case OP_EQ_D: OPC->_int = OPA->_double == OPB->_double; break; case OP_NE_D: OPC->_int = OPA->_double != OPB->_double; break; case OP_BITEXTEND_I: OPC->_int = A_RSHIFT_I(( signed int)(OPA->_int << (32-(OPB->_uint&0xff)-(OPB->_uint>>8))), (signed)(32-(OPB->_uint&0xff))); break; //shift it up and down. should sign extend. case OP_BITEXTEND_U: OPC->_uint = (unsigned int)(OPA->_uint << (32-(OPB->_uint&0xff)-(OPB->_uint>>8))) >> (32-(OPB->_uint&0xff)); break; //shift it up and down. should clear the bits. case OP_BITCOPY_I: i=((1<<(OPB->_uint&0xff))-1);OPC->_uint=(OPC->_uint&~(i<<(OPB->_uint>>8)))|(((OPA->_uint&i)<<(OPB->_uint>>8)));break; //replaces the specified bits (uses the same format bitextend uses to select its input to extend) case OP_CONV_UF: OPC->_float = OPA->_uint; break; case OP_CONV_FU: OPC->_uint = OPA->_float; break; case OP_UNUSED: case OP_POP: #ifdef __GNUC__ case OP_NUMREALOPS ... OP_NUMOPS: #endif safedefault: if (op & OP_BIT_BREAKPOINT) //break point! { op &= ~OP_BIT_BREAKPOINT; s = st-pr_statements; if (prinst.pr_xstatement != s) { prinst.pr_xstatement = s; externs->Printf("Break point hit in %s.\n", PR_StringToNative(&progfuncs->funcs, prinst.pr_xfunction->s_name)); s = ShowStep(progfuncs, s, NULL, false); st = &pr_statements[s]; //let the user move execution prinst.pr_xstatement = s = st-pr_statements; op = st->op & ~OP_BIT_BREAKPOINT; } goto reeval; //reexecute } prinst.pr_xstatement = st-pr_statements; PR_RunError (&progfuncs->funcs, "Bad opcode %i", st->op); } } #undef reeval #undef st #undef pr_statements #undef fakeop #undef dstatement_t #undef sofs #undef OPCODE #undef ENGINEPOINTER #undef QCPOINTER #undef QCPOINTERM fteqcc-20251105/./initlib.c0000644000200200001440000014773715233070110014545 0ustar twolifeusers#define PROGSUSED #include "progsint.h" #include static void PR_FreeAllTemps (progfuncs_t *progfuncs); typedef struct prmemb_s { struct prmemb_s *prev; int level; } prmemb_t; void *PRHunkAlloc(progfuncs_t *progfuncs, int ammount, const char *name) { prmemb_t *mem; ammount = sizeof(prmemb_t)+((ammount + 3)&~3); mem = progfuncs->funcs.parms->memalloc(ammount); memset(mem, 0, ammount); mem->prev = prinst.memblocks; if (!prinst.memblocks) mem->level = 1; else mem->level = ((prmemb_t *)prinst.memblocks)->level+1; prinst.memblocks = mem; return ((char *)mem)+sizeof(prmemb_t); } static void *PDECL QC_HunkAlloc(pubprogfuncs_t *ppf, int ammount, char *name) { return PRHunkAlloc((progfuncs_t*)ppf, ammount, name); } int PRHunkMark(progfuncs_t *progfuncs) { return ((prmemb_t *)prinst.memblocks)->level; } void PRHunkFree(progfuncs_t *progfuncs, int mark) { prmemb_t *omem; while(prinst.memblocks) { if (prinst.memblocks->level <= mark) return; omem = prinst.memblocks; prinst.memblocks = prinst.memblocks->prev; externs->memfree(omem); } return; } /*if we ran out of memory, the vm can allocate a new block, but doing so requires fixing up all sorts of pointers*/ static void PRAddressableRelocate(progfuncs_t *progfuncs, char *oldb, char *newb, int oldlen) { unsigned int i; edictrun_t *e; for (i=0 ; ifields >= oldb && (char*)e->fields < oldb+oldlen) e->fields = ((char*)e->fields - oldb) + newb; } if (progfuncs->funcs.stringtable >= oldb && progfuncs->funcs.stringtable < oldb+oldlen) progfuncs->funcs.stringtable = (progfuncs->funcs.stringtable - oldb) + newb; for (i=0; i < prinst.maxprogs; i++) { if ((char*)prinst.progstate[i].globals >= oldb && (char*)prinst.progstate[i].globals < oldb+oldlen) prinst.progstate[i].globals = (float*)(((char*)prinst.progstate[i].globals - oldb) + newb); if (prinst.progstate[i].strings >= oldb && prinst.progstate[i].strings < oldb+oldlen) prinst.progstate[i].strings = (prinst.progstate[i].strings - oldb) + newb; } for (i = 0; i < prinst.numfields; i++) { if (prinst.field[i].name >= oldb && prinst.field[i].name < oldb+oldlen) prinst.field[i].name = (prinst.field[i].name - oldb) + newb; } externs->addressablerelocated(&progfuncs->funcs, oldb, newb, oldlen); } //for 64bit systems. :) //addressable memory is memory available to the vm itself for writing. //once allocated, it cannot be freed for the lifetime of the VM. //if src is null, data srcsize is left uninitialised for speed. //pad is always 0-filled. void *PRAddressableExtend(progfuncs_t *progfuncs, void *src, size_t srcsize, int pad) { char *ptr; int ammount = (srcsize+pad + 4)&~3; //round up to 4 pad = ammount - srcsize; pad++; //make sure there's always a null, to allow strings to be a little more lazy. if (prinst.addressableused + ammount >= prinst.addressablesize) { /*only do this if the caller states that it can cope with addressable-block relocations/resizes*/ if (externs->addressablerelocated) { #if defined(_WIN32) && !defined(WINRT) char *newblock; #if 0//def _DEBUG int oldtot = addressablesize; #endif int newsize = (prinst.addressableused + ammount + 4096) & ~(4096-1); newblock = VirtualAlloc (NULL, prinst.addressablesize, MEM_RESERVE, PAGE_NOACCESS); if (newblock) { VirtualAlloc (newblock, prinst.addressableused, MEM_COMMIT, PAGE_READWRITE); memcpy(newblock, prinst.addressablehunk, prinst.addressableused); #if 0//def _DEBUG VirtualAlloc (prinst.addressablehunk, oldtot, MEM_RESERVE, PAGE_NOACCESS); #else VirtualFree (prinst.addressablehunk, 0, MEM_RELEASE); #endif PRAddressableRelocate(progfuncs, prinst.addressablehunk, newblock, prinst.addressableused); prinst.addressablehunk = newblock; prinst.addressablesize = newsize; } #else int newsize = (prinst.addressableused + ammount + 1024*1024) & ~(1024*1024-1); char *newblock = malloc(newsize); if (newblock) { PRAddressableRelocate(progfuncs, prinst.addressablehunk, newblock, prinst.addressableused); free(prinst.addressablehunk); prinst.addressablehunk = newblock; prinst.addressablesize = newsize; } #endif } if (prinst.addressableused + ammount >= prinst.addressablesize) externs->Sys_Error("Not enough addressable memory for progs VM (using %gmb)", prinst.addressablesize/(1024.0*1024)); } prinst.addressableused += ammount; progfuncs->funcs.stringtablesize = prinst.addressableused; #if defined(_WIN32) && !defined(WINRT) if (!VirtualAlloc (prinst.addressablehunk, prinst.addressableused+1, MEM_COMMIT, PAGE_READWRITE)) externs->Sys_Error("VirtualAlloc failed. Blame windows."); #endif ptr = &prinst.addressablehunk[prinst.addressableused-ammount]; if (src) memcpy(ptr, src, srcsize); #ifdef _DEBUG else memset(ptr, 0xcc, srcsize); #endif memset(ptr+srcsize, 0, pad); return &prinst.addressablehunk[prinst.addressableused-ammount]; } #define MARKER_USED 0xC2A4F5A6u #define MARKER_FREE 0xF1E3E3E7u typedef struct { #ifdef _DEBUG unsigned int marker; #endif unsigned int next; unsigned int prev; unsigned int size; //includes header size } qcmemfreeblock_t; typedef struct { unsigned int marker; #ifdef _DEBUG unsigned int next; unsigned int prev; #endif unsigned int size; //includes header size } qcmemusedblock_t; static void PF_fmem_unlink(progfuncs_t *progfuncs, qcmemfreeblock_t *p) { qcmemfreeblock_t *np; #ifdef _DEBUG if (p->marker != MARKER_FREE) { externs->Printf("PF_fmem_unlink: memory corruption\n"); PR_StackTrace(&progfuncs->funcs, false); } p->marker = 0; #endif if (p->prev) { np = (qcmemfreeblock_t*)(progfuncs->funcs.stringtable + p->prev); np->next = p->next; } else progfuncs->inst.mfreelist = p->next; if (p->next) { np = (qcmemfreeblock_t*)(progfuncs->funcs.stringtable + p->next); np->prev = p->prev; } } void PR_memvalidate (progfuncs_t *progfuncs) { qcmemfreeblock_t *p; unsigned int b,l; b = prinst.mfreelist; l = 0; while (b) { if ((size_t)b >= (size_t)prinst.addressableused) { externs->Printf("PF_memalloc: memory corruption\n"); PR_StackTrace(&progfuncs->funcs, false); return; } p = (qcmemfreeblock_t*)(progfuncs->funcs.stringtable + b); if ( #ifdef _DEBUG p->marker != MARKER_FREE || #endif p->prev != l || (p->next && p->next < b + p->size) || p->next >= prinst.addressableused || b + p->size >= prinst.addressableused || p->prev >= b) { externs->Printf("PF_memalloc: memory corruption\n"); PR_StackTrace(&progfuncs->funcs, false); return; } l = b; b = p->next; } } static void *PDECL PR_memalloc (pubprogfuncs_t *ppf, unsigned int size) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; qcmemfreeblock_t *p, *np; qcmemusedblock_t *ub = NULL; unsigned int b,n; /*round size up*/ size = (size+sizeof(qcmemusedblock_t) + 63) & ~63; PR_memvalidate(progfuncs); b = prinst.mfreelist; while (b) { if (/*b < 0 || */b+sizeof(qcmemfreeblock_t) >= prinst.addressableused) { externs->Printf("PF_memalloc: memory corruption\n"); PR_StackTrace(&progfuncs->funcs, false); return NULL; } p = (qcmemfreeblock_t*)(progfuncs->funcs.stringtable + b); if (p->size >= size) { if ((p->next && p->next < b + p->size) || p->next >= prinst.addressableused || b + p->size >= prinst.addressableused || p->prev >= b) { externs->Printf("PF_memalloc: memory corruption\n"); PR_StackTrace(&progfuncs->funcs, false); return NULL; } ub = (qcmemusedblock_t*)p; if (p->size > size + 63) { /*make a new header just after it, with basically the same properties, and shift the important fields over*/ n = b + size; np = (qcmemfreeblock_t*)(progfuncs->funcs.stringtable + b + size); #ifdef _DEBUG np->marker = MARKER_FREE; #endif np->prev = p->prev; np->next = p->next; np->size = p->size - size; if (np->prev) { p = (qcmemfreeblock_t*)(progfuncs->funcs.stringtable + np->prev); p->next = n; } else prinst.mfreelist = n; if (p->next) { p = (qcmemfreeblock_t*)(progfuncs->funcs.stringtable + np->next); p->prev = n; } } else { size = p->size; /*alloc the entire block*/ /*unlink this entry*/ PF_fmem_unlink(progfuncs, p); } break; } b = p->next; } /*assign more space*/ if (!ub) { ub = PRAddressableExtend(progfuncs, NULL, size, 0); if (!ub) { externs->Printf("PF_memalloc: memory exausted\n"); PR_StackTrace(&progfuncs->funcs, false); return NULL; } //FIXME: merge with previous block } memset(ub, 0, size); ub->marker = MARKER_USED; ub->size = size; PR_memvalidate(progfuncs); return ub+1; } static void PDECL PR_memfree (pubprogfuncs_t *ppf, void *memptr) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; qcmemusedblock_t *ub; qcmemfreeblock_t *b, *nb, *pb; unsigned int pa, na; //prev addr, next addr unsigned int size; unsigned int ptr = memptr?((char*)memptr - progfuncs->funcs.stringtable):0; /*freeing NULL is ignored*/ if (!ptr) return; PR_memvalidate(progfuncs); ptr -= sizeof(qcmemusedblock_t); if (/*ptr < 0 ||*/ ptr >= prinst.addressableused) { ptr += sizeof(qcmemusedblock_t); if (ptr < prinst.addressableused && !*(char*)memptr) { //the empty string is a point of contention. while we can detect it from fteqcc, its best to not give any special favours (other than nicer debugging, where possible) //we might not actually spot it from other qccs, so warning about it where possible is probably a very good thing. externs->Printf("PF_memfree: unable to free the non-null empty string constant at %x\n", ptr); } else externs->Printf("PF_memfree: pointer invalid - out of range (%x >= %x)\n", ptr, (unsigned int)prinst.addressableused); PR_StackTrace(&progfuncs->funcs, false); return; } //this is the used block that we're trying to free ub = (qcmemusedblock_t*)(progfuncs->funcs.stringtable + ptr); if (ub->marker != MARKER_USED || ub->size <= sizeof(*ub) || ptr + ub->size > (unsigned int)prinst.addressableused) { externs->Printf("PR_memfree: pointer lacks marker - double-freed?\n"); PR_StackTrace(&progfuncs->funcs, false); return; } ub->marker = 0; //invalidate it size = ub->size; ub = NULL; //we have an (ordered) list of free blocks. //in order to free our memory, we need to find the free block before+after the 'new' block for (na = prinst.mfreelist, pa = 0; ;) { if (/*na < 0 ||*/ na >= prinst.addressableused) { externs->Printf("PF_memfree: memory corruption\n"); PR_StackTrace(&progfuncs->funcs, false); return; } if (!na || na >= ptr) { pb = pa?(qcmemfreeblock_t*)(progfuncs->funcs.stringtable + pa):NULL; if (pb && pa+pb->size>ptr) { //previous free block extends into the block that we're trying to free. externs->Printf("PF_memfree: double free\n"); PR_StackTrace(&progfuncs->funcs, false); return; } #ifdef _DEBUG if (pb && pb->marker != MARKER_FREE) { externs->Printf("PF_memfree: use-after-free?\n"); PR_StackTrace(&progfuncs->funcs, false); return; } #endif nb = na?(qcmemfreeblock_t*)(progfuncs->funcs.stringtable + na):NULL; if (nb && ptr+size > na) { externs->Printf("PF_memfree: block extends into neighbour\n"); PR_StackTrace(&progfuncs->funcs, false); return; } #ifdef _DEBUG if (nb && nb->marker != MARKER_FREE) { externs->Printf("PF_memfree: use-after-free?\n"); PR_StackTrace(&progfuncs->funcs, false); return; } #endif /*generate the free block, now we know its proper values*/ b = (qcmemfreeblock_t*)(progfuncs->funcs.stringtable + ptr); #ifdef _DEBUG b->marker = MARKER_FREE; #endif b->prev = pa; b->next = na; b->size = size; if (na) nb->prev = ptr; if (!pa) prinst.mfreelist = ptr; else pb->next = ptr; /*extend this block and kill the next if they are adjacent*/ if (na && b->next == ptr + size) { b->size += nb->size; PF_fmem_unlink(progfuncs, nb); } /*we're adjacent to the previous block, so merge them by killing the newly freed region*/ if (pa && pa + pb->size == ptr) { pb->size += size; PF_fmem_unlink(progfuncs, b); } break; } pa = na; b = (qcmemfreeblock_t*)(progfuncs->funcs.stringtable + pa); na = b->next; } PR_memvalidate(progfuncs); } static void *PDECL PR_memrealloc (pubprogfuncs_t *ppf, void *memptr, unsigned int newsize) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; qcmemusedblock_t *ub; unsigned int ptr = memptr?((char*)memptr - progfuncs->funcs.stringtable):0; void *newptr; unsigned int oldsize; /*freeing NULL is ignored*/ if (!ptr) //realloc instead of malloc is accepted. return PR_memalloc(ppf, newsize); PR_memvalidate(progfuncs); ptr -= sizeof(qcmemusedblock_t); if (/*ptr < 0 ||*/ ptr >= prinst.addressableused) { ptr += sizeof(qcmemusedblock_t); if (ptr < prinst.addressableused && !*(char*)memptr) { //the empty string is a point of contention. while we can detect it from fteqcc, its best to not give any special favours (other than nicer debugging, where possible) //we might not actually spot it from other qccs, so warning about it where possible is probably a very good thing. externs->Printf("PR_memrealloc: unable to free the non-null empty string constant at %x\n", ptr); } else externs->Printf("PR_memrealloc: pointer invalid - out of range (%x >= %x)\n", ptr, (unsigned int)prinst.addressableused); PR_StackTrace(&progfuncs->funcs, false); return NULL; } //this is the used block that we're trying to free ub = (qcmemusedblock_t*)(progfuncs->funcs.stringtable + ptr); if (ub->marker != MARKER_USED || ub->size <= sizeof(*ub) || ptr + ub->size > (unsigned int)prinst.addressableused) { externs->Printf("PR_memrealloc: pointer lacks marker - double-freed?\n"); PR_StackTrace(&progfuncs->funcs, false); return NULL; } oldsize = ub->size; oldsize -= sizeof(qcmemusedblock_t); //ignore the header. newptr = PR_memalloc(ppf, newsize); if (oldsize > newsize) oldsize = newsize; //don't copy it all. memcpy(newptr, memptr, oldsize); newsize -= oldsize; memset((char*)newptr+oldsize, 0, newsize); //clear out any extended part. PR_memfree(ppf, memptr); //free the old. return newptr; } void PRAddressableFlush(progfuncs_t *progfuncs, size_t totalammount) { prinst.addressableused = 0; prinst.mfreelist = 0; // Clear localstack state when flushing memory prinst.localstack = NULL; prinst.localstack_used = 0; prinst.spushed = 0; if (totalammount <= 0) //flush { totalammount = prinst.addressablesize; // return; } #if defined(_WIN32) && !defined(WINRT) if (prinst.addressablehunk && prinst.addressablesize != totalammount) { VirtualFree(prinst.addressablehunk, 0, MEM_RELEASE); //doesn't this look complicated? :p prinst.addressablehunk = NULL; } if (!prinst.addressablehunk) prinst.addressablehunk = VirtualAlloc (prinst.addressablehunk, totalammount, MEM_RESERVE, PAGE_NOACCESS); #else if (prinst.addressablehunk && prinst.addressablesize != totalammount) { free(prinst.addressablehunk); prinst.addressablehunk = NULL; } if (!prinst.addressablehunk) prinst.addressablehunk = malloc(totalammount); //linux will allocate-on-use anyway, which is handy. // memset(prinst.addressablehunk, 0xff, totalammount); #endif if (!prinst.addressablehunk) externs->Sys_Error("Out of memory\n"); prinst.addressablesize = totalammount; progfuncs->funcs.stringtablemaxsize = totalammount; } int PDECL PR_InitEnts(pubprogfuncs_t *ppf, int max_ents) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; edictrun_t *e; prinst.maxedicts = max_ents; sv_num_edicts = 0; #if 0 { int i; for (i = 0; i < prinst.numfields; i++) { externs->Printf("%s(%i) %i -> %i\n", prinst.field[i].name, prinst.field[i].type, prinst.field[i].progsofs, prinst.field[i].ofs); } } #endif prinst.max_fields_size = prinst.fields_size; prinst.edicttable = (struct edictrun_s**)(progfuncs->funcs.edicttable = PRHunkAlloc(progfuncs, prinst.maxedicts*sizeof(struct edicts_s *), "edicttable")); progfuncs->funcs.edicttable_length = prinst.maxedicts; e = PRHunkAlloc(progfuncs, externs->edictsize, "edict0"); e->fieldsize = prinst.fields_size; e->entnum = 0; e->ereftype = ER_ENTITY; sv_edicts = (struct edict_s *)e; sv_num_edicts = 1; progfuncs->funcs.edicttable[0] = sv_edicts; e->fields = PRAddressableExtend(progfuncs, NULL, e->fieldsize, prinst.max_fields_size-e->fieldsize); QC_ClearEdict(&progfuncs->funcs, sv_edicts); if (externs->entspawn) externs->entspawn(sv_edicts, false); return prinst.max_fields_size; } edictrun_t tempedict={ER_FREE}; //used as a safty buffer static float tempedictfields[2048]; static void PDECL PR_Configure (pubprogfuncs_t *ppf, size_t addressable_size, int max_progs, pbool profiling) //can be used to wipe all memory { progfuncs_t *progfuncs = (progfuncs_t*)ppf; unsigned int i; edictrun_t *e; prinst.max_fields_size=0; prinst.fields_size = 0; progfuncs->funcs.stringtable = 0; QC_StartShares(progfuncs); QC_InitShares(progfuncs); for ( i=1 ; ientnum = i; if (e) externs->memfree(e); } PRHunkFree(progfuncs, 0); //clear mem - our hunk may not be a real hunk. if (addressable_size == (size_t)-1) { #if defined(_WIN64) && !defined(WINRT) addressable_size = 0x80000000; //use of virtual address space rather than physical memory means we can just go crazy and use the max of 2gb. #elif defined(FTE_TARGET_WEB) addressable_size = 8*1024*1024; #else addressable_size = 32*1024*1024; #endif } if (addressable_size > 0x80000000) addressable_size = 0x80000000; PRAddressableFlush(progfuncs, addressable_size); progfuncs->funcs.stringtable = prinst.addressablehunk; pr_progstate = PRHunkAlloc(progfuncs, sizeof(progstate_t) * max_progs, "progstatetable"); /* for(a = 0; a < max_progs; a++) { pr_progstate[a].progs = NULL; } */ prinst.maxprogs = max_progs; prinst.pr_typecurrent=-1; PR_FreeAllTemps(progfuncs); prinst.reorganisefields = false; prinst.profiling = profiling; prinst.profilingalert = Sys_GetClockRate(); progfuncs->funcs.edicttable_length = prinst.maxedicts = 0; prinst.edicttable = (edictrun_t**)(progfuncs->funcs.edicttable = &sv_edicts); sv_num_edicts = 0; //set up a safty buffer so things won't go horribly wrong too often sv_edicts=(struct edict_s *)&tempedict; tempedict.readonly = true; tempedict.fields = tempedictfields; tempedict.ereftype = ER_OBJECT; } static struct globalvars_s *PDECL PR_globals (pubprogfuncs_t *ppf, progsnum_t pnum) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; if (pnum < 0) { if (!current_progstate) { static float fallback[RESERVED_OFS]; return (struct globalvars_s *)fallback; //err.. you've not loaded one yet. } return (struct globalvars_s *)current_progstate->globals; } return (struct globalvars_s *)pr_progstate[pnum].globals; } static struct entvars_s *PDECL PR_entvars (pubprogfuncs_t *ppf, struct edict_s *ed) { // progfuncs_t *progfuncs = (progfuncs_t*)ppf; if (((edictrun_t *)ed)->ereftype != ER_ENTITY) return NULL; return (struct entvars_s *)edvars(ed); } static pbool PDECL PR_GetFunctionInfo(pubprogfuncs_t *ppf, func_t func, int *args, pbyte **argsizes, int *builtinnum, char *funcname, size_t funcnamesize) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; unsigned int pnum; unsigned int fnum; mfunction_t *f; pnum = (func & 0xff000000)>>24; fnum = (func & 0x00ffffff); if (pnum >= prinst.maxprogs || !pr_progstate[pnum].functions) return false; else if (fnum >= pr_progstate[pnum].progs->numfunctions) return false; else { f = pr_progstate[pnum].functions + fnum; if (args) *args = f->numparms; if (argsizes) *argsizes = f->parm_size; if (builtinnum) *builtinnum = -f->first_statement; if (funcname) { const char *srcname = PR_StringToNative(ppf, f->s_name); size_t nlen = strlen(srcname); if (nlen < funcnamesize) memcpy(funcname, srcname, nlen+1); else *funcname = 0; } return true; } } func_t PDECL PR_FindFunc(pubprogfuncs_t *ppf, const char *funcname, progsnum_t pnum) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; mfunction_t *f=NULL; if (pnum == PR_ANY) { for (pnum = 0; (unsigned)pnum < prinst.maxprogs; pnum++) { if (!pr_progstate[pnum].progs) continue; f = ED_FindFunction(progfuncs, funcname, &pnum, pnum); if (f) break; } } else if (pnum == PR_ANYBACK) //run backwards { for (pnum = prinst.maxprogs-1; pnum >= 0; pnum--) { if (!pr_progstate[pnum].progs) continue; f = ED_FindFunction(progfuncs, funcname, &pnum, pnum); if (f) break; } } else f = ED_FindFunction(progfuncs, funcname, &pnum, pnum); if (!f) return 0; { ddef16_t *var16; ddef32_t *var32; progstate_t *ps = &pr_progstate[pnum]; switch(ps->structtype) { case PST_KKQWSV: case PST_DEFAULT: var16 = ED_FindTypeGlobalFromProgs16(progfuncs, ps, funcname, ev_function); //we must make sure we actually have a function def - 'light' is defined as a field before it is defined as a function. if (!var16) return (f - ps->functions) | (pnum << 24); return *(int *)&ps->globals[var16->ofs]; case PST_QTEST: case PST_FTE32: case PST_UHEXEN2: var32 = ED_FindTypeGlobalFromProgs32(progfuncs, ps, funcname, ev_function); //we must make sure we actually have a function def - 'light' is defined as a field before it is defined as a function. if (!var32) return (f - ps->functions) | (pnum << 24); return *(int *)&ps->globals[var32->ofs]; } externs->Sys_Error("Error with def size (PR_FindFunc)"); } return 0; } static void PDECL QC_FindPrefixedGlobals(pubprogfuncs_t *ppf, int pnum, char *prefix, void (PDECL *found) (pubprogfuncs_t *progfuncs, char *name, union eval_s *val, etype_t type, void *ctx), void *ctx) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; unsigned int i; ddef16_t *def16; ddef32_t *def32; int len = strlen(prefix); if (pnum == PR_CURRENT) pnum = prinst.pr_typecurrent; if (pnum == PR_ANY) { for (pnum = 0; (unsigned)pnum < prinst.maxprogs; pnum++) { if (!pr_progstate[pnum].progs) continue; QC_FindPrefixedGlobals(ppf, pnum, prefix, found, ctx); } return; } if (!pr_progstate[pnum].progs) return; switch(pr_progstate[pnum].structtype) { case PST_DEFAULT: case PST_KKQWSV: for (i=1 ; inumglobaldefs ; i++) { def16 = &pr_progstate[pnum].globaldefs16[i]; if (!strncmp(def16->s_name+progfuncs->funcs.stringtable,prefix, len)) found(&progfuncs->funcs, def16->s_name+progfuncs->funcs.stringtable, (eval_t *)&pr_progstate[pnum].globals[def16->ofs], def16->type, ctx); } break; case PST_QTEST: case PST_FTE32: case PST_UHEXEN2: for (i=1 ; inumglobaldefs ; i++) { def32 = &pr_progstate[pnum].globaldefs32[i]; if (!strncmp(def32->s_name+progfuncs->funcs.stringtable,prefix, len)) found(&progfuncs->funcs, def32->s_name+progfuncs->funcs.stringtable, (eval_t *)&pr_progstate[pnum].globals[def32->ofs], def32->type, ctx); } break; } } static pbool PDECL PR_FindBuiltins (pubprogfuncs_t *ppf, progsnum_t prnum, int binum, pbool (PDECL *found) (pubprogfuncs_t *progfuncs, const char *name, void *ctx), void *ctx) //calls the callback for each function reference that's mapped to the specified builtin number. { progfuncs_t *progfuncs = (progfuncs_t*)ppf; mfunction_t *func; unsigned int i; if ((unsigned)prnum > (unsigned)prinst.maxprogs) { externs->Printf("Progsnum %"pPRIi" out of bounds\n", prnum); return false; } if (!pr_progstate[prnum].progs) return false; if (binum < 0) return false; //invalid binum = -binum; for (i=1 ; inumfunctions ; i++) { func = &pr_progstate[prnum].functions[i]; if (func->first_statement == binum) if (!found(ppf, PR_StringToNative(ppf, func->s_name), ctx)) return false; } return true; } eval_t *PDECL PR_FindGlobal(pubprogfuncs_t *ppf, const char *globname, progsnum_t pnum, etype_t *type) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; unsigned int i; ddef16_t *var16; ddef32_t *var32; progstate_t *cp; if (type) *type = ev_void; if (pnum == PR_CURRENT && current_progstate) cp = current_progstate; else if (pnum == PR_ANY) { eval_t *ev; for (i = 0; i < prinst.maxprogs; i++) { if (!pr_progstate[i].progs) continue; ev = PR_FindGlobal(&progfuncs->funcs, globname, i, type); if (ev) return ev; } return NULL; } else if (pnum >= 0 && (unsigned)pnum < prinst.maxprogs && pr_progstate[pnum].progs) cp = &pr_progstate[pnum]; else return NULL; switch(cp->structtype) { case PST_DEFAULT: case PST_KKQWSV: if (!(var16 = ED_FindGlobalFromProgs16(progfuncs, cp, globname))) return NULL; if (type) *type = var16->type; return (eval_t *)&cp->globals[var16->ofs]; case PST_QTEST: case PST_FTE32: case PST_UHEXEN2: if (!(var32 = ED_FindGlobalFromProgs32(progfuncs, cp, globname))) return NULL; if (type) *type = var32->type; return (eval_t *)&cp->globals[var32->ofs]; } externs->Sys_Error("Error with def size (PR_FindGlobal)"); return NULL; } static char *PDECL PR_VarString (pubprogfuncs_t *ppf, int first) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; int i; static char out[1024]; const char *s; out[0] = 0; for (i=first ; ifuncs.callargc ; i++) { s = PR_StringToNative(ppf, G_STRING(OFS_PARM0+i*3)); if (s) { if (strlen(out) + strlen(s) + 1 >= sizeof(out)) return out; strcat (out, s); } } return out; } static int PDECL PR_QueryField (pubprogfuncs_t *ppf, unsigned int fieldoffset, etype_t *type, char const**name, evalc_t *fieldcache) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; fdef_t *var; var = ED_FieldAtOfs(progfuncs, fieldoffset); if (!var) return false; if (type) *type = var->type & ~(DEF_SAVEGLOBAL|DEF_SHARED); if (name) *name = var->name; if (fieldcache) { fieldcache->ofs32 = var; fieldcache->varname = var->name; } return true; } eval_t *PDECL QC_GetEdictFieldValue(pubprogfuncs_t *ppf, struct edict_s *ed, const char *name, etype_t type, evalc_t *cache) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; fdef_t *var; if (!cache) { var = ED_FindField(progfuncs, name); if (!var || (var->type != type && type)) return NULL; return (eval_t *) &(((int*)(((edictrun_t*)ed)->fields))[var->ofs]); } if (!cache->varname) { cache->varname = name; var = ED_FindField(progfuncs, name); if (!var || (var->type != type && type)) { cache->ofs32 = NULL; return NULL; } cache->ofs32 = var; cache->varname = var->name; if (!ed) return (void*)~0; //something not null return (eval_t *) &(((int*)(((edictrun_t*)ed)->fields))[var->ofs]); } if (cache->ofs32 == NULL) return NULL; return (eval_t *) &(((int*)(((edictrun_t*)ed)->fields))[cache->ofs32->ofs]); } static struct edict_s *PDECL ProgsToEdict (pubprogfuncs_t *ppf, int progs) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; if ((unsigned)progs >= (unsigned)prinst.maxedicts) { externs->Printf("Bad entity index %i\n", progs); if (prinst.pr_depth) { PR_StackTrace (ppf, false); // progfuncs->funcs.pr_trace += 1; } progs = 0; } return (struct edict_s *)PROG_TO_EDICT_PB(progfuncs.inst, progs); } static int PDECL EdictToProgs (pubprogfuncs_t *ppf, struct edict_s *ed) { // progfuncs_t *progfuncs = (progfuncs_t*)ppf; return EDICT_TO_PROG(progfuncs, ed); } string_t PDECL PR_StringToProgs (pubprogfuncs_t *ppf, const char *str) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; char **ntable; int i, free=-1; if (!str) return 0; if (str >= progfuncs->funcs.stringtable && str < progfuncs->funcs.stringtable + prinst.addressableused) return str - progfuncs->funcs.stringtable; for (i = prinst.numallocedstrings-1; i >= 0; i--) { if (prinst.allocedstrings[i] == str) return (string_t)((unsigned int)i | STRING_STATIC); if (!prinst.allocedstrings[i]) free = i; } if (free != -1) { i = free; prinst.allocedstrings[i] = (char*)str; return (string_t)((unsigned int)i | STRING_STATIC); } if (prinst.numallocedstrings < prinst.maxallocedstrings) { i = prinst.numallocedstrings++; prinst.allocedstrings[i] = (char*)str; return (string_t)((unsigned int)i | STRING_STATIC); } prinst.maxallocedstrings += 1024; ntable = progfuncs->funcs.parms->memalloc(sizeof(char*) * prinst.maxallocedstrings); memcpy(ntable, prinst.allocedstrings, sizeof(char*) * prinst.numallocedstrings); memset(ntable + prinst.numallocedstrings, 0, sizeof(char*) * (prinst.maxallocedstrings - prinst.numallocedstrings)); if (prinst.allocedstrings) progfuncs->funcs.parms->memfree(prinst.allocedstrings); prinst.allocedstrings = ntable; i = prinst.numallocedstrings++; prinst.allocedstrings[i] = (char*)str; return (string_t)((unsigned int)i | STRING_STATIC); } //if ed is null, fld points to a global. if str_is_static, then s doesn't need its own memory allocated. static void PDECL PR_SetStringField(pubprogfuncs_t *progfuncs, struct edict_s *ed, string_t *fld, const char *str, pbool str_is_static) { if (!str) *fld = 0; else { #ifdef QCGC *fld = PR_AllocTempString(progfuncs, str); #else if (!str_is_static) str = PR_AddString(progfuncs, str, 0, false); *fld = PR_StringToProgs(progfuncs, str); #endif } } static char *PDECL PR_RemoveProgsString (pubprogfuncs_t *ppf, string_t str) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; char *ret; //input string is expected to be an allocated string //if its a temp, or a constant, just return NULL. if (((unsigned int)str & STRING_SPECMASK) == STRING_STATIC) { int i = str & ~STRING_SPECMASK; if (i >= prinst.numallocedstrings) { PR_RunWarning(&progfuncs->funcs, "invalid static string %x\n", str); return NULL; } if (prinst.allocedstrings[i]) { ret = prinst.allocedstrings[i]; prinst.allocedstrings[i] = NULL; //remove it return ret; } else { PR_RunWarning(&progfuncs->funcs, "invalid static string %x (already free)\n", str); return NULL; //urm, was freed... } } PR_RunWarning(&progfuncs->funcs, "invalid static string %x\n", str); return NULL; } const char *ASMCALL PR_StringToNative (pubprogfuncs_t *ppf, string_t str) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; if (((unsigned int)str & STRING_SPECMASK) == STRING_STATIC) { int i = str & ~STRING_SPECMASK; if (i >= prinst.numallocedstrings) { if (!progfuncs->funcs.debug_trace) //don't spam this PR_RunWarning(&progfuncs->funcs, "invalid static string %x\n", str); return ""; } if (prinst.allocedstrings[i]) return prinst.allocedstrings[i]; else { if (!progfuncs->funcs.debug_trace) PR_RunWarning(&progfuncs->funcs, "invalid static string %x\n", str); return ""; //urm, was freed... } } if (((unsigned int)str & STRING_SPECMASK) == STRING_TEMP) { unsigned int i = str & ~STRING_SPECMASK; tempstr_t *ts; if (i >= prinst.maxtempstrings || !(ts=prinst.tempstrings[i])) { if (!progfuncs->funcs.debug_trace) PR_RunWarning(&progfuncs->funcs, "invalid temp string %x\n", str); return ""; } return ts->value; } if ((unsigned int)str >= (unsigned int)prinst.addressableused) { if (!progfuncs->funcs.debug_trace) PR_RunWarning(&progfuncs->funcs, "invalid string offset %x\n", str); return ""; } return progfuncs->funcs.stringtable + str; } //guarentees a return value for tempstrings, but requires a small enough data size to do so. eval_t *PR_GetReadTempStringPtr(progfuncs_t *progfuncs, string_t str, size_t offset, size_t datasize) { static eval_t dummy; //don't resize anything when reading. if (((unsigned int)str & STRING_SPECMASK) == STRING_TEMP) { unsigned int i = str & ~STRING_SPECMASK; tempstr_t *temp; if (i < prinst.maxtempstrings && (temp=prinst.tempstrings[i])) { if (offset + datasize <= temp->size) return (eval_t*)(temp->value + offset); else if (datasize <= sizeof(dummy)) return &dummy; } } return NULL; } eval_t *PR_GetWriteTempStringPtr(progfuncs_t *progfuncs, string_t str, size_t offset, size_t datasize) { if (((unsigned int)str & STRING_SPECMASK) == STRING_TEMP) { unsigned int i = str & ~STRING_SPECMASK; tempstr_t *temp; if (i < prinst.maxtempstrings && (temp=prinst.tempstrings[i])) { if (offset + datasize >= temp->size) { //access is beyond the current size. expand it. unsigned int newsize; tempstr_t *newtemp; newsize = offset + datasize; if (newsize > (1u<<20u)) return NULL; //gotta have a cut-off point somewhere. newsize = (newsize+sizeof(float)-1)&~(sizeof(float)-1); newtemp = progfuncs->funcs.parms->memalloc(sizeof(tempstr_t) - sizeof(((tempstr_t*)NULL)->value) + newsize); if (!newtemp) return NULL; newtemp->size = newsize; memcpy(newtemp->value, temp->value, temp->size); memset(newtemp->value+temp->size, 0, newsize-temp->size); progfuncs->funcs.parms->memfree(temp); prinst.tempstrings[i] = temp = newtemp; } return (eval_t*)(temp->value + offset); } } return NULL; } //returns null for invalid accesses. WARNING: invalidates any other pointers to the same tempstring so use this before getting any read pointers (or strings!). void *PR_PointerToNative_Resize(pubprogfuncs_t *inst, pint_t ptr, size_t offset, size_t datasize) { progfuncs_t *progfuncs = (progfuncs_t*)inst; if (((unsigned int)ptr & STRING_SPECMASK) == STRING_TEMP) { //buffer. these auto-upsize. unsigned int i = ptr & ~STRING_SPECMASK; tempstr_t *temp; if (i < prinst.maxtempstrings && (temp=prinst.tempstrings[i])) { if (datasize > temp->size || offset >= temp->size-datasize) { //access is beyond the current size. expand it. unsigned int newsize; tempstr_t *newtemp; newsize = offset + datasize; if (newsize > (1u<<20u)) return NULL; //gotta have a cut-off point somewhere. newsize = (newsize+sizeof(float)-1)&~(sizeof(float)-1); newtemp = progfuncs->funcs.parms->memalloc(sizeof(tempstr_t) - sizeof(((tempstr_t*)NULL)->value) + newsize); if (!newtemp) return NULL; //erk! newtemp->size = newsize; memcpy(newtemp->value, temp->value, temp->size); memset(newtemp->value+temp->size, 0, newsize-temp->size); progfuncs->funcs.parms->memfree(temp); prinst.tempstrings[i] = temp = newtemp; } return (eval_t*)(temp->value + offset); } return NULL; //nothing not allocated. } else { //regular pointer offset += ptr; if (datasize > inst->stringtablesize || offset >= inst->stringtablesize-datasize || !offset) return NULL; //can't autoresize these. just fail. return inst->stringtable + ptr; } return NULL; } void *PR_PointerToNative_MoInvalidate(pubprogfuncs_t *inst, pint_t ptr, size_t offset, size_t datasize) { progfuncs_t *progfuncs = (progfuncs_t*)inst; if (((unsigned int)ptr & STRING_SPECMASK) == STRING_TEMP) { //buffer. these auto-upsize. unsigned int i = ptr & ~STRING_SPECMASK; tempstr_t *temp; if (i < prinst.maxtempstrings && (temp=prinst.tempstrings[i])) { if (datasize > temp->size || offset >= temp->size-datasize) { //access is beyond the current size. we're not allowed to break any other pointers though, so just fail and let the caller handle that. return NULL; //gotta have a cut-off point somewhere. } return (eval_t*)(temp->value + offset); } return NULL; //nothing not allocated. } else { //regular pointer offset += ptr; if (datasize > inst->stringtablesize || offset >= inst->stringtablesize-datasize || (!offset && datasize>1)) return NULL; //can't autoresize these. just fail. return inst->stringtable + ptr; } return NULL; } void QCBUILTIN PF_memgetval (pubprogfuncs_t *inst, struct globalvars_s *globals) { progfuncs_t *progfuncs = (progfuncs_t*)inst; //read 32 bits from a pointer. int dst = G_INT(OFS_PARM0); float ofs = G_FLOAT(OFS_PARM1); int size = sizeof(int); if (ofs != (float)(int)ofs) PR_RunWarning(inst, "PF_memgetval: non-integer offset\n"); dst += ofs*size; if (dst < 0 || dst+size >= inst->stringtablesize) { PR_RunError(inst, "PF_memgetval: invalid dest\n"); return; } if (dst & 3) PR_RunWarning(inst, "PF_memgetval: misaligned pointer (%#x)\n", dst); G_INT(OFS_RETURN) = *(int*)(inst->stringtable + dst); } void QCBUILTIN PF_memsetval (pubprogfuncs_t *inst, struct globalvars_s *globals) { progfuncs_t *progfuncs = (progfuncs_t*)inst; //write 32 bits to a pointer. int dst = G_INT(OFS_PARM0); float ofs = G_FLOAT(OFS_PARM1); int val = G_INT(OFS_PARM2); int size = sizeof(int); if (ofs != (float)(int)ofs) PR_RunWarning(inst, "PF_memsetval: non-integer offset\n"); dst += ofs*size; if (dst < 0 || dst+size >= inst->stringtablesize) { PR_RunError(inst, "PF_memsetval: invalid dest\n"); return; } if (dst & 3) PR_RunWarning(inst, "PF_memgetval: misaligned pointer (%#x)\n", dst); *(int*)(inst->stringtable + dst) = val; } //#define GCTIMINGS #ifdef QCGC #define smallbool char static smallbool *PR_QCGC_Mark(void *mem, size_t memsize, size_t numtemps) { unsigned int *str; //the reference we're considering size_t p; smallbool *marked; //just booleans. could compact. marked = malloc(sizeof(*marked) * numtemps); memset(marked, 0, sizeof(*marked) * numtemps); //mark everything the qc has access to, even if it isn't even a string! //note that I did try specifically checking only data explicitly marked as a string type, but that was: //a) a smidge slower (lots of extra loops and conditions I guess) //b) doesn't work with pointers/structs (yes, we assume it'll all be aligned). //c) both methods got the same number of false positives in my test (2, probably dead strunzoned references) for (str = mem, p = 0; p < memsize; p+=sizeof(*str), str++) { if ((*str & STRING_SPECMASK) == STRING_TEMP) { unsigned int idx = *str &~ STRING_SPECMASK; if (idx < numtemps) marked[idx] = true; } } return marked; } static size_t PR_QCGC_Sweep(progfuncs_t *progfuncs, smallbool *marked, tempstr_t **tempstrings, unsigned int numtemps) { unsigned int p; unsigned int swept = 0; #ifdef GCTIMINGS unsigned int unswept = 0; unsigned int errors = 0; #endif for (p = 0; p < numtemps; p++) { if (marked[p]) { //still live... #ifdef GCTIMINGS unswept++; if (!tempstrings[p]) errors++; #endif } else if (tempstrings[p]) { //not marked, but was valid at the time our snapshot was taken #ifdef _DEBUG if (tempstrings[p] != prinst.tempstrings[p]) { //something weird happened. tempstrings are supposed to be immutable (at least in length). externs->Sys_Error("tempstring was reallocated while gc was running"); continue; } #endif swept++; //FIXME: Security Race: its possible for a mod to do weird manipulations to access the tempstring while we're still freeing it, allowing it to read something outside of its sandbox. //one option would be to have the main thread bounce it back to the worker after its complete, so we can actually free the memory only after main thread has acknowledged that its tempstrings are nulled. prinst.tempstrings[p] = NULL; externs->memfree(tempstrings[p]); } } free(marked); return swept; } #ifdef THREADEDGC #include "quakedef.h" struct qcgccontext_s { int done; unsigned int clearedtemps; //number of temps that were swept away progfuncs_t *progfuncs; //careful! size_t maxtemps; //so it doesn't go stale tempstr_t **tempstrings;//so we don't get confused over temps added while marking size_t memsize; unsigned int amem[1]; }; void PR_QCGC_Done(void *ctx, void *data, size_t a, size_t b) { struct qcgccontext_s *gc = ctx; gc->done = true; } void PR_QCGC_Thread(void *ctx, void *data, size_t a, size_t b) { struct qcgccontext_s *gc = ctx; progfuncs_t *progfuncs = gc->progfuncs; smallbool *marked; #ifdef GCTIMINGS double starttime, markedtime, endtime; starttime = Sys_DoubleTime(); #endif marked = PR_QCGC_Mark(gc->amem, gc->memsize, gc->maxtemps); #ifdef GCTIMINGS markedtime = Sys_DoubleTime(); #endif gc->clearedtemps = PR_QCGC_Sweep(progfuncs, marked, gc->tempstrings, gc->maxtemps); #ifdef GCTIMINGS endtime = Sys_DoubleTime(); gc->externs->Printf("live: %u, dead: %u, threadtime: mark=%f, sweep=%f, total=%f\n", prinst.livetemps-gc->clearedtemps, gc->clearedtemps, (markedtime - starttime), (endtime - markedtime), endtime-starttime); #endif COM_InsertWork(WG_MAIN, PR_QCGC_Done, gc, NULL, 0, 0); } #endif static void PR_ExpandTempStrings(progfuncs_t *progfuncs, size_t newmax) { tempstr_t **ntable = progfuncs->funcs.parms->memalloc(sizeof(*ntable) * newmax); memcpy(ntable, prinst.tempstrings, sizeof(*ntable) * prinst.maxtempstrings); memset(ntable+prinst.maxtempstrings, 0, sizeof(*ntable) * (newmax-prinst.maxtempstrings)); prinst.maxtempstrings = newmax; if (prinst.tempstrings) progfuncs->funcs.parms->memfree(prinst.tempstrings); prinst.tempstrings = ntable; } static string_t PDECL PR_AllocTempStringLen (pubprogfuncs_t *ppf, char **str, unsigned int len) { progfuncs_t *progfuncs = (progfuncs_t *)ppf; int i; if (!str) return 0; if (prinst.livetemps == prinst.maxtempstrings) { #ifdef THREADEDGC //need to wait for the gc to finish, otherwise it might be wiping freed strings that we're still using. while (prinst.gccontext) { COM_WorkerPartialSync(prinst.gccontext, &prinst.gccontext->done, false); PR_RunGC(progfuncs); } #endif PR_ExpandTempStrings(progfuncs, prinst.maxtempstrings*2 + 1024); } for (i = prinst.nexttempstring; i < prinst.maxtempstrings && prinst.tempstrings[i]; i++) ; if (i == prinst.maxtempstrings) { for (i = 0; i < prinst.nexttempstring && prinst.tempstrings[i]; i++) ; if (i == prinst.nexttempstring) return 0; //panic! } prinst.nexttempstring = i; prinst.livetemps++; len = ((len+3)&~3); //round up, primarily so its safe to use loadp_i to read the last few chars of the string prinst.tempstrings[i] = progfuncs->funcs.parms->memalloc(sizeof(tempstr_t) - sizeof(((tempstr_t*)NULL)->value) + len); prinst.tempstrings[i]->size = len; *str = prinst.tempstrings[i]->value; return (string_t)((unsigned int)i | STRING_TEMP); } void PR_RunGC (progfuncs_t *progfuncs) { #ifdef THREADEDGC if (!prinst.gccontext) #endif { if (prinst.livetemps < prinst.maxtempstrings/2 || prinst.nexttempstring < prinst.maxtempstrings/2) { //don't bother yet return; } #ifdef THREADEDGC if (externs->usethreadedgc) { #ifdef GCTIMINGS double starttime = Sys_DoubleTime(), endtime; #endif struct qcgccontext_s *gc = prinst.gccontext = malloc(sizeof(*gc) - sizeof(gc->amem) + prinst.addressableused + sizeof(*gc->tempstrings)*prinst.maxtempstrings); gc->done = false; gc->clearedtemps = 0; gc->progfuncs = progfuncs; gc->memsize = prinst.addressableused; memcpy(gc->amem, prinst.addressablehunk, prinst.addressableused); gc->maxtemps = prinst.maxtempstrings; gc->tempstrings = (void*)((char*)gc->amem+prinst.addressableused); memcpy(gc->tempstrings, prinst.tempstrings, sizeof(*gc->tempstrings)*gc->maxtemps); COM_InsertWork(WG_LOADER, PR_QCGC_Thread, gc, NULL, 0, 0); #ifdef GCTIMINGS endtime = Sys_DoubleTime(); gc->externs->Printf("preparetime=%f\n", (endtime - starttime)); #endif return; } #endif { //same-thread gc. smallbool *marked = PR_QCGC_Mark(prinst.addressablehunk, prinst.addressableused, prinst.maxtempstrings); size_t swept = PR_QCGC_Sweep(progfuncs, marked, prinst.tempstrings, prinst.maxtempstrings); prinst.livetemps -= swept; //if over half the (max)strings are still live, just increase the max so we are not spamming collections if (prinst.livetemps >= prinst.maxtempstrings/2) PR_ExpandTempStrings(progfuncs, prinst.maxtempstrings * 2); } } #ifdef THREADEDGC else if (prinst.gccontext->done) { prinst.livetemps -= prinst.gccontext->clearedtemps; free(prinst.gccontext); prinst.gccontext = NULL; //if over half the (max)strings are still live, just increase the max so we are not spamming collections if (prinst.livetemps >= prinst.maxtempstrings/2) PR_ExpandTempStrings(progfuncs, prinst.maxtempstrings * 2); } #endif } static void PR_FreeAllTemps (progfuncs_t *progfuncs) { unsigned int i; #ifdef THREADEDGC while (prinst.gccontext) { COM_WorkerPartialSync(prinst.gccontext, &prinst.gccontext->done, false); PR_RunGC(progfuncs); } #endif for (i = 0; i < prinst.maxtempstrings; i++) { externs->memfree(prinst.tempstrings[i]); prinst.tempstrings[i] = NULL; } prinst.maxtempstrings = 0; prinst.nexttempstring = 0; prinst.livetemps = 0; } #else static string_t PDECL PR_AllocTempStringLen (pubprogfuncs_t *ppf, char **str, unsigned int len) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; tempstr_t **ntable, *n; int newmax; int i; if (!str) return 0; if (prinst.numtempstrings == prinst.maxtempstrings) { newmax = prinst.maxtempstrings + 1024; ntable = progfuncs->funcs.parms->memalloc(sizeof(char*) * newmax); memcpy(ntable, prinst.tempstrings, sizeof(char*) * prinst.numtempstrings); prinst.maxtempstrings = newmax; if (prinst.tempstrings) progfuncs->funcs.parms->memfree(prinst.tempstrings); prinst.tempstrings = ntable; } i = prinst.numtempstrings; if (i == 0x10000000) return 0; prinst.numtempstrings++; n = progfuncs->funcs.parms->memalloc(sizeof(tempstr_t) - sizeof(((tempstr_t*)NULL)->value) + len); n->size = len; *str = n->value; prinst.tempstrings[i] = n; //doesn't have its value yet... return (string_t)((unsigned int)i | STRING_TEMP); } void PR_FreeTemps (progfuncs_t *progfuncs, int depth) { int i; if (depth > prinst.numtempstrings) { Sys_Error("QC Temp stack inverted\n"); return; } for (i = depth; i < prinst.numtempstrings; i++) { externs->memfree(prinst.tempstrings[i]); } prinst.numtempstrings = depth; } static void PR_FreeAllTemps (progfuncs_t *progfuncs) { unsigned int i; for (i = 0; i < prinst.numtempstrings; i++) { externs->memfree(prinst.tempstrings[i]); prinst.tempstrings[i] = NULL; } prinst.numtempstrings = 0; prinst.nexttempstring = 0; } #endif string_t PDECL PR_AllocTempString (pubprogfuncs_t *ppf, const char *str) { char *out; string_t res; size_t len; if (!str) return 0; len = strlen(str)+1; res = PR_AllocTempStringLen(ppf, &out, len); if (res) memcpy(out, str, len); return res; } static pbool PDECL PR_DumpProfiles (pubprogfuncs_t *ppf, pbool resetprofiles) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; struct progstate_s *ps; unsigned int i, f, j, s; prclocks_t cpufrequency; struct { char *fname; int profile; prclocks_t profiletime; prclocks_t totaltime; } *sorted, t; if (!prinst.profiling) { prinst.profiling = true; return false; } cpufrequency = Sys_GetClockRate(); for (i = 0; i < prinst.maxprogs; i++) { ps = &pr_progstate[i]; if (ps->progs == NULL) //we havn't loaded it yet, for some reason continue; externs->Printf("%s:\n", ps->filename); sorted = malloc(sizeof(*sorted) * ps->progs->numfunctions); //pull out the functions in order to sort them for (s = 0, f = 0; f < ps->progs->numfunctions; f++) { if (!ps->functions[f].profile) continue; sorted[s].fname = ps->functions[f].s_name+progfuncs->funcs.stringtable; sorted[s].profile = ps->functions[f].profile; sorted[s].profiletime = ps->functions[f].profiletime - ps->functions[f].profilechildtime; sorted[s].totaltime = ps->functions[f].profiletime; if (resetprofiles) { ps->functions[f].profile = 0; ps->functions[f].profiletime = 0; ps->functions[f].profilechildtime = 0; } s++; } // good 'ol bubble sort for (f = 0; f < s; f++) { for (j = f; j < s; j++) if (sorted[f].profiletime > sorted[j].profiletime) { t = sorted[f]; sorted[f] = sorted[j]; sorted[j] = t; } } //print it out externs->Printf("%8s %9s %10s: %s\n", "ops", "self-time", "total-time", "function"); for (f = 0; f < s; f++) externs->Printf("%8u %9f %10f: %s\n", sorted[f].profile, ull2dbl(sorted[f].profiletime) / ull2dbl(cpufrequency), ull2dbl(sorted[f].totaltime) / ull2dbl(cpufrequency), sorted[f].fname); free(sorted); } return true; } static void PDECL PR_Shutdown(pubprogfuncs_t *ppf); static pubprogfuncs_t deffuncs = { PROGSTRUCT_VERSION, PR_Shutdown, PR_Configure, PR_LoadProgs, PR_InitEnts, PR_ExecuteProgram, PR_globals, PR_entvars, PR_RunError, ED_Print, ED_Alloc, ED_AllocIndex, ED_Free, QC_EDICT_NUM, QC_NUM_FOR_EDICT, PR_VarString, NULL, //progstate 0, //numprogs PR_FindFunc, #if defined(MINIMAL) || defined(OMIT_QCC) NULL, NULL, #else Comp_Begin, Comp_Continue, #endif filefromprogs, NULL,//filefromnewprogs, ED_Print, PR_SaveEnts, PR_LoadEnts, PR_SaveEnt, PR_RestoreEnt, PR_FindGlobal, QC_GetEdictFieldValue, ProgsToEdict, EdictToProgs, PR_EvaluateDebugString, 0,//trace PR_StackTrace, PR_ToggleBreakpoint, NULL, //parms #if 1//defined(MINIMAL) || defined(OMIT_QCC) NULL, //decompile #else QC_Decompile, #endif 0, //callargc 0, 0, //string table(pointer base address) 0, //string table size 0, //max size 0, //field adjust(aditional field offset) 0, //field slots allocated (for builtins to clamp field reference args). PR_ForkStack, PR_ResumeThread, PR_AbortStack, PR_GetBuiltinCallInfo, PR_FindBuiltins, QC_RegisterFieldVar, ED_NewString, QC_HunkAlloc, PR_memalloc, PR_memrealloc, PR_memfree, PR_AllocTempString, PR_AllocTempStringLen, PR_StringToProgs, PR_StringToNative, PR_QueryField, QC_ClearEdict, QC_FindPrefixedGlobals, PR_SetWatchPoint, QC_AddSharedVar, QC_AddSharedFieldVar, PR_RemoveProgsString, PR_GetFunctionInfo, PR_GenerateStatementString, ED_FieldInfo, PR_UglyValueString, ED_ParseEval, PR_SetStringField, PR_DumpProfiles, 0, NULL, }; static int PDECL qclib_null_printf(const char *s, ...) { return 0; } static void *PDECL qclib_malloc(int size) { return malloc(size); } static void PDECL qclib_free(void *ptr) { free(ptr); } #ifdef FTE_TARGET_WEB #undef printf #define printf NULL //should be some null wrapper instead #endif //progfuncs_t *progfuncs = NULL; #undef memfree #undef prinst #undef extensionbuiltin #undef field #undef shares #undef maxedicts #undef sv_num_edicts static void PDECL PR_Shutdown(pubprogfuncs_t *ppf) { void (VARGS *f) (void *); progfuncs_t *inst = (progfuncs_t*)ppf; unsigned int i; edictrun_t *e; f = inst->funcs.parms->memfree; for ( i=1 ; iinst.maxedicts; i++) { e = (edictrun_t *)(inst->funcs.edicttable[i]); inst->funcs.edicttable[i] = NULL; if (e) { // e->entnum = i; f(e); } } PRHunkFree(inst, 0); #if defined(_WIN32) && !defined(WINRT) VirtualFree(inst->inst.addressablehunk, 0, MEM_RELEASE); //doesn't this look complicated? :p #else free(inst->inst.addressablehunk); #endif PR_FreeAllTemps(inst); if (inst->inst.allocedstrings) f(inst->inst.allocedstrings); inst->inst.allocedstrings = NULL; if (inst->inst.tempstrings) f(inst->inst.tempstrings); inst->inst.tempstrings = NULL; free(inst->inst.watch_name); if (inst->inst.field) f(inst->inst.field); if (inst->inst.shares) f(inst->inst.shares); //free memory f(inst); } #ifndef WIN32 #define QCLIBINT //don't use dllspecifications #endif #if defined(QCLIBDLL_EXPORTS) #ifdef _WIN32 __declspec(dllexport) #else __attribute__((visibility("default"))) #endif #endif pubprogfuncs_t * PDECL InitProgs(progexterns_t *ext) { progfuncs_t *funcs; if (!ext) { static progexterns_t defexterns; ext = &defexterns; } else if (ext->progsversion != PROGSTRUCT_VERSION) return NULL; #undef memalloc #undef pr_progstate #undef pr_argc funcs = (ext->memalloc?ext->memalloc:qclib_malloc)(sizeof(progfuncs_t)); memcpy(&funcs->funcs, &deffuncs, sizeof(pubprogfuncs_t)); memset(&funcs->inst, 0, sizeof(funcs->inst)); funcs->funcs.progstate = &funcs->inst.progstate; funcs->funcs.parms = ext; { //defs incase following structure is not passed. static struct edict_s *safe_edicts; static int safe_num_edicts; static double safetime=0; if (!ext->progsversion) ext->progsversion = PROGSTRUCT_VERSION; if (!ext->Printf) ext->Printf = qclib_null_printf; if (!ext->DPrintf) ext->DPrintf = qclib_null_printf; if (!ext->Sys_Error) ext->Sys_Error = (void*)exit; if (!ext->memalloc) ext->memalloc = qclib_malloc; if (!ext->memfree) ext->memfree = qclib_free; if (!ext->gametime) ext->gametime = &safetime; if (!ext->edicts) ext->edicts = &safe_edicts; if (!ext->num_edicts) ext->num_edicts = &safe_num_edicts; if (!ext->edictsize) ext->edictsize = sizeof(edictrun_t); } SetEndian(); return &funcs->funcs; } #ifdef QCC void main (int argc, char **argv) { progexterns_t ext; progfuncs_t *funcs; funcs = InitProgs(&ext); if (funcs->PR_StartCompile(argc, argv)) while(funcs->PR_ContinueCompile()); } #endif fteqcc-20251105/./pr_x86.c0000644000200200001440000012652515233070110014231 0ustar twolifeusers/* when I say JIT, I mean load time, not execution time. notes: qc jump offsets are all constants. we have no variable offset jumps (other than function calls/returns) field remapping... fields are in place, and cannot be adjusted. if a field is not set to 0, its assumed to be a constant. optimisations: none at the moment... instructions need to be chained. stuff that writes to C should be cacheable, etc. maybe we don't even need to do the write to C it should also be possible to fold in eq+ifnot, so none of this silly storeing of floats in equality tests this means that we need to track which vars are cached and in what form: fpreg, ireg+floatasint, ireg+float. certain qccx hacks can use fpu operations on ints, so do what the instruction says, rather than considering an add an add regardless of types. OP_AND_F, OP_OR_F etc will generally result in ints, and we should be able to keep them as ints if they combine with other ints. some instructions are jump sites. any cache must be flushed before the start of the instruction. some variables are locals, and will only ever be written by a single instruction, then read by the following instruction. such temps do not need to be written, or are overwritten later in the function anyway. such locals need to be calculated PER FUNCTION as (fte)qcc can overlap locals making multiple distinct locals on a single offset. store locals on a proper stack instead of the current absurd mechanism. eax - tmp ebx - prinst->edicttable ecx - tmp edx - tmp esi - debug opcode number edi - tmp (because its preserved by subfunctions ebp - to use gas to provide binary opcodes: vim -N blob.s && as blob.s && objdump.exe -d a.out notable mods to test: prydon gate, due to fpu mangling to carry values between maps */ #define PROGSUSED #include "progsint.h" #ifdef QCJIT #ifndef _WIN32 #include #endif static float ta, tb, nullfloat=0; struct jitstate { unsigned int *statementjumps; //[MAX_STATEMENTS*3] unsigned char **statementoffsets; //[MAX_STATEMENTS] unsigned int numjumps; unsigned char *code; unsigned int codesize; unsigned int jitstatements; float *glob; unsigned int cachedglobal; unsigned int cachereg; }; static void Jit_EmitByte(struct jitstate *jit, unsigned char byte) { jit->code[jit->codesize++] = byte; } static void Jit_Emit4Byte(struct jitstate *jit, unsigned int value) { jit->code[jit->codesize++] = (value>> 0)&0xff; jit->code[jit->codesize++] = (value>> 8)&0xff; jit->code[jit->codesize++] = (value>>16)&0xff; jit->code[jit->codesize++] = (value>>24)&0xff; } static void Jit_EmitAdr(struct jitstate *jit, void *value) { Jit_Emit4Byte(jit, (unsigned int)value); } static void Jit_EmitFloat(struct jitstate *jit, float value) { union {float f; unsigned int i;} u; u.f = value; Jit_Emit4Byte(jit, u.i); } static void Jit_Emit2Byte(struct jitstate *jit, unsigned short value) { jit->code[jit->codesize++] = (value>> 0)&0xff; jit->code[jit->codesize++] = (value>> 8)&0xff; } static void Jit_EmitFOffset(struct jitstate *jit, const void *func, int bias) { union {const void *f; unsigned int i;} u; u.f = func; u.i -= (unsigned int)&jit->code[jit->codesize+bias]; Jit_Emit4Byte(jit, u.i); } static void Jit_Emit4ByteJump(struct jitstate *jit, int statementnum, int offset) { jit->statementjumps[jit->numjumps++] = jit->codesize; jit->statementjumps[jit->numjumps++] = statementnum; jit->statementjumps[jit->numjumps++] = offset; //the offset is filled in later jit->codesize += 4; } #ifdef _WIN32 #undef REG_NONE #endif enum { REG_EAX, REG_ECX, REG_EDX, REG_EBX, //note: edicttable REG_ESP, REG_EBP, REG_ESI, REG_EDI, /*I'm not going to list S1 here, as that makes things too awkward*/ REG_S0, REG_NONE }; #define XOR(sr,dr) EmitByte(0x31);EmitByte(0xc0 | (sr<<3) | dr); #define CLEARREG(reg) XOR(reg,reg) #define LOADREG(addr, reg) if (reg == REG_EAX) {EmitByte(0xa1);} else {EmitByte(0x8b); EmitByte((reg<<3) | 0x05);} EmitAdr(addr); #define STOREREG(reg, addr) if (reg == REG_EAX) {EmitByte(0xa3);} else {EmitByte(0x89); EmitByte((reg<<3) | 0x05);} EmitAdr(addr); #define STOREF(f, addr) EmitByte(0xc7);EmitByte(0x05); EmitAdr(addr);EmitFloat(f); #define STOREI(i, addr) EmitByte(0xc7);EmitByte(0x05); EmitAdr(addr);Emit4Byte(i); #define SETREGI(val,reg) EmitByte(0xbe);Emit4Byte(val); #define ARGREGS(a,b,c) GCache_Load(jit, op[i].a, a, op[i].b, b, op[i].c, c) #define RESULTREG(r) GCache_Store(jit, op[i].c, r) #define EmitByte(v) Jit_EmitByte(jit, v) #define EmitAdr(v) Jit_EmitAdr(jit, v) #define EmitFOffset(a,b) Jit_EmitFOffset(jit, a, b) #define Emit4ByteJump(a,b) Jit_Emit4ByteJump(jit, a, b) #define Emit4Byte(v) Jit_Emit4Byte(jit, v) #define EmitFloat(v) Jit_EmitFloat(jit, v) #define LocalJmp(v) Jit_LocalJmp(jit, v) #define LocalLoc() Jit_LocalLoc(jit) //for the purposes of the cache, 'temp' offsets are only read when they have been written only within the preceeding control block. //if they were read at any other time, then we must write them out in full. //this logic applies only to locals of a function. //#define USECACHE static void GCache_Load(struct jitstate *jit, int ao, int ar, int bo, int br, int co, int cr) { #if USECACHE if (jit->cachedreg != REG_NONE) { /*something is cached, if its one of the input offsets then can chain the instruction*/ if (jit->cachedglobal === ao && ar != REG_NONE) { if (jit->cachedreg == ar) ar = REG_NONE; } if (jit->cachedglobal === bo && br != REG_NONE) { if (jit->cachedreg == br) br = REG_NONE; } if (jit->cachedglobal === co && cr != REG_NONE) { if (jit->cachedreg == cr) cr = REG_NONE; } if (!istemp(ao)) { /*purge the old cache*/ switch(jit->cachedreg) { case REG_NONE: break; case REG_S0: //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(jit->glob + jit->cachedglobal); break; default: STOREREG(jit->cachedreg, jit->glob + jit->cachedglobal); break; } jit->cachedglobal = -1; jit->cachedreg = REG_NONE; } #endif switch(ar) { case REG_NONE: break; case REG_S0: //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(jit->glob + ao); break; default: LOADREG(jit->glob + ao, ar); break; } switch(br) { case REG_NONE: break; case REG_S0: //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(jit->glob + bo); break; default: LOADREG(jit->glob + bo, br); break; } switch(cr) { case REG_NONE: break; case REG_S0: //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(jit->glob + co); break; default: LOADREG(jit->glob + co, cr); break; } } static void GCache_Store(struct jitstate *jit, int ofs, int reg) { #if USECACHE jit->cachedglobal = ofs; jit->cachedreg = reg; #else switch(reg) { case REG_NONE: break; case REG_S0: //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(jit->glob + ofs); break; default: STOREREG(reg, jit->glob + ofs); break; } #endif } static void *Jit_LocalLoc(struct jitstate *jit) { return &jit->code[jit->codesize]; } static void *Jit_LocalJmp(struct jitstate *jit, int cond) { /*floating point ops don't set the sign flag, thus we use the 'above/below' instructions instead of 'greater/less' instructions*/ if (cond == OP_GOTO) Jit_EmitByte(jit, 0xeb); //jmp else if (cond == OP_LE_F) Jit_EmitByte(jit, 0x76); //jbe else if (cond == OP_GE_F) Jit_EmitByte(jit, 0x73); //jae else if (cond == OP_LT_F) Jit_EmitByte(jit, 0x72); //jb else if (cond == OP_GT_F) Jit_EmitByte(jit, 0x77); //ja else if (cond == OP_LE_I) Jit_EmitByte(jit, 0x7e); //jle else if (cond == OP_LT_I) Jit_EmitByte(jit, 0x7c); //jl else if ((cond >= OP_NE_F && cond <= OP_NE_FNC) || cond == OP_NE_I) Jit_EmitByte(jit, 0x75); //jne else if ((cond >= OP_EQ_F && cond <= OP_EQ_FNC) || cond == OP_EQ_I) Jit_EmitByte(jit, 0x74); //je #if defined(DEBUG) && defined(_WIN32) else { OutputDebugString("oh noes!\n"); return NULL; } #endif Jit_EmitByte(jit, 0); return Jit_LocalLoc(jit); } static void LocalJmpLoc(void *jmp, void *loc) { int offs; unsigned char *a = jmp; offs = (char *)loc - (char *)jmp; #if defined(DEBUG) && defined(_WIN32) if (offs > 127 || offs <= -128) { OutputDebugStringA("bad jump\n"); a[-2] = 0xcd; a[-1] = 0xcc; return; } #endif a[-1] = offs; } static void FixupJumps(struct jitstate *jit) { unsigned int j; unsigned char *codesrc; unsigned char *codedst; unsigned int offset; unsigned int v; for (j = 0; j < jit->numjumps;) { v = jit->statementjumps[j++]; codesrc = &jit->code[v]; v = jit->statementjumps[j++]; codedst = jit->statementoffsets[v]; v = jit->statementjumps[j++]; offset = (int)(codedst - (codesrc-v)); //3rd term because the jump is relative to the instruction start, not the instruction's offset codesrc[0] = (offset>> 0)&0xff; codesrc[1] = (offset>> 8)&0xff; codesrc[2] = (offset>>16)&0xff; codesrc[3] = (offset>>24)&0xff; } } int ASMCALL PR_LeaveFunction (progfuncs_t *progfuncs); int ASMCALL PR_EnterFunction (progfuncs_t *progfuncs, dfunction_t *f, int progsnum); void PR_CloseJit(struct jitstate *jit) { if (jit) { free(jit->statementjumps); free(jit->statementoffsets); #ifndef _WIN32 munmap(jit->code, jit->jitstatements * 500); #else free(jit->code); #endif free(jit); } } #if 0 //called from jit code static PDECL PR_CallFuncion(progfuncs_t *progfuncs, int fnum) { int callerprogs; int newpr; unsigned int fnum; fnum = OPA->function; glob = NULL; //try to derestrict it. callerprogs=prinst.pr_typecurrent; //so we can revert to the right caller. newpr = (fnum & 0xff000000)>>24; //this is the progs index of the callee fnum &= ~0xff000000; //the callee's function index. //if it's an external call, switch now (before any function pointers are used) if (callerprogs != newpr || !fnum || fnum > pr_progs->numfunctions) { char *msg = fnum?"OP_CALL references invalid function in %s\n":"NULL function from qc (inside %s).\n"; PR_SwitchProgsParms(progfuncs, callerprogs); glob = pr_globals; if (!progfuncs->funcs.debug_trace) QCFAULT(&progfuncs->funcs, msg, PR_StringToNative(&progfuncs->funcs, pr_xfunction->s_name)); //skip the instruction if they just try stepping over it anyway. PR_StackTrace(&progfuncs->funcs, 0); printf(msg, PR_StringToNative(&progfuncs->funcs, pr_xfunction->s_name)); pr_globals[OFS_RETURN] = 0; pr_globals[OFS_RETURN+1] = 0; pr_globals[OFS_RETURN+2] = 0; break; } newf = &pr_cp_functions[fnum & ~0xff000000]; if (newf->first_statement <= 0) { // negative statements are built in functions /*calling a builtin in another progs may affect that other progs' globals instead, is the theory anyway, so args and stuff need to move over*/ if (prinst.pr_typecurrent != 0) { //builtins quite hackily refer to only a single global. //for builtins to affect the globals of other progs, we need to first switch to the progs that it will affect, so they'll be correct when we switch back PR_SwitchProgsParms(progfuncs, 0); } i = -newf->first_statement; // p = pr_typecurrent; if (i < externs->numglobalbuiltins) { #ifndef QCGC prinst.numtempstringsstack = prinst.numtempstrings; #endif (*externs->globalbuiltins[i]) (&progfuncs->funcs, (struct globalvars_s *)current_progstate->globals); //in case ed_alloc was called num_edicts = sv_num_edicts; if (prinst.continuestatement!=-1) { st=&pr_statements[prinst.continuestatement]; prinst.continuestatement=-1; glob = pr_globals; break; } } else { // if (newf->first_statement == -0x7fffffff) // ((builtin_t)newf->profile) (progfuncs, (struct globalvars_s *)current_progstate->globals); // else PR_RunError (&progfuncs->funcs, "Bad builtin call number - %i", -newf->first_statement); } // memcpy(&pr_progstate[p].globals[OFS_RETURN], ¤t_progstate->globals[OFS_RETURN], sizeof(vec3_t)); PR_SwitchProgsParms(progfuncs, (progsnum_t)callerprogs); //decide weather non debugger wants to start debugging. s = st-pr_statements; return s; } // PR_SwitchProgsParms((OPA->function & 0xff000000)>>24); s = PR_EnterFunction (progfuncs, newf, callerprogs); st = &pr_statements[s]; } #endif struct jitstate *PR_GenerateJit(progfuncs_t *progfuncs) { struct jitstate *jit; void *j0, *l0; void *j1, *l1; void *j2, *l2; unsigned int i; dstatement16_t *op = (dstatement16_t*)current_progstate->statements; unsigned int numstatements = current_progstate->progs->numstatements; unsigned int numglobals = current_progstate->progs->numglobals+3; //vectors are annoying. int *glob = (int*)current_progstate->globals; unsigned int numfunctions = current_progstate->progs->numfunctions; mfunction_t *func; // pbyte *isconst; pbool failed = false; jit = malloc(sizeof(*jit)); jit->jitstatements = numstatements; // isconst = malloc(numglobals*sizeof(*isconst)); jit->statementjumps = malloc(numstatements*3*sizeof(int)); jit->statementoffsets = malloc(numstatements*sizeof(*jit->statementoffsets)); #ifndef _WIN32 jit->code = mmap(NULL, numstatements*500, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); #else jit->code = malloc(numstatements*500); #endif if (!jit->code) return NULL; jit->numjumps = 0; jit->codesize = 0; for (i = 0; i < numstatements; i++) jit->statementoffsets[i] = NULL; // for (i = 0; i < numglobals; i++) // isconst[i] = true; for (i = 0; i < numfunctions; i++) { } for (i = 0; i < numstatements; i++) { //figure out which statements are jumped to. these are statements that must flush registers prior to execution. switch(op[i].op) { case OP_GOTO: jit->statementoffsets[i + (short)op[i].a] = (void*)~0; break; case OP_IF_I: case OP_IFNOT_I: case OP_IF_F: case OP_IFNOT_F: case OP_IF_S: case OP_IFNOT_S: case OP_CASE: jit->statementoffsets[i + (short)op[i].b] = (void*)~0; break; case OP_CASERANGE: jit->statementoffsets[i + (short)op[i].c] = (void*)~0; break; } //we probably can't do anything about consts. //we might be able to do something about locals, but we would need to fix this to generate per-function. //we CAN do something about consts, most of them anyway. //visible types /* if (OpAssignsToA(op[i].op)) { if (op[i].a >= numglobals) failed = true; else isconst[op[i].a] = false; } if (OpAssignsToB(op[i].op)) { if (op[i].b >= numglobals) failed = true; else isconst[op[i].b] = false; } if (OpAssignsToC(op[i].op)) { if (op[i].c >= numglobals) failed = true; else isconst[op[i].c] = false; } */ } for (i = 0; i < numstatements && !failed; i++) { if (jit->statementoffsets[i]) { //FIXME: flush any registers. } jit->statementoffsets[i] = &jit->code[jit->codesize]; #ifdef _DEBUG /*DEBUG*/ SETREGI(op[i].op, REG_ESI); #endif switch(op[i].op) { //jumps case OP_IF_I: //integer compare //if a, goto b //cmpl $0,glob[A] EmitByte(0x83);EmitByte(0x3d);EmitAdr(glob + op[i].a);EmitByte(0x0); //jne B EmitByte(0x0f);EmitByte(0x85);Emit4ByteJump(i + (signed short)op[i].b, -4); break; case OP_IFNOT_I: //integer compare //if !a, goto b //cmpl $0,glob[A] EmitByte(0x83);EmitByte(0x3d);EmitAdr(glob + op[i].a);EmitByte(0x0); //je B EmitByte(0x0f);EmitByte(0x84);Emit4ByteJump(i + (signed short)op[i].b, -4); break; case OP_GOTO: EmitByte(0xE9);Emit4ByteJump(i + (signed short)op[i].a, -4); break; //function returns case OP_DONE: case OP_RETURN: //done and return are the same //part 1: store A into OFS_RETURN if (!op[i].a) { //assumption: anything that returns address 0 is a void or zero return. //thus clear eax and copy that to the return vector. CLEARREG(REG_EAX); STOREREG(REG_EAX, glob + OFS_RETURN+0); STOREREG(REG_EAX, glob + OFS_RETURN+1); STOREREG(REG_EAX, glob + OFS_RETURN+2); } else { LOADREG(glob + op[i].a+0, REG_EAX); LOADREG(glob + op[i].a+1, REG_EDX); LOADREG(glob + op[i].a+2, REG_ECX); STOREREG(REG_EAX, glob + OFS_RETURN+0); STOREREG(REG_EDX, glob + OFS_RETURN+1); STOREREG(REG_ECX, glob + OFS_RETURN+2); } //call leavefunction to get the return address // pushl progfuncs EmitByte(0x68);EmitAdr(progfuncs); // call PR_LeaveFunction EmitByte(0xe8);EmitFOffset(PR_LeaveFunction, 4); // add $4,%esp EmitByte(0x83);EmitByte(0xc4);EmitByte(0x04); // movl pr_depth,%edx EmitByte(0x8b);EmitByte(0x15);EmitAdr(&pr_depth); // cmp prinst->exitdepth,%edx EmitByte(0x3b);EmitByte(0x15);EmitAdr(&prinst.exitdepth); // je returntoc j1 = LocalJmp(OP_EQ_E); // mov statementoffsets[%eax*4],%eax EmitByte(0x8b);EmitByte(0x04);EmitByte(0x85);EmitAdr(jit->statementoffsets+1); // jmp *eax EmitByte(0xff);EmitByte(0xe0); // returntoc: l1 = LocalLoc(); // ret EmitByte(0xc3); LocalJmpLoc(j1,l1); break; //function calls case OP_CALL0: case OP_CALL1: case OP_CALL2: case OP_CALL3: case OP_CALL4: case OP_CALL5: case OP_CALL6: case OP_CALL7: case OP_CALL8: //FIXME: the size of this instruction is going to hurt cache performance if every single function call is expanded into this HUGE CHUNK of gibberish! //FIXME: consider the feasability of just calling a C function and just jumping to the address it returns. //save the state in place the rest of the engine can cope with //movl $i, pr_xstatement EmitByte( 0xc7);EmitByte(0x05);EmitAdr(&pr_xstatement);Emit4Byte(i); //movl $(op[i].op-OP_CALL0), pr_argc EmitByte( 0xc7);EmitByte(0x05);EmitAdr(&progfuncs->funcs.callargc);Emit4Byte(op[i].op-OP_CALL0); //figure out who we're calling, and what that involves //%eax = glob[A] LOADREG(glob + op[i].a, REG_EAX); //eax is now the func num //mov %eax,%ecx EmitByte(0x89); EmitByte(0xc1); //shr $24,%ecx EmitByte(0xc1); EmitByte(0xe9); EmitByte(0x18); //ecx is now the progs num for the new func /* //cmp %ecx,pr_typecurrent EmitByte(0x39); EmitByte(0x0d); EmitAdr(&pr_typecurrent); //je sameprogs j1 = LocalJmp(OP_EQ_I); { //can't handle switching progs //FIXME: recurse though PR_ExecuteProgram //push eax //push progfuncs //call PR_ExecuteProgram //add $8,%esp //remember to change the je above //err... exit depth? no idea EmitByte(0xcd);EmitByte(op[i].op); //int $X //ret EmitByte(0xc3); } //sameprogs: l1 = LocalLoc(); LocalJmpLoc(j1,l1); */ //andl $0x00ffffff, %eax EmitByte(0x25);Emit4Byte(0x00ffffff); //mov $sizeof(dfunction_t),%edx EmitByte(0xba);Emit4Byte(sizeof(dfunction_t)); //mul %edx EmitByte(0xf7); EmitByte(0xe2); //add pr_functions,%eax EmitByte(0x05); EmitAdr(current_progstate->functions); //eax is now the dfunction_t to be called //edx is clobbered. //mov (%eax),%edx EmitByte(0x8b);EmitByte(0x10); //edx is now the first statement number //cmp $0,%edx EmitByte(0x83);EmitByte(0xfa);EmitByte(0x00); //jl isabuiltin j1 = LocalJmp(OP_LT_I); { /* call the function*/ //push %ecx EmitByte(0x51); //push %eax EmitByte(0x50); //pushl progfuncs EmitByte(0x68);EmitAdr(progfuncs); //call PR_EnterFunction EmitByte(0xe8);EmitFOffset(PR_EnterFunction, 4); //sub $12,%esp EmitByte(0x83);EmitByte(0xc4);EmitByte(0xc); //eax is now the next statement number (first of the new function, usually equal to ecx, but not always) //jmp statementoffsets[%eax*4] EmitByte(0xff);EmitByte(0x24);EmitByte(0x85);EmitAdr(jit->statementoffsets+1); } /*its a builtin, figure out which, and call it*/ //isabuiltin: l1 = LocalLoc(); LocalJmpLoc(j1,l1); //push current_progstate->globals EmitByte(0x68);EmitAdr(current_progstate->globals); //push progfuncs EmitByte(0x68);EmitAdr(progfuncs); //neg %edx EmitByte(0xf7);EmitByte(0xda); //call externs->globalbuiltins[%edx,4] //FIXME: make sure this dereferences EmitByte(0xff);EmitByte(0x14);EmitByte(0x95);EmitAdr(externs->globalbuiltins); //add $8,%esp EmitByte(0x83);EmitByte(0xc4);EmitByte(0x8); //but that builtin might have been Abort() LOADREG(&prinst.continuestatement, REG_EAX); //cmp $-1,%eax EmitByte(0x83);EmitByte(0xf8);EmitByte(0xff); //je donebuiltincall j1 = LocalJmp(OP_EQ_I); { //mov $-1,prinst->continuestatement EmitByte(0xc7);EmitByte(0x05);EmitAdr(&prinst.continuestatement);Emit4Byte((unsigned int)-1); //jmp statementoffsets[%eax*4] EmitByte(0xff);EmitByte(0x24);EmitByte(0x85);EmitAdr(jit->statementoffsets); } //donebuiltincall: l1 = LocalLoc(); LocalJmpLoc(j1,l1); break; case OP_MUL_F: //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a); //fmuls glob[B] EmitByte(0xd8);EmitByte(0x0d);EmitAdr(glob + op[i].b); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c); break; case OP_DIV_F: //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a); //fdivs glob[B] EmitByte(0xd8);EmitByte(0x35);EmitAdr(glob + op[i].b); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c); break; case OP_ADD_F: //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a); //fadds glob[B] EmitByte(0xd8);EmitByte(0x05);EmitAdr(glob + op[i].b); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c); break; case OP_SUB_F: //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a); //fsubs glob[B] EmitByte(0xd8);EmitByte(0x25);EmitAdr(glob + op[i].b); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c); break; case OP_NOT_F: //fldz EmitByte(0xd9);EmitByte(0xee); //fcomps glob[A] EmitByte(0xd8); EmitByte(0x1d); EmitAdr(glob + op[i].a); //fnstsw %ax EmitByte(0xdf);EmitByte(0xe0); //testb 0x40,%ah EmitByte(0xf6);EmitByte(0xc4);EmitByte(0x40); j1 = LocalJmp(OP_NE_F); { STOREF(0.0f, glob + op[i].c); j2 = LocalJmp(OP_GOTO); } { //noteq: l1 = LocalLoc(); STOREF(1.0f, glob + op[i].c); } //end: l2 = LocalLoc(); LocalJmpLoc(j1,l1); LocalJmpLoc(j2,l2); break; case OP_STORE_F: case OP_STORE_S: case OP_STORE_ENT: case OP_STORE_FLD: case OP_STORE_FNC: LOADREG(glob + op[i].a, REG_EAX); STOREREG(REG_EAX, glob + op[i].b); break; case OP_STORE_V: LOADREG(glob + op[i].a+0, REG_EAX); LOADREG(glob + op[i].a+1, REG_EDX); LOADREG(glob + op[i].a+2, REG_ECX); STOREREG(REG_EAX, glob + op[i].b+0); STOREREG(REG_EDX, glob + op[i].b+1); STOREREG(REG_ECX, glob + op[i].b+2); break; case OP_LOAD_F: case OP_LOAD_S: case OP_LOAD_ENT: case OP_LOAD_FLD: case OP_LOAD_FNC: case OP_LOAD_V: //a is the ent number, b is the field //c is the dest LOADREG(glob + op[i].a, REG_EAX); LOADREG(glob + op[i].b, REG_ECX); //FIXME: bound eax (ent number) //FIXME: bound ecx (field index) //mov (ebx,eax,4).%eax EmitByte(0x8b); EmitByte(0x04); EmitByte(0x83); //eax is now an edictrun_t //mov fields(,%eax,4),%edx EmitByte(0x8b);EmitByte(0x50);EmitByte((int)&((edictrun_t*)NULL)->fields); //edx is now the field array for that ent //mov fieldajust(%edx,%ecx,4),%eax EmitByte(0x8b); EmitByte(0x84); EmitByte(0x8a); Emit4Byte(progfuncs->funcs.fieldadjust*4); STOREREG(REG_EAX, glob + op[i].c) if (op[i].op == OP_LOAD_V) { //mov fieldajust+4(%edx,%ecx,4),%eax EmitByte(0x8b); EmitByte(0x84); EmitByte(0x8a); Emit4Byte(4+progfuncs->funcs.fieldadjust*4); STOREREG(REG_EAX, glob + op[i].c+1) //mov fieldajust+8(%edx,%ecx,4),%eax EmitByte(0x8b); EmitByte(0x84); EmitByte(0x8a); Emit4Byte(8+progfuncs->funcs.fieldadjust*4); STOREREG(REG_EAX, glob + op[i].c+2) } break; case OP_ADDRESS: //a is the ent number, b is the field //c is the dest LOADREG(glob + op[i].a, REG_EAX); LOADREG(glob + op[i].b, REG_ECX); //FIXME: bound eax (ent number) //FIXME: bound ecx (field index) //mov (ebx,eax,4).%eax EmitByte(0x8b); EmitByte(0x04); EmitByte(0x83); //eax is now an edictrun_t //mov fields(,%eax,4),%edx EmitByte(0x8b);EmitByte(0x50);EmitByte((int)&((edictrun_t*)NULL)->fields); //edx is now the field array for that ent //mov fieldajust(%edx,%ecx,4),%eax //offset = progfuncs->fieldadjust //EmitByte(0x8d); EmitByte(0x84); EmitByte(0x8a); EmitByte(progfuncs->funcs.fieldadjust*4); EmitByte(0x8d); EmitByte(0x84); EmitByte(0x8a); Emit4Byte(progfuncs->funcs.fieldadjust*4); STOREREG(REG_EAX, glob + op[i].c); break; case OP_STOREP_F: case OP_STOREP_S: case OP_STOREP_ENT: case OP_STOREP_FLD: case OP_STOREP_FNC: LOADREG(glob + op[i].a, REG_EAX); LOADREG(glob + op[i].b, REG_ECX); //mov %eax,(%ecx) EmitByte(0x89);EmitByte(0x01); break; case OP_STOREP_V: LOADREG(glob + op[i].b, REG_ECX); LOADREG(glob + op[i].a+0, REG_EAX); //mov %eax,0(%ecx) EmitByte(0x89);EmitByte(0x01); LOADREG(glob + op[i].a+1, REG_EAX); //mov %eax,4(%ecx) EmitByte(0x89);EmitByte(0x41);EmitByte(0x04); LOADREG(glob + op[i].a+2, REG_EAX); //mov %eax,8(%ecx) EmitByte(0x89);EmitByte(0x41);EmitByte(0x08); break; case OP_NE_I: case OP_NE_E: case OP_NE_FNC: case OP_EQ_I: case OP_EQ_E: case OP_EQ_FNC: //integer equality LOADREG(glob + op[i].a, REG_EAX); //cmp glob[B],%eax EmitByte(0x3b); EmitByte(0x04); EmitByte(0x25); EmitAdr(glob + op[i].b); j1 = LocalJmp(op[i].op); { STOREF(0.0f, glob + op[i].c); j2 = LocalJmp(OP_GOTO); } { l1 = LocalLoc(); STOREF(1.0f, glob + op[i].c); } l2 = LocalLoc(); LocalJmpLoc(j1,l1); LocalJmpLoc(j2,l2); break; case OP_NOT_I: case OP_NOT_ENT: case OP_NOT_FNC: //cmp glob[B],$0 EmitByte(0x83); EmitByte(0x3d); EmitAdr(glob + op[i].a); EmitByte(0x00); j1 = LocalJmp(OP_NE_I); { STOREF(1.0f, glob + op[i].c); j2 = LocalJmp(OP_GOTO); } { l1 = LocalLoc(); STOREF(0.0f, glob + op[i].c); } l2 = LocalLoc(); LocalJmpLoc(j1,l1); LocalJmpLoc(j2,l2); break; case OP_BITOR_F: //floats... //flds glob[A] EmitByte(0xd9); EmitByte(0x05);EmitAdr(glob + op[i].a); //flds glob[B] EmitByte(0xd9); EmitByte(0x05);EmitAdr(glob + op[i].b); //fistp tb EmitByte(0xdb); EmitByte(0x1d);EmitAdr(&tb); //fistp ta EmitByte(0xdb); EmitByte(0x1d);EmitAdr(&ta); LOADREG(&ta, REG_EAX) //or %eax,tb EmitByte(0x09); EmitByte(0x05);EmitAdr(&tb); //fild tb EmitByte(0xdb); EmitByte(0x05);EmitAdr(&tb); //fstps glob[C] EmitByte(0xd9); EmitByte(0x1d);EmitAdr(glob + op[i].c); break; case OP_BITAND_F: //flds glob[A] EmitByte(0xd9); EmitByte(0x05);EmitAdr(glob + op[i].a); //flds glob[B] EmitByte(0xd9); EmitByte(0x05);EmitAdr(glob + op[i].b); //fistp tb EmitByte(0xdb); EmitByte(0x1d);EmitAdr(&tb); //fistp ta EmitByte(0xdb); EmitByte(0x1d);EmitAdr(&ta); /*two args are now at ta and tb*/ LOADREG(&ta, REG_EAX) //and tb,%eax EmitByte(0x21); EmitByte(0x05);EmitAdr(&tb); /*we just wrote the int value to tb, convert that to a float and store it at c*/ //fild tb EmitByte(0xdb); EmitByte(0x05);EmitAdr(&tb); //fstps glob[C] EmitByte(0xd9); EmitByte(0x1d);EmitAdr(glob + op[i].c); break; case OP_AND_F: //test floats properly, so we don't get confused with -0.0 //FIXME: is it feasable to grab the value as an int and test it against 0x7fffffff? //flds glob[A] EmitByte(0xd9); EmitByte(0x05); EmitAdr(glob + op[i].a); //fcomps nullfloat EmitByte(0xd8); EmitByte(0x1d); EmitAdr(&nullfloat); //fnstsw %ax EmitByte(0xdf); EmitByte(0xe0); //test $0x40,%ah EmitByte(0xf6); EmitByte(0xc4);EmitByte(0x40); //jz onefalse EmitByte(0x75); EmitByte(0x1f); //flds glob[B] EmitByte(0xd9); EmitByte(0x05); EmitAdr(glob + op[i].b); //fcomps nullfloat EmitByte(0xd8); EmitByte(0x1d); EmitAdr(&nullfloat); //fnstsw %ax EmitByte(0xdf); EmitByte(0xe0); //test $0x40,%ah EmitByte(0xf6); EmitByte(0xc4);EmitByte(0x40); //jnz onefalse EmitByte(0x75); EmitByte(0x0c); //mov float0,glob[C] EmitByte(0xc7); EmitByte(0x05); EmitAdr(glob + op[i].c); EmitFloat(1.0f); //jmp done EmitByte(0xeb); EmitByte(0x0a); //onefalse: //mov float1,glob[C] EmitByte(0xc7); EmitByte(0x05); EmitAdr(glob + op[i].c); EmitFloat(0.0f); //done: break; case OP_OR_F: //test floats properly, so we don't get confused with -0.0 //flds glob[A] EmitByte(0xd9); EmitByte(0x05); EmitAdr(glob + op[i].a); //fcomps nullfloat EmitByte(0xd8); EmitByte(0x1d); EmitAdr(&nullfloat); //fnstsw %ax EmitByte(0xdf); EmitByte(0xe0); //test $0x40,%ah EmitByte(0xf6); EmitByte(0xc4);EmitByte(0x40); //je onetrue EmitByte(0x74); EmitByte(0x1f); //flds glob[B] EmitByte(0xd9); EmitByte(0x05); EmitAdr(glob + op[i].b); //fcomps nullfloat EmitByte(0xd8); EmitByte(0x1d); EmitAdr(&nullfloat); //fnstsw %ax EmitByte(0xdf); EmitByte(0xe0); //test $0x40,%ah EmitByte(0xf6); EmitByte(0xc4);EmitByte(0x40); //je onetrue EmitByte(0x74); EmitByte(0x0c); //mov float0,glob[C] EmitByte(0xc7); EmitByte(0x05); EmitAdr(glob + op[i].c); EmitFloat(0.0f); //jmp done EmitByte(0xeb); EmitByte(0x0a); //onetrue: //mov float1,glob[C] EmitByte(0xc7); EmitByte(0x05); EmitAdr(glob + op[i].c); EmitFloat(1.0f); //done: break; case OP_EQ_S: case OP_NE_S: { //put a in ecx LOADREG(glob + op[i].a, REG_ECX); //put b in edi LOADREG(glob + op[i].b, REG_EDI); /* //early out if they're equal //cmp %ecx,%edi EmitByte(0x39); EmitByte(0xc0 | (REG_EDI<<3) | REG_ECX); j1c = LocalJmp(OP_EQ_S); //if a is 0, check if b is "" //jecxz ais0 EmitByte(0xe3); EmitByte(0x1a); //if b is 0, check if a is "" //cmp $0,%edi EmitByte(0x83); EmitByte(0xff); EmitByte(0x00); //jne bnot0 EmitByte(0x75); EmitByte(0x2a); { //push a EmitByte(0x51); //push progfuncs EmitByte(0x68); EmitAdr(progfuncs); //call PR_StringToNative EmitByte(0xe8); EmitFOffset(PR_StringToNative,4); //add $8,%esp EmitByte(0x83); EmitByte(0xc4); EmitByte(0x08); //cmpb $0,(%eax) EmitByte(0x80); EmitByte(0x38); EmitByte(0x00); j1b = LocalJmp(OP_EQ_S); j0b = LocalJmp(OP_GOTO); } //ais0: { //push edi EmitByte(0x57); //push progfuncs EmitByte(0x68); EmitAdr(progfuncs); //call PR_StringToNative EmitByte(0xe8); EmitFOffset(PR_StringToNative,4); //add $8,%esp EmitByte(0x83); EmitByte(0xc4); EmitByte(0x08); //cmpb $0,(%eax) EmitByte(0x80); EmitByte(0x38); EmitByte(0x00); //je _true EmitByte(0x74); EmitByte(0x36); //jmp _false EmitByte(0xeb); EmitByte(0x28); } //bnot0: */ LOADREG(glob + op[i].a, REG_ECX); //push ecx EmitByte(0x51); //push progfuncs EmitByte(0x68); EmitAdr(progfuncs); //call PR_StringToNative EmitByte(0xe8); EmitFOffset(PR_StringToNative,4); //push %eax EmitByte(0x50); LOADREG(glob + op[i].b, REG_EDI); //push %edi EmitByte(0x57); //push progfuncs EmitByte(0x68); EmitAdr(progfuncs); //call PR_StringToNative EmitByte(0xe8); EmitFOffset(PR_StringToNative,4); //add $8,%esp EmitByte(0x83); EmitByte(0xc4); EmitByte(0x08); //push %eax EmitByte(0x50); //call strcmp EmitByte(0xe8); EmitFOffset(strcmp,4); //add $16,%esp EmitByte(0x83); EmitByte(0xc4); EmitByte(0x10); //cmp $0,%eax EmitByte(0x83); EmitByte(0xf8); EmitByte(0x00); j1 = LocalJmp(OP_EQ_S); { l0 = LocalLoc(); STOREF((op[i].op == OP_NE_S)?1.0f:0.0f, glob + op[i].c); j2 = LocalJmp(OP_GOTO); } { l1 = LocalLoc(); STOREF((op[i].op == OP_NE_S)?0.0f:1.0f, glob + op[i].c); } l2 = LocalLoc(); // LocalJmpLoc(j0b, l0); LocalJmpLoc(j1, l1); // LocalJmpLoc(j1b, l1); LocalJmpLoc(j2, l2); } break; case OP_NOT_S: LOADREG(glob + op[i].a, REG_EAX) //cmp $0,%eax EmitByte(0x83); EmitByte(0xf8); EmitByte(0x00); j2 = LocalJmp(OP_EQ_S); //push %eax EmitByte(0x50); //push progfuncs EmitByte(0x68); EmitAdr(progfuncs); //call PR_StringToNative EmitByte(0xe8); EmitFOffset(PR_StringToNative,4); //add $8,%esp EmitByte(0x83); EmitByte(0xc4); EmitByte(0x08); //cmpb $0,(%eax) EmitByte(0x80); EmitByte(0x38); EmitByte(0x00); j1 = LocalJmp(OP_EQ_S); { STOREF(0.0f, glob + op[i].c); j0 = LocalJmp(OP_GOTO); } { l1 = LocalLoc(); STOREF(1.0f, glob + op[i].c); } l2 = LocalLoc(); LocalJmpLoc(j2, l1); LocalJmpLoc(j1, l1); LocalJmpLoc(j0, l2); break; case OP_ADD_V: //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a+0); //fadds glob[B] EmitByte(0xd8);EmitByte(0x05);EmitAdr(glob + op[i].b+0); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c+0); //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a+1); //fadds glob[B] EmitByte(0xd8);EmitByte(0x05);EmitAdr(glob + op[i].b+1); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c+1); //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a+2); //fadds glob[B] EmitByte(0xd8);EmitByte(0x05);EmitAdr(glob + op[i].b+2); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c+2); break; case OP_SUB_V: //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a+0); //fsubs glob[B] EmitByte(0xd8);EmitByte(0x25);EmitAdr(glob + op[i].b+0); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c+0); //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a+1); //fsubs glob[B] EmitByte(0xd8);EmitByte(0x25);EmitAdr(glob + op[i].b+1); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c+1); //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a+2); //fsubs glob[B] EmitByte(0xd8);EmitByte(0x25);EmitAdr(glob + op[i].b+2); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c+2); break; case OP_MUL_V: //this is actually a dotproduct //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a+0); //fmuls glob[B] EmitByte(0xd8);EmitByte(0x0d);EmitAdr(glob + op[i].b+0); //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a+1); //fmuls glob[B] EmitByte(0xd8);EmitByte(0x0d);EmitAdr(glob + op[i].b+1); //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a+2); //fmuls glob[B] EmitByte(0xd8);EmitByte(0x0d);EmitAdr(glob + op[i].b+2); //faddp EmitByte(0xde);EmitByte(0xc1); //faddp EmitByte(0xde);EmitByte(0xc1); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c); break; case OP_EQ_F: case OP_NE_F: case OP_LE_F: case OP_GE_F: case OP_LT_F: case OP_GT_F: //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].b); //flds glob[B] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a); //fcomip %st(1),%st EmitByte(0xdf);EmitByte(0xe9); //fstp %st(0) (aka: pop) EmitByte(0xdd);EmitByte(0xd8); j1 = LocalJmp(op[i].op); { STOREF(0.0f, glob + op[i].c); j2 = LocalJmp(OP_GOTO); } { l1 = LocalLoc(); STOREF(1.0f, glob + op[i].c); } l2 = LocalLoc(); LocalJmpLoc(j1,l1); LocalJmpLoc(j2,l2); break; case OP_MUL_FV: case OP_MUL_VF: // { int v; int f; if (op[i].op == OP_MUL_FV) { f = op[i].a; v = op[i].b; } else { v = op[i].a; f = op[i].b; } //flds glob[F] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + f); //flds glob[V0] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + v+0); //fmul st(1) EmitByte(0xd8);EmitByte(0xc9); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c+0); //flds glob[V0] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + v+1); //fmul st(1) EmitByte(0xd8);EmitByte(0xc9); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c+1); //flds glob[V0] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + v+2); //fmul st(1) EmitByte(0xd8);EmitByte(0xc9); //fstps glob[C] EmitByte(0xd9);EmitByte(0x1d);EmitAdr(glob + op[i].c+2); //fstp %st(0) (aka: pop) EmitByte(0xdd);EmitByte(0xd8); } break; case OP_STATE: //externs->stateop(progfuncs, OPA->_float, OPB->function); //push b EmitByte(0xff);EmitByte(0x35);EmitAdr(glob + op[i].b); //push a EmitByte(0xff);EmitByte(0x35);EmitAdr(glob + op[i].a); //push $progfuncs EmitByte(0x68); EmitAdr(progfuncs); //call externs->stateop EmitByte(0xe8); EmitFOffset(externs->stateop, 4); //add $12,%esp EmitByte(0x83); EmitByte(0xc4); EmitByte(0x0c); break; #if 1 /* case OP_NOT_V: //flds 0 //flds glob[A+0] //fcomip %st(1),%st //jne _true //flds glob[A+1] //fcomip %st(1),%st //jne _true //flds glob[A+1] //fcomip %st(1),%st //jne _true //mov 1,C //jmp done //_true: //mov 0,C //done: break; */ case OP_NOT_V: EmitByte(0xcd);EmitByte(op[i].op); printf("QCJIT: instruction %i is not implemented\n", op[i].op); break; #endif case OP_NE_V: case OP_EQ_V: { void *f0, *f1, *f2, *floc; //compare v[0] //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a+0); //flds glob[B] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].b+0); //fcomip %st(1),%st EmitByte(0xdf);EmitByte(0xe9); //fstp %st(0) (aka: pop) EmitByte(0xdd);EmitByte(0xd8); /*if the condition is true, don't fail*/ j1 = LocalJmp(op[i].op); { STOREF(0.0f, glob + op[i].c); f0 = LocalJmp(OP_GOTO); } l1 = LocalLoc(); LocalJmpLoc(j1,l1); //compare v[1] //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a+1); //flds glob[B] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].b+1); //fcomip %st(1),%st EmitByte(0xdf);EmitByte(0xe9); //fstp %st(0) (aka: pop) EmitByte(0xdd);EmitByte(0xd8); /*if the condition is true, don't fail*/ j1 = LocalJmp(op[i].op); { STOREF(0.0f, glob + op[i].c); f1 = LocalJmp(OP_GOTO); } l1 = LocalLoc(); LocalJmpLoc(j1,l1); //compare v[2] //flds glob[A] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].a+2); //flds glob[B] EmitByte(0xd9);EmitByte(0x05);EmitAdr(glob + op[i].b+2); //fcomip %st(1),%st EmitByte(0xdf);EmitByte(0xe9); //fstp %st(0) (aka: pop) EmitByte(0xdd);EmitByte(0xd8); /*if the condition is true, don't fail*/ j1 = LocalJmp(op[i].op); { STOREF(0.0f, glob + op[i].c); f2 = LocalJmp(OP_GOTO); } l1 = LocalLoc(); LocalJmpLoc(j1,l1); //success! STOREF(1.0f, glob + op[i].c); floc = LocalLoc(); LocalJmpLoc(f0,floc); LocalJmpLoc(f1,floc); LocalJmpLoc(f2,floc); break; } /*fteqcc generates these from reading 'fast arrays', and are part of hexenc extras*/ case OP_FETCH_GBL_F: case OP_FETCH_GBL_S: case OP_FETCH_GBL_E: case OP_FETCH_GBL_FNC: case OP_FETCH_GBL_V: { unsigned int max = ((unsigned int*)glob)[op[i].a-1]; unsigned int base = op[i].a; //flds glob[B] EmitByte(0xd9); EmitByte(0x05);EmitAdr(glob + op[i].b); //fistp ta EmitByte(0xdb); EmitByte(0x1d);EmitAdr(&ta); LOADREG(&ta, REG_EAX) //FIXME: if eax >= $max, abort if (op[i].op == OP_FETCH_GBL_V) { /*scale the index by 3*/ SETREGI(3, REG_EDX) //mul %edx EmitByte(0xf7); EmitByte(0xe2); } //lookup global //mov &glob[base](,%eax,4),%edx EmitByte(0x8b);EmitByte(0x14);EmitByte(0x85);Emit4Byte((unsigned int)(glob + base+0)); STOREREG(REG_EDX, glob + op[i].c+0) if (op[i].op == OP_FETCH_GBL_V) { //mov &glob[base+1](,%eax,4),%edx EmitByte(0x8b);EmitByte(0x14);EmitByte(0x85);Emit4Byte((unsigned int)(glob + base+1)); STOREREG(REG_EDX, glob + op[i].c+1) //mov &glob[base+2](,%eax,4),%edx EmitByte(0x8b);EmitByte(0x14);EmitByte(0x85);Emit4Byte((unsigned int)(glob + base+2)); STOREREG(REG_EDX, glob + op[i].c+2) } break; } /*fteqcc generates these from writing 'fast arrays'*/ case OP_GLOBALADDRESS: LOADREG(glob + op[i].b, REG_EAX); //lea &glob[A](, %eax, 4),%eax EmitByte(0x8d);EmitByte(0x04);EmitByte(0x85);EmitAdr(glob + op[i].b+2); STOREREG(REG_EAX, glob + op[i].c); break; // case OP_BOUNDCHECK: //FIXME: assert b <= a < c break; case OP_CONV_FTOI: //flds glob[A] EmitByte(0xd9); EmitByte(0x05);EmitAdr(glob + op[i].a); //fistp glob[C] EmitByte(0xdb); EmitByte(0x1d);EmitAdr(glob + op[i].c); break; case OP_MUL_I: LOADREG(glob + op[i].a, REG_EAX); //mull glob[C] (arg*eax => edx:eax) EmitByte(0xfc); EmitByte(0x25);EmitAdr(glob + op[i].b); STOREREG(REG_EAX, glob + op[i].c); break; /*other extended opcodes*/ case OP_BITOR_I: LOADREG(glob + op[i].a, REG_EAX) //or %eax,tb EmitByte(0x0b); EmitByte(0x05);EmitAdr(glob + op[i].b); STOREREG(REG_EAX, glob + op[i].c); break; default: { enum qcop_e e = op[i].op; printf("QCJIT: Extended instruction set %i is not supported, not using jit.\n", e); } failed = true; break; } } if(failed) { free(jit->statementjumps); //[MAX_STATEMENTS] free(jit->statementoffsets); //[MAX_STATEMENTS] free(jit->code); free(jit); return NULL; } FixupJumps(jit); /* most likely want executable memory calls somewhere else more common */ #ifdef _WIN32 { DWORD old; //this memory is on the heap. //this means that we must maintain read/write protection, or libc will crash us VirtualProtect(jit->code, jit->codesize, PAGE_EXECUTE_READWRITE, &old); } #else mprotect(jit->code, jit->codesize, PROT_READ|PROT_EXEC); #endif // externs->WriteFile("jit.x86", jit->code, jit->codesize); return jit; } static float foo(float arg) { float f; if (!arg) f = 1; else f = 0; return f; } void PR_EnterJIT(progfuncs_t *progfuncs, struct jitstate *jit, int statement) { #ifdef __GNUC__ //call, it clobbers pretty much everything. asm("call *%0" :: "r"(jit->statementoffsets[statement+1]),"b"(prinst->edicttable):"cc","memory","eax","ecx","edx","esi","edi"); #elif defined(_MSC_VER) void *entry = jit->statementoffsets[statement+1]; void *edicttable = prinst.edicttable; __asm { pushad mov eax,entry mov ebx,edicttable call eax popad } #else #error "Sorry, no idea how to enter assembler safely for your compiler" #endif } #endif fteqcc-20251105/./qccmain.c0000644000200200001440000064202115233070110014510 0ustar twolifeusers#if !defined(MINIMAL) && !defined(OMIT_QCC) #define PROGSUSED #include "qcc.h" #include #ifdef _WIN32 #include #endif #include #include "errno.h" #define countof(array) (sizeof(array)/sizeof(array[0])) //#define TODO_READWRITETRACK //#define DEBUG_DUMP //#define DISASM "M_Preset" extern QCC_def_t tempsdef; char QCC_copyright[1024]; int QCC_packid; char QCC_Packname[5][128]; extern int optres_test1; extern int optres_test2; pbool writeasm; int verbose; #define VERBOSE_WARNINGSONLY -1 #define VERBOSE_PROGRESS 0 #define VERBOSE_STANDARD 1 #define VERBOSE_DEBUG 2 #define VERBOSE_DEBUGSTATEMENTS 3 //figuring out the files can be expensive. pbool qcc_nopragmaoptimise; pbool opt_stripunusedfields; extern unsigned int locals_marshalled; extern int qccpersisthunk; pbool QCC_PR_SimpleGetToken (void); void QCC_PR_LexWhitespace (pbool inhibitpreprocessor); static char *QCC_PR_String (char *string); void *FS_ReadToMem(char *fname, size_t *len); void FS_CloseFromMem(void *mem); unsigned int MAX_REGS; unsigned int MAX_LOCALS = 0x10000; unsigned int MAX_TEMPS = 0x10000; int MAX_STRINGS; int MAX_GLOBALS; int MAX_FIELDS; int MAX_STATEMENTS; int MAX_FUNCTIONS; int MAX_CONSTANTS; int max_temps; int *qcc_tempofs; int tempsstart; #define MAXSOURCEFILESLIST 8 char sourcefileslist[MAXSOURCEFILESLIST][1024]; QCC_def_t *sourcefilesdefs[MAXSOURCEFILESLIST]; //for the gui to peek at. int sourcefilesnumdefs; //maximum used... int currentsourcefile; //currently compiling file. int numsourcefiles; //count pending. extern char *compilingfile; //file currently being compiled char compilingrootfile[1024]; //the .src file we started from (the current one, not original) char qccmsourcedir[1024]; //the -src path, for #includes void QCC_PR_ResetErrorScope(void); static void StartNewStyleCompile(void); pbool compressoutput; pbool newstylesource; char destfile[1024]; //the file we're going to output to pbool destfile_explicit; //destfile was override on the commandline, don't let qc change it. QCC_eval_basic_t *qcc_pr_globals; unsigned int numpr_globals; char *strings; int strofs; QCC_statement_t *statements; int numstatements; QCC_function_t *functions; //dfunction_t *dfunctions; int numfunctions; QCC_ddef_t *qcc_globals; int numglobaldefs; QCC_ddef_t *fields; int numfielddefs; //typedef char PATHSTRING[MAX_DATA_PATH]; precache_t *precache_sound; int numsounds; precache_t *precache_texture; int numtextures; precache_t *precache_model; int nummodels; precache_t *precache_file; int numfiles; extern int numCompilerConstants; hashtable_t compconstantstable; hashtable_t globalstable; hashtable_t localstable; hashtable_t typedeftable; #ifdef WRITEASM FILE *asmfile; pbool asmfilebegun; #endif hashtable_t floatconstdefstable; hashtable_t stringconstdefstable; hashtable_t stringconstdefstable_trans; extern int dotranslate_count; unsigned char qccwarningaction[WARN_MAX]; //0 = disabled, 1 = warn, 2 = error. unsigned int qcc_targetversion; qcc_targetformat_t qcc_targetformat; pbool bodylessfuncs; QCC_type_t *qcc_typeinfo; int numtypeinfos; int maxtypeinfos; pbool preprocessonly; static pbool flag_dumpfilenames; static pbool flag_dumpfields; static pbool flag_dumpsymbols; static pbool flag_dumpautocvars; static pbool flag_dumplocalisation; static pbool flag_dumptags; static pbool flag_dumpopcodes; struct { char *name; int index; } warningnames[] = { // {"", WARN_NOTREFERENCEDCONST}, // {"", WARN_CONFLICTINGRETURNS}, {" Q100", WARN_PRECOMPILERMESSAGE}, {" Q101", WARN_TOOMANYPARAMS}, //102: Indirect function: too many parameters //103: vararg func cannot have more than //104: type mismatch on parm %i {" Q105", WARN_TOOFEWPARAMS}, // {"", WARN_UNEXPECTEDPUNCT}, {" Q106", WARN_ASSIGNMENTTOCONSTANT}, //107: Array index should be type int //108: Mixed float and int types //109: Expecting int, float a parameter found //110: Expecting int, float b parameter found //112: Null 'if' statement //113: Null 'else' statement //114: Type mismatch on redeclaration //115: redeclared with different number of parms //116: Local %s redeclared //117: too many initializers //118: Too many closing braces //119: Too many #endifs {" Q120", WARN_BADPRAGMA}, //121: unknown directive //122: strofs exceeds limit //123: numstatements exceeds limit //124: numfunctions exceeds limit //125: numglobaldefs exceeds limit //126: numfielddefs exceeds limit //127: numpr_globals exceeds limit //128: rededeclared with different parms {" F129", WARN_COMPATIBILITYHACK}, //multiple errors are replaced by this, for compat purposes. {" Q203", WARN_MISSINGRETURNVALUE}, {" Q204", WARN_WRONGRETURNTYPE}, {" Q205", WARN_POINTLESSSTATEMENT}, {" Q206", WARN_MISSINGRETURN}, {" Q207", WARN_DUPLICATEDEFINITION}, //redeclared different scope {" Q208", WARN_SYSTEMCRC}, // {"", WARN_STRINGTOOLONG}, // {"", WARN_BADTARGET}, //301: %s defined as local in %s {" Q302", WARN_NOTREFERENCED}, //302: Unreferenced local variable %s from line %i //401: In function %s parameter %s is unused // {"", WARN_HANGINGSLASHR}, // {"", WARN_NOTDEFINED}, // {"", WARN_SWITCHTYPEMISMATCH}, // {"", WARN_CONFLICTINGUNIONMEMBER}, // {"", WARN_KEYWORDDISABLED}, // {"", WARN_ENUMFLAGS_NOTINTEGER}, // {"", WARN_ENUMFLAGS_NOTBINARY}, {" Q111", WARN_DUPLICATELABEL}, {" Q201", WARN_ASSIGNMENTINCONDITIONAL}, {" F300", WARN_DEADCODE}, {" F301", WARN_NOTUTF8}, {" F302", WARN_UNINITIALIZED}, {" F303", WARN_EVILPREPROCESSOR}, {" F304", WARN_UNARYNOTSCOPE}, {" F305", WARN_CASEINSENSITIVEFRAMEMACRO}, {" F306", WARN_SAMENAMEASGLOBAL}, {" F307", WARN_STRICTTYPEMISMATCH}, {" F308", WARN_TYPEMISMATCHREDECOPTIONAL}, {" F309", WARN_IGNORECOMMANDLINE}, {" F310", WARN_MISUSEDAUTOCVAR}, {" F311", WARN_FTE_SPECIFIC}, {" F312", WARN_OVERFLOW}, {" F313", WARN_DENORMAL}, {" F314", WARN_LAXCAST}, {" F315", WARN_DUPLICATEPRECOMPILER}, {" F316", WARN_IDENTICALPRECOMPILER}, {" F317", WARN_STALEMACRO}, {" F318", WARN_DUPLICATEMACRO}, {" F319", WARN_CONSTANTCOMPARISON}, {" F320", WARN_PARAMWITHNONAME}, {" F321", WARN_GMQCC_SPECIFIC}, {" F322", WARN_IFSTRING_USED}, {" F323", WARN_UNREACHABLECODE}, {" F324", WARN_FORMATSTRING}, {" F325", WARN_NESTEDCOMMENT}, {" F326", WARN_DEPRECATEDVARIABLE}, {" F327", WARN_ENUMFLAGS_NOTINTEGER}, {" F328", WARN_DEPRECACTEDSYNTAX}, {" F329", WARN_REDECLARATIONMISMATCH}, {" F330", WARN_MUTEDEPRECATEDVARIABLE}, {" F331", WARN_SELFNOTTHIS}, {" F332", WARN_DIVISIONBY0}, {" F333", WARN_ARGUMENTCHECK}, {" F334", WARN_MISSINGMEMBERQUALIFIER}, {" F335", WARN_MEMBERNOTDEFINED}, //defining a new member function inside a class that didn't list it. {" F207", WARN_NOTREFERENCEDFIELD}, {" F208", WARN_NOTREFERENCEDCONST}, {" F209", WARN_EXTRAPRECACHE}, {" F210", WARN_NOTPRECACHED}, {" F211", WARN_SYSTEMCRC2}, //frikqcc errors //Q608: PrecacheSound: numsounds //Q609: PrecacheModels: nummodels //Q610: PrecacheFile: numfiles //Q611: Bad parm order //Q612: PR_CompileFile: Didn't clear (internal error) //Q614: PR_DefForFieldOfs: couldn't find //Q613: Error writing error.log //Q615: Error writing //Q616: No function named //Q617: Malloc failure //Q618: Ran out of mem pointer space (malloc failure again) //we can put longer alternative names here... {" field-redeclared", WARN_REMOVEDWARNING}, {" deprecated", WARN_DEPRECATEDVARIABLE}, {" bounds", WARN_BOUNDS}, {" octal", WARN_OCTAL_IMMEDIATE}, {" unimplemented", WARN_IGNOREDKEYWORD}, {" localptr", WARN_UNSAFELOCALPOINTER}, {" largereturn", WARN_SLOW_LARGERETURN}, {NULL} }; char *QCC_NameForWarning(int idx) { int i; for (i = 0; warningnames[i].name; i++) { if (warningnames[i].index == idx) return warningnames[i].name; } return NULL; } int QCC_WarningForName(const char *name) { int i; for (i = 0; warningnames[i].name; i++) { if (!stricmp(name, warningnames[i].name+1)) return warningnames[i].index; } return -1; } optimisations_t optimisations[] = { //level 0 = no optimisations //level 1 = size optimisations //level 2 = speed optimisations //level 3 = unsafe optimisations (they break multiprogs). //level 4 = experimental or extreeme features... must be used explicitly {&opt_assignments, "t", 1, FLAG_ASDEFAULT, "assignments", "c = a*b is performed in one operation rather than two, and can cause older decompilers to fail."}, {&opt_shortenifnots, "i", 1, FLAG_ASDEFAULT, "shortenifs", "if (!a) was traditionally compiled in two statements. This optimisation does it in one, but can cause some decompilers to get confused."}, {&opt_nonvec_parms, "p", 1, FLAG_ASDEFAULT, "nonvec_parms", "In the original qcc, function parameters were specified as a vector store even for floats. This fixes that."}, {&opt_constant_names, "c", 2, FLAG_KILLSDEBUGGERS, "constant_names", "This optimisation strips out the names of constants (but not strings) from your progs, resulting in smaller files. It makes decompilers leave out names or fabricate numerical ones."}, {&opt_constant_names_strings, "cs", 3, FLAG_KILLSDEBUGGERS, "constant_names_strings", "This optimisation strips out the names of string constants from your progs. However, this can break addons, so don't use it in those cases."}, {&opt_dupconstdefs, "d", 1, FLAG_ASDEFAULT, "dupconstdefs", "This will merge definitions of constants which are the same value. Pay extra attention to assignment to constant warnings."}, {&opt_noduplicatestrings, "s", 1, FLAG_ASDEFAULT, "noduplicatestrings", "This will compact the string table that is stored in the progs. It will be considerably smaller with this."}, {&opt_locals, "l", 1, FLAG_KILLSDEBUGGERS, "locals", "Strips out local names and definitions. Most decompiles will break on this."}, {&opt_function_names, "n", 1, FLAG_KILLSDEBUGGERS, "function_names", "This strips out the names of functions which are never called. Doesn't make much of an impact though."}, {&opt_filenames, "f", 1, FLAG_KILLSDEBUGGERS, "filenames", "This strips out the filenames of the progs. This can confuse the really old decompilers, but is nothing to the more recent ones."}, // {&opt_unreferenced, "u", 1, FLAG_ASDEFAULT, "unreferenced", "Removes the entries of unreferenced variables. Doesn't make a difference in well maintained code."}, {&opt_overlaptemps, "r", 1, FLAG_ASDEFAULT, "overlaptemps", "Optimises the pr_globals count by overlapping temporaries. In QC, every multiplication, division or operation in general produces a temporary variable. This optimisation prevents excess, and in the case of Hexen2's gamecode, reduces the count by 50k. This is the most important optimisation, ever."}, {&opt_constantarithmatic, "a", 1, FLAG_ASDEFAULT, "constantarithmatic", "5*6 actually emits an operation into the progs. This prevents that happening, effectivly making the compiler see 30"}, {&opt_precache_file, "pf", 2, 0, "precache_file", "Strip out stuff wasted used in function calls and strings to the precache_file builtin (which is actually a stub in quake)."}, {&opt_return_only, "ro", 2, FLAG_KILLSDEBUGGERS, "return_only", "Functions ending in a return statement do not need a done statement at the end of the function. This can confuse some decompilers, making functions appear larger than they were."}, {&opt_compound_jumps, "cj", 2, FLAG_KILLSDEBUGGERS, "compound_jumps", "This optimisation plays an effect mostly with nested if/else statements, instead of jumping to an unconditional jump statement, it'll jump to the final destination instead. This will bewilder decompilers."}, // {&opt_comexprremoval, "cer", 4, 0, "expression_removal", "Eliminate common sub-expressions"}, //this would be too hard... {&opt_stripfunctions, "sf", 3, FLAG_KILLSDEBUGGERS, "strip_functions", "Strips out the 'defs' of functions that were only ever called directly. This does not affect saved games, but will prevent FTE_MULTIPROGS/mutators from being able to hook functions."}, {&opt_locals_overlapping, "lo", 2, FLAG_KILLSDEBUGGERS, "locals_overlapping", "Store all locals in a single section of the pr_globals. Vastly reducing it. This effectivly does the job of overlaptemps.\nHowever, locals are no longer automatically initialised to 0 (and never were in the case of recursion, but at least then its the same type).\nIf locals appear uninitialised, fteqcc will disable this optimisation for the affected functions, you can optionally get a warning about these locals using: #pragma warning enable F302"}, {&opt_vectorcalls, "vc", 4, FLAG_KILLSDEBUGGERS, "vectorcalls", "Where a function is called with just a vector, this causes the function call to store three floats instead of one vector. This can save a good number of pr_globals where those vectors contain many duplicate coordinates but do not match entirly."}, {&opt_classfields, "cf", 2, FLAG_KILLSDEBUGGERS, "class_fields", "Strip class field names. This will harm debugging and can result in 'gibberish' names appearing in saved games. Has no effect on engines other than FTEQW, which will not recognise these anyway."}, {&opt_stripunusedfields, "uf", 4, FLAG_KILLSDEBUGGERS, "strip_unused_fields","Strips any fields that have no references. This may result in extra warnings at load time, or disabling support for mapper-specified alpha in engines that do not provide that. FIXME: this is a little pointless until relocs are properly implemented."}, {NULL} }; #define defaultkeyword FLAG_HIDDENINGUI|FLAG_ASDEFAULT|FLAG_MIDCOMPILE #define typekeyword FLAG_HIDDENINGUI|FLAG_ASDEFAULT #define nondefaultkeyword FLAG_HIDDENINGUI|0|FLAG_MIDCOMPILE #define hideflag FLAG_HIDDENINGUI|FLAG_MIDCOMPILE #define defaultflag FLAG_ASDEFAULT|FLAG_MIDCOMPILE #define hidedefaultflag FLAG_HIDDENINGUI|FLAG_ASDEFAULT|FLAG_MIDCOMPILE //global to store useage to, flags, codename, human-readable name, help text compiler_flag_t compiler_flag[] = { //keywords {&keyword_asm, defaultkeyword, "asm", "Keyword: asm", "Disables the 'asm' keyword. Use the writeasm flag to see an example of the asm."}, {&keyword_break, defaultkeyword, "break", "Keyword: break", "Disables the 'break' keyword."}, {&keyword_case, defaultkeyword, "case", "Keyword: case", "Disables the 'case' keyword."}, {&keyword_class, defaultkeyword, "class", "Keyword: class", "Disables the 'class' keyword."}, {&keyword_accessor, defaultkeyword, "accessor", "Keyword: accessor", "Disables the 'accessor' keyword."}, {&keyword_const, defaultkeyword, "const", "Keyword: const", "Disables the 'const' keyword."}, {&keyword_continue, defaultkeyword, "continue", "Keyword: continue", "Disables the 'continue' keyword."}, {&keyword_default, defaultkeyword, "default", "Keyword: default", "Disables the 'default' keyword."}, {&keyword_entity, defaultkeyword, "entity", "Keyword: entity", "Disables the 'entity' keyword."}, {&keyword_enum, defaultkeyword, "enum", "Keyword: enum", "Disables the 'enum' keyword."}, //kinda like in c, but typedef not supported. {&keyword_enumflags, defaultkeyword, "enumflags", "Keyword: enumflags", "Disables the 'enumflags' keyword."}, //like enum, but doubles instead of adds 1. {&keyword_extern, defaultkeyword, "extern", "Keyword: extern", "Disables the 'extern' keyword. Use only on functions inside addons."}, //function is external, don't error or warn if the body was not found {&keyword_float, defaultkeyword, "float", "Keyword: float", "Disables the 'float' keyword. (Disables the float keyword without 'local' preceeding it)"}, {&keyword_for, defaultkeyword, "for", "Keyword: for", "Disables the 'for' keyword. Syntax: for(assignment; while; increment) {codeblock;}"}, {&keyword_goto, defaultkeyword, "goto", "Keyword: goto", "Disables the 'goto' keyword."}, {&keyword_int, typekeyword, "int", "Keyword: int", "Disables the 'int' keyword."}, {&keyword_integer, typekeyword, "integer", "Keyword: integer", "Disables the 'integer' keyword."}, {&keyword_double, nondefaultkeyword, "double", "Keyword: double", "Disables the 'double' keyword."}, {&keyword_long, nondefaultkeyword, "long", "Keyword: long", "Disables the 'long' keyword."}, {&keyword_short, nondefaultkeyword, "short", "Keyword: short", "Disables the 'short' keyword."}, {&keyword_char, nondefaultkeyword, "char", "Keyword: char", "Disables the 'char' keyword."}, {&keyword_signed, nondefaultkeyword, "signed", "Keyword: signed", "Disables the 'signed' keyword."}, {&keyword_unsigned, defaultkeyword, "unsigned", "Keyword: unsigned", "Disables the 'unsigned' keyword."}, {&keyword_register, nondefaultkeyword, "register", "Keyword: register", "Disables the 'register' keyword."}, {&keyword_volatile, nondefaultkeyword, "volatile", "Keyword: volatile", "Disables the 'volatile' keyword."}, {&keyword_noref, defaultkeyword, "noref", "Keyword: noref", "Disables the 'noref' keyword."}, //nowhere else references this, don't warn about it. {&keyword_unused, nondefaultkeyword, "unused", "Keyword: unused", "Disables the 'unused' keyword. 'unused' means that the variable is unused, you're aware that its unused, and you'd rather not know about all the warnings this results in."}, {&keyword_used, nondefaultkeyword, "used", "Keyword: used", "Disables the 'used' keyword. 'used' means that the variable is used even if the qcc can't see how - thus preventing it from ever being stripped."}, {&keyword_local, defaultkeyword, "local", "Keyword: local", "Disables the 'local' keyword."}, {&keyword_static, defaultkeyword, "static", "Keyword: static", "Disables the 'static' keyword. 'static' means that a variable has altered scope. On globals, the variable is visible only to the current .qc file. On locals, the variable's value does not change between calls to the function. On class variables, specifies that the field is a scoped global instead of a local. On class functions, specifies that 'this' is expected to be invalid and that the function will access any memembers via it."}, {&keyword_nonstatic, defaultkeyword, "nonstatic", "Keyword: nonstatic", "Disables the 'nonstatic' keyword. 'nonstatic' acts upon globals+functions, reverting the defaultstatic pragma on a per-variable basis. For use by people who prefer to keep their APIs explicit."}, {&keyword_ignore, nondefaultkeyword, "ignore", "Keyword: ignore", "Disables the 'ignore' keyword. 'ignore' is expected to typically be hidden behind a 'csqconly' define, and in such a context can be used to conditionally compile functions a little more gracefully. The opposite of the 'used' keyword. These variables/functions/members are ALWAYS stripped, and effectively ignored."}, {&keyword_auto, nondefaultkeyword, "auto", "Keyword: auto", "Disables the 'auto' keyword. This keyword denotes the variable as one that acts like C does, allowing locals to be passed recursively without screwing up the value. Can also be used on uninitialised globals to allocate at loadtime to reduce the size of the dat."}, {&keyword_nosave, defaultkeyword, "nosave", "Keyword: nosave", "Disables the 'nosave' keyword."}, //don't write the def to the output. {&keyword_inline, defaultkeyword, "inline", "Keyword: inline", "Disables the 'inline' keyword."}, //don't write the def to the output. {&keyword_strip, defaultkeyword, "strip", "Keyword: strip", "Disables the 'strip' keyword."}, //don't write the def to the output. {&keyword_shared, defaultkeyword, "shared", "Keyword: shared", "Disables the 'shared' keyword."}, //mark global to be copied over when progs changes (part of FTE_MULTIPROGS) {&keyword_state, nondefaultkeyword,"state", "Keyword: state", "Disables the 'state' keyword."}, {&keyword_optional, defaultkeyword,"optional", "Keyword: optional", "Disables the 'optional' keyword."}, {&keyword_inout, nondefaultkeyword,"inout", "Keyword: inout", "Disables the 'inout' keyword."}, {&keyword_string, defaultkeyword, "string", "Keyword: string", "Disables the 'string' keyword."}, {&keyword_struct, defaultkeyword, "struct", "Keyword: struct", "Disables the 'struct' keyword."}, {&keyword_switch, defaultkeyword, "switch", "Keyword: switch", "Disables the 'switch' keyword."}, {&keyword_thinktime, nondefaultkeyword,"thinktime", "Keyword: thinktime", "Disables the 'thinktime' keyword which is used in HexenC"}, {&keyword_until, nondefaultkeyword,"until", "Keyword: until", "Disables the 'until' keyword which is used in HexenC"}, {&keyword_loop, nondefaultkeyword,"loop", "Keyword: loop", "Disables the 'loop' keyword which is used in HexenC"}, {&keyword_typedef, defaultkeyword, "typedef", "Keyword: typedef", "Disables the 'typedef' keyword."}, //fixme {&keyword_union, defaultkeyword, "union", "Keyword: union", "Disables the 'union' keyword."}, //you surly know what a union is! {&keyword_var, defaultkeyword, "var", "Keyword: var", "Disables the 'var' keyword."}, {&keyword_vector, defaultkeyword, "vector", "Keyword: vector", "Disables the 'vector' keyword."}, {&keyword_wrap, defaultkeyword, "wrap", "Keyword: wrap", "Disables the 'wrap' keyword."}, {&keyword_weak, defaultkeyword, "weak", "Keyword: weak", "Disables the 'weak' keyword."}, {&keyword_accumulate, nondefaultkeyword,"accumulate", "Keyword: accumulate", "Disables the 'accumulate' keyword."}, {&keyword_using, nondefaultkeyword,"using", "Keyword: using", "Disables the 'using' keyword."}, //options {&flag_acc, 0, "acc", "Reacc support", "Reacc is a pascall like compiler. It was released before the Quake source was released. This flag has a few effects. It sorts all qc files in the current directory into alphabetical order to compile them. It also allows Reacc global/field distinctions, as well as allows | for linebreaks. Whilst case insensitivity and lax type checking are supported by reacc, they are seperate compiler flags in fteqcc."}, //reacc like behaviour of src files. {&flag_qccx, FLAG_MIDCOMPILE,"qccx", "QCCX syntax", "WARNING: This syntax makes mods inherantly engine specific.\nDo NOT use unless you know what you're doing.This is provided for compatibility only\nAny entity hacks will be unsupported in FTEQW, DP, and others, resulting in engine crashes if the code in question is executed."}, {&keywords_coexist, defaultflag, "kce", "Keywords Coexist", "If you want keywords to NOT be disabled when they a variable by the same name is defined, check here."}, // {&flag_lno, defaultflag, "lno", "Write Line Numbers", "Writes line number information. This is required for any real kind of debugging. Will be ignored if filenames were stripped."}, {&output_parms, 0, "parms", "Define offset parms", "if PARM0 PARM1 etc should be defined by the compiler. These are useful if you make use of the asm keyword for function calls, or you wish to create your own variable arguments. This is an easy way to break decompilers."}, //controls weather to define PARMx for the parms (note - this can screw over some decompilers) {&autoprototype, 0, "autoproto", "Automatic Prototyping","Causes compilation to take two passes instead of one. The first pass, only the definitions are read. The second pass actually compiles your code. This means you never have to remember to prototype functions again."}, //so you no longer need to prototype functions and things in advance. {&writeasm, 0, "wasm", "Dump Assembler", "Writes out a qc.asm which contains all your functions but in assembler. This is a great way to look for bugs in fteqcc, but can also be used to see exactly what your functions turn into, and thus how to optimise statements better."}, //spit out a qc.asm file, containing an assembler dump of the ENTIRE progs. (Doesn't include initialisation of constants) {&flag_guiannotate, FLAG_MIDCOMPILE,"annotate", "Annotate Sourcecode", "Annotate source code with assembler statements on compile (requires gui)."}, {&flag_nullemptystr, FLAG_MIDCOMPILE,"nullemptystr", "Null String Immediates", "Empty string immediates will have the raw value 0 instead of 1."}, {&flag_ifstring, FLAG_MIDCOMPILE,"ifstring", "if(string) fix", "Causes if(string) to behave identically to if(string!="") This is most useful with addons of course, but also has adverse effects with FRIK_FILE's fgets, where it becomes impossible to determin the end of the file. In such a case, you can still use asm {IF string 2;RETURN} to detect eof and leave the function."}, //correction for if(string) no-ifstring to get the standard behaviour. {&flag_iffloat, FLAG_MIDCOMPILE,"iffloat", "if(-0.0) fix","Fixes certain floating point logic."}, {&flag_ifvector, defaultflag, "ifvector", "if('0 1 0') fix","Fixes conditional vector logic."}, {&flag_vectorlogic, defaultflag, "vectorlogic", "v&&v||v fix", "Fixes conditional vector logic."}, {&flag_brokenarrays, FLAG_MIDCOMPILE,"brokenarray", "array[0] omission", "Treat references to arrays as references to the first index of said array, to replicate an old fteqcc bug."}, {&flag_rootconstructor, FLAG_MIDCOMPILE,"rootconstructor","root constructor first", "When enabled, the root constructor should be called first like in c++."}, {&flag_caseinsensitive, 0, "caseinsens", "Case insensitivity", "Causes fteqcc to become case insensitive whilst compiling names. It's generally not advised to use this as it compiles a little more slowly and provides little benefit. However, it is required for full reacc support."}, //symbols will be matched to an insensitive case if the specified case doesn't exist. This should b usable for any mod {&flag_laxcasts, FLAG_MIDCOMPILE,"lax", "Lax type checks", "Disables many errors (generating warnings instead) when function calls or operations refer to two normally incompatible types. This is required for reacc support, and can also allow certain (evil) mods to compile that were originally written for frikqcc."}, //Allow lax casting. This'll produce loadsa warnings of course. But allows compilation of certain dodgy code. {&flag_hashonly, FLAG_MIDCOMPILE,"hashonly", "Hash-only constants", "Allows use of only #constant for precompiler constants, allows certain preqcc using mods to compile"}, {&flag_macroinstrings, FLAG_MIDCOMPILE,"macroinstrings","Expand macros in strings", "Allows the use #constant inside string immediates. This is a feature of preqcc, but should otherwise probably be left disabled."}, {&opt_logicops, FLAG_MIDCOMPILE,"lo", "Logic ops", "This changes the behaviour of your code. It generates additional if operations to early-out in if statements. With this flag, the line if (0 && somefunction()) will never call the function. It can thus be considered an optimisation. However, due to the change of behaviour, it is not considered so by fteqcc. Note that due to inprecisions with floats, this flag can cause runaway loop errors within the player walk and run functions (without iffloat also enabled). This code is advised:\nplayer_stand1:\n if (self.velocity_x || self.velocity_y)\nplayer_run\n if (!(self.velocity_x || self.velocity_y))"}, {&flag_msvcstyle, FLAG_MIDCOMPILE,"msvcstyle", "MSVC-style errors", "Generates warning and error messages in a format that msvc understands, to facilitate ide integration."}, {&flag_debugmacros, FLAG_MIDCOMPILE,"debugmacros", "Verbose Macro Expansion", "Print out the contents of macros that are expanded. This can help look inside macros that are expanded and is especially handy if people are using preprocessor hacks."}, {&flag_filetimes, hideflag, "filetimes", "Check Filetimes", "Recompiles the progs only if the file times are modified."}, {&flag_fasttrackarrays, FLAG_MIDCOMPILE,"fastarrays", "fast arrays where possible", "Generates extra instructions inside array handling functions to detect engine and use extension opcodes only in supporting engines.\nAdds a global which is set by the engine if the engine supports the extra opcodes. Note that this applies to all arrays or none."}, {&flag_assume_integer, FLAG_MIDCOMPILE,"assumeint", "Assume Integers", "Numerical constants are assumed to be integers, instead of floats."}, {&pr_subscopedlocals, FLAG_MIDCOMPILE,"subscope", "Subscoped Locals", "Restrict the scope of locals to the block they are actually defined within, as in C."}, {&verbose, FLAG_MIDCOMPILE,"verbose", "Verbose", "Lots of extra compiler messages."}, {&flag_typeexplicit, FLAG_MIDCOMPILE,"typeexplicit", "Explicit types", "All type conversions must be explicit or directly supported by instruction set."}, {&flag_boundchecks, defaultflag, "boundchecks", "Enforce Bound Checks", "Enforce array index checks to avoid accessing arrays out of bounds. This can be disabled for a speedup (the qcvm will still verify that the access is within the qcvm's memory, but it can't verify that its within the intended array)."}, {&flag_attributes, hideflag, "attributes", "[[attributes]]", "WARNING: This syntax conflicts with vector constructors."}, {&flag_assumevar, hideflag, "assumevar", "explicit consts", "Initialised globals will be considered non-const by default."}, {&flag_dblstarexp, hideflag, "ssp", "** exponent", "Treat ** as an operator for exponents, instead of multiplying by a dereferenced pointer."}, {&flag_cpriority, hideflag, "cpriority", "C Operator Priority", "QC treats !a&&b as equivelent to !(a&&b). When this is set, behaviour will be (!a)&&b as in C. Other operators are also affected in similar ways."}, {&flag_assume_double, hideflag, "assumedouble", "Assume Doubles", "Floating point immediates will be treated as doubles, for C compat."}, {&flag_qcfuncs, hidedefaultflag,"qcfuncs", "Parse QC-style funcs", "Recognise void() as a function type. Required for QC compat."}, {&flag_allowuninit, hideflag, "allowuninit", "Uninitialised Locals", "Permit optimisations that may result in locals being uninitialised. This may allow for greater reductions in temps."}, {&flag_nopragmafileline,FLAG_MIDCOMPILE,"nofileline", "Ignore #pragma file", "Ignores #pragma file(foo) and #pragma line(foo), so that errors and symbols reflect the actual lines, instead of the original source."}, // {&flag_lno, hidedefaultflag,"lno", "Gen Debugging Info", "Writes debugging info."}, {&flag_utf8strings, FLAG_MIDCOMPILE,"utf8", "Unicode", "String immediates will use utf-8 encoding, instead of quake's encoding."}, {&flag_reciprocalmaths, FLAG_MIDCOMPILE,"reciprocal-math","Reciprocal Maths", "Optimise x/const as x*(1/const)."}, {&flag_ILP32, FLAG_MIDCOMPILE,"ILP32", "ILP32 Data Model", "Restricts the size of `long` to 32bits, consistent with pointers."}, {&flag_undefwordsize, hideflag, "undefwordsize","Undefined Word Size", "Do not make assumptions about pointer types and word sizes. Block functionality that depends upon specific word sizes. Changed as part of target."}, {&flag_pointerrelocs, hideflag, "pointerrelocs","Initialised Pointers", "Allow pointer types to be preinitialised at compile time, both at global scope and optimising local scope a little."}, {&flag_noreflection, FLAG_MIDCOMPILE,"omitinternals","Omit Reflection Info", "Keeps internal symbols private (equivelent to unix's hidden visibility). This has the effect of reducing filesize, thwarting debuggers, and breaking saved games. This allows you to use arrays without massively bloating the size of your progs.\nWARNING: The bit about breaking saved games was NOT a joke, but does not apply to menuqc or csqc. It also interferes with FTE_MULTIPROGS."}, {&flag_embedsrc, FLAG_MIDCOMPILE,"embedsrc", "Embed Sources", "Write the sourcecode into the output file. The resulting .dat can be opened as a standard zip archive (or by fteqccgui).\nGood for GPL compliance!"}, {&flag_dumpfilenames, FLAG_MIDCOMPILE,"dumpfilenames","Write a .lst file", "Writes a .lst file which contains a list of all file names that we can detect from the qc. This file list can then be passed into external compression tools."}, {&flag_dumpfields, FLAG_MIDCOMPILE,"dumpfields", "Write a .fld file", "Writes a .fld file that shows which fields are defined, along with their offsets etc, for weird debugging."}, {&flag_dumpsymbols, FLAG_MIDCOMPILE,"dumpsymbols", "Write a .sym file", "Writes a .sym file alongside the dat which contains a list of all global symbols defined in the code (before stripping)"}, {&flag_dumpautocvars, FLAG_MIDCOMPILE,"dumpautocvars","Write a .cfg file", "Writes a .cfg file that contains a default value for each autocvar listed in the code"}, {&flag_dumplocalisation,FLAG_MIDCOMPILE,"dumplocalisation","Write a .pot file", "Writes a .po template file from your _("") strings that can be edited (with eg gettext's tools) for translations, resulting in eg csprogs.en_US.po vs csprogs.en.po and other various other dialects vs languages."}, {&flag_dumptags, FLAG_MIDCOMPILE,"dumptags", "Write vi's tags file", "Writes a .tags file for text editors compatible with vi to locate symbols by name. This file is unsorted and per-module, so you will need to 'cat ../*.tags|LC_COLLATE=C sort>tags' for it to be used."}, {&flag_dumptags, FLAG_MIDCOMPILE|FLAG_HIDDENINGUI,"tags","aka dumptags", "See dumptags"}, {&flag_dumpopcodes, FLAG_MIDCOMPILE,"dumpopcodes", "Write a .op.inc file with the supported opcodes in C syntax", "Writes a .inc file for game engines to include to have opcodes defined. Executes at the end of the compile, and thus respects opcodes added via #pragma."}, {NULL} }; static const char *QCC_VersionString(void) { #if defined(SVNREVISION) && defined(SVNDATE) return "FTEQCC: " STRINGIFY(SVNREVISION) " (" STRINGIFY(SVNDATE) ")"; #elif defined(SVNREVISION) return "FTEQCC: " STRINGIFY(SVNREVISION) " (" __DATE__")"; #else return "FTEQCC: " __DATE__; #endif } /* ================= BspModels Runs qbsp and light on all of the models with a .bsp extension ================= */ static void QCC_BspModels (void) { /* int p; char *gamedir; int i; char *m; char cmd[1024]; char name[256]; size_t result; p = QCC_CheckParm ("-bspmodels"); if (!p) return; if (p == myargc-1) QCC_Error (ERR_BADPARMS, "-bspmodels must preceed a game directory"); gamedir = myargv[p+1]; for (i=0 ; iprev) { if (t->len >= len) if (!strcmp(t->ofs + t->len - len, str)) { optres_noduplicatestrings += len; return (t->ofs + t->len - len)-strings; } } t = qccHunkAlloc(sizeof(*t)); t->prev = stringtablist[key]; stringtablist[key] = t; t->ofs = strings+strofs; t->len = len; #elif 1 //bruteforce char *s; for (s = strings; s < strings+strofs; s++) if (!strcmp(s, str)) { optres_noduplicatestrings += strlen(str); return s-strings; } #endif } old = strofs; len = strlen(str)+1; if ( (strofs + len) > MAX_STRINGS) QCC_Error(ERR_INTERNAL, "QCC_CopyString: -max_strings %i limit exceeded\n", MAX_STRINGS); memcpy (strings+strofs, str, len); strofs += len; return old; } int QCC_CopyStringLength (const char *str, size_t length) { int old; if (!str) return 0; if (!*str && length == 1) return !flag_nullemptystr; old = strofs; if ( (strofs + length) > MAX_STRINGS) QCC_Error(ERR_INTERNAL, "QCC_CopyString: -max_strings %i limit exceeded\n", MAX_STRINGS); memcpy (strings+strofs, str, length); strings[strofs+length] = 0; strofs += length+1; return old; } /*static int QCC_CopyDupBackString (const char *str) { size_t length; int old; char *s; for (s = strings+strofs-1; s>strings ; s--) if (!strcmp(s, str)) return s-strings; old = strofs; length = strlen(str)+1; if ( (strofs + length) > MAX_STRINGS) QCC_Error(ERR_INTERNAL, "QCC_CopyString: stringtable size limit exceeded\n"); strcpy (strings+strofs, str); strofs += length; return old; } static void QCC_PrintStrings (void) { int i, l, j; for (i=0 ; iPrintf ("%5i : ",i); for (j=0 ; jPrintf ("\n"); } } void QCC_PrintFunctions (void) { int i,j; QCC_dfunction_t *d; for (i=0 ; iPrintf ("%s : %s : %i %i (", strings + d->s_file, strings + d->s_name, d->first_statement, d->parm_start); for (j=0 ; jnumparms ; j++) externs->Printf ("%i ",d->parm_size[j]); externs->Printf (")\n"); } }*/ static void QCC_SortFields (void) { int i, j; QCC_ddef32_t t; //insertion sort, of sorts. //(qsort doesn't guarentee ordering) for (i = 1; i < numfielddefs; i++) { if (fields[i].ofs < fields[i-1].ofs) { //this entry is out of order for (j = i-1; j > 0; j--) { if (fields[j].ofs <= fields[i].ofs) { //okay, so we're putting it after this element. j++; break; } } t = fields[i]; memmove(&fields[j+1], &fields[j], (i-j)*sizeof(t)); fields[j] = t; } } } static void QCC_DumpFields (const char *outputname) { char line[1024]; extern char *basictypenames[]; int i; QCC_ddef_t *d; int h; snprintf(line, sizeof(line), "%s.fld", outputname); h = SafeOpenWrite (line, 2*1024*1024); if (h >= 0) { for (i=0 ; iofs, basictypenames[d->type], strings + d->s_name); SafeWrite(h, line, strlen(line)); } SafeClose(h); } } static void QCC_DumpSymbolNames (const char *outputname) { char line[1024]; QCC_def_t *def; int h; snprintf(line, sizeof(line), "%s.sym", outputname); h = SafeOpenWrite (line, 2*1024*1024); if (h >= 0) { for (def = pr.def_head.next ; def ; def = def->next) { if ((def->scope && !def->isstatic) || !strcmp(def->name, "IMMEDIATE")) continue; if (def->symbolheader != def /*&& def->symbolheader->type != def->type*/) continue; //try to exclude vector components. snprintf(line, sizeof(line), "%s %10i %s\n", def->initialized?"data":"bss ", def->symbolsize*(int)sizeof(float), def->name); SafeWrite(h, line, strlen(line)); } SafeClose(h); } } /*static void QCC_DumpSymbolInfo (const char *outputname) { char line[1024]; char tname[512]; QCC_def_t *def; int h; snprintf(line, sizeof(line), "%s.sym", outputname); h = SafeOpenWrite (line, 2*1024*1024); if (h >= 0) { for (def = pr.def_head.next ; def ; def = def->next) { // if ((def->scope && !def->isstatic) || !strcmp(def->name, "IMMEDIATE")) // continue; // if (def->symbolheader != def && def->symbolheader->type != def->type) // continue; //try to exclude vector components. if (def->arraysize) snprintf(line, sizeof(line), "%s%i: %s[%i] %s = ", def->used?"":"(unused)", def->ofs, TypeName(def->type, tname, sizeof(tname)), def->arraysize, def->name); else snprintf(line, sizeof(line), "%s%i: %s %s = ", def->used?"":"(unused)", def->ofs, TypeName(def->type, tname, sizeof(tname)), def->name); SafeWrite(h, line, strlen(line)); switch(def->type->type) { case ev_vector: snprintf(line, sizeof(line), "%g %g %g\n", def->symboldata[0]._float, def->symboldata[1]._float, def->symboldata[2]._float); break; case ev_float: snprintf(line, sizeof(line), "%g\n", def->symboldata[0]._float); break; case ev_double: snprintf(line, sizeof(line), "%g\n", def->symboldata[0]._float); break; case ev_string: snprintf(line, sizeof(line), "%+i, %s\n", def->symboldata[0].string, QCC_PR_String(strings + def->symboldata[0].string)); break; case ev_function: snprintf(line, sizeof(line), "%+i, %s\n", def->symboldata[0]._int, functions[def->symboldata[0]._int].name); break; case ev_int64: case ev_uint64: snprintf(line, sizeof(line), "%i\n", def->symboldata[0]._int); break; default: snprintf(line, sizeof(line), "%i\n", def->symboldata[0]._int); break; } SafeWrite(h, line, strlen(line)); } SafeClose(h); } }*/ /* static void QCC_PrintGlobals (void) { int i; QCC_ddef_t *d; externs->Printf("Globals Listing:\n"); for (i=0 ; iPrintf ("%5i : (%i) %s\n", d->ofs, d->type, strings + d->s_name); } }*/ static void QCC_DumpAutoCvars (const char *outputname) { char line[1024]; int i, h; QCC_ddef_t *d; char *n; snprintf(line, sizeof(line), "%s.cfg", outputname); h = SafeOpenWrite (line, 2*1024*1024); if (h >= 0) { for (i=0 ; is_name; if (!strncmp(n, "autocvar_", 9)) { char *desc; const QCC_eval_t *val = (const QCC_eval_t*)&qcc_pr_globals[d->ofs]; QCC_def_t *def = QCC_PR_GetDef(NULL, n, NULL, false, 0, 0); n += 9; if (!def) continue; //erk? if (def->comment) desc = def->comment; else desc = NULL; switch(d->type & ~(DEF_SAVEGLOBAL|DEF_SHARED)) { case ev_float: snprintf(line, sizeof(line), "set %s\t%g%s%s\n", n, val->_float, desc?"\t//":"", desc?desc:""); break; case ev_double: snprintf(line, sizeof(line), "set %s\t%g%s%s\n", n, val->_double, desc?"\t//":"", desc?desc:""); break; case ev_vector: snprintf(line, sizeof(line), "set %s\t\"%g %g %g\"%s%s\n", n, val->vector[0], val->vector[1], val->vector[2], desc?"\t//":"", desc?desc:""); break; case ev_integer: snprintf(line, sizeof(line), "set %s\t%"pPRIi"%s%s\n", n, val->_int, desc?"\t//":"", desc?desc:""); break; case ev_uint: snprintf(line, sizeof(line), "set %s\t%"pPRIu"%s%s\n", n, val->_uint, desc?"\t//":"", desc?desc:""); break; case ev_int64: snprintf(line, sizeof(line), "set %s\t%"pPRIi64"%s%s\n", n, val->i64, desc?"\t//":"", desc?desc:""); break; case ev_uint64: snprintf(line, sizeof(line), "set %s\t%"pPRIu64"%s%s\n", n, val->u64, desc?"\t//":"", desc?desc:""); break; case ev_string: snprintf(line, sizeof(line), "set %s\t\"%s\"%s%s\n", n, strings + val->_int, desc?"\t//":"", desc?desc:""); break; default: snprintf(line, sizeof(line), "//set %s\t ?%s%s\n", n, desc?"\t//":"", desc?desc:""); break; } SafeWrite(h, line, strlen(line)); } } } } static void QCC_DumpLocalisation (const char *outputname) { char line[65536]; int h, o; QCC_def_t *def; char *n; snprintf(line, sizeof(line), "%s.pot", outputname); h = SafeOpenWrite (line, 2*1024*1024); if (h >= 0) { for (def = pr.def_head.next ; def ; def = def->next) { if (!strncmp(def->name, "dotranslate_", 12)) { const QCC_eval_t *val = (const QCC_eval_t*)def->symboldata; if (def->type->type != ev_string) continue; if (def->comment) { n = def->comment; snprintf(line, sizeof(line), "#. "); for (o = strlen(line); *n && o < countof(line)-10; n++) { if (*n == '\n') { if (n[1]) { line[o++] = '\n'; line[o++] = '#'; line[o++] = '.'; line[o++] = ' '; continue; } else line[++o] = 'n'; } else if (*n == '\\') line[++o] = '\\'; else if (*n == '\"') line[++o] = '\"'; else if (*n == '\n') line[++o] = 'n'; else if (*n == '\r') line[++o] = 'r'; else if (*n == '\t') line[++o] = 't'; else { //hopefully the programmer used utf-8... line[o++] = *n; continue; } line[o++-1] = '\\'; } line[o++] = '\n'; line[o++] = 0; SafeWrite(h, line, strlen(line)); } if (def->filen) { //strip any extra macro info there... char *c = strchr(def->filen, ':'); if (c && (c[1] < '0' || c[1] > '9')) //don't get fooled by windows paths... c = strchr(c+1, ':'); if (c) { *c = 0; snprintf(line, sizeof(line), "#: %s:%i\n", def->filen, def->s_line); *c = ':'; } else snprintf(line, sizeof(line), "#: %s:%i\n", def->filen, def->s_line); SafeWrite(h, line, strlen(line)); } n = strings + val->_int; snprintf(line, sizeof(line), "msgid \""); for (o = strlen(line); *n && o < countof(line)-5; n++) { if (*n == '\n' && n[1] && o < countof(line)-10) { //split multi-line stuff onto multiple lines, becase we can. line[o++] = '\\'; line[o++] = 'n'; line[o++] = '\"'; line[o++] = '\n'; line[o++] = '\"'; continue; } else if (*n == '\\') line[++o] = '\\'; else if (*n == '\"') line[++o] = '\"'; else if (*n == '\n') line[++o] = 'n'; else if (*n == '\r') line[++o] = 'r'; else if (*n == '\t') line[++o] = 't'; else { //hopefully the programmer used utf-8... line[o++] = *n; continue; } line[o++-1] = '\\'; } line[o++] = '\"'; line[o++] = '\n'; line[o++] = 0; SafeWrite(h, line, strlen(line)); snprintf(line, sizeof(line), "msgstr \"\"\n\n"); SafeWrite(h, line, strlen(line)); } } } SafeClose(h); } static void QCC_DumpFiles (const char *outputname) { struct { precache_t *list; int count; } precaches[] = { { precache_sound, numsounds}, { precache_texture, numtextures}, { precache_model, nummodels}, { precache_file, numfiles} }; int g, i, b; pbool header; externs->Printf("\nFile lists:\n"); for (b = 0; b < 64; b++) { for (header = false, g = 0; g < sizeof(precaches)/sizeof(precaches[0]); g++) { for (i = 0; i < precaches[g].count; i++) { if (precaches[g].list[i].block == b) { if (*precaches[g].list[i].name == '*') continue; //*-prefixed models are not real, and shouldn't be included in file lists. if (!header) { externs->Printf("pak%i:\n", b-1); header=true; } externs->Printf("%s\n", precaches[g].list[i].name); } } } if (header) externs->Printf("\n"); } } void QCC_DumpPreProcTags(void *ctx, void *data) { char line[2048]; int h = *(int*)ctx; CompilerConstant_t *def = data; if (def->fromfile) { snprintf(line, sizeof(line), "%s\t%s\t%i;\"\td\n", def->name, def->fromfile, def->fromline); SafeWrite(h, line, strlen(line)); } } static void QCC_DumpTags(const char *outputname) { char line[65536]; char scope[2048]; int h; QCC_def_t *def; QCC_function_t *fnc; int i; QCC_type_t *t; snprintf(line, sizeof(line), "%s.tags", outputname); h = SafeOpenWrite (line, 2*1024*1024); if (h >= 0) { Hash_Enumerate(&compconstantstable, QCC_DumpPreProcTags, &h); for (i = 0; i < numtypeinfos; i++) { t = &qcc_typeinfo[i]; if (!t->line || !t->filen) continue; //predefined type, not from any file. if (!*t->name || strchr(t->name, '<')) continue; //anonymous stuff happens. if (t->typedefed) snprintf(line, sizeof(line), "%s\t%s\t%i;\"\tt\n", t->name, t->filen, t->line); else if (t->type == ev_struct) snprintf(line, sizeof(line), "%s\t%s\t%i;\"\ts\n", t->name, t->filen, t->line); else if (t->type == ev_union) snprintf(line, sizeof(line), "%s\t%s\t%i;\"\tu\n", t->name, t->filen, t->line); else if (t->type == ev_enum) snprintf(line, sizeof(line), "%s\t%s\t%i;\"\tg\n", t->name, t->filen, t->line); else if ((t->type == ev_entity||t->type == ev_accessor) && t->parentclass) snprintf(line, sizeof(line), "%s\t%s\t%i;\"\tc\n", t->name, t->filen, t->line); else continue; SafeWrite(h, line, strlen(line)); } for (def = pr.def_head.next; def; def = def->next) { //immediates are uninteresting... if (!strcmp(def->name, "IMMEDIATE")) continue; if (strchr(def->name, '.') || strchr(def->name, '[') || strchr(def->name, '*')) continue; //weird symbols, listed for savedgames only... if (def->scope && !strchr(def->scope->name, ':')) snprintf(scope, sizeof(scope), "\tfunction:%s\n", def->scope->name); else if (def->isstatic) snprintf(scope, sizeof(scope), "\file:\n"); //local scope else *scope = 0; if (def->type->type == ev_function && def->constant && !def->arraysize) { int fnum = def->symboldata[0].function; if (fnum > 0 && fnum < numfunctions) { fnc = &functions[fnum]; if (fnc->code>=0 && fnc->filen) { snprintf(line, sizeof(line), "%s\t%s\t%i;\"\tf%s\n", def->name, fnc->filen, fnc->line, scope); SafeWrite(h, line, strlen(line)); } } if (def->filen) { snprintf(line, sizeof(line), "%s\t%s\t%i;\"\tp%s\n", def->name, def->filen, def->s_line, scope); SafeWrite(h, line, strlen(line)); } } else if (def->filen) { snprintf(line, sizeof(line), "%s\t%s\t%i;\"\tv%s\n", def->name, def->filen, def->s_line, scope); SafeWrite(h, line, strlen(line)); } } } SafeClose(h); } static void QCC_DumpOpcodes (const char *outputname) { char line[65536]; int h; snprintf(line, sizeof(line), "%s.op.inc", outputname); h = SafeOpenWrite (line, 2*1024*1024); if (h >= 0) { snprintf(line, sizeof(line), "enum qcop_e {\n"); SafeWrite(h, line, strlen(line)); for (enum qcop_e opcode = 0; opcode < OP_NUMREALOPS; ++opcode) { snprintf(line, sizeof(line), "\t%sOP_%s = %d,\n", QCC_OPCodeValid(&pr_opcodes[opcode]) ? "" : "// ", pr_opcodes[opcode].opname, (int) opcode); SafeWrite(h, line, strlen(line)); } snprintf(line, sizeof(line), "\tOP_NUMREALOPS = %d\n", (int) OP_NUMREALOPS); SafeWrite(h, line, strlen(line)); snprintf(line, sizeof(line), "};\n"); SafeWrite(h, line, strlen(line)); } SafeClose(h); } int WriteSourceFiles(qcc_cachedsourcefile_t *filelist, int h, pbool sourceaswell, pbool legacyembed) { //helpers to deal with misaligned data. writes little-endian. #define misbyte(ptr,ofs,data) ((unsigned char*)(ptr))[ofs] = (data)&0xff; #define misshort(ptr,ofs,data) misbyte((ptr),(ofs),(data));misbyte((ptr),(ofs)+1,(data)>>8); #define misint(ptr,ofs,data) misshort((ptr),(ofs),(data));misshort((ptr),(ofs)+2,(data)>>16); includeddatafile_t *idf; qcc_cachedsourcefile_t *f; int num=0; int ofs; pbool zipembed = true; int startofs; sourceaswell |= flag_embedsrc; for (f = filelist,num=0; f ; f=f->next) { if (f->type == FT_CODE && !sourceaswell) continue; num++; } if (!num) { if (zipembed) { //zips are found by scanning. so make sure something can be found so noone will erroneously find something char centralheader[22]; int centraldirofs = SafeSeek(h, 0, SEEK_CUR); misint (centralheader, 0, 0x06054b50); misshort(centralheader, 4, 0); //this disk number misshort(centralheader, 6, 0); //centraldir first disk misshort(centralheader, 8, 0); //centraldir entries misshort(centralheader, 10, 0); //total centraldir entries misint (centralheader, 12, 0); //centraldir size misint (centralheader, 16, centraldirofs); //centraldir offset misshort(centralheader, 20, 0); //comment length SafeWrite(h, centralheader, 22); } return 0; } startofs = SafeSeek(h, 0, SEEK_CUR); idf = qccHunkAlloc(sizeof(includeddatafile_t)*num); for (f = filelist,num=0; f ; f=f->next) { if (f->type == FT_CODE && !sourceaswell) continue; if (zipembed) { size_t end; char header[32]; size_t fnamelen = strlen(f->filename); f->zcrc = QC_encodecrc(f->size, f->file); misint (header, 0, 0x04034b50); misshort(header, 4, 0);//minver misshort(header, 6, 0);//general purpose flags misshort(header, 8, 0);//compression method, 0=store, 8=deflate misshort(header, 10, 0);//lastmodfiletime misshort(header, 12, 0);//lastmodfiledate misint (header, 14, f->zcrc);//crc32 misint (header, 18, f->size);//compressed size misint (header, 22, f->size);//uncompressed size misshort(header, 26, fnamelen);//filename length misshort(header, 28, 0);//extradata length f->zhdrofs = SafeSeek(h, 0, SEEK_CUR); SafeWrite(h, header, 30); SafeWrite(h, f->filename, fnamelen); strcpy(idf[num].filename, f->filename); idf[num].size = f->size; idf[num].compmethod = 8; //must be 0(raw) or 8(raw deflate) for zips to work idf[num].ofs = SafeSeek(h, 0, SEEK_CUR); if (idf[num].compmethod==0) SafeWrite(h, f->file, f->size); else { idf[num].compsize = QC_encode(progfuncs, f->size, idf[num].compmethod, f->file, h); misshort(header, 8, idf[num].compmethod);//compression method, 0=store, 8=deflate misint (header, 18, idf[num].compsize); end = SafeSeek(h, 0, SEEK_CUR); SafeSeek(h, f->zhdrofs, SEEK_SET); SafeWrite(h, header, 30); SafeSeek(h, end, SEEK_SET); } } else { if (strlen(f->filename) >= sizeof(idf[num].filename)) continue; strcpy(idf[num].filename, f->filename); idf[num].size = f->size; #ifdef AVAIL_ZLIB idf[num].compmethod = 2; #else idf[num].compmethod = 0; #endif idf[num].ofs = SafeSeek(h, 0, SEEK_CUR); idf[num].compsize = QC_encode(progfuncs, f->size, idf[num].compmethod, f->file, h); } num++; } if (zipembed) { char centralheader[46]; int centraldirsize; ofs = SafeSeek(h, 0, SEEK_CUR); for (f = filelist,num=0; f ; f=f->next) { size_t fnamelen; if (f->type == FT_CODE && !sourceaswell) continue; fnamelen = strlen(f->filename); misint (centralheader, 0, 0x02014b50); misshort(centralheader, 4, 0);//ourver misshort(centralheader, 6, 0);//minver misshort(centralheader, 8, 0);//general purpose flags misshort(centralheader, 10, idf[num].compmethod);//compression method, 0=store, 8=deflate misshort(centralheader, 12, 0);//lastmodfiletime misshort(centralheader, 14, 0);//lastmodfiledate misint (centralheader, 16, f->zcrc);//crc32 misint (centralheader, 20, idf[num].compsize);//compressed size misint (centralheader, 24, f->size);//uncompressed size misshort(centralheader, 28, fnamelen);//filename length misshort(centralheader, 30, 0);//extradata length misshort(centralheader, 32, 0);//comment length misshort(centralheader, 34, 0);//first disk number misshort(centralheader, 36, 0);//internal file attribs misint (centralheader, 38, 0);//external file attribs misint (centralheader, 42, f->zhdrofs);//local header offset SafeWrite(h, centralheader, 46); SafeWrite(h, f->filename, fnamelen); num++; } centraldirsize = SafeSeek(h, 0, SEEK_CUR)-ofs; misint (centralheader, 0, 0x06054b50); misshort(centralheader, 4, 0); //this disk number misshort(centralheader, 6, 0); //centraldir first disk misshort(centralheader, 8, num); //centraldir entries misshort(centralheader, 10, num); //total centraldir entries misint (centralheader, 12, centraldirsize); //centraldir size misint (centralheader, 16, ofs); //centraldir offset misshort(centralheader, 20, 0); //comment length SafeWrite(h, centralheader, 22); ofs = 0; } else if (legacyembed) { ofs = SafeSeek(h, 0, SEEK_CUR); SafeWrite(h, &num, sizeof(int)); SafeWrite(h, idf, sizeof(includeddatafile_t)*num); } else ofs = 0; externs->Printf("Embedded files take %u bytes\n", SafeSeek(h, 0, SEEK_CUR) - startofs); return ofs; } static void QCC_InitData (void) { static char parmname[12][MAX_PARMS]; int i; qcc_sourcefile = NULL; memset(stringtablist, 0, sizeof(stringtablist)); numstatements = 1; //first statement should be an OP_DONE, matching the null function(ish), in case it somehow doesn't get caught by the vm. strofs = 2; //null, empty, *other stuff* numfunctions = 1; //first function is a null function. numglobaldefs = 1; //first globaldef is a null def. just because. doesn't include parms+ret. is there any point to this? numfielddefs = 0; fields[numfielddefs].type = ev_void; fields[numfielddefs].s_name = 0; //should map to null fields[numfielddefs].ofs = 0; numfielddefs++; //FIXME: do we actually need a null field? is there any point to this at all? memset(&def_ret, 0, sizeof(def_ret)); def_ret.ofs = OFS_RETURN; def_ret.name = "return"; def_ret.constant = false; def_ret.type = NULL; def_ret.symbolheader = &def_ret; def_ret.symbolsize = type_size[ev_vector]; for (i=0 ; inext) { if (!d->used || !d->constant || d->symbolheader != d) continue; if (d->type->type == ev_function && !d->scope)// function parms are ok { if (d->isextern) { SafeWrite(handle, d->name, strlen(d->name)+1); ret++; } if (d->initialized == 0) { QCC_PR_Warning(ERR_NOFUNC, d->filen, d->s_line, "function %s has no body", d->name); QCC_PR_ParsePrintDef(ERR_NOFUNC, d); } } } return ret; } static void QCC_DetermineNeededSymbols(QCC_def_t *endsyssym) { QCC_def_t *sym = pr.def_head.next; size_t i; //make sure system defs are not hurt by this. if (endsyssym) { for (; sym; sym = sym->next) { if (sym->unused && !sym->initialized) sym->initialized = 1; //even if its not. sym->used = true; sym->referenced = true; //silence warnings about unreferenced things that can't be stripped if (sym == endsyssym) break; } } //non-system fields should maybe be present too. if (!opt_stripunusedfields) { for (; sym; sym = sym->next) { if (sym->constant && sym->type->type == ev_field) { sym->used = true; sym->referenced = true; } } } for (i=0 ; isymbolheader) sym->used = true; if ((sym = statements[i].b.sym)) if (sym->symbolheader) sym->used = true; if ((sym = statements[i].c.sym)) if (sym->symbolheader) sym->used = true; } } //allocates final space for the def, making it a true def static void QCC_FinaliseDef(QCC_def_t *def) { //#define DEBUG_DUMP_GLOBALMAP #if defined(DEBUG_DUMP) || defined(DEBUG_DUMP_GLOBALMAP) int ssize = def->symbolsize; const QCC_eval_t *v; QCC_sref_t sr; #endif if (def->symboldata == qcc_pr_globals + def->ofs) { #ifdef DEBUG_DUMP_GLOBALMAP externs->Printf("Prefinalised %s @ %i+%i\n", def->name, def->ofs, ssize); #endif return; //was already finalised. } if (def->symbolheader != def) { //finalise the parent/root symbol first. def->symbolheader->used |= def->used; QCC_FinaliseDef(def->symbolheader); def->referenced = true; } if (def->symbolheader == def && def->deftail) { //for head symbols, we go through and touch all of their children QCC_def_t *prev, *sub; if (def->used && !def->referenced) { pbool ignoreone = true; //touch all but one child for (prev = def, sub = prev->next; prev != def->deftail; sub = (prev=sub)->next) { if (sub->referenced) def->referenced=true; //if one child is referenced, the composite is referenced else if (!sub->referenced && ignoreone) ignoreone = false; //this is the one we're going to warn about } // if (def->referenced) //no child defs were referenced at all. if we're going to be warning about this then at least mute warnings for any other fields // for (prev = def, sub = prev->next; prev != def->deftail; sub = (prev=sub)->next) // sub->referenced = true; } else if (def->used) { //touch children to silence annoying warnings. for (prev = def, sub = prev->next; prev != def->deftail; sub = (prev=sub)->next) sub->referenced |= true; } #ifdef TODO_READWRITETRACK if (!def->read) { pbool ignoreone = true; //touch all but one child for (prev = def, sub = prev->next; prev != def->deftail; sub = (prev=sub)->next) { if (sub->read) def->read=true; //if one child is referenced, the composite is referenced else if (!sub->read && ignoreone) ignoreone = false; //this is the one we're going to warn about else sub->read |= def->read; } if (!def->read) //no child defs were referenced at all. if we're going to be warning about this then at least mute warnings for any other fields for (prev = def, sub = prev->next; prev != def->deftail; sub = (prev=sub)->next) sub->read = true; } else { //touch children for (prev = def, sub = prev->next; prev != def->deftail; sub = (prev=sub)->next) sub->read |= def->read; } if (!def->written) { pbool ignoreone = true; //touch all but one child for (prev = def, sub = prev->next; prev != def->deftail; sub = (prev=sub)->next) { if (sub->written) def->written=true; //if one child is referenced, the composite is referenced else if (!sub->written && ignoreone) ignoreone = false; //this is the one we're going to warn about else sub->written |= def->written; } if (!def->written) //no child defs were referenced at all. if we're going to be warning about this then at least mute warnings for any other fields for (prev = def, sub = prev->next; prev != def->deftail; sub = (prev=sub)->next) sub->written = true; } else { //touch children for (prev = def, sub = prev->next; prev != def->deftail; sub = (prev=sub)->next) sub->written |= def->written; } #endif } // else if (def->symbolheader) //if a child symbol is referenced, mark the entire parent as referenced too. this avoids vec_x+vec_y with no vec or vec_z from generating warnings about vec being unreferenced // def->symbolheader->referenced |= def->referenced; if (!def->symbolheader->used) { if (def->symboldata != qcc_pr_globals+def->ofs && def->symbolheader != def && def->symbolheader->symboldata == qcc_pr_globals + def->symbolheader->ofs) { def->ofs += def->symbolheader->ofs; def->symboldata = qcc_pr_globals + def->ofs; } if (verbose >= VERBOSE_DEBUG) externs->Printf("not needed: %s\n", def->name); return; } else if (def->symbolheader != def && def->symbolheader->symboldata == qcc_pr_globals + def->symbolheader->ofs) { def->ofs += def->symbolheader->ofs; } else { if (def->ofs) { if (def->symbolheader == def) QCC_Error(ERR_INTERNAL, "root symbol %s has an offset", def->name); else def->ofs = 0; } if (def->arraylengthprefix) { //for hexen2 arrays, we need to emit a length first if (numpr_globals+1+def->symbolsize >= MAX_REGS) { if (!opt_overlaptemps || !opt_locals_overlapping) QCC_Error(ERR_TOOMANYGLOBALS, "numpr_globals exceeded MAX_REGS - you'll need to use more optimisations"); else QCC_Error(ERR_TOOMANYGLOBALS, "numpr_globals exceeded MAX_REGS of %u. Increase with eg: -max_regs %u", MAX_REGS, MAX_REGS*2); } if (def->type->type == ev_vector) ((int *)qcc_pr_globals)[numpr_globals] = def->arraysize-1; else ((int *)qcc_pr_globals)[numpr_globals] = (def->arraysize*def->type->size)-1; //using float arrays for structs. numpr_globals+=1; } else { if (numpr_globals+def->symbolsize >= MAX_REGS) { if (!opt_overlaptemps || !opt_locals_overlapping) QCC_Error(ERR_TOOMANYGLOBALS, "numpr_globals exceeded MAX_REGS - you'll need to use more optimisations"); else QCC_Error(ERR_TOOMANYGLOBALS, "numpr_globals exceeded MAX_REGS of %u. Increase with eg: -max_regs %u", MAX_REGS, MAX_REGS*2); } } def->ofs += numpr_globals; numpr_globals += def->symbolsize; if (def->symboldata) memcpy(qcc_pr_globals+def->ofs, def->symboldata, def->symbolsize*sizeof(float)); else memset(qcc_pr_globals+def->ofs, 0, def->symbolsize*sizeof(float)); } def->symboldata = qcc_pr_globals + def->ofs; def->symbolsize = numpr_globals - def->ofs; if (def->reloc) { def->reloc->used = true; QCC_FinaliseDef(def->reloc); if (def->type->type == ev_function/*misordered inits/copies*/ || def->type->type == ev_integer/*dp-style global index*/ || def->type->type == ev_string/*immediates...*/) { //printf("func Reloc %s %s@%i==%x -> %s@%i==%x==%s\n", def->symbolheader->name, def->name,def->ofs, def->symboldata->_int, def->reloc->name,def->reloc->ofs, def->reloc->symboldata->_int, functions[def->reloc->symboldata->_int].name); def->symboldata->_int += def->reloc->symboldata->_int; } else if (def->type->type == ev_pointer/*signal to the engine to fix up the offset*/) { //printf("Reloc %s %s@%i==%x -> %s@%i==%x\n", def->symbolheader->name, def->name,def->ofs, def->symboldata->_int, def->reloc->name,def->reloc->ofs, def->symboldata->_int+def->reloc->ofs*VMWORDSIZE); if (def->symboldata->_int & 0x80000000) QCC_PR_ParseWarning(0, "dupe reloc, %s", def->type->name); else { if (flag_undefwordsize) QCC_PR_ParseWarning(ERR_BADEXTENSION, "pointer relocs are disabled for this target."); def->symboldata->_int += def->reloc->ofs*VMWORDSIZE; def->symboldata->_int |= 0x80000000; //we're using this as a hint to the engine to let it know that there's no dupes. } } else QCC_Error(ERR_INTERNAL, "unknown reloc type... %s", def->type->name); } if (def->deftail) { QCC_def_t *prev, *sub; for (prev = def, sub = prev->next; prev != def->deftail; sub = (prev=sub)->next) { if (sub->reloc && !sub->used) { //make sure any children are finalised properly if they're relocs. sub->used = true; sub->referenced = true; QCC_FinaliseDef(sub); } } } #ifdef DEBUG_DUMP_GLOBALMAP if (!def->referenced) externs->Printf("Unreferenced "); sr.sym = def; sr.ofs = 0; sr.cast = def->type; v = (QCC_eval_t*)&sr.sym->symboldata[sr.ofs]; if (v && def->type->type == ev_float) externs->Printf("Finalise %s(%f) @ %i+%i\n", def->name, v->_float, def->ofs, ssize); else if (v && def->type->type == ev_vector) externs->Printf("Finalise %s(%f %f %f) @ %i+%i\n", def->name, v->vector[0], v->vector[1], v->vector[2], def->ofs, ssize); else if (v && def->type->type == ev_integer) externs->Printf("Finalise %s(%i) @ %i+%i\n", def->name, v->_int, def->ofs, ssize); else if (v && def->type->type == ev_function) externs->Printf("Finalise %s(@%i) @ %i+%i\n", def->name, v->_int, def->ofs, ssize); else if (v && def->type->type == ev_field) externs->Printf("Finalise %s(.%i) @ %i+%i\n", def->name, v->_int, def->ofs, ssize); else if (v && def->type->type == ev_string) externs->Printf("Finalise %s(\"%s\") @ %i+%i\n", def->name, strings+v->_int, def->ofs, ssize); else externs->Printf("Finalise %s(?) @ %i+%i\n", def->name, def->ofs, ssize); #endif } //marshalled locals still point to the FIRST_LOCAL range. //this function remaps all the locals back into actual usable defs. static void QCC_UnmarshalLocals(void) { QCC_def_t *d; unsigned int onum, biggest, eog; size_t i; for (d = pr.def_head.next ; d ; d = d->next) ; //finalise all the globals that we've seen so far for (d = pr.def_head.next ; d ; d = d->next) { if (!d->localscope || d->isstatic) QCC_FinaliseDef(d); } //first, finalize all static locals that shouldn't form part of the local defs. for (i=0 ; inextlocal) if (d->isstatic || (d->constant && d->initialized)) QCC_FinaliseDef(d); } } eog = numpr_globals; //next, finalize non-static non-shared locals. for (i=0 ; iPrintf("function %s locals:\n", functions[i].name); #endif for (d = functions[i].firstlocal; d; d = d->nextlocal) if (!d->isstatic && !(d->constant && d->initialized)) QCC_FinaliseDef(d); if (verbose >= VERBOSE_DEBUG) { if (onum == numpr_globals) externs->Printf("code: %s:%i: function %s no private locals\n", functions[i].filen, functions[i].line, functions[i].name); else externs->Printf("code: %s:%i: function %s private locals %i-%i\n", functions[i].filen, functions[i].line, functions[i].name, onum, numpr_globals); } } } //these functions don't have any initialisation issues, allowing us to merge them onum = biggest = numpr_globals; for (i=0 ; inextlocal) if (!d->isstatic && !(d->constant && d->initialized)) QCC_FinaliseDef(d); if (biggest < numpr_globals) biggest = numpr_globals; if (verbose >= VERBOSE_DEBUG) { if (onum == numpr_globals) externs->Printf("code: %s:%i: function %s no locals\n", functions[i].filen, functions[i].line, functions[i].name); else { externs->Printf("code: %s:%i: function %s overlapped locals %i-%i\n", functions[i].filen, functions[i].line, functions[i].name, onum, numpr_globals); for (d = functions[i].firstlocal; d; d = d->nextlocal) { externs->Printf("code: %s:%i: %s @%i\n", functions[i].filen, functions[i].line, d->name, d->ofs); } } } } } numpr_globals = biggest; if (verbose >= VERBOSE_STANDARD) externs->Printf("%i shared locals, %i private, %i total\n", biggest - onum, onum - eog, numpr_globals-eog); } static void QCC_GenerateFieldDefs(QCC_def_t *def, char *fieldname, int ofs, QCC_type_t *type) { string_t sname; QCC_ddef32_t *dd; if (type->type == ev_struct || type->type == ev_union) { //the qcvm cannot cope with struct fields. so we need to generate lots of fake ones. char sub[256]; unsigned int p, a; unsigned int parms = type->num_parms; if (type->type == ev_union) parms = 1; //unions only generate the first element. it simplifies things (should really just be the biggest). for (p = 0; p < parms; p++) { if (type->params[p].arraysize) { for (a = 0; a < type->params[p].arraysize; a++) { QC_snprintfz(sub, sizeof(sub), "%s.%s[%u]", fieldname, type->params[p].paramname, a); QCC_GenerateFieldDefs(def, sub, ofs + type->params[p].ofs + a * type->params[p].type->size, type->params[p].type); } } else { QC_snprintfz(sub, sizeof(sub), "%s.%s", fieldname, type->params[p].paramname); QCC_GenerateFieldDefs(def, sub, ofs + type->params[p].ofs, type->params[p].type); } } return; } if (numfielddefs >= MAX_FIELDS) QCC_PR_ParseError(0, "Too many fields. Limit is %u\n", MAX_FIELDS); dd = &fields[numfielddefs]; numfielddefs++; dd->type = type->type; dd->s_name = sname = QCC_CopyString (fieldname); dd->ofs = def->symboldata[ofs]._int; if (numglobaldefs >= MAX_GLOBALS) QCC_PR_ParseError(0, "Too many globals. Limit is %u\n", MAX_GLOBALS); //and make sure that there's a global defined too, so field remapping isn't screwed. dd = &qcc_globals[numglobaldefs]; numglobaldefs++; dd->type = ev_field; dd->ofs = def->ofs+ofs; dd->s_name = sname; } static const char *QCC_FileForStatement(int st) { const char *ret = "???"; int i; for (i = 0; i < numfunctions; i++) { if (functions[i].code > 0) { if (st < functions[i].code) break; ret = functions[i].filen; } } return ret; } static const char *QCC_FunctionForStatement(int st) { const char *ret = "???"; int i; for (i = 0; i < numfunctions; i++) { if (functions[i].code > 0) { if (st < functions[i].code) break; ret = functions[i].name; } } return ret; } static void QCC_PR_CRCMessages(unsigned short crc); CompilerConstant_t *QCC_PR_CheckCompConstDefined(char *def); static pbool QCC_WriteData (int crc) { QCC_def_t *def; QCC_ddef_t *dd; dprograms_t progs; int h; int i, len; pbool debugtarget = false; pbool types = false; int outputsttype = PST_DEFAULT; int dupewarncount = 0, extwarncount = 0; int *statement_linenums; void *funcdata; size_t funcdatasize; const char *bigjumps = NULL; extern char *basictypenames[]; memset(&progs, 0, sizeof(progs)); if (numstatements==1 && numfunctions==1 && numglobaldefs==1 && numfielddefs==1) { externs->Printf("nothing to write\n"); return false; } progs.blockscompressed=0; if (numstatements > MAX_STATEMENTS) QCC_Error(ERR_TOOMANYSTATEMENTS, "Too many statements - %i\nAdd '-max_statements %i' to the commandline", numstatements, (numstatements+32768)&~32767); if (strofs > MAX_STRINGS) QCC_Error(ERR_TOOMANYSTRINGS, "Too many strings - %i\nAdd '-max_strings %i' to the commandline", strofs, (strofs+32768)&~32767); //part of how compilation works. This def is always present, and never used. def = QCC_PR_GetDef(NULL, "end_sys_globals", NULL, false, 0, false); if (def) def->referenced = true; def = QCC_PR_GetDef(NULL, "end_sys_fields", NULL, false, 0, false); if (def) def->referenced = true; QCC_PR_FinaliseFunctions(); QCC_DetermineNeededSymbols(def); QCC_UnmarshalLocals(); QCC_FinaliseTemps(); for (i=0 ; i 0x7fff || (int)statements[i].a.ofs < -0x7fff)) break; if (!statements[i].a.sym && ((int)statements[i].a.ofs > 0x7fff || (int)statements[i].a.ofs < -0x7fff)) break; if (!statements[i].a.sym && ((int)statements[i].a.ofs > 0x7fff || (int)statements[i].a.ofs < -0x7fff)) break; } if (i < numstatements) bigjumps = QCC_FunctionForStatement(i); if (!def) { QCC_PR_Warning(WARN_SYSTEMCRC, NULL, 0, "no end_sys_fields defined. system headers missing."); } else QCC_PR_CRCMessages(crc); switch (qcc_targetformat) { case QCF_HEXEN2: case QCF_STANDARD: case QCF_DARKPLACES: //grr. if (bodylessfuncs) externs->Printf("Warning: There are some functions without bodies.\n"); if (bigjumps) { externs->Printf("Forcing target to FTE32 due to large function %s\n", bigjumps); outputsttype = PST_FTE32; } else if (numpr_globals > 65530) { if (qcc_targetformat == QCF_HEXEN2) { externs->Printf("Forcing target to uHexen2 due to numpr_globals\n"); outputsttype = PST_UHEXEN2; } else { externs->Printf("Forcing target to FTE32 due to numpr_globals\n"); outputsttype = PST_FTE32; } } else if (qcc_targetformat == QCF_FTEH2) { externs->Printf("Progs execution will require FTE\n"); break; } else if (qcc_targetformat == QCF_HEXEN2) { externs->Printf("Progs execution requires a Hexen2 compatible HCVM\n"); break; } else if (qcc_targetformat == QCF_DARKPLACES) { externs->Printf("Progs execution uses extended opcodes.\n"); break; } else { if (numpr_globals >= 32768) //not much of a different format. Rewrite output to get it working on original executors? externs->Printf("Globals exceeds 32k - an enhanced QCVM will be required\n"); else if (verbose >= VERBOSE_STANDARD) externs->Printf("Progs should run on any QuakeC VM\n"); break; } QCC_OPCodeSetTarget((qcc_targetformat==QCF_HEXEN2)?QCF_FTEH2:QCF_FTE, 0); //intentional fallthrough case QCF_FTEDEBUG: case QCF_FTE: case QCF_FTEH2: case QCF_QSS: if (qcc_targetformat == QCF_FTEDEBUG) debugtarget = true; if (outputsttype != PST_FTE32 && outputsttype != PST_UHEXEN2) { if (bigjumps) { externs->Printf("Using 32 bit target due to large function %s\n", bigjumps); outputsttype = PST_FTE32; } else if (numpr_globals > 65530) { externs->Printf("Using 32 bit target due to numpr_globals\n"); outputsttype = PST_FTE32; } } if (qcc_targetformat == QCF_QSS || qcc_targetformat == QCF_DARKPLACES) compressoutput = 0; //compression of blocks? if (compressoutput) progs.blockscompressed |=1; //statements if (compressoutput) progs.blockscompressed |=2; //defs if (compressoutput) progs.blockscompressed |=4; //fields if (compressoutput) progs.blockscompressed |=8; //functions if (compressoutput) progs.blockscompressed |=16; //strings if (compressoutput) progs.blockscompressed |=32; //globals if (compressoutput) progs.blockscompressed |=64; //line numbers if (compressoutput) progs.blockscompressed |=128; //types //include a type block? //types = debugtarget; // if (types && sizeof(char *) != sizeof(string_t)) { //qcc_typeinfo_t has a char* inside it, which changes size // externs->Printf("64bit builds cannot write typeinfo structures\n"); types = false; } if (verbose >= VERBOSE_STANDARD) { if (qcc_targetformat == QCF_QSS) externs->Printf("QSS or FTE will be required\n"); else if (qcc_targetformat == QCF_DARKPLACES) externs->Printf("DarkPlaces or FTE will be required\n"); else if (outputsttype == PST_UHEXEN2) externs->Printf("FTE or uHexen2 will be required\n"); else externs->Printf("FTE's QCLib will be required\n"); } break; case QCF_UHEXEN2: debugtarget = false; outputsttype = PST_UHEXEN2; if (verbose >= VERBOSE_STANDARD) externs->Printf("uHexen2 will be required\n"); if (numpr_globals < 65535) externs->Printf("Warning: outputting 32 uHexen2 format when 16bit would suffice\n"); break; case QCF_KK7: if (bodylessfuncs) externs->Printf("Warning: There are some functions without bodies.\n"); if (numpr_globals > 65530) externs->Printf("Warning: Saving is not fully supported. Ensure all engine read fields and globals are defined early on.\n"); externs->Printf("A KK compatible executor will be required (FTE/KK)\n"); outputsttype = PST_KKQWSV; break; case QCF_QTEST: externs->Printf("Compiled QTest progs will most likely not work at all. YOU'VE BEEN WARNED!\n"); outputsttype = PST_QTEST; break; default: externs->Sys_Error("invalid progs type chosen!"); } switch (outputsttype) { case PST_QTEST: { // this sucks but the structures are just too different qtest_function_t *funcs = (qtest_function_t *)qccHunkAlloc(sizeof(qtest_function_t)*numfunctions); for (i=0 ; iMAX_PARMS)?MAX_PARMS:functions[i].numparms); funcs[i].locals = 0;//PRLittleLong (functions[i].locals); for (j = 0; j < MAX_PARMS; j++) funcs[i].parm_size[j] = 0;//PRLittleLong((int)functions[i].parm_size[j]); } funcdata = funcs; funcdatasize = numfunctions*sizeof(*funcs); } break; case PST_UHEXEN2: case PST_DEFAULT: case PST_KKQWSV: case PST_FTE32: { dfunction_t *funcs = (dfunction_t *)qccHunkAlloc(sizeof(dfunction_t)*numfunctions); for (i=0 ; iparm_start; funcs[i].locals = functions[i].merged->locals; funcs[i].numparms = functions[i].merged->numparms; for(p = 0; p < (unsigned)funcs[i].numparms; p++) funcs[i].parm_size[p] = functions[i].merged->parm_size[p]; } else if (functions[i].code == -1) { funcs[i].parm_start = 0; funcs[i].locals = 0; funcs[i].numparms = functions[i].type->num_parms; if (funcs[i].numparms > MAX_PARMS) funcs[i].numparms = MAX_PARMS; //shouldn't happen for builtins. p = 0; } else if (functions[i].firstlocal) { unsigned int size; funcs[i].parm_start = 0; for (local = functions[i].firstlocal, a = 0, p = 0; local && a < MAX_PARMS && a < functions[i].type->num_parms; local = local->deftail->nextlocal) { if (!local->used) { //all params should have been assigned space. logically we could have safely omitted the last ones, but blurgh. QCC_PR_Warning(ERR_INTERNAL, local->filen, local->s_line, "Argument %s was not marked used.", local->name); continue; } if (!p) funcs[i].parm_start = local->ofs; size = local->type->size; if (local->arraysize) //arrays are annoying size = local->arraylengthprefix+size*local->arraysize; funcs[i].locals += size; for (; size > 3 && p < MAX_PARMS; size -= 3) funcs[i].parm_size[p++] = 3; //the engine will copy PARMi over, it can't cope with larger types, the following args would be wrong. if (p < MAX_PARMS) funcs[i].parm_size[p++] = size; a++; } for (; local && (!local->used || local->isstatic || (local->constant && local->initialized)); local = local->nextlocal) ; if (!p && local) funcs[i].parm_start = local->ofs; for (; local; local = local->nextlocal) { if (!local->used || local->isstatic || (local->constant && local->initialized)) continue; size = local->type->size; if (local->arraysize) //arrays are annoying size = local->arraylengthprefix+size*local->arraysize; funcs[i].locals += size; } funcs[i].numparms = p; } else { //fucked up functions with no params. funcs[i].parm_start = 0; funcs[i].locals = 0; funcs[i].numparms = 0; p = 0; } while(p < MAX_PARMS) funcs[i].parm_size[p++] = 0; funcs[i].parm_start = PRLittleLong(funcs[i].parm_start); funcs[i].locals = PRLittleLong(funcs[i].locals); funcs[i].numparms = PRLittleLong(funcs[i].numparms); if (funcs[i].locals && !funcs[i].parm_start) QCC_PR_Warning(0, strings + funcs[i].s_file, functions[i].line, "%s:%i: func %s @%i locals@%i+%i, %i parms\n", functions[i].filen, functions[i].line, strings+funcs[i].s_name, funcs[i].first_statement, funcs[i].parm_start, funcs[i].locals, funcs[i].numparms); if (verbose >= VERBOSE_DEBUG) { externs->Printf("code: %s:%i: func %s @%i locals@%i+%i, %i parms\n", functions[i].filen, functions[i].line, strings+funcs[i].s_name, funcs[i].first_statement, funcs[i].parm_start, funcs[i].locals, funcs[i].numparms); externs->Printf("code: %s:%i: (%i,%i,%i,%i,%i,%i,%i,%i)\n", functions[i].filen, functions[i].line, funcs[i].parm_size[0], funcs[i].parm_size[1], funcs[i].parm_size[2], funcs[i].parm_size[3], funcs[i].parm_size[4], funcs[i].parm_size[5], funcs[i].parm_size[6], funcs[i].parm_size[7]); } } funcdata = funcs; funcdatasize = numfunctions*sizeof(*funcs); } break; default: externs->Sys_Error("structtype error"); funcdata = NULL; funcdatasize = 0; } for (dupewarncount = 0, def = pr.def_head.next ; def ; def = def->next) { if (def->scope && !def->isstatic && !def->scope->privatelocals) { //if we're merging locals, then we shouldn't ever bother writing those globals. it just confuses debuggers etc. they're utterly pointless. //which may be a problem if they're things that the engine is going to be swapping around... which they shouldn't be. continue; } /* if (def->type->type == ev_vector || (def->type->type == ev_field && def->type->aux_type->type == ev_vector)) { //do the references, so we don't get loadsa not referenced VEC_HULL_MINS_x s_file = def->s_file; QC_snprintfz (element, sizeof(element), "%s_x", def->name); comp_x = QCC_PR_GetDef(NULL, element, def->scope, false, 0, false); QC_snprintfz (element, sizeof(element), "%s_y", def->name); comp_y = QCC_PR_GetDef(NULL, element, def->scope, false, 0, false); QC_snprintfz (element, sizeof(element), "%s_z", def->name); comp_z = QCC_PR_GetDef(NULL, element, def->scope, false, 0, false); h = def->references; if (comp_x && comp_y && comp_z) { h += comp_x->references; h += comp_y->references; h += comp_z->references; if (!def->references) if (!comp_x->references || !comp_y->references || !comp_z->references) //one of these vars is useless... h=0; def->references = h; if (!h) h = 1; if (comp_x) comp_x->references = h; if (comp_y) comp_y->references = h; if (comp_z) comp_z->references = h; } } */ #ifdef TODO_READWRITETRACK if (def->symbolheader->read && !def->symbolheader->written && !def->symbolheader->referenced) { char typestr[256]; QCC_sref_t sr = {def, 0, def->type}; QCC_PR_Warning(WARN_READNOTWRITTEN, def->filen, def->s_line, "%s %s = %s read, but not writte.", TypeName(def->type, typestr, sizeof(typestr)), def->name, QCC_VarAtOffset(sr)); } if (def->symbolheader->written && !def->symbolheader->read && !def->symbolheader->referenced) { char typestr[256]; QCC_sref_t sr = {def, 0, def->type}; QCC_PR_Warning(WARN_WRITTENNOTREAD, def->filen, def->s_line, "%s %s = %s written, but not read.", TypeName(def->type, typestr, sizeof(typestr)), def->name, QCC_VarAtOffset(sr)); } #endif if (!def->symbolheader->read && !def->symbolheader->written && !def->referenced) { int wt = def->constant?WARN_NOTREFERENCEDCONST:WARN_NOTREFERENCED; if (def->type->type == ev_field && def->constant) wt = WARN_NOTREFERENCEDFIELD; pr_scope = def->scope; if (!strncmp(def->name, "spawnfunc_", 10)) ; //no warnings from unreferenced entry points. else if (strcmp(def->name, "IMMEDIATE") && qccwarningaction[wt] && !(def->type->type == ev_function && def->symbolheader->timescalled) && !def->symbolheader->used) { char typestr[256]; if (QC_strcasestr(def->filen, "extensions") && verbose < VERBOSE_STANDARD) { //try to avoid annoying warnings from dpextensions.qc extwarncount++; QCC_PR_Warning(wt, def->filen, def->s_line, NULL); } else if (def->arraysize) QCC_PR_Warning(wt, def->filen, def->s_line, (dupewarncount++ >= 10 && verbose < VERBOSE_STANDARD)?NULL:"%s %s%s%s[%i] no references.", TypeName(def->type, typestr, sizeof(typestr)), col_symbol, def->name, col_none, def->arraysize); else QCC_PR_Warning(wt, def->filen, def->s_line, (dupewarncount++ >= 10 && verbose < VERBOSE_STANDARD)?NULL:"%s %s%s%s no references.", TypeName(def->type, typestr, sizeof(typestr)), col_symbol, def->name, col_none); } pr_scope = NULL; if (def->symbolheader->used) { char typestr[256]; QCC_sref_t sr = {def, {0}, def->type}; QCC_PR_Warning(WARN_NOTREFERENCED, def->filen, def->s_line, "%s %s%s%s = %s used, but not referenced.", TypeName(def->type, typestr, sizeof(typestr)), col_symbol, def->name, col_none, QCC_VarAtOffset(sr)); } /*if (opt_unreferenced && def->type->type != ev_field) { optres_unreferenced++; #ifdef DEBUG_DUMP externs->Printf("code: %s:%i: strip noref %s %s@%i;\n", def->filen, def->s_line, def->type->name, def->name, def->ofs); #endif continue; }*/ } if ((def->type->type == ev_struct || def->type->type == ev_union || def->arraysize) && def->deftail) { #ifdef DEBUG_DUMP externs->Printf("code: %s:%i: strip struct %s %s@%i;\n", def->filen, def->s_line, def->type->name, def->name, def->ofs); #endif //the head of an array/struct is never written. only, its member fields are. continue; } if (def->strip || !def->symbolheader->used) { optres_unreferenced++; #ifdef DEBUG_DUMP externs->Printf("code: %s:%i: strip %s %s@%i;\n", def->filen, def->s_line, def->type->name, def->name, def->ofs); #endif continue; } if (def->type->type == ev_function) { if (opt_function_names && def->initialized && functions[def->symboldata[0].function].code<0) { optres_function_names++; def->name = ""; } #if IAMNOTLAZY if (!def->timescalled) { if (def->references<=1 && strncmp(def->name, "spawnfunc_", 10)) QCC_PR_Warning(WARN_DEADCODE, strings + def->s_file, def->s_line, "%s is never directly called or referenced (spawn function or dead code)", def->name); // else // QCC_PR_Warning(WARN_DEADCODE, strings + def->s_file, def->s_line, "%s is never directly called", def->name); } if (opt_stripfunctions && def->constant && def->timescalled >= def->references-1) //make sure it's not copied into a different var. { //if it ever does self.think then it could be needed for saves. optres_stripfunctions++; //if it's only ever called explicitly, the engine doesn't need to know. #ifdef DEBUG_DUMP externs->Printf("code: %s:%i: strip const %s %s@%i;\n", strings+def->s_file, def->s_line, def->type->name, def->name, def->ofs); #endif continue; } #endif } else if (def->type->type == ev_field && def->constant && (!def->scope || def->isstatic || def->initialized)) { QCC_GenerateFieldDefs(def, def->name, 0, def->type->aux_type); continue; } else if (def->type->type == ev_pointer && (def->symboldata[0]._int & 0x80000000)) { //pointer relocs must never be stripped as we don't know the final addresses in advance. would screw stuff up. if (opt_constant_names && !def->nostrip) def->name = ""; //reloc, can't strip it (engine needs to fix em up), but can clear its name. } else if (def->scope && !def->scope->privatelocals && !def->isstatic) continue; //def is a local, which got shared and should be 0... else if ((def->type->type == ev_pointer || def->type->type == ev_string) && def->initialized && (def->symboldata[0]._int || !strncmp(def->name, "dotranslate_", 12))) { //string types in addons cannot be stripped - the engine needs to update offset to the new string table. if (!def->nostrip && (def->scope||def->constant||flag_noreflection)) if (strncmp(def->name, "dotranslate_", 12) && strncmp(def->name, "autocvar_", 9)) //special crap. { //we can at least strip the name if (opt_constant_names_strings) continue; //drop entirely. if (opt_constant_names) { optres_constant_names_strings += strlen(def->name); /*char *n = qccHunkAlloc(7+strlen(def->name)+1); sprintf(n, "STRIP_%s", def->name); def->name = n;*/ def->name = ""; } } } else if ((opt_constant_names&&(def->scope||def->constant))||(flag_noreflection&&strncmp(def->name, "autocvar_", 9)))// && (def->type->type != ev_string || (opt_constant_names_strings && strncmp(def->name, "dotranslate_", 12)))) { if (!def->nostrip) { if (def->type->type == ev_string) optres_constant_names_strings += strlen(def->name); else optres_constant_names += strlen(def->name); #ifdef DEBUG_DUMP if (def->scope) externs->Printf("code: %s:%i: strip local %s %s @%i;\n", def->filen, def->s_line, def->type->name, def->name, def->ofs); else if (def->constant) externs->Printf("code: %s:%i: strip const %s %s @%i;\n", def->filen, def->s_line, def->type->name, def->name, def->ofs); else externs->Printf("code: %s:%i: strip globl %s %s @%i;\n", def->filen, def->s_line, def->type->name, def->name, def->ofs); #endif continue; } } // if (!def->saved && def->type->type != ev_string) // continue; dd = &qcc_globals[numglobaldefs]; numglobaldefs++; if (types) dd->type = def->type-qcc_typeinfo; else dd->type = def->type->type; #ifdef DEF_SAVEGLOBAL if ( def->saved && !def->constant// || def->type->type == ev_function) // && def->type->type != ev_function && def->type->type != ev_field && def->scope == NULL) { dd->type |= DEF_SAVEGLOBAL; } #endif if (def->shared) dd->type |= DEF_SHARED; if (opt_locals && ((def->scope&&!def->isstatic) || !strcmp(def->name, "IMMEDIATE"))) { dd->s_name = 0; optres_locals += strlen(def->name); } else dd->s_name = QCC_CopyString (def->name); dd->ofs = def->ofs; #ifdef DEBUG_DUMP if ((dd->type&~(DEF_SHARED|DEF_SAVEGLOBAL)) == ev_string) externs->Printf("code: %s:%i: %s%s%s %s@%i = \"%s\"\n", def->filen, def->s_line, dd->type&DEF_SAVEGLOBAL?"save ":"nosave ", dd->type&DEF_SHARED?"shared ":"", basictypenames[dd->type&~(DEF_SHARED|DEF_SAVEGLOBAL)], strings+dd->s_name, dd->ofs, ((unsigned)def->symboldata[0].string>=(unsigned)strofs)?"???":(strings + def->symboldata[0].string)); else if ((dd->type&~(DEF_SHARED|DEF_SAVEGLOBAL)) == ev_float) externs->Printf("code: %s:%i: %s%s%s %s@%i = %g\n", def->filen, def->s_line, dd->type&DEF_SAVEGLOBAL?"save ":"nosave ", dd->type&DEF_SHARED?"shared ":"", basictypenames[dd->type&~(DEF_SHARED|DEF_SAVEGLOBAL)], strings+dd->s_name, dd->ofs, def->symboldata[0]._float); else if ((dd->type&~(DEF_SHARED|DEF_SAVEGLOBAL)) == ev_integer) externs->Printf("code: %s:%i: %s%s%s %s@%i = %i\n", def->filen, def->s_line, dd->type&DEF_SAVEGLOBAL?"save ":"nosave ", dd->type&DEF_SHARED?"shared ":"", basictypenames[dd->type&~(DEF_SHARED|DEF_SAVEGLOBAL)], strings+dd->s_name, dd->ofs, def->symboldata[0]._int); else if ((dd->type&~(DEF_SHARED|DEF_SAVEGLOBAL)) == ev_vector) externs->Printf("code: %s:%i: %s%s%s %s@%i = '%g %g %g'\n", def->filen, def->s_line, dd->type&DEF_SAVEGLOBAL?"save ":"nosave ", dd->type&DEF_SHARED?"shared ":"", basictypenames[dd->type&~(DEF_SHARED|DEF_SAVEGLOBAL)], strings+dd->s_name, dd->ofs, def->symboldata[0].vector[0], def->symboldata[0].vector[1], def->symboldata[0].vector[2]); else if ((dd->type&~(DEF_SHARED|DEF_SAVEGLOBAL)) == ev_function) externs->Printf("code: %s:%i: %s%s%s %s@%i = %i(%s)\n", def->filen, def->s_line, dd->type&DEF_SAVEGLOBAL?"save ":"nosave ", dd->type&DEF_SHARED?"shared ":"", basictypenames[dd->type&~(DEF_SHARED|DEF_SAVEGLOBAL)], strings+dd->s_name, dd->ofs, def->symboldata[0].function, def->symboldata[0].function >= numfunctions?"???":functions[def->symboldata[0].function].name); else if ((dd->type&~(DEF_SHARED|DEF_SAVEGLOBAL)) == ev_field) externs->Printf("code: %s:%i: %s%s%s %s@%i = @%i\n", def->filen, def->s_line, dd->type&DEF_SAVEGLOBAL?"save ":"nosave ", dd->type&DEF_SHARED?"shared ":"", basictypenames[dd->type&~(DEF_SHARED|DEF_SAVEGLOBAL)], strings+dd->s_name, dd->ofs, def->symboldata[0]._int); else externs->Printf("code: %s:%i: %s%s%s %s@%i\n", def->filen, def->s_line, dd->type&DEF_SAVEGLOBAL?"save ":"nosave ", dd->type&DEF_SHARED?"shared ":"", basictypenames[dd->type&~(DEF_SHARED|DEF_SAVEGLOBAL)], strings+dd->s_name, dd->ofs); #endif } QCC_SortFields(); if (dupewarncount > 10 && verbose < VERBOSE_STANDARD) QCC_PR_Note(WARN_NOTREFERENCED, NULL, 0, "suppressed %i more warnings about unreferenced variables, as you clearly don't care about the first 10.", dupewarncount-10); if (extwarncount) QCC_PR_Note(WARN_NOTREFERENCED, NULL, 0, "suppressed %i warnings about unused extensions.", extwarncount); for (i = 0; i < numglobaldefs; i++) { dd = &qcc_globals[i]; if (!(dd->type & DEF_SAVEGLOBAL)) //only warn about saved ones. continue; for (h = 0; h < numglobaldefs; h++) { if (i == h || !(qcc_globals[h].type & DEF_SAVEGLOBAL)) continue; if (dd->ofs == qcc_globals[h].ofs) { if ((dd->type&~DEF_SAVEGLOBAL) != (qcc_globals[h].type&~DEF_SAVEGLOBAL)) { if (!(((dd->type&~DEF_SAVEGLOBAL) == ev_vector && (qcc_globals[h].type&~DEF_SAVEGLOBAL) == ev_float) || ((dd->type&~DEF_SAVEGLOBAL) == ev_struct || (dd->type&~DEF_SAVEGLOBAL) == ev_union))) QCC_PR_Warning(0, NULL, 0, "Mismatched union global types (%s and %s)", strings+dd->s_name, strings+qcc_globals[h].s_name); } //remove the saveglobal flag on the duplicate globals. qcc_globals[h].type &= ~DEF_SAVEGLOBAL; } } } for (i = 1; i < numfielddefs; i++) { dd = &fields[i]; if (dd->type == ev_vector || dd->type == ev_struct || dd->type == ev_union) //just ignore vectors, structs, and unions. continue; for (h = 1; h < numfielddefs; h++) { if (i == h) continue; if (dd->ofs == fields[h].ofs) { if (dd->type != fields[h].type) { if (fields[h].type != ev_vector && fields[h].type != ev_struct && fields[h].type != ev_union) { QCC_PR_Warning(0, NULL, 0, "Mismatched union field types (%s and %s @%i)", strings+dd->s_name, strings+fields[h].s_name, dd->ofs); } } } } } if (numglobaldefs > MAX_GLOBALS) QCC_Error(ERR_TOOMANYGLOBALS, "Too many globals - %i\nAdd '-max_globals %i' to the commandline", numglobaldefs, (numglobaldefs+32768)&~32767); dupewarncount = 0; for (i = 0; i < nummodels; i++) { if (!precache_model[i].used) dupewarncount+=QCC_PR_Warning(WARN_EXTRAPRECACHE, precache_model[i].filename, precache_model[i].fileline, (dupewarncount>10&&verbose < VERBOSE_STANDARD)?NULL:"Model \"%s\" was precached but not directly used%s", precache_model[i].name, dupewarncount?"":" (annotate the usage with the used_model intrinsic to silence this warning)"); else if (!precache_model[i].block) dupewarncount+=QCC_PR_Warning(WARN_NOTPRECACHED, precache_model[i].filename, precache_model[i].fileline, (dupewarncount>10&&verbose < VERBOSE_STANDARD)?NULL:"Model \"%s\" was used but not directly precached", precache_model[i].name); } for (i = 0; i < numsounds; i++) { if (!precache_sound[i].used) dupewarncount+=QCC_PR_Warning(WARN_EXTRAPRECACHE, precache_sound[i].filename, precache_sound[i].fileline, (dupewarncount>10&&verbose < VERBOSE_STANDARD)?NULL:"Sound \"%s\" was precached but not directly used%s", precache_sound[i].name, dupewarncount?"":" (annotate the usage with the used_sound intrinsic to silence this warning)"); else if (!precache_sound[i].block) dupewarncount+=QCC_PR_Warning(WARN_NOTPRECACHED, precache_sound[i].filename, precache_sound[i].fileline, (dupewarncount>10&&verbose < VERBOSE_STANDARD)?NULL:"Sound \"%s\" was used but not directly precached", precache_sound[i].name); } if (dupewarncount > 10 && verbose < VERBOSE_STANDARD) QCC_PR_Note(WARN_NOTREFERENCED, NULL, 0, "suppressed %i more %swarnings%s about precaches.", dupewarncount-10, col_warning, col_none); //PrintStrings (); //PrintFunctions (); //PrintFields (); //PrintGlobals (); strofs = (strofs+3)&~3; if (verbose >= VERBOSE_STANDARD) { externs->Printf ("%6i strofs (of %i)\n", strofs, MAX_STRINGS); externs->Printf ("%6i numstatements (of %i)\n", numstatements, MAX_STATEMENTS); externs->Printf ("%6i numfunctions (of %i)\n", numfunctions, MAX_FUNCTIONS); externs->Printf ("%6i numglobaldefs (of %i)\n", numglobaldefs, MAX_GLOBALS); externs->Printf ("%6i numfielddefs (%i unique) (of %i)\n", numfielddefs, pr.size_fields, MAX_FIELDS); externs->Printf ("%6i numpr_globals (of %i)\n", numpr_globals, MAX_REGS); externs->Printf ("%6i nummodels\n", nummodels); externs->Printf ("%6i numsounds\n", numsounds); } if (!*destfile) strcpy(destfile, "progs.dat"); if (verbose >= VERBOSE_PROGRESS) externs->Printf("Writing %s\n", destfile); h = SafeOpenWrite (destfile, 2*1024*1024); SafeWrite (h, &progs, sizeof(progs)); SafeWrite (h, "\r\n\r\n", 4); SafeWrite (h, QCC_copyright, strlen(QCC_copyright)+1); SafeWrite (h, "\r\n\r\n", 4); while(SafeSeek (h, 0, SEEK_CUR) & 3)//this is a lame way to do it { SafeWrite (h, "\0", 1); } progs.ofs_strings = SafeSeek (h, 0, SEEK_CUR); progs.numstrings = strofs; if (progs.blockscompressed&16) { SafeWrite (h, &len, sizeof(int)); //save for later len = QC_encode(progfuncs, strofs*sizeof(char), 2, (char *)strings, h); //write i = SafeSeek (h, 0, SEEK_CUR); SafeSeek(h, progs.ofs_strings, SEEK_SET);//seek back len = PRLittleLong(len); SafeWrite (h, &len, sizeof(int)); //write size. SafeSeek(h, i, SEEK_SET); } else SafeWrite (h, strings, strofs); progs.ofs_statements = SafeSeek (h, 0, SEEK_CUR); progs.numstatements = numstatements; if (qcc_targetformat == QCF_HEXEN2 || qcc_targetformat == QCF_UHEXEN2 || qcc_targetformat == QCF_FTEH2) { for (i=0 ; i= OP_CALL1 && statements[i].op <= OP_CALL8) QCC_Error(ERR_BADTARGETSWITCH, "Target switching produced incompatible instructions"); else if (statements[i].op >= OP_CALL1H && statements[i].op <= OP_CALL8H) statements[i].op = statements[i].op - OP_CALL1H + OP_CALL1; } } if (!opt_filenames) { statement_linenums = qccHunkAlloc(sizeof(statement_linenums) * numstatements); for (i = 0; i < numstatements; i++) statement_linenums[i] = statements[i].linenum; } else statement_linenums = NULL; switch(outputsttype) { case PST_UHEXEN2: { QCC_dstatement32_t *statements32 = qccHunkAlloc(sizeof(*statements32) * numstatements); for (i=0 ; iofs:0) + statements[i].a.ofs); statements32[i].b = PRLittleLong((statements[i].b.sym?statements[i].b.sym->ofs:0) + statements[i].b.ofs); statements32[i].c = PRLittleLong((statements[i].c.sym?statements[i].c.sym->ofs:0) + statements[i].c.ofs); if (verbose >= VERBOSE_DEBUGSTATEMENTS) externs->Printf("code: %s:%i: @%i %s %i %i %i\n", QCC_FileForStatement(i), statements[i].linenum, i, pr_opcodes[statements[i].op].name, statements32[i].a, statements32[i].b, statements32[i].c); } SafeWrite (h, statements32, numstatements*sizeof(QCC_dstatement32_t)); } break; case PST_KKQWSV: case PST_FTE32: { QCC_dstatement32_t *statements32 = qccHunkAlloc(sizeof(*statements32) * numstatements); for (i=0 ; iofs:0) + statements[i].a.ofs); statements32[i].b = PRLittleLong((statements[i].b.sym?statements[i].b.sym->ofs:0) + statements[i].b.ofs); statements32[i].c = PRLittleLong((statements[i].c.sym?statements[i].c.sym->ofs:0) + statements[i].c.ofs); if (verbose >= VERBOSE_DEBUGSTATEMENTS) externs->Printf("code: %s:%i: @%i %s %i %i %i\n", QCC_FileForStatement(i), statements[i].linenum, i, pr_opcodes[statements[i].op].name, statements32[i].a, statements32[i].b, statements32[i].c); } if (progs.blockscompressed&1) { SafeWrite (h, &len, sizeof(int)); //save for later len = QC_encode(progfuncs, numstatements*sizeof(QCC_dstatement32_t), 2, (char *)statements, h); //write i = SafeSeek (h, 0, SEEK_CUR); SafeSeek(h, progs.ofs_statements, SEEK_SET);//seek back len = PRLittleLong(len); SafeWrite (h, &len, sizeof(int)); //write size. SafeSeek(h, i, SEEK_SET); } else SafeWrite (h, statements32, numstatements*sizeof(QCC_dstatement32_t)); } break; case PST_QTEST: { qtest_statement_t *qtst = qccHunkAlloc(sizeof(*qtst) * numstatements); for (i=0 ; iofs:0) + statements[i].a.ofs; unsigned int b = (statements[i].b.sym?statements[i].b.sym->ofs:0) + statements[i].b.ofs; unsigned int c = (statements[i].c.sym?statements[i].c.sym->ofs:0) + statements[i].c.ofs; qtst[i].line = statements[i].linenum; qtst[i].op = PRLittleShort((unsigned short)statements[i].op); qtst[i].a = (unsigned short)PRLittleShort((unsigned short)a); qtst[i].b = (unsigned short)PRLittleShort((unsigned short)b); qtst[i].c = (unsigned short)PRLittleShort((unsigned short)c); } // no compression SafeWrite (h, qtst, numstatements*sizeof(qtest_statement_t)); } break; case PST_DEFAULT: { QCC_dstatement16_t *statements16 = qccHunkAlloc(sizeof(*statements16) * numstatements); #ifdef DISASM int start, end; for (i = 1; i < numfunctions; i++) { if (!strcmp(functions[i].name, DISASM)) { start = functions[i].code; end = functions[i+1].code; } } #endif for (i=0 ; iofs:0) + statements[i].a.ofs; unsigned int b = (statements[i].b.sym?statements[i].b.sym->ofs:0) + statements[i].b.ofs; unsigned int c = (statements[i].c.sym?statements[i].c.sym->ofs:0) + statements[i].c.ofs; statements16[i].op = PRLittleShort((unsigned short)statements[i].op); if ( #if defined(DISASM) (i >= start && i < end) || #endif verbose >= VERBOSE_DEBUGSTATEMENTS) { char line[2048]; QC_snprintfz(line, sizeof(line), "code: %s:%i: @%i %s %i %i %i (%s", QCC_FileForStatement(i), statements[i].linenum, i, pr_opcodes[statements[i].op].opname, a, b, c, QCC_VarAtOffset(statements[i].a)); QC_snprintfz(line+strlen(line), sizeof(line)-strlen(line), " %s", QCC_VarAtOffset(statements[i].b)); QC_snprintfz(line+strlen(line), sizeof(line)-strlen(line), " %s)\n", QCC_VarAtOffset(statements[i].c)); externs->Printf("%s", line); } #ifdef _DEBUG if (((signed)a >= (signed)numpr_globals && statements[i].a.sym) || ((signed)b >= (signed)numpr_globals && statements[i].b.sym) || ((signed)c >= (signed)numpr_globals && statements[i].c.sym)) externs->Printf("invalid offset on %s instruction (from line %i)\n", pr_opcodes[statements[i].op].opname, statements[i].linenum); #endif //truncate to 16bit. should probably warn if the high bits are not 0x0000 or 0xffff statements16[i].a = (unsigned short)PRLittleShort((unsigned short)a); statements16[i].b = (unsigned short)PRLittleShort((unsigned short)b); statements16[i].c = (unsigned short)PRLittleShort((unsigned short)c); } if (progs.blockscompressed&1) { SafeWrite (h, &len, sizeof(int)); //save for later len = QC_encode(progfuncs, numstatements*sizeof(QCC_dstatement16_t), 2, (char *)statements16, h); //write i = SafeSeek (h, 0, SEEK_CUR); SafeSeek(h, progs.ofs_statements, SEEK_SET);//seek back len = PRLittleLong(len); SafeWrite (h, &len, sizeof(int)); //write size. SafeSeek(h, i, SEEK_SET); } else SafeWrite (h, statements16, numstatements*sizeof(QCC_dstatement16_t)); } break; default: externs->Sys_Error("structtype error"); } progs.ofs_functions = SafeSeek (h, 0, SEEK_CUR); progs.numfunctions = numfunctions; if (progs.blockscompressed&8) { SafeWrite (h, &len, sizeof(int)); //save for later len = QC_encode(progfuncs, funcdatasize, 2, (char *)funcdata, h); //write i = SafeSeek (h, 0, SEEK_CUR); SafeSeek(h, progs.ofs_functions, SEEK_SET);//seek back len = PRLittleLong(len); SafeWrite (h, &len, sizeof(int)); //write size. SafeSeek(h, i, SEEK_SET); } else SafeWrite (h, funcdata, funcdatasize); if (flag_dumpfilenames) QCC_DumpFiles(destfile); if (flag_dumpfields) QCC_DumpFields(destfile); if (flag_dumpsymbols) QCC_DumpSymbolNames(destfile); // QCC_DumpSymbolInfo(destfile); if (flag_dumpautocvars) QCC_DumpAutoCvars(destfile); if (flag_dumplocalisation) QCC_DumpLocalisation(destfile); if (flag_dumptags) QCC_DumpTags(destfile); if (flag_dumpopcodes) QCC_DumpOpcodes(destfile); switch(outputsttype) { case PST_QTEST: // qtest needs a struct remap but should be able to get away with a simple swap here for (i=0 ; i 0xffff) { externs->Printf("Offset for field %s overflowed 16 bits.\n", strings+fields[i].s_name); fields16[i].ofs = 0; } else fields16[i].ofs = (unsigned short)PRLittleShort ((unsigned short)fields[i].ofs); fields16[i].s_name = PRLittleLong (fields[i].s_name); } if (progs.blockscompressed&4) { SafeWrite (h, &len, sizeof(int)); //save for later len = QC_encode(progfuncs, numfielddefs*sizeof(QCC_ddef16_t), 2, (char *)fields16, h); //write i = SafeSeek (h, 0, SEEK_CUR); SafeSeek(h, progs.ofs_fielddefs, SEEK_SET);//seek back len = PRLittleLong(len); SafeWrite (h, &len, sizeof(int)); //write size. SafeSeek(h, i, SEEK_SET); } else SafeWrite (h, fields16, numfielddefs*sizeof(QCC_ddef16_t)); break; default: externs->Sys_Error("structtype error"); } progs.ofs_globals = SafeSeek (h, 0, SEEK_CUR); progs.numglobals = numpr_globals; for (i=0 ; (unsigned)iPrintf ("WARNING: progs format cannot handle extern functions\n"); if (verbose >= VERBOSE_STANDARD) externs->Printf ("%6i TOTAL SIZE\n", (int)SafeSeek (h, 0, SEEK_CUR)); progs.entityfields = pr.size_fields; progs.crc = crc; if (flag_qccx) { def = QCC_PR_GetDef(NULL, "progs", NULL, false, 0, 0); //this is for qccx support if (def && (def->type->type == ev_entity || (def->type->type == ev_accessor && def->type->parentclass->type == ev_entity))) { int size = SafeSeek (h, 0, SEEK_CUR); size += 1; //the engine will add a null terminator size = (size+15)&(~15); //and will allocate it on the hunk with 16-byte alignment //this global receives the offset from world to the start of the progs def _IN VANILLA QUAKE_. //this is a negative index due to allocation ordering with the assumption that the progs.dat was loaded on the heap directly followed by the entities. //this will NOT work in FTE, DP, QuakeForge due to entity indexes. Various other engines will likely mess up too, if they change the allocation order or sizes etc. 64bit is screwed. if (progs.blockscompressed&32) externs->Printf("unable to write value for 'entity progs'\n"); //would not work anyway else { QCC_PR_Warning(WARN_DENORMAL, def->filen, def->s_line, "'entity progs' is non-portable and will not work across engines nor cpus."); if (def->initialized) i = PRLittleLong(qcc_pr_globals[def->ofs]._int); else { //entsize(=96)+hunk header size(=32) if (verbose >= VERBOSE_STANDARD) externs->Printf("qccx hack - 'entity progs' uninitialised. Assuming 112.\n"); i = 112; //match qccx. } i = -(size + i); i = PRLittleLong(i); SafeSeek (h, progs.ofs_globals + 4 * def->ofs, SEEK_SET); SafeWrite (h, &i, 4); } } } // qbyte swap the header and write it out for (i=0 ; iPrintf("%sError%s while writing output %s\n", col_error, col_none, destfile); return false; } if (verbose >= VERBOSE_PROGRESS) switch(qcc_targetformat) { case QCF_QTEST: externs->Printf("Compile finished: %s (qtest format)\n", destfile); break; case QCF_KK7: externs->Printf("Compile finished: %s (kk7 format)\n", destfile); break; case QCF_STANDARD: externs->Printf("Compile finished: %s (id format)\n", destfile); break; case QCF_HEXEN2: case QCF_UHEXEN2: if (progs.version == PROG_VERSION) externs->Printf("Compile finished: %s (hexen2 format)\n", destfile); else externs->Printf("Compile finished: %s (uhexen2 format)\n", destfile); break; case QCF_DARKPLACES: externs->Printf("Compile finished: %s (fte+dp format)\n", destfile); break; case QCF_QSS: externs->Printf("Compile finished: %s (fte+qss format)\n", destfile); break; case QCF_FTE: externs->Printf("Compile finished: %s (fte format)\n", destfile); break; case QCF_FTEH2: externs->Printf("Compile finished: %s (fteh2 format)\n", destfile); break; case QCF_FTEDEBUG: externs->Printf("Compile finished: %s (ftedbg format)\n", destfile); break; default: externs->Printf("Compile finished: %s\n", destfile); break; } if (statement_linenums) { unsigned int lnotype = *(unsigned int*)"LNOF"; unsigned int version = 1; pbool gz = false; while(1) { char *ext; ext = strrchr(destfile, '.'); if (!ext || strchr(ext, '/') || strchr(ext, '\\')) break; if (!stricmp(ext, ".gz")) { *ext = 0; gz = true; continue; } *ext = 0; break; } if (strlen(destfile) < sizeof(destfile)-(4+3)) { strcat(destfile, ".lno"); if (gz) strcat(destfile, ".gz"); if (verbose >= VERBOSE_STANDARD) externs->Printf("Writing %s for debugging\n", destfile); h = SafeOpenWrite (destfile, 2*1024*1024); SafeWrite (h, &lnotype, sizeof(int)); SafeWrite (h, &version, sizeof(int)); SafeWrite (h, &numglobaldefs, sizeof(int)); SafeWrite (h, &numpr_globals, sizeof(int)); SafeWrite (h, &numfielddefs, sizeof(int)); SafeWrite (h, &numstatements, sizeof(int)); SafeWrite (h, statement_linenums, numstatements*sizeof(int)); SafeClose (h); } } return true; } /* #merge "oldprogs" wrap void() worldspawn = { print("hello world\n"); prior(); }; Progs merging is done by loading in an existing progs.dat and essentially appending new stuff on the end. The resulting output should be the same, other than wraps (which replaces the previous function global with the new one). */ static void QCC_MergeStrings(char *in, unsigned int num) { memcpy(strings, in, num); strofs = num; } static int QCC_MergeValidateString(int str) { if (str < 0 || str >= strofs) str = 0; return str; } static void QCC_MergeFunctions(dfunction_t *in, unsigned int num) { numfunctions = 0; while(num --> 0) { if (in->first_statement <= 0) { functions[numfunctions].builtin = -in->first_statement; functions[numfunctions].code = -1; } else { functions[numfunctions].builtin = 0; functions[numfunctions].code = in->first_statement; } functions[numfunctions].s_filed = QCC_MergeValidateString(in->s_file); functions[numfunctions].filen = strings+functions[numfunctions].s_filed; functions[numfunctions].line = 0; functions[numfunctions].name = strings+QCC_MergeValidateString(in->s_name); functions[numfunctions].parentscope = NULL; functions[numfunctions].type = NULL; functions[numfunctions].def = NULL; functions[numfunctions].firstlocal = NULL; functions[numfunctions].privatelocals = true; functions[numfunctions].merged = in; numfunctions++; in++; } } static void QCC_MergeStatements16(dstatement16_t *in, unsigned int num) { QCC_statement_t *out = statements; numstatements = num; for (; num --> 0; out++, in++) { out->op = in->op; out->a.sym = NULL; out->a.cast = NULL; out->a.ofs = in->a; out->b.sym = NULL; out->b.cast = NULL; out->b.ofs = in->b; out->c.sym = NULL; out->c.cast = NULL; out->c.ofs = in->c; out->linenum = 0; if (in->op < OP_NUMREALOPS) { if (!pr_opcodes[in->op].type_a) out->a.ofs = (short)in->a; if (!pr_opcodes[in->op].type_b) out->b.ofs = (short)in->b; if (!pr_opcodes[in->op].type_c) out->c.ofs = (short)in->c; } } out->op = OP_DONE; out->a.ofs = 0; out->b.ofs = 0; out->c.ofs = 0; out->linenum = 0; numstatements++; } static etype_t QCC_MergeFindFieldType(unsigned int ofs, const char *fldname, ddef16_t *fields, size_t numfields) { size_t i; etype_t best = ev_void; for (i = 0; i < numfields; i++) { if (fields[i].ofs == ofs) { //sometimes we have field unions. go for the exact name match if we can so we don't get confused over vectors/floats. otherwise just go with the first (and hope they're correctly ordered) char *name = strings+QCC_MergeValidateString(fields[i].s_name); if (!strcmp(name, fldname)) return fields[i].type; if (best == ev_void) best = fields[i].type; } } return best; } static void QCC_MergeUnstrip(dfunction_t *in, unsigned int num) { size_t i; char *name; QCC_def_t *def; //functions may have been stripped. this results in an annoying lack of errors, and will likely confuse function wrapping... //generate a new def for each function, if it doesn't already exist. //these are probably going to be wasteful dupes, but they'll just get stripped again if they're still not used. for (i = 0; i < num; i++) { if (!in[i].s_name) continue; name = strings+QCC_MergeValidateString(in[i].s_name); def = QCC_PR_GetDef(NULL, name, NULL, false, 0, GDF_BASICTYPE); if (!def) { def = QCC_PR_GetDef(type_function, name, NULL, true, 0, GDF_BASICTYPE); def->symboldata[0].function = i; def->initialized = true; def->referenced = true; def->assumedtype = true; } QCC_FreeDef(def); } } QCC_type_t *QCC_PR_FieldType (QCC_type_t *pointsto); static void QCC_MergeGlobalDefs16(ddef16_t *in, size_t num, void *values, size_t defscount, ddef16_t *fields, size_t numfields) { QCC_def_t *root, *def; QCC_type_t *type; etype_t evt; char *name; unsigned int flags; pbool referrable; numpr_globals = 0; //that root object will replace the normal reserved globals. root = QCC_PR_GetDef(type_void, "", NULL, true, 0, GDF_USED); root->symboldata = values; root->symbolsize = defscount; for (; num --> 0; in++) { name = strings+QCC_MergeValidateString(in->s_name); flags = GDF_USED; if (in->type & DEF_SAVEGLOBAL) flags |= GDF_SAVED; evt = in->type&~DEF_SAVEGLOBAL; if (evt == ev_field) evt = QCC_MergeFindFieldType(root->symboldata[in->ofs]._int, name, fields, numfields); switch(evt) { case ev_void: type = type_void; break; case ev_vector: type = type_vector; break; case ev_float: type = type_float; break; case ev_string: type = type_string; break; case ev_entity: type = type_entity; break; case ev_integer: type = type_integer; break; case ev_function: type = type_function; break; default: type = type_variant; break; } if ((in->type&~DEF_SAVEGLOBAL) == ev_field) { type = QCC_PR_FieldType(type); flags |= GDF_CONST; } referrable = true; //fixme: disable if this appears to be within a function's local storage def = QCC_PR_DummyDef(type, name, NULL, 0, root, in->ofs, referrable, flags); def->initialized = 1; def->referenced = true; def->assumedtype = true; if (evt == ev_vector) { int j = 3; if ((in->type&~DEF_SAVEGLOBAL) == ev_field) { for (j = 0; j < 3; j++) { if (in[j+1].ofs == in->ofs+j && (in[j+1].type&~DEF_SAVEGLOBAL) == ev_field && QCC_MergeFindFieldType(root->symboldata[in[j+1].ofs]._int, strings+QCC_MergeValidateString(in[j+1].s_name), fields, numfields) == ev_float) continue; break; } } else { for (j = 0; j < 3; j++) { if (in[j+1].ofs == in->ofs+j && (in[j+1].type&~DEF_SAVEGLOBAL) == ev_float) continue; break; } } in += j; num -= j; } } QCC_FreeDef(root); } static unsigned char *PDECL QCC_LoadFileHunkAlloc(void *ctx, size_t size) { return (unsigned char*)qccHunkAlloc(size+1); } /*load a progs into the current compile state.*/ void QCC_ImportProgs(const char *filename) { size_t flen; dprograms_t *prog; //these keywords are implicitly enabled by #merge keyword_weak = true; keyword_wrap = true; // if (strofs != 0) //could be fixed with relocs // QCC_Error(ERR_BADEXTENSION, "#merge used too late. It must be used before any other definitions."); if (numstatements != 1) //should be easy to deal with. QCC_Error(ERR_BADEXTENSION, "#merge used too late. It must be used before any other definitions."); if (numfunctions != 1) //could be fixed with relocs QCC_Error(ERR_BADEXTENSION, "#merge used too late. It must be used before any other definitions."); if (numglobaldefs != 1) //could be fixed by inserting it properly. any already-defined defs must have their parentdef changed to union them with imported ones. QCC_Error(ERR_BADEXTENSION, "#merge used too late. It must be used before any other definitions (globals)."); if (numfielddefs != 1) //could be fixed with relocs QCC_Error(ERR_BADEXTENSION, "#merge used too late. It must be used before any other definitions (fields)."); if (numpr_globals != RESERVED_OFS) //not normally changed until after compiling QCC_Error(ERR_BADEXTENSION, "#merge used too late. It must be used before any other definitions (regs)."); externs->Printf ("\nnote: The #merge feature is still experimental\n\n"); //FIXME: find overlapped locals. strip them. merge with new ones. //FIXME: find temps. strip them. you get the idea. //FIXME: find immediates. set up hash tables for them for reuse. HAH! prog = externs->ReadFile(filename, QCC_LoadFileHunkAlloc, NULL, &flen, false); if (!prog) { QCC_Error(ERR_COULDNTOPENFILE, "Couldn't open file %s", filename); return; } if (prog->version == 7 && prog->secondaryversion == PROG_SECONDARYVERSION16 && !prog->blockscompressed && !prog->numtypes) ; else if (prog->version == 7 && prog->secondaryversion == PROG_SECONDARYVERSION32 && !prog->blockscompressed && !prog->numtypes) ; else if (prog->version != 6) { QCC_Error(ERR_COULDNTOPENFILE, "Unsupported version: %s", filename); return; } QCC_MergeStrings(((char*)prog+prog->ofs_strings), prog->numstrings); QCC_MergeFunctions((dfunction_t*)((char*)prog+prog->ofs_functions), prog->numfunctions); pr.size_fields = prog->entityfields; if (prog->version == 7 && prog->secondaryversion == PROG_SECONDARYVERSION32) { // QCC_MergeStatements32((dstatement32_t*)((char*)prog+prog->ofs_statements), prog->numstatements); // QCC_MergeGlobalDefs32((ddef32_t*)((char*)prog+prog->ofs_globaldefs), prog->numglobaldefs, ((char*)prog)+prog->ofs_globals, prog->numglobals, (ddef16_t*)((char*)prog+prog->ofs_fielddefs), prog->numfielddefs); QCC_Error(ERR_COULDNTOPENFILE, "32bit versions not supported: %s", filename); } else { QCC_MergeStatements16((dstatement16_t*)((char*)prog+prog->ofs_statements), prog->numstatements); QCC_MergeGlobalDefs16((ddef16_t*)((char*)prog+prog->ofs_globaldefs), prog->numglobaldefs, ((char*)prog)+prog->ofs_globals, prog->numglobals, (ddef16_t*)((char*)prog+prog->ofs_fielddefs), prog->numfielddefs); } QCC_MergeUnstrip((dfunction_t*)((char*)prog+prog->ofs_functions), prog->numfunctions); } /* =============== PR_String Returns a string suitable for printing (no newlines, max 60 chars length) =============== */ static char *QCC_PR_String (char *string) { static char buf[80]; char *s; s = buf; *s++ = '"'; while (string && *string) { if (s == buf + sizeof(buf) - 2) break; if (*string == '\n') { *s++ = '\\'; *s++ = 'n'; } else if (*string == '"') { *s++ = '\\'; *s++ = '"'; } else *s++ = *string; string++; if (s - buf > 60) { *s++ = '.'; *s++ = '.'; *s++ = '.'; break; } } *s++ = '"'; *s++ = 0; return buf; } static QCC_def_t *QCC_PR_DefForFieldOfs (gofs_t ofs) { QCC_def_t *d; for (d=pr.def_head.next ; d ; d=d->next) { if (d->type->type != ev_field) continue; if (*((unsigned int *)&qcc_pr_globals[d->ofs]) == ofs) return d; } QCC_Error (ERR_NOTDEFINED, "PR_DefForFieldOfs: couldn't find %i",ofs); return NULL; } /* ============ PR_ValueString Returns a string describing *data in a type specific manner ============= */ char *QCC_PR_ValueString (etype_t type, void *val) { static char line[256]; QCC_def_t *def; QCC_function_t *f; switch (type) { case ev_string: QC_snprintfz (line, sizeof(line), "%s", QCC_PR_String(strings + *(int *)val)); break; case ev_entity: QC_snprintfz (line, sizeof(line), "entity %i", *(int *)val); break; case ev_function: f = functions + *(int *)val; if (!f) QC_snprintfz (line, sizeof(line), "undefined function"); else QC_snprintfz (line, sizeof(line), "%s()", f->name); break; case ev_field: def = QCC_PR_DefForFieldOfs ( *(int *)val ); QC_snprintfz (line, sizeof(line), ".%s", def->name); break; case ev_void: QC_snprintfz (line, sizeof(line), "void"); break; case ev_float: QC_snprintfz (line, sizeof(line), "%5.1f", *(float *)val); break; case ev_integer: QC_snprintfz (line, sizeof(line), "%i", *(int *)val); break; case ev_vector: QC_snprintfz (line, sizeof(line), "'%5.1f %5.1f %5.1f'", ((float *)val)[0], ((float *)val)[1], ((float *)val)[2]); break; case ev_pointer: QC_snprintfz (line, sizeof(line), "pointer"); break; default: QC_snprintfz (line, sizeof(line), "bad type %i", type); break; } return line; } /* ============ PR_GlobalString Returns a string with a description and the contents of a global, padded to 20 field width ============ */ /*char *QCC_PR_GlobalStringNoContents (gofs_t ofs) { int i; QCC_def_t *def; void *val; static char line[128]; val = (void *)&qcc_pr_globals[ofs]; def = pr_global_defs[ofs]; if (!def) // Error ("PR_GlobalString: no def for %i", ofs); QC_snprintfz (line, sizeof(line), "%i(?""?""?)", ofs); else QC_snprintfz (line, sizeof(line), "%i(%s)", ofs, def->name); i = strlen(line); for ( ; i<16 ; i++) Q_strlcat (line," ", sizeof(line)); Q_strlcat (line," ", sizeof(line)); return line; } char *QCC_PR_GlobalString (gofs_t ofs) { char *s; int i; QCC_def_t *def; void *val; static char line[128]; val = (void *)&qcc_pr_globals[ofs]; def = pr_global_defs[ofs]; if (!def) return QCC_PR_GlobalStringNoContents(ofs); if (def->initialized && def->type->type != ev_function) { s = QCC_PR_ValueString (def->type->type, &qcc_pr_globals[ofs]); QC_snprintfz (line, sizeof(line), "%i(%s)", ofs, s); } else QC_snprintfz (line, sizeof(line), "%i(%s)", ofs, def->name); i = strlen(line); for ( ; i<16 ; i++) strcat (line," "); strcat (line," "); return line; }*/ /* ============ PR_PrintOfs ============ */ /*void QCC_PR_PrintOfs (gofs_t ofs) { externs->Printf ("%s\n",QCC_PR_GlobalString(ofs)); }*/ /* ================= PR_PrintStatement ================= */ /*void QCC_PR_PrintStatement (QCC_dstatement_t *s) { int i; externs->Printf ("%4i : %4i : %s ", (int)(s - statements), statement_linenums[s-statements], pr_opcodes[s->op].opname); i = strlen(pr_opcodes[s->op].opname); for ( ; i<10 ; i++) externs->Printf (" "); if (s->op == OP_IF || s->op == OP_IFNOT) externs->Printf ("%sbranch %i",QCC_PR_GlobalString(s->a),s->b); else if (s->op == OP_GOTO) { externs->Printf ("branch %i",s->a); } else if ( (unsigned)(s->op - OP_STORE_F) < 6) { externs->Printf ("%s",QCC_PR_GlobalString(s->a)); externs->Printf ("%s", QCC_PR_GlobalStringNoContents(s->b)); } else { if (s->a) externs->Printf ("%s",QCC_PR_GlobalString(s->a)); if (s->b) externs->Printf ("%s",QCC_PR_GlobalString(s->b)); if (s->c) externs->Printf ("%s", QCC_PR_GlobalStringNoContents(s->c)); } externs->Printf ("\n"); }*/ /* ============ PR_PrintDefs ============ */ /*void QCC_PR_PrintDefs (void) { QCC_def_t *d; for (d=pr.def_head.next ; d ; d=d->next) QCC_PR_PrintOfs (d->ofs); }*/ QCC_type_t *QCC_PR_NewType (const char *name, int basictype, pbool typedefed) { if (numtypeinfos>= maxtypeinfos) QCC_Error(ERR_TOOMANYTYPES, "Too many types"); memset(&qcc_typeinfo[numtypeinfos], 0, sizeof(QCC_type_t)); qcc_typeinfo[numtypeinfos].type = basictype; qcc_typeinfo[numtypeinfos].name = name; qcc_typeinfo[numtypeinfos].num_parms = 0; qcc_typeinfo[numtypeinfos].params = NULL; qcc_typeinfo[numtypeinfos].size = type_size[basictype]; qcc_typeinfo[numtypeinfos].typedefed = typedefed; qcc_typeinfo[numtypeinfos].align = 32; //assume this alignment for now. some types allow tighter alignment, though mostly only in structs. qcc_typeinfo[numtypeinfos].filen = s_filen; qcc_typeinfo[numtypeinfos].line = pr_source_line; if (typedefed) pHash_Add(&typedeftable, name, &qcc_typeinfo[numtypeinfos], qccHunkAlloc(sizeof(bucket_t))); numtypeinfos++; return &qcc_typeinfo[numtypeinfos-1]; } /* ============== PR_BeginCompilation called before compiling a batch of files, clears the pr struct ============== */ static void QCC_PR_BeginCompilation (void *memory, int memsize) { extern int recursivefunctiontype; int i; char name[16]; pr.memory = memory; pr.max_memory = memsize; pr.def_tail = &pr.def_head; pr.local_tail = &pr.local_head; QCC_PR_ResetErrorScope(); pr_scope = NULL; /* numpr_globals = RESERVED_OFS; for (i=0 ; iaux_type = type_void; type_function = QCC_PR_NewType("__function", ev_function, false); type_function->aux_type = type_void; type_pointer = QCC_PR_NewType("__pointer", ev_pointer, false); type_integer = QCC_PR_NewType("__int32", ev_integer, true); type_uint = QCC_PR_NewType("__uint32", ev_uint, true); type_int64 = QCC_PR_NewType("__int64", ev_int64, true); type_uint64 = QCC_PR_NewType("__uint64", ev_uint64, true); type_variant = QCC_PR_NewType("__variant", ev_variant, true); type_sint8 = QCC_PR_NewType("__int8", ev_bitfld, true); type_sint8 ->parentclass = type_integer; type_sint8 ->size = type_sint8 ->parentclass->size; type_sint8 ->align = type_sint8 ->bits = 8; type_uint8 = QCC_PR_NewType("__uint8", ev_bitfld, true); type_uint8 ->parentclass = type_uint; type_uint8 ->size = type_uint8 ->parentclass->size; type_uint8 ->align = type_uint8 ->bits = 8; type_sint16 = QCC_PR_NewType("__int16", ev_bitfld, true); type_sint16->parentclass = type_integer; type_sint16->size = type_sint16->parentclass->size; type_sint16->align = type_sint16->bits = 16; type_uint16 = QCC_PR_NewType("__uint16", ev_bitfld, true); type_uint16->parentclass = type_uint; type_uint16->size = type_uint16->parentclass->size; type_uint16->align = type_uint16->bits = 16; type_invalid = QCC_PR_NewType("invalid", ev_void, false); type_floatfield = QCC_PR_NewType("__fieldfloat", ev_field, false); type_floatfield->aux_type = type_float; type_pointer->aux_type = QCC_PR_NewType("__pointeraux", ev_float, false); type_intpointer = QCC_PR_NewType("__intpointer", ev_pointer, false); type_intpointer->aux_type = type_integer; type_floatpointer = QCC_PR_NewType("__floatpointer", ev_pointer, false); type_floatpointer->aux_type = type_float; type_floatfunction = QCC_PR_NewType("__floatfunction", ev_function, false); type_floatfunction->aux_type = type_float; type_bfloat = QCC_PR_NewType("__bfloat", ev_boolean, true); type_bfloat->parentclass = type_float; //has value 0.0 or 1.0 type_bint = QCC_PR_NewType("__bint", ev_boolean, true); type_bint->parentclass = type_integer; //has value 0 or 1 //type_field->aux_type = type_float; // QCC_PR_NewType("_Bool", ev_boolean, true); // QCC_PR_NewType("bool", ev_boolean, true); // QCC_PR_NewType("__int", ev_integer, keyword_integer?true:false); QCC_PR_NewType("variant", ev_variant, true); if (*type_string->name != '_') QCC_PR_NewType("__string", ev_string, true); //make sure some core types __string always work with a double-underscore prefix if (*type_vector->name != '_') QCC_PR_NewType("__vector", ev_vector, true); //make sure some core types __string always work with a double-underscore prefix if (*type_entity->name != '_') QCC_PR_NewType("__entity", ev_entity, true); //make sure some core types __string always work with a double-underscore prefix QCC_PR_NewType("__int", ev_integer, true); QCC_PR_NewType("__uint", ev_uint, true); if (output_parms) { //this tends to confuse the brains out of decompilers. :) numpr_globals = 1; QCC_PR_GetDef(type_vector, "RETURN", NULL, true, 0, false)->referenced=true; for (i = 0; i < MAX_PARMS; i++) { QC_snprintfz (name, sizeof(name), "PARM%i", i); QCC_PR_GetDef(type_vector, name, NULL, true, 0, false)->referenced=true; } } else { numpr_globals = RESERVED_OFS; // for (i=0 ; inext = NULL; pr_error_count = 0; pr_warning_count = 0; recursivefunctiontype = 0; QCC_PrioritiseOpcodes(); } static void QCC_PR_FinishFieldDef(QCC_def_t *d) { int i; if (d->symboldata) return; //nothing to finish d->symbolsize = (d->arraysize?d->arraysize:1) * d->type->size; if (d->symbolheader != d) { QCC_PR_FinishFieldDef(d->symbolheader); d->symboldata = d->symbolheader->symboldata + d->ofs; } else { d->symboldata = qccHunkAlloc (d->symbolsize * sizeof(float)); for (i = 0; i < d->symbolsize; i++) d->symboldata[i]._int = pr.size_fields++; } } /* ============== PR_FinishCompilation called after all files are compiled to check for errors Returns false if errors were detected. ============== */ static int QCC_PR_FinishCompilation (void) { QCC_def_t *d; QCC_type_t *t; int errors; pbool externokay = false; errors = false; if (pr_error_count) return false; if (qcc_targetformat == QCF_FTE || qcc_targetformat == QCF_FTEDEBUG || qcc_targetformat == QCF_FTEH2) externokay = true; // check to make sure all functions prototyped have code for (d=pr.def_head.next ; d ; d=d->next) { if (d->type->type == ev_field && !d->symboldata) QCC_PR_FinishFieldDef(d); if (d->type->type == ev_function && d->constant && d->symbolheader == d)// function parms are ok { if (d->isextern) { if (!externokay) { QCC_PR_Warning(ERR_NOFUNC, d->filen, d->s_line, "extern is not supported with this target format"); QCC_PR_ParsePrintDef(ERR_NOFUNC, d); errors = true; } bodylessfuncs = true; } if (!d->initialized) { if (!strncmp(d->name, "ArrayGet*", 9)) { QCC_PR_EmitArrayGetFunction(d, d->generatedfor, d->name+9); pr_scope = NULL; continue; } if (!strncmp(d->name, "ArraySet*", 9)) { QCC_PR_EmitArraySetFunction(d, d->generatedfor, d->name+9); pr_scope = NULL; continue; } if (!strncmp(d->name, "spawnfunc_", 10)) { //not all of these will have a class defined, as some will be regular spawn functions, so don't error on that t = QCC_TypeForName(d->name+10); if (t && t->type == ev_entity) { QCC_PR_EmitClassFromFunction(d, t); pr_scope = NULL; continue; } } if (d->unused && !d->used) { //d->initialized = 1; continue; } QCC_PR_Warning(ERR_NOFUNC, d->filen, d->s_line, "function %s has no body",d->name); QCC_PR_ParsePrintDef(ERR_NOFUNC, d); bodylessfuncs = true; errors = true; } } } pr_scope = NULL; return !errors; } //============================================================================= // FIXME: byte swap? // this is a 16 bit, non-reflected CRC using the polynomial 0x1021 // and the initial and final xor values shown below... in other words, the // CCITT standard CRC used by XMODEM #define CRC_INIT_VALUE 0xffff #define CRC_XOR_VALUE 0x0000 static unsigned short QCC_crctable[256] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 }; static void QCC_CRC_Init(unsigned short *crcvalue) { *crcvalue = CRC_INIT_VALUE; } static void QCC_CRC_ProcessByte(unsigned short *crcvalue, pbyte data) { *crcvalue = ((*crcvalue << 8) ^ QCC_crctable[(*crcvalue >> 8) ^ data]) & 0xffff; } /*static unsigned short QCC_CRC_Value(unsigned short crcvalue) { return crcvalue ^ CRC_XOR_VALUE; }*/ //============================================================================= /* ============ PR_WriteProgdefs Writes the global and entity structures out Returns a crc of the header, to be stored in the progs file for comparison at load time. ============ */ /* char *Sva(char *msg, ...) { va_list l; static char buf[1024]; va_start(l, msg); QC_vsnprintf (buf,sizeof(buf)-1, msg, l); va_end(l); return buf; } */ #define PROGDEFS_MAX_SIZE 16384 //write (to file buf) and add to the crc static void Add_WithCRC(char *p, unsigned short *crc, char *file) { char *s; int i = strlen(file); if (i + strlen(p)+1 >= PROGDEFS_MAX_SIZE) return; for(s=p;*s;s++,i++) { QCC_CRC_ProcessByte(crc, *s); file[i] = *s; } file[i]='\0'; } #define ADD_CRC(p) Add_WithCRC(p, &crc, file) //#define ADD(p) {char *s;int i = strlen(p);for(s=p;*s;s++,i++){QCC_CRC_ProcessByte(&crc, *s);file[i] = *s;}file[i]='\0';} static void Add_CrcOnly(char *p, unsigned short *crc, char *file) { char *s; for(s=p;*s;s++) QCC_CRC_ProcessByte(crc, *s); } #define EAT_CRC(p) Add_CrcOnly(p, &crc, file) static void QCC_PR_CRCMessages(unsigned short crc) { switch (crc) { case 12923: //#pragma sourcefile usage break; case 54730: if (verbose >= VERBOSE_STANDARD) externs->Printf("Recognised progs as QuakeWorld\n"); break; case 5927: if (verbose >= VERBOSE_STANDARD) externs->Printf("Recognised progs as NetQuake server gamecode\n"); break; case 26940: if (verbose >= VERBOSE_STANDARD) externs->Printf("Recognised progs as Quake pre-release...\n"); break; case 38488: if (verbose >= VERBOSE_STANDARD) externs->Printf("Recognised progs as original Hexen2\n"); break; case 26905: if (verbose >= VERBOSE_STANDARD) externs->Printf("Recognised progs as Hexen2 Mission Pack\n"); break; case 14046: if (verbose >= VERBOSE_STANDARD) externs->Printf("Recognised progs as Hexen2 (demo)\n"); break; case 22390: //EXT_CSQC_1 if (verbose >= VERBOSE_STANDARD) externs->Printf("Recognised progs as an EXT_CSQC_1 module\n"); break; case 17105: case 32199: //outdated ext_csqc QCC_PR_Warning(WARN_SYSTEMCRC2, NULL, 0, "Recognised progs as outdated CSQC module"); break; case 52195: //this is what DP requires. don't print it as the warning that it is as that would royally piss off xonotic and their use of -Werror. if (verbose >= VERBOSE_PROGRESS) externs->Printf("Recognised progs as DP-specific CSQC module\n"); break; case 10020: if (verbose >= VERBOSE_STANDARD) externs->Printf("Recognised progs as a MenuQC module\n"); break; case 32401: QCC_PR_Warning(WARN_SYSTEMCRC, NULL, 0, "please update your tenebrae system defs."); break; default: QCC_PR_Warning(WARN_SYSTEMCRC, NULL, 0, "system defs not recognised from quake nor clones, probably buggy (sys)defs.qc"); break; } } static unsigned short QCC_PR_WriteProgdefs (char *filename) { #define ADD_ONLY(p) QC_strlcat(file, p, sizeof(file)) //no crc (later changes) char file[PROGDEFS_MAX_SIZE]; QCC_def_t *d; int f; unsigned short crc; // int c; pbool hassystemfield = false; file[0] = '\0'; QCC_CRC_Init (&crc); // print global vars until the first field is defined ADD_CRC("\n/* "); if (qcc_targetformat == QCF_HEXEN2 || qcc_targetformat == QCF_UHEXEN2 || qcc_targetformat == QCF_FTEH2) EAT_CRC("generated by hcc, do not modify"); else EAT_CRC("file generated by qcc, do not modify"); ADD_ONLY("File generated by FTEQCC, relevent for engine modding only, the generated crc must be the same as your engine expects."); ADD_CRC(" */\n\ntypedef struct"); ADD_ONLY(" globalvars_s"); ADD_CRC(qcva("\n{")); ADD_ONLY("\n\tint ofs_null;\n" "\tint ofs_return[3];\n" //makes it easier with the get globals func "\tint ofs_parm0[3];\n" "\tint ofs_parm1[3];\n" "\tint ofs_parm2[3];\n" "\tint ofs_parm3[3];\n" "\tint ofs_parm4[3];\n" "\tint ofs_parm5[3];\n" "\tint ofs_parm6[3];\n" "\tint ofs_parm7[3];\n"); EAT_CRC(qcva("\tint\tpad[%i];\n", RESERVED_OFS)); for (d=pr.def_head.next ; d ; d=d->next) { if (!strcmp (d->name, "end_sys_globals")) break; if (!*d->name) continue; // if (d->symbolheader->ofssymbolheader != d) continue; switch (d->type->type) { case ev_float: ADD_CRC(qcva("\tfloat\t%s;\n",d->name)); break; case ev_vector: ADD_CRC(qcva("\tvec3_t\t%s;\n",d->name)); if (d->deftail) d=d->deftail; // skip the elements break; case ev_string: ADD_CRC(qcva("\tstring_t\t%s;\n",d->name)); break; case ev_function: ADD_CRC(qcva("\tfunc_t\t%s;\n",d->name)); break; case ev_entity: ADD_CRC(qcva("\tint\t%s;\n",d->name)); break; case ev_integer: ADD_CRC(qcva("\tint\t%s;\n",d->name)); break; default: ADD_CRC(qcva("\tint\t%s;\n",d->name)); break; } } ADD_CRC("} globalvars_t;\n\n"); // print all fields ADD_CRC("typedef struct"); ADD_ONLY(" entvars_s"); ADD_CRC("\n{\n"); for (d=pr.def_head.next ; d ; d=d->next) { if (!strcmp (d->name, "end_sys_fields")) break; if (d->type->type != ev_field) continue; if (d->symbolheader != d) continue; switch (d->type->aux_type->type) { case ev_float: ADD_CRC(qcva("\tfloat\t%s;\n",d->name)); break; case ev_vector: ADD_CRC(qcva("\tvec3_t\t%s;\n",d->name)); if (d->deftail) d=d->deftail; // skip the elements break; case ev_string: ADD_CRC(qcva("\tstring_t\t%s;\n",d->name)); break; case ev_function: ADD_CRC(qcva("\tfunc_t\t%s;\n",d->name)); break; case ev_entity: ADD_CRC(qcva("\tint\t%s;\n",d->name)); break; case ev_integer: ADD_CRC(qcva("\tint\t%s;\n",d->name)); break; default: ADD_CRC(qcva("\tint\t%s;\n",d->name)); break; } hassystemfield = true; } if (!hassystemfield) ADD_ONLY(qcva("\tint\tplaceholder_; //no system fields\n")); ADD_CRC("} entvars_t;\n\n"); /* ///temp ADD_ONLY("//with this the crc isn't needed for fields.\n#ifdef FIELDSSTRUCT\nstruct fieldvars_s {\n\tint ofs;\n\tint type;\n\tchar *name;\n} fieldvars[] = {\n"); f=0; for (d=pr.def_head.next ; d ; d=d->next) { if (!strcmp (d->name, "end_sys_fields")) break; if (d->type->type != ev_field) continue; if (f) ADD_ONLY(",\n"); ADD_ONLY(qcva("\t{%i,\t%i,\t\"%s\"}",G_INT(d->ofs), d->type->aux_type->type, d->name)); f = 1; } ADD_ONLY("\n};\n#endif\n\n"); //end temp */ ADD_ONLY(qcva("#define PROGHEADER_CRC %i\n", crc)); if (QCC_CheckParm("-progdefs")) { externs->Printf ("writing %s\n", filename); f = SafeOpenWrite(filename, 16384); SafeWrite(f, file, strlen(file)); SafeClose(f); } if (ForcedCRC) crc = ForcedCRC; return crc; } /*void QCC_PrintFunction (char *name) { int i; QCC_dstatement_t *ds; QCC_dfunction_t *df; for (i=0 ; iPrintf ("Statements for function %s:\n", name); ds = statements + df->first_statement; while (1) { QCC_PR_PrintStatement (ds); if (!ds->op) break; ds++; } }*/ /* void QCC_PrintOfs(unsigned int ofs) { int i; bool printfunc; QCC_dstatement_t *ds; QCC_dfunction_t *df; for (i=0 ; ifirst_statement; printfunc = false; while (1) { if (!ds->op) break; if (ds->a == ofs || ds->b == ofs || ds->c == ofs) { QCC_PR_PrintStatement (ds); printfunc = true; } ds++; } if (printfunc) { QCC_PrintFunction(strings + functions[i].s_name); externs->Printf(" \n \n"); } } } */ /* ============================================================================== DIRECTORY COPYING / PACKFILE CREATION ============================================================================== */ static packfile_t pfiles[4096], *pf; static int packhandle; static int packbytes; /* =========== PackFile Copy a file into the pak file =========== */ static void QCC_PackFile (char *src, char *name) { size_t remaining; #if 1 char *f; #else int in; int count; char buf[4096]; #endif if ( (pbyte *)pf - (pbyte *)pfiles > sizeof(pfiles) ) QCC_Error (ERR_TOOMANYPAKFILES, "Too many files in pak file"); #if 1 f = FS_ReadToMem(src, &remaining); if (!f) { externs->Printf ("%64s : %7s\n", name, ""); // QCC_Error("Failed to open file %s", src); return; } pf->filepos = PRLittleLong (SafeSeek (packhandle, 0, SEEK_CUR)); pf->filelen = PRLittleLong (remaining); strcpy (pf->name, name); externs->Printf ("%64s : %7u\n", pf->name, (unsigned int)remaining); packbytes += remaining; SafeWrite (packhandle, f, remaining); FS_CloseFromMem(f); #else in = SafeOpenRead (src); remaining = filelength (in); pf->filepos = PRLittleLong (lseek (packhandle, 0, SEEK_CUR)); pf->filelen = PRLittleLong (remaining); strcpy (pf->name, name); externs->Printf ("%64s : %7u\n", pf->name, (unsigned int)remaining); packbytes += remaining; while (remaining) { if (remaining < sizeof(buf)) count = remaining; else count = sizeof(buf); SafeRead (in, buf, count); SafeWrite (packhandle, buf, count); remaining -= count; } close (in); #endif pf++; } /* =========== CopyFile Copies a file, creating any directories needed =========== */ static void QCC_CopyFile (char *src, char *dest) { /* int in, out; int remaining, count; char buf[4096]; print ("%s to %s\n", src, dest); in = SafeOpenRead (src); remaining = filelength (in); QCC_CreatePath (dest); out = SafeOpenWrite (dest, remaining+10); while (remaining) { if (remaining < sizeof(buf)) count = remaining; else count = sizeof(buf); SafeRead (in, buf, count); SafeWrite (out, buf, count); remaining -= count; } close (in); SafeClose (out); */ } /* =========== CopyFiles =========== */ static void _QCC_CopyFiles (int blocknum, int copytype, char *srcdir, char *destdir) { int i; int dirlen; unsigned short crc; packheader_t header; char name[1024]; char srcfile[1024], destfile[1024]; packbytes = 0; if (copytype == 2) { pf = pfiles; packhandle = SafeOpenWrite (destdir, 1024*1024); SafeWrite (packhandle, &header, sizeof(header)); } for (i=0 ; iPrintf ("%i files packed in %i bytes (%i crc)\n",i, packbytes, crc); } } static void QCC_CopyFiles (void) { char *s; char srcdir[1024], destdir[1024]; int p; if (verbose) { if (numsounds > 0) externs->Printf ("%3i unique precache_sounds\n", numsounds); if (nummodels > 0) externs->Printf ("%3i unique precache_models\n", nummodels); if (numtextures > 0) externs->Printf ("%3i unique precache_textures\n", numtextures); if (numfiles > 0) externs->Printf ("%3i unique precache_files\n", numfiles); } p = QCC_CheckParm ("-copy"); if (p && p < myargc-2) { // create a new directory tree strcpy (srcdir, myargv[p+1]); strcpy (destdir, myargv[p+2]); if (srcdir[strlen(srcdir)-1] != '/') strcat (srcdir, "/"); if (destdir[strlen(destdir)-1] != '/') strcat (destdir, "/"); _QCC_CopyFiles(0, 1, srcdir, destdir); return; } for ( p = 0; p < countof(QCC_Packname); p++) { s = QCC_Packname[p]; if (!*s) continue; strcpy(destdir, s); strcpy(srcdir, ""); _QCC_CopyFiles(p+1, 2, srcdir, destdir); } return; /* blocknum = 1; p = QCC_CheckParm ("-pak2"); if (p && p 0 && state) ;//already on, don't force if they already gave it an actual rate. else qcc_framerate = state?10:0; } else if (!stricmp(arg, "arithmetic-exceptions")) qccwarningaction[WARN_DIVISIONBY0] = state?WA_ERROR:WA_IGNORE; else if (!stricmp(arg, "lno")) { //currently we always try to write lno files, when filename info isn't stripped if (opt_filenames) { QCC_PR_Warning(WARN_BADPARAMS, "cmdline", 0, "Disabling -Ofilenames to satisfy -flno request"); opt_filenames = false; } } else if (!stricmp(arg, "return-assignments")) ; //should really be a warning instead else if (!stricmp(arg, "relaxed-switch")) ; //again should be a warning/werror else if (!stricmp(arg, "bail-on-werror")) ; else QCC_PR_Warning(WARN_BADPARAMS, "cmdline", 0, "Unrecognised flag parameter (%s)", myargv[i]); } } else if ( !strncmp(myargv[i], "-T", 2) || WINDOWSARG(!strncmp(myargv[i], "/T", 2)) ) { p = 0; if (!strcmp("parse", myargv[i]+2)) parseonly = true; else { if (!QCC_OPCodeSetTargetName(myargv[i]+2)) QCC_PR_Warning(WARN_BADPARAMS, "cmdline", 0, "Unrecognised target parameter (%s)", myargv[i]); } } else if ( !strnicmp(myargv[i], "-W", 2) || WINDOWSARG(!strnicmp(myargv[i], "/W", 2)) ) { const char *a = myargv[i]+2; if (!stricmp(a, "all")) { for (j = 0; j < ERR_PARSEERRORS; j++) if (qccwarningaction[j] == WA_IGNORE) { switch(j) { //these warnings do not get switched on with -Wall when using -std=gmqcc, because mods that use -Werror would screw up too much case WARN_CONSTANTCOMPARISON: case WARN_POINTLESSSTATEMENT: case WARN_OVERFLOW: case WARN_STRICTTYPEMISMATCH: case WARN_PARAMWITHNONAME: case WARN_IFSTRING_USED: // case WARN_UNINITIALIZED: case WARN_GMQCC_SPECIFIC: case WARN_SYSTEMCRC: case WARN_SYSTEMCRC2: if (qccwarningaction[WARN_GMQCC_SPECIFIC]) qccwarningaction[j] = WA_WARN; break; //these warnings require -Wextra to enable, as they're too annoying to have to fix case WARN_NOTREFERENCEDCONST: //warning about every single constant is annoying as heck. note that this includes both stuff like MOVETYPE_ and builtins. case WARN_EXTRAPRECACHE: //we can't guarentee that we can parse this correctly. this warning is thus a common false positive. its available with -Wextra, and there's intrinsics to reduce false positives. case WARN_FTE_SPECIFIC: //kinda annoying when its actually valid code. case WARN_MUTEDEPRECATEDVARIABLE: //these were explicitly muted by the user using checkbuiltin/etc to mute specific symbols. case WARN_DIVISIONBY0: //breaks xonotic, which seems to want nans. break; default: qccwarningaction[j] = WA_WARN; break; } } } else if (!stricmp(a, "extra")) { for (j = 0; j < ERR_PARSEERRORS; j++) if (qccwarningaction[j] == WA_IGNORE) qccwarningaction[j] = WA_WARN; } else if (!stricmp(a, "none")) { for (j = 0; j < ERR_PARSEERRORS; j++) qccwarningaction[j] = WA_IGNORE; } else if(!stricmp(a, "error")) { werror = true; } else if (!stricmp(a, "no-mundane")) { //disable mundane performance/efficiency/blah warnings that don't affect code. qccwarningaction[WARN_SAMENAMEASGLOBAL] = WA_IGNORE; qccwarningaction[WARN_DUPLICATEDEFINITION] = WA_IGNORE; qccwarningaction[WARN_CONSTANTCOMPARISON] = WA_IGNORE; qccwarningaction[WARN_ASSIGNMENTINCONDITIONAL] = WA_IGNORE; qccwarningaction[WARN_DEADCODE] = WA_IGNORE; qccwarningaction[WARN_NOTREFERENCEDCONST] = WA_IGNORE; qccwarningaction[WARN_NOTREFERENCED] = WA_IGNORE; qccwarningaction[WARN_POINTLESSSTATEMENT] = WA_IGNORE; qccwarningaction[WARN_ASSIGNMENTTOCONSTANTFUNC] = WA_IGNORE; qccwarningaction[WARN_BADPRAGMA] = WA_IGNORE; //C specs say that these should be ignored. We're close enough to C that I consider that a valid statement. qccwarningaction[WARN_IDENTICALPRECOMPILER] = WA_IGNORE; qccwarningaction[WARN_UNDEFNOTDEFINED] = WA_IGNORE; qccwarningaction[WARN_EXTRAPRECACHE] = WA_IGNORE; qccwarningaction[WARN_CORRECTEDRETURNTYPE] = WA_IGNORE; qccwarningaction[WARN_NOTUTF8] = WA_IGNORE; qccwarningaction[WARN_SELFNOTTHIS] = WA_IGNORE; } else { unsigned char action = WA_WARN; p = -1; if (!strnicmp(a, "error-", 6)) { a+= 6; action = WA_ERROR; } else if (!strnicmp(a, "no-", 3)) { a+=3; action = WA_IGNORE; } p = QCC_WarningForName(a); if (p >= 0) qccwarningaction[p] = action; else QCC_PR_Warning(WARN_BADPARAMS, "cmdline", 0, "Unrecognised warning parameter (%s)", myargv[i]); } } else if ( !strcmp(myargv[i], "-stdout") ) { } else if ( !strcmp(myargv[i], "-log") || !strcmp(myargv[i], "-nolog") ) { } else if ( !strcmp(myargv[i], "-max_regs") || !strcmp(myargv[i], "-max_strings") || !strcmp(myargv[i], "-max_globals") || !strcmp(myargv[i], "-max_fields") || !strcmp(myargv[i], "-max_statements") || !strcmp(myargv[i], "-max_functions") || !strcmp(myargv[i], "-max_types") || !strcmp(myargv[i], "-max_temps") || !strcmp(myargv[i], "-max_macros") ) { if (++i == myargc) QCC_PR_Warning(WARN_BADPARAMS, "cmdline", 0, "Missing value for %s arg", myargv[--i]); } else if ( !strcmp(myargv[i], "--version") ) { externs->Printf("%s\n", QCC_VersionString()); exit(EXIT_SUCCESS); } else if ( !strcmp(myargv[i], "--help") || !strcmp(myargv[i], "-help") ) ; //hacks... checked later. *sigh* else if (*myargv[i] == '-' || WINDOWSARG(*myargv[i] == '/')) QCC_PR_Warning(WARN_BADPARAMS, "cmdline", 0, "Unrecognised parameter (%s)", myargv[i]); else { if (!QCC_RegisterSourceFile(myargv[i])) QCC_PR_Warning(WARN_BADPARAMS, "cmdline", 0, "too many source filename arguments"); } } if (werror) { for (j = 0; j < ERR_PARSEERRORS; j++) if (qccwarningaction[j]) qccwarningaction[j] = WA_ERROR; } } /* ============ main ============ */ int qccmline; char *qccmsrc; //char *qccmsrc2; char qccmfilename[1024]; char qccmprogsdat[1024*2]; void QCC_FinishCompile(void); void SetEndian(void); static void QCC_SetDefaultProperties (void) { int level; int i; #ifdef _WIN32 #define FWDSLASHARGS 1 #else #define FWDSLASHARGS 0 #endif Hash_InitTable(&compconstantstable, MAX_CONSTANTS, qccHunkAlloc(Hash_BytesForBuckets(MAX_CONSTANTS))); qcc_framerate = 0; //depends on target (engine's OP_STATE) ForcedCRC = 0; defaultstatic = 0; verbose = VERBOSE_PROGRESS; *qccmsourcedir = 0; QCC_PR_CloseProcessor(); QCC_PR_DefineName("FTEQCC", NULL); QCC_PR_DefineName("__FTEQCC__", NULL); if ((FWDSLASHARGS && QCC_CheckParm("/O0")) || QCC_CheckParm("-O0")) level = 0; else if ((FWDSLASHARGS && QCC_CheckParm("/O1")) || QCC_CheckParm("-O1")) level = 1; else if ((FWDSLASHARGS && QCC_CheckParm("/O2")) || QCC_CheckParm("-O2")) level = 2; else if ((FWDSLASHARGS && QCC_CheckParm("/O3")) || QCC_CheckParm("-O3")) level = 3; else level = -1; if (level == -1) { for (i = 0; optimisations[i].enabled; i++) { if (optimisations[i].flags & FLAG_ASDEFAULT) *optimisations[i].enabled = true; else *optimisations[i].enabled = false; } } else { for (i = 0; optimisations[i].enabled; i++) { if (level >= optimisations[i].optimisationlevel) *optimisations[i].enabled = true; else *optimisations[i].enabled = false; } } { //FIXME: outdated, should be using -Tfte qcc_targetformat_t targ; if (QCC_CheckParm ("-h2")) targ = QCF_HEXEN2; else if (QCC_CheckParm ("-fte")) targ = QCF_FTE; else if (QCC_CheckParm ("-fteh2")) targ = QCF_FTEH2; else if (QCC_CheckParm ("-dp")) targ = QCF_DARKPLACES; else targ = QCF_STANDARD; QCC_OPCodeSetTarget(targ, 0); } //enable all warnings for (i = 0; i < ERR_PARSEERRORS; i++) qccwarningaction[i] = WA_WARN; for (; i < WARN_MAX; i++) qccwarningaction[i] = WA_ERROR; //play with default warnings. qccwarningaction[WARN_NOTREFERENCEDCONST] = WA_IGNORE; qccwarningaction[WARN_MACROINSTRING] = WA_IGNORE; // qccwarningaction[WARN_ASSIGNMENTTOCONSTANT] = WA_IGNORE; qccwarningaction[WARN_EXTRAPRECACHE] = WA_IGNORE; qccwarningaction[WARN_DEADCODE] = WA_IGNORE; qccwarningaction[WARN_FTE_SPECIFIC] = WA_IGNORE; qccwarningaction[WARN_DIVISIONBY0] = WA_IGNORE; qccwarningaction[WARN_MUTEDEPRECATEDVARIABLE] = WA_IGNORE; qccwarningaction[WARN_EXTENSION_USED] = WA_IGNORE; qccwarningaction[WARN_IFSTRING_USED] = WA_IGNORE; qccwarningaction[WARN_CORRECTEDRETURNTYPE] = WA_IGNORE; qccwarningaction[WARN_NOTUTF8] = WA_IGNORE; qccwarningaction[WARN_UNINITIALIZED] = WA_IGNORE; //not sure about this being ignored by default. qccwarningaction[WARN_SELFNOTTHIS] = WA_IGNORE; qccwarningaction[WARN_UNSAFELOCALPOINTER] = WA_IGNORE; //only an issue with recursion. and annoying. qccwarningaction[WARN_EVILPREPROCESSOR] = WA_ERROR; //evil people do evil things. evil must be thwarted! qccwarningaction[WARN_IDENTICALPRECOMPILER] = WA_IGNORE; qccwarningaction[WARN_DENORMAL] = WA_ERROR; //DAZ provides a speedup on modern machines, so any engine compiled for sse2+ will have problems with denormals, so make their use look serious. if (qcc_targetformat == QCF_HEXEN2 || qcc_targetformat == QCF_UHEXEN2 || qcc_targetformat == QCF_FTEH2) qccwarningaction[WARN_CASEINSENSITIVEFRAMEMACRO] = WA_IGNORE; //hexenc consides these fair game. if (QCC_CheckParm ("-Fqccx")) { qccwarningaction[WARN_DENORMAL] = WA_IGNORE; //this is just too spammy qccwarningaction[WARN_LAXCAST] = WA_IGNORE; //more plausable, but still too spammy. easier to fix at least. } //Check the command line QCC_PR_CommandLinePrecompilerOptions(); if (qcc_targetformat == QCF_HEXEN2 || qcc_targetformat == QCF_UHEXEN2 || qcc_targetformat == QCF_FTEH2) //force on the thinktime keyword if hexen2 progs. { keyword_thinktime = true; //thinktime self : 0.1; keyword_until = true; //until(cond) {code}; or do{code}until(cond); keyword_loop = true; //loop {code}; } if ((FWDSLASHARGS && QCC_CheckParm("/Debug"))) //disable any debug optimisations { for (i = 0; optimisations[i].enabled; i++) { if (optimisations[i].flags & FLAG_KILLSDEBUGGERS) *optimisations[i].enabled = false; } } } //builds a list of files, pretends that they came from a progs.src //FIXME: use sourcedir! static int QCC_FindQCFiles(const char *sourcedir) { #ifdef _WIN32 WIN32_FIND_DATA fd; HANDLE h; #endif int numfiles = 0, i, j; char *filelist[256], *temp; qccmsrc = qccHunkAlloc(8192); strcat(qccmsrc, "progs.dat\n");//"#pragma PROGS_DAT progs.dat\n"); #if defined(_WIN32) && !defined(WINRT) h = FindFirstFile("*.qc", &fd); if (h == INVALID_HANDLE_VALUE) return 0; do { filelist[numfiles] = qccHunkAlloc (strlen(fd.cFileName)+1); strcpy(filelist[numfiles], fd.cFileName); numfiles++; } while(FindNextFile(h, &fd)!=0); FindClose(h); #else externs->Printf("-Facc is not supported on this platform. Please make a progs.src file instead\n"); #endif //Sort alphabetically. //bubble. :( for (i = 0; i < numfiles-1; i++) { for (j = i+1; j < numfiles; j++) { if (stricmp(filelist[i], filelist[j]) > 0) { temp = filelist[j]; filelist[j] = filelist[i]; filelist[i] = temp; } } } for (i = 0; i < numfiles; i++) { strcat(qccmsrc, filelist[i]); strcat(qccmsrc, "\n"); // strcat(qccmsrc, "#include \""); // strcat(qccmsrc, filelist[i]); // strcat(qccmsrc, "\"\n"); } return numfiles; } static pbool QCC_GenerateRelativePath(char *dest, size_t destsize, char *base, char *relative) { int p; char *s1, *s2; if (!QC_strlcpy (dest, base, destsize)) return false; s1 = strchr(dest, '\\'); s2 = strchr(dest, '/'); if (s2 > s1) s1 = s2; if (s1) *s1 = 0; else *dest = 0; p=0; s2 = relative; for (;;) { if (!strncmp(s2, "./", 2)) s2+=2; else if(!strncmp(s2, "../", 3)) { s2+=3; p++; } else break; } for (s1=dest+strlen(dest)-1;p && s1>=dest; s1--) { if (*s1 == '/' || *s1 == '\\') { *s1 = '\0'; p--; } } if (*dest) { if (p) { //we were still looking for a separator, but didn't find one, so kill the entire path. (void)QC_strlcpy(dest, "", destsize); p--; } else if (!QC_strlcat(dest, "/", destsize)) return false; } if (!QC_strlcat(dest, s2, destsize)) return false; while (p>0) { if (strlen(dest)+3 >= destsize) return false; memmove(dest+3, dest, strlen(dest)+1); dest[0] = '.'; dest[1] = '.'; dest[2] = '/'; p--; } return true; } const char *qcccol[COL_MAX]; int qcc_compileactive = false; extern int accglobalsblock; char *originalqccmsrc; //for autoprototype. pbool QCC_main (int argc, const char **argv) //as part of the quake engine { extern int pr_bracelevel; time_t long_time; extern QCC_type_t *pr_classtype; size_t p; extern int qccpersisthunk; const char *arg; char *s; //make sure any print colours are set up properly. for (p = 0; p < COL_MAX; p++) if (!qcccol[p]) qcccol[p] = ""; s_filen = "cmdline"; s_unitn = ""; s_filed = 0; pr_source_line = 0; if (numsourcefiles && currentsourcefile == numsourcefiles) { numsourcefiles = 0; return false; } else if (!numsourcefiles) currentsourcefile = 0; if (currentsourcefile && qccpersisthunk && numsourcefiles) QCC_PR_ResetErrorScope(); //don't clear the ram if we're retaining def info else { memset(sourcefilesdefs, 0, sizeof(sourcefilesdefs)); sourcefilesnumdefs = 0; if (!PreCompile()) return false; } SetEndian(); myargc = argc; myargv = argv; pr_scope = NULL; pr_classtype = NULL; locals_marshalled = 0; qcc_compileactive = true; pHash_Get = &Hash_Get; pHash_GetNext = &Hash_GetNext; pHash_Add = &Hash_Add; pHash_RemoveData = &Hash_RemoveData; MAX_REGS = 1<<21; MAX_STRINGS = 1<<21; MAX_GLOBALS = 1<<17; MAX_FIELDS = 1<<13; MAX_STATEMENTS = 1<<20; MAX_FUNCTIONS = 1<<15; maxtypeinfos = 1<<16; MAX_CONSTANTS = 1<<12; strcpy(destfile, ""); compressoutput = 0; if ((arg = QCC_ReadParm("-max_regs"))) MAX_REGS = max(100, atoi(arg)); if ((arg = QCC_ReadParm("-max_strings"))) MAX_STRINGS = max(100, atoi(arg)); if ((arg = QCC_ReadParm("-max_globals"))) MAX_GLOBALS = max(64, atoi(arg)); if ((arg = QCC_ReadParm("-max_fields"))) MAX_FIELDS = max(0, atoi(arg)); if ((arg = QCC_ReadParm("-max_statements"))) MAX_STATEMENTS = max(1, atoi(arg)); if ((arg = QCC_ReadParm("-max_functions"))) MAX_FUNCTIONS = max(1, atoi(arg)); if ((arg = QCC_ReadParm("-max_types"))) maxtypeinfos = max(100, atoi(arg)); if ((arg = QCC_ReadParm("-max_temps"))) max_temps = max(100, atoi(arg)); if ((arg = QCC_ReadParm("-max_macros"))) MAX_CONSTANTS = max(100, atoi(arg)); //FIXME: strip this. s = externs->ReadFile("qcc.cfg", QCC_LoadFileHunkAlloc, NULL, &p, false); if (s) { while(1) { s = QCC_COM_Parse(s); if (!strcmp(qcc_token, "MAX_REGS")) { s = QCC_COM_Parse(s); MAX_REGS = atoi(qcc_token); } else if (!strcmp(qcc_token, "MAX_STRINGS")) { s = QCC_COM_Parse(s); MAX_STRINGS = atoi(qcc_token); } else if (!strcmp(qcc_token, "MAX_GLOBALS")) { s = QCC_COM_Parse(s); MAX_GLOBALS = atoi(qcc_token); } else if (!strcmp(qcc_token, "MAX_FIELDS")) { s = QCC_COM_Parse(s); MAX_FIELDS = atoi(qcc_token); } else if (!strcmp(qcc_token, "MAX_STATEMENTS")) { s = QCC_COM_Parse(s); MAX_STATEMENTS = atoi(qcc_token); } else if (!strcmp(qcc_token, "MAX_FUNCTIONS")) { s = QCC_COM_Parse(s); MAX_FUNCTIONS = atoi(qcc_token); } else if (!strcmp(qcc_token, "MAX_TYPES")) { s = QCC_COM_Parse(s); maxtypeinfos = atoi(qcc_token); } else if (!strcmp(qcc_token, "MAX_TEMPS")) { s = QCC_COM_Parse(s); max_temps = atoi(qcc_token); } else if (!strcmp(qcc_token, "CONSTANTS")) { s = QCC_COM_Parse(s); MAX_CONSTANTS = atoi(qcc_token); } else if (!s) break; else externs->Printf("Bad token in qcc.cfg file\n"); } } /* don't try to be clever else if (p < 0) { s = qccHunkAlloc(8192); sprintf(s, "MAX_REGS\t%i\r\nMAX_STRINGS\t%i\r\nMAX_GLOBALS\t%i\r\nMAX_FIELDS\t%i\r\nMAX_STATEMENTS\t%i\r\nMAX_FUNCTIONS\t%i\r\nMAX_TYPES\t%i\r\n", MAX_REGS, MAX_STRINGS, MAX_GLOBALS, MAX_FIELDS, MAX_STATEMENTS, MAX_FUNCTIONS, maxtypeinfos); externs->WriteFile("qcc.cfg", s, strlen(s)); } */ time(&long_time); strftime(QCC_copyright, sizeof(QCC_copyright), "Compiled [%Y/%m/%d]" #ifdef SVNREVISION ", by fteqcc "STRINGIFY(SVNREVISION) #endif ". ", localtime( &long_time )); (void)QC_strlcat(QCC_copyright, QCC_VersionString(), sizeof(QCC_copyright)); for (p = 0; p < 5; p++) strcpy(QCC_Packname[p], ""); for (p = 0; compiler_flag[p].enabled; p++) { *compiler_flag[p].enabled = !!(compiler_flag[p].flags & FLAG_ASDEFAULT); } parseonly = autoprototyped = autoprototype = false; QCC_SetDefaultProperties(); autoprototype |= parseonly; optres_shortenifnots = 0; optres_overlaptemps = 0; optres_noduplicatestrings = 0; optres_constantarithmatic = 0; optres_nonvec_parms = 0; optres_constant_names = 0; optres_constant_names_strings = 0; optres_precache_file = 0; optres_filenames = 0; optres_assignments = 0; optres_unreferenced = 0; optres_function_names = 0; optres_locals = 0; optres_dupconstdefs = 0; optres_return_only = 0; optres_compound_jumps = 0; // optres_comexprremoval = 0; optres_stripfunctions = 0; optres_locals_overlapping = 0; optres_logicops = 0; optres_inlines = 0; optres_test1 = 0; optres_test2 = 0; accglobalsblock = 0; tempsused = 0; QCC_PurgeTemps(); strings = (void *)qccHunkAlloc(sizeof(char) * MAX_STRINGS); strofs = 2; statements = (void *)qccHunkAlloc(sizeof(QCC_statement_t) * MAX_STATEMENTS); numstatements = 0; functions = (void *)qccHunkAlloc(sizeof(QCC_function_t) * MAX_FUNCTIONS); numfunctions=0; pr_bracelevel = 0; qcc_pr_globals = (void *)qccHunkAlloc(sizeof(float) * (MAX_REGS + MAX_LOCALS + MAX_TEMPS)); numpr_globals=0; Hash_InitTable(&typedeftable, 1024, qccHunkAlloc(Hash_BytesForBuckets(1024))); Hash_InitTable(&globalstable, MAX_REGS/2, qccHunkAlloc(Hash_BytesForBuckets(MAX_REGS/2))); Hash_InitTable(&localstable, 128, qccHunkAlloc(Hash_BytesForBuckets(128))); Hash_InitTable(&floatconstdefstable, MAX_REGS/2+1, qccHunkAlloc(Hash_BytesForBuckets(MAX_REGS/2+1))); Hash_InitTable(&stringconstdefstable, MAX_REGS/2, qccHunkAlloc(Hash_BytesForBuckets(MAX_REGS/2))); Hash_InitTable(&stringconstdefstable_trans, 1000, qccHunkAlloc(Hash_BytesForBuckets(1000))); dotranslate_count = 0; // pr_global_defs = (QCC_def_t **)qccHunkAlloc(sizeof(QCC_def_t *) * MAX_REGS); qcc_globals = (void *)qccHunkAlloc(sizeof(QCC_ddef_t) * MAX_GLOBALS); numglobaldefs=0; fields = (void *)qccHunkAlloc(sizeof(QCC_ddef_t) * MAX_FIELDS); numfielddefs=0; memset(pr_immediate_string, 0, sizeof(pr_immediate_string)); precache_sound = (void *)qccHunkAlloc(sizeof(*precache_sound)*QCC_MAX_SOUNDS); numsounds=0; precache_texture = (void *)qccHunkAlloc(sizeof(*precache_texture)*QCC_MAX_TEXTURES); numtextures=0; precache_model = (void *)qccHunkAlloc(sizeof(*precache_model)*QCC_MAX_MODELS); nummodels=0; precache_file = (void *)qccHunkAlloc(sizeof(*precache_file)*QCC_MAX_FILES); numfiles = 0; qcc_typeinfo = (void *)qccHunkAlloc(sizeof(QCC_type_t)*maxtypeinfos); numtypeinfos = 0; qcc_tempofs = qccHunkAlloc(sizeof(int) * max_temps); tempsstart = 0; bodylessfuncs=0; memset(&pr, 0, sizeof(pr)); #ifdef MAX_EXTRA_PARMS memset(&extra_parms, 0, sizeof(extra_parms)); #endif if ( QCC_CheckParm ("/?") || QCC_CheckParm ("?") || QCC_CheckParm ("-?") || QCC_CheckParm ("-help") || QCC_CheckParm ("--help")) { externs->Printf ("Compile args:\n"); // externs->Printf ("to build a clean data tree: qcc -copy \n"); // externs->Printf ("to build a clean pak file: qcc -pak \n"); // externs->Printf ("to bsp all bmodels: qcc -bspmodels \n"); externs->Printf (" -src : look for the progs.src and qc files in a different directory\n"); externs->Printf (" -srcfile : explicit path for your starting .src file\n"); externs->Printf (" -O0 : disable optimisations\n"); externs->Printf (" -O1 : optimise for size\n"); externs->Printf (" -O2 : optimise more - some behaviours may change\n"); externs->Printf (" -O3 : optimise lots - experimental or non-future-proof\n"); externs->Printf (" -O : enable an optimisation\n"); externs->Printf (" -Ono- : disable optimisations\n"); externs->Printf (" -K[no-] : activate or deactivate a keyword\n"); externs->Printf (" note that inactive keywords can still be used via __keyword\n"); externs->Printf (" -Wall : give a stupid number of warnings\n"); externs->Printf (" -T : set an output format\n"); externs->Printf (" q1, h2, qtest, fte_5768, dp, kk7\n"); externs->Printf (" -F[no-] : Enable or disable some flagged setting\n"); externs->Printf (" wasm : causes FTEQCC to dump all asm to qc.asm\n"); externs->Printf (" autoproto : enable automatic prototyping\n"); externs->Printf (" subscope : make locals specific to their subscope\n"); externs->Printf (" assumeint : don't force immediates to floats, preserving precision\n"); externs->Printf (" dumpautocvars : write a .cfg containing all autocvars, to be inserted into your mod's default.cfg file\n"); externs->Printf (" dumplocalisation : write a localisation template file\n"); externs->Printf (" dumpopcodes : write an opcodes file\n"); externs->Printf (" -D= : define a preprocessor macro via the commandline\n"); externs->Printf (" -I : specify an alternative path to search for includes\n"); externs->Printf (" -std= : change default settings to be more accepting of code for other compilers\n"); externs->Printf (" qcc(vanilla), hcc(hexen2), C, qccx, reacc(for nehahra)\n"); qcc_compileactive = false; return true; } if (flag_caseinsensitive) { externs->Printf("Compiling without case sensitivity\n"); pHash_Get = &Hash_GetInsensitive; pHash_GetNext = &Hash_GetNextInsensitive; pHash_Add = &Hash_AddInsensitive; pHash_RemoveData = &Hash_RemoveDataInsensitive; } if (*qccmsourcedir) externs->Printf ("Source directory: %s\n", qccmsourcedir); QCC_InitData (); QCC_PR_BeginCompilation ((void *)qccHunkAlloc (0x100000), 0x100000); QCC_PR_ClearGrabMacros (false); qccmsrc = NULL; if (destfile_explicit && numsourcefiles && !currentsourcefile) { //generate an internal .src file from the argument list int i; qccmsrc = qccHunkAlloc(8192); *qccmsrc = 0; for (i = 0;iFileSize(tmp) <= 0) QC_snprintfz (qccmprogsdat, sizeof(qccmprogsdat), "progs.src"); else QC_snprintfz (qccmprogsdat, sizeof(qccmprogsdat), "preprogs.src"); } numsourcefiles = 0; strcpy(sourcefileslist[numsourcefiles++], qccmprogsdat); currentsourcefile = 0; } else if (currentsourcefile == numsourcefiles || (currentsourcefile && destfile_explicit)) { //no more. qcc_compileactive = false; numsourcefiles = 0; currentsourcefile = 0; return true; } if (currentsourcefile) externs->Printf("-------------------------------------\n"); else externs->Printf("%s\n", QCC_VersionString()); QC_snprintfz (qccmprogsdat, sizeof(qccmprogsdat), "%s%s", qccmsourcedir, sourcefileslist[currentsourcefile++]); externs->Printf ("Source file: %s\n", qccmprogsdat); QC_strlcpy(compilingrootfile, qccmprogsdat, sizeof(compilingrootfile)); if (QCC_LoadFile (qccmprogsdat, (void *)&qccmsrc) == -1) { return true; } } #ifdef WRITEASM if (writeasm) { asmfile = fopen("qc.asm", "wb"); if (!asmfile) QCC_Error (ERR_INTERNAL, "Couldn't open file for asm output."); } asmfilebegun = !!asmfile; #endif newstylesource = false; if (qccmsrc[0] == '#' && qccmsrc[1] == '!') qccmsrc = strchr(qccmsrc, '\n'); //ignore the first line if it starts with a #! for unix scripts. because we can. compilingfile = qccmprogsdat; preprocessonly = false; if (QCC_CheckParm ("-E")) { pr_file_p = qccmsrc; preprocessonly = true; goto newstyle; } pr_file_p = QCC_COM_Parse(qccmsrc); if (QCC_CheckParm ("-qc")) { strcpy(destfile, qccmprogsdat); StripExtension(destfile); strcat(destfile, ".qco"); p = QCC_CheckParm ("-o"); if (!p || p >= argc-1 || argv[p+1][0] == '-') if (p && p < argc-1 ) sprintf (destfile, "%s%s", qccmsourcedir, argv[p+1]); goto newstyle; } if (*qcc_token == '#') { newstyle: if (flag_filetimes) QCC_PR_Warning(0, qccmsrc, 0, "-ffiletimes unsupported with this input"); newstylesource = true; originalqccmsrc = qccmsrc; pr_source_line = qccmline = 1; StartNewStyleCompile(); return true; } pr_source_line = qccmline = 1; pr_file_p = qccmsrc; QCC_PR_LexWhitespace(false); qccmsrc = pr_file_p; s = qccmsrc; pr_file_p = qccmsrc; QCC_PR_SimpleGetToken (); strcpy(qcc_token, pr_token); qccmsrc = pr_file_p; qccmline = pr_source_line; if (!qccmsrc) QCC_Error (ERR_NOOUTPUT, "No destination filename. qcc -help for info."); QCC_GenerateRelativePath(destfile, sizeof(destfile), qccmprogsdat, qcc_token); p = QCC_CheckParm ("-o"); if (p > 0 && p < argc-1 && argv[p+1][0] != '-') sprintf (destfile, "%s", argv[p+1]); if (flag_filetimes) { struct stat s, os; pbool modified = false; if (stat(destfile, &os) != -1) { while ((pr_file_p=QCC_COM_Parse(pr_file_p))) { if (stat(qcc_token, &s) == -1 || s.st_mtime > os.st_mtime) { externs->Printf("%s changed\n", qcc_token); modified = true; break; } } if (!modified) { externs->Printf("No changes\n"); qcc_compileactive = false; return true; } else { pr_file_p = qccmsrc; } } } externs->Printf ("outputfile: %s\n", destfile); pr_dumpasm = false; currentchunk = NULL; originalqccmsrc = qccmsrc; return true; } void new_QCC_ContinueCompile(void); //called between exe frames - won't loose net connection (is the theory)... void QCC_ContinueCompile(void) { if (!qcc_compileactive) //HEY! return; if (newstylesource) { char *ofp = pr_file_p; do { new_QCC_ContinueCompile(); } while(currentchunk); //while parsing through preprocessor, make sure nothing gets hurt. if (ofp == pr_file_p && qcc_compileactive && pr_token_type != tt_eof) QCC_Error (ERR_INTERNAL, "Syntax error\n"); return; } pr_file_p = qccmsrc; s_filen = compilingrootfile; s_filed = 0; pr_source_line = qccmline; QCC_PR_LexWhitespace(false); qccmsrc = pr_file_p; qccmline = pr_source_line; qccmsrc = QCC_COM_Parse(qccmsrc); if (!qccmsrc) { if (parseonly) { qcc_compileactive = false; if (sourcefilesnumdefs < countof(sourcefilesdefs) && qccpersisthunk) sourcefilesdefs[currentsourcefile++] = pr.def_head.next; } else { if (autoprototype) { qccmsrc = originalqccmsrc; autoprototyped = autoprototype; QCC_SetDefaultProperties(); autoprototype = false; return; } QCC_FinishCompile(); } PostCompile(); if (currentsourcefile < numsourcefiles) { if (!QCC_main(myargc, myargv)) return; } else { qcc_compileactive = false; numsourcefiles = 0; currentsourcefile = 0; } return; } if(setjmp(pr_parse_abort)) { if (++pr_error_count > MAX_ERRORS) QCC_Error (ERR_PARSEERRORS, "%i errors have occured\n", pr_error_count); return; //just try move onto the next file, gather errors. } else QCC_FindBestInclude(qcc_token, compilingrootfile, 2); /* { int includepath = 0; while(1) { if (includepath) { if (includepath > MAXINCLUDEDIRS || !*qccincludedir[includepath-1]) { QCC_GenerateRelativePath(qccmfilename, sizeof(qccmfilename), compilingrootfile, qcc_token); break; } currentfile = qccincludedir[includepath-1]; } QCC_Canonicalize(qccmfilename, sizeof(fullname), qcc_token, compilingrootfile); { extern progfuncs_t *qccprogfuncs; if (qccprogfuncs->funcs.parms->FileSize(qccmfilename) == -1) { includepath++; continue; } } break; } } QCC_GenerateRelativePath(qccmfilename, sizeof(qccmfilename), compilingrootfile, qcc_token); if (autoprototype) externs->Printf ("prototyping %s\n", qccmfilename); else { externs->Printf ("compiling %s\n", qccmfilename); } QCC_LoadFile (qccmfilename, (void *)&qccmsrc2); if (!QCC_PR_CompileFile (qccmsrc2, qccmfilename) ) QCC_Error (ERR_PARSEERRORS, "%i errors have occured\n", pr_error_count); */ } void QCC_FinishCompile(void) { pbool donesomething; int crc; // int p; currentchunk = NULL; if (setjmp(pr_parse_abort)) QCC_Error(ERR_INTERNAL, "%s", ""); s_filen = ""; s_filed = 0; pr_source_line = 0; if (!QCC_PR_FinishCompilation ()) { QCC_Error (ERR_PARSEERRORS, "compilation errors"); } /* p = QCC_CheckParm ("-asm"); if (p) { for (p++ ; p= VERBOSE_STANDARD) { externs->Printf ("Compile Complete\n\n"); if (optres_shortenifnots) externs->Printf("optres_shortenifnots %i\n", optres_shortenifnots); if (optres_overlaptemps) externs->Printf("optres_overlaptemps %i\n", optres_overlaptemps); if (optres_noduplicatestrings) externs->Printf("optres_noduplicatestrings %i\n", optres_noduplicatestrings); if (optres_constantarithmatic) externs->Printf("optres_constantarithmatic %i\n", optres_constantarithmatic); if (optres_nonvec_parms) externs->Printf("optres_nonvec_parms %i\n", optres_nonvec_parms); if (optres_constant_names) externs->Printf("optres_constant_names %i\n", optres_constant_names); if (optres_constant_names_strings) externs->Printf("optres_constant_names_strings %i\n", optres_constant_names_strings); if (optres_precache_file) externs->Printf("optres_precache_file %i\n", optres_precache_file); if (optres_filenames) externs->Printf("optres_filenames %i\n", optres_filenames); if (optres_assignments) externs->Printf("optres_assignments %i\n", optres_assignments); if (optres_unreferenced) externs->Printf("optres_unreferenced %i\n", optres_unreferenced); if (optres_locals) externs->Printf("optres_locals %i\n", optres_locals); if (optres_function_names) externs->Printf("optres_function_names %i\n", optres_function_names); if (optres_dupconstdefs) externs->Printf("optres_dupconstdefs %i\n", optres_dupconstdefs); if (optres_return_only) externs->Printf("optres_return_only %i\n", optres_return_only); if (optres_compound_jumps) externs->Printf("optres_compound_jumps %i\n", optres_compound_jumps); // if (optres_comexprremoval) // externs->Printf("optres_comexprremoval %i\n", optres_comexprremoval); if (optres_stripfunctions) externs->Printf("optres_stripfunctions %i\n", optres_stripfunctions); if (optres_locals_overlapping) externs->Printf("optres_locals_overlapping %i\n", optres_locals_overlapping); if (optres_logicops) externs->Printf("optres_logicops %i\n", optres_logicops); if (optres_inlines) externs->Printf("optres_inlines %i\n", optres_inlines); if (optres_test1) externs->Printf("optres_test1 %i\n", optres_test1); if (optres_test2) externs->Printf("optres_test2 %i\n", optres_test2); externs->Printf("numtemps %u\n", (unsigned)tempsused); } if (!flag_msvcstyle && verbose >= VERBOSE_PROGRESS) externs->Printf("Done. %i warnings\n", pr_warning_count); } qcc_compileactive = false; } extern char *pr_file_p; extern int pr_source_line; static void StartNewStyleCompile(void) { char *tmp; if (setjmp(pr_parse_abort)) { if (++pr_error_count > MAX_ERRORS) return; if (setjmp(pr_parse_abort)) return; QCC_PR_SkipToSemicolon (); if (pr_token_type == tt_eof) return; } compilingfile = qccmprogsdat; s_filen = tmp = qccHunkAlloc(strlen(compilingfile)+1); strcpy(tmp, compilingfile); if (opt_filenames) { optres_filenames += strlen(compilingfile)+1; s_filed = 0; } else s_filed = QCC_CopyString (compilingfile); pr_file_p = qccmsrc; pr_source_line = 0; QCC_PR_NewLine (false); QCC_PR_Lex (); // read first token } void new_QCC_ContinueCompile(void) { if (setjmp(pr_parse_abort)) { // if (pr_error_count != 0) { QCC_Error (ERR_PARSEERRORS, "%i errors have occured\n", pr_error_count); return; } QCC_PR_SkipToSemicolon (); if (pr_token_type == tt_eof) return; } if (pr_token_type == tt_eof) { if (pr_error_count) QCC_Error (ERR_PARSEERRORS, "%i errors have occured\n", pr_error_count); if (autoprototype && !parseonly) { char *tmp; qccmsrc = originalqccmsrc; s_filen = tmp = qccHunkAlloc(strlen(compilingfile)+1); strcpy(tmp, compilingfile); if (opt_filenames) { optres_filenames += strlen(compilingfile)+1; s_filed = 0; } else s_filed = QCC_CopyString (compilingfile); pr_file_p = qccmsrc; autoprototyped = autoprototype; QCC_SetDefaultProperties(); autoprototype = false; QCC_PR_NewLine(false); QCC_PR_Lex(); return; } else { if (!parseonly) QCC_FinishCompile(); else { if (sourcefilesnumdefs < countof(sourcefilesdefs) && qccpersisthunk) sourcefilesdefs[currentsourcefile++] = pr.def_head.next; } PostCompile(); if (!QCC_main(myargc, myargv)) { qcc_compileactive = false; return; } return; } } pr_scope = NULL; // outside all functions if (preprocessonly) { pbool white = false; static int line = 1; while(pr_token_type != tt_eof) { //if there's whitespace next, make sure we represent that if (line < pr_token_line) { //keep line numbers correct by splurging multiple newlines. while(line++ < pr_source_line) externs->Printf("\n"); } else if (white) externs->Printf(" "); externs->Printf("%s", pr_token); white = (qcc_iswhite(*pr_file_p) || (*pr_file_p == '/' && (pr_file_p[1] == '/' || pr_file_p[1] == '*'))); QCC_PR_Lex(); } QCC_PR_Lex(); return; } QCC_PR_ParseDefs (NULL, false); } /*void new_QCC_ContinueCompile(void) { char *s, *s2; if (!qcc_compileactive) //HEY! return; // compile all the files qccmsrc = QCC_COM_Parse(qccmsrc); if (!qccmsrc) { QCC_FinishCompile(); return; } s = qcc_token; strcpy (qccmfilename, qccmsourcedir); while(1) { if (!strncmp(s, "..\\", 3)) { s2 = qccmfilename + strlen(qccmfilename)-2; while (s2>=qccmfilename) { if (*s2 == '/' || *s2 == '\\') { s2[1] = '\0'; break; } s2--; } s+=3; continue; } if (!strncmp(s, ".\\", 2)) { s+=2; continue; } break; } // strcat (qccmfilename, s); // externs->Printf ("compiling %s\n", qccmfilename); // QCC_LoadFile (qccmfilename, (void *)&qccmsrc2); // if (!new_QCC_PR_CompileFile (qccmsrc2, qccmfilename) ) // QCC_Error ("Errors have occured\n"); { if (!pr.memory) QCC_Error ("PR_CompileFile: Didn't clear"); QCC_PR_ClearGrabMacros (); // clear the frame macros compilingfile = filename; pr_file_p = qccmsrc2; s_file = QCC_CopyString (filename); pr_source_line = 0; QCC_PR_NewLine (); QCC_PR_Lex (); // read first token while (pr_token_type != tt_eof) { if (setjmp(pr_parse_abort)) { if (++pr_error_count > MAX_ERRORS) return false; QCC_PR_SkipToSemicolon (); if (pr_token_type == tt_eof) return false; } pr_scope = NULL; // outside all functions QCC_PR_ParseDefs (); } } return (pr_error_count == 0); }*/ #endif fteqcc-20251105/./pr_exec.c0000644000200200001440000016663115233070110014532 0ustar twolifeusers#define PROGSUSED #include "progsint.h" //#include "editor.h" #if __STDC_VERSION__ >= 199901L #define fte_restrict restrict #elif defined(_MSC_VER) && _MSC_VER >= 1400 #define fte_restrict __restrict #else #define fte_restrict #endif #if defined(_WIN32) || defined(__DJGPP__) #include #elif defined(__unix__) && !defined(__linux__) // quick hack for the bsds and other unix systems #include #elif !defined(alloca) //alloca.h isn't present on bsd (stdlib.h should define it to __builtin_alloca, and we can check for that here). #include #endif #define HunkAlloc BADGDFG sdfhhsf FHS #define Host_Error Sys_Error // I put the following here to resolve "undefined reference to `__imp__vsnprintf'" with MinGW64 ~ Moodles #if 0//def _WIN32 #if (_MSC_VER >= 1400) //with MSVC 8, use MS extensions #define snprintf linuxlike_snprintf_vc8 void VARGS linuxlike_snprintf_vc8(char *buffer, int size, const char *format, ...) LIKEPRINTF(3); #define vsnprintf(a, b, c, d) (void)(vsnprintf_s(a, b, _TRUNCATE, c, d)) #else //msvc crap #define snprintf linuxlike_snprintf void VARGS linuxlike_snprintf(char *buffer, int size, const char *format, ...) LIKEPRINTF(3); #define vsnprintf linuxlike_vsnprintf void VARGS linuxlike_vsnprintf(char *buffer, int size, const char *format, va_list argptr); #endif #endif //cpu clock stuff (glorified rdtsc), for profile timing only #if !defined(Sys_GetClock) && defined(_WIN32) //windows has some specific functions for this (traditionally wrapping rdtsc) //note: on some systems, you may need to force cpu affinity to a single core via task manager static prclocks_t Sys_GetClock(void) { LARGE_INTEGER li; QueryPerformanceCounter(&li); return li.QuadPart; } prclocks_t Sys_GetClockRate(void) { LARGE_INTEGER li; QueryPerformanceFrequency(&li); return li.QuadPart; } #define Sys_GetClock Sys_GetClock #endif #if 0//!defined(Sys_GetClock) && defined(__unix__) //linux/unix has some annoying abstraction and shows time in nanoseconds rather than cycles. lets hope we don't waste too much time reading it. #include #if defined(_POSIX_TIMERS) && _POSIX_TIMERS >= 0 #include #ifdef CLOCK_PROCESS_CPUTIME_ID static prclocks_t Sys_GetClock(void) { struct timespec c; clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &c); return (c.tv_sec*1000000000ull) + c.tv_nsec; } #define Sys_GetClock Sys_GetClock prclocks_t Sys_GetClockRate(void) { return 1000000000ull; } #endif #endif #endif #if !defined(Sys_GetClock) && defined(__unix__) #include #define Sys_GetClock() clock() prclocks_t Sys_GetClockRate(void) { return CLOCKS_PER_SEC; } #endif #ifndef Sys_GetClock //other systems have no choice but to omit this feature in some way. this is just for profiling, so we can get away with stubs. #define Sys_GetClock() 0 prclocks_t Sys_GetClockRate(void) { return 1; } #endif //============================================================================= /* ================= PR_PrintStatement ================= */ static void PR_PrintStatement (progfuncs_t *progfuncs, int statementnum) { unsigned int op; unsigned int arg[3]; switch(current_progstate->structtype) { default: case PST_DEFAULT: case PST_QTEST: op = ((dstatement16_t*)current_progstate->statements + statementnum)->op; arg[0] = ((dstatement16_t*)current_progstate->statements + statementnum)->a; arg[1] = ((dstatement16_t*)current_progstate->statements + statementnum)->b; arg[2] = ((dstatement16_t*)current_progstate->statements + statementnum)->c; break; case PST_KKQWSV: case PST_FTE32: op = ((dstatement32_t*)current_progstate->statements + statementnum)->op; arg[0] = ((dstatement32_t*)current_progstate->statements + statementnum)->a; arg[1] = ((dstatement32_t*)current_progstate->statements + statementnum)->b; arg[2] = ((dstatement32_t*)current_progstate->statements + statementnum)->c; break; } #if !defined(MINIMAL) && !defined(OMIT_QCC) #define TYPEHINT(a) (pr_opcodes[op].type_##a) if ( (unsigned)op < OP_NUMOPS) { int i; externs->Printf ("%s ", pr_opcodes[op].opname); i = strlen(pr_opcodes[op].opname); for ( ; i<10 ; i++) externs->Printf (" "); } else #endif externs->Printf ("op%3i ", op); #ifndef TYPEHINT #define TYPEHINT(a) NULL #endif if (op == OP_IF_F || op == OP_IFNOT_F || op == OP_IF_I || op == OP_IFNOT_I || op == OP_IF_S || op == OP_IFNOT_S) externs->Printf ("%sbranch %i",PR_GlobalString(progfuncs, arg[0], TYPEHINT(a)),arg[1]); else if (op == OP_GOTO) { externs->Printf ("branch %i",arg[0]); } else if (op == OP_BOUNDCHECK) { externs->Printf ("%s",PR_GlobalString(progfuncs, arg[0], TYPEHINT(a))); externs->Printf ("%s",PR_GlobalStringImmediate(progfuncs, arg[1])); externs->Printf ("%s",PR_GlobalStringImmediate(progfuncs, arg[2])); } else if ( (unsigned)(op - OP_STORE_F) < 6) { externs->Printf ("%s",PR_GlobalString(progfuncs, arg[0], TYPEHINT(a))); externs->Printf ("%s",PR_GlobalStringNoContents(progfuncs, arg[1])); } else { if (arg[0]) externs->Printf ("%s",PR_GlobalString(progfuncs, arg[0], TYPEHINT(a))); if (arg[1]) externs->Printf ("%s",PR_GlobalString(progfuncs, arg[1], TYPEHINT(b))); if (arg[2]) externs->Printf ("%s",PR_GlobalStringNoContents(progfuncs, arg[2])); } externs->Printf ("\n"); } #ifdef _WIN32 static void VARGS QC_snprintfz (char *dest, size_t size, const char *fmt, ...) { va_list args; va_start (args, fmt); _vsnprintf (dest, size-1, fmt, args); va_end (args); //make sure its terminated. dest[size-1] = 0; } #else #define QC_snprintfz snprintf #endif void PDECL PR_GenerateStatementString (pubprogfuncs_t *ppf, int statementnum, char *out, int outlen) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; unsigned int op; unsigned int arg[3]; *out = 0; outlen--; if ((unsigned)statementnum >= current_progstate->progs->numstatements) return; switch(current_progstate->structtype) { case PST_DEFAULT: case PST_QTEST: op = ((dstatement16_t*)current_progstate->statements + statementnum)->op; arg[0] = ((dstatement16_t*)current_progstate->statements + statementnum)->a; arg[1] = ((dstatement16_t*)current_progstate->statements + statementnum)->b; arg[2] = ((dstatement16_t*)current_progstate->statements + statementnum)->c; break; case PST_KKQWSV: case PST_FTE32: op = ((dstatement32_t*)current_progstate->statements + statementnum)->op; arg[0] = ((dstatement32_t*)current_progstate->statements + statementnum)->a; arg[1] = ((dstatement32_t*)current_progstate->statements + statementnum)->b; arg[2] = ((dstatement32_t*)current_progstate->statements + statementnum)->c; break; default: return; } op = op & ~0x8000; //break points. if (current_progstate->linenums) { QC_snprintfz (out, outlen, "%3i: ", current_progstate->linenums[statementnum]); outlen -= strlen(out); out += strlen(out); } else { QC_snprintfz (out, outlen, "%3i: ", statementnum); outlen -= strlen(out); out += strlen(out); } #if !defined(MINIMAL) && !defined(OMIT_QCC) if ( (unsigned)op < OP_NUMOPS) { QC_snprintfz (out, outlen, "%-12s ", pr_opcodes[op].opname); outlen -= strlen(out); out += strlen(out); } else #endif { QC_snprintfz (out, outlen, "op%3i ", op); outlen -= strlen(out); out += strlen(out); } if (op == OP_IF_F || op == OP_IFNOT_F || op == OP_IF_I || op == OP_IFNOT_I || op == OP_IF_S || op == OP_IFNOT_S) { QC_snprintfz (out, outlen, "%sbranch %i(%+i)",PR_GlobalString(progfuncs, arg[0], TYPEHINT(a)),(short)arg[1], statementnum+(short)arg[0]); outlen -= strlen(out); out += strlen(out); } else if (op == OP_GOTO) { QC_snprintfz (out, outlen, "branch %i(%+i)",(short)arg[0], statementnum+(short)arg[0]); outlen -= strlen(out); out += strlen(out); } else if ( (unsigned)(op - OP_STORE_F) < 6) { QC_snprintfz (out, outlen, "%s",PR_GlobalString(progfuncs, arg[0], TYPEHINT(a))); outlen -= strlen(out); out += strlen(out); QC_snprintfz (out, outlen, "%s", PR_GlobalStringNoContents(progfuncs, arg[1])); outlen -= strlen(out); out += strlen(out); } else { if (arg[0]) { QC_snprintfz (out, outlen, "%s",PR_GlobalString(progfuncs, arg[0], TYPEHINT(a))); outlen -= strlen(out); out += strlen(out); } if (arg[1]) { QC_snprintfz (out, outlen, "%s",PR_GlobalString(progfuncs, arg[1], TYPEHINT(b))); outlen -= strlen(out); out += strlen(out); } if (arg[2]) { QC_snprintfz (out, outlen, "%s", PR_GlobalStringNoContents(progfuncs, arg[2])); outlen -= strlen(out); out += strlen(out); } } QC_snprintfz (out, outlen, "\n"); outlen -= 1; out += 1; } /* ============ PR_StackTrace ============ */ static void PDECL PR_PrintRelevantLocals(progfuncs_t *progfuncs) { //scan for op_address/op_load instructions within the function int st, st2; int op; dstatement16_t *st16 = current_progstate->statements; int line; if (!current_progstate->linenums || current_progstate->structtype != PST_DEFAULT) return; line = current_progstate->linenums[prinst.pr_xstatement]; for (st = prinst.pr_xfunction->first_statement; st16[st].op != OP_DONE; st++) { if (current_progstate->linenums[st] < line - 2 || current_progstate->linenums[st] > line + 2) continue; //don't go crazy with this. op = st16[st].op & ~0x8000; if (op == OP_ADDRESS || (op >= OP_LOAD_F && op <= OP_LOAD_FNC) || op == OP_LOAD_I || op == OP_LOAD_P) { ddef16_t *ent = ED_GlobalAtOfs16(progfuncs, st16[st].a); ddef16_t *fld = ED_GlobalAtOfs16(progfuncs, st16[st].b); pbool skip = false; edictrun_t *ed; unsigned int entnum; eval_t *ptr; fdef_t *fdef; fdef_t *cnfd; const char *classname; if (!ent || !fld) continue; //all this extra code to avoid printing dupes... for (st2 = st-1; st2 >= prinst.pr_xfunction->first_statement; st2--) { if (current_progstate->linenums[st2] < line - 2 || current_progstate->linenums[st2] > line + 2) continue; op = st16[st2].op & ~0x8000; if (op == OP_ADDRESS || (op >= OP_LOAD_F && op <= OP_LOAD_FNC) || op == OP_LOAD_I || op == OP_LOAD_P) if (st16[st].a == st16[st2].a && st16[st].b == st16[st2].b) { skip = true; break; } } if (skip) continue; entnum = ((eval_t *)&pr_globals[st16[st].a])->edict; if (entnum >= sv_num_edicts) { classname = "INVALID"; continue; } else { ed = PROG_TO_EDICT_PB(progfuncs, entnum); if ((unsigned int)((eval_t *)&pr_globals[st16[st].b])->_int*4u >= ed->fieldsize) continue; else ptr = (eval_t *)(((int *)edvars(ed)) + ((eval_t *)&pr_globals[st16[st].b])->_int + progfuncs->funcs.fieldadjust); cnfd = ED_FindField(progfuncs, "classname"); if (cnfd) { string_t *v = (string_t *)((char *)edvars(ed) + cnfd->ofs*4); classname = PR_StringToNative(&progfuncs->funcs, *v); } else classname = ""; } if (*classname) fdef = ED_ClassFieldAtOfs(progfuncs, ((eval_t *)&pr_globals[st16[st].b])->_int, classname); else fdef = ED_FieldAtOfs(progfuncs, ((eval_t *)&pr_globals[st16[st].b])->_int); if (fdef) externs->Printf(" %s.%s: %s\n", PR_StringToNative(&progfuncs->funcs, ent->s_name), PR_StringToNative(&progfuncs->funcs, fld->s_name), PR_ValueString(progfuncs, fdef->type, ptr, false)); else externs->Printf(" %s.%s: BAD FIELD DEF - %#x\n", PR_StringToNative(&progfuncs->funcs, ent->s_name), PR_StringToNative(&progfuncs->funcs, fld->s_name), ptr->_int); } } } void PDECL PR_StackTrace (pubprogfuncs_t *ppf, int showlocals) { progfuncs_t *progfuncs = (progfuncs_t *)ppf; const mfunction_t *f; int prnum; int i, st; int progs; int ofs; int *globalbase; int tracing = progfuncs->funcs.debug_trace; progs = -1; if (prinst.pr_depth == 0) { externs->Printf ("\n"); return; } progfuncs->funcs.debug_trace = -10; //PR_StringToNative(+via PR_ValueString) has various error conditions that we want to mute instead of causing recursive errors. //point this to the function's locals globalbase = (int *)pr_globals + prinst.pr_xfunction->parm_start + prinst.pr_xfunction->locals; for (i=prinst.pr_depth ; i>0 ; i--) { if (i == prinst.pr_depth) { f = prinst.pr_xfunction; st = prinst.pr_xstatement; prnum = prinst.pr_typecurrent; } else { f = prinst.pr_stack[i].f; st = prinst.pr_stack[i].s; prnum = prinst.pr_stack[i].progsnum; } if (!f) { externs->Printf ("\n"); } else { globalbase -= f->locals; if (prnum != progs) { progs = prnum; externs->DPrintf ("<%s>\n", pr_progstate[progs].filename); } if (!f->s_file) externs->Printf ("unknown-file : %s\n", PR_StringToNative(ppf, f->s_name)); else { if (pr_progstate[progs].linenums) externs->Printf ("%12s:%i: %s\n", PR_StringToNative(ppf, f->s_file), pr_progstate[progs].linenums[st], PR_StringToNative(ppf, f->s_name)); else externs->Printf ("%12s : %s+%i\n", PR_StringToNative(ppf, f->s_file), PR_StringToNative(ppf, f->s_name), st-f->first_statement); } //locals:0 = no locals //locals:1 = top only //locals:2 = ALL locals. if ((i == prinst.pr_depth && showlocals == 1) || showlocals >= 2) { for (ofs = 0; ofs < f->locals; ofs++) { ddef16_t *local; local = ED_GlobalAtOfs16(progfuncs, f->parm_start+ofs); if (!local) { int arg, aofs; for (arg = 0, aofs = 0; arg < f->numparms; arg++) { if (ofs >= aofs && ofs < aofs + f->parm_size[arg]) break; aofs += f->parm_size[arg]; } if (arg < f->numparms) { if (f->parm_size[arg] == 3) { //looks like a vector. print it as such externs->Printf(" arg%i(%i): [%g, %g, %g]\n", arg, f->parm_start+ofs, *(float *)(globalbase+ofs), *(float *)(globalbase+ofs+1), *(float *)(globalbase+ofs+2)); ofs += 2; } else externs->Printf(" arg%i(%i): %g===%i\n", arg, f->parm_start+ofs, *(float *)(globalbase+ofs), *(int *)(globalbase+ofs) ); } else { externs->Printf(" unk(%i): %g===%i\n", f->parm_start+ofs, *(float *)(globalbase+ofs), *(int *)(globalbase+ofs) ); } } else { externs->Printf(" %s: %s\n", PR_StringToNative(ppf, local->s_name), PR_ValueString(progfuncs, local->type, (eval_t*)(globalbase+ofs), false)); if (local->type == ev_vector) ofs+=2; } } } if (i == prinst.pr_depth) { //scan for op_address/op_load instructions within the function PR_PrintRelevantLocals(progfuncs); } if (i == prinst.pr_depth) globalbase = prinst.localstack + prinst.localstack_used; } } progfuncs->funcs.debug_trace = tracing; } /* ============================================================================ PR_ExecuteProgram The interpretation main loop ============================================================================ */ /* ==================== PR_EnterFunction Returns the new program statement counter ==================== */ static int ASMCALL PR_EnterFunction (progfuncs_t *progfuncs, mfunction_t *f, int progsnum) { int i, j, c, o; prstack_t *st; if (prinst.pr_depth == MAX_STACK_DEPTH) { PR_StackTrace (&progfuncs->funcs, false); externs->Printf ("stack overflow on call to %s (depth %i)\n", progfuncs->funcs.stringtable+f->s_name, prinst.pr_depth); //comment this out if you want the progs to try to continue anyway (could cause infinate loops) PR_AbortStack(&progfuncs->funcs); externs->Abort("Stack Overflow in %s\n", progfuncs->funcs.stringtable+f->s_name); return prinst.pr_xstatement; } st = &prinst.pr_stack[prinst.pr_depth++]; st->s = prinst.pr_xstatement; st->f = prinst.pr_xfunction; st->progsnum = progsnum; st->pushed = prinst.spushed; st->stepping = progfuncs->funcs.debug_trace; if (progfuncs->funcs.debug_trace == DEBUG_TRACE_OVER) progfuncs->funcs.debug_trace = DEBUG_TRACE_OFF; if (prinst.profiling) { st->timestamp = Sys_GetClock(); } prinst.localstack_used += prinst.spushed; //make sure the call doesn't hurt pushed pointers // save off any locals that the new function steps on (to a side place, fromwhere they are restored on exit) c = f->locals; if (prinst.localstack_used + c > LOCALSTACK_SIZE) { prinst.localstack_used -= prinst.spushed; prinst.pr_depth--; PR_RunError (&progfuncs->funcs, "PR_ExecuteProgram: locals stack overflow\n"); } for (i=0 ; i < c ; i++) prinst.localstack[prinst.localstack_used+i] = ((int *)pr_globals)[f->parm_start + i]; prinst.localstack_used += c; // copy parameters (set initial values) o = f->parm_start; for (i=0 ; inumparms ; i++) { for (j=0 ; jparm_size[i] ; j++) { ((int *)pr_globals)[o] = ((int *)pr_globals)[OFS_PARM0+i*3+j]; o++; } } prinst.pr_xfunction = f; prinst.spushed = 0; return f->first_statement - 1; // offset the s++ } /* ==================== PR_LeaveFunction ==================== */ static int ASMCALL PR_LeaveFunction (progfuncs_t *progfuncs) { int i, c; prstack_t *st; if (prinst.pr_depth <= 0) externs->Sys_Error ("prog stack underflow"); // up stack st = &prinst.pr_stack[--prinst.pr_depth]; // restore locals from the stack c = prinst.pr_xfunction->locals; prinst.localstack_used -= c; if (prinst.localstack_used < 0) PR_RunError (&progfuncs->funcs, "PR_ExecuteProgram: locals stack underflow\n"); for (i=0 ; i < c ; i++) ((int *)pr_globals)[prinst.pr_xfunction->parm_start + i] = prinst.localstack[prinst.localstack_used+i]; PR_SwitchProgsParms(progfuncs, st->progsnum); prinst.spushed = st->pushed; if (!progfuncs->funcs.debug_trace) progfuncs->funcs.debug_trace = st->stepping; if (prinst.profiling) { prclocks_t cycles; cycles = Sys_GetClock() - st->timestamp; if (cycles > prinst.profilingalert) externs->Printf("QC call to %s took over a second\n", PR_StringToNative(&progfuncs->funcs,prinst.pr_xfunction->s_name)); prinst.pr_xfunction->profiletime += cycles; prinst.pr_xfunction = st->f; if (prinst.pr_depth) prinst.pr_xfunction->profilechildtime += cycles; } else prinst.pr_xfunction = st->f; prinst.localstack_used -= prinst.spushed; return st->s; } ddef32_t *ED_FindLocalOrGlobal(progfuncs_t *progfuncs, const char *name, eval_t **val) { static ddef32_t def; ddef32_t *def32; ddef16_t *def16; int i; progstate_t *cp = current_progstate; if (!cp) return NULL; switch (cp->structtype) { case PST_DEFAULT: case PST_KKQWSV: //this gets parms fine, but not locals if (prinst.pr_xfunction) for (i = 0; i < prinst.pr_xfunction->locals; i++) { def16 = ED_GlobalAtOfs16(progfuncs, prinst.pr_xfunction->parm_start+i); if (!def16) continue; if (!strcmp(def16->s_name+progfuncs->funcs.stringtable, name)) { *val = (eval_t *)&cp->globals[prinst.pr_xfunction->parm_start+i]; //we need something like this for functions that are not the top layer // *val = (eval_t *)&localstack[localstack_used-pr_xfunction->numparms*4]; def.ofs = def16->ofs; def.s_name = def16->s_name; def.type = def16->type; return &def; } } def16 = ED_FindGlobal16(progfuncs, name); if (!def16) return NULL; def.ofs = def16->ofs; def.type = def16->type; def.s_name = def16->s_name; def32 = &def; break; case PST_QTEST: case PST_FTE32: //this gets parms fine, but not locals if (prinst.pr_xfunction) for (i = 0; i < prinst.pr_xfunction->numparms; i++) { def32 = ED_GlobalAtOfs32(progfuncs, prinst.pr_xfunction->parm_start+i); if (!def32) continue; if (!strcmp(def32->s_name+progfuncs->funcs.stringtable, name)) { *val = (eval_t *)&cp->globals[prinst.pr_xfunction->parm_start+i]; //we need something like this for functions that are not the top layer // *val = (eval_t *)&localstack[localstack_used-pr_xfunction->numparms*4]; return def32; } } def32 = ED_FindGlobal32(progfuncs, name); if (!def32) return NULL; break; default: externs->Sys_Error("Bad struct type in ED_FindLocalOrGlobal"); def32 = NULL; } *val = (eval_t *)&cp->globals[def32->ofs]; return &def; } static char *TrimString(const char *str, char *buffer, int buffersize) { int i; while (*str <= ' ' && *str>'\0') str++; for (i = 0; i < buffersize-1; i++) { if (*str <= ' ') break; buffer[i] = *str++; } buffer[i] = '\0'; return buffer; } pbool LocateDebugTerm(progfuncs_t *progfuncs, const char *key, eval_t **result, etype_t *rettype, eval_t *store) { ddef32_t *def; fdef_t *fdef; int fofs; eval_t *val = NULL, *fval=NULL; char *c, *c2; etype_t type = ev_void; struct edictrun_s *ed; // etype_t ptrtype = ev_void; if (!strncmp(key, "*(float*)", 9)) { fofs = strtoul(key+9, NULL, 0); if (fofs < 0 || fofs+3 >= prinst.addressableused) return false; *result = (eval_t*)(pr_strings + fofs); *rettype = ev_float; return true; } if (!strncmp(key, "*(int*)", 7)) { fofs = strtoul(key+7, NULL, 0); if (fofs < 0 || fofs+3 >= prinst.addressableused) return false; *result = (eval_t*)(pr_strings + fofs); *rettype = ev_integer; return true; } if (!strncmp(key, "*(string*)", 7)) { fofs = strtoul(key+7, NULL, 0); if (fofs < 0 || fofs+3 >= prinst.addressableused) return false; *result = (eval_t*)(pr_strings + fofs); *rettype = ev_string; return true; } c = strchr(key, '.'); if (c) *c = '\0'; def = ED_FindLocalOrGlobal(progfuncs, key, &val); if (!def) { if (*key == '\'') { type = ev_vector; val = store; val->_vector[0] = 0; val->_vector[1] = 0; val->_vector[2] = 0; } else if (*key == '\"') { type = ev_string; val = store; val->string = 0; } else if (atoi(key)) { type = ev_entity; val = store; val->edict = atoi(key); } } else type = def->type; if (c) *c = '.'; if (!val) { return false; } //go through ent vars c = strchr(key, '.'); while(c) { c2 = c+1; c = strchr(c2, '.'); type = type &~DEF_SAVEGLOBAL; if (current_progstate && current_progstate->types) type = current_progstate->types[type].type; if (type != ev_entity) return false; if (c)*c = '\0'; fdef = ED_FindField(progfuncs, c2); if (!fdef) { char trimmed[256]; c2 = TrimString(c2, trimmed, sizeof(trimmed)); def = ED_FindLocalOrGlobal(progfuncs, c2, &fval); if (def && def->type == ev_field) { fofs = fval->_int + progfuncs->funcs.fieldadjust; fdef = ED_FieldAtOfs(progfuncs, fofs); } } if (c)*c = '.'; if (!fdef) return false; fofs = fdef->ofs; type = fdef->type; if ((unsigned int)val->_int >= prinst.maxedicts) ed = NULL; else ed = PROG_TO_EDICT_PB(progfuncs, val->_int); if (!ed) return false; if (fofs < 0 || fofs >= (int)prinst.max_fields_size) return false; val = (eval_t *) (((char *)ed->fields) + fofs*4); } *rettype = type; *result = val; return true; } pbool PDECL PR_SetWatchPoint(pubprogfuncs_t *ppf, const char *desc, const char *location) { progfuncs_t *progfuncs = (progfuncs_t *)ppf; eval_t *val; eval_t fakeval; etype_t type; if (!location) { free(prinst.watch_name); prinst.watch_name = NULL; prinst.watch_ptr = NULL; prinst.watch_type = ev_void; return false; } if (!LocateDebugTerm(progfuncs, location, &val, &type, &fakeval)) { externs->Printf("Unable to evaluate watch term \"%s\"\n", location); return false; } if (val == &fakeval) { externs->Printf("Do you like watching paint dry?\n"); return false; } if (type == ev_vector) { externs->Printf("Unable to watch vectors. Watching the x field instead.\n"); type = ev_float; } free(prinst.watch_name); prinst.watch_name = strdup(desc); prinst.watch_ptr = val; prinst.watch_old = *prinst.watch_ptr; prinst.watch_type = type &~ DEF_SAVEGLOBAL; return true; } static const char *PR_ParseCast(const char *key, etype_t *t, pbool *isptr) { extern char *basictypenames[]; int type; *t = ev_void; *isptr = false; while(*key == ' ') key++; if (*key == '(') { key++; for (type = 0; type <= ev_variant; type++) { if (!strncmp(key, basictypenames[type], strlen(basictypenames[type]))) { key += strlen(basictypenames[type]); while(*key == ' ') key++; if (*key == '*') { *isptr = true; key++; } *t = type; break; } } if (type > ev_variant) return NULL; while(*key == ' ') key++; if (*key++ != ')') return NULL; } return key; } char *PDECL PR_EvaluateDebugString(pubprogfuncs_t *ppf, const char *key) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; static char buf[8192]; fdef_t *fdef; eval_t *val; char *assignment; etype_t type; eval_t fakeval; extern char *basictypenames[]; if (*key == '*') { int ptr; eval_t v; etype_t cast; pbool isptr; type = ev_void; key = PR_ParseCast(key+1, &cast, &isptr); if (!key || !isptr) return "(unable to evaluate)"; if (*key == '&') { if (!LocateDebugTerm(progfuncs, key+1, &val, &type, &fakeval) && val != &fakeval) return "(unable to evaluate)"; v._int = (char*)val - progfuncs->funcs.stringtable; val = &v; type = ev_pointer; } else { if (!LocateDebugTerm(progfuncs, key, &val, &type, &fakeval) && val != &fakeval) return "(unable to evaluate)"; } if (type == ev_integer || type == ev_string || type == ev_pointer) ptr = val->_int; else if (type == ev_float) ptr = val->_float; else return "(unable to evaluate)"; return PR_ValueString(progfuncs, cast, (eval_t*)(progfuncs->funcs.stringtable + ptr), true); } if (*key == '&') { if (!LocateDebugTerm(progfuncs, key+1, &val, &type, &fakeval) && val != &fakeval) return "(unable to evaluate)"; QC_snprintfz(buf, sizeof(buf), "(%s*)%#x", ((type>=10)?"???":basictypenames[type]), (unsigned int)((char*)val - progfuncs->funcs.stringtable)); return buf; } assignment = strchr(key, '='); if (assignment) *assignment = '\0'; if (!LocateDebugTerm(progfuncs, key, &val, &type, &fakeval)) return "(unable to evaluate)"; /* c = strchr(key, '.'); if (c) *c = '\0'; def = ED_FindLocalOrGlobal(progfuncs, key, &val); if (!def) { if (atoi(key)) { def = &fakedef; def->ofs = 0; def->type = ev_entity; val = &fakeval; val->edict = atoi(key); } } if (c) *c = '.'; if (!def) { return "(Bad string)"; } type = def->type; //go through ent vars c = strchr(key, '.'); while(c) { c2 = c+1; c = strchr(c2, '.'); type = type &~DEF_SAVEGLOBAL; if (current_progstate && current_progstate->types) type = current_progstate->types[type].type; if (type != ev_entity) return "'.' without entity"; if (c)*c = '\0'; fdef = ED_FindField(progfuncs, TrimString(c2)); if (c)*c = '.'; if (!fdef) return "(Bad string)"; ed = PROG_TO_EDICT(progfuncs, val->_int); if (!ed) return "(Invalid Entity)"; val = (eval_t *) (((char *)ed->fields) + fdef->ofs*4); type = fdef->type; } */ if (assignment) { char *str = assignment+1; while(*str == ' ') str++; switch (type&~DEF_SAVEGLOBAL) { case ev_string: #ifdef QCGC *(string_t *)val = PR_AllocTempString(&progfuncs->funcs, str); #else *(string_t *)val = PR_StringToProgs(&progfuncs->funcs, ED_NewString (&progfuncs->funcs, assignment, 0, true)); #endif break; case ev_float: if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) *(float*)val = strtoul(str, NULL, 0); else *(float *)val = (float)atof (str); break; case ev_integer: *(int *)val = atoi (str); break; case ev_vector: { int i; if (*str == '\'') str++; for (i = 0; i < 3; i++) { while(*str == ' ' || *str == '\t') str++; ((float *)val)[i] = strtod(str, &str); } while(*str == ' ' || *str == '\t') str++; if (*str == '\'') str++; } break; case ev_entity: if (!EDICT_NUM(progfuncs, atoi (str))) return "(invalid entity)"; *(int *)val = EDICT_TO_PROG(progfuncs, EDICT_NUM(progfuncs, atoi (str))); break; case ev_field: fdef = ED_FindField (progfuncs, str); if (!fdef) { size_t l,nl = strlen(str); *assignment = '='; strcpy(buf, "Can't find field "); l = strlen(buf); if (nl > sizeof(buf)-l-2) nl = sizeof(buf)-l-2; memcpy(buf+l, str, nl); buf[l+nl+1] = 0; return buf; } *(int *)val = G_INT(fdef->ofs); break; case ev_function: { mfunction_t *func; progsnum_t i; progsnum_t progsnum = -1; char *end; if (!strcmp(str, "0")) { *(func_t *)val = 0; break; } progsnum = strtol(str, &end, 10); if (end != str && *end == ':') str = end+1; //skip past the num: prefix else progsnum = -1; //wasn't a num: prefix func = ED_FindFunction (progfuncs, str, &i, progsnum); if (!func) { size_t l,nl = strlen(str); *assignment = '='; strcpy(buf, "Can't find function "); l = strlen(buf); if (nl > sizeof(buf)-l-2) nl = sizeof(buf)-l-2; memcpy(buf+l, str, nl); buf[l+nl+1] = 0; return buf; } *(func_t *)val = (func - pr_progstate[i].functions) | (i<<24); } break; default: break; } *assignment = '='; } QC_snprintfz(buf, sizeof(buf), "%s", PR_ValueString(progfuncs, type, val, true)); return buf; } //int EditorHighlightLine(window_t *wnd, int line); void SetExecutionToLine(progfuncs_t *progfuncs, int linenum) { int pn = prinst.pr_typecurrent; int snum; const mfunction_t *f = prinst.pr_xfunction; switch(current_progstate->structtype) { case PST_DEFAULT: case PST_QTEST: for (snum = f->first_statement; pr_progstate[pn].linenums[snum] < linenum; snum++) { if (pr_statements16[snum].op == OP_DONE) return; } break; case PST_KKQWSV: case PST_FTE32: for (snum = f->first_statement; pr_progstate[pn].linenums[snum] < linenum; snum++) { if (pr_statements32[snum].op == OP_DONE) return; } break; default: externs->Sys_Error("Bad struct type"); snum = 0; } prinst.debugstatement = snum; // EditorHighlightLine(editwnd, pr_progstate[pn].linenums[snum]); } struct sortedfunc_s { int firststatement; int firstline; }; int PDECL PR_SortBreakFunctions(const void *va, const void *vb) { const struct sortedfunc_s *a = va; const struct sortedfunc_s *b = vb; if (a->firstline == b->firstline) return 0; return a->firstline > b->firstline; } //0 clear. 1 set, 2 toggle, 3 check int PDECL PR_ToggleBreakpoint(pubprogfuncs_t *ppf, const char *filename, int linenum, int flag) //write alternate route to work by function name. { progfuncs_t *progfuncs = (progfuncs_t*)ppf; int ret=0; unsigned int fl, stline; unsigned int i, j; progstate_t *cp; mfunction_t *f; int op = 0; //warning about not being initialized before use if (!pr_progstate) return ret; for (j = 0; j < prinst.maxprogs; j++) { cp = &pr_progstate[j]; if (!cp->progs) continue; if (linenum) //linenum is set means to set the breakpoint on a file and line { struct sortedfunc_s *sortedstatements; int numfilefunctions = 0; if (!cp->linenums) continue; sortedstatements = alloca(cp->progs->numfunctions * sizeof(*sortedstatements)); //we need to use the function table in order to set breakpoints in the right file. for (f = cp->functions, fl = 0; fl < cp->progs->numfunctions; f++, fl++) { const char *fncfile = f->s_file+progfuncs->funcs.stringtable; if (fncfile[0] == '.' && fncfile[1] == '/') fncfile+=2; if (!stricmp(fncfile, filename)) { sortedstatements[numfilefunctions].firststatement = f->first_statement; if (f->first_statement < 0 || f->first_statement >= (int)cp->progs->numstatements) sortedstatements[numfilefunctions].firstline = 0; else sortedstatements[numfilefunctions].firstline = cp->linenums[f->first_statement]; numfilefunctions++; } } f = NULL; qsort(sortedstatements, numfilefunctions, sizeof(*sortedstatements), PR_SortBreakFunctions); //our functions are now in terms of ascending line numbers. for (fl = 0; fl < numfilefunctions; fl++) { for (i = sortedstatements[fl].firststatement; i < cp->progs->numstatements; i++) { if (cp->linenums[i] >= linenum) { stline = cp->linenums[i]; for (; ; i++) { if ((unsigned int)cp->linenums[i] != stline) break; switch(cp->structtype) { case PST_DEFAULT: case PST_QTEST: op = ((dstatement16_t*)cp->statements + i)->op; break; case PST_KKQWSV: case PST_FTE32: case PST_UHEXEN2: op = ((dstatement32_t*)cp->statements + i)->op; break; default: externs->Sys_Error("Bad structtype"); op = 0; } switch (flag) { default: if (op & OP_BIT_BREAKPOINT) { op &= ~OP_BIT_BREAKPOINT; ret = false; flag = 0; } else { op |= OP_BIT_BREAKPOINT; ret = true; flag = 1; } break; case 0: op &= ~OP_BIT_BREAKPOINT; ret = false; break; case 1: op |= OP_BIT_BREAKPOINT; ret = true; break; case 3: if (op & OP_BIT_BREAKPOINT) return true; } switch(cp->structtype) { case PST_DEFAULT: case PST_QTEST: ((dstatement16_t*)cp->statements + i)->op = op; break; case PST_KKQWSV: case PST_FTE32: case PST_UHEXEN2: ((dstatement32_t*)cp->statements + i)->op = op; break; default: externs->Sys_Error("Bad structtype"); op = 0; } if (ret) //if its set, only set one breakpoint statement, not all of them. break; if ((op & ~OP_BIT_BREAKPOINT) == OP_DONE) break; //give up when we see the function's done. } goto cont; //next progs } } } } else //set the breakpoint on the first statement of the function specified. { for (f = cp->functions, fl = 0; fl < cp->progs->numfunctions; f++, fl++) { if (!strcmp(f->s_name+progfuncs->funcs.stringtable, filename)) { i = f->first_statement; switch(cp->structtype) { case PST_DEFAULT: case PST_QTEST: op = ((dstatement16_t*)cp->statements + i)->op; break; case PST_KKQWSV: case PST_FTE32: case PST_UHEXEN2: op = ((dstatement32_t*)cp->statements + i)->op; break; default: externs->Sys_Error("Bad structtype"); } switch (flag) { default: if (op & 0x8000) { op &= ~0x8000; ret = false; flag = 0; } else { op |= 0x8000; ret = true; flag = 1; } break; case 0: op &= ~0x8000; ret = false; break; case 1: op |= 0x8000; ret = true; break; case 3: if (op & 0x8000) return true; } switch(cp->structtype) { case PST_DEFAULT: case PST_QTEST: ((dstatement16_t*)cp->statements + i)->op = op; break; case PST_KKQWSV: case PST_FTE32: case PST_UHEXEN2: ((dstatement32_t*)cp->statements + i)->op = op; break; default: externs->Sys_Error("Bad structtype"); } break; } } } cont: continue; } return ret; } int ShowStep(progfuncs_t *progfuncs, int statement, char *fault, pbool fatal) { //FIXME: statics are evil, but at least the lastfile pointer check _should_ isolate different vms. static unsigned int lastline = 0; static unsigned int ignorestatement = 0; static const char *lastfile = NULL; const char *file = NULL; int pn = prinst.pr_typecurrent; int i; const mfunction_t *f = prinst.pr_xfunction; int faultline; int debugaction; prinst.pr_xstatement = statement; if (!externs->useeditor) { PR_PrintStatement(progfuncs, statement); if (fatal) { progfuncs->funcs.debug_trace = DEBUG_TRACE_ABORTERROR; progfuncs->funcs.parms->Abort ("%s", fault?fault:"Debugger Abort"); } return statement; } if (f) { for(;;) //for DEBUG_TRACE_NORESUME handling { file = PR_StringToNative(&progfuncs->funcs, f->s_file); if (pr_progstate[pn].linenums) { if (lastline == pr_progstate[pn].linenums[statement] && lastfile == file && statement == ignorestatement && !fault) { ignorestatement++; return statement; //no info/same line as last time } lastline = pr_progstate[pn].linenums[statement]; } else lastline = -1; lastfile = file; faultline = lastline; debugaction = externs->useeditor(&progfuncs->funcs, lastfile, ((lastline!=-1)?&lastline:NULL), &statement, f->first_statement, fault, fatal); // if (pn != prinst.pr_typecurrent) //if they changed the line to execute, we need to find a statement that is on that line if (lastline && faultline != lastline) if (pr_progstate[pn].linenums) { switch(pr_progstate[pn].structtype) { case PST_UHEXEN2: case PST_FTE32: case PST_KKQWSV: { dstatement32_t *st = pr_progstate[pn].statements; unsigned int *lnos = pr_progstate[pn].linenums; for (i = f->first_statement; ; i++) { if (lastline == lnos[i]) { statement = i; break; } else if (lastline <= lnos[i]) break; else if (st[i].op == OP_DONE) break; } } break; case PST_DEFAULT: case PST_QTEST: { dstatement16_t *st = pr_progstate[pn].statements; unsigned int *lnos = pr_progstate[pn].linenums; for (i = f->first_statement; ; i++) { if (lastline == lnos[i]) { statement = i; break; } else if (lastline <= lnos[i]) break; else if (st[i].op == OP_DONE) break; } } } } if (debugaction == DEBUG_TRACE_NORESUME) continue; else if(debugaction == DEBUG_TRACE_ABORTERROR) progfuncs->funcs.parms->Abort ("%s", fault?fault:"Debugger Abort"); else if (debugaction == DEBUG_TRACE_OFF) { //if we're resuming, don't hit any lingering step-over triggers progfuncs->funcs.debug_trace = DEBUG_TRACE_OFF; for (i = 0; i < prinst.pr_depth; i++) prinst.pr_stack[prinst.pr_depth-1].stepping = DEBUG_TRACE_OFF; } else if (debugaction == DEBUG_TRACE_OUT) { //clear tracing for now, but ensure that it'll be reactivated once we reach the caller (if from qc) progfuncs->funcs.debug_trace = DEBUG_TRACE_OFF; if (prinst.pr_depth) prinst.pr_stack[prinst.pr_depth-1].stepping = DEBUG_TRACE_INTO; } else //some other debug action. maybe resume. progfuncs->funcs.debug_trace = debugaction; break; } } ignorestatement = statement+1; return statement; } //called by the qcvm when executing some statement that cannot be execed. int PR_HandleFault (pubprogfuncs_t *ppf, char *error, ...) { progfuncs_t *progfuncs = (progfuncs_t *)ppf; va_list argptr; char string[1024]; int resumestatement; va_start (argptr,error); Q_vsnprintf (string,sizeof(string)-1, error,argptr); va_end (argptr); PR_StackTrace (ppf, true); ppf->parms->Printf ("%s\n", string); resumestatement = ShowStep(progfuncs, prinst.pr_xstatement, string, true); if (resumestatement == 0) { PR_AbortStack(ppf); return -1; // ppf->parms->Abort ("%s", string); } return resumestatement; } /* ============ PR_RunError Aborts the currently executing function ============ */ void VARGS PR_RunError (pubprogfuncs_t *progfuncs, const char *error, ...) { va_list argptr; char string[1024]; va_start (argptr,error); Q_vsnprintf (string,sizeof(string)-1, error,argptr); va_end (argptr); // PR_PrintStatement (pr_statements + pr_xstatement); PR_StackTrace (progfuncs, true); progfuncs->parms->Printf ("\n"); //editbadfile(pr_strings + pr_xfunction->s_file, -1); progfuncs->parms->Abort ("%s", string); } pbool PR_RunWarning (pubprogfuncs_t *ppf, char *error, ...) { progfuncs_t *progfuncs = (progfuncs_t *)ppf; va_list argptr; char string[1024]; va_start (argptr,error); Q_vsnprintf (string,sizeof(string)-1, error,argptr); va_end (argptr); progfuncs->funcs.parms->Printf ("%s", string); if (prinst.pr_depth != 0) PR_StackTrace (ppf, false); if (progfuncs->funcs.debug_trace == 0) { progfuncs->funcs.debug_trace = DEBUG_TRACE_INTO; return true; } return false; } static pbool PR_ExecRunWarning (pubprogfuncs_t *ppf, int xstatement, char *error, ...) { progfuncs_t *progfuncs = (progfuncs_t *)ppf; va_list argptr; char string[1024]; prinst.pr_xstatement = xstatement; va_start (argptr,error); Q_vsnprintf (string,sizeof(string)-1, error,argptr); va_end (argptr); progfuncs->funcs.parms->Printf ("%s", string); if (prinst.pr_depth != 0) PR_StackTrace (ppf, false); if (progfuncs->funcs.debug_trace == DEBUG_TRACE_OFF) { prinst.pr_xstatement = ShowStep(progfuncs, xstatement, string, false); if (progfuncs->funcs.debug_trace != DEBUG_TRACE_OFF) return true; } return false; } //For debugging. Assumes classname field exists. const char *PR_GetEdictClassname(progfuncs_t *progfuncs, unsigned int edict) { fdef_t *cnfd = ED_FindField(progfuncs, "classname"); if (cnfd && edict < prinst.maxedicts) { string_t *v = (string_t *)((char *)edvars(PROG_TO_EDICT_PB(progfuncs, edict)) + cnfd->ofs*4); return PR_StringToNative(&progfuncs->funcs, *v); } return ""; } static pbool casecmp_f(progfuncs_t *progfuncs, eval_t *ref, eval_t *val) {return ref->_float == val->_float;} static pbool casecmp_i(progfuncs_t *progfuncs, eval_t *ref, eval_t *val) {return ref->_int == val->_int;} static pbool casecmp_v(progfuncs_t *progfuncs, eval_t *ref, eval_t *val) {return ref->_vector[0] == val->_vector[0] && ref->_vector[1] == val->_vector[1] && ref->_vector[2] == val->_vector[2];} static pbool casecmp_s(progfuncs_t *progfuncs, eval_t *ref, eval_t *val) { const char *refs = PR_StringToNative(&progfuncs->funcs, ref->string); const char *vals = PR_StringToNative(&progfuncs->funcs, val->string); return !strcmp(refs, vals);} static pbool casecmprange_f(progfuncs_t *progfuncs, eval_t *ref, eval_t *min, eval_t *max) {return ref->_float >= min->_float && ref->_float <= max->_float;} static pbool casecmprange_i(progfuncs_t *progfuncs, eval_t *ref, eval_t *min, eval_t *max) {return ref->_int >= min->_int && ref->_int <= max->_int;} static pbool casecmprange_v(progfuncs_t *progfuncs, eval_t *ref, eval_t *min, eval_t *max) {return ref->_vector[0] >= min->_vector[0] && ref->_vector[0] <= max->_vector[0] && ref->_vector[1] >= min->_vector[1] && ref->_vector[1] <= max->_vector[1] && ref->_vector[2] >= min->_vector[2] && ref->_vector[2] <= max->_vector[2];} static pbool casecmprange_bad(progfuncs_t *progfuncs, eval_t *ref, eval_t *min, eval_t *max){ PR_RunError (&progfuncs->funcs, "OP_CASERANGE type not supported");//BUG: pr_xstatement will not be correct. return false;} typedef pbool (*casecmp_t)(progfuncs_t *progfuncs, eval_t *ref, eval_t *val); typedef pbool (*casecmprange_t)(progfuncs_t *progfuncs, eval_t *ref, eval_t *min, eval_t *max); static casecmp_t casecmp[] = { casecmp_f, //float casecmp_v, //vector casecmp_s, //string casecmp_i, //ent casecmp_i //func //pointer, field, int, etc are emulated with func or something. I dunno }; static casecmprange_t casecmprange[] = { casecmprange_f, //float casecmprange_v, //vector - I'm using a bbox, not really sure what it should be casecmprange_bad, //string - should it use stof? string ranges don't relly make sense, at all. casecmprange_i, //ent - doesn't really make sense, but as ints/pointers/fields/etc might be emulated with this, allow it anyway, as an int type. casecmprange_i //func }; #define RUNAWAYCHECK() \ if (!--*runaway) \ { \ prinst.pr_xstatement = st-pr_statements; \ PR_RunError (&progfuncs->funcs, "runaway loop error\n");\ PR_StackTrace(&progfuncs->funcs,false); \ externs->Printf ("runaway loop error\n"); \ while(prinst.pr_depth > prinst.exitdepth) \ PR_LeaveFunction(progfuncs); \ prinst.spushed = 0; \ return -1; \ } #if defined(SIMPLE_QCVM) static int PR_NoDebugVM(progfuncs_t *fte_restrict progfuncs) { char stack[4*1024]; size_t ofs; strcpy(stack, "This platform does not support QC debugging\nStack Trace:"); ofs = strlen(stack); PR_SaveCallStack (progfuncs, stack, &ofs, sizeof(stack)); PR_RunError (&progfuncs->funcs, "%s", stack); return -1; } #endif static int PR_ExecuteCode16 (progfuncs_t *fte_restrict progfuncs, int s, int *fte_restrict runaway) { unsigned int switchcomparison = 0; const dstatement16_t *fte_restrict st; mfunction_t *fte_restrict newf; int i; edictrun_t *ed; eval_t *ptr; float *fte_restrict glob = pr_globals; float tmpf; int tmpi; unsigned short op; eval_t *switchref = (eval_t*)glob; unsigned int num_edicts = sv_num_edicts; #define OPA ((eval_t *)&glob[st->a]) #define OPB ((eval_t *)&glob[st->b]) #define OPC ((eval_t *)&glob[st->c]) #define INTSIZE 16 st = &pr_statements16[s]; while (progfuncs->funcs.debug_trace || prinst.watch_ptr || prinst.profiling) { #if defined(SIMPLE_QCVM) reeval16: //this can generate huge functions, so disable it on systems that can't realiably cope with such things (IE initiates an unwanted denial-of-service attack when pointed our javascript, and firefox prints a warning too) prinst.pr_xstatement = st-pr_statements16; return PR_NoDebugVM(progfuncs); #else #define DEBUGABLE #ifdef SEPARATEINCLUDES #include "execloop16d.h" #else #include "execloop.h" #endif #undef DEBUGABLE #endif } while(1) { #include "execloop.h" } #undef INTSIZE } static int PR_ExecuteCode32 (progfuncs_t *fte_restrict progfuncs, int s, int *fte_restrict runaway) { #if defined(SIMPLE_QCVM) //this can generate huge functions, so disable it on systems that can't realiably cope with such things (IE initiates an unwanted denial-of-service attack when pointed our javascript, and firefox prints a warning too) prinst.pr_xstatement = s; PR_RunError (&progfuncs->funcs, "32bit qc statement support was disabled for this platform.\n"); PR_StackTrace(&progfuncs->funcs, false); return -1; #else unsigned int switchcomparison = 0; const dstatement32_t *fte_restrict st; mfunction_t *fte_restrict newf; int i; edictrun_t *ed; eval_t *ptr; float *fte_restrict glob = pr_globals; float tmpf; int tmpi; eval_t *switchref = (eval_t*)glob; unsigned int num_edicts = sv_num_edicts; unsigned int op; #define OPA ((eval_t *)&glob[st->a]) #define OPB ((eval_t *)&glob[st->b]) #define OPC ((eval_t *)&glob[st->c]) #define INTSIZE 32 st = &pr_statements32[s]; while (progfuncs->funcs.debug_trace || prinst.watch_ptr || prinst.profiling) { #define DEBUGABLE #ifdef SEPARATEINCLUDES #include "execloop32d.h" #else #include "execloop.h" #endif #undef DEBUGABLE } while(1) { #ifdef SEPARATEINCLUDES #include "execloop32.h" #else #include "execloop.h" #endif } #undef INTSIZE #endif } /* ==================== PR_ExecuteProgram ==================== */ static void PR_ExecuteCode (progfuncs_t *progfuncs, int s) { int runaway; if (prinst.watch_ptr && prinst.watch_ptr->_int != prinst.watch_old._int) { switch(prinst.watch_type) { case ev_float: externs->Printf("Watch point \"%s\" changed by engine from %g to %g.\n", prinst.watch_name, prinst.watch_old._float, prinst.watch_ptr->_float); break; case ev_vector: externs->Printf("Watch point \"%s\" changed by engine from '%g %g %g' to '%g %g %g'.\n", prinst.watch_name, prinst.watch_old._vector[0], prinst.watch_old._vector[1], prinst.watch_old._vector[2], prinst.watch_ptr->_vector[0], prinst.watch_ptr->_vector[1], prinst.watch_ptr->_vector[2]); break; default: externs->Printf("Watch point \"%s\" changed by engine from %i to %i.\n", prinst.watch_name, prinst.watch_old._int, prinst.watch_ptr->_int); break; case ev_entity: externs->Printf("Watch point \"%s\" changed by engine from %i(%s) to %i(%s).\n", prinst.watch_name, prinst.watch_old._int, PR_GetEdictClassname(progfuncs, prinst.watch_old._int), prinst.watch_ptr->_int, PR_GetEdictClassname(progfuncs, prinst.watch_ptr->_int)); break; case ev_function: case ev_string: externs->Printf("Watch point \"%s\" set by engine to %s.\n", prinst.watch_name, PR_ValueString(progfuncs, prinst.watch_type, prinst.watch_ptr, false)); break; } prinst.watch_old = *prinst.watch_ptr; //we can't dump stack or anything, as we don't really know the stack frame that it happened in. //stop watching // prinst->watch_ptr = NULL; } #ifdef QCJIT if (current_progstate->jit) { PR_EnterJIT(progfuncs, current_progstate->jit, s); return; } #endif runaway = 100000000; for(;;) { switch (current_progstate->structtype) { case PST_DEFAULT: case PST_QTEST: s = PR_ExecuteCode16(progfuncs, s, &runaway); if (s == -1) return; continue; case PST_KKQWSV: case PST_FTE32: case PST_UHEXEN2: s = PR_ExecuteCode32(progfuncs, s, &runaway); if (s == -1) return; continue; default: externs->Sys_Error("PR_ExecuteProgram - bad structtype"); } } } #if defined(__GNUC__) && defined(__GLIBC__) #define __USE_GNU #include #endif void PDECL PR_ExecuteProgram (pubprogfuncs_t *ppf, func_t fnum) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; mfunction_t *f; int i; unsigned int initial_progs; int oldexitdepth; int s; #ifndef QCGC int tempdepth; #endif unsigned int newprogs = (fnum & 0xff000000)>>24; initial_progs = prinst.pr_typecurrent; if (newprogs != initial_progs) { if (newprogs >= prinst.maxprogs || !pr_progstate[newprogs].globals) //can happen with hexen2... { externs->Printf("PR_ExecuteProgram: tried branching into invalid progs (%#"pPRIx")\n", fnum); return; } PR_SwitchProgsParms(progfuncs, newprogs); } if (!(fnum & ~0xff000000) || (signed)(fnum & ~0xff000000) >= pr_progs->numfunctions) { // if (pr_global_struct->self) // ED_Print (PROG_TO_EDICT(pr_global_struct->self)); #if defined(__GNUC__) && defined(__GLIBC__) Dl_info info; void *caller = __builtin_return_address(0); dladdr(caller, &info); externs->Printf("PR_ExecuteProgram: NULL function from %s+%p(%s)\n", info.dli_fname, (void*)((intptr_t)caller - (intptr_t)info.dli_fbase), info.dli_sname?info.dli_sname:""); #elif defined(__GNUC__) && !defined(FTE_TARGET_WEB) externs->Printf("PR_ExecuteProgram: NULL function from exe (address %p)\n", __builtin_return_address(0)); #else externs->Printf("PR_ExecuteProgram: NULL function from exe\n"); #endif // Host_Error ("PR_ExecuteProgram: NULL function from exe"); // PR_MoveParms(0, pr_typecurrent); PR_SwitchProgs(progfuncs, initial_progs); return; } oldexitdepth = prinst.exitdepth; f = &pr_cp_functions[fnum & ~0xff000000]; if (f->first_statement < 0) { // negative statements are built in functions i = -f->first_statement; if (i < externs->numglobalbuiltins) (*externs->globalbuiltins[i]) (&progfuncs->funcs, (struct globalvars_s *)current_progstate->globals); else { externs->Printf ("Bad builtin call number %i (from exe)\n", -f->first_statement); // PR_MoveParms(p, pr_typecurrent); PR_SwitchProgs(progfuncs, initial_progs); } PR_SwitchProgsParms(progfuncs, initial_progs); return; } //forget about any tracing if its active. control returning to the engine should not look like its calling some random function. progfuncs->funcs.debug_trace = DEBUG_TRACE_OFF; // make a stack frame prinst.exitdepth = prinst.pr_depth; s = PR_EnterFunction (progfuncs, f, initial_progs); #ifndef QCGC tempdepth = prinst.numtempstringsstack; #endif PR_ExecuteCode(progfuncs, s); PR_SwitchProgsParms(progfuncs, initial_progs); #ifndef QCGC PR_FreeTemps(progfuncs, tempdepth); prinst.numtempstringsstack = tempdepth; #else if (!prinst.pr_depth) PR_RunGC(progfuncs); #endif prinst.exitdepth = oldexitdepth; } typedef struct { int fnum; int progsnum; int statement; int spushed; } qcthreadstack_t; typedef struct qcthread_s { int fstackdepth; qcthreadstack_t fstack[MAX_STACK_DEPTH]; int lstackused; int lstack[LOCALSTACK_SIZE]; } qcthread_t; struct qcthread_s *PDECL PR_ForkStack(pubprogfuncs_t *ppf) { //QC code can call builtins that call qc code. //to get around the problems of restoring the builtins we simply don't save the thread over the builtin. //this may be an error when OP_PUSH has been used. progfuncs_t *progfuncs = (progfuncs_t*)ppf; int i, pushed; int ed = prinst.exitdepth; int localsoffset, baselocalsoffset; qcthread_t *thread = externs->memalloc(sizeof(qcthread_t)); const mfunction_t *f; int curprogs = ppf->callprogs; //notes: //pr_stack[prinst.exitdepth] is a dummy entry, with null function refs. we don't care about it. //pr_stack[pr_depth] is technically invalid but logically required - it refers to the current function instead. stoopid extra indirection. //[pr_depth] is the qc function that called whichever builtin we're executing right now so we want that (logical) one. [0] is irrelevant though. //entering a function copys its locals into the local stack for restoration on return, any OP_PUSHED stuff within the frame is then after that. //OP_PUSH/'pushed' is the PARENT function's pushes. f->locals stuff is the CHILD function's pushes. //copy out the functions stack. for (i = 1,localsoffset=0; i <= ed; i++) { if (i == prinst.pr_depth) { localsoffset += prinst.spushed; f = prinst.pr_xfunction; localsoffset += f->locals; } else { localsoffset += prinst.pr_stack[i].pushed; f = prinst.pr_stack[i].f; if (f) localsoffset += f->locals; } } //now we can start copying the stack. baselocalsoffset = localsoffset; thread->fstackdepth = 0; for (; i <= prinst.pr_depth; i++) { if (i == prinst.pr_depth) { //top of the stack. whichever function called the builtin we're executing. thread->fstack[thread->fstackdepth].fnum = prinst.pr_xfunction - pr_progstate[curprogs].functions; thread->fstack[thread->fstackdepth].progsnum = curprogs; thread->fstack[thread->fstackdepth].statement = prinst.pr_xstatement; thread->fstack[thread->fstackdepth].spushed = prinst.spushed; thread->fstackdepth++; localsoffset += prinst.spushed; f = prinst.pr_xfunction; localsoffset += f->locals; } else { thread->fstack[thread->fstackdepth].fnum = prinst.pr_stack[i].f - pr_progstate[prinst.pr_stack[i].progsnum].functions; thread->fstack[thread->fstackdepth].progsnum = prinst.pr_stack[i].progsnum; thread->fstack[thread->fstackdepth].statement = prinst.pr_stack[i].s; thread->fstack[thread->fstackdepth].spushed = prinst.pr_stack[i].pushed; thread->fstackdepth++; localsoffset += prinst.pr_stack[i].pushed; f = prinst.pr_stack[i].f; localsoffset += f->locals; } } //we now know how many locals we need... but life is not easy. //preserving on entry means the definitive location is the pr_globals - and they'll have been 'corrupted' by any child functions. //so we need to unwind(rewind) them here to find their proper 'current' values, so that resumption can rebuild the execution stack on resume to revert them properly to their prior values. messy. for (i = prinst.pr_depth; i > ed ; i--) { if (i == prinst.pr_depth) f = prinst.pr_xfunction, pushed = prinst.spushed; else f = prinst.pr_stack[i].f, pushed = prinst.pr_stack[i].pushed; //preseve any OP_PUSH stuff localsoffset -= pushed; memcpy(&thread->lstack[localsoffset-baselocalsoffset], prinst.localstack+localsoffset, pushed*sizeof(int)); //preserve the current locals localsoffset -= f->locals; memcpy(&thread->lstack[localsoffset-baselocalsoffset], ((int *)pr_globals)+f->parm_start, f->locals*sizeof(int)); //unwind the locals so parent functions we preserve have the proper current values. memcpy(((int *)pr_globals)+f->parm_start, prinst.localstack+localsoffset, f->locals*sizeof(int)); } //rewind the locals so they don't get corrupt when returning from fork etc. for (i = ed+1; i <= prinst.pr_depth ; i++) //we need to get the locals back to how they were. { if (i == prinst.pr_depth) f = prinst.pr_xfunction, pushed = prinst.spushed; else f = prinst.pr_stack[i].f, pushed = prinst.pr_stack[i].pushed; memcpy(((int *)pr_globals)+f->parm_start, &thread->lstack[localsoffset-baselocalsoffset], f->locals*sizeof(int)); localsoffset += f->locals; localsoffset += pushed; //we didn't need to clobber this, just skip it to avoid corrupting. } thread->lstackused = localsoffset - baselocalsoffset; return thread; } void PDECL PR_ResumeThread (pubprogfuncs_t *ppf, struct qcthread_s *thread) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; mfunction_t *f; int i; progsnum_t initial_progs = prinst.pr_typecurrent; unsigned int initial_stack; int oldexitdepth; int *glob; #ifndef QCGC int tempdepth; #endif if (prinst.localstack_used + thread->lstackused > LOCALSTACK_SIZE) PR_RunError(&progfuncs->funcs, "Too many locals on resumtion of QC thread\n"); if (prinst.pr_depth + thread->fstackdepth > MAX_STACK_DEPTH) PR_RunError(&progfuncs->funcs, "Too large stack on resumtion of QC thread\n"); //do progs switching stuff as appropriate. (fteqw only) oldexitdepth = prinst.exitdepth; prinst.exitdepth = prinst.pr_depth; initial_stack = prinst.localstack_used; //add on the callstack. for (i = 0; i < thread->fstackdepth; i++) { //make the new stack frame from the working values so stuff gets restored properly... prinst.pr_stack[prinst.pr_depth].f = prinst.pr_xfunction; prinst.pr_stack[prinst.pr_depth].s = prinst.pr_xstatement; prinst.pr_stack[prinst.pr_depth].progsnum = prinst.pr_typecurrent; prinst.pr_stack[prinst.pr_depth].pushed = prinst.spushed; prinst.pr_depth++; //restore the OP_PUSH data memcpy(&prinst.localstack[prinst.localstack_used], &thread->lstack[prinst.localstack_used-initial_stack], prinst.spushed*sizeof(int)); prinst.localstack_used += prinst.spushed; //and refill the working values from the inbound stack PR_SwitchProgs(progfuncs, thread->fstack[i].progsnum); prinst.pr_xfunction = pr_progstate[thread->fstack[i].progsnum].functions + thread->fstack[i].fnum; prinst.pr_xstatement = thread->fstack[i].statement; prinst.spushed = thread->fstack[i].spushed; f = pr_progstate[thread->fstack[i].progsnum].functions + thread->fstack[i].fnum; glob = (int*)pr_progstate[thread->fstack[i].progsnum].globals; //copy the 'new' function's current globals into the local stack for restoration memcpy(&prinst.localstack[prinst.localstack_used], &glob[f->parm_start], sizeof(int)*f->locals); //and overwrite them with the saved values. memcpy(&glob[f->parm_start], &thread->lstack[prinst.localstack_used-initial_stack], sizeof(int)*f->locals); prinst.localstack_used += f->locals; } if (prinst.localstack_used-initial_stack != thread->lstackused) PR_RunError(&progfuncs->funcs, "Thread stores incorrect locals count\n"); #ifndef QCGC tempdepth = prinst.numtempstringsstack; #endif PR_ExecuteCode(progfuncs, prinst.pr_xstatement); PR_SwitchProgsParms(progfuncs, initial_progs); //just in case #ifndef QCGC PR_FreeTemps(progfuncs, tempdepth); prinst.numtempstringsstack = tempdepth; #endif prinst.exitdepth = oldexitdepth; } void PDECL PR_AbortStack (pubprogfuncs_t *ppf) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; while(prinst.pr_depth > prinst.exitdepth) PR_LeaveFunction(progfuncs); prinst.pr_xstatement = -1; //should make the loop abort. } pbool PDECL PR_GetBuiltinCallInfo (pubprogfuncs_t *ppf, int *builtinnum, char *function, size_t sizeoffunction) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; int st = prinst.pr_xstatement; int op; int a; const char *fname; switch (current_progstate->structtype) { case PST_DEFAULT: case PST_QTEST: op = pr_statements16[st].op; a = pr_statements16[st].a; break; case PST_KKQWSV: case PST_FTE32: op = pr_statements32[st].op; a = pr_statements32[st].a; break; default: op = OP_DONE; a = 0; break; } *builtinnum = 0; *function = 0; if ((op >= OP_CALL0 && op <= OP_CALL8) || (op >= OP_CALL1H && op <= OP_CALL8H)) { a = ((eval_t *)&pr_globals[a])->function; *builtinnum = -current_progstate->functions[a].first_statement; fname = PR_StringToNative(ppf, current_progstate->functions[a].s_name); strncpy(function, fname, sizeoffunction-1); function[sizeoffunction-1] = 0; return true; } return false; } fteqcc-20251105/./hash.h0000644000200200001440000000372415233070110014026 0ustar twolifeusers//============================= //David's hash tables //string based. #ifndef HASH_H__ #define HASH_H__ #define Hash_BytesForBuckets(b) (sizeof(bucket_t*)*(b)) #define STRCMP(s1,s2) (((*s1)!=(*s2)) || strcmp(s1,s2)) //saves about 2-6 out of 120 - expansion of idea from fastqcc typedef struct bucket_s { void *data; union { const char *string; unsigned int value; } key; struct bucket_s *next; } bucket_t; typedef struct hashtable_s { unsigned int numbuckets; bucket_t **bucket; } hashtable_t; void Hash_InitTable(hashtable_t *table, unsigned int numbucks, void *mem); //mem must be 0 filled. (memset(mem, 0, size)) void *Hash_Enumerate(hashtable_t *table, void (*callback) (void *ctx, void *data), void *ctx); unsigned int Hash_Key(const char *name, unsigned int modulus); void *Hash_GetIdx(hashtable_t *table, unsigned int idx); void *Hash_Get(hashtable_t *table, const char *name); void *Hash_GetInsensitive(hashtable_t *table, const char *name); void *Hash_GetInsensitiveBucket(hashtable_t *table, const char *name); void *Hash_GetKey(hashtable_t *table, unsigned int key); void *Hash_GetNext(hashtable_t *table, const char *name, void *old); void *Hash_GetNextInsensitive(hashtable_t *table, const char *name, void *old); void *Hash_GetNextKey(hashtable_t *table, unsigned int key, void *old); void *Hash_Add(hashtable_t *table, const char *name, void *data, bucket_t *buck); void *Hash_AddInsensitive(hashtable_t *table, const char *name, void *data, bucket_t *buck); void Hash_Remove(hashtable_t *table, const char *name); void Hash_RemoveData(hashtable_t *table, const char *name, void *data); void Hash_RemoveDataInsensitive(hashtable_t *table, const char *name, void *data); void Hash_RemoveBucket(hashtable_t *table, const char *name, bucket_t *data); void Hash_RemoveKey(hashtable_t *table, unsigned int key); void Hash_RemoveDataKey(hashtable_t *table, unsigned int key, void *data); void *Hash_AddKey(hashtable_t *table, unsigned int key, void *data, bucket_t *buck); #endif fteqcc-20251105/./qccguistuff.c0000644000200200001440000006576415233070110015435 0ustar twolifeusers#include "qcc.h" #include "gui.h" #ifndef _WIN32 #include #endif #if defined(_WIN32) || defined(__DJGPP__) #include #elif defined(__unix__) && !defined(__linux__) // quick hack for the bsds and other unix systems #include #else #include #endif //common gui things pbool fl_nondfltopts; pbool fl_hexen2; pbool fl_ftetarg; pbool fl_compileonstart; pbool fl_showall; pbool fl_log; pbool fl_extramargins; int fl_tabsize; char parameters[16384]; char progssrcname[256]; char progssrcdir[256]; char enginebinary[MAX_OSPATH]; char enginebasedir[MAX_OSPATH]; char enginecommandline[8192]; //for finding symbol keywords extern QCC_def_t *sourcefilesdefs[]; extern int sourcefilesnumdefs; int Grep(const char *filename, const char *string) { int foundcount = 0; char *last, *found, *linestart; int line = 1; size_t sz; char *raw, *buf; pbool dofree; int origfmt; if (!filename) return foundcount; raw = GUIReadFile(filename, NULL, NULL, &sz, false); if (!raw) return foundcount; if (raw[sz] != 0) return foundcount; //error.... buf = QCC_SanitizeCharSet(raw, &sz, &dofree, &origfmt); linestart = last = found = buf; while ((found = QC_strcasestr(found, string))) { while (last < found) { if (*last++ == '\n') { line++; linestart = last; } } while (*found && *found != '\n') found++; if (*found) *found++ = '\0'; GUIprintf("%s:%i: %s\n", filename, line, linestart); line++; linestart = found; foundcount++; } if (dofree) free(buf); free(raw); return foundcount; } void GoToDefinition(const char *name) { #define MAXSOURCEFILESLIST 8 extern QCC_def_t *sourcefilesdefs[MAXSOURCEFILESLIST]; extern int sourcefilesnumdefs; int fno; QCC_def_t *def, *guess; QCC_function_t *fnc; const char *strip; //trim whitespace (for convieniance). while (*name <= ' ' && *name) name++; for (strip = name + strlen(name)-1; strip > name; strip--) { if (*strip <= ' ') continue; else //got some part of a word break; } if (*strip <= ' ') { char *t = alloca(strip-name+1); memcpy(t, name, strip-t); t[strip-t] = 0; name = t; } if (!globalstable.numbuckets) { GUI_DialogPrint("Not found", "You need to compile first."); return; } def = QCC_PR_GetDef(NULL, name, NULL, false, 0, false); //no exact match, see if we can get a case-insensitive match if (!def && *name) { for (fno = 0; fno < sourcefilesnumdefs; fno++) { for (def = sourcefilesdefs[fno]; def; def = def->next) { if (def->scope) continue; //ignore locals, because we don't know where we are, and they're probably irrelevent. if (!QC_strcasecmp(def->name, name)) { fno = sourcefilesnumdefs; break; } } } } //no exact match, see if we can get a partial if (!def && *name) { int prefixlen = strlen(name); for (fno = 0; fno < sourcefilesnumdefs; fno++) { for (guess = sourcefilesdefs[fno]; guess; guess = guess->next) { if (guess->scope) continue; //ignore locals, because we don't know where we are, and they're probably irrelevent. //make sure it has the right prefix if (!QC_strncasecmp(guess->name, name, prefixlen)) { if (guess->type->type == ev_function && guess->constant && !guess->arraysize) { //if we found a function, use that one above all others. def = guess; fno = sourcefilesnumdefs; break; } else if (!def) def = guess; } } } } if (def) { //with functions, the def is the prototype. //we want the body, so zoom to the first statement of the function instead if (def->type->type == ev_function && def->constant && !def->arraysize) { int fnum = def->symboldata[0].function; if (fnum > 0 && fnum < numfunctions) { fnc = &functions[fnum]; if (fnc->code>=0 && fnc->filen) { EditFile(fnc->filen, fnc->line, false); return; } } } if (!def->filen) { char msgbuffer[2048]; QC_snprintfz(msgbuffer, sizeof(msgbuffer), "Global definition of \"%s\" was not specified.", name); GUI_DialogPrint("Not found", msgbuffer); } else EditFile(def->filen, def->s_line-1, false); } else { char msgbuffer[2048]; QC_snprintfz(msgbuffer, sizeof(msgbuffer), "Global instance of \"%s\" was not found.", name); GUI_DialogPrint("Not found", msgbuffer); } } pbool GenAutoCompleteList(char *prefix, char *buffer, int buffersize) { QCC_def_t *def; int prefixlen = strlen(prefix); int usedbuffer = 0; int l; int fno; for (fno = 0; fno < sourcefilesnumdefs; fno++) { for (def = sourcefilesdefs[fno]; def; def = def->next) { if (def->scope) continue; //ignore locals, because we don't know where we are, and they're probably irrelevent. //make sure it has the right prefix if (!strncmp(def->name, prefix, prefixlen)) //but ignore it if its one of those special things that you're not meant to know about. if (strcmp(def->name, "IMMEDIATE") && !strchr(def->name, ':') && !strchr(def->name, '.') && !strchr(def->name, '*') && !strchr(def->name, '[')) { l = strlen(def->name); if (l && usedbuffer+2+l < buffersize) { if (usedbuffer) buffer[usedbuffer++] = ' '; memcpy(buffer+usedbuffer, def->name, l); usedbuffer += l; } } } } buffer[usedbuffer] = 0; return usedbuffer>0; } static pbool GUI_NeedsQuotes(const char *str) { //true if an empty string. if (!*str) return true; for(; *str; str++) { //true if any char is not alpha-numeric if (*str >= 'a' && *str <= 'z') ; else if (*str >= 'A' && *str <= 'Z') ; else if (*str >= '0' && *str <= '9') ; else if (*str == '_') ; else return true; } return false; } static void GUI_WriteConfigLine(FILE *file, char *part1, char *part2, char *part3, char *desc) { int align = 0; if (part1) { if (GUI_NeedsQuotes(part1)) align += fprintf(file, "\"%s\" ", part1); else align += fprintf(file, "%s ", part1); for (; align < 14; align++) fputc(' ', file); } if (part2) { if (GUI_NeedsQuotes(part2)) align += fprintf(file, "\"%s\" ", part2); else align += fprintf(file, "%s ", part2); for (; align < 28; align++) fputc(' ', file); } if (part3) { if (GUI_NeedsQuotes(part3)) align += fprintf(file, "\"%s\" ", part3); else align += fprintf(file, "%s ", part3); for (; align < 40; align++) fputc(' ', file); } if (desc) { if (align > 40) { fputc('\n', file); align = 0; } for (; align < 40; align++) fputc(' ', file); fputs("# ", file); align -= 40; if (align < 0) align = 0; while(*desc) { if (*desc == '\n' || (*desc == ' ' && align > 60)) { fputs("\n", file); for (align = 0; align < 40; align++) fputc(' ', file); fputs("# ", file); align = 0; } else { fputc(*desc, file); align++; } desc++; } } fputs("\n", file); } static void GUI_WriteConfigInt(FILE *file, char *part1, int part2, char *desc) { char buf[64]; QC_snprintfz(buf, sizeof(buf), "%i", part2); GUI_WriteConfigLine(file, part1, buf, NULL, desc); } void GUI_SaveConfig(void) { FILE *file = fopen("fteqcc.ini", "wt"); int p; if (!file) return; for (p = 0; optimisations[p].enabled; p++) { if ((!(optimisations[p].flags&FLAG_SETINGUI)) == (!(optimisations[p].flags&FLAG_ASDEFAULT))) GUI_WriteConfigLine(file, "optimisation", optimisations[p].abbrev, "default", optimisations[p].description); else GUI_WriteConfigLine(file, "optimisation", optimisations[p].abbrev, (optimisations[p].flags&FLAG_SETINGUI)?"on":"off", optimisations[p].description); } for (p = 0; compiler_flag[p].enabled; p++) { if (p>0 && compiler_flag[p].enabled == compiler_flag[p-1].enabled) continue; //don't list dupe names. if (!strncmp(compiler_flag[p].fullname, "Keyword: ", 9)) GUI_WriteConfigLine(file, "keyword", compiler_flag[p].abbrev, (compiler_flag[p].flags&FLAG_SETINGUI)?"true":"false", compiler_flag[p].description); else GUI_WriteConfigLine(file, "flag", compiler_flag[p].abbrev, (compiler_flag[p].flags&FLAG_SETINGUI)?"true":"false", compiler_flag[p].description); } GUI_WriteConfigLine(file, "showall", fl_showall?"on":"off", NULL, "Show all keyword options in the gui"); GUI_WriteConfigLine(file, "compileonstart", fl_compileonstart?"on":"off", NULL, "Recompile on GUI startup"); GUI_WriteConfigLine(file, "log", fl_log?"on":"off", NULL, "Write out a compile log"); GUI_WriteConfigLine(file, "enginebinary", enginebinary, NULL, "Location of the engine binary to run. Change this to something else to run a different engine, but not all support debugging."); GUI_WriteConfigLine(file, "basedir", enginebasedir, NULL, "The base directory of the game that contains your sub directory"); GUI_WriteConfigLine(file, "engineargs", enginecommandline, NULL, "The engine commandline to use when debugging. You'll likely want to ensure this contains -window as well as the appropriate -game argument."); GUI_WriteConfigLine(file, "srcfile", progssrcname, NULL, "The progs.src file to load to find ordering of other qc files."); GUI_WriteConfigLine(file, "src", progssrcdir, NULL, "Additional subdir to read qc files from. Typically blank (ie: the working directory)."); GUI_WriteConfigInt (file, "tabsize", fl_tabsize, "Specifies the size of tabs in scintilla windows."); GUI_WriteConfigLine(file, "extramargins", fl_extramargins?"on":"off", NULL, "Enables line number and folding margins."); GUI_WriteConfigLine(file, "hexen2", fl_hexen2?"on":"off", NULL, "Enable the extra tweaks needed for compatibility with hexen2 engines."); GUI_WriteConfigLine(file, "extendedopcodes", fl_ftetarg?"on":"off", NULL, "Utilise an extended instruction set, providing support for pointers and faster arrays and other speedups."); GUI_WriteConfigLine(file, "parameters", parameters, NULL, "Other additional parameters that are not supported by the gui. Likely including -DFOO"); fclose(file); } //grabs a token. modifies original string. static char *GUI_ParseInPlace(char **state) { char *str = *state, *end; while(*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n') str++; if (*str == '\"') { char *fmt; str++; for (end = str, fmt = str; *end; ) { if (*end == '\"') { *end++ = 0; break; } else if (*end == '\'' && end[1] == '\\') *fmt = '\\'; else if (*end == '\'' && end[1] == '\"') *fmt = '\"'; else if (*end == '\'' && end[1] == '\n') *fmt = '\n'; else if (*end == '\'' && end[1] == '\r') *fmt = '\r'; else if (*end == '\'' && end[1] == '\t') *fmt = '\t'; else { *fmt++ = *end++; continue; } fmt+=1; end+=2; } } else { for (end = str; *end; end++) { if (*end == '#') { *end = 0; while (*end && *end != '\n') end++; break; } if (*end==' ' || *end =='\t' || *end == '\n' || *end == '\r') break; } } if (end && *end) { *end = 0; *state = end+1; } else *state = end; return str; } static int GUI_ParseIntInPlace(char **state, int defaultval) { char *token = GUI_ParseInPlace(state); if (!stricmp(token, "default")) return defaultval; else if (!stricmp(token, "on") || !stricmp(token, "true") || !stricmp(token, "yes")) return 1; else if (!stricmp(token, "off") || !stricmp(token, "false") || !stricmp(token, "no")) return 0; else return atoi(token); } static int GUI_ParseBooleanInPlace(char **state, int defaultval) { char *token = GUI_ParseInPlace(state); if (!stricmp(token, "default")) return defaultval; else if (!stricmp(token, "on") || !stricmp(token, "true") || !stricmp(token, "yes")) return 1; else if (!stricmp(token, "off") || !stricmp(token, "false") || !stricmp(token, "no")) return 0; else return !!atoi(token); } void GUI_LoadConfig(void) { char buf[2048]; char *token, *str; FILE *file = fopen("fteqcc.ini", "rb"); int p; //initialise gui-only stuff. fl_compileonstart = false; fl_extramargins = false; fl_tabsize = 8; if (!file) return; fl_nondfltopts = false; while (fgets(buf, sizeof(buf), file)) { str = buf; token = GUI_ParseInPlace(&str); if (!stricmp(token, "optimisation") || !stricmp(token, "opt")) { char *item = GUI_ParseInPlace(&str); int value = GUI_ParseBooleanInPlace(&str, -1); for (p = 0; optimisations[p].enabled; p++) if (!stricmp(item, optimisations[p].abbrev)) { if (value == -1) value = !!(optimisations[p].flags & FLAG_ASDEFAULT); else fl_nondfltopts = true; if (value) optimisations[p].flags |= FLAG_SETINGUI; else optimisations[p].flags &= ~FLAG_SETINGUI; break; } //don't worry too much if its not known if (!optimisations[p].enabled) printf("Unknown flag: \"%s\"\n", item); } else if (!stricmp(token, "flag") || !stricmp(token, "fl") || !stricmp(token, "keyword")) { char *item = GUI_ParseInPlace(&str); int value = GUI_ParseBooleanInPlace(&str, -1); for (p = 0; compiler_flag[p].enabled; p++) if (!stricmp(item, compiler_flag[p].abbrev)) { if (value == -1) value = !!(compiler_flag[p].flags & FLAG_ASDEFAULT); if (value) compiler_flag[p].flags |= FLAG_SETINGUI; else compiler_flag[p].flags &= ~FLAG_SETINGUI; break; } if (!compiler_flag[p].enabled) printf("Unknown flag/keyword: \"%s\"\n", item); //don't worry if its not known } else if (!stricmp(token, "enginebinary")) QC_strlcpy(enginebinary, GUI_ParseInPlace(&str), sizeof(enginebinary)); else if (!stricmp(token, "basedir")) QC_strlcpy(enginebasedir, GUI_ParseInPlace(&str), sizeof(enginebasedir)); else if (!stricmp(token, "engineargs")) QC_strlcpy(enginecommandline, GUI_ParseInPlace(&str), sizeof(enginecommandline)); else if (!stricmp(token, "srcfile")) QC_strlcpy(progssrcname, GUI_ParseInPlace(&str), sizeof(progssrcname)); else if (!stricmp(token, "src")) QC_strlcpy(progssrcdir, GUI_ParseInPlace(&str), sizeof(progssrcdir)); else if (!stricmp(token, "parameters")) QC_strlcpy(parameters, GUI_ParseInPlace(&str), sizeof(parameters)); else if (!stricmp(token, "log")) fl_log = GUI_ParseBooleanInPlace(&str, false); else if (!stricmp(token, "compileonstart")) fl_compileonstart = GUI_ParseBooleanInPlace(&str, false); else if (!stricmp(token, "showall")) fl_showall = GUI_ParseBooleanInPlace(&str, false); else if (!stricmp(token, "tabsize")) fl_tabsize = GUI_ParseIntInPlace(&str, false); else if (!stricmp(token, "extramargins")) fl_extramargins = GUI_ParseBooleanInPlace(&str, false); else if (!stricmp(token, "hexen2")) fl_hexen2 = GUI_ParseBooleanInPlace(&str, false); else if (!stricmp(token, "extendedopcodes")) fl_ftetarg = GUI_ParseBooleanInPlace(&str, false); else if (*token) { printf("Unknown setting: \"%s\"\n", token); } } fclose(file); } //this function takes the windows specified commandline and strips out all the options menu items. int GUI_ParseCommandLine(const char *args, pbool keepsrcanddir) { int paramlen=0; int l, p; const char *next; int mode = 0; extern int qccpersisthunk; if (!*args) { int len; FILE *f; char *s; f = fopen("fteqcc.arg", "rb"); if (f) { fseek(f, 0, SEEK_END); len = ftell(f); fseek(f, 0, SEEK_SET); args = alloca(len+1); fread((char*)args, 1, len, f); ((char*)args)[len] = '\0'; fclose(f); while((s = strchr(args, '\r'))) *s = ' '; while((s = strchr(args, '\n'))) *s = ' '; } } //find the first argument while (*args == ' ' || *args == '\t') args++; for (next = args; *next&&*next!=' '&&*next !='\t'; next++) ; if (*args != '-') { pbool qt = *args == '\"'; l = 0; if (qt) args++; while ((*args != ' ' || qt) && *args) { if (qt && *args == '\"') { args++; break; } if (!keepsrcanddir) progssrcname[l++] = *args; args++; } if (!keepsrcanddir) progssrcname[l] = 0; next = args; if (!keepsrcanddir) { args = strrchr(progssrcname, '\\'); while(args && strchr(args, '/')) args = strchr(args, '/'); if (args) { memcpy(progssrcdir, progssrcname, args-progssrcname); progssrcdir[args-progssrcname] = 0; args++; memmove(progssrcname, args, strlen(args)+1); #ifdef _WIN32 SetCurrentDirectoryA(progssrcdir); #else chdir(progssrcdir); #endif *progssrcdir = 0; } } args = next; } GUI_LoadConfig(); paramlen = strlen(parameters); if (paramlen) parameters[paramlen++] = ' '; while(*args) { while (*args == ' ' || *args == '\t') args++; for (next = args; *next&&*next!=' '&&*next !='\t'; next++) ; strncpy(parameters+paramlen, args, next-args); parameters[paramlen+next-args] = '\0'; l = strlen(parameters+paramlen)+1; if (!strnicmp(parameters+paramlen, "-stdout", 7) || !strnicmp(parameters+paramlen, "--version", 9)) { mode = 1; } /*else if (!strnicmp(parameters+paramlen, "-zippatch", 9)) { mode = 2; }*/ else if (!strnicmp(parameters+paramlen, "-O", 2) || !strnicmp(parameters+paramlen, "/O", 2)) { //strip out all -O fl_nondfltopts = true; if (parameters[paramlen+2]) { if (parameters[paramlen+2] >= '0' && parameters[paramlen+2] <= '3') { p = parameters[paramlen+2]-'0'; for (l = 0; optimisations[l].enabled; l++) { if (optimisations[l].optimisationlevel<=p) optimisations[l].flags |= FLAG_SETINGUI; else optimisations[l].flags &= ~FLAG_SETINGUI; } } else if (!strncmp(parameters+paramlen+2, "no-", 3)) { if (parameters[paramlen+5]) { for (p = 0; optimisations[p].enabled; p++) if ((*optimisations[p].abbrev && !strcmp(parameters+paramlen+5, optimisations[p].abbrev)) || !strcmp(parameters+paramlen+5, optimisations[p].fullname)) { optimisations[p].flags &= ~FLAG_SETINGUI; break; } if (!optimisations[p].enabled) { parameters[paramlen+next-args] = ' '; paramlen += l; } } } else { for (p = 0; optimisations[p].enabled; p++) if ((*optimisations[p].abbrev && !strcmp(parameters+paramlen+2, optimisations[p].abbrev)) || !strcmp(parameters+paramlen+2, optimisations[p].fullname)) { optimisations[p].flags |= FLAG_SETINGUI; break; } if (!optimisations[p].enabled) { parameters[paramlen+next-args] = ' '; paramlen += l; } } } } else if (!strnicmp(parameters+paramlen, "-F", 2) || !strnicmp(parameters+paramlen, "/F", 2) || !strnicmp(parameters+paramlen, "-K", 2) || !strnicmp(parameters+paramlen, "/K", 2)) { if (parameters[paramlen+2]) { if (!strncmp(parameters+paramlen+2, "no-", 3)) { if (parameters[paramlen+5]) { for (p = 0; compiler_flag[p].enabled; p++) if ((*compiler_flag[p].abbrev && !strcmp(parameters+paramlen+5, compiler_flag[p].abbrev)) || !strcmp(parameters+paramlen+5, compiler_flag[p].fullname)) { compiler_flag[p].flags &= ~FLAG_SETINGUI; break; } if (!compiler_flag[p].enabled) { parameters[paramlen+next-args] = ' '; paramlen += l; } } } else { for (p = 0; compiler_flag[p].enabled; p++) if ((*compiler_flag[p].abbrev && !strcmp(parameters+paramlen+2, compiler_flag[p].abbrev)) || !strcmp(parameters+paramlen+2, compiler_flag[p].fullname)) { compiler_flag[p].flags |= FLAG_SETINGUI; break; } if (!compiler_flag[p].enabled) { parameters[paramlen+next-args] = ' '; paramlen += l; } } } } /* else if (!strnicmp(parameters+paramlen, "-Fno-kce", 8) || !strnicmp(parameters+paramlen, "/Fno-kce", 8)) //keywords stuph { fl_nokeywords_coexist = true; } else if (!strnicmp(parameters+paramlen, "-Fkce", 5) || !strnicmp(parameters+paramlen, "/Fkce", 5)) { fl_nokeywords_coexist = false; } else if (!strnicmp(parameters+paramlen, "-Facc", 5) || !strnicmp(parameters+paramlen, "/Facc", 5)) { fl_acc = true; } else if (!strnicmp(parameters+paramlen, "-autoproto", 10) || !strnicmp(parameters+paramlen, "/autoproto", 10)) { fl_autoprototype = true; } */ else if (!strnicmp(parameters+paramlen, "-showall", 8) || !strnicmp(parameters+paramlen, "/showall", 8)) { fl_showall = true; } else if (!strnicmp(parameters+paramlen, "-ac", 3) || !strnicmp(parameters+paramlen, "/ac", 3)) { fl_compileonstart = true; } else if (!strnicmp(parameters+paramlen, "-log", 4) || !strnicmp(parameters+paramlen, "/log", 4)) { fl_log = true; } else if (!strnicmp(parameters+paramlen, "-nolog", 4) || !strnicmp(parameters+paramlen, "/nolog", 4)) { fl_log = false; } else if (!strnicmp(parameters+paramlen, "-engine", 7) || !strnicmp(parameters+paramlen, "/engine", 7)) { while (*next == ' ') next++; l = 0; while (*next != ' ' && *next) enginebinary[l++] = *next++; enginebinary[l] = 0; } else if (!strnicmp(parameters+paramlen, "-basedir", 8) || !strnicmp(parameters+paramlen, "/basedir", 8)) { while (*next == ' ') next++; l = 0; while (*next != ' ' && *next) enginebasedir[l++] = *next++; enginebasedir[l] = 0; } //strcpy(enginecommandline, "-window +map start -nohome"); else if (!strnicmp(parameters+paramlen, "-srcfile", 8) || !strnicmp(parameters+paramlen, "/srcfile", 8)) { while (*next == ' ') next++; if (keepsrcanddir) { //ignore it while (*next != ' ' && *next) next++; } else { l = 0; while (*next != ' ' && *next) progssrcname[l++] = *next++; progssrcname[l] = 0; } } else if (!strnicmp(parameters+paramlen, "-src ", 5) || !strnicmp(parameters+paramlen, "/src ", 5)) { while (*next == ' ') next++; if (keepsrcanddir) { //ignore it while (*next != ' ' && *next) next++; } else { l = 0; while (*next != ' ' && *next) progssrcdir[l++] = *next++; progssrcdir[l] = 0; } } else if (!strnicmp(parameters+paramlen, "-T", 2) || !strnicmp(parameters+paramlen, "/T", 2)) //the target { if (!strnicmp(parameters+paramlen+2, "h2", 2)) { fl_hexen2 = true; } else { fl_hexen2 = false; parameters[paramlen+next-args] = ' '; paramlen += l; } } /* else if (isfirst && *args != '-' && *args != '/') { pbool qt = *args == '\"'; l = 0; if (qt) args++; while (*args != ' ' && *args) { if (qt && *args == '\"') { args++; break; } progssrcname[l++] = *args++; } progssrcname[l] = 0; args = strrchr(progssrcname, '\\'); while(args && strchr(args, '/')) args = strchr(args, '/'); if (args) { memcpy(progssrcdir, progssrcname, args-progssrcname); progssrcdir[args-progssrcname] = 0; args++; memmove(progssrcname, args, strlen(args)+1); SetCurrentDirectoryA(progssrcdir); *progssrcdir = 0; } } */ else { parameters[paramlen+next-args] = ' '; paramlen += l; } args=next; } while (paramlen>0 && (parameters[paramlen-1] == ' ' || parameters[paramlen-1] == '\t')) paramlen--; parameters[paramlen] = '\0'; qccpersisthunk = (mode!=1); return mode; } void GUI_SetDefaultOpts(void) { int i; for (i = 0; compiler_flag[i].enabled; i++) //enabled is a pointer { if (compiler_flag[i].flags & FLAG_ASDEFAULT) compiler_flag[i].flags |= FLAG_SETINGUI; else compiler_flag[i].flags &= ~FLAG_SETINGUI; } for (i = 0; optimisations[i].enabled; i++) //enabled is a pointer { if (optimisations[i].flags & FLAG_ASDEFAULT) optimisations[i].flags |= FLAG_SETINGUI; else optimisations[i].flags &= ~FLAG_SETINGUI; } } void GUI_RevealOptions(void) { int i; for (i = 0; compiler_flag[i].enabled; i++) //enabled is a pointer { if (fl_showall && compiler_flag[i].flags & FLAG_HIDDENINGUI) compiler_flag[i].flags &= ~FLAG_HIDDENINGUI; } for (i = 0; optimisations[i].enabled; i++) //enabled is a pointer { if (fl_showall && optimisations[i].flags & FLAG_HIDDENINGUI) optimisations[i].flags &= ~FLAG_HIDDENINGUI; if (optimisations[i].flags & FLAG_HIDDENINGUI) //hidden optimisations are disabled as default optimisations[i].optimisationlevel = 4; } } int GUI_BuildParms(const char *args, const char **argv, int argv_size, pbool quick)//, char *forceoutputfile) { static char param[2048]; int paramlen = 0; int argc; const char *next; int i; int targ; char *targs[] = {"", "-Th2", "-Tfte", "-Tfteh2"}; argc = 1; argv[0] = "fteqcc"; if (quick) { strcpy(param+paramlen, "-Tparse"); argv[argc++] = param+paramlen; paramlen += strlen(param+paramlen)+1; } targ = 0; targ |= fl_hexen2?1:0; targ |= fl_ftetarg?2:0; if (*targs[targ]) { strcpy(param+paramlen, targs[targ]); argv[argc++] = param+paramlen; paramlen += strlen(param+paramlen)+1; } if (fl_nondfltopts) { for (i = 0; optimisations[i].enabled; i++) //enabled is a pointer { if (optimisations[i].flags & FLAG_SETINGUI) sprintf(param+paramlen, "-O%s", optimisations[i].abbrev); else sprintf(param+paramlen, "-Ono-%s", optimisations[i].abbrev); argv[argc++] = param+paramlen; paramlen += strlen(param+paramlen)+1; } } for (i = 0; compiler_flag[i].enabled; i++) //enabled is a pointer { if (compiler_flag[i].flags & FLAG_SETINGUI) sprintf(param+paramlen, "-F%s", compiler_flag[i].abbrev); else sprintf(param+paramlen, "-Fno-%s", compiler_flag[i].abbrev); argv[argc++] = param+paramlen; paramlen += strlen(param+paramlen)+1; } /* while(*args) { while (*args <= ' '&& *args) args++; for (next = args; *next>' '; next++) ; strncpy(param+paramlen, args, next-args); param[paramlen+next-args] = '\0'; argv[argc++] = param+paramlen; paramlen += strlen(param+paramlen)+1; args=next; }*/ // if (*forceoutputfile) // { // argv[argc++] = "-destfile"; // argv[argc++] = forceoutputfile; // } if (*progssrcname) { argv[argc++] = "-srcfile"; argv[argc++] = progssrcname; } if (*progssrcdir) { argv[argc++] = "-src"; argv[argc++] = progssrcdir; } while(*args) { while (*args <= ' '&& *args) args++; if (argc >= argv_size) return 0; for (next = args; *next>' '; next++) ; strncpy(param+paramlen, args, next-args); param[paramlen+next-args] = '\0'; argv[argc++] = param+paramlen; paramlen += strlen(param+paramlen)+1; args=next; } return argc; } pbool GenBuiltinsList(char *buffer, int buffersize) { QCC_def_t *def; int usedbuffer = 0; int l; int fno; for (fno = 0; fno < sourcefilesnumdefs; fno++) { for (def = sourcefilesdefs[fno]; def; def = def->next) { if (def->scope) continue; //ignore locals, because we don't know where we are, and they're probably irrelevent. //if its a builtin function... if (def->type->type == ev_function && def->symboldata->function && functions[def->symboldata->function].code<0) ; else if (def->filen && strstr(def->filen, "extensions")) ; else continue; //but ignore it if its one of those special things that you're not meant to know about. if (strcmp(def->name, "IMMEDIATE") && !strchr(def->name, ':') && !strchr(def->name, '.') && !strchr(def->name, '*') && !strchr(def->name, '[')) { l = strlen(def->name); if (l && usedbuffer+2+l < buffersize) { if (usedbuffer) buffer[usedbuffer++] = ' '; memcpy(buffer+usedbuffer, def->name, l); usedbuffer += l; } } } } buffer[usedbuffer] = 0; return usedbuffer>0; } fteqcc-20251105/./qcc_pr_comp.c0000644000200200001440000261134715233070110015373 0ustar twolifeusers#if !defined(MINIMAL) && !defined(OMIT_QCC) #include "qcc.h" #include #if defined(_WIN32) || defined(__DJGPP__) #include #elif defined(__unix__) && !defined(__linux__) // quick hack for the bsds and other unix systems #include #elif !defined(alloca) //alloca.h isn't present on bsd (stdlib.h should define it to __builtin_alloca, and we can check for that here). #include #endif #ifdef _MSC_VER #define longlong __int64 #define LL(x) x##i64 #else #define longlong long long #define LL(x) x##ll #endif /* TODO: *foo++ = 5; is currently store foo->tmp; store 5->*tmp; add tmp,1->foo should be just store 5->*foo; add foo,1->foo the dereference does the post-inc. needs to be delayed somehow. could build a list of post-inc terms after the current expression, but might be really messy. */ #define WARN_IMPLICITVARIANTCAST 0 //FIXME: #define IAMNOTLAZY #define SUPPORTINLINE void QCC_PR_ParseAsm(void); #define MEMBERFIELDNAME "__m%s" #define STRCMP(s1,s2) (((*s1)!=(*s2)) || strcmp(s1,s2)) //saves about 2-6 out of 120 - expansion of idea from fastqcc #define STRNCMP(s1,s2,l) (((*s1)!=(*s2)) || strncmp(s1,s2,l)) //pathetic saving here. extern char *compilingfile; int conditional; //standard qc keywords #define keyword_do 1 #define keyword_return 1 #define keyword_if 1 #define keyword_else 1 #define keyword_while 1 //extended keywords. pbool keyword_switch; //hexen2/c pbool keyword_case; //hexen2/c pbool keyword_default; //hexen2/c pbool keyword_break; //hexen2/c pbool keyword_continue; //hexen2/c pbool keyword_loop; //hexen2 pbool keyword_until; //hexen2 pbool keyword_thinktime;//hexen2 pbool keyword_asm; pbool keyword_class; pbool keyword_accessor; pbool keyword_inout; pbool keyword_optional; pbool keyword_const; //fixme pbool keyword_entity; //for skipping the local pbool keyword_float; //for skipping the local pbool keyword_double; pbool keyword_for; pbool keyword_goto; pbool keyword_char; pbool keyword_short; pbool keyword_int; //for skipping the local pbool keyword_integer; //for skipping the local pbool keyword_long; pbool keyword_signed; pbool keyword_unsigned; pbool keyword_register; pbool keyword_volatile; pbool keyword_state; pbool keyword_string; //for skipping the local pbool keyword_struct; pbool keyword_var; //allow it to be initialised and set around the place. pbool keyword_vector; //for skipping the local pbool keyword_local; pbool keyword_static; pbool keyword_auto; pbool keyword_nonstatic; pbool keyword_used; pbool keyword_unused; pbool keyword_enum; //kinda like in c, but typedef not supported. pbool keyword_enumflags; //like enum, but doubles instead of adds 1. pbool keyword_typedef; //fixme #define keyword_codesys flag_acc //reacc needs this (forces the resultant crc) #define keyword_function flag_acc //reacc needs this (reacc has this on all functions, wierd eh?) #define keyword_objdata flag_acc //reacc needs this (following defs are fields rather than globals, use var to disable) #define keyword_object flag_acc //reacc needs this (an entity) #define keyword_pfunc flag_acc //reacc needs this (pointer to function) #define keyword_system flag_acc //reacc needs this (potatos) #define keyword_real flag_acc //reacc needs this (a float) #define keyword_exit flag_acc //emits an OP_DONE opcode. #define keyword_external flag_acc //reacc needs this (a builtin) pbool keyword_extern; //function is external, don't error or warn if the body was not found pbool keyword_shared; //mark global to be copied over when progs changes (part of FTE_MULTIPROGS) pbool keyword_noref; //nowhere else references this, don't strip it. pbool keyword_nosave; //don't write the def to the output. pbool keyword_inline; pbool keyword_strip; pbool keyword_ignore; pbool keyword_union; //you surly know what a union is! pbool keyword_weak; pbool keyword_wrap; pbool keyword_accumulate; pbool keyword_using; #define keyword_not 1 //hexenc support needs this, and fteqcc can optimise without it, but it adds an extra token after the if, so it can cause no namespace conflicts pbool keywords_coexist; //don't disable a keyword simply because a var was made with the same name. pbool output_parms; //emit some PARMX fields. confuses decompilers. pbool autoprototype; //take two passes over the source code. First time round doesn't enter and functions or initialise variables. pbool autoprototyped; //previously autoprototyped. no longer allowed to enable autoproto, but don't warn about it. pbool parseonly; //parse defs and stuff, but don't bother compiling any actual code. pbool pr_subscopedlocals; //causes locals to be valid ONLY within their statement block. (they simply can't be referenced by name outside of it) pbool flag_nullemptystr; //null immediates are 0, not 1. pbool flag_brokenifstring; //break strings even more pbool flag_ifstring; //makes if (blah) equivelent to if (blah != "") which resolves some issues in multiprogs situations. pbool flag_iffloat; //use an op_if_f instruction instead of op_if so if(-0) evaluates to false. pbool flag_ifvector; //use an op_not_v instruction instead of testing only _x. pbool flag_vectorlogic; //flag_ifvector but for && and || pbool flag_acc; //reacc like behaviour of src files (finds *.qc in start dir and compiles all in alphabetical order) pbool flag_caseinsensitive; //symbols will be matched to an insensitive case if the specified case doesn't exist. This should b usable for any mod pbool flag_laxcasts; //Allow lax casting. This'll produce loadsa warnings of course. But allows compilation of certain dodgy code. pbool flag_hashonly; //Allows use of only #constant for precompiler constants, allows certain preqcc using mods to compile pbool flag_macroinstrings; //preqcc's support macro expansion in strings (blind expansion). pbool flag_fasttrackarrays; //Faster arrays, dynamically detected, activated only in supporting engines. pbool flag_msvcstyle; //MSVC style warnings, so msvc's ide works properly pbool flag_debugmacros; //Print out #defines as they are expanded, for debugging. pbool flag_assume_integer; //5 - is that an integer or a float? qcc says float. but we support int too, so maybe we want that instead? pbool flag_assume_double; //5.0 - is that single or double precision? QC says float, but C says double. should probably only be used with assume-int enabled too. pbool flag_filetimes; //only rebuild if files were modified. pbool flag_typeexplicit; //no implicit type conversions, you must do the casts yourself. pbool flag_boundchecks; //Disable generation of bound check instructions. pbool flag_guiannotate; //spit out lots of extra text that the gui can interpret to display some inline asm pbool flag_brokenarrays; //return array; returns array[0] instead of &array; pbool flag_rootconstructor; //if true, class constructors are ordered to call the super constructor first, rather than the child constructor pbool flag_qccx; //accept qccx syntax. you may wish to disable warnings separately. pbool flag_attributes; //gmqcc-style attributes pbool flag_assumevar; //initialised globals will no longer be considered constant pbool flag_dblstarexp; // a**b is pow(a,b) instead of a*(*b) pbool flag_cpriority; //operator precidence should adhere to C standards, instead of QC compatibility. pbool flag_qcfuncs; //void() is a function type, and not a syntax error. pbool flag_allowuninit; //ignore uninitialised locals, avoiding all private locals. pbool flag_embedsrc; //embed all source files inside the .dat (can be opened with any zip program) pbool flag_noreflection; //no reflection stuff, for smaller unsavable binaries. pbool flag_nopragmafileline;//ignore #pragma file and #pragma line, so that I can actually read+debug xonotic's code. pbool flag_utf8strings; //strings default to u8"" string rules. pbool flag_reciprocalmaths; //unsafe maths optimisations pbool flag_ILP32; //restrict long to 32 bits. long long still exists. pbool flag_undefwordsize; //pointers are NOT always multiples of 4. sizeof becomes an error foo.length will still work though. pointer maths will resort to OP_ADD_PIW in order to still work (or just break). char and short types are unusable. casting between strings and pointers is an error (though you can still contrive casts past it). for compat with DP and its potential 64bit qcvm. pbool flag_pointerrelocs; //engine accepts ev_pointer globaldefs and biases them by the in-memory globals. pbool opt_overlaptemps; //reduce numpr_globals by reuse of temps. When they are not needed they are freed for reuse. The way this is implemented is better than frikqcc's. (This is the single most important optimisation) pbool opt_assignments; //STORE_F isn't used if an operation wrote to a temp. pbool opt_shortenifnots; //if(!var) is made an IF rather than NOT IFNOT pbool opt_noduplicatestrings; //brute force string check. time consuming but more effective than the equivelent in frikqcc. pbool opt_constantarithmatic; //3*5 appears as 15 instead of the extra statement. pbool opt_nonvec_parms; //store_f instead of store_v on function calls, where possible. pbool opt_constant_names; //take out the defs and name strings of constants. pbool opt_constant_names_strings;//removes the defs of strings too. plays havok with multiprogs. pbool opt_precache_file; //remove the call, the parameters, everything. pbool opt_filenames; //strip filenames. hinders older decompilers. pbool opt_unreferenced; //strip defs that are not referenced. pbool opt_function_names; //strip out the names of builtin functions. pbool opt_locals; //strip out the names of locals and immediates. pbool opt_dupconstdefs; //float X = 5; and float Y = 5; occupy the same global with this. pbool opt_return_only; //RETURN; DONE; at the end of a function strips out the done statement if there is no way to get to it. pbool opt_compound_jumps; //jumps to jump statements jump to the final point. pbool opt_stripfunctions; //if a functions is only ever called directly or by exe, don't emit the def. pbool opt_locals_overlapping; //make the local vars of all functions occupy the same globals. pbool opt_logicops; //don't make conditions enter functions if the return value will be discarded due to a previous value. (C style if statements) pbool opt_vectorcalls; //vectors can be packed into 3 floats, which can yield lower numpr_globals, but cost two more statements per call (only works for q1 calling conventions). pbool opt_classfields; pbool opt_simplifiedifs; //if (f != 0) -> if_f (f). if (f == 0) -> ifnot_f (f) //bool opt_comexprremoval; //these are the results of the opt_. The values are printed out when compilation is compleate, showing effectivness. int optres_shortenifnots; int optres_assignments; int optres_overlaptemps; int optres_noduplicatestrings; int optres_constantarithmatic; int optres_nonvec_parms; int optres_constant_names; int optres_constant_names_strings; int optres_precache_file; int optres_filenames; int optres_unreferenced; int optres_function_names; int optres_locals; int optres_dupconstdefs; int optres_return_only; int optres_compound_jumps; //int optres_comexprremoval; int optres_stripfunctions; int optres_locals_overlapping; int optres_logicops; int optres_inlines; int optres_test1; int optres_test2; void *(*pHash_Get)(hashtable_t *table, const char *name); void *(*pHash_GetNext)(hashtable_t *table, const char *name, void *old); void *(*pHash_Add)(hashtable_t *table, const char *name, void *data, bucket_t *); void (*pHash_RemoveData)(hashtable_t *table, const char *name, void *data); QCC_type_t *QCC_PR_FindType (QCC_type_t *type); QCC_type_t *QCC_PR_PointerType (QCC_type_t *pointsto); QCC_type_t *QCC_PR_FieldType (QCC_type_t *pointsto); QCC_sref_t QCC_PR_Term (unsigned int exprflags); QCC_sref_t QCC_PR_ParseValue (QCC_type_t *assumeclass, pbool allowarrayassign, pbool expandmemberfields, pbool makearraypointers); void QCC_Marshal_Locals(int firststatement, int laststatement); QCC_sref_t QCC_PR_ParseArrayPointer (QCC_sref_t d, pbool allowarrayassign, pbool makestructpointers); QCC_sref_t QCC_LoadFromArray(QCC_sref_t base, QCC_sref_t index, QCC_type_t *t, pbool preserve); void QCC_PR_ParseInitializerDef(QCC_def_t *def, unsigned int flags); static pbool QCC_RefNeedsCalls(QCC_ref_t *ref); QCC_ref_t *QCC_DefToRef(QCC_ref_t *ref, QCC_sref_t def); //ref is a buffer to write into, to avoid excessive allocs QCC_sref_t QCC_RefToDef(QCC_ref_t *ref, pbool freetemps); QCC_ref_t *QCC_PR_RefExpression (QCC_ref_t *retbuf, int priority, int exprflags); QCC_ref_t *QCC_PR_ParseRefValue (QCC_ref_t *refbuf, QCC_type_t *assumeclass, pbool allowarrayassign, pbool expandmemberfields, pbool makearraypointers); QCC_ref_t *QCC_PR_ParseRefArrayPointer (QCC_ref_t *refbuf, QCC_ref_t *d, pbool allowarrayassign, pbool makestructpointers); QCC_ref_t *QCC_PR_BuildRef(QCC_ref_t *retbuf, unsigned int reftype, QCC_sref_t base, QCC_sref_t index, QCC_type_t *cast, pbool readonly, unsigned int bitofs); QCC_ref_t *QCC_PR_BuildAccessorRef(QCC_ref_t *retbuf, QCC_sref_t base, QCC_sref_t index, struct accessor_s *accessor, pbool readonly); QCC_sref_t QCC_StoreSRefToRef(QCC_ref_t *dest, QCC_sref_t source, pbool readable, pbool preservedest); QCC_sref_t QCC_StoreRefToRef(QCC_ref_t *dest, QCC_ref_t *source, pbool readable, pbool preservedest); void QCC_PR_DiscardRef(QCC_ref_t *ref); QCC_function_t *QCC_PR_ParseImmediateStatements (QCC_def_t *def, QCC_type_t *type, pbool dowrap); const char *QCC_VarAtOffset(QCC_sref_t ref); QCC_sref_t QCC_EvaluateCast(QCC_sref_t src, QCC_type_t *cast, pbool implicit); QCC_sref_t QCC_PR_ParseInitializerTemp(QCC_type_t *type); pbool QCC_PR_ParseInitializerType(int arraysize, QCC_def_t *basedef, QCC_sref_t def, unsigned bitofs, unsigned int flags); #define PIF_WRAP 1 //new initialisation is meant to wrap an existing one. #define PIF_STRONGER 2 //previous initialisation was weak. #define PIF_ACCUMULATE 4 //glue them together... #define PIF_AUTOWRAP 8 //accumulate without wrap must autowrap if the function was not previously defined as accumulate... QCC_statement_t *QCC_Generate_OP_IFNOT(QCC_sref_t e, pbool preserve); QCC_statement_t *QCC_Generate_OP_IF(QCC_sref_t e, pbool preserve); QCC_statement_t *QCC_Generate_OP_GOTO(void); QCC_sref_t QCC_PR_GenerateLogicalNot(QCC_sref_t e, const char *errormessage); static QCC_function_t *QCC_PR_GenerateQCFunction (QCC_def_t *def, QCC_type_t *type, unsigned int *pif_flags); static void QCC_StoreToSRef(QCC_sref_t dest, QCC_sref_t source, QCC_type_t *type, pbool preservesource, pbool preservedest); void QCC_PR_ParseStatement (void); //NOTE: prints may use from the func argument's symbol, which can be awkward if its a temp. QCC_sref_t QCC_PR_GenerateFunctionCallSref (QCC_sref_t newself, QCC_sref_t func, QCC_sref_t *arglist, int argcount); QCC_sref_t QCC_PR_GenerateFunctionCallRef (QCC_sref_t newself, QCC_sref_t func, QCC_ref_t **arglist, unsigned int argcount); QCC_sref_t QCC_PR_GenerateFunctionCall1 (QCC_sref_t newself, QCC_sref_t func, QCC_sref_t a, QCC_type_t *type_a); QCC_sref_t QCC_PR_GenerateFunctionCall2 (QCC_sref_t newself, QCC_sref_t func, QCC_sref_t a, QCC_type_t *type_a, QCC_sref_t b, QCC_type_t *type_b); QCC_sref_t QCC_PR_GenerateFunctionCall3 (QCC_sref_t newself, QCC_sref_t func, QCC_sref_t a, QCC_type_t *type_a, QCC_sref_t b, QCC_type_t *type_b, QCC_sref_t c, QCC_type_t *type_c); QCC_sref_t QCC_MakeTranslateStringConst(const char *value); QCC_sref_t QCC_MakeStringConst(const char *value); QCC_sref_t QCC_MakeStringConstLength(const char *value, int length); QCC_sref_t QCC_MakeFloatConst(pvec_t value); QCC_sref_t QCC_MakeDoubleConst(double value); QCC_sref_t QCC_MakeFloatConstFromInt(longlong llvalue); QCC_sref_t QCC_MakeIntConst(longlong llvalue); //longlongs for warnings QCC_sref_t QCC_MakeUIntConst(unsigned longlong llvalue); QCC_sref_t QCC_MakeInt64Const(longlong llvalue); QCC_sref_t QCC_MakeUInt64Const(unsigned longlong llvalue); static QCC_sref_t QCC_MakeUniqueConst(QCC_type_t *type, void *data); QCC_sref_t QCC_MakeVectorConst(pvec_t a, pvec_t b, pvec_t c); static QCC_sref_t QCC_MakeGAddress(QCC_type_t *type, QCC_def_t *relocof, int idx, int bitofs); enum { STFL_PRESERVEA=1u<<0, //if a temp is released as part of the statement, it can be reused for the result. Which is bad if the temp is needed for something else, like e.e.f += 4; STFL_CONVERTA=1u<<1, //convert to/from ints/floats to match the operand types required by the opcode STFL_PRESERVEB=1u<<2, STFL_CONVERTB=1u<<3, STFL_DISCARDRESULT=1u<<4, STFL_NOEMULATE=1u<<5, //don't emulate unsupported opcode with other opcodes. }; #define QCC_PR_Statement(op,a,b,st) QCC_PR_StatementFlags(op,a,b,st,STFL_CONVERTA|STFL_CONVERTB) QCC_sref_t QCC_PR_StatementFlags ( QCC_opcode_t *op, QCC_sref_t var_a, QCC_sref_t var_b, QCC_statement_t **outstatement, unsigned int flags); #ifdef __GNUC__ void QCC_PR_StatementAnnotation(const char *fmt, ...) __attribute__((deprecated("don't forget about me")))LIKEPRINTF(1); #endif void QCC_PR_ParseState (void); pbool expandedemptymacro; //just inhibits warnings about hanging semicolons QCC_pr_info_t pr; //QCC_def_t **pr_global_defs/*[MAX_REGS]*/; // to find def for a global variable //keeps track of how many funcs are called while parsing a statement //int qcc_functioncalled; //======================================== const QCC_sref_t nullsref = {0}; struct QCC_function_s *pr_scope; // the function being parsed, or NULL QCC_type_t *pr_classtype; // the class that the current function is part of. QCC_type_t *pr_assumetermtype; //undefined things get this time, with no warning about being undeclared (used for the state function, so prototypes are not needed) QCC_function_t *pr_assumetermscope; unsigned int pr_assumetermflags; //GDF_ pbool pr_ignoredeprecation; pbool pr_dumpasm; const char *s_unitn; const char *s_filen; QCC_string_t s_filed; // filename for function definition unsigned int locals_marshalled; // largest local block size that needs to be allocated for locals overlapping. jmp_buf pr_parse_abort; // longjump with this on parse error pbool qcc_usefulstatement; pbool debug_armour_defined; int max_breaks; int max_continues; int max_cases; int num_continues; int num_breaks; int num_cases; int *pr_breaks; int *pr_continues; int *pr_cases; QCC_ref_t *pr_casesref; QCC_ref_t *pr_casesref2; #define MAX_LABEL_LENGTH 256 typedef struct { int statementno; int lineno; char name[MAX_LABEL_LENGTH]; } gotooperator_t; int max_labels; int max_gotos; gotooperator_t *pr_labels; gotooperator_t *pr_gotos; int num_gotos; int num_labels; QCC_sref_t extra_parms[MAX_EXTRA_PARMS]; static QCC_statement_t *initstatements; static size_t numinitstatements, maxinitstatements; //#define ASSOC_RIGHT_RESULT ASSOC_RIGHT //======================================== #undef PC_NONE enum { PC_NONE, PC_STORE, //stores are handled specially, but its still nice to mark them PC_UNARY, //these happen elsewhere PC_MEMBER, //these happen elsewhere PC_TERNARY, PC_UNARYNOT, PC_MULDIV, PC_ADDSUB, PC_SHIFT, PC_RELATION, PC_EQUALITY, PC_BITAND, PC_BITXOR, PC_BITOR, PC_LOGICAND, PC_LOGICOR, MAX_PRIORITY_CLASSES }; static int priority_class[MAX_PRIORITY_CLASSES+1]; //to simplify implementation slightly /*static char *reftypename[] = { "REF_GLOBAL", "REF_ARRAY", "REF_ARRAYHEAD", "REF_POINTER", "REF_POINTERARRAY", "REF_FIELD", "REF_STRING", "REF_NONVIRTUAL", "REF_THISCALL", "REF_ACCESSOR" };*/ //FIXME: modifiy list so most common GROUPS are first //use look up table for value of first char and sort by first char and most common...? //if true, effectivly {b=a; return a;} QCC_opcode_t pr_opcodes[] = { {6, "", "DONE", PC_NONE, ASSOC_LEFT, &type_void, &type_void, &type_void}, {6, "*", "MUL_F", PC_MULDIV, ASSOC_LEFT, &type_float, &type_float, &type_float, OPF_STD}, {6, "*", "MUL_V", PC_MULDIV, ASSOC_LEFT, &type_vector, &type_vector, &type_float, OPF_STD}, {6, "*", "MUL_FV", PC_MULDIV, ASSOC_LEFT, &type_float, &type_vector, &type_vector, OPF_STD}, {6, "*", "MUL_VF", PC_MULDIV, ASSOC_LEFT, &type_vector, &type_float, &type_vector, OPF_STD}, {6, "/", "DIV_F", PC_MULDIV, ASSOC_LEFT, &type_float, &type_float, &type_float, OPF_STD}, {6, "+", "ADD_F", PC_ADDSUB, ASSOC_LEFT, &type_float, &type_float, &type_float, OPF_STD}, {6, "+", "ADD_V", PC_ADDSUB, ASSOC_LEFT, &type_vector, &type_vector, &type_vector, OPF_STD}, {6, "-", "SUB_F", PC_ADDSUB, ASSOC_LEFT, &type_float, &type_float, &type_float, OPF_STD}, {6, "-", "SUB_V", PC_ADDSUB, ASSOC_LEFT, &type_vector, &type_vector, &type_vector, OPF_STD}, {6, "==", "EQ_F", PC_EQUALITY, ASSOC_LEFT, &type_float, &type_float, &type_bfloat, OPF_STD}, {6, "==", "EQ_V", PC_EQUALITY, ASSOC_LEFT, &type_vector, &type_vector, &type_bfloat, OPF_STD}, {6, "==", "EQ_S", PC_EQUALITY, ASSOC_LEFT, &type_string, &type_string, &type_bfloat, OPF_STD}, {6, "==", "EQ_E", PC_EQUALITY, ASSOC_LEFT, &type_entity, &type_entity, &type_bfloat, OPF_STD}, {6, "==", "EQ_FNC", PC_EQUALITY, ASSOC_LEFT, &type_function, &type_function, &type_bfloat, OPF_STD}, {6, "!=", "NE_F", PC_EQUALITY, ASSOC_LEFT, &type_float, &type_float, &type_bfloat, OPF_STD}, {6, "!=", "NE_V", PC_EQUALITY, ASSOC_LEFT, &type_vector, &type_vector, &type_bfloat, OPF_STD}, {6, "!=", "NE_S", PC_EQUALITY, ASSOC_LEFT, &type_string, &type_string, &type_bfloat, OPF_STD}, {6, "!=", "NE_E", PC_EQUALITY, ASSOC_LEFT, &type_entity, &type_entity, &type_bfloat, OPF_STD}, {6, "!=", "NE_FNC", PC_EQUALITY, ASSOC_LEFT, &type_function, &type_function, &type_bfloat, OPF_STD}, {6, "<=", "LE_F", PC_RELATION, ASSOC_LEFT, &type_float, &type_float, &type_bfloat, OPF_STD}, {6, ">=", "GE_F", PC_RELATION, ASSOC_LEFT, &type_float, &type_float, &type_bfloat, OPF_STD}, {6, "<", "LT_F", PC_RELATION, ASSOC_LEFT, &type_float, &type_float, &type_bfloat, OPF_STD}, {6, ">", "GT_F", PC_RELATION, ASSOC_LEFT, &type_float, &type_float, &type_bfloat, OPF_STD}, {6, ".", "LOADF_F", PC_MEMBER, ASSOC_LEFT, &type_entity, &type_field, &type_float}, {6, ".", "LOADF_V", PC_MEMBER, ASSOC_LEFT, &type_entity, &type_field, &type_vector}, {6, ".", "LOADF_S", PC_MEMBER, ASSOC_LEFT, &type_entity, &type_field, &type_string}, {6, ".", "LOADF_ENT", PC_MEMBER, ASSOC_LEFT, &type_entity, &type_field, &type_entity}, {6, ".", "LOADF_FLD", PC_MEMBER, ASSOC_LEFT, &type_entity, &type_field, &type_field}, {6, ".", "LOADF_FNC", PC_MEMBER, ASSOC_LEFT, &type_entity, &type_field, &type_function}, {6, ".", "FLDADDRESS", PC_MEMBER, ASSOC_LEFT, &type_entity, &type_field, &type_pointer}, {6, "=", "STORE_F", PC_STORE, ASSOC_RIGHT, &type_float, &type_float, &type_float, OPF_STORE}, {6, "=", "STORE_V", PC_STORE, ASSOC_RIGHT, &type_vector, &type_vector, &type_vector, OPF_STORE}, {6, "=", "STORE_S", PC_STORE, ASSOC_RIGHT, &type_string, &type_string, &type_string, OPF_STORE}, {6, "=", "STORE_ENT", PC_STORE, ASSOC_RIGHT, &type_entity, &type_entity, &type_entity, OPF_STORE}, {6, "=", "STORE_FLD", PC_STORE, ASSOC_RIGHT, &type_field, &type_field, &type_field, OPF_STORE}, {6, "=", "STORE_FNC", PC_STORE, ASSOC_RIGHT, &type_function, &type_function, &type_function, OPF_STORE}, {6, "=", "STOREP_F", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_float, &type_float, OPF_STOREPTROFS}, {6, "=", "STOREP_V", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_vector, &type_vector, OPF_STOREPTROFS}, {6, "=", "STOREP_S", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_string, &type_string, OPF_STOREPTROFS}, {6, "=", "STOREP_ENT", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_entity, &type_entity, OPF_STOREPTROFS}, {6, "=", "STOREP_FLD", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_field, &type_field, OPF_STOREPTROFS}, {6, "=", "STOREP_FNC", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_function, &type_function, OPF_STOREPTROFS}, {6, "", "RETURN", PC_NONE, ASSOC_LEFT, &type_vector, &type_void, &type_void}, {6, "!", "NOT_F", PC_UNARY, ASSOC_LEFT, &type_float, &type_void, &type_bfloat, OPF_STDUNARY}, {6, "!", "NOT_V", PC_UNARY, ASSOC_LEFT, &type_vector, &type_void, &type_bfloat, OPF_STDUNARY}, {6, "!", "NOT_S", PC_UNARY, ASSOC_LEFT, &type_vector, &type_void, &type_bfloat, OPF_STDUNARY}, {6, "!", "NOT_ENT", PC_UNARY, ASSOC_LEFT, &type_entity, &type_void, &type_bfloat, OPF_STDUNARY}, {6, "!", "NOT_FNC", PC_UNARY, ASSOC_LEFT, &type_function, &type_void, &type_bfloat, OPF_STDUNARY}, {6, "", "IF", PC_NONE, ASSOC_RIGHT, &type_float, NULL, &type_void}, {6, "", "IFNOT", PC_NONE, ASSOC_RIGHT, &type_float, NULL, &type_void}, // calls returns REG_RETURN {6, "", "CALL0", PC_NONE, ASSOC_LEFT, &type_function, &type_void, &type_void}, {6, "", "CALL1", PC_NONE, ASSOC_LEFT, &type_function, &type_void, &type_void}, {6, "", "CALL2", PC_NONE, ASSOC_LEFT, &type_function, &type_void, &type_void}, {6, "", "CALL3", PC_NONE, ASSOC_LEFT, &type_function, &type_void, &type_void}, {6, "", "CALL4", PC_NONE, ASSOC_LEFT, &type_function, &type_void, &type_void}, {6, "", "CALL5", PC_NONE, ASSOC_LEFT, &type_function, &type_void, &type_void}, {6, "", "CALL6", PC_NONE, ASSOC_LEFT, &type_function, &type_void, &type_void}, {6, "", "CALL7", PC_NONE, ASSOC_LEFT, &type_function, &type_void, &type_void}, {6, "", "CALL8", PC_NONE, ASSOC_LEFT, &type_function, &type_void, &type_void}, {6, "", "STATE", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_void}, {6, "", "GOTO", PC_NONE, ASSOC_RIGHT, NULL, &type_void, &type_void}, {6, "&&", "AND_F", PC_LOGICAND, ASSOC_LEFT, &type_float, &type_float, &type_bfloat, OPF_STD}, {6, "||", "OR_F", PC_LOGICOR, ASSOC_LEFT, &type_float, &type_float, &type_bfloat, OPF_STD}, {6, "&", "BITAND", PC_BITAND, ASSOC_LEFT, &type_float, &type_float, &type_float, OPF_STD}, {6, "|", "BITOR", PC_BITOR, ASSOC_LEFT, &type_float, &type_float, &type_float, OPF_STD}, //version 6 are in normal progs. //these are hexen2 {7, "*=", "MULSTORE_F", PC_STORE, ASSOC_RIGHT_RESULT, &type_float, &type_float, &type_float, OPF_STORE}, {7, "*=", "MULSTORE_VF", PC_STORE, ASSOC_RIGHT_RESULT, &type_vector, &type_float, &type_vector, OPF_STORE}, {7, "*=", "MULSTOREP_F", PC_STORE, ASSOC_RIGHT_RESULT, &type_pointer, &type_float, &type_float, OPF_STOREPTR}, {7, "*=", "MULSTOREP_VF", PC_STORE, ASSOC_RIGHT_RESULT, &type_pointer, &type_float, &type_vector, OPF_STOREPTR}, {7, "/=", "DIVSTORE_F", PC_STORE, ASSOC_RIGHT_RESULT, &type_float, &type_float, &type_float, OPF_STORE}, {7, "/=", "DIVSTOREP_F", PC_STORE, ASSOC_RIGHT_RESULT, &type_pointer, &type_float, &type_float, OPF_STOREPTR}, {7, "+=", "ADDSTORE_F", PC_STORE, ASSOC_RIGHT_RESULT, &type_float, &type_float, &type_float, OPF_STORE}, {7, "+=", "ADDSTORE_V", PC_STORE, ASSOC_RIGHT_RESULT, &type_vector, &type_vector, &type_vector, OPF_STORE}, {7, "+=", "ADDSTOREP_F", PC_STORE, ASSOC_RIGHT_RESULT, &type_pointer, &type_float, &type_float, OPF_STOREPTR}, {7, "+=", "ADDSTOREP_V", PC_STORE, ASSOC_RIGHT_RESULT, &type_pointer, &type_vector, &type_vector, OPF_STOREPTR}, {7, "-=", "SUBSTORE_F", PC_STORE, ASSOC_RIGHT_RESULT, &type_float, &type_float, &type_float, OPF_STORE}, {7, "-=", "SUBSTORE_V", PC_STORE, ASSOC_RIGHT_RESULT, &type_vector, &type_vector, &type_vector, OPF_STORE}, {7, "-=", "SUBSTOREP_F", PC_STORE, ASSOC_RIGHT_RESULT, &type_pointer, &type_float, &type_float, OPF_STOREPTR}, {7, "-=", "SUBSTOREP_V", PC_STORE, ASSOC_RIGHT_RESULT, &type_pointer, &type_vector, &type_vector, OPF_STOREPTR}, {7, "", "FETCH_GBL_F", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "", "FETCH_GBL_V", PC_NONE, ASSOC_LEFT, &type_vector, &type_float, &type_vector}, {7, "", "FETCH_GBL_S", PC_NONE, ASSOC_LEFT, &type_string, &type_float, &type_string}, {7, "", "FETCH_GBL_E", PC_NONE, ASSOC_LEFT, &type_entity, &type_float, &type_entity}, {7, "", "FETCH_GBL_FNC", PC_NONE, ASSOC_LEFT, &type_function, &type_float, &type_function}, {7, "", "CSTATE", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_void}, {7, "", "CWSTATE", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_void}, {7, "", "THINKTIME", PC_NONE, ASSOC_LEFT, &type_entity, &type_float, &type_void}, {7, "|=", "BITSETSTORE_F", PC_STORE, ASSOC_RIGHT, &type_float, &type_float, &type_float, OPF_STORE}, {7, "|=", "BITSETSTOREP_F", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_float, &type_float,OPF_STOREPTR}, {7, "&~=", "BITCLRSTORE_F", PC_STORE, ASSOC_RIGHT, &type_float, &type_float, &type_float, OPF_STORE}, {7, "&~=", "BITCLRSTOREP_F", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_float, &type_float,OPF_STOREPTR}, {7, "", "RAND0", PC_NONE, ASSOC_LEFT, &type_void, &type_void, &type_float}, {7, "", "RAND1", PC_NONE, ASSOC_LEFT, &type_float, &type_void, &type_float}, {7, "", "RAND2", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "", "RANDV0", PC_NONE, ASSOC_LEFT, &type_void, &type_void, &type_vector}, {7, "", "RANDV1", PC_NONE, ASSOC_LEFT, &type_vector, &type_void, &type_vector}, {7, "", "RANDV2", PC_NONE, ASSOC_LEFT, &type_vector, &type_vector, &type_vector}, {7, "", "SWITCH_F", PC_NONE, ASSOC_RIGHT, &type_float, NULL, &type_void}, {7, "", "SWITCH_V", PC_NONE, ASSOC_RIGHT, &type_vector, NULL, &type_void}, {7, "", "SWITCH_S", PC_NONE, ASSOC_RIGHT, &type_string, NULL, &type_void}, {7, "", "SWITCH_E", PC_NONE, ASSOC_RIGHT, &type_entity, NULL, &type_void}, {7, "", "SWITCH_FNC", PC_NONE, ASSOC_RIGHT, &type_function, NULL, &type_void}, {7, "", "CASE", PC_NONE, ASSOC_RIGHT, &type_variant, NULL, &type_void}, {7, "", "CASERANGE", PC_NONE, ASSOC_RIGHT, &type_float, &type_float, NULL}, //Later are additions by DMW. {7, "", "CALL1H", PC_NONE, ASSOC_RIGHT, &type_function, &type_variant, &type_void}, {7, "", "CALL2H", PC_NONE, ASSOC_RIGHT, &type_function, &type_variant, &type_variant}, {7, "", "CALL3H", PC_NONE, ASSOC_RIGHT, &type_function, &type_variant, &type_variant}, {7, "", "CALL4H", PC_NONE, ASSOC_RIGHT, &type_function, &type_variant, &type_variant}, {7, "", "CALL5H", PC_NONE, ASSOC_RIGHT, &type_function, &type_variant, &type_variant}, {7, "", "CALL6H", PC_NONE, ASSOC_RIGHT, &type_function, &type_variant, &type_variant}, {7, "", "CALL7H", PC_NONE, ASSOC_RIGHT, &type_function, &type_variant, &type_variant}, {7, "", "CALL8H", PC_NONE, ASSOC_RIGHT, &type_function, &type_variant, &type_variant}, {7, "=", "STORE_I", PC_STORE, ASSOC_RIGHT, &type_integer, &type_integer, &type_integer, OPF_STORE}, {7, "=", "STORE_IF", PC_STORE, ASSOC_RIGHT, &type_float, &type_integer, &type_integer, OPF_STORE}, {7, "=", "STORE_FI", PC_STORE, ASSOC_RIGHT, &type_integer, &type_float, &type_float, OPF_STORE}, {7, "+", "ADD_I", PC_ADDSUB, ASSOC_LEFT, &type_integer, &type_integer, &type_integer,OPF_STD}, {7, "+", "ADD_FI", PC_ADDSUB, ASSOC_LEFT, &type_float, &type_integer, &type_float, OPF_STD}, {7, "+", "ADD_IF", PC_ADDSUB, ASSOC_LEFT, &type_integer, &type_float, &type_float, OPF_STD}, {7, "-", "SUB_I", PC_ADDSUB, ASSOC_LEFT, &type_integer, &type_integer, &type_integer,OPF_STD}, {7, "-", "SUB_FI", PC_ADDSUB, ASSOC_LEFT, &type_float, &type_integer, &type_float, OPF_STD}, {7, "-", "SUB_IF", PC_ADDSUB, ASSOC_LEFT, &type_integer, &type_float, &type_float, OPF_STD}, {7, "", "CONV_IF", PC_STORE, ASSOC_LEFT, &type_integer, &type_void, &type_float}, {7, "", "CONV_FI", PC_STORE, ASSOC_LEFT, &type_float, &type_void, &type_integer}, {7, "", "CONVP_IF", PC_STORE, ASSOC_LEFT, &type_pointer, &type_integer, &type_float}, {7, "", "CONVP_FI", PC_STORE, ASSOC_LEFT, &type_pointer, &type_float, &type_integer}, {7, ".", "LOADF_I", PC_MEMBER, ASSOC_LEFT, &type_entity, &type_field, &type_integer}, {7, "=", "STOREP_I", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_integer, &type_integer, OPF_STOREPTROFS}, {7, "=", "STOREP_IF", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_float, &type_integer, OPF_STOREPTROFS}, {7, "=", "STOREP_FI", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_integer, &type_float, OPF_STOREPTROFS}, {7, "&", "BITAND_I", PC_BITAND, ASSOC_LEFT, &type_integer, &type_integer, &type_integer,OPF_STD}, {7, "|", "BITOR_I", PC_BITOR, ASSOC_LEFT, &type_integer, &type_integer, &type_integer,OPF_STD}, {7, "*", "MUL_I", PC_MULDIV, ASSOC_LEFT, &type_integer, &type_integer, &type_integer,OPF_STD}, {7, "/", "DIV_I", PC_MULDIV, ASSOC_LEFT, &type_integer, &type_integer, &type_integer,OPF_STD}, {7, "==", "EQ_I", PC_EQUALITY, ASSOC_LEFT, &type_integer, &type_integer, &type_bint ,OPF_STD}, {7, "!=", "NE_I", PC_EQUALITY, ASSOC_LEFT, &type_integer, &type_integer, &type_bint ,OPF_STD}, {7, "", "IFNOTS", PC_NONE, ASSOC_RIGHT, &type_string, NULL, &type_void}, {7, "", "IFS", PC_NONE, ASSOC_RIGHT, &type_string, NULL, &type_void}, {7, "!", "NOT_I", PC_UNARY, ASSOC_LEFT, &type_integer, &type_void, &type_bint, OPF_STDUNARY}, {7, "/", "DIV_VF", PC_MULDIV, ASSOC_LEFT, &type_vector, &type_float, &type_vector, OPF_STD}, {7, "^", "BITXOR_I", PC_BITXOR, ASSOC_LEFT, &type_integer, &type_integer, &type_integer,OPF_STD}, {7, ">>", "RSHIFT_I", PC_SHIFT, ASSOC_LEFT, &type_integer, &type_integer, &type_integer,OPF_STD}, {7, "<<", "LSHIFT_I", PC_SHIFT, ASSOC_LEFT, &type_integer, &type_integer, &type_integer,OPF_STD}, //var, offset return {7, "", "GLOBALADDRESS", PC_NONE, ASSOC_LEFT, &type_float, &type_integer, &type_pointer}, {7, "", "ADD_PIW", PC_NONE, ASSOC_LEFT, &type_pointer, &type_integer, &type_pointer}, {7, "=", "LOADA_F", PC_STORE, ASSOC_LEFT, &type_float, &type_integer, &type_float}, {7, "=", "LOADA_V", PC_STORE, ASSOC_LEFT, &type_vector, &type_integer, &type_vector}, {7, "=", "LOADA_S", PC_STORE, ASSOC_LEFT, &type_string, &type_integer, &type_string}, {7, "=", "LOADA_ENT", PC_STORE, ASSOC_LEFT, &type_entity, &type_integer, &type_entity}, {7, "=", "LOADA_FLD", PC_STORE, ASSOC_LEFT, &type_field, &type_integer, &type_field}, {7, "=", "LOADA_FNC", PC_STORE, ASSOC_LEFT, &type_function, &type_integer, &type_function}, {7, "=", "LOADA_I", PC_STORE, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, {7, "=", "STORE_P", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_pointer, &type_void, OPF_STORE}, {7, ".", "LOADF_P", PC_MEMBER, ASSOC_LEFT, &type_entity, &type_field, &type_pointer}, {7, "=", "LOADP_F", PC_STORE, ASSOC_LEFT, &type_pointer, &type_integer, &type_float, OPF_LOADPTR}, {7, "=", "LOADP_V", PC_STORE, ASSOC_LEFT, &type_pointer, &type_integer, &type_vector, OPF_LOADPTR}, {7, "=", "LOADP_S", PC_STORE, ASSOC_LEFT, &type_pointer, &type_integer, &type_string, OPF_LOADPTR}, {7, "=", "LOADP_ENT", PC_STORE, ASSOC_LEFT, &type_pointer, &type_integer, &type_entity, OPF_LOADPTR}, {7, "=", "LOADP_FLD", PC_STORE, ASSOC_LEFT, &type_pointer, &type_integer, &type_field, OPF_LOADPTR}, {7, "=", "LOADP_FNC", PC_STORE, ASSOC_LEFT, &type_pointer, &type_integer, &type_function, OPF_LOADPTR}, {7, "=", "LOADP_I", PC_STORE, ASSOC_LEFT, &type_pointer, &type_integer, &type_integer, OPF_LOADPTR}, {7, "<=", "LE_I", PC_RELATION, ASSOC_LEFT, &type_integer, &type_integer, &type_bint, OPF_STD}, {7, ">=", "GE_I", PC_RELATION, ASSOC_LEFT, &type_integer, &type_integer, &type_bint, OPF_STD}, {7, "<", "LT_I", PC_RELATION, ASSOC_LEFT, &type_integer, &type_integer, &type_bint, OPF_STD}, {7, ">", "GT_I", PC_RELATION, ASSOC_LEFT, &type_integer, &type_integer, &type_bint, OPF_STD}, {7, "<=", "LE_IF", PC_RELATION, ASSOC_LEFT, &type_integer, &type_float, &type_bint, OPF_STD}, {7, ">=", "GE_IF", PC_RELATION, ASSOC_LEFT, &type_integer, &type_float, &type_bint, OPF_STD}, {7, "<", "LT_IF", PC_RELATION, ASSOC_LEFT, &type_integer, &type_float, &type_bint, OPF_STD}, {7, ">", "GT_IF", PC_RELATION, ASSOC_LEFT, &type_integer, &type_float, &type_bint, OPF_STD}, {7, "<=", "LE_FI", PC_RELATION, ASSOC_LEFT, &type_float, &type_integer, &type_bint, OPF_STD}, {7, ">=", "GE_FI", PC_RELATION, ASSOC_LEFT, &type_float, &type_integer, &type_bint, OPF_STD}, {7, "<", "LT_FI", PC_RELATION, ASSOC_LEFT, &type_float, &type_integer, &type_bint, OPF_STD}, {7, ">", "GT_FI", PC_RELATION, ASSOC_LEFT, &type_float, &type_integer, &type_bint, OPF_STD}, {7, "==", "EQ_IF", PC_EQUALITY, ASSOC_LEFT, &type_integer, &type_float, &type_bint, OPF_STD}, {7, "==", "EQ_FI", PC_EQUALITY, ASSOC_LEFT, &type_float, &type_integer, &type_bint, OPF_STD}, //------------------------------------- //string manipulation. {7, "+", "ADD_SF", PC_ADDSUB, ASSOC_LEFT, &type_string, &type_float, &type_string, OPF_STD}, {7, "-", "SUB_S", PC_ADDSUB, ASSOC_LEFT, &type_string, &type_string, &type_float, OPF_STD}, {7, "", "STOREP_C", PC_STORE, ASSOC_RIGHT, &type_string, &type_float, &type_float, OPF_STOREPTROFS}, {7, "", "LOADP_C", PC_STORE, ASSOC_LEFT, &type_string, &type_float, &type_float, OPF_LOADPTR}, //------------------------------------- {7, "*", "MUL_IF", PC_MULDIV, ASSOC_LEFT, &type_integer, &type_float, &type_float, OPF_STD}, {7, "*", "MUL_FI", PC_MULDIV, ASSOC_LEFT, &type_float, &type_integer, &type_float, OPF_STD}, {7, "*", "MUL_VI", PC_MULDIV, ASSOC_LEFT, &type_vector, &type_integer, &type_vector, OPF_STD}, {7, "*", "MUL_IV", PC_MULDIV, ASSOC_LEFT, &type_integer, &type_vector, &type_vector, OPF_STD}, {7, "/", "DIV_IF", PC_MULDIV, ASSOC_LEFT, &type_integer, &type_float, &type_float, OPF_STD}, {7, "/", "DIV_FI", PC_MULDIV, ASSOC_LEFT, &type_float, &type_integer, &type_float, OPF_STD}, {7, "&", "BITAND_IF", PC_BITAND, ASSOC_LEFT, &type_integer, &type_float, &type_integer, OPF_STD}, {7, "|", "BITOR_IF", PC_BITOR, ASSOC_LEFT, &type_integer, &type_float, &type_integer, OPF_STD}, {7, "&", "BITAND_FI", PC_BITAND, ASSOC_LEFT, &type_float, &type_integer, &type_integer, OPF_STD}, {7, "|", "BITOR_FI", PC_BITOR, ASSOC_LEFT, &type_float, &type_integer, &type_integer, OPF_STD}, {7, "&&", "AND_I", PC_LOGICAND, ASSOC_LEFT, &type_integer, &type_integer, &type_bint, OPF_STD}, {7, "||", "OR_I", PC_LOGICOR, ASSOC_LEFT, &type_integer, &type_integer, &type_bint, OPF_STD}, {7, "&&", "AND_IF", PC_LOGICAND, ASSOC_LEFT, &type_integer, &type_float, &type_bint, OPF_STD}, {7, "||", "OR_IF", PC_LOGICOR, ASSOC_LEFT, &type_integer, &type_float, &type_bint, OPF_STD}, {7, "&&", "AND_FI", PC_LOGICAND, ASSOC_LEFT, &type_float, &type_integer, &type_bint, OPF_STD}, {7, "||", "OR_FI", PC_LOGICOR, ASSOC_LEFT, &type_float, &type_integer, &type_bint, OPF_STD}, {7, "!=", "NE_IF", PC_EQUALITY, ASSOC_LEFT, &type_integer, &type_float, &type_bint, OPF_STD}, {7, "!=", "NE_FI", PC_EQUALITY, ASSOC_LEFT, &type_float, &type_integer, &type_bint, OPF_STD}, {7, "<>", "GSTOREP_I", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "GSTOREP_F", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "GSTOREP_ENT", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "GSTOREP_FLD", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "GSTOREP_S", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "GSTOREP_FNC", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "GSTOREP_V", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "GADDRESS", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "GLOAD_I", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "GLOAD_F", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "GLOAD_FLD", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "GLOAD_ENT", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "GLOAD_S", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "GLOAD_FNC", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "<>", "BOUNDCHECK", PC_NONE, ASSOC_LEFT, &type_integer, NULL, NULL}, {7, "", "UNUSED", PC_NONE, ASSOC_RIGHT, &type_void, &type_void, &type_void}, {7, "", "PUSH", PC_NONE, ASSOC_RIGHT, &type_float, &type_void, &type_pointer}, {7, "", "POP", PC_NONE, ASSOC_RIGHT, &type_float, &type_void, &type_void}, {7, "", "SWITCH_I",PC_NONE, ASSOC_LEFT, &type_void, NULL, &type_void}, {7, "<>", "GLOAD_V", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "", "IF_F", PC_NONE, ASSOC_RIGHT, &type_float, NULL, &type_void}, {7, "","IFNOT_F", PC_NONE, ASSOC_RIGHT, &type_float, NULL, &type_void}, {7, "=", "STOREF_V", PC_NONE, ASSOC_RIGHT, &type_entity, &type_field, &type_vector, OPF_STOREFLD}, //ent.fld=c {7, "=", "STOREF_F", PC_NONE, ASSOC_RIGHT, &type_entity, &type_field, &type_float, OPF_STOREFLD}, {7, "=", "STOREF_S", PC_NONE, ASSOC_RIGHT, &type_entity, &type_field, &type_string, OPF_STOREFLD}, {7, "=", "STOREF_I", PC_NONE, ASSOC_RIGHT, &type_entity, &type_field, &type_integer, OPF_STOREFLD}, {7, "", "STOREP_I8", PC_STORE, ASSOC_RIGHT, &type_string, &type_integer, &type_integer, OPF_STOREPTROFS}, {7, "", "LOADP_U8", PC_STORE, ASSOC_LEFT, &type_string, &type_integer, &type_integer, OPF_LOADPTR}, //uint opcodes (not many, they're shared with ints for the most part) {7, "<=", "LE_U", PC_RELATION, ASSOC_LEFT, &type_uint, &type_uint, &type_bint, OPF_LOADPTR}, {7, "<", "LT_U", PC_RELATION, ASSOC_LEFT, &type_uint, &type_uint, &type_bint, OPF_LOADPTR}, {7, "/", "DIV_U", PC_MULDIV, ASSOC_LEFT, &type_uint, &type_uint, &type_uint, OPF_LOADPTR}, {7, ">>", "RSHIFT_U", PC_SHIFT, ASSOC_LEFT, &type_uint, &type_integer, &type_uint, OPF_LOADPTR}, //[u]int64+double opcodes {7, "+", "ADD_I64", PC_ADDSUB, ASSOC_LEFT, &type_int64, &type_int64, &type_int64, OPF_STD}, {7, "-", "SUB_I64", PC_ADDSUB, ASSOC_LEFT, &type_int64, &type_int64, &type_int64, OPF_STD}, {7, "*", "MUL_I64", PC_MULDIV, ASSOC_LEFT, &type_int64, &type_int64, &type_int64, OPF_STD}, {7, "/", "DIV_I64", PC_MULDIV, ASSOC_LEFT, &type_int64, &type_int64, &type_int64, OPF_STD}, {7, "&", "BITAND_I64", PC_BITAND, ASSOC_LEFT, &type_int64, &type_int64, &type_int64, OPF_STD}, {7, "|", "BITOR_I64", PC_BITOR, ASSOC_LEFT, &type_int64, &type_int64, &type_int64, OPF_STD}, {7, "^", "BITXOR_I64", PC_BITXOR, ASSOC_LEFT, &type_int64, &type_int64, &type_int64, OPF_STD}, {7, "<<", "LSHIFT_I64I", PC_SHIFT, ASSOC_LEFT, &type_int64, &type_integer, &type_int64, OPF_STD}, {7, ">>", "RSHIFT_I64I", PC_SHIFT, ASSOC_LEFT, &type_int64, &type_integer, &type_int64, OPF_STD}, {7, "<=", "LE_I64", PC_RELATION, ASSOC_LEFT, &type_int64, &type_int64, &type_bint, OPF_STD}, {7, "<", "LT_I64", PC_RELATION, ASSOC_LEFT, &type_int64, &type_int64, &type_bint, OPF_STD}, {7, "==", "EQ_I64", PC_EQUALITY, ASSOC_LEFT, &type_int64, &type_int64, &type_bint, OPF_STD}, {7, "!=", "NE_I64", PC_EQUALITY, ASSOC_LEFT, &type_int64, &type_int64, &type_bint, OPF_STD}, {7, "<", "LE_U64", PC_RELATION, ASSOC_LEFT, &type_uint64, &type_uint64, &type_bint, OPF_STD}, {7, "<", "LT_U64", PC_RELATION, ASSOC_LEFT, &type_uint64, &type_uint64, &type_bint, OPF_STD}, {7, "%", "DIV_U64", PC_MULDIV, ASSOC_LEFT, &type_uint64, &type_uint64, &type_uint64, OPF_STD}, {7, ">>", "RSHIFT_U64I", PC_SHIFT, ASSOC_LEFT, &type_uint64, &type_uint, &type_uint64, OPF_STD}, {7, "=", "STORE_I64", PC_STORE, ASSOC_RIGHT, &type_int64, &type_int64, &type_int64, OPF_STORE}, {7, "=", "STOREP_I64", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_int64, &type_int64, OPF_STOREPTROFS}, {7, "<=>", "STOREF_I64", PC_NONE, ASSOC_RIGHT, &type_entity, &type_field, &type_int64, OPF_STOREFLD}, {7, ".", "LOADF_I64", PC_MEMBER, ASSOC_LEFT, &type_entity, &type_field, &type_pointer}, {7, "=", "LOADA_I64", PC_STORE, ASSOC_LEFT, &type_int64, &type_integer, &type_int64}, {7, "=", "LOADP_I64", PC_STORE, ASSOC_LEFT, &type_pointer, &type_int64, &type_int64, OPF_LOADPTR}, {7, "=", "CONV_UI64", PC_STORE, ASSOC_LEFT, &type_uint, &type_void, &type_int64}, {7, "=", "CONV_II64", PC_STORE, ASSOC_LEFT, &type_integer, &type_void, &type_int64}, {7, "=", "CONV_I64I", PC_STORE, ASSOC_LEFT, &type_int64, &type_void, &type_integer}, {7, "=", "CONV_FD", PC_STORE, ASSOC_LEFT, &type_float, &type_void, &type_double}, {7, "=", "CONV_DF", PC_STORE, ASSOC_LEFT, &type_double, &type_void, &type_float}, {7, "=", "CONV_I64F", PC_STORE, ASSOC_LEFT, &type_int64, &type_void, &type_float}, {7, "=", "CONV_FI64", PC_STORE, ASSOC_LEFT, &type_float, &type_void, &type_int64}, {7, "=", "CONV_I64D", PC_STORE, ASSOC_LEFT, &type_int64, &type_void, &type_double}, {7, "=", "CONV_DI64", PC_STORE, ASSOC_LEFT, &type_double, &type_void, &type_int64}, {7, "+", "ADD_D", PC_ADDSUB, ASSOC_LEFT, &type_double, &type_double, &type_double, OPF_STD}, {7, "-", "SUB_D", PC_ADDSUB, ASSOC_LEFT, &type_double, &type_double, &type_double, OPF_STD}, {7, "*", "MUL_D", PC_MULDIV, ASSOC_LEFT, &type_double, &type_double, &type_double, OPF_STD}, {7, "/", "DIV_D", PC_MULDIV, ASSOC_LEFT, &type_double, &type_double, &type_double, OPF_STD}, {7, "<=", "LE_D", PC_RELATION, ASSOC_LEFT, &type_double, &type_double, &type_bint, OPF_STD}, {7, "<", "LT_D", PC_RELATION, ASSOC_LEFT, &type_double, &type_double, &type_bint, OPF_STD}, {7, "==", "EQ_D", PC_EQUALITY, ASSOC_LEFT, &type_double, &type_double, &type_bint, OPF_STD}, {7, "!=", "NE_D", PC_EQUALITY, ASSOC_LEFT, &type_double, &type_double, &type_bint, OPF_STD}, {7, "", "STOREP_I16", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_integer, &type_integer, OPF_STOREPTROFS}, {7, "", "LOADP_I16", PC_STORE, ASSOC_LEFT, &type_pointer, &type_integer, &type_integer, OPF_LOADPTR}, {7, "", "LOADP_U16", PC_STORE, ASSOC_LEFT, &type_pointer, &type_integer, &type_integer, OPF_LOADPTR}, {7, "", "LOADP_I8", PC_STORE, ASSOC_LEFT, &type_pointer, &type_integer, &type_integer, OPF_LOADPTR}, {7, "", "BITEXTEND_I", PC_NONE, ASSOC_LEFT, &type_integer, &type_integer, &type_integer, OPF_STD}, {7, "", "BITEXTEND_U", PC_NONE, ASSOC_LEFT, &type_integer, &type_integer, &type_integer, OPF_STD}, {7, "", "BITCOPY_I", PC_NONE, ASSOC_LEFT, &type_integer, &type_integer, &type_integer, OPF_STD}, {7, "=", "CONV_UF", PC_STORE, ASSOC_LEFT, &type_uint, &type_void, &type_float}, {7, "=", "CONV_FU", PC_STORE, ASSOC_LEFT, &type_float, &type_void, &type_uint}, {7, "=", "CONV_U64D", PC_STORE, ASSOC_LEFT, &type_uint64, &type_void, &type_double}, {7, "=", "CONV_DU64", PC_STORE, ASSOC_LEFT, &type_double, &type_void, &type_uint64}, {7, "=", "CONV_U64F", PC_STORE, ASSOC_LEFT, &type_uint64, &type_void, &type_float}, {7, "=", "CONV_FU64", PC_STORE, ASSOC_LEFT, &type_float, &type_void, &type_uint64}, /* emulated ops begin here */ {7, "<>", "OP_EMULATED", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_float}, {7, "|=", "BITSET_I", PC_STORE, ASSOC_RIGHT_RESULT, &type_integer, &type_integer, &type_integer, OPF_STORE}, {7, "|=", "BITSETP_I", PC_STORE, ASSOC_RIGHT_RESULT, &type_pointer, &type_integer, &type_integer, OPF_STOREPTR}, {7, "&~=", "BITCLR_I", PC_STORE, ASSOC_RIGHT_RESULT, &type_integer, &type_integer, &type_integer, OPF_STORE}, {7, "*=", "MULSTORE_I", PC_STORE, ASSOC_RIGHT_RESULT, &type_integer, &type_integer, &type_integer, OPF_STORE}, {7, "/=", "DIVSTORE_I", PC_STORE, ASSOC_RIGHT_RESULT, &type_integer, &type_integer, &type_integer, OPF_STORE}, {7, "+=", "ADDSTORE_I", PC_STORE, ASSOC_RIGHT_RESULT, &type_integer, &type_integer, &type_integer, OPF_STORE}, {7, "-=", "SUBSTORE_I", PC_STORE, ASSOC_RIGHT_RESULT, &type_integer, &type_integer, &type_integer, OPF_STORE}, {7, "*=", "MULSTOREP_I", PC_STORE, ASSOC_RIGHT_RESULT, &type_pointer, &type_integer, &type_integer, OPF_STOREPTR}, {7, "/=", "DIVSTOREP_I", PC_STORE, ASSOC_RIGHT_RESULT, &type_pointer, &type_integer, &type_integer, OPF_STOREPTR}, {7, "+=", "ADDSTOREP_I", PC_STORE, ASSOC_RIGHT_RESULT, &type_pointer, &type_integer, &type_integer, OPF_STOREPTR}, {7, "-=", "SUBSTOREP_I", PC_STORE, ASSOC_RIGHT_RESULT, &type_pointer, &type_integer, &type_integer, OPF_STOREPTR}, {7, "*=", "MULSTORE_IF", PC_STORE, ASSOC_RIGHT_RESULT, &type_integer, &type_float, &type_float, OPF_STORE}, {7, "*=", "MULSTOREP_IF", PC_STORE, ASSOC_RIGHT_RESULT, &type_intpointer, &type_float, &type_float, OPF_STOREPTR}, {7, "/=", "DIVSTORE_IF", PC_STORE, ASSOC_RIGHT_RESULT, &type_integer, &type_float, &type_float, OPF_STORE}, {7, "/=", "DIVSTOREP_IF", PC_STORE, ASSOC_RIGHT_RESULT, &type_intpointer, &type_float, &type_float, OPF_STOREPTR}, {7, "+=", "ADDSTORE_IF", PC_STORE, ASSOC_RIGHT_RESULT, &type_integer, &type_float, &type_float, OPF_STORE}, {7, "+=", "ADDSTOREP_IF", PC_STORE, ASSOC_RIGHT_RESULT, &type_intpointer, &type_float, &type_float, OPF_STOREPTR}, {7, "-=", "SUBSTORE_IF", PC_STORE, ASSOC_RIGHT_RESULT, &type_integer, &type_float, &type_float, OPF_STORE}, {7, "-=", "SUBSTOREP_IF", PC_STORE, ASSOC_RIGHT_RESULT, &type_intpointer, &type_float, &type_float, OPF_STOREPTR}, {7, "*=", "MULSTORE_FI", PC_STORE, ASSOC_RIGHT_RESULT, &type_float, &type_integer, &type_float, OPF_STORE}, {7, "*=", "MULSTOREP_FI", PC_STORE, ASSOC_RIGHT_RESULT, &type_floatpointer, &type_integer, &type_float, OPF_STOREPTR}, {7, "/=", "DIVSTORE_FI", PC_STORE, ASSOC_RIGHT_RESULT, &type_float, &type_integer, &type_float, OPF_STORE}, {7, "/=", "DIVSTOREP_FI", PC_STORE, ASSOC_RIGHT_RESULT, &type_floatpointer, &type_integer, &type_float, OPF_STOREPTR}, {7, "+=", "ADDSTORE_FI", PC_STORE, ASSOC_RIGHT_RESULT, &type_float, &type_integer, &type_float, OPF_STORE}, {7, "+=", "ADDSTOREP_FI", PC_STORE, ASSOC_RIGHT_RESULT, &type_floatpointer, &type_integer, &type_float, OPF_STOREPTR}, {7, "-=", "SUBSTORE_FI", PC_STORE, ASSOC_RIGHT_RESULT, &type_float, &type_integer, &type_float, OPF_STORE}, {7, "-=", "SUBSTOREP_FI", PC_STORE, ASSOC_RIGHT_RESULT, &type_floatpointer, &type_integer, &type_float, OPF_STOREPTR}, {7, "*=", "MULSTORE_VI", PC_STORE, ASSOC_RIGHT_RESULT, &type_vector, &type_integer, &type_vector, OPF_STORE}, {7, "*=", "MULSTOREP_VI", PC_STORE, ASSOC_RIGHT_RESULT, &type_pointer, &type_integer, &type_vector, OPF_STOREPTR}, {7, "=", "LOADA_STRUCT", PC_STORE, ASSOC_LEFT, &type_float, &type_integer, &type_float}, {7, "=", "LOADP_P", PC_STORE, ASSOC_LEFT, &type_pointer, &type_integer, &type_pointer, OPF_LOADPTR}, {7, "=", "STOREP_P", PC_STORE, ASSOC_RIGHT, &type_pointer, &type_pointer, &type_pointer, OPF_STOREPTROFS}, {7, "~", "BITNOT_F", PC_UNARY, ASSOC_LEFT, &type_float, &type_void, &type_float, OPF_STDUNARY}, {7, "~", "BITNOT_I", PC_UNARY, ASSOC_LEFT, &type_integer, &type_void, &type_integer, OPF_STDUNARY}, {7, "==", "EQ_P", PC_EQUALITY, ASSOC_LEFT, &type_pointer, &type_pointer, &type_bfloat, OPF_STD}, {7, "!=", "NE_P", PC_EQUALITY, ASSOC_LEFT, &type_pointer, &type_pointer, &type_bfloat, OPF_STD}, {7, "<=", "LE_P", PC_RELATION, ASSOC_LEFT, &type_pointer, &type_pointer, &type_bfloat, OPF_STD}, {7, ">=", "GE_P", PC_RELATION, ASSOC_LEFT, &type_pointer, &type_pointer, &type_bfloat, OPF_STD}, {7, "<", "LT_P", PC_RELATION, ASSOC_LEFT, &type_pointer, &type_pointer, &type_bfloat, OPF_STD}, {7, ">", "GT_P", PC_RELATION, ASSOC_LEFT, &type_pointer, &type_pointer, &type_bfloat, OPF_STD}, {7, "&=", "ANDSTORE_F", PC_STORE, ASSOC_RIGHT_RESULT, &type_float, &type_float, &type_float, OPF_STORE}, {7, "&~=", "BITCLR_F", PC_STORE, ASSOC_LEFT, &type_float, &type_float, &type_float, OPF_STORE}, {7, "&~=", "BITCLR_I", PC_STORE, ASSOC_LEFT, &type_integer, &type_integer, &type_integer, OPF_STORE}, {7, "&~=", "BITCLR_V", PC_STORE, ASSOC_LEFT, &type_vector, &type_vector, &type_vector, OPF_STORE}, {7, "+", "ADD_SI", PC_ADDSUB, ASSOC_LEFT, &type_string, &type_integer, &type_string, OPF_STD}, {7, "+", "ADD_IS", PC_ADDSUB, ASSOC_LEFT, &type_integer, &type_string, &type_string, OPF_STD}, {7, "+", "ADD_PF", PC_ADDSUB, ASSOC_LEFT, &type_pointer, &type_float, &type_pointer, OPF_STD},//var_a.cast->auxtype matters {7, "+", "ADD_FP", PC_ADDSUB, ASSOC_LEFT, &type_float, &type_pointer, &type_pointer, OPF_STD},//var_b.cast->auxtype matters {7, "+", "ADD_PI", PC_ADDSUB, ASSOC_LEFT, &type_pointer, &type_integer, &type_pointer, OPF_STD},//var_a.cast->auxtype matters {7, "+", "ADD_IP", PC_ADDSUB, ASSOC_LEFT, &type_integer, &type_pointer, &type_pointer, OPF_STD},//var_b.cast->auxtype matters {7, "+", "ADD_PU", PC_ADDSUB, ASSOC_LEFT, &type_pointer, &type_uint, &type_pointer, OPF_STD},//var_a.cast->auxtype matters {7, "+", "ADD_UP", PC_ADDSUB, ASSOC_LEFT, &type_uint, &type_pointer, &type_pointer, OPF_STD},//var_b.cast->auxtype matters {7, "-", "SUB_SI", PC_ADDSUB, ASSOC_LEFT, &type_string, &type_integer, &type_string, OPF_STD}, {7, "-", "SUB_PF", PC_ADDSUB, ASSOC_LEFT, &type_pointer, &type_float, &type_pointer, OPF_STD}, {7, "-", "SUB_PI", PC_ADDSUB, ASSOC_LEFT, &type_pointer, &type_integer, &type_pointer, OPF_STD}, {7, "-", "SUB_PU", PC_ADDSUB, ASSOC_LEFT, &type_pointer, &type_uint, &type_pointer, OPF_STD}, {7, "-", "SUB_PP", PC_ADDSUB, ASSOC_LEFT, &type_pointer, &type_pointer, &type_integer, OPF_STD}, {7, "%", "MOD_F", PC_MULDIV, ASSOC_LEFT, &type_float, &type_float, &type_float, OPF_STD}, {7, "%", "MOD_I", PC_MULDIV, ASSOC_LEFT, &type_integer, &type_integer, &type_integer, OPF_STD}, {7, "%", "MOD_FI", PC_MULDIV, ASSOC_LEFT, &type_float, &type_integer, &type_integer, OPF_STD}, {7, "%", "MOD_IF", PC_MULDIV, ASSOC_LEFT, &type_integer, &type_float, &type_integer, OPF_STD}, {7, "%", "MOD_V", PC_MULDIV, ASSOC_LEFT, &type_vector, &type_vector, &type_vector, OPF_STD}, {7, "^", "BITXOR_F", PC_BITXOR, ASSOC_LEFT, &type_float, &type_float, &type_float, OPF_STD}, {7, ">>", "RSHIFT_F", PC_SHIFT, ASSOC_LEFT, &type_float, &type_float, &type_float, OPF_STD}, {7, "<<", "LSHIFT_F", PC_SHIFT, ASSOC_LEFT, &type_float, &type_float, &type_float, OPF_STD}, {7, ">>", "RSHIFT_IF", PC_SHIFT, ASSOC_LEFT, &type_integer, &type_float, &type_integer, OPF_STD}, {7, "<<", "LSHIFT_IF", PC_SHIFT, ASSOC_LEFT, &type_integer, &type_float, &type_integer, OPF_STD}, {7, ">>", "RSHIFT_FI", PC_SHIFT, ASSOC_LEFT, &type_float, &type_integer, &type_integer, OPF_STD}, {7, "<<", "LSHIFT_FI", PC_SHIFT, ASSOC_LEFT, &type_float, &type_integer, &type_integer, OPF_STD}, {7, "&&", "AND_ANY", PC_LOGICAND, ASSOC_LEFT, &type_variant, &type_variant, &type_bfloat, OPF_STD}, {7, "||", "OR_ANY", PC_LOGICOR, ASSOC_LEFT, &type_variant, &type_variant, &type_bfloat, OPF_STD}, {7, "+", "ADD_EI", PC_ADDSUB, ASSOC_LEFT, &type_entity, &type_integer, &type_entity, OPF_STD}, {7, "+", "ADD_EF", PC_ADDSUB, ASSOC_LEFT, &type_entity, &type_float, &type_entity, OPF_STD}, {7, "-", "SUB_EI", PC_ADDSUB, ASSOC_LEFT, &type_entity, &type_integer, &type_entity, OPF_STD}, {7, "-", "SUB_EF", PC_ADDSUB, ASSOC_LEFT, &type_entity, &type_float, &type_entity, OPF_STD}, {7, "&", "BITAND_V", PC_BITAND, ASSOC_LEFT, &type_vector, &type_vector, &type_vector, OPF_STD}, {7, "|", "BITOR_V", PC_BITOR, ASSOC_LEFT, &type_vector, &type_vector, &type_vector, OPF_STD}, {7, "~", "BITNOT_V", PC_UNARY, ASSOC_LEFT, &type_vector, &type_void, &type_vector, OPF_STD}, {7, "^", "BITXOR_V", PC_BITXOR, ASSOC_LEFT, &type_vector, &type_vector, &type_vector, OPF_STD}, {7, "*^", "POW_F", PC_MULDIV, ASSOC_LEFT, &type_float, &type_float, &type_float, OPF_STD}, {7, "*^", "POW_I", PC_MULDIV, ASSOC_LEFT, &type_integer, &type_integer, &type_integer, OPF_STD}, {7, "*^", "POW_FI", PC_MULDIV, ASSOC_LEFT, &type_float, &type_integer, &type_float, OPF_STD}, {7, "*^", "POW_IF", PC_MULDIV, ASSOC_LEFT, &type_integer, &type_float, &type_float, OPF_STD}, {7, "><", "CROSS_V", PC_MULDIV, ASSOC_LEFT, &type_vector, &type_vector, &type_vector, OPF_STD}, {7, "==", "EQ_FLD", PC_EQUALITY, ASSOC_LEFT, &type_field, &type_field, &type_bfloat, OPF_STD}, {7, "!=", "NE_FLD", PC_EQUALITY, ASSOC_LEFT, &type_field, &type_field, &type_bfloat, OPF_STD}, {7, "<=>", "SPACESHIP_F", PC_EQUALITY, ASSOC_LEFT, &type_float, &type_float, &type_float, OPF_STD}, {7, "<=>", "SPACESHIP_S", PC_EQUALITY, ASSOC_LEFT, &type_string, &type_string, &type_float, OPF_STD}, //uint32 opcodes. they match the int32 ones so emulation is basically swapping them over. {7, "+", "ADD_U", PC_ADDSUB, ASSOC_LEFT, &type_uint, &type_uint, &type_uint, OPF_STD}, {7, "-", "SUB_U", PC_ADDSUB, ASSOC_LEFT, &type_uint, &type_uint, &type_uint, OPF_STD}, {7, "*", "MUL_U", PC_MULDIV, ASSOC_LEFT, &type_uint, &type_uint, &type_uint, OPF_STD}, {7, "%", "MOD_U", PC_MULDIV, ASSOC_LEFT, &type_uint, &type_uint, &type_uint, OPF_STD}, {7, "&", "BITAND_U", PC_BITAND, ASSOC_LEFT, &type_uint, &type_uint, &type_uint, OPF_STD}, {7, "|", "BITOR_U", PC_BITOR, ASSOC_LEFT, &type_uint, &type_uint, &type_uint, OPF_STD}, {7, "^", "BITXOR_U", PC_BITXOR, ASSOC_LEFT, &type_uint, &type_uint, &type_uint, OPF_STD}, {7, "~", "BITNOT_U", PC_UNARY, ASSOC_LEFT, &type_uint, &type_void, &type_uint, OPF_STDUNARY}, {7, "&~=", "BITCLR_U", PC_STORE, ASSOC_LEFT, &type_uint, &type_uint, &type_uint, OPF_STORE}, {7, "<<", "LSHIFT_U", PC_SHIFT, ASSOC_LEFT, &type_uint, &type_uint, &type_uint, OPF_STD}, {7, ">=", "GE_U", PC_RELATION, ASSOC_LEFT, &type_uint, &type_uint, &type_bint, OPF_STD}, {7, ">", "GT_U", PC_RELATION, ASSOC_LEFT, &type_uint, &type_uint, &type_bint, OPF_STD}, {7, "==", "EQ_U", PC_EQUALITY, ASSOC_LEFT, &type_uint, &type_uint, &type_bint, OPF_STD}, {7, "!=", "NE_U", PC_EQUALITY, ASSOC_LEFT, &type_uint, &type_uint, &type_bint, OPF_STD}, //64bit ones that we can emulate cheaply or are rare. {7, "~", "BITNOT_I64", PC_UNARY, ASSOC_LEFT, &type_int64, &type_void, &type_int64, OPF_STDUNARY}, {7, "&~=", "BITCLR_I64", PC_STORE, ASSOC_LEFT, &type_int64, &type_int64, &type_int64, OPF_STORE}, {7, ">=", "GE_I64", PC_RELATION, ASSOC_LEFT, &type_int64, &type_int64, &type_bint, OPF_STD}, {7, ">", "GT_I64", PC_RELATION, ASSOC_LEFT, &type_int64, &type_int64, &type_bint, OPF_STD}, //unsigned versions (emulated via signed int64 ones, just with different types). {7, "+", "ADD_U64", PC_ADDSUB, ASSOC_LEFT, &type_uint64, &type_uint64, &type_uint64, OPF_STD}, {7, "-", "SUB_U64", PC_ADDSUB, ASSOC_LEFT, &type_uint64, &type_uint64, &type_uint64, OPF_STD}, {7, "*", "MUL_U64", PC_MULDIV, ASSOC_LEFT, &type_uint64, &type_uint64, &type_uint64, OPF_STD}, {7, "%", "MOD_U64", PC_MULDIV, ASSOC_LEFT, &type_uint64, &type_uint64, &type_uint64, OPF_STD}, {7, "&", "BITAND_U64", PC_BITAND, ASSOC_LEFT, &type_uint64, &type_uint64, &type_uint64, OPF_STD}, {7, "|", "BITOR_U64", PC_BITOR, ASSOC_LEFT, &type_uint64, &type_uint64, &type_uint64, OPF_STD}, {7, "^", "BITXOR_U64", PC_BITXOR, ASSOC_LEFT, &type_uint64, &type_uint64, &type_uint64, OPF_STD}, {7, "~", "BITNOT_U64", PC_UNARY, ASSOC_LEFT, &type_uint64, &type_void, &type_uint64, OPF_STDUNARY}, {7, "&~=", "BITCLR_U64", PC_STORE, ASSOC_LEFT, &type_uint64, &type_uint64, &type_uint64, OPF_STORE}, {7, "<<", "LSHIFT_U64I", PC_SHIFT, ASSOC_LEFT, &type_uint64, &type_integer, &type_uint64, OPF_STD}, {7, ">=", "GE_U64", PC_RELATION, ASSOC_LEFT, &type_uint64, &type_uint64, &type_bint, OPF_STD}, {7, ">", "GT_U64", PC_RELATION, ASSOC_LEFT, &type_uint64, &type_uint64, &type_bint, OPF_STD}, {7, "==", "EQ_U64", PC_EQUALITY, ASSOC_LEFT, &type_uint64, &type_uint64, &type_bint, OPF_STD}, {7, "!=", "NE_U64", PC_EQUALITY, ASSOC_LEFT, &type_uint64, &type_uint64, &type_bint, OPF_STD}, {7, "&", "BITAND_D", PC_BITAND, ASSOC_LEFT, &type_double, &type_double, &type_double, OPF_STD}, {7, "|", "BITOR_D", PC_BITOR, ASSOC_LEFT, &type_double, &type_double, &type_double, OPF_STD}, {7, "^", "BITXOR_D", PC_BITXOR, ASSOC_LEFT, &type_double, &type_double, &type_double, OPF_STD}, {7, "~", "BITNOT_D", PC_UNARY, ASSOC_LEFT, &type_double, &type_void, &type_bint, OPF_STDUNARY}, {7, "&~=", "BITCLR_D", PC_STORE, ASSOC_LEFT, &type_double, &type_double, &type_double, OPF_STORE}, {7, "<<", "LSHIFT_DI", PC_SHIFT, ASSOC_LEFT, &type_double, &type_integer, &type_double, OPF_STD}, {7, ">>", "RSHIFT_DI", PC_SHIFT, ASSOC_LEFT, &type_double, &type_integer, &type_double, OPF_STD}, {7, ">=", "GE_D", PC_RELATION, ASSOC_LEFT, &type_double, &type_double, &type_bint, OPF_STD}, {7, ">", "GT_D", PC_RELATION, ASSOC_LEFT, &type_double, &type_double, &type_bint, OPF_STD}, {7, "", "WSTATE", PC_NONE, ASSOC_LEFT, &type_float, &type_float, &type_void}, {0, NULL, "OPD_GOTO_FORSTART"}, {0, NULL, "OPD_GOTO_WHILE1"}, {0, NULL, "OPD_GOTO_BREAK"}, {0, NULL, "OPD_GOTO_DEFAULT"}, {0, NULL} }; static pbool OpAssignsToC(unsigned int op) { // calls, switches and cases DON'T if(pr_opcodes[op].type_c == &type_void) return false; if(op >= OP_SWITCH_F && op <= OP_CALL8H) return false; // if(op >= OP_RAND0 && op <= OP_RANDV2) // return false; // they use a and b, but have 3 types // safety if(op >= OP_BITSETSTORE_F && op <= OP_BITCLRSTOREP_F) return false; /*if(op >= OP_STORE_I && op <= OP_STORE_FI) return false; <- add STOREP_*?*/ if(op == OP_STOREP_C || op == OP_STOREP_I8 || op == OP_STOREP_I16) return false; if((op >= OP_STORE_F && op <= OP_STOREP_FNC) || op == OP_STOREP_P || op == OP_STORE_P || op == OP_STORE_I64) return false; if(op >= OP_MULSTORE_F && op <= OP_SUBSTOREP_V) return false; if (op >= OP_STORE_I && op <= OP_STORE_FI) return false; if ((op >= OP_STOREF_V && op <= OP_STOREF_I) || op == OP_STOREF_I64) return false; //reads it, doesn't write. if (op == OP_BOUNDCHECK || op == OP_UNUSED || op == OP_POP) return false; return true; } static pbool OpAssignsToB(unsigned int op) { if(op >= OP_BITSETSTORE_F && op <= OP_BITCLRSTOREP_F) return true; if(op >= OP_STORE_I && op <= OP_STORE_FI) return true; if(op == OP_STOREP_C || op == OP_STOREP_I8 || op == OP_STOREP_I16) return true; if(op >= OP_MULSTORE_F && op <= OP_SUBSTOREP_V) return true; if((op >= OP_STORE_F && op <= OP_STOREP_FNC) || op == OP_STOREP_P || op == OP_STORE_P || op == OP_STORE_I64) return true; return false; } #define OpAssignsToA(op) false #ifdef _DEBUG static int OpAssignsCount(unsigned int op) { switch(op) { case OP_DONE: case OP_RETURN: return 0; //eep case OP_CALL0: case OP_CALL1: case OP_CALL2: case OP_CALL3: case OP_CALL4: case OP_CALL5: case OP_CALL6: case OP_CALL7: case OP_CALL8: case OP_CALL1H: case OP_CALL2H: case OP_CALL3H: case OP_CALL4H: case OP_CALL5H: case OP_CALL6H: case OP_CALL7H: case OP_CALL8H: return 0; //also, eep. case OP_STATE: case OP_WSTATE: case OP_CSTATE: case OP_CWSTATE: case OP_THINKTIME: return 0; //egads case OP_RAND0: case OP_RAND1: case OP_RAND2: case OP_RANDV0: case OP_RANDV1: case OP_RANDV2: return 1; //writes C, even when there's no A or B arg specified. case OP_UNUSED: case OP_POP: return 0; //FIXME //branches have no side effects, other than the next instruction (or runaway loop) case OP_SWITCH_F: case OP_SWITCH_V: case OP_SWITCH_S: case OP_SWITCH_E: case OP_SWITCH_FNC: case OP_SWITCH_I: case OP_GOTO: case OP_IF_I: case OP_IFNOT_I: case OP_IF_S: case OP_IFNOT_S: case OP_IF_F: case OP_IFNOT_F: case OP_CASE: case OP_CASERANGE: return 0; case OP_STOREF_V: case OP_STOREF_F: case OP_STOREF_S: case OP_STOREF_I: case OP_STOREF_I64: return 0; //stores to a.b rather than any direct value... case OP_BOUNDCHECK: return 0; default: //the majority will write c return 1; } } static void OpAssignsTo_Debug(void) { int i; for (i = 0; i < OP_NUMREALOPS; i++) { if (OpAssignsToA(i) + OpAssignsToB(i) + OpAssignsToC(i) != OpAssignsCount(i)) { //we don't know what it assigns to. bug. QCC_PR_ParseError(0, "opcode %s metadata is bugged", pr_opcodes[i].opname); } } } #endif /*pbool OpAssignedTo(QCC_def_t *v, unsigned int op) { if(OpAssignsToC(op)) { } else if(OpAssignsToB(op)) { } return false; } */ #undef ASSOC_RIGHT_RESULT //#define TERM_PRIORITY 0 #define FUNC_PRIORITY 1 #define UNARY_PRIORITY 1 //~ ! //#define MULDIV_PRIORITY priority_class[PC_MULDIV] //* / % //#define ADDSUB_PRIORITY priority_class[PC_ADDSUB] //+ - //#define BITSHIFT_PRIORITY priority_class[PC_BITSHIFT] //<< >> //#define COMPARISON_PRIORITY priority_class[PC_COMPARISON] //< <= > >= //#define EQUALITY_PRIORITY priority_class[PC_EQUALITY] //== != //#define BITAND_PRIORITY priority_class[PC_BITAND] //& //#define BITXOR_PRIORITY priority_class[PC_BITXOR] //^ //#define BITOR_PRIORITY priority_class[PC_BITOR] //| //#define LOGICAND_PRIORITY priority_class[PC_LOGICAND] //&& //#define LOGICOR_PRIORITY priority_class[PC_LOGICOR] //|| #define TERNARY_PRIORITY priority_class[PC_TERNARY] //?: (in C: (a||b)?(c):(d||e) ) #define ASSIGN_PRIORITY priority_class[PC_STORE] //QC is WRONG compared to C //#define COMMA_PRIORITY #define TOP_PRIORITY priority_class[MAX_PRIORITY_CLASSES] #define NOT_PRIORITY priority_class[PC_UNARYNOT] QCC_opcode_t *opcodes_store[] = { NULL }; QCC_opcode_t *opcodes_addstore[] = { &pr_opcodes[OP_ADD_F], &pr_opcodes[OP_ADD_V], &pr_opcodes[OP_ADD_I], &pr_opcodes[OP_ADD_U], &pr_opcodes[OP_ADD_I64], &pr_opcodes[OP_ADD_U64], &pr_opcodes[OP_ADD_D], &pr_opcodes[OP_ADD_FI], &pr_opcodes[OP_ADD_IF], &pr_opcodes[OP_ADD_SF], &pr_opcodes[OP_ADD_PI], &pr_opcodes[OP_ADD_IP], &pr_opcodes[OP_ADD_PU], &pr_opcodes[OP_ADD_UP], &pr_opcodes[OP_ADD_PF], &pr_opcodes[OP_ADD_FP], &pr_opcodes[OP_ADD_SI], &pr_opcodes[OP_ADD_IS], &pr_opcodes[OP_ADD_EI], &pr_opcodes[OP_ADD_EF], NULL }; QCC_opcode_t *opcodes_addstorep[] = { &pr_opcodes[OP_ADDSTOREP_F], &pr_opcodes[OP_ADDSTOREP_V], &pr_opcodes[OP_ADDSTOREP_I], &pr_opcodes[OP_ADDSTOREP_IF], &pr_opcodes[OP_ADDSTOREP_FI], NULL }; QCC_opcode_t *opcodes_substore[] = { &pr_opcodes[OP_SUB_F], &pr_opcodes[OP_SUB_V], &pr_opcodes[OP_SUB_I], &pr_opcodes[OP_SUB_U], &pr_opcodes[OP_SUB_FI], &pr_opcodes[OP_SUB_IF], &pr_opcodes[OP_SUB_S], &pr_opcodes[OP_SUB_PP], &pr_opcodes[OP_SUB_PI], &pr_opcodes[OP_SUB_PU], &pr_opcodes[OP_SUB_PF], &pr_opcodes[OP_SUB_SI], &pr_opcodes[OP_SUB_EI], &pr_opcodes[OP_SUB_EF], &pr_opcodes[OP_SUB_I64], &pr_opcodes[OP_SUB_U64], &pr_opcodes[OP_SUB_D], NULL }; QCC_opcode_t *opcodes_substorep[] = { &pr_opcodes[OP_SUBSTOREP_F], &pr_opcodes[OP_SUBSTOREP_V], &pr_opcodes[OP_SUBSTOREP_I], &pr_opcodes[OP_SUBSTOREP_IF], &pr_opcodes[OP_SUBSTOREP_FI], NULL }; QCC_opcode_t *opcodes_mulstore[] = { &pr_opcodes[OP_MUL_F], &pr_opcodes[OP_MUL_V], &pr_opcodes[OP_MUL_FV], &pr_opcodes[OP_MUL_IV], &pr_opcodes[OP_MUL_VF], &pr_opcodes[OP_MUL_VI], &pr_opcodes[OP_MUL_I], &pr_opcodes[OP_MUL_U], &pr_opcodes[OP_MUL_FI], &pr_opcodes[OP_MUL_IF], &pr_opcodes[OP_MUL_I64], &pr_opcodes[OP_MUL_U64], &pr_opcodes[OP_MUL_D], NULL }; QCC_opcode_t *opcodes_mulstorep[] = { &pr_opcodes[OP_MULSTOREP_F], &pr_opcodes[OP_MULSTOREP_VF], &pr_opcodes[OP_MULSTOREP_VI], &pr_opcodes[OP_MULSTOREP_I], &pr_opcodes[OP_MULSTOREP_IF], &pr_opcodes[OP_MULSTOREP_FI], NULL }; QCC_opcode_t *opcodes_divstore[] = { &pr_opcodes[OP_DIV_F], &pr_opcodes[OP_DIV_I], &pr_opcodes[OP_DIV_U], &pr_opcodes[OP_DIV_FI], &pr_opcodes[OP_DIV_IF], &pr_opcodes[OP_DIV_VF], &pr_opcodes[OP_DIV_I64], &pr_opcodes[OP_DIV_U64], &pr_opcodes[OP_DIV_D], NULL }; QCC_opcode_t *opcodes_divstorep[] = { &pr_opcodes[OP_DIVSTOREP_F], NULL }; QCC_opcode_t *opcodes_orstore[] = { &pr_opcodes[OP_BITOR_F], &pr_opcodes[OP_BITOR_I], &pr_opcodes[OP_BITOR_U], &pr_opcodes[OP_BITOR_IF], &pr_opcodes[OP_BITOR_FI], &pr_opcodes[OP_BITOR_V], &pr_opcodes[OP_BITOR_I64], &pr_opcodes[OP_BITOR_U64], &pr_opcodes[OP_BITOR_D], NULL }; QCC_opcode_t *opcodes_orstorep[] = { &pr_opcodes[OP_BITSETSTOREP_F], NULL }; QCC_opcode_t *opcodes_xorstore[] = { &pr_opcodes[OP_BITXOR_I], &pr_opcodes[OP_BITXOR_U], &pr_opcodes[OP_BITXOR_F], &pr_opcodes[OP_BITXOR_V], &pr_opcodes[OP_BITXOR_I64], &pr_opcodes[OP_BITXOR_U64], &pr_opcodes[OP_BITXOR_D], NULL }; QCC_opcode_t *opcodes_andstore[] = { &pr_opcodes[OP_BITAND_F], &pr_opcodes[OP_BITAND_I], &pr_opcodes[OP_BITAND_U], &pr_opcodes[OP_BITAND_IF], &pr_opcodes[OP_BITAND_FI], &pr_opcodes[OP_BITAND_V], &pr_opcodes[OP_BITAND_I64], &pr_opcodes[OP_BITAND_U64], &pr_opcodes[OP_BITAND_D], NULL }; QCC_opcode_t *opcodes_clearstore[] = { &pr_opcodes[OP_BITCLR_F], &pr_opcodes[OP_BITCLR_I], &pr_opcodes[OP_BITCLR_U], &pr_opcodes[OP_BITCLR_V], &pr_opcodes[OP_BITCLR_I64], &pr_opcodes[OP_BITCLR_U64], &pr_opcodes[OP_BITCLR_D], NULL }; QCC_opcode_t *opcodes_clearstorep[] = { &pr_opcodes[OP_BITCLRSTOREP_F], NULL }; QCC_opcode_t *opcodes_shlstore[] = { &pr_opcodes[OP_LSHIFT_F], &pr_opcodes[OP_LSHIFT_I], &pr_opcodes[OP_LSHIFT_U], &pr_opcodes[OP_LSHIFT_IF], &pr_opcodes[OP_LSHIFT_FI], &pr_opcodes[OP_LSHIFT_I64I], &pr_opcodes[OP_LSHIFT_U64I], &pr_opcodes[OP_LSHIFT_DI], NULL }; QCC_opcode_t *opcodes_shrstore[] = { &pr_opcodes[OP_RSHIFT_F], &pr_opcodes[OP_RSHIFT_I], &pr_opcodes[OP_RSHIFT_U], &pr_opcodes[OP_RSHIFT_IF], &pr_opcodes[OP_RSHIFT_FI], &pr_opcodes[OP_RSHIFT_I64I], &pr_opcodes[OP_RSHIFT_U64I], &pr_opcodes[OP_RSHIFT_DI], NULL }; QCC_opcode_t *opcodes_spaceship[] = { &pr_opcodes[OP_SPACESHIP_F], &pr_opcodes[OP_SPACESHIP_S], // &pr_opcodes[OP_SPACESHIP_I], // &pr_opcodes[OP_SPACESHIP_U], // &pr_opcodes[OP_SPACESHIP_I64], // &pr_opcodes[OP_SPACESHIP_U64], // &pr_opcodes[OP_SPACESHIP_D], NULL }; QCC_opcode_t *opcodes_none[] = { NULL }; //these evaluate as top first. static QCC_opcode_t *opcodeprioritized[13+1][128]; static int #ifdef _MSC_VER __cdecl #endif sort_opcodenames(const void*a,const void*b) { const QCC_opcode_t *opa = *(QCC_opcode_t*const*)a; const QCC_opcode_t *opb = *(QCC_opcode_t*const*)b; if (opa == NULL) return opb?1:0; if (opb == NULL) return -1; return strcmp(opa->name, opb->name); } void QCC_PrioritiseOpcodes(void) { int pcount[MAX_PRIORITY_CLASSES]; int i, j; QCC_opcode_t *op; priority_class[PC_NONE] = 0; priority_class[PC_UNARY] = 0; priority_class[PC_MEMBER] = 0; if (flag_cpriority) { priority_class[PC_UNARYNOT] = 1; priority_class[PC_MULDIV] = 2; priority_class[PC_ADDSUB] = 3; priority_class[PC_SHIFT] = 4; priority_class[PC_RELATION] = 5; priority_class[PC_EQUALITY] = 6; priority_class[PC_BITAND] = 7; priority_class[PC_BITXOR] = 8; priority_class[PC_BITOR] = 9; priority_class[PC_LOGICAND] = 10; priority_class[PC_LOGICOR] = 11; priority_class[PC_TERNARY] = 12; priority_class[PC_STORE] = 13; } else { priority_class[PC_UNARYNOT] = 5; priority_class[PC_MULDIV] = 3; priority_class[PC_ADDSUB] = 4; priority_class[PC_SHIFT] = 3; priority_class[PC_RELATION] = 5; priority_class[PC_EQUALITY] = 5; priority_class[PC_BITAND] = 3; priority_class[PC_BITXOR] = 3; priority_class[PC_BITOR] = 3; priority_class[PC_LOGICAND] = 7; priority_class[PC_LOGICOR] = 7; priority_class[PC_TERNARY] = 6; priority_class[PC_STORE] = 6; } priority_class[MAX_PRIORITY_CLASSES] = 0; for (j = 0; j < MAX_PRIORITY_CLASSES; j++) if (priority_class[MAX_PRIORITY_CLASSES] < priority_class[j]) priority_class[MAX_PRIORITY_CLASSES] = priority_class[j]; memset(pcount, 0, sizeof(pcount)); memset(opcodeprioritized, 0, sizeof(opcodeprioritized)); for (i = 0; pr_opcodes[i].name; i++) { op = &pr_opcodes[i]; j = priority_class[op->priorityclass]; if (j <= 0 || j > priority_class[MAX_PRIORITY_CLASSES]) continue; //class doesn't need prioritising opcodeprioritized[j][pcount[j]++] = op; } //operators need to be sorted so we don't have to scan through all of them. should probably have some better table for that. for (j = 0; j <= TOP_PRIORITY; j++) qsort (&opcodeprioritized[j][0], pcount[j], sizeof(opcodeprioritized[j][0]), sort_opcodenames); } static pbool QCC_OPCodeValidForTarget(qcc_targetformat_t targfmt, unsigned int qcc_targetversion, QCC_opcode_t *op) { int num; num = op - pr_opcodes; //never any emulated opcodes if (num >= OP_NUMREALOPS) return false; switch(targfmt) { case QCF_STANDARD: case QCF_KK7: case QCF_QTEST: if (num < OP_MULSTORE_F) return true; return false; case QCF_UHEXEN2: case QCF_HEXEN2: if (num >= OP_SWITCH_V && num <= OP_SWITCH_FNC) //these were assigned numbers but were never actually implemtented in standard h2. return false; // if (num >= OP_MULSTORE_F && num <= OP_SUBSTOREP_V) // return false; if (num <= OP_CALL8H) //CALLXH are fixed up. This is to provide more dynamic switching... return true; return false; case QCF_FTEH2: case QCF_FTE: case QCF_FTEDEBUG: #define QCTARGVER_FTE_DEF 5768//5529 #define QCTARGVER_FTE_MAX QCTARGVER_FTE_PRELOCS if (num == OP_PUSH || num >= OP_STOREP_I16) #define QCTARGVER_FTE_PRELOCS 6614//FIXME return (qcc_targetversion>=QCTARGVER_FTE_PRELOCS); //OP_PUSH was buggy before this. if (num >= OP_LT_U) //uint+double+int64+uint64 ops return (qcc_targetversion>=5768); if (num >= OP_STOREP_I8) //byte return (qcc_targetversion>=5744); #define QCTARGVER_FTE_STOREP_IDX 5712//added support for the argc arg for storep_* opcodes. // return (qcc_targetversion>=5744); if (num >= OP_STOREF_V) //field stores return (qcc_targetversion>=5698); if (num >= OP_IF_F) //iffloat fixes return (qcc_targetversion>=3349); return true; case QCF_QSS: #define QCTARGVER_QSS_MAX 0 if (num < OP_MULSTORE_F) return true; switch(num) { //int operations. case OP_ADD_I: case OP_ADD_IF: case OP_ADD_FI: case OP_SUB_I: case OP_SUB_IF: case OP_SUB_FI: case OP_MUL_I: case OP_MUL_IF: case OP_MUL_FI: case OP_MUL_VI: case OP_DIV_VF: case OP_DIV_I: case OP_DIV_IF: case OP_DIV_FI: case OP_BITAND_I: case OP_BITOR_I: case OP_BITAND_IF: case OP_BITOR_IF: case OP_BITAND_FI: case OP_BITOR_FI: case OP_GE_I: case OP_LE_I: case OP_GT_I: case OP_LT_I: case OP_AND_I: case OP_OR_I: case OP_GE_IF: case OP_LE_IF: case OP_GT_IF: case OP_LT_IF: case OP_AND_IF: case OP_OR_IF: case OP_GE_FI: case OP_LE_FI: case OP_GT_FI: case OP_LT_FI: case OP_AND_FI: case OP_OR_FI: case OP_NOT_I: case OP_EQ_I: case OP_EQ_IF: case OP_EQ_FI: case OP_NE_I: case OP_NE_IF: case OP_NE_FI: case OP_CONV_ITOF: case OP_CONV_FTOI: case OP_BITXOR_I: case OP_RSHIFT_I: case OP_LSHIFT_I: case OP_STORE_I: case OP_STORE_IF: case OP_STORE_FI: return true; //bugfixes... case OP_IF_F: case OP_IFNOT_F: case OP_IF_S: case OP_IFNOT_S: return true; //faster field access case OP_STOREF_V: //3 elements... case OP_STOREF_F: //1 fpu element... case OP_STOREF_S: //1 string reference case OP_STOREF_I: //1 non-string reference/int return true; //dp-style arrays case OP_GLOAD_I: case OP_GLOAD_F: case OP_GLOAD_FLD: case OP_GLOAD_ENT: case OP_GLOAD_S: case OP_GLOAD_FNC: case OP_GLOAD_V: case OP_GSTOREP_I: case OP_GSTOREP_F: case OP_GSTOREP_ENT: case OP_GSTOREP_FLD: case OP_GSTOREP_S: case OP_GSTOREP_FNC: case OP_GSTOREP_V: return true; case OP_BOUNDCHECK: return true; //fte-style arrays case OP_LOADA_F: case OP_LOADA_V: case OP_LOADA_S: case OP_LOADA_ENT: case OP_LOADA_FLD: case OP_LOADA_FNC: case OP_LOADA_I: case OP_GLOBALADDRESS: return true; //pointer-to-global //other useful pointer stuff // case OP_LOADP_F: // case OP_LOADP_V: // case OP_LOADP_S: // case OP_LOADP_ENT: // case OP_LOADP_FLD: // case OP_LOADP_FNC: // case OP_LOADP_I: // case OP_STOREP_I: // return true; //hexen2-style arrays. case OP_FETCH_GBL_F: case OP_FETCH_GBL_S: case OP_FETCH_GBL_E: case OP_FETCH_GBL_FNC: case OP_FETCH_GBL_V: return true; //hexen2's calling convention. case OP_CALL1H: case OP_CALL2H: case OP_CALL3H: case OP_CALL4H: case OP_CALL5H: case OP_CALL6H: case OP_CALL7H: case OP_CALL8H: return true; default: return false; } return false; case QCF_DARKPLACES: #define QCTARGVER_DP_DEF 12901 #define QCTARGVER_DP_MAX 20250104 //https://github.com/DarkPlacesEngine/DarkPlaces/pull/237 //all id opcodes. if (num < OP_MULSTORE_F) return true; //extended opcodes. switch(num) { case OP_RSHIFT_I: case OP_LSHIFT_I: case OP_LE_U: case OP_LT_U: case OP_DIV_U: case OP_RSHIFT_U: return (qcc_targetversion>=20250104); //https://github.com/DarkPlacesEngine/DarkPlaces/pull/237 case OP_ADD_PIW: case OP_GLOBALADDRESS: case OP_LOADA_F: case OP_LOADA_V: case OP_LOADA_S: case OP_LOADA_ENT: case OP_LOADA_FLD: case OP_LOADA_FNC: case OP_LOADA_I: case OP_LOAD_P: case OP_LOADP_F: case OP_LOADP_V: case OP_LOADP_S: case OP_LOADP_ENT: case OP_LOADP_FLD: case OP_LOADP_FNC: case OP_LOADP_I: #define QCTARGVER_DP_STOREP_IDX 20241108 //FIXME: set properly once https://github.com/DarkPlacesEngine/DarkPlaces/pull/215 is merged. return (qcc_targetversion>=QCTARGVER_DP_STOREP_IDX); //https://github.com/DarkPlacesEngine/DarkPlaces/pull/215 //opcodes that were buggy in DP. case OP_ADD_IF: //dp wrote these to ints, which doesn't match our defined opcodes. not really a problem. case OP_SUB_IF: //dp wrote these to ints, which doesn't match our defined opcodes. revert to _F. case OP_MUL_IF: //dp wrote these to ints, which doesn't match our defined opcodes. revert to _F case OP_DIV_IF: //dp wrote these to ints, which doesn't match our defined opcodes. revert to _F case OP_BITAND_FI: //dp outputs floats, which doesn't match our defined opcodes. case OP_BITOR_FI: //dp outputs floats, which doesn't match our defined opcodes. case OP_STORE_I: //was omitted. case OP_STORE_P: //was omitted. return (qcc_targetversion>=12901); case OP_ADD_I: case OP_ADD_FI: case OP_SUB_I: case OP_SUB_FI: case OP_CONV_ITOF: case OP_CONV_FTOI: case OP_LOAD_I: //no worse than the other OP_LOAD_X functions. case OP_STOREP_I: //no worse than the other OP_STOREP_X functions case OP_BITAND_I: case OP_BITOR_I: case OP_MUL_I: case OP_DIV_I: case OP_EQ_I: case OP_NE_I: case OP_NOT_I: case OP_DIV_VF: case OP_LE_I: case OP_GE_I: case OP_LT_I: case OP_GT_I: case OP_LE_IF: case OP_GE_IF: case OP_LT_IF: case OP_GT_IF: case OP_LE_FI: case OP_GE_FI: case OP_LT_FI: case OP_GT_FI: case OP_EQ_IF: case OP_EQ_FI: case OP_MUL_FI: case OP_MUL_VI: case OP_DIV_FI: case OP_BITAND_IF: case OP_BITOR_IF: case OP_AND_I: case OP_OR_I: case OP_AND_IF: case OP_OR_IF: case OP_AND_FI: case OP_OR_FI: case OP_NE_IF: case OP_NE_FI: case OP_GSTOREP_I: //stores into the globals array, they can change any global dynamically, but thats supposedly no real security risk. case OP_GSTOREP_F: case OP_GSTOREP_ENT: case OP_GSTOREP_FLD: case OP_GSTOREP_S: case OP_GSTOREP_FNC: case OP_GSTOREP_V: // case OP_GADDRESS: case OP_GLOAD_I://c = globals[inta] case OP_GLOAD_F://note: fte does not support these case OP_GLOAD_FLD: case OP_GLOAD_ENT: case OP_GLOAD_S: case OP_GLOAD_FNC: case OP_BOUNDCHECK: case OP_GLOAD_V: return true; //this opcode looks weird case OP_GADDRESS://floatc = globals[inta + floatb] (fte does not support) return false; default: //anything I forgot to mention is new, and doesn't work in DP that I'm aware of. return false; } } return false; } pbool QCC_OPCodeValid(QCC_opcode_t *op) { return op->flags & OPF_VALID; } static pbool QCC_OPCode_StorePOffset(void) { //5712 switch(qcc_targetformat) { case QCF_FTE: case QCF_FTEH2: case QCF_FTEDEBUG: return (qcc_targetversion>=QCTARGVER_FTE_STOREP_IDX); case QCF_DARKPLACES: return (qcc_targetversion>=QCTARGVER_DP_STOREP_IDX); case QCF_QSS: return true; default: return true; } } void QCC_OPCodeSetTarget(qcc_targetformat_t targfmt, unsigned int targver) { size_t i; qcc_targetformat = targfmt; qcc_targetversion = targver; flag_undefwordsize = false; flag_pointerrelocs = false; switch(qcc_targetformat) { case QCF_FTE: case QCF_FTEH2: case QCF_FTEDEBUG: if (qcc_targetversion > QCTARGVER_FTE_MAX) { if (qcc_targetversion != ~0u) QCC_PR_ParseWarning(WARN_BADTARGET, "target revision %u is unknown, assuming revision %u", qcc_targetversion, QCTARGVER_FTE_MAX); qcc_targetversion = QCTARGVER_FTE_MAX; } flag_pointerrelocs = qcc_targetversion >= QCTARGVER_FTE_PRELOCS; break; case QCF_DARKPLACES: flag_undefwordsize = true; //DP insists on using word-aligned pointers instead of byte-aligned ones. This breaks both pointer maths and string<>pointer casts. if (qcc_targetversion > QCTARGVER_DP_MAX) { if (qcc_targetversion != ~0u) QCC_PR_ParseWarning(WARN_BADTARGET, "target revision %u is unknown, assuming revision %u", qcc_targetversion, QCTARGVER_DP_MAX); qcc_targetversion = QCTARGVER_DP_MAX; } break; case QCF_QSS: if (qcc_targetversion > QCTARGVER_QSS_MAX) { if (qcc_targetversion != ~0u) QCC_PR_ParseWarning(WARN_BADTARGET, "target revision %u is unknown, assuming revision %u", qcc_targetversion, QCTARGVER_QSS_MAX); qcc_targetversion = QCTARGVER_QSS_MAX; } break; default: if (qcc_targetversion > 0) { if (qcc_targetversion != ~0u) QCC_PR_ParseWarning(WARN_BADTARGET, "target revision %u is unknown, assuming revision %u", qcc_targetversion, 0); qcc_targetversion = 0; } break; } for (i = 0; i < OP_NUMOPS; i++) { QCC_opcode_t *op = &pr_opcodes[i]; if (QCC_OPCodeValidForTarget(targfmt, qcc_targetversion, op)) op->flags |= OPF_VALID; else op->flags &= ~OPF_VALID; } } static struct { qcc_targetformat_t target; const char *name; unsigned int defaultrev; } targets[] = { {QCF_STANDARD, "standard", 0}, {QCF_STANDARD, "vanilla", 0}, {QCF_STANDARD, "q1", 0}, {QCF_STANDARD, "id", 0}, {QCF_STANDARD, "quakec", 0}, {QCF_STANDARD, "qs", 0}, {QCF_QSS, "qss", QCTARGVER_QSS_MAX}, {QCF_HEXEN2, "hexen2", 0}, {QCF_HEXEN2, "h2", 0}, {QCF_UHEXEN2, "uhexen2", 0}, {QCF_KK7, "kkqwsv", 0}, {QCF_KK7, "kk7", 0}, {QCF_KK7, "version7", 0}, {QCF_FTE, "fte", QCTARGVER_FTE_DEF}, //'latest' stable revision. {QCF_FTEH2, "fteh2", QCTARGVER_FTE_DEF}, {QCF_FTEDEBUG, "ftedebug", QCTARGVER_FTE_DEF}, {QCF_FTEDEBUG, "debug", QCTARGVER_FTE_DEF}, {QCF_FTE, "quake2c", 5744}, //an alias for Paril's project, which does various pointer stuff. the revision should be high enough for str[int] ops. {QCF_DARKPLACES,"darkplaces", QCTARGVER_DP_DEF}, {QCF_DARKPLACES,"dp", QCTARGVER_DP_DEF}, {QCF_QTEST, "qtest", 0}, {0, NULL} }; pbool QCC_OPCodeSetTargetName(const char *targ) { //fte_24 -> fmt=QCF_FTE, ver=24 const char *ver; size_t i, tlen; ver = strchr(targ, '_'); if (ver) { tlen = ver-targ; ver++; } else { tlen = strlen(targ); ver = NULL; } for (i = 0; targets[i].name; i++) if (!strnicmp(targ, targets[i].name, tlen) && strlen(targets[i].name)==tlen) { qcc_targetformat_t newtype = targets[i].target; if (numstatements > 1) { #define ish2(fmt) ((fmt) == QCF_HEXEN2 || (fmt) == QCF_UHEXEN2 || (fmt) == QCF_FTEH2) pbool nowh2 = ish2(newtype); pbool wash2 = ish2(qcc_targetformat); if (nowh2 != wash2) { //hexen2 involves bulk renaming of OP_CALL->OP_CALLH opcodes on load time, which breaks any previously compiled code, rather than just extra statements. QCC_PR_ParseWarning(WARN_BADTARGET, "Cannot switch to %shexen2 target \'%s\' after the first statement. Ignored.", targ, wash2?"non-":""); return true; } } QCC_OPCodeSetTarget(targets[i].target, ver?atoi(ver):targets[i].defaultrev); return true; } return false; } #define EXPR_WARN_ABOVE_1 2 #define EXPR_DISALLOW_COMMA 4 #define EXPR_DISALLOW_ARRAYASSIGN 8 QCC_sref_t QCC_PR_Expression (int priority, int exprflags); int QCC_AStatementJumpsTo(int targ, int first, int last); pbool QCC_StatementIsAJump(int stnum, int notifdest); //=========================================================================== //typically used for debugging. Also used to determine function names for intrinsics. static const char *QCC_GetSRefName(QCC_sref_t ref) { if (ref.sym && ref.sym->name/* && !ref.ofs*/) { if (ref.sym->temp) return ref.cast->name; return ref.sym->name; } return "TEMP"; } qc_inlinestatic QCC_eval_t *QCC_SRef_Data(QCC_sref_t ref) { return (QCC_eval_t*)&ref.sym->symboldata[ref.ofs]; } qc_inlinestatic QCC_eval_t *QCC_SRef_DataWord(QCC_sref_t ref, int word) { return (QCC_eval_t*)&ref.sym->symboldata[ref.ofs + word]; } //retrieves the data associated with the reference if its constant and thus readable at compile time. qc_inlinestatic const QCC_eval_t *QCC_SRef_EvalConst(QCC_sref_t ref) { if (ref.sym && ref.sym->initialized && ref.sym->constant && !ref.sym->reloc) { ref.sym->referenced = true; return QCC_SRef_Data(ref); } return NULL; } //retrieves the data associated with the reference if its constant and thus readable at compile time. qc_inlinestatic const char *QCC_SRef_EvalStringConst(QCC_sref_t ref) { if (ref.cast->type == ev_string) { const QCC_eval_t *c = QCC_SRef_EvalConst(ref); if (c) return &strings[c->string]; } return NULL; } //NULL is defined as the immediate 0 or 0i (aka __NULL__). //named constants that merely have the value 0 are NOT meant to count. qc_inlinestatic pbool QCC_SRef_IsNull(QCC_sref_t ref) { if (ref.cast->type == ev_integer || ref.cast->type == ev_uint || ref.cast->type == ev_float || ref.cast->type == ev_pointer) { const QCC_eval_t *c = QCC_SRef_EvalConst(ref); if (c) return !c->_int; } return false; } //retrieves the int value associated with the reference if its constant and thus readable at compile time. static pint64_t QCC_Eval_Int(const QCC_eval_t *eval, QCC_type_t *type) { if (!eval) { QCC_PR_ParseWarning(ERR_BADIMMEDIATETYPE, "Not an initialised constant"); return 0; } switch(type->type) { case ev_float: return eval->_float; case ev_double: return eval->_double; case ev_integer: return eval->_int; // case ev_function: // case ev_entity: // case ev_field: // case ev_pointer: case ev_uint: return eval->_uint; case ev_boolean: return QCC_Eval_Int(eval, type->parentclass); //bools are weird. case ev_int64: return eval->i64; case ev_uint64: return eval->u64; // case ev_string: // case ev_vector: case ev_struct: case ev_union: default: QCC_PR_ParseWarning(ERR_BADIMMEDIATETYPE, "Unable to evaluate to numeric constant"); return 0; } } //retrieves the int value associated with the reference if its constant and thus readable at compile time. static float QCC_Eval_Float(const QCC_eval_t *eval, QCC_type_t *type) { if (!eval) { QCC_PR_ParseWarning(ERR_BADIMMEDIATETYPE, "Not an initialised constant"); return 0; } switch(type->type) { case ev_float: return eval->_float; case ev_double: return eval->_double; case ev_integer: return eval->_int; // case ev_function: // case ev_entity: // case ev_field: // case ev_pointer: case ev_uint: return eval->_uint; case ev_boolean: return QCC_Eval_Float(eval, type->parentclass); //bools are weird. case ev_int64: return eval->i64; case ev_uint64: return eval->u64; // case ev_string: // case ev_vector: case ev_struct: case ev_union: default: QCC_PR_ParseWarning(ERR_BADIMMEDIATETYPE, "Unable to evaluate to numeric constant"); return 0; } } //retrieves the data associated with the reference if its constant and thus readable at compile time. static pbool QCC_Eval_Truth(const QCC_eval_t *eval, QCC_type_t *type, pbool assume) { pbool istrue = false; int i; if (!eval) return assume; switch(type->type) { case ev_float: istrue = (eval->_float != 0); break; case ev_double: istrue = (eval->_double != 0); break; case ev_integer: case ev_uint: case ev_function: case ev_entity: case ev_field: case ev_pointer: case ev_boolean: istrue = (eval->_int != 0); break; case ev_int64: case ev_uint64: istrue = (eval->i64 != 0); break; case ev_string: if (flag_ifstring) istrue = !!strings[eval->_int]; //value compare. else istrue = (eval->_int != 0); //offset compare break; case ev_vector: if (flag_ifvector) istrue = eval->vector[0] || eval->vector[1] || eval->vector[2]; else istrue = (eval->_float != 0); //legacy buggy behaviour break; case ev_struct: case ev_union: QCC_PR_ParseWarning(WARN_CONDITIONALTYPEMISMATCH, "conditional type mismatch: %s", basictypenames[type->type]); //fall through anyway. default: for (i = 0; i < type->size; i++) if ((&eval->_int)[i]) break; istrue = i!=type->size; break; } return istrue; } static const char *QCC_GetRefName(QCC_ref_t *ref, char *buffer, size_t buffersize) { switch(ref->type) { case REF_FIELD: case REF_NONVIRTUAL: QC_snprintfz(buffer, buffersize, "%s.%s", QCC_GetSRefName(ref->base), QCC_GetSRefName(ref->index)); return buffer; case REF_ARRAY: case REF_STRING: QC_snprintfz(buffer, buffersize, "%s[%s]", QCC_GetSRefName(ref->base), QCC_GetSRefName(ref->index)); return buffer; case REF_POINTER: QC_snprintfz(buffer, buffersize, "%s->%s", QCC_GetSRefName(ref->base), QCC_GetSRefName(ref->index)); return buffer; case REF_ACCESSOR: if (*ref->accessor->fieldname) { //not an anonymous field if (ref->index.sym) QC_snprintfz(buffer, buffersize, "%s.%s[%s]", QCC_GetSRefName(ref->base), ref->accessor->fieldname, QCC_GetSRefName(ref->index)); else QC_snprintfz(buffer, buffersize, "%s.%s", QCC_GetSRefName(ref->base), ref->accessor->fieldname); } else { if (ref->index.sym) QC_snprintfz(buffer, buffersize, "%s[%s]", QCC_GetSRefName(ref->base), QCC_GetSRefName(ref->index)); else QC_snprintfz(buffer, buffersize, "*%s", QCC_GetSRefName(ref->base)); } break; case REF_ARRAYHEAD: case REF_GLOBAL: default: break; } return QCC_GetSRefName(ref->base); } /* ============ PR_Statement Emits a primitive statement, returning the var it places it's value in ============ */ static int QCC_ShouldConvert(QCC_type_t *from, etype_t wanted) { if (from->type == ev_boolean && wanted != ev_boolean) from = from->parentclass; /*no conversion needed*/ if (from->type == wanted) return 0; if (from->type == ev_integer && wanted == ev_function) return 0; if (from->type == ev_integer && wanted == ev_pointer) return 0; /*stuff needs converting*/ if (from->type == ev_pointer && from->aux_type) { if (from->aux_type->type == ev_float && wanted == ev_integer) return OP_LOADP_FTOI; if (from->aux_type->type == ev_integer && wanted == ev_float) return OP_LOADP_ITOF; } else { if (from->type == ev_float && wanted == ev_integer) return OP_CONV_FTOI; if (from->type == ev_integer && wanted == ev_float) return OP_CONV_ITOF; if (from->type == ev_float && wanted == ev_uint) return OP_CONV_FU; if (from->type == ev_uint && wanted == ev_float) return OP_CONV_UF; if ((from->type == ev_integer||from->type == ev_uint) && (wanted == ev_integer||wanted == ev_uint)) return 0; if ((from->type == ev_int64||from->type == ev_uint64) && (wanted == ev_int64||wanted == ev_uint64)) return 0; if ((from->type == ev_int64||from->type == ev_uint64) && (wanted == ev_integer||wanted == ev_uint)) return OP_CONV_I64I; if ((from->type == ev_integer) && (wanted == ev_int64 || wanted == ev_uint64)) return OP_CONV_II64; if (from->type == ev_uint && (wanted == ev_int64 || wanted == ev_uint64)) return OP_CONV_UI64; if (from->type == ev_float && wanted == ev_double) return OP_CONV_FD; if (from->type == ev_double && wanted == ev_float) return OP_CONV_DF; if (from->type == ev_int64 && wanted == ev_float) return OP_CONV_I64F; if (from->type == ev_float && wanted == ev_int64) return OP_CONV_FI64; if (from->type == ev_int64 && wanted == ev_double) return OP_CONV_I64D; if (from->type == ev_double && wanted == ev_int64) return OP_CONV_DI64; if (from->type == ev_uint64 && wanted == ev_double) return OP_CONV_U64D; if (from->type == ev_double && wanted == ev_uint64) return OP_CONV_DU64; if (from->type == ev_uint64 && wanted == ev_float) return OP_CONV_U64F; if (from->type == ev_float && wanted == ev_uint64) return OP_CONV_FU64; if (from->type == ev_float && wanted == ev_vector) return OP_MUL_FV; } /*impossible*/ return -1; } static QCC_sref_t QCC_TryEvaluateCast(QCC_sref_t src, QCC_type_t *cast, pbool implicit); static QCC_sref_t QCC_SupplyConversionForAssignment(QCC_ref_t *to, QCC_sref_t from, QCC_type_t *wanted, pbool fatal) { int o; QCC_sref_t rhs; rhs = QCC_TryEvaluateCast(from, to->cast, !fatal); if (rhs.cast) return rhs; if (wanted->type == ev_accessor && wanted->parentclass && from.cast->type != ev_accessor) wanted = wanted->parentclass; if (from.cast->type == ev_accessor && from.cast->parentclass && wanted->type != ev_accessor) from.cast = from.cast->parentclass; o = QCC_ShouldConvert(from.cast, wanted->type); if (o == 0) //type already matches return from; if (flag_typeexplicit) { char totypename[256], fromtypename[256], destname[256]; TypeName(wanted, totypename, sizeof(totypename)); TypeName(from.cast, fromtypename, sizeof(fromtypename)); QCC_PR_ParseErrorPrintSRef(ERR_TYPEMISMATCH, from, "Implicit type mismatch on assignment to %s. Needed %s, got %s.", QCC_GetRefName(to, destname, sizeof(destname)), totypename, fromtypename); } if (o < 0) { if (fatal && wanted->type != ev_variant && from.cast->type != ev_variant) { char totypename[256], fromtypename[256], destname[256]; TypeName(wanted, totypename, sizeof(totypename)); TypeName(from.cast, fromtypename, sizeof(fromtypename)); if (flag_laxcasts) { QCC_PR_ParseWarning(WARN_LAXCAST, "Implicit type mismatch on assignment to %s. Needed %s, got %s.", QCC_GetRefName(to, destname, sizeof(destname)), totypename, fromtypename); QCC_PR_ParsePrintSRef(WARN_LAXCAST, from); } else QCC_PR_ParseErrorPrintSRef(ERR_TYPEMISMATCH, from, "Implicit type mismatch on assignment to %s. Needed %s, got %s.", QCC_GetRefName(to, destname, sizeof(destname)), totypename, fromtypename); } return from; } if (o == OP_MUL_FV) rhs = QCC_MakeVectorConst(1,1,1); else rhs = nullsref; return QCC_PR_Statement(&pr_opcodes[o], from, rhs, NULL); //conversion return value } static QCC_sref_t QCC_SupplyConversion(QCC_sref_t var, etype_t wanted, pbool fatal) { extern char *basictypenames[]; int o; QCC_sref_t rhs; o = QCC_ShouldConvert(var.cast, wanted); if (o == 0) //type already matches return var; if (flag_typeexplicit)// && !QCC_SRef_IsNull(var)) QCC_PR_ParseErrorPrintSRef(ERR_TYPEMISMATCH, var, "Automatic type conversions disabled. Needed %s, got %s.", basictypenames[wanted], basictypenames[var.cast->type]); if (o < 0) { if (fatal && wanted != ev_variant && var.cast->type != ev_variant) { if (flag_laxcasts) { QCC_PR_ParseWarning(WARN_LAXCAST, "Implicit type mismatch. Needed %s%s%s, got %s%s%s.", col_type,basictypenames[wanted],col_none, col_type,basictypenames[var.cast->type],col_none); QCC_PR_ParsePrintSRef(WARN_LAXCAST, var); } else QCC_PR_ParseErrorPrintSRef(ERR_TYPEMISMATCH, var, "Implicit type mismatch. Needed %s%s%s, got %s%s%s.", col_type,basictypenames[wanted],col_none, col_type,basictypenames[var.cast->type],col_none); } return var; } if (o == OP_MUL_FV) rhs = QCC_MakeVectorConst(1,1,1); else rhs = nullsref; return QCC_PR_Statement(&pr_opcodes[o], var, rhs, NULL); //conversion return value } size_t tempslocked; //stats size_t tempsused; size_t tempsmax; temp_t *tempsinfo; QCC_def_t *aliases; QCC_def_t *allaliases; static QCC_sref_t QCC_GetTemp(QCC_type_t *type); void QCC_FreeTemp(QCC_sref_t t); void QCC_FreeDef(QCC_def_t *def); QCC_sref_t QCC_MakeSRefForce(QCC_def_t *def, unsigned int ofs, QCC_type_t *type); QCC_sref_t QCC_MakeSRef(QCC_def_t *def, unsigned int ofs, QCC_type_t *type); //we're about to overwrite the given def, so if there's any aliases to it, we need to clear them out. //if def == NULL, then clobber all. //def should never be a temp... static void QCC_ClobberDef(QCC_def_t *def) { QCC_def_t *a, **link; for (link = &aliases; *link;) { a = *link; if (!def || a->generatedfor == def) { //okay, we have a live alias. bum. //create a new temp for it. update previous statements to refer to the original location instead of the alias. //copy the source into the temp, and then update the alias's def to be a sub-symbol of the temp's def instead of a sub-symbol of the original location. //yes. all this just to make mods like xonotic not an insane sea of copies. QCC_sref_t tmp, from; int st; *link = a->nextlocal; a->nextlocal = NULL; if (a->refcount) { tmp = QCC_GetTemp(a->type->type==ev_variant?type_vector:a->type); for (st = a->fromstatement; st < numstatements; st++) { if (statements[st].a.sym == a) statements[st].a.sym = a->generatedfor; if (statements[st].b.sym == a) statements[st].b.sym = a->generatedfor; if (statements[st].c.sym == a) statements[st].c.sym = a->generatedfor; } tmp.sym->refcount = a->symbolheader->refcount; a->symbolheader = tmp.sym; //the alias now refers to a temp from = QCC_MakeSRefForce(a->generatedfor, 0, a->type); a->generatedfor = tmp.sym; a->name = tmp.sym->name; a->temp = tmp.sym->temp; a->ofs = tmp.sym->ofs; tmp.sym = a; if (a->type->type==ev_variant || a->type->type == ev_vector) QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_V], from, tmp, NULL, STFL_PRESERVEB)); else if (a->type->type==ev_int64 || a->type->type == ev_uint64 || a->type->type == ev_double) QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_I64], from, tmp, NULL, STFL_PRESERVEB)); else QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], from, tmp, NULL, STFL_PRESERVEB)); } } else link = &(*link)->nextlocal; } } //return an alias to a reference. typically the return value. static QCC_sref_t QCC_GetAliasTemp(QCC_sref_t ref) { QCC_def_t *def; def = qccHunkAlloc(sizeof(QCC_def_t)); def->type = ref.cast; def->generatedfor = ref.sym; def->symbolheader = def; def->symbolsize = ref.sym->symbolsize; def->name = ref.sym->name; def->referenced = true; def->fromstatement = numstatements; def->scope = pr_scope; //allaliases allows them to be finalized correctly def->strip = true; def->next = allaliases; allaliases = def; //and active aliases are the ones that are currently live in their original location def->nextlocal = aliases; aliases = def; QCC_FreeTemp(ref); return QCC_MakeSRefForce(def, 0, ref.cast); } static QCC_sref_t QCC_GetTemp(QCC_type_t *type) { QCC_sref_t var_c = nullsref; size_t u; var_c.cast = type; if (opt_overlaptemps) //don't exceed. This lets us allocate a huge block, and still be able to compile smegging big funcs. { for (u = 0; u < tempsused; u += tempsinfo[u].size) { if (!tempsinfo[u].def->refcount && tempsinfo[u].size == type->size) break; } } else u = tempsused; if (u == tempsused) { char buffer[32]; gofs_t ofs = tempsused; unsigned int i; unsigned int size = type->size; if (type->type == ev_accessor || type->type == ev_bitfld) size = type->parentclass->size; tempsused += size; if (tempsused > tempsmax) { size_t newmax = (tempsused + 64) & ~63; tempsinfo = realloc(tempsinfo, newmax*sizeof(*tempsinfo)); memset(tempsinfo+ofs, 0, (newmax-ofs)*sizeof(*tempsinfo)); tempsmax = newmax; } for(i = u; size > 0; i++, size--) { // tempsinfo[i].used = (i==ofs)?0:2; //2 is an 'padded' temp. if encountered, scan down for one that doesn't have a 2 there. tempsinfo[i].size = (i==ofs)?size:0; tempsinfo[i].def = qccHunkAlloc(sizeof(QCC_def_t)); tempsinfo[i].def->symbolheader = tempsinfo[i].def; tempsinfo[i].def->symbolsize = tempsinfo[i].size; } for (i = 0; i < tempsused; i++) tempsinfo[i].def->temp = &tempsinfo[i]; tempsinfo[u].def->ofs = u; tempsinfo[u].def->type = type; sprintf(buffer, "temp_%u", (unsigned)u); tempsinfo[u].def->name = qccHunkAlloc(strlen(buffer)+1); strcpy(tempsinfo[u].def->name, buffer); } else optres_overlaptemps+=type->size; var_c.sym = tempsinfo[u].def; var_c.ofs = 0;//u; tempsinfo[u].def->refcount+=1; tempsinfo[u].lastfunc = pr_scope; tempsinfo[u].lastline = pr_source_line; tempsinfo[u].laststatement = numstatements; var_c.sym->referenced = true; return var_c; } void QCC_FinaliseTemps(void) { unsigned int i; for (i = 0; i < tempsused; ) { tempsinfo[i].def->ofs = numpr_globals; numpr_globals += tempsinfo[i].size; i += tempsinfo[i].size; } if (numpr_globals >= MAX_REGS) { if (!opt_overlaptemps || !opt_locals_overlapping) QCC_Error(ERR_TOOMANYGLOBALS, "numpr_globals exceeded MAX_REGS - you'll need to use more optimisations"); else QCC_Error(ERR_TOOMANYGLOBALS, "numpr_globals exceeded MAX_REGS of %u. Increase with eg: -max_regs %u", MAX_REGS, MAX_REGS*2); } //finalize alises so they map correctly. while(allaliases) { allaliases->symbolheader = allaliases->generatedfor->symbolheader; allaliases->ofs = allaliases->generatedfor->ofs; allaliases = allaliases->next; } } void QCC_FreeDef(QCC_def_t *def) { if (def && def->symbolheader) { if (--def->symbolheader->refcount < 0) QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: over-freed refcount to %s", def->name); } } //nothing else references this temp. void QCC_FreeTemp(QCC_sref_t t) { if (t.sym && t.sym->symbolheader) { if (--t.sym->symbolheader->refcount < 0) QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: over-freed refcount to %s", QCC_VarAtOffset(t)); } } static void QCC_ForceUnFreeDef(QCC_def_t *def) { if (def && def->symbolheader) def->symbolheader->refcount++; } /* static void QCC_UnFreeDef(QCC_def_t *def) { if (def && def->symbolheader) { if (!def->symbolheader->refcount++) QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: %s was already fully freed.", def->name); } } */ static void QCC_UnFreeTemp(QCC_sref_t t) { if (t.sym && t.sym->symbolheader) { if (!t.sym->symbolheader->refcount++) QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: %s+%i@%i was already fully freed.", QCC_VarAtOffset(t), t.ofs, t.sym->ofs); } } //We've just parsed a statement. //We can gaurentee that any used temps are now not used. #ifdef _DEBUG static void QCC_FreeTemps(void) { } #else #define QCC_FreeTemps() #endif void QCC_PurgeTemps(void) { free(tempsinfo); tempsinfo = NULL; tempsmax = 0; tempsused = 0; aliases = NULL; allaliases = NULL; max_labels = 0; max_gotos = 0; free(pr_labels); pr_labels = NULL; free(pr_gotos); pr_gotos = NULL; num_gotos = num_labels = 0; free(initstatements); initstatements = NULL; numinitstatements = maxinitstatements = 0; } //temps that are still in use over a function call can be considered dodgy. //we need to remap these to locally defined temps, on return from the function so we know we got them all. static void QCC_LockActiveTemps(QCC_sref_t exclude) { size_t u; size_t excludeofs = ~0; QCC_ClobberDef(NULL); if (exclude.sym && exclude.sym->temp) excludeofs = exclude.sym->temp - tempsinfo; for (u = 0; u < tempsused; u += tempsinfo[u].size) { if (tempsinfo[u].def->refcount && u != excludeofs) //don't print this after an error jump out. tempsinfo[u].locked = true; } } /* static void QCC_ForceLockTempForOffset(int ofs) { tempsinfo[ofs].locked = true; } */ static QCC_def_t *QCC_MakeLocked(gofs_t tofs, gofs_t tsize, QCC_def_t *tmp) { #ifdef WRITEASM char buffer[128]; #endif QCC_def_t *def = NULL; QCC_def_t *a, **link; tempslocked+=tsize; def = QCC_PR_DummyDef(type_float, NULL, pr_scope, tsize==1?0:tsize, NULL, 0, false, GDF_STRIP); def->arraylengthprefix = false; //don't waste space with temps. #ifdef WRITEASM sprintf(buffer, "locked_%i", tofs); def->name = qccHunkAlloc(strlen(buffer)+1); strcpy(def->name, buffer); #endif def->referenced = true; //aliases might refer to this temp. //make sure they point to the local instead. for (link = &allaliases; *link;) { a = *link; if (a->generatedfor == tmp && a->scope == pr_scope) { // *link = a->next; // a->next = NULL; a->generatedfor = def; a->name = def->name; } else link = &(*link)->next; } return def; } static void QCC_RemapLockedTemp(gofs_t tofs, gofs_t tsize, int firststatement, int laststatement) { QCC_def_t *def = NULL; QCC_statement_t *st; int i; for (i = firststatement, st = &statements[i]; i < laststatement; i++, st++) { if (pr_opcodes[st->op].type_a && st->a.sym && st->a.sym->temp && st->a.sym->ofs >= tofs && st->a.sym->ofs < tofs + tsize) { if (!def) def = QCC_MakeLocked(tofs, tsize, st->a.sym); st->a.sym = def; // st->a.ofs = st->a.ofs - tofs; } if (pr_opcodes[st->op].type_b && st->b.sym && st->b.sym->temp && st->b.sym->ofs >= tofs && st->b.sym->ofs < tofs + tsize) { if (!def) def = QCC_MakeLocked(tofs, tsize, st->b.sym); st->b.sym = def; // st->b.ofs = st->b.ofs - tofs; } if (pr_opcodes[st->op].type_c && st->c.sym && st->c.sym->temp && st->c.sym->ofs >= tofs && st->c.sym->ofs < tofs + tsize) { if (!def) def = QCC_MakeLocked(tofs, tsize, st->c.sym); st->c.sym = def; // st->c.ofs = st->c.ofs - tofs; } } } //called for function calls to avoid wasting copies static pbool QCC_RemapTemp(int firststatement, int laststatement, QCC_sref_t temp, QCC_sref_t targ) { QCC_def_t *def = NULL; QCC_statement_t *st; int i; pbool remapped = false; //if its not a temp, we'll need to do a copy. if (!temp.sym->temp) return false; if (temp.sym->refcount != 1) return false; //should only have one reference... something weird is happening if it doesn't. //make sure the target is not already used in the interim, because that gets messy. def = targ.sym->symbolheader; for (i = firststatement, st = &statements[i]; i < laststatement; i++, st++) { if (pr_opcodes[st->op].type_a && st->a.sym && st->a.sym->symbolheader == def) return false; if (pr_opcodes[st->op].type_b && st->b.sym && st->b.sym->symbolheader == def) return false; if (pr_opcodes[st->op].type_c && st->c.sym && st->c.sym->symbolheader == def) return false; } def = temp.sym->symbolheader; //make sure things actually look okay. reads from it imply something weird for (i = firststatement, st = &statements[i]; i < laststatement; i++, st++) { if (pr_opcodes[st->op].type_a && st->a.sym && st->a.sym->temp == def->temp) if (!OpAssignsToA(st->op)) return false; if (pr_opcodes[st->op].type_b && st->b.sym && st->b.sym->temp == def->temp) if (!OpAssignsToB(st->op)) return false; if (pr_opcodes[st->op].type_c && st->c.sym && st->c.sym->temp == def->temp) if (!OpAssignsToC(st->op)) return false; } //okay, go ahead and remap it for (i = firststatement, st = &statements[i]; i < laststatement; i++, st++) { if (pr_opcodes[st->op].type_a && st->a.sym && st->a.sym->temp == def->temp) { st->a.sym = targ.sym; remapped = true; } if (pr_opcodes[st->op].type_b && st->b.sym && st->b.sym->temp == def->temp) { st->b.sym = targ.sym; remapped = true; } if (pr_opcodes[st->op].type_c && st->c.sym && st->c.sym->temp == def->temp) { st->c.sym = targ.sym; remapped = true; } } return remapped; } static void QCC_RemapLockedTemps(int firststatement, int laststatement) { size_t u; for (u = 0; u < tempsused; u += tempsinfo[u].size) { if (tempsinfo[u].locked) { QCC_RemapLockedTemp(u, tempsinfo[u].size, firststatement, laststatement); tempsinfo[u].locked = false; } } } static void QCC_fprintfLocals(FILE *f, QCC_def_t *locals) { QCC_def_t *var; char typebuf[1024]; size_t u; for (var = locals; var; var = var->nextlocal) { if (var->arraysize) fprintf(f, "local %s %s[%i];\n", TypeName(var->type, typebuf, sizeof(typebuf)), var->name, var->arraysize); else fprintf(f, "local %s %s;\n", TypeName(var->type, typebuf, sizeof(typebuf)), var->name); } if (opt_overlaptemps) //don't spam. for (u = 0; u < tempsused; u += tempsinfo[u].size) { if (!tempsinfo[u].locked) // if (tempsinfo[u].lastfunc == pr_scope) { fprintf(f, "local %s temp_%u; //%i\n", (tempsinfo[u].size == 1)?"float":"vector", (unsigned)u, tempsinfo[u].lastline); } } } #ifdef WRITEASM void QCC_WriteAsmFunction(QCC_function_t *sc, unsigned int firststatement, QCC_def_t *firstlocal); const char *QCC_VarAtOffset(QCC_sref_t ref) { //for debugging, we don't need to preserve the cast. static char message[1024]; //check the temps if (ref.sym) { if (ref.sym && ref.sym->temp) { if (!ref.ofs) QC_snprintfz(message, sizeof(message), "temp_%i", ref.sym->ofs-tempsinfo[0].def->ofs); else QC_snprintfz(message, sizeof(message), "temp_%i+%i", ref.sym->ofs-tempsinfo[0].def->ofs, ref.ofs); return message; } else if (ref.sym->name && !STRCMP(ref.sym->name, "IMMEDIATE")) { int type; const QCC_eval_t *val = QCC_SRef_EvalConst(ref); type = !val?-1:ref.cast->type; if (type == ev_variant) type = ref.sym->type->type; if (ref.sym->reloc)// && ref.cast->type == ev_pointer) { QC_snprintfz(message, sizeof(message), "&(%s+%i)", ref.sym->reloc->name, ref.sym->symboldata[ref.ofs]._int); return message; } switch(type) { case ev_string: { char *in = &strings[val->string], *out=message; char *end = out+sizeof(message)-3; *out++ = '\"'; for(; out < end && *in; in++) { if (*in == '\n') { *out++ = '\\'; *out++ = 'n'; } else if (*in == '\t') { *out++ = '\\'; *out++ = 't'; } else if (*in == '\r') { *out++ = '\\'; *out++ = 'r'; } else if (*in == '\"') { *out++ = '\\'; *out++ = '"'; } else if (*in == '\'') { *out++ = '\\'; *out++ = '\''; } else *out++ = *in; } *out++ = '\"'; *out++ = 0; } return message; case ev_function: if (val->_int>0 && val->_int < numfunctions && *functions[val->_int].name) QC_snprintfz(message, sizeof(message), "%s", functions[val->_int].name); else QC_snprintfz(message, sizeof(message), "%"pPRIi"i", val->_int); return message; case ev_field: case ev_integer: QC_snprintfz(message, sizeof(message), "%#"pPRIx"i", val->_int); return message; case ev_uint: QC_snprintfz(message, sizeof(message), "%#"pPRIx"u", val->_uint); return message; case ev_int64: //QC_snprintfz(message, sizeof(message), "%"pPRIu64"ill", val->i64); QC_snprintfz(message, sizeof(message), "%#"pPRIx64"ill", val->i64); return message; case ev_uint64: //QC_snprintfz(message, sizeof(message), "%"pPRIu64"ull", val->u64); QC_snprintfz(message, sizeof(message), "%#"pPRIx64"ull", val->u64); return message; case ev_entity: QC_snprintfz(message, sizeof(message), "%"pPRIi"e", val->_int); return message; case ev_float: if (!val->_float || val->_int & 0x7f800000) QC_snprintfz(message, sizeof(message), "%gf", val->_float); else QC_snprintfz(message, sizeof(message), "%%%"pPRIi, val->_int); return message; case ev_double: QC_snprintfz(message, sizeof(message), "%gd", val->_double); return message; case ev_vector: QC_snprintfz(message, sizeof(message), "'%g %g %g'", val->vector[0], val->vector[1], val->vector[2]); return message; default: if (!ref.ofs) QC_snprintfz(message, sizeof(message), "IMMEDIATE"); else QC_snprintfz(message, sizeof(message), "IMMEDIATE+%i", ref.ofs); return message; } } else if (ref.ofs || ref.cast != ref.sym->type) { if (!ref.ofs) QC_snprintfz(message, sizeof(message), "%s", ref.sym->name); else QC_snprintfz(message, sizeof(message), "%s+%i", ref.sym->name, ref.ofs); return message; } return ref.sym->name; } QC_snprintfz(message, sizeof(message), "offset_%i", ref.ofs); return message; } #endif #if IAMNOTLAZY //need_lock is set if it crossed a function call. static int QCC_PR_FindSourceForTemp(QCC_def_t *tempdef, int op, pbool *need_lock) { int st = -1; *need_lock = false; if (tempdef->temp) { for (st = numstatements-1; st>=0; st--) { if (statements[st].c == tempdef->ofs) { if (statements[st].op == op) return st; return -1; } if ((statements[st].op >= OP_CALL0 && statements[st].op <= OP_CALL8) || (statements[st].op >= OP_CALL1H && statements[st].op <= OP_CALL8H)) *need_lock = true; } } return st; } #endif static int QCC_PR_FindSourceForAssignedOffset(QCC_def_t *sym, int firstst) { int st = -1; for (st = numstatements-1; st>=firstst; st--) { if (statements[st].c.sym == sym && OpAssignsToC(statements[st].op)) return st; if (statements[st].b.sym == sym && OpAssignsToB(statements[st].op)) return st; } return -1; } pbool QCC_Temp_Describe(QCC_def_t *def, char *buffer, int buffersize) { QCC_statement_t *s; int st; temp_t *t = def->temp; if (!t) return false; if (t->lastfunc != pr_scope) return false; st = QCC_PR_FindSourceForAssignedOffset(t->def, t->laststatement); if (st == -1) return false; s = &statements[st]; switch(s->op) { default: QC_snprintfz(buffer, buffersize, "%s %s %s", QCC_VarAtOffset(s->a), pr_opcodes[s->op].name, QCC_VarAtOffset(s->b)); break; } return true; } static int QCC_PR_RoundFloatConst(const QCC_eval_t *eval) { float val = eval->_float; int ival = val; if (val != (float)ival) QCC_PR_ParseWarning(WARN_OVERFLOW, "Constant float operand %f will be truncated to %i", val, ival); return ival; } QCC_statement_t *QCC_PR_SimpleStatement ( QCC_opcode_t *op, QCC_sref_t var_a, QCC_sref_t var_b, QCC_sref_t var_c, int force); QCC_sref_t QCC_PR_StatementFlags ( QCC_opcode_t *op, QCC_sref_t var_a, QCC_sref_t var_b, QCC_statement_t **outstatement, unsigned int flags) { char typea[256], typeb[256]; QCC_statement_t *statement; QCC_sref_t var_c=nullsref; pbool nan_eq_cond, sym_cmp; if (var_a.sym) { var_a.sym->referenced = true; if (flags&STFL_PRESERVEA) QCC_UnFreeTemp(var_a); } if (var_b.sym) { var_b.sym->referenced = true; if (flags&STFL_PRESERVEB) QCC_UnFreeTemp(var_b); } /* if (op->priority != -1 && op->priority != CONDITION_PRIORITY) { if (op->associative!=ASSOC_LEFT) { if (op->type_a != &type_pointer && (flags&STFL_CONVERTB)) var_b = QCC_SupplyConversion(var_b, (*op->type_a)->type, false); } else { if (var_a.cast && (flags&STFL_CONVERTA)) var_a = QCC_SupplyConversion(var_a, (*op->type_a)->type, false); if (var_b.cast && (flags&STFL_CONVERTB)) var_b = QCC_SupplyConversion(var_b, (*op->type_b)->type, false); } } */ //maths operators if (opt_constantarithmatic || !pr_scope) { const QCC_eval_t *eval_a = QCC_SRef_EvalConst(var_a); const QCC_eval_t *eval_b = QCC_SRef_EvalConst(var_b); if (eval_a) { if (outstatement) *outstatement = NULL; if (eval_b) { //both are constants switch (op - pr_opcodes) //improve some of the maths. { // case OP_GLOBALADDRESS: // if (flag_pointerrelocs) // return QCC_MakeGAddress(type_pointer, var_a.sym, var_a.ofs + QCC_Eval_Int(QCC_SRef_EvalConst(var_b), var_b.cast)); // break; case OP_AND_ANY: // case OP_AND_F: case OP_AND_FI: // case OP_AND_I: case OP_AND_IF: /*if (flag_pythonlogic) { //c=(a?b:a); optres_constantarithmatic++; if (QCC_Eval_Truth(eval_a, var_a.cast)) { QCC_FreeTemp(var_a); return var_b; } else { QCC_FreeTemp(var_b); return var_a; } }*/ optres_constantarithmatic++; QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); if (QCC_Eval_Truth(eval_a, var_a.cast, false) && QCC_Eval_Truth(eval_b, var_b.cast, false)) return flag_assume_integer?QCC_MakeIntConst(1):QCC_MakeFloatConst(1); else return flag_assume_integer?QCC_MakeIntConst(0):QCC_MakeFloatConst(0); break; case OP_OR_ANY: // case OP_OR_F: case OP_OR_FI: // case OP_OR_I: case OP_OR_IF: /*if (flag_pythonlogic) { //c=(a?a:b); optres_constantarithmatic++; if (QCC_Eval_Truth(eval_a, var_a.cast)) { QCC_FreeTemp(var_b); return var_a; } else { QCC_FreeTemp(var_a); return var_b; } }*/ optres_constantarithmatic++; QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); if (QCC_Eval_Truth(eval_a, var_a.cast, false) || QCC_Eval_Truth(eval_b, var_b.cast, false)) return flag_assume_integer?QCC_MakeIntConst(1):QCC_MakeFloatConst(1); else return flag_assume_integer?QCC_MakeIntConst(0):QCC_MakeFloatConst(0); break; case OP_LOADA_F: case OP_LOADA_V: case OP_LOADA_S: case OP_LOADA_ENT: case OP_LOADA_FLD: case OP_LOADA_FNC: case OP_LOADA_I: { QCC_sref_t nd = var_a; QCC_FreeTemp(var_b); nd.ofs += eval_b->_int; //FIXME: case away the array... return nd; } break; case OP_BITXOR_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int ^ eval_b->_int); case OP_RSHIFT_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int >> eval_b->_int); case OP_LSHIFT_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int << eval_b->_int); case OP_BITXOR_F: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(QCC_PR_RoundFloatConst(eval_a) ^ QCC_PR_RoundFloatConst(eval_b)); case OP_BITXOR_V: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeVectorConst( (int)eval_a->vector[0] ^ (int)eval_b->vector[0], (int)eval_a->vector[1] ^ (int)eval_b->vector[1], (int)eval_a->vector[2] ^ (int)eval_b->vector[2]); case OP_RSHIFT_F: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(QCC_PR_RoundFloatConst(eval_a) >> QCC_PR_RoundFloatConst(eval_b)); case OP_RSHIFT_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int >> QCC_PR_RoundFloatConst(eval_b)); case OP_RSHIFT_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(QCC_PR_RoundFloatConst(eval_a) >> eval_b->_int); case OP_LSHIFT_F: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(QCC_PR_RoundFloatConst(eval_a) << QCC_PR_RoundFloatConst(eval_b)); case OP_LSHIFT_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int << QCC_PR_RoundFloatConst(eval_b)); case OP_LSHIFT_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(QCC_PR_RoundFloatConst(eval_a) << eval_b->_int); case OP_BITOR_F: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(QCC_PR_RoundFloatConst(eval_a) | QCC_PR_RoundFloatConst(eval_b)); case OP_BITOR_V: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeVectorConst( (int)eval_a->vector[0] | (int)eval_b->vector[0], (int)eval_a->vector[1] | (int)eval_b->vector[1], (int)eval_a->vector[2] | (int)eval_b->vector[2]); case OP_BITAND_F: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(QCC_PR_RoundFloatConst(eval_a) & QCC_PR_RoundFloatConst(eval_b)); case OP_BITAND_V: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeVectorConst( (int)eval_a->vector[0] & (int)eval_b->vector[0], (int)eval_a->vector[1] & (int)eval_b->vector[1], (int)eval_a->vector[2] & (int)eval_b->vector[2]); case OP_MUL_F: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(eval_a->_float * eval_b->_float); case OP_DIV_F: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; if (!eval_b->_float) QCC_PR_ParseWarning(WARN_DIVISIONBY0, "Division of %g by 0\n", eval_a->_float); return QCC_MakeFloatConst(eval_a->_float / eval_b->_float); case OP_ADD_F: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(eval_a->_float + eval_b->_float); case OP_SUB_F: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(eval_a->_float - eval_b->_float); case OP_BITOR_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int | eval_b->_int); case OP_BITAND_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int & eval_b->_int); case OP_MUL_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int * eval_b->_int); case OP_MUL_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(eval_a->_int * eval_b->_float); case OP_MUL_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(eval_a->_float * eval_b->_int); case OP_DIV_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; if (eval_b->_int == 0) { QCC_PR_ParseWarning(WARN_DIVISIONBY0, "Division by constant 0"); return QCC_MakeIntConst(0); } else if (eval_b->_int == -1 && eval_a->_int == (int)0x80000000) { QCC_PR_ParseWarning(WARN_DIVISIONBY0, "Signed overflow on division by -1"); return QCC_MakeIntConst(0x7fffffff); } else return QCC_MakeIntConst(eval_a->_int / eval_b->_int); case OP_ADD_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int + eval_b->_int); case OP_SUB_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int - eval_b->_int); case OP_MOD_I: if (!eval_b->_int) break; QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int % eval_b->_int); case OP_MOD_U: if (!eval_b->_uint) break; QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeUIntConst(eval_a->_uint % eval_b->_uint); case OP_MOD_F: { float a = eval_a->_float,n=eval_b->_float; if (!n) break; QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(a - (n * (int)(a/n))); } case OP_ADD_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(eval_a->_int + eval_b->_float); case OP_ADD_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(eval_a->_float + eval_b->_int); case OP_ADD_PIW: if (flag_undefwordsize) break; //don't make assumptions. QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int + eval_b->_int*VMWORDSIZE); case OP_SUB_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(eval_a->_int - eval_b->_float); case OP_SUB_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst(eval_a->_float - eval_b->_int); case OP_AND_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int && eval_b->_int); case OP_OR_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_int || eval_b->_int); case OP_AND_F: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_float && eval_b->_float); case OP_OR_F: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeIntConst(eval_a->_float || eval_b->_float); case OP_MUL_V: //mul_v is actually a dot-product QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeFloatConst( eval_a->vector[0] * eval_b->vector[0] + eval_a->vector[1] * eval_b->vector[1] + eval_a->vector[2] * eval_b->vector[2]); case OP_MUL_FV: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeVectorConst( eval_a->_float * eval_b->vector[0], eval_a->_float * eval_b->vector[1], eval_a->_float * eval_b->vector[2]); case OP_MUL_VF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeVectorConst( eval_a->vector[0] * eval_b->_float, eval_a->vector[1] * eval_b->_float, eval_a->vector[2] * eval_b->_float); case OP_ADD_V: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeVectorConst( eval_a->vector[0] + eval_b->vector[0], eval_a->vector[1] + eval_b->vector[1], eval_a->vector[2] + eval_b->vector[2]); case OP_SUB_V: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; return QCC_MakeVectorConst( eval_a->vector[0] - eval_b->vector[0], eval_a->vector[1] - eval_b->vector[1], eval_a->vector[2] - eval_b->vector[2]); case OP_LE_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int <= eval_b->_int); case OP_GE_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int >= eval_b->_int); case OP_LT_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int < eval_b->_int); case OP_GT_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int > eval_b->_int); case OP_LE_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int <= eval_b->_float); case OP_GE_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int >= eval_b->_float); case OP_LT_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int < eval_b->_float); case OP_GT_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int > eval_b->_float); case OP_LE_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_float <= eval_b->_int); case OP_GE_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_float >= eval_b->_int); case OP_LT_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_float < eval_b->_int); case OP_GT_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_float > eval_b->_int); case OP_EQ_F: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeFloatConst(eval_a->_float == eval_b->_float); case OP_EQ_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int == eval_b->_int); case OP_EQ_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int == eval_b->_float); case OP_EQ_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_float == eval_b->_int); case OP_MUL_VI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeVectorConst( eval_a->vector[0] * eval_b->_int, eval_a->vector[1] * eval_b->_int, eval_a->vector[2] * eval_b->_int); case OP_MUL_IV: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeVectorConst( eval_a->_int * eval_b->vector[0], eval_a->_int * eval_b->vector[1], eval_a->_int * eval_b->vector[2]); case OP_DIV_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); if (!eval_b->_float) QCC_PR_ParseWarning(WARN_DIVISIONBY0, "Division of %d by 0\n", eval_a->_int); return QCC_MakeFloatConst(eval_a->_int / eval_b->_float); case OP_DIV_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); if (!eval_b->_int) QCC_PR_ParseWarning(WARN_DIVISIONBY0, "Division of %g by 0\n", eval_a->_float); return QCC_MakeFloatConst(eval_a->_float / eval_b->_int); case OP_BITAND_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int & QCC_PR_RoundFloatConst(eval_b)); case OP_BITOR_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int | QCC_PR_RoundFloatConst(eval_b)); case OP_BITAND_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(QCC_PR_RoundFloatConst(eval_a) & eval_b->_int); case OP_BITOR_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(QCC_PR_RoundFloatConst(eval_a) | eval_b->_int); case OP_NE_F: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeFloatConst(eval_a->_float != eval_b->_float); case OP_NE_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int != eval_b->_int); case OP_NE_IF: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_int != eval_b->_float); case OP_NE_FI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_float != eval_b->_int); case OP_LT_U: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeUIntConst(eval_a->_uint < eval_b->_uint); case OP_LE_U: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeUIntConst(eval_a->_uint <= eval_b->_uint); case OP_DIV_U: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeUIntConst(eval_a->_uint / eval_b->_uint); case OP_RSHIFT_U: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeUIntConst(eval_a->_uint >> eval_b->_int); case OP_ADD_I64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeInt64Const(eval_a->i64 + eval_b->i64); case OP_SUB_I64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeInt64Const(eval_a->i64 - eval_b->i64); case OP_MUL_I64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeInt64Const(eval_a->i64 * eval_b->i64); case OP_DIV_I64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; if (eval_b->i64 == 0) { QCC_PR_ParseWarning(WARN_DIVISIONBY0, "Division by constant 0"); return QCC_MakeInt64Const(0); } else if (eval_b->i64 == -1 && eval_a->i64 == INT64_MIN) { QCC_PR_ParseWarning(WARN_DIVISIONBY0, "Signed overflow on division by -1"); return QCC_MakeInt64Const(INT64_MAX); } else return QCC_MakeInt64Const(eval_a->i64 / eval_b->i64); case OP_LSHIFT_I64I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeInt64Const(eval_a->i64 << eval_b->_int); case OP_RSHIFT_I64I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeInt64Const(eval_a->i64 >> eval_b->_int); case OP_BITAND_I64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeInt64Const(eval_a->i64 & eval_b->i64); case OP_BITOR_I64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeInt64Const(eval_a->i64 | eval_b->i64); case OP_BITXOR_I64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeInt64Const(eval_a->i64 ^ eval_b->i64); case OP_LE_I64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->i64 <= eval_b->i64); case OP_LT_I64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->i64 < eval_b->i64); case OP_EQ_I64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->i64 == eval_b->i64); case OP_NE_I64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->i64 != eval_b->i64); case OP_LT_U64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->u64 < eval_b->u64); case OP_LE_U64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->u64 <= eval_b->u64); case OP_DIV_U64: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); optres_constantarithmatic++; if (eval_b->u64 == 0) { QCC_PR_ParseWarning(WARN_DIVISIONBY0, "Division by constant 0"); return QCC_MakeUIntConst(0); } else return QCC_MakeUInt64Const(eval_a->u64 / eval_b->u64); case OP_RSHIFT_U64I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeUInt64Const(eval_a->u64 >> eval_b->_int); case OP_ADD_D: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeDoubleConst(eval_a->_double + eval_b->_double); case OP_SUB_D: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeDoubleConst(eval_a->_double - eval_b->_double); case OP_MUL_D: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeDoubleConst(eval_a->_double * eval_b->_double); case OP_DIV_D: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeDoubleConst(eval_a->_double / eval_b->_double); case OP_LE_D: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_double <= eval_b->_double); case OP_LT_D: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_double < eval_b->_double); case OP_EQ_D: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_double == eval_b->_double); case OP_NE_D: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(eval_a->_double != eval_b->_double); case OP_LSHIFT_DI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeDoubleConst((pint64_t)eval_a->_double << eval_b->_int); case OP_RSHIFT_DI: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeDoubleConst((pint64_t)eval_a->_double >> eval_b->_int); case OP_BITAND_D: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeDoubleConst((pint64_t)eval_a->_double & (pint64_t)eval_b->_double); case OP_BITOR_D: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeDoubleConst((pint64_t)eval_a->_double | (pint64_t)eval_b->_double); case OP_BITXOR_D: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeDoubleConst((pint64_t)eval_a->_double ^ (pint64_t)eval_b->_double); case OP_BITEXTEND_I: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst((eval_a->_int << (32-(eval_b->_uint&0xff)-(eval_b->_uint>>8))) >> (32-(eval_b->_uint&0xff))); case OP_BITEXTEND_U: QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeUIntConst((eval_a->_uint << (32u-(eval_b->_uint&0xff)-(eval_b->_uint>>8))) >> (32u-(eval_b->_uint&0xff))); //case OP_BITCOPY_I: //reads var_c too. // break; } } else { //a is const, b is not switch (op - pr_opcodes) { //OP_NOT_S needs to do a string comparison case OP_NOT_F: QCC_FreeTemp(var_a); optres_constantarithmatic++; return QCC_MakeFloatConst(!eval_a->_float); case OP_NOT_V: QCC_FreeTemp(var_a); optres_constantarithmatic++; return QCC_MakeFloatConst(!eval_a->vector[0] && !eval_a->vector[1] && !eval_a->vector[2]); case OP_NOT_ENT: // o.O case OP_NOT_FNC: // o.O QCC_FreeTemp(var_a); optres_constantarithmatic++; return QCC_MakeFloatConst(!eval_a->_int); case OP_NOT_I: QCC_FreeTemp(var_a); optres_constantarithmatic++; return QCC_MakeIntConst(!eval_a->_int); case OP_BITNOT_F: QCC_FreeTemp(var_a); return QCC_MakeFloatConst(~QCC_PR_RoundFloatConst(eval_a)); case OP_BITNOT_I: QCC_FreeTemp(var_a); return QCC_MakeIntConst(~eval_a->_int); case OP_BITNOT_V: QCC_FreeTemp(var_a); return QCC_MakeVectorConst(~(int)eval_a->vector[0], ~(int)eval_a->vector[1], ~(int)eval_a->vector[2]); case OP_CONV_FTOI: QCC_FreeTemp(var_a); optres_constantarithmatic++; if ((int)eval_a->_float != eval_a->_float) { if (eval_a->_int & 0x7f800000) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical overflow. %f will be rounded to %i", eval_a->_float, (int)eval_a->_float); else QCC_PR_ParseWarning(WARN_OVERFLOW, "Denormalized float %g will be rounded to %i. This is probably not what you want.", eval_a->_float, (int)eval_a->_float); } return QCC_MakeIntConst(eval_a->_float); case OP_CONV_ITOF: QCC_FreeTemp(var_a); optres_constantarithmatic++; { float fl = eval_a->_int; if ((int)fl != eval_a->_int) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical truncation of %#x to %#x.", eval_a->_int, (int)fl); } return QCC_MakeFloatConst(eval_a->_int); case OP_CONV_FU: QCC_FreeTemp(var_a); optres_constantarithmatic++; if ((unsigned int)eval_a->_float != eval_a->_float) { if (eval_a->_int & 0x7f800000) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical overflow. %f will be rounded to %i", eval_a->_float, (unsigned int)eval_a->_float); else QCC_PR_ParseWarning(WARN_OVERFLOW, "Denormalized float %g will be rounded to %i. This is probably not what you want.", eval_a->_float, (unsigned int)eval_a->_float); } return QCC_MakeUIntConst(eval_a->_float); case OP_CONV_UF: QCC_FreeTemp(var_a); optres_constantarithmatic++; { float fl = eval_a->_uint; if ((unsigned int)fl != eval_a->_uint) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical truncation of %#x to %#x.", eval_a->_uint, (unsigned int)fl); } return QCC_MakeFloatConst(eval_a->_uint); case OP_CONV_FD: QCC_FreeTemp(var_a); optres_constantarithmatic++; return QCC_MakeDoubleConst(eval_a->_float); case OP_CONV_DF: QCC_FreeTemp(var_a); optres_constantarithmatic++; { float fl = eval_a->_double; // if ((double)fl != eval_a->_double) // QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical truncation of %g to %g.", eval_a->_double, (float)fl); return QCC_MakeFloatConst(fl); } case OP_CONV_DI64: QCC_FreeTemp(var_a); optres_constantarithmatic++; if ((pint64_t)eval_a->_double != eval_a->_double) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical overflow. %f will be rounded to %"pPRIi64"", eval_a->_double, (pint64_t)eval_a->_double); return QCC_MakeInt64Const(eval_a->_double); case OP_CONV_I64D: QCC_FreeTemp(var_a); optres_constantarithmatic++; { double d = eval_a->i64; if ((pint64_t)d != eval_a->i64) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical truncation of %#"pPRIx64" to %#"pPRIx64".", eval_a->i64, (pint64_t)d); return QCC_MakeDoubleConst(d); } case OP_CONV_DU64: QCC_FreeTemp(var_a); optres_constantarithmatic++; if ((pint64_t)eval_a->_double != eval_a->_double) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical overflow. %f will be rounded to %"pPRIi64"", eval_a->_double, (puint64_t)eval_a->_double); return QCC_MakeUInt64Const(eval_a->_double); case OP_CONV_U64D: QCC_FreeTemp(var_a); optres_constantarithmatic++; { double d = eval_a->u64; if ((pint64_t)d != eval_a->u64) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical truncation of %#"pPRIx64" to %#"pPRIx64".", eval_a->u64, (puint64_t)d); return QCC_MakeDoubleConst(d); } case OP_CONV_FI64: QCC_FreeTemp(var_a); optres_constantarithmatic++; if ((pint64_t)eval_a->_float != eval_a->_float) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical overflow. %f will be rounded to %"pPRIi64"", eval_a->_float, (pint64_t)eval_a->_float); return QCC_MakeInt64Const(eval_a->_float); case OP_CONV_I64F: QCC_FreeTemp(var_a); optres_constantarithmatic++; { float d = eval_a->i64; if ((pint64_t)d != eval_a->i64) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical truncation of %#"pPRIx64" to %#"pPRIx64".", eval_a->i64, (pint64_t)d); return QCC_MakeFloatConst(d); } case OP_CONV_FU64: QCC_FreeTemp(var_a); optres_constantarithmatic++; if ((puint64_t)eval_a->_float != eval_a->_float) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical overflow. %f will be rounded to %"pPRIu64"", eval_a->_float, (puint64_t)eval_a->_float); return QCC_MakeUInt64Const(eval_a->_float); case OP_CONV_U64F: QCC_FreeTemp(var_a); optres_constantarithmatic++; { float d = eval_a->u64; if ((puint64_t)d != eval_a->u64) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical truncation of %#"pPRIx64" to %#"pPRIx64".", eval_a->u64, (puint64_t)d); return QCC_MakeFloatConst(d); } case OP_CONV_II64: QCC_FreeTemp(var_a); optres_constantarithmatic++; { pint64_t d = eval_a->_int; if ((pint_t)d != eval_a->_int) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical truncation of %"pPRIi" to %#"pPRIx64".", eval_a->_int, (pint64_t)d); return QCC_MakeInt64Const(d); } case OP_CONV_UI64: QCC_FreeTemp(var_a); optres_constantarithmatic++; { puint64_t d = eval_a->_uint; if ((puint_t)d != eval_a->_uint) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical truncation of %"pPRIu" to %#"pPRIx64".", eval_a->_uint, (pint64_t)d); return QCC_MakeUInt64Const(d); } case OP_CONV_I64I: QCC_FreeTemp(var_a); optres_constantarithmatic++; { pint_t d = eval_a->i64; if ((pint64_t)d != eval_a->i64) QCC_PR_ParseWarning(WARN_OVERFLOW, "Numerical truncation of %"pPRIx64" to %#"pPRIx".", eval_a->i64, (pint_t)d); return QCC_MakeIntConst(d); } case OP_BITOR_F: case OP_ADD_F: if (eval_a->_float == 0) { optres_constantarithmatic++; QCC_FreeTemp(var_a); return var_b; } break; case OP_MUL_F: if (eval_a->_float == 1) { optres_constantarithmatic++; QCC_FreeTemp(var_a); return var_b; } break; case OP_BITAND_F: case OP_BITAND_FI: if (QCC_PR_RoundFloatConst(eval_a) == 0) { QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeFloatConst(0); } break; case OP_LSHIFT_U: case OP_RSHIFT_U: if (eval_a->_uint == 0) { QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeUIntConst(0); } break; case OP_LSHIFT_I: case OP_RSHIFT_I: case OP_BITAND_I: case OP_BITAND_IF: if (eval_a->_int == 0) { QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeIntConst(0); } break; case OP_AND_ANY: case OP_AND_F: case OP_AND_FI: case OP_AND_I: case OP_AND_IF: /*if (flag_pythonlogic) { //c=(a?b:a); optres_constantarithmatic++; if (QCC_Eval_Truth(eval_a, var_a.cast)) { QCC_FreeTemp(var_a); return var_b; } else { QCC_FreeTemp(var_b); return var_a; } }*/ if (!QCC_Eval_Truth(eval_a, var_a.cast, false)) { //one side is false, thus return false. optres_constantarithmatic++; QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeFloatConst(0); } break; case OP_OR_ANY: case OP_OR_F: case OP_OR_FI: case OP_OR_I: case OP_OR_IF: /*if (flag_pythonlogic) { //c=(a?a:b); optres_constantarithmatic++; if (QCC_Eval_Truth(eval_a, var_a.cast)) { QCC_FreeTemp(var_b); return var_a; } else { QCC_FreeTemp(var_a); return var_b; } }*/ if (QCC_Eval_Truth(eval_a, var_a.cast, false)) { //one side is true, thus return true. optres_constantarithmatic++; QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeFloatConst(1); } break; case OP_BITOR_I: case OP_ADD_I: if (eval_a->_int == 0) { optres_constantarithmatic++; QCC_FreeTemp(var_a); return var_b; } break; case OP_MUL_I: if (eval_a->_int == 1) { optres_constantarithmatic++; QCC_FreeTemp(var_a); return var_b; } break; } } } else if (eval_b) { if (outstatement) *outstatement = NULL; //b is const, a is not switch (op - pr_opcodes) { case OP_GLOBALADDRESS: if (flag_pointerrelocs && !pr_scope) return QCC_MakeGAddress(type_pointer, var_a.sym, var_a.ofs + QCC_Eval_Int(QCC_SRef_EvalConst(var_b), var_b.cast), 0); break; case OP_AND_ANY: case OP_AND_F: case OP_AND_FI: case OP_AND_I: case OP_AND_IF: /*if (flag_pythonlogic) { //c=(a?b:a); optres_constantarithmatic++; if (QCC_Eval_Truth(eval_a, var_a.cast)) { QCC_FreeTemp(var_a); return var_b; } else { QCC_FreeTemp(var_b); return var_a; } } else*/ if (!QCC_Eval_Truth(eval_b, var_b.cast, false)) { //one side is false, thus return false. optres_constantarithmatic++; QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeFloatConst(0); } break; case OP_OR_ANY: case OP_OR_F: case OP_OR_FI: case OP_OR_I: case OP_OR_IF: /*if (flag_pythonlogic) { //c=(a?a:b); optres_constantarithmatic++; if (QCC_Eval_Truth(eval_a, var_a.cast)) { QCC_FreeTemp(var_b); return var_a; } else { QCC_FreeTemp(var_a); return var_b; } }*/ if (QCC_Eval_Truth(eval_a, var_a.cast, false)) { //one side is true, thus return true. optres_constantarithmatic++; QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return QCC_MakeFloatConst(1); } break; #if IAMNOTLAZY case OP_LOADA_F: case OP_LOADA_V: case OP_LOADA_S: case OP_LOADA_ENT: case OP_LOADA_FLD: case OP_LOADA_FNC: case OP_LOADA_I: { QCC_def_t *nd; nd = (void *)qccHunkAlloc (sizeof(QCC_def_t)); memset (nd, 0, sizeof(QCC_def_t)); nd->type = var_a->type; nd->ofs = var_a->ofs + G_INT(var_b->ofs); nd->temp = var_a->temp; nd->constant = false; nd->name = var_a->name; return nd; } break; #endif case OP_BITOR_F: case OP_SUB_F: case OP_ADD_F: if (eval_b->_float == 0) { optres_constantarithmatic++; QCC_FreeTemp(var_b); return var_a; } break; case OP_DIV_VF: if (!eval_b->_float) QCC_PR_ParseWarning(WARN_DIVISIONBY0, "Division by 0\n"); else if (flag_reciprocalmaths && eval_b->_int&(0xff<<23)) { QCC_FreeTemp(var_b); var_b = QCC_MakeFloatConst(1.0 / eval_b->_float); return QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_VF], var_a, var_b, outstatement, 0); } if (eval_b->_float == 1) { optres_constantarithmatic++; QCC_FreeTemp(var_b); return var_a; } break; case OP_DIV_F: case OP_DIV_IF: if (!eval_b->_float) QCC_PR_ParseWarning(WARN_DIVISIONBY0, "Division by 0\n"); else if (flag_reciprocalmaths && (eval_b->_int&(0xff<<23)) && eval_b->_int != 3/*paranoid about vector indexing. don't ruin precision*/) { QCC_FreeTemp(var_b); var_b = QCC_MakeFloatConst(1.0 / eval_b->_float); return QCC_PR_StatementFlags((op == &pr_opcodes[OP_DIV_F])?&pr_opcodes[OP_MUL_F]:&pr_opcodes[OP_MUL_IF], var_a, var_b, outstatement, 0); } //fallthrough case OP_MUL_F: case OP_MUL_IF: if (eval_b->_float == 1) { optres_constantarithmatic++; QCC_FreeTemp(var_b); return var_a; } break; case OP_BITOR_I: case OP_SUB_I: case OP_ADD_I: case OP_ADD_PIW: if (eval_b->_int == 0) { optres_constantarithmatic++; QCC_FreeTemp(var_b); return var_a; } break; case OP_DIV_I: case OP_DIV_U: case OP_DIV_FI: if (!eval_b->_int) QCC_PR_ParseWarning(WARN_DIVISIONBY0, "Division by 0\n"); case OP_MUL_I: case OP_MUL_FI: if (eval_b->_int == 1) { optres_constantarithmatic++; QCC_FreeTemp(var_b); return var_a; } break; case OP_BITAND_I: if (eval_b->_int == ~0) { optres_constantarithmatic++; QCC_FreeTemp(var_b); return var_a; } break; case OP_BITEXTEND_I: if (eval_b->_uint == 32) { QCC_FreeTemp(var_b); return var_a; //wtf? } if ((eval_b->_uint&0xff) == 0) { QCC_FreeTemp(var_b); QCC_FreeTemp(var_a); return QCC_MakeIntConst(0); //wtf? } break; case OP_BITEXTEND_U: if (eval_b->_uint == 32) { QCC_FreeTemp(var_b); return var_a; //wtf? } if ((eval_b->_uint&0xff) == 0) { QCC_FreeTemp(var_b); QCC_FreeTemp(var_a); return QCC_MakeUIntConst(0); //wtf? } break; } } } // self-comparison that is impacted when NaN // e.g. NaN == NaN, NaN != NaN, [NaN, 0, 0] == [NaN, 0, 0], etc. nan_eq_cond = false; switch (op - pr_opcodes) { case OP_STATE: if (qcc_framerate>0 && qcc_framerate != (qcc_targetformat_ishexen2()?20:10)) { //can't use the normal opcode. QCC_ref_t tempref; QCC_sref_t self = QCC_PR_GetSRef(type_entity, "self", NULL, true, 0, false); QCC_sref_t time = QCC_PR_GetSRef(type_float, "time", NULL, true, 0, false); QCC_sref_t fldframe = QCC_PR_GetSRef(type_floatfield, "frame", NULL, true, 0, false); QCC_sref_t fldthink = QCC_PR_GetSRef(QCC_PR_FieldType(type_function), "think", NULL, true, 0, false); QCC_sref_t fldnextthink = QCC_PR_GetSRef(type_floatfield, "nextthink", NULL, true, 0, false); QCC_UnFreeTemp(self); QCC_UnFreeTemp(self); //self.frame = var_a; QCC_StoreSRefToRef(QCC_PR_BuildRef(&tempref, REF_FIELD, self, fldframe, fldframe.cast->aux_type, false, 0), var_a, false, false); //self.think = var_b; QCC_StoreSRefToRef(QCC_PR_BuildRef(&tempref, REF_FIELD, self, fldthink, fldthink.cast->aux_type, false, 0), var_b, false, false); //self.frame = time + interval; time = QCC_PR_Statement(&pr_opcodes[OP_ADD_F], time, QCC_MakeFloatConst(1/qcc_framerate), NULL); QCC_StoreSRefToRef(QCC_PR_BuildRef(&tempref, REF_FIELD, self, fldnextthink, fldnextthink.cast->aux_type, false, 0), time, false, false); return nullsref; } break; case OP_WSTATE: { //there is no normal opcode. QCC_ref_t tempref; QCC_sref_t self = QCC_PR_GetSRef(type_entity, "self", NULL, true, 0, false); QCC_sref_t time = QCC_PR_GetSRef(type_float, "time", NULL, true, 0, false); QCC_sref_t fldframe = QCC_PR_GetSRef(type_floatfield, "weaponframe", NULL, true, 0, false); QCC_sref_t fldthink = QCC_PR_GetSRef(QCC_PR_FieldType(type_function), "think", NULL, true, 0, false); QCC_sref_t fldnextthink = QCC_PR_GetSRef(type_floatfield, "nextthink", NULL, true, 0, false); float framerate = (qcc_framerate>0)?qcc_framerate:(qcc_targetformat_ishexen2()?20:10); QCC_UnFreeTemp(self); QCC_UnFreeTemp(self); //self.frame = var_a; QCC_StoreSRefToRef(QCC_PR_BuildRef(&tempref, REF_FIELD, self, fldframe, fldframe.cast->aux_type, false, 0), var_a, false, false); //self.think = var_b; QCC_StoreSRefToRef(QCC_PR_BuildRef(&tempref, REF_FIELD, self, fldthink, fldthink.cast->aux_type, false, 0), var_b, false, false); //self.frame = time + interval; time = QCC_PR_Statement(&pr_opcodes[OP_ADD_F], time, QCC_MakeFloatConst(1/framerate), NULL); QCC_StoreSRefToRef(QCC_PR_BuildRef(&tempref, REF_FIELD, self, fldnextthink, fldnextthink.cast->aux_type, false, 0), time, false, false); return nullsref; } break; case OP_STORE_F: case OP_STORE_V: case OP_STORE_FLD: case OP_STORE_P: case OP_STORE_I: case OP_STORE_ENT: case OP_STORE_FNC: case OP_STORE_I64: // case OP_STORE_D: { const QCC_eval_t *idxeval = QCC_SRef_EvalConst(var_a); if (idxeval && (var_a.cast->type == ev_integer || var_a.cast->type == ev_float) && !idxeval->_int) { //you're allowed to assign 0i to anything if (op - pr_opcodes == OP_STORE_V) //make sure vectors get set properly. { QCC_FreeTemp(var_a); var_a = QCC_MakeVectorConst(0, 0, 0); } if (op - pr_opcodes == OP_STORE_I64) //make sure vectors get set properly. { QCC_FreeTemp(var_a); var_a = QCC_MakeInt64Const(0); } } /*else { QCC_type_t *t = var_a->type; while(t) { if (!typecmp_lax(t, var_b->type)) break; t = t->parentclass; } if (!t) { TypeName(var_a->type, typea, sizeof(typea)); TypeName(var_b->type, typeb, sizeof(typeb)); QCC_PR_ParseWarning(WARN_STRICTTYPEMISMATCH, "Implicit assignment from %s to %s %s", typea, typeb, var_b->name); } }*/ } break; case OP_STOREP_F: case OP_STOREP_V: case OP_STOREP_FLD: case OP_STOREP_P: case OP_STOREP_I: case OP_STOREP_ENT: case OP_STOREP_FNC: { const QCC_eval_t *idxeval = QCC_SRef_EvalConst(var_a); if (idxeval && (var_a.cast->type == ev_integer || var_a.cast->type == ev_float) && !idxeval->_int) { //you're allowed to assign 0i to anything if (op - pr_opcodes == OP_STOREP_V) //make sure vectors get set properly. { QCC_FreeTemp(var_a); var_a = QCC_MakeVectorConst(0, 0, 0); } } /*else { QCC_type_t *t = var_a->type; while(t) { if (!typecmp_lax(t, var_b->type->aux_type)) break; t = t->parentclass; } if (!t) { TypeName(var_a->type, typea, sizeof(typea)); TypeName(var_b->type->aux_type, typeb, sizeof(typeb)); QCC_PR_ParseWarning(WARN_STRICTTYPEMISMATCH, "Implicit field assignment from %s to %s", typea, typeb); } }*/ } break; case OP_LOADA_F: case OP_LOADA_V: case OP_LOADA_S: case OP_LOADA_ENT: case OP_LOADA_FLD: case OP_LOADA_FNC: case OP_LOADA_I: break; case OP_AND_F: if (var_a.sym == var_b.sym && var_a.ofs == var_b.ofs) QCC_PR_ParseWarning(WARN_CONSTANTCOMPARISON, "Parameter offsets for && are the same"); if (var_a.sym && var_b.sym && (var_a.sym->constant && var_b.sym->constant)) { QCC_PR_ParseWarning(WARN_CONSTANTCOMPARISON, "Result of comparison is constant"); QCC_PR_ParsePrintDef(WARN_CONSTANTCOMPARISON, var_a.sym); QCC_PR_ParsePrintDef(WARN_CONSTANTCOMPARISON, var_b.sym); } break; case OP_OR_F: if (var_a.sym == var_b.sym && var_a.ofs == var_b.ofs) QCC_PR_ParseWarning(WARN_CONSTANTCOMPARISON, "Parameters for || are the same"); if (var_a.sym && var_b.sym && (var_a.sym->constant || var_b.sym->constant)) { QCC_PR_ParseWarning(WARN_CONSTANTCOMPARISON, "Result of comparison is constant"); QCC_PR_ParsePrintDef(WARN_CONSTANTCOMPARISON, var_a.sym); QCC_PR_ParsePrintDef(WARN_CONSTANTCOMPARISON, var_b.sym); } break; case OP_EQ_F: case OP_NE_F: case OP_EQ_V: case OP_NE_V: case OP_LE_F: case OP_GE_F: nan_eq_cond = true; case OP_EQ_S: case OP_EQ_E: case OP_EQ_FNC: // if (opt_shortenifnots) // if (var_b->constant && ((int*)qcc_pr_globals)[var_b->ofs]==0) // (a == 0) becomes (!a) // op = &pr_opcodes[(op - pr_opcodes) - OP_EQ_F + OP_NOT_F]; case OP_NE_S: case OP_NE_E: case OP_NE_FNC: case OP_LT_F: case OP_GT_F: if (typecmp_lax(var_a.cast, var_b.cast)) { QCC_type_t *t; //simplify a, see if we can get an inherited comparison for (t = var_a.cast; t; t = t->parentclass) { if (typecmp_lax(t, var_b.cast)) break; } if (t) break; //now try with b simplified for (t = var_b.cast; t; t = t->parentclass) { if (typecmp_lax(var_a.cast, t)) break; } if (t) break; //if both need to simplify then the classes are too diverse TypeName(var_a.cast, typea, sizeof(typea)); TypeName(var_b.cast, typeb, sizeof(typeb)); QCC_PR_ParseWarning(WARN_STRICTTYPEMISMATCH, "'%s' type mismatch: %s with %s", op->name, typea, typeb); } sym_cmp = !nan_eq_cond && var_a.sym == var_b.sym && var_a.ofs == var_b.ofs; if ((var_a.sym->constant && var_b.sym->constant && !var_a.sym->temp && !var_b.sym->temp) || sym_cmp) { QCC_PR_ParseWarning(WARN_CONSTANTCOMPARISON, "Result of comparison is constant"); QCC_PR_ParsePrintDef(WARN_CONSTANTCOMPARISON, var_a.sym); QCC_PR_ParsePrintDef(WARN_CONSTANTCOMPARISON, var_b.sym); //Note: EQ_S and NE_S compares the pointed-to data, rather than the pointers themselves. it would be too unsafe to optimise these //fixme: fold other comparisons. } break; case OP_IF_S: case OP_IFNOT_S: case OP_IF_F: case OP_IFNOT_F: case OP_IF_I: case OP_IFNOT_I: // if (var_a.cast->type == ev_function && !var_a.sym->temp) // QCC_PR_ParseWarning(WARN_CONSTANTCOMPARISON, "Result of comparison is constant"); // if (!var_a.sym || var_a.sym->constant && !var_a.sym->temp) // QCC_PR_ParseWarning(WARN_CONSTANTCOMPARISON, "Result of comparison is constant"); break; default: break; } if (numstatements && !(statements[numstatements-1].flags&STF_NOFOLD)) { //optimise based on last statement. if (op - pr_opcodes == OP_IFNOT_I) { if (opt_shortenifnots && var_a.cast && var_a.sym->temp && var_a.sym->refcount == 1 && (statements[numstatements-1].op == OP_NOT_F || statements[numstatements-1].op == OP_NOT_FNC || statements[numstatements-1].op == OP_NOT_ENT)) { if (statements[numstatements-1].c.sym == var_a.sym && statements[numstatements-1].c.ofs == var_a.ofs) { if (statements[numstatements-1].op == OP_NOT_F && QCC_OPCodeValid(&pr_opcodes[OP_IF_F])) op = &pr_opcodes[OP_IF_F]; else op = &pr_opcodes[OP_IF_I]; numstatements--; QCC_FreeTemp(var_a); var_a = statements[numstatements].a; QCC_ForceUnFreeDef(var_a.sym); optres_shortenifnots++; } } } else if (op - pr_opcodes == OP_IFNOT_F) { if (opt_shortenifnots && var_a.cast && var_a.sym->temp && var_a.sym->refcount == 1 && statements[numstatements-1].op == OP_NOT_F) { if (statements[numstatements-1].c.sym == var_a.sym && statements[numstatements-1].c.ofs == var_a.ofs) { op = &pr_opcodes[OP_IF_F]; numstatements--; QCC_FreeTemp(var_a); var_a = statements[numstatements].a; QCC_ForceUnFreeDef(var_a.sym); optres_shortenifnots++; } } } else if (op - pr_opcodes == OP_IFNOT_S) { if (opt_shortenifnots && var_a.cast && var_a.sym->temp && var_a.sym->refcount == 1 && statements[numstatements-1].op == OP_NOT_S) { if (statements[numstatements-1].c.sym == var_a.sym && statements[numstatements-1].c.ofs == var_a.ofs) { op = &pr_opcodes[OP_IF_S]; numstatements--; QCC_FreeTemp(var_a); var_a = statements[numstatements].a; QCC_ForceUnFreeDef(var_a.sym); optres_shortenifnots++; } } } else if (((unsigned) ((op - pr_opcodes) - OP_STORE_F) < 6) || (op-pr_opcodes) == OP_STORE_P || (op-pr_opcodes) == OP_STORE_I || (op-pr_opcodes) == OP_STORE_I64) { // remove assignments if what should be assigned is the 3rd operand of the previous statement? // don't if it's a call, callH, switch or case // && var_a->ofs >RESERVED_OFS) if (OpAssignsToC(statements[numstatements-1].op) && opt_assignments && var_a.cast && var_a.sym == statements[numstatements-1].c.sym && var_a.ofs == statements[numstatements-1].c.ofs) { if (var_a.cast->type == var_b.cast->type) { if (var_a.sym && var_b.sym && var_a.sym->temp && var_a.sym->refcount==1) { statement = &statements[numstatements-1]; statement->c = var_b; if (var_a.cast->type != var_b.cast->type) QCC_PR_ParseWarning(0, "store type mismatch"); var_b.sym->referenced=true; var_a.sym->referenced=true; QCC_FreeTemp(var_a); optres_assignments++; if (flags&STFL_DISCARDRESULT) { QCC_FreeTemp(var_b); var_b = nullsref; } return var_b; } } } } else if (op - pr_opcodes == OP_ADD_I && statements[numstatements-1].op == OP_MUL_I && opt_assignments && !flag_undefwordsize) { //mul_i idx 4i tmp //add_i tmp 2i out //becomes add_piw 2i idx out // const QCC_eval_t *eval_a = QCC_SRef_EvalConst(var_a); const QCC_eval_t *eval_b = QCC_SRef_EvalConst(statements[numstatements-1].b); if (eval_b && eval_b->_int == VMWORDSIZE) { if (var_a.cast && var_a.sym == statements[numstatements-1].c.sym && var_a.ofs == statements[numstatements-1].c.ofs) if (var_a.sym && var_b.sym && var_a.sym->temp && var_a.sym->refcount==1) { op = &pr_opcodes[OP_ADD_PIW]; QCC_FreeDef(var_a.sym); var_a = var_b; var_b = statements[numstatements-1].a; numstatements--; QCC_ForceUnFreeDef(var_b.sym); } } } } if (!QCC_OPCodeValid(op) && !(flags&STFL_NOEMULATE)) { #define QCC_PR_EmulationFunc(n) QCC_PR_GetSRef(NULL, #n, pr_scope, false, 0, 0) QCC_sref_t tmp; //FIXME: add support for flags so we don't corrupt temps switch(op - pr_opcodes) { case OP_LOADA_STRUCT: /*emit this anyway. if it reaches runtime then you messed up. this is valid only if you do &foo[0]*/ // QCC_PR_ParseWarning(0, "OP_LOADA_STRUCT: cannot emulate"); break; case OP_ADD_SF: var_c = QCC_PR_EmulationFunc(AddStringFloat); if (var_c.cast) var_c = QCC_PR_GenerateFunctionCall2(nullsref, var_c, var_a, type_string, var_b, type_float); else { if (!var_a.sym->constant) QCC_PR_ParseWarning(WARN_STRINGOFFSET, "OP_ADD_SF: string+float may be unsafe"); var_b = QCC_SupplyConversion(var_b, ev_integer, true); //FIXME: this should be an unconditional float->int conversion var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], var_a, var_b, NULL, 0); } var_c.cast = type_string; return var_c; case OP_ADD_EF: case OP_SUB_EF: if (flag_qccx) //no implicit cast. qccx always uses denormalised floats everywhere. QCC_PR_ParseWarning(WARN_DENORMAL, "OP_ADD_EF: qccx entity offsets are unsafe, and denormals are unsafe"); else if (1) { //slightly better defined. var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[(op==&pr_opcodes[OP_ADD_EF])?OP_ADD_EI:OP_SUB_EI], var_a, var_b, NULL, flags&STFL_PRESERVEA); } else { var_c = QCC_PR_EmulationFunc(nextent); if (!var_c.cast) { QCC_PR_ParseWarning(0, "the nextent builtin is not defined"); goto badopcode; } QCC_PR_ParseWarning(WARN_DENORMAL, "OP_ADD_EF: denormals are unsafe"); var_c = QCC_PR_GenerateFunctionCall1 (nullsref, var_c, QCC_MakeIntConst(0), type_entity); var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_b, nullsref, NULL, flags&STFL_PRESERVEB); var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_F], var_c, var_b, NULL, 0); flags&=~STFL_PRESERVEB; } var_c = QCC_PR_StatementFlags(&pr_opcodes[(op==&pr_opcodes[OP_ADD_EF])?OP_ADD_IF:OP_SUB_F], var_a, var_b, NULL, flags); var_c.cast = type_entity; return var_c; case OP_ADD_EI: case OP_SUB_EI: if (flag_qccx) QCC_PR_ParseWarning(0, "qccx entity offsets are unsafe"); else { var_c = QCC_PR_EmulationFunc(nextent); if (!var_c.cast) { QCC_PR_ParseWarning(0, "the nextent builtin is not defined"); goto badopcode; } var_c = QCC_PR_GenerateFunctionCall1 (nullsref, var_c, QCC_MakeIntConst(0), type_entity); var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], var_c, var_b, NULL, flags&STFL_PRESERVEB); flags&=~STFL_PRESERVEB; } var_c = QCC_PR_StatementFlags(&pr_opcodes[(op==&pr_opcodes[OP_ADD_EF])?OP_ADD_I:OP_SUB_I], var_a, var_b, NULL, flags); var_c.cast = type_entity; return var_c; case OP_ADD_SI: case OP_ADD_IS: QCC_PR_ParseWarning(WARN_STRINGOFFSET, "OP_ADD_SI: string+int may be unsafe"); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], var_a, var_b, NULL, 0); var_c.cast = type_string; return var_c; case OP_ADD_PF: case OP_ADD_FP: case OP_ADD_PI: case OP_ADD_IP: case OP_ADD_PU: case OP_ADD_UP: { QCC_type_t *t; var_c = (op == &pr_opcodes[OP_ADD_PF] || op == &pr_opcodes[OP_ADD_PI] || op == &pr_opcodes[OP_ADD_PU])?var_a:var_b; //ptr var_b = (op == &pr_opcodes[OP_ADD_PF] || op == &pr_opcodes[OP_ADD_PI] || op == &pr_opcodes[OP_ADD_PU])?var_b:var_a; //idx t = var_c.cast; if (op == &pr_opcodes[OP_ADD_FP] || op == &pr_opcodes[OP_ADD_PF]) var_b = QCC_SupplyConversion(var_b, ev_integer, true); //FIXME: this should be an unconditional float->int conversion if (t->aux_type->type == ev_void) //void* is treated as a byte type. { if (flag_undefwordsize) QCC_PR_ParseWarning(ERR_BADEXTENSION, "void* maths was disabled."); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], var_c, var_b, NULL, 0); } else if (t->aux_type->bits) //awkward bittyness { if (flag_undefwordsize) QCC_PR_ParseWarning(ERR_BADEXTENSION, "pointer* maths was disabled on this type."); if (t->aux_type->bits != 8) var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], var_b, QCC_MakeIntConst(t->aux_type->bits/8), NULL, 0); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], var_c, var_b, NULL, 0); //PIW doesn't make sense here. } else { var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], var_b, QCC_MakeIntConst(t->aux_type->size), NULL, 0); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_PIW], var_c, var_b, NULL, 0); } var_c.cast = t; } return var_c; case OP_SUB_PF: case OP_SUB_PI: case OP_SUB_PU: var_c = var_a; if (op == &pr_opcodes[OP_SUB_PF]) var_b = QCC_SupplyConversion(var_b, ev_integer, true); //FIXME: this should be an unconditional float->int conversion if (var_c.cast->aux_type->type == ev_void) //void* is treated as a byte type. { if (flag_undefwordsize) QCC_PR_ParseWarning(ERR_BADEXTENSION, "void* maths was disabled."); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_I], var_c, var_b, NULL, 0); } else if (var_c.cast->aux_type->bits) //awkward bittyness { if (flag_undefwordsize) QCC_PR_ParseWarning(ERR_BADEXTENSION, "pointer* maths was disabled on this type."); if (var_c.cast->aux_type->bits!=8) var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], var_b, QCC_MakeIntConst(var_c.cast->aux_type->bits/8), NULL, 0); //negated. this is a subtract after all. var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_I], var_c, var_b, NULL, 0); //PIW doesn't make sense here. } else if (1) { var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], var_b, QCC_MakeIntConst(-var_c.cast->aux_type->size), NULL, 0); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_PIW], var_c, var_b/*negative, making this a subtraction*/, NULL, 0); } else { QCC_sref_t idx = QCC_PR_Statement(&pr_opcodes[OP_ADD_PIW], QCC_MakeIntConst(0), QCC_MakeIntConst(var_c.cast->aux_type->size), NULL); var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], var_b, idx, NULL, 0); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_I], var_c, var_b, NULL, 0); } var_c.cast = var_a.cast; return var_c; case OP_SUB_PP: if (typecmp(var_a.cast, var_b.cast)) QCC_PR_ParseError(ERR_BADEXTENSION, "incompatible pointer types"); //determine byte offset var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_I], var_a, var_b, NULL, 0); if (var_a.cast->aux_type->type == ev_void) { if (flag_undefwordsize) QCC_PR_ParseWarning(ERR_BADEXTENSION, "void* maths was disabled."); return var_c; //we're done if we're using void/bytes } //determine divisor if (var_a.cast->aux_type->bits) { if (flag_undefwordsize) QCC_PR_ParseWarning(ERR_BADEXTENSION, "pointer sizes were marked as undefined."); var_b = QCC_MakeIntConst(var_a.cast->aux_type->bits/8); } else var_b = QCC_PR_Statement(&pr_opcodes[OP_ADD_PIW], QCC_MakeIntConst(0), QCC_MakeIntConst(var_a.cast->aux_type->size), NULL); //divide the result return QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_I], var_c, var_b, NULL, 0); case OP_BITAND_I: { QCC_sref_t fnc = QCC_PR_EmulationFunc(BitandInt); if (!fnc.cast) { QCC_PR_ParseWarning(0, "BitandInt function not defined: cannot emulate int&int"); goto badopcode; } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_integer, var_b, type_integer); var_c.cast = type_integer; return var_c; } break; case OP_BITOR_I: { QCC_sref_t fnc = QCC_PR_EmulationFunc(BitorInt); if (!fnc.cast) { QCC_PR_ParseWarning(0, "BitorInt function not defined: cannot emulate int|int"); goto badopcode; } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_integer, var_b, type_integer); var_c.cast = type_integer; return var_c; } break; case OP_BITAND_V: case OP_BITOR_V: op = &pr_opcodes[((op - pr_opcodes)==OP_BITAND_V)?OP_BITAND_F:OP_BITOR_F]; var_c = QCC_GetTemp(type_vector); var_a.cast = type_float; var_b.cast = type_float; var_c.cast = type_float; QCC_PR_SimpleStatement(op, var_a, var_b, var_c, true); var_a.ofs++; var_b.ofs++; var_c.ofs++; QCC_PR_SimpleStatement(op, var_a, var_b, var_c, true); var_a.ofs++; var_b.ofs++; var_c.ofs++; QCC_PR_SimpleStatement(op, var_a, var_b, var_c, true); var_a.ofs++; var_b.ofs++; var_c.ofs++; var_c.cast = type_vector; var_c.ofs -= 3; return var_c; case OP_ADD_I: { QCC_sref_t fnc = QCC_PR_EmulationFunc(AddInt); if (!fnc.cast) { QCC_PR_ParseWarning(0, "AddInt function not defined: cannot emulate int+int"); goto badopcode; } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_integer, var_b, type_integer); var_c.cast = type_integer; return var_c; } break; case OP_MOD_F: { QCC_sref_t fnc = QCC_PR_EmulationFunc(mod); if (!fnc.cast) { //a - (n * floor(a/n)); //(except using v|v instead of floor) var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_F], var_a, var_b, NULL, STFL_PRESERVEA|STFL_PRESERVEB); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_F], var_c, var_c, NULL, STFL_PRESERVEA); var_c = QCC_PR_Statement(&pr_opcodes[OP_MUL_F], var_b, var_c, NULL); return QCC_PR_Statement(&pr_opcodes[OP_SUB_F], var_a, var_c, NULL); // QCC_PR_ParseError(0, "mod function not defined: cannot emulate float%%float"); } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_float, var_b, type_float); var_c.cast = type_float; return var_c; } break; case OP_MOD_I: { QCC_sref_t fnc = QCC_PR_EmulationFunc(ModInt); if (!fnc.cast) { //a - (n * floor(a/n)); //(except using v|v instead of floor) var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_I], var_a, var_b, NULL, STFL_PRESERVEA|STFL_PRESERVEB); //var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_I], var_c, var_c, NULL, STFL_PRESERVEA); var_c = QCC_PR_Statement(&pr_opcodes[OP_MUL_I], var_b, var_c, NULL); return QCC_PR_Statement(&pr_opcodes[OP_SUB_I], var_a, var_c, NULL); // QCC_PR_ParseError(0, "mod function not defined: cannot emulate int%%int"); } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_integer, var_b, type_integer); var_c.cast = type_integer; return var_c; } break; case OP_MOD_U: { QCC_sref_t fnc = QCC_PR_EmulationFunc(ModInt); if (!fnc.cast) { //a - (n * floor(a/n)); //(except using v|v instead of floor) var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_U], var_a, var_b, NULL, STFL_PRESERVEA|STFL_PRESERVEB); //var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_U], var_c, var_c, NULL, STFL_PRESERVEA); var_c = QCC_PR_Statement(&pr_opcodes[OP_MUL_U], var_b, var_c, NULL); return QCC_PR_Statement(&pr_opcodes[OP_SUB_U], var_a, var_c, NULL); // QCC_PR_ParseError(0, "mod function not defined: cannot emulate int%%int"); } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_integer, var_b, type_integer); var_c.cast = type_integer; return var_c; } break; case OP_MOD_FI: { QCC_sref_t fnc = QCC_PR_EmulationFunc(mod); if (!fnc.cast) { //a - (n * floor(a/n)); //(except using v|v instead of floor) var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_FI], var_a, var_b, NULL, STFL_PRESERVEA|STFL_PRESERVEB); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_F], var_c, var_c, NULL, STFL_PRESERVEA); var_c = QCC_PR_Statement(&pr_opcodes[OP_MUL_IF], var_b, var_c, NULL); return QCC_PR_Statement(&pr_opcodes[OP_SUB_F], var_a, var_c, NULL); // QCC_PR_ParseError(0, "mod function not defined: cannot emulate float%%int"); } var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_float, var_b, type_float); var_c.cast = type_float; return var_c; } break; case OP_MOD_IF: { QCC_sref_t fnc = QCC_PR_EmulationFunc(mod); if (!fnc.cast) { //a - (n * floor(a/n)); //(except using v|v instead of floor) var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_IF], var_a, var_b, NULL, STFL_PRESERVEA|STFL_PRESERVEB); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_F], var_c, var_c, NULL, STFL_PRESERVEA); var_c = QCC_PR_Statement(&pr_opcodes[OP_MUL_F], var_b, var_c, NULL); return QCC_PR_Statement(&pr_opcodes[OP_SUB_IF], var_a, var_c, NULL); // QCC_PR_ParseError(0, "mod function not defined: cannot emulate int%%float"); } var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_a, nullsref, NULL, flags&STFL_PRESERVEA); var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_float, var_b, type_float); var_c.cast = type_float; return var_c; } break; case OP_MOD_V: { QCC_sref_t fnc = QCC_PR_EmulationFunc(ModVec); if (!fnc.cast) { QCC_PR_ParseWarning(0, "ModVec function not defined: cannot emulate vector%%vector"); goto badopcode; } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_vector, var_b, type_vector); var_c.cast = type_vector; return var_c; } break; case OP_SUB_I: { QCC_sref_t fnc = QCC_PR_EmulationFunc(SubInt); if (!fnc.cast) { QCC_PR_ParseWarning(0, "SubInt function not defined: cannot emulate int-int"); goto badopcode; } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_integer, var_b, type_integer); var_c.cast = type_integer; return var_c; } break; case OP_MUL_I: { QCC_sref_t fnc = QCC_PR_EmulationFunc(MulInt); if (!fnc.cast) { QCC_PR_ParseWarning(0, "MulInt function not defined: cannot emulate int*int"); goto badopcode; } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_integer, var_b, type_integer); var_c.cast = type_integer; return var_c; } break; case OP_DIV_I: { QCC_sref_t fnc = QCC_PR_EmulationFunc(DivInt); if (!fnc.cast) { QCC_PR_ParseWarning(0, "DivInt function not defined: cannot emulate int/int"); goto badopcode; } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_integer, var_b, type_integer); var_c.cast = type_integer; return var_c; } break; case OP_DIV_VF: //v/f === v*(1/f) op = &pr_opcodes[OP_MUL_VF]; var_b = QCC_PR_Statement(&pr_opcodes[OP_DIV_F], QCC_MakeFloatConst(1), var_b, NULL); var_b.sym->referenced = true; break; case OP_POW_F: { QCC_sref_t fnc = QCC_PR_EmulationFunc(powf); if (!fnc.cast) fnc = QCC_PR_EmulationFunc(pow); if (!fnc.cast) { QCC_PR_ParseWarning(0, "pow function not defined: cannot emulate float*^float"); goto badopcode; } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_float, var_b, type_float); var_c.cast = type_float; return var_c; } break; case OP_POW_I: { QCC_sref_t fnc = QCC_PR_EmulationFunc(powf); if (!fnc.cast) fnc = QCC_PR_EmulationFunc(pow); if (!fnc.cast) { QCC_PR_ParseWarning(0, "pow function not defined: cannot emulate int*^int"); goto badopcode; } var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_float, var_b, type_float); var_c.cast = type_float; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_c, nullsref, NULL, 0); return var_c; } break; case OP_POW_FI: { QCC_sref_t fnc = QCC_PR_EmulationFunc(powf); if (!fnc.cast) fnc = QCC_PR_EmulationFunc(pow); if (!fnc.cast) { QCC_PR_ParseWarning(0, "pow function not defined: cannot emulate float*^int"); goto badopcode; } var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_float, var_b, type_float); var_c.cast = type_float; return var_c; } break; case OP_POW_IF: { QCC_sref_t fnc = QCC_PR_EmulationFunc(powf); if (!fnc.cast) fnc = QCC_PR_EmulationFunc(pow); if (!fnc.cast) { QCC_PR_ParseWarning(0, "pow function not defined: cannot emulate int*^float"); goto badopcode; } var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_float, var_b, type_float); var_c.cast = type_float; return var_c; } break; case OP_CROSS_V: { QCC_sref_t t; var_c = QCC_GetTemp(type_vector); t = QCC_PR_Statement(&pr_opcodes[OP_SUB_F], QCC_PR_Statement(&pr_opcodes[OP_MUL_F], QCC_MakeSRef(var_a.sym, var_a.ofs+1, type_float), QCC_MakeSRef(var_b.sym, var_b.ofs+2, type_float), NULL), QCC_PR_Statement(&pr_opcodes[OP_MUL_F], QCC_MakeSRef(var_a.sym, var_a.ofs+2, type_float), QCC_MakeSRef(var_b.sym, var_b.ofs+1, type_float), NULL), NULL); QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], t, QCC_MakeSRef(var_c.sym, var_c.ofs+0, type_float), NULL, flags&STFL_DISCARDRESULT); t = QCC_PR_Statement(&pr_opcodes[OP_SUB_F], QCC_PR_Statement(&pr_opcodes[OP_MUL_F], QCC_MakeSRef(var_a.sym, var_a.ofs+2, type_float), QCC_MakeSRef(var_b.sym, var_b.ofs+0, type_float), NULL), QCC_PR_Statement(&pr_opcodes[OP_MUL_F], QCC_MakeSRef(var_a.sym, var_a.ofs+0, type_float), QCC_MakeSRef(var_b.sym, var_b.ofs+2, type_float), NULL), NULL); QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], t, QCC_MakeSRef(var_c.sym, var_c.ofs+1, type_float), NULL, flags&STFL_DISCARDRESULT); t = QCC_PR_Statement(&pr_opcodes[OP_SUB_F], QCC_PR_Statement(&pr_opcodes[OP_MUL_F], QCC_MakeSRef(var_a.sym, var_a.ofs+0, type_float), QCC_MakeSRef(var_b.sym, var_b.ofs+1, type_float), NULL), QCC_PR_Statement(&pr_opcodes[OP_MUL_F], QCC_MakeSRef(var_a.sym, var_a.ofs+1, type_float), QCC_MakeSRef(var_b.sym, var_b.ofs+0, type_float), NULL), NULL); QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], t, QCC_MakeSRef(var_c.sym, var_c.ofs+2, type_float), NULL, flags&STFL_DISCARDRESULT); return var_c; } break; case OP_SPACESHIP_F: { //basically just a subtraction-with-fsign. const QCC_eval_t *eval; QCC_statement_t *patch1; var_a = QCC_PR_Statement(&pr_opcodes[OP_SUB_F], var_a, var_b, NULL); eval = QCC_SRef_EvalConst(var_c); if (eval) { if (eval->_float < 0) return QCC_MakeFloatConst(-1); else return QCC_MakeFloatConst(eval->_float > 0); } //hack: make local, not temp. this prevents assignment/temp folding... var_c = QCC_MakeSRefForce(QCC_PR_DummyDef(type_float, "ternary", pr_scope, 0, NULL, 0, false, GDF_STRIP), 0, type_float); //var_c = a>0; QCC_PR_SimpleStatement(&pr_opcodes[OP_GT_F], var_a, QCC_MakeFloatConst(0), var_c, true); patch1 = QCC_Generate_OP_IFNOT(QCC_PR_StatementFlags(&pr_opcodes[OP_LT_F], var_a, QCC_MakeFloatConst(0), NULL, 0), false); QCC_PR_SimpleStatement(&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(-1), var_c, nullsref, true); patch1->b.jumpofs = &statements[numstatements] - patch1; return var_c; } break; case OP_SPACESHIP_S: { QCC_sref_t fnc = QCC_PR_EmulationFunc(strcmp); if (!fnc.cast) QCC_PR_ParseError(0, "strcmp function not defined: cannot emulate string<=>string"); var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_string, var_b, type_string); var_c.cast = type_float; return var_c; } break; case OP_CONV_UF: var_c = QCC_PR_EmulationFunc(utof); if (!var_c.cast) var_c = QCC_PR_EmulationFunc(quake_utof); if (var_c.cast) { var_a = QCC_PR_GenerateFunctionCall1(nullsref, var_c, var_a, type_uint); var_a.cast = type_float; return var_a; } var_c = QCC_PR_EmulationFunc(itof); if (!var_c.cast) var_c = QCC_PR_EmulationFunc(quake_itof); if (var_c.cast) { //there's some optional args that actually have it treated as unsigned var_a = QCC_PR_GenerateFunctionCall3(nullsref, var_c, var_a,type_integer, QCC_MakeFloatConst(0),type_float, QCC_MakeFloatConst(32),type_float); var_a.cast = type_float; return var_a; } //urgh... var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); /*statement = QCC_Generate_OP_IFNOT(QCC_PR_StatementFlags(&pr_opcodes[OP_LT_F], var_c, QCC_MakeFloatConst(0), NULL, STFL_PRESERVEA), false); if (statement) { QCC_PR_SimpleStatement(&pr_opcodes[OP_ADD_F], var_c, QCC_MakeFloatConst(0x100000000), var_c, false); //biiig number, to overpower the negative part. statement->b.jumpofs = &statements[numstatements] - statement; } else*/ QCC_PR_ParseWarning(WARN_NOTSTANDARDBEHAVIOUR, "utof emulation: will break if %s is big", var_a.sym->name); return var_c; case OP_CONV_FU: var_c = QCC_PR_EmulationFunc(ftou); if (!var_c.cast) return QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_a, var_b, NULL, flags&STFL_PRESERVEA); var_a = QCC_PR_GenerateFunctionCall1(nullsref, var_c, var_a, type_float); var_a.cast = type_uint; return var_a; case OP_CONV_ITOF: case OP_STORE_IF: { const QCC_eval_t *eval_a = QCC_SRef_EvalConst(var_a); if (eval_a) { QCC_FreeTemp(var_a); var_a = QCC_MakeFloatConst(eval_a->_int); } else { var_c = QCC_PR_EmulationFunc(itof); if (!var_c.cast) { //with denormals, 5.0 * 1i -> 5i, and 5i / 1i = 5.0 QCC_PR_ParseWarning(WARN_DENORMAL, "itof emulation: denormals are unsafe %s", var_a.sym->name); var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_F], var_a, QCC_MakeIntConst(1), NULL, 0); } else var_a = QCC_PR_GenerateFunctionCall1(nullsref, var_c, var_a, type_integer); var_a.cast = type_float; } if (var_b.cast) return QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], var_a, var_b, NULL, flags&STFL_DISCARDRESULT); } return var_a; case OP_CONV_FTOI: case OP_STORE_FI: { const QCC_eval_t *eval_a = QCC_SRef_EvalConst(var_a); if (eval_a) { QCC_FreeTemp(var_a); var_a = QCC_MakeIntConst(eval_a->_float); } else { var_c = QCC_PR_EmulationFunc(ftoi); if (!var_c.cast) { //with denormals, 5 * 1i -> 5i QCC_PR_ParseWarning(WARN_DENORMAL, "ftoi emulation: denormals are unsafe"); var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_F], var_a, QCC_MakeIntConst(1), NULL, 0); } else var_a = QCC_PR_GenerateFunctionCall1(nullsref, var_c, var_a, type_float); var_a.cast = type_integer; } if (var_b.cast) return QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_I], var_a, var_b, NULL, flags&STFL_DISCARDRESULT); } return var_a; case OP_STORE_P: case OP_STORE_I: op = pr_opcodes+OP_STORE_F; break; case OP_BITXOR_F: // r = (a & ~b) | (b & ~a); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITNOT_F], var_b, nullsref, NULL, STFL_PRESERVEA); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_F], var_a, var_c, NULL, STFL_PRESERVEA); var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_BITNOT_F], var_a, nullsref, NULL, 0); var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_F], var_b, var_a, NULL, 0); return QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_F], var_c, var_a, NULL, 0); case OP_BITXOR_I: { QCC_sref_t fnc = QCC_PR_EmulationFunc(BitxorInt); if (!fnc.cast) { /* QCC_PR_ParseWarning(0, "BitxorInt function not defined: cannot emulate int^int"); goto badopcode; */ var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITNOT_I], var_b, nullsref, NULL, STFL_PRESERVEA); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_I], var_a, var_c, NULL, STFL_PRESERVEA); var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_BITNOT_I], var_a, nullsref, NULL, 0); var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_I], var_b, var_a, NULL, 0); return QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_I], var_c, var_a, NULL, 0); } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_integer, var_b, type_integer); var_c.cast = type_integer; return var_c; } break; case OP_BITXOR_V: // r = (a & ~b) | (b & ~a); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITNOT_V], var_b, nullsref, NULL, STFL_PRESERVEA); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_V], var_a, var_c, NULL, STFL_PRESERVEA); var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_BITNOT_V], var_a, nullsref, NULL, STFL_PRESERVEA); var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_V], var_b, var_a, NULL, STFL_PRESERVEB); return QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_V], var_c, var_a, NULL, 0); case OP_BITXOR_D: // r = (a & ~b) | (b & ~a); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITNOT_D], var_b, nullsref, NULL, STFL_PRESERVEA); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_D], var_a, var_c, NULL, STFL_PRESERVEA); var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_BITNOT_D], var_a, nullsref, NULL, STFL_PRESERVEA); var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_D], var_b, var_a, NULL, STFL_PRESERVEB); return QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_D], var_c, var_a, NULL, 0); case OP_IF_S: tmp = QCC_MakeFloatConst(0); tmp.cast = type_string; var_a = QCC_PR_Statement(&pr_opcodes[OP_NE_S], var_a, tmp, NULL); op = &pr_opcodes[OP_IF_I]; break; case OP_IFNOT_S: tmp = QCC_MakeFloatConst(0); tmp.cast = type_string; var_a = QCC_PR_Statement(&pr_opcodes[OP_NE_S], var_a, tmp, NULL); op = &pr_opcodes[OP_IFNOT_I]; break; case OP_IF_F: tmp = QCC_MakeFloatConst(0); var_a = QCC_PR_Statement(&pr_opcodes[OP_NE_F], var_a, tmp, NULL); op = &pr_opcodes[OP_IF_I]; break; case OP_IFNOT_F: tmp = QCC_MakeFloatConst(0); var_a = QCC_PR_Statement(&pr_opcodes[OP_NE_F], var_a, tmp, NULL); op = &pr_opcodes[OP_IFNOT_I]; break; case OP_ADDSTORE_F: op = &pr_opcodes[OP_ADD_F]; tmp = var_b; var_b = var_a; var_a = tmp; break; case OP_ADDSTORE_I: op = &pr_opcodes[OP_ADD_I]; tmp = var_b; var_b = var_a; var_a = tmp; break; case OP_ADDSTORE_FI: op = &pr_opcodes[OP_ADD_FI]; tmp = var_b; var_b = var_a; var_a = tmp; break; // case OP_ADDSTORE_IF: // fixme: result is a float but needs to be an int // op = &pr_opcodes[OP_ADD_IF]; // tmp = var_b; // var_b = var_a; // var_a = tmp; // break; case OP_SUBSTORE_F: op = &pr_opcodes[OP_SUB_F]; tmp = var_b; var_b = var_a; var_a = tmp; break; case OP_SUBSTORE_FI: op = &pr_opcodes[OP_SUB_FI]; tmp = var_b; var_b = var_a; var_a = tmp; break; // case OP_SUBSTORE_IF: // fixme: result is a float but needs to be an int // op = &pr_opcodes[OP_SUB_IF]; // tmp = var_b; // var_b = var_a; // var_a = tmp; // break; case OP_SUBSTORE_I: op = &pr_opcodes[OP_SUB_I]; tmp = var_b; var_b = var_a; var_a = tmp; break; case OP_BITNOT_I: op = &pr_opcodes[OP_SUB_I]; if (QCC_OPCodeValid(op)) { op = &pr_opcodes[OP_SUB_I]; var_b = var_a; var_a = QCC_MakeIntConst(~0); var_a.sym->referenced = true; } else { QCC_sref_t fnc = QCC_PR_EmulationFunc(SubInt); if (!fnc.cast) { QCC_PR_ParseWarning(0, "SubInt function not defined: cannot emulate ~int"); goto badopcode; } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, QCC_MakeIntConst(~0), type_integer, var_a, type_integer); var_c.cast = type_integer; return var_c; } break; case OP_BITNOT_I64: op = &pr_opcodes[OP_SUB_I64]; var_b = var_a; var_a = QCC_MakeInt64Const(~(longlong)0); var_a.sym->referenced = true; break; case OP_BITNOT_F: op = &pr_opcodes[OP_SUB_F]; var_b = var_a; var_a = QCC_MakeFloatConst(-1); //divVerent says -1 is safe, even with floats. I guess I'm just too paranoid. var_a.sym->referenced = true; break; case OP_BITNOT_V: op = &pr_opcodes[OP_SUB_V]; var_b = var_a; var_a = QCC_MakeVectorConst(-1, -1, -1); var_a.sym->referenced=true; break; case OP_DIVSTORE_F: op = &pr_opcodes[OP_DIV_F]; tmp = var_b; var_b = var_a; var_a = tmp; break; case OP_DIVSTORE_FI: op = &pr_opcodes[OP_DIV_FI]; tmp = var_b; var_b = var_a; var_a = tmp; break; // case OP_DIVSTORE_IF: // fixme: result is a float, but needs to be an int // op = &pr_opcodes[OP_DIV_IF]; // tmp = var_b; // var_b = var_a; // var_a = tmp; // break; case OP_DIVSTORE_I: op = &pr_opcodes[OP_DIV_I]; tmp = var_b; var_b = var_a; var_a = tmp; break; case OP_MULSTORE_F: op = &pr_opcodes[OP_MUL_F]; tmp = var_b; var_b = var_a; var_a = tmp; break; // case OP_MULSTORE_IF: // fixme: result is a float, but needs to be an int // op = &pr_opcodes[OP_MUL_IF]; // var_c = var_b; // var_b = var_a; // var_a = var_c; // break; case OP_MULSTORE_FI: op = &pr_opcodes[OP_MUL_FI]; tmp = var_b; var_b = var_a; var_a = tmp; break; case OP_ADDSTORE_V: op = &pr_opcodes[OP_ADD_V]; tmp = var_b; var_b = var_a; var_a = tmp; break; case OP_SUBSTORE_V: op = &pr_opcodes[OP_SUB_V]; tmp = var_b; var_b = var_a; var_a = tmp; break; case OP_MULSTORE_VF: op = &pr_opcodes[OP_MUL_VF]; tmp = var_b; var_b = var_a; var_a = tmp; break; case OP_MULSTORE_VI: op = &pr_opcodes[OP_MUL_VI]; tmp = var_b; var_b = var_a; var_a = tmp; break; case OP_BITSETSTORE_I: op = &pr_opcodes[OP_BITOR_I]; tmp = var_b; var_b = var_a; var_a = tmp; break; case OP_BITSETSTORE_F: op = &pr_opcodes[OP_BITOR_F]; tmp = var_b; var_b = var_a; var_a = tmp; break; case OP_LOAD_P: case OP_LOAD_I: op = &pr_opcodes[OP_LOAD_F]; break; case OP_STOREP_P: op = &pr_opcodes[OP_STOREP_I]; break; case OP_EQ_P: op = &pr_opcodes[OP_EQ_E]; break; case OP_NE_P: op = &pr_opcodes[OP_NE_E]; break; case OP_GT_P: op = &pr_opcodes[OP_GT_I]; break; case OP_GE_P: op = &pr_opcodes[OP_GE_I]; break; case OP_LE_P: op = &pr_opcodes[OP_LE_I]; break; case OP_LT_P: op = &pr_opcodes[OP_LT_I]; break; case OP_BITCLR_I64: //b = var, a = bit field. var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_I64], var_a, var_b, NULL, STFL_PRESERVEA); var_c = nullsref; op = &pr_opcodes[OP_SUB_I64]; break; case OP_BITCLR_I: if (QCC_OPCodeValid(&pr_opcodes[OP_BITCLRSTORE_I])) { op = &pr_opcodes[OP_BITCLRSTORE_I]; break; } tmp = var_b; var_b = var_a; var_a = tmp; //fallthrough case OP_BITCLRSTORE_I: //b = var, a = bit field. var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_I], var_b, var_a, NULL, STFL_PRESERVEA); var_a = var_b; var_b = var_c; var_c = ((op - pr_opcodes)==OP_BITCLRSTORE_I)?var_a:nullsref; op = &pr_opcodes[OP_SUB_I]; break; case OP_BITCLR_V: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_V], var_a, var_b, NULL, STFL_PRESERVEA); op = &pr_opcodes[OP_SUB_V]; var_c = nullsref; break; case OP_BITCLR_D: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_D], var_a, var_b, NULL, STFL_PRESERVEA); op = &pr_opcodes[OP_SUB_D]; var_c = nullsref; break; case OP_BITCLR_F: var_c = var_b; var_b = var_a; var_a = var_c; if (QCC_OPCodeValid(&pr_opcodes[OP_BITCLRSTORE_F])) { //its all okay, just use it op = &pr_opcodes[OP_BITCLRSTORE_F]; break; } //fallthrough case OP_BITCLRSTORE_F: //b = var, a = bit field. //b - (b&a) var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_F], var_b, var_a, NULL, STFL_PRESERVEA); var_a = var_b; var_b = var_c; var_c = ((op - pr_opcodes)==OP_BITCLRSTORE_F)?var_a:nullsref; op = &pr_opcodes[OP_SUB_F]; break; #if IAMNOTLAZY case OP_SUBSTOREP_FI: case OP_SUBSTOREP_IF: case OP_ADDSTOREP_FI: case OP_ADDSTOREP_IF: case OP_MULSTOREP_FI: case OP_MULSTOREP_IF: case OP_DIVSTOREP_FI: case OP_DIVSTOREP_IF: case OP_MULSTOREP_VF: case OP_MULSTOREP_VI: case OP_SUBSTOREP_V: case OP_ADDSTOREP_V: case OP_SUBSTOREP_F: case OP_SUBSTOREP_I: case OP_ADDSTOREP_I: case OP_ADDSTOREP_F: case OP_MULSTOREP_F: case OP_DIVSTOREP_F: case OP_BITSETSTOREP_F: case OP_BITSETSTOREP_I: case OP_BITCLRSTOREP_F: // QCC_PR_ParseWarning(0, "XSTOREP_F emulation is still experimental"); QCC_UnFreeTemp(var_a); QCC_UnFreeTemp(var_b); statement = &statements[numstatements++]; //don't chain these... this expansion is not the same. { int st; pbool need_lock; st = QCC_PR_FindSourceForTemp(var_b, OP_ADDRESS, &need_lock); var_c = QCC_GetTemp(*op->type_c); if (st < 0) { /*generate new OP_LOADP instruction*/ statement->op = ((*op->type_c)->type==ev_vector)?OP_LOADP_V:OP_LOADP_F; statement->a = var_b->ofs; statement->b = var_c->ofs; statement->c = 0; } else { /*it came from an OP_ADDRESS - st says the instruction*/ if (need_lock) { QCC_ForceLockTempForOffset(statements[st].a); QCC_ForceLockTempForOffset(statements[st].b); // QCC_LockTemp(var_c); /*that temp needs to be preserved over calls*/ } /*generate new OP_ADDRESS instruction - FIXME: the arguments may have changed since the original instruction*/ statement->op = OP_ADDRESS; statement->flags = 0; statement->a = statements[st].a; statement->b = statements[st].b; statement->c = var_c->ofs; statement->linenum = statements[st].linenum; /*convert old one to an OP_LOAD*/ statements[st].op = ((*op->type_c)->type==ev_vector)?OP_LOAD_V:OP_LOAD_F; statement->flags = 0; // statements[st].a = statements[st].a; // statements[st].b = statements[st].b; // statements[st].c = statements[st].c; statements[st].linenum = pr_token_line_last; } } statement = &statements[numstatements++]; statement->linenum = pr_token_line_last; switch(op - pr_opcodes) { case OP_SUBSTOREP_V: statement->op = OP_SUB_V; break; case OP_ADDSTOREP_V: statement->op = OP_ADD_V; break; case OP_MULSTOREP_VF: statement->op = OP_MUL_VF; break; case OP_MULSTOREP_VI: statement->op = OP_MUL_VI; break; case OP_SUBSTOREP_F: statement->op = OP_SUB_F; break; case OP_SUBSTOREP_I: statement->op = OP_SUB_I; break; case OP_SUBSTOREP_IF: statement->op = OP_SUB_IF; break; case OP_SUBSTOREP_FI: statement->op = OP_SUB_FI; break; case OP_ADDSTOREP_IF: statement->op = OP_ADD_IF; break; case OP_ADDSTOREP_FI: statement->op = OP_ADD_FI; break; case OP_MULSTOREP_IF: statement->op = OP_MUL_IF; break; case OP_MULSTOREP_FI: statement->op = OP_MUL_FI; break; case OP_DIVSTOREP_IF: statement->op = OP_DIV_IF; break; case OP_DIVSTOREP_FI: statement->op = OP_DIV_FI; break; case OP_ADDSTOREP_F: statement->op = OP_ADD_F; break; case OP_ADDSTOREP_I: statement->op = OP_ADD_I; break; case OP_MULSTOREP_F: statement->op = OP_MUL_F; break; case OP_DIVSTOREP_F: statement->op = OP_DIV_F; break; case OP_BITSETSTOREP_F: statement->op = OP_BITOR_F; break; case OP_BITSETSTOREP_I: statement->op = OP_BITOR_I; break; case OP_BITCLRSTOREP_F: //float pointer float temp = QCC_GetTemp(type_float); statement->op = OP_BITAND_F; statement->flags = 0; statement->a = var_c ? var_c->ofs : 0; statement->b = var_a ? var_a->ofs : 0; statement->c = temp->ofs; statement = &statements[numstatements]; numstatements++; statement->linenum = pr_token_line_last; statement->op = OP_SUB_F; statement->flags = 0; //t = c & i //c = c - t break; default: //no way will this be hit... QCC_PR_ParseError(ERR_INTERNAL, "opcode invalid 3 times %i", op - pr_opcodes); } if (op - pr_opcodes == OP_BITCLRSTOREP_F) { statement->a = var_b ? var_b->ofs : 0; statement->b = temp ? temp->ofs : 0; statement->c = var_b->ofs; QCC_FreeTemp(temp); QCC_FreeTemp(var_a); var_a = var_b; //this is the value. var_b = var_c; //this is the ptr. } else { statement->a = var_b ? var_b->ofs : 0; statement->b = var_a ? var_a->ofs : 0; statement->c = var_b->ofs; QCC_FreeTemp(var_a); var_a = var_b; //this is the value. var_b = var_c; //this is the ptr. } op = &pr_opcodes[((*op->type_c)->type==ev_vector)?OP_STOREP_V:OP_STOREP_F]; QCC_FreeTemp(var_c); var_c = NULL; QCC_FreeTemp(var_b); break; #endif case OP_LSHIFT_IF: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_LSHIFT_I], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_LSHIFT_FI: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_LSHIFT_I], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_RSHIFT_IF: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_I], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_RSHIFT_FI: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_I], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_LSHIFT_I: { QCC_sref_t fnc = QCC_PR_EmulationFunc(LShiftInt); if (!fnc.cast) { const QCC_eval_t *eval_b = QCC_SRef_EvalConst(var_b); if (eval_b) var_b = QCC_MakeIntConst(1<_int); else var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_POW_I], QCC_MakeIntConst(2), var_b, NULL, flags&STFL_PRESERVEB); return QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], var_a, var_b, NULL, flags&STFL_PRESERVEA); } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_integer, var_b, type_integer); var_c.cast = type_integer; return var_c; } break; case OP_RSHIFT_I: //C leaves it undefined whether OP_RSHIFT_I is _I or actually _U { const QCC_eval_t *eval_b; QCC_sref_t fnc = QCC_PR_EmulationFunc(RShiftInt); if (fnc.cast && strcmp(fnc.sym->name, pr_scope->name)) { var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_integer, var_b, type_integer); var_c.cast = type_integer; return var_c; } //fix up the rhs of the shift to something we can actually use. eval_b = QCC_SRef_EvalConst(var_b); if (eval_b) { if (!(flags&STFL_PRESERVEB)) QCC_FreeTemp(var_b); //done with it now var_b = QCC_MakeIntConst(1<_int); } else var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_POW_I], QCC_MakeIntConst(2), var_b, NULL, flags&STFL_PRESERVEB); if (QCC_OPCodeValid(&pr_opcodes[OP_DIV_U])) op = &pr_opcodes[OP_DIV_U]; else op = &pr_opcodes[OP_DIV_I]; //might be quirky when the high bit is set in the divisor //jump-if->=0 statement = QCC_Generate_OP_IFNOT(QCC_PR_StatementFlags(&pr_opcodes[OP_LT_I], var_a, QCC_MakeFloatConst(0), NULL, STFL_PRESERVEA), false); //negative. we need to do this explicitly to avoid rounding var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITNOT_I], var_a, nullsref, NULL, STFL_PRESERVEA); //flip it var_c = QCC_PR_StatementFlags(op, var_c, var_b, NULL, STFL_PRESERVEB); //shift it (zero-extend)... var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITNOT_I], var_c, nullsref, NULL, 0); //now flip that. //else statement->b.jumpofs = &statements[numstatements+1] - statement;//jump to after the following goto statement = QCC_Generate_OP_GOTO(); //positive QCC_PR_SimpleStatement(op, var_a, var_b, var_c, false)->flags|=STF_NOFOLD; //can just shift into the same temp the negative form would have produced.. //end statement->a.jumpofs = &statements[numstatements] - statement; QCC_FreeTemp(var_b); //done with it now if (!(flags&STFL_PRESERVEA)) QCC_FreeTemp(var_a); //done with it now var_c.cast = type_integer; return var_c; } break; case OP_RSHIFT_U: { QCC_sref_t fnc = QCC_PR_EmulationFunc(RShiftUInt); if (!fnc.cast) { const QCC_eval_t *eval_b = QCC_SRef_EvalConst(var_b); if (eval_b) var_b = QCC_MakeUIntConst(1<_uint); else var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_POW_I], QCC_MakeUIntConst(2), var_b, NULL, flags&STFL_PRESERVEB); return QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_U], var_a, var_b, NULL, flags&STFL_PRESERVEA); } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_uint, var_b, type_uint); var_c.cast = type_uint; return var_c; } break; case OP_LSHIFT_DI: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_DI64], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_LSHIFT_I64I], var_a, var_b, NULL, 0); case OP_RSHIFT_DI: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_DI64], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_I64I], var_a, var_b, NULL, 0); //convert both to ints case OP_LSHIFT_F: if (QCC_OPCodeValid(&pr_opcodes[OP_LSHIFT_I])) { var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_a, nullsref, NULL, flags&STFL_PRESERVEA); var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_LSHIFT_I], var_a, var_b, NULL, 0); } else { QCC_sref_t fnc = QCC_PR_EmulationFunc(powf); if (!fnc.cast) fnc = QCC_PR_EmulationFunc(pow); if (fnc.cast) { //a<type_c; return var_c; } QCC_PR_ParseWarning(0, "bitshift function not defined: cannot emulate OP_LSHIFT_F*"); goto badopcode; } case OP_RSHIFT_F: if (QCC_OPCodeValid(&pr_opcodes[OP_RSHIFT_I])) { var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_a, nullsref, NULL, flags&STFL_PRESERVEA); var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_FTOI], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_I], var_a, var_b, NULL, 0); } else { QCC_sref_t fnc = QCC_PR_EmulationFunc(powf); if (!fnc.cast) fnc = QCC_PR_EmulationFunc(pow); if (fnc.cast) { //a<type_c; return var_c; } QCC_PR_ParseWarning(0, "bitshift function not defined: cannot emulate OP_RSHIFT_F*"); goto badopcode; } case OP_BITEXTEND_I: { const QCC_eval_t *eval_b = QCC_SRef_EvalConst(var_b); if (eval_b) { int bits = eval_b->_uint&0xff; int bitofs = eval_b->_uint>>8; var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_LSHIFT_I], var_a, QCC_MakeIntConst(32-bits-bitofs), NULL, flags&STFL_PRESERVEA); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_I], var_a, QCC_MakeIntConst(32-bits), NULL, flags&STFL_PRESERVEA); return var_c; } } goto badopcode; case OP_BITEXTEND_U: { const QCC_eval_t *eval_b = QCC_SRef_EvalConst(var_b); if (eval_b) { int bits = eval_b->_uint&0xff; int bitofs = eval_b->_uint>>8; var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_LSHIFT_U], var_a, QCC_MakeIntConst(32-bits-bitofs), NULL, flags&STFL_PRESERVEA); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_U], var_a, QCC_MakeIntConst(32-bits), NULL, flags&STFL_PRESERVEA); return var_c; } } goto badopcode; case OP_BITAND_D: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_DI64], var_a, nullsref, NULL, flags&STFL_PRESERVEA); var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_DI64], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_I64], var_a, var_b, NULL, 0); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_I64D], var_c, nullsref, NULL, 0); //grr return var_c; case OP_BITOR_D: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_DI64], var_a, nullsref, NULL, flags&STFL_PRESERVEA); var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_DI64], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_I64], var_a, var_b, NULL, 0); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_I64D], var_c, nullsref, NULL, 0); //grr return var_c; case OP_LOAD_I64: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_GetTemp(type_int64); QCC_PR_SimpleStatement(&pr_opcodes[OP_LOAD_FLD], var_a, var_b, var_c, false); var_b.ofs++; var_c.ofs++; QCC_PR_SimpleStatement(&pr_opcodes[OP_LOAD_FLD], var_a, var_b, var_c, false); var_c.ofs--; return var_c; case OP_BITAND_I64: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_GetTemp(type_int64); QCC_PR_SimpleStatement(&pr_opcodes[OP_BITAND_I], var_a, var_b, var_c, false); var_a.ofs++; var_b.ofs++; var_c.ofs++; QCC_PR_SimpleStatement(&pr_opcodes[OP_BITAND_I], var_a, var_b, var_c, false); var_c.ofs--; return var_c; case OP_BITOR_I64: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_GetTemp(type_int64); QCC_PR_SimpleStatement(&pr_opcodes[OP_BITOR_I], var_a, var_b, var_c, false); var_a.ofs++; var_b.ofs++; var_c.ofs++; QCC_PR_SimpleStatement(&pr_opcodes[OP_BITOR_I], var_a, var_b, var_c, false); var_c.ofs--; return var_c; case OP_BITXOR_I64: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_GetTemp(type_int64); QCC_PR_SimpleStatement(&pr_opcodes[OP_BITXOR_I], var_a, var_b, var_c, false); var_a.ofs++; var_b.ofs++; var_c.ofs++; QCC_PR_SimpleStatement(&pr_opcodes[OP_BITXOR_I], var_a, var_b, var_c, false); var_c.ofs--; return var_c; case OP_EQ_I64: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_EQ_I], var_a, var_b, NULL, STFL_PRESERVEA|STFL_PRESERVEB); var_a.ofs++; var_b.ofs++; var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_EQ_I], var_a, var_b, NULL, 0); return QCC_PR_StatementFlags(&pr_opcodes[OP_AND_I], var_c, var_a, NULL, 0); case OP_NE_I64: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_NE_I], var_a, var_b, NULL, STFL_PRESERVEA|STFL_PRESERVEB); var_a.ofs++; var_b.ofs++; var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_NE_I], var_a, var_b, NULL, 0); return QCC_PR_StatementFlags(&pr_opcodes[OP_OR_I], var_c, var_a, NULL, 0); //statements where the rhs is an input int and can be swapped with a float case OP_ADD_FI: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_F], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_SUB_FI: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_F], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_BITAND_FI: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_F], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_BITOR_FI: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_F], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_LT_FI: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_LT_F], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_LE_FI: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_LE_F], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_GT_FI: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_GT_F], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_GE_FI: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_GE_F], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_EQ_FI: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_EQ_F], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_NE_FI: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_NE_F], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_DIV_FI: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_F], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_MUL_FI: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_F], var_a, var_b, NULL, flags&STFL_PRESERVEA); case OP_MUL_VI: var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); return QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_VF], var_a, var_b, NULL, flags&STFL_PRESERVEA); //statements where the lhs is an input int and can be swapped with a float case OP_ADD_IF: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_F], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_SUB_IF: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_F], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_BITAND_IF: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_F], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_BITOR_IF: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_F], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_LT_IF: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_LT_F], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_LE_IF: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_LE_F], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_GT_IF: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_GT_F], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_GE_IF: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_GE_F], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_EQ_IF: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_EQ_F], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_NE_IF: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_NE_F], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_DIV_IF: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_F], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_MUL_IF: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_F], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_MUL_IV: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_FV], var_a, var_b, NULL, flags&STFL_PRESERVEB); //statements where both sides will need to be converted to floats to work case OP_LE_I: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_LE_FI], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_GE_I: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_GE_FI], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_LT_I: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_LT_FI], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_GT_I: var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); return QCC_PR_StatementFlags(&pr_opcodes[OP_GT_FI], var_a, var_b, NULL, flags&STFL_PRESERVEB); case OP_EQ_FLD: case OP_EQ_I: return QCC_PR_StatementFlags(&pr_opcodes[OP_EQ_FNC], var_a, var_b, NULL, flags&(STFL_PRESERVEA|STFL_PRESERVEB)); case OP_NE_FLD: case OP_NE_I: return QCC_PR_StatementFlags(&pr_opcodes[OP_NE_FNC], var_a, var_b, NULL, flags&(STFL_PRESERVEA|STFL_PRESERVEB)); case OP_NOT_I: return QCC_PR_StatementFlags(&pr_opcodes[OP_EQ_FNC], var_a, QCC_MakeIntConst(0), NULL, flags&(STFL_PRESERVEA)); case OP_AND_I: case OP_AND_FI: case OP_AND_IF: case OP_AND_ANY: if (var_a.cast->type == ev_vector && flag_vectorlogic) //we can do a dot-product to test if a vector has a value, instead of a double-not var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_V], var_a, var_a, NULL, STFL_PRESERVEA | (flags&STFL_PRESERVEA?STFL_PRESERVEB:0)); if (var_b.cast->type == ev_vector && flag_vectorlogic) var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_V], var_b, var_b, NULL, STFL_PRESERVEA | (flags&STFL_PRESERVEB?STFL_PRESERVEB:0)); if (((var_a.cast->size != 1 && flag_vectorlogic) || (var_a.cast->type == ev_string && flag_ifstring)) && ((var_b.cast->size != 1 && flag_vectorlogic) || (var_b.cast->type == ev_string && flag_ifstring))) { //just 3 extra instructions instead of 4. var_a = QCC_PR_GenerateLogicalNot(var_a, "%s used as truth value"); var_b = QCC_PR_GenerateLogicalNot(var_b, "%s used as truth value"); var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_OR_ANY], var_a, var_b, NULL, 0); return QCC_PR_GenerateLogicalNot(var_a, "%s isn't a float..."); } if (var_a.cast->type == ev_string && flag_ifstring) var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_NE_S], var_a, QCC_MakeStringConst(""), NULL, (flags&STFL_PRESERVEA?STFL_PRESERVEA:0)); else if ((var_a.cast->type == ev_string && flag_ifstring)) var_a = QCC_PR_GenerateLogicalNot(QCC_PR_GenerateLogicalNot(var_a, "%s used as truth value"), "%s isn't a float..."); if (var_b.cast->type == ev_string && flag_ifstring) var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_NE_S], QCC_MakeStringConst(""), var_b, NULL, (flags&STFL_PRESERVEB?STFL_PRESERVEB:0)); else if (var_b.cast->size != 1 && flag_vectorlogic) var_b = QCC_PR_GenerateLogicalNot(QCC_PR_GenerateLogicalNot(var_b, "%s used as truth value"), "%s isn't a float..."); if (var_a.cast->type != ev_float && var_b.cast->type != ev_float && QCC_OPCodeValid(&pr_opcodes[OP_AND_I])) op = &pr_opcodes[OP_AND_I]; //negative 0 as a float is considered zero by the fpu, which makes 0x80000000 tricky. avoid that if we can. else if (var_a.cast->type != ev_float && var_b.cast->type == ev_float && QCC_OPCodeValid(&pr_opcodes[OP_AND_IF])) op = &pr_opcodes[OP_AND_IF]; else if (var_a.cast->type == ev_float && var_b.cast->type != ev_float && QCC_OPCodeValid(&pr_opcodes[OP_AND_FI])) op = &pr_opcodes[OP_AND_FI]; else op = &pr_opcodes[OP_AND_F]; //generally works. if there's no other choice then meh. break; case OP_OR_I: case OP_OR_FI: case OP_OR_IF: case OP_OR_ANY: if (var_a.cast->type == ev_vector && flag_vectorlogic) //we can do a dot-product to test if a vector has a value, instead of a double-not var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_V], var_a, var_a, NULL, STFL_PRESERVEA | (flags&STFL_PRESERVEA?STFL_PRESERVEB:0)); if (var_b.cast->type == ev_vector && flag_vectorlogic) var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_V], var_b, var_b, NULL, STFL_PRESERVEA | (flags&STFL_PRESERVEB?STFL_PRESERVEB:0)); if (((var_a.cast->size != 1 && flag_vectorlogic) || (var_a.cast->type == ev_string && flag_ifstring)) && ((var_b.cast->size != 1 && flag_vectorlogic) || (var_b.cast->type == ev_string && flag_ifstring))) { //just 3 extra instructions instead of 4. var_a = QCC_PR_GenerateLogicalNot(var_a, "%s used as truth value"); var_b = QCC_PR_GenerateLogicalNot(var_b, "%s used as truth value"); var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_AND_ANY], var_a, var_b, NULL, 0); return QCC_PR_GenerateLogicalNot(var_a, "%s isn't a float..."); } if (var_a.cast->type == ev_string && flag_ifstring) var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_NE_S], var_a, QCC_MakeStringConst(""), NULL, (flags&STFL_PRESERVEA?STFL_PRESERVEA:0)); else if (var_a.cast->size != 1 && flag_vectorlogic) var_a = QCC_PR_GenerateLogicalNot(QCC_PR_GenerateLogicalNot(var_a, "%s used as truth value"), "%s isn't a float..."); if (var_b.cast->type == ev_string && flag_ifstring) var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_NE_S], QCC_MakeStringConst(""), var_b, NULL, (flags&STFL_PRESERVEB?STFL_PRESERVEB:0)); else if (var_b.cast->size != 1 && flag_vectorlogic) var_b = QCC_PR_GenerateLogicalNot(QCC_PR_GenerateLogicalNot(var_b, "%s used as truth value"), "%s isn't a float..."); if (var_a.cast->type != ev_float && var_b.cast->type != ev_float && QCC_OPCodeValid(&pr_opcodes[OP_OR_I])) op = &pr_opcodes[OP_OR_I]; //negative 0 as a float is considered zero by the fpu, which makes 0x80000000 tricky. avoid that if we can. else if (var_a.cast->type != ev_float && var_b.cast->type == ev_float && QCC_OPCodeValid(&pr_opcodes[OP_OR_IF])) op = &pr_opcodes[OP_OR_IF]; else if (var_a.cast->type == ev_float && var_b.cast->type != ev_float && QCC_OPCodeValid(&pr_opcodes[OP_OR_FI])) op = &pr_opcodes[OP_OR_FI]; else op = &pr_opcodes[OP_OR_F]; //generally works. if there's no other choice then meh. break; case OP_LOADP_U8: if (!var_b.cast) var_b = QCC_MakeUIntConst(0); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_I], var_b, QCC_MakeUIntConst(3), NULL, STFL_PRESERVEA); //read byte index for later var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_I], var_b, QCC_MakeUIntConst(2), NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); //go from byte to word precision var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_LOADP_I], var_a, var_b, NULL, flags&STFL_PRESERVEA); //read it as a[b], WARNING: var_a may be misaligned var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], QCC_MakeUIntConst(8), var_c, NULL, 0); //bytes to bits var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_I], QCC_MakeUIntConst(32-8), var_c, NULL, 0); //32-bits-bitofs var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_LSHIFT_U], var_a, var_c, NULL, 0); //shift the part we want to the high bit return QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_U], var_a, QCC_MakeIntConst(32-8), NULL, 0); //and shift down to the bits we actually want case OP_LOADP_I8: if (!var_b.cast) var_b = QCC_MakeUIntConst(0); if (QCC_OPCodeValid(&pr_opcodes[OP_LOADP_U8])) { //can just reextend it in combination with the older instruction set, for 2 or 3 instructions instead of 7. var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_LOADP_U8], var_a, var_b, NULL, flags&(STFL_PRESERVEA|STFL_PRESERVEB)); return QCC_PR_StatementFlags(&pr_opcodes[OP_BITEXTEND_I], var_c, QCC_MakeUIntConst(8), NULL, 0); } var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_I], var_b, QCC_MakeUIntConst(3), NULL, STFL_PRESERVEA); //read byte index for later var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_I], var_b, QCC_MakeUIntConst(2), NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); //go from byte to word precision var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_LOADP_I], var_a, var_b, NULL, flags&STFL_PRESERVEA); //read it as a[b], WARNING: var_a may be misaligned var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], QCC_MakeUIntConst(8), var_c, NULL, 0); //bytes to bits var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_I], QCC_MakeUIntConst(32-8), var_c, NULL, 0); //32-bits-bitofs var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_LSHIFT_I], var_a, var_c, NULL, 0); //shift the part we want to the high bit return QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_I], var_a, QCC_MakeIntConst(32-8), NULL, 0); //and shift down to the bits we actually want case OP_LOADP_U16: if (!var_b.cast) var_b = QCC_MakeUIntConst(0); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_I], var_b, QCC_MakeUIntConst(1), NULL, STFL_PRESERVEA); //read byte index for later var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_I], var_b, QCC_MakeUIntConst(1), NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); //go from short to word precision var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_LOADP_I], var_a, var_b, NULL, flags&STFL_PRESERVEA); //read it as a[b], WARNING: var_a may be misaligned var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], QCC_MakeUIntConst(16), var_c, NULL, 0); //short to bits var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_I], QCC_MakeUIntConst(32-16), var_c, NULL, 0); //32-bits-bitofs var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_LSHIFT_U], var_a, var_c, NULL, 0); //shift the part we want to the high bit return QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_U], var_a, QCC_MakeIntConst(32-16), NULL, 0); //and shift down to the bits we actually want case OP_LOADP_I16: if (!var_b.cast) var_b = QCC_MakeUIntConst(0); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_I], var_b, QCC_MakeUIntConst(1), NULL, STFL_PRESERVEA); //read byte index for later var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_I], var_b, QCC_MakeUIntConst(1), NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0); //go from short to word precision var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_LOADP_I], var_a, var_b, NULL, flags&STFL_PRESERVEA); //read it as a[b], WARNING: var_a may be misaligned var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], QCC_MakeUIntConst(16), var_c, NULL, 0); //short to bits var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_I], QCC_MakeUIntConst(32-16), var_c, NULL, 0); //32-bits-bitofs var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_LSHIFT_I], var_a, var_c, NULL, 0); //shift the part we want to the high bit return QCC_PR_StatementFlags(&pr_opcodes[OP_RSHIFT_I], var_a, QCC_MakeIntConst(32-16), NULL, 0); //and shift down to the bits we actually want // case OP_LOADP_V: // break; case OP_LOADP_F: case OP_LOADP_S: case OP_LOADP_ENT: case OP_LOADP_FLD: case OP_LOADP_FNC: case OP_LOADP_I: { QCC_sref_t fnc = QCC_PR_EmulationFunc(memgetval); if (!fnc.cast) { QCC_PR_ParseWarning(0, "memgetval function not defined: cannot emulate OP_LOADP_*"); goto badopcode; } var_c = QCC_PR_GenerateFunctionCall2(nullsref, fnc, var_a, type_pointer, QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_ITOF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB)?STFL_PRESERVEA:0), type_float); var_c.cast = *op->type_c; return var_c; } break; case OP_ADD_PIW: if (flag_undefwordsize) //fine if its the engine's doing it, not so fine if we're assuming it in the qcc. QCC_PR_ParseWarning(ERR_BADEXTENSION, "Making assumptions about pointer sizes was blocked."); var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], QCC_MakeIntConst(VMWORDSIZE), var_b, NULL, flags&STFL_PRESERVEB); var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], var_a, var_b, NULL, flags&~STFL_PRESERVEB); var_c.cast = var_a.cast; return var_c; case OP_LT_U: case OP_LE_U: if (!QCC_OPCodeValid(&pr_opcodes[OP_LT_I])) { //we're kinda fucked. go straight for floats instead. with any luck we might avoid precision issues. var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_UF], var_a, nullsref, NULL, flags&STFL_PRESERVEA); var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_CONV_UF], var_b, nullsref, NULL, (flags&STFL_PRESERVEB?STFL_PRESERVEA:0)); if (op == &pr_opcodes[OP_LT_U]) return QCC_PR_StatementFlags(&pr_opcodes[OP_LT_F], var_a, var_b, NULL, flags&~(STFL_PRESERVEA|STFL_PRESERVEB)); else if (op == &pr_opcodes[OP_LE_U]) return QCC_PR_StatementFlags(&pr_opcodes[OP_LE_F], var_a, var_b, NULL, flags&~(STFL_PRESERVEA|STFL_PRESERVEB)); goto badopcode; } else { //bias down, forcing it to wrap. then use the regular compares var_a = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_U], var_a, QCC_MakeUIntConst(0x80000000), NULL, flags&STFL_PRESERVEA); var_b = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_U], var_b, QCC_MakeUIntConst(0x80000000), NULL, (flags&STFL_PRESERVEB?STFL_PRESERVEA:0)); var_a.cast = type_integer; var_b.cast = type_integer; if (op == &pr_opcodes[OP_LT_U]) return QCC_PR_StatementFlags(&pr_opcodes[OP_LT_I], var_a, var_b, NULL, flags&~(STFL_PRESERVEA|STFL_PRESERVEB)); else if (op == &pr_opcodes[OP_LE_U]) return QCC_PR_StatementFlags(&pr_opcodes[OP_LE_I], var_a, var_b, NULL, flags&~(STFL_PRESERVEA|STFL_PRESERVEB)); goto badopcode; } //FIXME: assume the high/sign bit is not set case OP_DIV_U: QCC_PR_ParseWarning(0, "OP_DIV_U emulation: assuming the high bit is not set..."); var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_I], var_a, var_b, NULL, flags); var_c.cast = type_uint; return var_c; //other uint ops have the same bit pattern case OP_ADD_U: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], var_a, var_b, NULL, flags); var_c.cast = type_uint; return var_c; case OP_SUB_U: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_I], var_a, var_b, NULL, flags); var_c.cast = type_uint; return var_c; case OP_MUL_U: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], var_a, var_b, NULL, flags); var_c.cast = type_uint; return var_c; //carry/overflow differ, but we don't provide such feedback. // case OP_MOD_U: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_MOD_I], var_a, var_b, NULL, flags); var_c.cast = type_uint; return var_c; case OP_BITAND_U: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_I], var_a, var_b, NULL, flags); var_c.cast = type_uint; return var_c; case OP_BITOR_U: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_I], var_a, var_b, NULL, flags); var_c.cast = type_uint; return var_c; case OP_BITXOR_U: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITXOR_I], var_a, var_b, NULL, flags); var_c.cast = type_uint; return var_c; case OP_BITNOT_U: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITNOT_I], var_a, var_b, NULL, flags); var_c.cast = type_uint; return var_c; case OP_BITCLR_U: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITCLR_I], var_a, var_b, NULL, flags); var_c.cast = type_uint; return var_c; case OP_LSHIFT_U: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_LSHIFT_I], var_a, var_b, NULL, flags); var_c.cast = type_uint; return var_c; case OP_GE_U: return QCC_PR_StatementFlags(&pr_opcodes[OP_LE_U], var_b, var_a, NULL, flags); //just swap the args, fewer opcodes needed. case OP_GT_U: return QCC_PR_StatementFlags(&pr_opcodes[OP_LT_U], var_b, var_a, NULL, flags); //just swap the args, fewer opcodes needed. case OP_EQ_U: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_EQ_I], var_a, var_b, NULL, flags); var_c.cast = type_uint; return var_c; case OP_NE_U: var_a.cast = type_integer; var_b.cast = type_integer; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_NE_I], var_a, var_b, NULL, flags); var_c.cast = type_uint; return var_c; case OP_ADD_U64: var_a.cast = type_int64; var_b.cast = type_int64; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I64], var_a, var_b, NULL, flags); var_c.cast = type_uint64; return var_c; case OP_SUB_U64: var_a.cast = type_int64; var_b.cast = type_int64; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_I64], var_a, var_b, NULL, flags); var_c.cast = type_uint64; return var_c; case OP_MUL_U64: var_a.cast = type_int64; var_b.cast = type_int64; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I64], var_a, var_b, NULL, flags); var_c.cast = type_uint64; return var_c; // case OP_MOD_U64: var_a.cast = type_int64; var_b.cast = type_int64; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_MOD_I64], var_a, var_b, NULL, flags); var_c.cast = type_uint64; return var_c; case OP_BITAND_U64: var_a.cast = type_int64; var_b.cast = type_int64; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_I64], var_a, var_b, NULL, flags); var_c.cast = type_uint64; return var_c; case OP_BITOR_U64: var_a.cast = type_int64; var_b.cast = type_int64; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITOR_I64], var_a, var_b, NULL, flags); var_c.cast = type_uint64; return var_c; case OP_BITXOR_U64: var_a.cast = type_int64; var_b.cast = type_int64; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITXOR_I64], var_a, var_b, NULL, flags); var_c.cast = type_uint64; return var_c; case OP_BITNOT_U64: var_a.cast = type_int64; var_b.cast = type_int64; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITNOT_I64], var_a, var_b, NULL, flags); var_c.cast = type_uint64; return var_c; case OP_BITCLR_U64: var_a.cast = type_int64; var_b.cast = type_int64; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITCLR_I64], var_a, var_b, NULL, flags); var_c.cast = type_uint64; return var_c; case OP_LSHIFT_U64I: var_a.cast = type_int64; var_b.cast = type_int64; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_LSHIFT_I64I], var_a, var_b, NULL, flags); var_c.cast = type_uint64; return var_c; case OP_GE_U64: return QCC_PR_StatementFlags(&pr_opcodes[OP_LE_U64], var_b, var_a, NULL, flags); case OP_GT_U64: return QCC_PR_StatementFlags(&pr_opcodes[OP_LT_U64], var_b, var_a, NULL, flags); case OP_EQ_U64: var_a.cast = type_int64; var_b.cast = type_int64; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_EQ_I64], var_a, var_b, NULL, flags); var_c.cast = type_uint64; return var_c; case OP_NE_U64: var_a.cast = type_int64; var_b.cast = type_int64; var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_NE_I64], var_a, var_b, NULL, flags); var_c.cast = type_uint64; return var_c; case OP_GE_I64: var_a.cast = type_int64; var_b.cast = type_int64; return QCC_PR_StatementFlags(&pr_opcodes[OP_LE_I64], var_b, var_a, NULL, flags); case OP_GT_I64: var_a.cast = type_int64; var_b.cast = type_int64; return QCC_PR_StatementFlags(&pr_opcodes[OP_LT_I64], var_b, var_a, NULL, flags); case OP_GE_D: return QCC_PR_StatementFlags(&pr_opcodes[OP_LE_D], var_b, var_a, NULL, flags); case OP_GT_D: return QCC_PR_StatementFlags(&pr_opcodes[OP_LT_D], var_b, var_a, NULL, flags); case OP_STORE_I64: var_c = var_b; var_c.cast = type_integer; QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_FLD], var_a, var_c, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); var_a.ofs++; var_c.ofs++; QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_FLD], var_a, var_c, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); return var_b; default: badopcode: if (QCC_OPCodeValidForTarget(QCF_FTE, QCTARGVER_FTE_DEF, op)) QCC_PR_ParseWarning(ERR_BADEXTENSION, "Opcode \"%s|%s\" not valid for target. Consider the use of: #pragma target fte", op->name, op->opname); else if (QCC_OPCodeValidForTarget(QCF_FTE, QCTARGVER_FTE_MAX, op)) QCC_PR_ParseWarning(ERR_BADEXTENSION, "Opcode \"%s|%s\" not valid for target. Consider the use of: #pragma target fte_%u", op->name, op->opname, QCTARGVER_FTE_MAX); else QCC_PR_ParseWarning(ERR_BADEXTENSION, "Opcode \"%s|%s\" is not supported.", op->name, op->opname); break; } } if (!pr_scope) { switch(op - pr_opcodes) { case OP_GLOBALADDRESS: if (flag_pointerrelocs) return QCC_MakeGAddress(type_pointer, var_a.sym, var_a.ofs + (var_b.cast?QCC_Eval_Int(QCC_SRef_EvalConst(var_b), var_b.cast):0), 0); break; } QCC_PR_ParseWarning(ERR_BADEXTENSION, "Unable to generate statements at global scope (%s).", op->opname); } if (op->type_c == &type_void || op->associative==ASSOC_RIGHT || op->type_c == NULL) { QCC_FreeTemp(var_b); //returns a instead of some result/temp if (flags&STFL_DISCARDRESULT) QCC_FreeTemp(var_a); } else { QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); } if (outstatement) QCC_ClobberDef(NULL); statement = &statements[numstatements++]; if (outstatement) *outstatement = statement; statement->linenum = pr_token_line_last; statement->op = op - pr_opcodes; statement->flags = 0; statement->a = var_a; statement->b = var_b; if (var_c.cast && var_c.sym && !var_c.sym->referenced) { QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: var_c was not referenced"); QCC_PR_ParsePrintSRef(WARN_DEBUGGING, var_c); } if (var_b.cast && var_b.sym && !var_b.sym->referenced) { QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: var_b was not referenced"); QCC_PR_ParsePrintSRef(WARN_DEBUGGING, var_b); } if (var_a.cast && var_a.sym && !var_a.sym->referenced) { QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: var_a was not referenced"); QCC_PR_ParsePrintSRef(WARN_DEBUGGING, var_a); } if (var_c.cast) statement->c = var_c; else if (op->type_c == &type_void || op->associative==ASSOC_RIGHT || op->type_c == NULL) { var_c = nullsref; statement->c = nullsref; // ifs, gotos, and assignments // don't need vars allocated if (flags&STFL_DISCARDRESULT) return nullsref; return var_a; } else if (op-pr_opcodes == OP_ADD_PIW) { var_c = QCC_GetTemp(var_a.cast); statement->c = var_c; } else { // allocate result space var_c = QCC_GetTemp(*op->type_c); statement->c = var_c; if (op->type_b == &type_field) { //&(a.b) returns a pointer to b, so that pointer's auxtype should have the same type as b's auxtype if (var_b.cast->type == ev_variant) var_c.cast = type_variant; else if (var_c.cast->type == ev_pointer) var_c.cast = QCC_PR_PointerType(var_b.cast->aux_type); else if (var_c.cast->type == ev_field) var_c.cast = QCC_PR_FieldType(var_b.cast->aux_type); else var_c.cast = var_b.cast->aux_type; } } if (flags&STFL_DISCARDRESULT) { QCC_FreeTemp(var_c); var_c = nullsref; } return var_c; } /* ============ QCC_PR_SimpleStatement Emits a primitive statement ============ */ QCC_statement_t *QCC_PR_SimpleStatement ( QCC_opcode_t *op, QCC_sref_t var_a, QCC_sref_t var_b, QCC_sref_t var_c, int force) { QCC_statement_t *statement; if (!force && !QCC_OPCodeValid(op)) { // outputversion = op->extension; // if (noextensions) if (QCC_OPCodeValidForTarget(QCF_FTE, QCTARGVER_FTE_DEF, op)) QCC_PR_ParseError(ERR_BADEXTENSION, "Opcode \"%s|%s\" not valid for target. Consider the use of: #pragma target fte", op->name, op->opname); else if (QCC_OPCodeValidForTarget(QCF_FTE, QCTARGVER_FTE_MAX, op)) QCC_PR_ParseError(ERR_BADEXTENSION, "Opcode \"%s|%s\" not valid for target. Consider the use of: #pragma target fte_%u", op->name, op->opname, QCTARGVER_FTE_MAX); else QCC_PR_ParseError(ERR_BADEXTENSION, "Opcode \"%s|%s\" is not supported.", op->name, op->opname); } statement = &statements[numstatements]; numstatements++; statement->op = op - pr_opcodes; statement->flags = 0; statement->a = var_a; statement->b = var_b; statement->c = var_c; statement->linenum = pr_token_line_last; return statement; } QCC_statement_t *QCC_PR_SimpleInitStatement ( QCC_opcode_t *op, QCC_sref_t var_a, QCC_sref_t var_b, QCC_sref_t var_c) { //statements execed at global scope. will get inserted into an appropriate function at some later point. QCC_statement_t *statement; if (!QCC_OPCodeValid(op)) { if (QCC_OPCodeValidForTarget(QCF_FTE, QCTARGVER_FTE_DEF, op)) QCC_PR_ParseError(ERR_BADEXTENSION, "Opcode \"%s|%s\" not valid for target. Consider the use of: #pragma target fte", op->name, op->opname); else if (QCC_OPCodeValidForTarget(QCF_FTE, QCTARGVER_FTE_MAX, op)) QCC_PR_ParseError(ERR_BADEXTENSION, "Opcode \"%s|%s\" not valid for target. Consider the use of: #pragma target fte_%u", op->name, op->opname, QCTARGVER_FTE_MAX); else QCC_PR_ParseError(ERR_BADEXTENSION, "Opcode \"%s|%s\" is not supported.", op->name, op->opname); } if (numinitstatements+1 > maxinitstatements) { maxinitstatements+=64; initstatements = realloc(initstatements, sizeof(*initstatements)*maxinitstatements); } statement = &initstatements[numinitstatements]; numinitstatements++; statement->op = op - pr_opcodes; statement->flags = 0; statement->a = var_a; statement->b = var_b; statement->c = var_c; statement->linenum = 0;//pr_token_line_last; //will come from all over. the file won't be meaningful so nor will the line. return statement; } //this opcode updates its dest, so can't use QCC_PR_StatementFlags because that's only two args. We still want to be able to emulate it though. //NOTE: emulation might require an extra copy after. QCC_sref_t QCC_PR_Statement_BitCopy(QCC_sref_t var_a, unsigned int bitofs, unsigned int bits, QCC_sref_t var_c) { if (QCC_OPCodeValid(&pr_opcodes[OP_BITCOPY_I])) { //can do it in a single opcode. QCC_sref_t var_b = QCC_MakeUIntConst((bitofs<<8) | bits); var_b.sym->referenced = true; QCC_PR_SimpleStatement(&pr_opcodes[OP_BITCOPY_I], var_a, var_b, var_c, false); QCC_FreeTemp(var_a); QCC_FreeTemp(var_b); return var_c; } //this will take effort. we might still be able to fold part of it at least. var_c = QCC_PR_StatementFlags(&pr_opcodes[OP_BITAND_I], var_c, QCC_MakeUIntConst(~(((1u<= newstatementcount) { memmove(&pr_gotos[i], &pr_gotos[i+1], sizeof(*pr_gotos)*(num_gotos-(i+1))); num_gotos--; } else i++; } for (i = 0; i < num_labels; ) { //FIXME: stripping a label? erk? if (pr_labels[i].statementno >= newstatementcount) { memmove(&pr_labels[i], &pr_labels[i+1], sizeof(*pr_labels)*(num_labels-(i+1))); num_labels--; } else i++; } for (i = 0; i < num_breaks; ) { if (pr_breaks[i] >= newstatementcount) { memmove(&pr_breaks[i], &pr_breaks[i+1], sizeof(*pr_breaks)*(num_breaks-(i+1))); num_breaks--; } else i++; } for (i = 0; i < num_continues; ) { if (pr_continues[i] >= newstatementcount) { memmove(&pr_continues[i], &pr_continues[i+1], sizeof(*pr_continues)*(num_continues-(i+1))); num_continues--; } else i++; } for (i = 0; i < num_cases; ) { if (pr_cases[i] >= newstatementcount) { memmove(&pr_cases[i], &pr_cases[i+1], sizeof(*pr_cases)*(num_cases-(i+1))); memmove(&pr_casesref[i], &pr_casesref[i+1], sizeof(*pr_casesref)*(num_cases-(i+1))); memmove(&pr_casesref2[i], &pr_casesref2[i+1], sizeof(*pr_casesref2)*(num_cases-(i+1))); num_cases--; } else i++; } numstatements = newstatementcount; } /* ============ PR_ParseImmediate Looks for a preexisting constant ============ */ static QCC_sref_t QCC_PR_ParseImmediate (void) { QCC_sref_t cn; switch(pr_immediate_type->type) { case ev_float: cn = QCC_MakeFloatConst(pr_immediate._float); QCC_PR_Lex (); return cn; case ev_integer: cn = QCC_MakeIntConst(pr_immediate._int); QCC_PR_Lex (); return cn; case ev_uint: cn = QCC_MakeUIntConst(pr_immediate._uint); QCC_PR_Lex (); return cn; case ev_double: cn = QCC_MakeDoubleConst(pr_immediate._double); QCC_PR_Lex (); return cn; case ev_int64: cn = QCC_MakeInt64Const(pr_immediate.i64); QCC_PR_Lex (); return cn; case ev_uint64: cn = QCC_MakeUInt64Const(pr_immediate.u64); QCC_PR_Lex (); return cn; case ev_vector: cn = QCC_MakeVectorConst(pr_immediate.vector[0], pr_immediate.vector[1], pr_immediate.vector[2]); QCC_PR_Lex (); return cn; case ev_string: { int t=0,l; char tmp[8192]; do { l = pr_immediate_strlen; if (t+l+1 > sizeof(tmp)) QCC_PR_ParseError (ERR_NAMETOOLONG, "string immediate is too long"); memcpy(tmp+t, pr_immediate_string, l); t+=l; QCC_PR_Lex (); } while(pr_token_type == tt_immediate && pr_immediate_type == type_string); tmp[t++] = 0; cn = QCC_MakeStringConstLength(tmp, t); } return cn; default: QCC_PR_ParseError (ERR_BADIMMEDIATETYPE, "weird immediate type"); return nullsref; } } static QCC_ref_t *QCC_PR_GenerateAddressOf(QCC_ref_t *retbuf, QCC_ref_t *operand) { // QCC_def_t *e2; if (operand->type == REF_FIELD) { if (operand->bitofs) QCC_PR_ParseWarning (ERR_BADEXTENSION, "Address-of operator on bitfield"); //&e.f should generate a pointer def //as opposed to a ref return QCC_PR_BuildRef(retbuf, REF_GLOBAL, QCC_PR_Statement(&pr_opcodes[OP_ADDRESS], operand->base, operand->index, NULL), nullsref, QCC_PR_PointerType((operand->index.cast->type == ev_field)?operand->index.cast->aux_type:type_variant), true, 0); } if (operand->type == REF_ARRAYHEAD || operand->type == REF_GLOBAL || operand->type == REF_ARRAY) { QCC_sref_t ptr; const QCC_eval_t *eval; QCC_type_t *basetype = operand->cast; if (operand->type == REF_ARRAYHEAD) basetype = basetype->aux_type; if (!QCC_OPCodeValid(&pr_opcodes[OP_GLOBALADDRESS])) { if (operand->type == REF_ARRAYHEAD) QCC_PR_ParseError (ERR_BADEXTENSION, "Address-of operator is not supported in this form without extensions. Consider the use of either '#pragma target fte' or '#pragma flag enable brokenarray'"); else QCC_PR_ParseError (ERR_BADEXTENSION, "Address-of operator is not supported in this form without extensions. Consider the use of: #pragma target fte"); } if (operand->base.sym->scope && !operand->base.sym->addressedwarned && !operand->base.sym->isstatic) { char type[128]; QCC_PR_ParseWarning (WARN_UNSAFELOCALPOINTER, "Address-of operator on local %s %s is unsafe if recursing (and still bloated when not). Consider use of 'static' or 'auto'.", TypeName(operand->base.cast, type,sizeof(type)), operand->base.sym->name); operand->base.sym->addressedwarned = true; operand->base.sym->scope->privatelocals = true; //just in case. } if (operand->base.sym->temp) QCC_PR_ParseWarning (WARN_NOTSTANDARDBEHAVIOUR, "Address-of operator on temp from line %i", operand->base.sym->temp->lastline); eval = QCC_SRef_EvalConst(operand->index); if (eval && flag_pointerrelocs) { int ofs = QCC_Eval_Int(eval, operand->index.cast); ptr = QCC_MakeGAddress(type_pointer, operand->base.sym, operand->base.ofs, basetype->align * ofs + operand->bitofs); } else { if (basetype->align && basetype->align!=32) { //urgh. OP_GLOBALADDRESS is in terms of words, while pointers are often not (eg __int16, __int8, or even certain structs which lack 32bit members.) ptr = QCC_PR_Statement(&pr_opcodes[OP_GLOBALADDRESS], operand->base, nullsref, NULL); if (operand->index.cast) { QCC_sref_t idx = QCC_SupplyConversion(operand->index, ev_integer, true); if (basetype->align!=8) idx = QCC_PR_Statement(&pr_opcodes[OP_MUL_I], idx, QCC_MakeIntConst(basetype->align/8), NULL); ptr = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], ptr, idx, NULL); } } else ptr = QCC_PR_Statement(&pr_opcodes[OP_GLOBALADDRESS], operand->base, operand->index.cast?QCC_SupplyConversion(operand->index, ev_integer, true):nullsref, NULL); if (operand->bitofs) ptr = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], ptr, QCC_MakeIntConst(operand->bitofs/8), NULL); } //&foo (or &((&foo)[5]), which is basically an array). the result is a temp and thus cannot be assigned to (but should be possible to dereference further). return QCC_PR_BuildRef(retbuf, REF_GLOBAL, ptr, nullsref, QCC_PR_PointerType(basetype), true, 0); } if (operand->type == REF_POINTER || operand->type == REF_POINTERARRAY /*technically already meant to be an address, but hey, silently allow taking the address of it anyway*/) { //&(p[5]) just reverts back to p+5. it cannot be assigned to. QCC_sref_t addr, idx; const QCC_eval_t *eval; QCC_type_t *resulttype = operand->cast; //'int' * QCC_type_t *basetype = operand->cast; //'int' if (operand->type != REF_POINTERARRAY) resulttype = QCC_PR_PointerType(resulttype); else basetype = basetype->aux_type; //already a pointer. if (operand->index.cast) { if (basetype->align != 32) { //index is bytes or shorts or something small. if (!flag_undefwordsize && (eval=QCC_SRef_EvalConst(operand->index))) { int byteofs = QCC_Eval_Int(eval, operand->index.cast)*(basetype->align/8) + operand->bitofs/8; addr = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], operand->base, QCC_MakeIntConst(byteofs), NULL); } else { idx = QCC_PR_Statement(&pr_opcodes[OP_MUL_I], QCC_MakeIntConst(basetype->align/8), QCC_SupplyConversion(operand->index, ev_integer, true), NULL); if (operand->bitofs) addr = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], idx, QCC_MakeUInt64Const(operand->bitofs/8), NULL); addr = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], operand->base, idx, NULL); } } else { //index is words // if (!QCC_OPCodeValid(&pr_opcodes[OP_ADD_PIW])) // QCC_PR_ParseError (ERR_BADEXTENSION, "Address-of operator is not supported in this form without extensions. Consider the use of: #pragma target fte"); addr = QCC_PR_Statement(&pr_opcodes[OP_ADD_PIW], operand->base, QCC_SupplyConversion(operand->index, ev_integer, true), NULL); if (operand->bitofs) addr = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], addr, QCC_MakeUInt64Const(operand->bitofs/8), NULL); } } else { addr = operand->base; if (operand->bitofs) addr = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], addr, QCC_MakeUInt64Const(operand->bitofs/8), NULL); } return QCC_PR_BuildRef(retbuf, REF_GLOBAL, addr, nullsref, resulttype, true, 0); } QCC_PR_ParseError (ERR_BADEXTENSION, "Cannot use addressof operator ('&') on a global. Please use the FTE target."); return operand; } QCC_sref_t QCC_DP_GlobalAddress(QCC_sref_t base, QCC_sref_t index, unsigned int flags) { QCC_sref_t ptr = QCC_MakeGAddress(type_integer, base.sym, 0, 0); base.sym->used = true; if (!(flags&STFL_PRESERVEA)) QCC_FreeTemp(base); if (base.ofs) ptr = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], ptr, QCC_MakeIntConst(base.ofs), NULL, 0); if (index.cast) ptr = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], ptr, index, NULL, flags); return ptr; } static void QCC_PrecacheSound (const char *n, int ch) { int i; if (!*n) return; if (ch >= '1' && ch <= '9') ch -= '0'; else ch = 1; for (i=0 ; i ch) precache_sound[i].block = ch; return; } if (strchr(n, '\\')) QCC_PR_ParseWarning(WARN_NONPORTABLEFILENAME, "backslashes in path names are non-portable - %s", n); if (numsounds == QCC_MAX_SOUNDS) return; // QCC_Error ("PrecacheSound: numsounds == MAX_SOUNDS"); strcpy (precache_sound[i].name, n); precache_sound[i].block = ch; precache_sound[i].filename = s_filen; precache_sound[i].fileline = pr_source_line; numsounds++; } static void QCC_PrecacheModel (const char *n, int ch) { int i; if (!*n) return; for (i=0 ; i= '1' && ch <= '9') precache_model[i].block = ch - '0'; else precache_model[i].block = 1; } return; } if (strchr(n, '\\')) QCC_PR_ParseWarning(WARN_NONPORTABLEFILENAME, "backslashes in path names are non-portable - %s", n); if (nummodels == QCC_MAX_MODELS) return; // QCC_Error ("PrecacheModels: nummodels == MAX_MODELS"); strcpy (precache_model[i].name, n); if (ch >= '1' && ch <= '9') precache_model[i].block = ch - '0'; else precache_model[i].block = 1; precache_model[i].filename = s_filen; precache_model[i].fileline = pr_source_line; nummodels++; } static void QCC_SetModel (const char *n) { int i; if (!*n) return; for (i=0 ; i= '1' && ch <= '9') precache_texture[i].block = ch - '0'; else precache_texture[i].block = 1; numtextures++; } static void QCC_PrecacheFile (const char *n, int ch) { int i; if (!*n) return; for (i=0 ; i= '1' && ch <= '9') precache_file[i].block = ch - '0'; else precache_file[i].block = 1; numfiles++; } static void QCC_VerifyFormatString (const char *funcname, QCC_ref_t **arglist, unsigned int argcount) { const char *s = "%s", *reqtype; int firstarg = 1; const char *s0; char *err; int width, thisarg, arg; char formatbuf[16]; char temp[256]; int argpos = firstarg, argn_last = firstarg; int isfloat, is64bit; const QCC_eval_t *formatstring = QCC_SRef_EvalConst(arglist[0]->base); if (!formatstring) //can't check variables. return; if (!qccwarningaction[WARN_FORMATSTRING]) return; //don't bother if its not relevant anyway. s = strings + formatstring->string; #define ARGTYPE(a) (((a)>=firstarg && (a)cast->type==ev_boolean)?arglist[a]->cast->parentclass->type:arglist[a]->cast->type) : ev_void) #define ARGCTYPE(a) (((a)>=firstarg && (a)cast) : type_void) for(;;) { s0 = s; switch(*s) { case 0: if (argpos < argcount && argn_last < argcount) QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: surplus trailing %s%s%s argument(s) for format %s\"%s\"%s", funcname, col_type, TypeName(ARGCTYPE(argpos), temp, sizeof(temp)), col_none, col_name, strings + formatstring->string, col_none); return; case '%': if(*++s == '%') { s++; break; } // complete directive format: // %3$*1$.*2$ld width = -1; thisarg = -1; isfloat = -1; is64bit = 0; // is number following? if(*s >= '0' && *s <= '9') { width = strtol(s, &err, 10); if(!err) { QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: bad format string: %s%s%s", funcname, col_name, s0, col_none); return; } if(*err == '$') { thisarg = width + (firstarg-1); width = -1; s = err + 1; } else { if(*s == '0') { if(width == 0) width = -1; // it was just a flag } s = err; } } if(width < 0) { for(;;) { switch(*s) { case '#': //alternate case '0': //zero-pad case '-': //left-align case ' ': //space-positive case '+': //sign-positive break; default: goto noflags; } ++s; } noflags: if(*s == '*') { ++s; if(*s >= '0' && *s <= '9') { arg = strtol(s, &err, 10); if(!err || *err != '$') { QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: invalid format string: %s%s%s", funcname, col_name, s0, col_none); return; } s = err + 1; } else arg = argpos++; if (ARGTYPE(arg) != ev_float) QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: width modifier requires float at arg %i", funcname, arg+1); } else if(*s >= '0' && *s <= '9') { strtol(s, &err, 10); if(!err) { QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: invalid format string: %s%s%s", funcname, col_name, s0, col_none); return; } s = err; } // otherwise width stays -1 } //precision modifiers if(*s == '.') { ++s; if(*s == '*') { ++s; if(*s >= '0' && *s <= '9') { arg = strtol(s, &err, 10); if(!err || *err != '$') { QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: invalid format string: %s%s%s", funcname, col_name, s0, col_none); return; } s = err + 1; } else arg = argpos++; if (ARGTYPE(arg) != ev_float) QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: precision modifier requires float at arg %i", funcname, arg+1); } else if(*s >= '0' && *s <= '9') { strtol(s, &err, 10); if(!err) { QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: invalid format string: %s%s%s", funcname, col_name, s0, col_none); return; } s = err; } else { QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: invalid format string: %s%s%s", funcname, col_name, s0, col_none); return; } } //length modifiers for(;;) { switch(*s) { //case 'hh': //char case 'h': isfloat = 1; break; //short //case 'll': //long long case 'l': isfloat = 0; break; //long case 'L': isfloat = 0; break; //long double case 'q': is64bit = 1; break; //BSD's int64 case 'j': //[u]intmax_t case 'z': //size_t case 't': //ptrdiff_t QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: length modifier %s%c%s is a placeholder", funcname, col_type, *s, col_none); break; default: goto nolength; } ++s; } nolength: // now s points to the final directive char and is no longer changed if (*s == 'p' || *s == 'P') { //%p is slightly different from %x. //always 8-bytes wide with 0 padding, always ints. if (isfloat < 0) isfloat = 0; } else if (*s == 'i' || *s == 'I') { //%i defaults to ints, not floats. if(isfloat < 0) isfloat = 0; } //assume floats, not ints. if(isfloat < 0) isfloat = 1; if(thisarg < 0) thisarg = argpos++; if (argn_last < thisarg+1) argn_last = thisarg+1; memcpy(formatbuf, s0, s+1-s0); formatbuf[s+1-s0] = 0; reqtype = NULL; switch(*s) { //fixme: should we validate char ranges? case 'd': case 'i': case 'I': case 'c': case 'o': case 'u': case 'x': case 'X': case 'p': case 'P': case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': if (isfloat) { if (is64bit) { switch(ARGTYPE(thisarg)) { case ev_double: case ev_variant: break; default: reqtype = "double"; break; } } else { switch(ARGTYPE(thisarg)) { case ev_float: case ev_variant: break; default: reqtype = "float"; break; } } } else { if (*s == 'p' || *s == 'P') { if (is64bit) reqtype = "some kind of double-size pointer! oh noes!"; else switch(ARGTYPE(thisarg)) { case ev_pointer: case ev_variant: break; default: reqtype = "pointer"; break; } } else { if (is64bit) { switch(ARGTYPE(thisarg)) { case ev_int64: case ev_uint64: case ev_variant: break; default: reqtype = "__int64"; QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: %s%s%s requires __int64 at arg %i (got %s%s%s)", funcname, col_name, formatbuf, col_none, thisarg+1, col_type, TypeName(ARGCTYPE(thisarg), temp, sizeof(temp)), col_none); break; } } else { switch(ARGTYPE(thisarg)) { case ev_integer: case ev_uint: case ev_variant: break; case ev_entity: //accept ents ONLY for %i if (*s == 'i') break; //fallthrough default: reqtype = "int"; QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: %s%s%s requires int at arg %i (got %s%s%s)", funcname, col_name, formatbuf, col_none, thisarg+1, col_type, TypeName(ARGCTYPE(thisarg), temp, sizeof(temp)), col_none); break; } } } } break; case 'v': case 'V': if (isfloat) { switch(ARGTYPE(thisarg)) { case ev_vector: case ev_variant: break; default: reqtype = "vector"; break; } } else reqtype = "intvector"; break; case 's': case 'S': switch(ARGTYPE(thisarg)) { case ev_string: case ev_variant: break; default: reqtype = "string"; break; } break; default: QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: invalid format string: %s%s%s", funcname, col_name, s0, col_none); return; } if (reqtype) { QCC_PR_ParseWarning(WARN_FORMATSTRING, "%s: %s%s%s requires %s%s%s at arg %i (got %s%s%s)", funcname, col_name,formatbuf,col_none, col_type,reqtype,col_none, thisarg+1, col_type,TypeName(ARGCTYPE(thisarg), temp, sizeof(temp)),col_none); switch(ARGCTYPE(thisarg)->type) { case ev_string: QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %s or %S for strings"); break; case ev_float: QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %g or %f or %hx for floats"); break; case ev_vector: QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %v for vectors"); break; case ev_entity: QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %i for entities"); break; case ev_pointer: QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %p for pointer types"); break; case ev_bitfld: case ev_integer: QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %i or %lx for 32bit ints"); break; case ev_uint: QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %lu or or %lx for 32bit ints"); break; case ev_int64: QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %qi or %lqx for 64bit ints"); break; case ev_uint64: QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %lqu or or %lqx for 64bit ints"); break; case ev_double: QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "use %qg or %qf or %hqx for doubles"); break; case ev_void: //coder's problem case ev_field: //cast to int case ev_function: //cast to int case ev_variant: //should be accepted by anything... case ev_struct: //coder's problem case ev_union: //coder's problem case ev_accessor: //should be unreachable case ev_enum: //should be unreachable case ev_typedef: //should be unreachable case ev_boolean: //should be unreachable QCC_PR_Note(WARN_FORMATSTRING, s_filen, pr_source_line, "%s", "cast to something else"); break; } } s++; break; default: s++; break; } } } static void QCC_VerifyArgs_sendevent (const char *funcname, QCC_ref_t **arglist, unsigned int argcount) { //arg0 is the event name, and not meaningful //arg1 denotes the arg types passed and defines the types used for all other args. const QCC_eval_t *eval = (argcount >= 2 && arglist[1]->type == REF_GLOBAL)?QCC_SRef_EvalConst(arglist[1]->base):NULL; const char *argtypes = eval?strings + eval->string:NULL; size_t arg = 2; QCC_type_t *t; char temp[256]; if (!argtypes) return; for (; *argtypes; argtypes++, arg++) { switch(*argtypes) { case 's': t = type_string; break; case 'f': t = type_float; break; case 'F': t = type_double; break; case 'i': t = type_integer; break; case 'I': t = type_int64; break; case 'u': t = type_uint; break; case 'U': t = type_uint64; break; case 'v': t = type_vector; break; case 'e': t = type_entity; break; default: t = NULL; break; } if (arg >= argcount) { QCC_PR_ParseWarning(WARN_ARGUMENTCHECK, "%s: arg type list longer than args", funcname); break; } else if (!t) QCC_PR_ParseWarning(WARN_ARGUMENTCHECK, "%s: '%s%c%s' is not a recognised arg type", funcname, col_name, *argtypes, col_none); else if (typecmp_lax(arglist[arg]->cast, t)) QCC_PR_ParseWarning(WARN_ARGUMENTCHECK, "%s: '%s%c%s' specified, but was passed %s%s%s", funcname, col_name, *argtypes, col_none, col_type, TypeName(arglist[arg]->cast, temp, sizeof(temp)), col_none); } if (arg > 8) QCC_PR_ParseWarning(WARN_ARGUMENTCHECK, "%s: cannot pass more than 8 args to a builtin.", funcname); } static void QCC_VerifyArgs_setviewprop (const char *funcname, QCC_ref_t **arglist, unsigned int argcount) { static struct { const char *name; int n; etype_t t1; etype_t t2; etype_t t3; } argtypes[] = { {"VF_MIN", 1, ev_vector}, {"VF_MIN_X", 2, ev_float}, {"VF_MIN_Y", 3, ev_float}, {"VF_SIZE", 4, ev_vector}, {"VF_SIZE_X", 5, ev_float}, {"VF_SIZE_Y", 6, ev_float}, {"VF_VIEWPORT", 7, ev_vector, ev_vector}, {"VF_FOV", 8, ev_vector}, {"VF_FOVX", 9, ev_float}, {"VF_FOVY", 10, ev_float}, {"VF_ORIGIN", 11, ev_vector}, {"VF_ORIGIN_X", 12, ev_float}, {"VF_ORIGIN_Y", 13, ev_float}, {"VF_ORIGIN_Z", 14, ev_float}, {"VF_ANGLES", 15, ev_vector}, {"VF_ANGLES_X", 16, ev_float}, {"VF_ANGLES_Y", 17, ev_float}, {"VF_ANGLES_Z", 18, ev_float}, {"VF_DRAWWORLD", 19, ev_float}, {"VF_ENGINESBAR", 20, ev_float}, {"VF_DRAWCROSSHAIR", 21, ev_float}, // {"VF_CARTESIAN_ANGLES", 22, ev_vector}, {"VF_MINDIST", 23, ev_float}, {"VF_MAXDIST", 24, ev_float}, {"VF_CL_VIEWANGLES_V", 33, ev_vector}, {"VF_CL_VIEWANGLES_X", 34, ev_float}, {"VF_CL_VIEWANGLES_X", 35, ev_float}, {"VF_CL_VIEWANGLES_X", 36, ev_float}, {"VF_PERSPECTIVE", 200, ev_float}, // {"VF_DP_CLEARSCENE", 201, ev_float}, {"VF_ACTIVESEAT", 202, ev_float, ev_float}, {"VF_AFOV", 203, ev_float}, // {"VF_SCREENVSIZE", 204, ev_vector}, // {"VF_SCREENPSIZE", 205, ev_vector}, {"VF_VIEWENTITY", 206, ev_float}, // {"VF_STATSENTITY", 207, ev_float}, // {"VF_SCREENVOFFSET", 208, ev_float}, {"VF_RT_SOURCECOLOUR", 209, ev_string}, {"VF_RT_DEPTH", 210, ev_string, ev_float, ev_vector}, {"VF_RT_RIPPLE", 211, ev_string, ev_float, ev_vector}, {"VF_RT_DESTCOLOUR0", 212, ev_string, ev_float, ev_vector}, {"VF_RT_DESTCOLOUR1", 213, ev_string, ev_float, ev_vector}, {"VF_RT_DESTCOLOUR2", 214, ev_string, ev_float, ev_vector}, {"VF_RT_DESTCOLOUR3", 215, ev_string, ev_float, ev_vector}, {"VF_RT_DESTCOLOUR4", 216, ev_string, ev_float, ev_vector}, {"VF_RT_DESTCOLOUR5", 217, ev_string, ev_float, ev_vector}, {"VF_RT_DESTCOLOUR6", 218, ev_string, ev_float, ev_vector}, {"VF_RT_DESTCOLOUR7", 219, ev_string, ev_float, ev_vector}, {"VF_ENVMAP", 220, ev_string}, {"VF_USERDATA", 221, ev_pointer, ev_uint}, {"VF_SKYROOM_CAMERA", 222, ev_vector}, // {"VF_PIXELPSCALE", 223, ev_vector}, {"VF_PROJECTIONOFFSET", 224, ev_vector}, {"VF_VRBASEORIENTATION",225, ev_vector, ev_vector}, {"VF_DP_MAINVIEW", 400, ev_float}, // {"VF_DP_MINFPS_QUALITY", 401, ev_float}, }; char temp[256]; const QCC_eval_t *ev; int i, vf; if (!argcount) return; // o.O ev = QCC_SRef_EvalConst(arglist[0]->base); if (!ev) //can't check variables. return; vf = ev->_float; if (!qccwarningaction[WARN_ARGUMENTCHECK]) return; //don't bother if its not relevant anyway. for (i = 0; i < sizeof(argtypes)/sizeof(argtypes[0]); i++) { if (argtypes[i].n == vf) { if (argcount >= 2 && argtypes[i].t1 != ((arglist[1]->cast->type==ev_boolean)?arglist[1]->cast->parentclass->type:arglist[1]->cast->type)) { QCC_PR_ParseWarning(WARN_ARGUMENTCHECK, "%s(%s, ...): expected %s, got %s", funcname, argtypes[i].name, basictypenames[argtypes[i].t1], TypeName(arglist[1]->cast, temp, sizeof(temp))); return; } if (argcount >= 3 && argtypes[i].t2 != ((arglist[2]->cast->type==ev_boolean)?arglist[2]->cast->parentclass->type:arglist[2]->cast->type)) { QCC_PR_ParseWarning(WARN_ARGUMENTCHECK, "%s(%s, X, ...): expected %s, got %s", funcname, argtypes[i].name, basictypenames[argtypes[i].t2], TypeName(arglist[2]->cast, temp, sizeof(temp))); return; } if (argcount >= 4 && argtypes[i].t3 != ((arglist[3]->cast->type==ev_boolean)?arglist[3]->cast->parentclass->type:arglist[3]->cast->type)) { QCC_PR_ParseWarning(WARN_ARGUMENTCHECK, "%s(%s, X, Y, ...): expected %s, got %s", funcname, argtypes[i].name, basictypenames[argtypes[i].t3], TypeName(arglist[3]->cast, temp, sizeof(temp))); return; } return; } } QCC_PR_ParseWarning(WARN_ARGUMENTCHECK, "%s: unknown argument %i", funcname, vf); } void QCC_VerifyArgs_cvar(const char *funcname, QCC_ref_t *cvarname) { if (cvarname->type == REF_GLOBAL && cvarname->cast->type == ev_string) { const QCC_eval_t *a = QCC_SRef_EvalConst(cvarname->base); const char *str; if (!a) return; str = strings + a->string; if (!strcmp(str, "vid_conwidth") || !strcmp(str, "vid_conheight")) QCC_PR_ParseWarning(WARN_ARGUMENTCHECK, "%s: cvar(\"%s\") is deprecated and is likely to give some hacky value to work around old API usage which does not necessarily reflect the actual cvar value. Use getviewprop(VF_SCREENVSIZE) for the screen's virtual use, or use ftos+cvar_string to read the actual value of the cvar, or cast to variant to mute this warning.", funcname, str); } } void QCC_VerifyArgs_MatchingFieldType(const char *funcname, QCC_ref_t *type, QCC_ref_t *fldref) { if (type->type == REF_GLOBAL && fldref->cast->type == ev_field) { char temp[256]; const QCC_eval_t *a = QCC_SRef_EvalConst(type->base); if (a) { int i; if (type->cast->type == ev_integer || type->cast->type == ev_uint) i = a->_int; else if (type->cast->type == ev_float) i = a->_float; else return; if (i != fldref->cast->aux_type->type && fldref->cast->aux_type->type != ev_variant) { if (i >= 0 && i < ev_variant) QCC_PR_ParseWarning(WARN_ARGUMENTCHECK, "%s: indicated type ev_%s does not match passed field type .%s", funcname, basictypenames[i], TypeName(fldref->cast->aux_type, temp, sizeof(temp))); else QCC_PR_ParseWarning(WARN_ARGUMENTCHECK, "%s: indicated type %i is not a basic type", funcname, i); } } } } #ifdef SUPPORTINLINE struct inlinectx_s { QCC_def_t *fdef; QCC_function_t *func; QCC_sref_t arglist[8]; pbool argisout[8]; QCC_sref_t result; struct { QCC_def_t *def; QCC_def_t *srcsym; int bias; } locals[64]; int numlocals; const char *error; }; static pbool QCC_PR_InlinePushResult(struct inlinectx_s *ctx, QCC_sref_t src/*original statement's symbol*/, QCC_sref_t mappedto/*effective value*/) { QCC_def_t *local; int i, p; for (i = 0; i < ctx->numlocals; i++) { if (ctx->locals[i].srcsym == src.sym) break; } if (i == ctx->numlocals) { if (ctx->numlocals >= sizeof(ctx->locals)/sizeof(ctx->locals[0])) { ctx->error = "too many temps"; return false; } for (local = ctx->func->firstlocal, p = 0; local && p < MAX_PARMS && (unsigned int)p < ctx->func->type->num_parms; local = local->deftail->nextlocal, p++) { if (src.sym->symbolheader == local) { if (ctx->argisout[p]) { /*if (ctx->arglist[p].sym->symbolheader != mappedto.sym || ctx->arglist[p].ofs != mappedto.ofs) { // ctx->error = "assignment wrote to variable other than intended output."; return false; }*/ return true; } } } ctx->locals[i].srcsym = src.sym; ctx->numlocals++; } else if (ctx->locals[i].def) QCC_FreeDef(ctx->locals[i].def); ctx->locals[i].def = mappedto.sym; ctx->locals[i].bias = mappedto.ofs - src.ofs; //FIXME: this feels unsafe (needed for array[immediate] fixups) return true; } static QCC_sref_t QCC_PR_InlineFindDef(struct inlinectx_s *ctx, QCC_sref_t src, pbool assign) { QCC_def_t *d; int p; // int pstart = ctx->func->parm_start; //aliases are weird and annoying if (src.sym && src.sym->generatedfor) src.sym = src.sym->generatedfor; for (p = 0; p < ctx->numlocals; p++) { if (ctx->locals[p].srcsym == src.sym && ctx->locals[p].def) { d = ctx->locals[p].def; if (assign && src.sym) { if (!(src.sym->localscope || src.sym->temp)) { //update the symbol to refer to its original value... // QCC_FreeDef(ctx->locals[p].def); ctx->locals[p].def = ctx->locals[p].srcsym; ctx->locals[p].bias = 0; return QCC_MakeSRefForce(src.sym, src.ofs, src.cast); } //substitute the assignment with a new temp // QCC_FreeDef(ctx->locals[p].def); ctx->locals[p].srcsym = src.sym; ctx->locals[p].def = QCC_GetTemp(src.sym->type).sym; ctx->locals[p].bias = 0; return QCC_MakeSRefForce(ctx->locals[p].def, src.ofs, src.cast); } return QCC_MakeSRefForce(d, src.ofs + ctx->locals[p].bias, src.cast); } } if (src.sym && (src.sym->localscope || src.sym->temp)) { //if its a parm, use that QCC_def_t *local; for (local = ctx->func->firstlocal, p = 0; local && p < MAX_PARMS && (unsigned int)p < ctx->func->type->num_parms; local = local->deftail->nextlocal, p++) { if (src.sym->symbolheader == local) { if (assign && !ctx->argisout[p]) { // QCC_FreeDef(ctx->locals[p].def); ctx->locals[p].srcsym = src.sym; ctx->locals[p].def = QCC_GetTemp(src.sym->type).sym; ctx->locals[p].bias = 0; return QCC_MakeSRefForce(ctx->locals[p].def, src.ofs, src.cast); } return QCC_MakeSRefForce(ctx->arglist[p].sym->symbolheader, ctx->arglist[p].ofs+src.ofs, src.cast); } } //otherwise its a local or a temp. if (ctx->numlocals >= sizeof(ctx->locals)/sizeof(ctx->locals[0])) return nullsref; ctx->locals[ctx->numlocals].srcsym = src.sym; ctx->locals[ctx->numlocals].def = QCC_GetTemp(src.sym->type).sym; ctx->locals[ctx->numlocals].bias = 0; return QCC_MakeSRefForce(ctx->locals[ctx->numlocals++].def, src.ofs, src.cast); } return QCC_MakeSRefForce(src.sym, src.ofs, src.cast); /* if (ofs < RESERVED_OFS) { if (ofs == OFS_RETURN) { def_ret.type = type_void; return &def_ret; } ofs -= OFS_PARM0; ofs /= 3; def_parms[ofs].type = type_void; return &def_parms[ofs]; } if (ofs < pstart || ofs >= pstart+ctx->func->locals) { for (d = &pr.def_head; d; d = d->next) { if (d->ofs == ofs) return d; } return NULL; //not found? } for (p = 0; p < ctx->func->numparms; pstart += ctx->func->parm_size[p++]) { if (ofs < pstart+ctx->func->parm_size[p]) return ctx->arglist[p]; } if (ctx->numlocals >= sizeof(ctx->locals)/sizeof(ctx->locals[0])) return NULL; ctx->locals[ctx->numlocals].srcofs = ofs; ctx->locals[ctx->numlocals].def = QCC_GetTemp(type_float); return ctx->locals[ctx->numlocals++].def; */ } //returns a string saying why inlining failed. static const char *QCC_PR_InlineStatements(struct inlinectx_s *ctx) { /*FIXME: what happens with: t = foo; foo = 5; return t; */ QCC_sref_t a, b, c; QCC_statement_t *st, *est; const QCC_eval_t *eval; // float af,bf; // int i; if (ctx->func->statements) { st = ctx->func->statements; est = st + ctx->func->numstatements; } else { st = &statements[ctx->func->code]; est = statements+numstatements; } while(st < est) { switch(st->op) { case OP_IF_F: case OP_IFNOT_F: case OP_IF_I: case OP_IFNOT_I: case OP_IF_S: case OP_IFNOT_S: if (st->b.ofs > 0 && st[st->b.jumpofs].op == OP_DONE) { //logically, an if statement around the entire function is safe because the locals are safe } case OP_GOTO: case OP_SWITCH_F: case OP_SWITCH_I: case OP_SWITCH_E: case OP_SWITCH_FNC: case OP_SWITCH_S: case OP_SWITCH_V: return "function contains branches"; //conditionals are not supported in any way. this keeps the code linear. each input can only come from a single place. /* case OP_CALL0: case OP_CALL1: case OP_CALL2: case OP_CALL3: case OP_CALL4: case OP_CALL5: case OP_CALL6: case OP_CALL7: case OP_CALL8: case OP_CALL1H: case OP_CALL2H: case OP_CALL3H: case OP_CALL4H: case OP_CALL5H: case OP_CALL6H: case OP_CALL7H: case OP_CALL8H: return "function contains function calls"; //conditionals are not supported in any way. this keeps the code linear. each input can only come from a single place. */ case OP_RETURN: case OP_DONE: a = QCC_PR_InlineFindDef(ctx, st->a, false); ctx->result = a; if (!a.cast) { if (ctx->func->type->aux_type->type == ev_void) ctx->result.cast = type_void; else return "OP_RETURN no a"; } return NULL; case OP_BOUNDCHECK: a = QCC_PR_InlineFindDef(ctx, st->a, false); QCC_PR_InlinePushResult(ctx, st->a, a); eval = QCC_SRef_EvalConst(a); if (eval) { if (eval->_int < st->c.jumpofs || eval->_int >= st->b.jumpofs) QCC_PR_ParseWarning(0, "constant value exceeds bounds failed bounds check while inlining"); } else QCC_PR_SimpleStatement(&pr_opcodes[OP_BOUNDCHECK], a, st->b, st->c, false); break; default: if ((st->op >= OP_CALL0 && st->op <= OP_CALL8) || (st->op >= OP_CALL1H && st->op <= OP_CALL8H)) { //function calls are a little weird in that they have no outputs if (st->a.cast) { a = QCC_PR_InlineFindDef(ctx, st->a, false); if (!a.cast) return "unable to determine what a was"; } else a = nullsref; if (st->b.cast) { b = QCC_PR_InlineFindDef(ctx, st->b, false); if (!b.cast) return "unable to determine what a was"; } else b = nullsref; if (st->c.cast) { c = QCC_PR_InlineFindDef(ctx, st->c, false); if (!c.cast) return "unable to determine what a was"; } else c = nullsref; { QCC_sref_t r; r.sym = &def_ret; r.ofs = 0; r.cast = a.cast; QCC_PR_InlinePushResult(ctx, r, nullsref); } QCC_ClobberDef(&def_ret); QCC_FreeTemp(a); QCC_FreeTemp(b); QCC_FreeTemp(c); QCC_LockActiveTemps(a); QCC_PR_SimpleStatement(&pr_opcodes[st->op], a, b, c, false); { QCC_sref_t r; r.sym = &def_ret; r.ofs = 0; r.cast = a.cast; QCC_PR_InlinePushResult(ctx, r, QCC_GetAliasTemp(QCC_MakeSRefForce(&def_ret, 0, a.cast->aux_type))); } } else if (pr_opcodes[st->op].flags & (OPF_STOREFLD|OPF_STOREPTROFS)) { //these forms don't write to any actual globals, we've no real scope for optimising these out. a = QCC_PR_InlineFindDef(ctx, st->a, false); b = QCC_PR_InlineFindDef(ctx, st->b, false); c = QCC_PR_InlineFindDef(ctx, st->c, false); QCC_PR_SimpleStatement(&pr_opcodes[st->op], a, b, c, false); } else if (pr_opcodes[st->op].associative == ASSOC_RIGHT) { //a->b if (st->a.cast) { a = QCC_PR_InlineFindDef(ctx, st->a, false); if (!a.cast) return "unable to determine what a was"; } else a = nullsref; b = QCC_PR_InlineFindDef(ctx, st->b, !(pr_opcodes[st->op].flags & OPF_STOREPTR)); c = QCC_PR_StatementFlags(&pr_opcodes[st->op], a, b, NULL, 0); if (!QCC_PR_InlinePushResult(ctx, st->b, c)) return ctx->error; } else if (OpAssignsToC(st->op)) { //a+b->c if (st->a.cast) { a = QCC_PR_InlineFindDef(ctx, st->a, false); if (!a.cast) return "unable to determine what a was"; } else a = nullsref; if (st->b.cast) { b = QCC_PR_InlineFindDef(ctx, st->b, false); if (!b.cast) return "unable to determine what b was"; } else b = nullsref; if (pr_opcodes[st->op].associative == ASSOC_LEFT && pr_opcodes[st->op].type_c != &type_void) { QCC_sref_t r; c = QCC_PR_InlineFindDef(ctx, st->c, true); r = QCC_PR_StatementFlags(&pr_opcodes[st->op], a, b, NULL, STFL_NOEMULATE); if (c.cast && !QCC_SRef_EvalConst(r)) c = QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_ENT], r, c, NULL, STFL_NOEMULATE); else { QCC_FreeTemp(c); c = r; } if (!QCC_PR_InlinePushResult(ctx, st->c, c)) return ctx->error; } else { if (st->c.cast) { c = QCC_PR_InlineFindDef(ctx, st->c, false); if (!c.cast) return "unable to determine what c was"; } else c = nullsref; QCC_PR_SimpleStatement(&pr_opcodes[st->op], a, b, c, false); } } else { return "nonstandard opcode form"; } break; } st++; } a = QCC_PR_InlineFindDef(ctx, nullsref, false); ctx->result = a; if (!a.cast) { if (ctx->func->type->aux_type->type == ev_void) ctx->result.cast = type_void; else return "missing return type"; } return NULL; } #endif static QCC_sref_t QCC_PR_Inline(QCC_sref_t fdef, QCC_ref_t **arglist, unsigned int argcount) { #ifndef SUPPORTINLINE return nullsref; #else // QCC_def_t *dd = NULL; struct inlinectx_s ctx; const char *error; int statements, i; unsigned int a; const QCC_eval_t *eval = QCC_SRef_EvalConst(fdef); //make sure that its a function type and that there's no special weirdness if (!eval || eval->function < 0 || argcount > 8 || eval->function >= numfunctions || fdef.sym->arraysize != 0 || fdef.cast->type != ev_function || argcount != fdef.cast->num_parms || fdef.cast->vargs || fdef.cast->vargcount) { QCC_PR_ParseWarning(0, "Couldn't inline \"%s\": %s", fdef.sym->name, "inconsistent context"); return nullsref; } ctx.func = &functions[eval->function]; if (fdef.cast != ctx.func->type) { QCC_PR_ParseWarning(0, "Couldn't inline \"%s\": %s", ctx.func->name, "function was cast"); return nullsref; } ctx.numlocals = 0; for (a = 0; a < argcount; a++) { ctx.arglist[a] = QCC_RefToDef(arglist[a], true); ctx.argisout[a] = ctx.func->type->params[a].out; } ctx.fdef = fdef.sym; ctx.result = nullsref; ctx.error = NULL; if ((int)ctx.func->code <= 0) { char *fname = ctx.func->name; if (argcount == 1) { const QCC_eval_t *eval = QCC_SRef_EvalConst(ctx.arglist[0]); if (eval && !strcmp(fname, "sin")) return QCC_MakeFloatConst(sin(eval->_float)); if (eval && !strcmp(fname, "cos")) return QCC_MakeFloatConst(cos(eval->_float)); if (eval && !strcmp(fname, "floor")) return QCC_MakeFloatConst(floor(eval->_float)); if (eval && !strcmp(fname, "ceil")) return QCC_MakeFloatConst(ceil(eval->_float)); if (eval && !strcmp(fname, "rint")) return QCC_MakeFloatConst((int)((eval->_float>0)?(eval->_float+0.5):(eval->_float-0.5))); if (eval && !strcmp(fname, "fabs")) return QCC_MakeFloatConst(fabs(eval->_float)); if (eval && !strcmp(fname, "sqrt")) return QCC_MakeFloatConst(sqrt(eval->_float)); if (eval && !strcmp(fname, "log")) return QCC_MakeFloatConst(log(eval->_float)); if (eval && !strcmp(fname, "log10")) return QCC_MakeFloatConst(log10(eval->_float)); if (eval && !strcmp(fname, "ftoi")) return QCC_MakeIntConst(eval->_float); if (eval && !strcmp(fname, "itof")) return QCC_MakeFloatConst(eval->_int); } else if (argcount == 2) { const QCC_eval_t *a1 = QCC_SRef_EvalConst(ctx.arglist[0]); const QCC_eval_t *a2 = QCC_SRef_EvalConst(ctx.arglist[1]); if (a1 && a2 && !strcmp(fname, "pow")) return QCC_MakeFloatConst(pow(a1->_float, a2->_float)); } return nullsref; //don't try to inline builtins. that simply cannot work. } //FIXME: inefficient: we can't revert this on failure, so make sure its done early, just in case. if (argcount && ctx.arglist[0].sym->generatedfor == &def_ret) QCC_ClobberDef(&def_ret); QCC_ClobberDef(NULL); statements = numstatements; error = QCC_PR_InlineStatements(&ctx); if (!error) error = ctx.error; if (error) { QCC_PR_ParseWarning(0, "Couldn't inline \"%s\": %s", ctx.func->name, error); QCC_PR_ParsePrintDef(0, fdef.sym); } for(i = 0; i < ctx.numlocals; i++) { if (ctx.locals[i].def) QCC_FreeDef(ctx.locals[i].def); } if (!ctx.result.cast) QCC_UngenerateStatements(statements); else { //on success, make sure the args were freed while (argcount-->0) QCC_FreeTemp(ctx.arglist[argcount]); } return ctx.result; #endif } pbool QCC_Intrinsic_strlen(QCC_sref_t *result, const QCC_eval_t *a) { const char *str = &strings[a->string]; size_t l = 0; for (l = 0; str[l]; l++) { //don't shortcut it when its an extended char. we don't know what the engine's strlen function would return. if ((unsigned char)str[l] & 0x80) return false; } if (l > 1u<<24) //wait, what? return false; //too big for a float. *result = QCC_MakeFloatConst(l); return true; } QCC_sref_t QCC_PR_GenerateFunctionCallRef (QCC_sref_t newself, QCC_sref_t func, QCC_ref_t **arglist, unsigned int argcount) //warning, the func could have no name set if it's a field call. { QCC_sref_t d, oself, self, retval; unsigned int i; QCC_type_t *t; // int np; int callconvention; QCC_statement_t *st; unsigned int parm; struct { QCC_sref_t ref; int firststatement; } args[MAX_PARMS+MAX_EXTRA_PARMS]; QCC_sref_t bigret = nullsref; const char *funcname = QCC_GetSRefName(func); if (opt_constantarithmatic && argcount == 1 && arglist[0]->type == REF_GLOBAL) { const QCC_eval_t *a = QCC_SRef_EvalConst(arglist[0]->base); if (a) { // if (!strcmp(funcname, "isnan")) // return QCC_MakeFloatConst(isnan(a->_float)); // if (!strcmp(funcname, "isinf")) // return QCC_MakeFloatConst(isinf(a->_float)); if (!strcmp(funcname, "floor")) return QCC_MakeFloatConst(floor(a->_float)); if (!strcmp(funcname, "ceil")) return QCC_MakeFloatConst(ceil(a->_float)); if (!strcmp(funcname, "sin")) return QCC_MakeFloatConst(sin(a->_float)); if (!strcmp(funcname, "cos")) return QCC_MakeFloatConst(cos(a->_float)); if (!strcmp(funcname, "log")) return QCC_MakeFloatConst(log(a->_float)); if (!strcmp(funcname, "sqrt")) return QCC_MakeFloatConst(sqrt(a->_float)); // if (!strcmp(funcname, "ftos")) // return QCC_MakeStringConst(ftos(a->_float)); //engines differ too much in their ftos implementation for this to be worthwhile if (!strcmp(funcname, "strlen") && QCC_Intrinsic_strlen(&d, a)) return d; if (!strcmp(funcname, "stof")) return QCC_MakeFloatConst(atof(&strings[a->string])); } } else if (opt_constantarithmatic && argcount == 2 && arglist[0]->type == REF_GLOBAL && arglist[1]->type == REF_GLOBAL) { const QCC_eval_t *a = QCC_SRef_EvalConst(arglist[0]->base); const QCC_eval_t *b = QCC_SRef_EvalConst(arglist[1]->base); if (a && b) { if (arglist[0]->cast == type_float && arglist[1]->cast == type_float) { if (!strcmp(funcname, "pow")) return QCC_MakeFloatConst(pow(a->_float, b->_float)); if (!strcmp(funcname, "mod")) return QCC_MakeFloatConst(fmodf((int)a->_float, (int)b->_float)); if (!strcmp(funcname, "min")) return QCC_MakeFloatConst(min(a->_float, b->_float)); if (!strcmp(funcname, "max")) return QCC_MakeFloatConst(max(a->_float, b->_float)); if (!strcmp(funcname, "bitshift")) { if (b->_float < 0) return QCC_MakeFloatConst((int)a->_float >> (int)-b->_float); else return QCC_MakeFloatConst((int)a->_float << (int)b->_float); } } } } if (!strcmp(funcname, "sprintf")) QCC_VerifyFormatString(funcname, arglist, argcount); if (!strcmp(funcname, "cvar") && argcount == 1) QCC_VerifyArgs_cvar(funcname, arglist[0]); if ((!strcmp(funcname, "clientstat")||!strcmp(funcname, "addstat")) && argcount == 3) QCC_VerifyArgs_MatchingFieldType(funcname, arglist[1], arglist[2]); if (!strcmp(funcname, "setviewprop") || !strcmp(funcname, "setproperty")) QCC_VerifyArgs_setviewprop(funcname, arglist, argcount); if (!strcmp(funcname, "sendevent")) //void(string eventname, string argtypes, ...) QCC_VerifyArgs_sendevent(funcname, arglist, argcount); func.sym->timescalled++; if (!newself.cast && func.sym->constant && func.sym->allowinline) { d = QCC_PR_Inline(func, arglist, argcount); if (d.cast) { optres_inlines++; func.sym->referenced = true; //not really, but hey, the warning is stupid. QCC_FreeTemp(func); return d; } } if (QCC_OPCodeValid(&pr_opcodes[OP_CALL1H])) callconvention = OP_CALL1H; //FTE extended else callconvention = OP_CALL1; //standard t = func.cast; if (t->type == ev_function) { if (t->aux_type->size > type_vector->size) bigret = QCC_GetTemp(QCC_PR_PointerType(t->aux_type)); } else if (t->type != ev_variant) { //all varg QCC_PR_ParseErrorPrintSRef (ERR_NOTAFUNCTION, func, "not a function"); } self = nullsref; oself = nullsref; d = nullsref; if (newself.cast) { //we're entering OO code with a different self. make sure self is preserved. //eg: other.touch(self) self = QCC_PR_GetSRef(type_entity, "self", NULL, true, 0, false); if (newself.ofs != self.ofs || newself.sym != self.sym) { oself = QCC_GetTemp(pr_classtype?pr_classtype:type_entity); //oself = self QCC_PR_SimpleStatement(&pr_opcodes[OP_STORE_ENT], self, oself, nullsref, false); //self = other QCC_PR_SimpleStatement(&pr_opcodes[OP_STORE_ENT], newself, self, nullsref, false); //if the args refered to self, update them to refer to oself instead //(as self is now set to 'other') for (i = 0; i < argcount; i++) { if (arglist[i]->base.ofs == self.ofs && arglist[i]->base.sym && arglist[i]->base.sym->symbolheader == self.sym->symbolheader) { QCC_FreeTemp(arglist[i]->base); arglist[i]->base = oself; QCC_UnFreeTemp(arglist[i]->base); } if (arglist[i]->index.ofs == self.ofs && arglist[i]->index.sym && arglist[i]->index.sym->symbolheader == self.sym->symbolheader) { QCC_FreeTemp(arglist[i]->index); arglist[i]->index = oself; QCC_UnFreeTemp(arglist[i]->index); } } } else { QCC_FreeTemp(self); self = nullsref; } QCC_FreeTemp(newself); } // write the arguments into temps parm = 0; for (i = 0; i < argcount; i++) { if (func.cast->type == ev_function && func.cast->params && i < func.cast->num_parms && func.cast->params[i].out == 2) { args[parm].firststatement = numstatements; args[parm++].ref = nullsref; //__out args do not actually need to pass anything. } else if (callconvention == OP_CALL1H && parm < 2 && arglist[i]->cast->size <= 3) { args[parm].firststatement = numstatements; args[parm++].ref = QCC_RefToDef(arglist[i], func.cast->type != ev_function || !func.cast->params || i >= func.cast->num_parms || !func.cast->params[i].out); } else { int firststatement; QCC_sref_t sref = nullsref, copyop_index = nullsref; int copyop[3] = {0,0,0}, copyop_idx=0; if (arglist[i]->postinc || arglist[i]->cast->align!=32) { arglist[i]->base = QCC_RefToDef(arglist[i], true); arglist[i]->index = nullsref; arglist[i]->type = REF_GLOBAL; arglist[i]->postinc = false; arglist[i]->readonly = true; } switch(arglist[i]->type) { case REF_GLOBAL: case REF_ARRAY: if (!arglist[i]->index.cast || QCC_SRef_EvalConst(arglist[i]->index)) break; //no problem if (arglist[i]->cast->align != 32) QCC_PR_ParseWarning (ERR_INTERNAL, "QCC_PR_GenerateFunctionCallRef: REF_ARRAY: align not 32"); if (QCC_OPCodeValid(&pr_opcodes[OP_LOADA_F])) { copyop[2] = OP_LOADA_V; copyop[1] = OP_LOADA_I64; copyop[0] = OP_LOADA_F; copyop_idx = -2; //offset the base ref copyop_index = arglist[i]->index; copyop_index = QCC_SupplyConversion(copyop_index, ev_integer, true); sref = arglist[i]->base; } else if (arglist[i]->base.sym->arraylengthprefix && QCC_OPCodeValid(&pr_opcodes[OP_FETCH_GBL_F])) { copyop[2] = 0; copyop[1] = 0; copyop[0] = OP_FETCH_GBL_F; copyop_idx = OP_ADD_F; copyop_index = arglist[i]->index; sref = arglist[i]->base; } break; case REF_FIELD: if (arglist[i]->cast->align != 32) QCC_PR_ParseWarning (ERR_INTERNAL, "QCC_PR_GenerateFunctionCallRef: REF_FIELD: align not 32"); copyop[2] = OP_LOAD_V; copyop[1] = OP_LOAD_I64; copyop[0] = OP_LOAD_F; copyop_index = arglist[i]->index; copyop_idx = -1; if (!QCC_SRef_EvalConst(copyop_index)) { //if its a variable then its probably not a proper field (which has extra field ref values following it). do the shitty thing and make assumptions about ordering. :( copyop_index.cast = type_integer; copyop_idx = OP_ADD_I; } sref = arglist[i]->base; break; case REF_POINTER: if (arglist[i]->cast->align == 8) { copyop[2] = 0; copyop[1] = 0; copyop[0] = (arglist[i]->cast->type==ev_bitfld&&arglist[i]->cast->parentclass==type_integer)?OP_LOADP_I8:OP_LOADP_U8; } else if (arglist[i]->cast->align == 16) { copyop[2] = 0; copyop[1] = 0; copyop[0] = (arglist[i]->cast->type==ev_bitfld&&arglist[i]->cast->parentclass==type_integer)?OP_LOADP_I16:OP_LOADP_U16; } else { copyop[2] = OP_LOADP_V; copyop[1] = OP_LOADP_I64; copyop[0] = OP_LOADP_F; } copyop_idx = OP_ADD_I; copyop_index = arglist[i]->index; if (!copyop_index.cast) copyop_index = QCC_MakeUIntConst(0); //don't bug out! sref = arglist[i]->base; break; default: //warning:reftodef may be doing a copy for various ref types. we're then copying it into the args as an extra step. this is obviously wasteful. //reading pointer, field, or struct types needs special code here to avoid that copy. break; } firststatement = numstatements; if (!sref.cast) { sref = QCC_RefToDef(arglist[i], func.cast->type != ev_function || !func.cast->params || i >= func.cast->num_parms || !func.cast->params[i].out); copyop[2] = OP_STORE_V; copyop[1] = OP_STORE_I64; copyop[0] = OP_STORE_F; copyop_idx = 0; } else if (!(func.cast->type != ev_function || !func.cast->params || i >= func.cast->num_parms || !func.cast->params[i].out)) { QCC_UnFreeTemp(sref); QCC_UnFreeTemp(copyop_index); } if (copyop_index.cast || arglist[i]->cast->size > 3) { unsigned int ofs; QCC_sref_t src, fparm, newindex; int asz; src = sref; if (copyop_idx == OP_ADD_I && copyop_index.cast) copyop_index = QCC_SupplyConversion(copyop_index, ev_integer, true); else if (copyop_idx == OP_ADD_F && copyop_index.cast) copyop_index = QCC_SupplyConversion(copyop_index, ev_float, true); for (ofs = 0; ofs < arglist[i]->cast->size; ) { if (copyop_idx == -1) { //with this mode, the base reference can just be updated. no mess with the index. newindex = copyop_index; newindex.ofs += ofs; QCC_UnFreeTemp(copyop_index); } else if (copyop_idx == -2) { //with this mode, the base reference can just be updated. no mess with the index. newindex = copyop_index; QCC_UnFreeTemp(copyop_index); } else if (copyop_idx == OP_ADD_I) { if (!copyop_index.cast) newindex = QCC_MakeIntConst(ofs); //index can be simple constants else { //if the index is a constant, then things get a little easier const QCC_eval_t *cnst = QCC_SRef_EvalConst(copyop_index); if (cnst) newindex = QCC_MakeIntConst(ofs + cnst->_int); else if (ofs) //okay, looks like we'll have to actually do some maths then newindex = QCC_PR_StatementFlags (&pr_opcodes[OP_ADD_I], copyop_index, QCC_MakeIntConst(ofs), NULL, STFL_PRESERVEA); else //first index needs no offset. { newindex = copyop_index; QCC_UnFreeTemp(copyop_index); } } } else if (copyop_idx == OP_ADD_F) { if (!copyop_index.cast) newindex = QCC_MakeFloatConst(ofs); //index can be simple constants else { //if the index is a constant, then things get a little easier const QCC_eval_t *cnst = QCC_SRef_EvalConst(copyop_index); if (cnst) newindex = QCC_MakeFloatConst(ofs + cnst->_float); else if (ofs) //okay, looks like we'll have to actually do some maths then newindex = QCC_PR_StatementFlags (&pr_opcodes[OP_ADD_F], copyop_index, QCC_MakeFloatConst(ofs), NULL, STFL_PRESERVEA); else //first index needs no offset. { newindex = copyop_index; QCC_UnFreeTemp(copyop_index); } } } else newindex = nullsref; asz = 3-(ofs%3); if (ofs+asz > arglist[i]->cast->size) asz = arglist[i]->cast->size-ofs; while (asz > 3 || !copyop[asz-1] || (asz>1&&!QCC_OPCodeValid(&pr_opcodes[copyop[asz-1]]))) asz--; //can't do that size... if (copyop[0] == OP_STORE_F) { if (ofs+asz != arglist[i]->cast->size) QCC_UnFreeTemp(src); if (!(ofs%3)) { args[parm].firststatement = numstatements; args[parm].ref = QCC_GetTemp(type_vector); QCC_FreeTemp(QCC_PR_StatementFlags (&pr_opcodes[copyop[asz-1]], src, args[parm].ref, NULL, STFL_PRESERVEB)); parm++; } else { QCC_sref_t t = args[parm-1].ref; t.ofs += ofs%3; QCC_FreeTemp(QCC_PR_StatementFlags (&pr_opcodes[copyop[asz-1]], src, t, NULL, STFL_PRESERVEB)); } } else { if (ofs%3) parm--; if (parm>=MAX_PARMS) { fparm = extra_parms[parm - MAX_PARMS]; if (!fparm.cast) { char name[128]; QC_snprintfz(name, sizeof(name), "$parm%u", parm); fparm = extra_parms[parm - MAX_PARMS] = QCC_PR_GetSRef(type_vector, name, NULL, true, 0, GDF_STRIP); } else QCC_ForceUnFreeDef(fparm.sym); } else { fparm.sym = &def_parms[parm]; fparm.cast = type_vector; QCC_ForceUnFreeDef(fparm.sym); } fparm.ofs = ofs%3; if (!fparm.ofs) { args[parm].firststatement = numstatements; args[parm].ref = fparm; } parm++; if (ofs+asz == arglist[i]->cast->size) { QCC_FreeTemp(src); QCC_FreeTemp(copyop_index); } QCC_FreeTemp(newindex); if (copyop_idx == -2) { //with this mode, the base reference can just be updated. no mess with the index. src.ofs += ofs; QCC_PR_SimpleStatement(&pr_opcodes[copyop[asz-1]], src, newindex, fparm, false); src.ofs -= ofs; } else QCC_PR_SimpleStatement(&pr_opcodes[copyop[asz-1]], src, newindex, fparm, false); } ofs += asz; if (copyop_idx == 0) src.ofs += asz; } } else { //small and simple. yay. args[parm].firststatement = firststatement; args[parm].ref = sref; parm++; } } } //remap those temps to the actual parms if the parms were not used in the interim. for (i = ((callconvention==OP_CALL1H)?2:0); i < parm; i++) { if (i>=MAX_PARMS) { if (i - MAX_PARMS >= MAX_EXTRA_PARMS) QCC_PR_ParseErrorPrintSRef (ERR_TOOMANYTOTALPARAMETERS, func, "Function call needs too much paramater storage"); d = extra_parms[i - MAX_PARMS]; if (!d.cast) { char name[128]; QC_snprintfz(name, sizeof(name), "$parm%u", i); d = extra_parms[i - MAX_PARMS] = QCC_PR_GetSRef(type_vector, name, NULL, true, 0, GDF_STRIP); QCC_FreeTemp(d); } } else { d.sym = &def_parms[i]; d.ofs = 0; d.cast = type_vector; } d.cast = args[i].ref.cast; if (!d.cast) continue; if (QCC_RemapTemp(args[i].firststatement, numstatements, args[i].ref, d)) { QCC_FreeTemp(args[i].ref); } else { if (args[i].ref.sym != d.sym || args[i].ref.ofs != d.ofs) { QCC_ForceUnFreeDef(d.sym); #if 0 QCC_StoreToSRef(d, args[i].ref, d.cast, false, false); #else if (args[i].ref.cast->size == 3) QCC_FreeTemp(QCC_PR_StatementFlags (&pr_opcodes[OP_STORE_V], args[i].ref, d, NULL, 0)); else if (args[i].ref.cast->size == 2) QCC_FreeTemp(QCC_PR_StatementFlags (&pr_opcodes[QCC_OPCodeValid(&pr_opcodes[OP_STORE_I64])?OP_STORE_I64:OP_STORE_V], args[i].ref, d, NULL, 0)); else if (args[i].ref.cast->size == 1) QCC_FreeTemp(QCC_PR_StatementFlags (&pr_opcodes[OP_STORE_F], args[i].ref, d, NULL, 0)); else QCC_PR_ParseErrorPrintSRef (ERR_BADEXTENSION, func, "arg storage not 1, 2, or 3."); #endif } else QCC_FreeTemp(args[i].ref); } args[i].ref = d; } if (func.cast->vargcount) { QCC_sref_t va_passcount = QCC_PR_GetSRef(type_float, "__va_count", NULL, true, 0, 0); QCC_FreeTemp(QCC_PR_StatementFlags (&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(argcount), va_passcount, NULL, 0)); } //free any references to ofs_ret if we know we're not going to make any calls that will clobber it. //ie: func(func()); should not try to preserve ofs_return because of it being in use as part of the nested call if (callconvention == OP_CALL1H) { for (i = 0; i < parm && i < 2; i++) if (args[i].ref.sym && args[i].ref.sym->generatedfor == &def_ret && args[i].ref.sym->refcount == 1) { QCC_FreeTemp(args[i].ref); args[i].ref.sym = &def_ret; QCC_ForceUnFreeDef(args[i].ref.sym); break; } } QCC_ClobberDef(&def_ret); /*can free temps used for arguments now*/ if (callconvention == OP_CALL1H) { for (i = 0; i < parm && i < 2; i++) { if (!args[i].ref.cast) continue; args[i].ref.sym->referenced=true; QCC_FreeTemp(args[i].ref); } } //we dont need to lock the local containing the function index because its thrown away after the call anyway //(if a function is called in the argument list then it'll be locked as part of that call) QCC_LockActiveTemps(func); //any temps before are likly to be used with the return value. if (bigret.cast) //get some storage { QCC_PR_SimpleStatement(&pr_opcodes[OP_PUSH], QCC_MakeUIntConst(bigret.cast->aux_type->size), nullsref, bigret, false); //get some cheap/auto storage the child can safely write to. QCC_PR_SimpleStatement(QCC_OPCodeValid(&pr_opcodes[OP_STORE_P])?&pr_opcodes[OP_STORE_P]:&pr_opcodes[OP_STORE_F], bigret, QCC_MakeSRefForce(&def_ret, 0, bigret.cast), nullsref, false); //let the child know where to write. } //generate the call if (parm>MAX_PARMS) QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[callconvention-1+MAX_PARMS], func, nullsref, (QCC_statement_t **)&st)); else if (parm) QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[callconvention-1+parm], func, nullsref, (QCC_statement_t **)&st)); else QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_CALL0], func, nullsref, (QCC_statement_t **)&st)); if (callconvention == OP_CALL1H) { if (parm) { st->b = args[0].ref; if (parm>1) st->c = args[1].ref; } } if (bigret.cast) //get some storage { QCC_ref_t refbuf, *r; r = QCC_PR_BuildRef(&refbuf, REF_POINTER, bigret, nullsref, bigret.cast->aux_type, false, 0); retval = QCC_RefToDef(r, true); //this line sucks perf //QCC_PR_SimpleStatement(&pr_opcodes[OP_POP], QCC_MakeUIntConst(bigret.cast->aux_type->size), nullsref, bigret, false); //should really be part of the qcc_ref_t } else if (t->type == ev_variant) retval = QCC_GetAliasTemp(QCC_MakeSRefForce(&def_ret, 0, type_variant)); else retval = QCC_GetAliasTemp(QCC_MakeSRefForce(&def_ret, 0, t->aux_type)); //restore the class owner if (oself.cast) QCC_PR_SimpleStatement(&pr_opcodes[OP_STORE_ENT], oself, self, nullsref, false); //handle outs. if (func.cast->type == ev_function && func.cast->params) { unsigned int parm = 0; for (i = 0; i < argcount && i < func.cast->num_parms; i++) //fixme: parm offset should be (deftype->size+2)/3 { if (func.cast->params[i].out) { if (oself.cast) { //gah, this is messy if (arglist[i]->base.ofs == oself.ofs && arglist[i]->base.sym == oself.sym) { QCC_UnFreeTemp(self); QCC_FreeTemp(arglist[i]->base); arglist[i]->base = self; } if (arglist[i]->index.ofs == oself.ofs && arglist[i]->index.sym == oself.sym) { QCC_UnFreeTemp(self); QCC_FreeTemp(arglist[i]->index); arglist[i]->index = self; } } if (arglist[i]->readonly) { QCC_PR_ParseWarning(ERR_TYPEMISMATCHPARM, "Unable to write to read-only out argument"); continue; } if (parm>=MAX_PARMS) { d = extra_parms[parm - MAX_PARMS]; if (!d.cast) { char name[128]; QC_snprintfz(name, sizeof(name), "$parm%u", parm); d = extra_parms[parm - MAX_PARMS] = QCC_PR_GetSRef(type_vector, name, NULL, true, 0, GDF_STRIP); } else QCC_ForceUnFreeDef(d.sym); } else { d.sym = &def_parms[parm]; d.ofs = 0; d.cast = type_vector; QCC_ForceUnFreeDef(d.sym); } d.cast = arglist[i]->cast; //FIXME: this may need to generate function calls, which can potentially clobber parms. This would be bad. we may need to copy them all out first THEN do the assignments. //FIXME: this can't cope with splitting return values over different extra_parms. QCC_StoreSRefToRef(arglist[i], d, false, false); } parm += (func.cast->params[i].type->size+2)/3; } } QCC_FreeTemp(oself); QCC_FreeTemp(self); return retval; } QCC_sref_t QCC_PR_GenerateFunctionCallSref (QCC_sref_t newself, QCC_sref_t func, QCC_sref_t *arglist, int argcount) { QCC_ref_t arg[MAX_PARMS]; QCC_ref_t *outlist[MAX_PARMS]; int i; for (i = 0; i < argcount; i++) { memset(&arg[i], 0, sizeof(arg[i])); arg[i].type = REF_GLOBAL; arg[i].base = arglist[i]; arg[i].cast = arglist[i].cast; outlist[i] = &arg[i]; } return QCC_PR_GenerateFunctionCallRef(newself, func, outlist, argcount); } QCC_sref_t QCC_PR_GenerateFunctionCall3 (QCC_sref_t newself, QCC_sref_t func, QCC_sref_t a, QCC_type_t *type_a, QCC_sref_t b, QCC_type_t *type_b, QCC_sref_t c, QCC_type_t *type_c) { QCC_ref_t arg_a = {REF_GLOBAL}; QCC_ref_t arg_b = {REF_GLOBAL}; QCC_ref_t arg_c = {REF_GLOBAL}; QCC_ref_t *arglist[3] = {&arg_a, &arg_b, &arg_c}; arg_a.base = a; arg_a.cast = type_a?type_a:a.cast; arg_b.base = b; arg_b.cast = type_b?type_b:b.cast; arg_c.base = c; arg_c.cast = type_c?type_c:c.cast; return QCC_PR_GenerateFunctionCallRef(newself, func, arglist, 3); } QCC_sref_t QCC_PR_GenerateFunctionCall2 (QCC_sref_t newself, QCC_sref_t func, QCC_sref_t a, QCC_type_t *type_a, QCC_sref_t b, QCC_type_t *type_b) { QCC_ref_t arg_a = {REF_GLOBAL}; QCC_ref_t arg_b = {REF_GLOBAL}; QCC_ref_t *arglist[2] = {&arg_a, &arg_b}; arg_a.base = a; arg_a.cast = type_a?type_a:a.cast; arg_b.base = b; arg_b.cast = type_b?type_b:b.cast; return QCC_PR_GenerateFunctionCallRef(newself, func, arglist, 2); } QCC_sref_t QCC_PR_GenerateFunctionCall1 (QCC_sref_t newself, QCC_sref_t func, QCC_sref_t a, QCC_type_t *type_a) { QCC_ref_t arg_a = {REF_GLOBAL}; QCC_ref_t *arglist[1] = {&arg_a}; arg_a.base = a; arg_a.cast = type_a?type_a:a.cast; return QCC_PR_GenerateFunctionCallRef(newself, func, arglist, 1); } /* ============ PR_ParseFunctionCall ============ */ static QCC_sref_t QCC_PR_ParseFunctionCall (QCC_ref_t *funcref) //warning, the func could have no name set if it's a field call. { QCC_sref_t newself, func; QCC_sref_t e, d, out; unsigned int arg; QCC_type_t *t, *p; int extraparms=false; unsigned int np; const char *funcname, *value; QCC_ref_t *param[MAX_PARMS+MAX_EXTRA_PARMS]; QCC_ref_t parambuf[MAX_PARMS+MAX_EXTRA_PARMS]; if (funcref->type == REF_FIELD && strstr(QCC_GetSRefName(funcref->index), "::")) { newself = funcref->base; QCC_UnFreeTemp(newself); func = QCC_RefToDef(funcref, true); } else if (funcref->type == REF_NONVIRTUAL) { newself = funcref->index; QCC_UnFreeTemp(newself); func = QCC_RefToDef(funcref, true); } else { newself = nullsref; func = QCC_RefToDef(funcref, true); } func.sym->timescalled++; t = func.cast; if (t->type == ev_variant) { t->aux_type = type_variant; } if (t->type != ev_function && t->type != ev_variant) { QCC_PR_ParseErrorPrintSRef (ERR_NOTAFUNCTION, func, "not a function"); } funcname = QCC_GetSRefName(func); if (!newself.cast && !t->num_parms&&t->type != ev_variant) //intrinsics. These base functions have variable arguments. I would check for (...) args too, but that might be used for extended builtin functionality. (this code wouldn't compile otherwise) { if (!strcmp(funcname, "alloca")) { //FIXME: half of these functions with known arguments should be handled later or something QCC_sref_t sz, ret; func.sym->unused = true; func.sym->referenced = true; QCC_FreeTemp(func); sz = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); QCC_PR_Expect(")"); sz = QCC_SupplyConversion(sz, ev_integer, true); //result = push_words((sz+3)/4); sz = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], sz, QCC_MakeIntConst(3), NULL); sz = QCC_PR_Statement(&pr_opcodes[OP_DIV_I], sz, QCC_MakeIntConst(4), NULL); QCC_FreeTemp(sz); ret = QCC_GetTemp(QCC_PointerTypeTo(type_void)); QCC_PR_SimpleStatement(&pr_opcodes[OP_PUSH], sz, nullsref, ret, false); //push *(int*)&a elements return ret; } if (!strcmp(funcname, "_")) { char *comment = pr_token_precomment; func.sym->unused = true; func.sym->referenced = true; QCC_FreeTemp(func); if (pr_token_type == tt_immediate && pr_immediate_type->type == ev_string) { d = QCC_MakeTranslateStringConst(pr_immediate_string); d.sym->comment = comment; QCC_PR_Lex(); if (!d.sym->comment) d.sym->comment = pr_token_precomment; if (QCC_PR_CheckTokenComment (")", &d.sym->comment)) return d; } else { QCC_PR_ParseErrorPrintSRef (ERR_TYPEMISMATCHPARM, func, "_() intrinsic accepts only a string immediate"); d = nullsref; } QCC_PR_Expect(")"); return d; } if (!strcmp(funcname, "va_arg") || !strcmp(funcname, "...")) //second for compat with gmqcc { QCC_sref_t va_list; QCC_sref_t idx; QCC_type_t *type; va_list = QCC_PR_GetSRef(type_vector, "__va_list", pr_scope, false, 0, 0); idx = QCC_PR_Expression (TOP_PRIORITY, EXPR_DISALLOW_COMMA); if (idx.cast->type == ev_float) idx = QCC_PR_Statement(&pr_opcodes[OP_MUL_F], idx, QCC_MakeFloatConst(3), NULL); else idx = QCC_PR_Statement(&pr_opcodes[OP_MUL_I], QCC_SupplyConversion(idx, ev_integer, true), QCC_MakeIntConst(3), NULL); QCC_PR_Expect(","); type = QCC_PR_ParseType(false, false, false); QCC_PR_Expect(")"); if (!va_list.cast || !va_list.sym || !va_list.sym->arraysize) QCC_PR_ParseError (ERR_TYPEMISMATCHPARM, "va_arg() intrinsic only works inside varadic functions"); func.sym->unused = true; func.sym->referenced = true; QCC_FreeTemp(func); return QCC_LoadFromArray(va_list, idx, type, false); } if (!strcmp(funcname, "random")) { func.sym->unused = true; func.sym->referenced = true; QCC_FreeTemp(func); if (!QCC_PR_CheckToken(")")) { e = QCC_PR_Expression (TOP_PRIORITY, EXPR_DISALLOW_COMMA); e = QCC_SupplyConversion(e, ev_float, true); if (e.cast->type != ev_float) QCC_PR_ParseErrorPrintSRef (ERR_TYPEMISMATCHPARM, func, "type mismatch on parm %i", 1); if (!QCC_PR_CheckToken(")")) { QCC_PR_Expect(","); d = QCC_PR_Expression (TOP_PRIORITY, EXPR_DISALLOW_COMMA); d = QCC_SupplyConversion(d, ev_float, true); if (d.cast->type != ev_float) QCC_PR_ParseErrorPrintSRef (ERR_TYPEMISMATCHPARM, func, "type mismatch on parm %i", 2); QCC_PR_Expect(")"); } else d = nullsref; } else { e = nullsref; d = nullsref; } if (QCC_OPCodeValid(&pr_opcodes[OP_RAND0])) { if(qcc_targetformat != QCF_HEXEN2 && qcc_targetformat != QCF_UHEXEN2) out = QCC_GetTemp(type_float); else { //hexen2 requires the output be def_ret QCC_ClobberDef(&def_ret); out = nullsref; } if (e.cast) { if (d.cast) { QCC_PR_SimpleStatement(&pr_opcodes[OP_RAND2], e, d, out, false); QCC_FreeTemp(d); } else QCC_PR_SimpleStatement(&pr_opcodes[OP_RAND1], e, nullsref, out, false); QCC_FreeTemp(e); } else QCC_PR_SimpleStatement(&pr_opcodes[OP_RAND0], nullsref, nullsref, out, false); if (!out.cast) out = QCC_GetAliasTemp(QCC_MakeSRefForce(&def_ret, 0, type_float)); } else { QCC_ClobberDef(&def_ret); //this is normally a builtin, so don't bother locking temps. QCC_PR_SimpleStatement(&pr_opcodes[OP_CALL0], func, nullsref, nullsref, false); out = QCC_GetAliasTemp(QCC_MakeSRefForce(&def_ret, 0, type_float)); if (d.cast) { QCC_sref_t t; //min + (max-min)*random() t = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_F], d, e, NULL, STFL_PRESERVEB); out = QCC_PR_Statement(&pr_opcodes[OP_MUL_F], out, t, NULL); out = QCC_PR_Statement(&pr_opcodes[OP_ADD_F], out, e, NULL); } else if (e.cast) out = QCC_PR_Statement(&pr_opcodes[OP_MUL_F], out, e, NULL); } return out; } if (!strcmp(funcname, "randomv")) { func.sym->unused = true; func.sym->referenced=true; QCC_FreeTemp(func); if (!QCC_PR_CheckToken(")")) { e = QCC_PR_Expression (TOP_PRIORITY, EXPR_DISALLOW_COMMA); if (e.cast->type != ev_vector) QCC_PR_ParseErrorPrintSRef (ERR_TYPEMISMATCHPARM, func, "type mismatch on parm %i", 1); if (!QCC_PR_CheckToken(")")) { QCC_PR_Expect(","); d = QCC_PR_Expression (TOP_PRIORITY, EXPR_DISALLOW_COMMA); if (d.cast->type != ev_vector) QCC_PR_ParseErrorPrintSRef (ERR_TYPEMISMATCHPARM, func, "type mismatch on parm %i", 2); QCC_PR_Expect(")"); } else d = nullsref; } else { e = nullsref; d = nullsref; } if (QCC_OPCodeValid(&pr_opcodes[OP_RANDV0])) { if(qcc_targetformat != QCF_HEXEN2 && qcc_targetformat != QCF_UHEXEN2) out = QCC_GetTemp(type_vector); else { //hexen2 requires the output be def_ret QCC_ClobberDef(&def_ret); out = nullsref; } if (e.cast) { if (d.cast) { QCC_PR_SimpleStatement(&pr_opcodes[OP_RANDV2], e, d, out, false); QCC_FreeTemp(d); } else QCC_PR_SimpleStatement(&pr_opcodes[OP_RANDV1], e, nullsref, out, false); QCC_FreeTemp(e); } else QCC_PR_SimpleStatement(&pr_opcodes[OP_RANDV0], nullsref, nullsref, out, false); if (!out.cast) out = QCC_GetAliasTemp(QCC_MakeSRefForce(&def_ret, 0, type_vector)); } else { QCC_sref_t x,y,z; QCC_sref_t min = nullsref; QCC_sref_t scale = nullsref; if (d.cast) { min = e; scale = QCC_PR_StatementFlags(&pr_opcodes[OP_SUB_V], d, min, NULL, STFL_PRESERVEB); } else if (e.cast) scale = e; QCC_ClobberDef(&def_ret); out = QCC_GetAliasTemp(QCC_MakeSRefForce(&def_ret, 0, type_vector)); x = out; x.cast = type_float; y = x; y.ofs += 1; z = y; z.ofs += 1; QCC_PR_SimpleStatement(&pr_opcodes[OP_CALL0], func, nullsref, nullsref, false); if (scale.cast) { scale.cast = type_float; scale.ofs += 2; QCC_PR_SimpleStatement(&pr_opcodes[OP_MUL_F], x, scale, z, false); scale.ofs--; } else QCC_PR_SimpleStatement(&pr_opcodes[OP_STORE_F], x, z, nullsref, false); QCC_PR_SimpleStatement(&pr_opcodes[OP_CALL0], func, nullsref, nullsref, false); if (scale.cast) { QCC_PR_SimpleStatement(&pr_opcodes[OP_MUL_F], x, scale, y, false); scale.ofs--; } else QCC_PR_SimpleStatement(&pr_opcodes[OP_STORE_F], x, y, nullsref, false); QCC_PR_SimpleStatement(&pr_opcodes[OP_CALL0], func, nullsref, nullsref, false); if (scale.cast) { QCC_PR_SimpleStatement(&pr_opcodes[OP_MUL_F], x, scale, x, false); scale.sym->referenced = true; scale.ofs--; scale.cast = type_vector; QCC_FreeTemp(scale); } if (min.cast) out = QCC_PR_Statement(&pr_opcodes[OP_ADD_V], out, min, NULL); } return out; } else if (!strcmp(funcname, "spawn")) { //foo_c e = spawn(foo_c, fld:val, fld:val); //regular spawn... //foo_c e = spawn(existingent, foo_c, fld:val, fld:val); //placement-spawn... QCC_sref_t result = nullsref; QCC_type_t *rettype; /* ret = spawn(); ret.FOO* = FOO*; result.(classcall)spawnfunc_foo(); return result; this mechanism means entities can be spawned easily via maps. */ if (!QCC_PR_CheckToken(")")) { rettype = QCC_PR_ParseType(false, true, false); if (!rettype) { result = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); QCC_PR_Expect(","); rettype = QCC_PR_ParseType(false, true, false); } //FIXME: C++'s Placement New syntax: obj *p= new(ptr) obj(); if (!rettype || rettype->type != ev_entity) QCC_PR_ParseError(ERR_NOTANAME, "Spawn operator with undefined class: %s", QCC_PR_ParseName()); } else rettype = NULL; //default, corrected to entity later if (!result.cast) { //ret = spawn() result = QCC_PR_GenerateFunctionCallRef(nullsref, func, NULL, 0); } if (rettype) { char genfunc[256]; //do field assignments. while(QCC_PR_CheckToken(",")) { QCC_sref_t f, p, v; f = QCC_PR_ParseValue(rettype, false, false, true); if (f.cast->type != ev_field) QCC_PR_ParseError(0, "Named field is not a field."); if (QCC_PR_CheckToken("=")) //allow : or = as a separator, but throw a warning for = QCC_PR_ParseWarning(0, "That = should be a :"); //rejecting = helps avoid qcc bugs. :P else QCC_PR_Expect(":"); v = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); p = QCC_PR_StatementFlags(&pr_opcodes[OP_ADDRESS], result, f, NULL, STFL_PRESERVEA); if (v.cast->size == 3) QCC_FreeTemp(QCC_PR_Statement(&pr_opcodes[OP_STOREP_V], v, p, NULL)); else QCC_FreeTemp(QCC_PR_Statement(&pr_opcodes[OP_STOREP_F], v, p, NULL)); } QCC_PR_Expect(")"); QC_snprintfz(genfunc, sizeof(genfunc), "spawnfunc_%s", rettype->name); func = QCC_PR_GetSRef(type_function, genfunc, NULL, true, 0, GDF_CONST); func.sym->referenced = true; QCC_UnFreeTemp(result); QCC_FreeTemp(QCC_PR_GenerateFunctionCallRef(result, func, NULL, 0)); result.cast = rettype; } return result; } else if (!strcmp(funcname, "used_sound")) { e = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); QCC_PR_Expect(")"); if ((value=QCC_SRef_EvalStringConst(e))) QCC_SoundUsed(value); else QCC_PR_ParseWarning(ERR_BADIMMEDIATETYPE, "Argument to used_sound intrinsic was not a string immediate."); return e; } else if (!strcmp(funcname, "used_model")) { e = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); QCC_PR_Expect(")"); if ((value=QCC_SRef_EvalStringConst(e))) QCC_SetModel(value); else QCC_PR_ParseWarning(ERR_BADIMMEDIATETYPE, "Argument to used_model intrinsic was not a string immediate."); return e; } else if (!strcmp(funcname, "autocvar") && !QCC_PR_CheckToken(")")) { char autocvarname[256]; char *desc = NULL; QCC_FreeTemp(func); QC_snprintfz(autocvarname, sizeof(autocvarname), "autocvar_%s", QCC_PR_ParseName()); QCC_PR_Expect(","); e = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); if (QCC_PR_CheckToken(",")) { if (pr_token_type == tt_immediate && pr_immediate_type->type == ev_string) { //okay, its a string immediate, consume it for the description, being careful to not generate any string immediate defs (which still require an entry in the string table). desc = qccHunkAlloc(strlen(pr_immediate_string)+1); strcpy(desc, pr_immediate_string); QCC_PR_Lex (); } } QCC_PR_Expect(")"); d = QCC_PR_GetSRef(e.cast, autocvarname, NULL, true, 0, GDF_USED); if (!d.sym->comment) d.sym->comment = desc; if (!e.sym->constant) QCC_PR_ParseWarning(ERR_BADIMMEDIATETYPE, "autocvar default value is not constant"); if (d.sym->initialized) { if (memcmp(QCC_SRef_Data(d), QCC_SRef_Data(e), d.sym->symbolsize*sizeof(int))) QCC_PR_ParseErrorPrintSRef (ERR_REDECLARATION, d, "autocvar %s was already initialised with another value", autocvarname+9); } else { memcpy(QCC_SRef_Data(d), QCC_SRef_Data(e), d.sym->symbolsize*sizeof(int)); d.sym->initialized = true; } QCC_FreeTemp(e); return d; } else if (!strcmp(funcname, "entnum") && !QCC_PR_CheckToken(")")) { //t = (a/%1) / (nextent(world)/%1) //a/%1 does a (int)entity to float conversion type thing func.sym->unused = true; func.sym->referenced = true; QCC_FreeTemp(func); e = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); QCC_PR_Expect(")"); e = QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_I], e, QCC_MakeIntConst(1), NULL, 0); d = QCC_PR_EmulationFunc(nextent); if (!d.cast) QCC_PR_ParseError(0, "the nextent builtin is not defined"); QCC_UnFreeTemp(e); d = QCC_PR_GenerateFunctionCall1 (nullsref, d, e, type_entity); d = QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_I], d, QCC_MakeIntConst(1), NULL, 0); e = QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_I], e, d, NULL, 0); return e; } } //so it's not an intrinsic. else if (!newself.cast && t->num_parms == 1 && t->type == ev_function) { if (!strcmp(funcname, "checkbuiltin")) { pr_ignoredeprecation = true; param[0] = QCC_PR_RefExpression (¶mbuf[0], TOP_PRIORITY, EXPR_DISALLOW_COMMA); pr_ignoredeprecation = false; QCC_PR_Expect(")"); if (param[0]->type == REF_GLOBAL && param[0]->cast == param[0]->base.sym->type && !param[0]->base.sym->arraysize) { e.ofs = param[0]->base.ofs; e.cast = param[0]->cast; e.sym = QCC_PR_DummyDef(e.cast, param[0]->base.sym->name, pr_scope, 0, param[0]->base.sym, 0, true, GDF_ALIAS|GDF_STRIP); e = QCC_PR_GenerateFunctionCallSref(newself, func, &e, 1); } else e = QCC_PR_GenerateFunctionCallRef(newself, func, param, 1); return e; } } if (opt_precache_file) //should we strip out all precache_file calls? { if (!newself.cast && !strncmp(funcname,"precache_file", 13)) { if (pr_token_type == tt_immediate && pr_immediate_type->type == ev_string && pr_scope && !strcmp(pr_scope->name, "main")) { optres_precache_file += strlen(pr_immediate_string); QCC_PR_Lex(); QCC_PR_Expect(")"); QCC_PrecacheFile (pr_immediate_string, funcname[13]); QCC_FreeTemp(func); QCC_FreeTemp(newself); return QCC_MakeFloatConst(0); } } } // copy the arguments to the global parameter variables arg = 0; if (t->type == ev_variant) { extraparms = true; np = 0; } else if (t->vargs) { extraparms = true; np = t->num_parms; } else np = t->num_parms; //any temps referenced to build the parameters don't need to be locked. if (!QCC_PR_CheckToken(")")) { QCC_ref_t *e; do { if (arg >= t->num_parms) p = NULL; else p = t->params[arg].type; if (arg >= MAX_PARMS+MAX_EXTRA_PARMS) QCC_PR_ParseErrorPrintSRef (ERR_TOOMANYTOTALPARAMETERS, func, "More than %i parameters", MAX_PARMS+MAX_EXTRA_PARMS); if (QCC_PR_CheckToken("#")) { QCC_sref_t sr = QCC_MakeSRefForce(&def_parms[arg], 0, p?p:type_variant); // sr.sym = &def_parms[arg]; // sr.ofs = 0; // sr.cast = p?p:type_variant; e = QCC_PR_BuildRef(¶mbuf[arg], REF_GLOBAL, sr, nullsref, p?p:type_variant, true, 0); } else if (arg < t->num_parms && (QCC_PR_PeekToken (",") || QCC_PR_PeekToken (")"))) { if (!func.cast->params[arg].defltvalue.cast) QCC_PR_ParseErrorPrintSRef (ERR_NOTDEFINED, func, "Default value not specified for implicit argument %i", arg+1); e = QCC_DefToRef(¶mbuf[arg], func.cast->params[arg].defltvalue); } else e = QCC_PR_RefExpression(¶mbuf[arg], TOP_PRIORITY, EXPR_DISALLOW_COMMA); if (extraparms && arg >= MAX_PARMS && !t->vargcount) { //vararg builtins cannot accept more than 8 args. they can't tell if they got more, and wouldn't know where to read them. QCC_PR_ParseWarning (WARN_TOOMANYPARAMETERSVARARGS, "More than %i parameters on varargs function", MAX_PARMS); QCC_PR_ParsePrintSRef(WARN_TOOMANYPARAMETERSVARARGS, func); } else if (!extraparms && arg >= t->num_parms && !p) { char buf[256]; QCC_PR_ParseWarning (WARN_TOOMANYPARAMETERSFORFUNC, "too many parameters on call to %s, argument %s will be ignored", funcname, QCC_GetRefName(e, buf, sizeof(buf))); QCC_PR_ParsePrintSRef(WARN_TOOMANYPARAMETERSFORFUNC, func); } //with vectorcalls, we store the vector into the args as individual floats //this allows better reuse of vector constants. //the immediate vector def will be discarded while linking, if its still unused. if (opt_vectorcalls && e->cast == type_vector && e->type == REF_GLOBAL && !e->postinc && e->readonly) { const QCC_eval_t *eval = QCC_SRef_EvalConst(e->base); if (eval) { QCC_sref_t t = QCC_GetTemp(type_vector); t.cast = type_float; t.ofs = 0; QCC_FreeTemp(QCC_PR_StatementFlags (&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(eval->vector[0]), t, NULL, STFL_PRESERVEB)); t.ofs = 1; QCC_FreeTemp(QCC_PR_StatementFlags (&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(eval->vector[1]), t, NULL, STFL_PRESERVEB)); t.ofs = 2; QCC_FreeTemp(QCC_PR_StatementFlags (&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(eval->vector[2]), t, NULL, STFL_PRESERVEB)); t.ofs = 0; QCC_FreeTemp(e->base); e = QCC_PR_BuildRef(¶mbuf[arg], REF_GLOBAL, t, nullsref, type_vector, true, 0); } } if (!p && e->cast->type == ev_float && t->vargtodouble) e = QCC_PR_BuildRef(¶mbuf[arg], REF_GLOBAL, QCC_EvaluateCast(QCC_RefToDef(e, true), type_double, true), nullsref, p, true, 0);//C promotes floats to double on variadic functions, for some reason. else if (p && typecmp(e->cast, p)) { e = QCC_PR_BuildRef(¶mbuf[arg], REF_GLOBAL, QCC_EvaluateCast(QCC_RefToDef(e, true), p, true), nullsref, p, true, 0); } else if (QCC_RefNeedsCalls(e)) { e = QCC_PR_BuildRef(¶mbuf[arg], REF_GLOBAL, QCC_RefToDef(e, true), nullsref, p, true, 0); } param[arg] = e; if (arg == 0) { // save information for model and sound caching if (!strncmp(funcname,"precache_", 9) && e->cast->type == ev_string && e->base.cast->type == ev_string && e->type == REF_GLOBAL) { const QCC_eval_t *eval = QCC_SRef_EvalConst(e->base); if (eval) { const char *value = &strings[eval->string]; if (!strncmp(funcname+9,"sound", 5)) QCC_PrecacheSound (value, funcname[14]); else if (!strncmp(funcname+9,"model", 5)) QCC_PrecacheModel (value, funcname[14]); else if (!strncmp(funcname+9,"texture", 7)) QCC_PrecacheTexture (value, funcname[16]); else if (!strncmp(funcname+9,"file", 4)) QCC_PrecacheFile (value, funcname[13]); } } } if (arg == 1 && e->cast->type == ev_string && e->type == REF_GLOBAL && !STRCMP(funcname, "setmodel") ) { const QCC_eval_t *eval = QCC_SRef_EvalConst(e->base); if (eval) { const char *value = &strings[eval->string]; QCC_SetModel(value); } } if (arg == 1 && e->cast->type == ev_string && e->type == REF_GLOBAL && !STRCMP(funcname, "localsound") ) { const QCC_eval_t *eval = QCC_SRef_EvalConst(e->base); if (eval) { const char *value = &strings[eval->string]; QCC_SoundUsed(value); } } if (arg == 2 && e->cast->type == ev_string && e->type == REF_GLOBAL && !STRCMP(funcname, "sound")) { const QCC_eval_t *eval = QCC_SRef_EvalConst(e->base); if (eval) { const char *value = &strings[eval->string]; QCC_SoundUsed(value); } } arg++; } while (QCC_PR_CheckToken (",")); QCC_PR_Expect (")"); } //don't warn if we omited optional arguments while (arg < np && func.cast->params[arg].defltvalue.cast && !func.cast->params[arg].optional) { QCC_ForceUnFreeDef(func.cast->params[arg].defltvalue.sym); param[arg] = QCC_DefToRef(¶mbuf[arg], func.cast->params[arg].defltvalue); arg++; } if (arg < np && func.cast->params[arg].optional) np = arg; if (arg < np) { /*if (arg+1==np && !strcmp(QCC_GetSRefName(func), "makestatic")) { //vanilla QC sucks. I want fteextensions.qc to compile with vanilla, yet also result in errors for when the mod fucks up. QCC_PR_ParseWarning (WARN_COMPATIBILITYHACK, "too few parameters on call to %s. Passing 'self'.", QCC_GetSRefName(func)); QCC_PR_ParsePrintSRef (WARN_COMPATIBILITYHACK, func); param[arg] = QCC_PR_GetSRef(NULL, "self", NULL, 0, 0, false); arg++; } else if (arg+1==np && !strcmp(QCC_GetSRefName(func), "ai_charge")) { //vanilla QC sucks. I want fteextensions.qc to compile with vanilla, yet also result in errors for when the mod fucks up. QCC_PR_ParseWarning (WARN_COMPATIBILITYHACK, "too few parameters on call to %s. Passing 0.", QCC_GetSRefName(func)); QCC_PR_ParsePrintSRef (WARN_COMPATIBILITYHACK, func); param[arg] = QCC_MakeFloatConst(0); arg++; } else*/ { if (func.cast->params[arg].paramname) QCC_PR_ParseWarning (WARN_TOOFEWPARAMS, "too few parameters on call to %s, %s will be UNDEFINED", QCC_GetSRefName(func), func.cast->params[arg].paramname); else QCC_PR_ParseWarning (WARN_TOOFEWPARAMS, "too few parameters on call to %s", QCC_GetSRefName(func)); QCC_PR_ParsePrintSRef (WARN_TOOFEWPARAMS, func); } } return QCC_PR_GenerateFunctionCallRef(newself, func, param, arg); } //returns a usable sref_t, always increases the def's refcount even if the def is not live yet. should only be used when a term is created/named. //this special distinction allows temp reuse to be caught/debugged more reliably. QCC_sref_t QCC_MakeSRefForce(QCC_def_t *def, unsigned int ofs, QCC_type_t *type) { QCC_sref_t sr; sr.sym = def; sr.ofs = ofs; sr.cast = type; if (def) QCC_ForceUnFreeDef(def); return sr; } //makes a sref from a def+ofs+type. also increases refcount. considered an error if the specified def is not currently live. QCC_sref_t QCC_MakeSRef(QCC_def_t *def, unsigned int ofs, QCC_type_t *type) { QCC_sref_t sr; sr.sym = def; sr.ofs = ofs; sr.cast = type; if (def) QCC_UnFreeTemp(sr); return sr; } //int constchecks; //int varchecks; //int typechecks; extern hashtable_t floatconstdefstable; static QCC_sref_t QCC_Make32bitConst(QCC_type_t *type, puint_t value) //none of these types may be mutilated by the engine (allowing 0s to merge) { QCC_def_t *cn; unsigned int key = value; cn = Hash_GetKey(&floatconstdefstable, key); while (cn) { if (cn->type->size == type->size) if (((QCC_eval_t*)cn->symboldata)->_uint == value) return QCC_MakeSRefForce(cn, 0, type); cn = Hash_GetNextKey(&floatconstdefstable, key, cn); } // allocate a new one cn = (void *)qccHunkAlloc (sizeof(QCC_def_t) + sizeof(value)); cn->next = NULL; pr.def_tail->next = cn; pr.def_tail = cn; cn->type = type; cn->name = "IMMEDIATE"; cn->constant = true; cn->initialized = 1; cn->scope = NULL; // always share immediates cn->arraysize = 0; cn->referenced = true; cn->ofs = 0; cn->symbolheader = cn; cn->symbolsize = cn->type->size; cn->symboldata = (QCC_eval_basic_t*)(cn+1); ((QCC_eval_t*)cn->symboldata)->_uint = value; Hash_AddKey(&floatconstdefstable, key, cn, qccHunkAlloc(sizeof(bucket_t))); return QCC_MakeSRefForce(cn, 0, type); } static QCC_sref_t QCC_Make64bitConst(QCC_type_t *type, puint64_t value) //all values MUST be word-swapped, for big-endian machines to byteswap their 64bit immediates, or something. { QCC_def_t *cn; unsigned int key = value ^ (value>>32); cn = Hash_GetKey(&floatconstdefstable, key); while (cn) { if (cn->type->size == type->size) if (((QCC_eval_t*)cn->symboldata)->u64 == value) return QCC_MakeSRefForce(cn, 0, type); cn = Hash_GetNextKey(&floatconstdefstable, key, cn); } // allocate a new one cn = (void *)qccHunkAlloc (sizeof(QCC_def_t) + sizeof(value)); cn->next = NULL; pr.def_tail->next = cn; pr.def_tail = cn; cn->type = type; cn->name = "IMMEDIATE"; cn->constant = true; cn->initialized = 1; cn->scope = NULL; // always share immediates cn->arraysize = 0; cn->ofs = 0; cn->symbolheader = cn; cn->symbolsize = cn->type->size; cn->symboldata = (QCC_eval_basic_t*)(cn+1); ((QCC_eval_t*)cn->symboldata)->u64 = value; Hash_AddKey(&floatconstdefstable, key, cn, qccHunkAlloc(sizeof(bucket_t))); return QCC_MakeSRefForce(cn, 0, type); } static QCC_sref_t QCC_Make96bitConst(QCC_type_t *type, puint_t *value) //basically just vectors... { QCC_def_t *cn; unsigned int key = value[0] ^ value[1] ^ value[2]; cn = Hash_GetKey(&floatconstdefstable, key); while (cn) { if (cn->type->size == type->size) if (((puint_t*)cn->symboldata)[0] == value[0] && ((puint_t*)cn->symboldata)[1] == value[1] && ((puint_t*)cn->symboldata)[2] == value[2]) return QCC_MakeSRefForce(cn, 0, type); cn = Hash_GetNextKey(&floatconstdefstable, key, cn); } // allocate a new one cn = (void *)qccHunkAlloc (sizeof(QCC_def_t) + sizeof(*value)*3); cn->next = NULL; pr.def_tail->next = cn; pr.def_tail = cn; cn->type = type; cn->name = "IMMEDIATE"; cn->constant = true; cn->initialized = 1; cn->scope = NULL; // always share immediates cn->arraysize = 0; cn->ofs = 0; cn->symbolheader = cn; cn->symbolsize = cn->type->size; cn->symboldata = (QCC_eval_basic_t*)(cn+1); ((puint_t*)cn->symboldata)[0] = value[0]; ((puint_t*)cn->symboldata)[1] = value[1]; ((puint_t*)cn->symboldata)[2] = value[2]; Hash_AddKey(&floatconstdefstable, key, cn, qccHunkAlloc(sizeof(bucket_t))); return QCC_MakeSRefForce(cn, 0, type); } QCC_sref_t QCC_MakeFloatConst(float value) { union { float d; puint_t i; } u = {value}; return QCC_Make32bitConst(type_float, u.i); } QCC_sref_t QCC_MakeIntConst(longlong llvalue) { pint_t value = llvalue; if (value != llvalue) QCC_PR_ParseWarning(WARN_OVERFLOW, "Constant int operand %llu will be truncated to %i", llvalue, value); return QCC_Make32bitConst(type_integer, value); } QCC_sref_t QCC_MakeUIntConst(unsigned longlong llvalue) { puint_t value = llvalue; if (value != llvalue) QCC_PR_ParseWarning(WARN_OVERFLOW, "Constant int operand %llu will be truncated to %i", llvalue, value); return QCC_Make32bitConst(type_uint, value); } QCC_sref_t QCC_MakeInt64Const(longlong llvalue) { pint64_t value = llvalue; if (value != llvalue) QCC_PR_ParseWarning(WARN_OVERFLOW, "Constant int operand %llu will be truncated to %"pPRIi64, llvalue, value); return QCC_Make64bitConst(type_int64, value); } QCC_sref_t QCC_MakeUInt64Const(unsigned longlong llvalue) { puint64_t value = llvalue; if (value != llvalue) QCC_PR_ParseWarning(WARN_OVERFLOW, "Constant int operand %llu will be truncated to %"pPRIu64, llvalue, value); return QCC_Make64bitConst(type_uint64, value); } QCC_sref_t QCC_MakeDoubleConst(double value) { union { double d; puint64_t i; } u = {value}; return QCC_Make64bitConst(type_double, u.i); } //immediates with no overlapping. this means aliases can be set up to mark them as strings/fields/functions and the engine can safely remap them as needed static QCC_sref_t QCC_MakeUniqueConst(QCC_type_t *type, void *data) { QCC_def_t *cn; // allocate a new one cn = (void *)qccHunkAlloc (sizeof(QCC_def_t) + sizeof(pint_t) * type->size); cn->next = NULL; pr.def_tail->next = cn; pr.def_tail = cn; cn->type = type; cn->name = "IMMEDIATE"; cn->constant = true; cn->initialized = 1; cn->scope = NULL; // always share immediates cn->arraysize = 0; cn->ofs = 0; cn->symbolheader = cn; cn->symbolsize = cn->type->size; cn->symboldata = (QCC_eval_basic_t*)(cn+1); memcpy(cn->symboldata, data, sizeof(pint_t) * type->size); return QCC_MakeSRefForce(cn, 0, type); } static QCC_sref_t QCC_MakeGAddress(QCC_type_t *type, QCC_def_t *relocof, int idx, int bitofs) { QCC_def_t *cn; if (relocof->temp) QCC_PR_ParseWarning(ERR_INTERNAL, "generating reloc of temp"); idx += bitofs>>5; bitofs &= 31; if (type->type == ev_pointer) { if (bitofs&7) QCC_PR_ParseWarning(ERR_INTERNAL, "pointer has too fine granularity"); idx = idx*VMWORDSIZE + (bitofs>>3); //fix up fte style pointer } else { if (bitofs&7) QCC_PR_ParseWarning(ERR_INTERNAL, "pointer has too fine granularity"); //sucky granularity } if (relocof->gaddress && !idx && relocof->gaddress->type->type == type->type) return QCC_MakeSRefForce(relocof->gaddress, 0, type); // allocate a new one cn = (void *)qccHunkAlloc (sizeof(QCC_def_t) + sizeof(pint_t) * type->size); cn->next = NULL; pr.def_tail->next = cn; pr.def_tail = cn; cn->type = type; cn->name = "IMMEDIATE"; cn->constant = true; cn->initialized = 0; //we don't know addresses until the end, which hurts folding. :( cn->scope = NULL; // always share immediates cn->arraysize = 0; cn->ofs = 0; cn->symbolheader = cn; cn->symbolsize = cn->type->size; cn->symboldata = (QCC_eval_basic_t*)(cn+1); cn->reloc = relocof; if (!idx && !bitofs) relocof->gaddress = cn; memset(cn->symboldata, 0, sizeof(pint_t) * type->size); cn->symboldata->_int = idx; return QCC_MakeSRefForce(cn, 0, type); } QCC_sref_t QCC_PR_GenerateVector(QCC_sref_t x, QCC_sref_t y, QCC_sref_t z); QCC_sref_t QCC_MakeVectorConst(pvec_t a, pvec_t b, pvec_t c) { /* QCC_def_t *cn; // check for a constant with the same value for (cn=pr.def_head.next ; cn ; cn=cn->next) { if (!cn->initialized) continue; if (!cn->constant) continue; if (cn->type != type_vector) continue; if (cn->arraysize) continue; if (cn->symboldata[0].vector[0] == a && cn->symboldata[0].vector[1] == b && cn->symboldata[0].vector[2] == c) { return QCC_MakeSRefForce(cn, 0, type_vector); } }*/ { union { pvec_t f[3]; pint_t i[3]; } u = {{a,b,c}}; return QCC_Make96bitConst(type_vector, u.i); } } extern hashtable_t stringconstdefstable, stringconstdefstable_trans; int dotranslate_count; static QCC_sref_t QCC_MakeStringConstInternal(const char *value, size_t length, pbool translate) { QCC_def_t *cn; int string; pbool usehash = (length == strlen(value)+1); //if there are embedded nulls, our hash code will not be able to cope. if (usehash) { cn = pHash_Get(translate?&stringconstdefstable_trans:&stringconstdefstable, value); if (cn) { return QCC_MakeSRefForce(cn, 0, type_string); } } // allocate a new one if(translate) { char buf[64]; QC_snprintfz(buf, sizeof(buf), "dotranslate_%i", ++dotranslate_count); cn = (void *)qccHunkAlloc (sizeof(QCC_def_t)+sizeof(string_t) + strlen(buf)+1); cn->name = (char*)((string_t*)(cn+1)+1); strcpy(cn->name, buf); cn->used = true; // cn->referenced = true; cn->nofold = true; } else { cn = (void *)qccHunkAlloc (sizeof(QCC_def_t)+sizeof(string_t)); cn->name = "IMMEDIATE"; } cn->next = NULL; pr.def_tail->next = cn; pr.def_tail = cn; cn->type = type_string; cn->constant = !translate; cn->initialized = 1; cn->scope = NULL; // always share immediates cn->arraysize = 0; cn->localscope = false; cn->filen = s_filen; cn->s_line = pr_source_line; // copy the immediate to the global area cn->ofs = 0; cn->symbolheader = cn; cn->symbolsize = cn->type->size; cn->symboldata = (QCC_eval_basic_t*)(cn+1); if (usehash) { string = QCC_CopyString (value); pHash_Add(translate?&stringconstdefstable_trans:&stringconstdefstable, strings+string, cn, qccHunkAlloc(sizeof(bucket_t))); } else string = QCC_CopyStringLength (value, length); cn->symboldata[0].string = string; return QCC_MakeSRefForce(cn, 0, type_string); } QCC_sref_t QCC_MakeStringConstLength(const char *value, int length) { return QCC_MakeStringConstInternal(value, length, false); } QCC_sref_t QCC_MakeStringConst(const char *value) { return QCC_MakeStringConstInternal(value, strlen(value)+1, false); } QCC_sref_t QCC_MakeTranslateStringConst(const char *value) { return QCC_MakeStringConstInternal(value, strlen(value)+1, true); } QCC_type_t *QCC_PointerTypeTo(QCC_type_t *type) { QCC_type_t *newtype; newtype = QCC_PR_NewType("ptr", ev_pointer, false); newtype->aux_type = type; return newtype; } QCC_type_t *QCC_GenArrayType(QCC_type_t *type, unsigned int arraysize) { struct QCC_typeparam_s *param = qccHunkAlloc(sizeof(*param)); param->type = type; param->arraysize = arraysize; param->paramname = NULL; type = QCC_PR_NewType("array", ev_union, false); type->params = param; type->num_parms = 1; if (param->type->bits) { type->bits = param->type->bits * param->arraysize; type->size = (param->type->bits * param->arraysize + 31) & ~31; } else type->size = param->type->size * param->arraysize; type->align = param->type->align; return type; } QCC_type_t **basictypes[] = { &type_void, &type_string, &type_float, &type_vector, &type_entity, &type_field, &type_function, &type_pointer, &type_integer, &type_uint, &type_int64, &type_uint64, &type_double, &type_variant, NULL, //type_struct NULL, //type_union NULL, //type_accessor NULL, //type_enum NULL, //type_boolean }; /*static QCC_def_t *QCC_MemberInParentClass(char *name, QCC_type_t *clas) { //if a member exists, return the member field (rather than mapped-to field) QCC_def_t *def; unsigned int p; char membername[2048]; if (!clas) { def = QCC_PR_GetDef(NULL, name, NULL, 0, 0, false); if (def && def->type->type == ev_field) //the member existed as a normal entity field. return def; return NULL; } for (p = 0; p < clas->num_parms; p++) { if (strcmp(clas->params[p].paramname, name)) continue; //the parent has it. QC_snprintfz(membername, sizeof(membername), "%s::"MEMBERFIELDNAME, clas->name, clas->params[p].paramname); def = QCC_PR_GetDef(NULL, membername, NULL, false, 0, false); if (def) return def; break; } return QCC_MemberInParentClass(name, clas->parentclass); }*/ static void QCC_PR_EmitClassFunctionTable(QCC_type_t *clas, QCC_type_t *childclas, QCC_sref_t ed) { //go through clas, do the virtual thing only if the child class does not override. char membername[2048]; QCC_type_t *type; QCC_type_t *oc; unsigned int p; QCC_sref_t point, member; QCC_sref_t virt; if (clas->parentclass) QCC_PR_EmitClassFunctionTable(clas->parentclass, childclas, ed); for (p = 0; p < clas->num_parms; p++) { type = clas->params[p].type; for (oc = childclas; oc != clas; oc = oc->parentclass) { QC_snprintfz(membername, sizeof(membername), "%s::"MEMBERFIELDNAME, oc->name, clas->params[p].paramname); if (QCC_PR_GetSRef(NULL, membername, NULL, false, 0, false).cast) break; //a child class overrides. } if (oc != clas) continue; if (type->type == ev_function) //FIXME: inheritance will not install all the member functions. { member = nullsref; for (oc = childclas; oc && !member.cast; oc = oc->parentclass) { QC_snprintfz(membername, sizeof(membername), "%s::"MEMBERFIELDNAME, oc->name, clas->params[p].paramname); member = QCC_PR_GetSRef(NULL, membername, NULL, false, 0, false); } if (!member.cast) { QC_snprintfz(membername, sizeof(membername), "%s::"MEMBERFIELDNAME, clas->name, clas->params[p].paramname); QCC_PR_Warning(ERR_INTERNAL, NULL, 0, "Member function %s was not defined", membername); continue; } QC_snprintfz(membername, sizeof(membername), "%s::%s", clas->name, clas->params[p].paramname); virt = QCC_PR_GetSRef(type, membername, NULL, false, 0, false); if (!virt.cast) { QCC_PR_Warning(0, NULL, 0, "Member function %s was not defined", membername); continue; } point = QCC_PR_StatementFlags(&pr_opcodes[OP_ADDRESS], ed, member, NULL, STFL_PRESERVEA); type_pointer->aux_type = virt.cast; QCC_PR_Statement(&pr_opcodes[OP_STOREP_FNC], virt, point, NULL); } } } //take all functions in the type, and parent types, and make sure the links all work properly. void QCC_PR_EmitClassFromFunction(QCC_def_t *scope, QCC_type_t *basetype) { QCC_type_t *parenttype; QCC_sref_t ed; QCC_sref_t constructor; int basictypefield[ev_union+1]; int src,dst; // int func; if (numfunctions >= MAX_FUNCTIONS) QCC_Error(ERR_INTERNAL, "Too many function defs"); pr_scope = NULL; memset(basictypefield, 0, sizeof(basictypefield)); // QCC_PR_EmitFieldsForMembers(basetype, basictypefield); pr_source_line = pr_token_line_last = scope->s_line; pr_scope = QCC_PR_GenerateQCFunction(scope, scope->type, NULL); //reset the locals chain pr.local_head.nextlocal = NULL; pr.local_tail = &pr.local_head; scope->initialized = true; scope->symboldata[0].function = pr_scope - functions; ed = QCC_PR_GetSRef(type_entity, "self", NULL, true, 0, false); { QCC_sref_t fclassname = QCC_PR_GetSRef(NULL, "classname", NULL, false, 0, false); if (fclassname.cast) { QCC_sref_t point = QCC_PR_StatementFlags(&pr_opcodes[OP_ADDRESS], ed, fclassname, NULL, STFL_PRESERVEA); type_pointer->aux_type = type_string; QCC_PR_Statement(&pr_opcodes[OP_STOREP_FNC], QCC_MakeStringConst(basetype->name), point, NULL); } } QCC_PR_EmitClassFunctionTable(basetype, basetype, ed); src = numstatements; for (parenttype = basetype; parenttype; parenttype = parenttype->parentclass) { char membername[2048]; QC_snprintfz(membername, sizeof(membername), "%s::%s", parenttype->name, parenttype->name); constructor = QCC_PR_GetSRef(NULL, membername, NULL, false, 0, false); if (constructor.cast) { constructor.sym->referenced = true; QCC_PR_SimpleStatement(&pr_opcodes[OP_CALL0], constructor, nullsref, nullsref, false); QCC_FreeTemp(constructor); } } if (flag_rootconstructor) { dst = numstatements-1; while(src < dst) { QCC_sref_t t = statements[src].a; statements[src].a = statements[dst].a; statements[dst].a = t; src++; dst--; } } QCC_FreeTemp(QCC_PR_Statement(&pr_opcodes[OP_DONE], nullsref, nullsref, NULL)); QCC_WriteAsmFunction(pr_scope, pr_scope->code, pr_scope->firstlocal); QCC_Marshal_Locals(pr_scope->code, numstatements); } static QCC_sref_t QCC_PR_ExpandField(QCC_sref_t ent, QCC_sref_t field, QCC_type_t *fieldtype, unsigned int preserveflags) { QCC_type_t *basicfieldtype; QCC_sref_t r; if (!fieldtype) { if (field.cast->type == ev_field) fieldtype = field.cast->aux_type; else { if (field.cast->type != ev_variant) QCC_PR_ParseErrorPrintSRef(ERR_INTERNAL, field, "QCC_PR_ExpandField: invalid field type"); fieldtype = type_variant; } } basicfieldtype = fieldtype; while(basicfieldtype->type == ev_accessor || basicfieldtype->type == ev_boolean || basicfieldtype->type == ev_enum) basicfieldtype = (basicfieldtype->type == ev_enum)?basicfieldtype->aux_type:basicfieldtype->parentclass; //FIXME: class.staticmember should directly read staticmember instead of trying to dereference switch(basicfieldtype->type) { case ev_struct: case ev_union: { int i = 0; QCC_type_t *type = fieldtype; QCC_sref_t dest = QCC_GetTemp(type); QCC_sref_t source = field; //don't bother trying to optimise any temps here, its not likely to happen anyway. for (; i+2 < type->size; i+=3, dest.ofs += 3, source.ofs += 3) QCC_PR_SimpleStatement(&pr_opcodes[OP_LOAD_V], ent, source, dest, false); if (QCC_OPCodeValid(&pr_opcodes[OP_LOAD_I64])) for (; i+1 < type->size; i+=2, dest.ofs += 2, source.ofs += 2) QCC_PR_SimpleStatement(&pr_opcodes[OP_LOAD_I64], ent, source, dest, false); for (; i < type->size; i++, dest.ofs++, source.ofs++) QCC_PR_SimpleStatement(&pr_opcodes[OP_LOAD_F], ent, source, dest, false); source.ofs -= type->size; dest.ofs -= type->size; if (!(preserveflags & STFL_PRESERVEA)) QCC_FreeTemp(ent); if (!(preserveflags & STFL_PRESERVEB)) QCC_FreeTemp(field); if (type->size > 3) QCC_PR_ParseWarning(WARN_UNDESIRABLECONVENTION, "inefficient - copying %u words to a temp", type->size); return dest; } break; case ev_void: case ev_accessor: case ev_boolean: case ev_enum: default: { char temp[256]; QCC_PR_ParseErrorPrintSRef(ERR_INTERNAL, field, "QCC_PR_ExpandField: invalid field type %s%s%s", col_type,TypeName(fieldtype, temp, sizeof(temp)),col_none); } r = field; break; case ev_integer: r = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_I], ent, field, NULL, preserveflags); break; case ev_uint: r = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_I], ent, field, NULL, preserveflags); r.cast = type_uint; break; case ev_double: r = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_I64], ent, field, NULL, preserveflags); r.cast = type_double; break; case ev_int64: r = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_I64], ent, field, NULL, preserveflags); r.cast = type_int64; break; case ev_uint64: r = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_I64], ent, field, NULL, preserveflags); r.cast = type_uint64; break; case ev_pointer: r = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_P], ent, field, NULL, preserveflags); break; case ev_field: r = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_FLD], ent, field, NULL, preserveflags); break; case ev_variant: r = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_FLD], ent, field, NULL, preserveflags); break; case ev_float: r = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_F], ent, field, NULL, preserveflags); break; case ev_string: r = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_S], ent, field, NULL, preserveflags); break; case ev_vector: r = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_V], ent, field, NULL, preserveflags); break; case ev_function: r = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_FNC], ent, field, NULL, preserveflags); break; case ev_entity: r = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_ENT], ent, field, NULL, preserveflags); break; } r.cast = fieldtype; return r; } /*checks for .foo and expands in a class-aware fashion normally invoked via QCC_PR_ParseArrayPointer */ static QCC_ref_t *QCC_PR_ParseField(QCC_ref_t *refbuf, QCC_ref_t *lhs) { QCC_type_t *t; t = lhs->cast; if ((t->accessors || t->type == ev_entity) && (QCC_PR_CheckToken(".") || QCC_PR_CheckToken("->"))) { QCC_ref_t *field; QCC_ref_t fieldbuf; if (pr_token_type == tt_name) { QCC_sref_t index = nullsref; char *fieldname = pr_token; struct accessor_s *acc = NULL, *anon = NULL; QCC_type_t *a; for (a = t; a && !acc; a = a->parentclass) for (acc = a->accessors; acc; acc = acc->next) { if (!*acc->fieldname && acc->indexertype) { if (!anon) anon = acc; } else if (!strcmp(acc->fieldname, fieldname)) { fieldname = QCC_PR_ParseName(); //do it for real now. if (acc->indexertype) { if (QCC_PR_CheckToken(".") || QCC_PR_CheckToken("->")) index = QCC_MakeStringConst(QCC_PR_ParseName()); else { QCC_PR_Expect("["); index = QCC_PR_Expression (TOP_PRIORITY, 0); QCC_PR_Expect("]"); } } break; } } if (!acc && anon) { acc = anon; fieldname = QCC_PR_ParseName(); //do it for real now. index = QCC_MakeStringConst(fieldname); } if (acc) { lhs = QCC_PR_BuildAccessorRef(refbuf, QCC_RefToDef(lhs, true), index, acc, lhs->readonly); lhs = QCC_PR_ParseField(refbuf, lhs); return lhs; } } if (t->type == ev_entity) { if (QCC_PR_CheckToken("(")) { field = QCC_PR_RefExpression(&fieldbuf, TOP_PRIORITY, 0); QCC_PR_Expect(")"); } else field = QCC_PR_ParseRefValue(&fieldbuf, t, false, false, true); if (field->type != REF_ARRAYHEAD && (field->cast->type == ev_field || field->cast->type == ev_variant)) { //fields are generally always readonly. that refers to the field def itself, rather than products of said field. //entities, like 'world' might also be consts. just ignore that fact. the def itself is not assigned, but the fields of said def. //the engine may have a problem with this, but the qcc has no way to referenced locations as readonly separately from the def itself. lhs = QCC_PR_BuildRef(refbuf, REF_FIELD, QCC_RefToDef(lhs, true), QCC_RefToDef(field, true), (field->cast->type == ev_field)?field->cast->aux_type:type_variant, false, 0); } else { if (field->type == REF_GLOBAL && strstr(QCC_GetSRefName(field->base), "::")) { QCC_sref_t theent = QCC_RefToDef(lhs, true); *refbuf = *field; refbuf->type = REF_NONVIRTUAL; refbuf->index = theent; return refbuf; } if (t->parentclass) QCC_PR_ParseError(ERR_BADMEMBER, "%s is not a field of class %s", QCC_GetSRefName(QCC_RefToDef(field, false)), t->name); else QCC_PR_ParseError(ERR_BADMEMBER, "%s is not a field", QCC_GetSRefName(QCC_RefToDef(field, false))); } lhs = QCC_PR_ParseField(refbuf, lhs); lhs = QCC_PR_ParseRefArrayPointer (refbuf, lhs, false, false); } else { QCC_PR_ParseWarning(ERR_BADMEMBER, "%s is not a member of %s", QCC_PR_ParseName(), t->name); if (t->filen) QCC_PR_Note(ERR_BADMEMBER, t->filen, t->line, "%s is defined here", t->name); return QCC_PR_BuildRef(refbuf, REF_GLOBAL, QCC_MakeIntConst(0), nullsref, type_void, false, 0); } } else if (flag_qccx && t->type == ev_entity && QCC_PR_CheckToken("[")) { //p[%0] gives a regular array reference. except that p is probably a float, and we're expecting OP_LOAD_F //might also be assigned to, so just create a regular field ref and figure that stuff out later. QCC_ref_t *field; QCC_ref_t fieldbuf; field = QCC_PR_RefExpression(&fieldbuf, TOP_PRIORITY, 0); field->cast = type_floatfield; QCC_PR_Expect("]"); lhs = QCC_PR_BuildRef(refbuf, REF_FIELD, QCC_RefToDef(lhs, true), QCC_RefToDef(field, true), type_float, false, 0); lhs = QCC_PR_ParseField(refbuf, lhs); lhs = QCC_PR_ParseRefArrayPointer (refbuf, lhs, false, false); } else if (flag_qccx && t->type == ev_entity && QCC_PR_CheckToken("^")) { //p^[%0] is evaluated as an OP_LOAD_V (or OP_ADDRESS+OP_STOREP_V) QCC_ref_t *field; QCC_ref_t fieldbuf; QCC_PR_Expect("["); field = QCC_PR_RefExpression(&fieldbuf, TOP_PRIORITY, 0); field->cast = type_floatfield; QCC_PR_Expect("]"); lhs = QCC_PR_BuildRef(refbuf, REF_FIELD, QCC_RefToDef(lhs, true), QCC_RefToDef(field, true), type_vector, false, 0); lhs = QCC_PR_ParseField(refbuf, lhs); lhs = QCC_PR_ParseRefArrayPointer (refbuf, lhs, false, false); } return lhs; } //this is more complex than it needs to be, in order to ensure that anon unions/structs can be handled. struct QCC_typeparam_s *QCC_PR_FindStructMember(QCC_type_t *t, const char *membername, unsigned int *out_ofs, unsigned int *out_bitofs) { unsigned int nofs, nbitofs; int i; struct QCC_typeparam_s *r = NULL, *n; for (i = 0; i < t->num_parms; i++) { if ((!t->params[i].paramname || !*t->params[i].paramname) && (t->params[i].type->type == ev_struct || t->params[i].type->type == ev_union)) { //anonymous structs/unions can nest n = QCC_PR_FindStructMember(t->params[i].type, membername, &nofs, &nbitofs); if (n) { if (r) break; r = n; *out_ofs = t->params[i].ofs + nofs; *out_bitofs = t->params[i].bitofs + nbitofs; } } else if (flag_caseinsensitive?!stricmp (t->params[i].paramname, membername):!STRCMP(t->params[i].paramname, membername)) { if (r) break; r = t->params+i; *out_ofs = r->ofs; *out_bitofs = r->bitofs; } } if (i < t->num_parms) { QCC_PR_ParseError(0, "multiple members found matching %s.%s", t->name, membername); return NULL; } if (!r && t->parentclass) //chain through the parent struct return QCC_PR_FindStructMember(t->parentclass, membername, out_ofs, out_bitofs); return r; } /*checks for: [X] [X].foo .foo within types which are a contiguous block, expanding to an array index. Also calls QCC_PR_ParseField, which does fields too. */ QCC_ref_t *QCC_PR_ParseRefArrayPointer (QCC_ref_t *retbuf, QCC_ref_t *r, pbool allowarrayassign, pbool makearraypointers) { QCC_type_t *t, *p; QCC_sref_t idx; QCC_sref_t tmp; pbool allowarray, arraytype; unsigned int arraysize; unsigned int rewindpoint = numstatements; pbool dereference = false; const QCC_eval_t *eval; unsigned int bitofs = 0; pbool dorecurse = false; QCC_ref_t addr; idx = nullsref; t = r->cast; if (r->type == REF_ARRAYHEAD || r->type == REF_POINTERARRAY) { if (r->type == REF_POINTERARRAY) dereference = true; if (t->type != ev_pointer) QCC_PR_ParseWarning(ERR_INTERNAL, "QCC_PR_ParseRefArrayPointer: array reference not a cast to pointer\n"); t = t->aux_type; arraysize = r->arraysize; } else { if (r->type == REF_POINTER && r->cast->type != ev_pointer && !r->postinc && (r->cast->type==ev_union || r->cast->type==ev_struct) && QCC_PR_PeekToken(".")) { // (*ptr).blah === ptr->blah //try to undo the *ptr so we can do it automatically from the -> // r = QCC_PR_GenerateAddressOf(&addr, r); // return QCC_PR_ParseRefArrayPointer(retbuf, r, allowarrayassign, makearraypointers); addr = *r; r = &addr; r->cast = t = QCC_PR_PointerType(t); // dereference = true; idx = r->index; r->index = nullsref; r->type = REF_GLOBAL; } arraysize = 0; } while(1) { allowarray = false; arraytype = (t->type == ev_union && t->num_parms == 1 && !t->params[0].paramname); //FIXME if (arraytype) allowarray = true; if (idx.cast) allowarray = arraysize>0 || (t->type == ev_vector) || (t->type == ev_field && t->aux_type->type == ev_vector) || (arraytype && !arraysize); else if (!idx.cast) { allowarray = arraysize>0 || (t->type == ev_pointer) || //we can dereference pointers (t->type == ev_string) || //strings are effectively pointers (t->type == ev_vector) || //vectors are mini arrays (t->type == ev_field && t->aux_type->type == ev_vector) || //as are field vectors (arraytype && !arraysize) || (!arraysize&&t->accessors); //custom accessors } if (allowarray && QCC_PR_CheckToken("[")) { p = t; tmp = QCC_PR_Expression (TOP_PRIORITY, 0); QCC_PR_Expect("]"); if (!arraysize && t->accessors) { struct accessor_s *acc; for (acc = t->accessors; acc; acc = acc->next) if (!*acc->fieldname) break; if(acc) { r = QCC_PR_BuildAccessorRef(retbuf, QCC_RefToDef(r, true), tmp, acc, r->readonly); return QCC_PR_ParseRefArrayPointer(retbuf, r, allowarrayassign, makearraypointers); } } /*if its a pointer that got dereferenced, follow the type*/ if (!idx.cast && t->type == ev_pointer && !arraysize) t = t->aux_type; else if (idx.cast && (arraytype && !arraysize)) { arraysize = t->params[0].arraysize; bitofs += t->params[0].bitofs; t = t->params[0].type; } if (!idx.cast && p->type == ev_pointer && !arraysize) { /*no bounds checks on pointer dereferences*/ if (dereference) { //resolve it now r = QCC_PR_BuildRef(retbuf, REF_POINTER, QCC_RefToDef(r, true), idx, t, r->readonly, bitofs); idx = nullsref; } dereference = true; } else if (!idx.cast && p->type == ev_string && !arraysize) { if (flag_qccx) { QCC_sref_t base = QCC_RefToDef(r, true); if (tmp.cast && tmp.cast->type == ev_float) { QCC_PR_ParseWarning(WARN_DENORMAL, "string offsetting emulation: denormals are unsafe"); idx = QCC_PR_Statement(&pr_opcodes[OP_ADD_F], base, QCC_SupplyConversion(tmp, ev_float, true), NULL); } else idx = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], base, QCC_SupplyConversion(tmp, ev_integer, true), NULL); return QCC_PR_BuildRef(retbuf, REF_GLOBAL, idx, nullsref, type_string, true, 0); } else { /*automatic runtime bounds checks on strings, I'm not going to check this too much...*/ r = QCC_PR_BuildRef(retbuf, REF_STRING, QCC_RefToDef(r, true), tmp, (tmp.cast->type != ev_float)?type_integer:type_float, r->readonly, 0); return QCC_PR_ParseRefArrayPointer(retbuf, r, allowarrayassign, makearraypointers); } } else if ((!idx.cast && p->type == ev_vector && !arraysize) || (idx.cast && t->type == ev_vector && !arraysize)) { /*array notation on vector*/ vectorarrayindex: if ((eval=QCC_SRef_EvalConst(tmp))) { unsigned long i = QCC_Eval_Int(eval, tmp.cast); if (i >= 3u) QCC_PR_ParseErrorPrintSRef(0, r->base, "(vector) array index out of bounds"); } else if (QCC_OPCodeValid(&pr_opcodes[OP_BOUNDCHECK]) && flag_boundchecks) { tmp = QCC_SupplyConversion(tmp, ev_integer, true); QCC_PR_SimpleStatement (&pr_opcodes[OP_BOUNDCHECK], tmp, QCC_MakeSRef(NULL, 3, NULL), nullsref, false); } t = type_float; } else if ((!idx.cast && p->type == ev_field && r->cast->aux_type->type == ev_vector && !arraysize) || (idx.cast && t->type == ev_field && t->aux_type->type && !arraysize)) { /*array notation on vector field*/ fieldarrayindex: if ((eval=QCC_SRef_EvalConst(tmp))) { unsigned long i = QCC_Eval_Int(eval, tmp.cast); if (i >= 3u) QCC_PR_ParseErrorPrintSRef(0, r->base, "(.vector) array index out of bounds"); } else if (QCC_OPCodeValid(&pr_opcodes[OP_BOUNDCHECK]) && flag_boundchecks) { tmp = QCC_SupplyConversion(tmp, ev_integer, true); QCC_PR_SimpleStatement (&pr_opcodes[OP_BOUNDCHECK], tmp, QCC_MakeSRef(NULL, 3, NULL), nullsref, false); } t = type_floatfield; } else if (!arraysize) { QCC_PR_ParseErrorPrintSRef(0, r->base, "array index on non-array"); } else if ((eval=QCC_SRef_EvalConst(tmp))) { unsigned i = QCC_Eval_Int(eval, tmp.cast); if (i >= (unsigned)arraysize) { QCC_PR_ParseWarning(WARN_BOUNDS, "(constant) array index out of bounds (0 <= %i < %i)", i, arraysize); QCC_PR_ParsePrintSRef(WARN_BOUNDS, r->base); } } else { if (QCC_OPCodeValid(&pr_opcodes[OP_BOUNDCHECK]) && flag_boundchecks) { tmp = QCC_SupplyConversion(tmp, ev_integer, true); QCC_PR_SimpleStatement (&pr_opcodes[OP_BOUNDCHECK], tmp, QCC_MakeSRef(NULL, arraysize, NULL), nullsref, false); } } arraysize = 0; if (t->bits) { //convert it to ints if that makes sense if (idx.cast) idx = QCC_SupplyConversion(idx, ev_integer, true); tmp = QCC_SupplyConversion(tmp, ev_integer, true); tmp = QCC_PR_Statement(&pr_opcodes[OP_MUL_I], QCC_SupplyConversion(tmp, ev_integer, true), QCC_MakeIntConst(t->bits/t->align), NULL); } else if (t->size != 1) /*don't multiply by type size if the instruction/emulation will do that instead*/ { //convert it to ints if that makes sense if (QCC_OPCodeValid(&pr_opcodes[OP_ADD_I]) && ((idx.cast && idx.cast->type == ev_integer) || tmp.cast->type == ev_integer)) { if (idx.cast) idx = QCC_SupplyConversion(idx, ev_integer, true); tmp = QCC_SupplyConversion(tmp, ev_integer, true); } if (tmp.cast->type == ev_float) tmp = QCC_PR_Statement(&pr_opcodes[OP_MUL_F], QCC_SupplyConversion(tmp, ev_float, true), QCC_MakeFloatConst(t->size), NULL); else tmp = QCC_PR_Statement(&pr_opcodes[OP_MUL_I], QCC_SupplyConversion(tmp, ev_integer, true), QCC_MakeIntConst(t->size), NULL); //FIXME: we really need some sort of x*stride+offset ref type. it would allow adding offsets more efficiently. } //legacy opcodes needs to stay using floats even if an int was specified. avoid int immediates. // if (!QCC_OPCodeValid(&pr_opcodes[OP_ADD_I])) // { // if (idx.cast) // idx = QCC_SupplyConversion(idx, ev_float, true); // tmp = QCC_SupplyConversion(tmp, ev_float, true); // } /*calc the new index*/ if (idx.cast && idx.cast->type == ev_float && tmp.cast->type == ev_float) idx = QCC_PR_Statement(&pr_opcodes[OP_ADD_F], QCC_SupplyConversion(idx, ev_float, true), QCC_SupplyConversion(tmp, ev_float, true), NULL); else if (idx.cast) idx = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], QCC_SupplyConversion(idx, ev_integer, true), QCC_SupplyConversion(tmp, ev_integer, true), NULL); else idx = tmp; } else if (arraysize && (QCC_PR_CheckToken(".") /*|| QCC_PR_CheckToken("->")*/)) { //the only field of an array type is the 'length' property. //if we calculated offsets etc, discard those statements. //FIXME: if this is an array of pointer-to-struct then '->' will dereference and we just misparsed. numstatements = rewindpoint; QCC_PR_Expect("length"); QCC_FreeTemp(r->base); QCC_FreeTemp(r->index); QCC_FreeTemp(idx); return QCC_PR_BuildRef(retbuf, REF_GLOBAL, QCC_MakeIntConst(arraysize), nullsref, type_integer, true, 0); } else if (arraytype && (QCC_PR_CheckToken(".")/* || QCC_PR_CheckToken("->")*/)) { //the only field of an array type is the 'length' property. //if we calculated offsets etc, discard those statements. numstatements = rewindpoint; QCC_PR_Expect("length"); QCC_FreeTemp(r->base); QCC_FreeTemp(r->index); QCC_FreeTemp(idx); return QCC_PR_BuildRef(retbuf, REF_GLOBAL, QCC_MakeIntConst(t->params[0].arraysize), nullsref, type_integer, true, 0); } else if (t->type == ev_string && !idx.cast && (QCC_PR_CheckToken(".")/* || QCC_PR_CheckToken("->")*/)) { if (QCC_PR_CheckName("length")) { const char *val = QCC_SRef_EvalStringConst(r->base); if (val) { //the only field of an array type is the 'length' property. //if we calculated offsets etc, discard those statements. numstatements = rewindpoint; QCC_FreeTemp(r->base); QCC_FreeTemp(r->index); QCC_FreeTemp(idx); return QCC_PR_BuildRef(retbuf, REF_GLOBAL, QCC_MakeIntConst(strlen(val)), nullsref, type_integer, true, 0); } QCC_PR_ParseError(0, "not a constant"); } else QCC_PR_ParseError(0, "unsupported string method %s", pr_token); } else if (t->type == ev_vector && !arraysize && !t->accessors && QCC_PR_CheckToken(".")) { char *swizzle = QCC_PR_ParseName(); //single-channel swizzles just result in a float. nice and easy. assignable, too. if ((!strcmp(swizzle, "x") || !strcmp(swizzle, "r")) && t->size >= 1) { tmp = QCC_MakeIntConst(0); goto vectorarrayindex; } else if ((!strcmp(swizzle, "y") || !strcmp(swizzle, "g")) && t->size >= 2) { tmp = QCC_MakeIntConst(1); goto vectorarrayindex; } else if ((!strcmp(swizzle, "z") || !strcmp(swizzle, "b")) && t->size >= 3) { tmp = QCC_MakeIntConst(2); goto vectorarrayindex; } else if ((!strcmp(swizzle, "w") || !strcmp(swizzle, "a")) && t->size >= 4) { tmp = QCC_MakeIntConst(3); goto vectorarrayindex; } else QCC_PR_ParseError(0, "unsupported vector swizzle '.%s'", swizzle); } else if ((t->type == ev_field && t->aux_type->type == ev_vector) && !arraysize && !t->accessors && QCC_PR_CheckToken(".")) { char *swizzle = QCC_PR_ParseName(); //single-channel swizzles just result in a float. nice and easy. assignable, too. if ((!strcmp(swizzle, "x") || !strcmp(swizzle, "r")) && t->size >= 1) { tmp = QCC_MakeIntConst(0); goto fieldarrayindex; } else if ((!strcmp(swizzle, "y") || !strcmp(swizzle, "g")) && t->size >= 2) { tmp = QCC_MakeIntConst(1); goto fieldarrayindex; } else if ((!strcmp(swizzle, "z") || !strcmp(swizzle, "b")) && t->size >= 3) { tmp = QCC_MakeIntConst(2); goto fieldarrayindex; } else if ((!strcmp(swizzle, "w") || !strcmp(swizzle, "a")) && t->size >= 4) { tmp = QCC_MakeIntConst(3); goto fieldarrayindex; } else QCC_PR_ParseError(0, "unsupported vector swizzle '.%s'", swizzle); } else if (((t->type == ev_pointer && !arraysize) || (t->type == ev_field && (t->aux_type->type == ev_struct || t->aux_type->type == ev_union)) || t->type == ev_struct || t->type == ev_union) && (QCC_PR_CheckToken(".") || QCC_PR_CheckToken("->"))) { const char *tname, *mname; unsigned int ofs, mbitofs; pbool fld = t->type == ev_field; struct QCC_typeparam_s *p; if (t->type == ev_field) t = t->aux_type; else if (t->type == ev_pointer && !arraysize) { t = t->aux_type; if (dereference) { r = QCC_PR_BuildRef(retbuf, REF_POINTER, QCC_RefToDef(r, true), idx, t, false, bitofs); idx = nullsref; } dereference = true; } tname = t->name; if (t->type == ev_struct || t->type == ev_union) { if (!t->size) QCC_PR_ParseError(0, "%s was not defined yet", tname); } else { char typea[256]; char typeb[256]; TypeName(t, typea, sizeof(typea)); if (idx.cast) { TypeName(idx.cast, typeb, sizeof(typeb)); QCC_PR_ParseError(0, "indirection in %s [%s %s] - not a struct or union", typea, typeb, idx.sym->name); } else QCC_PR_ParseError(0, "indirection in %s - not a struct or union", typea); } mname = QCC_PR_ParseName(); p = QCC_PR_FindStructMember(t, mname, &ofs, &mbitofs); if (!p) { //check for static or non-virtual-function QCC_type_t *c; char membername[2048]; for (c = t; c; c = c->parentclass) { QC_snprintfz(membername, sizeof(membername), "%s::%s", c->name, mname); tmp = QCC_PR_GetSRef(NULL, membername, NULL, false, 0, false); if (tmp.cast) { //static or non-virtual QCC_sref_t base = nullsref; //for static r = QCC_PR_BuildRef(retbuf, base.cast?REF_THISCALL:REF_GLOBAL, tmp, base, tmp.cast, tmp.sym->constant, 0); return QCC_PR_ParseRefArrayPointer(retbuf, r, allowarrayassign, makearraypointers); } } QCC_PR_ParseWarning(ERR_BADMEMBER, "%s is not a member of %s", mname, t->name); if (t->filen) QCC_PR_Note(ERR_BADMEMBER, t->filen, t->line, "%s is defined here", t->name); QCC_PR_ParseError(ERR_BADMEMBER, NULL); } if (idx.cast && p->type->align != t->align) //switching alignment requires dealing with what the previous was defined as. should onlly switch to tighter alignment idx = QCC_PR_Statement(&pr_opcodes[OP_MUL_I], QCC_SupplyConversion(idx, ev_integer, true), QCC_MakeIntConst(t->align/p->type->align), NULL); if(p->type->align == 8) { ofs*=(32/p->type->align); ofs+=(mbitofs>>3); mbitofs &= 7; } else if(p->type->align == 16) { ofs*=(32/p->type->align); ofs+=(mbitofs>>4); mbitofs &= 15; } else ofs *= (32/p->type->align); if (!ofs && idx.cast) ; else if (QCC_OPCodeValid(&pr_opcodes[OP_ADD_I])) { tmp = QCC_MakeIntConst(ofs); if (idx.cast) idx = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], QCC_SupplyConversion(idx, ev_integer, true), tmp, NULL); else idx = tmp; } else { tmp = QCC_MakeFloatConst(ofs*(32/p->type->align)); if (idx.cast) idx = QCC_PR_Statement(&pr_opcodes[OP_ADD_F], QCC_SupplyConversion(idx, ev_float, true), QCC_SupplyConversion(tmp, ev_float, true), NULL); else idx = tmp; } arraysize = p->arraysize; t = p->type; bitofs += mbitofs; if (fld) t = QCC_PR_FieldType(t); } else break; dorecurse = true; if (t->type == ev_pointer && !arraysize) { QCC_sref_t base; if (r->type == REF_ARRAYHEAD) { r->type = REF_ARRAY; r->cast = type_void; base = QCC_RefToDef(r, true); r->type = REF_ARRAYHEAD; } else base = QCC_RefToDef(r, true); if (t->type == ev_union && t->num_parms == 1 && !t->params[0].paramname) //FIXME: this destroys type info required for sizeof, and breaks struct lvalues. { arraysize = t->params[0].arraysize; t = t->params[0].type; } //QCC_PR_StatementAnnotation("ParseRefArrayPointer:%i %s->",__LINE__, reftypename[r->type]); if (dereference) r = QCC_PR_BuildRef(retbuf, REF_POINTER, base, idx, t, r->readonly, bitofs); else r = QCC_PR_BuildRef(retbuf, REF_ARRAY, base, idx, t, r->readonly, bitofs); //QCC_PR_StatementAnnotation("->%s", reftypename[r->type]); return QCC_PR_ParseRefArrayPointer(retbuf, r, allowarrayassign, makearraypointers); } } if (idx.cast) { QCC_sref_t base; if (r->type == REF_ARRAYHEAD) { r->type = REF_ARRAY; r->cast = type_void; base = QCC_RefToDef(r, true); r->type = REF_ARRAYHEAD; } else base = QCC_RefToDef(r, true); if (t->type == ev_union && t->num_parms == 1 && !t->params[0].paramname && makearraypointers) //FIXME: this destroys type info required for sizeof, and breaks struct lvalues. { arraysize = t->params[0].arraysize; t = t->params[0].type; } //okay, not a pointer, we'll have to read it in somehow //QCC_PR_StatementAnnotation("ParseRefArrayPointer:%i %s->",__LINE__, reftypename[r->type]); if (arraysize && makearraypointers) { if (dereference) r = QCC_PR_BuildRef(retbuf, REF_POINTERARRAY, base, idx, QCC_PR_PointerType(t), r->readonly, bitofs); else r = QCC_PR_BuildRef(retbuf, REF_ARRAYHEAD, base, idx, QCC_PR_PointerType(t), r->readonly, bitofs); r->arraysize = arraysize; } else { if (dereference) r = QCC_PR_BuildRef(retbuf, REF_POINTER, base, idx, t, r->readonly, bitofs); else r = QCC_PR_BuildRef(retbuf, REF_ARRAY, base, idx, t, r->readonly, bitofs); r->arraysize = arraysize; } //QCC_PR_StatementAnnotation("->%s", reftypename[r->type]); //parse recursively if (dorecurse) r = QCC_PR_ParseRefArrayPointer(retbuf, r, allowarrayassign, makearraypointers); } else r->bitofs += bitofs; r = QCC_PR_ParseField(retbuf, r); return r; } QCC_sref_t QCC_PR_GenerateVector(QCC_sref_t x, QCC_sref_t y, QCC_sref_t z) { QCC_sref_t d; const QCC_eval_t *c[3]; if (x.cast->type != ev_float && x.cast->type != ev_integer) x = QCC_EvaluateCast(x, type_float, true); if (y.cast->type != ev_float && y.cast->type != ev_integer) y = QCC_EvaluateCast(y, type_float, true); if (z.cast->type != ev_float && z.cast->type != ev_integer) z = QCC_EvaluateCast(z, type_float, true); if ((x.cast->type != ev_float && x.cast->type != ev_integer) || (y.cast->type != ev_float && y.cast->type != ev_integer) || (z.cast->type != ev_float && z.cast->type != ev_integer)) { QCC_PR_ParseError(ERR_TYPEMISMATCH, "Argument not a single numeric value in vector constructor"); return QCC_MakeVectorConst(0, 0, 0); } //return a constant if we can. c[0] = QCC_SRef_EvalConst(x); c[1] = QCC_SRef_EvalConst(y); c[2] = QCC_SRef_EvalConst(z); if (c[0] && c[1] && c[2]) { d = QCC_MakeVectorConst( QCC_Eval_Float(c[0], x.cast), QCC_Eval_Float(c[1], y.cast), QCC_Eval_Float(c[2], z.cast)); QCC_FreeTemp(x); QCC_FreeTemp(y); QCC_FreeTemp(z); return d; } if (QCC_SRef_IsNull(y) && QCC_SRef_IsNull(z)) { QCC_FreeTemp(y); QCC_FreeTemp(z); return QCC_PR_StatementFlags(pr_opcodes + OP_MUL_VF, QCC_MakeVectorConst(1, 0, 0), QCC_SupplyConversion(x, ev_float, true), NULL, 0); } if (QCC_SRef_IsNull(x) && QCC_SRef_IsNull(z)) { QCC_FreeTemp(x); QCC_FreeTemp(z); return QCC_PR_StatementFlags(pr_opcodes + OP_MUL_VF, QCC_MakeVectorConst(0, 1, 0), QCC_SupplyConversion(y, ev_float, true), NULL, 0); } if (QCC_SRef_IsNull(x) && QCC_SRef_IsNull(y)) { QCC_FreeTemp(x); QCC_FreeTemp(y); return QCC_PR_StatementFlags(pr_opcodes + OP_MUL_VF, QCC_MakeVectorConst(0, 0, 1), QCC_SupplyConversion(z, ev_float, true), NULL, 0); } //pack the variables into a vector d = QCC_GetTemp(type_vector); d.cast = type_float; if (x.cast->type == ev_float) QCC_PR_StatementFlags(pr_opcodes + OP_STORE_F, x, d, NULL, STFL_PRESERVEB|STFL_DISCARDRESULT); else QCC_PR_StatementFlags(pr_opcodes+OP_STORE_IF, x, d, NULL, STFL_PRESERVEB|STFL_DISCARDRESULT); d.ofs++; if (y.cast->type == ev_float) QCC_PR_StatementFlags(pr_opcodes + OP_STORE_F, y, d, NULL, STFL_PRESERVEB|STFL_DISCARDRESULT); else QCC_PR_StatementFlags(pr_opcodes+OP_STORE_IF, y, d, NULL, STFL_PRESERVEB|STFL_DISCARDRESULT); d.ofs++; if (z.cast->type == ev_float) QCC_PR_StatementFlags(pr_opcodes + OP_STORE_F, z, d, NULL, STFL_PRESERVEB|STFL_DISCARDRESULT); else QCC_PR_StatementFlags(pr_opcodes+OP_STORE_IF, z, d, NULL, STFL_PRESERVEB|STFL_DISCARDRESULT); d.ofs++; d.ofs -= 3; d.cast = type_vector; return d; } /* ============ PR_ParseValue Returns the global ofs for the current token ============ */ QCC_ref_t *QCC_PR_ParseRefValue (QCC_ref_t *refbuf, QCC_type_t *assumeclass, pbool allowarrayassign, pbool expandmemberfields, pbool makearraypointers) { QCC_sref_t d; QCC_type_t *t; char *name; QCC_ref_t *r; char membername[2048]; // if the token is an immediate, allocate a constant for it if (pr_token_type == tt_immediate) { d = QCC_PR_ParseImmediate (); // d.sym->referenced = true; // return QCC_DefToRef(refbuf, d); name = NULL; } else if (QCC_PR_CheckToken("[")) { //originally used for reacc - taking the form of [5 84 2] //we redefine it to include statements - [a+b, c, 3+(d*2)] //and to not need the 2nd/3rd parts if you're lazy - [5] or [5,6] - FIXME: should we accept 1-d vector? or is that too risky with arrays and weird error messages? //note the addition of commas. //if we're parsing reacc code, we will still accept [(a+b) c (3+(d*2))], as QCC_PR_Term contains the () handling. We do also allow optional commas. QCC_sref_t x,y,z; if (flag_acc) { x = QCC_PR_Term(EXPR_DISALLOW_COMMA); QCC_PR_CheckToken(","); y = QCC_PR_Term(EXPR_DISALLOW_COMMA); QCC_PR_CheckToken(","); z = QCC_PR_Term(EXPR_DISALLOW_COMMA); } else { x = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); if (QCC_PR_CheckToken(",")) y = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); else y = QCC_MakeFloatConst(0); if (QCC_PR_CheckToken(",")) z = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); else z = QCC_MakeFloatConst(0); } QCC_PR_Expect("]"); d = QCC_PR_GenerateVector(x,y,z); // d.sym->referenced = true; // return QCC_DefToRef(refbuf, d); name = NULL; } else { if (QCC_PR_CheckToken("::")) { assumeclass = NULL; expandmemberfields = false; //::classname is always usable for eg: the find builtin. } name = QCC_PR_ParseName (); //fixme: namespaces should be relative if (QCC_PR_CheckToken("::")) { struct accessor_s *a; QCC_type_t *p; char membername[1024]; expandmemberfields = false; //this::classname should also be available to the find builtin, etc. this won't affect self.classname::member nor classname::staticfunc if (assumeclass && !strcmp(name, "super")) t = assumeclass->parentclass; else if (assumeclass && !strcmp(name, "this")) t = assumeclass; else t = QCC_TypeForName(name); if (!t) { d = QCC_PR_GetSRef (pr_assumetermtype, name, pr_assumetermscope, false, 0, pr_assumetermflags); if (d.cast) { QCC_FreeTemp(d); t = d.cast; } else QCC_PR_ParseError (ERR_UNKNOWNVALUE, "\"%s\" is not a type", name); } name = QCC_PR_ParseName (); //walk up the parents if needed, to find one that has that field for(d = nullsref, p = t; ; ) { if (!d.cast && p->accessors) { for (a = t->accessors; a; a = a->next) { if (!strcmp(a->fieldname, name)) { d = a->staticval; QCC_ForceUnFreeDef(d.sym); break; } } if (d.cast) break; } if (!d.cast && t->type == ev_entity) { //use static functions in preference to virtual functions. kinda needed so you can use super::func... QC_snprintfz(membername, sizeof(membername), "%s::%s", p->name, name); d = QCC_PR_GetSRef (NULL, membername, pr_scope, false, 0, false); if (!d.cast) { QC_snprintfz(membername, sizeof(membername), "%s::"MEMBERFIELDNAME, p->name, name); d = QCC_PR_GetSRef (NULL, membername, pr_scope, false, 0, false); } p = p->parentclass; if (p) continue; } if (!d.cast && t->type == ev_struct) { //use static functions in preference to virtual functions. kinda needed so you can use super::func... QC_snprintfz(membername, sizeof(membername), "%s::%s", p->name, name); d = QCC_PR_GetSRef (NULL, membername, pr_scope, false, 0, false); p = p->parentclass; } break; } if (!d.cast) { QCC_PR_ParseError (ERR_UNKNOWNVALUE, "Unknown value \"%s::%s\"", t->name, name); } } else { d = nullsref; // 'testvar' becomes 'this::testvar' if (assumeclass && assumeclass->parentclass) { //try getting a member. QCC_type_t *type; if (assumeclass->type == ev_struct) { unsigned int ofs, bitofs; struct QCC_typeparam_s *p = QCC_PR_FindStructMember(assumeclass, name, &ofs, &bitofs); if (p) { QCC_sref_t ths; ths = QCC_PR_GetSRef(QCC_PR_PointerType(pr_classtype), "this", pr_scope, false, 0, false); if (ths.cast) { ths.cast = QCC_PR_PointerType(p->type); if (d.sym->arraysize) { //FIXME: this should result in a pointer type, and not this->member[0] } return QCC_PR_ParseRefArrayPointer(refbuf, QCC_PR_BuildRef(refbuf, REF_POINTER, ths, QCC_MakeIntConst(ofs), p->type, false, bitofs), allowarrayassign, makearraypointers); } } } else { for(type = assumeclass; type && !d.cast; type = type->parentclass) { //look for virtual things QC_snprintfz(membername, sizeof(membername), "%s::"MEMBERFIELDNAME, type->name, name); d = QCC_PR_GetSRef (NULL, membername, pr_scope, false, 0, false); } for(type = assumeclass; type && !d.cast; type = type->parentclass) { //look for non-virtual things (functions: after virtual stuff, because this will find the actual function def too) QC_snprintfz(membername, sizeof(membername), "%s::%s", type->name, name); d = QCC_PR_GetSRef (NULL, membername, pr_scope, false, 0, false); } } } if (!d.cast) { // look through the defs d = QCC_PR_GetSRef (NULL, name, pr_scope, false, 0, false); } } if (!d.cast) { if (!strcmp(name, "nil")) d = QCC_MakeIntConst(0); else if ( (!strcmp(name, "randomv")) || (!strcmp(name, "alloca")) || (!strcmp(name, "entnum")) || (!strcmp(name, "autocvar")) || (!strcmp(name, "used_model")) || (!strcmp(name, "used_sound")) || (!strcmp(name, "va_arg")) || (!strcmp(name, "...")) || //for compat. otherwise wtf? (!strcmp(name, "_")) ) //intrinsics, any old function with no args will do. { d = QCC_PR_GetSRef (type_function, name, NULL, true, 0, false); // d->initialized = 0; } else if ( (!strcmp(name, "random" )) ) //intrinsics, any old function with no args will do. returning a float just in case people declare things in the wrong order { d = QCC_PR_GetSRef (type_floatfunction, name, NULL, true, 0, false); // d.sym->initialized = 0; } else if (keyword_class && !strcmp(name, "this")) { if (!pr_classtype) QCC_PR_ParseError(ERR_NOTANAME, "Cannot use 'this' outside of an OO function\n"); d = QCC_PR_GetSRef(type_entity, "self", NULL, true, 0, false); d.cast = pr_classtype; } else if (keyword_class && !strcmp(name, "super")) { if (!assumeclass) QCC_PR_ParseError(ERR_NOTANAME, "Cannot use 'super' outside of an OO function\n"); if (!assumeclass->parentclass) QCC_PR_ParseError(ERR_NOTANAME, "class %s has no super\n", pr_classtype->name); d = QCC_PR_GetSRef(NULL, "self", NULL, true, 0, false); d.cast = assumeclass->parentclass; } else if (pr_assumetermtype) { d = QCC_PR_GetSRef (pr_assumetermtype, name, pr_assumetermscope, true, 0, pr_assumetermflags); if (!d.cast) QCC_PR_ParseError (ERR_UNKNOWNVALUE, "Unknown value \"%s\"", name); } else { d = QCC_PR_GetSRef (type_variant, name, pr_scope, true, 0, false); if (!expandmemberfields && assumeclass) { if (!d.cast) QCC_PR_ParseError (ERR_UNKNOWNVALUE, "Unknown field \"%s\" in class \"%s\"", name, assumeclass->name); else if (!assumeclass->parentclass && assumeclass != type_entity) { QCC_PR_ParseWarning (ERR_UNKNOWNVALUE, "Class \"%s\" is not defined, cannot access member \"%s\"", assumeclass->name, name); if (!autoprototype && !autoprototyped) QCC_PR_Note(ERR_UNKNOWNVALUE, s_filen, pr_source_line, "Consider using #pragma autoproto"); } else { QCC_PR_ParseWarning (ERR_UNKNOWNVALUE, "Unknown field \"%s\" in class \"%s\"", name, assumeclass->name); } } else { if (!d.cast) QCC_PR_ParseError (ERR_UNKNOWNVALUE, "Unknown value \"%s\"", name); else { QCC_PR_ParseWarning (ERR_UNKNOWNVALUE, "Unknown value \"%s\".", name); } } } } } d.sym->referenced = true; //class code uses self as though it was 'this'. its a hack, but this is QC. if (assumeclass && pr_classtype && name && !strcmp(name, "self")) { //use 'this' instead. QCC_sref_t t = QCC_PR_GetSRef(NULL, "this", pr_scope, false, 0, false); if (!t.cast) //shouldn't happen. { t.sym = QCC_PR_DummyDef(pr_classtype, "this", pr_scope, 0, d.sym, 0, true, GDF_CONST|GDF_STRIP|GDF_ALIAS); t.cast = pr_classtype; t.ofs = 0; } else QCC_FreeTemp(d); d = t; QCC_PR_ParseWarning (WARN_SELFNOTTHIS, "'self' used inside OO function, use 'this'."); } if (!d.cast) QCC_PR_ParseError (ERR_INTERNAL, "d.cast == NULL"); //within class functions, refering to fields should use them directly. if (pr_classtype && expandmemberfields && d.cast->type == ev_field) { QCC_sref_t t; if (assumeclass) { t = QCC_PR_GetSRef(NULL, "this", pr_scope, false, 0, false); if (!t.cast) { t.sym = QCC_PR_DummyDef(pr_classtype, "this", pr_scope, 0, QCC_PR_GetDef(NULL, "self", NULL, true, 0, false), 0, true, GDF_CONST|GDF_STRIP|GDF_ALIAS); //create a union into it t.cast = pr_classtype; t.ofs = 0; } } else t = QCC_PR_GetSRef(NULL, "self", NULL, true, 0, false); if (d.sym->arraysize) { QCC_DefToRef(refbuf, d); refbuf->type = REF_ARRAYHEAD; refbuf->arraysize = d.sym->arraysize; refbuf->cast = QCC_PointerTypeTo(refbuf->cast); d = QCC_RefToDef(QCC_PR_ParseRefArrayPointer(refbuf, refbuf, allowarrayassign, makearraypointers), true); } else d = QCC_PR_ParseArrayPointer(d, allowarrayassign, makearraypointers); //opportunistic vecmember[0] handling //then return a reference to this.field QCC_PR_BuildRef(refbuf, REF_FIELD, t, d, d.cast->aux_type, false, 0); //(a.(foo[4]))[2] should still function, and may be common with field vectors return QCC_PR_ParseRefArrayPointer(refbuf, refbuf, allowarrayassign, makearraypointers); //opportunistic vecmember[0] handling } t = d.cast; if (d.sym->arraysize) { QCC_DefToRef(refbuf, d); refbuf->type = REF_ARRAYHEAD; refbuf->arraysize = d.sym->arraysize; refbuf->cast = QCC_PointerTypeTo(refbuf->cast); r = QCC_PR_ParseRefArrayPointer(refbuf, refbuf, allowarrayassign, makearraypointers); if (r->type == REF_ARRAYHEAD && flag_brokenarrays) { r->type = REF_GLOBAL; r->cast = r->cast->aux_type; } /*if (r->type == REF_ARRAYHEAD) { r->type = REF_GLOBAL; return QCC_PR_GenerateAddressOf(refbuf, r); }*/ return r; } else if (t->type == ev_union && t->num_parms == 1 && !t->params[0].paramname) //FIXME: this destroys type info required for sizeof, and breaks struct lvalues. { //convert it to a proper array, instead of leaving as a simple global. QCC_DefToRef(refbuf, d); refbuf->type = REF_ARRAYHEAD; refbuf->arraysize = t->params[0].arraysize; refbuf->cast = QCC_PointerTypeTo(t->params[0].type); return QCC_PR_ParseRefArrayPointer(refbuf, refbuf, allowarrayassign, makearraypointers); } else if (d.sym->autoderef) { t = t->aux_type; if (t->type == ev_union && t->num_parms == 1 && !t->params[0].paramname) //FIXME: this destroys type info required for sizeof, and breaks struct lvalues. { r = QCC_PR_BuildRef(refbuf, REF_POINTERARRAY, d, nullsref, QCC_PointerTypeTo(t->params[0].type), false, 0); //it points to an array, which resolves to a pointer to its base type... r->arraysize = t->params[0].arraysize; //make sure it has the proper size etc in case someone typedefs it. } else r = QCC_PR_BuildRef(refbuf, REF_POINTER, d, nullsref, t, false, 0); // just dereference it before parsing any array/fields/etc stuff. } else r = QCC_DefToRef(refbuf, d); return QCC_PR_ParseRefArrayPointer(refbuf, r, allowarrayassign, makearraypointers); } //true if its NOT 0 QCC_sref_t QCC_PR_GenerateLogicalTruth(QCC_sref_t e, const char *errormessage) { etype_t t; QCC_type_t *type = e.cast; while(type->type == ev_accessor || type->type == ev_boolean) type = type->parentclass; t = type->type; if (t == ev_float) return QCC_PR_Statement (&pr_opcodes[OP_NE_F], e, QCC_MakeFloatConst(0), NULL); else if (t == ev_string) return QCC_PR_Statement (&pr_opcodes[flag_brokenifstring?OP_NE_E:OP_NE_S], e, QCC_MakeIntConst(0), NULL); else if (t == ev_entity) return QCC_PR_Statement (&pr_opcodes[OP_NE_E], e, QCC_MakeIntConst(0), NULL); else if (t == ev_vector) return QCC_PR_Statement (&pr_opcodes[OP_NE_V], e, QCC_MakeVectorConst(0,0,0), NULL); else if (t == ev_function) return QCC_PR_Statement (&pr_opcodes[OP_NE_FNC], e, QCC_MakeIntConst(0), NULL); else if (t == ev_integer || t == ev_uint) return QCC_PR_Statement (&pr_opcodes[OP_NE_I], e, QCC_MakeIntConst(0), NULL); //functions are integer values too. else if (t == ev_pointer) return QCC_PR_Statement (&pr_opcodes[OP_NE_I], e, QCC_MakeIntConst(0), NULL); //Pointers are too. else if (t == ev_double) return QCC_PR_Statement (&pr_opcodes[OP_NE_D], e, QCC_MakeDoubleConst(0), NULL); else if (t == ev_int64) return QCC_PR_Statement (&pr_opcodes[OP_NE_I64], e, QCC_MakeInt64Const(0), NULL); else if (t == ev_uint64) return QCC_PR_Statement (&pr_opcodes[OP_NE_U64], e, QCC_MakeUInt64Const(0), NULL); else if (t == ev_void && flag_laxcasts) { QCC_PR_ParseWarning(WARN_LAXCAST, errormessage, "void"); return QCC_PR_Statement (&pr_opcodes[OP_NE_F], e, QCC_MakeFloatConst(0), NULL); } else { char etype[256]; TypeName(e.cast, etype, sizeof(etype)); QCC_PR_ParseError (ERR_BADNOTTYPE, errormessage, etype); return nullsref; } } QCC_sref_t QCC_PR_GenerateLogicalNot(QCC_sref_t e, const char *errormessage) { etype_t t; QCC_type_t *type = e.cast; while(type->type == ev_accessor || type->type == ev_boolean) type = type->parentclass; t = type->type; if (t == ev_float) return QCC_PR_Statement (&pr_opcodes[OP_NOT_F], e, nullsref, NULL); else if (t == ev_string) return QCC_PR_Statement (&pr_opcodes[flag_brokenifstring?OP_NOT_ENT:OP_NOT_S], e, nullsref, NULL); else if (t == ev_entity) return QCC_PR_Statement (&pr_opcodes[OP_NOT_ENT], e, nullsref, NULL); else if (t == ev_vector) return QCC_PR_Statement (&pr_opcodes[OP_NOT_V], e, nullsref, NULL); else if (t == ev_function) return QCC_PR_Statement (&pr_opcodes[OP_NOT_FNC], e, nullsref, NULL); else if (t == ev_integer || t == ev_uint) return QCC_PR_Statement (&pr_opcodes[OP_NOT_I], e, nullsref, NULL); //functions are integer values too. else if (t == ev_pointer) return QCC_PR_Statement (&pr_opcodes[OP_NOT_I], e, nullsref, NULL); //Pointers are too. else if (t == ev_double) return QCC_PR_Statement (&pr_opcodes[OP_EQ_D], e, QCC_MakeDoubleConst(0), NULL); else if (t == ev_int64) return QCC_PR_Statement (&pr_opcodes[OP_EQ_I64], e, QCC_MakeInt64Const(0), NULL); else if (t == ev_uint64) return QCC_PR_Statement (&pr_opcodes[OP_EQ_U64], e, QCC_MakeUInt64Const(0), NULL); else if (t == ev_void && flag_laxcasts) { QCC_PR_ParseWarning(WARN_LAXCAST, errormessage, "void"); return QCC_PR_Statement (&pr_opcodes[OP_NOT_F], e, nullsref, NULL); } else { char etype[256]; TypeName(e.cast, etype, sizeof(etype)); QCC_PR_ParseError (ERR_BADNOTTYPE, errormessage, etype); return nullsref; } } QCC_sref_t QCC_PR_GenerateBitwiseNot(QCC_sref_t e, const char *errormessage) { etype_t t; QCC_type_t *type = e.cast; while(type->type == ev_accessor || type->type == ev_boolean) type = type->parentclass; t = type->type; if (t == ev_float) return QCC_PR_Statement (&pr_opcodes[OP_BITNOT_F], e, nullsref, NULL); else if (t == ev_string) return QCC_PR_Statement (&pr_opcodes[flag_brokenifstring?OP_NOT_ENT:OP_NOT_S], e, nullsref, NULL); // else if (t == ev_entity) // return QCC_PR_Statement (&pr_opcodes[OP_BITNOT_ENT], e, nullsref, NULL); else if (t == ev_vector) return QCC_PR_Statement (&pr_opcodes[OP_BITNOT_V], e, nullsref, NULL); // else if (t == ev_function) // return QCC_PR_Statement (&pr_opcodes[OP_BITNOT_FNC], e, nullsref, NULL); else if (t == ev_integer || t == ev_uint) return QCC_PR_Statement (&pr_opcodes[OP_BITNOT_I], e, nullsref, NULL); //functions are integer values too. else if (t == ev_pointer) return QCC_PR_Statement (&pr_opcodes[OP_BITNOT_I], e, nullsref, NULL); //Pointers are too. else if (t == ev_double) return QCC_PR_Statement (&pr_opcodes[OP_BITNOT_D], e, QCC_MakeDoubleConst(0), NULL); else if (t == ev_int64) return QCC_PR_Statement (&pr_opcodes[OP_BITNOT_I64], e, QCC_MakeInt64Const(0), NULL); else if (t == ev_uint64) return QCC_PR_Statement (&pr_opcodes[OP_BITNOT_U64], e, QCC_MakeUInt64Const(0), NULL); else if (t == ev_void && flag_laxcasts) { QCC_PR_ParseWarning(WARN_LAXCAST, errormessage, "void"); return QCC_PR_Statement (&pr_opcodes[OP_NOT_F], e, nullsref, NULL); } else { char etype[256]; TypeName(e.cast, etype, sizeof(etype)); QCC_PR_ParseError (ERR_BADNOTTYPE, errormessage, etype); return nullsref; } } //doesn't consider parents static QCC_sref_t QCC_TryEvaluateCast(QCC_sref_t src, QCC_type_t *cast, pbool implicit) { QCC_type_t *tmp; int totype; for (tmp = cast; tmp->type == ev_accessor || tmp->type == ev_bitfld; tmp = tmp->parentclass) ; totype = tmp->type; while (src.cast->type == ev_boolean) src.cast = src.cast->parentclass; /*you may cast from a type to itself*/ if (!typecmp(src.cast, cast)) { //no-op } /*you may cast from const 0 to any other basic type for free (from either int or float for simplicity). things get messy when its a struct*/ else if (QCC_SRef_IsNull(src) && totype != ev_struct && totype != ev_union) { QCC_FreeTemp(src); if (cast->size == 3)// || cast->type == ev_variant) src = QCC_MakeVectorConst(0,0,0); else src = QCC_MakeIntConst(0); src.cast = cast; } else if (totype == ev_boolean) { src = QCC_PR_GenerateLogicalTruth(src, "cast to boolean"); //src will often be a float(eg:NQ_F) with a bint totype. make sure we evaluate it fully. return QCC_TryEvaluateCast(src, tmp->parentclass, implicit); } else if (totype == ev_float && src.cast->type == ev_uint) //uint->float src = QCC_PR_Statement (&pr_opcodes[OP_CONV_UF], src, nullsref, NULL); else if (totype == ev_uint && src.cast->type == ev_float) //float->uint src = QCC_PR_Statement (&pr_opcodes[OP_CONV_FU], src, nullsref, NULL); /*cast from int->float will convert*/ else if (totype == ev_float && (src.cast->type == ev_integer || (src.cast->type == ev_entity && !implicit))) { src = QCC_PR_Statement (&pr_opcodes[OP_CONV_ITOF], src, nullsref, NULL); src.cast = cast; } /*cast from float->int will convert*/ else if ((totype == ev_integer || (totype == ev_entity && !implicit)) && src.cast->type == ev_float) { src = QCC_PR_Statement (&pr_opcodes[OP_CONV_FTOI], src, nullsref, NULL); src.cast = cast; } else if ((totype == ev_integer || totype == ev_uint) && (src.cast->type == ev_integer || src.cast->type == ev_uint)) src.cast = cast; //fine, just treat it as-is else if ((totype == ev_int64 || totype == ev_uint64) && (src.cast->type == ev_int64 || src.cast->type == ev_uint64)) src.cast = cast; //fine, just treat it as-is else if (totype == ev_float && src.cast->type == ev_double) src = QCC_PR_Statement (&pr_opcodes[OP_CONV_DF], src, nullsref, NULL); else if (totype == ev_double && src.cast->type == ev_float) src = QCC_PR_Statement (&pr_opcodes[OP_CONV_FD], src, nullsref, NULL); else if (totype == ev_float && src.cast->type == ev_int64) src = QCC_PR_Statement (&pr_opcodes[OP_CONV_I64F], src, nullsref, NULL); else if (totype == ev_int64 && src.cast->type == ev_float) src = QCC_PR_Statement (&pr_opcodes[OP_CONV_FI64], src, nullsref, NULL); else if (totype == ev_float && src.cast->type == ev_uint64) src = QCC_PR_Statement (&pr_opcodes[OP_CONV_U64F], src, nullsref, NULL); else if (totype == ev_uint64 && src.cast->type == ev_float) src = QCC_PR_Statement (&pr_opcodes[OP_CONV_FU64], src, nullsref, NULL); else if (totype == ev_uint64 && src.cast->type == ev_double) src = QCC_PR_Statement (&pr_opcodes[OP_CONV_DU64], src, nullsref, NULL); else if (totype == ev_double && src.cast->type == ev_uint64) src = QCC_PR_Statement (&pr_opcodes[OP_CONV_U64D], src, nullsref, NULL); else if (totype == ev_int64 && src.cast->type == ev_double) src = QCC_PR_Statement (&pr_opcodes[OP_CONV_DI64], src, nullsref, NULL); else if (totype == ev_double && src.cast->type == ev_int64) src = QCC_PR_Statement (&pr_opcodes[OP_CONV_I64D], src, nullsref, NULL); else if (totype == ev_uint && src.cast->type == ev_double) { src = QCC_PR_Statement (&pr_opcodes[OP_CONV_DU64], src, nullsref, NULL); src = QCC_PR_Statement (&pr_opcodes[OP_CONV_I64I], src, nullsref, NULL); src.cast = cast; } else if ((totype == ev_integer||totype == ev_uint) && src.cast->type == ev_double) { src = QCC_PR_Statement (&pr_opcodes[OP_CONV_DI64], src, nullsref, NULL); src = QCC_PR_Statement (&pr_opcodes[OP_CONV_I64I], src, nullsref, NULL); src.cast = cast; } else if (totype == ev_double && (src.cast->type == ev_integer || src.cast->type == ev_uint)) { if (src.cast->type == ev_uint) src = QCC_PR_Statement (&pr_opcodes[OP_CONV_UI64], src, nullsref, NULL); else src = QCC_PR_Statement (&pr_opcodes[OP_CONV_II64], src, nullsref, NULL); src = QCC_PR_Statement (&pr_opcodes[OP_CONV_I64D], src, nullsref, NULL); src.cast = cast; } else if ((totype == ev_int64||totype == ev_uint64) && src.cast->type == ev_uint) { //zero extends. we could emulate this but that means endian issues. src = QCC_PR_Statement (&pr_opcodes[OP_CONV_UI64], src, nullsref, NULL); src.cast = cast; } else if ((totype == ev_int64||totype == ev_uint64) && src.cast->type == ev_integer) { //sign extends src = QCC_PR_Statement (&pr_opcodes[OP_CONV_II64], src, nullsref, NULL); src.cast = cast; } else if ((totype == ev_integer||totype == ev_uint) && (src.cast->type == ev_int64||src.cast->type == ev_uint64)) { //truncates. we could emulate this but that means endian issues. src = QCC_PR_Statement (&pr_opcodes[OP_CONV_I64I], src, nullsref, NULL); src.cast = cast; } else if (totype == ev_integer && (src.cast->type == ev_bitfld && src.cast->parentclass->type == ev_integer)) { //just read out the relevant bits, sign extending. src = QCC_PR_Statement (&pr_opcodes[OP_BITEXTEND_I], src, QCC_MakeUIntConst(src.cast->bits), NULL); src.cast = cast; } else if (totype == ev_uint && (src.cast->type == ev_bitfld && src.cast->parentclass->type == ev_uint)) { //just read out the relevant bits, sign extending. src = QCC_PR_Statement (&pr_opcodes[OP_BITAND_I], src, QCC_MakeUIntConst(~((1<bits)-1)), NULL); src.cast = cast; } else if (totype == ev_vector && src.cast->type == ev_float) { if (implicit) { char typea[256]; char typeb[256]; TypeName(src.cast, typea, sizeof(typea)); TypeName(cast, typeb, sizeof(typeb)); QCC_PR_ParseWarning(WARN_LAXCAST, "Implicit cast from %s%s%s to %s%s%s", col_type,typea,col_none, col_type,typeb,col_none); } src = QCC_PR_Statement (&pr_opcodes[OP_MUL_FV], src, QCC_MakeVectorConst(1,1,1), NULL); } else if (totype == ev_vector && src.cast->type == ev_integer) { if (implicit) { char typea[256]; char typeb[256]; TypeName(src.cast, typea, sizeof(typea)); TypeName(cast, typeb, sizeof(typeb)); QCC_PR_ParseWarning(WARN_LAXCAST, "Implicit cast from %s%s%s to %s%s%s", col_type,typea,col_none, col_type,typeb,col_none); } src = QCC_PR_Statement (&pr_opcodes[OP_MUL_IV], src, QCC_MakeVectorConst(1,1,1), NULL); } else if (totype == ev_entity && src.cast->type == ev_entity) { if (implicit) { //this is safe if the source inherits from the dest type //but we should warn if the other way around QCC_type_t *t = src.cast; while(t) { if (!typecmp_lax(t, cast)) break; t = t->parentclass; } if (!t) { char typea[256]; char typeb[256]; TypeName(src.cast, typea, sizeof(typea)); TypeName(cast, typeb, sizeof(typeb)); QCC_PR_ParseWarning(WARN_LAXCAST, "Implicit cast from %s%s%s to %s%s%s", col_type,typea,col_none, col_type,typeb,col_none); } } src.cast = cast; } else if (flag_undefwordsize && ((totype == ev_pointer && src.cast->type == ev_string) || (totype == ev_pointer && src.cast->type == ev_string))) { //pointer<->string is blocked when pointers are not bytes. if words are 8 bytes then everything is screwed, or if pointers cannot store byte offsets... QCC_PR_ParseWarning(ERR_BADEXTENSION, "pointer<->string casts were disabled."); src.cast = cast; } //string and char* are the same type. don't care if its signed or unsigned. else if (((totype == ev_pointer && tmp->aux_type->type == ev_bitfld && tmp->aux_type->bits == 8) && src.cast->type == ev_string) || (totype == ev_string && (src.cast->type == ev_pointer && src.cast->aux_type->type == ev_bitfld && src.cast->aux_type->bits == 8))) src.cast = cast; //can cast any pointer type to void* else if (totype == ev_pointer && tmp->aux_type->type == ev_void && (src.cast->type == ev_pointer || src.cast->type == ev_function || src.cast->type == ev_string)) src.cast = cast; //can cast void* to any pointer type. else if (src.cast->type == ev_pointer && src.cast->aux_type->type == ev_void && (totype == ev_pointer || totype == ev_function || totype == ev_string)) src.cast = cast; else if (totype == ev_pointer && src.cast->type == ev_pointer) { if (implicit) { //this is safe if the source inherits from the dest type //but we should warn if the other way around QCC_type_t *t = src.cast->aux_type; while(t) { if (!typecmp_lax(t, cast->aux_type)) break; t = t->parentclass; } if (!t) { char typea[256]; char typeb[256]; TypeName(src.cast, typea, sizeof(typea)); TypeName(cast, typeb, sizeof(typeb)); QCC_PR_ParseWarning(WARN_LAXCAST, "Implicit cast from %s%s%s to %s%s%s", col_type,typea,col_none, col_type,typeb,col_none); } } src.cast = cast; } /*variants can be cast from/to anything without warning, even implicitly. FIXME: size issues*/ else if (totype == ev_variant || src.cast->type == ev_variant || (totype == ev_field && totype == src.cast->type && (tmp->aux_type->type == ev_variant || src.cast->aux_type->type == ev_variant))) { src.cast = cast; if (implicit && typecmp_lax(src.cast, cast)) { char typea[256]; char typeb[256]; TypeName(src.cast, typea, sizeof(typea)); TypeName(cast, typeb, sizeof(typeb)); QCC_PR_ParseWarning(WARN_LAXCAST, "Implicit cast from %s%s%s to %s%s%s", col_type,typea,col_none, col_type,typeb,col_none); } } /*these casts are acceptable but probably an error (so warn when implicit)*/ else if ( /*you may explicitly cast between pointers and ints (strings count as pointers - WARNING: some strings may not be expressable as pointers)*/ ((totype == ev_pointer || totype == ev_string || totype == ev_integer || totype == ev_uint || totype == ev_function) && (src.cast->type == ev_pointer || src.cast->type == ev_string || src.cast->type == ev_integer || src.cast->type == ev_uint || src.cast->type == ev_function)) //ents->ints || ints->ents. WARNING: the integer value of ent types is engine specific. || (totype == ev_entity && src.cast->type == ev_integer) || (totype == ev_entity && src.cast->type == ev_float && flag_qccx) || (totype == ev_integer && src.cast->type == ev_entity) || (totype == ev_float && src.cast->type == ev_entity && flag_qccx) || (totype == ev_string && src.cast->type == ev_float && flag_qccx) || (totype == ev_float && src.cast->type == ev_string && flag_qccx) ) { //direct cast if (implicit && typecmp_lax(src.cast, cast)) { char typea[256]; char typeb[256]; TypeName(src.cast, typea, sizeof(typea)); TypeName(cast, typeb, sizeof(typeb)); QCC_PR_ParseWarning(WARN_LAXCAST, "Implicit cast from %s%s%s to %s%s%s", col_type,typea,col_none, col_type,typeb,col_none); } src.cast = cast; } else if (!implicit && !typecmp_lax(src.cast, cast)) src.cast = cast; else if (!implicit && cast->type == ev_void) src.cast = type_void; //anything can be cast to void, but only do it explicitly. else //failed return nullsref; return src; } QCC_sref_t QCC_EvaluateCast(QCC_sref_t src, QCC_type_t *cast, pbool implicit) { QCC_sref_t r; if ( (cast->type == ev_accessor && cast->parentclass == src.cast) || (src.cast->type == ev_accessor && src.cast->parentclass == cast)) { if (implicit) { char typea[256]; char typeb[256]; TypeName(src.cast, typea, sizeof(typea)); TypeName(cast, typeb, sizeof(typeb)); QCC_PR_ParseWarning(0, "Implicit cast from %s%s%s to %s%s%s", col_type,typea,col_none, col_type,typeb,col_none); } src.cast = cast; return src; } //casting from an accessor uses the base type of that accessor (this allows us to properly read void* accessors) for(;;) { r = QCC_TryEvaluateCast(src, cast, implicit); if (r.cast) return r; //success if (src.cast->type == ev_accessor || src.cast->type == ev_bitfld) src.cast = src.cast->parentclass; else if (flag_laxcasts) { if (implicit) { char typea[256]; char typeb[256]; TypeName(src.cast, typea, sizeof(typea)); TypeName(cast, typeb, sizeof(typeb)); QCC_PR_ParseWarning(0, "Implicit lax cast from %s%s%s to %s%s%s", col_type,typea,col_none, col_type,typeb,col_none); } r = src; r.cast = cast; //decompilers suck return r; } else { char typea[256]; char typeb[256]; TypeName(src.cast, typea, sizeof(typea)); TypeName(cast, typeb, sizeof(typeb)); QCC_PR_ParseError(0, "Cannot cast from %s%s%s to %s%s%s", col_type,typea,col_none, col_type, typeb,col_none); } } } /* ============ PR_Term ============ */ static QCC_ref_t *QCC_PR_RefTerm (QCC_ref_t *retbuf, unsigned int exprflags) { QCC_ref_t *r; QCC_sref_t e, e2; if (pr_token_type == tt_punct) //a little extra speed... { int preinc; if (QCC_PR_CheckToken("++")) preinc = 1; else if (QCC_PR_CheckToken("--")) preinc = -1; else preinc = 0; if (preinc) { QCC_ref_t tmp; qcc_usefulstatement=true; r = QCC_PR_RefTerm (&tmp, 0); //parse the term, as if we were going to return it if (r->readonly) QCC_PR_ParseError(ERR_BADPLUSPLUSOPERATOR, "%s operator on read-only value", (preinc>0)?"++":"--"); e = QCC_RefToDef(r, false); //read it as needed if (e.sym->constant) { QCC_PR_ParseWarning(WARN_ASSIGNMENTTOCONSTANT, "Assignment to constant %s", QCC_GetSRefName(e)); QCC_PR_ParsePrintSRef(WARN_ASSIGNMENTTOCONSTANT, e); } // if (e.sym->temp && r->type == REF_GLOBAL) // QCC_PR_ParseWarning(WARN_ASSIGNMENTTOCONSTANT, "Hey! That's a temp! ++ operators cannot work on temps!"); switch (e.cast->type) { case ev_integer: e = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], e, QCC_MakeIntConst(preinc), NULL); break; case ev_uint: e = QCC_PR_Statement(&pr_opcodes[OP_ADD_U], e, QCC_MakeIntConst(preinc), NULL); break; case ev_pointer: e = QCC_PR_Statement(&pr_opcodes[OP_ADD_PIW], e, QCC_MakeIntConst(preinc * e.cast->aux_type->size), NULL); break; case ev_float: e = QCC_PR_Statement(&pr_opcodes[OP_ADD_F], e, QCC_MakeFloatConst(preinc), NULL); break; case ev_double: e = QCC_PR_Statement(&pr_opcodes[OP_ADD_D], e, QCC_MakeDoubleConst(preinc), NULL); break; case ev_int64: e = QCC_PR_Statement(&pr_opcodes[OP_ADD_I64], e, QCC_MakeInt64Const(preinc), NULL); break; case ev_uint64: e = QCC_PR_Statement(&pr_opcodes[OP_ADD_U64], e, QCC_MakeInt64Const(preinc), NULL); break; default: QCC_PR_ParseError(ERR_BADPLUSPLUSOPERATOR, "++ operator on unsupported type"); break; } //return the 'result' of the store. its read-only now. return QCC_DefToRef(retbuf, QCC_StoreSRefToRef(r, e, true, false)); } if (QCC_PR_CheckToken ("!")) { e = QCC_PR_Expression (NOT_PRIORITY, EXPR_DISALLOW_COMMA|EXPR_WARN_ABOVE_1); e = QCC_PR_GenerateLogicalNot(e, "Type mismatch: !%s"); return QCC_DefToRef(retbuf, e); } if (QCC_PR_CheckToken ("~")) { e = QCC_PR_Expression (NOT_PRIORITY, EXPR_DISALLOW_COMMA|EXPR_WARN_ABOVE_1); e = QCC_PR_GenerateBitwiseNot(e, "Type mismatch: ~%s"); return QCC_DefToRef(retbuf, e); } if (QCC_PR_CheckToken ("&")) { r = QCC_PR_RefExpression (retbuf, UNARY_PRIORITY, EXPR_DISALLOW_COMMA); if (flag_qccx && r->cast->type == ev_float) { //&%342 casts it to a (pre-dereferenced) pointer. r = QCC_PR_BuildRef(retbuf, REF_POINTER, QCC_RefToDef(r, true), nullsref, type_float, false, 0); } else if (flag_qccx && (r->cast->type == ev_string || r->cast->type == ev_field || r->cast->type == ev_entity || r->cast->type == ev_function)) { //&string casts it to a float. does not dereference it r->cast = type_float; } else { if (r->base.sym->temp && r->type != REF_POINTER) QCC_PR_ParseWarning(ERR_INTERNAL, "Address-of temp on line %s:%i", s_filen, pr_source_line); else r = QCC_PR_GenerateAddressOf(retbuf, r); } return r; } if (QCC_PR_CheckToken ("*")) { e = QCC_PR_Expression (UNARY_PRIORITY, EXPR_DISALLOW_COMMA); if (flag_qccx && (e.cast->type == ev_float || e.cast->type == ev_integer)) { //just an evil cast. note that qccx assumes offsets rather than indexes, so these are often quite large and typically refer to some index into the world entity. return QCC_PR_BuildRef(retbuf, REF_GLOBAL, e, nullsref, type_entity, false, 0); } else if (e.cast->type == ev_pointer) //FIXME: arrays return QCC_PR_BuildRef(retbuf, REF_POINTER, e, nullsref, e.cast->aux_type, false, 0); else if (e.cast->type == ev_string) //FIXME: arrays return QCC_PR_BuildRef(retbuf, REF_STRING, e, nullsref, type_integer, false, 0); else if (e.cast->type == ev_function) { // (*funcptr)(args); is one way to call a function pointer in C, but the * is actually irrelevant. if (flag_qcfuncs) QCC_PR_ParseErrorPrintSRef (ERR_TYPEMISMATCH, e, "attempt to dereference function type."); return QCC_PR_BuildRef(retbuf, REF_GLOBAL, e, nullsref, e.cast, false, 0); } else if (e.cast->accessors) { struct accessor_s *acc; for (acc = e.cast->accessors; acc; acc = acc->next) if (!strcmp(acc->fieldname, "")) return QCC_PR_BuildAccessorRef(retbuf, e, nullsref, acc, e.sym->constant); } QCC_PR_ParseErrorPrintSRef (ERR_TYPEMISMATCH, e, "Unable to dereference non-pointer type."); } if (flag_qccx && QCC_PR_CheckToken ("@")) { //@foo is equivelent to (string)*(int*)&foo r = QCC_PR_RefExpression (retbuf, UNARY_PRIORITY, EXPR_DISALLOW_COMMA); if (r->cast->type == ev_float || r->cast->type == ev_integer) { r->cast = type_string; return r; } QCC_PR_ParseErrorPrintSRef (ERR_BADEXTENSION, QCC_RefToDef(r, true), "'@' operator only functions on floats and ints. go figure."); } if (QCC_PR_CheckToken ("-")) { QCC_type_t *type; e = QCC_PR_Expression (UNARY_PRIORITY, EXPR_DISALLOW_COMMA); type = e.cast; while (type->type == ev_boolean) type = type->parentclass; switch(type->type) { case ev_float: e2 = QCC_PR_Statement (&pr_opcodes[OP_SUB_F], QCC_MakeFloatConst(0), e, NULL); break; case ev_double: e2 = QCC_PR_Statement (&pr_opcodes[OP_SUB_D], QCC_MakeDoubleConst(0), e, NULL); break; case ev_vector: e2 = QCC_PR_Statement (&pr_opcodes[OP_SUB_V], QCC_MakeVectorConst(0, 0, 0), e, NULL); break; case ev_integer: e2 = QCC_PR_Statement (&pr_opcodes[OP_SUB_I], QCC_MakeIntConst(0), e, NULL); break; case ev_uint: e2 = QCC_PR_Statement (&pr_opcodes[OP_SUB_U], QCC_MakeIntConst(0), e, NULL); break; case ev_int64: e2 = QCC_PR_Statement (&pr_opcodes[OP_SUB_I64], QCC_MakeInt64Const(0), e, NULL); break; case ev_uint64: e2 = QCC_PR_Statement (&pr_opcodes[OP_SUB_U64], QCC_MakeInt64Const(0), e, NULL); break; default: QCC_PR_ParseError (ERR_BADNOTTYPE, "type mismatch for -"); e2 = nullsref; break; } return QCC_DefToRef(retbuf, e2); } if (QCC_PR_CheckToken ("+")) { e = QCC_PR_Expression (UNARY_PRIORITY, EXPR_DISALLOW_COMMA); switch(e.cast->type) { case ev_float: case ev_double: case ev_vector: case ev_integer: case ev_uint: case ev_int64: case ev_uint64: e2 = e; break; default: QCC_PR_ParseError (ERR_BADNOTTYPE, "type mismatch for +"); e2 = nullsref; break; } return QCC_DefToRef(retbuf, e2); } if (QCC_PR_CheckToken ("(")) { QCC_type_t *newtype; newtype = QCC_PR_ParseType(false, true, false); if (newtype) { /*if (QCC_PR_Check ("[")) { QCC_PR_Expect ("]"); QCC_PR_Expect(")"); QCC_PR_Expect("{"); QCC_PR_Expect ("}"); return array; }*/ QCC_PR_Expect (")"); if (QCC_PR_PeekToken("{")) { if (newtype->type == ev_vector) { QCC_sref_t x,y,z; QCC_PR_Expect("{"); x = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); QCC_PR_Expect(","); y = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); QCC_PR_Expect(","); z = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); QCC_PR_Expect ("}"); return QCC_DefToRef(retbuf, QCC_PR_GenerateVector(x,y,z)); } else { e = QCC_PR_ParseInitializerTemp(newtype); if (newtype->type == ev_function && QCC_PR_PeekToken("(")) e.sym->allowinline = true; } } else { //not a single term, so we can cast the result of function calls. just make sure its not too high a priority //and yeah, okay, use defs not refs. whatever. e = QCC_PR_Expression (UNARY_PRIORITY, EXPR_DISALLOW_COMMA); e = QCC_EvaluateCast(e, newtype, false); } return QCC_DefToRef(retbuf, e); } else { pbool oldcond = conditional; conditional = conditional?2:0; r = QCC_PR_RefExpression(retbuf, TOP_PRIORITY, 0); QCC_PR_Expect (")"); conditional = oldcond; // QCC_PR_ParseArrayPointer(r, true, true); r = QCC_PR_ParseRefArrayPointer(retbuf, r, true, true); } return r; } } if (pr_token_type == tt_name) //a little extra speed... { if (QCC_PR_CheckKeyword(true, "sizeof")) //'returns size_t', which we interpret as uintptr_t aka uint32_t for us. { QCC_type_t *t; pbool bracket = QCC_PR_CheckToken("("); t = QCC_PR_ParseType(false, true, false); if (t) { if (flag_undefwordsize) QCC_PR_ParseWarning(ERR_BADEXTENSION, "sizeof was disabled, use array.length instead."); if (bracket) QCC_PR_Expect(")"); if (t->bits&31) return QCC_DefToRef(retbuf, QCC_MakeUIntConst(t->bits/8)); else //word-aligned types should use PIW to get byte counts, but its not necessarily meaningful, so be efficient instead. return QCC_DefToRef(retbuf, QCC_MakeUIntConst(t->size*VMWORDSIZE)); //return QCC_DefToRef(retbuf, QCC_PR_Statement(&pr_opcodes[OP_ADD_PIW], QCC_MakeIntConst(0), QCC_MakeIntConst(t->size), NULL)); } else { int sz; int oldstcount = numstatements; QCC_ref_t refbuf, *r; r = QCC_PR_RefExpression(&refbuf, TOP_PRIORITY, 0); //formulas are accepted weirdly enough, its not just terms. if (r->type == REF_GLOBAL && r->base.sym->type == type_string && !strcmp(r->base.sym->name, "IMMEDIATE")) sz = strlen(&strings[QCC_SRef_EvalConst(r->base)->string]) + 1; //sizeof("hello") includes the null, and is bytes not codepoints else { if ((r->cast->align&7)) QCC_PR_ParseWarning(ERR_BADEXTENSION, "sizeof on bit-field is invalid"); else if (flag_undefwordsize) QCC_PR_ParseWarning(ERR_BADEXTENSION, "sizeof was disabled."); t = r->cast; if (r->type == REF_ARRAYHEAD || r->type == REF_POINTERARRAY) t = t->aux_type; // else if (r->cast->type == ev_pointer) // QCC_PR_ParseWarning(WARN_IGNOREDKEYWORD, "sizeof on pointer? (%i)", r->type); if (t->bits) sz = t->bits/8; else sz = VMWORDSIZE*t->size; //4 bytes per word. we don't support char/short (our string type is logically char*) if (r->type == REF_ARRAYHEAD || r->type == REF_POINTERARRAY) sz *= r->arraysize; } QCC_FreeTemp(r->base); if (r->index.cast) QCC_FreeTemp(r->index); //the term should not have side effects, or generate any actual statements. numstatements = oldstcount; if (bracket) QCC_PR_Expect(")"); return QCC_DefToRef(retbuf, QCC_MakeUIntConst(sz)); } } /*if (QCC_PR_CheckKeyword(keyword_new, "new")) { //note: C++ requires struct-based classes rather than entity ones. const char *cname = QCC_PR_ParseName(); QCC_type_t *rettype = QCC_TypeForName(cname); QCC_sref_t result, func; if (!rettype || rettype->type != ev_entity) QCC_PR_ParseError (ERR_TYPEMISMATCHPARM, "new %s() unsupported argument type for intrinsic", cname); e = QCC_PR_GetSRef(NULL, "spawn", NULL, 0, 0, 0); if (!e.cast) QCC_PR_ParseError (ERR_TYPEMISMATCHPARM, "new %s() spawn builtin not defined", cname); result = QCC_PR_GenerateFunctionCallRef(nullsref, e, NULL,0); result.cast = rettype; if (QCC_PR_CheckToken("(")) { //arglist is optional, apparently char genfunc[256]; //do field assignments. while(QCC_PR_CheckToken(",")) { QCC_sref_t f, p, v; f = QCC_PR_ParseValue(rettype, false, false, true); if (f.cast->type != ev_field) QCC_PR_ParseError(0, "Named field is not a field."); if (QCC_PR_CheckToken("=")) //allow : or = as a separator, but throw a warning for = QCC_PR_ParseWarning(0, "That = should be a :"); //rejecting = helps avoid qcc bugs. :P else QCC_PR_Expect(":"); v = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); p = QCC_PR_StatementFlags(&pr_opcodes[OP_ADDRESS], result, f, NULL, STFL_PRESERVEA); if (v.cast->size == 3) QCC_FreeTemp(QCC_PR_Statement(&pr_opcodes[OP_STOREP_V], v, p, NULL)); else QCC_FreeTemp(QCC_PR_Statement(&pr_opcodes[OP_STOREP_F], v, p, NULL)); } QCC_PR_Expect(")"); QC_snprintfz(genfunc, sizeof(genfunc), "spawnfunc_%s", rettype->name); func = QCC_PR_GetSRef(type_function, genfunc, NULL, true, 0, GDF_CONST); func.sym->referenced = true; QCC_UnFreeTemp(result); QCC_FreeTemp(QCC_PR_GenerateFunctionCallRef(result, func, NULL, 0)); result.cast = rettype; } return QCC_DefToRef(retbuf, result); }*/ if (QCC_PR_CheckKeyword(true, "_length")) { //for compat with gmqcc. use array.length in fte instead. pbool bracket = QCC_PR_CheckToken("("); /*QCC_type_t *t; t = QCC_PR_ParseType(false, true); if (t) { if (bracket) QCC_PR_Expect(")"); return QCC_DefToRef(retbuf, QCC_PR_Statement(&pr_opcodes[OP_ADD_PIW], QCC_MakeIntConst(0), QCC_MakeIntConst(t->size), NULL)); } else*/ { int sz = 0; int oldstcount = numstatements; QCC_ref_t refbuf, *r; r = QCC_PR_RefExpression(&refbuf, TOP_PRIORITY, EXPR_DISALLOW_COMMA); if (r->type == REF_ARRAYHEAD || r->type == REF_POINTERARRAY) sz = r->arraysize; else if (r->cast == type_string) { QCC_sref_t d = QCC_RefToDef(r, false); const QCC_eval_t *c = QCC_SRef_EvalConst(d); if (c) sz = strlen(&strings[c->string]); //_length("hello") does NOT include the null (like strlen), but is bytes not codepoints else QCC_PR_ParseError (ERR_TYPEMISMATCHPARM, "_length(string) requires an initialised constant"); } else if (r->cast == type_vector) sz = 3; //might as well. considering that vectors can be indexed as an array. else QCC_PR_ParseError (ERR_TYPEMISMATCHPARM, "_length() unsupported argument type for intrinsic"); QCC_FreeTemp(r->base); if (r->index.cast) QCC_FreeTemp(r->index); //the term should not have side effects, or generate any actual statements. numstatements = oldstcount; if (bracket) QCC_PR_Expect(")"); return QCC_DefToRef(retbuf, QCC_MakeIntConst(sz)); } } } return QCC_PR_ParseRefValue (retbuf, pr_classtype, !(exprflags&EXPR_DISALLOW_ARRAYASSIGN), true, true); } static int QCC_NumericTypeRanking(etype_t t) { switch(t) { case ev_double: return 15; case ev_float: return 10; //large gap, to try to de-prioritize the opcodes that mix float and int types. case ev_uint64: return 6; case ev_int64: return 3; case ev_uint: return 1; case ev_integer:return 0; default: return -1; //unranked } } //type promotions: //identity=0 > inheritance=1 > null=2 > double=3 > float=4 > uint64=5 > int64=6 > uint=7 > int=8 > short=9 > char=10 > variant=11 > vector=12 > failure static int QCC_canConv(QCC_sref_t from, etype_t to) { int frompri; int topri; while(from.cast->type == ev_accessor || from.cast->type == ev_boolean) //no opcodes use accessors. convert to their real type. from.cast = from.cast->parentclass; if (from.cast->type == to) return 0; //identity if (pr_classtype) { if (from.cast->type == ev_field) { if (from.cast->aux_type->type == to) return 1; } } if (QCC_SRef_IsNull(from)) return 2; frompri = QCC_NumericTypeRanking(from.cast->type); topri = QCC_NumericTypeRanking(to); if (frompri >= 0 && topri >= 0) { //3...12 if (topri >= frompri) return 3+(topri-frompri); else return 150+(frompri-topri); } //somewhat high penalty, ensures the other side is correct if (to == ev_variant) return 9; if (from.cast->type == ev_variant) return 10; //triggers a warning. if (from.cast->type == ev_float && to == ev_vector) return 200; //triggers a warning. conversion works by using _x if (from.cast->type == ev_vector && to == ev_float) return 201; return -1; } /* ============== QCC_PR_RefExpression ============== */ QCC_ref_t *QCC_PR_BuildAccessorRef(QCC_ref_t *retbuf, QCC_sref_t base, QCC_sref_t index, struct accessor_s *accessor, pbool readonly) { retbuf->postinc = 0; retbuf->type = REF_ACCESSOR; retbuf->base = base; retbuf->index = index; retbuf->accessor = accessor; retbuf->cast = accessor->type; retbuf->readonly = readonly; retbuf->bitofs = 0; retbuf->arraysize = 0; return retbuf; } QCC_ref_t *QCC_PR_BuildRef(QCC_ref_t *retbuf, unsigned int reftype, QCC_sref_t base, QCC_sref_t index, QCC_type_t *cast, pbool readonly, unsigned int bitofs) { retbuf->postinc = 0; retbuf->type = reftype; retbuf->base = base; retbuf->index = index; retbuf->cast = cast?cast:base.cast; retbuf->readonly = readonly; retbuf->accessor = NULL; retbuf->bitofs = bitofs; retbuf->arraysize = base.sym->arraysize; return retbuf; } QCC_ref_t *QCC_DefToRef(QCC_ref_t *retbuf, QCC_sref_t def) { return QCC_PR_BuildRef(retbuf, REF_GLOBAL, def, nullsref, def.cast, !def.sym || !!def.sym->constant || def.sym->temp, 0); } /* void QCC_StoreToOffset(int dest, int source, QCC_type_t *type) { //fixme: we should probably handle entire structs or something switch(type->type) { default: case ev_float: QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F, source, dest, 0, false); break; case ev_vector: QCC_PR_SimpleStatement(OP_STORE_V, source, dest, 0, false); break; case ev_entity: QCC_PR_SimpleStatement(OP_STORE_ENT, source, dest, 0, false); break; case ev_string: QCC_PR_SimpleStatement(OP_STORE_S, source, dest, 0, false); break; case ev_function: QCC_PR_SimpleStatement(OP_STORE_FNC, source, dest, 0, false); break; case ev_field: QCC_PR_SimpleStatement(OP_STORE_FLD, source, dest, 0, false); break; case ev_integer: QCC_PR_SimpleStatement(OP_STORE_I, source, dest, 0, false); break; case ev_pointer: QCC_PR_SimpleStatement(OP_STORE_P, source, dest, 0, false); break; } }*/ static void QCC_StoreToSRef(QCC_sref_t dest, QCC_sref_t source, QCC_type_t *type, pbool preservesource, pbool preservedest) { unsigned int i; int flags = 0; if (preservesource) flags |= STFL_PRESERVEA; if (preservedest) flags |= STFL_PRESERVEB; //fixme: we should probably handle entire structs or something switch(type->type) { default: case ev_struct: case ev_union: case ev_enum: //don't bother trying to optimise any temps here, its not likely to happen anyway. if (QCC_SRef_IsNull(source)) { for (i = 0; i+2 < type->size; i+=3, dest.ofs += 3) QCC_PR_SimpleStatement(&pr_opcodes[OP_STORE_V], QCC_MakeVectorConst(0,0,0), dest, nullsref, false); if (QCC_OPCodeValid(&pr_opcodes[OP_STORE_I64])) for (; i+1 < type->size; i+=2, dest.ofs+=2) QCC_PR_SimpleStatement(&pr_opcodes[OP_STORE_I64], QCC_MakeInt64Const(0), dest, nullsref, false); for (; i < type->size; i++, dest.ofs++) QCC_PR_SimpleStatement(&pr_opcodes[OP_STORE_F], QCC_MakeIntConst(0), dest, nullsref, false); } else { for (i = 0; i+2 < type->size; i+=3, dest.ofs += 3, source.ofs += 3) QCC_PR_SimpleStatement(&pr_opcodes[OP_STORE_V], source, dest, nullsref, false); if (QCC_OPCodeValid(&pr_opcodes[OP_STORE_I64])) for (; i+1 < type->size; i+=2, dest.ofs+=2, source.ofs+=2) QCC_PR_SimpleStatement(&pr_opcodes[OP_STORE_I64], source, dest, nullsref, false); for (; i < type->size; i++, dest.ofs++, source.ofs++) QCC_PR_SimpleStatement(&pr_opcodes[OP_STORE_F], source, dest, nullsref, false); source.ofs -= type->size; } dest.ofs -= type->size; if (!preservesource) QCC_FreeTemp(source); if (!preservedest) QCC_FreeTemp(dest); break; case ev_float: QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], source, dest, NULL, flags)); break; case ev_vector: QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_V], source, dest, NULL, flags)); break; case ev_entity: QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_ENT], source, dest, NULL, flags)); break; case ev_string: QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_S], source, dest, NULL, flags)); break; case ev_function: QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_FNC], source, dest, NULL, flags)); break; case ev_field: QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_FLD], source, dest, NULL, flags)); break; case ev_boolean: QCC_StoreToSRef(dest, source, type->parentclass, preservesource, preservedest); return; case ev_integer: case ev_uint: QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_I], source, dest, NULL, flags)); break; case ev_int64: case ev_uint64: QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_I64], source, dest, NULL, flags)); break; case ev_pointer: QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_P], source, dest, NULL, flags)); break; } } //if readable, returns source (or dest if the store was folded), otherwise returns NULL static QCC_sref_t QCC_CollapseStore(QCC_sref_t dest, QCC_sref_t source, QCC_type_t *type, pbool readable, pbool preservedest) { if (opt_assignments && OpAssignsToC(statements[numstatements-1].op) && source.sym == statements[numstatements-1].c.sym && source.ofs == statements[numstatements-1].c.ofs) { if (source.sym->temp) { QCC_statement_t *statement = &statements[numstatements-1]; statement->c.sym = dest.sym; statement->c.ofs = dest.ofs; dest.sym->referenced = true; optres_assignments++; QCC_FreeTemp(source); if (readable) return dest; if (!preservedest) QCC_FreeTemp(dest); return nullsref; } } QCC_StoreToSRef(dest, source, type, readable, preservedest); if (readable) return source; return nullsref; } static void QCC_StoreToPointer(QCC_sref_t dest, QCC_sref_t idx, QCC_sref_t source, QCC_type_t *type) { pbool freedest = false; if (type->type == ev_variant) type = source.cast; if (type->type == ev_bitfld) { if (type->bits == 8) QCC_PR_Statement_SmallStore(OP_STOREP_I8, source, dest, idx); else if (type->bits == 16) QCC_PR_Statement_SmallStore(OP_STOREP_I16, source, dest, idx); else QCC_PR_ParseError(ERR_TYPEMISMATCH, "ptr-to-bitfld has unsupported bit depth"); if (freedest) QCC_FreeTemp(dest); return; } while (type->type == ev_accessor) type = type->parentclass; if (idx.sym) { if (type->align != 32) { //these all assume word indexes. but our index is actually elements... misalign. eww. idx = QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], idx, QCC_MakeIntConst(32/type->align), NULL, STFL_PRESERVEA); dest = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], dest, idx, NULL, STFL_PRESERVEA); idx = nullsref; } else if (!QCC_OPCode_StorePOffset()) { //can't do an offset store yet... bake any index into the pointer. freedest = true; dest = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_PIW], dest, idx, NULL, STFL_PRESERVEA); idx = nullsref; } } //fixme: we should probably handle entire structs or something switch(type->type) { default: case ev_struct: case ev_union: { unsigned int i = 0; if (QCC_OPCode_StorePOffset()) { //store-with-offset works. unsigned int bits = type->bits; if (!bits) bits = type->size*32; if (0 && bits > 96) //if its going to be bit, we can save some adds by combining pointer and base... but its unsafe where tempbuffers are involved. { dest = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_PIW], dest, idx, NULL, STFL_PRESERVEA); idx = nullsref; } if (QCC_OPCodeValid(&pr_opcodes[OP_STOREP_V])) for (; i+(32*3) <= bits; i+=32*3) { QCC_sref_t newidx = idx.sym?QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], idx, QCC_MakeIntConst(i/32), NULL, STFL_PRESERVEA):QCC_MakeIntConst(i/32); if (QCC_SRef_IsNull(source)) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_V], QCC_MakeVectorConst(0,0,0), dest, newidx, false); else { QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_V], source, dest, newidx, false); source.ofs += 3; } QCC_FreeTemp(newidx); } if (QCC_OPCodeValid(&pr_opcodes[OP_STOREP_I64])) for (; i+(32*2) <= type->size; i+=32*2) { QCC_sref_t newidx = idx.sym?QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], idx, QCC_MakeIntConst(i/32), NULL, STFL_PRESERVEA):QCC_MakeIntConst(i/32); if (QCC_SRef_IsNull(source)) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_I64], QCC_MakeVectorConst(0,0,0), dest, newidx, false); else { QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_I64], source, dest, newidx, false); source.ofs += 2; } QCC_FreeTemp(newidx); } for (; i+32 <= bits; i+=32) { QCC_sref_t newidx = idx.sym?QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], idx, QCC_MakeIntConst(i/32), NULL, STFL_PRESERVEA):QCC_MakeIntConst(i/32); if (QCC_OPCodeValid(&pr_opcodes[OP_STOREP_I])) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_I], source, dest, newidx, false); else QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_F], source, dest, newidx, false); QCC_FreeTemp(newidx); source.ofs += 1; } if (i+16 <= bits) { QCC_sref_t newidx = idx.sym?QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_I], idx, QCC_MakeIntConst(2), NULL, STFL_PRESERVEA), QCC_MakeIntConst(i/16), NULL, 0):QCC_MakeIntConst(i/16); QCC_PR_Statement_SmallStore(OP_STOREP_I16, source, dest, newidx); QCC_FreeTemp(newidx); source.ofs += 1; i+=16; } else if (i+8 <= bits) { QCC_sref_t newidx = idx.sym?QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_PIW], QCC_MakeIntConst(i/8), idx, NULL, STFL_PRESERVEB):QCC_MakeIntConst(i/8); QCC_PR_Statement_SmallStore(OP_STOREP_I8, source, dest, newidx); QCC_FreeTemp(newidx); source.ofs += 1; i+=8; } if (i != bits) QCC_PR_ParseError(ERR_INTERNAL, "Underwrote"); } else { //no store-with-offset. for (i = 0; i+2 < type->size; i+=3) { QCC_sref_t newptr = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_PIW], dest, QCC_MakeIntConst(i), NULL, STFL_PRESERVEA); if (QCC_SRef_IsNull(source)) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_V], QCC_MakeVectorConst(0,0,0), newptr, idx, false); else { QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_V], source, newptr, idx, false); source.ofs += 3; } QCC_FreeTemp(newptr); } if (QCC_OPCodeValid(&pr_opcodes[OP_STOREP_I64])) for (; i+1 < type->size; i+=2) { QCC_sref_t newptr = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_PIW], dest, QCC_MakeIntConst(i), NULL, STFL_PRESERVEA); if (QCC_SRef_IsNull(source)) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_I64], QCC_MakeVectorConst(0,0,0), newptr, idx, false); else { QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_I64], source, newptr, idx, false); source.ofs += 2; } QCC_FreeTemp(newptr); } for (; i < type->size; i++) { QCC_sref_t newptr = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_PIW], dest, QCC_MakeIntConst(i), NULL, STFL_PRESERVEA); if (QCC_SRef_IsNull(source)) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_F], QCC_MakeIntConst(0), newptr, idx, false); else { QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_F], source, newptr, idx, false); source.ofs += 1; } QCC_FreeTemp(newptr); } } } break; case ev_float: QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_F], source, dest, idx, false); break; case ev_vector: QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_V], source, dest, idx, false); break; case ev_entity: QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_ENT], source, dest, idx, false); break; case ev_string: QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_S], source, dest, idx, false); break; case ev_function: QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_FNC], source, dest, idx, false); break; case ev_field: QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_FLD], source, dest, idx, false); break; case ev_integer: case ev_uint: if (QCC_OPCodeValid(&pr_opcodes[OP_STOREP_I])) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_I], source, dest, idx, false); else QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_FLD], source, dest, idx, false); break; case ev_double: case ev_int64: case ev_uint64: if (QCC_OPCodeValid(&pr_opcodes[OP_STOREP_I64])) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_I64], source, dest, idx, false); else { QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_FLD], source, dest, idx, false); if (QCC_OPCode_StorePOffset()) idx = idx.sym?QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], idx, QCC_MakeIntConst(1), NULL, STFL_PRESERVEA):QCC_MakeIntConst(1); else dest = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_PIW], dest, QCC_MakeIntConst(1), NULL, STFL_PRESERVEA); source.ofs+=1; QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_FLD], source, dest, idx, false); } break; case ev_pointer: if (QCC_OPCodeValid(&pr_opcodes[OP_STOREP_P])) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_P], source, dest, idx, false); else if (QCC_OPCodeValid(&pr_opcodes[OP_STOREP_I])) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_I], source, dest, idx, false); else QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_FLD], source, dest, idx, false); break; } if (freedest) QCC_FreeTemp(dest); } static void QCC_StoreToGPointer(QCC_sref_t dest, QCC_sref_t source, QCC_type_t *type) { pbool freedest = false; if (type->type == ev_variant) type = source.cast; while (type->type == ev_accessor) type = type->parentclass; //fixme: we should probably handle entire structs or something switch(type->type) { default: QCC_PR_ParseErrorPrintSRef(ERR_INTERNAL, dest, "QCC_StoreToPointer doesn't know how to store to that type"); case ev_struct: case ev_union: case ev_double: case ev_int64: case ev_uint64: { unsigned int i; for (i = 0; i+2 < type->size; i+=3) { QCC_sref_t newptr = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], dest, QCC_MakeIntConst(i), NULL, STFL_PRESERVEA); if (QCC_SRef_IsNull(source)) QCC_PR_SimpleStatement(&pr_opcodes[OP_GSTOREP_V], QCC_MakeVectorConst(0,0,0), newptr, nullsref, false); else { QCC_PR_SimpleStatement(&pr_opcodes[OP_GSTOREP_V], source, newptr, nullsref, false); source.ofs += 3; } QCC_FreeTemp(newptr); } for (; i < type->size; i++) { QCC_sref_t newptr = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], dest, QCC_MakeIntConst(i), NULL, STFL_PRESERVEA); if (QCC_SRef_IsNull(source)) QCC_PR_SimpleStatement(&pr_opcodes[OP_GSTOREP_F], QCC_MakeIntConst(0), newptr, nullsref, false); else { QCC_PR_SimpleStatement(&pr_opcodes[OP_GSTOREP_F], source, newptr, nullsref, false); source.ofs += 1; } QCC_FreeTemp(newptr); } } break; case ev_float: QCC_PR_SimpleStatement(&pr_opcodes[OP_GSTOREP_F], source, dest, nullsref, false); break; case ev_vector: QCC_PR_SimpleStatement(&pr_opcodes[OP_GSTOREP_V], source, dest, nullsref, false); break; case ev_entity: QCC_PR_SimpleStatement(&pr_opcodes[OP_GSTOREP_ENT], source, dest, nullsref, false); break; case ev_string: QCC_PR_SimpleStatement(&pr_opcodes[OP_GSTOREP_S], source, dest, nullsref, false); break; case ev_function: QCC_PR_SimpleStatement(&pr_opcodes[OP_GSTOREP_FNC], source, dest, nullsref, false); break; case ev_field: QCC_PR_SimpleStatement(&pr_opcodes[OP_GSTOREP_FLD], source, dest, nullsref, false); break; case ev_integer: case ev_uint: case ev_pointer: QCC_PR_SimpleStatement(&pr_opcodes[OP_GSTOREP_I], source, dest, nullsref, false); break; } if (freedest) QCC_FreeTemp(dest); } static QCC_sref_t QCC_LoadFromPointer(QCC_sref_t source, QCC_sref_t idx, QCC_type_t *type) { QCC_sref_t ret; int op; if (type->type == ev_bitfld) { if (type->bits == 8) { ret = QCC_PR_StatementFlags(&pr_opcodes[(type->parentclass->type == ev_integer)?OP_LOADP_I8:OP_LOADP_U8], source, idx, NULL, STFL_PRESERVEA|STFL_PRESERVEB); //get pointer to precise def. ret.cast = type->parentclass; return ret; } else if (type->bits == 16) { ret = QCC_PR_StatementFlags(&pr_opcodes[(type->parentclass->type == ev_integer)?OP_LOADP_I16:OP_LOADP_U16], source, idx, NULL, STFL_PRESERVEA|STFL_PRESERVEB); //get pointer to precise def. ret.cast = type->parentclass; return ret; } QCC_PR_ParseError(ERR_TYPEMISMATCH, "ptr-to-bitfld has unsupported bit depth"); } // if (type->align != 32) // QCC_PR_ParseWarning(ERR_TYPEMISMATCH, "(%s) align is %i, not 32... %ibit", type->name, type->align, type->bits); while (type->type == ev_accessor) type = type->parentclass; switch(type->type) { default: case ev_struct: case ev_union: { unsigned int i; ret = QCC_GetTemp(type); for (i = 0; i+2 < type->size; i+=3) { QCC_sref_t ofs = QCC_MakeIntConst(i); if (idx.sym) ofs = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], idx, ofs, NULL, STFL_PRESERVEA); QCC_PR_SimpleStatement(&pr_opcodes[OP_LOADP_V], source, ofs, ret, false); QCC_FreeTemp(ofs); ret.ofs += 3; } for (; i < type->size; i++) { QCC_sref_t ofs = QCC_MakeIntConst(i); if (idx.sym) ofs = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], idx, ofs, NULL, STFL_PRESERVEA); QCC_PR_SimpleStatement(&pr_opcodes[OP_LOADP_I], source, ofs, ret, false); QCC_FreeTemp(ofs); ret.ofs += 1; } ret.ofs -= type->size; return ret; } break; // case ev_double: op = OP_LOADP_D; break; case ev_int64: op = OP_LOADP_I64; break; case ev_uint64: op = OP_LOADP_I64; break; case ev_float: op = OP_LOADP_F; break; case ev_string: op = OP_LOADP_S; break; case ev_vector: op = OP_LOADP_V; break; case ev_entity: op = OP_LOADP_ENT; break; case ev_field: op = OP_LOADP_FLD; break; case ev_function: op = OP_LOADP_FNC; break; case ev_integer: op = OP_LOADP_I; break; case ev_uint: op = OP_LOADP_I; break; case ev_void: case ev_variant: case ev_bitfld: QCC_PR_ParseError(ERR_TYPEMISMATCH, "ptr-to-variants must be cast to a different type before being read"); op = OP_LOADP_I; break; case ev_pointer: op = OP_LOADP_P; if (!QCC_OPCodeValid(&pr_opcodes[op])) op = OP_LOADP_I; break; } ret = QCC_PR_StatementFlags(&pr_opcodes[op], source, idx, NULL, STFL_PRESERVEA|STFL_PRESERVEB); //get pointer to precise def. ret.cast = type; return ret; } static void QCC_StoreToArray(QCC_sref_t base, QCC_sref_t index, QCC_sref_t source, QCC_type_t *t) { /*if its assigned to, generate a functioncall to do the store*/ QCC_sref_t funcretr; if (QCC_OPCodeValid(&pr_opcodes[OP_GLOBALADDRESS])) { QCC_sref_t addr; //ptr = &base[index]; if (QCC_OPCode_StorePOffset()) addr = QCC_PR_Statement(&pr_opcodes[OP_GLOBALADDRESS], base, nullsref, NULL); else { addr = QCC_PR_Statement(&pr_opcodes[OP_GLOBALADDRESS], base, index, NULL); index = nullsref; } //*ptr = source QCC_StoreToPointer(addr, index.cast?QCC_SupplyConversion(index, ev_integer, true):index, source, t); source.sym->referenced = true; QCC_FreeTemp(addr); QCC_FreeTemp(source); } else if (QCC_OPCodeValid(&pr_opcodes[OP_GSTOREP_F])) { QCC_sref_t addr; //ptr = &base[index]; addr = QCC_DP_GlobalAddress(base, QCC_SupplyConversion(index, ev_integer, true), 0); //*ptr = source QCC_StoreToGPointer(addr, source, t); source.sym->referenced = true; QCC_FreeTemp(addr); QCC_FreeTemp(source); } else { const char *basename = QCC_GetSRefName(base); base.sym->referenced = true; QCC_FreeTemp(base); funcretr = QCC_PR_GetSRef(NULL, qcva("ArraySet*%s", basename), base.sym->scope, false, 0, GDF_CONST|(base.sym->scope?GDF_STATIC:0)); if (!funcretr.cast) { QCC_type_t *arraysetfunc = qccHunkAlloc(sizeof(*arraysetfunc)); struct QCC_typeparam_s *fparms = qccHunkAlloc(sizeof(*fparms)*2); arraysetfunc->size = 1; arraysetfunc->type = ev_function; arraysetfunc->aux_type = type_void; arraysetfunc->params = fparms; arraysetfunc->num_parms = 2; arraysetfunc->name = "ArraySet"; fparms[0].type = type_float; fparms[1].type = base.sym->type; funcretr = QCC_PR_GetSRef(arraysetfunc, qcva("ArraySet*%s", basename), base.sym->scope, true, 0, GDF_CONST|(base.sym->scope?GDF_STATIC:0)); funcretr.sym->generatedfor = base.sym; } if (QCC_SRef_IsNull(source)) { if (base.sym->type->type == ev_vector) source = QCC_MakeVectorConst(0,0,0); else source = QCC_MakeFloatConst(0); } else if (source.cast->type != t->type) { char typea[128], typeb[128]; TypeName(source.cast, typea, sizeof(typea)); TypeName(t, typeb, sizeof(typeb)); QCC_PR_ParseErrorPrintSRef(ERR_TYPEMISMATCH, base, "Type mismatch on indexed assignment of %s: %s %s, needed %s.", basename, typea, QCC_GetSRefName(source), typeb); } if (base.sym->type->type == ev_vector) { //FIXME: we may very well have a *3 already, dividing by 3 again is crazy. index = QCC_PR_Statement(&pr_opcodes[OP_DIV_F], index, QCC_MakeFloatConst(3), NULL); } qcc_usefulstatement=true; QCC_FreeTemp(QCC_PR_GenerateFunctionCall2(nullsref, funcretr, QCC_SupplyConversion(index, ev_float, true), NULL, source, NULL)); } } QCC_sref_t QCC_LoadFromArray(QCC_sref_t base, QCC_sref_t index, QCC_type_t *t, pbool preserve) { int flags; int accel; //1: hexen2's FETCH_GBL opcodes take float indicies, but have a built in boundscheck that wrecks havoc with vectors and structs (and thus sucks when the types don't match) //2: fte's LOADA opcodes use ints without hidden boundcheck (engine will still ensure it reads an actual global), and vectors are not special(needs a separate *3 opcode) so they're struct friendly. //3: dp's GLOAD opcodes -- like fte's but without any offsetting so you need an extra addition. if (index.cast->type != ev_float || t->type != base.cast->type) accel = 2; else accel = 1; if (accel == 2 && !QCC_OPCodeValid(&pr_opcodes[OP_LOADA_F])) accel = QCC_OPCodeValid(&pr_opcodes[OP_GLOAD_F])&&!base.sym->temp?3:1; //chose best for ints if (accel == 1 && (!base.sym->arraylengthprefix || !QCC_OPCodeValid(&pr_opcodes[OP_FETCH_GBL_F]))) accel = QCC_OPCodeValid(&pr_opcodes[OP_LOADA_F])?2:0; //chose best for floats if (accel == 3) { //dp-style, somewhat annoying. if (index.cast->type == ev_integer) flags = preserve?STFL_PRESERVEA|STFL_PRESERVEB:0; else { flags = preserve?STFL_PRESERVEA:0; if (preserve) { flags = STFL_PRESERVEA; QCC_UnFreeTemp(index); } QCC_SupplyConversion(index, ev_integer, true); } index = QCC_DP_GlobalAddress(base, index, flags); switch(t->type) { case ev_string: base = QCC_PR_StatementFlags(&pr_opcodes[OP_GLOAD_S], index, nullsref, NULL, flags); //get pointer to precise def. break; case ev_float: base = QCC_PR_StatementFlags(&pr_opcodes[OP_GLOAD_F], index, nullsref, NULL, flags); //get pointer to precise def. break; case ev_vector: base = QCC_PR_StatementFlags(&pr_opcodes[OP_GLOAD_V], index, nullsref, NULL, flags); //get pointer to precise def. break; case ev_entity: base = QCC_PR_StatementFlags(&pr_opcodes[OP_GLOAD_ENT], index, nullsref, NULL, flags); //get pointer to precise def. break; case ev_field: base = QCC_PR_StatementFlags(&pr_opcodes[OP_GLOAD_FLD], index, nullsref, NULL, flags); //get pointer to precise def. break; case ev_function: base = QCC_PR_StatementFlags(&pr_opcodes[OP_GLOAD_FNC], index, nullsref, NULL, flags); //get pointer to precise def. break; case ev_pointer: case ev_integer: base = QCC_PR_StatementFlags(&pr_opcodes[OP_GLOAD_I], index, nullsref, NULL, flags); //get pointer to precise def. break; case ev_variant: case ev_struct: case ev_union: case ev_int64: case ev_uint64: case ev_double: { QCC_sref_t r; unsigned int i; r = QCC_GetTemp(t); //we can just bias the base offset instead of lots of statements to add to the index. how handy. for (i = 0; i < t->size; ) { if (t->size - i >= 3) { QCC_PR_SimpleStatement(&pr_opcodes[OP_GLOAD_V], index, nullsref, r, false); i+=3; index = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], index, QCC_MakeIntConst(3), NULL); r.ofs+=3; } else { QCC_PR_SimpleStatement(&pr_opcodes[OP_GLOAD_I], index, nullsref, r, false); i++; index = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], index, QCC_MakeIntConst(1), NULL); r.ofs++; } } if (!preserve) QCC_FreeTemp(index); r.ofs -= i; return r; } return nullsref; default: QCC_PR_ParseError(ERR_NOVALIDOPCODES, "Unable to load type %s with GLOAD... oops.", basictypenames[t->type]); return nullsref; } base.cast = t; return base; } else if (accel == 2) { //fte-style, simpler indexing. if (index.cast->type == ev_integer) flags = preserve?STFL_PRESERVEA|STFL_PRESERVEB:0; else { flags = preserve?STFL_PRESERVEA:0; if (preserve) { flags = STFL_PRESERVEA; QCC_UnFreeTemp(index); } index = QCC_SupplyConversion(index, ev_integer, true); } switch(t->type) { case ev_string: base = QCC_PR_StatementFlags(&pr_opcodes[OP_LOADA_S], base, index, NULL, flags); //get pointer to precise def. break; case ev_float: base = QCC_PR_StatementFlags(&pr_opcodes[OP_LOADA_F], base, index, NULL, flags); //get pointer to precise def. break; case ev_vector: base = QCC_PR_StatementFlags(&pr_opcodes[OP_LOADA_V], base, index, NULL, flags); //get pointer to precise def. break; case ev_entity: base = QCC_PR_StatementFlags(&pr_opcodes[OP_LOADA_ENT], base, index, NULL, flags); //get pointer to precise def. break; case ev_field: base = QCC_PR_StatementFlags(&pr_opcodes[OP_LOADA_FLD], base, index, NULL, flags); //get pointer to precise def. break; case ev_function: base = QCC_PR_StatementFlags(&pr_opcodes[OP_LOADA_FNC], base, index, NULL, flags); //get pointer to precise def. break; case ev_pointer: //no OP_LOADA_P case ev_integer: case ev_uint: base = QCC_PR_StatementFlags(&pr_opcodes[OP_LOADA_I], base, index, NULL, flags); //get pointer to precise def. break; case ev_int64: case ev_uint64: case ev_double: base = QCC_PR_StatementFlags(&pr_opcodes[OP_LOADA_I64], base, index, NULL, flags); //get pointer to precise def. break; case ev_bitfld: case ev_variant: case ev_struct: case ev_union: { QCC_sref_t r; unsigned int i; r = QCC_GetTemp(t); //we can just bias the base offset instead of lots of statements to add to the index. how handy. for (i = 0; i < t->size; ) { if (t->size - i >= 3) { QCC_PR_SimpleStatement(&pr_opcodes[OP_LOADA_V], base, index, r, false); i+=3; base.ofs += 3; r.ofs+=3; } else if (t->size - i >= 2 && QCC_OPCodeValid(&pr_opcodes[OP_LOADA_I64])) { QCC_PR_SimpleStatement(&pr_opcodes[OP_LOADA_I64], base, index, r, false); i+=2; base.ofs += 2; r.ofs+=2; } else { QCC_PR_SimpleStatement(&pr_opcodes[OP_LOADA_I], base, index, r, false); i++; base.ofs++; r.ofs++; } } if (!preserve) { QCC_FreeTemp(base); QCC_FreeTemp(index); } r.ofs -= i; base.ofs -= i; return r; } return nullsref; default: QCC_PR_ParseError(ERR_NOVALIDOPCODES, "Unable to load type %s with LOADA... oops.", basictypenames[t->type]); return nullsref; } base.cast = t; return base; } else if (accel == 1) { //hexen2-style, using float indexes and built-in bounds that hurts for arrays in structs. if (!base.sym->arraysize) QCC_PR_ParseErrorPrintSRef(ERR_TYPEMISMATCH, base, "array lookup on non-array"); if (!base.sym->arraylengthprefix) QCC_PR_ParseErrorPrintSRef(ERR_TYPEMISMATCH, base, "array lookup on symbol without length prefix"); if (base.sym->temp) QCC_PR_ParseErrorPrintSRef(ERR_TYPEMISMATCH, base, "array lookup on a temp"); if (index.cast->type == ev_float) flags = preserve?STFL_PRESERVEA|STFL_PRESERVEB:0; else { flags = preserve?STFL_PRESERVEA:0; if (preserve) { flags = STFL_PRESERVEA; QCC_UnFreeTemp(index); } QCC_SupplyConversion(index, ev_float, true); } /*hexen2 format has opcodes to read arrays (but has no way to write)*/ switch(t->type) { case ev_field: case ev_pointer: case ev_integer: case ev_float: base = QCC_PR_StatementFlags(&pr_opcodes[OP_FETCH_GBL_F], base, index, NULL, flags); //get pointer to precise def. base.cast = t; break; case ev_vector: //hexen2 uses element indicies. we internally use words. //words means you can pack vectors into structs without the offset needing to be a multiple of 3. //as its floats, I'm going to try using 0/0.33/0.66 just for the luls //FIXME: we may very well have a *3 already, dividing by 3 again is crazy. index = QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_F], index, QCC_MakeFloatConst(3), NULL, flags&STFL_PRESERVEA); flags &= ~STFL_PRESERVEB; base = QCC_PR_StatementFlags(&pr_opcodes[OP_FETCH_GBL_V], base, index, NULL, flags); //get pointer to precise def. break; case ev_string: base = QCC_PR_StatementFlags(&pr_opcodes[OP_FETCH_GBL_S], base, index, NULL, flags); //get pointer to precise def. break; case ev_entity: base = QCC_PR_StatementFlags(&pr_opcodes[OP_FETCH_GBL_E], base, index, NULL, flags); //get pointer to precise def. break; case ev_function: base = QCC_PR_StatementFlags(&pr_opcodes[OP_FETCH_GBL_FNC], base, index, NULL, flags); //get pointer to precise def. break; default: { QCC_sref_t r, newidx; unsigned int i; r = QCC_GetTemp(t); for (i = 0; i < t->size; ) { //we can't cheese these offsets because OP_FETCH_GBL_ has some built-in bounds check based upon a global just before the start of the array. //I'm going to skip using vector reads, because offsets get weird then, necessitating division ops, and breaking bounds checks. if (i) { newidx = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_F], index, QCC_MakeFloatConst(i), NULL, STFL_PRESERVEA); // if (t->size - i >= 3) // newidx = QCC_PR_StatementFlags(&pr_opcodes[OP_DIV_F], newidx, QCC_MakeFloatConst(3), NULL, 0); } else { newidx = index; QCC_UnFreeTemp(newidx); } /*if (t->size - i >= 3) { QCC_PR_SimpleStatement(&pr_opcodes[OP_FETCH_GBL_V], base, newidx, r, false); i+=3; r.ofs+=3; } else*/ { QCC_PR_SimpleStatement(&pr_opcodes[OP_FETCH_GBL_F], base, newidx, r, false); i++; r.ofs++; } QCC_FreeTemp(newidx); } if (!preserve) { QCC_FreeTemp(base); QCC_FreeTemp(index); } r.ofs -= i; return r; } QCC_PR_ParseError(ERR_NOVALIDOPCODES, "No op available to read array with FETCHGBL"); return nullsref; } base.cast = t; return base; } else { /*emulate the array access using a function call to do the read for us*/ QCC_sref_t args[1], funcretr; base.sym->referenced = true; if (base.cast->type == ev_field && base.sym->constant && !base.sym->initialized && !flag_boundchecks && flag_fasttrackarrays) { int i; //denormalised floats means we could do: //return (add_f: base + (mul_f: index*1i)) //make sure the array has no gaps //the initialised thing is to ensure that it doesn't contain random consecutive system fields that might get remapped weirdly by an engine. for (i = 1; i < base.sym->arraysize; i++) { if (QCC_SRef_DataWord(base, i)->_int != QCC_SRef_DataWord(base, i-1)->_int+1) break; } //its contiguous. we'll do this in two instructions. if (i == base.sym->arraysize) { if (QCC_OPCodeValid(&pr_opcodes[OP_ADD_I])) return QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], base, index, NULL, 0); else { QCC_PR_ParseWarning(WARN_DENORMAL, "using denormals to accelerate field-array access, which is unsafe"); return QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_F], base, QCC_PR_StatementFlags(&pr_opcodes[OP_MUL_F], index, QCC_MakeIntConst(1), NULL, 0), NULL, 0); } } } funcretr = QCC_PR_GetSRef(NULL, qcva("ArrayGet*%s", QCC_GetSRefName(base)), base.sym->scope, false, 0, GDF_CONST|(base.sym->scope?GDF_STATIC:0)); if (!funcretr.cast) { QCC_type_t *ftype = qccHunkAlloc(sizeof(*ftype)); struct QCC_typeparam_s *fparms = qccHunkAlloc(sizeof(*fparms)*1); ftype->size = 1; ftype->type = ev_function; ftype->aux_type = base.cast; ftype->params = fparms; ftype->num_parms = 1; ftype->name = "ArrayGet"; fparms[0].type = type_float; funcretr = QCC_PR_GetSRef(ftype, qcva("ArrayGet*%s", QCC_GetSRefName(base)), base.sym->scope, true, 0, GDF_CONST|(base.sym->scope?GDF_STATIC:0)); funcretr.sym->generatedfor = base.sym; if (!funcretr.sym->constant) externs->Printf("not constant?\n"); } if (preserve) QCC_UnFreeTemp(index); else QCC_FreeTemp(base); /*make sure the function type that we're calling exists*/ if (base.sym->type->type == ev_vector) { //FIXME: we may very well have a *3 already, dividing by 3 again is crazy. args[0] = QCC_PR_Statement(&pr_opcodes[OP_DIV_F], QCC_SupplyConversion(index, ev_float, true), QCC_MakeFloatConst(3), NULL); base = QCC_PR_GenerateFunctionCall1(nullsref, funcretr, args[0], NULL); base.cast = t; } else { if (t->size > 1) { QCC_sref_t r; unsigned int i; int old_op = opt_assignments; base = QCC_GetTemp(t); index = QCC_SupplyConversion(index, ev_float, true); for (i = 0; i < t->size; i++) { if (i) args[0] = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_F], index, QCC_MakeFloatConst(i), NULL, STFL_PRESERVEA); else { QCC_UnFreeTemp(index); args[0] = index; opt_assignments = false; } QCC_UnFreeTemp(funcretr); r = QCC_PR_GenerateFunctionCall1(nullsref, funcretr, args[0], type_float); opt_assignments = old_op; QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], r, base, NULL, STFL_PRESERVEB)); base.ofs++; } QCC_FreeTemp(funcretr); QCC_FreeTemp(index); base.ofs -= i; } else { base = QCC_PR_GenerateFunctionCall1(nullsref, funcretr, QCC_SupplyConversion(index, ev_float, true), type_float); } base.cast = t; } } return base; } static pbool QCC_RefNeedsCalls(QCC_ref_t *ref) { if (ref->type == REF_ACCESSOR) return true; if (ref->type == REF_ARRAY) { if (ref->index.cast) { int accel; if (QCC_SRef_EvalConst(ref->index)) return false; //can short it. if (ref->index.cast->type != ev_float || ref->cast->type != ref->base.cast->type) accel = 2; else accel = 1; if (accel == 2 && !QCC_OPCodeValid(&pr_opcodes[OP_LOADA_F])) accel = QCC_OPCodeValid(&pr_opcodes[OP_GLOAD_F])&&!ref->base.sym->temp?3:1; if (accel == 1 && (!ref->base.sym->arraylengthprefix || !QCC_OPCodeValid(&pr_opcodes[OP_FETCH_GBL_F]))) accel = QCC_OPCodeValid(&pr_opcodes[OP_LOADA_F])?2:0; return !accel; //if we've no acceleration, we need a call. } } return false; } QCC_sref_t QCC_BitfieldToDef(QCC_sref_t field, unsigned int bitofs) { //Note: this assumes the bitfield does not cross its basetype boundary. QCC_type_t *basetype = field.cast->parentclass; unsigned int bits = field.cast->bits; QCC_sref_t mask = QCC_MakeUIntConst((bitofs<<8)|bits); mask.sym->referenced = true; //shift it by the base. if (basetype->type == ev_integer) field = QCC_PR_StatementFlags(&pr_opcodes[OP_BITEXTEND_I], field, mask, NULL, 0); else if (basetype->type == ev_uint) field = QCC_PR_StatementFlags(&pr_opcodes[OP_BITEXTEND_U], field, mask, NULL, 0); else QCC_PR_ParseErrorPrintSRef(ERR_INTERNAL, field, "unsupported bitfield type"); field.cast = basetype; return field; } //reads a ref as required //the result sref should ALWAYS be freed, even if freetemps is set. QCC_sref_t QCC_RefToDef(QCC_ref_t *ref, pbool freetemps) { QCC_sref_t tmp = nullsref, idx; QCC_sref_t ret = ref->base; unsigned int bitofs = ref->bitofs; if (ref->postinc) { int inc = ref->postinc; if (ref->bitofs) QCC_PR_ParseErrorPrintSRef(ERR_INTERNAL, ret, "post increment operator on bitfield"); ref->postinc = 0; //read the value, without preventing the store later ret = QCC_RefToDef(ref, false); //archive off the old value tmp = QCC_GetTemp(ret.cast); QCC_StoreToSRef(tmp, ret, ret.cast, false, true); ret = tmp; //update the value switch(ref->cast->type) { case ev_float: QCC_StoreSRefToRef(ref, QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_F], ret, QCC_MakeFloatConst(inc), NULL, STFL_PRESERVEA), false, !freetemps); break; case ev_double: QCC_StoreSRefToRef(ref, QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_D], ret, QCC_MakeDoubleConst(inc), NULL, STFL_PRESERVEA), false, !freetemps); break; case ev_string: case ev_integer: QCC_StoreSRefToRef(ref, QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], ret, QCC_MakeIntConst(inc), NULL, STFL_PRESERVEA), false, !freetemps); break; case ev_uint: QCC_StoreSRefToRef(ref, QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_U], ret, QCC_MakeIntConst(inc), NULL, STFL_PRESERVEA), false, !freetemps); break; case ev_int64: QCC_StoreSRefToRef(ref, QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I64], ret, QCC_MakeInt64Const(inc), NULL, STFL_PRESERVEA), false, !freetemps); break; case ev_uint64: QCC_StoreSRefToRef(ref, QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_U64], ret, QCC_MakeInt64Const(inc), NULL, STFL_PRESERVEA), false, !freetemps); break; case ev_pointer: if (ref->cast->aux_type->bits) { if (flag_undefwordsize) QCC_PR_ParseErrorPrintSRef(ERR_INTERNAL, ret, "not allowed to make assumptions about pointer sizes. sorry."); inc *= ref->cast->aux_type->bits/8; tmp = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], ret, QCC_MakeIntConst(inc), NULL, STFL_PRESERVEA); } else { inc *= ref->cast->aux_type->size; tmp = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_PIW], ret, QCC_MakeIntConst(inc), NULL, STFL_PRESERVEA); } tmp.cast = ref->cast; //make sure its the right pointer type. QCC_StoreSRefToRef is picky. QCC_StoreSRefToRef(ref, tmp, false, !freetemps); break; default: QCC_PR_ParseErrorPrintSRef(ERR_INTERNAL, ret, "post increment operator not supported with this type"); break; } //hack any following uses of the ref to refer to the temp ref->type = REF_GLOBAL; ref->base = ret; ref->index = nullsref; ref->readonly = true; return ret; } switch(ref->type) { case REF_THISCALL: case REF_NONVIRTUAL: if (freetemps) QCC_FreeTemp(ref->index); else QCC_UnFreeTemp(ret); break; case REF_POINTERARRAY: case REF_ARRAYHEAD: { QCC_ref_t buf; ref = QCC_PR_GenerateAddressOf(&buf, ref); return QCC_RefToDef(ref, freetemps); } break; case REF_GLOBAL: case REF_ARRAY: if (!ret.sym->type->size) QCC_PR_ParseErrorPrintSRef(ERR_BADARRAYSIZE, ret, "symbol definition is incomplete"); if (ref->index.cast) { //FIXME: array reads of array[immediate+offset] should generate (array+immediate)[offset] instead. //FIXME: this needs to be deprecated (and moved elsewhere) const QCC_eval_t *idxeval = QCC_SRef_EvalConst(ref->index); if (idxeval) { ref->base.sym->referenced = true; if (ref->index.sym) ref->index.sym->referenced = true; if (ref->cast->align) { bitofs += QCC_Eval_Int(idxeval, ref->index.cast)*ref->cast->align; ret.ofs += bitofs>>5; bitofs&=31; } else ret.ofs += QCC_Eval_Int(idxeval, ref->index.cast); if (freetemps) QCC_FreeTemp(ref->index); else QCC_UnFreeTemp(ret); } else { if (ref->cast->align!=32) ///bits must be a multiple of 8 here. { QCC_ref_t buf; QCC_UnFreeTemp(ret); tmp = QCC_RefToDef(QCC_PR_GenerateAddressOf(&buf, ref), freetemps); ret = QCC_LoadFromPointer(tmp, nullsref, ref->cast); if (ref->cast->type == ev_bitfld) ret.cast = ref->cast->parentclass; else ret.cast = ref->cast; return ret; } else ret = QCC_LoadFromArray(ref->base, ref->index, ref->cast, !freetemps); } } else if (freetemps) QCC_FreeTemp(ref->index); else QCC_UnFreeTemp(ret); break; case REF_POINTER: if (!ret.sym->symbolheader->symbolsize) QCC_PR_ParseErrorPrintSRef(ERR_BADARRAYSIZE, ret, "symbol definition is incomplete"); if (ref->index.cast) { // if (!freetemps) QCC_UnFreeTemp(ref->index); idx = QCC_SupplyConversion(ref->index, ev_integer, true); } else idx = nullsref; if (ref->cast->type == ev_bitfld && ref->bitofs) idx = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], idx, QCC_MakeIntConst(ref->bitofs / ref->cast->bits), NULL); tmp = QCC_LoadFromPointer(ref->base, idx, ref->cast); QCC_FreeTemp(idx); if (freetemps) QCC_PR_DiscardRef(ref); return tmp; case REF_FIELD: return QCC_PR_ExpandField(ref->base, ref->index, ref->cast, freetemps?0:(STFL_PRESERVEA|STFL_PRESERVEB)); case REF_STRING: if (ref->cast->type == ev_float) { idx = ref->index.cast?QCC_SupplyConversion(ref->index, ev_float, true):nullsref; return QCC_PR_StatementFlags(&pr_opcodes[OP_LOADP_C], ref->base, idx, NULL, freetemps?0:(STFL_PRESERVEA|STFL_PRESERVEB)); } else { idx = ref->index.cast?QCC_SupplyConversion(ref->index, ev_integer, true):nullsref; return QCC_PR_StatementFlags(&pr_opcodes[OP_LOADP_U8], ref->base, idx, NULL, freetemps?0:(STFL_PRESERVEA|STFL_PRESERVEB)); } case REF_ACCESSOR: if (ref->accessor && ref->accessor->getset_func[0].cast) { int args = 0; QCC_sref_t arg[2]; if (ref->accessor->getset_isref[0] == 1) { if (ref->base.sym->temp) QCC_PR_ParseErrorPrintSRef(ERR_NOFUNC, ref->base, "Accessor %s(get) cannot be used on a temporary accessor reference", ref->accessor?ref->accessor->fieldname:""); //there shouldn't really be any need for this, but its problematic if the accessor is a field. arg[args++] = QCC_PR_Statement(&pr_opcodes[OP_GLOBALADDRESS], ref->base, nullsref, NULL); } else arg[args++] = ref->base; if (ref->accessor->indexertype) arg[args++] = ref->index.cast?QCC_SupplyConversion(ref->index, ref->accessor->indexertype->type, true):QCC_MakeIntConst(0); QCC_ForceUnFreeDef(ref->accessor->getset_func[0].sym); return QCC_PR_GenerateFunctionCallSref(nullsref, ref->accessor->getset_func[0], arg, args); } else if (ref->accessor && ref->accessor->staticval.cast) { QCC_ForceUnFreeDef(ref->accessor->staticval.sym); return ref->accessor->staticval; } else QCC_PR_ParseErrorPrintSRef(ERR_NOFUNC, ref->base, "Accessor %s has no get function", ref->accessor?ref->accessor->fieldname:""); break; } ret.cast = ref->cast; if (ret.cast->type == ev_bitfld) ret = QCC_BitfieldToDef(ret, bitofs); return ret; } //return value is the 'source', unless we folded the store and stripped a temp, in which case it'll be the new value at the given location, either way should have the same value as source. QCC_sref_t QCC_StoreSRefToRef(QCC_ref_t *dest, QCC_sref_t source, pbool readable, pbool preservedest) { const QCC_eval_t *eval; QCC_ref_t ptrref; pbool nullsrc = QCC_SRef_IsNull(source); if (dest->readonly) { QCC_PR_ParseWarning(WARN_ASSIGNMENTTOCONSTANT, "Assignment to constant %s", QCC_GetSRefName(dest->base)); QCC_PR_ParsePrintSRef(WARN_ASSIGNMENTTOCONSTANT, dest->base); if (dest->index.cast) QCC_PR_ParsePrintSRef(WARN_ASSIGNMENTTOCONSTANT, dest->index); } if (!nullsrc) /*{ if (dest->cast->type == ev_struct || dest->cast->type == ev_union) { QCC_FreeTemp(source); source = QCC_MakeIntConst(0); } else if (dest->cast->type == ev_vector) { QCC_FreeTemp(source); source = QCC_MakeVectorConst(0, 0, 0); } source.cast = dest->cast; } else*/ { QCC_type_t *t = source.cast; while(t) { if (!typecmp_lax(t, dest->cast)) break; t = t->parentclass; } if (!t && !(source.cast->type == ev_pointer && dest->cast->type == ev_pointer && (source.cast->aux_type->type == ev_void || source.cast->aux_type->type == ev_variant)) && source.cast->type != ev_variant && dest->cast->type != ev_variant) { //extra check to allow void*->any* char typea[256]; char typeb[256]; if (source.cast->type == ev_variant || dest->cast->type == ev_variant) QCC_PR_ParseWarning(WARN_IMPLICITVARIANTCAST, "type mismatch: %s %s to %s %s.%s", typea, QCC_GetSRefName(source), typeb, QCC_GetSRefName(dest->base), QCC_GetSRefName(dest->index)); else if (dest->cast->type == ev_bitfld && dest->cast->parentclass == source.cast) ; //dest is a bitfield of the same source type. silent truncation is fine. else if ((dest->cast->type == ev_float || dest->cast->type == ev_integer || dest->cast->type == ev_uint || dest->cast->type == ev_int64 || dest->cast->type == ev_uint64 || dest->cast->type == ev_double || dest->cast->type == ev_boolean) && ( source.cast->type == ev_float || source.cast->type == ev_integer || source.cast->type == ev_uint || source.cast->type == ev_int64 || source.cast->type == ev_uint64 || source.cast->type == ev_double || source.cast->type == ev_boolean)) { if (dest->cast->type == ev_boolean) source = QCC_SupplyConversion(QCC_PR_GenerateLogicalTruth(source, "cannot convert to boolean"), dest->cast->parentclass->type, true); else source = QCC_SupplyConversion(source, dest->cast->type, true); } else { TypeName(source.cast, typea, sizeof(typea)); TypeName(dest->cast, typeb, sizeof(typeb)); if (dest->type == REF_FIELD) { QCC_PR_ParseWarning(WARN_STRICTTYPEMISMATCH, "type mismatch: %s %s to %s %s.%s", typea, QCC_GetSRefName(source), typeb, QCC_GetSRefName(dest->base), QCC_GetSRefName(dest->index)); QCC_PR_ParsePrintDef(WARN_STRICTTYPEMISMATCH, source.sym); } else if (dest->index.cast && strcmp("IMMEDIATE", dest->index.sym->name)) QCC_PR_ParseWarning(WARN_STRICTTYPEMISMATCH, "type mismatch: %s %s to %s %s[%s]", typea, QCC_GetSRefName(source), typeb, QCC_GetSRefName(dest->base), QCC_GetSRefName(dest->index)); else QCC_PR_ParseWarning(WARN_STRICTTYPEMISMATCH, "type mismatch: %s %s to %s %s", typea, QCC_GetSRefName(source), typeb, QCC_GetSRefName(dest->base)); } } } if (dest->cast->type == ev_bitfld && dest->type != REF_POINTER && QCC_SRef_EvalConst(dest->index)) { unsigned int bits = dest->cast->bits; unsigned int bitofs = dest->bitofs; QCC_sref_t old; if (bits == 32 && !bitofs) ; //o.O no packing needed. else { QCC_type_t *bittype = dest->cast; dest->bitofs = 0; if (dest->cast->align != 32) { int idx = QCC_Eval_Int(QCC_SRef_EvalConst(dest->index), dest->index.cast); if (idx) { idx *= dest->cast->align; bitofs += idx&31; idx /= 32; dest->index = QCC_MakeUIntConst(idx); } } dest->cast = dest->cast->parentclass; old = QCC_RefToDef(dest, false); dest->bitofs = bitofs; dest->cast = bittype; source = QCC_PR_Statement_BitCopy(source, bitofs, bits, old); } // if (readable) // QCC_PR_ParseWarning(ERR_PARSEERRORS, "left operand (%s) is a bitfield and not readable! oh noes!", QCC_GetSRefName(dest->base)); } else if (dest->bitofs) QCC_PR_ParseWarning(ERR_INTERNAL, "left operand (%s) has a bit offset", QCC_GetSRefName(dest->base)); for(;;) { switch(dest->type) { case REF_ARRAYHEAD: case REF_POINTERARRAY: QCC_PR_ParseWarning(ERR_PARSEERRORS, "left operand must be an l-value (did you mean %s[0]?)", QCC_GetSRefName(dest->base)); if (!preservedest) QCC_PR_DiscardRef(dest); break; default: QCC_PR_ParseWarning(ERR_PARSEERRORS, "left operand must be an l-value (unsupported reference type)"); if (!preservedest) QCC_PR_DiscardRef(dest); break; case REF_GLOBAL: case REF_ARRAY: if (!dest->index.cast || QCC_SRef_EvalConst(dest->index)) { QCC_sref_t dd; // QCC_PR_ParseWarning(0, "FIXME: trying to do references: assignments to arrays with const offset not supported.\n"); case REF_NONVIRTUAL: dd.cast = dest->cast; dd.ofs = dest->base.ofs; dd.sym = dest->base.sym; if ((eval=QCC_SRef_EvalConst(dest->index))) { if (!preservedest) QCC_FreeTemp(dest->index); dd.ofs += QCC_Eval_Int(eval, dest->index.cast); } //FIXME: can dest even be a temp? // if (readable) // QCC_UnFreeTemp(source); source = QCC_CollapseStore(dd, source, dest->cast, readable, preservedest); } else { if (readable) QCC_UnFreeTemp(source); QCC_StoreToArray(dest->base, dest->index, source, dest->cast); } break; case REF_POINTER: source.sym->referenced = true; QCC_StoreToPointer(dest->base, dest->index.cast?QCC_SupplyConversion(dest->index, ev_integer, true):nullsref, source, dest->cast); if (dest->base.sym) dest->base.sym->referenced = true; if (!preservedest) QCC_FreeTemp(dest->base); if (!readable) { QCC_FreeTemp(source); source = nullsref; } break; case REF_STRING: { QCC_sref_t addr = dest->base; QCC_sref_t idx = dest->index; int op; if (source.cast->type==ev_float && QCC_OPCodeValid(&pr_opcodes[OP_STOREP_C])) op = OP_STOREP_C; else if (QCC_OPCodeValid(&pr_opcodes[OP_STOREP_I8])) op = OP_STOREP_I8; else if (QCC_OPCodeValid(&pr_opcodes[OP_STOREP_C])) op = OP_STOREP_C; else op = OP_STOREP_I8; if (op == OP_STOREP_C) source = QCC_SupplyConversion(source, ev_float, true); else source = QCC_SupplyConversion(source, ev_integer, true); if (idx.cast) idx = QCC_SupplyConversion(idx, ev_integer, true); if (idx.sym && !QCC_OPCode_StorePOffset()) { //can't do an offset store yet... bake any index into the pointer. if (idx.cast) addr = QCC_PR_Statement(&pr_opcodes[OP_ADD_I], addr, idx, NULL); idx = nullsref; } QCC_PR_Statement_SmallStore(op, source, addr, idx); } break; case REF_ACCESSOR: if (dest->accessor && dest->accessor->getset_func[1].cast) { int args = 0; QCC_sref_t arg[3]; if (dest->accessor->getset_isref[1] == 1) { if (dest->base.sym->temp) QCC_PR_ParseErrorPrintDef(ERR_NOFUNC, dest->base.sym, "Accessor %s(set) cannot be used on a temporary accessor reference", dest->accessor?dest->accessor->fieldname:""); //there shouldn't really be any need for this, but its problematic if the accessor is a field. arg[args++] = QCC_PR_Statement(&pr_opcodes[OP_GLOBALADDRESS], dest->base, nullsref, NULL); } else arg[args++] = dest->base; if (dest->accessor->indexertype) arg[args++] = dest->index.cast?QCC_SupplyConversion(dest->index, dest->accessor->indexertype->type, true):QCC_MakeIntConst(0); arg[args++] = source; if (readable) //if we're returning source, make sure it can't get freed QCC_UnFreeTemp(source); QCC_ForceUnFreeDef(dest->accessor->getset_func[1].sym); QCC_FreeTemp(QCC_PR_GenerateFunctionCallSref(nullsref, dest->accessor->getset_func[1], arg, args)); } else QCC_PR_ParseErrorPrintSRef(ERR_NOFUNC, dest->base, "Accessor has no set function"); break; case REF_FIELD: { int storef_opcode; //fixme: we should do this earlier, to preserve original instruction ordering. //such that self.enemy = (self = world); still has the same result (more common with function calls) dest->base.sym->referenced = true; dest->index.sym->referenced = true; source.sym->referenced = true; if (dest->cast->type == ev_float) storef_opcode = OP_STOREF_F; else if (dest->cast->type == ev_vector) storef_opcode = OP_STOREF_V; else if (dest->cast->type == ev_string) storef_opcode = OP_STOREF_S; else if ( dest->cast->type == ev_entity || dest->cast->type == ev_field || dest->cast->type == ev_function || dest->cast->type == ev_pointer || dest->cast->type == ev_integer || dest->cast->type == ev_uint || dest->cast->type == ev_struct || dest->cast->type == ev_union || dest->cast->type == ev_enum || dest->cast->type == ev_bitfld || dest->cast->type == ev_int64 || dest->cast->type == ev_uint64 || dest->cast->type == ev_double) storef_opcode = OP_STOREF_I; else storef_opcode = OP_DONE; //don't use it for arrays. address+storep_with_offset is less opcodes. if (storef_opcode!=OP_DONE && dest->index.cast->size == dest->cast->size && QCC_OPCodeValid(&pr_opcodes[storef_opcode])) { //doesn't generate any temps. int sz = dest->cast->size, i; if (nullsrc) { for (i = 0; i+2 < sz; i+=3, dest->index.ofs += 3) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREF_V], dest->base, dest->index, QCC_MakeVectorConst(0,0,0), true); if (QCC_OPCodeValid(&pr_opcodes[OP_STOREF_I64])) for (; i+1 < sz; i+=2, dest->index.ofs += 2) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREF_V], dest->base, dest->index, QCC_MakeVectorConst(0,0,0), true); for (; i < sz; i++, dest->index.ofs++) QCC_PR_SimpleStatement(&pr_opcodes[storef_opcode], dest->base, dest->index, QCC_MakeIntConst(0), true); } else { for (i = 0; i+2 < sz; i+=3, dest->index.ofs += 3, source.ofs += 3) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREF_V], dest->base, dest->index, source, true); if (QCC_OPCodeValid(&pr_opcodes[OP_STOREF_I64])) for (; i+1 < sz; i+=2, dest->index.ofs += 2, source.ofs += 2) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREF_I64], dest->base, dest->index, source, true); for (; i < sz; i++, dest->index.ofs++, source.ofs++) QCC_PR_SimpleStatement(&pr_opcodes[storef_opcode], dest->base, dest->index, source, true); source.ofs -= i; } dest->index.ofs -= i; if (!readable) { QCC_FreeTemp(source); source = nullsref; } if (!preservedest) { QCC_FreeTemp(dest->base); QCC_FreeTemp(dest->index); } } else if (1)//dest->cast->type >= ev_variant) { QCC_sref_t t; int sz = dest->cast->size, i; for (i = 0; i+2 < sz; i+=3, dest->index.ofs += 3, source.ofs+=3) { t = QCC_PR_StatementFlags(&pr_opcodes[OP_ADDRESS], dest->base, dest->index, NULL, STFL_PRESERVEA|STFL_PRESERVEB); QCC_StoreToPointer(t, nullsref, nullsrc?QCC_MakeVectorConst(0,0,0):source, type_vector); QCC_FreeTemp(t); } if (QCC_OPCodeValid(&pr_opcodes[OP_STOREP_I64])) for (; i+1 < sz; i+=2, dest->index.ofs += 2, source.ofs+=2) { t = QCC_PR_StatementFlags(&pr_opcodes[OP_ADDRESS], dest->base, dest->index, NULL, STFL_PRESERVEA|STFL_PRESERVEB); QCC_StoreToPointer(t, nullsref, nullsrc?QCC_MakeInt64Const(0):source, type_int64); QCC_FreeTemp(t); } for (; i < sz; i++, dest->index.ofs++, source.ofs++) { t = QCC_PR_StatementFlags(&pr_opcodes[OP_ADDRESS], dest->base, dest->index, NULL, STFL_PRESERVEA|STFL_PRESERVEB); QCC_StoreToPointer(t, nullsref, nullsrc?QCC_MakeFloatConst(0):source, (dest->cast->size!=1)?type_float:dest->cast); QCC_FreeTemp(t); } source.ofs -= i; dest->index.ofs -= i; if (!readable) { QCC_FreeTemp(source); source = nullsref; } if (!preservedest) { QCC_FreeTemp(dest->base); QCC_FreeTemp(dest->index); } } else { dest = QCC_PR_BuildRef(&ptrref, REF_POINTER, QCC_PR_StatementFlags(&pr_opcodes[OP_ADDRESS], dest->base, dest->index, NULL, preservedest?STFL_PRESERVEA:0), //pointer address nullsref, (dest->index.cast->type == ev_field)?dest->index.cast->aux_type:type_variant, dest->readonly, 0); preservedest = false; continue; } } break; } break; } return source; } /*QCC_ref_t *QCC_PR_RefTerm (QCC_ref_t *ref, unsigned int exprflags) { return QCC_DefToRef(ref, QCC_PR_Term(exprflags)); }*/ QCC_sref_t QCC_PR_Term (unsigned int exprflags) { QCC_ref_t refbuf; return QCC_RefToDef(QCC_PR_RefTerm(&refbuf, exprflags), true); } QCC_sref_t QCC_PR_ParseValue (QCC_type_t *assumeclass, pbool allowarrayassign, pbool expandmemberfields, pbool makearraypointers) { QCC_ref_t refbuf; return QCC_RefToDef(QCC_PR_ParseRefValue(&refbuf, assumeclass, allowarrayassign, expandmemberfields, makearraypointers), true); } QCC_sref_t QCC_PR_ParseArrayPointer (QCC_sref_t d, pbool allowarrayassign, pbool makestructpointers) { QCC_ref_t refbuf; QCC_ref_t inr; QCC_DefToRef(&inr, d); return QCC_RefToDef(QCC_PR_ParseRefArrayPointer(&refbuf, &inr, allowarrayassign, makestructpointers), true); } void QCC_PR_DiscardRef(QCC_ref_t *ref) { if (ref->postinc) { QCC_sref_t oval; int inc = ref->postinc; if (ref->bitofs) QCC_PR_ParseErrorPrintSRef(ERR_INTERNAL, ref->base, "post increment operator on bitfield"); ref->postinc = 0; //read the value oval = QCC_RefToDef(ref, false); //and update it switch(oval.cast->type) { case ev_float: oval = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_F], oval, QCC_MakeFloatConst(inc), NULL, 0); break; case ev_double: oval = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_D], oval, QCC_MakeDoubleConst(inc), NULL, 0); break; case ev_string: case ev_integer: oval = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], oval, QCC_MakeIntConst(inc), NULL, 0); break; case ev_uint: oval = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_U], oval, QCC_MakeIntConst(inc), NULL, 0); break; case ev_int64: oval = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I64], oval, QCC_MakeInt64Const(inc), NULL, 0); break; case ev_uint64: oval = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_U64], oval, QCC_MakeInt64Const(inc), NULL, 0); break; case ev_pointer: if (ref->cast->aux_type->bits) { inc *= ref->cast->aux_type->bits/8; oval = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_I], oval, QCC_MakeIntConst(inc), NULL, 0); } else { inc *= ref->cast->aux_type->size; oval = QCC_PR_StatementFlags(&pr_opcodes[OP_ADD_PIW], oval, QCC_MakeIntConst(inc), NULL, 0); } oval.cast = ref->cast; break; default: QCC_PR_ParseErrorPrintSRef(ERR_INTERNAL, oval, "post increment operator not supported with this type"); break; } QCC_StoreSRefToRef(ref, oval, false, false); qcc_usefulstatement = true; } else { QCC_FreeTemp(ref->base); if (ref->index.cast) QCC_FreeTemp(ref->index); } } static QCC_opcode_t *QCC_PR_ChooseOpcode(QCC_sref_t lhs, QCC_sref_t rhs, QCC_opcode_t **priority) { QCC_opcode_t *op, *oldop; QCC_opcode_t *bestop; int numconversions, c; etype_t type_a; etype_t type_c; op = oldop = *priority++; // type check type_a = lhs.cast->type; // type_b = rhs.cast->type; if (type_a == ev_enum) { if (lhs.cast == rhs.cast) { lhs.cast = lhs.cast->aux_type; rhs.cast = rhs.cast->aux_type; // type_a = lhs.cast->type; } } if (type_a == ev_boolean) { lhs.cast = lhs.cast->parentclass; type_a = lhs.cast->type; } if (rhs.cast->type == ev_boolean) rhs.cast = rhs.cast->parentclass; if (op->name[0] == '.')// field access gets type from field { if (rhs.cast->aux_type) type_c = rhs.cast->aux_type->type; else type_c = -1; // not a field } else type_c = ev_void; bestop = NULL; numconversions = 32767; while (op) { if (!(type_c != ev_void && type_c != (*op->type_c)->type)) { if (!STRCMP (op->name , oldop->name)) //matches { //return values are never converted - what to? // if (type_c != ev_void && type_c != op->type_c->type->type) // { // op++; // continue; // } if (op->associative!=ASSOC_LEFT) {//assignment #if 0 if (op->type_a == &type_pointer) //ent var { /*FIXME: I don't like this code*/ if (lhs->type->type != ev_pointer) c = -200; //don't cast to a pointer. else if ((*op->type_c)->type == ev_void && op->type_b == &type_pointer && rhs.cast->type == ev_pointer) c = 0; //generic pointer... fixme: is this safe? make sure both sides are equivelent else if (lhs->type->aux_type->type != (*op->type_b)->type) //if e isn't a pointer to a type_b c = -200; //don't let the conversion work else c = QCC_canConv(rhs, (*op->type_c)->type); } else #endif { c=QCC_canConv(rhs, (*op->type_b)->type); if (type_a != (*op->type_a)->type) //in this case, a is the final assigned value c = -300; //don't use this op, as we must not change var b's type else if ((*op->type_a)->type == ev_pointer && lhs.cast->aux_type->type != (*op->type_a)->aux_type->type) c = -300; //don't use this op if its a pointer to a different type } } else { int l = QCC_canConv(lhs, (*op->type_a)->type); int r = QCC_canConv(rhs, (*op->type_b)->type); c = min(l,r); if (c >= 0) { c = max(l,r); /*QCC_PR_ParseWarning(WARN_IMPLICITCONVERSION, "%s (%i): Possible conversion from %s to %s (%i), %s to %s (%i)", op->opname, c, lhs.cast->name, (*op->type_a)->name, l, rhs.cast->name, (*op->type_b)->name, r);*/ } } if (c>=0 && c < numconversions) { bestop = op; numconversions=c; if (c == 0)//can't get less conversions than 0... break; } } else break; } op = *priority++; } if (bestop == NULL) { // if (oldop->priority == CONDITION_PRIORITY) // op = oldop; // else { char temp1[256]; char temp2[256]; op = oldop; QCC_PR_ParseWarning(flag_laxcasts?WARN_LAXCAST:ERR_TYPEMISMATCH, "type mismatch for %s (%s%s%s and %s%s%s)", oldop->name, col_type,TypeName(lhs.cast,temp1,sizeof(temp1)),col_none, col_type,TypeName(rhs.cast,temp2,sizeof(temp2)),col_none); QCC_PR_ParsePrintSRef(flag_laxcasts?WARN_LAXCAST:ERR_TYPEMISMATCH, lhs); QCC_PR_ParsePrintSRef(flag_laxcasts?WARN_LAXCAST:ERR_TYPEMISMATCH, rhs); } } else { op = bestop; /*if (numconversions>3) { c=QCC_canConv(lhs, (*op->type_a)->type); if (c>3) QCC_PR_ParseWarning(WARN_IMPLICITCONVERSION, "Implicit conversion from %s to %s", lhs.cast->name, (*op->type_a)->name); c=QCC_canConv(rhs, (*op->type_b)->type); if (c>3) QCC_PR_ParseWarning(WARN_IMPLICITCONVERSION, "Implicit conversion from %s to %s", rhs.cast->name, (*op->type_b)->name); }*/ } return op; } //used to optimise logicops slightly. static pbool QCC_OpHasSideEffects(QCC_statement_t *st) { //function calls potentially always have side effects (and are expensive) if ((st->op >= OP_CALL0 && st->op <= OP_CALL8) || (st->op >= OP_CALL1H && st->op <= OP_CALL8H)) return true; //otherwise if we're assigning to some variable (that isn't a temp) then it has a side effect. //FIXME: this doesn't catch op_address+op_storep_*, but that should generally not happen as logicops is tied to a single statement. if (st->c.sym && !st->c.sym->temp && OpAssignsToC(st->op)) return true; if (st->b.sym && !st->b.sym->temp && OpAssignsToB(st->op)) return true; if (st->a.sym && !st->a.sym->temp && OpAssignsToA(st->op)) return true; return false; } QCC_ref_t *QCC_PR_RefExpression (QCC_ref_t *retbuf, int priority, int exprflags) { QCC_ref_t rhsbuf; // QCC_dstatement32_t *st; QCC_opcode_t *op; int opnum; QCC_ref_t *lhsr, *rhsr; QCC_sref_t lhsd, rhsd; if (priority == 0) { lhsr = QCC_PR_RefTerm (retbuf, exprflags); if (!STRCMP(pr_token, "++")) { if (lhsr->readonly) QCC_PR_ParseError(ERR_PARSEERRORS, "postincrement: lhs is readonly"); lhsr->postinc += 1; QCC_PR_Lex(); } else if (!STRCMP(pr_token, "--")) { if (lhsr->readonly) QCC_PR_ParseError(ERR_PARSEERRORS, "postdecrement: lhs is readonly"); lhsr->postinc += -1; QCC_PR_Lex(); } return lhsr; } lhsr = QCC_PR_RefExpression (retbuf, priority-1, exprflags); while (1) { if (priority == FUNC_PRIORITY && QCC_PR_CheckToken ("(") ) { qcc_usefulstatement=true; lhsd = QCC_PR_ParseFunctionCall (lhsr); lhsr = QCC_DefToRef(&rhsbuf, lhsd); lhsr = QCC_PR_ParseRefArrayPointer(retbuf, lhsr, true, true); if (lhsr == &rhsbuf) { *retbuf = rhsbuf; lhsr = retbuf; } } if (priority == TERNARY_PRIORITY && QCC_PR_CheckToken ("?")) { //if we have no int types, force all ints to floats here, just to ensure that we don't end up with non-constant ints that we then can't cope with. QCC_sref_t r; QCC_statement_t *fromj, *elsej, *truthstore; QCC_sref_t val = QCC_RefToDef(lhsr, true); const QCC_eval_t *eval = QCC_SRef_EvalConst(val); pbool lvalisnull = false; if (pr_scope) eval = NULL; //FIXME: we need the gotos to avoid sideeffects, which is annoying. if (QCC_PR_CheckToken(":")) { if (eval) { if (QCC_Eval_Truth(eval, val.cast, false)) { QCC_FreeTemp(QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA)); return QCC_DefToRef(retbuf, val); } else { QCC_FreeTemp(val); val = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); return QCC_DefToRef(retbuf, val); } } eval = NULL; //r=a?:b -> if (a) r=a else r=b; fromj = QCC_Generate_OP_IFNOT(val, true); lvalisnull = QCC_SRef_IsNull(val); #if 1 //hack: make local, not temp. this prevents assignment/temp folding... r = QCC_MakeSRefForce(QCC_PR_DummyDef(r.cast=val.cast, "ternary", pr_scope, 0, NULL, 0, false, GDF_STRIP), 0, val.cast); #else r = QCC_GetTemp(val.cast); #endif QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[(r.cast->size>=3)?OP_STORE_V:OP_STORE_F], val, r, &truthstore, STFL_PRESERVEB)); } else { if (eval) { if (eval->_int) { QCC_FreeTemp(val); val = QCC_PR_Expression(TOP_PRIORITY, 0); QCC_PR_Expect(":"); QCC_FreeTemp(QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA)); return QCC_DefToRef(retbuf, val); } else { QCC_FreeTemp(val); QCC_FreeTemp(QCC_PR_Expression(TOP_PRIORITY, 0)); QCC_PR_Expect(":"); val = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); return QCC_DefToRef(retbuf, val); } } fromj = QCC_Generate_OP_IFNOT(val, false); val = QCC_PR_Expression(TOP_PRIORITY, 0); if (val.cast->type == ev_integer && !QCC_OPCodeValid(&pr_opcodes[OP_STORE_IF])) val = QCC_SupplyConversion(val, ev_float, true); lvalisnull = QCC_SRef_IsNull(val); #if 1 //hack: make local, not temp. this prevents assignment/temp folding... r = QCC_MakeSRefForce(QCC_PR_DummyDef(r.cast=val.cast, "ternary", pr_scope, 0, NULL, 0, false, GDF_STRIP), 0, val.cast); #else r = QCC_GetTemp(val.cast); #endif //fixme: QCC_StoreToSRef if it were not for saving truthstore switch(r.cast->size) { case 3: QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_V], val, r, &truthstore, STFL_PRESERVEB)); break; case 2: QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_I64], val, r, &truthstore, STFL_PRESERVEB)); break; case 1: QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], val, r, &truthstore, STFL_PRESERVEB)); break; default: QCC_PR_ParseError(ERR_BADEXTENSION, "oversized ternary result"); break; } //r can be stomped upon until its reused anyway QCC_PR_Expect(":"); } if (fromj) { QCC_PR_Statement(&pr_opcodes[OP_GOTO], nullsref, nullsref, &elsej); fromj->b.jumpofs = &statements[numstatements] - fromj; } else elsej = NULL; val = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); if (val.cast->type == ev_integer && !QCC_OPCodeValid(&pr_opcodes[OP_STORE_IF])) val = QCC_SupplyConversion(val, ev_float, true); if (r.cast->type == ev_integer && val.cast->type == ev_float) { //cond?5i:5.1 should be accepted. change the initial store_f to a store_if. truthstore->op = OP_STORE_IF; r.cast = type_float; } else if (r.cast->type == ev_float && (val.cast->type == ev_integer||val.cast->type == ev_uint)) { //cond?5.1:5i should be accepted. change the just-parsed value to a float, as needed. val = QCC_SupplyConversion(val, ev_float, true); } //promote to unsigned if one isn't. else if (r.cast->type == ev_uint && val.cast->type == ev_integer) val.cast = type_uint; else if (r.cast->type == ev_integer && val.cast->type == ev_uint) r.cast = type_uint; else if (r.cast->type == ev_uint64 && val.cast->type == ev_int64) val.cast = type_uint64; else if (r.cast->type == ev_int64 && val.cast->type == ev_uint64) r.cast = type_uint64; if (typecmp(val.cast, r.cast) != 0) { while (val.cast->type == ev_boolean) val.cast = val.cast->parentclass; while (r.cast->type == ev_boolean) r.cast = r.cast->parentclass; if (typecmp(val.cast, r.cast) == 0) ; else if (QCC_SRef_IsNull(val) && r.cast->size == val.cast->size) val.cast = r.cast; //null is null... unless its a vector... else if (lvalisnull && r.cast->size == val.cast->size) r.cast = val.cast; //null is null... unless its a vector... else if (typecmp_lax(val.cast, r.cast) != 0) { char typebuf1[256]; char typebuf2[256]; QCC_PR_ParseWarning(0, "Type mismatch on ternary operator: %s vs %s", TypeName(r.cast, typebuf1, sizeof(typebuf1)), TypeName(val.cast, typebuf2, sizeof(typebuf2))); } else { //if they're mixed int/float, cast to floats. QCC_PR_ParseError(0, "Ternary operator with mismatching types\n"); } } QCC_StoreToSRef(r, val, val.cast, false, true); // QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[(r.cast->size>=3)?OP_STORE_V:OP_STORE_F], val, r, NULL, STFL_PRESERVEB)); if (elsej) elsej->a.jumpofs = &statements[numstatements] - elsej; return QCC_DefToRef(retbuf, r); } opnum=0; if (pr_token_type == tt_immediate && *pr_token=='-') { //work around (4-3) being parsed as 4 -3 with no operator between //(don't get confused by "-foo" strings though if (pr_immediate_type->type == ev_float || pr_immediate_type->type == ev_double || pr_immediate_type->type == ev_integer || pr_immediate_type->type == ev_uint || pr_immediate_type->type == ev_int64 || pr_immediate_type->type == ev_uint64) { QCC_PR_IncludeChunk(pr_token, true, NULL); strcpy(pr_token, "+");//two negatives would make a positive. pr_token_type = tt_punct; } } if (pr_token_type != tt_punct) { if (priority == TOP_PRIORITY) QCC_PR_ParseWarning(WARN_UNEXPECTEDPUNCT, "Expected punctuation"); } if (priority == ASSIGN_PRIORITY) { //assignments QCC_opcode_t **ops = NULL, **ops_ptr; char *opname = NULL; int i; if (QCC_PR_CheckToken ("=")) { ops = opcodes_store; ops_ptr = NULL; opname = "="; } else if (QCC_PR_CheckToken ("+=")) { ops = opcodes_addstore; ops_ptr = opcodes_addstorep; opname = "+="; } else if (QCC_PR_CheckToken ("-=")) { ops = opcodes_substore; ops_ptr = opcodes_substorep; opname = "-="; } else if (QCC_PR_CheckToken ("|=")) { ops = opcodes_orstore; ops_ptr = opcodes_orstorep; opname = "|="; } else if (QCC_PR_CheckToken ("&=")) { ops = opcodes_andstore; ops_ptr = NULL; opname = "&="; } else if (QCC_PR_CheckToken ("&~=")) { ops = opcodes_clearstore; ops_ptr = opcodes_clearstorep; opname = "&~="; } else if (QCC_PR_CheckToken ("^=")) { ops = opcodes_xorstore; ops_ptr = NULL; opname = "^="; } else if (QCC_PR_CheckToken ("*=")) { ops = opcodes_mulstore; ops_ptr = opcodes_mulstorep; opname = "*="; } else if (QCC_PR_CheckToken ("<<=")) { ops = opcodes_shlstore; ops_ptr = opcodes_none; opname = "<<="; } else if (QCC_PR_CheckToken (">>=")) { ops = opcodes_shrstore; ops_ptr = opcodes_none; opname = ">>="; } else if (QCC_PR_CheckToken ("/=")) { ops = opcodes_divstore; ops_ptr = opcodes_divstorep; opname = "/="; } else if (QCC_PR_CheckToken ("<=>")) { ops = opcodes_spaceship; ops_ptr = opcodes_none; opname = "<=>"; } else { ops = NULL; ops_ptr = NULL; opname = NULL; } if (ops) { if (lhsr->postinc) QCC_PR_ParseError(ERR_INTERNAL, "Assignment to post-inc result"); if (lhsr->readonly) { QCC_PR_ParseWarning(WARN_ASSIGNMENTTOCONSTANT, "Assignment to const"); QCC_PR_ParsePrintSRef(WARN_ASSIGNMENTTOCONSTANT, lhsr->base); if (lhsr->index.cast) QCC_PR_ParsePrintSRef(WARN_ASSIGNMENTTOCONSTANT, lhsr->index); } rhsr = QCC_PR_RefExpression (&rhsbuf, priority, exprflags | EXPR_DISALLOW_ARRAYASSIGN|EXPR_DISALLOW_COMMA); if ((lhsr->type == REF_POINTERARRAY || lhsr->type == REF_ARRAYHEAD) && lhsr->arraysize != 0 && (rhsr->cast->type == ev_union && rhsr->cast->num_parms == 1 && !rhsr->cast->params[0].paramname) && rhsr->cast->params[0].arraysize == lhsr->arraysize) { //this is some sort of assignment, but these types are logically const pointers that cannot be assigned to themselves. //change the lhsr to a REF_POINTER instead if (lhsr->type == REF_POINTERARRAY) lhsr->type = REF_POINTER; else lhsr->type = REF_ARRAY; lhsr->cast = rhsr->cast; lhsr->arraysize = 0; //just in case. } if (conditional&1) QCC_PR_ParseWarning(WARN_ASSIGNMENTINCONDITIONAL, "suggest parenthesis for assignment used as truth value"); //FIXME: if this is a simple store, rhsr->type == REF_FIELD, and lhsd is a simple def, then we should just expand the field directly to the lhsd for more effient .structs rhsd = QCC_RefToDef(rhsr, true); if (ops_ptr && (lhsr->type == REF_FIELD || lhsr->type == REF_POINTER)) { //*ptr += 5; //can become (address), addstorep_f 5,ptr,sideeffect //instead of loadf_f, add_f, address, storep_f //or instead of loadp_f, add_f, storep_f for (i = 0; (op=ops_ptr[i]); i++) { if (QCC_OPCodeValid(op)) { if ((*op->type_b)->type == rhsd.cast->type && (*op->type_a)->type == ev_pointer && (*op->type_c)->type == lhsr->cast->type) break; } } if (op) { QCC_ref_t ptr; qcc_usefulstatement = true; lhsr = QCC_PR_GenerateAddressOf(&ptr, lhsr); lhsd = QCC_RefToDef(lhsr, true); rhsd = QCC_PR_Statement(op, rhsd, lhsd, NULL); lhsr = QCC_DefToRef(retbuf, rhsd); //we read the rhs, we can just return that as the result lhsr->readonly = true; //(a=b)=c is an error continue; } } if (ops != opcodes_store) { lhsd = QCC_RefToDef(lhsr, false); for (i = 0; (op=ops[i]); i++) { // if (QCC_OPCodeValid(op)) { if ((*op->type_b)->type == rhsd.cast->type && (*op->type_a)->type == lhsd.cast->type) break; } } if (!ops[i]) { rhsd = QCC_EvaluateCast(rhsd, lhsd.cast, true); for (i = 0; ops[i]; i++) { op = ops[i]; // if (QCC_OPCodeValid(op)) { if ((*op->type_b)->type == rhsd.cast->type && (*op->type_a)->type == lhsd.cast->type) break; } } if (!ops[i]) QCC_PR_ParseError(0, "Type mismatch on assignment. %s %s %s is not supported", lhsd.cast->name, opname, rhsd.cast->name); } if (op->associative != ASSOC_LEFT) rhsd = QCC_PR_Statement(op, lhsd, rhsd, NULL); else rhsd = QCC_PR_Statement(op, lhsd, rhsd, NULL); //convert so we don't have issues with: i = (int)(float)(i+f) //this will also catch things like vec *= vec; which would be trying to store a float into a vector. rhsd = QCC_SupplyConversionForAssignment(lhsr, rhsd, lhsr->cast, true); } else { #if 1 rhsd = QCC_EvaluateCast(rhsd, lhsr->cast, true); #else /*if (flag_qccx && lhsr->cast->type == ev_pointer && rhsd.cast->type == ev_float) { //&%555 = 4.0; char destname[256]; QCC_PR_ParseWarning(WARN_LAXCAST, "Implicit pointer dereference on assignment to %s", QCC_GetRefName(lhsr, destname, sizeof(destname))); lhsd = QCC_RefToDef(lhsr, true); lhsr = QCC_PR_BuildRef(retbuf, REF_POINTER, lhsd, nullsref, lhsd.cast->aux_type, false); } else */if (QCC_SRef_IsNull(rhsd)) { QCC_FreeTemp(rhsd); rhsd = QCC_MakeIntConst(0); /*if (lhsr->cast->type == ev_vector) rhsd = QCC_MakeVectorConst(0,0,0); else if (lhsr->cast->type == ev_struct || lhsr->cast->type == ev_union) { QCC_PR_ParseError(0, "Type mismatch on assignment. %s %s %s is not supported", lhsr->cast->name, opname, rhsd.cast->name); } else if(lhsr->cast->type == ev_float) rhsd = QCC_MakeFloatConst(0); else if(lhsr->cast->type == ev_integer) rhsd = QCC_MakeIntConst(0); else rhsd = QCC_MakeIntConst(0); rhsd.cast = lhsr->cast;*/ } else rhsd = QCC_SupplyConversionForAssignment(lhsr, rhsd, lhsr->cast, true); #endif } rhsd = QCC_StoreSRefToRef(lhsr, rhsd, true, false); //FIXME: this should not always be true, but we don't know if the caller actually needs it qcc_usefulstatement = true; lhsr = QCC_DefToRef(retbuf, rhsd); //we read the rhs, we can just return that as the result lhsr->readonly = true; //(a=b)=c is an error } else break; } else { QCC_statement_t *logicjump; QCC_statement_t *logictest; //go straight for the correct priority. for (op = opcodeprioritized[priority][opnum]; op; op = opcodeprioritized[priority][++opnum]) // for (op=pr_opcodes ; op->name ; op++) { // if (op->priority != priority) // continue; if (!QCC_PR_CheckToken (op->name)) continue; logicjump = NULL; lhsd = QCC_RefToDef(lhsr, true); if (opt_logicops && (lhsd.cast->size == 1 || lhsd.cast->type == ev_vector)) { if (!strcmp(op->name, "&&")) //guarenteed to be false if the lhs is false { if (lhsd.cast->type == ev_vector && flag_ifvector) lhsd = QCC_PR_StatementFlags (&pr_opcodes[OP_MUL_V], lhsd, lhsd, NULL, STFL_PRESERVEA); if (lhsd.cast->type == ev_string && flag_ifstring) lhsd = QCC_PR_StatementFlags (&pr_opcodes[OP_NE_S], lhsd, QCC_MakeStringConst(""), NULL, 0); if (lhsd.cast->type == ev_double) lhsd = QCC_PR_StatementFlags (&pr_opcodes[OP_NE_D], lhsd, QCC_MakeDoubleConst(0), NULL, 0); if (lhsd.cast->type == ev_int64 || lhsd.cast->type == ev_uint64) lhsd = QCC_PR_StatementFlags (&pr_opcodes[OP_NE_I64], lhsd, QCC_MakeInt64Const(0), NULL, 0); if (!QCC_Eval_Truth(QCC_SRef_EvalConst(lhsd), lhsd.cast, false)) { QCC_ClobberDef(NULL); //FIXME... logicjump = QCC_Generate_OP_IFNOT(lhsd, true); } } else if (!strcmp(op->name, "||")) //guarenteed to be true if the lhs is true { if (lhsd.cast->type == ev_vector && flag_ifvector) lhsd = QCC_PR_StatementFlags (&pr_opcodes[OP_MUL_V], lhsd, lhsd, NULL, STFL_PRESERVEA); if (lhsd.cast->type == ev_string && flag_ifstring) lhsd = QCC_PR_StatementFlags (&pr_opcodes[OP_NE_S], lhsd, QCC_MakeStringConst(""), NULL, 0); if (lhsd.cast->type == ev_double) lhsd = QCC_PR_StatementFlags (&pr_opcodes[OP_NE_D], lhsd, QCC_MakeDoubleConst(0), NULL, 0); if (lhsd.cast->type == ev_int64 || lhsd.cast->type == ev_uint64) lhsd = QCC_PR_StatementFlags (&pr_opcodes[OP_NE_I64], lhsd, QCC_MakeInt64Const(0), NULL, 0); if (!QCC_Eval_Truth(QCC_SRef_EvalConst(lhsd), lhsd.cast, false)) { QCC_ClobberDef(NULL); //FIXME... logicjump = QCC_Generate_OP_IF(lhsd, true); } } } rhsr = QCC_PR_RefExpression (&rhsbuf, priority-1, exprflags | EXPR_DISALLOW_ARRAYASSIGN); if (op->associative!=ASSOC_LEFT) { QCC_PR_ParseError(ERR_INTERNAL, "internal error: should be unreachable\n"); } else { rhsd = QCC_RefToDef(rhsr, true); op = QCC_PR_ChooseOpcode(lhsd, rhsd, &opcodeprioritized[priority][opnum]); if ((*op->type_a)->type != lhsd.cast->type && (*op->type_a)->type != ev_variant) { if (QCC_canConv(lhsd, (*op->type_a)->type) >= 100) QCC_PR_ParseWarning(WARN_IMPLICITCONVERSION, "Implicit conversion from %s to %s", lhsd.cast->name, (*op->type_a)->name); lhsd = QCC_EvaluateCast(lhsd, (*op->type_a), true); } if ((*op->type_b)->type != rhsd.cast->type && (*op->type_b)->type != ev_variant) { if (QCC_canConv(rhsd, (*op->type_b)->type) >= 100) QCC_PR_ParseWarning(WARN_IMPLICITCONVERSION, "Implicit conversion from %s to %s", rhsd.cast->name, (*op->type_b)->name); rhsd = QCC_EvaluateCast(rhsd, (*op->type_b), true); } if (logicjump) //logic shortcut jumps to just before the if. the rhs is uninitialised if the jump was taken, but the lhs makes it deterministic. { logicjump->flags |= STF_LOGICOP; logicjump->b.jumpofs = &statements[numstatements] - logicjump; if (logicjump->b.jumpofs == 1) { numstatements--; //err, that was pointless. logicjump = NULL; } else if (logicjump->b.jumpofs == 2 && !QCC_OpHasSideEffects(&logicjump[1])) { logicjump[0] = logicjump[1]; numstatements--; //don't bother if the jump is the same cost as the thing we're trying to skip (calls are expensive despite being a single opcode). logicjump = NULL; } else optres_logicops++; } if (logicjump) { lhsd = QCC_PR_Statement (op, lhsd, rhsd, &logictest); if (!logictest) numstatements = logicjump-statements; else logicjump->b.jumpofs = logictest - logicjump; } else lhsd = QCC_PR_Statement (op, lhsd, rhsd, NULL); lhsr = QCC_DefToRef(retbuf, lhsd); } if (priority > 1 && exprflags & EXPR_WARN_ABOVE_1) QCC_PR_ParseWarning(WARN_UNARYNOTSCOPE, "suggest parenthesis for unary operator that applies to multiple terms"); break; } if (!op) break; } } if (lhsr == NULL) QCC_PR_ParseError(ERR_INTERNAL, "e == null"); if (!(exprflags&EXPR_DISALLOW_COMMA) && priority == TOP_PRIORITY && QCC_PR_CheckToken (",")) { QCC_PR_DiscardRef(lhsr); if (!qcc_usefulstatement) QCC_PR_ParseWarning(WARN_POINTLESSSTATEMENT, "Statement does not do anything"); qcc_usefulstatement = false; lhsr = QCC_PR_RefExpression(retbuf, TOP_PRIORITY, exprflags); } return lhsr; } QCC_sref_t QCC_PR_Expression (int priority, int exprflags) { QCC_ref_t refbuf, *ret; ret = QCC_PR_RefExpression(&refbuf, priority, exprflags); return QCC_RefToDef(ret, true); } //parse the expression and discard the result. generate a warning if there were no assignments //this avoids generating getter statements from RefToDef in QCC_PR_Expression. static void QCC_PR_DiscardExpression (int priority, int exprflags) { QCC_ref_t refbuf, *ref; pbool olduseful = qcc_usefulstatement; qcc_usefulstatement = false; ref = QCC_PR_RefExpression(&refbuf, priority, exprflags); QCC_PR_DiscardRef(ref); if (ref->cast->type != ev_void && !qcc_usefulstatement) { // int osl = pr_source_line; // pr_source_line = statementstart; QCC_PR_ParseWarning(WARN_POINTLESSSTATEMENT, "Statement does not do anything"); // pr_source_line = osl; } qcc_usefulstatement = olduseful; } long QCC_PR_IntConstExpr(void) { //fixme: should make sure that no actual statements are generated QCC_sref_t def = QCC_PR_Expression(TOP_PRIORITY, 0); const QCC_eval_t *ev = QCC_SRef_EvalConst(def); if (ev) { QCC_FreeTemp(def); def.sym->referenced = true; switch(def.cast->type) { case ev_integer: return ev->_int; case ev_float: { int i = ev->_float; if ((float)i == ev->_float) return i; } break; case ev_double: { int i = ev->_double; if ((double)i == ev->_double) return i; } break; case ev_uint: return ev->_uint; case ev_int64: return ev->i64; case ev_uint64: return ev->u64; default: QCC_PR_ParseError(ERR_NOTACONSTANT, "Value is not an integer constant"); } } QCC_PR_ParseError(ERR_NOTACONSTANT, "Value is not an integer constant"); return true; } static void QCC_PR_GotoStatement (QCC_statement_t *patch2, char *labelname) { if (num_gotos >= max_gotos) { max_gotos += 8; pr_gotos = realloc(pr_gotos, sizeof(*pr_gotos)*max_gotos); } if (!QC_strlcpy(pr_gotos[num_gotos].name, labelname, sizeof(pr_gotos[num_gotos].name))) QCC_PR_ParseWarning(WARN_STRINGTOOLONG, "Label name too long"); pr_gotos[num_gotos].lineno = pr_source_line; pr_gotos[num_gotos].statementno = patch2 - statements; num_gotos++; } /* static pbool QCC_PR_StatementBlocksMatch(QCC_statement_t *p1, int p1count, QCC_statement_t *p2, int p2count) { if (p1count != p2count) return false; while(p1count>0) { if (p1->op != p2->op) return false; if (memcmp(&p1->a, &p2->a, sizeof(p1->a))) return false; if (memcmp(&p1->b, &p2->b, sizeof(p1->b))) return false; if (memcmp(&p1->c, &p2->c, sizeof(p1->c))) return false; p1++; p2++; p1count--; } return true; }*/ //vanilla qc only has an OP_IFNOT_I, others will be emulated as required, so we tend to need to emulate other opcodes. QCC_statement_t *QCC_Generate_OP_IF(QCC_sref_t e, pbool preserve) { unsigned int flags = (preserve?STFL_PRESERVEA:0); QCC_statement_t *st; int op = 0; while (e.cast->type == ev_accessor) e.cast = e.cast->parentclass; switch(e.cast->type) { //int/pointer types case ev_entity: case ev_field: case ev_function: case ev_pointer: case ev_integer: case ev_uint: case ev_boolean: //should be 0, 1i, or 1.0f, either way -0 isn't a problem so we can use the vanilla OP_IF_I for this op = OP_IF_I; break; //emulated types case ev_string: QCC_PR_ParseWarning(WARN_IFSTRING_USED, "if (string) tests for null, not empty."); if (flag_ifstring) op = OP_IF_S; else op = OP_IF_I; break; case ev_float: if (flag_iffloat || QCC_OPCodeValid(&pr_opcodes[OP_IF_F])) op = OP_IF_F; else op = OP_IF_I; break; case ev_vector: if (flag_ifvector) { e = QCC_PR_StatementFlags (&pr_opcodes[OP_NOT_V], e, nullsref, NULL, flags); op = OP_IFNOT_I; } else op = OP_IF_I; break; case ev_double: e = QCC_PR_StatementFlags (&pr_opcodes[OP_NE_D], e, QCC_MakeDoubleConst(0), NULL, flags); op = OP_IF_I; break; case ev_int64: case ev_uint64: e = QCC_PR_StatementFlags (&pr_opcodes[OP_NE_I64], e, QCC_MakeInt64Const(0), NULL, flags); op = OP_IF_I; break; case ev_variant: case ev_struct: case ev_union: case ev_void: default: QCC_PR_ParseWarning(WARN_CONDITIONALTYPEMISMATCH, "conditional type mismatch: %s", basictypenames[e.cast->type]); op = OP_IF_I; break; } QCC_FreeTemp(QCC_PR_StatementFlags (&pr_opcodes[op], e, nullsref, &st, flags)); return st; } QCC_statement_t *QCC_Generate_OP_IFNOT(QCC_sref_t e, pbool preserve) { unsigned int flags = (preserve?STFL_PRESERVEA:0); QCC_statement_t *st; int op = 0; if (!e.cast) e.cast = type_void; while (e.cast->type == ev_accessor) e.cast = e.cast->parentclass; switch(e.cast->type) { //int/pointer types case ev_entity: case ev_field: case ev_function: case ev_pointer: case ev_integer: case ev_uint: case ev_boolean: //should be 0, 1i, or 1.0f, either way -0 isn't a problem so we can use the vanilla OP_IFNOT_I for this op = OP_IFNOT_I; break; //emulated types case ev_string: QCC_PR_ParseWarning(WARN_IFSTRING_USED, "if (string) tests for null, not empty"); if (flag_ifstring) op = OP_IFNOT_S; else op = OP_IFNOT_I; break; case ev_float: if (flag_iffloat || QCC_OPCodeValid(&pr_opcodes[OP_IFNOT_F])) op = OP_IFNOT_F; else op = OP_IFNOT_I; break; case ev_vector: if (flag_ifvector) { e = QCC_PR_StatementFlags (&pr_opcodes[OP_NOT_V], e, nullsref, NULL, flags); op = OP_IF_I; } else { QCC_PR_ParseWarning(WARN_IFVECTOR_DISABLED, "if (vector) tests only the first element with the current compiler flags"); op = OP_IFNOT_I; } break; case ev_double: e = QCC_PR_StatementFlags (&pr_opcodes[OP_NE_D], e, QCC_MakeDoubleConst(0), NULL, flags); op = OP_IFNOT_I; break; case ev_int64: case ev_uint64: e = QCC_PR_StatementFlags (&pr_opcodes[OP_NE_I64], e, QCC_MakeInt64Const(0), NULL, flags); op = OP_IFNOT_I; break; case ev_variant: case ev_struct: case ev_union: case ev_void: default: QCC_PR_ParseWarning(WARN_CONDITIONALTYPEMISMATCH, "conditional type mismatch: %s", basictypenames[e.cast->type]); op = OP_IFNOT_I; break; } QCC_FreeTemp(QCC_PR_StatementFlags (&pr_opcodes[op], e, nullsref, &st, flags)); return st; } //for consistancy with if+ifnot QCC_statement_t *QCC_Generate_OP_GOTO(void) { QCC_statement_t *st; QCC_FreeTemp(QCC_PR_StatementFlags (&pr_opcodes[OP_GOTO], nullsref, nullsref, &st, STFL_DISCARDRESULT)); return st; } //called for return statements static void PR_GenerateReturnOuts(void) { unsigned int i; QCC_sref_t p; QCC_def_t *local; int parm; for (i = 0, parm = 0, local = pr.local_head.nextlocal; i < pr_scope->type->num_parms; i++) { if (pr_scope->type->params[i].out) { if (parm > MAX_PARMS) p = extra_parms[parm-MAX_PARMS]; else { p.sym = &def_parms[parm]; p.ofs = 0; p.cast = type_vector; } QCC_ForceUnFreeDef(p.sym); QCC_StoreToSRef(p, QCC_MakeSRefForce(local, 0, local->type), local->type, false, false); } parm += (pr_scope->type->params[i].type->size+2)/3; local = local->deftail->nextlocal; } } static void QCC_PR_ParseStatement_Using(void) { //the 'using(...) {}' control statement exists mainly to mute warnings about deprecations within its block. //it does this by basically creating a private alias for each name given in the parenthasis (new=orig to give the alias a different name). QCC_def_t *d; QCC_def_t *subscopestop; QCC_def_t *subscopestart = pr.local_tail; //QCC_type_t *lt = NULL, *type; pbool block = QCC_PR_CheckToken("("); do { /*type = QCC_PR_ParseType (false, true); if (type) { d = QCC_PR_GetDef (type, QCC_PR_ParseName(), pr_scope, true, 0, 0); if (QCC_PR_CheckToken("=")) QCC_PR_ParseInitializerDef(d, 0); QCC_FreeDef(d); lt = type; } else*/ { //define an alias of the variable with the same name const char *name = QCC_PR_ParseName(); QCC_def_t *def = QCC_PR_GetDef (NULL, name, pr_scope, false, 0, 0); if (!def) QCC_PR_ParseError(ERR_NOTDEFINED, "%s is not defined", name); def->referenced = true; if (QCC_PR_CheckToken("=")) name = QCC_PR_ParseName(); //they wanted a different name for it. if (def->deprecated) { if (*def->deprecated) //we have a reason for it QCC_PR_ParseWarning(WARN_MUTEDEPRECATEDVARIABLE, "Variable \"%s\" is deprecated: %s", def->name, def->deprecated); else //we don't have any reason for it. QCC_PR_ParseWarning(WARN_MUTEDEPRECATEDVARIABLE, "Variable \"%s\" is deprecated", def->name); } def = QCC_PR_DummyDef(def->type, name, pr_scope, def->arraysize, def, 0, true, GDF_STRIP|GDF_ALIAS); } } while(QCC_PR_CheckToken(",")); pr_assumetermtype = NULL; if (block) QCC_PR_Expect(")"); else { //applies to whole rest of scope... QCC_PR_Expect(";"); return; } subscopestop = pr_subscopedlocals?NULL:pr.local_tail->nextlocal; QCC_PR_ParseStatement(); //don't give the hanging ';' warning. //remove any new locals from the hashtable. //typically this is just the stuff inside the for(here;;) for (d = subscopestart->nextlocal; d != subscopestop; d = d->nextlocal) { if (!d->subscoped_away) { pHash_RemoveData(&localstable, d->name, d); d->subscoped_away = true; } } return; } /* ============ QCC_PR_ParseStatement_For pulled out of QCC_PR_ParseStatement because of stack use. ============ */ static void QCC_PR_ParseStatement_For(void) { int continues; int breaks; int i; QCC_sref_t e; QCC_def_t *d; QCC_statement_t *patch1, *patch2, *patch3, *patch4; int old_numstatements; int numtemp; QCC_def_t *subscopestop; QCC_def_t *subscopestart = pr.local_tail; QCC_statement_t temp[256]; continues = num_continues; breaks = num_breaks; QCC_PR_Expect("("); if (!QCC_PR_CheckToken(";")) { QCC_type_t *lt = NULL, *type; do { type = QCC_PR_ParseType (false, true, false); if (type) { d = QCC_PR_GetDef (type, QCC_PR_ParseName(), pr_scope, true, 0, 0); if (QCC_PR_CheckToken("=")) QCC_PR_ParseInitializerDef(d, 0); QCC_FreeDef(d); lt = type; } else { pr_assumetermtype = lt; pr_assumetermscope = pr_scope; pr_assumetermflags = 0; QCC_PR_DiscardExpression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); } } while(QCC_PR_CheckToken(",")); pr_assumetermtype = NULL; QCC_PR_Expect(";"); } subscopestop = pr_subscopedlocals?NULL:pr.local_tail->nextlocal; QCC_ClobberDef(NULL); patch2 = &statements[numstatements]; //restart of the loop if (!QCC_PR_CheckToken(";")) { conditional = 1; e = QCC_PR_Expression(TOP_PRIORITY, 0); conditional = 0; QCC_PR_Expect(";"); } else e = nullsref; if (e.cast) //final condition+jump QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_IFNOT_I], e, nullsref, &patch1, STFL_DISCARDRESULT)); else patch1 = NULL; if (!QCC_PR_CheckToken(")")) { old_numstatements = numstatements; QCC_PR_DiscardExpression(TOP_PRIORITY, 0); numtemp = numstatements - old_numstatements; if (numtemp > sizeof(temp)/sizeof(temp[0])) QCC_PR_ParseError(ERR_TOOCOMPLEX, "Update expression too large"); numstatements = old_numstatements; for (i = 0 ; i < numtemp ; i++) { temp[i] = statements[numstatements + i]; } QCC_PR_Expect(")"); } else numtemp = 0; //parse the statement block if (!QCC_PR_CheckToken(";")) QCC_PR_ParseStatement(); //don't give the hanging ';' warning. patch3 = &statements[numstatements]; //location for continues //reinsert the 'increment' statements. lets hope they didn't have any gotos... for (i = 0 ; i < numtemp ; i++) { statements[numstatements] = temp[i]; statements[numstatements].linenum = pr_token_line_last; numstatements++; } patch4 = QCC_Generate_OP_GOTO(); patch4->a.jumpofs = patch2 - patch4; if (patch1) patch1->b.jumpofs = &statements[numstatements] - patch1; //condition failure jumps here //fix up breaks+continues if (breaks != num_breaks) { for(i = breaks; i < num_breaks; i++) { patch1 = &statements[pr_breaks[i]]; statements[pr_breaks[i]].a.jumpofs = &statements[numstatements] - patch1; } num_breaks = breaks; } if (continues != num_continues) { for(i = continues; i < num_continues; i++) { patch1 = &statements[pr_continues[i]]; statements[pr_continues[i]].a.jumpofs = patch3 - patch1; } num_continues = continues; } //remove any new locals from the hashtable. //typically this is just the stuff inside the for(here;;) for (d = subscopestart->nextlocal; d != subscopestop; d = d->nextlocal) { if (!d->subscoped_away) { pHash_RemoveData(&localstable, d->name, d); d->subscoped_away = true; } } return; } /* ============ PR_ParseStatement ============ */ void QCC_PR_ParseStatement (void) { int continues; int breaks; int cases; int i; QCC_sref_t e, e2; QCC_def_t *d; QCC_statement_t *patch1, *patch2, *patch3; int statementstart = pr_source_line; pbool wasuntil; QCC_ClobberDef(NULL); //make sure any conditionals don't weird out. if (QCC_PR_CheckToken ("{")) { int startingtypes = numtypeinfos; d = pr.local_tail; while (!QCC_PR_CheckToken("}")) QCC_PR_ParseStatement (); if (pr_subscopedlocals) { //remove any new locals from the hashtable. for (d = d->nextlocal; d; d = d->nextlocal) { if (!d->subscoped_away) { pHash_RemoveData(&localstable, d->name, d); d->subscoped_away = true; } } } for (; startingtypes < numtypeinfos; startingtypes++) { if (qcc_typeinfo[startingtypes].typedefed) { qcc_typeinfo[startingtypes].typedefed = false; pHash_RemoveData(&typedeftable, qcc_typeinfo[startingtypes].name, &qcc_typeinfo[startingtypes]); } } return; } if (QCC_PR_CheckKeyword(keyword_return, "return")) { /* accumulate behaviour requires the ability to just run code without explicit returns. return = foo; sets the value that will be returned when the function finally exits, without returning now. return; returns that value now, without execing later accumulations. return 5; also returns now. */ if (QCC_PR_CheckToken (";")) { PR_GenerateReturnOuts(); if (pr_scope->type->aux_type->type != ev_void) { //accumulated functions are not required to return anything, on the assumption that a previous 'part' of the function did so if (pr_scope->type->aux_type->size > type_vector->size) QCC_PR_ParseError(ERR_BADEXTENSION, "\'%s\' returned nothing, expected %s", pr_scope->name, pr_scope->type->aux_type->name); //just make it fatal. too lazy to handle it else if ((!pr_scope->def || !pr_scope->def->accumulate) && !pr_scope->returndef.cast) QCC_PR_ParseWarning(WARN_MISSINGRETURNVALUE, "\'%s\' returned nothing, expected %s", pr_scope->name, pr_scope->type->aux_type->name); //this should not normally happen if (!pr_scope->returndef.cast) { //but if it does, allocate a local that can be return=foo; before the return. depend upon qc's null initialisation rules for the default value. pr_scope->returndef = QCC_PR_GetSRef(pr_scope->type->aux_type, "ret*", pr_scope, true, 0, GDF_NONE); QCC_FreeTemp(pr_scope->returndef); } } if (pr_scope->returndef.cast) { QCC_ForceUnFreeDef(pr_scope->returndef.sym); QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_RETURN], pr_scope->returndef, nullsref, NULL)); return; } // if (opt_return_only) // QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_DONE], nullsref, nullsref, NULL)); // else if (pr_scope->type->aux_type->type == ev_vector) //make sure bad returns don't return junk in the y+z members. QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_RETURN], QCC_MakeVectorConst(0,0,0), nullsref, NULL)); else QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_RETURN], nullsref, nullsref, NULL)); return; } if (QCC_PR_CheckToken ("=")) { QCC_ref_t r; if (pr_scope->type->aux_type->size > type_vector->size) QCC_PR_ParseError(ERR_BADEXTENSION, "\'%s\' not supported when returning %s", pr_scope->name, pr_scope->type->aux_type->name); //just make it fatal. too lazy to handle it if (!pr_scope->returndef.cast) pr_scope->returndef = QCC_PR_GetSRef(pr_scope->type->aux_type, "ret*", pr_scope, true, 0, GDF_NONE); else QCC_ForceUnFreeDef(pr_scope->returndef.sym); e = QCC_PR_Expression(TOP_PRIORITY, 0); QCC_PR_Expect (";"); QCC_StoreSRefToRef(QCC_PR_BuildRef(&r, REF_GLOBAL, pr_scope->returndef, nullsref, pr_scope->type->aux_type, false, 0), e, false, false); return; } e = QCC_PR_Expression (TOP_PRIORITY, 0); QCC_PR_Expect (";"); if (QCC_SRef_IsNull(e)) { QCC_FreeTemp(e); //return __NULL__; is allowed regardless of actual return type. switch (pr_scope->type->aux_type->type) { case ev_vector: e = QCC_MakeVectorConst(0, 0, 0); break; default: case ev_float: e = QCC_MakeFloatConst(0); break; } e.cast = pr_scope->type->aux_type; } else if (pr_scope->type->aux_type->type != e.cast->type) { if (pr_scope->type->aux_type->type == ev_void) { //returning a value inside a function defined to return void is bad dude. QCC_PR_ParseWarning(WARN_WRONGRETURNTYPE, "\'%s\' returned %s, expected %s", pr_scope->name, e.sym->type->name, pr_scope->type->aux_type->name); e = QCC_EvaluateCast(e, type_variant, true); } else e = QCC_EvaluateCast(e, pr_scope->type->aux_type, true); } PR_GenerateReturnOuts(); if (pr_scope->type->aux_type->size > type_vector->size) { QCC_StoreToPointer(pr_scope->returndef, nullsref, e, pr_scope->type->aux_type); QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_RETURN], pr_scope->returndef, nullsref, NULL)); //standard return has no real meaning, might as well return the address though even though we'll treat it as void. } else QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_RETURN], e, nullsref, NULL)); return; } if (QCC_PR_CheckKeyword(keyword_exit, "exit")) { PR_GenerateReturnOuts(); QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_DONE], nullsref, nullsref, NULL)); QCC_PR_Expect (";"); return; } if (QCC_PR_CheckKeyword(keyword_loop, "loop")) { continues = num_continues; breaks = num_breaks; patch2 = &statements[numstatements]; QCC_PR_ParseStatement (); QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_GOTO], nullsref, nullsref, &patch3)); patch3->a.jumpofs = patch2 - patch3; if (breaks != num_breaks) { for(i = breaks; i < num_breaks; i++) { patch1 = &statements[pr_breaks[i]]; statements[pr_breaks[i]].a.jumpofs = &statements[numstatements] - patch1; //jump to after the return-to-top goto } num_breaks = breaks; } if (continues != num_continues) { for(i = continues; i < num_continues; i++) { patch1 = &statements[pr_continues[i]]; statements[pr_continues[i]].a.jumpofs = patch2 - patch1; //jump back to top } num_continues = continues; } return; } wasuntil = QCC_PR_CheckKeyword(keyword_until, "until"); if (wasuntil || QCC_PR_CheckKeyword(keyword_while, "while")) { const QCC_eval_t *eval; continues = num_continues; breaks = num_breaks; QCC_PR_Expect ("("); patch2 = &statements[numstatements]; conditional = 1; e = QCC_PR_Expression (TOP_PRIORITY, 0); conditional = 0; eval = QCC_SRef_EvalConst(e); if (eval && /*opt_compound_jumps &&*/ e.cast->type == ev_float) { //optres_compound_jumps++; QCC_FreeTemp(e); if ((!eval->_float) != wasuntil) QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_GOTO], nullsref, nullsref, &patch1)); else patch1 = NULL; } else if (wasuntil) patch1 = QCC_Generate_OP_IF(e, false); else patch1 = QCC_Generate_OP_IFNOT(e, false); QCC_PR_Expect (")"); //after the line number is noted.. QCC_PR_ParseStatement (); patch3 = QCC_Generate_OP_GOTO(); patch3->a.jumpofs = patch2 - patch3; if (patch1) { if (patch1->op == OP_GOTO) patch1->a.jumpofs = &statements[numstatements] - patch1; else patch1->b.jumpofs = &statements[numstatements] - patch1; } if (breaks != num_breaks) { for(i = breaks; i < num_breaks; i++) { patch1 = &statements[pr_breaks[i]]; statements[pr_breaks[i]].a.jumpofs = &statements[numstatements] - patch1; //jump to after the return-to-top goto } num_breaks = breaks; } if (continues != num_continues) { for(i = continues; i < num_continues; i++) { patch1 = &statements[pr_continues[i]]; statements[pr_continues[i]].a.jumpofs = patch2 - patch1; //jump back to top } num_continues = continues; } return; } if (QCC_PR_CheckKeyword(keyword_for, "for")) { QCC_PR_ParseStatement_For(); return; } if (QCC_PR_CheckKeyword(keyword_do, "do")) { const QCC_eval_t *eval; pbool until; continues = num_continues; breaks = num_breaks; patch1 = &statements[numstatements]; QCC_PR_ParseStatement (); until = QCC_PR_CheckKeyword(keyword_until, "until"); if (!until) QCC_PR_Expect ("while"); QCC_PR_Expect ("("); patch3 = &statements[numstatements]; conditional = 1; e = QCC_PR_Expression (TOP_PRIORITY, 0); conditional = 0; eval = QCC_SRef_EvalConst(e); if (eval) { if (until?!eval->_int:eval->_int) { QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_GOTO], nullsref, nullsref, &patch2)); patch2->a.jumpofs = patch1 - patch2; } QCC_FreeTemp(e); } else { if (until) patch2 = QCC_Generate_OP_IFNOT(e, false); else patch2 = QCC_Generate_OP_IF(e, false); patch2->b.jumpofs = patch1 - patch2; } QCC_PR_Expect (")"); QCC_PR_Expect (";"); if (breaks != num_breaks) { for(i = breaks; i < num_breaks; i++) { patch2 = &statements[pr_breaks[i]]; statements[pr_breaks[i]].a.jumpofs = &statements[numstatements] - patch2; } num_breaks = breaks; } if (continues != num_continues) { //continue in do{}while(cond); jumps to the while, not the do. for(i = continues; i < num_continues; i++) { patch2 = &statements[pr_continues[i]]; statements[pr_continues[i]].a.jumpofs = patch3 - patch2; } num_continues = continues; } return; } if (QCC_PR_CheckKeyword(keyword_local, "local")) { // if (locals_end != numpr_globals) //is this breaking because of locals? // QCC_PR_ParseWarning("local vars after temp vars\n"); QCC_PR_ParseDefs (NULL, true); return; } if (pr_token_type == tt_name) { QCC_type_t *type = QCC_TypeForName(pr_token); if (type) { if (strncmp(pr_file_p, "::", 2)) { QCC_PR_ParseDefs (NULL, false); return; } } if ((keyword_var && !STRCMP ("var", pr_token)) || (keyword_noref && !STRCMP ("noref", pr_token)) || (keyword_string && !STRCMP ("string", pr_token)) || (keyword_float && !STRCMP ("float", pr_token)) || (keyword_double && !STRCMP ("double", pr_token)) || (keyword_entity && !STRCMP ("entity", pr_token)) || (keyword_vector && !STRCMP ("vector", pr_token)) || (keyword_integer && !STRCMP ("integer", pr_token)) || (keyword_unsigned && !STRCMP ("unsigned", pr_token)) || (keyword_signed && !STRCMP ("signed", pr_token)) || (keyword_long && !STRCMP ("long", pr_token)) || (keyword_int && !STRCMP ("int", pr_token)) || (keyword_short && !STRCMP ("short", pr_token)) || (keyword_char && !STRCMP ("char", pr_token)) || ( !STRCMP ("_Bool", pr_token)) || (keyword_register && !STRCMP ("register", pr_token)) || (keyword_volatile && !STRCMP ("volatile", pr_token)) || (keyword_static && !STRCMP ("static", pr_token)) || (keyword_class && !STRCMP ("class", pr_token)) || (keyword_struct && !STRCMP ("struct", pr_token)) || (keyword_union && !STRCMP ("union", pr_token)) || (keyword_enum && !STRCMP ("enum", pr_token)) || (keyword_extern && !STRCMP ("extern", pr_token)) || (keyword_auto && !STRCMP ("auto", pr_token)) || (keyword_typedef && !STRCMP ("typedef", pr_token)) || (keyword_const && !STRCMP ("const", pr_token))) { QCC_PR_ParseDefs (NULL, true); return; } } if (pr_token_type == tt_punct && QCC_PR_PeekToken (".")) { //for local .entity without var/local QCC_PR_ParseDefs (NULL, true); return; } if (QCC_PR_CheckKeyword(keyword_state, "state")) { QCC_PR_Expect("["); QCC_PR_ParseState(); QCC_PR_Expect(";"); return; } if (QCC_PR_CheckToken("#")) { char *name; float frame = pr_immediate._float; QCC_PR_Lex(); name = QCC_PR_ParseName(); QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_STATE], QCC_MakeFloatConst(frame), QCC_PR_GetSRef(type_function, name, NULL, false, 0, false), NULL)); QCC_PR_Expect(";"); return; } if (QCC_PR_CheckKeyword(keyword_if, "if")) { unsigned int oldnumst, oldlab; pbool striptruth = false; pbool stripfalse = false; const QCC_eval_t *eval; int negate = QCC_PR_CheckKeyword(keyword_not, "not")/*hexenc*/; if (!negate && QCC_PR_CheckToken("!")) { QCC_PR_ParseWarning (WARN_FTE_SPECIFIC, "if !() is specific to fteqcc"); negate = 2; } else if (negate && qcc_targetformat != QCF_HEXEN2 && qcc_targetformat != QCF_UHEXEN2 && qcc_targetformat != QCF_FTEH2) QCC_PR_ParseWarning (WARN_FTE_SPECIFIC, "if not() is specific to fteqcc or hexen2"); QCC_PR_Expect ("("); conditional = 1; e = QCC_PR_Expression (TOP_PRIORITY, 0); conditional = 0; if (negate == 2) { if (e.cast->type == ev_string/*deal with empty properly*/ || e.cast->type == ev_float/*deal with -0.0*/ || e.cast->type == ev_accessor/*its complicated*/ ) { e = QCC_PR_GenerateLogicalNot(e, "Type mismatch: !%s"); negate = 0; } } eval = QCC_SRef_EvalConst(e); // negate = negate != 0; oldnumst = numstatements; if (eval) { if (e.cast->type == ev_float) striptruth = eval->_float == 0; else striptruth = eval->_int == 0; if (negate) striptruth = !striptruth; stripfalse = !striptruth; patch1 = NULL; QCC_FreeTemp(e); if (striptruth) patch1 = QCC_Generate_OP_GOTO(); } else if (negate) { patch1 = QCC_Generate_OP_IF(e, false); } else { patch1 = QCC_Generate_OP_IFNOT(e, false); } QCC_PR_Expect (")"); //close bracket is after we save the statement to mem (so debugger does not show the if statement as being on the line after oldlab = num_labels; QCC_PR_ParseStatement (); if (striptruth && oldlab == num_labels) { QCC_UngenerateStatements(oldnumst); patch1 = NULL; } else striptruth = false; if (QCC_PR_CheckKeyword (keyword_else, "else")) { int lastwasreturn; lastwasreturn = statements[numstatements-1].op == OP_RETURN || statements[numstatements-1].op == OP_DONE || statements[numstatements-1].op == OP_GOTO; //the last statement of the if was a return, so we don't need the goto at the end if (lastwasreturn && opt_compound_jumps && patch1 && !QCC_AStatementJumpsTo(numstatements, patch1-statements, numstatements)) { // QCC_PR_ParseWarning(0, "optimised the else"); optres_compound_jumps++; if (patch1) patch1->b.jumpofs = &statements[numstatements] - patch1; oldnumst = numstatements; oldlab = num_labels; QCC_PR_ParseStatement (); if (stripfalse && oldlab == num_labels) { patch2 = NULL; QCC_UngenerateStatements(oldnumst); if (patch1) patch1->b.jumpofs = &statements[numstatements] - patch1; } } else { // QCC_PR_ParseWarning(0, "using the else"); oldnumst = numstatements; if (striptruth) patch2 = NULL; else patch2 = QCC_Generate_OP_GOTO(); if (patch1) patch1->b.jumpofs = &statements[numstatements] - patch1; oldlab = num_labels; QCC_PR_ParseStatement (); if (stripfalse && oldlab == num_labels) { patch2 = NULL; QCC_UngenerateStatements(oldnumst); if (patch1) patch1->b.jumpofs = &statements[numstatements] - patch1; } if (patch2) patch2->a.jumpofs = &statements[numstatements] - patch2; /*FIXME: this doesn't work right if (patch1 && patch2) { if (QCC_PR_StatementBlocksMatch(patch1+1, patch2-(patch1+1), patch2+1, &statements[numstatements] - (patch2+1))) QCC_PR_ParseWarning(0, "Two identical blocks each side of an else"); } */ } } else if (patch1) { if (patch1->op == OP_GOTO) patch1->a.jumpofs = &statements[numstatements] - patch1; else patch1->b.jumpofs = &statements[numstatements] - patch1; } return; } if (QCC_PR_CheckKeyword(keyword_switch, "switch")) { int op; int hcstyle; int defaultcase = -1; int oldst; QCC_type_t *switchtype; struct accessor_s *acc; breaks = num_breaks; cases = num_cases; QCC_PR_Expect ("("); conditional = 1; e = QCC_PR_Expression (TOP_PRIORITY, 0); conditional = 0; e.sym->referenced = true; //expands //switch (CONDITION) //{ //case 1: // break; //case 2: //default: // break; //} //to // x = CONDITION, goto start // l1: // goto end // l2: // def: // goto end // goto end P1 // start: // if (x == 1) goto l1; // if (x == 2) goto l2; // goto def // end: //x is emitted in an opcode, stored as a register that we cannot access later. //it should be possible to nest these. switchtype = e.cast; switch(switchtype->type==ev_enum?switchtype->aux_type->type:switchtype->type) { case ev_float: op = OP_SWITCH_F; break; case ev_entity: //whu??? op = OP_SWITCH_E; break; case ev_vector: op = OP_SWITCH_V; break; case ev_string: op = OP_SWITCH_S; break; case ev_function: op = OP_SWITCH_FNC; break; default: //err hmm. op = 0; break; } if (op) hcstyle = QCC_OPCodeValid(&pr_opcodes[op]); else hcstyle = false; QCC_ClobberDef(NULL); if (hcstyle) QCC_FreeTemp(QCC_PR_StatementFlags (&pr_opcodes[op], e, nullsref, &patch1, STFL_DISCARDRESULT)); else { patch1 = QCC_Generate_OP_GOTO(); //fixme: rearrange this, to avoid the goto QCC_FreeTemp(e); } QCC_PR_Expect (")"); //close bracket is after we save the statement to mem (so debugger does not show the if statement as being on the line after oldst = numstatements; QCC_PR_ParseStatement (); //this is so that a missing goto at the end of your switch doesn't end up in the jumptable again if (oldst == numstatements || !QCC_StatementIsAJump(numstatements-1, numstatements-1) || QCC_AStatementJumpsTo(numstatements, pr_scope->code, numstatements)) { patch2 = QCC_Generate_OP_GOTO(); //the P1 statement/the theyforgotthebreak statement. // QCC_PR_ParseWarning(0, "emitted goto"); } else { patch2 = NULL; // QCC_PR_ParseWarning(0, "No goto"); } if (hcstyle) patch1->b.jumpofs = &statements[numstatements] - patch1; //the goto start part else patch1->a.jumpofs = &statements[numstatements] - patch1; //the goto start part oldst = numstatements; for (acc = switchtype->accessors; acc; acc = acc->next) { const QCC_eval_t *match = QCC_SRef_EvalConst(acc->staticval); if (!match) continue; //not an enum value, ignore it. for (i = cases; i < num_cases; i++) { if (!pr_casesref[i].cast) break; //its a default if (pr_casesref2[i].cast) { //caserange break; //FIXME: too lazy to check these. } else { //case if (pr_casesref[i].type == REF_GLOBAL && pr_casesref[i].cast == switchtype) { const QCC_eval_t *eval = QCC_SRef_EvalConst(pr_casesref[i].base); if (!eval) break; //can't verify it if (!memcmp(eval, match, sizeof(*eval)*switchtype->size)) break; //validated. } else break; //can't verify it } } if (i == num_cases) { QCC_PR_ParseWarning(0, "%s::%s not part of switch", switchtype->name, acc->fieldname); } } QCC_ForceUnFreeDef(e.sym); //in the following code, e should still be live for (i = cases; i < num_cases; i++) { if (!pr_casesref[i].cast) { if (defaultcase >= 0) QCC_PR_ParseError(ERR_MULTIPLEDEFAULTS, "Duplicated default case"); defaultcase = i; } else { QCC_sref_t dmin, dmax; dmin = QCC_RefToDef(&pr_casesref[i], true); if (dmin.cast->type != e.cast->type) dmin = QCC_SupplyConversion(dmin, e.cast->type, true); if (pr_casesref2[i].cast) { dmax = QCC_RefToDef(&pr_casesref2[i], true); if (dmax.cast->type != e.cast->type) dmax = QCC_SupplyConversion(dmax, e.cast->type, true); if (hcstyle) { QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_CASERANGE], dmin, dmax, &patch3)); patch3->c.jumpofs = &statements[pr_cases[i]] - patch3; } else { QCC_sref_t e3; if (e.cast->type == ev_float) { e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_GE_F], e, dmin, NULL, STFL_PRESERVEA); e3 = QCC_PR_StatementFlags (&pr_opcodes[OP_LE_F], e, dmax, NULL, STFL_PRESERVEA); e2 = QCC_PR_Statement (&pr_opcodes[OP_AND_F], e2, e3, NULL); patch3 = QCC_Generate_OP_IF(e2, false); patch3->b.jumpofs = &statements[pr_cases[i]] - patch3; } else if (e.cast->type == ev_double) { e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_GE_D], e, dmin, NULL, STFL_PRESERVEA); e3 = QCC_PR_StatementFlags (&pr_opcodes[OP_LE_D], e, dmax, NULL, STFL_PRESERVEA); e2 = QCC_PR_Statement (&pr_opcodes[OP_AND_F], e2, e3, NULL); patch3 = QCC_Generate_OP_IF(e2, false); patch3->b.jumpofs = &statements[pr_cases[i]] - patch3; } else if (e.cast->type == ev_integer) { e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_GE_I], e, dmin, NULL, STFL_PRESERVEA); e3 = QCC_PR_StatementFlags (&pr_opcodes[OP_LE_I], e, dmax, NULL, STFL_PRESERVEA); e2 = QCC_PR_Statement (&pr_opcodes[OP_AND_I], e2, e3, NULL); patch3 = QCC_Generate_OP_IF(e2, false); patch3->b.jumpofs = &statements[pr_cases[i]] - patch3; } else if (e.cast->type == ev_uint) { e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_GE_U], e, dmin, NULL, STFL_PRESERVEA); e3 = QCC_PR_StatementFlags (&pr_opcodes[OP_LE_U], e, dmax, NULL, STFL_PRESERVEA); e2 = QCC_PR_Statement (&pr_opcodes[OP_AND_I], e2, e3, NULL); patch3 = QCC_Generate_OP_IF(e2, false); patch3->b.jumpofs = &statements[pr_cases[i]] - patch3; } else if (e.cast->type == ev_int64) { e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_GE_I64], e, dmin, NULL, STFL_PRESERVEA); e3 = QCC_PR_StatementFlags (&pr_opcodes[OP_LE_I64], e, dmax, NULL, STFL_PRESERVEA); e2 = QCC_PR_Statement (&pr_opcodes[OP_AND_I], e2, e3, NULL); patch3 = QCC_Generate_OP_IF(e2, false); patch3->b.jumpofs = &statements[pr_cases[i]] - patch3; } else if (e.cast->type == ev_uint64) { e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_GE_U64], e, dmin, NULL, STFL_PRESERVEA); e3 = QCC_PR_StatementFlags (&pr_opcodes[OP_LE_U64], e, dmax, NULL, STFL_PRESERVEA); e2 = QCC_PR_Statement (&pr_opcodes[OP_AND_I], e2, e3, NULL); patch3 = QCC_Generate_OP_IF(e2, false); patch3->b.jumpofs = &statements[pr_cases[i]] - patch3; } else QCC_PR_ParseWarning(WARN_SWITCHTYPEMISMATCH, "switch caserange MUST be a float or integer"); } } else { if (hcstyle) { QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_CASE], dmin, nullsref, &patch3)); patch3->b.jumpofs = &statements[pr_cases[i]] - patch3; } else { const QCC_eval_t *eval = QCC_SRef_EvalConst(dmin); if (!eval || eval->_int) { switch(e.cast->type==ev_enum?e.cast->aux_type->type:e.cast->type) { case ev_float: e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_EQ_F], e, dmin, NULL, STFL_PRESERVEA); break; case ev_entity: //whu??? e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_EQ_E], e, dmin, NULL, STFL_PRESERVEA); break; case ev_vector: e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_EQ_V], e, dmin, NULL, STFL_PRESERVEA); break; case ev_string: e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_EQ_S], e, dmin, NULL, STFL_PRESERVEA); break; case ev_function: e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_EQ_FNC], e, dmin, NULL, STFL_PRESERVEA); break; case ev_field: e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_EQ_FNC], e, dmin, NULL, STFL_PRESERVEA); break; case ev_pointer: e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_EQ_P], e, dmin, NULL, STFL_PRESERVEA); break; case ev_integer: case ev_uint: e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_EQ_I], e, dmin, NULL, STFL_PRESERVEA); break; case ev_int64: case ev_uint64: e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_EQ_I64], e, dmin, NULL, STFL_PRESERVEA); break; case ev_double: e2 = QCC_PR_StatementFlags (&pr_opcodes[OP_EQ_D], e, dmin, NULL, STFL_PRESERVEA); break; default: QCC_PR_ParseError(ERR_BADSWITCHTYPE, "Bad switch type"); e2 = nullsref; break; } QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_IF_I], e2, nullsref, &patch3)); } else { QCC_FreeTemp(dmin); QCC_UnFreeTemp(e); patch3 = QCC_Generate_OP_IFNOT(e, false); } patch3->b.jumpofs = &statements[pr_cases[i]] - patch3; } } } } QCC_FreeTemp(e); if (defaultcase>=0) { patch3 = QCC_Generate_OP_GOTO(); patch3->a.jumpofs = &statements[pr_cases[defaultcase]] - patch3; } num_cases = cases; patch3 = &statements[numstatements]; if (patch2) patch2->a.jumpofs = patch3 - patch2; //set P1 jump if (breaks != num_breaks) { for(i = breaks; i < num_breaks; i++) { patch2 = &statements[pr_breaks[i]]; patch2->a.jumpofs = patch3 - patch2; } num_breaks = breaks; } //update the jumptable statements to hide as part of the switch itself. while (oldst < numstatements) statements[oldst++].linenum = patch1->linenum; return; } if (QCC_PR_CheckKeyword(keyword_using, "using")) { QCC_PR_ParseStatement_Using(); return; } if (QCC_PR_CheckKeyword(keyword_asm, "asm")) { if (QCC_PR_CheckToken("{")) { while (!QCC_PR_CheckToken("}")) QCC_PR_ParseAsm (); } else QCC_PR_ParseAsm (); return; } //frikqcc-style labels if (QCC_PR_CheckToken(":")) { if (pr_token_type != tt_name) { QCC_PR_ParseError(ERR_BADLABELNAME, "invalid label name \"%s\"", pr_token); return; } for (i = 0; i < num_labels; i++) if (!STRNCMP(pr_labels[i].name, pr_token, sizeof(pr_labels[num_labels].name) -1)) { QCC_PR_ParseWarning(WARN_DUPLICATELABEL, "Duplicate label %s", pr_token); QCC_PR_Lex(); return; } if (num_labels >= max_labels) { max_labels += 8; pr_labels = realloc(pr_labels, sizeof(*pr_labels)*max_labels); } QC_strlcpy(pr_labels[num_labels].name, pr_token, sizeof(pr_labels[num_labels].name)); pr_labels[num_labels].lineno = pr_source_line; pr_labels[num_labels].statementno = numstatements; num_labels++; // QCC_PR_ParseWarning("Gotos are evil"); QCC_PR_Lex(); return; } if (QCC_PR_CheckKeyword(keyword_goto, "goto")) { if (pr_token_type != tt_name) { QCC_PR_ParseError(ERR_NOLABEL, "invalid label name \"%s\"", pr_token); return; } patch2 = QCC_Generate_OP_GOTO(); QCC_PR_GotoStatement (patch2, pr_token); // QCC_PR_ParseWarning("Gotos are evil"); QCC_PR_Lex(); QCC_PR_Expect(";"); return; } if (QCC_PR_CheckKeyword(keyword_break, "break")) { if (!STRCMP ("(", pr_token)) { //make sure it wasn't a call to the break function. QCC_PR_IncludeChunk("break(", true, NULL); QCC_PR_Lex(); //so it sees the break. } else { if (num_breaks >= max_breaks) { max_breaks += 8; pr_breaks = realloc(pr_breaks, sizeof(*pr_breaks)*max_breaks); } pr_breaks[num_breaks] = numstatements; QCC_PR_Statement (&pr_opcodes[OP_GOTO], nullsref, nullsref, NULL); num_breaks++; QCC_PR_Expect(";"); return; } } if (QCC_PR_CheckKeyword(keyword_continue, "continue")) { if (num_continues >= max_continues) { max_continues += 8; pr_continues = realloc(pr_continues, sizeof(*pr_continues)*max_continues); } pr_continues[num_continues] = numstatements; QCC_PR_Statement (&pr_opcodes[OP_GOTO], nullsref, nullsref, NULL); num_continues++; QCC_PR_Expect(";"); return; } if (QCC_PR_CheckKeyword(keyword_case, "case")) { if (num_cases >= max_cases) { max_cases += 8; pr_cases = realloc(pr_cases, sizeof(*pr_cases)*max_cases); pr_casesref = realloc(pr_casesref, sizeof(*pr_casesref)*max_cases); pr_casesref2 = realloc(pr_casesref2, sizeof(*pr_casesref2)*max_cases); } pr_cases[num_cases] = numstatements; QCC_PR_RefExpression(&pr_casesref[num_cases], TOP_PRIORITY, EXPR_DISALLOW_COMMA); if (QCC_PR_CheckToken("..")) { //const QCC_eval_t *evalmin, *evalmax; QCC_PR_RefExpression (&pr_casesref2[num_cases], TOP_PRIORITY, EXPR_DISALLOW_COMMA); //pr_casesref2[num_cases] = QCC_SupplyConversion(pr_casesdef2[num_cases], pr_casesdef[num_cases].cast->type, true); /*evalmin = QCC_Ref_EvalConst(pr_casesref[num_cases]); evalmax = QCC_Ref_EvalConst(pr_casesref2[num_cases]); if (evalmin && evalmax) { if ((pr_casesref[num_cases].cast->type == ev_integer && evalmin->_int >= evalmax->_int) || (pr_casesref[num_cases].cast->type == ev_float && evalmin->_float >= evalmax->_float)) QCC_PR_ParseError(ERR_CASENOTIMMEDIATE, "Caserange statement uses backwards range\n"); }*/ } else pr_casesref2[num_cases].cast = NULL; if (numstatements != pr_cases[num_cases]) QCC_PR_ParseError(ERR_CASENOTIMMEDIATE, "Case statements may not use formulas\n"); //fixme: insert them... num_cases++; QCC_PR_Expect(":"); return; } if (QCC_PR_CheckKeyword(keyword_default, "default")) { if (num_cases >= max_cases) { max_cases += 8; pr_cases = realloc(pr_cases, sizeof(*pr_cases)*max_cases); pr_casesref = realloc(pr_casesref, sizeof(*pr_casesref)*max_cases); pr_casesref2 = realloc(pr_casesref2, sizeof(*pr_casesref2)*max_cases); } pr_cases[num_cases] = numstatements; pr_casesref[num_cases].cast = NULL; pr_casesref2[num_cases].cast = NULL; num_cases++; QCC_PR_Expect(":"); return; } if (QCC_PR_CheckKeyword(keyword_thinktime, "thinktime")) { QCC_sref_t nextthink; QCC_sref_t time; e = QCC_PR_Expression (TOP_PRIORITY, 0); QCC_PR_Expect(":"); e2 = QCC_PR_Expression (TOP_PRIORITY, 0); e2 = QCC_SupplyConversion(e2, ev_float, true); if (e.cast->type != ev_entity || e2.cast->type != ev_float) QCC_PR_ParseError(ERR_THINKTIMETYPEMISMATCH, "thinktime type mismatch"); if (QCC_OPCodeValid(&pr_opcodes[OP_THINKTIME])) QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_THINKTIME], e, e2, NULL)); else { nextthink = QCC_PR_GetSRef(NULL, "nextthink", NULL, false, 0, false); if (!nextthink.cast) QCC_PR_ParseError (ERR_UNKNOWNVALUE, "Unknown value \"%s\"", "nextthink"); time = QCC_PR_GetSRef(type_float, "time", NULL, false, 0, false); if (!time.cast) QCC_PR_ParseError (ERR_UNKNOWNVALUE, "Unknown value \"%s\"", "time"); nextthink = QCC_PR_Statement(&pr_opcodes[OP_ADDRESS], e, nextthink, NULL); time = QCC_PR_Statement(&pr_opcodes[OP_ADD_F], time, e2, NULL); QCC_FreeTemp(QCC_PR_Statement(&pr_opcodes[OP_STOREP_F], time, nextthink, NULL)); } QCC_PR_Expect(";"); return; } if (QCC_PR_CheckToken(";")) { int osl = pr_source_line; pr_source_line = statementstart; if (!expandedemptymacro) { if (!currentchunk || !currentchunk->cnst) QCC_PR_ParseWarning(WARN_POINTLESSSTATEMENT, "Hanging ';'"); while (QCC_PR_CheckToken(";")) ; } pr_source_line = osl; return; } //C-style labels. if (pr_token_type == tt_name && pr_file_p[0] == ':' && pr_file_p[1] != ':') { if (pr_token_type != tt_name) { QCC_PR_ParseError(ERR_BADLABELNAME, "invalid label name \"%s\"", pr_token); return; } for (i = 0; i < num_labels; i++) if (!STRNCMP(pr_labels[i].name, pr_token, sizeof(pr_labels[num_labels].name) -1)) { QCC_PR_ParseWarning(WARN_DUPLICATELABEL, "Duplicate label %s", pr_token); QCC_PR_Lex(); return; } if (num_labels >= max_labels) { max_labels += 8; pr_labels = realloc(pr_labels, sizeof(*pr_labels)*max_labels); } QC_strlcpy(pr_labels[num_labels].name, pr_token, sizeof(pr_labels[num_labels].name)); pr_labels[num_labels].lineno = pr_source_line; pr_labels[num_labels].statementno = numstatements; num_labels++; // QCC_PR_ParseWarning("Gotos are evil"); QCC_PR_Lex(); QCC_PR_Expect(":"); return; } // qcc_functioncalled=0; QCC_PR_DiscardExpression (TOP_PRIORITY, 0); expandedemptymacro = false; QCC_PR_Expect (";"); // qcc_functioncalled=false; } /* ============== PR_ParseState States are special functions made for convenience. They automatically set frame, nextthink (implicitly), and think (allowing forward definitions). // void() name = [framenum, nextthink] {code} // expands to: // function void name () // { // self.frame=framenum; // self.nextthink = time + 0.1; // self.think = nextthink // // }; ============== */ void QCC_PR_ParseState (void) { QCC_sref_t s1, def; pbool isinc; //FIXME: this is ambiguous with pre-inc and post-inc logic. if ((isinc=QCC_PR_CheckToken("++")) || QCC_PR_CheckToken("--")) { const QCC_eval_t *first, *last; int dir = 0; int op = OP_CSTATE; if (QCC_PR_CheckToken("(")) { op = OP_CWSTATE; if (!QCC_PR_CheckToken("w")) QCC_PR_Expect("W"); QCC_PR_Expect(")"); } // s1 = QCC_PR_ParseImmediate (); s1 = QCC_PR_Expression (TOP_PRIORITY, EXPR_DISALLOW_COMMA); s1 = QCC_SupplyConversion(s1, ev_float, true); QCC_PR_Expect(".."); // def = QCC_PR_ParseImmediate (); def = QCC_PR_Expression (TOP_PRIORITY, EXPR_DISALLOW_COMMA); def = QCC_SupplyConversion(def, ev_float, true); QCC_PR_Expect ("]"); if (s1.cast->type != ev_float || def.cast->type != ev_float) QCC_PR_ParseError(ERR_STATETYPEMISMATCH, "state type mismatch"); first = QCC_SRef_EvalConst(s1); last = QCC_SRef_EvalConst(def); if (first&&last) { //whether its a ++ or -- doesn't really matter, but hcc generates an error so we should at least generate a warning. dir = (last->_float >= first->_float)?1:-1; if (isinc) { if (first->_float > last->_float) QCC_PR_ParseWarning(ERR_STATETYPEMISMATCH, "Forwards State Cycle with backwards range"); } else { if (first->_float < last->_float) QCC_PR_ParseWarning(ERR_STATETYPEMISMATCH, "Forwards State Cycle with backwards range"); } } if (QCC_OPCodeValid(&pr_opcodes[op])) QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[op], s1, def, NULL)); else { QCC_statement_t *patch1, *entercycf, *entercycb, *fwd, *back; QCC_sref_t t1, t2; QCC_sref_t framef, frame; QCC_sref_t self; QCC_sref_t cycle_wrapped; self = QCC_PR_GetSRef(type_entity, "self", NULL, false, 0, false); framef = QCC_PR_GetSRef(NULL, (op==OP_CWSTATE)?"weaponframe":"frame", NULL, false, 0, false); cycle_wrapped = QCC_PR_GetSRef(type_float, "cycle_wrapped", NULL, false, 0, false); frame = QCC_PR_StatementFlags(&pr_opcodes[OP_LOAD_F], self, framef, NULL, 0); if (cycle_wrapped.cast) QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(0), cycle_wrapped, NULL, STFL_PRESERVEB)); if (dir) fwd = NULL; //can skip the checks else { t1 = QCC_PR_StatementFlags(&pr_opcodes[OP_GE_F], def, s1, NULL, STFL_PRESERVEA|STFL_PRESERVEB); fwd = QCC_Generate_OP_IFNOT(t1, false); } if (dir >= 0) { //this block is the 'it's in a forwards direction' //make sure the frame is within the bounds given. t1 = QCC_PR_StatementFlags(&pr_opcodes[OP_LT_F], frame, s1, NULL, STFL_PRESERVEA|STFL_PRESERVEB); t2 = QCC_PR_StatementFlags(&pr_opcodes[OP_GT_F], frame, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB); t1 = QCC_PR_Statement(&pr_opcodes[OP_OR_F], t1, t2, NULL); patch1 = QCC_Generate_OP_IFNOT(t1, false); { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], s1, frame, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); entercycf = QCC_Generate_OP_GOTO(); } patch1->b.jumpofs = &statements[numstatements] - patch1; QCC_PR_SimpleStatement(&pr_opcodes[OP_ADD_F], frame, QCC_MakeFloatConst(1), frame, false); t1 = QCC_PR_StatementFlags(&pr_opcodes[OP_GT_F], frame, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB); patch1 = QCC_Generate_OP_IFNOT(t1, false); { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], s1, frame, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); if (cycle_wrapped.cast) QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(1), cycle_wrapped, NULL, STFL_PRESERVEB)); } patch1->b.jumpofs = &statements[numstatements] - patch1; } else entercycf = NULL; if (fwd) { back = QCC_Generate_OP_GOTO(); fwd->b.jumpofs = &statements[numstatements] - fwd; } else back = NULL; if (dir <= 0) { //reverse animation. //make sure the frame is within the bounds given. t1 = QCC_PR_StatementFlags(&pr_opcodes[OP_GT_F], frame, s1, NULL, STFL_PRESERVEA|STFL_PRESERVEB); t2 = QCC_PR_StatementFlags(&pr_opcodes[OP_LT_F], frame, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB); t1 = QCC_PR_Statement(&pr_opcodes[OP_OR_F], t1, t2, NULL); patch1 = QCC_Generate_OP_IFNOT(t1, false); { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], s1, frame, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); entercycb = QCC_Generate_OP_GOTO(); } patch1->b.jumpofs = &statements[numstatements] - patch1; QCC_PR_SimpleStatement(&pr_opcodes[OP_SUB_F], frame, QCC_MakeFloatConst(1), frame, false); t1 = QCC_PR_StatementFlags(&pr_opcodes[OP_LT_F], frame, def, NULL, STFL_PRESERVEA); patch1 = QCC_Generate_OP_IFNOT(t1, false); { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], s1, frame, NULL, STFL_PRESERVEB)); if (cycle_wrapped.cast) QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], QCC_MakeFloatConst(1), cycle_wrapped, NULL, 0)); } patch1->b.jumpofs = &statements[numstatements] - patch1; } else entercycb = NULL; if (back) back->a.jumpofs = &statements[numstatements] - back; if (entercycf) /*out of range*/entercycf->a.jumpofs = &statements[numstatements] - entercycf; if (entercycb) /*out of range*/entercycb->a.jumpofs = &statements[numstatements] - entercycb; //self.frame = frame happens with the normal state opcode. QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[(op==OP_CWSTATE)?OP_WSTATE:OP_STATE], frame, QCC_MakeSRef(pr_scope->def, 0, pr_scope->type), NULL)); } return; } s1 = QCC_PR_Expression (TOP_PRIORITY, EXPR_DISALLOW_COMMA); s1 = QCC_SupplyConversion(s1, ev_float, true); if (!QCC_PR_CheckToken (",")) QCC_PR_ParseWarning(WARN_UNEXPECTEDPUNCT, "missing comma in state definition"); pr_assumetermtype = type_function; pr_assumetermscope = pr_scope->parentscope; pr_assumetermflags = GDF_CONST | (pr_assumetermscope?GDF_STATIC:0); def = QCC_PR_Expression (TOP_PRIORITY, EXPR_DISALLOW_COMMA); if (typecmp(def.cast, type_function)) { if (!QCC_SRef_IsNull(def)) { char typebuf1[256]; char typebuf2[256]; QCC_PR_ParseErrorPrintSRef (ERR_TYPEMISMATCH, def, "Type mismatch: %s, should be %s", TypeName(def.cast, typebuf1, sizeof(typebuf1)), TypeName(type_function, typebuf2, sizeof(typebuf2))); } } pr_assumetermtype = NULL; QCC_PR_Expect ("]"); QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_STATE], s1, def, NULL)); } void QCC_PR_ParseAsm(void) { QCC_statement_t *patch1; int op, p; QCC_sref_t a, b, c; if (QCC_PR_CheckKeyword(keyword_local, "local")) { QCC_PR_ParseDefs (NULL, true); return; } for (op = 0; op < OP_NUMOPS; op++) { if (!STRCMP(pr_token, pr_opcodes[op].opname)) { QCC_PR_Lex(); if (/*pr_opcodes[op].priority==-1 &&*/ pr_opcodes[op].associative!=ASSOC_LEFT) { if (pr_opcodes[op].type_a==NULL) { patch1 = QCC_PR_SimpleStatement(&pr_opcodes[op], nullsref, nullsref, nullsref, true); if (pr_token_type == tt_name) { QCC_PR_GotoStatement(patch1, QCC_PR_ParseName()); } else { p = (int)pr_immediate._float; patch1->a.ofs = p; } QCC_PR_Lex(); } else if (pr_opcodes[op].type_b==NULL) { a = QCC_PR_ParseValue(pr_classtype, false, false, true); patch1 = QCC_PR_SimpleStatement(&pr_opcodes[op], a, nullsref, nullsref, true); if (pr_token_type == tt_name) { QCC_PR_GotoStatement(patch1, QCC_PR_ParseName()); } else { p = (int)pr_immediate._float; patch1->b.ofs = (int)p; } QCC_PR_Lex(); } else { if (QCC_PR_CheckName("void")) a = nullsref; else a = QCC_PR_ParseValue(pr_classtype, false, false, true); QCC_PR_Expect(","); if (QCC_PR_CheckName("void")) b = nullsref; else b = QCC_PR_ParseValue(pr_classtype, false, false, true); patch1 = QCC_PR_SimpleStatement(&pr_opcodes[op], a, b, nullsref, true); if (pr_token_type == tt_name) { QCC_PR_GotoStatement(patch1, QCC_PR_ParseName()); } else { p = (int)pr_immediate._float; patch1->c.ofs = p; } } } else { if (pr_opcodes[op].type_a != &type_void) { if (QCC_PR_CheckName("void")) a = nullsref; else a = QCC_PR_ParseValue(pr_classtype, false, false, true); } else a=nullsref; if (pr_opcodes[op].type_b != &type_void) { QCC_PR_CheckToken(","); if (QCC_PR_CheckName("void")) b = nullsref; else b = QCC_PR_ParseValue(pr_classtype, false, false, true); } else b=nullsref; if (pr_opcodes[op].associative==ASSOC_LEFT && pr_opcodes[op].type_c != &type_void) { QCC_PR_CheckToken(","); if (QCC_PR_CheckName("void")) c = nullsref; else c = QCC_PR_ParseValue(pr_classtype, false, false, true); } else c=nullsref; QCC_PR_SimpleStatement(&pr_opcodes[op], a, b, c, true); } QCC_PR_Expect(";"); return; } } QCC_PR_ParseError(ERR_BADOPCODE, "Bad op code name %s", pr_token); } static pbool QCC_FuncJumpsTo(int first, int last, int statement) { int st; for (st = first; st < last; st++) { if (pr_opcodes[statements[st].op].type_a == NULL) { if (st + statements[st].a.jumpofs == statement) { if (st != first) { if (statements[st-1].op == OP_RETURN) continue; if (statements[st-1].op == OP_DONE) continue; return true; } } } if (pr_opcodes[statements[st].op].type_b == NULL) { if (st + statements[st].b.jumpofs == statement) { if (st != first) { if (statements[st-1].op == OP_RETURN) continue; if (statements[st-1].op == OP_DONE) continue; return true; } } } if (pr_opcodes[statements[st].op].type_c == NULL) { if (st + statements[st].c.jumpofs == statement) { if (st != first) { if (statements[st-1].op == OP_RETURN) continue; if (statements[st-1].op == OP_DONE) continue; return true; } } } } return false; } /* static pbool QCC_FuncJumpsToRange(int first, int last, int firstr, int lastr) { int st; for (st = first; st < last; st++) { if (pr_opcodes[statements[st].op].type_a == NULL) { if (st + (signed)statements[st].a >= firstr && st + (signed)statements[st].a <= lastr) { if (st != first) { if (statements[st-1].op == OP_RETURN) continue; if (statements[st-1].op == OP_DONE) continue; return true; } } } if (pr_opcodes[statements[st].op].type_b == NULL) { if (st + (signed)statements[st].b >= firstr && st + (signed)statements[st].b <= lastr) { if (st != first) { if (statements[st-1].op == OP_RETURN) continue; if (statements[st-1].op == OP_DONE) continue; return true; } } } if (pr_opcodes[statements[st].op].type_c == NULL) { if (st + (signed)statements[st].c >= firstr && st + (signed)statements[st].c <= lastr) { if (st != first) { if (statements[st-1].op == OP_RETURN) continue; if (statements[st-1].op == OP_DONE) continue; return true; } } } } return false; } */ #if 0 void QCC_CompoundJumps(int first, int last) { //jumps to jumps are reordered so they become jumps to the final target. int statement; int st; for (st = first; st < last; st++) { if (pr_opcodes[statements[st].op].type_a == NULL) { statement = st + (signed)statements[st].a; if (statements[statement].op == OP_RETURN || statements[statement].op == OP_DONE) { //goto leads to return. Copy the command out to remove the goto. statements[st].op = statements[statement].op; statements[st].a = statements[statement].a; statements[st].b = statements[statement].b; statements[st].c = statements[statement].c; optres_compound_jumps++; } while (statements[statement].op == OP_GOTO) { statements[st].a = statement+statements[statement].a - st; statement = st + (signed)statements[st].a; optres_compound_jumps++; } } if (pr_opcodes[statements[st].op].type_b == NULL) { statement = st + (signed)statements[st].b; while (statements[statement].op == OP_GOTO) { statements[st].b = statement+statements[statement].a - st; statement = st + (signed)statements[st].b; optres_compound_jumps++; } } if (pr_opcodes[statements[st].op].type_c == NULL) { statement = st + (signed)statements[st].c; while (statements[statement].op == OP_GOTO) { statements[st].c = statement+statements[statement].a - st; statement = st + (signed)statements[st].c; optres_compound_jumps++; } } } } #else static void QCC_CompoundJumps(int first, int last) { //jumps to jumps are reordered so they become jumps to the final target. int statement; int st; int infloop; for (st = first; st < last; st++) { if (pr_opcodes[statements[st].op].type_a == NULL) { statement = st + statements[st].a.jumpofs; if (statements[statement].op == OP_RETURN || statements[statement].op == OP_DONE) { //goto leads to return. Copy the command out to remove the goto. statements[st] = statements[statement]; optres_compound_jumps++; } infloop = 1000; while (statements[statement].op == OP_GOTO) { if (!infloop--) { QCC_PR_ParseWarning(0, "Infinate loop detected"); break; } statements[st].a.ofs = (statement+statements[statement].a.ofs - st); statement = st + statements[st].a.jumpofs; optres_compound_jumps++; } } if (pr_opcodes[statements[st].op].type_b == NULL) { statement = st + statements[st].b.jumpofs; infloop = 1000; while (statements[statement].op == OP_GOTO) { if (!infloop--) { QCC_PR_ParseWarning(0, "Infinate loop detected"); break; } statements[st].b.ofs = (statement+statements[statement].a.ofs - st); statement = st + statements[st].b.jumpofs; optres_compound_jumps++; } } if (pr_opcodes[statements[st].op].type_c == NULL) { statement = st + statements[st].c.jumpofs; infloop = 1000; while (statements[statement].op == OP_GOTO) { if (!infloop--) { QCC_PR_ParseWarning(0, "Infinate loop detected"); break; } statements[st].c.ofs = (statement+statements[statement].a.ofs - st); statement = st + statements[st].c.jumpofs; optres_compound_jumps++; } } } } #endif static void QCC_CheckForDeadAndMissingReturns(int first, int last, int rettype) { QCC_function_t *fnc; int st, st2; if (statements[last-1].op == OP_DONE) last--; //don't want the done if (rettype != ev_void) if (statements[last-1].op != OP_RETURN) { if (statements[last-1].op != OP_GOTO || statements[last-1].a.jumpofs > 0) { QCC_PR_Warning(WARN_MISSINGRETURN, s_filen, statements[last].linenum, "%s: not all control paths return a value", pr_scope->name ); return; } } for (st = first; st < last; st++) { if (statements[st].op == OP_RETURN || statements[st].op == OP_GOTO) { st++; if (st == last) continue; //erm... end of function doesn't count as unreachable. if (!opt_compound_jumps) { //we can ignore single statements like these without compound jumps (compound jumps correctly removes all). if (statements[st].op == OP_GOTO) //inefficient compiler, we can ignore this. continue; if (statements[st].op == OP_DONE) //inefficient compiler, we can ignore this. continue; } //always allow random return statements, because people like putting returns after switches even when all the switches have a return. if (statements[st].op == OP_RETURN) //inefficient compiler, we can ignore this. continue; //check for embedded functions. FIXME: should generate outside of the parent function. for (fnc = pr_scope+1; fnc < &functions[numfunctions]; fnc++) { if (fnc->code == st) break; } if (fnc < &functions[numfunctions]) continue; //make sure something goes to just after this return. for (st2 = first; st2 < last; st2++) { if (pr_opcodes[statements[st2].op].associative == ASSOC_RIGHT) { if (pr_opcodes[statements[st2].op].type_a == NULL) { if (st2 + statements[st2].a.jumpofs == st) break; } if (pr_opcodes[statements[st2].op].type_b == NULL) { if (st2 + statements[st2].b.jumpofs == st) break; } if (pr_opcodes[statements[st2].op].type_c == NULL) { if (st2 + statements[st2].c.jumpofs == st) break; } } } if (st2 == last) { QCC_PR_Warning(WARN_UNREACHABLECODE, pr_scope->filen, statements[st].linenum, "%s: contains unreachable code (line %i)", pr_scope->name, statements[st].linenum); } continue; } if (rettype != ev_void && pr_opcodes[statements[st].op].associative == ASSOC_RIGHT) { if (pr_opcodes[statements[st].op].type_a == NULL) { if (st + statements[st].a.jumpofs == last) { QCC_PR_ParseWarning(WARN_MISSINGRETURN, "%s: not all control paths return a value", pr_scope->name ); return; } } if (pr_opcodes[statements[st].op].type_b == NULL) { if (st + statements[st].b.jumpofs == last) { QCC_PR_ParseWarning(WARN_MISSINGRETURN, "%s: not all control paths return a value", pr_scope->name ); return; } } if (pr_opcodes[statements[st].op].type_c == NULL) { if (st + statements[st].c.jumpofs == last) { QCC_PR_ParseWarning(WARN_MISSINGRETURN, "%s: not all control paths return a value", pr_scope->name ); return; } } } } } pbool QCC_StatementIsAJump(int stnum, int notifdest) //only the unconditionals. { if (statements[stnum].op == OP_RETURN) return true; if (statements[stnum].op == OP_DONE) return true; if (statements[stnum].op == OP_GOTO) if (statements[stnum].a.jumpofs != notifdest) return true; return false; } int QCC_AStatementJumpsTo(int targ, int first, int last) { int st; for (st = first; st < last; st++) { if (pr_opcodes[statements[st].op].type_a == NULL) { if (st + statements[st].a.jumpofs == targ && statements[st].a.ofs) { return true; } } if (pr_opcodes[statements[st].op].type_b == NULL) { if (st + statements[st].b.jumpofs == targ) { return true; } } if (pr_opcodes[statements[st].op].type_c == NULL) { if (st + statements[st].c.jumpofs == targ) { return true; } } } for (st = 0; st < num_labels; st++) //assume it's used. { if (pr_labels[st].statementno == targ) return true; } for (st = 0; st < num_cases; st++) //assume it's used. { if (pr_cases[st] == targ) return true; } return false; } /* //goes through statements, if it sees a matching statement earlier, it'll strim out the current. void QCC_CommonSubExpressionRemoval(int first, int last) { int cur; //the current int prev; //the earlier statement for (cur = last-1; cur >= first; cur--) { if (pr_opcodes[statements[cur].op].priority == -1) continue; for (prev = cur-1; prev >= first; prev--) { if (statements[prev].op >= OP_CALL0 && statements[prev].op <= OP_CALL8) { optres_test1++; break; } if (statements[prev].op >= OP_CALL1H && statements[prev].op <= OP_CALL8H) { optres_test1++; break; } if (pr_opcodes[statements[prev].op].right_associative) { //make sure no changes to var_a occur. if (statements[prev].b == statements[cur].a) { optres_test2++; break; } if (statements[prev].b == statements[cur].b && !pr_opcodes[statements[cur].op].right_associative) { optres_test2++; break; } } else { if (statements[prev].c == statements[cur].a) { optres_test2++; break; } if (statements[prev].c == statements[cur].b && !pr_opcodes[statements[cur].op].right_associative) { optres_test2++; break; } } if (statements[prev].op == statements[cur].op) if (statements[prev].a == statements[cur].a) if (statements[prev].b == statements[cur].b) if (statements[prev].c == statements[cur].c) { if (!QCC_FuncJumpsToRange(first, last, prev, cur)) { statements[cur].op = OP_STORE_F; statements[cur].a = 28; statements[cur].b = 28; optres_comexprremoval++; } else optres_test1++; break; } } } } */ //follow branches (by recursing). //stop on first read(error, return statement) or write(no error, return -1) //end-of-block returns 0, done/return/goto returns -2 static int QCC_CheckOneUninitialised(int firststatement, int laststatement, union QCC_eval_basic_s *min, union QCC_eval_basic_s *max) { #define SPLIT \ if (!(ofs > max || ofs+sz <= min)) \ { \ if (min < ofs) \ { /*keep checking before*/ \ ret = QCC_CheckOneUninitialised(i + 1, laststatement, min, ofs); \ if (ret > 0) \ return ret; \ } \ if (ofs+sz < max) \ { /*keep checking after*/ \ ret = QCC_CheckOneUninitialised(i + 1, laststatement, ofs+sz, max); \ if (ret > 0) \ return ret; \ } \ if (iswrite) \ return -1; /*okay, we wrote it all*/ \ return i; /*an error when its a read*/ \ } int ret; int i; QCC_statement_t *st; for (i = firststatement; i < laststatement; i++) { st = &statements[i]; if (st->op == OP_DONE || st->op == OP_RETURN) { if (st->a.cast && st->a.sym) { union QCC_eval_basic_s *ofs = st->a.sym->symboldata+st->a.ofs; int sz = st->a.cast->size; if (!(ofs > max || ofs+sz < min)) return i; } return -2; } // this code catches gotos, but can cause issues with while statements. // if (st->op == OP_GOTO && (int)st->a < 1) // return -2; if (pr_opcodes[st->op].type_a && st->a.sym) { union QCC_eval_basic_s *ofs = st->a.sym->symboldata+st->a.ofs; int sz = st->a.cast->size; pbool iswrite = OpAssignsToA(st->op) || st->op == OP_GLOBALADDRESS; /* address-of counts as a write here, because we're too dumb to track when/if that is assigned to.*/ SPLIT } else if (pr_opcodes[st->op].associative == ASSOC_RIGHT && st->a.jumpofs > 0 && !st->a.sym) { int jump = i + st->a.jumpofs; ret = QCC_CheckOneUninitialised(i + 1, jump, min, max); if (ret > 0) return ret; i = jump-1; } if (pr_opcodes[st->op].type_b && st->b.sym) { union QCC_eval_basic_s *ofs = st->b.sym->symboldata+st->b.jumpofs; int sz = st->b.cast->size; pbool iswrite = OpAssignsToB(st->op); SPLIT } else if (pr_opcodes[st->op].associative == ASSOC_RIGHT && st->b.jumpofs > 0 && !st->b.sym && !(st->flags & STF_LOGICOP)) { int jump = i + st->b.jumpofs; //check if there's an else. st = &statements[jump-1]; if (st->op == OP_GOTO && st->a.jumpofs > 0) { int jump2 = jump-1 + st->a.ofs; int rett = QCC_CheckOneUninitialised(i + 1, jump - 1, min, max); if (rett > 0) return rett; ret = QCC_CheckOneUninitialised(jump, jump2, min, max); if (ret > 0) return ret; if (rett < 0 && ret < 0) return (rett == ret)?ret:-1; //inited or aborted in both, don't need to continue along this branch i = jump2-1; } else { ret = QCC_CheckOneUninitialised(i + 1, jump, min, max); if (ret > 0) return ret; i = jump-1; } continue; } if (pr_opcodes[st->op].type_c && st->c.sym) { union QCC_eval_basic_s *ofs = st->c.sym->symboldata+st->c.ofs; int sz = st->c.cast->size; pbool iswrite = OpAssignsToC(st->op); SPLIT } else if (pr_opcodes[st->op].associative == ASSOC_RIGHT && st->c.jumpofs > 0 && !st->c.sym) { int jump = i + st->c.jumpofs; ret = QCC_CheckOneUninitialised(i + 1, jump, min, max); if (ret > 0) return ret; i = jump-1; continue; } } return 0; #undef SPLIT } static pbool QCC_CheckUninitialised(int firststatement, int laststatement) { QCC_def_t *local, *c, *uninit; unsigned int i; pbool result = false; unsigned int paramend = FIRST_LOCAL; QCC_type_t *type = pr_scope->type; int err; //assume all, because we don't care for optimisations once we know we're not going to compile anything (removes warning about uninitialised unknown variables/typos). if (pr_error_count) return true; for (i = 0; i < type->num_parms; i++) { paramend += type->params[i].type->size; } for (local = pr.local_head.nextlocal; local; local = local->nextlocal) { if (local->constant) continue; //will get some other warning, so we don't care. if (local->isstatic) continue; //not a real local, so will be properly initialised. if (local->symbolheader != local) continue; //ignore slave symbols, cos they're not interesting and should have been checked as part of the parent. if (local->isparameter) continue; if (local->arraysize) continue; //probably indexed. we won't detect things properly. its the user's resposibility to check. :( err = QCC_CheckOneUninitialised(firststatement, laststatement, local->symboldata, local->symboldata + local->type->size * (local->arraysize?local->arraysize:1)); if (err > 0) { //try to refine it to a single component if we can. uninit = NULL; for (c = local; ; c = c->next) { if (c != local) { err = QCC_CheckOneUninitialised(firststatement, laststatement, c->symboldata, c->symboldata + c->type->size * (c->arraysize?c->arraysize:1)); if (err > 0) { if (uninit) { uninit = NULL; break; } uninit = c; } } if (c == local->deftail) break; //that was the last of them. } if (!uninit) //otherwise give up and print the whole struct. uninit = local; QCC_PR_Warning(WARN_UNINITIALIZED, s_filen, statements[err].linenum, "Potentially uninitialised variable %s%s%s", col_symbol,uninit->name,col_none); result = true; // break; } } return result; } void QCC_Marshal_Locals(int firststatement, int laststatement) { QCC_def_t *local; pbool error = false; if (pr.local_head.nextlocal) //only check if there's actually somthing to check { //FIXME: we should just insert extra statements to clear any we deem uninitialised, instead of generating errors etc. //then we can overlap all functions always without worrying. if (flag_allowuninit) { if (qccwarningaction[WARN_UNINITIALIZED]) QCC_CheckUninitialised(firststatement, laststatement); //still need to call it for warnings, but if those warnings are off we can skip the cost } else if (!opt_locals_overlapping) { if (qccwarningaction[WARN_UNINITIALIZED]) QCC_CheckUninitialised(firststatement, laststatement); //still need to call it for warnings, but if those warnings are off we can skip the cost error = true; //always use the legacy behaviour } else if (QCC_CheckUninitialised(firststatement, laststatement)) { error = true; // QCC_PR_Note(ERR_INTERNAL, strings+s_file, pr_source_line, "Not overlapping locals from %s due to uninitialised locals", pr_scope->name); } else { //make sure we're allowed to marshall this function's locals for (local = pr.local_head.nextlocal; local; local = local->nextlocal) { if (local->isstatic) continue; //static variables are actually globals if (local->constant && local->initialized) continue; //as are initialised consts, because its pointless otherwise. if (local->symbolheader && local->symbolheader->scope != local->scope) continue; //FIXME: check for uninitialised locals. //these matter when the function goes recursive (and locals marshalling counts as recursive every time). if (local->symboldata[0]._int) { if (!error) QCC_PR_Note(ERR_INTERNAL, local->filen, local->s_line, "Marshaling non-const initialised %s", local->name); error = true; } /*if (local->constant) { QCC_PR_Note(ERR_INTERNAL, local->filen, local->s_line, "Marshaling const %s", local->name); error = true; }*/ } } //func(&somelocal) reuses the same memory address for both caller and callee. there's nothing we safely do to fix recursive functions, but we can at least stop -Olo from breaking things more. if (!error) { int i; QCC_statement_t *st; for (i = firststatement; i < laststatement; i++) { st = &statements[i]; if (st->op == OP_GLOBALADDRESS && st->a.sym->scope && !st->a.sym->isstatic) { error = true; break; } } } } if (error) pr_scope->privatelocals = true; else pr_scope->privatelocals = false; pr_scope->firstlocal = pr.local_head.nextlocal; pr.local_head.nextlocal = NULL; pr.local_tail = &pr.local_head; } #ifdef WRITEASM static void QCC_WriteGUIAsmFunction(QCC_function_t *sc, unsigned int firststatement) { unsigned int i; // QCC_type_t *type; char typebuf[512]; char line[2048]; extern int currentsourcefile; // type = sc->type; for (i = firststatement; i < (unsigned int)numstatements; i++) { line[0] = 0; // QC_snprintfz(line, sizeof(line), "%i ", QCC_VarAtOffset(statements[i].a)); QC_strlcat(line, pr_opcodes[statements[i].op].opname, sizeof(line)); if (pr_opcodes[statements[i].op].type_a != &type_void) { // if (strlen(pr_opcodes[statements[i].op].opname)<6) // QC_strlcat(line, " ", sizeof(line)); if (pr_opcodes[statements[i].op].type_a) QC_snprintfz(typebuf, sizeof(typebuf), " %s", QCC_VarAtOffset(statements[i].a)); else QC_snprintfz(typebuf, sizeof(typebuf), " %i", statements[i].a.jumpofs); QC_strlcat(line, typebuf, sizeof(line)); if (pr_opcodes[statements[i].op].type_b != &type_void) { if (pr_opcodes[statements[i].op].type_b) QC_snprintfz(typebuf, sizeof(typebuf), ", %s", QCC_VarAtOffset(statements[i].b)); else QC_snprintfz(typebuf, sizeof(typebuf), ", %i", statements[i].b.jumpofs); QC_strlcat(line, typebuf, sizeof(line)); if (pr_opcodes[statements[i].op].type_c != &type_void && (pr_opcodes[statements[i].op].associative==ASSOC_LEFT || statements[i].c.cast)) { if (pr_opcodes[statements[i].op].type_c) QC_snprintfz(typebuf, sizeof(typebuf), ", %s", QCC_VarAtOffset(statements[i].c)); else QC_snprintfz(typebuf, sizeof(typebuf), ", %i", statements[i].c.jumpofs); QC_strlcat(line, typebuf, sizeof(line)); } } else { if (pr_opcodes[statements[i].op].type_c != &type_void) { if (pr_opcodes[statements[i].op].type_c) QC_snprintfz(typebuf, sizeof(typebuf), ", %s", QCC_VarAtOffset(statements[i].c)); else QC_snprintfz(typebuf, sizeof(typebuf), ", %i", statements[i].c.jumpofs); QC_strlcat(line, typebuf, sizeof(line)); } } } else { if (pr_opcodes[statements[i].op].type_c != &type_void) { if (pr_opcodes[statements[i].op].type_c) QC_snprintfz(typebuf, sizeof(typebuf), " %s", QCC_VarAtOffset(statements[i].c)); else QC_snprintfz(typebuf, sizeof(typebuf), " %i", statements[i].c.jumpofs); QC_strlcat(line, typebuf, sizeof(line)); } } if (currentsourcefile) externs->Printf("code: %s:%i: %i:%s;\n", sc->filen, statements[i].linenum, currentsourcefile, line); else externs->Printf("code: %s:%i: %s;\n", sc->filen, statements[i].linenum, line); } } void QCC_WriteAsmFunction(QCC_function_t *sc, unsigned int firststatement, QCC_def_t *firstparm) { unsigned int i; QCC_def_t *o = firstparm; QCC_type_t *type; char typebuf[512]; if (sc->parentscope) //don't print dupes. return; if (flag_guiannotate) QCC_WriteGUIAsmFunction(sc, firststatement); if (!asmfile) return; type = sc->type; fprintf(asmfile, "%s(", TypeName(type->aux_type, typebuf, sizeof(typebuf))); for (o = pr.local_head.nextlocal, i = 0; i < type->num_parms; i++) { if (i) fprintf(asmfile, ", "); if (o) { fprintf(asmfile, "%s %s", TypeName(o->type, typebuf, sizeof(typebuf)), o->name); o = o->nextlocal; } else fprintf(asmfile, "%s", TypeName(type->params[i].type, typebuf, sizeof(typebuf))); } fprintf(asmfile, ") %s = asm\n{\n", sc->name); QCC_fprintfLocals(asmfile, o); for (i = firststatement; i < (unsigned int)numstatements; i++) { fprintf(asmfile, "\t%s", pr_opcodes[statements[i].op].opname); if (pr_opcodes[statements[i].op].type_a != &type_void) { if (strlen(pr_opcodes[statements[i].op].opname)<6) fprintf(asmfile, "\t"); if (pr_opcodes[statements[i].op].type_a) fprintf(asmfile, "\t%s", QCC_VarAtOffset(statements[i].a)); else fprintf(asmfile, "\t%i", statements[i].a.ofs); if (pr_opcodes[statements[i].op].type_b != &type_void) { if (pr_opcodes[statements[i].op].type_b) fprintf(asmfile, ",\t%s", QCC_VarAtOffset(statements[i].b)); else fprintf(asmfile, ",\t%i", statements[i].b.ofs); if (pr_opcodes[statements[i].op].type_c != &type_void && (pr_opcodes[statements[i].op].associative==ASSOC_LEFT || statements[i].c.sym)) { if (pr_opcodes[statements[i].op].type_c) fprintf(asmfile, ",\t%s", QCC_VarAtOffset(statements[i].c)); else fprintf(asmfile, ",\t%i", statements[i].c.ofs); } } else { if (pr_opcodes[statements[i].op].type_c != &type_void) { if (pr_opcodes[statements[i].op].type_c) fprintf(asmfile, ",\t%s", QCC_VarAtOffset(statements[i].c)); else fprintf(asmfile, ",\t%i", statements[i].c.ofs); } } } else { if (pr_opcodes[statements[i].op].type_c != &type_void) { if (pr_opcodes[statements[i].op].type_c) fprintf(asmfile, "\t%s", QCC_VarAtOffset(statements[i].c)); else fprintf(asmfile, "\t%i", statements[i].c.ofs); } } fprintf(asmfile, "; /*%i*/\n", statements[i].linenum); } fprintf(asmfile, "}\n\n"); } #endif static QCC_function_t *QCC_PR_GenerateBuiltinFunction (QCC_def_t *def, int builtinnum, char *builtinname) { QCC_function_t *func; if (numfunctions >= MAX_FUNCTIONS) QCC_PR_ParseError(ERR_INTERNAL, "Too many functions - %i\nAdd '-max_functions %i' to the commandline", numfunctions, (numfunctions+4096)&~4095); func = &functions[numfunctions++]; func->filen = s_filen; func->unitn = s_unitn; func->s_filed = s_filed; func->line = def->s_line; //FIXME if (builtinname==def->name) func->name = builtinname; else { func->name = qccHunkAlloc(strlen(builtinname)+1); strcpy(func->name, builtinname); } func->builtin = builtinnum; func->code = -1; func->type = def->type; func->firstlocal = NULL; func->def = def; return func; } static QCC_function_t *QCC_PR_GenerateQCFunction (QCC_def_t *def, QCC_type_t *type, unsigned int *pif_flags) { QCC_function_t *func = NULL; if (numfunctions >= MAX_FUNCTIONS) QCC_PR_ParseError(ERR_INTERNAL, "Too many functions - %i\nAdd '-max_functions %i' to the commandline", numfunctions, (numfunctions+4096)&~4095); if (!pif_flags) ; else if ((*pif_flags & PIF_ACCUMULATE) && !(*pif_flags & PIF_WRAP)) { if (def->symboldata[0].function) { func = &functions[def->symboldata[0].function]; //just resume the old one... if (func->def != def || func->type != type || func->parentscope != pr_scope) QCC_PR_ParseError(ERR_INTERNAL, "invalid function accumulation"); //fixme: validate stuff return func; } } else if ((*pif_flags&PIF_WRAP) && def->symboldata[0].function) { QCC_def_t *locals; QCC_function_t *prior; func = &functions[def->symboldata[0].function]; if ((*pif_flags&PIF_AUTOWRAP) && func->statements && func->def == def && func->type == type && func->parentscope == pr_scope) { *pif_flags &= ~(PIF_WRAP|PIF_AUTOWRAP); return func; //looks like we should be able to just reuse it. no need to wrap. } prior = &functions[numfunctions++]; memcpy(prior, func, sizeof(*prior)); //FIXME: we need a proper algorithm to generate valid anonymous function names. prior->name = qccHunkAlloc(6+strlen(func->name)+1); strcpy(prior->name, "prior*"); strcpy(prior->name+6, func->name); memset(func, 0, sizeof(*func)); for (locals = prior->firstlocal; locals; locals = locals->nextlocal) { if (locals->scope != func) QCC_PR_ParseError(ERR_INTERNAL, "internal consistency check failed while wrapping %s", def->name); locals->scope = prior; } } else if (*pif_flags&PIF_WRAP) { QCC_PR_ParseError(ERR_INTERNAL, "cannot wrap bodyless function %s", def->name); return NULL; } if (!func) func = &functions[numfunctions++]; func->filen = s_filen; func->unitn = s_unitn; func->s_filed = s_filed; func->line = pr_source_line;//def?def->s_line:0; //FIXME func->name = def?def->name:""; func->builtin = 0; func->code = numstatements; func->firstlocal = NULL; func->def = def; func->type = type; func->parentscope = pr_scope; return func; } static void QCC_PR_ResumeFunction(QCC_function_t *f) { if (pr_scope != f) { pr_scope = f; //reset the locals chain pr.local_head.nextlocal = f->firstlocal; pr.local_tail = &pr.local_head; while (pr.local_tail->nextlocal) pr.local_tail = pr.local_tail->nextlocal; //qcvm sees the function start here. f->code = numstatements; if (f->statements) { memcpy(statements+f->code, f->statements, sizeof(*statements) * f->numstatements); numstatements += f->numstatements; f->statements = NULL; f->numstatements = 0; } } } static void QCC_PR_FinaliseFunction(QCC_function_t *f) { QCC_statement_t *st; pbool needsdone=false; QCC_PR_ResumeFunction(f); pr_token_line_last = f->line_end; s_filen = f->filen; if (f->returndef.cast && f->type->aux_type->size <= type_vector->size) { PR_GenerateReturnOuts(); QCC_ForceUnFreeDef(f->returndef.sym); QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_RETURN], f->returndef, nullsref, NULL)); } if (f->code == numstatements) needsdone = true; else if (statements[numstatements - 1].op != OP_RETURN && statements[numstatements - 1].op != OP_DONE) needsdone = true; if (opt_return_only && !needsdone) needsdone = QCC_FuncJumpsTo(f->code, numstatements, numstatements); // emit an end of statements opcode if (!opt_return_only || needsdone) { /*if (pr_classtype) { QCC_def_t *e, *e2; e = QCC_PR_GetDef(NULL, "__oself", pr_scope, false, 0); e2 = QCC_PR_GetDef(NULL, "self", NULL, false, 0); QCC_FreeTemp(QCC_PR_Statement(&pr_opcodes[OP_STORE_ENT], e, QCC_PR_DummyDef(pr_classtype, "self", pr_scope, 0, e2->ofs, false), NULL)); }*/ if (needsdone) PR_GenerateReturnOuts(); QCC_PR_Statement (&pr_opcodes[OP_DONE], nullsref, nullsref, &st); } else optres_return_only++; QCC_CheckForDeadAndMissingReturns(f->code, numstatements, f->type->aux_type->type); // if (opt_comexprremoval) // QCC_CommonSubExpressionRemoval(f->code, numstatements); QCC_RemapLockedTemps(f->code, numstatements); QCC_Marshal_Locals(f->code, numstatements); QCC_WriteAsmFunction(f, f->code, f->firstlocal); if (opt_compound_jumps) QCC_CompoundJumps(f->code, numstatements); } void QCC_PR_FinaliseFunctions(void) { QCC_function_t *f; if (numinitstatements) { QCC_statement_t *ns; f = functions+numfunctions; if (f==functions+numfunctions) for (f = functions+1; f < functions+numfunctions; f++) if (!strcmp(f->name, "_atinit")) break; if (f==functions+numfunctions) for (f = functions+1; f < functions+numfunctions; f++) if (!strcmp(f->name, "init")) break; if (f==functions+numfunctions) for (f = functions+1; f < functions+numfunctions; f++) if (!strcmp(f->name, "initents")) break; if (f==functions+numfunctions) for (f = functions+1; f < functions+numfunctions; f++) if (!strcmp(f->name, "m_init")||!strcmp(f->name, "CSQC_Init")||!strcmp(f->name, "worldspawn")) break; if (f==functions+numfunctions) QCC_PR_ParseError (ERR_BADBUILTINIMMEDIATE, "Construction statements were not part of any function. call _atinit manually."); else { //copy it into the start of the function. shouldn't be any locals/temps/etc to worry about. ns = qccHunkAlloc((numinitstatements + f->numstatements) * sizeof(*ns)); memcpy(ns, initstatements, numinitstatements*sizeof(*ns)); memcpy(ns+numinitstatements, f->statements, f->numstatements*sizeof(*ns)); f->statements = ns; f->numstatements += numinitstatements; numinitstatements = 0; } } for (f = functions; f < functions+numfunctions; f++) { if (f->statements) QCC_PR_FinaliseFunction(f); } } /* ============ PR_ParseImmediateStatements Parse a function body If def is set, allows stuff to refer back to a def for the function. ============ */ QCC_function_t *QCC_PR_ParseImmediateStatements (QCC_def_t *def, QCC_type_t *type, unsigned int pif_flags) { unsigned int u, p; QCC_function_t *f; QCC_sref_t parm; QCC_def_t *prior = NULL, *d, *lastparm; pbool mergeargs; int startingtypes = numtypeinfos; conditional = 0; expandedemptymacro = false; // // check for builtin function definition #1, #2, etc // // hexenC has void name() : 2; if (!(pif_flags & PIF_ACCUMULATE)) { if (QCC_PR_CheckToken ("#") || QCC_PR_CheckToken (":")) { int binum = 0; if (pr_token_type == tt_immediate && pr_immediate_type == type_float && pr_immediate._float == (int)pr_immediate._float) binum = (int)pr_immediate._float; else if (pr_token_type == tt_immediate && pr_immediate_type == type_integer) binum = pr_immediate._int; else QCC_PR_ParseError (ERR_BADBUILTINIMMEDIATE, "Bad builtin immediate"); f = QCC_PR_GenerateBuiltinFunction(def, binum, def->name); QCC_PR_Lex (); return f; } if (QCC_PR_CheckKeyword(keyword_external, "external")) { //reacc style builtin if (pr_token_type != tt_immediate || pr_immediate_type != type_float || pr_immediate._float != (int)pr_immediate._float) QCC_PR_ParseError (ERR_BADBUILTINIMMEDIATE, "Bad builtin immediate"); f = QCC_PR_GenerateBuiltinFunction(def, (int)-pr_immediate._float, def->name); QCC_PR_Lex (); QCC_PR_Expect(";"); return f; } } // if (type->vargs) // QCC_PR_ParseError (ERR_FUNCTIONWITHVARGS, "QC function with variable arguments and function body"); f = QCC_PR_GenerateQCFunction(def, type, &pif_flags); QCC_PR_ResumeFunction(f); QCC_RemapLockedTemps(-1, -1); mergeargs = !!f->firstlocal; // // define the basic parms // if (mergeargs) { QCC_def_t *arg; for (u=0, p=0, arg=f->firstlocal ; unum_parms; u++, arg = arg->deftail->nextlocal) { QCC_PR_DummyDef(type->params[u].type, pr_parm_names[u], pr_scope, 0, arg, 0, true, GDF_PARAMETER); } if (type->vargcount) { if (!pr_parm_argcount_name) QCC_Error(ERR_INTERNAL, "I forgot what the va_count argument is meant to be called"); else QCC_PR_DummyDef(type_float, pr_parm_argcount_name, pr_scope, 0, arg, 0, true, 0); } } else { for (u=0, p=0 ; unum_parms; u++) { unsigned int o; if (!*pr_parm_names[u]) { QC_snprintfz(pr_parm_names[u], sizeof(pr_parm_names[u]), "$arg_%u", u); QCC_PR_ParseWarning(WARN_PARAMWITHNONAME, "Parameter %u of %s is not named", u+1, pr_scope->name); } parm = QCC_PR_GetSRef (type->params[u].type, pr_parm_names[u], pr_scope, 2, 0, 0); parm.sym->used = true; //make sure system parameters get seen by the engine, even if the names are stripped.. parm.sym->referenced = true; for (o = 0; o < (type->params[u].type->size+2)/3; o++) { if (p < MAX_PARMS) { parm.sym->isparameter = true; parm.ofs+=3;//no need to copy anything, as the engine will do it for us. } else { //extra parms need to be explicitly copied. if (!extra_parms[p - MAX_PARMS].sym) { char name[128]; QC_snprintfz(name, sizeof(name), "$parm%u", p); extra_parms[p - MAX_PARMS] = QCC_PR_GetSRef(type_vector, name, NULL, true, 0, GDF_STRIP); } else QCC_ForceUnFreeDef(extra_parms[p - MAX_PARMS].sym); QCC_UnFreeTemp(parm); extra_parms[p - MAX_PARMS].cast = parm.cast; if (type->params[u].type->size-o*3 >= 3) { QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_STORE_V], extra_parms[p - MAX_PARMS], parm, NULL)); parm.ofs+=3; } else if (type->params[u].type->size-o*3 == 2 && QCC_OPCodeValid(&pr_opcodes[OP_STORE_I64])) { QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_STORE_I64], extra_parms[p - MAX_PARMS], parm, NULL)); parm.ofs+=2; } else if (type->params[u].type->size-o*3 == 2) { QCC_sref_t t = extra_parms[p - MAX_PARMS]; QCC_UnFreeTemp(t); QCC_UnFreeTemp(parm); QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_STORE_F], t, parm, NULL)); t.ofs++; parm.ofs++; QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_STORE_F], t, parm, NULL)); parm.ofs++; } else { QCC_FreeTemp(QCC_PR_Statement (&pr_opcodes[OP_STORE_F], extra_parms[p - MAX_PARMS], parm, NULL)); parm.ofs++; } } p++; } QCC_FreeTemp(parm); } if (type->vargcount) { if (!pr_parm_argcount_name) QCC_Error(ERR_INTERNAL, "I forgot what the va_count argument is meant to be called"); else { QCC_sref_t va_passcount = QCC_PR_GetSRef(type_float, "__va_count", NULL, true, 0, 0); QCC_sref_t va_count = QCC_PR_GetSRef(type_float, pr_parm_argcount_name, pr_scope, true, 0, GDF_SCANLOCAL); QCC_sref_t numparms = QCC_MakeFloatConst(type->num_parms); va_passcount.sym->referenced = true; QCC_PR_SimpleStatement(&pr_opcodes[OP_SUB_F], va_passcount, numparms, va_count, false); QCC_FreeTemp(numparms); QCC_FreeTemp(va_passcount); QCC_FreeTemp(va_count); } } } if (f->type->aux_type->size > type_vector->size) if (!mergeargs) { //okay, awkward... for large returns, the caller passed us a pointer via OFS_RETURN f->returndef = QCC_PR_GetSRef(QCC_PR_PointerType(f->type->aux_type), "ret**", f, true, 0, 0); QCC_PR_SimpleStatement(&pr_opcodes[OP_STORE_P], QCC_MakeSRefForce(&def_ret, 0, type_variant), f->returndef, nullsref, false); } if (type->vargs) { int i; int maxvacount = 24; QCC_sref_t a; //if we have opcode extensions, we can use those instead of via a function. this allows to use proper locals for the vargs. //otherwise we have to use static/globals instead, so that the child function can access them without clobbering. FIXME: this is silly. we should be able to bias our locals to avoid stomping the array access' locals pbool opcodeextensions = QCC_OPCodeValid(&pr_opcodes[OP_FETCH_GBL_F]) || QCC_OPCodeValid(&pr_opcodes[OP_LOADA_F]); QCC_sref_t va_list; va_list = QCC_PR_GetSRef(type_vector, "__va_list", pr_scope, true, maxvacount, GDF_SCANLOCAL|(opcodeextensions?0:GDF_STATIC)); if (QCC_OPCodeValid(&pr_opcodes[OP_FETCH_GBL_F]) && !QCC_OPCodeValid(&pr_opcodes[OP_LOADA_F])) va_list.sym->arraylengthprefix = true; if (!mergeargs) { for (i = 0; i < maxvacount; i++) { QCC_ref_t varef; u = i + type->num_parms; if (u >= MAX_PARMS) { if (!extra_parms[u - MAX_PARMS].sym) { char name[128]; QC_snprintfz(name, sizeof(name), "$parm%u", u); extra_parms[u - MAX_PARMS] = QCC_PR_GetSRef(type_vector, name, NULL, true, 0, GDF_STRIP); } else QCC_ForceUnFreeDef(extra_parms[u - MAX_PARMS].sym); a = extra_parms[u - MAX_PARMS]; } else { a.sym = &def_parms[u]; a.ofs = 0; QCC_ForceUnFreeDef(a.sym); } a.cast = type_vector; QCC_UnFreeTemp(va_list); QCC_StoreSRefToRef(QCC_PR_BuildRef(&varef, REF_ARRAY, va_list, QCC_MakeIntConst(i*3), type_vector, false, 0), a, false, false); } } QCC_FreeTemp(va_list); } if (pif_flags & (PIF_WRAP|PIF_AUTOWRAP)) { //if we're wrapping, then we moved the old function entry to the end and reused it for our function. //so we need to define some local that refers to the prior def. int funcref = numfunctions-1; QCC_sref_t priorim = QCC_MakeUniqueConst(type_function, &funcref); priorim.sym->referenced = true; priorim.cast = f->type; prior = QCC_PR_DummyDef(f->type, "prior", f, 0, priorim.sym, 0, true, GDF_CONST|GDF_STATIC|GDF_SCANLOCAL); //create a union into it prior->initialized = true; prior->filen = functions[numfunctions-1].filen; prior->s_filed = functions[numfunctions-1].s_filed; prior->s_line = functions[numfunctions-1].line; QCC_FreeTemp(priorim); } /*if (pr_classtype) { QCC_def_t *e, *e2; e = QCC_PR_GetDef(pr_classtype, "__oself", pr_scope, true, 0); e2 = QCC_PR_GetDef(type_entity, "self", NULL, true, 0); QCC_FreeTemp(QCC_PR_Statement(&pr_opcodes[OP_STORE_ENT], QCC_PR_DummyDef(pr_classtype, "self", pr_scope, 0, e2->ofs, false), e, NULL)); }*/ lastparm = pr.local_tail; // // check for a state opcode // if (QCC_PR_CheckToken ("[")) QCC_PR_ParseState (); //accumulate implies wrap when the first function wasn't defined to accumulate (should really be explicit, but gmqcc compat) if (pif_flags & PIF_AUTOWRAP) { QCC_def_t *arg; QCC_sref_t args[MAX_PARMS+MAX_EXTRA_PARMS]; QCC_sref_t r; unsigned int i; for (i=0, arg=pr.local_head.nextlocal ; inum_parms; i++, arg = arg->deftail->nextlocal) { QCC_ForceUnFreeDef(arg); args[i].sym = arg; args[i].ofs = 0; args[i].cast = type->params[i].type; } r = QCC_PR_GenerateFunctionCallSref(nullsref, QCC_MakeSRefForce(prior, 0, prior->type), args, type->num_parms); prior->referenced = true; if (f->type->aux_type->type == ev_void) QCC_FreeTemp(r); else { if (!f->returndef.cast) f->returndef = QCC_PR_GetSRef(f->type->aux_type, "ret*", pr_scope, true, 0, 0); QCC_StoreToSRef(f->returndef, r, type, false, false); } } if (QCC_PR_CheckKeyword (keyword_asm, "asm")) { QCC_PR_Expect ("{"); while (STRCMP ("}", pr_token)) QCC_PR_ParseAsm (); } else { if (!(pif_flags & PIF_ACCUMULATE) && QCC_PR_CheckKeyword (keyword_var, "var")) //reacc support { //parse lots of locals char *name; do { name = QCC_PR_ParseName(); QCC_PR_Expect(":"); QCC_FreeDef(QCC_PR_GetDef(QCC_PR_ParseType(false, false, false), name, pr_scope, true, 0, false)); QCC_PR_Expect(";"); } while(!QCC_PR_CheckToken("{")); } else QCC_PR_Expect ("{"); // // parse regular statements // while (STRCMP ("}", pr_token)) //not check token to avoid the lex consuming following pragmas { QCC_PR_ParseStatement (); QCC_FreeTemps(); } } QCC_FreeTemps(); if (prior && !prior->referenced) QCC_PR_ParseError(ERR_REDECLARATION, "Wrapper function \"%s\" does not refer to its prior function", def->name); pr_token_line_last = pr_token_line; // this is cheap // if (type->aux_type->type) // if (statements[numstatements - 1].op != OP_RETURN) // QCC_PR_ParseWarning(WARN_MISSINGRETURN, "%s: not all control paths return a value", pr_scope->name ); if (num_gotos) { int i, j; for (i = 0; i < num_gotos; i++) { for (j = 0; j < num_labels; j++) { if (!strcmp(pr_gotos[i].name, pr_labels[j].name)) { if (!pr_opcodes[statements[pr_gotos[i].statementno].op].type_a) statements[pr_gotos[i].statementno].a.ofs += pr_labels[j].statementno - pr_gotos[i].statementno; else if (!pr_opcodes[statements[pr_gotos[i].statementno].op].type_b) statements[pr_gotos[i].statementno].b.ofs += pr_labels[j].statementno - pr_gotos[i].statementno; else statements[pr_gotos[i].statementno].c.ofs += pr_labels[j].statementno - pr_gotos[i].statementno; break; } } if (j == num_labels) { num_gotos = 0; QCC_PR_ParseError(ERR_NOLABEL, "Goto statement with no matching label \"%s\"", pr_gotos[i].name); } } num_gotos = 0; } f->line_end = pr_token_line_last; if (1)//(pif_flags & PIF_ACCUMULATE) || pr_scope->parentscope) { //FIXME: should probably always take this path, but kinda pointless until we have relocs for defs QCC_RemapLockedTemps(f->code, numstatements); QCC_Marshal_Locals(f->code, numstatements); // QCC_WriteAsmFunction(f, f->code, f->firstlocal); //FIXME: this will print the entire function, not just the part that we added. and we'll print it all again later, too. should probably make it a function attribute that we check at the end. f->numstatements = numstatements - f->code; f->statements = qccHunkAlloc(sizeof(*statements)*f->numstatements); memcpy(f->statements, statements+f->code, sizeof(*statements) * f->numstatements); numstatements = f->code; } else { QCC_PR_FinaliseFunction(f); } pr_scope = NULL; if (num_labels) num_labels = 0; if (num_cases) { num_cases = 0; QCC_PR_ParseError(ERR_ILLEGALCASES, "%s: function contains illegal cases", f->name); } if (num_continues) { num_continues=0; QCC_PR_ParseError(ERR_ILLEGALCONTINUES, "%s: function contains illegal continues", f->name); } if (num_breaks) { num_breaks=0; QCC_PR_ParseError(ERR_ILLEGALBREAKS, "%s: function contains illegal breaks", f->name); } //clean up the locals. remove parms from the hashtable but don't clean subscoped_away so that we can repopulate on the next accumulation for (d = f->firstlocal; d != lastparm->nextlocal; d = d->nextlocal) { if (!d->subscoped_away) pHash_RemoveData(&localstable, d->name, d); } //any non-arg locals defined within the accumulation should not be visible next time around. for (; d; d = d->nextlocal) { if (!d->subscoped_away) { pHash_RemoveData(&localstable, d->name, d); d->subscoped_away = true; } } #if 0//def _DEBUG for (u = 0; u < localstable.numbuckets; u++) { if (localstable.bucket[u]) localstable.bucket[u] = NULL; } #endif for (; startingtypes < numtypeinfos; startingtypes++) { if (qcc_typeinfo[startingtypes].typedefed) { qcc_typeinfo[startingtypes].typedefed = false; pHash_RemoveData(&typedeftable, qcc_typeinfo[startingtypes].name, &qcc_typeinfo[startingtypes]); } } QCC_PR_Lex(); return f; } static void QCC_PR_ArrayRecurseDivideRegular(QCC_sref_t array, QCC_sref_t index, int min, int max) { QCC_statement_t *st; QCC_sref_t eq; int stride; if (array.cast->type == ev_vector) stride = 3; else stride = 1; //struct arrays should be 1, so that every element can be accessed... if (min == max || min+1 == max) { eq = QCC_PR_StatementFlags(pr_opcodes+OP_LT_F, index, QCC_MakeFloatConst(min+1), NULL, STFL_PRESERVEA); QCC_FreeTemp(QCC_PR_Statement(pr_opcodes+OP_IFNOT_I, eq, nullsref, &st)); st->b.ofs = 2; QCC_PR_Statement(pr_opcodes+OP_RETURN, array, nullsref, &st); st->a.ofs += min*stride; } else { int mid = min + (max-min)/2; if (max-min>4) { eq = QCC_PR_StatementFlags(pr_opcodes+OP_LT_F, index, QCC_MakeFloatConst(mid+1), NULL, STFL_PRESERVEA); QCC_FreeTemp(QCC_PR_Statement(pr_opcodes+OP_IFNOT_I, eq, nullsref, &st)); } else st = NULL; QCC_PR_ArrayRecurseDivideRegular(array, index, min, mid); if (st) st->b.jumpofs = numstatements - (st-statements); QCC_PR_ArrayRecurseDivideRegular(array, index, mid, max); } } //the idea here is that we return a vector, the caller then figures out the extra 3rd. //This is useful when we have a load of indexes. static void QCC_PR_ArrayRecurseDivideUsingVectors(QCC_sref_t array, QCC_sref_t index, int min, int max) { QCC_statement_t *st; QCC_sref_t eq; if (min == max || min+1 == max) { eq = QCC_PR_StatementFlags(pr_opcodes+OP_LT_F, index, QCC_MakeFloatConst(min+1), NULL, STFL_PRESERVEA); QCC_FreeTemp(QCC_PR_Statement(pr_opcodes+OP_IFNOT_I, eq, nullsref, &st)); st->b.ofs = 2; QCC_PR_Statement(pr_opcodes+OP_RETURN, array, nullsref, &st); st->a.ofs += min*3; } else { int mid = min + (max-min)/2; if (max-min>4) { eq = QCC_PR_StatementFlags(pr_opcodes+OP_LT_F, index, QCC_MakeFloatConst(mid+1), NULL, STFL_PRESERVEA); QCC_FreeTemp(QCC_PR_Statement(pr_opcodes+OP_IFNOT_I, eq, nullsref, &st)); } else st = NULL; QCC_PR_ArrayRecurseDivideUsingVectors(array, index, min, mid); if (st) st->b.jumpofs = numstatements - (st-statements); QCC_PR_ArrayRecurseDivideUsingVectors(array, index, mid, max); } } //returns a vector overlapping the result needed. QCC_def_t *QCC_PR_EmitArrayGetVector(QCC_sref_t array) { QCC_sref_t temp, index; QCC_def_t *func; int numslots; QCC_type_t *ftype = qccHunkAlloc(sizeof(*ftype)); struct QCC_typeparam_s *fparms = qccHunkAlloc(sizeof(*fparms)*1); ftype->size = 1; ftype->type = ev_function; ftype->aux_type = type_vector; ftype->params = fparms; ftype->num_parms = 1; ftype->name = "ArrayGet"; fparms[0].type = type_float; //array shouldn't ever be a vector array numslots = array.sym->arraysize*array.cast->size; numslots = (numslots+2)/3; s_filen = array.sym->filen; s_filed = array.sym->s_filed; func = QCC_PR_GetDef(ftype, qcva("ArrayGetVec*%s", array.sym->name), NULL, true, 0, false); pr_source_line = pr_token_line_last = array.sym->s_line; //thankfully these functions are emitted after compilation. if (numfunctions >= MAX_FUNCTIONS) QCC_Error(ERR_INTERNAL, "Too many function defs"); pr_scope = QCC_PR_GenerateQCFunction(func, ftype, NULL); pr_source_line = pr_token_line_last = pr_scope->line = array.sym->s_line; //thankfully these functions are emitted after compilation. pr_scope->filen = array.sym->filen; pr_scope->s_filed = array.sym->s_filed; func->symboldata[0]._int = pr_scope - functions; index = QCC_PR_GetSRef(type_float, "index___", pr_scope, true, 0, false); index.sym->referenced = true; temp = QCC_PR_GetSRef(type_float, "div3___", pr_scope, true, 0, false); QCC_PR_SimpleStatement(pr_opcodes+OP_DIV_F, index, QCC_MakeFloatConst(3), temp, false); QCC_PR_SimpleStatement(pr_opcodes+OP_BITAND_F, temp, temp, temp, false);//round down to int QCC_PR_ArrayRecurseDivideUsingVectors(array, temp, 0, numslots); QCC_PR_Statement(pr_opcodes+OP_RETURN, QCC_MakeVectorConst(0,0,0), nullsref, NULL); //err... we didn't find it, give up. QCC_PR_Statement(pr_opcodes+OP_DONE, nullsref, nullsref, NULL); //err... we didn't find it, give up. func->initialized = 1; QCC_WriteAsmFunction(pr_scope, pr_scope->code, pr_scope->firstlocal); QCC_Marshal_Locals(pr_scope->code, numstatements); QCC_FreeTemps(); return func; } void QCC_PR_EmitArrayGetFunction(QCC_def_t *scope, QCC_def_t *arraydef, char *arrayname) { QCC_sref_t vectortrick; QCC_sref_t index, thearray = QCC_MakeSRefForce(arraydef, 0, arraydef->type); QCC_statement_t *st; QCC_sref_t eq; QCC_statement_t *bc1=NULL, *bc2=NULL; // QCC_sref_t fasttrackpossible = nullsref; int numslots; numslots = thearray.sym->arraysize; if (!numslots) numslots = 1; if (thearray.cast->type != ev_vector) numslots *= thearray.cast->size; // if (flag_fasttrackarrays && numslots > 6) // fasttrackpossible = QCC_PR_GetSRef(type_float, "__ext__fasttrackarrays", NULL, true, 0, false); s_filen = scope->filen; s_filed = scope->s_filed; vectortrick = nullsref; // if (numslots >= 15 && thearray.cast->type != ev_vector) // { // vectortrick.sym = QCC_PR_EmitArrayGetVector(thearray); // vectortrick.cast = vectortrick.sym->type; // } pr_scope = QCC_PR_GenerateQCFunction(scope, scope->type, NULL); pr_source_line = pr_token_line_last = pr_scope->line = thearray.sym->s_line; //thankfully these functions are emitted after compilation. pr_scope->filen = thearray.sym->filen; pr_scope->s_filed = thearray.sym->s_filed; index = QCC_PR_GetSRef(type_float, "__indexg", pr_scope, true, 0, GDF_PARAMETER); scope->initialized = true; scope->symboldata[0]._int = pr_scope - functions; /* if (fasttrackpossible) { QCC_PR_Statement(pr_opcodes+OP_IFNOT_I, fasttrackpossible, nullsref, &st); //fetch_gbl takes: (float size, variant array[]), float index, variant pos //note that the array size is coded into the globals, one index before the array. if (thearray.cast->type == ev_vector) QCC_PR_SimpleStatement(&pr_opcodes[OP_FETCH_GBL_V], thearray, index, sref_ret, true); else QCC_PR_SimpleStatement(&pr_opcodes[OP_FETCH_GBL_F], thearray, index, sref_ret, true); QCC_FreeTemp(QCC_PR_Statement(&pr_opcodes[OP_RETURN], sref_ret, nullsref, NULL)); //finish the jump st->b.ofs = &statements[numstatements] - st; } */ if (flag_boundchecks) QCC_FreeTemp(QCC_PR_Statement(pr_opcodes+OP_IF_I, QCC_PR_StatementFlags(pr_opcodes+OP_LT_F, index, QCC_MakeFloatConst(0), NULL, STFL_PRESERVEA), nullsref, &bc1)); if (vectortrick.cast) { QCC_sref_t div3, intdiv3, ret; //okay, we've got a function to retrieve the var as part of a vector. //we need to work out which part, x/y/z that it's stored in. //0,1,2 = i - ((int)i/3 *) 3; if (flag_boundchecks) QCC_FreeTemp(QCC_PR_Statement(pr_opcodes+OP_IF_I, QCC_PR_StatementFlags(pr_opcodes+OP_GE_F, index, QCC_MakeFloatConst(numslots), NULL, STFL_PRESERVEA), nullsref, &bc2)); div3 = QCC_PR_GetSRef(type_float, "div3___", pr_scope, true, 0, false); intdiv3 = QCC_PR_GetSRef(type_float, "intdiv3___", pr_scope, true, 0, false); div3.sym->referenced = true; QCC_PR_SimpleStatement(pr_opcodes+OP_BITAND_F, index, index, index, false); QCC_PR_SimpleStatement(pr_opcodes+OP_DIV_F, index, QCC_MakeFloatConst(3), div3, false); QCC_PR_SimpleStatement(pr_opcodes+OP_BITAND_F, div3, div3, intdiv3, false); // ret = QCC_PR_GenerateFunctionCall1(nullsref, floor, index, type_float); QCC_UnFreeTemp(index); ret = QCC_PR_GenerateFunctionCall1(nullsref, vectortrick, index, type_float); div3 = QCC_PR_Statement(pr_opcodes+OP_MUL_F, intdiv3, QCC_MakeFloatConst(3), NULL); QCC_PR_SimpleStatement(pr_opcodes+OP_SUB_F, index, div3, index, false); QCC_FreeTemp(div3); eq = QCC_PR_StatementFlags(pr_opcodes+OP_LT_F, index, QCC_MakeFloatConst(0+0.5f), NULL, STFL_PRESERVEA); QCC_FreeTemp(QCC_PR_Statement(pr_opcodes+OP_IFNOT_I, eq, nullsref, &st)); st->b.ofs = 2; ret.cast = type_float; QCC_PR_Statement(pr_opcodes+OP_RETURN, ret, nullsref, NULL); ret.ofs++; eq = QCC_PR_StatementFlags(pr_opcodes+OP_LT_F, index, QCC_MakeFloatConst(1+0.5f), NULL, STFL_PRESERVEA); QCC_FreeTemp(QCC_PR_Statement(pr_opcodes+OP_IFNOT_I, eq, nullsref, &st)); st->b.ofs = 2; QCC_PR_Statement(pr_opcodes+OP_RETURN, ret, nullsref, NULL); ret.ofs++; eq = QCC_PR_StatementFlags(pr_opcodes+OP_LT_F, index, QCC_MakeFloatConst(2+0.5), NULL, STFL_PRESERVEA); QCC_FreeTemp(QCC_PR_Statement(pr_opcodes+OP_IFNOT_I, eq, nullsref, &st)); st->b.ofs = 2; QCC_PR_Statement(pr_opcodes+OP_RETURN, ret, nullsref, NULL); ret.ofs++; ret.ofs-=3; QCC_FreeTemp(ret); } else { // QCC_PR_SimpleStatement(pr_opcodes+OP_BITAND_F, index, index, index, false); QCC_PR_ArrayRecurseDivideRegular(thearray, index, 0, numslots); } if (bc1) bc1->b.jumpofs = &statements[numstatements] - bc1; if (bc2) bc2->b.jumpofs = &statements[numstatements] - bc2; if (bc1 || bc2) { QCC_sref_t errfnc = QCC_PR_GetSRef(NULL, "error", NULL, false, 0, false); QCC_sref_t sprintffnc = QCC_PR_GetSRef(NULL, "sprintf", NULL, false, 0, false); QCC_sref_t errmsg; if (sprintffnc.cast) { //if sprintf is defined, we generate a more verbose error message. this message should appear in at least one of the engines the mod was made for, and in others it should be obivious enough. QCC_sref_t args[3]; args[0] = QCC_MakeStringConst("bounds check failed (0 <= %g < %g is false)\n"); QCC_UnFreeTemp(index); args[1] = index; args[2] = QCC_MakeFloatConst(numslots); errmsg = QCC_PR_GenerateFunctionCallSref(nullsref, sprintffnc, args, 3); } else errmsg = QCC_MakeStringConst("bounds check failed\n"); if (!errfnc.cast) { errfnc = QCC_MakeIntConst(~0); errfnc.cast = type_function; } QCC_FreeTemp(QCC_PR_GenerateFunctionCall1(nullsref, errfnc, errmsg, type_string)); } QCC_FreeTemp(index); //we get here if they tried reading beyond the end of the array with bounds checks disabled. just return the last valid element. if (thearray.cast->type == ev_vector) thearray.ofs += (numslots-1)*3; else thearray.ofs += (numslots-1); QCC_PR_Statement(pr_opcodes+OP_RETURN, thearray, nullsref, &st); QCC_PR_Statement(pr_opcodes+OP_DONE, nullsref, nullsref, NULL); QCC_WriteAsmFunction(pr_scope, pr_scope->code, pr_scope->firstlocal); QCC_Marshal_Locals(pr_scope->code, numstatements); QCC_FreeTemps(); } static void QCC_PR_ArraySetRecurseDivide(QCC_sref_t array, QCC_sref_t index, QCC_sref_t value, int min, int max) { QCC_statement_t *st; QCC_sref_t eq; int stride; if (array.cast->type == ev_vector) stride = 3; else stride = 1; //struct arrays should be 1, so that every element can be accessed... if (min == max || min+1 == max) { eq = QCC_PR_StatementFlags(pr_opcodes+OP_LT_F, index, QCC_MakeFloatConst(min+1), NULL, STFL_PRESERVEA); QCC_FreeTemp(QCC_PR_Statement(pr_opcodes+OP_IFNOT_I, eq, nullsref, &st)); st->b.ofs = 3; if (stride == 3) QCC_PR_StatementFlags(pr_opcodes+OP_STORE_V, value, array, &st, STFL_PRESERVEB); else QCC_PR_StatementFlags(pr_opcodes+OP_STORE_F, value, array, &st, STFL_PRESERVEB); st->b.ofs += min*stride; QCC_PR_Statement(pr_opcodes+OP_RETURN, nullsref, nullsref, NULL); } else { int mid = min + (max-min)/2; if (max-min>4) { eq = QCC_PR_StatementFlags(pr_opcodes+OP_LT_F, index, QCC_MakeFloatConst(mid+1), NULL, STFL_PRESERVEA); QCC_FreeTemp(QCC_PR_Statement(pr_opcodes+OP_IFNOT_I, eq, nullsref, &st)); } else st = NULL; QCC_PR_ArraySetRecurseDivide(array, index, value, min, mid); if (st) st->b.jumpofs = numstatements - (st-statements); QCC_PR_ArraySetRecurseDivide(array, index, value, mid, max); } } void QCC_PR_EmitArraySetFunction(QCC_def_t *scope, QCC_def_t *arraydef, char *arrayname) { QCC_sref_t index, value, thearray = QCC_MakeSRefForce(arraydef, 0, arraydef->type); QCC_sref_t fasttrackpossible; int numslots; QCC_statement_t *bc1=NULL, *bc2=NULL; if (thearray.cast->type == ev_vector) numslots = thearray.sym->arraysize; else numslots = thearray.sym->arraysize*thearray.cast->size; fasttrackpossible = nullsref; if (flag_fasttrackarrays && numslots > 6) fasttrackpossible = QCC_PR_GetSRef(type_float, "__ext__fasttrackarrays", NULL, true, 0, false); if (numfunctions >= MAX_FUNCTIONS) QCC_Error(ERR_INTERNAL, "Too many function defs"); s_filen = arraydef->filen; s_filed = arraydef->s_filed; pr_scope = QCC_PR_GenerateQCFunction(scope, scope->type, NULL); pr_source_line = pr_token_line_last = pr_scope->line = thearray.sym->s_line; //thankfully these functions are emitted after compilation. pr_scope->filen = thearray.sym->filen; pr_scope->s_filed = thearray.sym->s_filed; index = QCC_PR_GetSRef(type_float, "indexs___", pr_scope, true, 0, GDF_PARAMETER); value = QCC_PR_GetSRef(thearray.cast, "value___", pr_scope, true, 0, GDF_PARAMETER); scope->initialized = true; scope->symboldata[0]._int = pr_scope - functions; if (fasttrackpossible.cast) { QCC_statement_t *st; QCC_PR_Statement(pr_opcodes+OP_IFNOT_I, fasttrackpossible, nullsref, &st); //note that the array size is coded into the globals, one index before the array. QCC_PR_SimpleStatement(&pr_opcodes[OP_CONV_FTOI], index, nullsref, index, true); //address stuff is integer based, but standard qc (which this accelerates in supported engines) only supports floats if (flag_boundchecks) QCC_PR_SimpleStatement (&pr_opcodes[OP_BOUNDCHECK], index, QCC_MakeSRef(NULL, numslots, NULL), nullsref, true);//annoy the programmer. :p if (thearray.cast->type == ev_vector)//shift it upwards for larger types QCC_PR_SimpleStatement(&pr_opcodes[OP_MUL_I], index, QCC_MakeIntConst(thearray.cast->size), index, true); QCC_PR_SimpleStatement(&pr_opcodes[OP_GLOBALADDRESS], thearray, index, index, true); //comes with built in add if (thearray.cast->type == ev_vector) QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_V], value, index, nullsref, true); //*b = a else QCC_PR_SimpleStatement(&pr_opcodes[OP_STOREP_F], value, index, nullsref, true); QCC_PR_Statement(&pr_opcodes[OP_RETURN], value, nullsref, NULL); //finish the jump st->b.jumpofs = &statements[numstatements] - st; } if (flag_boundchecks) QCC_FreeTemp(QCC_PR_Statement(pr_opcodes+OP_IF_I, QCC_PR_StatementFlags(pr_opcodes+OP_LT_F, index, QCC_MakeFloatConst(0), NULL, STFL_PRESERVEA), nullsref, &bc1)); if (flag_boundchecks) QCC_FreeTemp(QCC_PR_Statement(pr_opcodes+OP_IF_I, QCC_PR_StatementFlags(pr_opcodes+OP_GE_F, index, QCC_MakeFloatConst(numslots), NULL, STFL_PRESERVEA), nullsref, &bc2)); QCC_PR_ArraySetRecurseDivide(thearray, index, value, 0, numslots); if (bc1) bc1->b.jumpofs = &statements[numstatements] - bc1; if (bc2) bc2->b.jumpofs = &statements[numstatements] - bc2; if (bc1 || bc2) { QCC_sref_t errfnc = QCC_PR_GetSRef(NULL, "error", NULL, false, 0, false); QCC_sref_t errmsg = QCC_MakeStringConst("bounds check failed\n"); if (!errfnc.cast) { errfnc = QCC_MakeIntConst(~0); errfnc.cast = type_function; } QCC_FreeTemp(QCC_PR_GenerateFunctionCall1(nullsref, errfnc, errmsg, type_string)); } QCC_PR_Statement(pr_opcodes+OP_DONE, nullsref, nullsref, NULL); QCC_WriteAsmFunction(pr_scope, pr_scope->code, pr_scope->firstlocal); QCC_Marshal_Locals(pr_scope->code, numstatements); QCC_FreeTemps(); } //register a def, and all of it's sub parts. //only the main def is of use to the compiler. //the subparts are emitted to the compiler and allow correct saving/loading //be careful with fields, this doesn't allocated space, so will it allocate fields. It only creates defs at specified offsets. QCC_def_t *QCC_PR_DummyDef(QCC_type_t *type, const char *name, QCC_function_t *scope, int arraysize, QCC_def_t *rootsymbol, unsigned int ofs, int referable, unsigned int flags) { char array[64]; char newname[256]; int a; QCC_def_t *def, *first=NULL; char typebuf[1024]; pbool redec; while (rootsymbol && rootsymbol->symbolheader != rootsymbol) { ofs += rootsymbol->ofs; rootsymbol = rootsymbol->symbolheader; } #define KEYWORD(x) if (!STRCMP(name, #x) && keyword_##x) do{if (keyword_##x)QCC_PR_ParseWarning(WARN_KEYWORDDISABLED, "\""#x"\" keyword used as variable name%s", keywords_coexist?" - coexisting":" - disabling");keyword_##x=keywords_coexist;}while(0) if (name) { KEYWORD(var); KEYWORD(thinktime); KEYWORD(for); KEYWORD(switch); KEYWORD(case); KEYWORD(local); KEYWORD(default); KEYWORD(goto); if (type->type != ev_function) KEYWORD(break); KEYWORD(continue); KEYWORD(state); KEYWORD(string); if (qcc_targetformat != QCF_HEXEN2 && qcc_targetformat != QCF_UHEXEN2) KEYWORD(float); //hmm... hexen2 requires this... KEYWORD(entity); KEYWORD(vector); KEYWORD(const); KEYWORD(asm); } if (!type) return NULL; for (a = -1; a < arraysize||a==-1; a++) { if (a == -1) *array = '\0'; else QC_snprintfz(array, sizeof(array), "[%i]", a); if (name) QC_snprintfz(newname, sizeof(newname), "%s%s", name, array); else QC_snprintfz(newname, sizeof(newname), "%s", array); // allocate a new def if (a == -1 && rootsymbol && !rootsymbol->symbolsize) { //we had a prototype, but now we get to define everything else. def = rootsymbol; referable = false; //should already be added. redec = true; if (def->constant != !!(flags & GDF_CONST) || def->isstatic != !!(flags & GDF_STATIC) || def->isparameter != !!(flags & GDF_PARAMETER) || def->autoderef != !!(flags & GDF_AUTODEREF)) QCC_PR_ParseErrorPrintDef (ERR_TYPEMISMATCHREDEC, def, "%s%s%s %s%s%s redeclared with different storage type", col_type,TypeName(type, typebuf, sizeof(typebuf)),col_none, col_symbol,name,col_none); } else { def = (void *)qccHunkAlloc (sizeof(QCC_def_t)); memset (def, 0, sizeof(*def)); def->next = NULL; def->name = (void *)qccHunkAlloc (strlen(newname)+1); strcpy (def->name, newname); redec = false; } def->arraysize = a>=0?0:arraysize; def->s_line = pr_source_line; def->unitn = s_unitn; def->filen = s_filen; def->s_filed = s_filed; if (a>=0) def->initialized = 1; def->type = type; def->scope = scope; def->constant = !!(flags & GDF_CONST); def->isstatic = !!(flags & GDF_STATIC); def->isparameter = !!(flags & GDF_PARAMETER); def->autoderef = !!(flags & GDF_AUTODEREF); //should be a pointer type... if (arraysize && a < 0) { //array headers can be stripped safely. def->saved = false; def->strip = true; def->arraylengthprefix = !rootsymbol && !def->localscope && QCC_OPCodeValid(&pr_opcodes[OP_FETCH_GBL_F]); //only prefix arrays with a length if its not an embedded symbol, its not a (true)local, and if there's actually a point to doing it. } else { def->saved = (!arraysize || a>=0) && !!(flags & GDF_SAVED); //saved is never set on the head def in an array. def->strip = !!(flags & GDF_STRIP); def->arraylengthprefix = false; } def->allowinline = !!(flags & GDF_INLINE); if (flags & GDF_USED) { def->nostrip = true; def->used = true; def->referenced = true; } def->ofs = ofs + ((a>0)?type->size*a:0); if (!first) first = def; if (!rootsymbol || (def==rootsymbol && !rootsymbol->symboldata)) { if (!rootsymbol) { rootsymbol = first; rootsymbol->deftail = rootsymbol; } if ((flags & GDF_POSTINIT) || arraysize<0 || !type->size) rootsymbol->symboldata = NULL; else rootsymbol->symboldata = qccHunkAlloc ((def->arraysize?def->arraysize:1) * type->size * sizeof(float)); } if (redec) ; //symbol already linked. else if (rootsymbol != def && !(flags&GDF_ALIAS)) { //we're inserting the symbol into the 'middle' of the parent after the fact. so this is kinda messy. if (!rootsymbol->deftail) rootsymbol->deftail = rootsymbol; def->next = rootsymbol->deftail->next; rootsymbol->deftail->next = def; if (pr.def_tail == rootsymbol->deftail) pr.def_tail = def; //urgh if (scope)// && !(flags & (GDF_CONST|GDF_STATIC))) { //constants are never considered locals. static (local) variables are also not counted as locals as they're logically globals in all regards other than visibility. //just insert it at the start. who cares. def->nextlocal = rootsymbol->deftail->nextlocal; rootsymbol->deftail->nextlocal = def; if (pr.local_tail == rootsymbol->deftail) pr.local_tail = def; def->localscope = true; } rootsymbol->deftail = def; first->deftail = def; } else { pr.def_tail->next = def; pr.def_tail = def; first->deftail = def; if (scope)// && !(flags & (GDF_CONST|GDF_STATIC))) { //constants are never considered locals. static (local) variables are also not counted as locals as they're logically globals in all regards other than visibility. pr.local_tail->nextlocal = def; pr.local_tail = def; def->localscope = true; } } def->symbolheader = rootsymbol; if (arraysize < 0 || !type->size) break; //don't define the rest if the struct/union ain't defined properly yet def->symboldata = (rootsymbol->symboldata?rootsymbol->symboldata + def->ofs:NULL); if (type->bits) def->symbolsize = (((def->arraysize?def->arraysize:1) * ((type->bits+7)/8))+(VMWORDSIZE-1))/VMWORDSIZE; else def->symbolsize = (def->arraysize?def->arraysize:1) * type->size; if ((type->type == ev_struct||type->type == ev_union) && (!arraysize || a>=0)) { unsigned int partnum; QCC_type_t *parttype; def->saved = false; //struct headers don't get saved. for (partnum = 0; partnum < (type->type == ev_union?max(1,type->num_parms):type->num_parms); partnum++) { parttype = type->params[partnum].type; while (parttype->type == ev_accessor) parttype = parttype->parentclass; switch (parttype->type) { case ev_vector: QC_snprintfz(newname, sizeof(newname), "%s.%s", def->name, type->params[partnum].paramname); QCC_PR_DummyDef(parttype, newname, scope, type->params[partnum].arraysize, rootsymbol, def->ofs+type->params[partnum].ofs, false, flags); // QC_snprintfz(newname, sizeof(newname), "%s.%s_x", def->name, type->params[partnum].paramname); // QCC_PR_DummyDef(type_float, newname, scope, 0, rootsymbol, type->params[partnum].ofs - rootsymbol->ofs, false, flags | GDF_CONST); // QC_snprintfz(newname, sizeof(newname), "%s.%s_y", def->name, type->params[partnum].paramname); // QCC_PR_DummyDef(type_float, newname, scope, 0, rootsymbol, type->params[partnum].ofs+1 - rootsymbol->ofs, false, flags | GDF_CONST); // QC_snprintfz(newname, sizeof(newname), "%s.%s_z", def->name, type->params[partnum].paramname); // QCC_PR_DummyDef(type_float, newname, scope, 0, rootsymbol, type->params[partnum].ofs+2 - rootsymbol->ofs, false, flags | GDF_CONST); break; case ev_accessor: //shouldn't happen. case ev_enum: case ev_float: case ev_double: case ev_boolean: case ev_string: case ev_entity: case ev_field: case ev_pointer: case ev_integer: case ev_uint: case ev_int64: case ev_uint64: case ev_struct: case ev_union: case ev_variant: //for lack of any better alternative if (type->params[partnum].paramname) QC_snprintfz(newname, sizeof(newname), "%s.%s", def->name, type->params[partnum].paramname); else //anon or something (eg array types). QC_snprintfz(newname, sizeof(newname), "%s", def->name); QCC_PR_DummyDef(parttype, newname, scope, type->params[partnum].arraysize, rootsymbol, def->ofs+type->params[partnum].ofs, false, flags); break; case ev_function: QC_snprintfz(newname, sizeof(newname), "%s.%s", def->name, parttype->name); QCC_PR_DummyDef(parttype, newname, scope, type->params[partnum].arraysize, rootsymbol, def->ofs+type->params[partnum].ofs, false, flags)->initialized = true; break; case ev_bitfld: //FIXME: breaks saved games case ev_void: break; case ev_typedef: //invalid QCC_PR_ParseWarning(ERR_INTERNAL, "unexpected typedef"); break; } } } else if (type->type == ev_vector && !arraysize) { //do the vector thing. QC_snprintfz(newname, sizeof(newname), "%s_x", def->name); QCC_PR_DummyDef(type_float, newname, scope, 0, rootsymbol, def->ofs+0, referable, (flags&~GDF_SAVED) | GDF_STRIP); QC_snprintfz(newname, sizeof(newname), "%s_y", def->name); QCC_PR_DummyDef(type_float, newname, scope, 0, rootsymbol, def->ofs+1, referable, (flags&~GDF_SAVED) | GDF_STRIP); QC_snprintfz(newname, sizeof(newname), "%s_z", def->name); QCC_PR_DummyDef(type_float, newname, scope, 0, rootsymbol, def->ofs+2, referable, (flags&~GDF_SAVED) | GDF_STRIP); } else if (type->type == ev_field) { if (type->aux_type->type == ev_vector && !arraysize && *def->name != ':') { //do the vector thing. QC_snprintfz(newname, sizeof(newname), "%s_x", def->name); QCC_PR_DummyDef(type_floatfield, newname, scope, 0, rootsymbol, def->ofs+0, referable, flags); QC_snprintfz(newname, sizeof(newname), "%s_y", def->name); QCC_PR_DummyDef(type_floatfield, newname, scope, 0, rootsymbol, def->ofs+1, referable, flags); QC_snprintfz(newname, sizeof(newname), "%s_z", def->name); QCC_PR_DummyDef(type_floatfield, newname, scope, 0, rootsymbol, def->ofs+2, referable, flags); } } } if (referable) { // if (!arraysize && first->type->type != ev_field) // first->constant = false; if (scope) pHash_Add(&localstable, first->name, first, qccHunkAlloc(sizeof(bucket_t))); else pHash_Add(&globalstable, first->name, first, qccHunkAlloc(sizeof(bucket_t))); if (!scope && asmfile) fprintf(asmfile, "%s %s;\n", TypeName(first->type, typebuf, sizeof(typebuf)), first->name); } return first; } /* ============ PR_GetDef If type is NULL, it will match any type If arraysize=0, its not an array and has 1 element. If arraysize>0, its an array and requires array notation If arraysize<0, its an array with undefined size - GetDef will fail if its not already allocated. If allocate is 0, will only get the def If allocate is 1, a new def will be allocated if it can't be found If allocate is 2, a new def will be allocated, and it'll error if there's a dupe with scope (for ensuring that arguments are created properly) ============ */ QCC_def_t *QCC_PR_GetDef (QCC_type_t *type, const char *name, struct QCC_function_s *scope, pbool allocate, int arraysize, unsigned int flags) { int ofs; QCC_def_t *def; // char element[MAX_NAME]; QCC_def_t *foundstatic = NULL; char typebuf1[1024], typebuf2[1024]; int ins, insmax; if (!allocate) arraysize = -1; else if (!strncmp(name, "autocvar_", 9)) { if (scope) QCC_PR_ParseWarning(WARN_MISUSEDAUTOCVAR, "Autocvar \"%s\" defined with local scope. promoting to global.", name); else if (flags & GDF_CONST) QCC_PR_ParseWarning(WARN_MISUSEDAUTOCVAR, "Autocvar \"%s\" defined as constant. attempting to correct that for you.", name); else if (flags & GDF_STATIC) QCC_PR_ParseWarning(WARN_MISUSEDAUTOCVAR, "Autocvar \"%s\" defined as static. attempting to correct that for you.", name); scope = NULL; flags &= ~(GDF_CONST|GDF_STATIC); if (!(flags & GDF_STRIP)) flags |= GDF_USED; //aka nostrip } if (pHash_Get != &Hash_Get) { ins = 0; insmax = allocate?1:2; } else { ins = 1; insmax = 2; } for (; ins < insmax; ins++) { if (scope) { //FIXME: should we be scanning the locals list instead, and remove the localstable? def = pHash_Get(&localstable, name); while(def) { //ignore differing case the first time around. if (ins == 0 && strcmp(def->name, name)) { def = pHash_GetNext(&localstable, name, def); continue; // in a different function } if ( def->scope && def->scope != scope) { struct QCC_function_s *pscope = NULL; if (def->isstatic) { for (pscope = scope->parentscope; pscope; pscope = pscope->parentscope) if (def->scope == pscope) break; } if (!pscope) { def = pHash_GetNext(&localstable, name, def); continue; // in a different function } } if (type && typecmp(def->type, type)) { if (scope && allocate && pr_subscopedlocals) { //assume it was defined in a different subscope. hopefully we'll start favouring the new one until it leaves subscope. def = pHash_GetNext(&localstable, name, def); continue; } QCC_PR_ParseErrorPrintDef (ERR_TYPEMISMATCHREDEC, def, "Type mismatch on redeclaration of %s%s%s. %s%s%s, should be %s%s%s",col_symbol,name,col_none, col_type,TypeName(type, typebuf1, sizeof(typebuf1)),col_none, col_type,TypeName(def->type, typebuf2, sizeof(typebuf2)),col_none); } if (def->arraysize != arraysize && arraysize>=0) QCC_PR_ParseErrorPrintDef (ERR_TYPEMISMATCHARRAYSIZE, def, "Array sizes for redecleration of %s%s%s do not match (%i -> %i)",col_symbol,name,col_none, def->arraysize,arraysize); if (allocate && scope && !(flags & GDF_STATIC)) { if (pr_subscopedlocals) { //subscopes mean that the later one replaces the first, hopefully. def = pHash_GetNext(&localstable, name, def); continue; } if (allocate == 2) QCC_PR_ParseErrorPrintDef (ERR_TYPEMISMATCHREDEC, def, "Duplicate definition of %s%s%s.", col_symbol,name,col_none); if (def->isstatic) QCC_PR_ParseWarning (WARN_DUPLICATEDEFINITION, "nonstatic redeclaration of %s%s%s ignored", col_symbol,name,col_none); else QCC_PR_ParseWarning (WARN_DUPLICATEDEFINITION, "%s%s%s duplicate definition ignored", col_symbol,name,col_none); QCC_PR_ParsePrintDef(WARN_DUPLICATEDEFINITION, def); // if (!scope) // QCC_PR_ParsePrintDef(def); } else if (allocate && (flags & GDF_STATIC) && !def->isstatic) QCC_PR_ParseErrorPrintDef (ERR_TYPEMISMATCHREDEC, def, "static redefinition of %s%s%s follows non-static definition.", col_symbol,name,col_none); QCC_ForceUnFreeDef(def); return def; } } def = pHash_Get(&globalstable, name); while(def) { //ignore differing case the first time around. if (ins == 0 && strcmp(def->name, name)) { def = pHash_GetNext(&globalstable, name, def); continue; // in a different function } if ( (def->scope || (scope && allocate)) && def->scope != scope) { def = pHash_GetNext(&globalstable, name, def); continue; // in a different function } //ignore it if its static in some other file. if ((def->isstatic||(flags&GDF_STATIC)) && strcmp(def->unitn, scope?scope->unitn:s_unitn)) { if (!foundstatic) foundstatic = def; //save it off purely as a warning. def = pHash_GetNext(&globalstable, name, def); continue; // in a different function } if (def->assumedtype && !(flags & GDF_BASICTYPE)) { if (allocate) { //if we're asserting a type for it in some def then it'll no longer be assumed. if (def->type->type != type->type) QCC_PR_ParseErrorPrintDef (ERR_TYPEMISMATCHREDEC, def, "Type mismatch on redeclaration of %s%s%s. Basic types are different.",col_symbol,name,col_none); def->type = type; def->assumedtype = false; def->filen = s_filen; def->s_line = pr_source_line; if (flags & GDF_CONST) def->constant = true; } else { //we know enough of its type to write it out, but not enough to actually use it safely, so pretend this def isn't defined yet. def = pHash_GetNext(&globalstable, name, def); continue; } } if (type && typecmp(def->type, type)) { if (pr_scope || typecmp_lax(def->type, type)) { if (!pr_scope && ( !strcmp("droptofloor", def->name) || //vanilla !strcmp("callfunction", def->name) || //should be (..., string name) but dpextensions gets this wrong. !strcmp("trailparticles", def->name) //dp got the two arguments the wrong way. fteqw doesn't care any more, but dp is still wrong. )) { //this is a hack. droptofloor was wrongly declared in vanilla qc, which causes problems with replacement extensions.qc. //yes, this is a selfish lazy hack for this, there's probably a better way, but at least we spit out a warning still. QCC_PR_ParseWarning (WARN_COMPATIBILITYHACK, "%s builtin was wrongly redefined as %s. ignoring later definition",name, TypeName(type, typebuf1, sizeof(typebuf1))); QCC_PR_ParsePrintDef(WARN_COMPATIBILITYHACK, def); } else { int flen = strlen(s_filen); if (!pr_scope && flen >= 13 && !QC_strcasecmp(s_filen+flen-13, "extensions.qc") && def->type->type == ev_function) { //this is a hack. droptofloor was wrongly declared in vanilla qc, which causes problems with replacement extensions.qc. //yes, this is a selfish lazy hack for this, there's probably a better way, but at least we spit out a warning still. QCC_PR_ParseWarning (WARN_COMPATIBILITYHACK, "%s builtin was redefined as %s. ignoring alternative definition",name, TypeName(type, typebuf1, sizeof(typebuf1))); QCC_PR_ParsePrintDef(WARN_COMPATIBILITYHACK, def); } else if (def->unused && !def->referenced && allocate && !def->scope) { //previous def was norefed and still wasn't used yet. QCC_PR_ParseWarning (WARN_COMPATIBILITYHACK, "Type redeclaration of %s %s replaces existing variable", TypeName(type, typebuf1, sizeof(typebuf1)), name); QCC_PR_ParsePrintDef(WARN_COMPATIBILITYHACK, def); def = pHash_GetNext(&localstable, name, def); continue; } else { if (type->type == ev_function && type->vargs && !type->num_parms && def->type->type == ev_function) ; //c89-style dumb redeclarations... else //unequal even when we're lax QCC_PR_ParseErrorPrintDef (ERR_TYPEMISMATCHREDEC, def, "Type mismatch on redeclaration of %s%s%s. %s%s%s, should be %s%s%s",col_symbol,name,col_none, col_type,TypeName(type, typebuf1, sizeof(typebuf1)),col_none, col_type,TypeName(def->type, typebuf2, sizeof(typebuf2)),col_none); } } } else { if (type->type != ev_function || type->num_parms != def->type->num_parms || !(def->type->vargs && !type->vargs)) { //if the second def simply has no ..., don't bother warning about it. QCC_PR_ParseWarning (WARN_TYPEMISMATCHREDECOPTIONAL, "Optional arguments differ on redeclaration of %s%s%s. %s%s%s, should be %s%s%s",col_symbol,name,col_none, col_type,TypeName(type, typebuf1, sizeof(typebuf1)),col_none, col_type,TypeName(def->type, typebuf2, sizeof(typebuf2)),col_none); QCC_PR_ParsePrintDef(WARN_TYPEMISMATCHREDECOPTIONAL, def); if (type->type == ev_function) { //update the def's type to the new one if the mandatory argument count is longer //FIXME: don't change the param names! if (type->num_parms > def->type->num_parms) def->type = type; } } } } if (def->arraysize != arraysize && arraysize>=0) { if (allocate && !def->symbolsize) QCC_PR_DummyDef(def->type, name, def->scope, arraysize, def, 0, true, flags); else QCC_PR_ParseErrorPrintDef(ERR_TYPEMISMATCHARRAYSIZE, def, "Array sizes for redecleration of %s do not match (%i->%i)",name, def->arraysize,arraysize); } if (!def->symbolsize && def->arraysize >= 0) //variables are allowed to be declared (with their addresses taken) before struct types are even defined. QCC_PR_DummyDef(def->type, name, def->scope, def->arraysize, def, 0, true, flags); if (allocate && scope && !(flags & GDF_STATIC)) { if (allocate == 2) QCC_PR_ParseErrorPrintDef (ERR_TYPEMISMATCHREDEC, def, "Duplicate definition of %s.", name); if (pr_scope) { //warn? or would that be pointless? def = pHash_GetNext(&globalstable, name, def); continue; // in a different function } if (def->isstatic) QCC_PR_ParseWarning (WARN_DUPLICATEDEFINITION, "nonstatic redeclaration of %s ignored", name); else QCC_PR_ParseWarning (WARN_DUPLICATEDEFINITION, "%s duplicate definition ignored", name); QCC_PR_ParsePrintDef(WARN_DUPLICATEDEFINITION, def); // if (!scope) // QCC_PR_ParsePrintDef(def); } QCC_ForceUnFreeDef(def); return def; } } if (foundstatic && !allocate && !(flags & GDF_SILENT)) { QCC_PR_ParseWarning (WARN_SAMENAMEASGLOBAL, "%s defined static", name); QCC_PR_ParsePrintDef(WARN_SAMENAMEASGLOBAL, foundstatic); } if (flags & GDF_SCANLOCAL) { for (def = pr.local_head.nextlocal; def; def = def->nextlocal) { if (!strcmp(def->name, name)) { if (allocate && def->arraysize != arraysize) continue; if (allocate) { //relink it pHash_Add(&localstable, name, def, qccHunkAlloc(sizeof(bucket_t))); def->subscoped_away = false; } QCC_ForceUnFreeDef(def); return def; } } } if (!allocate) return NULL; if (scope && qccwarningaction[WARN_SAMENAMEASGLOBAL]) { def = QCC_PR_GetDef(NULL, name, NULL, false, arraysize, GDF_SILENT); if (def && def->type->type == type->type) { //allow type differences. this means that arguments called 'min' or 'mins' are accepted with the 'min' builtin or the 'mins' field in existance. QCC_PR_ParseWarning(WARN_SAMENAMEASGLOBAL, "Local \"%s\" hides global with same name and type", name); QCC_PR_ParsePrintDef(WARN_SAMENAMEASGLOBAL, def); } QCC_FreeDef(def); } ofs = 0; def = QCC_PR_DummyDef(type, name, scope, arraysize, NULL, ofs, true, flags); QCC_ForceUnFreeDef(def); return def; } QCC_sref_t QCC_PR_GetSRef (QCC_type_t *type, const char *name, QCC_function_t *scope, pbool allocate, int arraysize, unsigned int flags) { QCC_sref_t sr; QCC_def_t *def = QCC_PR_GetDef(type, name, scope, allocate, arraysize, flags); if (def) { sr.sym = def; sr.cast = def->type; sr.ofs = 0; if (def->deprecated) { if (*def->deprecated) //we have a reason for it QCC_PR_ParseWarning(pr_ignoredeprecation?WARN_MUTEDEPRECATEDVARIABLE:WARN_DEPRECATEDVARIABLE, "Variable \"%s\" is deprecated: %s", def->name, def->deprecated); else //we don't have any reason for it. QCC_PR_ParseWarning(pr_ignoredeprecation?WARN_MUTEDEPRECATEDVARIABLE:WARN_DEPRECATEDVARIABLE, "Variable \"%s\" is deprecated", def->name); } return sr; } return nullsref; } //.union {}; static QCC_def_t *QCC_PR_DummyFieldDef(QCC_type_t *type, QCC_function_t *scope, int arraysize, unsigned int *fieldofs, unsigned int saved) { char array[64]; char newname[256]; int a, parms, o; QCC_def_t *def, *first=NULL; unsigned int maxfield, startfield; QCC_type_t *ftype; pbool isunion; startfield = *fieldofs; maxfield = startfield; for (a = 0; a < (arraysize?arraysize:1); a++) { if (a == 0) *array = '\0'; else QC_snprintfz(array, sizeof(array), "[%i]", a); // externs->Printf("Emited %s\n", newname); if ((type)->type == ev_struct||(type)->type == ev_union) { int memberalen; int partnum; QCC_type_t *parttype; isunion = ((type)->type == ev_union); for (partnum = 0, parms = (type)->num_parms; partnum < parms; partnum++) { parttype = type->params[partnum].type; while(parttype->type == ev_accessor) parttype = parttype->parentclass; memberalen = type->params[partnum].arraysize; switch (parttype->type) { case ev_union: case ev_struct: if (!*type->params[partnum].paramname) { //recursively generate new fields QC_snprintfz(newname, sizeof(newname), "%s%s", type->params[partnum].paramname, array); def = QCC_PR_DummyFieldDef(parttype, scope, memberalen, fieldofs, saved); break; } //fallthrough. any named structs will become global structs that contain field references. hopefully. case ev_enum: case ev_accessor: case ev_float: case ev_double: case ev_boolean: case ev_string: case ev_vector: case ev_entity: case ev_field: case ev_pointer: case ev_integer: case ev_uint: case ev_int64: case ev_uint64: case ev_variant: case ev_function: if (!*type->params[partnum].paramname) { QCC_PR_ParseWarning(WARN_CONFLICTINGUNIONMEMBER, "nameless field union/struct generating nameless def."); break; } QC_snprintfz(newname, sizeof(newname), "%s%s", type->params[partnum].paramname, array); ftype = QCC_PR_NewType("FIELD_TYPE", ev_field, false); ftype->aux_type = parttype; if (parttype->type == ev_vector) ftype->size = parttype->size; //vector fields create a _y and _z too, so we need this still. def = QCC_PR_GetDef(NULL, newname, scope, false, memberalen, saved); if (!def) { def = QCC_PR_GetDef(ftype, newname, scope, true, memberalen, saved); if (parttype->type == ev_function) def->initialized = true; for (o = 0; o < parttype->size*(memberalen?memberalen:1); o++) def->symboldata[o]._int = *fieldofs + o; *fieldofs += parttype->size*(memberalen?memberalen:1); } else { QCC_PR_ParseWarning(WARN_CONFLICTINGUNIONMEMBER, "conflicting offsets for nameless union/struct expansion of %s. Ignoring new def.", newname); QCC_PR_ParsePrintDef(WARN_CONFLICTINGUNIONMEMBER, def); //hcc just PR_GetDefs the fields. it allocates field space as part of the def, which is skipped if it already exists. //so don't update fieldofs, because that would result in incompatibilities. } QCC_FreeDef(def); break; case ev_void: case ev_bitfld: //FIXME: breaks saved games break; case ev_typedef: //invalid QCC_PR_ParseWarning(ERR_INTERNAL, "unexpected typedef"); break; } if (*fieldofs > maxfield) maxfield = *fieldofs; if (isunion) *fieldofs = startfield; } } } *fieldofs = maxfield; //final size of the union. return first; } static void QCC_PR_ExpandUnionToFields(QCC_type_t *type, unsigned int *fields) { QCC_type_t *pass = type->aux_type; QCC_PR_DummyFieldDef(pass, pr_scope, 1, fields, GDF_SAVED|GDF_CONST); } //copies tmp into def //FIXME: is basedef redundant? //FIXME: is type redundant? static pbool QCC_PR_GenerateInitializerType(QCC_def_t *basedef, QCC_sref_t tmp, QCC_sref_t def, QCC_type_t *type, unsigned bitofs, unsigned int flags) { pbool ret = true; unsigned i; def.ofs += bitofs>>5; bitofs&=31; if (bitofs && !type->bits) QCC_PR_ParseWarning(ERR_BADIMMEDIATETYPE, "'%s' is not word aligned", basedef?basedef->name:""); if (basedef && (!basedef->scope || basedef->constant || basedef->isstatic)) { if (!tmp.sym->constant) { if (basedef->scope && !basedef->isstatic && !basedef->initialized) { // QCC_PR_ParseWarning(WARN_GMQCC_SPECIFIC, "initializer for '%s' is not constant", basedef->name); // QCC_PR_ParsePrintSRef(WARN_GMQCC_SPECIFIC, tmp); // basedef->constant = false; goto finalnotconst; } QCC_PR_ParseWarning(ERR_BADIMMEDIATETYPE, "initializer for '%s' is not constant", basedef->name); QCC_PR_ParsePrintSRef(ERR_BADIMMEDIATETYPE, tmp); } else { tmp.sym->referenced = true; if ((!basedef->scope||basedef->isstatic) && tmp.sym->reloc) { QCC_def_t *dt; if (!flag_pointerrelocs) QCC_PR_ParseWarning(ERR_BADEXTENSION, "preinitialised pointer variables is disabled for this target"); dt = QCC_PR_DummyDef(type_pointer, "$reloc", basedef->scope, 0, def.sym, def.ofs, false, GDF_CONST); dt->reloc = tmp.sym->reloc; dt->referenced = true; dt->constant = 1; dt->initialized = 1; dt->strip = true; //engine does need to know it... but there should be some other def that we're mapping intohere. for (i = 0; (unsigned)i < type->size; i++) QCC_SRef_DataWord(def, i)->_int = QCC_SRef_DataWord(tmp, i)->_int;//tmp.ofs + tmp.sym->reloc->symbolheader->arraylengthprefix; QCC_FreeTemp(tmp); return ret; } if ((!basedef->scope||basedef->isstatic) && def.cast->type==ev_pointer && tmp.sym->type->type==ev_string) { QCC_def_t *dt; dt = QCC_PR_DummyDef(type_string, "$reloc", basedef->scope, 0, def.sym, def.ofs, false, GDF_CONST); dt->reloc = tmp.sym->reloc; dt->referenced = true; dt->constant = 1; dt->initialized = 1; dt->strip = true; //engine does need to know it... but there should be some other def that we're mapping intohere. for (i = 0; (unsigned)i < type->size; i++) QCC_SRef_DataWord(def, i)->_int = QCC_SRef_DataWord(tmp, i)->_int;//tmp.ofs + tmp.sym->reloc->symbolheader->arraylengthprefix; QCC_FreeTemp(tmp); return ret; } if (basedef->initialized && !basedef->unused && !(flags & PIF_STRONGER)) { //dupe initialisation. compare the two. if (!tmp.sym->initialized) { //FIXME: we NEED to support relocs somehow QCC_PR_ParseWarning(WARN_UNINITIALIZED, "initializer is not initialised, %s will be treated as 0", QCC_GetSRefName(tmp)); QCC_PR_ParsePrintSRef(WARN_UNINITIALIZED, tmp); } for (i = 0; (unsigned)i < type->size; i++) if (QCC_SRef_DataWord(def,i)->_int != QCC_SRef_DataWord(tmp,i)->_int) { if (!def.sym->arraysize && def.cast->type == ev_function && !strcmp(def.sym->name, "parseentitydata") && (functions[QCC_SRef_DataWord(def, i)->_int].builtin == 608 || functions[QCC_SRef_DataWord(def, i)->_int].builtin == 613)) { //dpextensions is WRONG, and claims it to be 608. its also too common, so lets try working around that. if (functions[QCC_SRef_DataWord(def, i)->_int].builtin == 608) functions[QCC_SRef_DataWord(def, i)->_int].builtin = 613; QCC_PR_ParseWarning (WARN_COMPATIBILITYHACK, "incompatible redeclaration. Please validate builtin numbers. parseentitydata is #613"); QCC_PR_ParsePrintSRef(WARN_COMPATIBILITYHACK, tmp); } else if (!def.sym->arraysize && def.cast->type == ev_function && functions[QCC_SRef_DataWord(def, i)->_int].code>-1 && functions[QCC_SRef_DataWord(tmp, i)->_int].code==-1) { QCC_PR_ParseWarning (WARN_COMPATIBILITYHACK, "incompatible redeclaration. Ignoring replacement of qc function with builtin."); QCC_PR_ParsePrintSRef(WARN_COMPATIBILITYHACK, tmp); } else QCC_PR_ParseErrorPrintSRef (ERR_REDECLARATION, def, "incompatible redeclaration"); } } else { const int *srcdata = (const void*)QCC_SRef_EvalConst(tmp); if (!srcdata) { if ((!basedef->scope||basedef->isstatic) && def.cast->type == ev_function && def.sym->symboldata==basedef->symboldata) { //set to a function which is not yet initialised. insert a function reloc at this location, so we can update it once it is actually known. for (i = 0; (unsigned)i < type->size; i++) { QCC_def_t *dt; dt = QCC_PR_DummyDef(type_function, "$relocf", basedef->scope, 0, def.sym, def.ofs, false, GDF_CONST); dt->reloc = tmp.sym; dt->referenced = true; dt->constant = 1; dt->initialized = 1; dt->strip = true; //hide it. engine doesn't need to know. QCC_SRef_DataWord(def, i)->_int = 0; } } else { QCC_PR_ParseWarning(WARN_NOTCONSTANT, "initializer for %s is not initialised yet, %s will be treated as 0", QCC_GetSRefName(def), QCC_GetSRefName(tmp)); QCC_PR_ParsePrintSRef(WARN_NOTCONSTANT, tmp); for (i = 0; (unsigned)i < type->size; i++) QCC_SRef_DataWord(def, i)->_int = 0; } } else if (type->bits) { //a small type/bitfield... unsigned int old, new; if (bitofs + type->bits > 32) QCC_PR_ParseWarning(ERR_INTERNAL, "bitfield cross 32bit boundary"); old = (QCC_SRef_DataWord(def, bitofs>>5)->_int&~(((1u<bits)-1)<bits)-1)) << bitofs; QCC_SRef_DataWord(def, bitofs>>5)->_int = old|new; } else { for (i = 0; (unsigned)i < type->size; i++) QCC_SRef_DataWord(def, i)->_int = srcdata[i]; } } } } else { QCC_sref_t rhs; pbool nullsource; finalnotconst: rhs = tmp; nullsource = QCC_SRef_IsNull(rhs); if (def.sym->initialized) QCC_PR_ParseErrorPrintSRef (ERR_REDECLARATION, def, "%s initialised twice", basedef->name); else if (type->bits) { if (bitofs + type->bits > 32) QCC_PR_ParseErrorPrintSRef (ERR_REDECLARATION, def, "%s dynamically initialised (%i bit)", basedef->name, type->bits); rhs = QCC_PR_Statement_BitCopy(nullsource?QCC_MakeVectorConst(0,0,0):rhs, bitofs, type->bits, def); if (rhs.sym == def.sym && rhs.ofs == def.ofs && rhs.cast == def.cast) { QCC_FreeTemp(rhs); return ret; } //else still need to copy it. } ret = 0; for (i = 0; (unsigned)i < type->size; ) { if (type->size - i >= 3) { rhs.cast = def.cast = type_vector; if (type->size - i == 3) { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_V], nullsource?QCC_MakeVectorConst(0,0,0):rhs, def, NULL, STFL_PRESERVEB)); return ret; } else QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_V], nullsource?QCC_MakeVectorConst(0,0,0):rhs, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); i+=3; def.ofs += 3; rhs.ofs += 3; } else if (type->size - i >= 2) { rhs.cast = def.cast = type_vector; if (type->size - i == 2) { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_I64], nullsource?QCC_MakeVectorConst(0,0,0):rhs, def, NULL, STFL_PRESERVEB)); return ret; } else QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_I64], nullsource?QCC_MakeVectorConst(0,0,0):rhs, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); i+=2; def.ofs += 2; rhs.ofs += 2; } else { if (nullsource) rhs = QCC_MakeIntConst(0); if (def.cast->type == ev_function) { rhs.cast = def.cast = type_function; if (type->size - i == 1) { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_FNC], rhs, def, NULL, STFL_PRESERVEB)); return ret; } else QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_FNC], rhs, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); } else if (def.cast->type == ev_string) { rhs.cast = def.cast = type_string; if (type->size - i == 1) { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_S], rhs, def, NULL, STFL_PRESERVEB)); return ret; } else QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_S], rhs, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); } else if (def.cast->type == ev_entity) { rhs.cast = def.cast = type_entity; if (type->size - i == 1) { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_ENT], rhs, def, NULL, STFL_PRESERVEB)); return ret; } else QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_ENT], rhs, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); } else if (def.cast->type == ev_field) { rhs.cast = def.cast = type_field; if (type->size - i == 1) { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_FLD], rhs, def, NULL, STFL_PRESERVEB)); return ret; } else QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_FLD], rhs, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); } else if (def.cast->type == ev_integer || def.cast->type == ev_uint) { rhs.cast = def.cast = type_integer; if (type->size - i == 1) { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_I], rhs, def, NULL, STFL_PRESERVEB)); return ret; } else QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_I], rhs, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); } else if (def.cast->type == ev_int64 || def.cast->type == ev_uint64 || def.cast->type == ev_double) { rhs.cast = def.cast = type_int64; if (type->size - i == 2) { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_I64], rhs, def, NULL, STFL_PRESERVEB)); return ret; } else QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_I64], rhs, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); } else { rhs.cast = def.cast = type_float; if (type->size - i == 1) { QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], rhs, def, NULL, STFL_PRESERVEB)); return ret; } else QCC_FreeTemp(QCC_PR_StatementFlags(&pr_opcodes[OP_STORE_F], rhs, def, NULL, STFL_PRESERVEA|STFL_PRESERVEB)); } i++; def.ofs++; rhs.ofs++; } } } QCC_FreeTemp(tmp); return ret; } QCC_sref_t QCC_PR_ParseInitializerType_Internal(int arraysize, QCC_def_t *basedef, QCC_sref_t def, unsigned int bitofs, unsigned int flags) { QCC_sref_t tmp; int i; pbool ret = true; if (arraysize) { if (pr_token_type == tt_immediate && pr_immediate_type == type_string && def.cast->type == ev_bitfld && def.cast->bits == 8) { if (pr_immediate_strlen > arraysize) //problem! QCC_PR_ParseWarning (ERR_BADARRAYSIZE, "initializer-string too long"); for (i = 0; i < arraysize; i++) { tmp = QCC_MakeIntConst((i < pr_immediate_strlen)?pr_immediate_string[i]:0); tmp = QCC_EvaluateCast(tmp, def.cast, true); // QCC_ForceUnFreeDef(tmp.sym); ret &= QCC_PR_GenerateInitializerType(basedef, tmp, def, def.cast, bitofs, flags); bitofs += def.cast->bits; } QCC_PR_Lex(); } else { //arrays go recursive QCC_PR_Expect("{"); if (!QCC_PR_CheckToken("}")) { for (i = 0; i < arraysize; i++) { ret &= QCC_PR_ParseInitializerType(0, basedef, def, bitofs, flags); if (def.cast->bits) bitofs += def.cast->bits; else def.ofs += def.cast->size; if (!QCC_PR_CheckToken(",")) { QCC_PR_Expect("}"); break; } if (QCC_PR_CheckToken("}")) break; } } } } else { QCC_type_t *type = def.cast; if (type->type == ev_function && pr_token_type == tt_punct) { /*begin function special case*/ QCC_function_t *parentfunc = pr_scope; QCC_function_t *f; char fname[256]; const char *defname = QCC_GetSRefName(def); tmp = nullsref; *fname = 0; if (QCC_PR_CheckToken ("#") || QCC_PR_CheckToken (":")) { int binum = 0; if (pr_token_type == tt_immediate && pr_immediate_type == type_float && pr_immediate._float == (int)pr_immediate._float) binum = (int)pr_immediate._float; else if (pr_token_type == tt_immediate && pr_immediate_type == type_integer) binum = pr_immediate._int; else if (pr_token_type == tt_immediate && pr_immediate_type == type_string) QC_strlcpy(fname, pr_immediate_string, sizeof(fname)); else if (pr_token_type == tt_name) QC_strlcpy(fname, pr_token, sizeof(fname)); else QCC_PR_ParseError (ERR_BADBUILTINIMMEDIATE, "Bad builtin immediate"); QCC_PR_Lex(); if (!*fname && QCC_PR_CheckToken (":")) QC_strlcpy(fname, QCC_PR_ParseName(), sizeof(fname)); //if the builtin already exists, just use that dfunction instead if (basedef && basedef->initialized) { if (*fname) { for (i = 1; i < numfunctions; i++) { if (functions[i].code == -1 && functions[i].builtin == binum) { if (!*functions[i].name) { functions[i].name = qccHunkAlloc(strlen(fname)+1); strcpy(functions[i].name, fname); } if (!strcmp(functions[i].name, fname)) { tmp = QCC_MakeIntConst(i); break; } } } } else { for (i = 1; i < numfunctions; i++) { if (functions[i].code == -1 && functions[i].builtin == binum) { if (!*functions[i].name || !strcmp(functions[i].name, defname)) { tmp = QCC_MakeIntConst(i); break; } } } } } if (!tmp.cast) f = QCC_PR_GenerateBuiltinFunction(def.sym, binum, *fname?fname:def.sym->name); else f = NULL; } else if (QCC_PR_PeekToken("{") || QCC_PR_PeekToken("[")) { if (basedef) { if (flags&PIF_WRAP) { if (!basedef->initialized || !QCC_SRef_Data(def)->_int) QCC_PR_ParseErrorPrintSRef (ERR_REDECLARATION, def, "wrapper function does not wrap anything"); } else if (basedef->initialized == 1 && !(flags & PIF_STRONGER)) { //normally this is an error, but to aid supporting new stuff with old, we convert it into a warning if a vanilla(ish) qc function replaces extension builtins. //the qc function is the one that is used, but there is a warning so you know how to gain efficiency. int bi = -1; if (def.cast->type == ev_function && !arraysize) { if (!strcmp(defname, "anglemod") || !strcmp(defname, "crossproduct")) bi = QCC_SRef_Data(def)->_int; } if (bi <= 0 || bi >= numfunctions) bi = 0; else bi = functions[bi].code; if (bi < 0) { QCC_PR_ParseWarning(WARN_NOTSTANDARDBEHAVIOUR, "%s already declared as a builtin", defname); QCC_PR_ParsePrintSRef(WARN_NOTSTANDARDBEHAVIOUR, def); basedef->unused = true; } else { QCC_PR_ParseWarning (ERR_REDECLARATION, "redeclaration of function body"); QCC_PR_ParsePrintSRef(WARN_NOTSTANDARDBEHAVIOUR, def); } } } if (pr_scope) { // QCC_PR_ParseErrorPrintSRef (ERR_INITIALISEDLOCALFUNCTION, def, "initialisation of function body within function body"); //save some state of the parent QCC_def_t *firstlocal = pr.local_head.nextlocal; QCC_def_t *lastlocal = pr.local_tail; QCC_function_t *parent = pr_scope; QCC_statement_t *patch; //FIXME: make sure gotos/labels/cases/continues/breaks are not broken by this. //generate a goto statement around the nested function, so that nothing is hurt. patch = QCC_Generate_OP_GOTO(); f = QCC_PR_ParseImmediateStatements (def.sym->isstatic?def.sym:NULL, type, flags&PIF_WRAP); patch->a.jumpofs = &statements[numstatements] - patch; if (patch->a.jumpofs == 1) numstatements--; //never mind then. //make sure parent state is restored properly. pr.local_head.nextlocal = firstlocal; pr.local_tail = lastlocal; pr_scope = parent; } else f = QCC_PR_ParseImmediateStatements (def.sym, type, flags&PIF_WRAP); //allow dupes if its a builtin if (basedef && !f->code && basedef->initialized) { for (i = 1; i < numfunctions; i++) { if (functions[i].code == -f->builtin) { tmp = QCC_MakeIntConst(i); break; } } } } else { f = NULL; tmp = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); tmp = QCC_EvaluateCast(tmp, type, true); } if (!tmp.cast && f) { if (type->aux_type->size > 3 && !autoprototype) //fixme: handle properly, without breaking __out QCC_PR_ParseWarning(WARN_SLOW_LARGERETURN, "Function %s returns (large) %s. This is inefficient.", def.sym->name, TypeName(type->aux_type, fname,sizeof(fname))); pr_scope = parentfunc; tmp = QCC_MakeIntConst(f - functions); if (!basedef && def.sym->temp) { //skip the store-to-temp // QCC_FreeTemp(def); tmp.cast = def.cast; return tmp; } } } else if (type->type == ev_string && QCC_PR_CheckName("_")) { char trname[128]; QCC_PR_Expect("("); if (pr_token_type != tt_immediate || pr_immediate_type->type != ev_string) QCC_PR_ParseError(0, "_() intrinsic accepts only a string immediate"); if (!pr_scope || basedef->constant || basedef->isstatic) { //if this is a static initialiser, embed a dotranslate in there QCC_SRef_Data(def)->_int = QCC_CopyString (pr_immediate_string); if (!pr_scope || def.sym->constant) { QCC_def_t *dt; QC_snprintfz(trname, sizeof(trname), "dotranslate_%i", ++dotranslate_count); dt = QCC_PR_DummyDef(type_string, trname, pr_scope, 0, def.sym, def.ofs, true, GDF_CONST); dt->referenced = true; dt->constant = 1; dt->initialized = 1; } QCC_PR_Lex(); QCC_PR_Expect(")"); return ret?def:nullsref; } tmp = QCC_MakeTranslateStringConst(pr_immediate_string); QCC_PR_Lex(); QCC_PR_Expect(")"); } else if (type->type == ev_union && type->num_parms == 1 && !type->params->paramname) { //weird typedefed array hack def.cast = (type)->params[0].type; ret &= QCC_PR_ParseInitializerType((type)->params[0].arraysize, basedef, def, bitofs, flags); def.cast = type; return ret?def:nullsref; } else if ((type->type == ev_struct || type->type == ev_union) && QCC_PR_CheckToken("{")) { //structs go recursive QCC_type_t *parenttype; unsigned int partnum; pbool isunion; gofs_t offset = def.ofs; gofs_t reloffset; unsigned int *isinited = alloca(sizeof(*isinited)*type->size); const char *mname; struct QCC_typeparam_s *tp; memset(isinited, 0, sizeof(pbool)*type->size); if (QCC_PR_PeekToken(".")) //won't be a float (.0 won't match) { //designated initialisers for(;;) { if (QCC_PR_CheckToken(".")) { mname = QCC_PR_ParseName(); QCC_PR_Expect("="); tp = QCC_PR_FindStructMember(type, mname, &def.ofs, &reloffset); if (isinited[def.ofs] & (1u<name, mname); isinited[def.ofs] |= 1u<type; ret &= QCC_PR_ParseInitializerType(tp->arraysize, basedef, def, bitofs+reloffset, flags); } else QCC_PR_ParseError (ERR_EXPECTED, "designated initialisers require all members to be initialised the same way"); if (QCC_PR_CheckToken("}")) break; QCC_PR_Expect(","); } partnum = 0; } else if (type->parentclass) { if (!QCC_PR_CheckToken("}")) QCC_PR_ParseError (ERR_EXPECTED, "inherited structs must use designated initialisers"); } else { //FIXME: inheritance makes stuff weird // int i; isunion = ((type)->type == ev_union); for (partnum = 0; partnum < (type)->num_parms; partnum++) { if (QCC_PR_CheckToken("}")) break; if ((type)->params[partnum].isvirtual) continue; //these are pre-initialised.... // if ((type)->params[partnum].optional) // continue; //float parts of a vector. def.cast = (type)->params[partnum].type; def.ofs = (type)->params[partnum].ofs; if (isinited[def.ofs] & (1<<(type)->params[partnum].bitofs)) { QCC_PR_ParseError (ERR_EXPECTED, "member %s.%s was already ininitialised", type->name, (type)->params[partnum].paramname); continue; } isinited[def.ofs] |= (1<<(type)->params[partnum].bitofs); /*i = (type)->params[partnum].arraysize; if (!i) i = 1; if ((type)->params[partnum].type->bits) ; else { i *= (type)->params[partnum].type->size; while(i --> 0) isinited[def.ofs+i] |= ~0; }*/ def.ofs += offset; ret &= QCC_PR_ParseInitializerType((type)->params[partnum].arraysize, basedef, def, bitofs + (type)->params[partnum].bitofs, flags); if (isunion || !QCC_PR_CheckToken(",")) { QCC_PR_Expect("}"); break; } } } //anything not already set needs to be filled with a default value. for (parenttype=type; parenttype; parenttype = parenttype->parentclass) { for (partnum = 0; partnum < (parenttype)->num_parms; partnum++) { //copy into the def. should be from a const. def.cast = (parenttype)->params[partnum].type; def.ofs = (parenttype)->params[partnum].ofs; if (isinited[def.ofs] & (1<<(parenttype)->params[partnum].bitofs)) continue; isinited[def.ofs] |= (1<<(parenttype)->params[partnum].bitofs); def.ofs += offset; if ((parenttype)->params[partnum].defltvalue.cast) tmp = (parenttype)->params[partnum].defltvalue; else tmp = QCC_MakeIntConst(0); QCC_ForceUnFreeDef(tmp.sym); QCC_PR_GenerateInitializerType(basedef, tmp, def, def.cast, bitofs, flags); } } def.cast = type; def.ofs = offset; return ret?def:nullsref; } else if (type->type == ev_vector && QCC_PR_PeekToken("{")) { //vectors can be treated as an array of 3 floats. def.cast = type_float; ret &= QCC_PR_ParseInitializerType(3, basedef, def, bitofs, flags); def.cast = type; return ret?def:nullsref; } else if (type->type == ev_pointer && QCC_PR_CheckToken("{")) { //generate a temp array QCC_ref_t buf, buf2; tmp.sym = QCC_PR_DummyDef(type->aux_type, NULL, pr_scope, 0, NULL, 0, false, GDF_STRIP|(pr_scope?GDF_STATIC:0)); tmp.ofs = 0; tmp.cast = tmp.sym->type; tmp.sym->refcount+=1; //fill up the array do { //expand the array unsigned int newsize = tmp.sym->arraysize * tmp.cast->size; if (tmp.sym->symbolsize < newsize) { void *newdata; newsize += 64 * tmp.cast->size; newdata = qccHunkAlloc (newsize * sizeof(float)); memcpy(newdata, tmp.sym->symboldata, tmp.sym->symbolsize*sizeof(float)); tmp.sym->symboldata = newdata; tmp.sym->symbolsize = newsize; } tmp.sym->arraysize++; //generate the def... QCC_PR_DummyDef(tmp.cast, NULL, pr_scope, 0, tmp.sym, tmp.ofs, false, GDF_STRIP|(pr_scope?GDF_STATIC:0)); //and fill it in. ret &= QCC_PR_ParseInitializerType(0, tmp.sym, tmp, bitofs, flags); tmp.ofs += type->aux_type->size; } while(QCC_PR_CheckToken(",")); QCC_PR_Expect("}"); //drop the size back down to something sane tmp.sym->symbolsize = tmp.ofs*sizeof(float); //grab the address of it. tmp.ofs = 0; tmp = QCC_RefToDef(QCC_PR_GenerateAddressOf(&buf, QCC_DefToRef(&buf2, tmp)), true); } else { tmp = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); tmp = QCC_EvaluateCast(tmp, type, true); } ret = QCC_PR_GenerateInitializerType(basedef, tmp, def, type, bitofs, flags); } return ret?def:nullsref; } QCC_sref_t QCC_PR_ParseInitializerTemp(QCC_type_t *type) { QCC_sref_t def = QCC_GetTemp(type); QCC_sref_t imm; imm = QCC_PR_ParseInitializerType_Internal(0, NULL, def, 0, 0); if (imm.cast) { if (imm.cast != type) QCC_PR_ParseError(ERR_INTERNAL, "QCC_PR_ParseInitializerTemp changed type\n"); QCC_FreeTemp(def); return imm; //just use the immediate. } return def; //use our silly temp. } //returns true where its a const/static initialiser. false if non-const/final initialiser pbool QCC_PR_ParseInitializerType(int arraysize, QCC_def_t *basedef, QCC_sref_t def, unsigned bitofs, unsigned int flags) { if (QCC_PR_ParseInitializerType_Internal(arraysize, basedef, def, bitofs, flags).cast) return true; return false; } void QCC_PR_ParseInitializerDef(QCC_def_t *def, unsigned int flags) { pr_ignoredeprecation = !!def->deprecated; //mute deprecation warnings if the symbol we're defining has its own warning. if (QCC_PR_ParseInitializerType(def->arraysize, def, QCC_MakeSRef(def, 0, def->type), 0, flags)) if (!def->initialized) def->initialized = 1; pr_ignoredeprecation = false; QCC_FreeDef(def); } QCC_sref_t QCC_PR_ParseDefaultInitialiser(QCC_type_t *type) { QCC_sref_t ref = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); if (!ref.sym->constant) QCC_PR_ParseError(0, "Default value not a constant\n"); if (!ref.sym->initialized) { if (autoprototype) return nullsref; QCC_PR_ParseError(0, "Default value not initialized yet\n"); } return QCC_EvaluateCast(ref, type, true); } int accglobalsblock; //0 = error, 1 = var, 2 = function, 3 = objdata QCC_type_t *QCC_PR_ParseEnum(pbool flags) { const char *name = NULL; QCC_sref_t sref; puint64_t next_i = flags?1:0; double next_f = next_i; struct accessor_s *acc; QCC_type_t *enumtype = NULL, *basetype; pbool strictenum = false; basetype = (flag_assume_integer?type_integer:type_float); if (!QCC_PR_CheckToken("{")) { QCC_type_t *type; strictenum = QCC_PR_CheckName("class"); //c++11 style type = autoprototyped?NULL:QCC_PR_ParseType(false, true, false); //legacy behaviour if (type) { QCC_PR_ParseWarning(WARN_DEPRECACTEDSYNTAX, "legacy enum base type. Use \"enum [class] [name_e]:type\" instead"); basetype = type; } else { if (pr_token_type == tt_name) name = QCC_PR_ParseName(); else name = NULL; if (QCC_PR_CheckToken(":")) { basetype = QCC_PR_ParseType(false, false, false); if (!basetype) QCC_PR_ParseError(ERR_NOTATYPE, "enumflags - must be numeric type"); } else if (strictenum) QCC_PR_Expect(":"); } QCC_PR_Expect("{"); } if (name && !enumtype) { enumtype = QCC_TypeForName(name); if (!enumtype) { if (strictenum) { enumtype = QCC_PR_NewType(name, basetype->type, true); enumtype->aux_type = basetype; enumtype->type = ev_enum; } else enumtype = QCC_PR_NewType(name, basetype->type, true); enumtype->size = basetype->size; } } while(1) { name = QCC_PR_ParseName(); if (QCC_PR_CheckToken("=")) { /*if (pr_token_type == tt_immediate && pr_immediate_type->type == ev_float) { iv = fv = pr_immediate._float; QCC_PR_Lex(); } else if (pr_token_type == tt_immediate && pr_immediate_type->type == ev_integer) { fv = iv = pr_immediate._int; QCC_PR_Lex(); } else*/ { const QCC_eval_t *eval; sref = QCC_PR_Expression(TOP_PRIORITY, EXPR_DISALLOW_COMMA); sref = QCC_SupplyConversion(sref, basetype->type, true); eval = QCC_SRef_EvalConst(sref); if (eval) { if (sref.cast->type == ev_float) next_i = next_f = eval->_float; else if (sref.cast->type == ev_double) next_i = next_f = eval->_double; else if (sref.cast->type == ev_integer) next_f = (int)(next_i = eval->_int); else if (sref.cast->type == ev_uint) next_f = next_i = eval->_uint; else if (sref.cast->type == ev_int64) next_f = (longlong)(next_i = eval->i64); else if (sref.cast->type == ev_uint64) next_f = next_i = eval->u64; } else if (sref.sym) QCC_PR_ParseError(ERR_NOTANUMBER, "enum - %s is not a compile-time constant", sref.sym->name); else QCC_PR_ParseError(ERR_NOTANUMBER, "enum - not a number"); //do this, because we can. with any luck we'll just hit the same const anyway, and if not then we may have managed to avoid hitting a global. if (basetype->type==ev_integer) { QCC_FreeTemp(sref); sref = QCC_MakeIntConst(next_i); } else if (basetype->type==ev_uint) { QCC_FreeTemp(sref); sref = QCC_MakeUIntConst(next_i); } else if (basetype->type==ev_int64) { QCC_FreeTemp(sref); sref = QCC_MakeInt64Const(next_i); } else if (basetype->type==ev_uint64) { QCC_FreeTemp(sref); sref = QCC_MakeUInt64Const(next_i); } else if (basetype->type==ev_float) { QCC_FreeTemp(sref); sref = QCC_MakeFloatConst(next_f); } else if (basetype->type==ev_double) { QCC_FreeTemp(sref); sref = QCC_MakeDoubleConst(next_f); } } } else { if (basetype->type==ev_integer) sref = QCC_MakeIntConst(next_i); else if (basetype->type==ev_uint) sref = QCC_MakeUIntConst(next_i); else if (basetype->type==ev_int64) sref = QCC_MakeInt64Const(next_i); else if (basetype->type==ev_uint64) sref = QCC_MakeUInt64Const(next_i); else if (basetype->type==ev_float) sref = QCC_MakeFloatConst(next_f); else if (basetype->type==ev_double) sref = QCC_MakeDoubleConst(next_f); else QCC_PR_ParseError(ERR_NOTANUMBER, "values for enums of this type must be initialised"); } if (flags) { int bits = 0; puint64_t i; if (basetype->type == ev_float) i = (longlong)next_f; else if (basetype->type == ev_double) i = (longlong)next_f; else if (basetype->type == ev_integer) i = (int)next_i; else if (basetype->type == ev_uint) i = (unsigned int)next_i; else if (basetype->type == ev_int64) i = (longlong)next_i; else if (basetype->type == ev_uint64) i = (unsigned longlong)next_i; else QCC_PR_ParseError(ERR_NOTATYPE, "enumflags - must be numeric type"); if (basetype->type!=ev_integer && (double)i != next_f) QCC_PR_ParseWarning(WARN_ENUMFLAGS_NOTINTEGER, "enumflags - %f not an integer value", next_f); else { while(i) { if (((i>>1u)<<1u) != i) bits++; i>>=1u; } if (bits > 1) //be mute about 0. QCC_PR_ParseWarning(WARN_ENUMFLAGS_NOTINTEGER, "enumflags - %#"pPRIx64"(%"pPRIi64") has multiple bits set", next_i, next_i); } } sref.sym->referenced = true; //value gets added to global pool too (but referable whenever not strict) //we just generate an entirely new def (within the parent's pr_globals allocation). this also gives 'symbol was defined HERE' info. sref.sym = QCC_PR_DummyDef(sref.cast, name, pr_scope, 0, sref.sym, sref.ofs, !strictenum, GDF_CONST|GDF_STRIP); sref.sym->initialized = true; //must be true for it to have been considered a compile-time constant. sref.ofs = 0; if (enumtype) { //generate enumname::valname symbol info for (acc = enumtype->accessors; acc; acc = acc->next) if (!strcmp(acc->fieldname, name)) { const QCC_eval_t *old, *new; old = QCC_SRef_EvalConst(acc->staticval); new = QCC_SRef_EvalConst(sref); if (old && old == new && !typecmp(acc->staticval.cast, sref.cast)) break; QCC_PR_ParseError(ERR_TOOMANYINITIALISERS, "%s::%s already declared", enumtype->name, name); break; } if (!acc) { acc = qccHunkAlloc(sizeof(*acc)); acc->fieldname = (char*)name; acc->next = enumtype->accessors; acc->type = enumtype;//sref.cast; acc->indexertype = NULL; enumtype->accessors = acc; acc->staticval = sref; acc->staticval.cast = enumtype; } } QCC_FreeTemp(sref); if (flags) { next_f *= 2; next_i <<= 1; if (!next_i) next_f = next_i = 1; //so you can start with an explicit =0 without needing an =1. } else { next_f++; next_i++; } if (QCC_PR_CheckToken("}")) break; QCC_PR_Expect(","); if (QCC_PR_CheckToken("}")) break; // accept trailing comma } return enumtype?enumtype:basetype; } QCC_sref_t QCC_PR_ParseDefArray(QCC_type_t **type, char *name, pbool istypedef, pbool fixedsize) { QCC_sref_t exr; QCC_sref_t dynlength = nullsref; const QCC_eval_t *eval; size_t dim[16]; int dims = 0; do { dim[dims] = 0; //not known yet... if (dims== 0 && QCC_PR_CheckToken("]")) { //not specified, but that may be okay... perhaps. } else { exr = QCC_PR_Expression(TOP_PRIORITY, 0); eval = QCC_SRef_EvalConst(exr); if (pr_scope && dims==0 && QCC_OPCodeValid(&pr_opcodes[OP_PUSH]) && !flag_qcfuncs && !fixedsize) dynlength = exr; else if (eval) { dim[dims] = QCC_Eval_Int(eval, exr.cast); QCC_FreeTemp(exr); } else { QCC_PR_ParseWarning (ERR_BADARRAYSIZE, "Array length is not a constant"); QCC_FreeTemp(exr); } QCC_PR_Expect("]"); } dims++; } while(QCC_PR_CheckToken ("[")); if (dynlength.cast && QCC_PR_PeekToken("=")) { //if its initialised then we need to statically init it. annoying. eval = QCC_SRef_EvalConst(dynlength); if (eval) { dim[0] = QCC_Eval_Int(eval, dynlength.cast); QCC_FreeTemp(dynlength); } else QCC_PR_ParseWarning (ERR_BADARRAYSIZE, "Array length is not a constant"); dynlength = nullsref; } #if 1 if (dim[0] == 0 && !istypedef && !dynlength.cast) { char *oldprfile = pr_file_p; int oldline = pr_token_line_last; int oldsline = pr_source_line; int depth; //FIXME: preprocessor will hate this with a passion. if (QCC_PR_CheckToken("=")) { if (pr_token_type == tt_immediate && pr_immediate_type == type_string) dim[0] = pr_immediate_strlen+1; else { QCC_PR_Expect("{"); dim[0]++; depth = 1; while(1) { if(pr_token_type == tt_eof) { QCC_PR_ParseError (ERR_EOF, "EOF inside definition of %s", name); break; } else if (depth == 1 && QCC_PR_CheckToken(",")) { if (QCC_PR_CheckToken("}")) break; dim[0]++; } else if (QCC_PR_CheckToken("{") || QCC_PR_CheckToken("[")) depth++; else if (QCC_PR_CheckToken("}") || QCC_PR_CheckToken("]")) { depth--; if (depth == 0) break; } else QCC_PR_Lex(); } } pr_file_p = oldprfile; pr_token_line = oldline; pr_source_line = oldsline; pr_token_type = tt_punct; pr_immediate_type = type_void; strcpy(pr_token, "="); } } #endif while (dims-- > 1) { //go backwards. last parsed is the innermost array. *type = QCC_GenArrayType(*type, dim[dims]); } if (!dynlength.cast) dynlength.ofs = dim[0]; return dynlength; } /* ================ PR_ParseDefs Called at the outer layer and when a local statement is hit ================ */ void QCC_PR_ParseDefs (const char *classname, pbool fatal_unused) { char *name; QCC_type_t *basetype, *type, *defclass; QCC_def_t *def, *d; QCC_sref_t dynlength; QCC_function_t *f; pbool shared=false; pbool isstatic=defaultstatic; pbool externfnc=false; pbool isauto=false; pbool istypedef=false; pbool isconstant = false; pbool isvar = false; pbool isinitialised = false; pbool noref = defaultnoref; pbool nosave = defaultnosave; pbool allocatenew = true; pbool inlinefunction = false; pbool allowinline = false; pbool dostrip = false; pbool dowrap = false; pbool doweak = false; pbool forceused = false; pbool accumulate = false; pbool hadmodifier = false; //if true then its a def. and we assume int when the tye was omitted. const char *deprecated = NULL; int arraysize; unsigned int gd_flags; const char *aliasof = NULL; pr_assumetermtype = NULL; pr_ignoredeprecation = false; while (QCC_PR_CheckToken(";")) ; if (flag_acc) { char *oldp; if (QCC_PR_CheckKeyword (keyword_codesys, "CodeSys")) //reacc support. { if (ForcedCRC) QCC_PR_ParseError(ERR_BADEXTENSION, "progs crc was already specified - only one is allowed"); ForcedCRC = (int)pr_immediate._float; QCC_PR_Lex(); QCC_PR_Expect(";"); return; } oldp = pr_file_p; if (QCC_PR_CheckKeyword (keyword_var, "var")) //reacc support. { if (accglobalsblock == 3) { if (!QCC_PR_GetDef(type_void, "end_sys_fields", NULL, false, 0, false)) QCC_PR_GetDef(type_void, "end_sys_fields", NULL, true, 0, false); } QCC_PR_ParseName(); if (QCC_PR_CheckToken(":")) accglobalsblock = 1; pr_file_p = oldp; QCC_PR_Lex(); } if (QCC_PR_CheckKeyword (keyword_function, "function")) //reacc support. { accglobalsblock = 2; } if (QCC_PR_CheckKeyword (keyword_objdata, "objdata")) //reacc support. { if (accglobalsblock == 3) { if (!QCC_PR_GetDef(type_void, "end_sys_fields", NULL, false, 0, false)) QCC_PR_GetDef(type_void, "end_sys_fields", NULL, true, 0, false); } else if (!QCC_PR_GetDef(type_void, "end_sys_globals", NULL, false, 0, false)) QCC_PR_GetDef(type_void, "end_sys_globals", NULL, true, 0, false); accglobalsblock = 3; } if (!pr_scope) switch(accglobalsblock)//reacc support. { case 1: { char *oldp = pr_file_p; name = QCC_PR_ParseName(); if (!QCC_PR_CheckToken(":")) //nope, it wasn't! { QCC_PR_IncludeChunk(name, true, NULL); QCC_PR_Lex(); QCC_PR_UnInclude(); pr_file_p = oldp; break; } if (QCC_PR_CheckKeyword(keyword_object, "object")) QCC_PR_GetDef(type_entity, name, NULL, true, 0, true); else if (QCC_PR_CheckKeyword(keyword_string, "string")) QCC_PR_GetDef(type_string, name, NULL, true, 0, true); else if (QCC_PR_CheckKeyword(keyword_real, "real")) { def = QCC_PR_GetDef(type_float, name, NULL, true, 0, true); if (QCC_PR_CheckToken("=")) { def->symboldata[0]._float = pr_immediate._float; QCC_PR_Lex(); } } else if (QCC_PR_CheckKeyword(keyword_vector, "vector")) { def = QCC_PR_GetDef(type_vector, name, NULL, true, 0, true); if (QCC_PR_CheckToken("=")) { QCC_PR_Expect("["); def->symboldata[0].vector[0] = pr_immediate._float; QCC_PR_Lex(); def->symboldata[0].vector[1] = pr_immediate._float; QCC_PR_Lex(); def->symboldata[0].vector[2] = pr_immediate._float; QCC_PR_Lex(); QCC_PR_Expect("]"); } } else if (QCC_PR_CheckKeyword(keyword_pfunc, "pfunc")) QCC_PR_GetDef(type_function, name, NULL, true, 0, true); else QCC_PR_ParseError(ERR_BADNOTTYPE, "Bad type\n"); QCC_PR_Expect (";"); if (QCC_PR_CheckKeyword (keyword_system, "system")) QCC_PR_Expect (";"); return; } case 2: name = QCC_PR_ParseName(); QCC_PR_GetDef(type_function, name, NULL, true, 0, true); QCC_PR_CheckToken (";"); return; case 3: { char *oldp = pr_file_p; name = QCC_PR_ParseName(); if (!QCC_PR_CheckToken(":")) //nope, it wasn't! { QCC_PR_IncludeChunk(name, true, NULL); QCC_PR_Lex(); QCC_PR_UnInclude(); pr_file_p = oldp; break; } if (QCC_PR_CheckKeyword(keyword_object, "object")) def = QCC_PR_GetDef(QCC_PR_FieldType(type_entity), name, NULL, true, 0, GDF_CONST|GDF_SAVED); else if (QCC_PR_CheckKeyword(keyword_string, "string")) def = QCC_PR_GetDef(QCC_PR_FieldType(type_string), name, NULL, true, 0, GDF_CONST|GDF_SAVED); else if (QCC_PR_CheckKeyword(keyword_real, "real")) def = QCC_PR_GetDef(QCC_PR_FieldType(type_float), name, NULL, true, 0, GDF_CONST|GDF_SAVED); else if (QCC_PR_CheckKeyword(keyword_vector, "vector")) def = QCC_PR_GetDef(QCC_PR_FieldType(type_vector), name, NULL, true, 0, GDF_CONST|GDF_SAVED); else if (QCC_PR_CheckKeyword(keyword_pfunc, "pfunc")) def = QCC_PR_GetDef(QCC_PR_FieldType(type_function), name, NULL, true, 0, GDF_CONST|GDF_SAVED); else { QCC_PR_ParseError(ERR_BADNOTTYPE, "Bad type\n"); QCC_PR_Expect (";"); return; } if (!def->initialized) { unsigned int u; def->initialized = 1; for (u = 0; u < def->type->size*(def->arraysize?def->arraysize:1); u++) //make arrays of fields work. { if (*(int *)&def->symboldata[u]) { QCC_PR_ParseWarning(0, "Field def already has a value:"); QCC_PR_ParsePrintDef(0, def); } *(int *)&def->symboldata[u] = pr.size_fields+u; } pr.size_fields += u; } QCC_PR_Expect (";"); return; } } } while(1) { //storage classes... if (QCC_PR_CheckKeyword (keyword_typedef, "typedef")) //not storage but defines a type instead. istypedef=true; else if (QCC_PR_CheckKeyword(keyword_extern, "extern")) //came from some other unit (or at least outside of the current function) { if (!hadmodifier && pr_token_type == tt_immediate && pr_immediate_type==type_string) { //this C++ism is for parsing types, we don't currently change operator precedence, keywords, etc pbool old_qcfuncs = flag_qcfuncs; pbool old_cpriority = flag_cpriority; pbool old_assumeint = flag_assume_integer; pbool old_assumef64 = flag_assume_double; pbool old_assumevar = flag_assumevar; pbool old_subscope = pr_subscopedlocals; if (!strcasecmp(pr_token, "C")) { flag_qcfuncs = false; //ignore * on funcptrs, promote varg floats to doubles, some other quirks flag_cpriority = true; flag_assume_integer = true; //flag_assume_double = true; //not forced, too annoying, but is popped so you can explicitly enable it within the block for some reason. flag_assumevar = true; pr_subscopedlocals = true; } else if (!strcasecmp(pr_token, "QC")) { flag_qcfuncs = true; //no promotion, func references are simply references, etc //flag_cpriority = false; //refrain from changing it //flag_assume_integer = false; //flag_assume_double = false; //flag_assumevar = false; //pr_subscopedlocals = false; } QCC_PR_Lex(); if (!QCC_PR_CheckToken ("{")) QCC_PR_ParseDefs(NULL, false); else for(;;) { if (QCC_PR_CheckToken("}")) break; if (pr_token_type == tt_eof) { QCC_PR_Expect ("}"); break; } QCC_PR_ParseDefs(NULL, false); } //restore stuff to original mode. //FIXME: reset all flags+keywords? flag_qcfuncs = old_qcfuncs; flag_cpriority = old_cpriority; flag_assume_integer = old_assumeint; flag_assume_double = old_assumef64; flag_assumevar = old_assumevar; pr_subscopedlocals = old_subscope; return; } externfnc=true; } else if (QCC_PR_CheckKeyword(keyword_auto, "auto")) //allocated on the stack like C's locals. may automatically/temporarily be promoted to register... default for C code. isauto=true; else if (QCC_PR_CheckKeyword(keyword_register, "register")) //allow a leading register keyword. technically EVERYTHING is a register in qc, so we can just ignore this. isauto=false; else if (QCC_PR_CheckKeyword(keyword_static, "static")) //comes from a global. isstatic = true; //else if (QCC_PR_CheckKeyword(keyword_static, "register")) //bans address-of FIXME: this is checked as part of the type as it needs to be valid for function args too. // isregister = true; //else if (QCC_PR_CheckKeyword(keyword_static, "local")) //QC weirdness. like register, but you can still take an address of it etc... // islocal = true; else if (QCC_PR_CheckKeyword(keyword_shared, "shared")) { shared=true; if (pr_scope) QCC_PR_ParseError (ERR_NOSHAREDLOCALS, "Cannot have shared locals"); } else if (QCC_PR_CheckKeyword(keyword_const, "const")) isconstant = true; else if (QCC_PR_CheckKeyword(keyword_var, "var")) isvar = true; else if (!pr_scope && QCC_PR_CheckKeyword(keyword_nonstatic, "nonstatic")) isstatic = false; else if (QCC_PR_CheckKeyword(keyword_unused, "unused") || QCC_PR_CheckKeyword(keyword_noref, "noref")) noref=true; else if (QCC_PR_CheckKeyword(keyword_used, "used")) forceused=true; else if (QCC_PR_CheckKeyword(keyword_nosave, "nosave")) nosave = true; else if (QCC_PR_CheckKeyword(keyword_strip, "strip") || QCC_PR_CheckKeyword(keyword_ignore, "ignore")) dostrip = true; else if (QCC_PR_CheckKeyword(keyword_inline, "inline")) allowinline = true; else if (QCC_PR_CheckKeyword(keyword_wrap, "wrap")) dowrap = true; else if (QCC_PR_CheckKeyword(keyword_weak, "weak")) doweak = true; else if (QCC_PR_CheckKeyword(keyword_accumulate, "accumulate")) accumulate = true; else if (QCC_PR_CheckKeyword(false, "deprecated")) { if (!QCC_PR_CheckToken("(")) deprecated = ""; else { if (pr_token_type == tt_immediate && pr_immediate_type == type_string) { if (deprecated) { char *n = qccHunkAlloc(strlen(deprecated)+2+strlen(pr_immediate_string)+1); sprintf(n, "%s; %s", deprecated, pr_immediate_string); deprecated = n; } else deprecated = strcpy(qccHunkAlloc(strlen(pr_immediate_string)+1), pr_immediate_string); QCC_PR_Lex(); } else deprecated = ""; QCC_PR_Expect(")"); } } else if ((hadmodifier||(flag_attributes && !pr_scope)) && QCC_PR_CheckToken("[")) { QCC_PR_Expect("["); while(pr_token_type != tt_eof) { if (QCC_PR_CheckToken(",")) continue; else if (QCC_PR_CheckToken("]")) break; else if (QCC_PR_CheckName("alias")) { QCC_PR_Expect("("); if (pr_token_type == tt_name) aliasof = QCC_PR_ParseName(); else if (pr_token_type == tt_immediate) { aliasof = strcpy(qccHunkAlloc(strlen(pr_immediate_string)+1), pr_immediate_string); QCC_PR_Lex(); } QCC_PR_Expect(")"); } else if (QCC_PR_CheckName("accumulate")) { if (accumulate) QCC_PR_ParseWarning(WARN_GMQCC_SPECIFIC, "accumulating an accumulating accumulation"); accumulate = true; } else if (QCC_PR_CheckName("eraseable")) noref = true; else if (QCC_PR_CheckKeyword(false, "deprecated")) { if (!QCC_PR_CheckToken("(")) deprecated = ""; else { if (pr_token_type == tt_immediate && pr_immediate_type == type_string) { deprecated = strcpy(qccHunkAlloc(strlen(pr_immediate_string)+1), pr_immediate_string); QCC_PR_Lex(); } else QCC_PR_ParseError (ERR_EXPECTED, "expected string"); QCC_PR_Expect(")"); } } else { QCC_PR_ParseWarning(WARN_GMQCC_SPECIFIC, "Unknown attribute \"%s\"", pr_token); while(pr_token_type != tt_eof) { if (QCC_PR_PeekToken("]")) break; if (QCC_PR_PeekToken(",")) break; QCC_PR_Lex(); } } } QCC_PR_Expect("]"); } else break; hadmodifier = true; } basetype = QCC_PR_ParseType (false, hadmodifier, !flag_qcfuncs); if (basetype == NULL) //ignore { if (hadmodifier) { //banned since c99... //if (c23 && isauto) basetype=guess; else basetype = type_integer; } else return; } inlinefunction = type_inlinefunction; if (externfnc && !pr_scope && basetype->type != ev_function) { if (flag_qcfuncs) QCC_PR_ParseWarning(WARN_IGNOREDKEYWORD, "extern keyword may only apply to qc functions."); externfnc=false; } if (!pr_scope && QCC_PR_CheckKeyword(keyword_function, "function")) //reacc support. { name = QCC_PR_ParseName (); QCC_PR_Expect("("); type = QCC_PR_ParseFunctionTypeReacc(false, basetype); QCC_PR_Expect(";"); if (istypedef) return; else def = QCC_PR_GetDef (basetype, name, NULL, true, 0, false); if (autoprototype || dostrip) { //ignore the code and stuff if (QCC_PR_CheckKeyword(keyword_external, "external")) { //builtin QCC_PR_Lex(); QCC_PR_Expect(";"); } else { int blev = 1; while (!QCC_PR_CheckToken("{")) //skip over the locals. { if (pr_token_type == tt_eof) { QCC_PR_ParseError(0, "Unexpected EOF"); break; } QCC_PR_Lex(); } //balance out the { and } while(blev) { if (pr_token_type == tt_eof) break; if (QCC_PR_CheckToken("{")) blev++; else if (QCC_PR_CheckToken("}")) blev--; else QCC_PR_Lex(); //ignore it. } } return; } else { def->referenced = true; f = QCC_PR_ParseImmediateStatements (def, basetype, false); def->initialized = 1; def->isstatic = isstatic; def->symboldata[0].function = numfunctions; f->def = def; // if (pr_dumpasm) // PR_PrintFunction (def); if (numfunctions >= MAX_FUNCTIONS) QCC_Error(ERR_INTERNAL, "Too many function defs"); } return; } // if (pr_scope && (type->type == ev_field) ) // QCC_PR_ParseError ("Fields must be global"); do { isinitialised = false; type = basetype; if (QCC_PR_CheckToken (";")) { if (istypedef) { QCC_PR_ParseWarning(WARN_UNEXPECTEDPUNCT, "typedef defines no types"); return; } if (type->type == ev_field && (type->aux_type->type == ev_union || type->aux_type->type == ev_struct)) { QCC_PR_ExpandUnionToFields(type, &pr.size_fields); return; } if (type->type == ev_struct && strcmp(type->name, "struct")) return; //allow named structs if (type->type == ev_entity && type != type_entity) return; //allow forward class definititions with or without a variable. if (type->type == ev_accessor) //accessors shouldn't trigger problems if they're just a type. return; // if (type->type == ev_union) // { // return; // } // QCC_PR_ParseError (WARN_TYPEWITHNONAME, "type (%s) with no name", type->name); return; } if (!istypedef && !classname && type->typedefed && QCC_PR_CheckToken("::")) { classname = type->name; //FIXME: doesn't work with commas... type = type_invalid; } else while (QCC_PR_CheckToken ("*")) type = QCC_PointerTypeTo(type); arraysize = 0; name = NULL; while(QCC_PR_CheckToken ("(")) { QCC_type_t *ftype; int isptr = 0; ftype = QCC_PR_ParseFunctionType(false, type); if (ftype) { //qc-style function... possibly returning a function. type = ftype; } else { //c-style function pointer while (QCC_PR_CheckToken("*")) isptr++; name = QCC_PR_ParseName (); if (QCC_PR_CheckToken ("[")) { if (QCC_PR_CheckToken ("]")) arraysize = -1; else { arraysize = QCC_PR_IntConstExpr(); QCC_PR_Expect ("]"); } } QCC_PR_Expect (")"); if (!istypedef) isvar |= isptr>0; if (QCC_PR_CheckToken ("(")) { type = QCC_PR_ParseFunctionType(false, type); if (!type) QCC_PR_ParseError(ERR_BADNOTTYPE, "expected function arg list"); } while(isptr-- > 1) //C's function pointers are basically the same as a qc function reference, though the definition is a bit different. type = QCC_PointerTypeTo(type); break; } } if (!name) name = QCC_PR_ParseName (); if (!istypedef && !classname && QCC_PR_CheckToken("::")) { classname = name; //FIXME: doesn't work with commas... name = QCC_PR_ParseName(); } if (type == type_invalid) { type = type_void; if (!strcmp(classname, name)) ; //constructor. allowed. else QCC_PR_ParseWarning(ERR_NOTATYPE, "no type specified for %s::%s. this is only allowed for constructors", classname, name); } //check for an array dynlength = nullsref; if (!arraysize && QCC_PR_CheckToken ("[")) { dynlength = QCC_PR_ParseDefArray(&type, name, istypedef, istypedef||isstatic); if (dynlength.cast) arraysize = 0; else { arraysize = dynlength.ofs; if (!arraysize) arraysize = -1; } } if (QCC_PR_CheckToken("(")) { if (inlinefunction) QCC_PR_ParseWarning(WARN_UNSAFEFUNCTIONRETURNTYPE, "Function returning function. Is this what you meant? (suggestion: use typedefs)"); inlinefunction = false; if (!flag_qcfuncs && pr_scope) isconstant = externfnc = true; //in C, a locally defined function refers to an external one. type = QCC_PR_ParseFunctionType(false, type); if (!type) QCC_PR_ParseError(ERR_BADNOTTYPE, "expected function arg list"); } allocatenew = true; if (classname) { unsigned int ofs, bitofs; struct QCC_typeparam_s *p; char *membername = name; name = qccHunkAlloc(strlen(classname) + strlen(name) + 3); sprintf(name, "%s::%s", classname, membername); defclass = QCC_TypeForName(classname); allocatenew = (dynlength.cast || aliasof)?false:2; if (defclass && defclass->type == ev_struct) allocatenew = false; else if (!defclass || !defclass->parentclass) QCC_PR_ParseError(ERR_NOTANAME, "%s is not a class\n", classname); if (defclass->type == ev_struct) { p = QCC_PR_FindStructMember(defclass, membername, &ofs, &bitofs); if (p && p->isvirtual) type = QCC_PR_MakeThiscall(type, defclass); } } else defclass = NULL; if (istypedef+externfnc+isauto+isstatic+shared+(aliasof!=NULL)>1) QCC_PR_ParseWarning(WARN_IGNOREDKEYWORD, "Conflicting storage classes."); if (isconstant+isvar>1) QCC_PR_ParseWarning(WARN_IGNOREDKEYWORD, "Conflicting constness."); if ((allowinline || dowrap || doweak || accumulate) && type->type != ev_function) QCC_PR_ParseWarning(WARN_IGNOREDKEYWORD, "function modifier on non-function."); if (istypedef) { QCC_type_t *old; if (externfnc||shared||isconstant||isvar||forceused||dostrip||allowinline||dowrap||doweak||accumulate||aliasof||deprecated ||(isstatic && !defaultstatic) ||(noref && !defaultnoref) ||(nosave && !defaultnosave) ) QCC_PR_ParseWarning(ERR_BADEXTENSION, "bad combination of modifiers with typedef (defining %s%s%s)", col_type,name,col_none); if (arraysize) { struct QCC_typeparam_s *param = qccHunkAlloc(sizeof(*param)); // QCC_PR_ParseWarning(ERR_BADEXTENSION, "unsupported typedefed array (defining %s%s%s[%i])", col_type,name,col_none, arraysize); param->type = type; param->arraysize = arraysize; param->paramname = NULL; type = QCC_PR_NewType(name, ev_union, true); type->params = param; type->num_parms = 1; type->size = param->type->size * param->arraysize; } else if (dynlength.cast) { QCC_PR_ParseWarning(ERR_BADEXTENSION, "unsupported typedefed array (defining %s%s%s[])", col_type,name,col_none); type = QCC_PointerTypeTo(type); } old = QCC_TypeForName(name); if (old && old->scope == pr_scope) { if (typecmp(old, type)) { char obuf[1024]; char nbuf[1024]; QCC_PR_ParseWarning(ERR_NOTATYPE, "Cannot redeclare typedef %s%s%s from %s%s%s to %s%s%s", col_type,name,col_none, col_type,TypeName(old, obuf, sizeof(obuf)),col_none, col_type,TypeName(type, nbuf, sizeof(nbuf)),col_none); } } else { old = type; type = QCC_PR_NewType(name, ev_typedef, true); type->aux_type = old; type->scope = pr_scope; } def = NULL; continue; } isinitialised = QCC_PR_CheckToken ("=") || ((type->type == ev_function) && (pr_token[0] == '{' || pr_token[0] == '[' || pr_token[0] == ':')); gd_flags = 0; if (isstatic) gd_flags |= GDF_STATIC; if (isconstant || (!isvar && !pr_scope && ((isinitialised && !flag_assumevar) || type->type == ev_function || type->type == ev_field))) gd_flags |= GDF_CONST; //initialised things are assumed to be consts, unless assumevar is specified. functions and fields are always assumed to be consts even when not initialised. nothing is assumed const at local scope. if (!nosave) gd_flags |= GDF_SAVED; if (allowinline) gd_flags |= GDF_INLINE; if (dostrip) gd_flags |= GDF_STRIP; else if (forceused) //FIXME: make proper pragma(used) thingie gd_flags |= GDF_USED; if (!type->size) { // char buf[1024]; // QCC_PR_ParseError(ERR_BADEXTENSION, "type %s%s%s not yet defined, cannot create %s%s%s", col_type,TypeName(type,buf,sizeof(buf)),col_none, col_name,name,col_none); } if (dynlength.cast && !aliasof) { const QCC_eval_t *eval; size_t fixedlen; dynlength = QCC_SupplyConversion(dynlength, ev_uint, true); eval = QCC_SRef_EvalConst(dynlength); if (eval) { gd_flags |= GDF_AUTODEREF; fixedlen = QCC_Eval_Int(eval, dynlength.cast); def = QCC_PR_GetDef (QCC_PR_PointerType(QCC_GenArrayType(type, fixedlen)), name, pr_scope, allocatenew, 0, gd_flags); } else def = QCC_PR_GetDef (QCC_PR_PointerType(type), name, pr_scope, allocatenew, 0, gd_flags); if (type->bits) //OP_PUSH takes words. we need to round up. { dynlength = QCC_PR_Statement(pr_opcodes+OP_MUL_I, dynlength, QCC_MakeUIntConst(type->bits/8), NULL); //convert to bytes dynlength = QCC_PR_Statement(pr_opcodes+OP_ADD_I, dynlength, QCC_MakeUIntConst(3), NULL); //extend by the worst case dynlength = QCC_PR_Statement(pr_opcodes+OP_DIV_I, dynlength, QCC_MakeUIntConst(4), NULL); //convert to words } else if (type->size != 1) dynlength = QCC_PR_Statement(pr_opcodes+OP_MUL_I, dynlength, QCC_MakeIntConst(type->size), NULL); QCC_PR_SimpleStatement(&pr_opcodes[OP_PUSH], dynlength, nullsref, QCC_MakeSRef(def, 0, def->type), false); //push *(int*)&a elements QCC_FreeTemp(dynlength); QCC_FreeDef(def); } else if (isauto) { gd_flags |= GDF_AUTODEREF; if (arraysize) type = QCC_GenArrayType(type, arraysize); def = QCC_PR_GetDef (QCC_PR_PointerType(type), name, pr_scope, allocatenew, 0, gd_flags); if (pr_scope && !isstatic) QCC_PR_SimpleStatement(&pr_opcodes[OP_PUSH], QCC_MakeIntConst(type->size), nullsref, QCC_MakeSRef(def, 0, def->type), false); //push *(int*)&a elements else { QCC_sref_t f = QCC_PR_EmulationFunc(memalloc); if (!f.cast) f = QCC_PR_EmulationFunc(malloc); if (!f.cast) QCC_PR_ParseError(ERR_BADEXTENSION, "memalloc not yet defined, cannot define __auto %s at global scope", name); if (QCC_OPCodeValid(&pr_opcodes[OP_CALL1H])) QCC_PR_SimpleInitStatement(&pr_opcodes[OP_CALL1H], f, QCC_MakeIntConst(type->size*4), nullsref); else { QCC_PR_SimpleInitStatement(&pr_opcodes[OP_STORE_F], QCC_MakeIntConst(type->size*4), QCC_MakeSRef(&def_parms[0], 0, type_integer), nullsref); QCC_PR_SimpleInitStatement(&pr_opcodes[OP_CALL1], f, nullsref, nullsref); } QCC_PR_SimpleInitStatement(&pr_opcodes[QCC_OPCodeValid(&pr_opcodes[OP_STORE_P])?OP_STORE_P:OP_STORE_I], QCC_MakeSRef(&def_ret, 0, def->type), QCC_MakeSRef(def, 0, def->type), nullsref); } } else { if (aliasof) { if (dynlength.cast) QCC_PR_ParseWarning(ERR_BADEXTENSION, "array aliases are not supported"); def = QCC_PR_GetDef (NULL, aliasof, externfnc?NULL:pr_scope, false, arraysize, gd_flags); if (!def) QCC_PR_ParseError(ERR_BADEXTENSION, "%s not yet defined, cannot create %s as an alias", aliasof, name); def->referenced = true; def = QCC_PR_DummyDef(type, name, externfnc?NULL:pr_scope, arraysize, def, 0, true, gd_flags); } else if (allocatenew != 1) { //we always allocate here, because it lets us handle syntax errors a little more gracefully, but an error is still an error and will be fatal later. def = QCC_PR_GetDef (type, name, externfnc?NULL:pr_scope, false, arraysize, gd_flags); if (!def) { QCC_PR_ParseWarning(allocatenew?WARN_MEMBERNOTDEFINED:ERR_NOTDEFINED, "%s is not part of class %s", name, classname); def = QCC_PR_GetDef (type, name, externfnc?NULL:pr_scope, true, arraysize, gd_flags); } } else def = QCC_PR_GetDef (type, name, externfnc?NULL:pr_scope, allocatenew, arraysize, gd_flags); } if (!def) QCC_PR_ParseError(ERR_NOTANAME, "%s is not part of class %s", name, classname); if (accumulate && (def->type->type != ev_function || def->arraysize != 0)) QCC_PR_ParseError(ERR_NOTAFUNCTION, "accumulate applies only to functions, not %s", def->name); if (noref) { def->unused = true; def->referenced = true; } if (!def->initialized && shared) //shared count as initiialised { def->shared = shared; def->initialized = true; def->nofold = true; } if (externfnc && !pr_scope) { def->initialized = true; def->isextern = true; } if (deprecated) def->deprecated = deprecated; if (isstatic) { if (!strcmp(def->filen, s_filen)) def->isstatic = isstatic; else //if (type->type != ev_function && defaultstatic) //functions don't quite consitiute a definition QCC_PR_ParseErrorPrintDef (ERR_REDECLARATION, def, "can't redefine non-static as static"); } // check for an initialization /*if (type->type == ev_function && (pr_scope)) { if ( QCC_PR_CheckToken ("=") ) { QCC_PR_ParseError (ERR_INITIALISEDLOCALFUNCTION, "local functions may not be initialised"); } d = def; while (d != def->deftail) { d = d->next; d->initialized = 1; //fake function d->symboldata[0].function = 0; } continue; }*/ if (type->type == ev_field && QCC_PR_CheckName ("alias")) { QCC_PR_ParseError(ERR_INTERNAL, "FTEQCC does not support this variant of decompiled hexenc\nPlease obtain the original version released by Raven Software instead."); name = QCC_PR_ParseName(); } else if (isinitialised) //this is an initialisation (or a function) { pbool isconst; QCC_type_t *parentclass; if (aliasof) QCC_PR_ParseError (ERR_SHAREDINITIALISED, "alias %s may not be initialised", name); if (def->shared) QCC_PR_ParseError (ERR_SHAREDINITIALISED, "shared values may not be assigned an initial value"); //if weak, only use the first non-weak version of the function if (autoprototype || dostrip || (def->initialized && doweak) || (!def->initialized && doweak && dowrap)) { //ignore the code and stuff if ((dostrip || (doweak && dowrap))) def->unused = true; if (dostrip) def->referenced = true; if (QCC_PR_CheckToken("[")) { while (!QCC_PR_CheckToken("]")) { if (pr_token_type == tt_eof) break; QCC_PR_Lex(); } } if (QCC_PR_CheckToken("{")) { int blev = 1; //balance out the { and } while(blev) { if (pr_token_type == tt_eof) break; if (QCC_PR_CheckToken("{")) blev++; else if (QCC_PR_CheckToken("}")) blev--; else QCC_PR_Lex(); //ignore it. } } else { if (type->type == ev_string && QCC_PR_CheckName("_")) { QCC_PR_Expect("("); QCC_PR_Lex(); QCC_PR_Expect(")"); } else { QCC_PR_CheckToken("#"); do { QCC_PR_Lex(); } while (*pr_token && strcmp(pr_token, ",") && strcmp(pr_token, ";")); } } QCC_FreeDef(def); continue; } parentclass = pr_classtype; pr_classtype = defclass?defclass:pr_classtype; if (flag_assumevar) isconst = isconstant || (!isvar && !pr_scope && (type->type == ev_function || type->type == ev_field)); else isconst = (isconstant || (!isvar && !pr_scope)); if (isconst != def->constant) { //we should only be optimising consts if its initialised, so it shouldn't have been read as 0 at any point so far. QCC_PR_ParseWarning(WARN_REDECLARATIONMISMATCH, "Redeclaration of %s would change to %sconst.", def->name, isconst?"":"non-"); QCC_PR_ParsePrintDef(WARN_REDECLARATIONMISMATCH, def); // def->constant = isconst; } if (accumulate || def->accumulate) { unsigned int pif_flags = PIF_ACCUMULATE; if (!def->initialized) def->accumulate |= true; //first time else if (dowrap) pif_flags |= PIF_WRAP; //explicitly wrapping a prior accumulation... else if (!def->accumulate) { QCC_PR_ParseWarning(WARN_GMQCC_SPECIFIC, "%s redeclared to accumulate after initial declaration", def->name); pif_flags |= PIF_WRAP|PIF_AUTOWRAP; //wrap it automatically so its not so obvious } def->accumulate |= true; def->initialized = true; def->symboldata[0].function = QCC_PR_ParseImmediateStatements (def, def->type, pif_flags) - functions; } else QCC_PR_ParseInitializerDef(def, (dowrap?PIF_WRAP:0)|(def->weak?PIF_STRONGER:0)); if (doweak) def->weak = true; else def->weak = false; pr_classtype = parentclass; if (!def->nofold && def->constant && def->initialized && def->symbolheader == def && def->ofs == 0 && !def->arraysize) { QCC_def_t *base = NULL; const QCC_eval_t *val = (const QCC_eval_t *)&def->symboldata[0]; if (type->type == ev_float) base = QCC_MakeFloatConst(val->_float).sym; else if (type->type == ev_integer) base = QCC_MakeIntConst(val->_int).sym; else if (type->type == ev_vector) base = QCC_MakeVectorConst(val->vector[0], val->vector[1], val->vector[2]).sym; if (base && !base->symbolheader->nofold) { def->ofs = base->ofs; def->symbolheader = base->symbolheader; } } } else { if (1)//isconstant || isvar) { int c = isconstant; if (type->type == ev_function) c |= !isvar && !pr_scope; if (type->type == ev_field) { if (c) c = 2; else if (isvar || (pr_scope && !isstatic)) c = 0; else c = 1; } if (def->constant != c) { QCC_PR_ParseWarning(WARN_REDECLARATIONMISMATCH, "Redeclaration of %s changes const.", def->name); QCC_PR_ParsePrintDef(WARN_REDECLARATIONMISMATCH, def); } } if (accumulate) { if (def->initialized) QCC_PR_ParseWarning(WARN_GMQCC_SPECIFIC, "%s redeclared to accumulate after initial declaration", def->name); def->accumulate |= true; } if (dostrip) def->referenced = true; else if (type->type == ev_field) { //fields are const by default, even when not initialised (as they are initialised behind the scenes) if (!def->initialized && def->constant) { unsigned int i; def->initialized = true; //if the field already has a value, don't allocate new field space for it as that would confuse things. //otherwise allocate new space. if (def->symboldata[0]._int) { for (i = 0; i < type->size*(arraysize?arraysize:1); i++) //make arrays of fields work. { if (def->symboldata[i]._int != i + def->symboldata[0]._int) { QCC_PR_ParseWarning(0, "Inconsistant field def:"); QCC_PR_ParsePrintDef(0, def); break; } } } else { for (i = 0; i < type->size*(arraysize?arraysize:1); i++) //make arrays of fields work. { if (def->symboldata[i]._int) { QCC_PR_ParseWarning(0, "Field def already has a value:"); QCC_PR_ParsePrintDef(0, def); } def->symboldata[i]._int = pr.size_fields+i; } pr.size_fields += i; } } } } d = def; QCC_FreeDef(d); while (d != def->deftail) { d = d->next; d->constant = def->constant; d->initialized = def->initialized; } } while (QCC_PR_CheckToken (",")); if (type->type == ev_function) QCC_PR_CheckTokenComment (";", def?&def->comment:NULL); else { if (!QCC_PR_CheckTokenComment (";", def?&def->comment:NULL)) QCC_PR_ParseWarning(WARN_UNDESIRABLECONVENTION, "Missing semicolon at end of definition"); } } /* ============ PR_CompileFile compiles the 0 terminated text, adding defintions to the pr structure ============ */ void QCC_PR_LexWhitespace (pbool inhibitpreprocessor); pbool QCC_PR_CompileFile (char *string, char *filename) { char *tmp; jmp_buf oldjb; if (!pr.memory) QCC_Error (ERR_INTERNAL, "PR_CompileFile: Didn't clear"); QCC_PR_ClearGrabMacros (true); // clear the frame macros compilingfile = filename; s_unitn = s_filen = tmp = qccHunkAlloc(strlen(filename)+1); strcpy(tmp, filename); if (opt_filenames) { optres_filenames += strlen(filename); s_filed = 0; } else s_filed = QCC_CopyString (filename); pr_file_p = string; pr_assumetermtype = NULL; pr_ignoredeprecation = false; pr_source_line = 0; memcpy(&oldjb, &pr_parse_abort, sizeof(oldjb)); if( setjmp( pr_parse_abort ) ) { pr_error_count++; // dont count it as error } else { //clock up the first line QCC_PR_NewLine (false); QCC_PR_Lex (); // read first token } if (preprocessonly) { pbool white = false; static int line = 1;//pr_source_line; static const char *fname = NULL; static QCC_string_t fnamed = 0; while(pr_token_type != tt_eof) { // white = (qcc_iswhite(*pr_file_p) || (*pr_file_p == '/' && (pr_file_p[1] == '/' || pr_file_p[1] == '*'))); // QCC_PR_LexWhitespace (false); if (fnamed != s_filed) { line = pr_token_line; fname = s_filen; fnamed = s_filed; externs->Printf("\n#pragma file(%s)\n",fname); externs->Printf("#pragma line(%i)\n",line); } else { //if there's whitespace next, make sure we represent that while(line < pr_token_line) { //keep line numbers correct by splurging multiple newlines. externs->Printf("\n"); white = false; line++; } if (white) externs->Printf(" "); } if (pr_token_type == tt_immediate && pr_immediate_type == type_string) { const char *s = pr_token; externs->Printf("\""); while (*s) { switch(*s) { case 0: externs->Printf("%c", 0); break; case '\\': externs->Printf("\\\\"); break; case '\"': externs->Printf("\\\""); break; case '\r': externs->Printf("\\r"); break; case '\n': externs->Printf("\\n"); break; case '\t': externs->Printf("\\t"); break; default: externs->Printf("%c", *s); break; } s++; } externs->Printf("\""); } else externs->Printf("%s", pr_token); white = (qcc_iswhite(*pr_file_p) || (*pr_file_p == '/' && (pr_file_p[1] == '/' || pr_file_p[1] == '*'))); QCC_PR_Lex(); } } else while (pr_token_type != tt_eof) { if (setjmp(pr_parse_abort)) { num_continues = 0; num_breaks = 0; num_cases = 0; if (++pr_error_count > MAX_ERRORS) { memcpy(&pr_parse_abort, &oldjb, sizeof(oldjb)); return false; } QCC_PR_SkipToSemicolon (); if (pr_token_type == tt_eof) { memcpy(&pr_parse_abort, &oldjb, sizeof(oldjb)); return false; } } pr_scope = NULL; // outside all functions QCC_PR_ParseDefs (NULL, true); #if 0//def _DEBUG if (!pr_error_count) { QCC_def_t *d; unsigned int i; for (i = 0; i < MAX_PARMS; i++) { d = &def_parms[i]; if (d->refcount) { QCC_sref_t sr; sr.sym = d; sr.cast = d->type; sr.ofs = 0; QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: %i references still held on %s (%s)", d->refcount, d->name, QCC_VarAtOffset(sr)); d->refcount = 0; } } for (d = pr.def_head.next; d; d = d->next) { if (d->refcount) { QCC_sref_t sr; sr.sym = d; sr.cast = d->type; sr.ofs = 0; QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: %i references still held on %s (%s)", d->refcount, d->name, QCC_VarAtOffset(sr)); d->refcount = 0; } } for (i = 0; i < tempsused; i++) { d = tempsinfo[i].def; if (d->refcount) { QCC_sref_t sr; sr.sym = d; sr.cast = d->type; sr.ofs = 0; QCC_PR_ParseWarning(WARN_DEBUGGING, "INTERNAL: %i references still held on %s (%s)", d->refcount, d->name, QCC_VarAtOffset(sr)); d->refcount = 0; } } } #endif } memcpy(&pr_parse_abort, &oldjb, sizeof(oldjb)); return (pr_error_count == 0); } pbool QCC_Include(const char *filename, pbool newunit) { char *newfile; char fname[512]; char *opr_file_p; const char *os_unitn; const char *os_filen; QCC_string_t os_filed; int opr_source_line; char *ocompilingfile; struct qcc_includechunk_s *oldcurrentchunk; ocompilingfile = compilingfile; os_unitn = s_unitn; os_filen = s_filen; os_filed = s_filed; opr_source_line = pr_source_line; opr_file_p = pr_file_p; oldcurrentchunk = currentchunk; strcpy(fname, filename); QCC_LoadFile(fname, (void*)&newfile); if (!newunit) { QCC_PR_IncludeChunkEx(newfile, false, NULL, NULL); s_filen = strcpy(qccHunkAlloc(strlen(filename)+1), filename); return true; } currentchunk = NULL; pr_file_p = newfile; QCC_PR_CompileFile(newfile, fname); currentchunk = oldcurrentchunk; compilingfile = ocompilingfile; s_unitn = os_unitn; s_filen = os_filen; s_filed = os_filed; pr_source_line = opr_source_line; pr_file_p = opr_file_p; if (pr_error_count > MAX_ERRORS) longjmp (pr_parse_abort, 1); // QCC_PR_IncludeChunk(newfile, false, fname); return true; } void QCC_Cleanup(void) { free(pr_breaks); free(pr_continues); free(pr_cases); free(pr_casesref); free(pr_casesref2); max_breaks = max_continues = max_cases = num_continues = num_breaks = num_cases = 0; pr_breaks = NULL; pr_continues = NULL; pr_cases = NULL; pr_casesref = NULL; pr_casesref2 = NULL; *compilingrootfile = 0; #ifdef _DEBUG OpAssignsTo_Debug(); #endif } #endif fteqcc-20251105/./hash.c0000644000200200001440000001710215233070110014014 0ustar twolifeusers#if _MSC_VER >= 1300 #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #ifndef _CRT_NONSTDC_NO_WARNINGS #define _CRT_NONSTDC_NO_WARNINGS #endif #endif #include "hash.h" #include #include #ifndef _WIN32 #ifndef stricmp #define stricmp strcasecmp #endif #endif // hash init assumes we get clean memory void Hash_InitTable(hashtable_t *table, unsigned int numbucks, void *mem) { table->numbuckets = numbucks; table->bucket = (bucket_t **)mem; } void *Hash_Enumerate(hashtable_t *table, void (*callback) (void *ctx, void *data), void *ctx) { unsigned int bucknum; bucket_t *buck; void *data; for (bucknum = 0; bucknum < table->numbuckets; bucknum++) { buck = table->bucket[bucknum]; while(buck) { data = buck->data; buck = buck->next; //now that we don't care about backlinks etc, we can call the callback and it can safely nuke it (Hash_RemoveData or even destroy the bucket if the hash table is going to die). callback(ctx, data); } } return NULL; } unsigned int Hash_Key(const char *name, unsigned int modulus) { //fixme: optimize. unsigned int key; for (key=0;*name; name++) key += ((key<<3) + (key>>28) + *name); return (key%modulus); } unsigned int Hash_KeyInsensitive(const char *name, unsigned int modulus) { //fixme: optimize. unsigned int key; for (key=0;*name; name++) { if (*name >= 'A' && *name <= 'Z') key += ((key<<3) + (key>>28) + (*name-'A'+'a')); else key += ((key<<3) + (key>>28) + *name); } return (key%modulus); } void *Hash_GetIdx(hashtable_t *table, unsigned int idx) { unsigned int bucknum; bucket_t *buck; for (bucknum = 0; bucknum < table->numbuckets; bucknum++) { buck = table->bucket[bucknum]; while(buck) { if (!idx--) return buck->data; buck = buck->next; } } return NULL; } void *Hash_Get(hashtable_t *table, const char *name) { unsigned int bucknum = Hash_Key(name, table->numbuckets); bucket_t *buck; buck = table->bucket[bucknum]; while(buck) { if (!STRCMP(name, buck->key.string)) return buck->data; buck = buck->next; } return NULL; } void *Hash_GetInsensitive(hashtable_t *table, const char *name) { unsigned int bucknum = Hash_KeyInsensitive(name, table->numbuckets); bucket_t *buck; buck = table->bucket[bucknum]; while(buck) { if (!stricmp(name, buck->key.string)) return buck->data; buck = buck->next; } return NULL; } void *Hash_GetInsensitiveBucket(hashtable_t *table, const char *name) { unsigned int bucknum = Hash_KeyInsensitive(name, table->numbuckets); bucket_t *buck; buck = table->bucket[bucknum]; while(buck) { if (!stricmp(name, buck->key.string)) return buck; buck = buck->next; } return NULL; } void *Hash_GetKey(hashtable_t *table, unsigned int key) { unsigned int bucknum = key%table->numbuckets; bucket_t *buck; buck = table->bucket[bucknum]; while(buck) { if (buck->key.value == key) return buck->data; buck = buck->next; } return NULL; } /*Does _NOT_ support items that are added with two names*/ void *Hash_GetNextKey(hashtable_t *table, unsigned int key, void *old) { unsigned int bucknum = key%table->numbuckets; bucket_t *buck; buck = table->bucket[bucknum]; while(buck) { if (buck->data == old) //found the old one break; buck = buck->next; } if (!buck) return NULL; buck = buck->next;//don't return old while(buck) { if (buck->key.value == key) return buck->data; buck = buck->next; } return NULL; } /*Does _NOT_ support items that are added with two names*/ void *Hash_GetNext(hashtable_t *table, const char *name, void *old) { unsigned int bucknum = Hash_Key(name, table->numbuckets); bucket_t *buck; buck = table->bucket[bucknum]; while(buck) { if (buck->data == old) //found the old one // if (!STRCMP(name, buck->key.string)) break; buck = buck->next; } if (!buck) return NULL; buck = buck->next;//don't return old while(buck) { if (!STRCMP(name, buck->key.string)) return buck->data; buck = buck->next; } return NULL; } /*Does _NOT_ support items that are added with two names*/ void *Hash_GetNextInsensitive(hashtable_t *table, const char *name, void *old) { unsigned int bucknum = Hash_KeyInsensitive(name, table->numbuckets); bucket_t *buck; buck = table->bucket[bucknum]; while(buck) { if (buck->data == old) //found the old one { // if (!stricmp(name, buck->key.string)) break; } buck = buck->next; } if (!buck) return NULL; buck = buck->next;//don't return old while(buck) { if (!stricmp(name, buck->key.string)) return buck->data; buck = buck->next; } return NULL; } void *Hash_Add(hashtable_t *table, const char *name, void *data, bucket_t *buck) { unsigned int bucknum = Hash_Key(name, table->numbuckets); buck->data = data; buck->key.string = name; buck->next = table->bucket[bucknum]; table->bucket[bucknum] = buck; return buck; } void *Hash_AddInsensitive(hashtable_t *table, const char *name, void *data, bucket_t *buck) { unsigned int bucknum = Hash_KeyInsensitive(name, table->numbuckets); buck->data = data; buck->key.string = name; buck->next = table->bucket[bucknum]; table->bucket[bucknum] = buck; return buck; } void *Hash_AddKey(hashtable_t *table, unsigned int key, void *data, bucket_t *buck) { unsigned int bucknum = key%table->numbuckets; buck->data = data; buck->key.value = key; buck->next = table->bucket[bucknum]; table->bucket[bucknum] = buck; return buck; } void Hash_Remove(hashtable_t *table, const char *name) { unsigned int bucknum = Hash_Key(name, table->numbuckets); bucket_t *buck; buck = table->bucket[bucknum]; if (!STRCMP(name, buck->key.string)) { table->bucket[bucknum] = buck->next; return; } while(buck->next) { if (!STRCMP(name, buck->next->key.string)) { buck->next = buck->next->next; return; } buck = buck->next; } return; } void Hash_RemoveDataInsensitive(hashtable_t *table, const char *name, void *data) { unsigned int bucknum = Hash_KeyInsensitive(name, table->numbuckets); bucket_t **link, *buck; for (link = &table->bucket[bucknum]; *link; link = &(*link)->next) { buck = *link; if (buck->data == data && !stricmp(name, buck->key.string)) { *link = buck->next; return; } } } void Hash_RemoveData(hashtable_t *table, const char *name, void *data) { unsigned int bucknum = Hash_Key(name, table->numbuckets); bucket_t **link, *buck; for (link = &table->bucket[bucknum]; *link; link = &(*link)->next) { buck = *link; if (buck->data == data && !stricmp(name, buck->key.string)) { *link = buck->next; return; } } } void Hash_RemoveBucket(hashtable_t *table, const char *name, bucket_t *data) { unsigned int bucknum = Hash_Key(name, table->numbuckets); bucket_t **link, *buck; for (link = &table->bucket[bucknum]; *link; link = &(*link)->next) { buck = *link; if (buck == data && !stricmp(name, buck->key.string)) { *link = buck->next; return; } } return; } void Hash_RemoveDataKey(hashtable_t *table, unsigned int key, void *data) { unsigned int bucknum = key%table->numbuckets; bucket_t **link, *buck; for (link = &table->bucket[bucknum]; *link; link = &(*link)->next) { buck = *link; if (buck->data == data && buck->key.value == key) { *link = buck->next; return; } } } void Hash_RemoveKey(hashtable_t *table, unsigned int key) { unsigned int bucknum = key%table->numbuckets; bucket_t *buck; buck = table->bucket[bucknum]; if (buck->key.value == key) { table->bucket[bucknum] = buck->next; return; } while(buck->next) { if (buck->next->key.value == key) { buck->next = buck->next->next; return; } buck = buck->next; } return; } fteqcc-20251105/./test.c0000644000200200001440000005627415233070110014065 0ustar twolifeusers//This is basically a sample program. //It deomnstrates the code required to get qclib up and running. //This code does not demonstrate entities, however. //It does demonstrate the built in qc compiler, and does demonstrate a globals-only progs interface. //It also demonstrates basic builtin(s). #include "progtype.h" #include "progslib.h" #include #include #include #include #include enum{false,true}; //builtins and builtin management. void PF_puts (pubprogfuncs_t *prinst, struct globalvars_s *gvars) { char *s; s = prinst->VarString(prinst, 0); printf("%s", s); } void PF_strcat (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { G_INT(OFS_RETURN) = prinst->TempString(prinst, prinst->VarString(prinst, 0)); } void PF_ftos (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { char temp[64]; sprintf(temp, "%g", G_FLOAT(OFS_PARM0)); G_INT(OFS_RETURN) = prinst->TempString(prinst, temp); } void PF_vtos (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { char temp[64]; sprintf(temp, "'%g %g %g'", G_VECTOR(OFS_PARM0)[0], G_VECTOR(OFS_PARM0)[1], G_VECTOR(OFS_PARM0)[2]); G_INT(OFS_RETURN) = prinst->TempString(prinst, temp); } void PF_etos (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { char temp[64]; sprintf(temp, "%i", G_INT(OFS_PARM0)); G_INT(OFS_RETURN) = prinst->TempString(prinst, temp); } void PF_itos (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { char temp[64]; sprintf(temp, "%x", G_INT(OFS_PARM0)); G_INT(OFS_RETURN) = prinst->TempString(prinst, temp); } void PF_ltos (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { char temp[64]; sprintf(temp, "%"PRIx64, G_INT64(OFS_PARM0)); G_INT(OFS_RETURN) = prinst->TempString(prinst, temp); } void PF_dtos (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { char temp[64]; sprintf(temp, "%g", G_DOUBLE(OFS_PARM0)); G_INT(OFS_RETURN) = prinst->TempString(prinst, temp); } void PF_stof (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { G_FLOAT(OFS_RETURN) = strtod(PR_GetStringOfs(prinst, OFS_PARM0), NULL); } void PF_stov (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { sscanf(PR_GetStringOfs(prinst, OFS_PARM0), " ' %f %f %f ' ", &G_FLOAT(OFS_RETURN+0), &G_FLOAT(OFS_RETURN+1), &G_FLOAT(OFS_RETURN+2)); } void PF_strcmp (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { if (prinst->callargc >= 3) G_FLOAT(OFS_RETURN) = strncmp(PR_GetStringOfs(prinst, OFS_PARM0), PR_GetStringOfs(prinst, OFS_PARM1), G_FLOAT(OFS_PARM2)); else G_FLOAT(OFS_RETURN) = strcmp(PR_GetStringOfs(prinst, OFS_PARM0), PR_GetStringOfs(prinst, OFS_PARM1)); } void PF_vlen (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { float *v = G_VECTOR(OFS_PARM0); G_FLOAT(OFS_RETURN) = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]); } void PF_normalize (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { float *v = G_VECTOR(OFS_PARM0); double l = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]); if (l) l = 1./l; else l = 0; G_VECTOR(OFS_RETURN)[0] = v[0] * l; G_VECTOR(OFS_RETURN)[1] = v[1] * l; G_VECTOR(OFS_RETURN)[2] = v[2] * l; } void PF_floor (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { G_FLOAT(OFS_RETURN) = floor(G_FLOAT(OFS_PARM0)); } void PF_pow (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { G_FLOAT(OFS_RETURN) = pow(G_FLOAT(OFS_PARM0), G_FLOAT(OFS_PARM1)); } void PF_sqrt (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { G_FLOAT(OFS_RETURN) = sqrt(G_FLOAT(OFS_PARM0)); } void PF_putv (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { printf("%f %f %f\n", G_FLOAT(OFS_PARM0+0), G_FLOAT(OFS_PARM0+1), G_FLOAT(OFS_PARM0+2)); } void PF_putf (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { printf("%f\n", G_FLOAT(OFS_PARM0)); } #ifdef _WIN32 #define Q_snprintfz _snprintf #define Q_vsnprintf _vsnprintf #else #define Q_snprintfz snprintf #define Q_vsnprintf vsnprintf #endif char *va(char *format, ...) { va_list argptr; static char string[1024]; va_start (argptr, format); Q_vsnprintf (string, sizeof(string), format,argptr); va_end (argptr); return string; } void QCBUILTIN PF_sprintf_internal (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals, const char *s, int firstarg, char *outbuf, int outbuflen) { const char *s0; char *o = outbuf, *end = outbuf + outbuflen, *err; int width, precision, thisarg, flags; char formatbuf[16]; char *f; int argpos = firstarg; int isfloat; static int dummyivec[3] = {0, 0, 0}; static float dummyvec[3] = {0, 0, 0}; #define PRINTF_ALTERNATE 1 #define PRINTF_ZEROPAD 2 #define PRINTF_LEFT 4 #define PRINTF_SPACEPOSITIVE 8 #define PRINTF_SIGNPOSITIVE 16 formatbuf[0] = '%'; #define GETARG_FLOAT(a) (((a)>=firstarg && (a)callargc) ? (G_FLOAT(OFS_PARM0 + 3 * (a))) : 0) #define GETARG_VECTOR(a) (((a)>=firstarg && (a)callargc) ? (G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyvec) #define GETARG_INT(a) (((a)>=firstarg && (a)callargc) ? (G_INT(OFS_PARM0 + 3 * (a))) : 0) #define GETARG_INTVECTOR(a) (((a)>=firstarg && (a)callargc) ? ((int*) G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyivec) #define GETARG_STRING(a) (((a)>=firstarg && (a)callargc) ? (PR_GetStringOfs(prinst, 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) { printf("PF_sprintf: bad format string: %s\n", s0); goto finished; } if(*err == '$') { thisarg = width + (firstarg-1); 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 != '$') { printf("PF_sprintf: invalid format string: %s\n", 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) { printf("PF_sprintf: invalid format string: %s\n", 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 != '$') { printf("PF_sprintf: invalid format string: %s\n", 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) { printf("PF_sprintf: invalid format string: %s\n", s0); goto finished; } s = err; } else { printf("PF_sprintf: invalid format string: %s\n", s0); goto finished; } } for(;;) { switch(*s) { case 'h': isfloat = 1; break; case 'l': isfloat = 0; break; case 'L': isfloat = 0; break; case 'j': break; case 'z': break; case 't': break; default: goto nolength; } ++s; } nolength: // now s points to the final directive char and is no longer changed if (*s == 'p' || *s == 'P') { //%p is slightly different from %x. //always 8-bytes wide with 0 padding, always ints. flags |= PRINTF_ZEROPAD; if (width < 0) width = 8; if (isfloat < 0) isfloat = 0; } else if (*s == 'i') { //%i defaults to ints, not floats. if(isfloat < 0) isfloat = 0; } //assume floats, not ints. if(isfloat < 0) isfloat = 1; if(thisarg < 0) thisarg = argpos++; if(o < end - 1) { f = &formatbuf[1]; if(*s != 's' && *s != 'c') if(flags & PRINTF_ALTERNATE) *f++ = '#'; if(flags & PRINTF_ZEROPAD) *f++ = '0'; if(flags & PRINTF_LEFT) *f++ = '-'; if(flags & PRINTF_SPACEPOSITIVE) *f++ = ' '; if(flags & PRINTF_SIGNPOSITIVE) *f++ = '+'; *f++ = '*'; if(precision >= 0) { *f++ = '.'; *f++ = '*'; } if (*s == 'p') *f++ = 'x'; else if (*s == 'P') *f++ = 'X'; else *f++ = *s; *f++ = 0; if(width < 0) // not set width = 0; switch(*s) { case 'd': case 'i': if(precision < 0) // not set Q_snprintfz(o, end - o, formatbuf, width, (isfloat ? (int) GETARG_FLOAT(thisarg) : (int) GETARG_INT(thisarg))); else Q_snprintfz(o, end - o, formatbuf, width, precision, (isfloat ? (int) GETARG_FLOAT(thisarg) : (int) GETARG_INT(thisarg))); o += strlen(o); break; case 'o': case 'u': case 'x': case 'X': case 'p': case 'P': if(precision < 0) // not set Q_snprintfz(o, end - o, formatbuf, width, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); else Q_snprintfz(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); o += strlen(o); break; case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': if(precision < 0) // not set Q_snprintfz(o, end - o, formatbuf, width, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg))); else Q_snprintfz(o, end - o, formatbuf, width, precision, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg))); o += strlen(o); break; case 'v': case 'V': f[-2] += 'g' - 'v'; if(precision < 0) // not set Q_snprintfz(o, end - o, va("%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf), width, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]), width, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]), width, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2]) ); else Q_snprintfz(o, end - o, va("%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf), width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]), width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]), width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2]) ); o += strlen(o); break; case 'c': //UTF-8-FIXME: figure it out yourself // if(flags & PRINTF_ALTERNATE) { if(precision < 0) // not set Q_snprintfz(o, end - o, formatbuf, width, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); else Q_snprintfz(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); o += strlen(o); } /* else { unsigned int c = (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)); char charbuf16[16]; const char *buf = u8_encodech(c, NULL, charbuf16); if(!buf) buf = ""; if(precision < 0) // not set precision = end - o - 1; o += u8_strpad(o, end - o, buf, (flags & PRINTF_LEFT) != 0, width, precision); } */ break; case 's': //UTF-8-FIXME: figure it out yourself // if(flags & PRINTF_ALTERNATE) { if(precision < 0) // not set Q_snprintfz(o, end - o, formatbuf, width, GETARG_STRING(thisarg)); else Q_snprintfz(o, end - o, formatbuf, width, precision, GETARG_STRING(thisarg)); o += strlen(o); } /* else { if(precision < 0) // not set precision = end - o - 1; o += u8_strpad(o, end - o, GETARG_STRING(thisarg), (flags & PRINTF_LEFT) != 0, width, precision); } */ break; default: printf("PF_sprintf: invalid format string: %s\n", s0); goto finished; } } ++s; break; default: verbatim: if(o < end - 1) *o++ = *s; s++; break; } } finished: *o = 0; } void PF_printf (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { char outbuf[4096]; PF_sprintf_internal(prinst, pr_globals, PR_GetStringOfs(prinst, OFS_PARM0), 1, outbuf, sizeof(outbuf)); printf("%s", outbuf); } struct edict_s { enum ereftype_e ereftype; float freetime; // realtime when the object was freed unsigned int entnum; unsigned int fieldsize; pbool readonly; //causes error when QC tries writing to it. (quake's world entity) void *fields; }; void PF_spawn (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { struct edict_s *ed; ed = ED_Alloc(prinst, false, 0); pr_globals = PR_globals(prinst, PR_CURRENT); RETURN_EDICT(prinst, ed); } void PF_remove (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { struct edict_s *ed = (void*)G_EDICT(prinst, OFS_PARM0); if (ed->ereftype == ER_FREE) { printf("Tried removing free entity\n"); PR_StackTrace(prinst, false); return; } ED_Free (prinst, (void*)ed); } void PF_error(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { PF_puts(prinst, pr_globals); } void PF_bad (pubprogfuncs_t *prinst, struct globalvars_s *gvars) { printf("bad builtin\n"); } builtin_t builtins[] = { PF_bad, PF_puts, PF_ftos, PF_spawn, PF_remove, PF_vtos, PF_error, PF_vlen, PF_etos, PF_stof, PF_strcat, PF_strcmp, PF_normalize, PF_sqrt, PF_floor, PF_pow, PF_stov, PF_itos, PF_ltos, PF_dtos, PF_puts, PF_putv, PF_putf, PF_printf, }; //Called when the qc library has some sort of serious error. void Sys_Abort(const char *s, ...) { //quake handles this with a longjmp. va_list ap; va_start(ap, s); vprintf(s, ap); va_end(ap); exit(1); } //Called when the library has something to say. //Kinda required for the compiler... //Not really that useful for the normal vm. int Sys_Printf(char *s, ...) { //look up quake's va function to find out how to deal with variable arguments properly. return printf("%s", s); } #include //copy file into buffer. note that the buffer will have been sized to fit the file (obtained via FileSize) void *PDECL Sys_ReadFile(const char *fname, unsigned char *(PDECL *buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *out_size, pbool issourcefile) { void *buffer; int len; FILE *f; if (!strncmp(fname, "src/", 4)) fname+=4; //skip the src part f = fopen(fname, "rb"); if (!f) return NULL; fseek(f, 0, SEEK_END); len = ftell(f); buffer = buf_get(buf_ctx, len); fseek(f, 0, SEEK_SET); fread(buffer, 1, len, f); fclose(f); *out_size = len; return buffer; } //Finds the size of a file. int Sys_FileSize (const char *fname) { int len; FILE *f; if (!strncmp(fname, "src/", 4)) fname+=4; //skip the src part f = fopen(fname, "rb"); if (!f) return -1; fseek(f, 0, SEEK_END); len = ftell(f); fclose(f); return len; } //Writes a file. pbool Sys_WriteFile (const char *fname, void *data, int len) { FILE *f; f = fopen(fname, "wb"); if (!f) return 0; fwrite(data, 1, len, f); fclose(f); return 1; } void ASMCALL StateOp(pubprogfuncs_t *prinst, float var, func_t func) { //note: inefficient. stupid globals abuse and not making assumptions about fields int *selfg = (int*)PR_FindGlobal(prinst, "self", 0, NULL); struct edict_s *ed = PROG_TO_EDICT(prinst, selfg?*selfg:0); float *time = (float*)PR_FindGlobal(prinst, "time", 0, NULL); eval_t *think = prinst->GetEdictFieldValue(prinst, ed, "think", ev_function, NULL); eval_t *nextthink = prinst->GetEdictFieldValue(prinst, ed, "nextthink", ev_float, NULL); eval_t *frame = prinst->GetEdictFieldValue(prinst, ed, "frame", ev_float, NULL); if (time && nextthink) nextthink->_float = *time+0.1; if (think) think->function = func; if (frame) frame->_float = var; } void ASMCALL CStateOp(pubprogfuncs_t *progs, float first, float last, func_t currentfunc) { /* float min, max; float step; float *vars = PROG_TO_WEDICT(progs, *w->g.self)->fields; float frame = e->v->frame; // if (progstype == PROG_H2) // e->v->nextthink = *w->g.time+0.05; // else e->v->nextthink = *w->g.time+0.1; e->v->think = currentfunc; if (csqcg.cycle_wrapped) *csqcg.cycle_wrapped = false; if (first > last) { //going backwards min = last; max = first; step = -1.0; } else { //forwards min = first; max = last; step = 1.0; } if (frame < min || frame > max) frame = first; //started out of range, must have been a different animation else { frame += step; if (frame < min || frame > max) { //became out of range, must have wrapped if (csqcg.cycle_wrapped) *csqcg.cycle_wrapped = true; frame = first; } } e->v->frame = frame; */ } static void ASMCALL CWStateOp (pubprogfuncs_t *prinst, float first, float last, func_t currentfunc) { /* float min, max; float step; world_t *w = prinst->parms->user; wedict_t *e = PROG_TO_WEDICT(prinst, *w->g.self); float frame = e->v->weaponframe; e->v->nextthink = *w->g.time+0.1; e->v->think = currentfunc; if (csqcg.cycle_wrapped) *csqcg.cycle_wrapped = false; if (first > last) { //going backwards min = last; max = first; step = -1.0; } else { //forwards min = first; max = last; step = 1.0; } if (frame < min || frame > max) frame = first; //started out of range, must have been a different animation else { frame += step; if (frame < min || frame > max) { //became out of range, must have wrapped if (csqcg.cycle_wrapped) *csqcg.cycle_wrapped = true; frame = first; } } e->v->weaponframe = frame; */ } void ASMCALL ThinkTimeOp(pubprogfuncs_t *prinst, struct edict_s *ed, float var) { float *self = (float*)PR_FindGlobal(prinst, "self", 0, NULL); float *time = (float*)PR_FindGlobal(prinst, "time", 0, NULL); int *nextthink = (int*)PR_FindGlobal(prinst, "nextthink", 0, NULL); float *vars = PROG_TO_EDICT(prinst, self?*self:0)->fields; if (time && nextthink) vars[*nextthink] = *time+0.1; } void runtest(const char *progsname, const char **args) { pubprogfuncs_t *pf; func_t func; progsnum_t pn; progparms_t ext; memset(&ext, 0, sizeof(ext)); ext.progsversion = PROGSTRUCT_VERSION; ext.ReadFile = Sys_ReadFile; ext.FileSize= Sys_FileSize; ext.Sys_Error = Sys_Abort; ext.Abort = Sys_Abort; ext.Printf = printf; ext.stateop = StateOp; ext.cstateop = CStateOp; ext.cwstateop = CWStateOp; ext.thinktimeop = ThinkTimeOp; ext.numglobalbuiltins = sizeof(builtins)/sizeof(builtins[0]); ext.globalbuiltins = builtins; pf = InitProgs(&ext); pf->Configure(pf, 1024*1024*64, 1, false); //memory quantity of 1mb. Maximum progs loadable into the instance of 1 //If you support multiple progs types, you should tell the VM the offsets here, via RegisterFieldVar pn = pf->LoadProgs(pf, progsname); //load the progs. if (pn < 0) printf("test: Failed to load progs \"%s\"\n", progsname); else { //allocate qc-acessable strings here for 64bit cpus. (allocate via AddString, tempstringbase is a holding area not used by the actual vm) //you can call functions before InitEnts if you want. it's not really advised for anything except naming additional progs. This sample only allows one max. pf->InitEnts(pf, 10); //Now we know how many fields required, we can say how many maximum ents we want to allow. 10 in this case. This can be huge without too many problems. //now it's safe to ED_Alloc. func = pf->FindFunction(pf, "main", PR_ANY); //find the function 'main' in the first progs that has it. if (!func) printf("Couldn't find function\n"); else { //feed it some complex args. void *pr_globals = PR_globals(pf, PR_CURRENT); int i; const char *atypes = *args++; for (i = 0; atypes[i]; i++) switch(atypes[i]) { case 'f': G_FLOAT(OFS_PARM0+i*3) = atof(*args++); break; case 'v': sscanf(*args++, " %f %f %f ", &G_VECTOR(OFS_PARM0+i*3)[0], &G_VECTOR(OFS_PARM0+i*3)[1], &G_VECTOR(OFS_PARM0+i*3)[2]); break; case 's': G_INT(OFS_PARM0+i*3) = pf->TempString(pf, *args++); break; } pf->ExecuteProgram(pf, func); //call the function } } pf->Shutdown(pf); } //Run a compiler and nothing else. //Note that this could be done with an autocompile of PR_COMPILEALWAYS. pbool compile(int argc, const char **argv) { pbool success = false; pubprogfuncs_t *pf; progparms_t ext; if (0) { char *testsrcfile = //newstyle progs.src must start with a #. //it's newstyle to avoid using multiple source files. "#pragma PROGS_DAT \"testprogs.dat\"\r\n" "//INTERMEDIATE FILE - EDIT TEST.C INSTEAD\r\n" "\r\n" "void(...) print = #1;\r\n" "void() main =\r\n" "{\r\n" " print(\"hello world\\n\");\r\n" "};\r\n"; //so that the file exists. We could insert it via the callbacks instead Sys_WriteFile("progs.src", testsrcfile, strlen(testsrcfile)); } memset(&ext, 0, sizeof(ext)); ext.progsversion = PROGSTRUCT_VERSION; ext.ReadFile = Sys_ReadFile; ext.FileSize= Sys_FileSize; ext.WriteFile= Sys_WriteFile; ext.Abort = Sys_Abort; ext.Printf = printf; pf = InitProgs(&ext); if (pf->StartCompile) { if (pf->StartCompile(pf, argc, argv)) { while(pf->ContinueCompile(pf) == 1) ; success = true; } else printf("compilation failed to start\n"); } else printf("no compiler in this qcvm build\n"); pf->Shutdown(pf); return success; } int main(int argc, const char **argv) { int i, a=0; char atypes[9]; const char *args[9] = {atypes}; const char *dat = NULL; if (argc < 2) { printf("Invalid arguments!\nPlease run as, for example:\n%s testprogs.dat -srcfile progs.src\nThe first argument is the name of the progs.dat to run, the remaining arguments are the qcc args to use\n", argv[0]); return 0; } for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "-float")) {atypes[a] = 'f'; args[++a] = argv[++i];} else if (!strcmp(argv[i], "-vector")) {atypes[a] = 'v'; args[++a] = argv[++i];} else if (!strcmp(argv[i], "-string")) {atypes[a] = 's'; args[++a] = argv[++i];} else if (!strcmp(argv[i], "-srcfile")) {if (!compile(argc-i, argv+i))return EXIT_FAILURE; break;} //compile it, woo. consume the rest of the args, too else if (!dat && argv[i][0] != '-') {dat = argv[i];} else {printf("unknown arg %s\n", argv[i]); return EXIT_FAILURE;} } atypes[a] = 0; if (dat) runtest(dat, args); else printf("Nothing to run\n"); return EXIT_SUCCESS; } fteqcc-20251105/./byshpuld.ico0000644000200200001440000021312615233070110015257 0ustar twolifeusershf  ¨Î00 ¨%v@@ (B<hF~`` ¨”®( 444BCDCDDEEDEEEFEEEEFGGFGGGHHHKKJKKKMLLNNNccccdddddfffgggkkknnnÀÀÁÂÁÀÂÁÁÁÂÂÃÂÂÃÂÃÄÃÂÄÄÄÅÄÄÅÅÅØÖÕØ×ÕÚÛÜÜÝÝÝÞÞÞÞÞÞÞßÞßßßààßàáãããäåååææö÷ø÷øø÷øùùúûýýý,,,,,0,,,,/.!##!!!(-##""" -%&''''*- ) ) ) ) ) -) ))) ))) +-))))))))))+- )))) )) - )) )) )'-$$$$$$ -  -( @ rke-rkedrkefrkefrkefrkefrkegrkegrkefrkefrkefrkefrkefrkefrkefrkefrkefrkefrkefrkefrkegrkegrkefrkefqjdfrkefrkeTWQMMrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿtmgÿ}wrÿwpkÿxqlÿ|vpÿ„yÿ‚|wÿ‘Œ‡ÿ‹†ÿ}wrÿpic×WROûrkeÿ[VRÿMJGÿNJHÿNJHÿNJHÿNJHÿNJHÿNJHÿNJHÿNJHÿNJHÿNJHÿNJHÿNJHÿNJHÿNJHÿMJGÿJFCÿLIFÿLHEÿKGDÿHDAÿHDAÿEA>ÿDA=ÿnjfý~xrÿrke~¥]XTÿrkeÿFB?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ...ÿMIFÿƒ}xÿrkeƒÅ]XTÿrkeÿœ›ÿÔÚßÿÔÚßÿÔÛàÿÔÛàÿÔÚàÿÔÚàÿÕÛàÿÔÛàÿÔÛàÿÔÛàÿÕÛàÿÕÜàÿÕÜßÿÕÛßÿÕÜßÿÕÛßÿÕÜàÿÖÛàÿÕÛàÿÖÜàÿÖÛàÿÕÛàÿÆËÏÿÿKGDÿ|vÿrkeƒÆ]XTÿrkeÿ§§¦ÿÄÊÍÿÄÊÍÿÄÉÍÿÄÉÍÿÅÊÍÿÄÊÍÿÄÉÍÿÄÊÍÿÅÉÍÿÅÊÍÿÆÊÍÿÄÊÍÿÆÊÍÿÆÊÍÿÅÊÍÿÆÊÍÿÆËÍÿÅËÎÿÆÊÎÿÅÊÎÿÅËÍÿÆËÎÿÛàäÿÿJFCÿ}wrÿrkeƒÅ]XTÿrkeÿª©¨ÿÊÍÐÿÊÍÐÿÊÎÐÿÊÎÐÿÉÎÐÿÉÎÐÿº¾ÀÿÊÎÐÿÉÎÐÿÉÎÐÿÉÎÑÿÊÍÐÿÉÎÐÿÊÍÑÿÊÎÑÿÊÎÑÿÊÏÑÿËÍÑÿËÏÑÿËÎÑÿÊÎÑÿËÎÑÿàåèÿÿJGDÿ|vqÿrkeƒÆ]XTÿrkeÿ¬«ªÿÍÑÔÿÄÈËÿÿ111ÿ122ÿ—™šÿÿ°´´ÿÎÒÓÿÏÒÔÿ677ÿ...ÿ---ÿ677ÿÂÅÇÿÏÑÔÿÎÒÔÿ#$$ÿÿ455ÿÿšœÿåéìÿÿJFCÿ~xsÿrkeƒÆ]XTÿrkeÿ°®«ÿËÍÎÿ222ÿÿ‘’ÿsuvÿÿ$$$ÿ½ÀÂÿ¹½¾ÿ"""ÿ***ÿ•—˜ÿ…††ÿ$$$ÿ$$$ÿÂÅÇÿBCCÿÿrstÿ€‚ÿ...ÿ$$$ÿ¦¨©ÿÿNJGÿwpkÿqjd…Æ\XSÿrkeÿ²°­ÿ˜™šÿ%%%ÿ‘‘’ÿ×ÙÚÿDEEÿ––—ÿ%%%ÿ·¹ºÿÍÏÐÿ333ÿŒÿ×ÚÚÿ×ÚÛÿ»½¾ÿ›œÿØÚÛÿ&&&ÿ“”•ÿÈËÌÿÙÚÛÿרÙÿ™™šÿÕÖØÿÿNKHÿtmgÿrkfŽÆ\XSÿrkeÿµ²°ÿ—˜™ÿ&&&ÿÕÖØÿÛÜÞÿÒÔÕÿ®¯°ÿ&&&ÿ¸¹¹ÿ»¼½ÿ&&&ÿÈÉÊÿÝÞÞÿÝÞÞÿÝÞÞÿÜÞÞÿÜÞÞÿ%%%ÿ°²²ÿÜÞÞÿÜÞßÿÜÞÞÿÜÞÞÿõö÷ÿÿNJHÿrkeÿrkeˆÅ\XSÿrkeÿ·´±ÿÿ&&&ÿ²±²ÿáàáÿáàáÿÛÚÛÿ&&&ÿ¿¿ÀÿÆÆÆÿ&&&ÿ˜™™ÿàââÿáââÿáââÿáââÿâââÿ&&&ÿ†††ÿâââÿââáÿâââÿâââÿûûûÿÿNJHÿrkeÿrkeˆÆ\XSÿrkeÿ¹¶³ÿ¹¹¹ÿ'''ÿ<<<ÿ¤¤¤ÿ­­­ÿ[[[ÿ'''ÿ¹¹¹ÿÛÛÛÿ'''ÿ'''ÿ›››ÿ®®®ÿGGGÿ444ÿÕÕÕÿFFFÿ'''ÿÿ½½½ÿ¬¬¬ÿ'''ÿ¤¤¤ÿÿNJHÿrkeÿrkeˆfffÇ\WSÿrkeÿ¹¶³ÿãããÿ‹‹‹ÿ'''ÿ(((ÿ(((ÿ'''ÿfffÿãããÿãããÿ¦¦¦ÿ'''ÿ(((ÿ(((ÿ(((ÿlllÿãããÿÜÜÜÿHHHÿ(((ÿ(((ÿ(((ÿ777ÿàààÿÿNJHÿrkeÿrkeˆ??? Ç]XTÿrkeÿ¹¶³ÿãããÿãããÿßßßÿÇÇÇÿ½½½ÿÑÑÑÿãããÿãããÿãããÿãããÿãããÿÒÒÒÿÎÎÎÿÙÙÙÿãããÿãããÿãããÿãããÿÒÒÒÿÈÈÈÿ¾¾¾ÿãããÿýýýÿÿNJHÿrkeÿrkeˆÅ]XTÿrkeÿ¹¶³ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿýýýÿÿNJHÿrkeÿqjd‰Æ]XTÿrkeÿ¹¶³ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿýýýÿÿNJHÿrkeÿrke‡Æ]XTÿrkeÿ¹¶³ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿýýýÿÿNJHÿrkeÿrkfÆ[VRÿrkeÿ¹¶³ÿãããÿÊÊÊÿ©©©ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿÊÊÊÿœœœÿÖÖÖÿãããÿãããÿãããÿãããÿÃÃÃÿ¾¾¾ÿÆÆÆÿÊÊÊÿ¹¹¹ÿñññÿÿNJHÿrkeÿrkfÆjeaÿngaÿ¹¶³ÿãããÿ¾¾¾ÿ,,,ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿÂÂÂÿ+++ÿÆÆÆÿãããÿãããÿãããÿãããÿ+++ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿâââÿÿNJHÿrkeÿrkeˆÆhc_ÿrkeÿ¹¶³ÿãããÿÅÅÅÿ,,,ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿºººÿ,,,ÿÆÆÆÿãããÿãããÿãããÿãããÿ,,,ÿ¨¨¨ÿãããÿãããÿãããÿýýýÿÿNJHÿrkeÿrkeˆÆic_ÿohbÿ¹¶³ÿãããÿµµµÿ---ÿ®®®ÿÈÈÈÿ½½½ÿÇÇÇÿãããÿãããÿãããÿ»»»ÿ,,,ÿÆÆÆÿãããÿãããÿãããÿããâÿ---ÿ”””ÿ»»ºÿ½½½ÿ¬¬«ÿüüüÿÿNJHÿrkeÿrkeˆÇhc_ÿohbÿ·³±ÿààßÿ³³³ÿ---ÿ:::ÿ888ÿ888ÿ¯¯¯ÿàààÿààßÿààßÿ½½½ÿ...ÿÅÅÄÿààßÿààßÿààßÿÖÖÕÿ---ÿ555ÿ;;;ÿ666ÿ999ÿùø÷ÿÿNJHÿrkeÿrkeˆÆlgcÿngaÿ¶±¯ÿÝÜÛÿ¹¸·ÿ222ÿÈÇÆÿÝÜÚÿÝÜÚÿÝÜÚÿÝÜÚÿÝÜÚÿÝÜÚÿ¯¯­ÿ222ÿ¹¸·ÿÜÛÙÿÝÛÚÿÝÜÚÿÝÜÚÿ...ÿ……„ÿÎÍËÿÐÎÍÿÝÛÛÿöôóÿÿNJHÿrkeÿrkeˆÆfb^ÿohbÿ´¯«ÿÚÙ×ÿ¯­­ÿ777ÿ333ÿ444ÿ444ÿŽÿÚØÖÿÃÃÀÿ555ÿ222ÿ555ÿ444ÿ888ÿÐÎÌÿÚØÖÿÚØÖÿ555ÿAAAÿ888ÿ777ÿ777ÿÜÚÙÿÿNJHÿrkeÿqjd‰Ærlhÿlf`ÿ²­©ÿ×ÔÓÿÀ¾¼ÿ´³°ÿ¸¶´ÿ¼º¸ÿ¸¶´ÿÄÂÀÿ×ÕÓÿÏÍËÿ»º¸ÿ¶³³ÿ¼»¹ÿ°®­ÿ´³±ÿÔÑÏÿ×ÔÓÿ×ÕÓÿ¦¤¤ÿ¸¶´ÿ¸¶´ÿ¸¶µÿžœšÿæäáÿÿNJHÿrkeÿrkeˆÆrmiÿle_ÿ²­¨ÿÔÑÏÿÔÑÏÿÔÑÏÿÔÑÏÿÔÑÏÿÔÑÎÿÔÑÎÿÔÑÎÿÔÑÎÿÓÑÎÿÔÑÎÿÔÑÎÿÔÑÎÿÔÑÎÿÔÑÍÿÔÑÍÿÔÑÎÿÓÑÍÿÔÑÍÿÔÑÎÿÔÑÍÿÔÑÍÿëçäÿÿMJGÿrkeÿrkeˆÆplhÿvnhÿ†€{ÿ¯«¥ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ®©¤ÿ¥ ›ÿFC@ÿ^YTÿrkeÿrke…ª?=;ÿ•މÿohaÿkd^ÿjc]ÿmf`ÿpibÿg_YÿhaZÿjc\ÿohbÿngaÿngbÿpicÿngaÿoicÿle^ÿohcÿohcÿrkeÿrkeÿrkeÿqjdÿohbÿohbÿohbÿmf`ÿrkeÿrkeÿrkeE")))ù=;9ÿ`\Xÿupmÿ{wÿ}yÿ|yuÿ†ƒÿ…€}ÿ|wtÿvrnÿmhdÿkfaÿje`ÿmhdÿhb]ÿvqmÿhc]ÿic^ÿ\XSÿ]XTÿ\WSÿa\Xÿhc^ÿjeaÿkfaÿmhdÿVRNüVQM`÷÷÷"¨ÅÆÆÅÆÆÆÇÆÆÆÆÆÆÆÆÆÆÆÆÅůō"~~~ÿÿÿÿàÀ€€€€€€€€€€€€€€€€€€€€€€€€€€Ààÿÿÿÿ(0` jlnttutuv*//10.100110000001000001011/1000000-! KFC©^YTæ]YTì^XTí^XTí^XTí^XTí^XTí^XTî^XTí^XTí^XTí^XTí^XTí^XTí^XTí^XTí^XTí^XTí^XTí^XTí^XTí^XTí^XTí^XTí^XTí^XTí^XTí^XTí^YTí^XTí^XTí^XTí^XTí^YTí^XTí^XTí]XTí]XSé642ª! HEAÊunhÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿslfÿsmfÿwpjÿuoiÿtmgÿtmgÿtmgÿvoiÿyrlÿwpjÿwpjÿ}wrÿ~xrÿ}vqÿyrlÿunhÿLHEß%'&%œpidÿrkeÿqjeÿkd_ÿohcÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿohbÿpjdÿ}vrÿ{upÿtmhÿvpkÿslhÿztoÿ|wÿ€zvÿ€{vÿ‹‡‚ÿ‹†‚ÿІÿ‚~yÿŠ„ÿtngÿQLI¾><:âtmgÿslfÿg`\ÿ'&&ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ)('ÿ987ÿˆ„€ÿwqkÿic]ì&(DA?êtmgÿslfÿa[Vÿ‘ÿÞåêÿÙàåÿÙàåÿÙàåÿÙàåÿÙàåÿÙàåÿÙáåÿÙàåÿÚàåÿÚàåÿÙáåÿÙàåÿÙáåÿÚáåÿÚáåÿÚáåÿÚáåÿÚáåÿÚâåÿÚáåÿÚáåÿÚáåÿÚáåÿÚáåÿÚâæÿÛáæÿÚáæÿÚáæÿÛáæÿÛáæÿÛáæÿÚáæÿßçëÿ’”–ÿ*)(ÿƒ}xÿyrlÿhb]ð5)B@=ëtmgÿslfÿZTNÿØßãÿÀÅÉÿÁÆÉÿÁÆÊÿÁÆÊÿÁÆÊÿÁÆÊÿÁÆÊÿÁÆÊÿÁÆÊÿÁÆÊÿÁÆÊÿÁÆÊÿÁÆÊÿÂÆÊÿÁÆÊÿÁÇÊÿÁÆÊÿÁÆÊÿÁÆÊÿÁÇÊÿÂÇÊÿÂÇÊÿÁÇÊÿÂÈÊÿÁÇÊÿÂÇÊÿÂÇÊÿÂÇÊÿÂÇÊÿÂÇÊÿÂÇËÿÂÇÊÿÁÇËÿÀÅÈÿãéîÿÿ€zuÿxrlÿhb]ð9stv)B@>ëtmgÿslfÿ[TNÿØÜáÿÅÊÌÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÅËÎÿÅËÎÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÅËÎÿÆËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÏÿÇÌÎÿÇÌÏÿÇËÏÿÇÌÏÿÇËÏÿÇÌÏÿÆÌÏÿÇÌÏÿÅÊÍÿãçëÿÿ}wsÿvoiÿib]ï6)B@>ëtmgÿslfÿZTNÿÛßâÿÇÌÏÿÈÌÏÿÈÌÏÿÉÍÐÿÉÍÐÿÊÎÑÿÊÎÑÿÉÍÐÿÉÍÐÿÆÊÍÿÉÍÐÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÉÍÐÿÉÍÐÿÊÎÑÿÊÎÑÿÉÍÐÿÉÌÐÿÉÍÐÿÉÍÐÿÉÍÐÿÉÍÐÿÉÍÐÿÊÎÑÿÊÎÑÿËÏÒÿËÎÑÿÉÎÑÿÉÍÐÿÉÍÐÿÈÌÏÿæêîÿÿ{tpÿunhÿib]ð7ÿÿÿ)B@>ëtmgÿslfÿYTNÿÞâäÿÊÏÑÿËÏÑÿÑÕ×ÿÇËÌÿ®±²ÿ•˜™ÿ™œÿ½ÁÃÿ”—˜ÿ€‚ƒÿÎÒÔÿËÏÑÿËÐÒÿÌÏÒÿÑÕ×ÿÅÉÌÿ©¬®ÿ’•—ÿœŸ¡ÿ»¿ÂÿÑÔ×ÿÍÐÓÿÌÏÒÿÌÏÒÿÌÐÒÿÒ×ÙÿÃÆÈÿ¥¨ªÿ’•–ÿŸ¢£ÿ¿ÃÅÿÒ×ÙÿÌÐÒÿËÏÑÿéîðÿÿ{uqÿunhÿib]ð6)B@>ëtmgÿslfÿYSNÿáåèÿÍÐÔÿ×ÚÝÿ~€ÿ999ÿ///ÿ)))ÿ***ÿ233ÿ***ÿ$%%ÿccdÿÕØÚÿÏÒÔÿÙÜÞÿxz{ÿ788ÿ-..ÿ(((ÿ+++ÿ111ÿ[\]ÿÎÑÓÿÐÓÕÿÐÓÕÿØÛÝÿjjlÿ777ÿ,--ÿ(((ÿ+++ÿ334ÿjkkÿÜÞâÿÎÑÓÿíðóÿÿupkÿsmgÿib]ð6)B@>ëtmgÿslfÿYSNÿåééÿÒÕÕÿÿÿ###ÿ(((ÿ333ÿ+++ÿ$$$ÿ$$$ÿÿ¨ª«ÿÓÖØÿÔ×Ùÿvxyÿÿ###ÿ(((ÿ334ÿ---ÿ$$$ÿ ÿabcÿÎÐÒÿÏÒÔÿccdÿÿ###ÿ***ÿ333ÿ---ÿ"""ÿÿ_``ÿÕØÙÿñõöÿÿ|uqÿunhÿib]ð8*C@>ëtmgÿslfÿYSNÿéëîÿ–˜™ÿ$$$ÿ$$$ÿ<<<ÿ”•–ÿ¹»½ÿAAAÿ$$$ÿ###ÿ"""ÿ@@AÿÛÞßÿ›ÿ%%%ÿ$$$ÿ777ÿ”•–ÿÆÉÊÿ°²²ÿCCDÿÿ;;<ÿÃÅÇÿ§¨ªÿÿ$$$ÿ444ÿ¢¤¤ÿÅÇÉÿª«­ÿ9::ÿ!!!ÿIIJÿÉËÌÿõ÷ùÿÿysnÿtmgÿib]ð&&%>*DB?ëtmgÿslfÿYRMÿêëìÿnooÿ$$$ÿ)))ÿ”•–ÿÛÜÝÿÂÃÄÿ000ÿ ÿ666ÿ$$$ÿÿµ¶·ÿmnnÿ$$#ÿ+++ÿœžÿÚÜÝÿÙÛÜÿÛÝßÿÄÆÇÿ¢¤¤ÿÍÏÏÿàâãÿKKLÿ"""ÿÿÌÎÏÿÜÝÞÿÙÛÜÿÚÛÝÿ½¾¾ÿ±²³ÿÐÐÑÿÙÚÛÿøúüÿÿuojÿslfÿic]ðêtmgÿslfÿXRMÿíîïÿbbbÿ%%%ÿ889ÿÚÚÛÿÞßàÿÞÞßÿââãÿáâãÿÉÉÊÿÿ!!!ÿƒ„„ÿ```ÿ%%%ÿ888ÿØÙÙÿßààÿÞàßÿÞßßÿßààÿâããÿÞßßÿéêêÿÿ"""ÿbccÿåççÿÞßßÿßàßÿßßàÿàááÿàààÿàßßÿÞÞßÿÿÿÿÿÿoicÿsleÿjc]ð9)D@?êtmgÿslfÿXRMÿóóòÿlllÿ%%%ÿ...ÿ­­®ÿããäÿâââÿâáâÿéééÿvvwÿ ÿÿ¬¬¬ÿlklÿ%%%ÿ///ÿ¶¶¶ÿãääÿâããÿãããÿáââÿ»»»ÿëëëÿìììÿ999ÿ$$$ÿ!!!ÿêêêÿãããÿãããÿãããÿÕÕÕÿèççÿæææÿááâÿÿÿÿÿÿohbÿrkeÿjc]ð9+B@=ëtmgÿslfÿXQLÿùúùÿÿ(((ÿ&&&ÿ^^^ÿÊÊÊÿ×××ÿ×××ÿ°°°ÿÿ&&&ÿ000ÿèèèÿ‘‘‘ÿ'''ÿ%%%ÿ\\\ÿËËËÿ×ÖÖÿÖÖÖÿkkkÿ!!!ÿ}}}ÿàààÿ›››ÿÿ&&&ÿKKKÿÒÒÒÿ×××ÿÑÑÑÿgggÿÿ™™™ÿÞÞÞÿÿÿÿÿÿohbÿrkeÿjc]ð9*CA?ëtmgÿslfÿXQLÿùúùÿ×××ÿRRRÿÿ###ÿ000ÿFFFÿ???ÿ$$$ÿ&&&ÿ"""ÿ¢¢¢ÿçççÿÜÜÜÿTTTÿÿ"""ÿ000ÿEEEÿ999ÿ$$$ÿ$$$ÿAAAÿÔÔÔÿ×××ÿIIIÿ###ÿ%%%ÿ111ÿIIIÿ555ÿ###ÿ ÿHHHÿÝÝÝÿÿÿÿÿÿohbÿrkeÿjc]ð8••• 0986éungÿslfÿXQLÿùùøÿããäÿÛÛÛÿ}}}ÿÿ$$$ÿ%%%ÿ%%%ÿ$$$ÿ'''ÿ­­­ÿãããÿãããÿäääÿÙÙÙÿÿÿ$$$ÿ%%%ÿ%%%ÿ"""ÿ999ÿÑÑÑÿãããÿãããÿÓÓÓÿ\\\ÿÿ%%%ÿ%%%ÿ%%%ÿÿlllÿØØØÿãããÿÿÿÿÿÿohbÿrkeÿjc]ð8ˆˆˆ 3B@>ìtmgÿslfÿXQLÿùùøÿâââÿãããÿéééÿÅÅÅÿqqqÿdddÿcccÿ~~~ÿóóóÿåååÿãããÿãããÿãããÿãããÿéééÿÅÅÅÿqqqÿdddÿdddÿ”””ÿñññÿãããÿãããÿãããÿãããÿìììÿ¾¾¾ÿkkkÿbbbÿhhhÿ¤¤¤ÿçççÿãããÿâââÿÿÿÿÿÿohbÿrkeÿjc]ð8*B@>ëtmgÿslfÿXQLÿùùøÿâââÿãããÿãããÿæææÿëëëÿêêêÿëëëÿêêêÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿæææÿëëëÿêêêÿëëëÿéééÿãããÿãããÿãããÿãããÿãããÿãããÿæææÿëëëÿêêêÿëëëÿèèèÿãããÿãããÿâââÿÿÿÿÿÿohbÿrkeÿjc]ð9#+1)B@>êtmgÿslfÿXQLÿùùøÿâââÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿâââÿÿÿÿÿÿohbÿrkeÿjc]ð""!<¤¥¦*B@>ëtmgÿslfÿXQLÿùùøÿâââÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿâââÿÿÿÿÿÿohbÿrkeÿjc]ð9(*+*C@>ëtmgÿslfÿXQLÿùùøÿâââÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿâââÿÿÿÿÿÿohbÿrkeÿjc]ð:*B@>ëtmgÿslfÿXQLÿùùøÿâââÿãããÿäääÿäääÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿäääÿäääÿäääÿãããÿãããÿãããÿãããÿãããÿãããÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿãããÿâââÿÿÿÿÿÿohbÿrkeÿjc^ñ==ëtmgÿslfÿXQLÿùùøÿãããÿÚÚÚÿ¼¼¼ÿ»»»ÿÛÛÛÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿáááÿÇÇÇÿ¿¿¿ÿÂÂÂÿâââÿãããÿãããÿãããÿãããÿãããÿÏÏÏÿ¾¾¾ÿ¿¿¿ÿ¿¿¿ÿ¿¿¿ÿ¿¿¿ÿ¿¿¿ÿ¼¼¼ÿÚÚÚÿâââÿÿÿÿÿÿohbÿrkeÿjc^ñ654G“““)B@>ërkeÿtlfÿXQLÿùùøÿæææÿ¸¸¸ÿ!!!ÿÿ¾¾¾ÿäääÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿÚÚÚÿUUUÿ(((ÿ<<<ÿáááÿãããÿãããÿãããÿãããÿåååÿƒƒƒÿ'''ÿ***ÿ***ÿ***ÿ***ÿ***ÿÿ¶¶¶ÿåååÿÿÿÿÿÿohbÿrkeÿjc]ð9)A><뉂|ÿpicÿXQLÿùùøÿæææÿ¸¸¸ÿ$$$ÿ!!!ÿ¿¿¿ÿäääÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿÚÚÚÿVVVÿ+++ÿ???ÿáááÿãããÿãããÿãããÿãããÿåååÿ‡‡‡ÿ***ÿ+++ÿ$$$ÿ"""ÿ###ÿ###ÿÿ°°°ÿæææÿÿÿÿÿÿohbÿrkeÿjc]ð9)A>=닃}ÿpicÿXQLÿùùøÿæææÿ¸¸¸ÿ%%%ÿ!!!ÿÀÀÀÿåååÿäääÿäääÿäääÿãããÿãããÿãããÿãããÿãããÿÚÚÚÿWWWÿ+++ÿ???ÿáááÿãããÿãããÿãããÿãããÿåååÿˆˆˆÿ***ÿ555ÿ´´´ÿØØØÿ×××ÿ×××ÿÕÕÕÿàààÿâââÿÿÿÿÿÿohbÿrkeÿjc]ð8)@>;늃}ÿrkeÿXQLÿùùùÿæææÿ¸¸¸ÿ%%%ÿ###ÿ±±±ÿÏÏÏÿÎÎÎÿÎÎÎÿÏÏÏÿÝÝÝÿãããÿãããÿãããÿãããÿÚÚÚÿWWWÿ+++ÿ???ÿáááÿãããÿãããÿãããÿãããÿåååÿ‡‡‡ÿ***ÿ555ÿ®®®ÿÑÑÑÿÐÐÐÿÐÐÐÿÍÍÍÿãããÿâââÿÿÿÿÿÿohbÿrkeÿjc]ð9*@><ë†ÿpicÿXQMÿøøøÿåååÿ···ÿ&&&ÿ,,,ÿ999ÿ<<<ÿ;;;ÿ;;;ÿ@@@ÿªªªÿãããÿâââÿâââÿâââÿÙÙÙÿWWWÿ,,,ÿ@@@ÿàààÿââãÿâââÿâââÿâââÿäääÿˆˆˆÿ+++ÿ...ÿ999ÿ<<<ÿ;;;ÿ;;;ÿ222ÿÜÜÛÿââáÿÿÿÿÿÿohbÿrkeÿjc]ð9111,B@>ë…}vÿqjdÿXRMÿõöõÿãããÿ·¶·ÿ&&&ÿ---ÿ444ÿ666ÿ666ÿ666ÿ;;;ÿ¨¨¨ÿâââÿáááÿááàÿááàÿØØ×ÿWWWÿ,,,ÿ@@@ÿÞÞÞÿááàÿááàÿááàÿááàÿããâÿ‡‡†ÿ+++ÿ...ÿ444ÿ655ÿ655ÿ655ÿ+++ÿÜÛÚÿààßÿÿÿÿÿÿohbÿrkeÿjc]ð8ŽŽŽ-B@=ëˆyÿpjdÿXRMÿóòòÿáàßÿµµµÿ***ÿ)))ÿ  Ÿÿµ´´ÿµ´³ÿ´´³ÿ¶µµÿÑÐÐÿßÞÞÿÞÝÜÿßÞÝÿàßßÿ×ÖÕÿZYYÿ/00ÿBBBÿÞÝÝÿàßßÿßÞÞÿÞÝÝÿÞÞÝÿáßßÿ‡††ÿ---ÿ777ÿ™™˜ÿµ´³ÿ´³²ÿ´³³ÿ­¬¬ÿÞÝÜÿÝÜÛÿþýüÿÿohbÿrkeÿjc]ð9lll+CA?ëtlfÿslfÿXRMÿñðïÿßÞÜÿµ´³ÿ///ÿ+++ÿ»ººÿ×ÖÕÿÖÕÔÿÖÖÔÿÖÕÓÿÚÙ×ÿÜÛÙÿÜÛÙÿØ×ÕÿÖÕÓÿÌÌÊÿ[ZZÿ333ÿDDDÿÕÔÒÿÔÓÑÿÙØÖÿÝÛÚÿÜÚÙÿÞÝÛÿ‡††ÿ001ÿ<<;ÿµ´³ÿ×ÕÓÿÕÔÓÿÕÔÓÿÓÒÑÿÛÚÙÿÛÙØÿüúùÿÿohbÿrkeÿjc]ð9*?=:ꔆÿohbÿXRNÿðíìÿÜÛÙÿµ³²ÿ222ÿ:::ÿ011ÿ000ÿ///ÿ///ÿ111ÿ••”ÿÜÛÙÿÜÚØÿ€€ÿ---ÿ///ÿ555ÿ777ÿ777ÿ///ÿ,,,ÿsqqÿâáßÿÛÙØÿÝÛÙÿ‰‰‡ÿ666ÿ666ÿ///ÿ...ÿ...ÿ---ÿ'''ÿ¯®­ÿÜÚÙÿù÷öÿÿohbÿrkeÿjc]ð:,.0*A?=ê€ysÿqjdÿYSNÿíêèÿÚØÖÿ³±±ÿ333ÿ;;<ÿ;;;ÿ;:;ÿ;:;ÿ;:;ÿ>>>ÿœšÿÙ×ÕÿÙ×Õÿ‡††ÿ999ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ444ÿ‚‚ÿßÝÛÿØ×ÕÿÚØÖÿ‡†…ÿ777ÿ988ÿ999ÿ999ÿ999ÿ:99ÿ///ÿ·µ³ÿÚØÖÿ÷öóÿÿohbÿrkeÿjc]ñ'&&>ŒŒwww+C@>땎ˆÿngaÿYSNÿëèåÿÖÓÑÿÏÍËÿ¶µ²ÿ¸·´ÿ¸¶´ÿ¸¶´ÿ¸¶´ÿ¸¶´ÿ¹·´ÿËÈÆÿ×ÔÒÿÖÔÒÿÆÄÁÿ¸¶³ÿ¸¶´ÿ¸¶´ÿ¸µ´ÿ¸¶´ÿ¸µ´ÿ¶´³ÿÆÄÁÿØÕÓÿÖÓÑÿ×ÔÒÿÆÃÂÿ·¶³ÿ¸µ´ÿ¸µ´ÿ¸µ³ÿ¸µ´ÿ¸µ³ÿµ³²ÿÐÍËÿÕÓÐÿõòïÿÿohbÿrkeÿjc]ð8www*@><ê—ˆÿnhbÿYSNÿéæâÿÔÒÎÿÕÒÐÿÖÒÐÿÖÓÐÿÖÓÐÿÖÓÐÿÖÓÐÿÖÒÐÿÖÒÐÿÕÒÏÿÕÒÏÿÕÒÐÿÕÒÏÿÖÒÏÿÕÓÏÿÕÒÏÿÖÓÏÿÖÒÏÿÖÓÏÿÕÓÐÿÕÒÏÿÔÒÎÿÕÒÎÿÕÒÏÿÕÒÎÿÖÓÐÿÕÒÏÿÕÓÏÿÕÓÏÿÕÓÏÿÕÓÏÿÕÒÏÿÕÒÏÿÓÐÍÿóïìÿÿohbÿrkeÿjc]ð8*C@?ꕎˆÿngaÿXRNÿëèäÿÐÍÊÿÑÏËÿÑÏËÿÑÏËÿÑÎËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏÊÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏÊÿÑÏËÿÑÎËÿÑÏËÿÑÎËÿÑÏÊÿÑÏËÿÑÏËÿÑÏÊÿÑÎËÿÑÏËÿÏÌÉÿõòíÿÿohbÿrkeÿic]ð8)B?<ê§¡œÿmf_ÿd^YÿŽŒÿéåáÿãßÚÿãßÛÿãßÛÿäàÜÿãßÚÿãßÛÿãßÛÿãßÛÿãßÛÿãßÛÿãßÛÿãßÛÿãßÛÿâßÛÿãßÛÿãßÛÿãßÛÿãßÛÿãßÛÿãßÛÿâßÛÿâßÚÿâßÛÿâßÛÿãßÛÿãßÚÿâßÛÿâßÛÿâßÛÿâßÛÿâßÛÿâßÛÿçäàÿ”“‘ÿ)((ÿle`ÿskeÿic]ð6ÿÿÿ0/.ሃÿ‚{uÿrkeÿd^YÿXSNÿYTOÿYTOÿYTOÿYTNÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYTOÿYSNÿa[Vÿid^ÿrkeÿrkeÿib]ì%£b]Xÿœ•ÿxpiÿohbÿmf`ÿle^ÿme_ÿphbÿrjcÿogaÿib\ÿjc\ÿjc\ÿmf_ÿpicÿogaÿohbÿpicÿqjdÿqjdÿpicÿpicÿqjdÿng`ÿqjdÿqjdÿqjdÿrkeÿslfÿslfÿslfÿslfÿrkeÿrkeÿpicÿpicÿpicÿpicÿohbÿslfÿrkeÿunhÿUPMÅ#'&%Ç]XTÿztmÿ‹‚{ÿž—’ÿ¨¢ÿ²¬§ÿ¯ª¥ÿ«¦¡ÿ­¨¢ÿ·²­ÿ¶¯«ÿ´°«ÿ¦Ÿšÿ¢›•ÿœ–ÿކ€ÿ…~ÿŠ‚{ÿ‡€xÿ…ÿŒ…~ÿ„{tÿ–ÿƒ{sÿ†~wÿ‡xÿ}unÿslfÿtmgÿtmgÿtmgÿ~wpÿ}unÿ…ÿ‹ƒ}ÿŠƒ}ÿŠƒ}ÿ’‹…ÿpicÿqjeÿPLHä &RUX~~~"¡11/àA?=êB?=ëB?<ëA?<ëB?=ë@=;êA?=ëB?<ê@>;êA>;ë?=:ê?=:ëA>;ëB@=êB?=ëC@>ëCA>ëC@>ëC@>ëCA>ëB><ëCA>ëDA?ëB@=ëCA>êB@>êB@=ëCA>êC@>êA><ëCA>ê@>;ê@=<ëC@>ëA?<ë@=:ê?<;â*)(ª" ccc“““'))**)**)+*****+))))))**)*+)*+)****'ÿÿÿÿÿÿÿÿÿÿÿÿðàÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀàðÿÿÿÿÿÿÿÿÿÿÿÿ(@€ ~~~~~~~~~ "E_ejglhlflikikjkjiiiiiijkjkijilgkilikhkjjiiiiiiheT*641„^XTòb]Xüb]Xþb]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþc]Xþb]Xþc]XýSOKò†#752¦qjdÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿqjdÿqjdÿrkeÿqjdÿqjdÿrkeÿrkeÿslfÿqjdÿqjdÿqjdÿtnhÿvoiÿunhÿtmgÿslfÿqjdÿoicÿ=:8¿#!yga\þrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿwqkÿއƒÿŠ…€ÿ‹…ÿ{toÿ|vÿ~wsÿzsnÿ‹†ÿ—“Žÿ˜“ÿ“ŽŠÿ—“ÿ«¨¤ÿ¬©¦ÿ«¨¤ÿ«¨¥ÿ›—“ÿ„}yÿohaÿoicÿ964¢&754äpicÿrkeÿrkeÿngbÿ]XTÿb\Xÿc]Yÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿc]Xÿb\Xÿ_YUÿ_YUÿ_YUÿb\Wÿ`[Vÿa\Wÿb\Xÿ`ZUÿ^XTÿ^XSÿ_XTÿ^XSÿ\VQÿ\VQÿ\VQÿ[UPÿYUPÿ¡™ÿ„yÿrkeÿhb]û$" 7 FEB@ùrkeÿrkeÿrkeÿ`ZVÿ***ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ+**ÿ.--ÿd`\ÿš–’ÿrkeÿmf`þ/,*a[JFDürkeÿrkeÿrkeÿa\WÿrrsÿÐÖÛÿÑ×ÜÿÑ×ÜÿÑ×ÜÿÑ×ÜÿÐ×ÜÿÑ×ÜÿÑ×ÜÿÑ×ÜÿÑ×ÜÿÑØÜÿÑ×ÜÿÑ×ÜÿÒØÜÿÑ×ÜÿÑØÜÿÑ×ÜÿÑ×ÜÿÑØÜÿÒØÜÿÑØÜÿÒØÜÿÒØÜÿÒØÜÿÒÙÜÿÒØÜÿÒÙÜÿÒØÜÿÒÙÜÿÒØÜÿÒØÜÿÒØÜÿÒØÜÿÒÙÝÿÒÙÝÿÒØÝÿÒØÝÿÒØÝÿÒØÝÿÒØÝÿÓÙÝÿÒØÝÿÒØÝÿÒØÝÿÒØÝÿÑØÜÿrstÿ***ÿe`Zÿ–‘ÿtmgÿmfaÿ,*(r^HEBýrkeÿrkeÿrkeÿ_YTÿÍÒÖÿÁÇÊÿÁÆÊÿÁÇÊÿÁÇÊÿÁÆËÿÁÇËÿÁÇËÿÁÇËÿÁÇËÿÁÆËÿÁÆËÿÁÇËÿÁÆËÿÁÇËÿÂÇËÿÁÆËÿÁÇËÿÁÇËÿÂÇËÿÁÆËÿÁÇËÿÁÇËÿÁÇËÿÁÇËÿÁÇËÿÁÇÊÿÁÇËÿÁÇÊÿÁÇÊÿÂÈËÿÁÇÊÿÁÈËÿÂÇÊÿÁÇËÿÂÇËÿÃÇËÿÂÇÊÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÃÇËÿÁÇËÿÂÇËÿÂÇËÿÔÙÞÿÿd_Zÿމ…ÿtmgÿmfaÿ,*(v ~~~^HECýrkeÿrkeÿrkeÿ_YTÿÏÕØÿÃÈÌÿÃÈÌÿÃÈËÿÃÈÌÿÃÈËÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÄÈÌÿÄÈÌÿÃÈÌÿÄÉÌÿÄÈÌÿÃÈÌÿÃÈÌÿÄÈÌÿÄÉÌÿÅÉÌÿÅÉÌÿÄÉÌÿÄÉÌÿÅÊÌÿÄÉÌÿÄÉÌÿÄÊÌÿÄÉÌÿÄÉÌÿÄÉÍÿÄÉÌÿÄÉÌÿÄÉÍÿÄÉÌÿÄÉÌÿÄÉÍÿÄÉÌÿÄÉÌÿ×Ýàÿÿb]Xÿ’ˆÿsmgÿmfaÿ,*(w ~~~^HECýrkeÿrkeÿrkeÿ_YTÿÓ×ÛÿÆËÍÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÅËÎÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÆËÎÿÆÌÏÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÏÿÇËÏÿÇÌÎÿÇÌÏÿÇÌÏÿÇËÏÿÇÌÏÿÇËÏÿÇÌÏÿÇÌÏÿÆÌÏÿÇÌÏÿÇÌÏÿÆËÏÿÛßâÿÿ_YUÿމ…ÿqjdÿmfaÿ+)'s^HECýrkeÿrkeÿrkeÿ_YTÿÕÙÜÿÇÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÉÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÉÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÉÌÐÿÈÍÏÿÉÍÐÿÉÍÐÿÉÍÐÿÈÍÐÿÉÍÐÿÉÍÐÿÉÌÏÿÉÍÐÿÉÍÐÿÉÌÐÿÉÍÐÿÈÍÐÿÉÍÐÿÉÍÐÿÉÍÐÿÉÍÐÿÝáåÿÿ_YUÿ‹…ÿqjdÿmfaÿ+)'výýý ]HECýrkeÿrkeÿrkeÿ^YTÿØÛÝÿÊÏÑÿËÎÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÒÿ™œÿËÏÑÿËÏÒÿËÏÑÿËÏÒÿËÏÑÿËÏÒÿËÏÑÿËÏÒÿËÏÒÿËÏÒÿÊÏÒÿÌÏÒÿËÏÒÿËÏÒÿËÏÒÿÌÏÒÿËÏÒÿËÏÒÿËÏÒÿËÏÒÿÌÐÒÿÌÐÒÿÌÏÒÿÌÎÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿËÐÒÿßãæÿÿ`YUÿ‰„€ÿqjdÿmfaÿ+)'sýýý^HECýrkeÿrkeÿrkeÿ^YTÿÙÜßÿÌÐÒÿÌÐÒÿÌÐÒÿÌÑÒÿ½ÁÃÿ¢¥¦ÿ~€ÿ\]^ÿSUUÿrstÿ¢¥§ÿjllÿ###ÿŠŽÿÊÍÏÿÌÐÒÿÌÐÒÿÌÐÒÿÍÐÒÿÍÐÒÿ»¾ÁÿŸ¡£ÿuvwÿUVWÿVWXÿxz{ÿ£¦¨ÿÃÆÈÿÍÐÓÿÍÐÓÿÍÐÓÿÍÐÓÿÍÐÓÿÍÑÓÿÈÌÎÿ®±³ÿŒÿ_abÿRTTÿcdeÿ‘”•ÿ²¶¸ÿÍÑÓÿÍÑÓÿÍÑÓÿÍÑÓÿáæèÿÿ_ZUÿ‹†‚ÿqjdÿmfaÿ,*(w^HECýrkeÿrkeÿrkeÿ^YTÿÜßâÿÎÑÕÿÏÒÕÿÏÒÕÿ{}~ÿ666ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿijkÿÐÓÕÿÐÓÕÿÐÓÕÿÐÓÕÿuwxÿ233ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ:;;ÿ²µ¶ÿÐÓÕÿÐÓÕÿÐÓÕÿÐÓÕÿÇÉËÿHHIÿ***ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ,,-ÿfggÿÑÓÖÿÑÓÕÿÐÓÕÿäçêÿÿa\Wÿztÿqjdÿmfaÿ+)'t^HECýrkeÿrkeÿrkeÿ^YTÿÞââÿÑÔÕÿÑÔÕÿÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ¿ÂÃÿÑÔÖÿÑÔÖÿÑÔÖÿuwxÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿÀÂÄÿÒÔÖÿÑÔÖÿ¿ÁÂÿ%%%ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ\\]ÿÒÕÖÿÒÕÖÿçëìÿÿ_YUÿŒ†‚ÿqjdÿmfaÿ,*(w^HECýrkeÿrkeÿrkeÿ^YTÿâãåÿÔÖ×ÿ€‚ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ^^_ÿžŸ¡ÿŒŽÿ&&&ÿ$$$ÿ%%%ÿ$$$ÿ$$$ÿqrsÿÓÖ×ÿÔרÿŒŽŽÿ$$$ÿ%%%ÿ$$$ÿ$$$ÿ^_`ÿ¢¤¥ÿ¢¤¤ÿGHHÿ$$$ÿ$$$ÿ$$$ÿ//0ÿËÍÏÿÒÔÖÿ>>>ÿ$$$ÿ$$$ÿ$$$ÿ000ÿ‘’ÿ¤¦§ÿ†‡ˆÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿŸ ÿÔ×Ùÿêìîÿÿc]Yÿ‡‚}ÿqjdÿmfaÿ/-+v''' ppp _IFCýrkeÿrkeÿrkeÿ^YTÿãåçÿÈÊÌÿ9::ÿ%%%ÿ%%%ÿ'''ÿŸ ¡ÿÕ×ÙÿÕ×Ùÿ999ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ001ÿËÍÎÿÏÑÒÿ000ÿ%%%ÿ%%%ÿ%%%ÿ“”•ÿרÚÿÖØÙÿÖØÙÿÖØÚÿYZZÿ)))ÿFFFÿš›œÿÖØÚÿ—™šÿ%%%ÿ%%%ÿ%%%ÿAAAÿÑÓÔÿÖÙÚÿÖØÚÿרÚÿ¥§§ÿ(((ÿ666ÿffgÿËÍÎÿ×ÙÚÿíïðÿÿd_Zÿ~xsÿqjdÿmfaÿ31/}hhhššš_JGDýrkeÿrkeÿrkeÿ^XTÿççèÿ³µµÿ)))ÿ%%%ÿ%%%ÿ[[\ÿÙÚÛÿÙÚÛÿÑÒÓÿ[[\ÿ%%%ÿ%%%ÿCCCÿ%%%ÿ%%%ÿ%%%ÿ¥¥¦ÿ­¯°ÿ&&&ÿ%%%ÿ%%%ÿmmnÿÙÛÜÿÙÛÜÿÙÛÜÿØÛÜÿÙÛÜÿÍÏÐÿ³µµÿÕ××ÿÙÛÜÿÚÛÜÿNNOÿ%%%ÿ%%%ÿ%%%ÿÑÓÔÿÚÛÜÿÙÛÜÿÙÛÜÿÚÛÜÿÚÛÜÿ»»¼ÿÇÈÉÿÚÛÜÿÚÛÜÿÚÛÜÿïðòÿÿa[Vÿytÿqjdÿmgaÿ1/-z ppp `GDBýrkeÿrkeÿrkeÿ^XTÿééêÿ¦§¨ÿ%%%ÿ%%%ÿ%%%ÿ©©ªÿÚÛÜÿÚÛÜÿÚÛÜÿÚÛÜÿbbbÿXYYÿÏÐÑÿ%%%ÿ%%%ÿ%%%ÿ‡‡ˆÿ—˜˜ÿ%%%ÿ%%%ÿ%%%ÿ¬¬­ÿÚÜÝÿÛÜÝÿÛÜÝÿÜÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿ%%%ÿ%%%ÿ%%%ÿ]]^ÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿñòóÿÿd^Yÿwpkÿrkeÿngaÿ/-+y _IFDürkeÿrkeÿrkeÿ^XTÿêìíÿ¦¦¦ÿ&&&ÿ&&&ÿ&&&ÿÉÊÊÿÝÞßÿÝÞßÿÝÞßÿÝÞßÿÛÜÝÿÓÔÕÿÝÞßÿ&&&ÿ&&&ÿ&&&ÿvvvÿ–——ÿ&&&ÿ&&&ÿ&&&ÿÃÄÄÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÝßßÿÞßßÿ%%%ÿ&&&ÿ&&&ÿz{{ÿÝßßÿÝßßÿÝßßÿÞßßÿÞßàÿÞßàÿÞßàÿÞßßÿÞßßÿÞßßÿÞßßÿôõöÿÿc]Xÿtmgÿrkeÿngaÿ/-+x_GDBýrkeÿrkeÿrkeÿ^XTÿíííÿ©©©ÿ&&&ÿ&&&ÿ&&&ÿ­­®ÿßßàÿßßàÿßßßÿßßàÿàßàÿßßàÿÔÔÕÿ&&&ÿ&&&ÿ&&&ÿ„……ÿ™™šÿ&&&ÿ&&&ÿ&&&ÿ°°°ÿßààÿßààÿßààÿßààÿßààÿßààÿßààÿßààÿàààÿßààÿ%%%ÿ&&&ÿ&&&ÿ___ÿàààÿàààÿàààÿáààÿàààÿàààÿàààÿáààÿáààÿáàáÿààáÿ÷÷÷ÿÿc]Xÿrkeÿrkeÿngaÿ/-+z ]JFDürkeÿrkeÿrkeÿ^XTÿððïÿºººÿ***ÿ&&&ÿ'''ÿaaaÿâââÿâââÿâââÿâââÿâââÿâââÿxxxÿ&&&ÿ&&&ÿ&&&ÿ©©©ÿ´³´ÿ(((ÿ&&&ÿ&&&ÿpppÿãããÿâããÿâããÿâããÿãããÿרØÿ©©©ÿãããÿãããÿãããÿPPPÿ&&&ÿ&&&ÿ&&&ÿÖÖÖÿãããÿãããÿãããÿãããÿãããÿ¼¼¼ÿáááÿãããÿãããÿãããÿùùùÿÿc]Xÿrkeÿrkeÿngaÿ/-+yaHEBýrkeÿrkeÿrkeÿ^XSÿòòñÿÖÖÖÿ:::ÿ'''ÿ'''ÿ,,,ÿ¬¬¬ÿáááÿãããÿãããÿâââÿ¶¶¶ÿ(((ÿ'''ÿ'''ÿ333ÿØØØÿÝÝÝÿ222ÿ'''ÿ'''ÿ'''ÿ¯¯¯ÿáááÿãããÿãããÿÛÛÛÿiiiÿ'''ÿ___ÿ¾¾¾ÿãããÿ£££ÿ'''ÿ&&&ÿ'''ÿFFFÿÓÓÓÿãããÿãããÿãããÿÇÇÇÿ'''ÿ(((ÿÿØØØÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ/-+z ^IFCürkeÿrkeÿrkeÿ^XSÿòòñÿãããÿÿ'''ÿ'''ÿ'''ÿ+++ÿQQQÿpppÿsssÿTTTÿ...ÿ'''ÿ'''ÿ'''ÿ|||ÿãããÿãããÿ™™™ÿ'''ÿ'''ÿ'''ÿ)))ÿQQQÿpppÿoooÿGGGÿ'''ÿ'''ÿ'''ÿ>>>ÿßßßÿÚÚÚÿAAAÿ'''ÿ'''ÿ'''ÿ<<<ÿaaaÿyyyÿ\\\ÿ...ÿ'''ÿ'''ÿ'''ÿµµµÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ/-+yaHECýrkeÿrkeÿrkeÿ^XSÿòòñÿãããÿãããÿlllÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿUUUÿãããÿãããÿãããÿãããÿmmmÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ...ÿ½½½ÿãããÿãããÿ···ÿ333ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿbbbÿßßßÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ0-+x———]]]b><:ûrkeÿrkeÿrkeÿ^XSÿòòñÿãããÿãããÿãããÿ¢¢¢ÿ'''ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ'''ÿ“““ÿßßßÿãããÿãããÿãããÿãããÿáááÿ¥¥¥ÿ***ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ555ÿÅÅÅÿãããÿãããÿãããÿãããÿÎÎÎÿ[[[ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿÿßßßÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ0-+xŠŠŠ UUUgGDBýrkeÿrkeÿrkeÿ^XSÿòòñÿãããÿãããÿãããÿãããÿãããÿ†††ÿYYYÿQQQÿNNNÿVVVÿvvvÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ………ÿYYYÿQQQÿOOOÿXXXÿŽŽŽÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ¼¼¼ÿfffÿSSSÿNNNÿSSSÿdddÿÅÅÅÿãããÿãããÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ0-+x _HECýrkeÿrkeÿrkeÿ^XSÿòòñÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿßßßÿÞÞÞÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿßßßÿßßßÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿáááÿÞÞÞÿàààÿãããÿãããÿãããÿãããÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ/-+x_HEBýrkeÿrkeÿrkeÿ^XSÿòòñÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ1.,y ~~~ ]HECürkeÿrkeÿrkeÿ^XSÿòòñÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ20.z::: ¨¨¨_HECýrkeÿrkeÿrkeÿ^XSÿòòñÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ/-+y TTT_IFCýrkeÿrkeÿrkeÿ^XSÿòòñÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ/-+z `HECýrkeÿrkeÿrkeÿ^XSÿòòñÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ1/-{$$$HHH^HECýrkeÿrkeÿrkeÿ^XSÿòòñÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ=;9…xxx$––– ^HECýrkeÿrkeÿrkeÿ^XSÿòòñÿãããÿãããÿÝÝÝÿÑÑÑÿÒÒÒÿÐÐÐÿáááÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿÚÚÚÿÒÒÒÿÒÒÒÿÑÑÑÿàààÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿÐÐÐÿÒÒÒÿÒÒÒÿÒÒÒÿÒÒÒÿÒÒÒÿÒÒÒÿÒÒÒÿÒÒÒÿÑÑÑÿÝÝÝÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ:86ooo ———#^HECýrkeÿrkeÿrkeÿ^XSÿòòñÿãããÿãããÿ¯¯¯ÿ888ÿ;;;ÿ222ÿÕÕÕÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ“““ÿ;;;ÿ;;;ÿ:::ÿÍÍÍÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ222ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ444ÿ­­­ÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ0-+{ ???^HECýqjdÿrkeÿrkeÿ^XSÿòòñÿãããÿãããÿ©©©ÿ+++ÿ+++ÿ+++ÿÔÔÔÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿŒŒŒÿ+++ÿ+++ÿ+++ÿÊÊÊÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ¨¨¨ÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ/-+x^HEBýŒ…ÿngaÿrkeÿ^XSÿòòñÿãããÿãããÿªªªÿ,,,ÿ,,,ÿ,,,ÿÔÔÔÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿŒŒŒÿ,,,ÿ,,,ÿ,,,ÿËËËÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ£££ÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ0-+z^HECýŠƒ}ÿohbÿrkeÿ^XSÿòòñÿãããÿãããÿªªªÿ,,,ÿ,,,ÿ,,,ÿÔÔÔÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿŒŒŒÿ,,,ÿ,,,ÿ,,,ÿËËËÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ,,,ÿ,,,ÿ,,,ÿ{{{ÿÏÏÏÿÊÊÊÿÊÊÊÿÊÊÊÿÊÊÊÿÉÉÉÿÛÛÛÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ/-+w^HEBý‹„~ÿngaÿrkeÿ^XSÿòòñÿãããÿãããÿªªªÿ,,,ÿ,,,ÿ,,,ÿÖÖÖÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿŒŒŒÿ,,,ÿ,,,ÿ,,,ÿËËËÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ,,,ÿ,,,ÿ,,,ÿˆˆˆÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ0-+z^GDAýˆ{ÿunhÿrkeÿ^XSÿòòñÿãããÿãããÿªªªÿ,,,ÿ---ÿ---ÿ¢¢¢ÿ«««ÿªªªÿªªªÿªªªÿªªªÿ¼¼¼ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿÿ---ÿ,,,ÿ---ÿËËËÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ,,,ÿ---ÿ---ÿkkkÿ¯¯¯ÿªªªÿªªªÿªªªÿªªªÿ§§§ÿÝÝÝÿãããÿãããÿúúúÿÿc]Xÿrkeÿrkeÿngaÿ/-+x`HECý†ÿngaÿrkeÿ^XTÿññðÿâââÿâââÿ©©©ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿcccÿáááÿâââÿâââÿâââÿâââÿâââÿâââÿŒŒŒÿ---ÿ---ÿ---ÿÊÊÊÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿÐÐÏÿââáÿââáÿùùøÿÿc]Xÿrkeÿrkeÿngaÿ0-+z `IFDý…}vÿpicÿrkeÿ^XTÿïïîÿáááÿáááÿ©¨©ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿdddÿàààÿáááÿáááÿáááÿááàÿááàÿááàÿŒŒŒÿ---ÿ---ÿ---ÿÉÉÉÿáááÿááàÿááàÿááàÿááàÿááàÿááàÿááàÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿÐÐÏÿááàÿàáßÿ÷÷öÿÿc]Xÿrkeÿrkeÿngaÿ/-+x———*** bJGDý…}vÿpicÿrkeÿ^XTÿíììÿßÞÞÿßßÞÿ¨¨¨ÿ000ÿ000ÿ000ÿyyyÿ~~~ÿÿ~~}ÿ~~~ÿ~~~ÿœœÿßÞÞÿßÞßÿßÞÞÿßÞÞÿßÞÞÿßÞßÿßÞÞÿŒŒŒÿ///ÿ///ÿ...ÿÈÇÇÿßÞÞÿßÞÞÿßÞÞÿßÞÞÿßÞÞÿßÞÞÿßÞÞÿßÞÞÿ...ÿ///ÿ///ÿVVUÿ~~ÿ}||ÿ}||ÿ}||ÿ}||ÿtttÿÖÕÔÿßÞÝÿßÞÝÿõôóÿÿc]Xÿrkeÿrkeÿngaÿ/-+z~~~333 _GEBü‡~xÿoicÿrkeÿ^XTÿìëêÿÞÜÜÿÞÝÜÿ¨¨¨ÿ222ÿ222ÿ222ÿÚÙØÿÞÝÝÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÛÿÞÝÜÿÞÝÛÿÞÝÜÿÞÝÜÿŒŒÿ222ÿ222ÿ111ÿÊÉÉÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÛÿ111ÿ000ÿ000ÿŽÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÝÝÛÿôóñÿÿc]Xÿrkeÿrkeÿngaÿ/-+x`IFDýqjdÿrkeÿrkeÿ^XTÿêéèÿÜÛÚÿÜÛÙÿ¨§¦ÿ666ÿ555ÿ555ÿÂÁÁÿËÊÉÿÊÉÉÿÊÉÇÿÊÊÈÿÊÉÇÿÎÎÌÿÛÚØÿÜÛÙÿÜÛÙÿÛÚØÿÊÉÇÿËÊÈÿÉÉÇÿ†…„ÿ444ÿ444ÿ444ÿ¹¸·ÿÊÉÇÿÉÈÆÿÑÐÎÿÜÛÙÿÜÚÙÿÜÚÙÿÜÛÙÿÜÚÙÿ333ÿ333ÿ333ÿ€~ÿÍËÊÿÉÈÆÿÉÈÇÿÉÈÆÿÉÈÇÿÇÆÅÿ×ÕÔÿÜÚÙÿÜÚÙÿòðïÿÿc]Xÿrkeÿrkeÿngaÿ/-+z ^GDAü–‰ÿngaÿrkeÿ^XTÿêçæÿÚÙ×ÿÛÚØÿ¨§¦ÿ888ÿ888ÿ999ÿ777ÿ888ÿ777ÿ777ÿ777ÿ777ÿVVUÿ××ÕÿÛÚØÿÛÚØÿÓÒÐÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ888ÿ777ÿ777ÿ777ÿfeeÿÛÙØÿÛÚØÿÛÙØÿÛÙØÿÛÚØÿ777ÿ777ÿ666ÿ555ÿ666ÿ666ÿ666ÿ666ÿ555ÿ666ÿ¤£¢ÿÛÙØÿÚØ×ÿðîíÿÿc]Xÿrkeÿrkeÿngaÿ/-+z aJGDývoiÿrkeÿrkeÿ^YTÿèåãÿÙ×ÕÿÙ×Õÿ¨¦¥ÿ;;;ÿ;;;ÿ<<<ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ``_ÿÖÔÒÿÙ×ÕÿÙ×ÕÿÒÐÎÿ>>>ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ999ÿ999ÿrqpÿÙ×ÕÿÙ×ÕÿÙ×ÕÿÙ×ÕÿÙ×Õÿ999ÿ:::ÿ888ÿ999ÿ888ÿ999ÿ999ÿ999ÿ:::ÿ888ÿª©§ÿÙ×ÕÿÙ×Õÿîíëÿÿc]Xÿrkeÿrkeÿngaÿ531}UUU \FCAü†€ÿngaÿrkeÿ^YTÿæãàÿ×ÕÓÿØÖÔÿ«©©ÿGGGÿJJJÿJJJÿJIIÿJIIÿJIIÿJIIÿJIIÿJIIÿlkjÿÕÓÑÿØÖÔÿØÖÔÿÑÏÎÿLLLÿJIIÿJIIÿJIIÿJIIÿJIIÿJIIÿJIIÿJIIÿFEEÿ||{ÿØÖÔÿØÖÔÿØÖÔÿØÖÔÿØÖÔÿBBAÿIHHÿIHHÿIHHÿIHHÿIHHÿIHHÿIHHÿHHGÿCBBÿ¯­«ÿØÖÔÿ×ÕÓÿíëèÿÿc]Xÿrkeÿrkeÿngaÿ20.{*** ~~~~~~aKHEý—Šÿmf`ÿrkeÿ^YTÿåâÞÿÖÓÑÿÖÓÑÿÑÏÍÿÇÆÃÿÈÆÃÿÈÆÃÿÈÅÃÿÈÆÃÿÈÆÃÿÈÅÃÿÈÆÃÿÈÆÃÿËÈÆÿÖÓÑÿÖÔÑÿÖÔÑÿÕÓÐÿÇÄÂÿÈÆÃÿÈÅÃÿÈÆÃÿÈÅÃÿÈÅÃÿÈÆÃÿÈÅÃÿÈÅÃÿÇÅÃÿÍËÈÿÖÓÑÿÖÓÑÿÖÓÐÿÖÓÑÿÖÓÒÿÆÃÁÿÈÆÃÿÈÅÃÿÈÅÃÿÈÅÃÿÈÅÂÿÈÅÃÿÈÅÂÿÈÅÃÿÇÄÃÿÒÏÍÿÖÓÑÿÖÓÐÿëèæÿÿc]Xÿrkeÿrkeÿngaÿ0-+x~~~^HECü—‰ÿmgaÿrkeÿ^YTÿãàÜÿÕÓÏÿÕÓÐÿÕÒÐÿÕÒÐÿÕÒÐÿÕÓÐÿÕÒÐÿÕÒÐÿÕÓÐÿÕÒÐÿÕÒÐÿÕÒÐÿÕÒÐÿÕÒÏÿÕÒÏÿÕÒÐÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÐÿÕÒÏÿÔÒÏÿÕÒÎÿÕÒÏÿÕÒÏÿÕÒÎÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÔÑÏÿêæãÿÿc]Xÿrkeÿrkeÿngaÿ0-+xaLHFý˜‘‹ÿmf`ÿrkeÿ^YTÿáÞÙÿÓÐÌÿÓÐÎÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÌÿÓÐÍÿÓÐÌÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÌÿÒÐÍÿÒÐÍÿÓÐÌÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÍÿÒÐÍÿÓÐÌÿÓÐÍÿÒÐÌÿÒÐÌÿÒÐÍÿÒÏÌÿÒÐÌÿÓÐÍÿÒÏÌÿÒÐÌÿÒÐÌÿÒÐÌÿÒÐÌÿÒÐÌÿÒÐÌÿÒÏÌÿÒÐÌÿÒÏÌÿÒÏÌÿçäàÿÿc]Xÿrkeÿrkeÿngaÿ0-+x ]JGEü–‰ÿmf`ÿrkeÿ^XTÿÞÛØÿÒÏËÿÒÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÎËÿÑÏËÿÑÏËÿÑÏÊÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏÊÿÑÏÊÿÒÏËÿÑÏÊÿÑÏËÿÑÏÊÿÑÏÊÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏËÿÑÏÊÿÑÏËÿÑÏËÿÒÏËÿÑÏÊÿÑÏËÿÑÏÊÿÑÏËÿÑÏËÿÑÏËÿÑÏÊÿÑÏËÿÑÏËÿÑÏËÿÑÎËÿÒÏÊÿäáÝÿÿb\Xÿrkeÿrkeÿmgaÿ0-+x]JGDüª¤Ÿÿohbÿrkeÿf`[ÿusqÿÝÙÕÿÝÙÕÿÝÙÔÿÝÙÕÿÞÙÕÿÝÙÕÿÞÚÖÿÝÙÔÿÝÙÕÿÝÙÕÿÝÙÕÿÝÙÕÿÝÙÕÿÝÙÕÿÝÙÕÿÝÙÕÿÝÙÕÿÝÙÕÿÝÙÕÿÜÙÕÿÝÙÕÿÝÙÕÿÝÙÕÿÝÙÕÿÝÙÔÿÝÙÕÿÝÙÕÿÝÙÕÿÝÙÕÿÜÙÕÿÜÙÔÿÜÙÕÿÜÙÕÿÜÙÕÿÝÙÕÿÝÙÔÿÜÙÕÿÜÙÕÿÜÙÕÿÜÙÕÿÜÙÕÿÜÙÕÿÜÙÕÿÜÙÕÿÜÙÕÿÛØÔÿutsÿ-,,ÿ_ZVÿrkeÿrkeÿmgaÿ0-+týýý I:86øˆƒÿŠƒ~ÿrkdÿqjdÿf`[ÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿ^YTÿb\Wÿc^Yÿohcÿrkeÿrkeÿmf`þ-+)`*,,+åohbÿª¤Ÿÿ{uÿqjdÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿga\ú$" 7…DB?þyqkÿŸ˜’ÿvngÿohbÿmf`ÿkd^ÿkd^ÿmf`ÿpicÿumfÿvngÿskeÿha[ÿib\ÿib\ÿib\ÿjc]ÿohaÿrkeÿogaÿngaÿohbÿohbÿqjdÿqjdÿpicÿohbÿohbÿpicÿpicÿmf_ÿpicÿpicÿpicÿpicÿpicÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿqjdÿqjdÿohbÿohbÿohbÿohbÿohbÿohbÿngaÿrkeÿrkeÿrkeÿrkeÿFB?°#£DB@þc]Xÿysmÿˆ€yÿ•މÿ¨¢ÿ«¥ ÿ´®ªÿ³®©ÿ®©¥ÿ¬§£ÿ­¨£ÿºµ±ÿºµ°ÿ¹³¯ÿ¹µ±ÿ­¦¢ÿ£˜ÿ¢œ–ÿŸ™“ÿ‰ƒÿކ€ÿŒ…~ÿŠ‚{ÿ‰‚{ÿ…}vÿކ€ÿ†ÿ…}vÿ…|uÿ ™”ÿƒ{tÿ…~vÿ…}vÿ†~wÿ}voÿqjdÿrkeÿrkeÿrkeÿrkeÿslfÿ}vpÿyrkÿŒ…~ÿŠƒ}ÿŠƒ}ÿŠƒ}ÿŠƒ}ÿŠƒ}ÿ‘Š„ÿohbÿpjdÿhb]ÿC@=Ä#***~~~~~~#ƒ--,ã986÷GEBüJGEýJGEýKHEýKHEýKHEýLIFýIFDüKHFýJGDüNKHýJGDüKHEýHEBüIFCýHEBýIFCýIFCüJGDýIFCýJGDýJGDýJGDýJGEýJGEýJGDýJGDýJFDýJGDýJGDýKHEýIFCýJGDüGDBýIFCüHEBýIFCüGDAýJFDüGDAýJGDüFDAýIFCüGDBýJGDýIFCýHEBýGDAüEB@ù975ç%$##~~~———* IZ]^^^__ ]_ ]a ]a \a ^_ ]__`^^^^^^^^___ ]_ ]a \a ]a ]_ ]__^^Z I*ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøððàààààààààààààààààààààààààààààààààààààààààààààààààààààðøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ( 444444444444444444444444444444444444444444444444gggddddddddddddddddddddddddddddddddddddcccfff444dddÁÂÂö÷øö÷øö÷øö÷øö÷øýýýö÷øö÷øö÷øö÷øùúû÷øùccc444cccÄÄÄÚÛÜBCDCDDÝÞÞÝÞÞCDDÚÛÜÚÛÜÚÛÜCDDßàá÷øøccc444cccÃÂÃEEEÝÞÞEEFÝÞÞEEFÜÝÝEEFÜÝÝEEFÜÝÝKKK÷øøccc444cccÄÄÄGGGÞÞßGGGÞßßGGGßààßààßààGGGßààäåå÷øøccc444cccÅÅÅHHHãããHHHãããHHHãããHHHãããHHHãããNNN÷øøccc444cccÅÅÅãããHHHãããããããããHHHãããããããããHHHåææ÷øøccc444cccÅÅÅããããããããããããããããããããããããããããããåææ÷øøccc444cccÅÅÅHHHããããããããããããHHHããããããHHHHHHNNN÷øøccc444cccÅÅÅHHHHHHHHHããããããHHHããããããHHHãããßàà÷øøccc444cccÄÄÄGGGÞÞÞÞÞÞÞÞÞÞÞÞGGFÞÞÞÞÞÞGGFGGFMLL÷øøccc444cccÃÂÂEEDEEDEEDØ×ÕFEEEEEEEDØÖÕFEEEEEKKJ÷øøccc444cddÅÄÄÂÁÁÂÁÀÂÁÀÂÁÀÂÁÀÂÁÀÂÁÀÂÁÀÂÁÀÂÁÀÄÃÂÀÀÁccc444gggdddccccccccccccccccccccccccccccccddddddggg444kkkkkknnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnkkk(`À ~~~~~~~~~~~~  ( 5 ; > ? > ? A ? ? A = ? A ? ? @ > ? @ > @ A ? ? > > > > > > > > > ? ? A @ ? ? ? ? ? ? @ @ ? ? ? ? @ @ ? ? ? ? ? @ @ @ ? ? > > > > > > > > > = ; 4% .2/-g:75œ;86¼<97Ñ<97Ø<97Ø<:8Ý<:8Û<97Ú=:8Þ<:8Ü<97Ù=:8Ý<:8Û<97Ù=:8Ý<:8Ü<97Ú<:8Ý<:8Û<97Ú<:8Ý<:8Ü<:8Ü<:8Ý<:8Û<:8Û<97Û<:8Û<:8Û<:8Û<:8Û<:8Û<:8Û<:8Û<97Û<:8Û<:8Û<:8Ý<:8Ü<:8Ü<:8Ý<97Ú<:8Û<:8Ý<97Û<:8Ü=:8Ý<97Ù<:8Û=:8Ý<97Ù<:8Ü=:8Þ<97Ú<:8Û<:8Ý<97Ú<:8Û<:8Ý<:8Ü<:8Ý<:8Û<:8Û<97Û<:8Û<:8Û<:8Û<:8Û<:8Û<:8Û<:8Û<:8Û=:8Ù<97Ö<97Ê642¬f8  953e]XSÜic^üke_þke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿke_ÿjd^ÿVQNø.-,À^$ 852xha\õrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿmgaÿTPLîv$ )'%`a\Wðrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿslfÿumhÿqjdÿpicÿpicÿrkeÿqjdÿqjdÿpicÿrkeÿskfÿslgÿwpkÿztnÿunhÿrkfÿrkeÿohbÿvpjÿ{vÿƒ~yÿ‰ƒÿƒ}xÿƒ}xÿ}wrÿ}wqÿrkeÿqicÿrkeÿpjdÿQLIì \ 1OKGÎpicÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿqjdÿytÿІÿˆƒÿމ…ÿІÿxqlÿ€zuÿ‚}wÿˆ„ÿwpkÿyrmÿŠ…€ÿŠ…ÿ™•ÿ™–’ÿ˜”ÿ™”ÿމ…ÿ£Ÿœÿ­ª§ÿ±®«ÿ­ª§ÿ¯¬©ÿ¨¥¢ÿ°­ªÿ œ˜ÿš–’ÿ„}yÿpicÿrkeÿpicÿLHD×8g`[Vúrkeÿrkeÿrkeÿrkeÿqjdÿmgaÿhb]ÿke_ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿke_ÿkd^ÿkd^ÿkd^ÿkd^ÿlf`ÿke_ÿke_ÿkd^ÿlf`ÿlf`ÿke_ÿkd^ÿkd^ÿkd^ÿkd^ÿkd^ÿkd^ÿjc^ÿjc^ÿjc^ÿjc^ÿjc^ÿjc^ÿib\ÿe_Zÿ…€{ÿ²¯¬ÿ‚}wÿrkeÿrkeÿlf`þ>;8Ž!+)(›ke_ÿrkeÿrkeÿrkeÿrkeÿngaÿLIFÿ755ÿ?=;ÿDB@ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDA?ÿDB@ÿ@><ÿ765ÿDA>ÿ†|ÿ™•ÿyrmÿrkeÿrkeÿUPLÎ & -753Åpicÿrkeÿrkeÿrkeÿrkeÿjd_ÿ<:9ÿ,,,ÿ&&&ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ$$$ÿ%$$ÿ$$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ%$$ÿ&&&ÿ,,,ÿ---ÿ654ÿtojÿž›—ÿxqlÿrkeÿqjdÿUPLà 4  6<97Örkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿA?=ÿBBBÿ´·¹ÿÇÍÒÿÇÍÑÿÇÍÑÿÇÍÑÿÇÍÑÿÇÌÑÿÇÍÑÿÆÍÑÿÇÍÑÿÇÍÑÿÇÍÑÿÆÍÑÿÇÍÑÿÇÍÑÿÇÍÑÿÇÎÑÿÇÍÑÿÈÍÑÿÇÍÑÿÈÎÑÿÈÍÑÿÇÍÑÿÇÎÑÿÈÍÑÿÇÍÑÿÇÍÑÿÇÍÑÿÇÎÑÿÈÎÑÿÈÍÑÿÇÎÑÿÈÎÑÿÈÎÑÿÈÎÑÿÈÎÑÿÈÎÑÿÈÎÑÿÈÎÒÿÈÎÒÿÈÎÑÿÈÎÒÿÈÎÒÿÈÎÒÿÈÍÑÿÈÎÒÿÈÎÒÿÈÎÑÿÈÎÒÿÈÎÒÿÈÎÒÿÈÎÒÿÈÎÒÿÈÎÒÿÈÎÒÿÈÎÒÿÈÎÒÿÈÎÒÿÈÎÒÿÈÎÒÿÇÎÒÿÉÎÒÿÈÎÒÿÉÎÒÿÈÎÒÿÈÎÒÿÈÎÒÿÈÎÒÿÈÏÓÿ´·ºÿBBBÿ,,,ÿ?=;ÿtohÿ›—“ÿ~xsÿrkeÿqjdÿUPLè <  9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ?=:ÿ³¶¹ÿÃÉÍÿÀÆÊÿ¿ÅÊÿÀÆÊÿÀÆÉÿÀÆÊÿÀÆÉÿÀÅÊÿ¿ÆÊÿÀÆÊÿ¿ÆÊÿ¿ÆÊÿÀÆÊÿÀÆÊÿ¿ÅÊÿÀÅÊÿ¿ÅÊÿÀÆÊÿÀÆÊÿÀÅÊÿÀÆÊÿÀÆÊÿÁÆÊÿÀÅÊÿÀÆÊÿÀÆÊÿÀÆÊÿÀÆÊÿÁÆÊÿÀÅÊÿÁÆÊÿÀÆÊÿÁÆÊÿÀÆÊÿÀÆÊÿÀÆÊÿÀÆÊÿÀÇÊÿÀÆÉÿÀÇÉÿÁÇÊÿÁÆÉÿÀÇÉÿÀÇÉÿÁÇÊÿÁÇÊÿÀÆÉÿÁÇÊÿÁÆÊÿÀÆÉÿÁÇÊÿÁÇÊÿÁÇËÿÂÆËÿÁÆÊÿÁÆÊÿÁÆÊÿÁÆÊÿÂÆËÿÂÆËÿÁÇÊÿÁÇÊÿÂÆËÿÁÆÊÿÂÆÊÿÁÆËÿÂÆËÿÂÆËÿÁÆÊÿÅÊÏÿµ¸ºÿ&&%ÿDA?ÿpkeÿ‘Œˆÿ}wqÿrkeÿqjdÿUPLë?  9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ>;9ÿÉÎÓÿÂÇËÿÁÇÊÿÂÇËÿÁÇÊÿÂÇËÿÁÇÊÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÇËÿÂÈËÿÂÈËÿÂÇËÿÂÇËÿÃÈËÿÂÈËÿÂÇËÿÂÈËÿÂÈËÿÃÈËÿÂÈËÿÂÇËÿÃÈËÿÃÈËÿÃÈËÿÂÈËÿÃÈÌÿÂÈËÿÃÈËÿÃÈËÿÂÇËÿÃÈÌÿÂÈËÿÃÈÌÿÃÈÌÿÂÈËÿÂÈËÿÃÈÌÿÃÈÌÿÃÇËÿÌÑÖÿ$$$ÿCA?ÿqkeÿ‹†ÿ}wrÿrkeÿqjdÿUPLëA???~~~ 9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ>;9ÿÉÏÒÿÂÈËÿÃÈÌÿÃÈÌÿÂÈËÿÃÈËÿÂÈËÿÃÈÌÿÂÈËÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÃÈÌÿÄÈÌÿÄÈÌÿÃÈÌÿÃÈÌÿÄÈÌÿÃÈÌÿÄÈÌÿÄÈÌÿÄÉÌÿÄÉÌÿÄÈÌÿÄÈÌÿÄÉÌÿÄÉÌÿÃÈÌÿÄÉÌÿÄÉÌÿÄÉÌÿÃÈÌÿÄÉÌÿÄÉÌÿÄÉÌÿÄÉÌÿÄÉÌÿÄÉÌÿÄÉÍÿÄÉÌÿÄÉÌÿÄÉÍÿÄÉÌÿÄÉÌÿÄÉÌÿÄÉÌÿÄÉÌÿÄÉÍÿÄÉÌÿÄÉÌÿÄÉÍÿÍÒÕÿ$$$ÿCA?ÿpkeÿ‘Œ‡ÿ}wqÿrkeÿqjdÿUPLëB???~~~ 9:75Ùrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ>;9ÿËÑÔÿÅÊÍÿÅÊÌÿÅÉÌÿÅÊÌÿÄÊÍÿÅÊÍÿÅÊÌÿÄÊÌÿÄÊÍÿÄÊÌÿÄÊÍÿÅÊÍÿÄÊÌÿÄÊÌÿÄÊÍÿÄÊÍÿÄÊÍÿÄÊÍÿÅÊÍÿÅÊÍÿÄÊÍÿÄÊÍÿÄÊÍÿÄÊÍÿÅÊÍÿÅÊÍÿÅÉÍÿÅÊÍÿÆÊÍÿÆÊÎÿÅÉÍÿÅÊÍÿÅËÎÿÅÊÍÿÅÊÍÿÄÉÍÿÅÊÍÿÄÊÍÿÅÊÍÿÆÊÎÿÅÊÍÿÆÊÍÿÆÊÍÿÆËÎÿÆÊÍÿÅÊÍÿÅÊÍÿÆËÎÿÆÊÎÿÅÊÎÿÆÊÎÿÆËÎÿÅËÍÿÆÊÎÿÅÊÎÿÆËÎÿÆÊÎÿÆÊÍÿÆËÍÿÅÊÍÿÆËÍÿÆËÎÿÅÊÎÿÅÊÎÿÅÊÎÿÅÊÎÿÅÊÍÿÆËÍÿÅÊÎÿÆÊÍÿÎÔ×ÿ$$$ÿDA?ÿkc^ÿš•’ÿwqkÿrkeÿqjdÿUPLê ?  9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ>;9ÿÍÒÕÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÇËÎÿÆËÎÿÆËÎÿÇËÎÿÆËÎÿÇËÎÿÇËÎÿÆËÎÿÆËÎÿÆËÎÿÆËÎÿÇËÎÿÆËÎÿÇËÎÿÆËÎÿÆËÎÿÇËÎÿÇËÎÿÆËÎÿÇËÎÿÇËÎÿÆËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇËÎÿÇÌÏÿÇËÎÿÇËÎÿÇÌÏÿÇÌÏÿÇËÎÿÇËÎÿÇËÎÿÇÌÏÿÇËÎÿÇËÎÿÇËÎÿÇÌÏÿÇÌÏÿÇËÎÿÇÌÏÿÇÌÏÿÇÌÏÿÇÌÏÿÇËÏÿÇÌÏÿÇÌÏÿÈÌÏÿÇËÏÿÇÌÏÿÈÌÏÿÇÌÏÿÇÌÏÿÇÌÏÿÈÌÏÿÇÌÏÿÇÌÏÿÇÌÏÿÑÕØÿ$$$ÿDA?ÿkd^ÿ‹‡ÿrkeÿrkeÿqjdÿTPLê ?  9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ>;9ÿÏÓÖÿÇÌÏÿÇÌÏÿÇÌÏÿÇÌÏÿÇÌÏÿÇÌÏÿÈÌÏÿÇÌÏÿÈÌÏÿÇÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÏÿÈÌÐÿÈÌÏÿÉÍÐÿÈÌÐÿÈÌÏÿÉÌÐÿÈÍÐÿÈÍÐÿÈÌÏÿÈÍÐÿÉÍÐÿÈÍÐÿÈÌÏÿÈÌÐÿÈÍÐÿÉÌÐÿÈÌÐÿÉÌÐÿÈÌÐÿÈÍÐÿÈÍÐÿÉÍÐÿÉÍÐÿÈÍÐÿÉÌÐÿÉÍÐÿÉÍÐÿÒÖÚÿ$$$ÿDA?ÿkd^ÿމ…ÿpicÿrkeÿqjdÿUPLë @  9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ>;9ÿÏÔ×ÿÉÍÐÿÉÎÑÿÊÍÐÿÉÍÐÿÊÎÑÿÉÍÑÿÉÍÑÿÊÎÑÿÉÎÑÿÉÎÑÿÉÎÑÿÉÍÑÿÊÎÑÿÊÎÑÿÉÎÑÿÉÎÑÿÊÎÑÿÉÎÐÿÈÍÐÿÊÎÑÿÊÎÑÿÊÎÑÿÉÎÑÿÊÎÑÿÊÎÑÿÉÎÑÿÊÎÑÿÉÎÑÿÉÎÐÿÊÎÑÿÊÎÑÿÊÎÑÿÊÎÐÿÊÎÐÿÊÎÐÿÉÍÐÿÉÍÐÿËÎÐÿÉÍÐÿÊÎÐÿÊÎÐÿÊÎÐÿÊÎÑÿËÎÐÿÉÎÐÿÊÎÐÿÊÎÐÿËÏÑÿÊÎÐÿÊÎÐÿÊÎÐÿÊÎÐÿËÎÑÿÊÎÐÿËÎÑÿËÍÐÿËÏÑÿËÏÑÿÊÎÐÿËÎÑÿËÏÑÿËÏÑÿÊÎÐÿÊÎÑÿÊÎÑÿËÎÑÿËÎÑÿËÏÑÿÊÏÑÿËÏÑÿÔØÚÿ$$$ÿDA?ÿkd^ÿމ…ÿpicÿrkeÿqjdÿUPLê ? ýýý~~~ 9:86Ùrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ>;9ÿÒÖØÿËÏÑÿËÏÑÿËÎÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÑÿËÏÒÿËÏÑÿmopÿ¿ÃÅÿËÏÑÿËÏÒÿËÏÑÿËÏÑÿÌÏÒÿËÏÑÿËÏÑÿËÏÒÿËÏÑÿËÏÑÿËÏÒÿÌÏÒÿËÏÒÿËÏÒÿËÏÒÿËÏÒÿÌÏÒÿÌÏÒÿËÏÒÿËÏÒÿÌÏÒÿËÏÒÿÌÏÒÿÌÏÒÿËÐÒÿÌÏÒÿËÏÒÿËÏÒÿÌÏÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÏÒÿÌÏÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÕØÛÿ$$#ÿDA?ÿkd^ÿˆ„ÿpicÿrkeÿqjdÿTPLê ? ýýý~~~ :975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ>;9ÿÒÖÚÿÌÐÒÿÌÐÒÿÌÏÒÿÌÐÒÿÌÏÒÿÌÐÒÿÌÐÒÿÌÏÒÿÆÉÌÿ½ÀÂÿ¥¨ªÿ€‚„ÿprsÿghiÿsuvÿ“”ÿ·»½ÿÃÇÉÿwyzÿ###ÿ788ÿ®±³ÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÌÐÒÿÍÐÓÿÌÐÒÿÇÊÍÿ½ÀÂÿ¡¤¦ÿ}ÿjlmÿhjkÿtuwÿ’”–ÿ·º½ÿÄÇÊÿÍÐÓÿÍÐÓÿÍÐÓÿÍÐÓÿÍÐÓÿÍÐÓÿÍÐÓÿÍÐÓÿÍÐÓÿÍÑÓÿÍÐÓÿÍÑÓÿÇÊÌÿ½ÁÂÿž¡£ÿ{}~ÿjllÿhjkÿuwxÿ–™šÿº½¿ÿÅÉÊÿÍÑÓÿÍÑÓÿÍÑÓÿÍÑÓÿÍÑÓÿÍÑÓÿÖÛÝÿ$##ÿDA?ÿkd^ÿ›—“ÿohbÿrkeÿqjdÿUPLì A  9:75Ùrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ>;9ÿÕØÛÿÎÑÔÿÍÑÔÿÍÑÔÿÍÒÔÿÍÒÔÿÎÒÔÿÎÑÔÿ’•—ÿWXYÿ,,-ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ###ÿ<<=ÿ(((ÿ$$$ÿ$$$ÿ112ÿ‘“”ÿÎÒÔÿÏÒÔÿÎÒÔÿÎÒÔÿÏÒÔÿÏÒÔÿÏÒÓÿÏÒÔÿ–˜™ÿYZ[ÿ**+ÿ$$$ÿ$$$ÿ###ÿ###ÿ###ÿ###ÿ###ÿKLLÿÿÏÒÔÿÎÑÔÿÏÒÕÿÎÒÔÿÎÒÕÿÏÒÔÿÎÒÔÿÏÒÕÿÏÒÕÿÎÒÕÿŽ“ÿTUVÿ(((ÿ###ÿ###ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ###ÿPQRÿ”–˜ÿÏÒÕÿÏÒÕÿÏÒÕÿÎÒÕÿÏÒÕÿØÛÞÿ$##ÿDA?ÿlf`ÿtmhÿrkeÿrkeÿqjdÿUPLë @  9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ>;9ÿÖÙÝÿÏÒÕÿÏÒÕÿÏÒÕÿÐÓÕÿÏÒÕÿÀÃÆÿJKLÿ(((ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿÀÃÄÿÐÓÕÿÐÓÕÿÐÓÕÿÐÓÕÿÐÓÕÿËÎÐÿWXXÿ(((ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿWXYÿÑÔÕÿÐÓÕÿÐÓÕÿÐÓÕÿÐÓÕÿÑÓÕÿÐÓÕÿÂÅÇÿMMNÿ'''ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ%%%ÿ`abÿÑÓÕÿÑÔÕÿÑÔÕÿÑÓÕÿÚÝßÿ$##ÿDA?ÿke_ÿƒ~xÿqjdÿrkeÿqjdÿUPLé ?  9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ>;9ÿØÛÝÿÑÔÕÿÑÔÕÿÑÔÕÿÑÔÕÿ¹¼½ÿ788ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿšœÿÑÔÖÿÑÔÖÿÑÔÖÿÑÔÖÿÑÔÖÿÈËÍÿBBCÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿHIIÿÎÐÒÿÑÔÖÿÑÔÖÿÑÔÖÿÒÕÖÿÁÄÆÿ9::ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿUVVÿÐÓÔÿÒÕÖÿÒÕÖÿÜßàÿ###ÿDA?ÿkd^ÿމ…ÿpicÿrkeÿqjdÿUPLìA  9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ=;9ÿÚÝÞÿÒÕÖÿÓÕÖÿÒÕÖÿ¿ÁÂÿ666ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ%%%ÿ(((ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ’””ÿÒÖ×ÿÒÖ×ÿÓÖØÿÒÖ×ÿÇËËÿEFFÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ###ÿ)))ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿghiÿÐÒÔÿÓÖØÿÓÖØÿÄÆÉÿ<<=ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ###ÿ)))ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿxzzÿÑÔÖÿÔרÿÝàáÿ###ÿDA?ÿkd^ÿމ…ÿpicÿrkeÿqjdÿUPLëA222??? 9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ=;9ÿÜÞàÿÔÖØÿÔÖØÿÔÖØÿLMMÿ%%%ÿ$$$ÿ$$$ÿ$$$ÿ%%%ÿ$$$ÿ\\]ÿ«¬®ÿÁÃÅÿÅÇÉÿCDDÿ$$$ÿ$$$ÿ$$$ÿ%%%ÿ$$$ÿ%%%ÿ$$$ÿ'''ÿ¶·¸ÿÔרÿÕרÿÕרÿrrsÿ$$$ÿ$$$ÿ%%%ÿ$$$ÿ%%%ÿ$$$ÿKKLÿ¡¢¤ÿÃÆÆÿÄÆÆÿ·¹»ÿ]^^ÿ$$$ÿ$$$ÿ$$$ÿ%%%ÿ$$$ÿ$$$ÿ¦§©ÿÕ×ÙÿÕ×Ùÿ^__ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿSSTÿ§©ªÿÃÅÇÿÃÅÇÿ²´µÿSSTÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ###ÿ¸¹»ÿÕ×Ùÿßáãÿ###ÿCA?ÿoicÿŠ…€ÿpicÿrkeÿqjdÿUPLêC%%%sss ~~~  :975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ=;9ÿÝßáÿÕ×ÙÿÕ×ÙÿœŸÿ+++ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ++,ÿžŸ¡ÿÕ×ÙÿÕ×ÙÿÕ×Ùÿghiÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ`aaÿÖØÙÿÖØÙÿ½¿Àÿ344ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ'''ÿ{|}ÿÖØÚÿÖØÙÿÖØÙÿÖØÙÿÖØÚÿÖØÙÿ{|}ÿ%%%ÿ%%%ÿ'''ÿ666ÿ|}~ÿÎÐÒÿÖØÚÿ­¯°ÿ///ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ(((ÿŒŽÿÖØÚÿ×ÙÚÿÖØÚÿרÚÿÖØÚÿ×ÙÚÿmnnÿ$$$ÿ%%%ÿ(((ÿ899ÿ†‡ˆÿÓÔÖÿ×ÙÚÿáãäÿ###ÿCA?ÿqlfÿˆƒ~ÿqicÿrkeÿqjdÿUQMì$#"JWWW¥¥¥ ;;97Ürkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ=;9ÿÞàâÿ×ÙÚÿÓÕÖÿijkÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ&&&ÿ‹ÿ×ÙÚÿ×ÙÚÿ×ÙÚÿ¨©ªÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ888ÿ¿ÀÁÿØÙÚÿ‘’ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿhhiÿØÚÚÿØÚÚÿ×ÚÛÿ×ÚÚÿ×ÚÛÿ×ÚÚÿ×ÚÛÿ×ÚÚÿoppÿEFFÿŒŽŽÿÊÍÍÿØÚÚÿØÚÚÿÖÙÙÿ}~~ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿvxxÿØÚÚÿ×ÚÚÿØÙÚÿ×ÙÚÿØÚÛÿØÚÛÿØÚÛÿØÙÚÿeffÿLMMÿ’””ÿÏÑÒÿØÙÚÿÙÛÜÿØÚÛÿâäæÿ###ÿDA?ÿke_ÿtmgÿrkeÿrkeÿqjdÿVRNìG999~~~’’’ :;97Ürkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ=;9ÿáâãÿÙÚÛÿÌÎÏÿBBBÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ===ÿÙÚÛÿÙÚÛÿÙÚÛÿÙÚÛÿÕרÿžŸŸÿ%%%ÿ%%%ÿ%%%ÿ233ÿTTUÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ(()ÿ¦§¨ÿÙÛÜÿUVVÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ(((ÿØÚÛÿÙÛÜÿÙÛÜÿÚÛÜÿÙÛÜÿÙÛÜÿÙÛÜÿÚÛÜÿÚÛÜÿÓÕÖÿÎÐÑÿÚÛÜÿÚÛÜÿÚÛÜÿÚÛÜÿÖØÙÿKLLÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ000ÿÚÛÜÿÚÛÜÿÚÛÜÿÚÛÜÿÚÛÜÿÚÛÜÿÚÛÜÿÚÛÜÿÚÛÜÿÓÔÕÿÑÑÒÿÚÛÜÿÚÛÜÿÚÛÜÿÚÛÜÿÚÛÜÿäåæÿ###ÿDA?ÿke_ÿ{vÿqjdÿrkeÿqjdÿYTPíD***TTT ;643Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ=;9ÿâãäÿÚÛÜÿÈÉÊÿ***ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ€ÿÚÛÜÿÚÛÜÿÚÛÜÿÚÛÜÿÚÛÜÿÚÛÜÿ³´µÿ:::ÿ'''ÿŽÿ¼½½ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ‘““ÿÚÜÝÿ000ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿjkkÿÚÜÝÿÚÜÝÿÚÜÝÿÚÜÝÿÛÜÝÿÛÜÝÿÚÜÝÿÚÜÝÿÛÜÝÿÚÜÝÿÛÜÝÿÚÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÖØÙÿ---ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿuvvÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿÛÜÝÿåæçÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîCýýý~~~;<:7Ûrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ=;9ÿäåæÿÜÜÞÿÇÇÉÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ««¬ÿÜÝÞÿÜÝÞÿÜÝÞÿÛÜÞÿÛÜÞÿÜÝÞÿÜÜÝÿÀÀÁÿ‹ŒŒÿÜÝÞÿÜÝÞÿ'''ÿ&&&ÿ%%%ÿ&&&ÿ&&&ÿ„„…ÿÝÝÞÿ%%%ÿ&&&ÿ&&&ÿ%%%ÿ&&&ÿ•––ÿÜÝÝÿÝÝÞÿÝÞÞÿÜÝÝÿÝÞÞÿÝÞÞÿÝÝÞÿÝÞÞÿÝÞÞÿÝÞÞÿÜÞÞÿÝÞÞÿÜÞÞÿÜÞÞÿÜÞÞÿØÙÙÿ%%%ÿ&&&ÿ&&&ÿ%%%ÿ&&&ÿ¡££ÿÜÞÞÿÜÞÞÿÜÞÞÿÜÞÞÿÝÞÞÿÜÞßÿÝÞßÿÜÞßÿÜÞÞÿÜÞÞÿÝÞßÿÝÞÞÿÝÞßÿÝÞßÿÝÞÞÿÝÞÞÿçèéÿ###ÿCA?ÿojdÿ|wÿqjdÿrkeÿqjdÿYTPíB  :;97Ùrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ=;9ÿåæçÿÝÞÞÿÈÈÉÿ%%%ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ³´µÿÝÞßÿÝÞßÿÝÞßÿÝÞßÿÝÞßÿÝÞßÿÝÞßÿÝÞßÿÝÞßÿÝÞßÿÝÞßÿ'''ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ}~~ÿÞßßÿ%%%ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿœÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿÙÚÚÿ%%%ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿªªªÿÞßßÿÞßßÿÞßßÿÞßßÿÞßßÿßßàÿÞßßÿßßàÿßßàÿßßàÿÞßßÿÞßßÿßßàÿÞßßÿßßàÿßßàÿééêÿ###ÿDA?ÿlf`ÿslfÿrkeÿrkeÿqjdÿYTPíB  :643Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ=;9ÿçççÿÞßßÿÊËËÿ%%%ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿŸŸŸÿÞßßÿßßàÿÞßßÿÞßßÿßßàÿßßàÿßßàÿßßàÿßßàÿßßàÿÔÔÕÿ'''ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿˆˆˆÿßßàÿ%%%ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ‡‡ˆÿßààÿßààÿßààÿßààÿßààÿßààÿßààÿßààÿßààÿßààÿßààÿßààÿàààÿßààÿßààÿÛÛÛÿ%%%ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ”””ÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿààáÿààáÿààáÿàààÿëëëÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîC  9;97Ûrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ=;9ÿèééÿàààÿÐÐÐÿ555ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ```ÿáàáÿààáÿáàáÿáààÿààáÿàààÿáàâÿáàáÿààáÿààáÿ¡¡¢ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿžž ÿáàâÿAAAÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿIIIÿáááÿááâÿàáâÿàáâÿàáâÿàáâÿááâÿáââÿàáâÿááâÿááâÿááâÿááâÿááâÿáââÿÝÞÞÿ:;;ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿSSSÿáââÿáââÿáââÿâââÿâââÿáââÿâââÿáââÿáââÿâââÿâââÿââáÿââáÿââáÿâââÿâââÿìììÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB  :;86Ùrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ=;9ÿêêêÿâââÿÙÙÙÿUUUÿ'''ÿ&&&ÿ&&&ÿ'''ÿ///ÿÄÄÄÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿàààÿYYYÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ111ÿ···ÿâââÿrrrÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ°°°ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ¸¸¸ÿÿãããÿãããÿãããÿãããÿàààÿdddÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ»»»ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ®®®ÿÿãããÿãããÿãããÿãããÿãããÿíííÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîB  <754Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿ†††ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿZZZÿ×××ÿãããÿãããÿãããÿãããÿãããÿãããÿâââÿ˜˜˜ÿ&&&ÿ'''ÿ'''ÿ'''ÿ'''ÿJJJÿÚÚÚÿãããÿ···ÿ'''ÿ&&&ÿ'''ÿ'''ÿ'''ÿ888ÿÌÌÌÿãããÿãããÿãããÿãããÿãããÿãããÿÙÙÙÿ777ÿ'''ÿ===ÿ¦¦¦ÿÙÙÙÿâââÿãããÿŸŸŸÿ'''ÿ'''ÿ&&&ÿ'''ÿ&&&ÿHHHÿÒÒÒÿãããÿãããÿãããÿãããÿãããÿãããÿÓÓÓÿ000ÿ'''ÿEEEÿ®®®ÿÛÛÛÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîC  ;<:8Ürkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿÇÇÇÿ333ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿYYYÿ°°°ÿãããÿãããÿãããÿãããÿÄÄÄÿ|||ÿ&&&ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ–––ÿãããÿãããÿØØØÿPPPÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿDDDÿ£££ÿÛÛÛÿãããÿãããÿãããÿ°°°ÿBBBÿ'''ÿ'''ÿ'''ÿ'''ÿDDDÿÀÀÀÿãããÿÐÐÐÿAAAÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿNNNÿ©©©ÿßßßÿãããÿãããÿãããÿ©©©ÿ:::ÿ'''ÿ'''ÿ'''ÿ'''ÿHHHÿÎÎÎÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB  ::76Ørkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿ‹‹‹ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ///ÿ===ÿQQQÿYYYÿCCCÿ333ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿTTTÿãããÿãããÿãããÿãããÿªªªÿ(((ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ,,,ÿ:::ÿOOOÿZZZÿ???ÿ...ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ@@@ÿÌÌÌÿãããÿãããÿœœœÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ---ÿ;;;ÿPPPÿYYYÿ===ÿ,,,ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿLLLÿÖÖÖÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB  <753Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿgggÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ@@@ÿÝÝÝÿãããÿãããÿãããÿãããÿãããÿ‚‚‚ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ---ÿ£££ÿãããÿãããÿãããÿãããÿvvvÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ222ÿ³³³ÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB lllHHH<<:8Ürkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿÿ'''ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ'''ÿ(((ÿ(((ÿ(((ÿSSSÿÊÊÊÿãããÿãããÿãããÿãããÿãããÿâââÿãããÿ–––ÿ222ÿ(((ÿ'''ÿ(((ÿ(((ÿ'''ÿ'''ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ...ÿœœœÿãããÿãããÿãããÿãããÿâââÿãããÿ‹‹‹ÿ,,,ÿ(((ÿ'''ÿ'''ÿ(((ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ222ÿ§§§ÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB žžžŽŽŽPPPB+*)Ðrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿâââÿ²²²ÿCCCÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ'''ÿ‹‹‹ÿÝÝÝÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿÂÂÂÿVVVÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿBBBÿÀÀÀÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ»»»ÿLLLÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿMMMÿÊÊÊÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB ˆˆˆ ~~~PPPC321Ùrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ¨¨¨ÿ]]]ÿ:::ÿ666ÿ333ÿ222ÿ444ÿ888ÿCCCÿ„„„ÿÚÚÚÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ¹¹¹ÿgggÿ>>>ÿ666ÿ444ÿ333ÿ444ÿ777ÿIIIÿ˜˜˜ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ±±±ÿbbbÿ;;;ÿ666ÿ333ÿ333ÿ444ÿ888ÿNNNÿ¡¡¡ÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB eeeTTT >><:Þrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿßßßÿÊÊÊÿ¿¿¿ÿºººÿÁÁÁÿÓÓÓÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿËËËÿÁÁÁÿ»»»ÿÂÂÂÿÕÕÕÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿàààÿËËËÿÀÀÀÿ»»»ÿÃÃÃÿ×××ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB ;975Ùrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB  ;753Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíC???~~~ 9<97Ûrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíDeee~~~ 9975Ørkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîF***lll¨¨¨ :753Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîC???TTT :<:8Ûrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîC :;97Ûrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîD ;:75Ürkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîG***222 :975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿZUQî$#"MHHHjjjppp 9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿ[VRð887Trrr*”””"ššš$ 9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿZUQî543Sccc&#ŸŸŸ& 9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿÛÛÛÿ€€€ÿ†††ÿ†††ÿ†††ÿ‚‚‚ÿÐÐÐÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿÿ†††ÿ†††ÿ†††ÿ‚‚‚ÿÂÂÂÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿâââÿ€€€ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ………ÿºººÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîH222hhhwww 9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿÒÒÒÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ¾¾¾ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ   ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿáááÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ’’’ÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîD****** 9975Úrkeÿrkeÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿÒÒÒÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ¿¿¿ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ¡¡¡ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿáááÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ“““ÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíC 9975Úrkeÿslfÿrkeÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿÒÒÒÿ+++ÿ,,,ÿ+++ÿ+++ÿ,,,ÿ¿¿¿ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ,,,ÿ,,,ÿ+++ÿ,,,ÿ,,,ÿ¡¡¡ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿáááÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ+++ÿ+++ÿ,,,ÿ,,,ÿ,,,ÿ+++ÿ+++ÿ,,,ÿ’’’ÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîC  9975Útmgÿˆ‚ÿpicÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿÒÒÒÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ¿¿¿ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ¢¢¢ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿáááÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîD  9975Útmgÿ†€ÿqjdÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿÒÒÒÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ¿¿¿ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ¢¢¢ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿáááÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ•••ÿºººÿ¸¸¸ÿ¸¸¸ÿ¸¸¸ÿ¸¸¸ÿ¸¸¸ÿ¸¸¸ÿ¸¸¸ÿ···ÿÐÐÐÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPìB  9975Útmgÿ†€ÿqjdÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿÒÒÒÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ¿¿¿ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ¢¢¢ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿáááÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ¶¶¶ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB  9975Úslfÿˆ‚ÿpicÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿÒÒÒÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ¿¿¿ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ¢¢¢ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿáááÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿµµµÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîD  9975Úpicÿ†€ÿvoiÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿÒÒÒÿ---ÿ,,,ÿ---ÿ---ÿ---ÿ®®®ÿÏÏÏÿÌÌÌÿÌÌÌÿÌÌÌÿÌÌÌÿÌÌÌÿÌÌÌÿÌÌÌÿÌÌÌÿàààÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ---ÿ---ÿ,,,ÿ,,,ÿ---ÿ¢¢¢ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿáááÿ,,,ÿ,,,ÿ---ÿ---ÿ---ÿ¥¥¥ÿÏÏÏÿÌÌÌÿÌÌÌÿÌÌÌÿÌÌÌÿÌÌÌÿÌÌÌÿÌÌÌÿÌÌÌÿßßßÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPìB  :975ÚpicÿŒ…ÿwpjÿrkeÿrkeÿlf`ÿ<:8ÿìììÿãããÿãããÿãããÿãããÿÒÒÒÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿÍÍÍÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ---ÿ---ÿ---ÿ---ÿ---ÿ¢¢¢ÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿáááÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿÀÀÀÿãããÿãããÿãããÿîîîÿ"""ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîC  ;;97Ýqjdÿ‘Š„ÿpicÿrkeÿrkeÿlf`ÿ=;9ÿëëëÿâââÿâââÿâââÿâââÿÑÑÑÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿÎÎÎÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿââáÿâââÿâââÿâââÿ---ÿ---ÿ---ÿ---ÿ---ÿ¢¢¢ÿâââÿâââÿââáÿââáÿâââÿââáÿâââÿâââÿââáÿââáÿâââÿâââÿààßÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿÁÁÀÿââáÿââáÿââáÿííìÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîD ;<97Üyrlÿˆ€yÿqjdÿrkeÿrkeÿlf`ÿ=;9ÿéééÿáááÿáááÿáááÿáááÿÐÐÐÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿÍÍÍÿáááÿáááÿáááÿáááÿáááÿáááÿááàÿááàÿááàÿáááÿáááÿ---ÿ---ÿ---ÿ---ÿ---ÿ¡¡¡ÿáááÿáááÿááàÿááàÿááàÿáááÿáááÿááàÿááàÿááàÿááàÿáááÿßßÞÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿÁÁÀÿááàÿááàÿááàÿëëêÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB ¨¨¨~~~=864Ù{smÿ†~wÿqjdÿrkeÿrkeÿlf`ÿ=;9ÿèèèÿàààÿáààÿàààÿáßàÿÏÏÏÿ...ÿ...ÿ...ÿ///ÿ...ÿ...ÿ...ÿ...ÿ///ÿ///ÿ...ÿ///ÿ...ÿ...ÿ///ÿÌÌÌÿàààÿàààÿàààÿààßÿàààÿààßÿàààÿàààÿàààÿààßÿàààÿ...ÿ---ÿ---ÿ---ÿ---ÿ ¡ ÿààßÿààßÿßàÞÿßàßÿßàßÿßàßÿààßÿààßÿààßÿßàßÿààßÿßàßÿÞÞÝÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ...ÿ---ÿ---ÿ¾¿½ÿßàÞÿßàÞÿßàÞÿêêéÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB ———lll***>:86Û{tmÿ†~wÿqjdÿrkeÿrkeÿlf`ÿ=;9ÿçææÿßÞÞÿßÞÞÿßÞÞÿßßÞÿÏÏÎÿ111ÿ111ÿ111ÿ111ÿ111ÿ†††ÿšššÿ™™˜ÿ™˜˜ÿ™™™ÿ™˜˜ÿ™™˜ÿ™˜˜ÿ™˜˜ÿ˜——ÿÙØØÿßÞÞÿßÞÞÿßÞÞÿßÞÞÿßÞÝÿßÞÞÿßÞÞÿßÞÞÿßÞÝÿßÞÞÿßÞÝÿ000ÿ000ÿ000ÿ000ÿ///ÿ¢¡¡ÿßÞÞÿßÞÞÿßÞÞÿßÞÞÿßÞÞÿßÞÞÿßÞÞÿßÞÞÿßÞÝÿßÞÝÿßÞÞÿßÞÞÿÝÜÛÿ///ÿ///ÿ///ÿ///ÿ///ÿ~~}ÿ™˜—ÿ——–ÿ——–ÿ——–ÿ——–ÿ——–ÿ——–ÿ—––ÿ•”“ÿÒÑÑÿßÞÝÿßÞÝÿßÞÝÿéèçÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîC ———~~~,,, =:86Ùxqkÿ‡~xÿqjdÿrkeÿrkeÿlf`ÿ=;9ÿææåÿÞÝÝÿÞÝÝÿÞÝÝÿÞÝÝÿÎÍÍÿ222ÿ222ÿ222ÿ222ÿ222ÿÂÁÁÿÞÞÝÿÞÝÝÿÞÝÜÿÞÝÝÿÞÝÝÿÞÝÝÿÞÝÜÿÞÝÝÿÞÝÝÿÞÝÝÿÞÝÝÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÝÿÞÝÜÿÞÝÝÿÞÝÝÿ222ÿ222ÿ222ÿ111ÿ111ÿ¤£¢ÿÞÝÝÿÞÝÜÿÞÝÝÿÞÝÝÿÞÝÜÿÞÝÝÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÜÛÚÿ111ÿ000ÿ000ÿ000ÿ000ÿ·¶µÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿÞÝÜÿèçæÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB  ;643Øwpjÿ‡~xÿqjdÿrkeÿrkeÿlf`ÿ=;9ÿæåãÿÝÜÛÿÞÜÛÿÝÜÚÿÝÜÛÿÍÌËÿ444ÿ444ÿ444ÿ444ÿ444ÿÄÃÁÿÝÜÛÿÝÜÛÿÞÝÛÿÝÜÚÿÝÜÚÿÝÜÚÿÝÜÚÿÝÜÚÿÝÜÚÿÝÝÚÿÝÜÚÿÝÝÚÿÝÜÚÿÝÜÚÿÝÝÚÿÜÛÚÿÝÜÚÿÝÜÚÿÝÜÚÿÝÜÚÿÝÝÚÿ222ÿ333ÿ333ÿ222ÿ222ÿ§§¦ÿÝÜÚÿÜÛÚÿÝÜÚÿÝÝÚÿÝÜÛÿÝÜÚÿÝÜÛÿÝÜÚÿÜÛÚÿÝÜÚÿÝÜÚÿÜÛÚÿÚÙØÿ222ÿ222ÿ222ÿ222ÿ222ÿ·¶µÿÜÛÚÿÝÜÛÿÜÛÚÿÜÜÚÿÜÜÚÿÜÜÚÿÜÜÚÿÜÜÚÿÜÜÚÿÜÜÚÿÜÜÚÿÜÜÚÿÜÜÚÿçæåÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB  ;;86Ürkeÿohbÿrkeÿrkeÿrkeÿlf`ÿ=;9ÿäãâÿÜÛÚÿÜÛÚÿÜÛÙÿÜÛÙÿÍÌÊÿ777ÿ777ÿ555ÿ666ÿ666ÿ£¢¢ÿ½¼ºÿ»º¹ÿ»º¸ÿ»º¸ÿºº¸ÿ»º¸ÿ»º¸ÿº¹¸ÿº¹·ÿÒÑÏÿÜÛÙÿÜÛÙÿÜÛÙÿÜÛÙÿÜÛÙÿÕÔÒÿ¹¸¶ÿº¹¸ÿºº¸ÿ¹¹·ÿ¿¾½ÿ555ÿ555ÿ444ÿ555ÿ555ÿŒ‹‹ÿ»»¹ÿº¹·ÿº¹¸ÿ¹·¶ÿÅÄÃÿÛÚØÿÜÚÙÿÜÚÙÿÜÚÙÿÜÛÙÿÜÛÙÿÜÚÙÿÚØ×ÿ444ÿ444ÿ444ÿ444ÿ333ÿš˜˜ÿ»º¹ÿ¹·¶ÿ¹¸·ÿ¹¸·ÿ¹··ÿ¹·¶ÿ¹¸·ÿ¹·¶ÿ·¶µÿÎÍÌÿÜÚÙÿÜÚÙÿÜÚÙÿæäãÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîC  :;97Útmgÿ†€ÿqjdÿrkeÿrkeÿlf`ÿ=;9ÿãâáÿÛÚØÿÛÚØÿÛÚØÿÛÚÙÿÌËÉÿ777ÿ888ÿ888ÿ777ÿ777ÿ777ÿ777ÿ888ÿ777ÿ888ÿ777ÿ777ÿ777ÿ777ÿ777ÿ¥¤£ÿÛÚØÿÛÚØÿÛÚØÿÛÚØÿÛÚØÿ¸·µÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿffeÿ×ÕÔÿÛÚØÿÛÙØÿÛÙØÿÛÚØÿÛÙØÿÛÚØÿÙØÖÿ666ÿ777ÿ666ÿ666ÿ666ÿ555ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ555ÿ666ÿ666ÿ“‘‘ÿÛÙØÿÛÙØÿÛÙ×ÿåãâÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîC ;643Ùunhÿ›”Žÿpicÿrkeÿrkeÿlf`ÿ=;9ÿãàßÿÚØ×ÿÚØÖÿÛØ×ÿÛØ×ÿËÉÈÿ:::ÿ999ÿ:::ÿ999ÿ:::ÿ999ÿ999ÿ:::ÿ:::ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ§¦¥ÿÚØÖÿÚØÖÿÚØÖÿÚØÖÿÚØÖÿ¹·µÿ999ÿ888ÿ888ÿ999ÿ999ÿ888ÿ888ÿ888ÿ888ÿ999ÿ999ÿ888ÿ888ÿ888ÿ888ÿllkÿÖÖÓÿÚÙÖÿÚÙÖÿÚÙÖÿÙØÖÿÚÙÖÿÚÙÖÿ×ÖÔÿ888ÿ888ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ888ÿ888ÿ888ÿ888ÿ777ÿ777ÿ——•ÿÙÙ×ÿÙØÖÿÙØÖÿããàÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîG???TTT <<:8Ýrkeÿvoiÿrkeÿrkeÿrkeÿlf`ÿ=;9ÿáßÝÿÙ×ÕÿÙ×ÕÿÙ×ÕÿÙ×ÕÿÊÉÇÿ<<<ÿ<<<ÿ;;;ÿ<<<ÿ<<<ÿ<<<ÿ;;;ÿ;;;ÿ;;;ÿ<<<ÿ;;;ÿ<<<ÿ;;;ÿ;;;ÿ<<<ÿ¨§¦ÿÙ×ÕÿÙ×ÕÿÙ×ÕÿÙ×ÕÿÙ×Õÿ¸·µÿ;;;ÿ;;;ÿ;;;ÿ:::ÿ;;;ÿ:::ÿ;;;ÿ;;;ÿ;;;ÿ:::ÿ:::ÿ;;;ÿ:::ÿ:::ÿ:::ÿnmlÿÖÔÒÿÙ×ÕÿÙ×ÕÿÙ×ÕÿÙ×ÕÿÙ×ÕÿÙ×Õÿ×ÕÓÿ999ÿ:::ÿ:::ÿ999ÿ999ÿ:::ÿ999ÿ999ÿ:::ÿ999ÿ999ÿ999ÿ:::ÿ999ÿ999ÿ˜—–ÿÙ×ÕÿÙ×ÕÿÙ×Õÿãáßÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPî$#"HEEE~~~žžž :;86Øtmgÿއÿqjdÿrkeÿrkeÿlf`ÿ=;9ÿàÞÛÿØÖÔÿØÖÔÿØ×ÔÿØÖÔÿÉÈÆÿ<<<ÿ<<<ÿ<<<ÿ===ÿ===ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ¨§¥ÿØÖÔÿØÖÔÿØÖÔÿØÖÔÿØÖÔÿ¸¶µÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;ÿnnmÿÕÓÑÿØÖÔÿØÖÔÿØÖÔÿØÖÔÿØÖÔÿØÖÔÿÖÔÒÿ<<<ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ:::ÿ;;;ÿ;;;ÿ™—–ÿØÖÔÿØÖÔÿØÖÓÿâàÝÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPîG***lll———~~~ :532×tmgÿ‘Š„ÿpicÿrkeÿrkeÿlf`ÿ=;9ÿßÝÚÿ×ÕÒÿ×ÕÒÿØÕÒÿ×ÔÒÿÐÍËÿ……ƒÿ‹‰ˆÿЉ‡ÿЉ‡ÿЉˆÿŠˆ‡ÿ‹‰ˆÿŠˆ‡ÿ‰ˆ‡ÿŠˆ‡ÿŠˆ‡ÿЉ‡ÿЉ‡ÿЉˆÿЉˆÿÀ½¼ÿ×ÔÓÿÖÕÒÿ×ÔÓÿ×ÔÓÿ×ÔÒÿÇÄÂÿ…„ƒÿ‰ˆ‡ÿ‰ˆ‡ÿ‰ˆ†ÿ‰ˆ‡ÿЉ‡ÿŠˆˆÿ‰ˆ‡ÿЉ‡ÿ‰‡†ÿ‰ˆ‡ÿˆ‡†ÿ‰‡‡ÿ‰ˆ‡ÿ‰ˆ‡ÿ£¡¡ÿÖÓÑÿÖÔÒÿÖÔÒÿÖÔÒÿ×ÔÒÿ×ÔÒÿ×ÔÓÿÕÓÑÿƒ‚‚ÿˆˆ‡ÿ‰‡†ÿ‰‡‡ÿˆ‡†ÿˆ‡‡ÿˆ‡†ÿˆ‡†ÿˆ‡†ÿˆ‡†ÿˆ‡†ÿˆ‡†ÿˆ‡†ÿˆ‡‡ÿ‡†…ÿ·µ´ÿÖÔÒÿÖÔÒÿÖÔÒÿàÞÜÿ###ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB~~~TTT==;9Þxpjÿ›”Žÿpicÿrkeÿrkeÿlf`ÿ=;9ÿÞÜÙÿÖÔÑÿÖÓÑÿÖÓÑÿÖÓÑÿÖÔÑÿÖÔÑÿÖÔÑÿÖÓÑÿÖÔÑÿÖÓÑÿÖÓÑÿÖÔÑÿÖÔÑÿÖÔÑÿÖÓÑÿÖÔÑÿÖÓÑÿÖÓÑÿÖÔÑÿÖÓÑÿÖÓÑÿÖÔÑÿÖÓÑÿÖÔÑÿÖÔÑÿÖÔÑÿÖÓÑÿÖÔÑÿÖÔÑÿÖÓÑÿÖÓÑÿÖÔÑÿÖÓÑÿÖÓÑÿÖÓÐÿÖÓÑÿÖÔÑÿÖÓÑÿÖÓÑÿÖÔÑÿÖÓÑÿÖÓÐÿÖÔÑÿÖÓÑÿÖÓÑÿÖÓÑÿÖÓÐÿÖÓÑÿÖÓÑÿÖÓÑÿÖÓÐÿÖÔÑÿÖÓÐÿÖÓÐÿÖÓÑÿÖÓÐÿÖÓÐÿÖÓÑÿÖÓÐÿÖÓÐÿÖÓÑÿÖÓÐÿÖÓÑÿÖÓÑÿÖÓÑÿÖÓÐÿÖÓÑÿÖÓÑÿÖÓÐÿÖÓÑÿàÝÚÿ##$ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB ~~~::86Ø}uoÿš’Œÿpicÿrkeÿrkeÿlf`ÿ=;9ÿÝÚ×ÿÕÓÐÿÕÓÐÿÕÓÐÿÕÒÐÿÕÒÐÿÕÒÐÿÕÓÐÿÕÒÐÿÕÓÐÿÕÓÐÿÕÒÐÿÕÒÐÿÕÒÐÿÕÓÐÿÕÒÐÿÕÓÐÿÕÒÐÿÕÒÐÿÕÒÐÿÕÒÐÿÕÒÐÿÕÒÐÿÕÒÐÿÕÒÐÿÕÒÏÿÕÓÐÿÕÓÐÿÕÒÏÿÕÒÐÿÕÒÐÿÕÒÏÿÕÒÐÿÕÒÐÿÕÒÏÿÕÒÐÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÐÿÕÓÐÿÕÒÏÿÕÒÐÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÐÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÐÿÕÒÐÿÕÒÏÿÕÒÐÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÕÒÏÿÔÒÏÿßÛØÿ##$ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB  ;643Ù|unÿš’Œÿpicÿrkeÿrkeÿlf`ÿ=;9ÿÛÙÕÿÔÒÎÿÔÒÎÿÓÒÏÿÓÒÎÿÓÒÎÿÔÒÏÿÔÑÏÿÔÒÏÿÓÑÎÿÔÑÏÿÔÒÎÿÓÒÎÿÓÒÎÿÓÒÏÿÓÒÏÿÓÑÏÿÓÒÏÿÓÒÎÿÓÒÎÿÓÒÏÿÓÒÎÿÓÑÍÿÓÑÎÿÓÒÏÿÓÑÎÿÓÑÍÿÓÑÎÿÓÑÎÿÓÑÎÿÓÒÎÿÓÑÍÿÓÑÎÿÔÒÍÿÓÑÍÿÓÑÎÿÓÑÏÿÓÑÎÿÓÒÏÿÓÐÍÿÓÑÍÿÓÑÎÿÔÒÎÿÓÐÍÿÓÑÎÿÔÑÍÿÓÑÍÿÓÐÍÿÓÑÎÿÓÑÎÿÓÐÍÿÓÒÎÿÓÑÎÿÓÑÎÿÔÑÍÿÔÑÎÿÔÐÍÿÔÒÎÿÔÑÎÿÔÐÎÿÔÑÎÿÔÑÎÿÔÑÎÿÔÑÎÿÔÑÎÿÔÑÎÿÔÑÎÿÔÑÎÿÔÐÎÿÔÐÎÿÔÑÎÿÝÚ×ÿ#$$ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB  <=:8Þ~vpÿš“ÿpicÿrkeÿrkeÿlf`ÿ=;9ÿÛØÓÿÓÐÍÿÓÐÌÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÌÿÓÐÍÿÓÐÌÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÌÿÓÐÌÿÓÐÌÿÓÐÌÿÓÐÍÿÓÐÌÿÓÐÍÿÓÐÍÿÓÐÌÿÓÐÌÿÓÐÍÿÓÐÍÿÓÐÌÿÓÐÌÿÒÏÌÿÓÐÍÿÒÏÌÿÒÏÌÿÓÐÍÿÓÐÌÿÓÐÌÿÓÐÍÿÓÐÍÿÓÐÍÿÓÐÍÿÒÏÌÿÒÏÌÿÓÐÌÿÓÐÍÿÒÏÌÿÓÐÍÿÒÐÌÿÒÏÌÿÓÐÍÿÒÏÌÿÒÏÌÿÓÐÌÿÓÐÍÿÒÏÌÿÒÏÌÿÒÐÌÿÒÏÌÿÒÐÌÿÒÐÌÿÒÏÌÿÒÐÌÿÒÏÌÿÒÐÌÿÒÏÌÿÒÏÌÿÒÐÌÿÒÐÌÿÒÏÌÿÒÏÌÿÒÐÌÿÜÙÕÿ#$$ÿDA?ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB  :975Ùƒ|vÿœ•ÿpicÿrkeÿrkeÿlf`ÿ=;9ÿÚ×ÔÿÒÏËÿÒÏÌÿÒÏËÿÒÏÌÿÒÏÌÿÒÏÌÿÒÏËÿÒÏÌÿÒÏÌÿÒÏËÿÒÏËÿÒÏÌÿÒÏÌÿÒÏÌÿÒÏËÿÒÏËÿÒÏÌÿÒÏÌÿÒÏËÿÒÏÌÿÒÏÌÿÒÏËÿÒÏÌÿÒÏËÿÒÏÌÿÒÏÌÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏÌÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏÌÿÒÏÌÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏÌÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÑÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÒÏËÿÜÙÕÿ#$$ÿDB@ÿlf`ÿrkeÿrkeÿrkeÿqjdÿYTPíB  9975Û}uoÿ™’Œÿpicÿrkeÿrkeÿlf`ÿ><:ÿ½¼¹ÿÕÒÎÿÐÎÊÿÑÎÊÿÑÎÊÿÐÎÉÿÐÎÊÿÐÎÊÿÑÎËÿÐÎÊÿÐÎÊÿÑÍËÿÐÎÉÿÐÎÊÿÐÎÉÿÐÎÊÿÐÎÊÿÐÎÉÿÐÎÊÿÐÎÊÿÐÎÉÿÐÎÊÿÐÎÉÿÐÎÊÿÐÎÊÿÐÎÉÿÐÎÊÿÐÎÊÿÐÎÊÿÐÎÉÿÑÎÊÿÐÎÊÿÐÎÊÿÐÎÊÿÐÎÊÿÐÎÉÿÐÎÊÿÐÎÉÿÐÎÉÿÐÎÊÿÐÎÊÿÐÎÊÿÐÎÊÿÐÎÊÿÐÎÊÿÐÎÊÿÐÎÉÿÐÎÉÿÐÎÊÿÐÎÊÿÐÎÊÿÑÎÊÿÐÎÉÿÐÎÊÿÐÎÉÿÑÎÉÿÐÎÊÿÐÎÊÿÐÎÊÿÐÎÊÿÐÎÊÿÐÎÊÿÐÎÉÿÐÎÊÿÑÎÊÿÑÎÊÿÐÎÊÿÐÎÊÿÐÍÊÿÐÍÉÿÕÒÍÿ¾¼ºÿ%%%ÿ@>=ÿle`ÿrkeÿrkeÿrkeÿqjdÿYTOìB TTTýýý 8:86؃}wÿ®¨£ÿvoiÿqjdÿrkeÿngaÿIFCÿDCCÿ½º¸ÿØÔÐÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿØÓÏÿ×ÓÏÿ×ÓÏÿØÔÐÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿÖÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿØÓÏÿ×ÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿ×ÓÏÿ×ÓÏÿ×ÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿÖÓÏÿ×ÔÐÿ¼º¸ÿBBBÿ-,,ÿ;98ÿjd^ÿrkeÿrkeÿrkeÿqjdÿXSOê> ýýý~~~ //.-Ãngaÿª¤ ÿngaÿqjdÿrkeÿqjdÿe`[ÿIFCÿ><;ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ>;9ÿ?<;ÿCA?ÿB@>ÿSOLÿohbÿrkeÿrkeÿrkeÿrkeÿWRNá6 ýýý$$$#!! b\Wþ‘Š„ÿž˜“ÿxqjÿrkeÿrkeÿqjdÿngaÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿlf`ÿohcÿrkeÿrkeÿrkeÿrkeÿqjdÿUPLÎ &kPLIø€xqÿ­§¢ÿœ–‘ÿslfÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿoicÿNIF¤820/Äc]Xÿ…~xÿ¤˜ÿœ–‘ÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿqjdÿqjdÿqjdÿqjdÿqjdÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿe_Zñ731X^A?<åle`ÿ}uoÿœ•ÿ~voÿpicÿqjdÿpicÿohbÿohbÿohbÿngaÿslfÿunhÿxpjÿ|tmÿ|tmÿ|tmÿxpjÿngaÿngaÿngaÿngaÿngaÿngaÿngaÿohbÿslfÿvoiÿvoiÿrkeÿqjdÿpicÿqjdÿqjdÿpicÿslfÿunhÿqjdÿqjdÿqjdÿpicÿqjdÿqjdÿqjdÿqjdÿpicÿpibÿqjdÿqjdÿqjdÿqjdÿqjdÿqjdÿqjdÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿqjdÿrkeÿrkeÿpicÿqjdÿqjdÿqjdÿqjdÿqjdÿqjdÿqjdÿqjdÿpicÿpicÿrkeÿrkeÿrkeÿrkeÿrkeÿke_ûJFB–~~~ $mA><äd^Zÿmf`ÿzsmÿ…~xÿŠ‚{ÿ‘Š„ÿ§¡œÿ­§¢ÿ®¨£ÿ³­¨ÿºµ±ÿºµ±ÿµ°¬ÿ´¯«ÿ²­©ÿ²­©ÿ³®ªÿ¿º¶ÿ¿º¶ÿ¾¹µÿ¿¹µÿ¿º¶ÿ¿»¸ÿ¹³¯ÿ©£žÿ©£žÿ¦ ›ÿ«¤Ÿÿ£˜ÿˆ‚ÿ™’Œÿˆ‚ÿŠ‚zÿ“Œ†ÿ‹„}ÿˆ‚ÿ†~wÿ†~wÿ†}vÿ˜‘‹ÿއ€ÿ†~wÿ†~wÿ…|uÿ ™”ÿ¡›–ÿ„|uÿ†~wÿ†~wÿ†~wÿ†~wÿ†~wÿ~wpÿqkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿqjdÿ†yÿyrlÿxpjÿ‰‚ÿ†€ÿ†€ÿ†€ÿ†€ÿ†€ÿ†€ÿ†€ÿ†€ÿ‰ƒÿ”‡ÿpicÿrkeÿrkeÿpicÿe_Z÷GC? ( ???~~~~~~——— $_20/ÂPLI÷a\Wþle`ÿ{tnÿ€ysÿxrÿƒ|vÿ„}wÿ†yÿ†zÿ…yÿ„}vÿ„}wÿ„}wÿ„}wÿ„}wÿ‡{ÿŠ„~ÿ‡ÿ‡{ÿ†€zÿ„}wÿ‚{tÿ{smÿyqkÿ|tmÿxqjÿyrkÿ{smÿ}uoÿ{tmÿ{smÿ|tnÿ{smÿzslÿ{tmÿ{tmÿ{smÿƒ|vÿwqÿ{smÿ{tmÿ{smÿ}uoÿ}uoÿ{smÿ{tmÿ{tmÿ{tmÿ{tmÿ{tmÿxpjÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿrkeÿqjdÿrkeÿslfÿtmgÿtmgÿtmgÿtmgÿtmgÿtmgÿtmgÿtmgÿtmgÿrkeÿpicÿpicÿle`ÿb\XûRMIÝ41/{ (~~~¨¨¨¨¨¨ 8i ™.-+À753Ô975×975Ù975Ú975Ù975Ú975Ú975Û:86Ü;97Ý642Ø975Ù:86Ü642Ø975Ù<97Ý642Ù:86Ù;97Ý531×;97Ù;97Ý643Ù<97Û:75Ü643Ø:86Ù975Û643Ø;97Ü:86Ý:75Ú975Ú864Ú975Ú975Ú975Ú975Ú975Ú975Ú975Ú975Ù975Ú975Ú975Ü;86Ü<97Û643Ú:75Ù;97Û643Ú:75Ù<:8Ü754Ú;86Ø<:7Û542Ú;86Ù;97Ü753Û;97Ú;86Ú643Ú:86Ù;86Ú643Ú<97Ü;97Ü975Ú975Ú:76Ø975Ô753Ç.,*¤o= ~~~¨¨¨#/6799999;:;99:99;:9;99<;::99:9:;9999999999999;:9:99:9:;99;9;<:9:99;:;9976/# ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿþü?øøððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððøøüþ?ÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfteqcc-20251105/./readme.txt0000644000200200001440000001077215233070110014731 0ustar twolifeusersReadme for the FTE QCLib This library is a library for running QuakeC gamecode. It does not provide any builtins itself. Features: * Multiple library instances, enabling server qc, client qc, and menu qc. There is no maximum instance limit other than memory. * Addons, for running multiple progs in any individual instance. * Field reassignment, allowing a single engine to support multiple subtly different QC APIs. Also makes additional fields easier. * Step-by-step debugging. Requires a text editor of some form, however. A printout of the current line is also useful of course. * 64bit support. All strings, globals, and fields are allocated in a consecutive addressable section of memory. This also allows pointers and secure access (not implemented yet, but should be relativly easy bar builtins, which are your responsability). * Multiple 'threads'. The library allows a builtin to make a duplicate of the current execution state, or to wipe the current state. This allows sleep commands and fork commands. How handy. * Integrated QC compiler. FTEQCC comes as part of qclib. By setting up an interface with a specific value, you can cause it to always run, or run only if it detects a source change. * Support for different sorts of progs. Namly Hexen2's, kkqwsv's bigprogs, and FTE's extended format with extra opcodes and possibly fully 32bit offsets. The use of kkqwsv's progs is not recommended - this might be removed at some point. Quirks: * don't use multiple instances of fteqcc at the same time. Compilation will fail. * 64bit support requires all strings to be allocated by qclib itself, achivable via a method call. Compatability requires a certain ammount of caution. * a fair number of methods are obsolete. * An overuse of pointers in the API. There are some macros which you can use to hide some of the dereferences. * kkqwsv progs are not reliable. Do not try saving the game. Avoid letting your users know of support. * Builtin structures are different from original quake. You'll need to convert the arguments to qclib style. This change was required for both multiple instances as well as addon support. It should be straightforward enough. * Entity fields are accessed via a pointer from the edict_t structure. This was required to place entity fields within the 64bit accessable section. Changing a . to a -> is not a major issue though. However, there are a lot. do a find and replace of ->v. to ->v-> * FTE's entities are numbers not pointers. This fact is not made into a big feature as it's kinda incompatable with standard quake. Please do not use numbers directly to refer to ents but instead use the EDICT_TO_PROGS macro which will give protection. This is consistant with standard quake. Basic usage: * refer to test.c for a sample on how to set up the library. * refer to progslib.h for the things that I've forgotten to mention. * Call the InitProgs function to get a handle to the instance. It takes a parameter which should be set up with some fields. You'll require ReadFile, FileSize, Abort and printf for basic execution. * Call the configure function to say how much memory to use, and how many progs/addons to support. * Load your progs via LoadProgs. Use a crc of 0 to use any. Otherwise progs will be rejected if it doesn't match. Give it a list of progs-specific builtins too. :) * Before calling the spawn builtin, call the InitEnts method. It's parameter stating how many maximum entities to spawn. Using a really large quantity is not much of an issue, as they are allocated as required. * Before calling InitEnts, you can tell the VM which fields your engine uses (state all basic ones or none). This will place the entity fields in the same order as your engine expects for entvars_t. * Obtain pointers to globals, or just use the globals structure directly. * Call the ExecuteProgram method to start execution. * Call the FindFunction method to find a function to run in the first place. * Call the 'globals' method to retrieve a pointer to the globals (you should always use PR_CURRENT here). Set the parameters with the G_INT/G_FLOAT macros and friends. Use OFS_PARM0 - OFS_PARM7 to set params before calling or read inside a builtin. Use OFS_RETURN to read the return value. These macros are hard coded to use a 'pr_globals' symbol, so avoid renaming builtin parameter names. * Ask me on IRC when it all starts keeling over. * These are the C files that form qclib: pr_edict.c pr_exec.c pr_multi.c initlib.c qcc_pr_comp.c qcc_pr_lex.c qccmain.c qcc_cmdlib.c comprout.c hash.c qcd_main.c qcdecomp.c fteqcc-20251105/./qccguiqt.cpp0000644000200200001440000022156715233070110015265 0ustar twolifeusers/*Copyright Spoike, license is GPLv2+ */ /*Todo (in no particular order): tooltips for inspecting variables/types. variables/watch list. initial open project prompt shpuld's styling decompiler output saving right-click popup goto-def grep-for toggle-breakpoint set-next bracket/brace highlights autoindentation on enter, etc different displays for non-text files? utf-16? mneh, who gives a shit give focus back to the engine on resume */ #ifdef __PIC__ #undef __PIE__ //QT is being annoying. #endif #include #include #include #include #include extern "C" { #include "qcc.h" #include "gui.h" extern pbool fl_nondfltopts; extern pbool fl_hexen2; extern pbool fl_ftetarg; extern pbool fl_compileonstart; extern pbool fl_showall; extern pbool fl_log; extern pbool fl_extramargins; extern int fl_tabsize; extern char enginebinary[MAX_OSPATH]; extern char enginebasedir[MAX_OSPATH]; extern char enginecommandline[8192]; extern QCC_def_t *sourcefilesdefs[]; extern int sourcefilesnumdefs; }; static char *cmdlineargs; static progfuncs_t guiprogfuncs; static progexterns_t guiprogexterns; #undef NULL #define NULL nullptr #undef Sys_Error //c++ sucks and just pisses me off with its lack of support for void* template inline T cpprealloc(T p, size_t s) {return static_cast(realloc(static_cast(p),s));}; #define STRINGIFY2(s) #s #define STRINGIFY(s) STRINGIFY2(s) static QProcess *qcdebugger; static void DebuggerStop(void); static bool DebuggerSendCommand(const char *msg, ...); static void DebuggerStart(void); void Sys_Error(const char *text, ...) { va_list argptr; static char msg[2048]; va_start (argptr,text); QC_vsnprintf (msg,sizeof(msg)-1, text,argptr); va_end (argptr); QCC_Error(ERR_INTERNAL, "%s", msg); } void RunCompiler(const char *args, pbool quick); static void *QCC_ReadFile(const char *fname, unsigned char *(*buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *out_size) { size_t len; unsigned char *buffer; vfile_t *v = QCC_FindVFile(fname); if (v) { len = v->size; if (buf_get) buffer = buf_get(buf_ctx, len+1); else buffer = static_cast(malloc(len+1)); if (!buffer) return NULL; buffer[len] = 0; if (len > v->size) len = v->size; memcpy(buffer, v->file, len); if (out_size) *out_size = len; return buffer; } auto f = fopen(fname, "rb"); if (!f) { if (out_size) *out_size = 0; return nullptr; } fseek(f, 0, SEEK_END); len = ftell(f); fseek(f, 0, SEEK_SET); if (buf_get) buffer = buf_get(buf_ctx, len+1); else buffer = static_cast(malloc(len+1)); buffer[len] = 0; if (len != fread(buffer, 1, len, f)) { if (!buf_get) free(buffer); buffer = nullptr; } fclose(f); if (out_size) *out_size = len; return buffer; } static int PDECL QCC_FileSize (const char *fname) { vfile_t *v = QCC_FindVFile(fname); if (v) return v->size; long length; auto f = fopen(fname, "rb"); if (!f) return -1; fseek(f, 0, SEEK_END); length = ftell(f); fclose(f); return length; } static int PDECL QCC_PopFileSize (const char *fname) { //populates the file list, as well as returning the size extern int qcc_compileactive; int len = QCC_FileSize(fname); if (len >= 0 && qcc_compileactive) { AddSourceFile(compilingrootfile, fname); } return len; } static int PDECL QCC_StatFile (const char *fname, struct stat *sbuf) { vfile_t *v = QCC_FindVFile(fname); if (v) { memset(sbuf, 0, sizeof(*sbuf)); sbuf->st_size = v->size; return 0; } return stat(fname, sbuf); } pbool PDECL QCC_WriteFile (const char *name, void *data, int len) { long length; FILE *f; auto *ext = strrchr(name, '.'); if (ext && !stricmp(ext, ".gz")) { #ifdef AVAIL_ZLIB pbool okay = true; char out[1024*8]; z_stream strm = { data, len, 0, out, sizeof(out), 0, NULL, NULL, NULL, NULL, NULL, Z_BINARY, 0, 0 }; f = fopen(name, "wb"); if (!f) return false; deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, MAX_WBITS|16, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); while(okay && deflate(&strm, Z_FINISH) == Z_OK) { if (sizeof(out) - strm.avail_out != fwrite(out, 1, sizeof(out) - strm.avail_out, f)) okay = false; strm.next_out = out; strm.avail_out = sizeof(out); } if (sizeof(out) - strm.avail_out != fwrite(out, 1, sizeof(out) - strm.avail_out, f)) okay = false; deflateEnd(&strm); fclose(f); if (!okay) unlink(name); return okay; #else return false; #endif } if (QCC_FindVFile(name)) return !!QCC_AddVFile(name, data, len); f = fopen(name, "wb"); if (!f) return false; length = fwrite(data, 1, len, f); fclose(f); if (length != len) return false; return true; } //for the project's treeview to work, we need a subclass to provide the info to be displayed class filelist : public QAbstractItemModel { public: struct filenode_s { filenode_s *parent = nullptr; int numchildren; filenode_s **children; char *name; ~filenode_s() { while(numchildren) { delete(children[--numchildren]); } free(children); free(name); } } *root; filenode_s *getItem(const QModelIndex &idx) const { if (idx.isValid()) return static_cast(idx.internalPointer()); return root; } virtual int rowCount(const QModelIndex &parent = QModelIndex()) const { const filenode_s *n = getItem(parent); return n->numchildren; } virtual int columnCount(const QModelIndex &parent = QModelIndex()) const { return 1; } virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { const filenode_s *n = getItem(index); switch(role) { case Qt::DisplayRole: return QVariant(n->name); } return QVariant(); } virtual QModelIndex index(int row, int column, const QModelIndex &parent) const { filenode_s *n; if (column) return QModelIndex(); n = getItem(parent); if (row >= 0 && row < n->numchildren) return createIndex(row, column, n->children[row]); return QModelIndex(); } virtual QModelIndex parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); filenode_s *n = getItem(index); filenode_s *p = n->parent; if (p == root) return QModelIndex(); else { int parentrow = 0; if (n->parent) while (n != n->parent->children[parentrow]) parentrow++; return createIndex(parentrow, 0, p); } } virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const { switch(role) { case Qt::DisplayRole: return QVariant("Files"); } return QVariant(); } public: filelist() { root = new filenode_s(); } void GrepAll(const char *search, filenode_s *n = nullptr) { if (!n) { GUIprintf(""); GUIprintf("Grep for %s\n", search); n = root; } Grep(n->name, search); for (int i = 0; i < n->numchildren; i++) { auto c = n->children[i]; GrepAll(search, c); } } filenode_s *FindChild(filenode_s *p, const char *filename) { for (int i = 0; i < p->numchildren; i++) { auto c = p->children[i]; if (!strcasecmp(c->name, filename)) return c; } return nullptr; } void AddFile(const char *parentpath, const char *filename) { filenode_s *p = root, *c; if (!filename) { delete root; root = new filenode_s(); return; } while (parentpath && *parentpath) { auto sl = strchr(parentpath, '/'); c = FindChild(p, parentpath); if (!c) break; p = c; if (sl) sl++; parentpath = sl; } while(!strncmp(filename, "./", 2)) filename+=2; if (p->parent && FindChild(p->parent, filename) == p) return; //matches its parent. probably the .src file itself c = FindChild(p, filename); if (c) return; //already in there. beginResetModel(); c = new filenode_s(); c->name = strdup(filename); c->parent = p; p->children = cpprealloc(p->children, sizeof(*p->children)*(p->numchildren+1)); p->children[p->numchildren] = c; p->numchildren++; endResetModel(); } }; class WrappedQsciScintilla:public QsciScintilla { public: WrappedQsciScintilla(QWidget *parent):QsciScintilla(parent){} char *WordUnderCursor(char *word, int wordsize, char *term, int termsize, int position) { unsigned char linebuf[1024]; long charidx; long lineidx; long len; pbool noafter = (position==-2); if (position == -3) { len = SendScintilla(QsciScintillaBase::SCI_GETSELTEXT, NULL); if (len > 0 && len < wordsize) { len = SendScintilla(QsciScintillaBase::SCI_GETSELTEXT, word); if (len>0) return word; } } if (position < 0) position = SendScintilla(QsciScintillaBase::SCI_GETCURRENTPOS); lineidx = SendScintilla(QsciScintillaBase::SCI_LINEFROMPOSITION, position); charidx = position - SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE, lineidx); len = SendScintilla(QsciScintillaBase::SCI_LINELENGTH, lineidx); if (len >= sizeof(linebuf)) { *word = 0; return word; } len = SendScintilla(QsciScintillaBase::SCI_GETLINE, lineidx, linebuf); linebuf[len] = 0; if (charidx >= len) charidx = len-1; if (noafter) //truncate it here if we're not meant to be reading anything after. linebuf[charidx] = 0; if (word) { //skip back to the start of the word while(charidx > 0 && ( (linebuf[charidx-1] >= 'a' && linebuf[charidx-1] <= 'z') || (linebuf[charidx-1] >= 'A' && linebuf[charidx-1] <= 'Z') || (linebuf[charidx-1] >= '0' && linebuf[charidx-1] <= '9') || linebuf[charidx-1] == '_' || linebuf[charidx-1] == ':' || linebuf[charidx-1] >= 128 )) { charidx--; } //copy the result out lineidx = 0; wordsize--; while (wordsize && ( (linebuf[charidx] >= 'a' && linebuf[charidx] <= 'z') || (linebuf[charidx] >= 'A' && linebuf[charidx] <= 'Z') || (linebuf[charidx] >= '0' && linebuf[charidx] <= '9') || linebuf[charidx] == '_' || linebuf[charidx] == ':' || linebuf[charidx] >= 128 )) { word[lineidx++] = linebuf[charidx++]; wordsize--; } word[lineidx++] = 0; } if (term) { //skip back to the start of the word while(charidx > 0 && ( (linebuf[charidx-1] >= 'a' && linebuf[charidx-1] <= 'z') || (linebuf[charidx-1] >= 'A' && linebuf[charidx-1] <= 'Z') || (linebuf[charidx-1] >= '0' && linebuf[charidx-1] <= '9') || linebuf[charidx-1] == '_' || linebuf[charidx-1] == ':' || linebuf[charidx-1] == '.' || linebuf[charidx-1] == '[' || linebuf[charidx-1] == ']' || linebuf[charidx-1] >= 128 )) { charidx--; } //copy the result out lineidx = 0; termsize--; while (termsize && ( (linebuf[charidx] >= 'a' && linebuf[charidx] <= 'z') || (linebuf[charidx] >= 'A' && linebuf[charidx] <= 'Z') || (linebuf[charidx] >= '0' && linebuf[charidx] <= '9') || linebuf[charidx] == '_' || linebuf[charidx] == ':' || linebuf[charidx] == '.' || linebuf[charidx] == '[' || linebuf[charidx] == ']' || linebuf[charidx] >= 128 )) { term[lineidx++] = linebuf[charidx++]; termsize--; } term[lineidx++] = 0; } return word; } protected: void contextMenuEvent(QContextMenuEvent *event); }; class documentlist : public QAbstractListModel { public: enum endings_e { NONE = 0, //no endings at all yet UNIX = 1, MAC = 2, MIXED = 3, WINDOWS = 4, }; private: WrappedQsciScintilla *s; //this is the widget that we load our documents into int numdocuments; struct document_s { //these are swapped in/out of the scintilla widget const char *fname; const char *shortname; time_t filemodifiedtime; bool modified; int cursorline; int cursorindex; enum endings_e endings; //line endings for this file. int savefmt; //encoding to save as QsciDocument doc; QsciLexer *l; } **docs, *curdoc = nullptr; class docstacklock { struct document_s *oldval; documentlist &dl; public: docstacklock(documentlist *ptr_, struct document_s *newval) : dl(*ptr_) { //pick new stuff oldval = dl.curdoc; dl.curdoc = newval; dl.s->setDocument(dl.curdoc->doc); } ~docstacklock() { //restore state to how it used to be if (!oldval) return; dl.curdoc = oldval; dl.s->setDocument(dl.curdoc->doc); //annoying, but it completely loses your position otherwise. dl.s->setCursorPosition(dl.curdoc->cursorline-1, dl.curdoc->cursorindex); dl.s->ensureCursorVisible(); } }; document_s *getItem(const QModelIndex &idx) const { if (idx.isValid()) return docs[idx.row()]; return nullptr; } virtual int rowCount(const QModelIndex &parent = QModelIndex()) const { return numdocuments; } virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { const document_s *n = getItem(index); if(n) switch(role) { case Qt::DisplayRole: if (n->modified) return QVariant(QString::asprintf("%s*", n->fname)); else return QVariant(n->fname); } return QVariant(); } virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const { switch(role) { case Qt::DisplayRole: return QVariant("Files"); } return QVariant(); } void UpdateTitle(void); public: documentlist(WrappedQsciScintilla *editor) { s = editor; numdocuments = 0; docs = nullptr; connect(s, &QsciScintilla::cursorPositionChanged, [=](int line, int index) { if (curdoc) { curdoc->cursorline = line+1; curdoc->cursorindex = index; UpdateTitle(); } }); connect(s, &QsciScintilla::modificationChanged, [=](bool m) { if (curdoc) { curdoc->modified = m; for(int row = 0; row < numdocuments; row++) { if(docs[row] == curdoc) { auto i = index(row); this->dataChanged(i, i); } } } }); } void SetupScintilla(document_s *ed) { // ed->l = new QsciLexerCPP (s); s->SendScintilla(QsciScintillaBase::SCI_STYLERESETDEFAULT); // s->SendScintilla(QsciScintillaBase::SCI_STYLESETFONT, QsciScintillaBase::STYLE_DEFAULT, "Consolas"); s->setFont(QFont(QString("Consolas"), 8)); s->SendScintilla(QsciScintillaBase::SCI_STYLECLEARALL); s->SendScintilla(QsciScintillaBase::SCI_SETCODEPAGE, QsciScintillaBase::SC_CP_UTF8); s->SendScintilla(QsciScintillaBase::SCI_SETLEXER, QsciScintillaBase::SCLEX_CPP); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Default, QColor(0x00, 0x00, 0x00)); s->SendScintilla(QsciScintillaBase::SCI_STYLECLEARALL); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Comment, QColor(0x00, 0x80, 0x00)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::CommentLine, QColor(0x00, 0x80, 0x00)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::CommentDoc, QColor(0x00, 0x80, 0x00)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Number, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Keyword, QColor(0x00, 0x00, 0xFF)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::DoubleQuotedString, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::SingleQuotedString, QColor(0xA0, 0x10, 0x10)); // s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::UUID, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::PreProcessor, QColor(0x00, 0x00, 0xFF)); // s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Operator, QColor(0x00, 0x00, 0x00)); // s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Identifier, QColor(0x00, 0x00, 0x00)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::UnclosedString, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::VerbatimString, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::Regex, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::CommentLineDoc, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::KeywordSet2, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::CommentDocKeyword, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::CommentDocKeywordError, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::GlobalClass, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::RawString, QColor(0xA0, 0x00, 0x00)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::TripleQuotedVerbatimString, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::HashQuotedString, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::PreProcessorComment, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::PreProcessorCommentLineDoc, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::UserLiteral, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::TaskMarker, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciLexerCPP::EscapeSequence, QColor(0xA0, 0x10, 0x10)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciScintillaBase::STYLE_BRACELIGHT, QColor(0x00, 0x00, 0x3F)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETBACK, QsciScintillaBase::STYLE_BRACELIGHT, QColor(0xef, 0xaf, 0xaf)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETBOLD, QsciScintillaBase::STYLE_BRACELIGHT, true); s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, QsciScintillaBase::STYLE_BRACEBAD, QColor(0x3F, 0x00, 0x00)); s->SendScintilla(QsciScintillaBase::SCI_STYLESETBACK, QsciScintillaBase::STYLE_BRACEBAD, QColor(0xff, 0xaf, 0xaf)); //SCE_C_WORD s->SendScintilla(QsciScintillaBase::SCI_SETKEYWORDS, 0ul, "if else for do not while asm break case const continue " "default enum enumflags extern " "float goto __in __out __inout noref " "nosave shared __state optional string " "struct switch thinktime until loop " "typedef union var " "accessor get set inline " "virtual nonvirtual class static nonstatic local return " "string float vector void int integer __variant entity" ); //SCE_C_WORD2 { char buffer[65536]; GenBuiltinsList(buffer, sizeof(buffer)); s->SendScintilla(QsciScintillaBase::SCI_SETKEYWORDS, 1ul, buffer); } //SCE_C_COMMENTDOCKEYWORDERROR //SCE_C_GLOBALCLASS s->SendScintilla(QsciScintillaBase::SCI_SETKEYWORDS, 3ul, "" ); //preprocessor listing { char *deflist = QCC_PR_GetDefinesList(); if (!deflist) deflist = strdup(""); s->SendScintilla(QsciScintillaBase::SCI_SETKEYWORDS, 4ul, deflist); free(deflist); } //task markers (in comments only) s->SendScintilla(QsciScintillaBase::SCI_SETKEYWORDS, 5ul, "TODO FIXME BUG" ); s->SendScintilla(QsciScintillaBase::SCI_USEPOPUP, QsciScintillaBase::SC_POPUP_NEVER); //so we can do right-click menus ourselves. s->SendScintilla(QsciScintillaBase::SCI_SETMOUSEDWELLTIME, 1000); s->SendScintilla(QsciScintillaBase::SCI_AUTOCSETORDER, QsciScintillaBase::SC_ORDER_PERFORMSORT); s->SendScintilla(QsciScintillaBase::SCI_AUTOCSETFILLUPS, nullptr, ".,[<>(*/+-=\t\n"); //Set up gui options. s->SendScintilla(QsciScintillaBase::SCI_SETMARGINWIDTHN, 0, fl_extramargins?40:0); //line numbers+folding s->SendScintilla(QsciScintillaBase::SCI_SETTABWIDTH, fl_tabsize); //tab size //add margin for breakpoints s->SendScintilla(QsciScintillaBase::SCI_SETMARGINMASKN, 1, ~QsciScintillaBase::SC_MASK_FOLDERS); s->SendScintilla(QsciScintillaBase::SCI_SETMARGINWIDTHN, 1, 16); s->SendScintilla(QsciScintillaBase::SCI_SETMARGINSENSITIVEN, 1, true); //give breakpoints a nice red circle. s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, 0, QsciScintillaBase::SC_MARK_CIRCLE); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, 0, QColor(0x7F, 0x00, 0x00)); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, 0, QColor(0xFF, 0x00, 0x00)); //give current line a yellow arrow s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, 1, QsciScintillaBase::SC_MARK_SHORTARROW); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, 1, QColor(0xFF, 0xFF, 0x00)); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, 1, QColor(0x7F, 0x7F, 0x00)); s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, 2, QsciScintillaBase::SC_MARK_BACKGROUND); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, 2, QColor(0x00, 0x00, 0x00)); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, 2, QColor(0xFF, 0xFF, 0x00)); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETALPHA, 2, 0x40); //add margin for folding s->SendScintilla(QsciScintillaBase::SCI_SETPROPERTY, "fold", "1"); s->SendScintilla(QsciScintillaBase::SCI_SETMARGINWIDTHN, 2, fl_extramargins?16:0); s->SendScintilla(QsciScintillaBase::SCI_SETMARGINMASKN, 2, QsciScintillaBase::SC_MASK_FOLDERS); s->SendScintilla(QsciScintillaBase::SCI_SETMARGINSENSITIVEN, 2, true); //stop the images from being stupid s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDEROPEN, QsciScintillaBase::SC_MARK_BOXMINUS); s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDER, QsciScintillaBase::SC_MARK_BOXPLUS); s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDERSUB, QsciScintillaBase::SC_MARK_VLINE); s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDERTAIL, QsciScintillaBase::SC_MARK_LCORNERCURVE); s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDEREND, QsciScintillaBase::SC_MARK_BOXPLUSCONNECTED); s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDEROPENMID, QsciScintillaBase::SC_MARK_BOXMINUSCONNECTED); s->SendScintilla(QsciScintillaBase::SCI_MARKERDEFINE, QsciScintillaBase::SC_MARKNUM_FOLDERMIDTAIL, QsciScintillaBase::SC_MARK_TCORNERCURVE); //and fuck with colours so that its visible. #define FOLDBACK QColor(0x50, 0x50, 0x50) s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, QsciScintillaBase::SC_MARKNUM_FOLDER, QColor(0xFF, 0xFF, 0xFF)); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDER, FOLDBACK); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, QsciScintillaBase::SC_MARKNUM_FOLDEROPEN, QColor(0xFF, 0xFF, 0xFF)); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDEROPEN, FOLDBACK); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, QsciScintillaBase::SC_MARKNUM_FOLDEROPENMID, QColor(0xFF, 0xFF, 0xFF)); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDEROPENMID, FOLDBACK); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDERSUB, FOLDBACK); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETFORE, QsciScintillaBase::SC_MARKNUM_FOLDEREND, QColor(0xFF, 0xFF, 0xFF)); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDEREND, FOLDBACK); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDERTAIL, FOLDBACK); s->SendScintilla(QsciScintillaBase::SCI_MARKERSETBACK, QsciScintillaBase::SC_MARKNUM_FOLDERMIDTAIL, FOLDBACK); //disable preprocessor tracking, because QC preprocessor is not specific to an individual file, and even if it was, includes would be messy. // s->SendScintilla(QsciScintillaBase::SCI_SETPROPERTY, (WPARAM)"lexer.cpp.track.preprocessor", (LPARAM)"0"); for (int i = 0; i < 0x100; i++) { const char *lowtab[32] = {"QNUL",NULL,NULL,NULL,NULL,".",NULL,NULL,NULL,NULL,NULL,"#",NULL,">",".",".", "[","]","0","1","2","3","4","5","6","7","8","9",".","<-","-","->"}; const char *hightab[32] = {"(=","=","=)","=#=","White",".","Green","Red","Yellow","Blue",NULL,"Purple",NULL,">",".",".", "[","]","0","1","2","3","4","5","6","7","8","9",".","<-","-","->"}; char foo[4]; char bar[4]; unsigned char c = i&0xff; foo[0] = i; //these are invalid encodings or control chars. foo[1] = 0; if (c < 32) { if (lowtab[c]) s->SendScintilla(QsciScintillaBase::SCI_SETREPRESENTATION, foo, lowtab[c]); } else if (c >= (128|0) && c < (128|32)) { if (hightab[c-128]) s->SendScintilla(QsciScintillaBase::SCI_SETREPRESENTATION, foo, hightab[c-128]); } else if (c < 128) continue; //don't do anything weird for ascii (other than control chars) else { int b = 0; bar[b++] = c&0x7f; bar[b++] = 0; s->SendScintilla(QsciScintillaBase::SCI_SETREPRESENTATION, foo, bar); } } for (int i = 0xe000; i < 0xe100; i++) { const char *lowtab[32] = {"QNUL",NULL,NULL,NULL,NULL,".",NULL,NULL,NULL,NULL,NULL,"#",NULL,">",".",".", "[","]","0","1","2","3","4","5","6","7","8","9",".","<-","-","->"}; const char *hightab[32] = {"(=","=","=)","=#=","White",".","Green","Red","Yellow","Blue",NULL,"Purple",NULL,">",".",".", "[","]","^0","^1","^2","^3","^4","^5","^6","^7","^8","^9",".","^<-","^-","^->"}; char foo[4]; char bar[4]; unsigned char c = i&0xff; foo[0] = ((i>>12) & 0xf) | 0xe0; foo[1] = ((i>>6) & 0x3f) | 0x80; foo[2] = ((i>>0) & 0x3f) | 0x80; foo[3] = 0; if (c < 32) { if (lowtab[c]) s->SendScintilla(QsciScintillaBase::SCI_SETREPRESENTATION, foo, lowtab[c]); } else if (c >= (128|0) && c < (128|32)) { if (hightab[c-128]) s->SendScintilla(QsciScintillaBase::SCI_SETREPRESENTATION, foo, hightab[c-128]); } else { int b = 0; if (c >= 128) bar[b++] = '^'; bar[b++] = c&0x7f; bar[b++] = 0; s->SendScintilla(QsciScintillaBase::SCI_SETREPRESENTATION, foo, bar); } } /* auto f = fopen("scintilla.cfg", "rt"); if (f) { char buf[256]; while(fgets(buf, sizeof(buf)-1, f)) { int msg; long lparam; long wparam; char *c; buf[sizeof(buf)-1] = 0; c = buf; while(*c == ' ' || *c == '\t') c++; if (c[0] == '#') continue; if (c[0] == '/' && c[1] == '/') continue; if (c[0] == '\r' || c[0] == '\n' || !c[0]) continue; msg = strtoul(c, &c, 0); while(*c == ' ' || *c == '\t') c++; if (*c == '\"') { c++; wparam = c; c = strrchr(c, '\"'); if (c) *c++ = 0; } else wparam = strtoul(c, &c, 0); while(*c == ' ' || *c == '\t') c++; if (*c == '\"') { c++; lparam = c; c = strrchr(c, '\"'); if (c) *c++ = 0; } else lparam = strtoul(c, &c, 0); s->SendScintilla(QsciScintillaBase::msg, wparam, lparam); } if (!ftell(f)) { fclose(f); f = fopen("scintilla.cfg", "wt"); if (f) { int i; int val; for (i = 0; i < STYLE_LASTPREDEFINED; i++) { val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETFORE, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETFORE, i, val); val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETBACK, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETBACK, i, val); val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETBOLD, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETBOLD, i, val); val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETITALIC, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETITALIC, i, val); val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETSIZE, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETSIZE, i, val); val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETFONT, i, (LPARAM)buf); fprintf(f, "%i\t%i\t\"%s\"\n", SCI_STYLESETFONT, i, buf); val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETUNDERLINE, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETUNDERLINE, i, val); val = s->SendScintilla(QsciScintillaBase::SCI_STYLEGETCASE, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETCASE, i, val); } fclose(f); } } else fclose(f); } */ } void SwitchToDocument_Internal(document_s *ed) { curdoc = ed; s->setDocument(ed->doc); switch(ed->endings) { #ifdef _WIN32 case endings_e::NONE: //new file with no endings, default to windows on windows. #endif case endings_e::WINDOWS: //windows s->setEolMode(QsciScintilla::EolMode::EolWindows); s->setEolVisibility(false); break; #ifndef _WIN32 case endings_e::NONE: //new file with no endings, default to unix on non-windows. #endif case endings_e::UNIX: //unix s->setEolMode(QsciScintilla::EolMode::EolUnix); s->setEolVisibility(false); break; case endings_e::MAC: //mac. traditionally qccs have never supported this. one of the mission packs has a \r in the middle of some single-line comment. s->setEolMode(QsciScintilla::EolMode::EolMac); s->setEolVisibility(false); break; default: //panic! everyone panic! s->setEolMode(QsciScintilla::EolMode::EolUnix); s->setEolVisibility(true); break; } s->setUtf8(ed->savefmt != UTF_ANSI); } void ConvertEndings(endings_e newendings) { curdoc->endings = newendings; switch(newendings) { case endings_e::WINDOWS: //windows s->convertEols(QsciScintilla::EolWindows); break; case endings_e::UNIX: //unix s->convertEols(QsciScintilla::EolUnix); break; case endings_e::MAC: //mac. traditionally qccs have never supported this. one of the mission packs has a \r in the middle of some single-line comment. s->convertEols(QsciScintilla::EolMac); break; default: break; //not real endings. } SwitchToDocument_Internal(curdoc); } bool CreateDocument(document_s *ed) { size_t flensz; auto rawfile = QCC_ReadFile(ed->fname, nullptr, nullptr, &flensz); if (!rawfile) return false; auto flen = flensz; pbool dofree; auto file = QCC_SanitizeCharSet(static_cast(rawfile), &flen, &dofree, &ed->savefmt); struct stat sbuf; QCC_StatFile(ed->fname, &sbuf); ed->filemodifiedtime = sbuf.st_mtime; endings_e endings = endings_e::NONE; char *e, *stop; for (e = file, stop=file+flen; e < stop; ) { if (*e == '\r') { e++; if (*e == '\n') { e++; if (endings != endings_e::WINDOWS) endings = endings?(endings_e::MIXED):endings_e::WINDOWS; } else { if (endings != endings_e::MAC) endings = endings?(endings_e::MIXED):endings_e::MAC; } } else if (*e == '\n') { e++; if (endings != endings_e::UNIX) endings = endings?(endings_e::MIXED):endings_e::UNIX; } else e++; } ed->endings = endings; SwitchToDocument_Internal(ed); connect(s, &QsciScintillaBase::SCN_CHARADDED, [=](int charadded) { if (charadded == '(') { int pos = s->SendScintilla(QsciScintillaBase::SCI_GETCURRENTPOS); char *tooltext = GetCalltipForLine(pos-1); //tooltip_editor = NULL; if (tooltext) s->SendScintilla(QsciScintillaBase::SCI_CALLTIPSHOW, pos, tooltext); } }); s->setText(QString(file)); SetupScintilla(ed); s->SendScintilla(QsciScintillaBase::SCI_SETSAVEPOINT); ed->modified = false; return true; } void SwitchToDocument(document_s *ed) { struct stat sbuf; QCC_StatFile(ed->fname, &sbuf); if (ed->filemodifiedtime < sbuf.st_mtime) { CreateDocument(ed); return; } SwitchToDocument_Internal(ed); } document_s *FindFile(const char *filename) { if (!filename) return curdoc; for (int i = 0; i < numdocuments; i++) { if (!strcasecmp(filename, docs[i]->fname)) return docs[i]; } return nullptr; } void *getFileData(document_s *d, unsigned char *(*buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *out_size) { docstacklock lock(this, d); unsigned char *ret = NULL; auto text = s->text().toUtf8(); *out_size = text.length(); if (buf_get) ret = buf_get(buf_ctx, *out_size+1); else ret = static_cast(malloc(*out_size+1)); memcpy(ret, text.data(), *out_size); ret[*out_size] = 0; return ret; } int getFileSize(document_s *d) { docstacklock lock(this,d); int ret = -1; auto text = s->text().toUtf8(); ret = text.length(); return ret; } char *GetCalltipForLine(int cursorpos) //calltips are used for tooltips too, so there's some extra weirdness in here { static char buffer[1024]; char wordbuf[256], *text; char term[256]; char *defname; defname = s->WordUnderCursor(wordbuf, sizeof(wordbuf), term, sizeof(term), cursorpos); if (!*defname) return NULL; else if (globalstable.numbuckets) { QCC_def_t *def; int fno; int line; int best, bestline; char *macro = QCC_PR_CheckCompConstTooltip(defname, buffer, buffer + sizeof(buffer)); if (macro && *macro) return macro; /*if (dwell) { tooltip_editor = NULL; *tooltip_variable = 0; tooltip_position = 0; *tooltip_type = 0; *tooltip_comment = 0; }*/ line = s->SendScintilla(QsciScintillaBase::SCI_LINEFROMPOSITION, cursorpos); for (best = 0,bestline=0, fno = 1; fno < numfunctions; fno++) { if (line > functions[fno].line && bestline < functions[fno].line) { if (!strcmp(curdoc->fname, functions[fno].filen)) { best = fno; bestline = functions[fno].line; } } } if (best) { if (strstr(functions[best].name, "::")) { QCC_type_t *type; char tmp[256]; char *c; QC_strlcpy(tmp, functions[best].name, sizeof(tmp)); c = strstr(tmp, "::"); if (c) *c = 0; type = QCC_TypeForName(tmp); if (type->type == ev_entity) { QCC_def_t *def; QC_snprintfz(tmp, sizeof(tmp), "%s::__m%s", type->name, term); for (fno = 0, def = NULL; fno < sourcefilesnumdefs && !def; fno++) { for (def = sourcefilesdefs[fno]; def; def = def->next) { if (def->scope && def->scope != &functions[best]) continue; // OutputDebugString(def->name); // OutputDebugString("\n"); if (!strcmp(def->name, tmp)) { //FIXME: look at the scope's function to find the start+end of the function and filter based upon that, to show locals break; } } } if (def && def->type->type == ev_field) { // QC_strlcpy(tmp, term, sizeof(tmp)); QC_snprintfz(term, sizeof(term), "self.%s", tmp); } else { for (fno = 0, def = NULL; fno < sourcefilesnumdefs && !def; fno++) { for (def = sourcefilesdefs[fno]; def; def = def->next) { if (def->scope && def->scope != &functions[best]) continue; if (!strcmp(def->name, term)) { //FIXME: look at the scope's function to find the start+end of the function and filter based upon that, to show locals break; } } } if (def && def->type->type == ev_field) { QC_strlcpy(tmp, term, sizeof(tmp)); QC_snprintfz(term, sizeof(term), "self.%s", tmp); } } } } } //FIXME: we may need to display types too for (fno = 0, def = NULL; fno < sourcefilesnumdefs && !def; fno++) { for (def = sourcefilesdefs[fno]; def; def = def->next) { if (def->scope) continue; if (!strcmp(def->name, defname)) { //FIXME: look at the scope's function to find the start+end of the function and filter based upon that, to show locals break; } } } if (def) { char typebuf[1024]; char valuebuf[1024]; if (def->constant && def->type->type == ev_float) QC_snprintfz(valuebuf, sizeof(valuebuf), " = %g", def->symboldata[def->ofs]._float); else if (def->constant && def->type->type == ev_integer) QC_snprintfz(valuebuf, sizeof(valuebuf), " = %i", def->symboldata[def->ofs]._int); else if (def->constant && def->type->type == ev_vector) QC_snprintfz(valuebuf, sizeof(valuebuf), " = '%g %g %g'", def->symboldata[def->ofs].vector[0], def->symboldata[def->ofs].vector[1], def->symboldata[def->ofs].vector[2]); else *valuebuf = 0; //note function argument names do not persist beyond the function def. we might be able to read the function's localdefs for them, but that's unreliable/broken with builtins where they're most needed. if (def->comment) QC_snprintfz(buffer, sizeof(buffer)-1, "%s %s%s\r\n%s", TypeName(def->type, typebuf, sizeof(typebuf)), def->name, valuebuf, def->comment); else QC_snprintfz(buffer, sizeof(buffer)-1, "%s %s%s", TypeName(def->type, typebuf, sizeof(typebuf)), def->name, valuebuf); text = buffer; } else text = NULL; return text; } else return NULL;//"Type info not available. Compile first."; } void clearannotates(void) { for (int i = 0; i < numdocuments; i++) { document_s *d = docs[i]; s->setDocument(d->doc); s->clearAnnotations(); } if (curdoc) s->setDocument(curdoc->doc); } bool annotate(const char *line) { auto filename = line+6; auto filenameend = strchr(filename, ':'); if (!filenameend) return false; auto linenum = atoi(filenameend+1); line = strchr(filenameend+1, ':')+1; if (!line) return false; if (strncmp(curdoc->fname, filename, filenameend-filename) || curdoc->fname[filenameend-filename]) { auto d = FindFile(filename); if (d) { curdoc = d; s->setDocument(d->doc); } else return false; //some other file that we're not interested in } s->annotate(linenum-1, s->annotation(linenum-1) + line + "\n", 0); return true; } bool saveDocument(document_s *d) { struct stat sbuf; bool saved = false; //wordpad will corrupt any embedded quake chars if we force a bom, because it'll re-save using the wrong char encoding by default. int bomlen = 0; const char *bom = ""; if (!d) d = curdoc; if (!d) return false; if (d->savefmt == UTF32BE || d->savefmt == UTF32LE || d->savefmt == UTF16BE) d->savefmt = UTF16LE; if (d->savefmt == UTF8_BOM) { bomlen = 3; bom = "\xEF\xBB\xBF"; } else if (d->savefmt == UTF16BE) { bomlen = 2; bom = "\xFE\xFF"; } else if (d->savefmt == UTF16LE) { bomlen = 2; bom = "\xFF\xFE"; } else if (d->savefmt == UTF32BE) { bomlen = 4; bom = "\x00\x00\xFE\xFF"; } else if (d->savefmt == UTF32LE) { bomlen = 4; bom = "\xFF\xFE\x00\x00"; } docstacklock lock(this, d); auto text = s->text().toUtf8(); text.prepend(bom, bomlen); //because wordpad saves in ansi by default instead of the format the file was originally saved in, we HAVE to use ansi without if (d->savefmt != UTF8_BOM && d->savefmt != UTF8_RAW) { /*int mchars; char *mc; int wchars = MultiByteToWideChar(CP_UTF8, 0, text.data(), text.length(), NULL, 0); if (wchars) { wchar_t *wc = malloc(wchars * sizeof(wchar_t)); MultiByteToWideChar(CP_UTF8, 0, text.data(), text.length(), wc, wchars); if (d->savefmt == UTF_ANSI) { mchars = WideCharToMultiByte(CP_ACP, 0, wc, wchars, NULL, 0, "", &failed); if (mchars) { mc = malloc(mchars); WideCharToMultiByte(CP_ACP, 0, wc, wchars, mc, mchars, "", &failed); if (!failed) { saved = QCC_WriteFile(d->filename, mc, mchars)) } free(mc); } } else saved = QCC_WriteFile(d->filename, wc, wchars); free(wc); }*/ } else saved = QCC_WriteFile(d->fname, text.data(), bomlen+text.length()); if (!saved) { QMessageBox::critical(NULL, "Failure", "Save failed\nCheck path and ReadOnly flags"); return false; } else { s->SendScintilla(QsciScintillaBase::SCI_SETSAVEPOINT); /*now whatever is on disk should have the current time*/ //d->modified = false; QCC_StatFile(d->fname, &sbuf); d->filemodifiedtime = sbuf.st_mtime; //remove the * in a silly way. //d->oldline=~0; //UpdateEditorTitle(d); } UpdateTitle(); return true; } bool saveAll(void) { document_s *d; struct stat sbuf; for (int i = 0; i < numdocuments; i++) { d = docs[i]; QCC_StatFile(d->fname, &sbuf); if (d->modified) { if (d->filemodifiedtime != sbuf.st_mtime) { switch(QMessageBox::question(nullptr, "Modification conflict", QString::asprintf("%s is modified in both memory and on disk. Overwrite external modification? (saying no will reload from disk)", d->fname), QMessageBox::Save|QMessageBox::Reset|QMessageBox::Ignore, QMessageBox::Ignore)) { case QMessageBox::Save: if (!saveDocument(d)) QMessageBox::critical(nullptr, "Error", QString::asprintf("Unable to write %s, file was not saved", d->fname)); break; case QMessageBox::Reset: CreateDocument(d); break; default: case QMessageBox::Ignore: break; /*compiling will use whatever is in memory*/ } } else { /*not modified on disk, but modified in memory? try and save it, cos we might as well*/ if (!saveDocument(d)) QMessageBox::critical(nullptr, "Error", QString::asprintf("Unable to write %s, file was not saved", d->fname)); } } else { /*modified on disk but not in memory? just reload it off disk*/ if (d->filemodifiedtime != sbuf.st_mtime) CreateDocument(d); } } return true; } void reapplyAllBreakpoints(void) { for (int i = 0; i < numdocuments; i++) { document_s *d = docs[i]; int line = -1; s->setDocument(d->doc); for (;;) { line = s->SendScintilla(QsciScintillaBase::SCI_MARKERNEXT, line, 1); if (line == -1) break; //no more. line++; DebuggerSendCommand("qcbreakpoint 1 \"%s\" %i\n", d->fname, line); } } if (curdoc) s->setDocument(curdoc->doc); } void toggleBreak(void) { if (!curdoc) return; int mode = !(s->SendScintilla(QsciScintillaBase::SCI_MARKERGET, curdoc->cursorline-1) & 1); s->SendScintilla(mode?QsciScintillaBase::SCI_MARKERADD:QsciScintillaBase::SCI_MARKERDELETE, curdoc->cursorline-1); DebuggerSendCommand("qcbreakpoint %i \"%s\" %i\n", mode, curdoc->fname, curdoc->cursorline); } /*void setMarkerLine(int linenum) { //moves the 'current-line' marker to the active document and current line for (int i = 0; i < numdocuments; i++) { docstacklock lock(this, docs[i]); s->SendScintilla(QsciScintillaBase::SCI_MARKERDELETEALL, 1); s->SendScintilla(QsciScintillaBase::SCI_MARKERDELETEALL, 2); } if (linenum <= 0) return; s->SendScintilla(QsciScintillaBase::SCI_MARKERADD, linenum-1, 1); s->SendScintilla(QsciScintillaBase::SCI_MARKERADD, linenum-1, 2); }*/ void setNextStatement(void) { if (!curdoc) return; if (!qcdebugger) return; //can't move it if we're not running. DebuggerSendCommand("qcjump \"%s\" %i\n", curdoc->fname, curdoc->cursorline); } void autoComplete(void) { if (!curdoc) return; char word[256]; char suggestions[65536]; s->WordUnderCursor(word, sizeof(word), NULL, 0, -2); if (*word && GenAutoCompleteList(word, suggestions, sizeof(suggestions))) { s->SendScintilla(QsciScintillaBase::SCI_AUTOCSETFILLUPS, ".,[<>(*/+-=\t\n"); s->SendScintilla(QsciScintillaBase::SCI_AUTOCSHOW, strlen(word), suggestions); } //s->SendScintilla(QsciScintillaBase::SCI_CALLTIPSHOW, (int)0, "hello world"); //SCI_CALLTIPSHOW pos, "text" } void setNext(void) { if (!curdoc) return; DebuggerSendCommand("qcjump \"%s\" %i\n", curdoc->fname, curdoc->cursorline); } void EditFile(document_s *doc, const char *filename, int linenum=-1, bool setcontrol=false); void EditFile(const char *filename, int linenum=-1, bool setcontrol=false) { EditFile(NULL, filename, linenum, setcontrol); } void EditFile(QModelIndex idx, int linenum=-1, bool setcontrol=false) { auto d = getItem(idx); if (d) EditFile(d, d->fname, linenum, setcontrol); } }; template class keyEnterReceiver : public QObject { void(*cb)(T &ctx); T ctx; public: keyEnterReceiver(void(*cb_)(T &ctx), T &ctx_) : cb(cb_), ctx(T(ctx_)) { } protected: bool eventFilter(QObject* obj, QEvent* event) { if (event->type()==QEvent::KeyPress) { QKeyEvent* key = static_cast(event); if ( (key->key()==Qt::Key_Enter) || (key->key()==Qt::Key_Return) ) cb(ctx); else return QObject::eventFilter(obj, event); return true; } else return QObject::eventFilter(obj, event); return false; } }; class optionswindow : public QDialog { QWidget *newlineedit(const char *initial, void(*changed)(const QString&)) { auto w = new QLineEdit(initial); connect(w, &QLineEdit::textEdited, changed); return w; } void SetOptsEnabled(QGridLayout *layout, int lev = -1) { for(int i = 0; i < layout->count(); i++) { auto w = static_cast(layout->itemAt(i)->widget()); if (!w) break; auto id = w->property("optnum").toInt(); if (lev >= 0 && lev <= 3) w->setCheckState((lev >= optimisations[id].optimisationlevel)?Qt::Checked:Qt::Unchecked); else if (lev == 4) { if (optimisations[id].flags & FLAG_KILLSDEBUGGERS) w->setCheckState(Qt::Unchecked); } else if (lev == 5) w->setCheckState((optimisations[id].flags&FLAG_ASDEFAULT)?Qt::Checked:Qt::Unchecked); w->setEnabled(fl_nondfltopts); //FIXME: should use tristate on a per-setting basis } } public: ~optionswindow() { GUI_SaveConfig(); } optionswindow() { setModal(true); auto toplayout = new QHBoxLayout; auto leftlayout = new QVBoxLayout; auto rightlayout = new QVBoxLayout; { auto layout = new QGridLayout; for (int i = 0,n=0; optimisations[i].fullname; i++) { if (optimisations[i].flags & FLAG_HIDDENINGUI) continue; auto w = new QCheckBox(optimisations[i].fullname); w->setProperty("optnum", i); w->setCheckState((optimisations[i].flags & FLAG_SETINGUI)?Qt::Checked:Qt::Unchecked); w->setEnabled(!fl_nondfltopts); connect(w, &QCheckBox::stateChanged, [=](int v){if (v)optimisations[i].flags |= FLAG_SETINGUI;else optimisations[i].flags &= ~FLAG_SETINGUI;}); layout->addWidget(w, n>>1, n&1); n++; } SetOptsEnabled(layout, -1); auto gb = new QGroupBox("Optimisations"); auto opts = new QVBoxLayout; opts->addLayout(layout); auto optlevels = new QHBoxLayout; for (int i=0; i<6; i++) { const char *buttons[] = {"O0", "O1", "O2", "O3", "Debug", "Default"}; auto w = new QPushButton(buttons[i]); if (i == 5) { w->setCheckable(true); w->setChecked(fl_nondfltopts); } connect(w, &QPushButton::clicked, [=](bool checked) { if (i == 5) fl_nondfltopts = !checked; else fl_nondfltopts = true; SetOptsEnabled(layout, i); }); optlevels->addWidget(w); } opts->addLayout(optlevels); gb->setLayout(opts); leftlayout->addWidget(gb); } { auto layout = new QFormLayout; layout->addRow("Engine:", newlineedit(enginebinary, [](const QString&str){QC_strlcpy(enginebinary, str.toUtf8().data(), sizeof(enginebinary));})); layout->addRow("Basedir:", newlineedit(enginebasedir, [](const QString&str){QC_strlcpy(enginebasedir, str.toUtf8().data(), sizeof(enginebasedir));})); layout->addRow("Cmdline:", newlineedit(enginecommandline, [](const QString&str){QC_strlcpy(enginecommandline, str.toUtf8().data(), sizeof(enginecommandline));})); leftlayout->addLayout(layout); } { auto layout = new QGridLayout; int n = 0; auto w = new QCheckBox("HexenC"); w->setStatusTip("Compile for hexen2.\nThis changes the opcodes slightly, the progs crc, and enables some additional keywords."); w->setCheckState((fl_hexen2)?Qt::Checked:Qt::Unchecked); connect(w, &QCheckBox::stateChanged, [=](int v){fl_hexen2=!!v;}); layout->addWidget(w, n/3, n%3); n++; w = new QCheckBox("Extended Instructions"); w->setStatusTip("Enables the use of additional opcodes, which only FTE supports at this time.\nThis gives both smaller and faster code, as well as allowing pointers, ints, and other extensions not possible with the vanilla QCVM"); w->setCheckState((fl_ftetarg)?Qt::Checked:Qt::Unchecked); connect(w, &QCheckBox::stateChanged, [=](int v){fl_ftetarg=!!v;}); layout->addWidget(w, n/3, n%3); n++; for (int i = 0; compiler_flag[i].fullname; i++) { if (compiler_flag[i].flags & FLAG_HIDDENINGUI) continue; auto w = new QCheckBox(compiler_flag[i].fullname); w->setCheckState((compiler_flag[i].flags & FLAG_SETINGUI)?Qt::Checked:Qt::Unchecked); connect(w, &QCheckBox::stateChanged, [=](int v){if (v)compiler_flag[i].flags |= FLAG_SETINGUI;else compiler_flag[i].flags &= ~FLAG_SETINGUI;}); layout->addWidget(w, n/3, n%3); n++; } auto gb = new QGroupBox("Compiler Flags"); gb->setLayout(layout); rightlayout->addWidget(gb); auto argbox = new QTextEdit(); argbox->setText(parameters); rightlayout->addWidget(argbox); } toplayout->addLayout(leftlayout); toplayout->addLayout(rightlayout); setLayout(toplayout); } }; static class guimainwindow : public QMainWindow { public: WrappedQsciScintilla s; QSplitter leftrightsplit; QSplitter logsplit; QSplitter leftsplit; QTreeView files_w; QListView docs_w; QTextEdit log; filelist files; documentlist docs; private: void CreateMenus(void) { { auto fileMenu = menuBar()->addMenu(tr("&File")); //editor UI things auto fileopen = new QAction(tr("Open"), this); fileMenu->addAction(fileopen); fileopen->setShortcuts(QKeySequence::listFromString("Ctrl+O")); connect(fileopen, &QAction::triggered, [=]() { GUIprintf("Ctrl+O hit\n"); QMessageBox::critical(nullptr, "Error", QString::asprintf("Not yet implemented")); }); auto filesave = new QAction(tr("Save"), this); fileMenu->addAction(filesave); filesave->setShortcuts(QKeySequence::listFromString("Ctrl+S")); connect(filesave, &QAction::triggered, [=]() { if (!docs.saveDocument(NULL)) QMessageBox::critical(nullptr, "Error", QString::asprintf("Unable to save")); }); auto prefs = new QAction(tr("&Preferences"), this); fileMenu->addAction(prefs); prefs->setShortcuts(QKeySequence::Preferences); prefs->setStatusTip(tr("Reconfigure stuff")); connect(prefs, &QAction::triggered, [](){auto w = new optionswindow();w->setAttribute(Qt::WidgetAttribute::WA_DeleteOnClose, true); w->show();}); auto goline = new QAction(tr("Go to line"), this); fileMenu->addAction(goline); goline->setShortcuts(QKeySequence::listFromString("Ctrl+G")); goline->setStatusTip(tr("Jump to line")); connect(goline, &QAction::triggered, [=]() { struct grepargs_s { guimainwindow *mw; QDialog *d; QLineEdit *t; } args = {this, new QDialog()}; args.t = new QLineEdit(QString("")); auto l = new QVBoxLayout; l->addWidget(args.t); args.d->setLayout(l); args.d->setWindowTitle("FTEQCC Go To Line"); args.t->installEventFilter(new keyEnterReceiver([](grepargs_s &ctx) { ctx.mw->docs.EditFile(NULL, atoi(ctx.t->text().toUtf8().data())); ctx.d->done(0); }, args)); args.d->show(); }); auto find = new QAction(tr("Find In File"), this); fileMenu->addAction(find); find->setShortcuts(QKeySequence::listFromString("Ctrl+F")); find->setStatusTip(tr("Search for a token in the current file")); connect(find, &QAction::triggered, [=]() { struct grepargs_s { guimainwindow *mw; QDialog *d; QLineEdit *t; } args = {this, new QDialog()}; args.t = new QLineEdit(this->s.selectedText()); auto l = new QVBoxLayout; l->addWidget(args.t); args.d->setLayout(l); args.d->setWindowTitle("Find In File"); args.t->installEventFilter(new keyEnterReceiver([](grepargs_s &ctx) { ctx.mw->s.findFirst(ctx.t->text(), false, false, false, true); ctx.d->done(0); }, args)); args.d->show(); }); connect(new QShortcut(QKeySequence(tr("F3", "File|FindNext")), this), &QShortcut::activated, [=]() { s.findNext(); }); auto grep = new QAction(tr("Find In Project"), this); fileMenu->addAction(grep); grep->setShortcuts(QKeySequence::listFromString("Ctrl+Shift+F")); grep->setStatusTip(tr("Search through all project files")); connect(grep, &QAction::triggered, [=]() { struct grepargs_s { guimainwindow *mw; QDialog *d; QLineEdit *t; } args = {this, new QDialog()}; args.t = new QLineEdit(this->s.selectedText()); auto l = new QVBoxLayout; l->addWidget(args.t); args.d->setLayout(l); args.d->setWindowTitle("Find In Project"); args.t->installEventFilter(new keyEnterReceiver([](grepargs_s &ctx) { ctx.mw->files.GrepAll(ctx.t->text().toUtf8().data()); ctx.d->done(0); }, args)); args.d->show(); }); auto quit = new QAction(tr("Quit"), this); fileMenu->addAction(quit); connect(quit, &QAction::triggered, [=]() { this->close(); }); } { auto editMenu = menuBar()->addMenu(tr("&Edit")); //undo //redo auto godef = new QAction(tr("Go To Definition"), this); editMenu->addAction(godef); godef->setShortcuts(QKeySequence::listFromString("F12")); connect(godef, &QAction::triggered, [=]() { struct grepargs_s { guimainwindow *mw; QDialog *d; QLineEdit *t; } args = {this, new QDialog()}; args.t = new QLineEdit(this->s.selectedText()); auto l = new QVBoxLayout; l->addWidget(args.t); args.d->setLayout(l); args.d->setWindowTitle("Go To Definition"); args.t->installEventFilter(new keyEnterReceiver([](grepargs_s &ctx) { GoToDefinition(ctx.t->text().toUtf8().data()); ctx.d->done(0); }, args)); args.d->show(); }); auto autocomplete = new QAction(tr("AutoComplete"), this); editMenu->addAction(autocomplete); autocomplete->setShortcuts(QKeySequence::listFromString("Ctrl+Space")); connect(autocomplete, &QAction::triggered, [=]() { docs.autoComplete(); }); auto convertunix = new QAction(tr("Convert to Unix Endings"), this); editMenu->addAction(convertunix); connect(convertunix, &QAction::triggered, [=]() { docs.ConvertEndings(documentlist::endings_e::UNIX); }); auto convertdos = new QAction(tr("Convert to DOS Endings"), this); editMenu->addAction(convertdos); connect(convertdos, &QAction::triggered, [=]() { docs.ConvertEndings(documentlist::endings_e::WINDOWS); }); //convert to utf-8 chars //convert to Quake chars } { auto debugMenu = menuBar()->addMenu(tr("&Debug")); auto debugrebuild = new QAction(tr("Rebuild"), this); debugMenu->addAction(debugrebuild); debugrebuild->setShortcuts(QKeySequence::listFromString("F7")); connect(debugrebuild, &QAction::triggered, [=]() { RunCompiler(parameters, false); }); auto debugsetnext = new QAction(tr("Set Next Statement"), this); debugMenu->addAction(debugsetnext); debugsetnext->setShortcuts(QKeySequence::listFromString("F8")); connect(debugsetnext, &QAction::triggered, [=]() { docs.setNextStatement(); }); auto debugresume = new QAction(tr("Resume"), this); debugMenu->addAction(debugresume); debugresume->setShortcuts(QKeySequence::listFromString("F5")); connect(debugresume, &QAction::triggered, [=]() { if (!DebuggerSendCommand("qcresume\n")) DebuggerStart(); //unable to send? assume its not running. }); auto debugover = new QAction(tr("Step Over"), this); debugMenu->addAction(debugover); debugover->setShortcuts(QKeySequence::listFromString("F10")); connect(debugover, &QAction::triggered, [=]() { if (!DebuggerSendCommand("qcstep over\n")) DebuggerStart(); }); auto debuginto = new QAction(tr("Step Into"), this); debugMenu->addAction(debuginto); debuginto->setShortcuts(QKeySequence::listFromString("F11")); connect(debuginto, &QAction::triggered, [=]() { if (!DebuggerSendCommand("qcstep into\n")) DebuggerStart(); }); auto debugout = new QAction(tr("Step Out"), this); debugMenu->addAction(debugout); debugout->setShortcuts(QKeySequence::listFromString("Shift+F11")); connect(debugout, &QAction::triggered, [=]() { if (!DebuggerSendCommand("qcstep out\n")) DebuggerStart(); }); auto debugbreak = new QAction(tr("Toggle Breakpoint"), this); debugMenu->addAction(debugbreak); debugbreak->setShortcuts(QKeySequence::listFromString("F9")); connect(debugbreak, &QAction::triggered, [=]() { docs.toggleBreak(); }); } } public: ~guimainwindow() { //if we're dying, make sure there's no engine waiting for us DebuggerStop(); } guimainwindow() : s(this), leftrightsplit(Qt::Horizontal, this), logsplit(Qt::Vertical, this), leftsplit(Qt::Vertical, this), files_w(this), docs_w(this), docs(&s) { setWindowTitle(QString("FTEQCC Gui")); s.setReadOnly(true); files_w.setModel(&files); connect(&files_w, &QTreeView::clicked, [=](const QModelIndex &index) { docs.EditFile(files.getItem(index)->name); }); leftrightsplit.addWidget(&leftsplit); leftrightsplit.addWidget(&logsplit); leftsplit.addWidget(&files_w); leftsplit.addWidget(&docs_w); docs_w.setModel(&docs); logsplit.addWidget(&s); logsplit.addWidget(&log); QList sizes; sizes.append(64); sizes.append(256); leftrightsplit.setSizes(sizes); QList sizes2; sizes.append(1); sizes.append(0); logsplit.setSizes(sizes2); setCentralWidget(&leftrightsplit); log.setReadOnly(true); log.clear(); connect(&docs_w, &QAbstractItemView::clicked, [=](const QModelIndex &index) { docs.EditFile(index); }); connect(&log, &QTextEdit::selectionChanged, [=]() { auto foo = log.textCursor(); foo.select(QTextCursor::LineUnderCursor); auto txt = foo.selectedText(); auto colon = txt.indexOf(':'); if (colon>0) { auto colon2 = txt.indexOf(':', colon+1); if (colon2>0) { auto line = txt.mid(colon+1, colon2-colon-1).toInt(); EditFile(txt.mid(0, colon).toUtf8().data(), line, true); } else EditFile(txt.mid(0, colon).toUtf8().data(), -1, true); } }); CreateMenus(); } } *mainwnd; void WrappedQsciScintilla::contextMenuEvent(QContextMenuEvent *event) { QMenu *menu = createStandardContextMenu(); static char blah[256]; if (*WordUnderCursor(blah, sizeof(blah), NULL, 0, -3)) { menu->addSeparator(); connect(menu->addAction(tr("Go to definition")), &QAction::triggered, [=]() { GoToDefinition(blah); }); connect(menu->addAction(tr("Find Usages")), &QAction::triggered, [=]() { mainwnd->files.GrepAll(blah); }); } menu->addSeparator(); connect(menu->addAction(tr("Toggle Breakpoint")), &QAction::triggered, [=]() { mainwnd->docs.toggleBreak(); }); if (qcdebugger) { connect(menu->addAction(tr("Set Next Statement")), &QAction::triggered, [=]() { mainwnd->docs.setNextStatement(); }); connect(menu->addAction(tr("Resume")), &QAction::triggered, [=]() { }); } else { connect(menu->addAction(tr("Start Debugging")), &QAction::triggered, [=]() { DebuggerStart(); }); } menu->exec(event->globalPos()); delete menu; } //called when progssrcname has changed. //progssrcname should already have been set. void UpdateFileList(void) { char *buffer; AddSourceFile(nullptr, progssrcname); size_t size; buffer = static_cast(QCC_ReadFile(progssrcname, nullptr, nullptr, &size)); pr_file_p = QCC_COM_Parse(buffer); if (*qcc_token == '#') { //aaaahhh! newstyle! } else { pr_file_p = QCC_COM_Parse(pr_file_p); //we dont care about the produced progs.dat while(pr_file_p) { if (*qcc_token == '#') //panic if there's preprocessor in there. break; AddSourceFile(progssrcname, qcc_token); pr_file_p = QCC_COM_Parse(pr_file_p); //we dont care about the produced progs.dat } } free(buffer); //handle any #includes in there RunCompiler(parameters, true); //expand everything, so the user doesn't get annoyed. mainwnd->files_w.expandAll(); } void AddSourceFile(const char *parentpath, const char *filename) { mainwnd->files.AddFile(parentpath, filename); } static int Dummyprintf(const char *msg, ...){return 0;} void RunCompiler(const char *args, pbool quick) { static FILE *logfile; const char *argv[256]; int argc; mainwnd->docs.saveAll(); memset(&guiprogfuncs, 0, sizeof(guiprogfuncs)); guiprogfuncs.funcs.parms = &guiprogexterns; memset(&guiprogexterns, 0, sizeof(guiprogexterns)); guiprogexterns.ReadFile = GUIReadFile; guiprogexterns.FileSize = GUIFileSize; guiprogexterns.WriteFile = QCC_WriteFile; guiprogexterns.Sys_Error = Sys_Error; if (quick) guiprogexterns.Printf = Dummyprintf; else { guiprogexterns.Printf = GUIprintf; GUIprintf(""); } guiprogexterns.DPrintf = guiprogexterns.Printf; if (logfile) fclose(logfile); if (fl_log && !quick) logfile = fopen("fteqcc.log", "wb"); else logfile = NULL; argc = GUI_BuildParms(args, argv, sizeof(argv)/sizeof(argv[0]), quick); if (!argc) guiprogexterns.Printf("Too many args\n"); else if (CompileParams(&guiprogfuncs, NULL, argc, argv)) { if (!quick) { //DebuggerGiveFocus(); DebuggerSendCommand("qcresume\nqcreload\n"); } } if (logfile) { fclose(logfile); logfile = NULL; } } void GUI_DoDecompile(void *buf, size_t size) { const char *c = ReadProgsCopyright((char*)buf, size); if (!c || !*c) c = "COPYRIGHT OWNER NOT KNOWN"; //all work is AUTOMATICALLY copyrighted under the terms of the Berne Convention in all major nations. It _IS_ copyrighted, even if there's no license etc included. Good luck guessing what rights you have. if (QMessageBox::Open == QMessageBox::question(mainwnd, "Copyright", QString::asprintf("The copyright message from this progs is\n%s\n\nPlease respect the wishes and legal rights of the person who created this.", c), QMessageBox::Open|QMessageBox::Cancel, QMessageBox::Cancel)) { extern pbool qcc_vfiles_changed; extern vfile_t *qcc_vfiles; GUIprintf(""); DecompileProgsDat(progssrcname, buf, size); if (qcc_vfiles_changed) { switch (QMessageBox::question(mainwnd, "Decompile", "Save as archive?", QMessageBox::Yes|QMessageBox::SaveAll|QMessageBox::Ignore, QMessageBox::Ignore)) { case QMessageBox::Yes: { QString fname = QFileDialog::getSaveFileName(mainwnd, "Output Archive", QString(), "Zips (*.zip)"); if (!fname.isNull()) { int h = SafeOpenWrite(fname.toUtf8().data(), -1); memset(&guiprogfuncs, 0, sizeof(guiprogfuncs)); guiprogfuncs.funcs.parms = &guiprogexterns; memset(&guiprogexterns, 0, sizeof(guiprogexterns)); guiprogexterns.ReadFile = GUIReadFile; guiprogexterns.FileSize = GUIFileSize; guiprogexterns.WriteFile = QCC_WriteFile; guiprogexterns.Sys_Error = Sys_Error; guiprogexterns.Printf = GUIprintf; qccprogfuncs = &guiprogfuncs; WriteSourceFiles(qcc_vfiles, h, true, false); qccprogfuncs = NULL; SafeClose(h); qcc_vfiles_changed = false; return; } } break; case QMessageBox::SaveAll: { QString path = QFileDialog::getExistingDirectory(mainwnd, "Where do you want to save the decompiled code?", QString()); for (vfile_t *f = qcc_vfiles; f; f = f->next) { char nname[MAX_OSPATH]; int h; QC_snprintfz(nname, sizeof(nname), "%s/%s", path.toUtf8().data(), f->filename); h = SafeOpenWrite(f->filename, -1); if (h >= 0) { SafeWrite(h, f->file, f->size); SafeClose(h); } } } break; default: return; } } } } static void QCC_EnumerateFilesResult(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize) { auto buffer = new char[plainsize]; if (QC_decode(nullptr, compsize, plainsize, method, compdata, buffer)) QCC_AddVFile(name, buffer, plainsize); delete [] buffer; } static void SetMainSrcFile(const char *src) { //if its a path, chdir to it instead const char *sl = strrchr(src, '/'); if (sl) { sl++; auto gah = static_cast(malloc(sl-src+1)); memcpy(gah, src, sl-src); gah[sl-src] = 0; chdir(gah); free(gah); src = sl; } strcpy(progssrcname, src); QCC_CloseAllVFiles(); GUI_SetDefaultOpts(); GUI_ParseCommandLine(cmdlineargs, true); GUI_RevealOptions(); //if the project is a .dat or .zip then decompile it now (so we can access the 'source') { char *ext = strrchr(progssrcname, '.'); if (ext && (!QC_strcasecmp(ext, ".dat") || !QC_strcasecmp(ext, ".pak") || !QC_strcasecmp(ext, ".zip") || !QC_strcasecmp(ext, ".pk3"))) { FILE *f = fopen(progssrcname, "rb"); if (f) { size_t size; fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); auto buf = new char[size]; fread(buf, 1, size, f); fclose(f); if (!QC_EnumerateFilesFromBlob(buf, size, QCC_EnumerateFilesResult) && !QC_strcasecmp(ext, ".dat")) { //its a .dat and contains no .src files GUI_DoDecompile(buf, size); } else if (!QCC_FindVFile("progs.src")) { vfile_t *f; char *archivename = progssrcname; while(strchr(archivename, '\\')) archivename = strchr(archivename, '\\')+1; AddSourceFile(NULL, archivename); // for (f = qcc_vfiles; f; f = f->next) // AddSourceFile(archivename, f->filename); f = QCC_FindVFile("progs.dat"); if (f) GUI_DoDecompile(f->file, f->size); } delete [] buf; strcpy(progssrcname, "progs.src"); } else strcpy(progssrcname, "progs.src"); for (int i = 0; ; i++) { if (!strcmp("embedsrc", compiler_flag[i].abbrev)) { compiler_flag[i].flags |= FLAG_SETINGUI; break; } } } } //then populate the file list. UpdateFileList(); EditFile(progssrcname, -1, false); } int main(int argc, char* argv[]) { //handle initial commandline args GUI_SetDefaultOpts(); { size_t argl = 1; for (int i = 1; i < argc; i++) argl += strlen(argv[i])+3; cmdlineargs = (char*)malloc(argl); argl = 0; for (int i = 1; i < argc; i++) { if (strchr(argv[i], ' ')) { cmdlineargs[argl++] = '\"'; memcpy(cmdlineargs+argl, argv[i], strlen(argv[i])); argl += strlen(argv[i]); cmdlineargs[argl++] = '\"'; } else { memcpy(cmdlineargs+argl, argv[i], strlen(argv[i])); argl += strlen(argv[i]); } cmdlineargs[argl++] = ' '; } cmdlineargs[argl] = 0; int mode = GUI_ParseCommandLine(cmdlineargs, false); if (mode == 1) { //compile-only RunCompiler(cmdlineargs, false); return EXIT_SUCCESS; } } //start up the gui parts QCoreApplication *app = new QApplication(argc, argv); mainwnd = new guimainwindow(); mainwnd->show(); GUIprintf("Welcome to FTEQCC!\n(QT edition)\n"); #ifdef SVNREVISION if (strcmp(STRINGIFY(SVNREVISION), "-")) GUIprintf("FTE SVN Revision: %s\n",STRINGIFY(SVNREVISION)); #endif //and now set up our project... if (*progssrcname) SetMainSrcFile(progssrcname); else SetMainSrcFile("progs.src"); //done, run the gui's main loop. return app->exec(); } void compilecb(void) { } int GUIprintf(const char *msg, ...) { static QString l; va_list va; if (!*msg) { //starting a compile or something. //clear the text and make sure the log is visible. mainwnd->log.setText(QString("")); if (!mainwnd->logsplit.sizes()[1]) { //force it visible QList sizes; sizes.append(2); sizes.append(1); mainwnd->logsplit.setSizes(sizes); } mainwnd->docs.clearannotates(); return 0; } va_start(va, msg); l.append(QString::vasprintf(msg, va)); va_end(va); for (;;) { auto idx = l.indexOf('\n'); if (idx >= 0) { QString s = l.mid(0, idx); l = l.mid(idx+1); if (!s.mid(0, 6).compare("code: ")) { mainwnd->docs.annotate(s.toUtf8().data()); continue; } else if (s.contains(": error") || s.contains(": werror") || !s.mid(0,5).compare("error", Qt::CaseInsensitive)) mainwnd->log.setTextColor(QColor(255, 0, 0)); else if (s.contains(": warning")) mainwnd->log.setTextColor(QColor(128, 128, 0)); else mainwnd->log.setTextColor(QColor(0, 0, 0)); mainwnd->log.append(s); } else break; } return 0; } void documentlist::UpdateTitle(void) { if (curdoc) mainwnd->setWindowTitle(QString::asprintf("%s%s:%i", curdoc->fname, curdoc->modified?"*":"", curdoc->cursorline)); else mainwnd->setWindowTitle("FTEQCC"); } void documentlist::EditFile(document_s *c, const char *filename, int linenum, bool setcontrol) { if (setcontrol) { for (int i = 0; i < numdocuments; i++) { docstacklock lock(this, docs[i]); s->SendScintilla(QsciScintillaBase::SCI_MARKERDELETEALL, 1); s->SendScintilla(QsciScintillaBase::SCI_MARKERDELETEALL, 2); } } if (!c) c = FindFile(filename); if (!c) { c = new document_s(); c->fname = strdup(filename); docs = cpprealloc(docs, sizeof(*docs)*(numdocuments+1)); if (!CreateDocument(c)) { delete(c); return; } beginInsertRows(QModelIndex(), numdocuments, numdocuments); docs[numdocuments] = c; numdocuments++; endInsertRows(); } else { SwitchToDocument(c); } UpdateTitle(); if (linenum >= 1) { linenum--; //scintilla is 0-based, apparently. s->ensureLineVisible(max(1, linenum - 3)); s->ensureLineVisible(linenum + 3); s->setCursorPosition(linenum, 0); s->ensureCursorVisible(); s->setFocus(); if (setcontrol) { s->SendScintilla(QsciScintillaBase::SCI_MARKERADD, linenum, 1); s->SendScintilla(QsciScintillaBase::SCI_MARKERADD, linenum, 2); } } } void EditFile(const char *name, int line, pbool setcontrol) { mainwnd->docs.EditFile(name, line, setcontrol); } void GUI_DialogPrint(const char *title, const char *text) { QMessageBox::information(mainwnd, title, text); } void *GUIReadFile(const char *fname, unsigned char *(*buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *out_size, pbool issourcefile) { auto d = mainwnd->docs.FindFile(fname); if (d) return mainwnd->docs.getFileData(d, buf_get, buf_ctx, out_size); if (issourcefile) AddSourceFile(compilingrootfile, fname); return QCC_ReadFile(fname, buf_get, buf_ctx, out_size); } int GUIFileSize(const char *fname) { auto d = mainwnd->docs.FindFile(fname); if (d) return mainwnd->docs.getFileSize(d); return QCC_PopFileSize(fname); } static void DebuggerStop(void) { if (qcdebugger) { //GUIprintf("Detatching from debuggee\n"); qcdebugger->closeWriteChannel(); qcdebugger->closeReadChannel(QProcess::StandardOutput); qcdebugger->closeReadChannel(QProcess::StandardError); qcdebugger->waitForFinished(); delete qcdebugger; qcdebugger = NULL; } } static bool DebuggerSendCommand(const char *msg, ...) { va_list va; //qcresume //qcstep out|over|into //qcjump "file" line //debuggerwnd windowid //qcinspect "variable" //qcreload //qcbreakpoint off=0|on=1|toggle=2 "file" line if (!qcdebugger || qcdebugger->state() == QProcess::NotRunning) return false; //not running, can't send. va_start(va, msg); qcdebugger->write(QString::vasprintf(msg, va).toUtf8().data()); va_end(va); return true; } extern "C" pbool QCC_PR_SimpleGetToken (void); static void DebuggerStart(void) { DebuggerStop(); const char *engine = enginebinary; char *cmdline = enginecommandline; if (!*enginebinary) { engine = "fteqw"; if(!*cmdline) cmdline = (char*)"-window"; } qcdebugger = new QProcess(mainwnd); qcdebugger->setProgram(engine); qcdebugger->setWorkingDirectory(enginebasedir); QStringList args; pr_file_p = cmdline; while (QCC_PR_SimpleGetToken()) args.append(pr_token); qcdebugger->setArguments(args); QObject::connect(qcdebugger, static_cast(&QProcess::finished), [=](int exitcode,QProcess::ExitStatus status) { // GUIprintf("Debuggee finished\n"); // DebuggerStop(); //can't kill it here, there's still code running inside it mainwnd->activateWindow(); //try and grab the user's attention }); QObject::connect(qcdebugger, &QProcess::readyReadStandardOutput, [=]() { while(qcdebugger->canReadLine()) { auto l = qcdebugger->readLine(); const char *line = l.data(); if (!strncmp(line, "qcstep ", 7) || !strncmp(line, "qcfault ", 8)) { //engine hit a breakpoint or some such. //file, linenum, message line = QCC_COM_Parse (line+7); QString s(qcc_token); if (*line == ':') line++; //grr line = QCC_COM_Parse (line); EditFile(s.toUtf8().data(), atoi(qcc_token), true); line = QCC_COM_Parse (line); mainwnd->activateWindow(); if (*qcc_token) QMessageBox::critical(mainwnd, "Debugger Fault", qcc_token); } else if (!strncmp(line, "qcreloaded ", 10)) { //vmname, progsname mainwnd->docs.reapplyAllBreakpoints(); //send all breakpoints... DebuggerSendCommand("qcresume\n"); //and let it run } //status //curserver //qcstack //qcvalue //refocuswindow else GUIprintf(line); } }); qcdebugger->start(QProcess::ReadWrite|QProcess::Unbuffered); qcdebugger->waitForStarted(); switch (qcdebugger->state()) { case QProcess::NotRunning: GUIprintf("Child process not running\n"); break; case QProcess::Starting: GUIprintf("Child starting up\n"); //still forking? break; case QProcess::Running: // GUIprintf("Child is running\n"); break; } } fteqcc-20251105/./progsint.h0000644000200200001440000004456115233070110014754 0ustar twolifeusers#ifndef PROGSINT_H_INCLUDED #define PROGSINT_H_INCLUDED #ifdef _WIN32 #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #define _CRT_NONSTDC_NO_WARNINGS #ifndef _CRT_SECURE_NO_DEPRECATE #define _CRT_SECURE_NO_DEPRECATE #endif #ifndef _CRT_NONSTDC_NO_DEPRECATE #define _CRT_NONSTDC_NO_DEPRECATE #endif #ifndef AVAIL_ZLIB #ifdef _MSC_VER //#define AVAIL_ZLIB #endif #endif #ifndef _XBOX #include #else #include #endif #else #include #include #include #include #include #include #ifndef __declspec #define __declspec(mode) #endif //#define _inline inline #endif typedef unsigned char pbyte; #include #define DLL_PROG #ifndef PROGSUSED #define PROGSUSED #endif #define false 0 #define true 1 #include "progtype.h" #include "progslib.h" #include "pr_comp.h" #ifndef safeswitch //safeswitch(foo){safedefault: break;} //switch, but errors for any omitted enum values despite the presence of a default case. //(gcc will generally give warnings without the default, but sometimes you don't have control over the source of your enumeration values) #if (__GNUC__ >= 4) #define safeswitch \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic error \"-Wswitch-enum\"") \ _Pragma("GCC diagnostic error \"-Wswitch-default\"") \ switch #define safedefault _Pragma("GCC diagnostic pop") default #else #define safeswitch switch #define safedefault default #endif #endif #ifdef _MSC_VER #pragma warning(disable : 4244) #pragma warning(disable : 4267) #endif #ifndef stricmp #ifdef _WIN32 //Windows-specific... #define stricmp _stricmp #define strnicmp _strnicmp #else //Posix #define stricmp strcasecmp #define strnicmp strncasecmp #endif #endif //extern progfuncs_t *progfuncs; typedef struct sharedvar_s { int varofs; int size; } sharedvar_t; typedef struct { mfunction_t *f; unsigned char stepping; unsigned char progsnum; int s; int pushed; prclocks_t timestamp; } prstack_t; #if defined(QCGC) && defined(MULTITHREAD) #define THREADEDGC #endif typedef struct { unsigned int size; //size of the data. char value[4]; //contents of the tempstring (or really any binary data - but not tempstring references because we don't mark these!). } tempstr_t; //FIXME: the defines hidden inside this structure are evil. typedef struct prinst_s { //temp strings are GCed, and can be created by engine, builtins, or just by ent parsing code. tempstr_t **tempstrings; unsigned int maxtempstrings; #if defined(QCGC) unsigned int nexttempstring; unsigned int livetemps; //increased on alloc, decremented after sweep #ifdef THREADEDGC struct qcgccontext_s *gccontext; #endif #else unsigned int numtempstrings; unsigned int numtempstringsstack; #endif //alloced strings are generally used to direct strings outside of the vm itself, sharing them with general engine state. char **allocedstrings; int maxallocedstrings; int numallocedstrings; struct progstate_s * progstate; #define pr_progstate prinst.progstate unsigned int maxprogs; progsnum_t pr_typecurrent; //active index into progstate array. fixme: remove in favour of only using current_progstate struct progstate_s *current_progstate; #define current_progstate prinst.current_progstate char * watch_name; eval_t * watch_ptr; eval_t watch_old; etype_t watch_type; unsigned int numshares; sharedvar_t *shares; //shared globals, not including parms unsigned int maxshares; struct prmemb_s *memblocks; unsigned int maxfields; unsigned int numfields; fdef_t *field; //biggest size int reorganisefields; //pr_exec.c //call stack #define MAX_STACK_DEPTH 1024 //insanely high value requried for xonotic. prstack_t pr_stack[MAX_STACK_DEPTH]; int pr_depth; //locals #define LOCALSTACK_SIZE (65536*16) //in words int *localstack; int localstack_used; int spushed; //extra //step-by-step debug state int debugstatement; int exitdepth; pbool profiling; prclocks_t profilingalert; //one second, in cpu clocks mfunction_t *pr_xfunction; //active function int pr_xstatement; //active statement //pr_edict.c evalc_t spawnflagscache; unsigned int fields_size; // in bytes unsigned int max_fields_size; //initlib.c int mfreelist; char * addressablehunk; size_t addressableused; size_t addressablesize; unsigned int maxedicts; struct edictrun_s **edicttable; } prinst_t; typedef struct progfuncs_s { struct pubprogfuncs_s funcs; struct prinst_s inst; //private fields. Leave alone. } progfuncs_t; #define prinst progfuncs->inst #define externs progfuncs->funcs.parms #include "qcd.h" #define STRING_SPECMASK 0xc0000000 // #define STRING_TEMP 0x80000000 //temp string, will be collected. #define STRING_STATIC 0xc0000000 //pointer to non-qcvm string. #define STRING_NORMAL_ 0x00000000 //stringtable/mutable. should always be a fallthrough #define STRING_NORMAL2_ 0x40000000 //stringtable/mutable. should always be a fallthrough typedef struct { int targetflags; //weather we need to mark the progs as a newer version char *name; char *opname; int priorityclass; enum {ASSOC_LEFT, ASSOC_RIGHT, ASSOC_RIGHT_RESULT} associative; struct QCC_type_s **type_a, **type_b, **type_c; unsigned int flags; //OPF_* //ASSIGNS_B //ASSIGNS_IB //ASSIGNS_C //ASSIGNS_IC } QCC_opcode_t; extern QCC_opcode_t pr_opcodes[]; // sized by initialization #define OPF_VALID 0x001 //we're allowed to use this opcode in the current target. #define OPF_STD 0x002 //reads a+b, writes c. #define OPF_STORE 0x010 //b+=a or just b=a #define OPF_STOREPTR 0x020 //the form of c=(*b+=a) #define OPF_STOREPTROFS 0x040 //a[c] <- b (c must be 0 when QCC_OPCode_StorePOffset returns false) #define OPF_STOREFLD 0x080 //a.b <- c #define OPF_LOADPTR 0x100 #define OPF_STDUNARY 0x200 //reads a, writes c. //FIXME: add jumps #if defined(_MSC_VER) && _MSC_VER < 1900 #define Q_vsnprintf _vsnprintf #else #define Q_vsnprintf vsnprintf #endif #ifndef max #define max(a,b) ((a) > (b) ? (a) : (b)) #define min(a,b) ((a) < (b) ? (a) : (b)) #endif #define sv_num_edicts (*externs->num_edicts) #define sv_edicts (*externs->edicts) #define PR_DPrintf externs->DPrintf //#define printf syntax error //#define Sys_Error externs->Sys_Error int PRHunkMark(progfuncs_t *progfuncs); void PRHunkFree(progfuncs_t *progfuncs, int mark); void *PRHunkAlloc(progfuncs_t *progfuncs, int size, const char *name); void *PRAddressableExtend(progfuncs_t *progfuncs, void *src, size_t srcsize, int pad); #ifdef printf #undef LIKEPRINTF #define LIKEPRINTF(x) #endif //void *HunkAlloc (int size); char *VARGS qcva (char *text, ...) LIKEPRINTF(1); void QC_InitShares(progfuncs_t *progfuncs); void QC_StartShares(progfuncs_t *progfuncs); void PDECL QC_AddSharedVar(pubprogfuncs_t *progfuncs, int num, int type); void PDECL QC_AddSharedFieldVar(pubprogfuncs_t *progfuncs, int num, char *stringtable); void QC_AddFieldGlobal(pubprogfuncs_t *ppf, int *globdata); int PDECL QC_RegisterFieldVar(pubprogfuncs_t *progfuncs, unsigned int type, const char *name, signed long requestedpos, signed long originalofs); pbool PDECL QC_Decompile(pubprogfuncs_t *progfuncs, const char *fname); int PDECL PR_ToggleBreakpoint(pubprogfuncs_t *progfuncs, const char *filename, int linenum, int flag); void StripExtension (char *path); #define edvars(ed) (((edictrun_t*)ed)->fields) //pointer to the field vars, given an edict void SetEndian(void); extern short (*PRBigShort) (short l); extern short (*PRLittleShort) (short l); extern int (*PRBigLong) (int l); extern int (*PRLittleLong) (int l); extern float (*PRBigFloat) (float l); extern float (*PRLittleFloat) (float l); /* #ifndef COMPILER typedef union eval_s { string_t string; float _float; float vector[3]; func_t function; int _int; int edict; progsnum_t prog; //so it can easily be changed } eval_t; #endif */ typedef struct edictrun_s { enum ereftype_e ereftype; float freetime; // realtime when the object was freed unsigned int entnum; unsigned int fieldsize; pbool readonly; //causes error when QC tries writing to it. (quake's world entity) void *fields; // other fields from progs come immediately after } edictrun_t; int PDECL Comp_Begin(pubprogfuncs_t *progfuncs, int nump, const char **parms); int PDECL Comp_Continue(pubprogfuncs_t *progfuncs); pbool PDECL PR_SetWatchPoint(pubprogfuncs_t *progfuncs, const char *desc, const char *location); char *PDECL PR_EvaluateDebugString(pubprogfuncs_t *progfuncs, const char *key); char *PDECL PR_SaveEnts(pubprogfuncs_t *progfuncs, char *mem, size_t *size, size_t maxsize, int mode); int PDECL PR_LoadEnts(pubprogfuncs_t *ppf, const char *file, void *ctx, void (PDECL *memoryreset) (pubprogfuncs_t *progfuncs, void *ctx), void (PDECL *entspawned) (pubprogfuncs_t *progfuncs, struct edict_s *ed, void *ctx, const char *entstart, const char *entend), pbool(PDECL *extendedterm)(pubprogfuncs_t *progfuncs, void *ctx, const char **extline)); char *PDECL PR_SaveEnt (pubprogfuncs_t *progfuncs, char *buf, size_t *size, size_t maxsize, struct edict_s *ed); struct edict_s *PDECL PR_RestoreEnt (pubprogfuncs_t *progfuncs, const char *buf, size_t *size, struct edict_s *ed); void PDECL PR_StackTrace (pubprogfuncs_t *progfuncs, int showlocals); eval_t *PR_GetReadTempStringPtr(progfuncs_t *progfuncs, string_t str, size_t offset, size_t datasize); eval_t *PR_GetWriteTempStringPtr(progfuncs_t *progfuncs, string_t str, size_t offset, size_t datasize); extern int noextensions; typedef enum { PST_DEFAULT,//everything 16bit PST_FTE32, //everything 32bit PST_KKQWSV, //32bit statements, 16bit globaldefs. NO SAVED GAMES. PST_QTEST, //16bit statements, 32bit globaldefs(other differences converted on load) PST_UHEXEN2,//everything 32bit like fte's without a header, but with pre-padding rather than post-extended (little-endian) types. } progstructtype_t; #ifndef COMPILER typedef struct progstate_s { dprograms_t *progs; mfunction_t *functions; char *strings; union { ddefXX_t *globaldefs; ddef16_t *globaldefs16; //vanilla, kk ddef32_t *globaldefs32; //fte, qtest }; union { ddefXX_t *fielddefs; ddef16_t *fielddefs16; //vanilla, kk ddef32_t *fielddefs32; //fte, qtest }; // union { void *statements; // dstatement16_t *statements16; //vanilla, qtest // dstatement32_t *statements32; //fte, kk // }; // void *global_struct; float *globals; // same as pr_global_struct unsigned int globals_bytes; // in bytes typeinfo_t *types; int edict_size; // in bytes char filename[128]; int *linenums; //debug versions only progstructtype_t structtype; //specifies the sized struct types above. FIXME: should probably just load as 32bit or something. #ifdef QCJIT struct jitstate *jit; #endif } progstate_t; //============================================================================ #define pr_progs current_progstate->progs #define pr_cp_functions current_progstate->functions #define pr_strings current_progstate->strings #define pr_globaldefs16 ((ddef16_t*)current_progstate->globaldefs16) #define pr_globaldefs32 ((ddef32_t*)current_progstate->globaldefs32) #define pr_fielddefs16 ((ddef16_t*)current_progstate->fielddefs16) #define pr_fielddefs32 ((ddef32_t*)current_progstate->fielddefs32) #define pr_statements16 ((dstatement16_t*)current_progstate->statements) #define pr_statements32 ((dstatement32_t*)current_progstate->statements) //#define pr_global_struct current_progstate->global_struct #define pr_globals current_progstate->globals #define pr_linenums current_progstate->linenums #define pr_types current_progstate->types //============================================================================ void PR_Init (void); pbool PR_RunWarning (pubprogfuncs_t *progfuncs, char *error, ...); void PDECL PR_ExecuteProgram (pubprogfuncs_t *progfuncs, func_t fnum); progsnum_t PDECL PR_LoadProgs(pubprogfuncs_t *progfncs, const char *s); pbool PR_ReallyLoadProgs (progfuncs_t *progfuncs, const char *filename, progstate_t *progstate, pbool complain); void *PRHunkAlloc(progfuncs_t *progfuncs, int ammount, const char *name); void PR_Profile_f (void); struct edict_s *PDECL ED_Alloc (pubprogfuncs_t *progfuncs, pbool object, size_t extrasize); struct edict_s *PDECL ED_AllocIndex (pubprogfuncs_t *progfuncs, unsigned int num, pbool object, size_t extrasize); void PDECL ED_Free (pubprogfuncs_t *progfuncs, struct edict_s *ed, pbool instant); #ifdef QCGC void PR_RunGC (progfuncs_t *progfuncs); #else void PR_FreeTemps (progfuncs_t *progfuncs, int depth); #endif string_t PDECL PR_AllocTempString (pubprogfuncs_t *ppf, const char *str); char *PDECL ED_NewString (pubprogfuncs_t *ppf, const char *string, int minlength, pbool demarkup); // returns a copy of the string allocated from the server's string heap void PDECL ED_Print (pubprogfuncs_t *progfuncs, struct edict_s *ed); //void ED_Write (FILE *f, edictrun_t *ed); //void ED_WriteGlobals (FILE *f); void ED_ParseGlobals (char *data); //void ED_LoadFromFile (char *data); //define EDICT_NUM(n) ((edict_t *)(sv.edicts+ (n)*pr_edict_size)) //define NUM_FOR_EDICT(e) (((byte *)(e) - sv.edicts)/pr_edict_size) struct edict_s *PDECL QC_EDICT_NUM(pubprogfuncs_t *progfuncs, unsigned int n); unsigned int PDECL QC_NUM_FOR_EDICT(pubprogfuncs_t *progfuncs, struct edict_s *e); #define EDICT_NUM(pf, num) QC_EDICT_NUM(&pf->funcs,num) #define NUM_FOR_EDICT(pf, e) QC_NUM_FOR_EDICT(&pf->funcs,e) //#define NEXT_EDICT(e) ((edictrun_t *)( (byte *)e + pr_edict_size)) #define EDICT_TO_PROG(pf, e) (((edictrun_t*)e)->entnum) #define PROG_TO_EDICT_PB(pf, e) ((struct edictrun_s *)prinst.edicttable[e]) //index already validated #define PROG_TO_EDICT_UB(pf, e) ((struct edictrun_s *)prinst.edicttable[((unsigned int)(e)v)[o]) #define E_INT(e,o) (*(int *)&((float*)&e->v)[o]) #define E_VECTOR(e,o) (&((float*)&e->v)[o]) //#define E_STRING(e,o) (*(string_t *)&((float*)(e+1))[o]) extern const unsigned int type_size[]; extern unsigned short pr_crc; void VARGS PR_RunError (pubprogfuncs_t *progfuncs, const char *error, ...) LIKEPRINTF(2); void ED_PrintEdicts (progfuncs_t *progfuncs); void ED_PrintNum (progfuncs_t *progfuncs, int ent); pbool PR_SwitchProgs(progfuncs_t *progfuncs, progsnum_t type); pbool PR_SwitchProgsParms(progfuncs_t *progfuncs, progsnum_t newprogs); eval_t *PDECL QC_GetEdictFieldValue(pubprogfuncs_t *progfuncs, struct edict_s *ed, const char *name, etype_t type, evalc_t *cache); void PDECL PR_GenerateStatementString (pubprogfuncs_t *progfuncs, int statementnum, char *out, int outlen); fdef_t *PDECL ED_FieldInfo (pubprogfuncs_t *progfuncs, unsigned int *count); char *PDECL PR_UglyValueString (pubprogfuncs_t *progfuncs, etype_t type, eval_t *val); pbool PDECL ED_ParseEval (pubprogfuncs_t *progfuncs, eval_t *eval, int type, const char *s); char *PR_SaveCallStack (progfuncs_t *progfuncs, char *buf, size_t *bufofs, size_t bufmax); prclocks_t Sys_GetClockRate(void); #endif #ifndef COMPILER //this is windows - all files are written with this endian standard //optimisation //leave undefined if in doubt over os. #ifdef _WIN32 #define NOENDIAN #endif //pr_multi.c extern pvec3_t pvec3_origin; struct qcthread_s *PDECL PR_ForkStack (pubprogfuncs_t *progfuncs); void PDECL PR_ResumeThread (pubprogfuncs_t *progfuncs, struct qcthread_s *thread); void PDECL PR_AbortStack (pubprogfuncs_t *progfuncs); pbool PDECL PR_GetBuiltinCallInfo (pubprogfuncs_t *ppf, int *builtinnum, char *function, size_t sizeoffunction); eval_t *PDECL PR_FindGlobal(pubprogfuncs_t *prfuncs, const char *globname, progsnum_t pnum, etype_t *type); ddef16_t *ED_FindTypeGlobalFromProgs16 (progfuncs_t *progfuncs, progstate_t *ps, const char *name, int type); ddef32_t *ED_FindTypeGlobalFromProgs32 (progfuncs_t *progfuncs, progstate_t *ps, const char *name, int type); ddef16_t *ED_FindGlobalFromProgs16 (progfuncs_t *progfuncs, progstate_t *ps, const char *name); ddef32_t *ED_FindGlobalFromProgs32 (progfuncs_t *progfuncs, progstate_t *ps, const char *name); fdef_t *ED_FindField (progfuncs_t *progfuncs, const char *name); fdef_t *ED_ClassFieldAtOfs (progfuncs_t *progfuncs, unsigned int ofs, const char *classname); fdef_t *ED_FieldAtOfs (progfuncs_t *progfuncs, unsigned int ofs); mfunction_t *ED_FindFunction (progfuncs_t *progfuncs, const char *name, progsnum_t *pnum, progsnum_t fromprogs); func_t PDECL PR_FindFunc(pubprogfuncs_t *progfncs, const char *funcname, progsnum_t pnum); //void PDECL PR_Configure (pubprogfuncs_t *progfncs, size_t addressable_size, int max_progs); int PDECL PR_InitEnts(pubprogfuncs_t *progfncs, int maxents); char *PR_ValueString (progfuncs_t *progfuncs, etype_t type, eval_t *val, pbool verbose); void PDECL QC_ClearEdict (pubprogfuncs_t *progfuncs, struct edict_s *ed); void PRAddressableFlush(progfuncs_t *progfuncs, size_t totalammount); void QC_FlushProgsOffsets(progfuncs_t *progfuncs); ddef16_t *ED_GlobalAtOfs16 (progfuncs_t *progfuncs, int ofs); ddef16_t *ED_FindGlobal16 (progfuncs_t *progfuncs, const char *name); ddef32_t *ED_FindGlobal32 (progfuncs_t *progfuncs, const char *name); ddef32_t *ED_GlobalAtOfs32 (progfuncs_t *progfuncs, unsigned int ofs); string_t PDECL PR_StringToProgs (pubprogfuncs_t *inst, const char *str); const char *ASMCALL PR_StringToNative (pubprogfuncs_t *inst, string_t str); char *PR_GlobalString (progfuncs_t *progfuncs, int ofs, struct QCC_type_s **typehint); char *PR_GlobalStringNoContents (progfuncs_t *progfuncs, int ofs); char *PR_GlobalStringImmediate (progfuncs_t *progfuncs, int ofs); pbool CompileFile(progfuncs_t *progfuncs, const char *filename); struct jitstate; struct jitstate *PR_GenerateJit(progfuncs_t *progfuncs); void PR_EnterJIT(progfuncs_t *progfuncs, struct jitstate *jitstate, int statement); void PR_CloseJit(struct jitstate *jit); char *QCC_COM_Parse (const char *data); extern char qcc_token[1024]; extern char *basictypenames[]; #endif #endif fteqcc-20251105/./qccgui.c0000644000200200001440000061461615233070110014361 0ustar twolifeusers #include #include #include #include #include #include #include #include #include #include "qcc.h" #include "gui.h" //#define AVAIL_PNGLIB //#define AVAIL_ZLIB #define EMBEDDEBUG #define IDI_ICON_FTEQCC MAKEINTRESOURCE(101) void OptionsDialog(void); static void GUI_CreateInstaller_Windows(void); static void GUI_CreateInstaller_Android(void); static void SetProgsSrcFileAndPath(char *filename); static void CreateOutputWindow(pbool doannoates); void AddSourceFile(const char *parentsrc, const char *filename); #ifndef TVM_SETBKCOLOR #define TVM_SETBKCOLOR (TV_FIRST + 29) #endif #ifndef TreeView_SetBkColor #define TreeView_SetBkColor(hwnd, clr) \ (COLORREF)SNDMSG((hwnd), TVM_SETBKCOLOR, 0, (LPARAM)(clr)) #endif #ifndef TTF_TRACK #define TTF_TRACK 0x0020 #endif #ifndef TTF_ABSOLUTE #define TTF_ABSOLUTE 0x0080 #endif #ifndef TTM_SETMAXTIPWIDTH #define TTM_SETMAXTIPWIDTH (WM_USER + 24) #endif #ifndef TTM_TRACKACTIVATE #define TTM_TRACKACTIVATE (WM_USER + 17) #endif #ifndef TTM_TRACKPOSITION #define TTM_TRACKPOSITION (WM_USER + 18) #endif //scintilla stuff #define SCI_GETLENGTH 2006 #define SCI_GETCHARAT 2007 #define SCI_GETCURRENTPOS 2008 #define SCI_GETANCHOR 2009 #define SCI_REDO 2011 #define SCI_SETSAVEPOINT 2014 #define SCI_CANREDO 2016 #define SCI_GETCURLINE 2027 #define SCI_CONVERTEOLS 2029 #define SC_EOL_CRLF 0 #define SC_EOL_CR 1 #define SC_EOL_LF 2 #define SCI_SETEOLMODE 2031 #define SCI_SETTABWIDTH 2036 #define SCI_SETCODEPAGE 2037 #define SCI_MARKERDEFINE 2040 #define SCI_MARKERSETFORE 2041 #define SCI_MARKERSETBACK 2042 #define SCI_MARKERADD 2043 #define SCI_MARKERDELETE 2044 #define SCI_MARKERDELETEALL 2045 #define SCI_MARKERSETALPHA 2476 #define SCI_MARKERGET 2046 #define SCI_MARKERNEXT 2047 #define SCI_STYLECLEARALL 2050 #define SCI_STYLESETFORE 2051 #define SCI_STYLESETBACK 2052 #define SCI_STYLESETBOLD 2053 #define SCI_STYLESETITALIC 2054 #define SCI_STYLESETSIZE 2055 #define SCI_STYLESETFONT 2056 #define SCI_STYLERESETDEFAULT 2058 #define SCI_STYLESETUNDERLINE 2059 #define SCI_STYLESETCASE 2060 #define SCI_AUTOCSHOW 2100 #define SCI_AUTOCCANCEL 2101 #define SCI_AUTOCACTIVE 2102 #define SCI_AUTOCSETFILLUPS 2112 #define SCI_GETLINE 2153 #define SCI_SETSEL 2160 #define SCI_GETSELTEXT 2161 #define SCI_LINEFROMPOSITION 2166 #define SCI_POSITIONFROMLINE 2167 #define SCI_REPLACESEL 2170 #define SCI_CANUNDO 2174 #define SCI_UNDO 2176 #define SCI_CUT 2177 #define SCI_COPY 2178 #define SCI_PASTE 2179 #define SCI_SETTEXT 2181 #define SCI_GETTEXT 2182 #define SCI_CALLTIPSHOW 2200 #define SCI_CALLTIPCANCEL 2201 #define SCI_TOGGLEFOLD 2231 #define SCI_SETMARGINWIDTHN 2242 #define SCI_SETMARGINMASKN 2244 #define SCI_SETMARGINSENSITIVEN 2246 #define SCI_SETMOUSEDWELLTIME 2264 #define SCI_CHARLEFT 2304 #define SCI_CHARRIGHT 2306 #define SCI_BACKTAB 2328 #define SCI_SEARCHANCHOR 2366 #define SCI_SEARCHNEXT 2367 #define SCI_SEARCHPREV 2368 #define SCI_STYLEGETFORE 2481 #define SCI_STYLEGETBACK 2482 #define SCI_STYLEGETBOLD 2483 #define SCI_STYLEGETITALIC 2484 #define SCI_STYLEGETSIZE 2485 #define SCI_STYLEGETFONT 2486 #define SCI_STYLEGETUNDERLINE 2488 #define SCI_STYLEGETCASE 2489 #define SCI_BRACEHIGHLIGHTINDICATOR 2498 #define SCI_BRACEBADLIGHTINDICATOR 2499 #define SCI_LINELENGTH 2350 #define SCI_BRACEHIGHLIGHT 2351 #define SCI_BRACEBADLIGHT 2352 #define SCI_BRACEMATCH 2353 #define SCI_SETVIEWEOL 2356 #define SCI_USEPOPUP 2371 #define SCI_ANNOTATIONSETTEXT 2540 #define SCI_ANNOTATIONGETTEXT 2541 #define SCI_ANNOTATIONSETSTYLE 2542 #define SCI_ANNOTATIONGETSTYLE 2543 #define SCI_ANNOTATIONSETSTYLES 2544 #define SCI_ANNOTATIONGETSTYLES 2545 #define SCI_ANNOTATIONGETLINES 2546 #define SCI_ANNOTATIONCLEARALL 2547 #define ANNOTATION_HIDDEN 0 #define ANNOTATION_STANDARD 1 #define ANNOTATION_BOXED 2 #define ANNOTATION_INDENTED 3 #define SCI_ANNOTATIONSETVISIBLE 2548 #define SCI_ANNOTATIONGETVISIBLE 2549 #define SCI_ANNOTATIONSETSTYLEOFFSET 2550 #define SCI_ANNOTATIONGETSTYLEOFFSET 2551 #define SCI_AUTOCSETORDER 2660 #define SCI_SETREPRESENTATION 2665 #define SCI_SETLEXER 4001 #define SCI_SETPROPERTY 4004 #define SCI_SETKEYWORDS 4005 #define SC_ORDER_PERFORMSORT 1 #define SC_CP_UTF8 65001 #define SCLEX_CPP 3 #define SCE_C_DEFAULT 0 #define SCE_C_COMMENT 1 #define SCE_C_COMMENTLINE 2 #define SCE_C_COMMENTDOC 3 #define SCE_C_NUMBER 4 #define SCE_C_WORD 5 #define SCE_C_STRING 6 #define SCE_C_CHARACTER 7 #define SCE_C_UUID 8 #define SCE_C_PREPROCESSOR 9 #define SCE_C_OPERATOR 10 #define SCE_C_IDENTIFIER 11 #define SCE_C_STRINGEOL 12 #define SCE_C_VERBATIM 13 #define SCE_C_REGEX 14 #define SCE_C_COMMENTLINEDOC 15 #define SCE_C_WORD2 16 #define SCE_C_COMMENTDOCKEYWORD 17 #define SCE_C_COMMENTDOCKEYWORDERROR 18 #define SCE_C_GLOBALCLASS 19 #define SCE_C_STRINGRAW 20 #define SCE_C_TRIPLEVERBATIM 21 #define SCE_C_HASHQUOTEDSTRING 22 #define SCE_C_PREPROCESSORCOMMENT 23 #define SCE_C_PREPROCESSORCOMMENTDOC 24 #define SCE_C_USERLITERAL 25 #define SCE_C_TASKMARKER 26 #define SCE_C_ESCAPESEQUENCE 27 #define STYLE_DEFAULT 32 #define STYLE_BRACELIGHT 34 #define STYLE_BRACEBAD 35 #define STYLE_LASTPREDEFINED 39 #define SC_MARKNUM_FOLDEREND 25 #define SC_MARKNUM_FOLDEROPENMID 26 #define SC_MARKNUM_FOLDERMIDTAIL 27 #define SC_MARKNUM_FOLDERTAIL 28 #define SC_MARKNUM_FOLDERSUB 29 #define SC_MARKNUM_FOLDER 30 #define SC_MARKNUM_FOLDEROPEN 31 #define SC_MASK_FOLDERS 0xFE000000 #define SC_MARK_CIRCLE 0 #define SC_MARK_ROUNDRECT 1 #define SC_MARK_ARROW 2 #define SC_MARK_SMALLRECT 3 #define SC_MARK_SHORTARROW 4 #define SC_MARK_EMPTY 5 #define SC_MARK_ARROWDOWN 6 #define SC_MARK_MINUS 7 #define SC_MARK_PLUS 8 #define SC_MARK_VLINE 9 #define SC_MARK_LCORNER 10 #define SC_MARK_TCORNER 11 #define SC_MARK_BOXPLUS 12 #define SC_MARK_BOXPLUSCONNECTED 13 #define SC_MARK_BOXMINUS 14 #define SC_MARK_BOXMINUSCONNECTED 15 #define SC_MARK_LCORNERCURVE 16 #define SC_MARK_TCORNERCURVE 17 #define SC_MARK_CIRCLEPLUS 18 #define SC_MARK_CIRCLEPLUSCONNECTED 19 #define SC_MARK_CIRCLEMINUS 20 #define SC_MARK_CIRCLEMINUSCONNECTED 21 #define SC_MARK_BACKGROUND 22 #define SC_MARK_DOTDOTDOT 23 #define SC_MARK_ARROWS 24 #define SC_MARK_PIXMAP 25 #define SC_MARK_FULLRECT 26 #define SC_MARK_LEFTRECT 27 #define SC_MARK_AVAILABLE 28 #define SC_MARK_UNDERLINE 29 #define SC_MARK_RGBAIMAGE 30 #define SC_MARK_BOOKMARK 31 #define SCN_CHARADDED 2001 #define SCN_SAVEPOINTREACHED 2002 #define SCN_SAVEPOINTLEFT 2003 #define SCN_UPDATEUI 2007 #define SCN_MARGINCLICK 2010 #define SCN_DWELLSTART 2016 #define SCN_DWELLEND 2017 #define SCN_FOCUSOUT 2029 struct SCNotification { NMHDR nmhdr; int position; int ch; int modifiers; int modificationType; const char *text; int length; int linesAdded; int message; DWORD_PTR wParam; LONG_PTR lParam; int line; int foldLevelNow; int foldLevelPrev; int margin; int listType; int x; int y; int token; int annotationLinesAdded; int updated; }; //these all run on the main thread typedef struct editor_s { char filename[MAX_PATH]; //abs HWND window; HWND editpane; HWND tooltip; char tooltiptext[1024]; int curline; pbool modified; pbool scintilla; int savefmt; time_t filemodifiedtime; struct editor_s *next; //for avoiding silly redraws etc when titles don't actually change... int oldsavefmt; int oldline; } editor_t; editor_t *editors; typedef struct { editor_t *editor; //will need to be validated unsigned int selpos; unsigned int anchorpos; } navhistory_t; navhistory_t navhistory[8]; const unsigned int navhistory_size = sizeof(navhistory)/sizeof(navhistory[0]); unsigned int navhistory_first; //don't allow rewinding past this. unsigned int navhistory_pos; //the engine thread simply sits waiting for responses from the engine typedef struct { int pipeclosed; DWORD tid; HWND window; HWND refocuswindow; HANDLE thread; HANDLE pipefromengine; HANDLE pipetoengine; size_t embedtype; //0 = not. 1 = separate. 2 = mdi child } enginewindow_t; static pbool EngineCommandf(char *message, ...); static void EngineGiveFocus(void); /* static pbool QCC_RegGetStringValue(HKEY base, char *keyname, char *valuename, void *data, int datalen) { pbool result = false; HKEY subkey; DWORD type = REG_NONE; if (RegOpenKeyEx(base, keyname, 0, KEY_READ, &subkey) == ERROR_SUCCESS) { DWORD dwlen = datalen-1; result = ERROR_SUCCESS == RegQueryValueEx(subkey, valuename, NULL, &type, data, &dwlen); datalen = dwlen; RegCloseKey (subkey); } if (type == REG_SZ || type == REG_EXPAND_SZ) ((char*)data)[datalen] = 0; else ((char*)data)[0] = 0; return result; } static pbool QCC_RegSetValue(HKEY base, char *keyname, char *valuename, int type, void *data, int datalen) { pbool result = false; HKEY subkey; if (RegCreateKeyEx(base, keyname, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &subkey, NULL) == ERROR_SUCCESS) { if (ERROR_SUCCESS == RegSetValueEx(subkey, valuename, 0, type, data, datalen)) result = true; RegCloseKey (subkey); } return result; } */ #undef printf #undef Sys_Error void Sys_Error(const char *text, ...); extern pbool qcc_vfiles_changed; extern vfile_t *qcc_vfiles; HWND mainwindow; HINSTANCE ghInstance; static INT CALLBACK StupidBrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) ; void QCC_SaveVFiles(void) { vfile_t *f; if (qcc_vfiles_changed) { switch (MessageBox(mainwindow, "Save files as archive?", "FTEQCCGUI", MB_YESNOCANCEL)) { case IDYES: { char filename[MAX_PATH]; char oldpath[MAX_PATH+10]; OPENFILENAME ofn; memset(&ofn, 0, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hInstance = ghInstance; ofn.lpstrFile = filename; ofn.lpstrTitle = "Output archive"; ofn.nMaxFile = sizeof(filename)-1; ofn.lpstrFilter = "QuakeC Projects\0*.zip\0All files\0*.*\0"; memset(filename, 0, sizeof(filename)); GetCurrentDirectory(sizeof(oldpath)-1, oldpath); ofn.lpstrInitialDir = oldpath; if (GetSaveFileName(&ofn)) { int h = SafeOpenWrite(ofn.lpstrFile, -1); progfuncs_t funcs; progexterns_t ext; memset(&funcs, 0, sizeof(funcs)); funcs.funcs.parms = &ext; memset(&ext, 0, sizeof(ext)); ext.ReadFile = GUIReadFile; ext.FileSize = GUIFileSize; ext.WriteFile = QCC_WriteFile; ext.Sys_Error = Sys_Error; ext.Printf = GUIprintf; qccprogfuncs = &funcs; WriteSourceFiles(qcc_vfiles, h, true, false); qccprogfuncs = NULL; SafeClose(h); qcc_vfiles_changed = false; return; } } break; case IDNO: { char oldworkingdir[MAX_PATH], newdir[MAX_PATH+10], workingdir[MAX_PATH]; BROWSEINFO bi; LPITEMIDLIST il; memset(&bi, 0, sizeof(bi)); bi.hwndOwner = mainwindow; bi.pidlRoot = NULL; GetCurrentDirectory(sizeof(oldworkingdir)-1, oldworkingdir); GetCurrentDirectory(sizeof(workingdir)-1, workingdir); bi.pszDisplayName = workingdir; bi.lpszTitle = "Where do you want the source?"; bi.ulFlags = BIF_RETURNONLYFSDIRS|BIF_STATUSTEXT; bi.lpfn = StupidBrowseCallbackProc; bi.lParam = 0; bi.iImage = 0; il = SHBrowseForFolder(&bi); if (il) { SHGetPathFromIDList(il, newdir); CoTaskMemFree(il); for (f = qcc_vfiles; f; f = f->next) { char nname[MAX_PATH]; int h; QC_snprintfz(nname, sizeof(nname), "%s\\%s", newdir, f->filename); h = SafeOpenWrite(f->filename, -1); if (h >= 0) { SafeWrite(h, f->file, f->size); SafeClose(h); } } } SetCurrentDirectory(oldworkingdir); //revert microsoft stupidity. } break; default: return; } } } void QCC_EnumerateFilesResult(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize) { void *buffer = malloc(plainsize); if (QC_decode(NULL, compsize, plainsize, method, compdata, buffer)) QCC_AddVFile(name, buffer, plainsize); free(buffer); } /* ============== LoadFile ============== */ static void *QCC_ReadFile(const char *fname, unsigned char *(*buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *out_size) //unsigned char *PDECL QCC_ReadFile (const char *fname, void *buffer, int len, size_t *sz) { size_t len; FILE *f; char *buffer; vfile_t *v = QCC_FindVFile(fname); if (v) { len = v->size; if (buf_get) buffer = buf_get(buf_ctx, len+1); else buffer = malloc(len+1); if (!buffer) return NULL; ((char*)buffer)[len] = 0; if (len > v->size) len = v->size; memcpy(buffer, v->file, len); if (out_size) *out_size = len; return buffer; } f = fopen(fname, "rb"); if (!f) { if (out_size) *out_size = 0; return NULL; } fseek(f, 0, SEEK_END); len = ftell(f); fseek(f, 0, SEEK_SET); if (buf_get) buffer = buf_get(buf_ctx, len+1); else buffer = malloc(len+1); ((char*)buffer)[len] = 0; if (len != fread(buffer, 1, len, f)) { if (!buf_get) free(buffer); buffer = NULL; } fclose(f); if (out_size) *out_size = len; return buffer; } int PDECL QCC_RawFileSize (const char *fname) { long length; FILE *f; vfile_t *v = QCC_FindVFile(fname); if (v) return v->size; f = fopen(fname, "rb"); if (!f) return -1; fseek(f, 0, SEEK_END); length = ftell(f); fclose(f); return length; } int PDECL QCC_PopFileSize (const char *fname) { extern int qcc_compileactive; int len = QCC_RawFileSize(fname); if (len >= 0 && qcc_compileactive) { AddSourceFile(compilingrootfile, fname); } return len; } #ifdef AVAIL_ZLIB #include "../libs/zlib.h" #endif pbool PDECL QCC_WriteFileW (const char *name, wchar_t *data, int maxchars) { char *u8start = malloc(3+maxchars*4+1); char *u8 = u8start; int offset; pbool result = false; unsigned int inc; FILE *f; //start with the bom //lets just always write a BOM when the file contains something outside ascii. it'll just be more robust when microsoft refuse to use utf8 by default. //its just much less likely to fuck up when people use notepad/wordpad. :s inc = 0xfeff; *u8++ = ((inc>>12) & 0xf) | 0xe0; *u8++ = ((inc>>6) & 0x3f) | 0x80; *u8++ = ((inc>>0) & 0x3f) | 0x80; offset = u8-u8start; //assume its not needed. will set to 0 if it is. while(*data) { inc = *data++; //handle surrogates if (inc >= 0xd800u && inc < 0xdc00u) { unsigned int l = *data; if (l >= 0xdc00u && l < 0xe000u) { data++; inc = (((inc & 0x3ffu)<<10) | (l & 0x3ffu)) + 0x10000; } } if (inc <= 127) *u8++ = inc; else { offset = 0; if (inc <= 0x7ff) { *u8++ = ((inc>>6) & 0x1f) | 0xc0; *u8++ = ((inc>>0) & 0x3f) | 0x80; } else if (inc <= 0xffff) { *u8++ = ((inc>>12) & 0xf) | 0xe0; *u8++ = ((inc>>6) & 0x3f) | 0x80; *u8++ = ((inc>>0) & 0x3f) | 0x80; } else if (inc <= 0x1fffff) { *u8++ = ((inc>>18) & 0x07) | 0xf0; *u8++ = ((inc>>12) & 0x3f) | 0x80; *u8++ = ((inc>> 6) & 0x3f) | 0x80; *u8++ = ((inc>> 0) & 0x3f) | 0x80; } else { inc = 0xFFFD; *u8++ = ((inc>>12) & 0xf) | 0xe0; *u8++ = ((inc>>6) & 0x3f) | 0x80; *u8++ = ((inc>>0) & 0x3f) | 0x80; } } } f = fopen(name, "wb"); if (f) { result = fwrite(u8start+offset, 1, u8-(u8start+offset), f) == (u8-(u8start+offset)); fclose(f); } free(u8start); return result; } pbool PDECL QCC_WriteFile (const char *name, void *data, int len) { long length; FILE *f; char *ext = strrchr(name, '.'); if (ext && !stricmp(ext, ".gz")) { #ifdef AVAIL_ZLIB pbool okay = true; char out[1024*8]; z_stream strm = { data, len, 0, out, sizeof(out), 0, NULL, NULL, NULL, NULL, NULL, Z_BINARY, 0, 0 }; f = fopen(name, "wb"); if (!f) return false; deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, MAX_WBITS|16, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); while(okay && deflate(&strm, Z_FINISH) == Z_OK) { if (sizeof(out) - strm.avail_out != fwrite(out, 1, sizeof(out) - strm.avail_out, f)) okay = false; strm.next_out = out; strm.avail_out = sizeof(out); } if (sizeof(out) - strm.avail_out != fwrite(out, 1, sizeof(out) - strm.avail_out, f)) okay = false; deflateEnd(&strm); fclose(f); if (!okay) unlink(name); return okay; #else return false; #endif } if (QCC_FindVFile(name)) return !!QCC_AddVFile(name, data, len); f = fopen(name, "wb"); if (!f) return false; length = fwrite(data, 1, len, f); fclose(f); if (length != len) return false; return true; } #undef printf #undef Sys_Error void Sys_Error(const char *text, ...) { va_list argptr; static char msg[2048]; va_start (argptr,text); QC_vsnprintf (msg,sizeof(msg)-1, text,argptr); va_end (argptr); QCC_Error(ERR_INTERNAL, "%s", msg); } FILE *logfile; int logprintf(const char *format, ...) { va_list argptr; static char string[1024]; va_start (argptr, format); #ifdef _WIN32 _vsnprintf (string,sizeof(string)-1, format,argptr); #else vsnprintf (string,sizeof(string), format,argptr); #endif va_end (argptr); printf("%s", string); if (logfile) fputs(string, logfile); return 0; } #define Edit_Redo(hwndCtl) ((BOOL)(DWORD)SNDMSG((hwndCtl), EM_REDO, 0L, 0L)) #define MAIN_WINDOW_CLASS_NAME "FTEMAINWINDOW" #define MDI_WINDOW_CLASS_NAME "FTEMDIWINDOW" #define EDIT_WINDOW_CLASS_NAME "FTEEDITWINDOW" #define OPTIONS_WINDOW_CLASS_NAME "FTEOPTIONSWINDOW" #define ENGINE_WINDOW_CLASS_NAME "FTEEMBEDDEDWINDOW" #define EM_GETSCROLLPOS (WM_USER + 221) #define EM_SETSCROLLPOS (WM_USER + 222) int GUIprintf(const char *msg, ...); void GUIPrint(HWND wnd, char *msg); char finddef[256]; char greptext[256]; extern pbool fl_extramargins; extern int fl_tabsize; extern char enginebinary[MAX_OSPATH]; extern char enginebasedir[MAX_OSPATH]; extern char enginecommandline[8192]; extern QCC_def_t *sourcefilesdefs[]; extern int sourcefilesnumdefs; void RunCompiler(char *args, pbool quick); void RunEngine(void); HINSTANCE ghInstance; HMODULE richedit; HMODULE scintilla; pbool resetprogssrc; //progs.src was changed, reload project info. HWND mainwindow; HWND gamewindow; HWND mdibox; HWND watches; HWND optionsmenu; HWND outputbox; HWND projecttree; HWND search_name; HACCEL accelerators; //our splitter... #define SPLITTER_SIZE 4 static struct splits_s { HWND wnd; HWND splitter; int minsize; int cury; int cursize; float frac; } *splits; static size_t numsplits; static RECT splitterrect; static struct splits_s *SplitterGet(HWND id) { size_t s; for (s = 0; s < numsplits; s++) { if (splits[s].wnd == id) return &splits[s]; } return NULL; } static int SplitterShrinkPrior(size_t s, int px) { int found = 0; int avail; for (; px && s > 0; s--) { avail = splits[s].cursize - splits[s].minsize; if (avail > px) avail = px; splits[s].cursize -= avail; found += avail; px -= avail; } if (px) { avail = splits[0].cursize - splits[0].minsize; if (avail > px) avail = px; splits[0].cursize -= avail; found += avail; px -= avail; } return found; } static int SplitterShrinkNext(size_t s, int px) { int found = 0; int avail; for (; px && s < numsplits; s++) { avail = splits[s].cursize - splits[s].minsize; if (avail > px) avail = px; splits[s].cursize -= avail; found += avail; px -= avail; } return found; } static void SplitterUpdate(void); static LRESULT CALLBACK SplitterWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { size_t s; PAINTSTRUCT ps; RECT rect; int y; switch(message) { case WM_LBUTTONDOWN: SetCapture(hWnd); return TRUE; case WM_MOUSEMOVE: if (wParam & MK_LBUTTON) if (GetCapture() == hWnd) goto doresize; return true; case WM_LBUTTONUP: ReleaseCapture(); doresize: y = GET_Y_LPARAM(lParam); GetClientRect(hWnd, &rect); y = y - rect.top - SPLITTER_SIZE/2; for (s = 1; s < numsplits; s++) { if (splits[s].splitter == hWnd) { if (y < 0) splits[s].cursize += SplitterShrinkPrior(s-1, -y); else splits[s-1].cursize += SplitterShrinkNext(s, y); SplitterUpdate(); break; } } return TRUE; case WM_PAINT: BeginPaint(hWnd,(LPPAINTSTRUCT)&ps); EndPaint(hWnd,(LPPAINTSTRUCT)&ps); return TRUE; default: return DefWindowProc(hWnd,message,wParam,lParam); } } static void SplitterUpdate(void) { int y = 0; size_t s; if (!numsplits) return; y = splitterrect.bottom-splitterrect.top; //now figure out their positions relative to that for (s = numsplits; s-- > 1; ) { y -= splits[s].cursize; splits[s].cury = y; y -= SPLITTER_SIZE; } splits[0].cursize = y; splits[0].cury = 0; if (splits[0].cursize < splits[0].minsize) splits[0].cursize += SplitterShrinkNext(1, splits[0].minsize-splits[0].cursize); for (s = 0; s < numsplits; s++) { if (s) { if (!splits[s].splitter) { WNDCLASSA wclass; wclass.style = 0; wclass.lpfnWndProc = SplitterWndProc; wclass.cbClsExtra = 0; wclass.cbWndExtra = 0; wclass.hInstance = ghInstance; wclass.hIcon = NULL; wclass.hCursor = LoadCursor(0, IDC_SIZENS); wclass.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); wclass.lpszMenuName = NULL; wclass.lpszClassName = "splitter"; RegisterClassA(&wclass); splits[s].splitter = CreateWindowExA(0, wclass.lpszClassName, "", WS_CHILD|WS_VISIBLE, splitterrect.left, splitterrect.top+splits[s].cury-SPLITTER_SIZE, splitterrect.right-splitterrect.left, SPLITTER_SIZE, mainwindow, NULL, ghInstance, NULL); } else SetWindowPos(splits[s].splitter, HWND_TOP, splitterrect.left, splitterrect.top+splits[s].cury-SPLITTER_SIZE, splitterrect.right-splitterrect.left, SPLITTER_SIZE, SWP_NOZORDER); } else { if (splits[s].splitter) { DestroyWindow(splits[s].splitter); splits[s].splitter = NULL; } } SetWindowPos(splits[s].wnd, HWND_TOP, splitterrect.left, splitterrect.top+splits[s].cury, splitterrect.right-splitterrect.left, splits[s].cursize, SWP_NOZORDER); } } static void SplitterAdd(HWND w, int minsize, int newsize) { struct splits_s *n = malloc(sizeof(*n)*(numsplits+1)); memcpy(n, splits, sizeof(*n)*numsplits); free(splits); splits = n; n += numsplits; n->wnd = w; n->splitter = NULL; n->minsize = minsize; n->cursize = newsize; n->cury = 0; numsplits++; SplitterUpdate(); ShowWindow(w, SW_SHOW); } //adds if needed. static void SplitterFocus(HWND w, int minsize, int newsize) { struct splits_s *s = SplitterGet(w); if (s) { if (s->cursize < newsize) { s->cursize += SplitterShrinkPrior(s-splits-1, (newsize-s->cursize)/2); if (s->cursize < newsize) s->cursize += SplitterShrinkNext(s-splits+1, newsize-s->cursize); if (s->cursize < newsize) s->cursize += SplitterShrinkPrior(s-splits-1, newsize-s->cursize); SplitterUpdate(); } } else SplitterAdd(w, minsize, newsize); SetFocus(w); } static void SplitterRemove(HWND w) { struct splits_s *s = SplitterGet(w); size_t idx; if (!s) return; if (s->splitter) DestroyWindow(s->splitter); idx = s-splits; numsplits--; memmove(splits+idx, splits+idx+1, sizeof(*s)*(numsplits-idx)); ShowWindow(w, SW_HIDE); SplitterUpdate(); } FILE *logfile; void GrepAllFiles(char *string); struct{ char *text; HWND hwnd; int washit; } buttons[] = { {"Compile"}, #ifdef EMBEDDEBUG {NULL}, {"Debug"}, #endif {"Options"}, {"Def"}, {"Grep"} }; enum { ID_COMPILE = 0, #ifdef EMBEDDEBUG ID_NULL, ID_RUN, #endif ID_OPTIONS, ID_DEF, ID_GREP }; #define NUMBUTTONS sizeof(buttons)/sizeof(buttons[0]) void GUI_DialogPrint(const char *title, const char *text) { MessageBox(mainwindow, text, title, 0); } static void FindNextScintilla(editor_t *editor, char *findtext, pbool next) { int pos = SendMessage(editor->editpane, SCI_GETCURRENTPOS, 0, 0); Edit_SetSel(editor->editpane, pos+1, pos+1); SendMessage(editor->editpane, SCI_SEARCHANCHOR, 0, 0); if (SendMessage(editor->editpane, next?SCI_SEARCHNEXT:SCI_SEARCHPREV, 0, (LPARAM)findtext) != -1) Edit_ScrollCaret(editor->editpane); //make sure its focused else { Edit_SetSel(editor->editpane, pos, pos); //revert the selection change as nothing was found MessageBox(editor->editpane, "No more occurences found", "FTE Editor", 0); } } static char *WordUnderCursor(editor_t *editor, char *word, int wordsize, char *term, int termsize, int position); pbool GenAutoCompleteList(char *prefix, char *buffer, int buffersize); //available in xp+ typedef LRESULT (CALLBACK *SUBCLASSPROC)(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); BOOL (WINAPI * pSetWindowSubclass)(HWND hWnd, SUBCLASSPROC pfnSubclass, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); LRESULT (WINAPI *pDefSubclassProc)(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK MySubclassWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { editor_t *editor; if (uMsg == WM_CHAR || uMsg == WM_UNICHAR) { switch(wParam) { case VK_ESCAPE: SplitterRemove(outputbox); break; case VK_SPACE: { BYTE keystate[256]; GetKeyboardState(keystate); if ((keystate[VK_CONTROL] | keystate[VK_LCONTROL]) & 128) { for (editor = editors; editor; editor = editor->next) { if (editor->editpane == hWnd) break; } if (editor->scintilla) { if (!SendMessage(editor->editpane, SCI_AUTOCACTIVE, 0, 0)) { static char buffer[65536]; char prefixbuffer[128]; char *pre = WordUnderCursor(editor, prefixbuffer, sizeof(prefixbuffer), NULL, 0, SendMessage(editor->editpane, SCI_GETCURRENTPOS, 0, 0)); if (pre && *pre) if (GenAutoCompleteList(pre, buffer, sizeof(buffer))) { SendMessage(editor->editpane, SCI_AUTOCSETFILLUPS, 0, (LPARAM)".,[<>(*/+-=\t\n"); SendMessage(editor->editpane, SCI_AUTOCSHOW, strlen(pre), (LPARAM)buffer); } return FALSE; } } } } break; } } if (uMsg == WM_LBUTTONDBLCLK && hWnd == outputbox) { CHARRANGE selrange = {0}; SendMessage(hWnd, EM_EXGETSEL, 0, (LPARAM)&selrange); if (1) //some text is selected. { unsigned int bytes; char line[1024]; char *colon1, *colon2 = NULL; int l1; int l2; l1 = Edit_LineFromChar(hWnd, selrange.cpMin); l2 = Edit_LineFromChar(hWnd, selrange.cpMax); if (l1 == l2) { bytes = Edit_GetLine(hWnd, Edit_LineFromChar(outputbox, selrange.cpMin), line, sizeof(line)-1); line[bytes] = 0; for (colon1 = line+strlen(line)-1; *colon1 <= ' ' && colon1>=line; colon1--) *colon1 = '\0'; if (!strncmp(line, "warning: ", 9)) memmove(line, line+9, sizeof(line)-9); colon1=line; do { colon1 = strchr(colon1+1, ':'); } while (colon1 && colon1[1] == '\\'); if (colon1) { colon2 = strchr(colon1+1, ':'); while (colon2 && colon2[1] == '\\') { colon2 = strchr(colon2+1, ':'); } if (colon2) { *colon1 = '\0'; *colon2 = '\0'; EditFile(line, atoi(colon1+1)-1, 2); } else if (!strncmp(line, "Source file: ", 13)) EditFile(line+13, -1, 2); else if (!strncmp(line, "Including: ", 11)) EditFile(line+11, -1, 2); } else if (!strncmp(line, "including ", 10)) EditFile(line+10, -1, 2); else if (!strncmp(line, "compiling ", 10)) EditFile(line+10, -1, 2); else if (!strncmp(line, "prototyping ", 12)) EditFile(line+12, -1, 2); else if (!strncmp(line, "Couldn't open file ", 19)) EditFile(line+19, -1, 2); Edit_SetSel(hWnd, selrange.cpMin, selrange.cpMin); //deselect it. } } return 0; } return pDefSubclassProc(hWnd, uMsg, wParam, lParam); } HWND CreateAnEditControl(HWND parent, pbool *scintillaokay) { HWND newc = NULL; #ifdef SCISTATIC extern int Scintilla_RegisterClasses(void *hinst); scintilla = ghInstance; Scintilla_RegisterClasses(scintilla); #else if (!scintilla && scintillaokay) { #ifdef _WIN64 scintilla = LoadLibrary("SciLexer_64.dll"); if (!scintilla) #endif scintilla = LoadLibrary("SciLexer.dll"); } #endif if (!richedit) richedit = LoadLibrary("RICHED32.DLL"); if (!newc && scintilla && scintillaokay) { newc=CreateWindowEx(WS_EX_CLIENTEDGE, "Scintilla", "", WS_CHILD /*| ES_READONLY*/ | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | ES_LEFT | ES_WANTRETURN | ES_MULTILINE | ES_AUTOVSCROLL, 0, 0, 0, 0, parent, NULL, ghInstance, NULL); } if (newc) *scintillaokay = true; else if (scintillaokay) { *scintillaokay = false; scintillaokay = NULL; } if (!newc) newc=CreateWindowExW(WS_EX_CLIENTEDGE, richedit?RICHEDIT_CLASSW:L"EDIT", L"", WS_CHILD /*| ES_READONLY*/ | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | ES_LEFT | ES_WANTRETURN | ES_MULTILINE | ES_AUTOVSCROLL, 0, 0, 0, 0, parent, NULL, ghInstance, NULL); if (!newc) newc=CreateWindowEx(WS_EX_CLIENTEDGE, richedit?RICHEDIT_CLASS10A:"EDIT", //fall back to the earlier version "", WS_CHILD /*| ES_READONLY*/ | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | ES_LEFT | ES_WANTRETURN | ES_MULTILINE | ES_AUTOVSCROLL, 0, 0, 0, 0, parent, NULL, ghInstance, NULL); if (!newc) { //you've not got RICHEDIT installed properly, I guess FreeLibrary(richedit); richedit = NULL; newc=CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD /*| ES_READONLY*/ | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | ES_LEFT | ES_WANTRETURN | ES_MULTILINE | ES_AUTOVSCROLL, 0, 0, 0, 0, parent, NULL, ghInstance, NULL); } if (!newc) return NULL; if (scintillaokay) { FILE *f; int i; SendMessage(newc, SCI_STYLERESETDEFAULT, 0, 0); SendMessage(newc, SCI_STYLESETFONT, STYLE_DEFAULT, (LPARAM)"Consolas"); SendMessage(newc, SCI_STYLECLEARALL, 0, 0); SendMessage(newc, SCI_SETCODEPAGE, SC_CP_UTF8, 0); SendMessage(newc, SCI_SETLEXER, SCLEX_CPP, 0); SendMessage(newc, SCI_STYLESETFORE, SCE_C_DEFAULT, RGB(0x00, 0x00, 0x00)); SendMessage(newc, SCI_STYLECLEARALL,0, 0); SendMessage(newc, SCI_STYLESETFORE, SCE_C_COMMENT, RGB(0x00, 0x80, 0x00)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_COMMENTLINE, RGB(0x00, 0x80, 0x00)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_COMMENTDOC, RGB(0x00, 0x80, 0x00)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_NUMBER, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_WORD, RGB(0x00, 0x00, 0xFF)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_STRING, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_CHARACTER, RGB(0xA0, 0x10, 0x10)); // SendMessage(newc, SCI_STYLESETFORE, SCE_C_UUID, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_PREPROCESSOR, RGB(0x00, 0x00, 0xFF)); // SendMessage(newc, SCI_STYLESETFORE, SCE_C_OPERATOR, RGB(0x00, 0x00, 0x00)); // SendMessage(newc, SCI_STYLESETFORE, SCE_C_IDENTIFIER, RGB(0x00, 0x00, 0x00)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_STRINGEOL, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_VERBATIM, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_REGEX, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_COMMENTLINEDOC, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_WORD2, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_COMMENTDOCKEYWORD, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_COMMENTDOCKEYWORDERROR, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_GLOBALCLASS, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_STRINGRAW, RGB(0xA0, 0x00, 0x00)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_TRIPLEVERBATIM, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_HASHQUOTEDSTRING, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_PREPROCESSORCOMMENT, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_PREPROCESSORCOMMENTDOC, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_USERLITERAL, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_TASKMARKER, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, SCE_C_ESCAPESEQUENCE, RGB(0xA0, 0x10, 0x10)); SendMessage(newc, SCI_STYLESETFORE, STYLE_BRACELIGHT, RGB(0x00, 0x00, 0x3F)); SendMessage(newc, SCI_STYLESETBACK, STYLE_BRACELIGHT, RGB(0xef, 0xaf, 0xaf)); SendMessage(newc, SCI_STYLESETBOLD, STYLE_BRACELIGHT, TRUE); SendMessage(newc, SCI_STYLESETFORE, STYLE_BRACEBAD, RGB(0x3F, 0x00, 0x00)); SendMessage(newc, SCI_STYLESETBACK, STYLE_BRACEBAD, RGB(0xff, 0xaf, 0xaf)); //SCE_C_WORD SendMessage(newc, SCI_SETKEYWORDS, 0, (LPARAM) "if else for do not while asm break case const continue " "default enum enumflags extern " "float goto __in __out __inout noref " "nosave shared state optional string " "struct switch thinktime until loop " "typedef union var " "accessor get set inline " "virtual nonvirtual class static nonstatic local return " "string float vector void int integer __variant entity" ); //SCE_C_WORD2 { char buffer[65536]; GenBuiltinsList(buffer, sizeof(buffer)); SendMessage(newc, SCI_SETKEYWORDS, 1, (LPARAM)buffer); } //SCE_C_COMMENTDOCKEYWORDERROR //SCE_C_GLOBALCLASS SendMessage(newc, SCI_SETKEYWORDS, 3, (LPARAM) "" ); //preprocessor listing { char *deflist = QCC_PR_GetDefinesList(); SendMessage(newc, SCI_SETKEYWORDS, 4, (LPARAM)deflist); free(deflist); } //task markers (in comments only) SendMessage(newc, SCI_SETKEYWORDS, 5, (LPARAM) "TODO FIXME BUG" ); SendMessage(newc, SCI_USEPOPUP, 0/*SC_POPUP_NEVER*/, 0); //so we can do right-click menus ourselves. SendMessage(newc, SCI_SETMOUSEDWELLTIME, 1000, 0); SendMessage(newc, SCI_AUTOCSETORDER, SC_ORDER_PERFORMSORT, 0); SendMessage(newc, SCI_AUTOCSETFILLUPS, 0, (LPARAM)".,[<>(*/+-=\t\n"); //Set up gui options. SendMessage(newc, SCI_SETMARGINWIDTHN, 0, (LPARAM)fl_extramargins?40:0); //line numbers+folding SendMessage(newc, SCI_SETTABWIDTH, fl_tabsize, 0); //tab size //add margin for breakpoints SendMessage(newc, SCI_SETMARGINMASKN, 1, (LPARAM)~SC_MASK_FOLDERS); SendMessage(newc, SCI_SETMARGINWIDTHN, 1, (LPARAM)16); SendMessage(newc, SCI_SETMARGINSENSITIVEN, 1, (LPARAM)true); //give breakpoints a nice red circle. SendMessage(newc, SCI_MARKERDEFINE, 0, SC_MARK_CIRCLE); SendMessage(newc, SCI_MARKERSETFORE, 0, RGB(0x7F, 0x00, 0x00)); SendMessage(newc, SCI_MARKERSETBACK, 0, RGB(0xFF, 0x00, 0x00)); //give current line a yellow arrow SendMessage(newc, SCI_MARKERDEFINE, 1, SC_MARK_SHORTARROW); SendMessage(newc, SCI_MARKERSETFORE, 1, RGB(0xFF, 0xFF, 0x00)); SendMessage(newc, SCI_MARKERSETBACK, 1, RGB(0x7F, 0x7F, 0x00)); SendMessage(newc, SCI_MARKERDEFINE, 2, SC_MARK_BACKGROUND); SendMessage(newc, SCI_MARKERSETFORE, 2, RGB(0x00, 0x00, 0x00)); SendMessage(newc, SCI_MARKERSETBACK, 2, RGB(0xFF, 0xFF, 0x00)); SendMessage(newc, SCI_MARKERSETALPHA, 2, 0x40); //add margin for folding SendMessage(newc, SCI_SETPROPERTY, (WPARAM)"fold", (LPARAM)"1"); SendMessage(newc, SCI_SETMARGINWIDTHN, 2, (LPARAM)fl_extramargins?16:0); SendMessage(newc, SCI_SETMARGINMASKN, 2, (LPARAM)SC_MASK_FOLDERS); SendMessage(newc, SCI_SETMARGINSENSITIVEN, 2, (LPARAM)true); //stop the images from being stupid SendMessage(newc, SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPEN, SC_MARK_BOXMINUS); SendMessage(newc, SCI_MARKERDEFINE, SC_MARKNUM_FOLDER, SC_MARK_BOXPLUS); SendMessage(newc, SCI_MARKERDEFINE, SC_MARKNUM_FOLDERSUB, SC_MARK_VLINE); SendMessage(newc, SCI_MARKERDEFINE, SC_MARKNUM_FOLDERTAIL, SC_MARK_LCORNERCURVE); SendMessage(newc, SCI_MARKERDEFINE, SC_MARKNUM_FOLDEREND, SC_MARK_BOXPLUSCONNECTED); SendMessage(newc, SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPENMID, SC_MARK_BOXMINUSCONNECTED); SendMessage(newc, SCI_MARKERDEFINE, SC_MARKNUM_FOLDERMIDTAIL, SC_MARK_TCORNERCURVE); //and fuck with colours so that its visible. #define FOLDBACK RGB(0x50, 0x50, 0x50) SendMessage(newc, SCI_MARKERSETFORE, SC_MARKNUM_FOLDER, RGB(0xFF, 0xFF, 0xFF)); SendMessage(newc, SCI_MARKERSETBACK, SC_MARKNUM_FOLDER, FOLDBACK); SendMessage(newc, SCI_MARKERSETFORE, SC_MARKNUM_FOLDEROPEN, RGB(0xFF, 0xFF, 0xFF)); SendMessage(newc, SCI_MARKERSETBACK, SC_MARKNUM_FOLDEROPEN, FOLDBACK); SendMessage(newc, SCI_MARKERSETFORE, SC_MARKNUM_FOLDEROPENMID, RGB(0xFF, 0xFF, 0xFF)); SendMessage(newc, SCI_MARKERSETBACK, SC_MARKNUM_FOLDEROPENMID, FOLDBACK); SendMessage(newc, SCI_MARKERSETBACK, SC_MARKNUM_FOLDERSUB, FOLDBACK); SendMessage(newc, SCI_MARKERSETFORE, SC_MARKNUM_FOLDEREND, RGB(0xFF, 0xFF, 0xFF)); SendMessage(newc, SCI_MARKERSETBACK, SC_MARKNUM_FOLDEREND, FOLDBACK); SendMessage(newc, SCI_MARKERSETBACK, SC_MARKNUM_FOLDERTAIL, FOLDBACK); SendMessage(newc, SCI_MARKERSETBACK, SC_MARKNUM_FOLDERMIDTAIL, FOLDBACK); //disable preprocessor tracking, because QC preprocessor is not specific to an individual file, and even if it was, includes would be messy. // SendMessage(newc, SCI_SETPROPERTY, (WPARAM)"lexer.cpp.track.preprocessor", (LPARAM)"0"); for (i = 0; i < 0x100; i++) { char *lowtab[32] = {"QNUL",NULL,NULL,NULL,NULL,".",NULL,NULL,NULL,NULL,NULL,"#",NULL,">",".",".", "[","]","0","1","2","3","4","5","6","7","8","9",".","<-","-","->"}; char *hightab[32] = {"(=","=","=)","=#=","White",".","Green","Red","Yellow","Blue",NULL,"Purple",NULL,">",".",".", "[","]","0","1","2","3","4","5","6","7","8","9",".","<-","-","->"}; char foo[4]; char bar[4]; unsigned char c = i; foo[0] = i; //these are invalid encodings or control chars. foo[1] = 0; if (c >= 0 && c < 32) { if (lowtab[c]) SendMessage(newc, SCI_SETREPRESENTATION, (WPARAM)foo, (LPARAM)lowtab[c]); } else if (c >= (128|0) && c < (128|32)) { if (hightab[c-128]) SendMessage(newc, SCI_SETREPRESENTATION, (WPARAM)foo, (LPARAM)hightab[c-128]); } else if (c < 128) continue; //don't do anything weird for ascii (other than control chars) else { int b = 0; bar[b++] = c&0x7f; bar[b++] = 0; SendMessage(newc, SCI_SETREPRESENTATION, (WPARAM)foo, (LPARAM)bar); } } for (i = 0xe000; i < 0xe100; i++) { char *lowtab[32] = {"QNUL",NULL,NULL,NULL,NULL,".",NULL,NULL,NULL,NULL,NULL,"#",NULL,">",".",".", "[","]","0","1","2","3","4","5","6","7","8","9",".","<-","-","->"}; char *hightab[32] = {"(=","=","=)","=#=","White",".","Green","Red","Yellow","Blue",NULL,"Purple",NULL,">",".",".", "[","]","^0","^1","^2","^3","^4","^5","^6","^7","^8","^9",".","^<-","^-","^->"}; char foo[4]; char bar[4]; unsigned char c = i; foo[0] = ((i>>12) & 0xf) | 0xe0; foo[1] = ((i>>6) & 0x3f) | 0x80; foo[2] = ((i>>0) & 0x3f) | 0x80; foo[3] = 0; if (c >= 0 && c < 32) { if (lowtab[c]) SendMessage(newc, SCI_SETREPRESENTATION, (WPARAM)foo, (LPARAM)lowtab[c]); } else if (c >= (128|0) && c < (128|32)) { if (hightab[c-128]) SendMessage(newc, SCI_SETREPRESENTATION, (WPARAM)foo, (LPARAM)hightab[c-128]); } else { int b = 0; if (c >= 128) bar[b++] = '^'; bar[b++] = c&0x7f; bar[b++] = 0; SendMessage(newc, SCI_SETREPRESENTATION, (WPARAM)foo, (LPARAM)bar); } } f = fopen("scintilla.cfg", "rt"); if (f) { char buf[256]; while(fgets(buf, sizeof(buf)-1, f)) { int msg; LPARAM lparam; WPARAM wparam; char *c; buf[sizeof(buf)-1] = 0; c = buf; while(*c == ' ' || *c == '\t') c++; if (c[0] == '#') continue; if (c[0] == '/' && c[1] == '/') continue; if (c[0] == '\r' || c[0] == '\n' || !c[0]) continue; msg = strtoul(c, &c, 0); while(*c == ' ' || *c == '\t') c++; if (*c == '\"') { c++; wparam = (LPARAM)c; c = strrchr(c, '\"'); if (c) *c++ = 0; } else wparam = strtoul(c, &c, 0); while(*c == ' ' || *c == '\t') c++; if (*c == '\"') { c++; lparam = (LPARAM)c; c = strrchr(c, '\"'); if (c) *c++ = 0; } else lparam = strtoul(c, &c, 0); SendMessage(newc, msg, wparam, lparam); } if (!ftell(f)) { fclose(f); f = fopen("scintilla.cfg", "wt"); if (f) { int i; int val; for (i = 0; i < STYLE_LASTPREDEFINED; i++) { val = SendMessage(newc, SCI_STYLEGETFORE, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETFORE, i, val); val = SendMessage(newc, SCI_STYLEGETBACK, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETBACK, i, val); val = SendMessage(newc, SCI_STYLEGETBOLD, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETBOLD, i, val); val = SendMessage(newc, SCI_STYLEGETITALIC, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETITALIC, i, val); val = SendMessage(newc, SCI_STYLEGETSIZE, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETSIZE, i, val); val = SendMessage(newc, SCI_STYLEGETFONT, i, (LPARAM)buf); fprintf(f, "%i\t%i\t\"%s\"\n", SCI_STYLESETFONT, i, buf); val = SendMessage(newc, SCI_STYLEGETUNDERLINE, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETUNDERLINE, i, val); val = SendMessage(newc, SCI_STYLEGETCASE, i, 0); fprintf(f, "%i\t%i\t%#x\n", SCI_STYLESETCASE, i, val); } fclose(f); } } else fclose(f); } } else { //go to lucidia console, 10pt CHARFORMAT cf; memset(&cf, 0, sizeof(cf)); cf.cbSize = sizeof(cf); cf.dwMask = CFM_BOLD | CFM_FACE;// | CFM_SIZE; strcpy(cf.szFaceName, "Lucida Console"); cf.yHeight = 5; SendMessage(newc, EM_SETCHARFORMAT, SCF_ALL, (WPARAM)&cf); if (richedit) { SendMessage(newc, EM_EXLIMITTEXT, 0, 1<<20); } } if (!pDefSubclassProc || !pSetWindowSubclass) { HMODULE lib = LoadLibrary("comctl32.dll"); if (lib) { pDefSubclassProc = (void*)GetProcAddress(lib, "DefSubclassProc"); pSetWindowSubclass = (void*)GetProcAddress(lib, "SetWindowSubclass"); } } if (pDefSubclassProc && pSetWindowSubclass) pSetWindowSubclass(newc, MySubclassWndProc, 0, (DWORD_PTR)parent); ShowWindow(newc, SW_SHOW); return newc; } enum { IDM_OPENDOCU=32, IDM_OPENNEW, IDM_OPENPROJECT, IDM_GREP, IDM_GOTODEF, IDM_RETURNDEF, IDM_OUTPUT_WINDOW, IDM_UI_SHOWLINENUMBERS, IDM_UI_TABSIZE, IDM_SAVE, IDM_RECOMPILE, IDM_FIND, IDM_FINDNEXT, IDM_FINDPREV, IDM_QUIT, IDM_UNDO, IDM_REDO, IDM_CUT, IDM_COPY, IDM_PASTE, IDM_ABOUT, IDM_CASCADE, IDM_TILE_HORIZ, IDM_TILE_VERT, IDM_DEBUG_REBUILD, IDM_DEBUG_BUILD_OPTIONS, IDM_DEBUG_SETNEXT, IDM_DEBUG_RUN, IDM_DEBUG_STEPOVER, IDM_DEBUG_STEPINTO, IDM_DEBUG_STEPOUT, IDM_DEBUG_TOGGLEBREAK, IDM_ENCODING_PRIVATEUSE, IDM_ENCODING_DEPRIVATEUSE, IDM_ENCODING_UNIX, IDM_ENCODING_WINDOWS, IDM_CREATEINSTALLER_WINDOWS, IDM_CREATEINSTALLER_ANDROID, IDM_CREATEINSTALLER_PACKAGES, IDI_O_LEVEL0, IDI_O_LEVEL1, IDI_O_LEVEL2, IDI_O_LEVEL3, IDI_O_DEFAULT, IDI_O_DEBUG, // IDI_O_CHANGE_PROGS_SRC, IDI_O_ADDITIONALPARAMETERS, IDI_O_OPTIMISATION, IDI_O_COMPILER_FLAG, IDI_O_APPLYSAVE, IDI_O_APPLY, IDI_O_TARGETH2, IDI_O_TARGETFTE, IDI_O_ENGINE, IDI_O_ENGINEBASEDIR, IDI_O_ENGINECOMMANDLINE, IDM_FIRSTCHILD }; static void EditorReload(editor_t *editor); int EditorSave(editor_t *edit); void EditFile(const char *name, int line, pbool setcontrol); pbool EditorModified(editor_t *e); void QueryOpenFile(void) { char filename[MAX_PATH]; char oldpath[MAX_PATH+10]; OPENFILENAME ofn; memset(&ofn, 0, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hInstance = ghInstance; ofn.lpstrFile = filename; ofn.nMaxFile = sizeof(filename)-1; memset(filename, 0, sizeof(filename)); GetCurrentDirectory(sizeof(oldpath)-1, oldpath); if (GetOpenFileName(&ofn)) EditFile(filename, -1, false); SetCurrentDirectory(oldpath); } static void Packager_MessageCallback(void *ctx, const char *fmt, ...); //IDM_ stuff that needs no active window void GenericMenu(WPARAM wParam) { switch(LOWORD(wParam)) { case IDM_OPENPROJECT: { char filename[MAX_PATH]; char oldpath[MAX_PATH+10]; OPENFILENAME ofn; memset(&ofn, 0, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hInstance = ghInstance; ofn.lpstrFile = filename; ofn.lpstrTitle = "Please find progs.src or progs.dat"; ofn.nMaxFile = sizeof(filename)-1; ofn.lpstrFilter = "QuakeC Projects\0*.src;*.dat\0All files\0*.*\0"; memset(filename, 0, sizeof(filename)); GetCurrentDirectory(sizeof(oldpath)-1, oldpath); ofn.lpstrInitialDir = oldpath; if (GetOpenFileName(&ofn)) { SetProgsSrcFileAndPath(filename); } resetprogssrc = true; } break; case IDM_OPENNEW: QueryOpenFile(); break; case IDM_QUIT: PostQuitMessage(0); break; case IDM_RECOMPILE: buttons[ID_COMPILE].washit = true; break; case IDM_CREATEINSTALLER_WINDOWS: GUI_CreateInstaller_Windows(); break; case IDM_CREATEINSTALLER_ANDROID: GUI_CreateInstaller_Android(); break; case IDM_CREATEINSTALLER_PACKAGES: { struct pkgctx_s *ctx; CreateOutputWindow(false); GUIprintf(""); ctx = Packager_Create(Packager_MessageCallback, NULL); Packager_ParseFile(ctx, "packages.src"); Packager_WriteDataset(ctx, NULL); Packager_Destroy(ctx); } break; case IDM_ABOUT: #if defined(SVNREVISION) && defined(SVNDATE) MessageBox(NULL, "FTE QuakeC Compiler "STRINGIFY(SVNREVISION)" ("STRINGIFY(SVNDATE)")\nWritten by Forethought Entertainment, whoever that is.\n\nIf you have problems with wordpad corrupting your qc files, try saving them using utf-16 encoding via notepad.\nDecompiler component derived from frikdec.", "About", 0); #elif defined(SVNREVISION) MessageBox(NULL, "FTE QuakeC Compiler "STRINGIFY(SVNREVISION)" ("__DATE__" "__TIME__")\nWritten by Forethought Entertainment, whoever that is.\n\nIf you have problems with wordpad corrupting your qc files, try saving them using utf-16 encoding via notepad.\nDecompiler component derived from frikdec.", "About", 0); #else MessageBox(NULL, "FTE QuakeC Compiler ("__DATE__")\nWritten by Forethought Entertainment, whoever that is.\n\nIf you have problems with wordpad corrupting your qc files, try saving them using utf-16 encoding via notepad.\nDecompiler component derived from frikdec.", "About", 0); #endif break; case IDM_CASCADE: SendMessage(mdibox, WM_MDICASCADE, 0, 0); break; case IDM_TILE_HORIZ: SendMessage(mdibox, WM_MDITILE, MDITILE_HORIZONTAL, 0); break; case IDM_TILE_VERT: SendMessage(mdibox, WM_MDITILE, MDITILE_VERTICAL, 0); break; case IDM_OUTPUT_WINDOW: if (GetFocus() == outputbox) SplitterRemove(outputbox); else SplitterFocus(outputbox, 64, 128); break; case IDM_UI_SHOWLINENUMBERS: { editor_t *ed; MENUITEMINFO mii = {sizeof(mii)}; fl_extramargins = !fl_extramargins; mii.fMask = MIIM_STATE; mii.fState = fl_extramargins?MFS_CHECKED:MFS_UNCHECKED; SetMenuItemInfo(GetMenu(mainwindow), IDM_UI_SHOWLINENUMBERS, FALSE, &mii); for (ed = editors; ed; ed = ed->next) { if (ed->scintilla) { SendMessage(ed->editpane, SCI_SETMARGINWIDTHN, 0, (LPARAM)fl_extramargins?40:0); SendMessage(ed->editpane, SCI_SETMARGINWIDTHN, 2, (LPARAM)fl_extramargins?16:0); } } } break; case IDM_UI_TABSIZE: { editor_t *ed; MENUITEMINFO mii = {sizeof(mii)}; fl_tabsize = (fl_tabsize>4)?4:8; mii.fMask = MIIM_STATE; mii.fState = (fl_tabsize>4)?MFS_CHECKED:MFS_UNCHECKED; SetMenuItemInfo(GetMenu(mainwindow), IDM_UI_TABSIZE, FALSE, &mii); for (ed = editors; ed; ed = ed->next) { if (ed->scintilla) { SendMessage(ed->editpane, SCI_SETTABWIDTH, fl_tabsize, 0); } } } break; case IDM_DEBUG_RUN: EditFile(NULL, -1, true); EngineGiveFocus(); if (!EngineCommandf("qcresume\n")) RunEngine(); return; case IDM_DEBUG_REBUILD: buttons[ID_COMPILE].washit = true; return; case IDM_DEBUG_BUILD_OPTIONS: OptionsDialog(); return; case IDM_DEBUG_STEPOVER: EditFile(NULL, -1, true); EngineCommandf("qcstep over\n"); return; case IDM_DEBUG_STEPINTO: EditFile(NULL, -1, true); EngineCommandf("qcstep into\n"); return; case IDM_DEBUG_STEPOUT: EditFile(NULL, -1, true); EngineCommandf("qcstep out\n"); return; } } static char *WordUnderCursor(editor_t *editor, char *word, int wordsize, char *term, int termsize, int position) { unsigned char linebuf[1024]; DWORD charidx; DWORD lineidx; POINT pos; RECT rect; if (editor->scintilla) { DWORD len; lineidx = SendMessage(editor->editpane, SCI_LINEFROMPOSITION, position, 0); charidx = position - SendMessage(editor->editpane, SCI_POSITIONFROMLINE, lineidx, 0); len = SendMessage(editor->editpane, SCI_LINELENGTH, lineidx, 0); if (len >= sizeof(linebuf)) return ""; len = SendMessage(editor->editpane, SCI_GETLINE, lineidx, (LPARAM)linebuf); linebuf[len] = 0; if (charidx >= len) charidx = len-1; } else { GetCursorPos(&pos); GetWindowRect(editor->editpane, &rect); pos.x -= rect.left; pos.y -= rect.top; charidx = SendMessage(editor->editpane, EM_CHARFROMPOS, 0, (LPARAM)&pos); lineidx = SendMessage(editor->editpane, EM_LINEFROMCHAR, charidx, 0); charidx -= SendMessage(editor->editpane, EM_LINEINDEX, lineidx, 0); Edit_GetLine(editor->editpane, lineidx, linebuf, sizeof(linebuf)); } if (word) { //skip back to the start of the word while(charidx > 0 && ( (linebuf[charidx-1] >= 'a' && linebuf[charidx-1] <= 'z') || (linebuf[charidx-1] >= 'A' && linebuf[charidx-1] <= 'Z') || (linebuf[charidx-1] >= '0' && linebuf[charidx-1] <= '9') || linebuf[charidx-1] == '_' || linebuf[charidx-1] == ':' || linebuf[charidx-1] >= 128 )) { charidx--; } //copy the result out lineidx = 0; wordsize--; while (wordsize && ( (linebuf[charidx] >= 'a' && linebuf[charidx] <= 'z') || (linebuf[charidx] >= 'A' && linebuf[charidx] <= 'Z') || (linebuf[charidx] >= '0' && linebuf[charidx] <= '9') || linebuf[charidx] == '_' || linebuf[charidx] == ':' || linebuf[charidx] >= 128 )) { word[lineidx++] = linebuf[charidx++]; wordsize--; } word[lineidx++] = 0; } if (term) { //skip back to the start of the word while(charidx > 0 && ( (linebuf[charidx-1] >= 'a' && linebuf[charidx-1] <= 'z') || (linebuf[charidx-1] >= 'A' && linebuf[charidx-1] <= 'Z') || (linebuf[charidx-1] >= '0' && linebuf[charidx-1] <= '9') || linebuf[charidx-1] == '_' || linebuf[charidx-1] == ':' || linebuf[charidx-1] == '.' || linebuf[charidx-1] == '[' || linebuf[charidx-1] == ']' || linebuf[charidx-1] >= 128 )) { charidx--; } //copy the result out lineidx = 0; termsize--; while (termsize && ( (linebuf[charidx] >= 'a' && linebuf[charidx] <= 'z') || (linebuf[charidx] >= 'A' && linebuf[charidx] <= 'Z') || (linebuf[charidx] >= '0' && linebuf[charidx] <= '9') || linebuf[charidx] == '_' || linebuf[charidx] == ':' || linebuf[charidx] == '.' || linebuf[charidx] == '[' || linebuf[charidx] == ']' || linebuf[charidx] >= 128 )) { term[lineidx++] = linebuf[charidx++]; termsize--; } term[lineidx++] = 0; } return word; } static char *ReadTextSelection(editor_t *editor, char *word, int wordsize) { int total; if (editor->scintilla) { total = SendMessage(editor->editpane, SCI_GETSELTEXT, 0, (LPARAM)NULL); if (total < wordsize) total = SendMessage(editor->editpane, SCI_GETSELTEXT, 0, (LPARAM)word); else total = 0; } else { CHARRANGE ffs; SendMessage(editor->editpane, EM_EXGETSEL, 0, (LPARAM)&ffs); if (ffs.cpMax-ffs.cpMin > wordsize-1) total = 0; //don't crash through the use of a crappy API. else total = SendMessage(editor->editpane, EM_GETSELTEXT, (WPARAM)0, (LPARAM)word); } if (total) word[total]='\0'; else { if (*WordUnderCursor(editor, word, wordsize, NULL, 0, SendMessage(editor->editpane, SCI_GETCURRENTPOS, 0, 0))) return word; return NULL; } return word; } static void GUI_Recode(editor_t *editor, int target) { if (target == UTF8_BOM && editor->savefmt == UTF_ANSI) { //we're currently using some ansi-like format. convert it to quake's format. pbool errors = false; int len; char *wfile, *in; if (IDCANCEL==MessageBox(editor->window, "Really convert?", editor->filename, MB_OKCANCEL)) return; if (editor->scintilla) { char *afile, *out; len = SendMessage(editor->editpane, SCI_GETLENGTH, 0, 0); wfile = malloc(len+1); SendMessage(editor->editpane, SCI_GETTEXT, len, (LPARAM)wfile); wfile[len] = 0; afile = malloc((len+1)*3); in = wfile; out = afile; while(*in) { unsigned int c = (unsigned char)*in++; //fixme: do we care about ascii control codes? quake tends not to, but also abuses them... if ((c >= 32 && c < 0x80) || c == '\n' || c == '\r' || c == '\t') *out++ = c; //ascii chars are still ascii else if (c >= 0 && c < 0xff) //controll chars and high-value chars are not considered ascii and thus not safe { c |= 0xe000; //maps to private use // *out++ = ((c>>6) & 0x1f) | 0xc0; // *out++ = ((c>>0) & 0x3f) | 0x80; *out++ = ((c>>12) & 0xf) | 0xe0; *out++ = ((c>>6) & 0x3f) | 0x80; *out++ = ((c>>0) & 0x3f) | 0x80; } else { *out++ = c; errors = true; } } *out++ = 0; if (errors) errors = IDCANCEL==MessageBox(editor->window, "Encoding quake's char set to utf-8 will corrupt some characters (and cannot be displayed correctly in this editor). continue anyway?", editor->filename, MB_OKCANCEL); if (!errors) { editor->savefmt = UTF8_BOM; //always use a bom, because notepad is shite. SendMessage(editor->editpane, SCI_SETTEXT, 0, (LPARAM)afile); SendMessage(editor->editpane, SCI_SETCODEPAGE, SC_CP_UTF8, 0); } free(afile); } else { wchar_t *afile, *out; len = GetWindowTextLengthA(editor->editpane); wfile = malloc(len+1); GetWindowTextA(editor->editpane, wfile, len+1); afile = malloc((len+1)*2); in = wfile; out = afile; while(*in) { unsigned char c = *in++; //fixme: do we care about ascii control codes? quake tends not to, but also abuses them... if ((c >= 32 && c < 0x80) || c == '\n' || c == '\r' || c == '\t') *out++ = c; //ascii chars are still ascii else if (c >= 0 && c < 0xff) //controll chars and high-value chars are not considered ascii and thus not safe *out++ = c | 0xe000; //maps to private use else { *out++ = c; errors = true; } } *out++ = 0; if (errors) errors = IDCANCEL==MessageBox(editor->window, "Encoding quake's char set to utf-8 will corrupt some characters (and cannot be displayed correctly in this editor). continue anyway?", editor->filename, MB_OKCANCEL); if (!errors) { editor->savefmt = UTF8_BOM; //always use a bom, because notepad is shite. SetWindowTextW(editor->editpane, afile); } free(afile); } free(wfile); } else if (target == UTF_ANSI && editor->savefmt != UTF_ANSI) { //we're currently using some unicode format. convert it to quake's format. pbool errors = false; int len; wchar_t *wfile, *in; char *afile, *out; if (IDCANCEL==MessageBox(editor->window, "Really convert?", editor->filename, MB_OKCANCEL)) return; len = GetWindowTextLengthW(editor->editpane); wfile = malloc((len+1)*2); afile = malloc(len+1); GetWindowTextW(editor->editpane, wfile, len+1); in = wfile; out = afile; while(*in) { //fixme: do we care about ascii control codes? quake tends not to, but also abuses them... if (*in >= 0 && *in < 0x80) *out++ = *in++; //ascii is ascii else if (*in >= 0xe000 && *in < 0xe100) *out++ = *in++; //quake's charset is quake's charset //FIXME: no utf-16 surrogates else { *out++ = *in++; errors = true; } } *out++ = 0; if (errors) errors = IDCANCEL==MessageBox(editor->window, "Encoding to quake's char set will corrupt some characters (and cannot be displayed correctly in this editor). continue anyway?", editor->filename, MB_OKCANCEL); if (!errors) { editor->savefmt = UTF_ANSI; if (editor->scintilla) { SendMessage(editor->editpane, SCI_SETCODEPAGE, 28591, 0); SendMessage(editor->editpane, SCI_SETTEXT, 0, (LPARAM)afile); } else SetWindowTextA(editor->editpane, afile); } free(wfile); free(afile); } } void EditorMenu(editor_t *editor, WPARAM wParam) { switch(LOWORD(wParam)) { case IDM_OPENDOCU: { char buffer[1024]; if (!ReadTextSelection(editor, buffer, sizeof(buffer))) { MessageBox(NULL, "There is no name currently selected.", "Whoops", 0); break; } else EditFile(buffer, -1, false); } break; case IDM_SAVE: EditorSave(editor); break; case IDM_FIND: SetFocus(search_name); break; case IDM_FINDNEXT: case IDM_FINDPREV: { char buffer[128]; GetWindowText(search_name, buffer, sizeof(buffer)); if (*buffer != 0) { HWND ew = (HWND)SendMessage(mdibox, WM_MDIGETACTIVE, 0, 0); editor_t *editor; for (editor = editors; editor; editor = editor->next) { if (editor->window == ew) break; } if (editor && editor->scintilla) { FindNextScintilla(editor, buffer, LOWORD(wParam) == IDM_FINDNEXT); SetFocus(editor->window); SetFocus(editor->editpane); } } } break; case IDM_GREP: { char buffer[1024]; if (!ReadTextSelection(editor, buffer, sizeof(buffer))) { MessageBox(NULL, "There is no search text specified.", "Whoops", 0); break; } else GrepAllFiles(buffer); } break; case IDM_RETURNDEF: if (navhistory_pos > navhistory_first) { editor_t *ed; navhistory_pos--; //search for the editor to make sure its still open for (ed = editors; ed; ed = ed->next) { if (ed == navhistory[navhistory_pos&navhistory_size].editor) { SetFocus(ed->window); SetFocus(ed->editpane); SendMessage(ed->editpane, SCI_SETSEL, navhistory[navhistory_pos&navhistory_size].selpos, navhistory[navhistory_pos&navhistory_size].anchorpos); break; } } } break; case IDM_GOTODEF: { char buffer[1024]; { navhistory[navhistory_pos&navhistory_size].editor = editor; navhistory[navhistory_pos&navhistory_size].selpos = SendMessage(editor->editpane, SCI_GETANCHOR, 0, 0); navhistory[navhistory_pos&navhistory_size].anchorpos = SendMessage(editor->editpane, SCI_GETCURRENTPOS, 0, 0); navhistory_pos++; if (navhistory_pos > navhistory_first + navhistory_size) navhistory_first = navhistory_pos - navhistory_size; } if (!ReadTextSelection(editor, buffer, sizeof(buffer))) { MessageBox(NULL, "There is no name currently selected.", "Whoops", 0); break; } else GoToDefinition(buffer); } break; case IDM_UNDO: if (editor->scintilla) SendMessage(editor->editpane, SCI_UNDO, 0, 0); else Edit_Undo(editor->editpane); break; case IDM_REDO: if (editor->scintilla) SendMessage(editor->editpane, SCI_REDO, 0, 0); else Edit_Redo(editor->editpane); break; case IDM_CUT: if (editor->scintilla) SendMessage(editor->editpane, SCI_CUT, 0, 0); break; case IDM_COPY: if (editor->scintilla) SendMessage(editor->editpane, SCI_COPY, 0, 0); break; case IDM_PASTE: if (editor->scintilla) SendMessage(editor->editpane, SCI_PASTE, 0, 0); break; case IDM_DEBUG_TOGGLEBREAK: { int mode; if (editor->scintilla) { mode = !(SendMessage(editor->editpane, SCI_MARKERGET, editor->curline, 0) & 1); SendMessage(editor->editpane, mode?SCI_MARKERADD:SCI_MARKERDELETE, editor->curline, 0); } else mode = 2; EngineCommandf("qcbreakpoint %i \"%s\" %i\n", mode, editor->filename, editor->curline+1); } return; case IDM_DEBUG_SETNEXT: EngineCommandf("qcjump \"%s\" %i\n", editor->filename, editor->curline+1); return; case IDM_ENCODING_PRIVATEUSE: GUI_Recode(editor, UTF8_BOM); break; case IDM_ENCODING_DEPRIVATEUSE: GUI_Recode(editor, UTF_ANSI); break; case IDM_ENCODING_UNIX: SendMessage(editor->editpane, SCI_CONVERTEOLS, SC_EOL_LF, 0); SendMessage(editor->editpane, SCI_SETVIEWEOL, false, 0); break; case IDM_ENCODING_WINDOWS: SendMessage(editor->editpane, SCI_CONVERTEOLS, SC_EOL_CRLF, 0); SendMessage(editor->editpane, SCI_SETVIEWEOL, false, 0); break; default: GenericMenu(wParam); break; } } editor_t *tooltip_editor = NULL; char tooltip_variable[256]; char tooltip_type[256]; char tooltip_comment[2048]; size_t tooltip_position; char *GetTooltipText(editor_t *editor, int pos, pbool dwell) { static char buffer[1024]; char wordbuf[256], *text; char term[256]; char *defname; defname = WordUnderCursor(editor, wordbuf, sizeof(wordbuf), term, sizeof(term), pos); if (!*defname) return NULL; else if (globalstable.numbuckets) { QCC_def_t *def; int fno; int line; int best, bestline; char *macro = QCC_PR_CheckCompConstTooltip(defname, buffer, buffer + sizeof(buffer)); if (macro && *macro) return macro; if (dwell) { tooltip_editor = NULL; *tooltip_variable = 0; tooltip_position = 0; *tooltip_type = 0; *tooltip_comment = 0; } line = SendMessage(editor->editpane, SCI_LINEFROMPOSITION, pos, 0); for (best = 0,bestline=0, fno = 1; fno < numfunctions; fno++) { if (line > functions[fno].line && bestline < functions[fno].line) { if (!strcmp(editor->filename, functions[fno].filen)) { best = fno; bestline = functions[fno].line; } } } if (best) { if (strstr(functions[best].name, "::")) { QCC_type_t *type; char tmp[256]; char *c; QC_strlcpy(tmp, functions[best].name, sizeof(tmp)); c = strstr(tmp, "::"); if (c) *c = 0; type = QCC_TypeForName(tmp); if (type->type == ev_entity) { QCC_def_t *def; QC_snprintfz(tmp, sizeof(tmp), "%s::__m%s", type->name, term); for (fno = 0, def = NULL; fno < sourcefilesnumdefs && !def; fno++) { for (def = sourcefilesdefs[fno]; def; def = def->next) { if (def->scope && def->scope != &functions[best]) continue; // OutputDebugString(def->name); // OutputDebugString("\n"); if (!strcmp(def->name, tmp)) { //FIXME: look at the scope's function to find the start+end of the function and filter based upon that, to show locals break; } } } if (def && def->type->type == ev_field) { // QC_strlcpy(tmp, term, sizeof(tmp)); QC_snprintfz(term, sizeof(term), "self.%s", tmp); } else { for (fno = 0, def = NULL; fno < sourcefilesnumdefs && !def; fno++) { for (def = sourcefilesdefs[fno]; def; def = def->next) { if (def->scope && def->scope != &functions[best]) continue; if (!strcmp(def->name, term)) { //FIXME: look at the scope's function to find the start+end of the function and filter based upon that, to show locals break; } } } if (def && def->type->type == ev_field) { QC_strlcpy(tmp, term, sizeof(tmp)); QC_snprintfz(term, sizeof(term), "self.%s", tmp); } } } } } //FIXME: we may need to display types too for (fno = 0, def = NULL; fno < sourcefilesnumdefs && !def; fno++) { for (def = sourcefilesdefs[fno]; def; def = def->next) { if (def->scope) continue; if (!strcmp(def->name, defname)) { //FIXME: look at the scope's function to find the start+end of the function and filter based upon that, to show locals break; } } } if (def) { char typebuf[1024]; char valuebuf[1024]; char *value = ""; if (def->constant && def->type->type == ev_float) QC_snprintfz(value=valuebuf, sizeof(valuebuf), " = %g", def->symboldata[def->ofs]._float); else if (def->constant && def->type->type == ev_integer) QC_snprintfz(value=valuebuf, sizeof(valuebuf), " = %i", def->symboldata[def->ofs]._int); else if (def->constant && def->type->type == ev_vector) QC_snprintfz(value=valuebuf, sizeof(valuebuf), " = '%g %g %g'", def->symboldata[def->ofs].vector[0], def->symboldata[def->ofs].vector[1], def->symboldata[def->ofs].vector[2]); //note function argument names do not persist beyond the function def. we might be able to read the function's localdefs for them, but that's unreliable/broken with builtins where they're most needed. if (def->comment) QC_snprintfz(buffer, sizeof(buffer)-1, "%s %s%s\r\n%s", TypeName(def->type, typebuf, sizeof(typebuf)), def->name, value, def->comment); else QC_snprintfz(buffer, sizeof(buffer)-1, "%s %s%s", TypeName(def->type, typebuf, sizeof(typebuf)), def->name, value); if (dwell) { strncpy(tooltip_type, TypeName(def->type, typebuf, sizeof(typebuf)), sizeof(tooltip_type)-1); if (def->comment) strncpy(tooltip_comment, def->comment, sizeof(tooltip_comment)-1); } text = buffer; } else text = NULL; if (dwell) { strncpy(tooltip_variable, term, sizeof(tooltip_variable)); tooltip_variable[sizeof(tooltip_variable)-1] = 0; tooltip_position = pos; tooltip_editor = editor; EngineCommandf("qcinspect \"%s\" \"%s\"\n", term, (def && def->scope)?def->scope->name:""); if (text) SendMessage(editor->editpane, SCI_CALLTIPSHOW, (WPARAM)pos, (LPARAM)text); } return text; } else return NULL;//"Type info not available. Compile first."; } //scans the preceeding line(s) to find the ideal indentation for the highlighted line //indentbuf may contain spaces or tabs. preferably tabs. static void scin_get_line_indent(HWND editpane, int lineidx, char *indentbuf, size_t sizeofbuf) { size_t i, len; while (lineidx --> 0) { len = SendMessage(editpane, SCI_LINELENGTH, lineidx, 0); *indentbuf = 0; if (len+2 < sizeofbuf) { //FIXME: ignore whitespace len = SendMessage(editpane, SCI_GETLINE, lineidx, (LPARAM)indentbuf); for (i = 0; i < len; i++) { if (indentbuf[i] == ' ' || indentbuf[i] == '\t') continue; break; } if (i == len) continue; if (len >= 3 && indentbuf[len-3] == '{') indentbuf[i++] = '\t'; //add an indent indentbuf[i] = 0; return; } } *indentbuf = 0; //failed } void Scin_HandleCharAdded(editor_t *editor, struct SCNotification *not, int pos) { if (not->ch == '(') { char *s = GetTooltipText(editor, pos-1, FALSE); tooltip_editor = NULL; if (s) SendMessage(editor->editpane, SCI_CALLTIPSHOW, (WPARAM)pos, (LPARAM)s); } else if (not->ch == '}') { //if the first char on the line, fix up indents to match previous-1 char prevline[65536]; char newline[4096]; int pos = SendMessage(editor->editpane, SCI_GETCURRENTPOS, 0, 0); int lineidx = SendMessage(editor->editpane, SCI_LINEFROMPOSITION, pos, 0); int linestart = SendMessage(editor->editpane, SCI_POSITIONFROMLINE, lineidx, 0); int plen; int nlen = SendMessage(editor->editpane, SCI_LINELENGTH, lineidx, 0); if (nlen >= sizeof(newline)) return; nlen = SendMessage(editor->editpane, SCI_GETLINE, lineidx, (LPARAM)newline); if (linestart > 2) { scin_get_line_indent(editor->editpane, lineidx, prevline, sizeof(prevline)); plen = strlen(prevline); if (plen > nlen) return; //already indented a bit or something if (!strncmp(prevline, newline, plen)) //same indent { SendMessage(editor->editpane, SCI_CHARLEFT, 0, 0); //move to the indent SendMessage(editor->editpane, SCI_BACKTAB, 0, 0); //do shift-tab to un-indent the current selection (one line supposedly) SendMessage(editor->editpane, SCI_CHARRIGHT, 0, 0); //and move back to the right of the } } } } else if (not->ch == '\r' || not->ch == '\n') { char linebuf[65536]; int pos = SendMessage(editor->editpane, SCI_GETCURRENTPOS, 0, 0); int lineidx = SendMessage(editor->editpane, SCI_LINEFROMPOSITION, pos, 0); int linestart = SendMessage(editor->editpane, SCI_POSITIONFROMLINE, lineidx, 0); //int len = SendMessage(editor->editpane, SCI_LINELENGTH, lineidx, 0); if (pos == linestart) { scin_get_line_indent(editor->editpane, lineidx, linebuf, sizeof(linebuf)); SendMessage(editor->editpane, SCI_REPLACESEL, 0, (LPARAM)linebuf); } } /* else if (0)//(!SendMessage(editor->editpane, SCI_AUTOCACTIVE, 0, 0)) { char buffer[65536]; char prefixbuffer[128]; char *pre = WordUnderCursor(editor, prefixbuffer, sizeof(prefixbuffer), NULL, 0, SendMessage(editor->editpane, SCI_GETCURRENTPOS, 0, 0)); if (pre && *pre) if (GenAutoCompleteList(pre, buffer, sizeof(buffer))) { SendMessage(editor->editpane, SCI_AUTOCSETFILLUPS, 0, (LPARAM)"\t\n"); SendMessage(editor->editpane, SCI_AUTOCSHOW, strlen(pre), (LPARAM)buffer); } } */ } static void UpdateEditorTitle(editor_t *editor) { char title[2048]; char *encoding = "unknown"; if (editor->oldsavefmt == editor->savefmt && editor->oldline == editor->curline) return; //nothing changed. editor->oldsavefmt = editor->savefmt; editor->oldline = editor->curline; switch(editor->savefmt) { case UTF8_RAW: encoding = "utf-8"; break; case UTF8_BOM: encoding = "utf-8(bom)"; break; case UTF_ANSI: encoding = "unspecified"; break; case UTF16LE: encoding = "utf-16(le)"; break; case UTF16BE: encoding = "utf-16(be)"; break; case UTF32LE: encoding = "utf-32(le)"; break; case UTF32BE: encoding = "utf-32(be)"; break; default: encoding = "unknown"; break; } if (QCC_FindVFile(editor->filename)) sprintf(title, "%s:%i - Virtual", editor->filename, 1+editor->curline); else if (editor->modified) sprintf(title, "*%s:%i - %s", editor->filename, 1+editor->curline, encoding); else sprintf(title, "%s:%i - %s", editor->filename, 1+editor->curline, encoding); SetWindowText(editor->window, title); } static LRESULT CALLBACK EditorWndProc(HWND hWnd,UINT message, WPARAM wParam,LPARAM lParam) { RECT rect; PAINTSTRUCT ps; editor_t *editor; for (editor = editors; editor; editor = editor->next) { if (editor->window == hWnd) break; if (editor->window == NULL) break; //we're actually creating it now. } if (!editor) goto gdefault; switch (message) { case WM_CLOSE: case WM_QUIT: if (editor->modified) { switch (MessageBox(hWnd, "Would you like to save?", editor->filename, MB_YESNOCANCEL)) { case IDCANCEL: return false; case IDYES: if (!EditorSave(editor)) return false; case IDNO: default: break; } } goto gdefault; case WM_DESTROY: { editor_t *e; if (editor == editors) { editors = editor->next; free(editor); return 0; } for (e = editors; e; e = e->next) { if (e->next == editor) { e->next = editor->next; free(editor); return 0; } } MessageBox(0, "Couldn't destroy file reference", "WARNING", 0); } goto gdefault; case WM_CREATE: editor->editpane = CreateAnEditControl(hWnd, &editor->scintilla); if (richedit) { SendMessage(editor->editpane, EM_EXLIMITTEXT, 0, 1<<31); SendMessage(editor->editpane, EM_SETUNDOLIMIT, 256, 256); } editor->tooltip = CreateWindowEx(0, TOOLTIPS_CLASS, NULL, WS_POPUP|TTS_ALWAYSTIP|TTS_NOPREFIX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hWnd, NULL, ghInstance, NULL); if (editor->tooltip) { TOOLINFO toolInfo = { 0 }; toolInfo.cbSize = sizeof(toolInfo); toolInfo.hwnd = hWnd; toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS | TTF_TRACK | TTF_ABSOLUTE; toolInfo.uId = (UINT_PTR)editor->editpane; toolInfo.lpszText = ""; SendMessage(editor->tooltip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo); SendMessage(editor->tooltip, TTM_SETMAXTIPWIDTH, 0, 500); } goto gdefault; case WM_SETFOCUS: SetFocus(editor->editpane); goto gdefault; case WM_SIZE: GetClientRect(hWnd, &rect); SetWindowPos(editor->editpane, NULL, 0, 0, rect.right-rect.left, rect.bottom-rect.top, 0); goto gdefault; case WM_ERASEBKGND: return TRUE; case WM_PAINT: BeginPaint(hWnd,(LPPAINTSTRUCT)&ps); EndPaint(hWnd,(LPPAINTSTRUCT)&ps); return TRUE; break; case WM_SETCURSOR: if (!editor->scintilla) { POINT pos; char *newtext; TOOLINFO toolInfo = { 0 }; toolInfo.cbSize = sizeof(toolInfo); toolInfo.hwnd = hWnd; toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS | TTF_TRACK | TTF_ABSOLUTE; toolInfo.uId = (UINT_PTR)editor->editpane; newtext = GetTooltipText(editor, -1, FALSE); toolInfo.lpszText = editor->tooltiptext; if (!newtext) newtext = ""; if (strcmp(editor->tooltiptext, newtext)) { strncpy(editor->tooltiptext, newtext, sizeof(editor->tooltiptext)-1); SendMessage(editor->tooltip, TTM_UPDATETIPTEXT, (WPARAM)0, (LPARAM)&toolInfo); if (*editor->tooltiptext) SendMessage(editor->tooltip, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&toolInfo); else SendMessage(editor->tooltip, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&toolInfo); } GetCursorPos(&pos); if (pos.x >= 60) pos.x -= 60; else pos.x = 0; pos.y += 30; SendMessage(editor->tooltip, TTM_TRACKPOSITION, (WPARAM)0, MAKELONG(pos.x, pos.y)); } goto gdefault; case WM_COMMAND: if (HIWORD(wParam) == EN_CHANGE && (HWND)lParam == editor->editpane) { if (!editor->modified && !editor->scintilla) { CHARRANGE chrg; if (!editor->modified) editor->oldline=~0; editor->modified = true; if (EditorModified(editor)) if (MessageBox(NULL, "warning: file was modified externally. reload?", "Modified!", MB_YESNO) == IDYES) EditorReload(editor); SendMessage(editor->editpane, EM_EXGETSEL, 0, (LPARAM) &chrg); editor->curline = Edit_LineFromChar(editor->editpane, chrg.cpMin); UpdateEditorTitle(editor); } } else { // if (mdibox) // goto gdefault; EditorMenu(editor, wParam); } break; case WM_CONTEXTMENU: { char buffer[1024]; int x = GET_X_LPARAM(lParam), y = GET_Y_LPARAM(lParam); HMENU menu = CreatePopupMenu(); if (x == -1 && y == -1) { POINT p; GetCursorPos(&p); //not the best. but too lazy to work out scintilla/richedit. x = p.x; y = p.y; } if (ReadTextSelection(editor, buffer, sizeof(buffer))) { char tmp[1024]; QC_snprintfz(tmp, sizeof(tmp), "Go to definition: %s", buffer); AppendMenuA(menu, MF_ENABLED, IDM_GOTODEF, tmp); QC_snprintfz(tmp, sizeof(tmp), "Grep for %s", buffer); AppendMenuA(menu, MF_ENABLED, IDM_GREP, tmp); AppendMenuA(menu, MF_SEPARATOR, 0, NULL); } AppendMenuA(menu, MF_ENABLED, IDM_DEBUG_TOGGLEBREAK, "Toggle Breakpoint"); if (gamewindow) { AppendMenuA(menu, MF_ENABLED, IDM_DEBUG_SETNEXT, "Set next statement"); AppendMenuA(menu, MF_ENABLED, IDM_DEBUG_RUN, "Resume"); } else AppendMenuA(menu, MF_ENABLED, IDM_DEBUG_RUN, "Begin Debugging"); AppendMenuA(menu, MF_SEPARATOR, 0, NULL); AppendMenuA(menu, editor->modified?MF_ENABLED:(MF_DISABLED|MF_GRAYED), IDM_SAVE, "Save File"); // AppendMenuA(menu, MF_ENABLED, IDM_FIND, "&Find"); AppendMenuA(menu, (editor->scintilla&&!SendMessage(editor->editpane, SCI_CANUNDO,0,0))?(MF_DISABLED|MF_GRAYED):MF_ENABLED, IDM_UNDO, "Undo"); AppendMenuA(menu, (editor->scintilla&&!SendMessage(editor->editpane, SCI_CANREDO,0,0))?(MF_DISABLED|MF_GRAYED):MF_ENABLED, IDM_REDO, "Redo"); AppendMenuA(menu, MF_ENABLED, IDM_CUT, "Cut"); AppendMenuA(menu, MF_ENABLED, IDM_COPY, "Copy"); AppendMenuA(menu, MF_ENABLED, IDM_PASTE, "Paste"); TrackPopupMenu(menu, TPM_LEFTBUTTON|TPM_RIGHTBUTTON, x, y, 0, hWnd, NULL); DestroyMenu(menu); } break; case WM_NOTIFY: { NMHDR *nmhdr; nmhdr = (NMHDR *)lParam; if (editor->scintilla) { struct SCNotification *not = (struct SCNotification*)nmhdr; int pos = SendMessage(editor->editpane, SCI_GETCURRENTPOS, 0, 0); int l = SendMessage(editor->editpane, SCI_LINEFROMPOSITION, pos, 0); int mode; if (editor->curline != l) editor->curline = l; switch(nmhdr->code) { case SCN_MARGINCLICK: l = SendMessage(editor->editpane, SCI_LINEFROMPOSITION, not->position, 0); if (not->margin == 1) { /*fixme: should we scan the statements to ensure the line is valid? this applies to the f9 key too*/ mode = !(SendMessage(editor->editpane, SCI_MARKERGET, l, 0) & 1); SendMessage(editor->editpane, mode?SCI_MARKERADD:SCI_MARKERDELETE, l, 0); EngineCommandf("qcbreakpoint %i \"%s\" %i\n", mode, editor->filename, l+1); } else if (not->margin == 2) { SendMessage(editor->editpane, SCI_TOGGLEFOLD, l, 0); } break; case SCN_CHARADDED: Scin_HandleCharAdded(editor, not, pos); break; case SCN_SAVEPOINTREACHED: editor->oldline=~0; editor->modified = false; break; case SCN_SAVEPOINTLEFT: editor->oldline=~0; editor->modified = true; if (EditorModified(editor)) if (MessageBox(NULL, "warning: file was modified externally. reload?", "Modified!", MB_YESNO) == IDYES) EditorReload(editor); break; case SCN_UPDATEUI: { int pos1, pos2; if (strchr("{}[]()", SendMessage(editor->editpane, SCI_GETCHARAT, pos, 0))) pos1 = pos; else if (strchr("{}[]()", SendMessage(editor->editpane, SCI_GETCHARAT, pos-1, 0))) pos1 = pos-1; else pos1 = -1; if (pos1 != -1) pos2 = SendMessage(editor->editpane, SCI_BRACEMATCH, pos1, 0); else pos2 = -1; if (pos2 == -1) SendMessage(editor->editpane, SCI_BRACEBADLIGHT, pos1, 0); else SendMessage(editor->editpane, SCI_BRACEHIGHLIGHT, pos1, pos2); } break; case SCN_DWELLSTART: GetTooltipText(editor, not->position, TRUE); break; case SCN_DWELLEND: case SCN_FOCUSOUT: tooltip_editor = NULL; SendMessage(editor->editpane, SCI_CALLTIPCANCEL, 0, 0); break; } UpdateEditorTitle(editor); } else { SELCHANGE *sel; switch(nmhdr->code) { case EN_SELCHANGE: sel = (SELCHANGE *)nmhdr; editor->curline = Edit_LineFromChar(editor->editpane, sel->chrg.cpMin); UpdateEditorTitle(editor); break; } } } default: gdefault: if (mdibox) return DefMDIChildProc(hWnd,message,wParam,lParam); else return DefWindowProc(hWnd,message,wParam,lParam); } return 0; } static void EditorReload(editor_t *editor) { struct stat sbuf; size_t flensz; char *rawfile; char *file; size_t flen; pbool dofree; rawfile = QCC_ReadFile(editor->filename, NULL, NULL, &flensz); flen = flensz; file = QCC_SanitizeCharSet(rawfile, &flen, &dofree, &editor->savefmt); stat(editor->filename, &sbuf); editor->filemodifiedtime = sbuf.st_mtime; if (editor->scintilla) { int endings = 0; char *e, *stop; for (e = file, stop=file+flen; e < stop; ) { if (*e == '\r') { e++; if (*e == '\n') { e++; endings |= 4; } else endings |= 2; } else if (*e == '\n') { e++; endings |= 1; } else e++; } switch(endings) { case 0: //new file with no endings, default to windows on windows. case 4: //windows SendMessage(editor->editpane, SCI_SETEOLMODE, SC_EOL_CRLF, 0); SendMessage(editor->editpane, SCI_SETVIEWEOL, false, 0); break; case 1: //unix SendMessage(editor->editpane, SCI_SETEOLMODE, SC_EOL_LF, 0); SendMessage(editor->editpane, SCI_SETVIEWEOL, false, 0); break; case 2: //mac. traditionally qccs have never supported this. one of the mission packs has a \r in the middle of some single-line comment. SendMessage(editor->editpane, SCI_SETEOLMODE, SC_EOL_CR, 0); SendMessage(editor->editpane, SCI_SETVIEWEOL, false, 0); break; default: //panic! everyone panic! SendMessage(editor->editpane, SCI_SETEOLMODE, SC_EOL_LF, 0); SendMessage(editor->editpane, SCI_SETVIEWEOL, true, 0); break; } // SendMessage(editor->editpane, SCI_SETTEXT, 0, (LPARAM)file); // SendMessage(editor->editpane, SCI_SETUNDOCOLLECTION, 0, 0); SendMessage(editor->editpane, SCI_SETTEXT, 0, (LPARAM)file); // SendMessage(editor->editpane, SCI_SETUNDOCOLLECTION, 1, 0); SendMessage(editor->editpane, EM_EMPTYUNDOBUFFER, 0, 0); SendMessage(editor->editpane, SCI_SETSAVEPOINT, 0, 0); if (editor->savefmt == UTF_ANSI) SendMessage(editor->editpane, SCI_SETCODEPAGE, 28591, 0); else SendMessage(editor->editpane, SCI_SETCODEPAGE, SC_CP_UTF8, 0); } else { SendMessage(editor->editpane, EM_SETEVENTMASK, 0, 0); /*clear it out*/ Edit_SetSel(editor->editpane,0,Edit_GetTextLength(editor->editpane)); Edit_ReplaceSel(editor->editpane,""); if (file) { pbool errors; wchar_t *ch = QCC_makeutf16(file, flen, NULL, &errors); Edit_SetSel(editor->editpane,0,0); SetWindowTextW(editor->editpane, ch); if (errors) { // char msg[1024]; editor->savefmt = UTF_ANSI; SetWindowTextA(editor->editpane, file); // QC_snprintfz(msg, sizeof(msg), "%s contains unicode encoding errors. File will be interpreted as ansi.", editor->filename); // MessageBox(editor->editpane, msg, "Encoding errors.", MB_ICONWARNING); } free(ch); } SendMessage(editor->editpane, EM_SETEVENTMASK, 0, ENM_SELCHANGE|ENM_CHANGE); } if (dofree) free(file); free(rawfile); editor->modified = false; if (editor->scintilla) { } else { CHARRANGE chrg; SendMessage(editor->editpane, EM_EXGETSEL, 0, (LPARAM) &chrg); editor->curline = Edit_LineFromChar(editor->editpane, chrg.cpMin); } UpdateEditorTitle(editor); } //line is 0-based. use -1 for no reselection //setcontrol is the reason we're opening it. //0: just load and go to the line. //1: show the line as the executing one //2: draw extra focus to it void EditFile(const char *name, int line, pbool setcontrol) { const char *ext; char title[1024]; editor_t *neweditor; WNDCLASS wndclass; HMENU menu, menufile, menuhelp, menunavig; if (setcontrol) { for (neweditor = editors; neweditor; neweditor = neweditor->next) { if (neweditor->scintilla) { SendMessage(neweditor->editpane, SCI_MARKERDELETEALL, 1, 0); SendMessage(neweditor->editpane, SCI_MARKERDELETEALL, 2, 0); } } } if (!name) return; for (neweditor = editors; neweditor; neweditor = neweditor->next) { if (neweditor->window && !strcmp(neweditor->filename, name)) { if (line >= 0) { if (setcontrol) Edit_SetSel(neweditor->editpane, Edit_LineIndex(neweditor->editpane, line+1)-1, Edit_LineIndex(neweditor->editpane, line+1)-1); else Edit_SetSel(neweditor->editpane, Edit_LineIndex(neweditor->editpane, line), Edit_LineIndex(neweditor->editpane, line+1)-1); Edit_ScrollCaret(neweditor->editpane); if (setcontrol && neweditor->scintilla) { SendMessage(neweditor->editpane, SCI_MARKERADD, line, 1); SendMessage(neweditor->editpane, SCI_MARKERADD, line, 2); } } if (mdibox) SendMessage(mdibox, WM_MDIACTIVATE, (WPARAM)neweditor->window, 0); SetFocus(neweditor->window); SetFocus(neweditor->editpane); return; } } if (QCC_RawFileSize(name) == -1) { QC_snprintfz(title, sizeof(title), "File not found:\n%s\nCreate it?", name); if (MessageBox(NULL, title, "Error", MB_ICONWARNING|MB_YESNO|MB_DEFBUTTON2) != IDYES) return; } ext = strrchr(name, '.'); if (ext) { if (!QC_strcasecmp(ext, ".wav")) { size_t flensz; char *rawfile = QCC_ReadFile(name, NULL, NULL, &flensz); //fixme: thread this... BOOL (WINAPI *pPlaySound)(LPCSTR pszSound, HMODULE hmod, DWORD fdwSound); HMODULE winmm = LoadLibrary("winmm.dll"); pPlaySound = (void*)GetProcAddress(winmm, "PlaySoundA"); if (pPlaySound) pPlaySound(rawfile, NULL, SND_MEMORY|SND_SYNC|SND_NODEFAULT); free(rawfile); return; } else if (!QC_strcasecmp(ext, ".ogg") || !QC_strcasecmp(ext, ".mp3") || !QC_strcasecmp(ext, ".opus") || !QC_strcasecmp(ext, ".bsp") || !QC_strcasecmp(ext, ".mdl") || !QC_strcasecmp(ext, ".md2") || !QC_strcasecmp(ext, ".md3") || !QC_strcasecmp(ext, ".iqm") || !QC_strcasecmp(ext, ".wad") || !QC_strcasecmp(ext, ".lmp") || !QC_strcasecmp(ext, ".png") || !QC_strcasecmp(ext, ".tga") || !QC_strcasecmp(ext, ".jpeg") || !QC_strcasecmp(ext, ".jpg") || !QC_strcasecmp(ext, ".dds") || !QC_strcasecmp(ext, ".ktx") || !QC_strcasecmp(ext, ".bmp") || !QC_strcasecmp(ext, ".pcx") || !QC_strcasecmp(ext, ".bin") || !QC_strcasecmp(ext, ".dat") || !QC_strcasecmp(ext, ".pak") || !QC_strcasecmp(ext, ".pk3") || !QC_strcasecmp(ext, ".dem") || !QC_strcasecmp(ext, ".spr")) { if (IDOK != MessageBox(NULL, "The file extension implies that it is a binary file. Open as text anyway?", "FTEQCCGUI", MB_OKCANCEL)) return; } } neweditor = malloc(sizeof(editor_t)); if (!neweditor) { MessageBox(NULL, "Low memory", "Error", 0); return; } neweditor->next = editors; editors = neweditor; neweditor->savefmt = UTF8_RAW; strncpy(neweditor->filename, name, sizeof(neweditor->filename)-1); if (!mdibox) { menu = CreateMenu(); menufile = CreateMenu(); menuhelp = CreateMenu(); menunavig = CreateMenu(); AppendMenu(menu, MF_POPUP, (UINT_PTR)menufile, "&File"); AppendMenu(menu, MF_POPUP, (UINT_PTR)menunavig, "&Navigation"); AppendMenu(menu, MF_POPUP, (UINT_PTR)menuhelp, "&Help"); AppendMenu(menufile, 0, IDM_OPENNEW, "Open new file "); AppendMenu(menufile, 0, IDM_SAVE, "&Save "); // AppendMenu(menufile, 0, IDM_FIND, "&Find"); AppendMenu(menufile, 0, IDM_UNDO, "Undo Ctrl+Z"); AppendMenu(menufile, 0, IDM_REDO, "Redo Ctrl+Y"); AppendMenu(menunavig, 0, IDM_GOTODEF, "Go to definition"); AppendMenu(menunavig, 0, IDM_OPENDOCU, "Open selected file"); AppendMenu(menuhelp, 0, IDM_ABOUT, "About"); } else menu = NULL; wndclass.style = 0; wndclass.lpfnWndProc = EditorWndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = ghInstance; wndclass.hIcon = LoadIcon(ghInstance, IDI_ICON_FTEQCC); wndclass.hCursor = LoadCursor (NULL,IDC_ARROW); wndclass.hbrBackground = (void *)COLOR_WINDOW; wndclass.lpszMenuName = 0; wndclass.lpszClassName = EDIT_WINDOW_CLASS_NAME; RegisterClass(&wndclass); neweditor->window = NULL; if (mdibox) { MDICREATESTRUCT mcs; sprintf(title, "%s", name); mcs.szClass = EDIT_WINDOW_CLASS_NAME; mcs.szTitle = name; mcs.hOwner = ghInstance; mcs.x = mcs.cx = CW_USEDEFAULT; mcs.y = mcs.cy = CW_USEDEFAULT; mcs.style = WS_OVERLAPPEDWINDOW|WS_MAXIMIZE; mcs.lParam = 0; neweditor->window = (HWND) SendMessage (mdibox, WM_MDICREATE, 0, (LONG_PTR) (LPMDICREATESTRUCT) &mcs); } else { sprintf(title, "%s - FTEEditor", name); neweditor->window=CreateWindow(EDIT_WINDOW_CLASS_NAME, title, WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, NULL, NULL, ghInstance, NULL); } if (menu) SetMenu(neweditor->window, menu); if (!neweditor->window) { MessageBox(NULL, "Failed to create editor window", "Error", 0); return; } SetWindowLongPtr(neweditor->window, GWLP_USERDATA, (LONG_PTR)neweditor); EditorReload(neweditor); if (line >= 0) { if (setcontrol) Edit_SetSel(neweditor->editpane, Edit_LineIndex(neweditor->editpane, line+1)-1, Edit_LineIndex(neweditor->editpane, line+1)-1); else Edit_SetSel(neweditor->editpane, Edit_LineIndex(neweditor->editpane, line), Edit_LineIndex(neweditor->editpane, line+1)-1); } else Edit_SetSel(neweditor->editpane, Edit_LineIndex(neweditor->editpane, 0), Edit_LineIndex(neweditor->editpane, 0)); Edit_ScrollCaret(neweditor->editpane); ShowWindow(neweditor->window, SW_SHOW); SetFocus(mainwindow); SetFocus(neweditor->window); SetFocus(neweditor->editpane); if (setcontrol && neweditor->scintilla) { SendMessage(neweditor->editpane, SCI_MARKERADD, line, 1); SendMessage(neweditor->editpane, SCI_MARKERADD, line, 2); } } int EditorSave(editor_t *edit) { struct stat sbuf; int len; wchar_t *wfile; char *afile; BOOL failed = TRUE; int saved = false; if (edit->scintilla) { //wordpad will corrupt any embedded quake chars if we force a bom, because it'll re-save using the wrong char encoding by default. int bomlen = 0; char *bom = ""; if (edit->savefmt == UTF32BE || edit->savefmt == UTF32LE || edit->savefmt == UTF16BE) edit->savefmt = UTF16LE; if (edit->savefmt == UTF8_BOM) { bomlen = 3; bom = "\xEF\xBB\xBF"; } else if (edit->savefmt == UTF16BE) { bomlen = 2; bom = "\xFE\xFF"; } else if (edit->savefmt == UTF16LE) { bomlen = 2; bom = "\xFF\xFE"; } else if (edit->savefmt == UTF32BE) { bomlen = 4; bom = "\x00\x00\xFE\xFF"; } else if (edit->savefmt == UTF32LE) { bomlen = 4; bom = "\xFF\xFE\x00\x00"; } len = SendMessage(edit->editpane, SCI_GETLENGTH, 0, 0); afile = malloc(bomlen+len+1); if (!afile) { MessageBox(NULL, "Save failed - not enough mem", "Error", 0); return false; } memcpy(afile, bom, bomlen); SendMessage(edit->editpane, SCI_GETTEXT, len+1, bomlen+(LPARAM)afile); //because wordpad saves in ansi by default instead of the format the file was originally saved in, we HAVE to use ansi without if (edit->savefmt != UTF8_BOM && edit->savefmt != UTF8_RAW) { int mchars; char *mc; int wchars = MultiByteToWideChar(CP_UTF8, 0, afile, len, NULL, 0); if (wchars) { wchar_t *wc = malloc(wchars * sizeof(wchar_t)); MultiByteToWideChar(CP_UTF8, 0, afile, len, wc, wchars); if (edit->savefmt == UTF_ANSI) { mchars = WideCharToMultiByte(CP_ACP, 0, wc, wchars, NULL, 0, "", &failed); if (mchars) { mc = malloc(mchars); WideCharToMultiByte(CP_ACP, 0, wc, wchars, mc, mchars, "", &failed); if (!failed) { if (!QCC_WriteFile(edit->filename, mc, mchars)) saved = -1; else saved = true; } free(mc); } } else { if (!QCC_WriteFile(edit->filename, wc, wchars)) saved = -1; else saved = true; } free(wc); } } if (!saved) { if (!QCC_WriteFile(edit->filename, afile, bomlen+len)) saved = -1; else saved = true; } free(afile); if (saved < 0) { MessageBox(NULL, "Save failed\nCheck path and ReadOnly flags", "Failure", 0); return false; } SendMessage(edit->editpane, SCI_SETSAVEPOINT, 0, 0); } else { if (edit->savefmt == UTF_ANSI) { len = GetWindowTextLengthA(edit->editpane); afile = malloc(len+1); if (!afile) { MessageBox(NULL, "Save failed - not enough mem", "Error", 0); return false; } GetWindowText(edit->editpane, afile, len+1); if (!QCC_WriteFile(edit->filename, afile, len)) { free(afile); MessageBox(NULL, "Save failed\nCheck path and ReadOnly flags", "Failure", 0); return false; } free(afile); } else { len = GetWindowTextLengthW(edit->editpane); wfile = malloc((len+1)*2); if (!wfile) { MessageBox(NULL, "Save failed - not enough mem", "Error", 0); return false; } GetWindowTextW(edit->editpane, wfile, len+1); if (!QCC_WriteFileW(edit->filename, wfile, len)) { free(wfile); MessageBox(NULL, "Save failed\nCheck path and ReadOnly flags", "Failure", 0); return false; } free(wfile); } } /*now whatever is on disk should have the current time*/ edit->modified = false; stat(edit->filename, &sbuf); edit->filemodifiedtime = sbuf.st_mtime; //remove the * in a silly way. edit->oldline=~0; UpdateEditorTitle(edit); return true; } void EditorsRun(void) { } static unsigned char *buf_get_malloc(void *ctx, size_t len) { return malloc(len); } void *GUIReadFile(const char *fname, unsigned char *(*buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *out_size, pbool issourcefile) { editor_t *e; size_t blen; unsigned char *buffer; if (!buf_get) buf_get = buf_get_malloc; for (e = editors; e; e = e->next) { if (e->window && !strcmp(e->filename, fname)) { //our qcc itself is fine with utf-16, so long as it has a BOM. if (e->scintilla) { //take the opportunity to grab a predefined preprocessor list for this file char *deflist = QCC_PR_GetDefinesList(); SendMessage(e->editpane, SCI_SETKEYWORDS, 4, (LPARAM)deflist); free(deflist); { //and just in case some system defs changed. char buffer[65536]; GenBuiltinsList(buffer, sizeof(buffer)); SendMessage(e->editpane, SCI_SETKEYWORDS, 1, (LPARAM)buffer); } blen = SendMessage(e->editpane, SCI_GETLENGTH, 0, 0); buffer = buf_get(buf_ctx, blen+1); blen = SendMessage(e->editpane, SCI_GETTEXT, blen+1, (LPARAM)buffer); } else if (e->savefmt == UTF_ANSI) { blen = GetWindowTextLengthA(e->editpane); buffer = buf_get(buf_ctx, blen); GetWindowTextA(e->editpane, buffer, blen); } else { blen = (GetWindowTextLengthW(e->editpane)+1)*2; buffer = buf_get(buf_ctx, blen); *(wchar_t*)buffer = 0xfeff; GetWindowTextW(e->editpane, (wchar_t*)buffer+1, blen-sizeof(wchar_t)); } if (e->modified) { if (EditorModified(e)) { if (MessageBox(e->window, "File was modified on disk. Overwrite?", e->filename, MB_YESNO) == IDYES) { if (e->scintilla) { QCC_WriteFile(e->filename, buffer, blen); SendMessage(e->editpane, SCI_SETSAVEPOINT, 0, 0); //tell the control that it was saved. } else { QCC_WriteFileW(e->filename, (wchar_t*)buffer+1, blen); } } } } *out_size = blen; return buffer; } } if (issourcefile) AddSourceFile(compilingrootfile, fname); return QCC_ReadFile(fname, buf_get, buf_ctx, out_size); } int GUIFileSize(const char *fname) { editor_t *e; for (e = editors; e; e = e->next) { if (e->window && !strcmp(e->filename, fname)) { int len; if (e->scintilla) len = SendMessage(e->editpane, SCI_GETLENGTH, 0, 0); else if (e->savefmt == UTF_ANSI) len = GetWindowTextLengthA(e->editpane); else len = (GetWindowTextLengthW(e->editpane)+1)*2; return len; } } return QCC_PopFileSize(fname); } /*checks if the file has been modified externally*/ pbool EditorModified(editor_t *e) { struct stat sbuf; stat(e->filename, &sbuf); if (e->filemodifiedtime != sbuf.st_mtime) return true; return false; } char *COM_ParseOut (const char *data, char *out, int outlen) { int c; int len; len = 0; out[0] = 0; if (!data) return NULL; // skip whitespace skipwhite: while ( (c = *data) <= ' ') { if (c == 0) return NULL; // end of file; data++; } // skip // comments if (c=='/') { if (data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; } } //skip / * comments if (c == '/' && data[1] == '*') { data+=2; while(*data) { if (*data == '*' && data[1] == '/') { data+=2; goto skipwhite; } data++; } goto skipwhite; } // handle marked up quoted strings specially (c-style, but with leading \ before normal opening ") if (c == '\\' && data[1] == '\"') { data+=2; while (1) { if (len >= outlen-2) { out[len] = '\0'; return (char*)data; } c = *data++; if (!c) { out[len] = 0; return (char*)data-1; } if (c == '\\') { c = *data++; switch(c) { case '\r': if (*data == '\n') data++; case '\n': continue; case 'n': c = '\n'; break; case 't': c = '\t'; break; case 'r': c = '\r'; break; case '$': case '\\': case '\'': break; case '"': c = '"'; out[len] = c; len++; continue; default: c = '?'; break; } } if (c=='\"' || !c) { out[len] = 0; return (char*)data; } out[len] = c; len++; } } // handle legacy quoted strings specially if (c == '\"') { data++; while (1) { if (len >= outlen-1) { out[len] = 0; return (char*)data; } c = *data++; if (c=='\"' || !c) { out[len] = 0; return (char*)data; } out[len] = c; len++; } } // parse a regular word do { if (len >= outlen-1) { out[len] = 0; return (char*)data; } out[len] = c; data++; len++; c = *data; } while (c>32); out[len] = 0; return (char*)data; } static void EngineGiveFocus(void) { HWND game; if (gamewindow) { enginewindow_t *ctx = (enginewindow_t*)(LONG_PTR)GetWindowLongPtr(gamewindow, GWLP_USERDATA); if (ctx) { if (ctx->refocuswindow) { SetForegroundWindow(ctx->refocuswindow); return; } } SetFocus(gamewindow); game = GetWindow(gamewindow, GW_CHILD); if (game) SetForegroundWindow(game); //make sure the game itself has focus } } static pbool EngineCommandWnd(HWND wnd, char *message) { //qcresume - resume running //qcinto - singlestep. execute-with-debugging child functions //qcover - singlestep. execute-without-debugging child functions //qcout - singlestep. leave current function and enter parent. //qcbreak "$loc" - set breakpoint //qcwatch "$var" - set watchpoint //qcstack - force-report stack trace enginewindow_t *ctx; if (wnd) { ctx = (enginewindow_t*)(LONG_PTR)GetWindowLongPtr(gamewindow, GWLP_USERDATA); if (ctx) { if (ctx->pipetoengine) { DWORD written = 0; WriteFile(ctx->pipetoengine, message, strlen(message), &written, NULL); return TRUE; } } } return FALSE; } static pbool EngineCommandf(char *message, ...) { va_list va; char finalmessage[1024]; va_start (va, message); vsnprintf (finalmessage, sizeof(finalmessage)-1, message, va); va_end (va); return EngineCommandWnd(gamewindow, finalmessage); } static pbool EngineCommandWndf(HWND wnd, char *message, ...) { va_list va; char finalmessage[1024]; va_start (va, message); vsnprintf (finalmessage, sizeof(finalmessage)-1, message, va); va_end (va); return EngineCommandWnd(wnd, finalmessage); } DWORD WINAPI threadwrapper(void *args) { pbool hadstatus = false; enginewindow_t *ctx = args; { char workingdir[MAX_PATH+10]; char absexe[MAX_PATH+10]; char absbase[MAX_PATH+10]; char mssucks[MAX_PATH+10]; char *gah; PROCESS_INFORMATION childinfo; STARTUPINFO startinfo; SECURITY_ATTRIBUTES pipesec = {sizeof(pipesec), NULL, TRUE}; char cmdline[8192]; _snprintf(cmdline, sizeof(cmdline), "\"%s\" %s -qcdebug", enginebinary, enginecommandline); memset(&startinfo, 0, sizeof(startinfo)); startinfo.cb = sizeof(startinfo); startinfo.hStdInput = NULL; startinfo.hStdError = NULL; startinfo.hStdOutput = NULL; startinfo.dwFlags |= STARTF_USESTDHANDLES; //create pipes for the stdin/stdout. CreatePipe(&ctx->pipefromengine, &startinfo.hStdOutput, &pipesec, 0); CreatePipe(&startinfo.hStdInput, &ctx->pipetoengine, &pipesec, 0); SetHandleInformation(ctx->pipefromengine, HANDLE_FLAG_INHERIT, 0); SetHandleInformation(ctx->pipetoengine, HANDLE_FLAG_INHERIT, 0); //let the engine know who to give focus to { char message[256]; DWORD written; _snprintf(message, sizeof(message)-1, "debuggerwnd %#"PRIxPTR"\n", (uintptr_t)(void*)mainwindow); WriteFile(ctx->pipetoengine, message, strlen(message), &written, NULL); } //let the engine know which window to embed itself in if (ctx->embedtype) { char message[256]; DWORD written; RECT rect; GetClientRect(ctx->window, &rect); _snprintf(message, sizeof(message)-1, "vid_recenter %i %i %i %i %#"PRIxPTR"\n", 0, 0, (int)(rect.right - rect.left), (int)(rect.bottom-rect.top), (uintptr_t)(void*)ctx->window); WriteFile(ctx->pipetoengine, message, strlen(message), &written, NULL); } GetCurrentDirectory(sizeof(workingdir)-1, workingdir); strcpy(mssucks, enginebasedir); while ((gah = strchr(mssucks, '/'))) *gah = '\\'; PathCombine(absbase, workingdir, mssucks); strcpy(mssucks, enginebinary); while ((gah = strchr(mssucks, '/'))) *gah = '\\'; PathCombine(absexe, absbase, mssucks); if (!CreateProcess(absexe, cmdline, NULL, NULL, TRUE, 0, NULL, absbase, &startinfo, &childinfo)) { HRESULT hr = GetLastError(); switch(hr) { case ERROR_FILE_NOT_FOUND: MessageBox(mainwindow, "File Not Found", "Cannot Start Engine", 0); break; case ERROR_PATH_NOT_FOUND: MessageBox(mainwindow, "Path Not Found", "Cannot Start Engine", 0); break; case ERROR_ACCESS_DENIED: MessageBox(mainwindow, "Access Denied", "Cannot Start Engine", 0); break; default: MessageBox(mainwindow, qcva("gla: %x", (unsigned)hr), "Cannot Start Engine", 0); break; } hadstatus = true; //don't warn about other stuff } //these ends of the pipes were inherited by now, so we can discard them in the caller. CloseHandle(startinfo.hStdOutput); CloseHandle(startinfo.hStdInput); } { char buffer[8192]; unsigned int bufoffs = 0; char *nl; while(1) { DWORD avail; //use Peek so we can read exactly how much there is without blocking, so we don't have to read byte-by-byte. PeekNamedPipe(ctx->pipefromengine, NULL, 0, NULL, &avail, NULL); if (!avail) avail = 1; //so we do actually sleep. if (avail > sizeof(buffer)-1 - bufoffs) avail = sizeof(buffer)-1 - bufoffs; if (!ReadFile(ctx->pipefromengine, buffer + bufoffs, avail, &avail, NULL) || !avail) { break; } bufoffs += avail; while(1) { buffer[bufoffs] = 0; nl = strchr(buffer, '\n'); if (nl) { *nl = 0; if (!strncmp(buffer, "status ", 7)) { //SetWindowText(ctx->window, buffer+7); hadstatus = true; } else if (!strcmp(buffer, "status")) { //SetWindowText(ctx->window, "Engine"); hadstatus = true; } else if (!strcmp(buffer, "curserver")) { //not interesting } else if (!strncmp(buffer, "qcstack ", 6)) { //qcvm is giving a stack trace //stack reset //stack "$func" "$loc" //local $depth } else if (!strncmp(buffer, "qcstep ", 7) || !strncmp(buffer, "qcfault ", 8)) { //post it, because of thread ownership issues. static char filenamebuffer[256]; char line[16]; char error[256]; char *l = COM_ParseOut(buffer+7, filenamebuffer, sizeof(filenamebuffer)); while (*l == ' ') l++; if (*l == ':') l++; l = COM_ParseOut(l, line, sizeof(line)); l = COM_ParseOut(l, error, sizeof(error)); PostMessage(ctx->window, WM_USER, atoi(line), (LPARAM)filenamebuffer); //and tell the owning window to try to close it again if (*error) PostMessage(ctx->window, WM_USER+3, 0, (LPARAM)strdup(error)); //and tell the owning window to try to close it again } else if (!strncmp(buffer, "qcvalue ", 8)) { //qcvalue "$variableformula" "$value" //update tooltip to show engine's current value PostMessage(ctx->window, WM_USER+2, 0, (LPARAM)strdup(buffer+8)); //and tell the owning window to try to close it again } else if (!strncmp(buffer, "qcreloaded ", 10)) { //so we can resend any breakpoint commands //qcreloaded "$vmname" "$progsname" char caption[256]; HWND gw = GetWindow(ctx->window, GW_CHILD); if (gw) { GetWindowText(gw, caption, sizeof(caption)); SetWindowText(ctx->window, caption); } PostMessage(ctx->window, WM_USER+1, 0, 0); //and tell the owning window to try to close it again hadstatus = true; } else if (!strncmp(buffer, "refocuswindow", 13) && (buffer[13] == ' ' || !buffer[13])) { char *l = buffer+13; while(*l == ' ') l++; ctx->refocuswindow = (HWND)(size_t)strtoull(l, &l, 0); ShowWindow(ctx->window, SW_HIDE); hadstatus = true; } else { //handle anything else we need to handle here printf("Unknown command from engine \"%s\"\n", buffer); } nl++; bufoffs -= (nl-buffer); memmove(buffer, nl, bufoffs); } else break; } } CloseHandle(ctx->pipefromengine); ctx->pipefromengine = NULL; CloseHandle(ctx->pipetoengine); ctx->pipetoengine = NULL; if (!hadstatus) MessageBox(mainwindow, "Engine terminated without acknowledging debug session.\nCurrently only FTE supports debugging.", "Debugging Failed", MB_OK); } ctx->pipeclosed = true; PostMessage(ctx->window, WM_CLOSE, 0, 0); //and tell the owning window to try to close it again return 0; } static LRESULT CALLBACK EngineWndProc(HWND hWnd,UINT message, WPARAM wParam,LPARAM lParam) { enginewindow_t *ctx; editor_t *editor; switch (message) { case WM_CREATE: ctx = malloc(sizeof(*ctx)); memset(ctx, 0, sizeof(*ctx)); SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)ctx); ctx->window = hWnd; ctx->embedtype = (size_t)((CREATESTRUCT*)lParam)->lpCreateParams; ctx->thread = (HANDLE)CreateThread(NULL, 0, threadwrapper, ctx, 0, &ctx->tid); break; case WM_SIZE: ctx = (enginewindow_t*)(LONG_PTR)GetWindowLongPtr(gamewindow, GWLP_USERDATA); if (ctx && ctx->embedtype) { RECT r; GetClientRect(hWnd, &r); EngineCommandWndf(hWnd, "vid_recenter %i %i %i %i %#p\n", r.left, r.top, r.right-r.left, r.bottom - r.top, (void*)ctx->window); } goto gdefault; case WM_CLOSE: //ask the engine to quit ctx = (enginewindow_t*)(LONG_PTR)GetWindowLongPtr(gamewindow, GWLP_USERDATA); if (ctx && !ctx->pipeclosed) { EngineCommandWnd(hWnd, "quit force\n"); break; } goto gdefault; case WM_DESTROY: EngineCommandWnd(hWnd, "quit force\n"); //just in case ctx = (enginewindow_t*)(LONG_PTR)GetWindowLongPtr(gamewindow, GWLP_USERDATA); if (ctx) { WaitForSingleObject(ctx->thread, INFINITE); CloseHandle(ctx->thread); free(ctx); } if (hWnd == gamewindow) { SplitterRemove(watches); gamewindow = NULL; } break; case WM_USER: //engine broke. show code. if (lParam) SetForegroundWindow(mainwindow); EditFile((char*)lParam, wParam-1, true); if (watches) { char text[MAX_PATH]; int i, lim = ListView_GetItemCount(watches); for (i = 0; i < lim; i++) { ListView_GetItemText(watches, i, 0, text, sizeof(text)); EngineCommandWndf(hWnd, "qcinspect \"%s\" \"%s\"\n", text, ""); //term, scope } } break; case WM_USER+1: //engine loaded a progs, reset breakpoints. for (editor = editors; editor; editor = editor->next) { int line = -1; if (!editor->scintilla) continue; for (;;) { line = SendMessage(editor->editpane, SCI_MARKERNEXT, line, 1); if (line == -1) break; //no more. line++; EngineCommandWndf(hWnd, "qcbreakpoint 1 \"%s\" %i\n", editor->filename, line); } } //and now let the engine continue SetFocus(hWnd); EngineCommandWnd(hWnd, "qcresume\n"); break; case WM_USER+2: { char varname[1024]; char varvalue[1024]; char *line = (char*)lParam; line = COM_ParseOut(line, varname, sizeof(varname)); line = COM_ParseOut(line, varvalue, sizeof(varvalue)); if (tooltip_editor && !strcmp(varname, tooltip_variable)) { char tip[2048]; if (*tooltip_comment) _snprintf(tip, sizeof(tip)-1, "%s %s = %s\r\n%s", tooltip_type, tooltip_variable, varvalue, tooltip_comment); else _snprintf(tip, sizeof(tip)-1, "%s %s = %s", tooltip_type, tooltip_variable, varvalue); SendMessage(tooltip_editor->editpane, SCI_CALLTIPSHOW, (WPARAM)tooltip_position, (LPARAM)tip); } if (watches) { char text[MAX_PATH]; int i, lim = ListView_GetItemCount(watches); for (i = 0; i < lim; i++) { ListView_GetItemText(watches, i, 0, text, sizeof(text)); if (!strcmp(text, varname)) ListView_SetItemText(watches, i, 1, varvalue); } } free((char*)lParam); } break; case WM_USER+3: { char *msg = (char*)lParam; MessageBox(mainwindow, msg, "QC Fault", 0); free(msg); } break; default: gdefault: return DefMDIChildProc(hWnd,message,wParam,lParam); } return 0; } static INT CALLBACK StupidBrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) { //'stolen' from microsoft's knowledge base. //required to work around microsoft being annoying. TCHAR szDir[MAX_PATH]; char *foo; switch(uMsg) { case BFFM_INITIALIZED: if (GetCurrentDirectory(sizeof(szDir)/sizeof(TCHAR), szDir)) { foo = strrchr(szDir, '\\'); if (foo) *foo = 0; foo = strrchr(szDir, '\\'); if (foo) *foo = 0; SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)szDir); } break; case BFFM_SELCHANGED: if (SHGetPathFromIDList((LPITEMIDLIST) lp ,szDir)) { while((foo = strchr(szDir, '\\'))) *foo = '/'; //fixme: verify that id1 is a subdir perhaps? SendMessage(hwnd,BFFM_SETSTATUSTEXT,0,(LPARAM)szDir); } break; } return 0; } pbool PromptForFile(const char *prompt, const char *filter, const char *basepath, const char *defaultfile, char outname[], size_t outsize, pbool create) { char oldworkingdir[MAX_PATH+10]; //cmdlg changes it... char workingdir[MAX_PATH+10]; #ifndef OFN_DONTADDTORECENT #define OFN_DONTADDTORECENT 0x02000000 #endif char *s; char initialdir[MAX_PATH+10]; char absengine[MAX_PATH+10]; OPENFILENAME ofn; pbool okay; memset(&ofn, 0, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = mainwindow; ofn.hInstance = ghInstance; ofn.lpstrFile = absengine; ofn.Flags = OFN_EXPLORER|(create?OFN_PATHMUSTEXIST|OFN_CREATEPROMPT:OFN_FILEMUSTEXIST)|OFN_DONTADDTORECENT; ofn.lpstrTitle = prompt; ofn.nMaxFile = outsize-1; ofn.lpstrFilter = filter; GetCurrentDirectory(sizeof(oldworkingdir)-1, oldworkingdir); memcpy(workingdir, oldworkingdir, sizeof(workingdir)); _snprintf(absengine, sizeof(absengine), "%s/%s", basepath, defaultfile); for (s = absengine; *s; s++) if (*s == '/') *s = '\\'; strrchr(absengine, '\\')[1] = 0; PathCombine(initialdir, workingdir, absengine); if (strchr(defaultfile, '/')) strcpy(absengine, strrchr(defaultfile, '/')+1); else strcpy(absengine, defaultfile); //and the fuck-you-microsoft loop for (s = initialdir; *s; s++) if (*s == '/') *s = '\\'; ofn.lpstrInitialDir = initialdir; okay = GetOpenFileName(&ofn); while (!okay) { switch(CommDlgExtendedError()) { case FNERR_INVALIDFILENAME: *outname = 0; okay = GetOpenFileName(&ofn); continue; } break; } if (!PathRelativePathToA(outname, initialdir, FILE_ATTRIBUTE_DIRECTORY, absengine, FILE_ATTRIBUTE_DIRECTORY)) QC_strlcpy(outname, absengine, sizeof(outsize)); if (!strncmp(outname, ".\\", 2)) memmove(outname, outname+2, strlen(outname+2)+1); //undo any damage caused by microsoft's stupidity SetCurrentDirectory(oldworkingdir); return okay; } void PromptForEngine(int force) { char oldworkingdir[MAX_PATH+10]; //cmdlg changes it... char workingdir[MAX_PATH+10]; GetCurrentDirectory(sizeof(oldworkingdir)-1, oldworkingdir); if (!*enginebasedir || force==1) { BROWSEINFO bi; LPITEMIDLIST il; memset(&bi, 0, sizeof(bi)); bi.hwndOwner = mainwindow; bi.pidlRoot = NULL; GetCurrentDirectory(sizeof(workingdir)-1, workingdir); bi.pszDisplayName = workingdir; bi.lpszTitle = "Please locate your base directory"; bi.ulFlags = BIF_RETURNONLYFSDIRS|BIF_STATUSTEXT; bi.lpfn = StupidBrowseCallbackProc; bi.lParam = 0; bi.iImage = 0; il = SHBrowseForFolder(&bi); SetCurrentDirectory(oldworkingdir); //revert microsoft stupidity. if (il) { char *foo; char absbase[MAX_PATH+10]; SHGetPathFromIDList(il, absbase); CoTaskMemFree(il); GetCurrentDirectory(sizeof(workingdir)-1, workingdir); //use the relative path instead. this'll be stored in a file, and I expect people will zip+email without thinking. if (!PathRelativePathToA(enginebasedir, workingdir, FILE_ATTRIBUTE_DIRECTORY, absbase, FILE_ATTRIBUTE_DIRECTORY)) QC_strlcpy(enginebasedir, absbase, sizeof(enginebasedir)); while((foo = strchr(enginebasedir, '\\'))) *foo = '/'; } else return; if (optionsmenu) DestroyWindow(optionsmenu); buttons[ID_OPTIONS].washit = true; } if (!*enginebinary || force==2) { if (!PromptForFile("Please choose an engine", "Executables\0*.exe\0All files\0*.*\0", enginebasedir, "fteglqw.exe", enginebinary, sizeof(enginebinary), false)) return; if (optionsmenu) DestroyWindow(optionsmenu); buttons[ID_OPTIONS].washit = true; if (*enginebinary && (!*enginecommandline || force==2)) { char absbase[MAX_PATH+10]; char guessdir[MAX_PATH+10]; char *slash; GetCurrentDirectory(sizeof(workingdir)-1, workingdir); _snprintf(guessdir, sizeof(guessdir), "%s/", enginebasedir); for (slash = guessdir; *slash; slash++) if (*slash == '/') *slash = '\\'; PathCombine(absbase, workingdir, guessdir); if (PathRelativePathToA(guessdir, absbase, FILE_ATTRIBUTE_DIRECTORY, workingdir, FILE_ATTRIBUTE_DIRECTORY)) { if (!strncmp(guessdir, ".\\", 2)) memmove(guessdir, guessdir+2, strlen(guessdir+2)+1); slash = strchr(guessdir, '/'); if (slash) *slash = 0; slash = strchr(guessdir, '\\'); if (slash) *slash = 0; if (!*guessdir) QC_snprintfz(enginecommandline, sizeof(enginecommandline), "-window -nohome"); else if (!strchr(guessdir, ' ')) QC_snprintfz(enginecommandline, sizeof(enginecommandline), "-window -nohome -game %s", guessdir); else QC_snprintfz(enginecommandline, sizeof(enginecommandline), "-window -nohome -game \"%s\"", guessdir); } } } } void RunEngine(void) { size_t embedtype = 0; //0 has focus issues. if (!gamewindow) { WNDCLASS wndclass; MDICREATESTRUCT mcs; PromptForEngine(0); memset(&wndclass, 0, sizeof(wndclass)); wndclass.style = 0; wndclass.lpfnWndProc = EngineWndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = ghInstance; wndclass.hIcon = 0; wndclass.hCursor = LoadCursor (NULL,IDC_ARROW); wndclass.hbrBackground = (void *)COLOR_WINDOW; wndclass.lpszMenuName = 0; wndclass.lpszClassName = ENGINE_WINDOW_CLASS_NAME; RegisterClass(&wndclass); if (embedtype != 2) { gamewindow = CreateWindowA(ENGINE_WINDOW_CLASS_NAME, "Debug", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, NULL, NULL, ghInstance, (void*)embedtype); if (embedtype) ShowWindow(gamewindow, SW_SHOW); } else { memset(&mcs, 0, sizeof(mcs)); mcs.szClass = ENGINE_WINDOW_CLASS_NAME; mcs.szTitle = "Debug"; mcs.hOwner = ghInstance; mcs.x = CW_USEDEFAULT; mcs.y = CW_USEDEFAULT; mcs.cx = 640; mcs.cy = 480; mcs.style = WS_OVERLAPPEDWINDOW; mcs.lParam = embedtype; gamewindow = (HWND) SendMessage (mdibox, WM_MDICREATE, 0, (LONG_PTR) (LPMDICREATESTRUCT) &mcs); } SplitterFocus(watches, 64, 64); } else { // enginewindow_t *e = (enginewindow_t*)(LONG_PTR)GetWindowLongPtr(gamewindow, GWLP_USERDATA); } // SendMessage(mdibox, WM_MDIACTIVATE, (WPARAM)gamewindow, 0); PostMessage(mainwindow, WM_SIZE, 0, 0); } static void SetProgsSrcFileAndPath(char *filename) { char *s, *s2; strcpy(progssrcdir, filename); for(s = progssrcdir; s; s = s2) { s2 = strchr(s+1, '\\'); if (!s2) break; s = s2; } if (s) { *s = '\0'; strcpy(progssrcname, s+1); } else strcpy(progssrcname, filename); SetCurrentDirectory(progssrcdir); *progssrcdir = '\0'; } qcc_cachedsourcefile_t *androidfiles; static void Android_FreeFiles(void) { qcc_cachedsourcefile_t *f; while((f = androidfiles)) { androidfiles = f->next; free(f); } } static void Android_CopyFile(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize) { qcc_cachedsourcefile_t *nf, **link; if (!strncmp(name, "META-INF", 8)) return; //ignore any existing signatures. for (link = &androidfiles; *link; link = &(*link)->next) { nf = *link; if (!stricmp(name, nf->filename)) { //nuke the old file if we have a dupe. *link = nf->next; free(nf); break; } } nf = malloc(sizeof(*nf) + plainsize); if (!nf) { GUIprintf("Error: out of memory\n", name, plainsize); return; } QC_strlcpy(nf->filename, name, sizeof(nf->filename)); nf->file = (char*)(nf+1); nf->size = plainsize; nf->type = FT_DATA; if (QC_decode(NULL, compsize, nf->size, method, compdata, nf->file)) { GUIprintf("Android: Including %s (%i bytes)\n", name, plainsize); nf->next = androidfiles; androidfiles = nf; } else { GUIprintf("Android: Unable to read %s from source apk\n", name, plainsize); free(nf); } } static pbool Android_PrepareAPK(FILE *f) { if (f) { char *buf; size_t size; fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); buf = malloc(size); fread(buf, 1, size, f); fclose(f); QC_EnumerateFilesFromBlob(buf, size, Android_CopyFile); free(buf); return true; } return false; } void GUI_CreateInstaller_Android(void) { FILE *f; char inputapkname[MAX_PATH]; //files char *keystore = "my-release-key.keystore"; char targetapk[MAX_PATH]; //other stuff char *storepass = "fte123"; char *alias = "FTEDroid"; char tmp[MAX_PATH]; char *mandata = NULL; char *pngdata = NULL; char *modname = "my_application"; size_t manlen, pnglen; int h; char cmdline[2048]; FILE *inputapk; if (MessageBox(mainwindow, "The 'Create Installer' option is still experimental.\nIt's probably still defective.\nSo be sure to test stuff extensively.", "Create Installer", MB_OKCANCEL|MB_DEFBUTTON2) != IDOK) return; if (!mandata) { QC_snprintfz(tmp, sizeof(tmp), "default.fmf"); mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); } if (!mandata) { QC_snprintfz(tmp, sizeof(tmp), "%s.fmf", modname); mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); } if (!mandata) { QC_snprintfz(tmp, sizeof(tmp), "../default.fmf"); mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); } if (!mandata) { QC_snprintfz(tmp, sizeof(tmp), "../%s.fmf", modname); mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); } if (!mandata) { QC_snprintfz(tmp, sizeof(tmp), "%s.fmf", modname); if (PromptForFile("Please Select Manifest File", "FTE Manifests\0*.fmf\0All files\0*.*\0", ".", tmp, tmp, sizeof(tmp), false)) mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); } if (!mandata) { MessageBox(mainwindow, "No manifest selected.\n", "Create Installer", MB_OK|MB_ICONERROR); return; } PathCombine(inputapkname, enginebasedir, "FTEDroid.apk"); inputapk = fopen(inputapkname, "rb"); if (!inputapk) { if (PromptForFile("Please find base FTE android package", "The FTE Android Package\0*.apk\0All files\0*.*\0", enginebasedir, "FTEDroid.apk", tmp, sizeof(tmp), false)) { PathCombine(inputapkname, enginebasedir, tmp); inputapk = fopen(inputapkname, "rb"); } } if (inputapk) { QC_snprintfz(tmp, sizeof(tmp), "%s.apk", modname); if (PromptForFile("Please output apk", "Android Packages\0*.apk\0All files\0*.*\0", ".", tmp, tmp, sizeof(tmp), true)) PathCombine(targetapk, enginebasedir, tmp); GUIprintf(""); //read the files from an existing apk if (!Android_PrepareAPK(inputapk)) MessageBox(mainwindow, "Unable to read source package", "Create Installer", MB_OK|MB_ICONERROR); else { //add/replace some existing files pngdata = QCC_ReadFile ("../droid_72.png", NULL, 0, &pnglen); if (!pngdata) GUIprintf("Could not open ../droid_72.png launcher icon\n"); Android_CopyFile("res/drawable-hdpi/icon.png", pngdata, pnglen, 0, pnglen); free(pngdata); pngdata = QCC_ReadFile ("../droid_48.png", NULL, 0, &pnglen); if (!pngdata) GUIprintf("Could not open ../droid_48.png launcher icon\n"); Android_CopyFile("res/drawable-mdpi/icon.png", NULL, 0, 0, 0); free(pngdata); Android_CopyFile("default.fmf", mandata, manlen, 0, manlen); //write out the new zip... err... apk... :) h = SafeOpenWrite (targetapk, 2*1024*1024); if (h < 0) { GUIprintf("Unable to open %s\n", targetapk); } else { progfuncs_t funcs; progexterns_t ext; memset(&funcs, 0, sizeof(funcs)); funcs.funcs.parms = &ext; memset(&ext, 0, sizeof(ext)); ext.ReadFile = GUIReadFile; ext.FileSize = GUIFileSize; ext.WriteFile = QCC_WriteFile; ext.Sys_Error = Sys_Error; ext.Printf = GUIprintf; qccprogfuncs = &funcs; WriteSourceFiles(androidfiles, h, true, false); if (!SafeClose(h)) GUIprintf("Error: Unable to write output android package %s\n", targetapk); else { f = fopen(keystore, "rb"); if (f) fclose(f); else { GUIprintf("Key store does not exist. Trying to create\n"); //try to create a keystore for them, as this is their first time. QC_snprintfz(cmdline, sizeof(cmdline), "keytool -genkey -keystore %s -storepass %s -keypass %s -alias %s -keyalg RSA -keysize 2048 -validity 10000", keystore, storepass, storepass, alias); system(cmdline); } f = fopen(keystore, "rb"); if (f) { fclose(f); //we now need to invoke the jarsigner program, so I hope you have java installed. QC_snprintfz(cmdline, sizeof(cmdline), "jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore %s -storepass %s %s %s", keystore, storepass, targetapk, alias); if (EXIT_SUCCESS == system(cmdline)) GUIprintf("Android Package Complete. Go ahead and test it!\n"); else GUIprintf("Failed to sign package.\n"); } else GUIprintf("Keystore creation failed or was aborted.\n"); } } } } Android_FreeFiles(); free(mandata); } #ifdef AVAIL_PNGLIB //size info that microsoft recommends static const struct { int width; int height; int bpp; } icosizes[] = { // {96, 96, 32}, {48, 48, 32}, {32, 32, 32}, {16, 16, 32}, // {16, 16, 4}, // {48, 48, 4}, // {32, 32, 4}, // {16, 16, 1}, // {48, 48, 1}, // {32, 32, 1}, {256, 256, 32} //vista! }; #endif //dates back to 16bit windows. bah. #pragma pack(push) #pragma pack(2) typedef struct { WORD idReserved; WORD idType; WORD idCount; struct { BYTE bWidth; BYTE bHeight; BYTE bColorCount; BYTE bReserved; WORD wPlanes; WORD wBitCount; DWORD dwBytesInRes; WORD nId; } idEntries[256]; } icon_group_t; #pragma pack(pop) #ifdef AVAIL_PNGLIB static void Image_ResampleTexture (unsigned *in, int inwidth, int inheight, unsigned *out, int outwidth, int outheight) { int i, j; unsigned *inrow; unsigned frac, fracstep; /*if (gl_lerpimages.ival) { Image_Resample32Lerp(in, inwidth, inheight, out, outwidth, outheight); return; }*/ fracstep = inwidth*0x10000/outwidth; for (i=0 ; i>16]; } for ( ; j>=4 ;) { j-=4; frac -= fracstep; out[j+3] = inrow[frac>>16]; frac -= fracstep; out[j+2] = inrow[frac>>16]; frac -= fracstep; out[j+1] = inrow[frac>>16]; frac -= fracstep; out[j+0] = inrow[frac>>16]; } } } #endif #ifndef MSVCLIBSPATH #ifdef MSVCLIBPATH #define MSVCLIBSPATH STRINGIFY(MSVCLIBPATH) #elif _MSC_VER == 1200 #define MSVCLIBSPATH "../" "../libs/vc6-libs/" #else #define MSVCLIBSPATH "../" "../libs/" #endif #endif #ifdef AVAIL_PNGLIB #ifndef AVAIL_ZLIB #error PNGLIB requires ZLIB #endif #undef channels #ifndef PNG_SUCKS_WITH_SETJMP #if defined(MINGW) #include "./mingw-libs/png.h" #elif defined(_WIN32) #include "../png.h" #else #include #endif #endif #ifdef DYNAMIC_LIBPNG #define PSTATIC(n) static dllhandle_t *libpng_handle; #define LIBPNG_LOADED() (libpng_handle != NULL) #else #define LIBPNG_LOADED() 1 #define PSTATIC(n) = &n #ifdef _MSC_VER #ifdef _WIN64 #pragma comment(lib, MSVCLIBSPATH "libpng64.lib") #else #pragma comment(lib, MSVCLIBSPATH "libpng.lib") #endif #endif #endif #ifndef PNG_NORETURN #define PNG_NORETURN #endif #ifndef PNG_ALLOCATED #define PNG_ALLOCATED #endif #if PNG_LIBPNG_VER < 10500 #define png_const_infop png_infop #define png_const_structp png_structp #define png_const_bytep png_bytep #define png_const_unknown_chunkp png_unknown_chunkp #endif #if PNG_LIBPNG_VER < 10600 #define png_inforp png_infop #define png_const_inforp png_const_infop #define png_structrp png_structp #define png_const_structrp png_const_structp #endif void (PNGAPI *qpng_error) PNGARG((png_const_structrp png_ptr, png_const_charp error_message)) PSTATIC(png_error); void (PNGAPI *qpng_read_end) PNGARG((png_structp png_ptr, png_infop info_ptr)) PSTATIC(png_read_end); void (PNGAPI *qpng_read_image) PNGARG((png_structp png_ptr, png_bytepp image)) PSTATIC(png_read_image); png_byte (PNGAPI *qpng_get_bit_depth) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_bit_depth); png_byte (PNGAPI *qpng_get_channels) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_channels); #if PNG_LIBPNG_VER < 10400 png_uint_32 (PNGAPI *qpng_get_rowbytes) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_rowbytes); #else png_size_t (PNGAPI *qpng_get_rowbytes) PNGARG((png_const_structp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_get_rowbytes); #endif void (PNGAPI *qpng_read_update_info) PNGARG((png_structp png_ptr, png_infop info_ptr)) PSTATIC(png_read_update_info); void (PNGAPI *qpng_set_strip_16) PNGARG((png_structp png_ptr)) PSTATIC(png_set_strip_16); void (PNGAPI *qpng_set_expand) PNGARG((png_structp png_ptr)) PSTATIC(png_set_expand); void (PNGAPI *qpng_set_gray_to_rgb) PNGARG((png_structp png_ptr)) PSTATIC(png_set_gray_to_rgb); void (PNGAPI *qpng_set_tRNS_to_alpha) PNGARG((png_structp png_ptr)) PSTATIC(png_set_tRNS_to_alpha); png_uint_32 (PNGAPI *qpng_get_valid) PNGARG((png_const_structp png_ptr, png_const_infop info_ptr, png_uint_32 flag)) PSTATIC(png_get_valid); #if PNG_LIBPNG_VER >= 10400 void (PNGAPI *qpng_set_expand_gray_1_2_4_to_8) PNGARG((png_structp png_ptr)) PSTATIC(png_set_expand_gray_1_2_4_to_8); #else void (PNGAPI *qpng_set_gray_1_2_4_to_8) PNGARG((png_structp png_ptr)) PSTATIC(png_set_gray_1_2_4_to_8); #endif void (PNGAPI *qpng_set_bgr) PNGARG((png_structp png_ptr)) PSTATIC(png_set_bgr); void (PNGAPI *qpng_set_filler) PNGARG((png_structp png_ptr, png_uint_32 filler, int flags)) PSTATIC(png_set_filler); void (PNGAPI *qpng_set_palette_to_rgb) PNGARG((png_structp png_ptr)) PSTATIC(png_set_palette_to_rgb); png_uint_32 (PNGAPI *qpng_get_IHDR) PNGARG((png_const_structrp png_ptr, png_const_inforp info_ptr, png_uint_32 *width, png_uint_32 *height, int *bit_depth, int *color_type, int *interlace_method, int *compression_method, int *filter_method)) PSTATIC(png_get_IHDR); void (PNGAPI *qpng_read_info) PNGARG((png_structp png_ptr, png_infop info_ptr)) PSTATIC(png_read_info); void (PNGAPI *qpng_set_sig_bytes) PNGARG((png_structp png_ptr, int num_bytes)) PSTATIC(png_set_sig_bytes); void (PNGAPI *qpng_set_read_fn) PNGARG((png_structp png_ptr, png_voidp io_ptr, png_rw_ptr read_data_fn)) PSTATIC(png_set_read_fn); void (PNGAPI *qpng_destroy_read_struct) PNGARG((png_structpp png_ptr_ptr, png_infopp info_ptr_ptr, png_infopp end_info_ptr_ptr)) PSTATIC(png_destroy_read_struct); png_infop (PNGAPI *qpng_create_info_struct) PNGARG((png_const_structrp png_ptr)) PSTATIC(png_create_info_struct); png_structp (PNGAPI *qpng_create_read_struct) PNGARG((png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warn_fn)) PSTATIC(png_create_read_struct); int (PNGAPI *qpng_sig_cmp) PNGARG((png_const_bytep sig, png_size_t start, png_size_t num_to_check)) PSTATIC(png_sig_cmp); void (PNGAPI *qpng_write_end) PNGARG((png_structrp png_ptr, png_inforp info_ptr)) PSTATIC(png_write_end); void (PNGAPI *qpng_write_image) PNGARG((png_structrp png_ptr, png_bytepp image)) PSTATIC(png_write_image); void (PNGAPI *qpng_write_info) PNGARG((png_structrp png_ptr, png_const_inforp info_ptr)) PSTATIC(png_write_info); void (PNGAPI *qpng_set_IHDR) PNGARG((png_const_structrp png_ptr, png_infop info_ptr, png_uint_32 width, png_uint_32 height, int bit_depth, int color_type, int interlace_method, int compression_method, int filter_method)) PSTATIC(png_set_IHDR); void (PNGAPI *qpng_set_compression_level) PNGARG((png_structrp png_ptr, int level)) PSTATIC(png_set_compression_level); void (PNGAPI *qpng_init_io) PNGARG((png_structp png_ptr, png_FILE_p fp)) PSTATIC(png_init_io); png_voidp (PNGAPI *qpng_get_io_ptr) PNGARG((png_const_structrp png_ptr)) PSTATIC(png_get_io_ptr); void (PNGAPI *qpng_destroy_write_struct) PNGARG((png_structpp png_ptr_ptr, png_infopp info_ptr_ptr)) PSTATIC(png_destroy_write_struct); png_structp (PNGAPI *qpng_create_write_struct) PNGARG((png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warn_fn)) PSTATIC(png_create_write_struct); void (PNGAPI *qpng_set_unknown_chunks) PNGARG((png_const_structrp png_ptr, png_inforp info_ptr, png_const_unknown_chunkp unknowns, int num_unknowns)) PSTATIC(png_set_unknown_chunks); png_voidp (PNGAPI *qpng_get_error_ptr) PNGARG((png_const_structrp png_ptr)) PSTATIC(png_get_error_ptr); pbool LibPNG_Init(void) { #ifdef DYNAMIC_LIBPNG static dllfunction_t pngfuncs[] = { {(void **) &qpng_error, "png_error"}, {(void **) &qpng_read_end, "png_read_end"}, {(void **) &qpng_read_image, "png_read_image"}, {(void **) &qpng_get_bit_depth, "png_get_bit_depth"}, {(void **) &qpng_get_channels, "png_get_channels"}, {(void **) &qpng_get_rowbytes, "png_get_rowbytes"}, {(void **) &qpng_read_update_info, "png_read_update_info"}, {(void **) &qpng_set_strip_16, "png_set_strip_16"}, {(void **) &qpng_set_expand, "png_set_expand"}, {(void **) &qpng_set_gray_to_rgb, "png_set_gray_to_rgb"}, {(void **) &qpng_set_tRNS_to_alpha, "png_set_tRNS_to_alpha"}, {(void **) &qpng_get_valid, "png_get_valid"}, #if PNG_LIBPNG_VER > 10400 {(void **) &qpng_set_expand_gray_1_2_4_to_8, "png_set_expand_gray_1_2_4_to_8"}, #else {(void **) &qpng_set_gray_1_2_4_to_8, "png_set_gray_1_2_4_to_8"}, #endif {(void **) &qpng_set_bgr, "png_set_bgr"}, {(void **) &qpng_set_filler, "png_set_filler"}, {(void **) &qpng_set_palette_to_rgb, "png_set_palette_to_rgb"}, {(void **) &qpng_get_IHDR, "png_get_IHDR"}, {(void **) &qpng_read_info, "png_read_info"}, {(void **) &qpng_set_sig_bytes, "png_set_sig_bytes"}, {(void **) &qpng_set_read_fn, "png_set_read_fn"}, {(void **) &qpng_destroy_read_struct, "png_destroy_read_struct"}, {(void **) &qpng_create_info_struct, "png_create_info_struct"}, {(void **) &qpng_create_read_struct, "png_create_read_struct"}, {(void **) &qpng_sig_cmp, "png_sig_cmp"}, {(void **) &qpng_write_end, "png_write_end"}, {(void **) &qpng_write_image, "png_write_image"}, {(void **) &qpng_write_info, "png_write_info"}, {(void **) &qpng_set_IHDR, "png_set_IHDR"}, {(void **) &qpng_set_compression_level, "png_set_compression_level"}, {(void **) &qpng_init_io, "png_init_io"}, {(void **) &qpng_get_io_ptr, "png_get_io_ptr"}, {(void **) &qpng_destroy_write_struct, "png_destroy_write_struct"}, {(void **) &qpng_create_write_struct, "png_create_write_struct"}, {(void **) &qpng_set_unknown_chunks, "png_set_unknown_chunks"}, {(void **) &qpng_get_error_ptr, "png_get_error_ptr"}, {NULL, NULL} }; static qboolean tried; if (!tried) { tried = true; if (!LIBPNG_LOADED()) { char *libnames[] = { #ifdef _WIN32 va("libpng%i", PNG_LIBPNG_VER_DLLNUM); #else //linux... //lsb uses 'libpng12.so' specifically, so make sure that works. "libpng" STRINGIFY(PNG_LIBPNG_VER_MAJOR) STRINGIFY(PNG_LIBPNG_VER_MINOR) ".so." STRINGIFY(PNG_LIBPNG_VER_SONUM), "libpng" STRINGIFY(PNG_LIBPNG_VER_MAJOR) STRINGIFY(PNG_LIBPNG_VER_MINOR) ".so", "libpng.so." STRINGIFY(PNG_LIBPNG_VER_SONUM) "libpng.so", #endif }; size_t i; for (i = 0; i < countof(libnames); i++) { libpng_handle = Sys_LoadLibrary(libnames[i], pngfuncs); if (libpng_handle) break; } if (!libpng_handle) Con_Printf("Unable to load %s\n", libnames[0]); } // if (!LIBPNG_LOADED()) // libpng_handle = Sys_LoadLibrary("libpng", pngfuncs); } #endif return LIBPNG_LOADED(); } typedef struct { char *data; int readposition; int filelen; } pngreadinfo_t; static void VARGS readpngdata(png_structp png_ptr,png_bytep data,png_size_t len) { pngreadinfo_t *ri = (pngreadinfo_t*)qpng_get_io_ptr(png_ptr); if (ri->readposition+len > ri->filelen) { qpng_error(png_ptr, "unexpected eof"); return; } memcpy(data, &ri->data[ri->readposition], len); ri->readposition+=len; } struct pngerr { const char *fname; jmp_buf jbuf; }; static void VARGS png_onerror(png_structp png_ptr, png_const_charp error_msg) { struct pngerr *err = qpng_get_error_ptr(png_ptr); // Con_Printf("libpng %s: %s\n", err->fname, error_msg); longjmp(err->jbuf, 1); abort(); } static void VARGS png_onwarning(png_structp png_ptr, png_const_charp warning_msg) { struct pngerr *err = qpng_get_error_ptr(png_ptr); // Con_DPrintf("libpng %s: %s\n", err->fname, warning_msg); } qbyte *ReadPNGFile(qbyte *buf, int length, int *width, int *height, const char *fname) { qbyte header[8], **rowpointers = NULL, *data = NULL; png_structp png; png_infop pnginfo; int y, bitdepth, colortype, interlace, compression, filter, bytesperpixel; unsigned long rowbytes; pngreadinfo_t ri; png_uint_32 pngwidth, pngheight; struct pngerr errctx; if (!LibPNG_Init()) return NULL; memcpy(header, buf, 8); errctx.fname = fname; if (setjmp(errctx.jbuf)) { error: if (data) free(data); if (rowpointers) free(rowpointers); qpng_destroy_read_struct(&png, &pnginfo, NULL); return NULL; } if (qpng_sig_cmp(header, 0, 8)) { return NULL; } if (!(png = qpng_create_read_struct(PNG_LIBPNG_VER_STRING, &errctx, png_onerror, png_onwarning))) { return NULL; } if (!(pnginfo = qpng_create_info_struct(png))) { qpng_destroy_read_struct(&png, &pnginfo, NULL); return NULL; } ri.data=buf; ri.readposition=8; ri.filelen=length; qpng_set_read_fn(png, &ri, readpngdata); qpng_set_sig_bytes(png, 8); qpng_read_info(png, pnginfo); qpng_get_IHDR(png, pnginfo, &pngwidth, &pngheight, &bitdepth, &colortype, &interlace, &compression, &filter); *width = pngwidth; *height = pngheight; if (colortype == PNG_COLOR_TYPE_PALETTE) { qpng_set_palette_to_rgb(png); qpng_set_filler(png, 255, PNG_FILLER_AFTER); } if (colortype == PNG_COLOR_TYPE_GRAY && bitdepth < 8) { #if PNG_LIBPNG_VER > 10400 qpng_set_expand_gray_1_2_4_to_8(png); #else qpng_set_gray_1_2_4_to_8(png); #endif } if (qpng_get_valid( png, pnginfo, PNG_INFO_tRNS)) qpng_set_tRNS_to_alpha(png); if (bitdepth >= 8 && colortype == PNG_COLOR_TYPE_RGB) qpng_set_filler(png, 255, PNG_FILLER_AFTER); if (colortype == PNG_COLOR_TYPE_GRAY || colortype == PNG_COLOR_TYPE_GRAY_ALPHA) { qpng_set_gray_to_rgb( png ); qpng_set_filler(png, 255, PNG_FILLER_AFTER); } if (bitdepth < 8) qpng_set_expand (png); else if (bitdepth == 16) qpng_set_strip_16(png); qpng_read_update_info(png, pnginfo); rowbytes = qpng_get_rowbytes(png, pnginfo); bytesperpixel = qpng_get_channels(png, pnginfo); bitdepth = qpng_get_bit_depth(png, pnginfo); if (bitdepth != 8 || bytesperpixel != 4) { // Con_Printf ("Bad PNG color depth and/or bpp (%s)\n", fname); qpng_destroy_read_struct(&png, &pnginfo, NULL); return NULL; } data = malloc(*height * rowbytes); rowpointers = malloc(*height * sizeof(*rowpointers)); if (!data || !rowpointers) goto error; for (y = 0; y < *height; y++) rowpointers[y] = data + y * rowbytes; qpng_read_image(png, rowpointers); qpng_read_end(png, NULL); qpng_destroy_read_struct(&png, &pnginfo, NULL); free(rowpointers); return data; } #endif static void GUI_CreateInstaller_Windows(void) { #define RESLANG MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_UK) unsigned char *mandata = NULL; size_t manlen; unsigned char *pngdata = NULL; size_t pnglen; char *error = NULL; HANDLE bin; char ourname[MAX_PATH]; char *basedir = enginebasedir; char newname[MAX_PATH]; char modname[MAX_PATH+10] = "unknownmod"; char tmpname[MAX_PATH]; char tmp[MAX_PATH]; if (MessageBox(mainwindow, "The 'Create Installer' option is still experimental.\nIt's probably still defective.\nSo be sure to test stuff extensively.", "Create Installer", MB_OKCANCEL|MB_DEFBUTTON2) != IDOK) return; PromptForEngine(0); { char workingdir[MAX_PATH]; char absbase[MAX_PATH]; char *slash; GetCurrentDirectory(sizeof(workingdir)-1, workingdir); _snprintf(modname, sizeof(modname), "%s/", enginebasedir); for (slash = modname; *slash; slash++) if (*slash == '/') *slash = '\\'; PathCombine(absbase, workingdir, modname); if (PathRelativePathToA(modname, absbase, FILE_ATTRIBUTE_DIRECTORY, workingdir, FILE_ATTRIBUTE_DIRECTORY)) { if (!strncmp(modname, ".\\", 2)) memmove(modname, modname+2, strlen(modname+2)+1); slash = strchr(modname, '/'); if (slash) *slash = 0; slash = strchr(modname, '\\'); if (slash) *slash = 0; if (!*modname) _snprintf(modname, sizeof(modname), "unknownmod"); } } QC_snprintfz(tmp, sizeof(tmp), "default.fmf"); mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); if (!mandata) { QC_snprintfz(tmp, sizeof(tmp), "%s.fmf", modname); mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); } if (!mandata) { QC_snprintfz(tmp, sizeof(tmp), "../default.fmf"); mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); } if (!mandata) { QC_snprintfz(tmp, sizeof(tmp), "../%s.fmf", modname); mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); } if (!mandata) { QC_snprintfz(tmp, sizeof(tmp), "%s.fmf", modname); if (!PromptForFile("Please Select Manifest File", "FTE Manifests\0*.fmf\0All files\0*.*\0", ".", tmp, tmp, sizeof(tmp), true)) mandata = QCC_ReadFile (tmp, NULL, 0, &manlen); } if (!mandata) { FILE *f; if (MessageBox(mainwindow, "Creating an installer requires a manifest.\nCreate+edit one now?", "Create Installer", MB_OKCANCEL) != IDOK) return; f = fopen(tmp, "wb"); fprintf(f, "FTEManifestVer 1\n"); fprintf(f, "///basic information\n"); fprintf(f, "game \"quake\" ///change this to isolate your mod from quake. if the game is known to the engine itself then other settings will receive default values unless overriden. Should be safe to use as a filename, so should have no spaces or full stops.\n"); fprintf(f, "name \"Quake\" ///this is the full name of your game that you wish the engine to display. Use spaces.\n"); fprintf(f, "//protocolname \"FTE-Quake\" ///allows isolation from other games using the same engine. Should only be changed for standalone total conversions.\n"); fprintf(f, "///filesystem\n"); fprintf(f, "//basegame \"id1\"\n"); fprintf(f, "//basegame \"qw\"\n"); fprintf(f, "//basegame \"*fte\" ///* prefix means its never networked\n"); fprintf(f, "gamedir \"%s\"\n", modname); fprintf(f, "//disablehomedir 0\n"); fprintf(f, "///required packages. add more as needed. these will be downloaded+installed from the get-go\n"); fprintf(f, "///the engine will tell you the correct crc if you get it wrong/don't know it. Its not mandatory, but allows for autoupdates if the fmf changes.\n"); fprintf(f, "//package id1/example.pk3\tmirror \"https://example.com/example.pak\"\t//crc 0xdeadbeef\n"); fprintf(f, "///updateurl points to an (updated) copy of this manifest file, so you can update basic stuff easily. should always be https\n"); fprintf(f, "//updateurl \"https://example.com/example.fmf\"\n"); fprintf(f, "///downloadsurl is a list of optional updates, including engine updates, displayed via the updates menu. should always be https\n"); fprintf(f, "//downloadsurl \"https://fte.triptohell.info/downloadables.php\"\n"); fprintf(f, "///eula displayed when first installing\n"); fprintf(f, "//eula \"By using this game software, you assign your eternal soul to me for me to do as I wish, including but not limited to trading it for a pint of beer. This example is not legally binding.\"\n"); fclose(f); EditFile(tmp, -1, false); return; } else { QC_snprintfz(newname, sizeof(newname), "%s_setup.exe", modname); if (!PromptForFile("Please Select Output Executable", "Executables\0*.exe\0All files\0*.*\0", basedir, newname, tmpname, sizeof(tmpname), true)) return; PathCombine(newname, basedir, tmpname); PathCombine(tmpname, basedir, "tmp.exe"); PathCombine(ourname, enginebasedir, enginebinary); if (!CopyFile(ourname, tmpname, FALSE)) error = "output already exists or cannot be written"; if (!(bin = BeginUpdateResource(tmpname, FALSE))) error = "BeginUpdateResource failed"; else { QC_snprintfz(tmp, sizeof(tmp), "%s.ico", modname); pngdata = QCC_ReadFile (tmp, NULL, 0, &pnglen); #ifdef AVAIL_PNGLIB if (!pngdata) { QC_snprintfz(tmp, sizeof(tmp), "%s.png", modname); pngdata = QCC_ReadFile (tmp, NULL, 0, &pnglen); } if (!pngdata) pngdata = QCC_ReadFile ("default.png", NULL, 0, &pnglen); #endif if (!pngdata) if (PromptForFile("Please Select Icon", "Icons\0*.ico" #ifdef AVAIL_PNGLIB ";*.png" #endif "\0All files\0*.*\0", ".", tmp, tmp, sizeof(tmp), false)) pngdata = QCC_ReadFile (tmp, NULL, 0, &pnglen); if (pngdata && pngdata[0] == 0 && pngdata[1] == 0 && pngdata[2] == 1 && pngdata[3] == 0) { unsigned int iconid = 1, img; unsigned short images; struct { BYTE bWidth; BYTE bHeight; BYTE bColorCount; BYTE bReserved; WORD wPlanes; WORD wBitCount; DWORD dwBytesInRes; DWORD dwOffset; } *iconinfo; icon_group_t icondata; memset(&icondata, 0, sizeof(icondata)); icondata.idType = 1; images = pngdata[4] | (pngdata[5]<<8); UpdateResource(bin, RT_GROUP_ICON, MAKEINTRESOURCE(1), RESLANG, NULL, 0); UpdateResource(bin, RT_GROUP_ICON, MAKEINTRESOURCE(2), RESLANG, NULL, 0); for (iconinfo = (void*)(pngdata+6), img = 0; img < images; iconinfo++, img++) { if (!error && !UpdateResource(bin, RT_ICON, MAKEINTRESOURCE(iconid), 0, pngdata+iconinfo->dwOffset, iconinfo->dwBytesInRes)) error = "UpdateResource failed (icon data)"; //and make a copy of it in the icon list icondata.idEntries[icondata.idCount].bWidth = iconinfo->bWidth; icondata.idEntries[icondata.idCount].bHeight = iconinfo->bHeight; icondata.idEntries[icondata.idCount].wBitCount = iconinfo->wBitCount; icondata.idEntries[icondata.idCount].wPlanes = iconinfo->wPlanes; icondata.idEntries[icondata.idCount].bColorCount = iconinfo->bColorCount; icondata.idEntries[icondata.idCount].dwBytesInRes = iconinfo->dwBytesInRes; icondata.idEntries[icondata.idCount].nId = iconid++; icondata.idCount++; } if (!error && !UpdateResource(bin, RT_GROUP_ICON, MAKEINTRESOURCE(1), RESLANG, &icondata, (pbyte*)&icondata.idEntries[icondata.idCount] - (pbyte*)&icondata)) error = "UpdateResource failed (icon group)"; } #ifdef AVAIL_PNGLIB else if (pngdata) { icon_group_t icondata; qbyte *rgbadata; int imgwidth, imgheight; int iconid = 1; memset(&icondata, 0, sizeof(icondata)); icondata.idType = 1; if (MessageBox(mainwindow, error, "Embedding PNGs is probably buggy/suboptimal. You should consider using an .ico instead.\nContinue anyway?", MB_OKCANCEL) != IDOK) error = "User aborted"; UpdateResource(bin, RT_GROUP_ICON, MAKEINTRESOURCE(1), RESLANG, NULL, 0); UpdateResource(bin, RT_GROUP_ICON, MAKEINTRESOURCE(2), RESLANG, NULL, 0); // UpdateResource(bin, RT_GROUP_ICON, MAKEINTRESOURCE(3), RESLANG, NULL, 0); rgbadata = ReadPNGFile(pngdata, pnglen, &imgwidth, &imgheight, "default.png"); if (!rgbadata) error = "unable to read icon image"; else { void *data = NULL; unsigned int datalen = 0; unsigned int i; // extern cvar_t gl_lerpimages; // gl_lerpimages.ival = 1; for (i = 0; i < sizeof(icosizes)/sizeof(icosizes[0]); i++) { unsigned int x,y; unsigned int pixels; if (icosizes[i].width > imgwidth || icosizes[i].height > imgheight) continue; //ignore icons if they're bigger than the original icon. if (icosizes[i].bpp == 32 && icosizes[i].width >= 128 && icosizes[i].height >= 128 && icosizes[i].width == imgwidth && icosizes[i].height == imgheight) { //png compression. oh look. we originally loaded a png! data = pngdata; datalen = pnglen; } else { //generate the bitmap info BITMAPV4HEADER *bi; qbyte *out, *outmask; qbyte *in, *inrow; unsigned int outidx; pixels = icosizes[i].width * icosizes[i].height; bi = data = malloc(sizeof(*bi) + icosizes[i].width * icosizes[i].height * 5 + icosizes[i].height*4); memset(bi,0, sizeof(BITMAPINFOHEADER)); bi->bV4Size = sizeof(BITMAPINFOHEADER); bi->bV4Width = icosizes[i].width; bi->bV4Height = icosizes[i].height * 2; //icons are logically double-height, with the second half being a silly alpha mask. bi->bV4Planes = 1; bi->bV4BitCount = icosizes[i].bpp; bi->bV4V4Compression = BI_RGB; bi->bV4ClrUsed = (icosizes[i].bpp>=32?0:(1u<bV4Size; out = (qbyte*)data + datalen; datalen += ((icosizes[i].width*icosizes[i].bpp/8+3)&~3) * icosizes[i].height; outmask = (qbyte*)data + datalen; datalen += ((icosizes[i].width+31)&~31)/8 * icosizes[i].height; in = malloc(pixels*4); Image_ResampleTexture((unsigned int*)rgbadata, imgwidth, imgheight, (unsigned int*)in, icosizes[i].width, icosizes[i].height); inrow = in; outidx = 0; if (icosizes[i].bpp == 32) { for (y = 0; y < icosizes[i].height; y++) { inrow = in + 4*icosizes[i].width*(icosizes[i].height-1-y); for (x = 0; x < icosizes[i].width; x++) { if (inrow[3] == 0) //transparent outmask[outidx>>3] |= 1u<<(outidx&7); else { out[0] = inrow[2]; out[1] = inrow[1]; out[2] = inrow[0]; } out += 4; outidx++; inrow += 4; } if (x & 3) out += 4 - (x&3); outidx = (outidx + 31)&~31; } } } if (!error && !UpdateResource(bin, RT_ICON, MAKEINTRESOURCE(iconid), 0, data, datalen)) error = "UpdateResource failed (icon data)"; //and make a copy of it in the icon list icondata.idEntries[icondata.idCount].bWidth = (icosizes[i].width<256)?icosizes[i].width:0; icondata.idEntries[icondata.idCount].bHeight = (icosizes[i].height<256)?icosizes[i].height:0; icondata.idEntries[icondata.idCount].wBitCount = icosizes[i].bpp; icondata.idEntries[icondata.idCount].wPlanes = 1; icondata.idEntries[icondata.idCount].bColorCount = (icosizes[i].bpp>=8)?0:(1u<iCtrlId) { case IDI_O_DEFAULT: MessageBox(hWnd, "Sets the default optimisations", "Help", MB_OK|MB_ICONINFORMATION); break; case IDI_O_DEBUG: MessageBox(hWnd, "Clears all optimisations which can make your progs harder to debug", "Help", MB_OK|MB_ICONINFORMATION); break; case IDI_O_LEVEL0: case IDI_O_LEVEL1: case IDI_O_LEVEL2: case IDI_O_LEVEL3: MessageBox(hWnd, "Sets a specific optimisation level", "Help", MB_OK|MB_ICONINFORMATION); break; // case IDI_O_CHANGE_PROGS_SRC: // MessageBox(hWnd, "Use this button to change your root source file.\nNote that fteqcc compiles sourcefiles from editors first, rather than saving. This means that changes are saved ONLY when you save them, but means that switching project mid-compile can result in problems.", "Help", MB_OK|MB_ICONINFORMATION); // break; case IDI_O_ADDITIONALPARAMETERS: MessageBox(hWnd, "Type in additional commandline parameters here. Use -Dname to define a named precompiler constant before compiling.", "Help", MB_OK|MB_ICONINFORMATION); break; case IDI_O_APPLY: MessageBox(hWnd, "Apply changes shown.", "Help", MB_OK|MB_ICONINFORMATION); break; case IDI_O_APPLYSAVE: MessageBox(hWnd, "Apply changes shown and save the settings for next time.", "Help", MB_OK|MB_ICONINFORMATION); break; case IDI_O_OPTIMISATION: for (i = 0; optimisations[i].enabled; i++) { if (optimisations[i].guiinfo == hi->hItemHandle) { MessageBox(hWnd, optimisations[i].description, "Help", MB_OK|MB_ICONINFORMATION); break; } } break; case IDI_O_COMPILER_FLAG: for (i = 0; compiler_flag[i].enabled; i++) { if (compiler_flag[i].guiinfo == hi->hItemHandle) { MessageBox(hWnd, compiler_flag[i].description, "Help", MB_OK|MB_ICONINFORMATION); break; } } break; case IDI_O_TARGETH2: MessageBox(hWnd, "Click here to compile a hexen2 compatible progs, as well as enable all hexen2 keywords. Note that this uses the -Thexen2. There are other targets available.", "Help", MB_OK|MB_ICONINFORMATION); break; case IDI_O_TARGETFTE: MessageBox(hWnd, "Click here to allow the use of extended instructions not found in the original instruction set.", "Help", MB_OK|MB_ICONINFORMATION); break; } } break; default: return DefWindowProc(hWnd,message,wParam,lParam); } return 0; } static void AddTip(HWND tipwnd, HWND tool, char *message) { TOOLINFO toolInfo = { 0 }; toolInfo.cbSize = sizeof(toolInfo); toolInfo.hwnd = tool; toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS; toolInfo.uId = (UINT_PTR)tool; toolInfo.lpszText = message; SendMessage(tipwnd, TTM_ADDTOOL, 0, (LPARAM)&toolInfo); } void OptionsDialog(void) { char nicername[256], *us; HWND subsection; RECT r; WNDCLASS wndclass; HWND wnd, tipwnd; int i; int flagcolums=1; int x; int y; int my; int lheight; int rheight; int num; int cflagsshown; if (optionsmenu) { BringWindowToTop(optionsmenu); return; } memset(&wndclass, 0, sizeof(wndclass)); wndclass.style = 0; wndclass.lpfnWndProc = OptionsWndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = ghInstance; wndclass.hIcon = LoadIcon(ghInstance, IDI_ICON_FTEQCC); wndclass.hCursor = LoadCursor (NULL,IDC_ARROW); wndclass.hbrBackground = (void *)COLOR_WINDOW; wndclass.lpszMenuName = 0; wndclass.lpszClassName = OPTIONS_WINDOW_CLASS_NAME; RegisterClass(&wndclass); lheight = 0; for (i = 0; optimisations[i].enabled; i++) { if (optimisations[i].flags & FLAG_HIDDENINGUI) continue; lheight++; } lheight = (lheight+1)/2; //double columns for optimisations lheight *= 16; lheight += 112; lheight += 88; cflagsshown = 0; cflagsshown += 2; //hexenc, extended opcodes for (i = 0; compiler_flag[i].enabled; i++) { if (compiler_flag[i].flags & FLAG_HIDDENINGUI) continue; cflagsshown++; } do { flagcolums++; cflagsshown += flagcolums-1; //round up rheight = (cflagsshown/flagcolums)*16; rheight += 16+4+20; //extra parms cap,gap,parmsbox(min) }while (rheight > lheight*flagcolums); r.right = 408 + flagcolums*168; if (r.right < 640) r.right = 640; r.left = GetSystemMetrics(SM_CXSCREEN)/2-320; r.top = GetSystemMetrics(SM_CYSCREEN)/2-240; if (rheight > lheight) r.bottom = r.top + rheight; else { r.bottom = r.top + lheight; rheight = lheight; } r.right += r.left; AdjustWindowRectEx (&r, WS_CAPTION|WS_SYSMENU, FALSE, 0); optionsmenu=CreateWindowEx(WS_EX_CONTEXTHELP, OPTIONS_WINDOW_CLASS_NAME, "Options - FTE QuakeC compiler", WS_CAPTION|WS_SYSMENU, r.left, r.top, r.right-r.left, r.bottom-r.top, NULL, NULL, ghInstance, NULL); tipwnd = CreateWindow(TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, optionsmenu, NULL, ghInstance, NULL); SetWindowPos(tipwnd, HWND_TOPMOST,0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); SendMessage(tipwnd, TTM_SETMAXTIPWIDTH, 0, 500); subsection = CreateWindow("BUTTON", "Optimisations", WS_CHILD|WS_VISIBLE|BS_GROUPBOX, 0, 0, 400, lheight-40*4+24, optionsmenu, NULL, ghInstance, NULL); num = 0; for (i = 0; optimisations[i].enabled; i++) { if (optimisations[i].flags & FLAG_HIDDENINGUI) { optimisations[i].guiinfo = NULL; continue; } QC_strlcpy(nicername, optimisations[i].fullname, sizeof(nicername)); while((us = strchr(nicername, '_'))) *us = ' '; optimisations[i].guiinfo = wnd = CreateWindow("BUTTON",nicername, WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, 8+200*(num&1),16+16*(num/2),200-16,16, subsection, (HMENU)IDI_O_OPTIMISATION, ghInstance, NULL); if (optimisations[i].flags&FLAG_SETINGUI) Button_SetCheck(wnd, 1); else Button_SetCheck(wnd, 0); if (!fl_nondfltopts) EnableWindow(wnd, FALSE); AddTip(tipwnd, wnd, optimisations[i].description); num++; } wnd = CreateWindow("BUTTON","O0", WS_CHILD | WS_VISIBLE, 8,lheight-40*5+24,64,32, optionsmenu, (HMENU)IDI_O_LEVEL0, ghInstance, NULL); AddTip(tipwnd, wnd, "Disable optimisations completely, giving code more similar to vanilla."); wnd = CreateWindow("BUTTON","O1", WS_CHILD | WS_VISIBLE, 8+64,lheight-40*5+24,64,32, optionsmenu, (HMENU)IDI_O_LEVEL1, ghInstance, NULL); AddTip(tipwnd, wnd, "Enable simple optimisations (primarily size). Probably still breaks decompilers."); wnd = CreateWindow("BUTTON","O2", WS_CHILD | WS_VISIBLE, 8+64*2,lheight-40*5+24,64,32, optionsmenu, (HMENU)IDI_O_LEVEL2, ghInstance, NULL); AddTip(tipwnd, wnd, "Enable most optimisations. Does not optimise anything that is likely to break any engines."); wnd = CreateWindow("BUTTON","O3", WS_CHILD | WS_VISIBLE, 8+64*3,lheight-40*5+24,64,32, optionsmenu, (HMENU)IDI_O_LEVEL3, ghInstance, NULL); AddTip(tipwnd, wnd, "Enable unsafe optimisations. The extra optimisations may cause the progs to fail in certain cases, especially if used to compile addon modules."); wnd = CreateWindow("BUTTON","Debug", WS_CHILD | WS_VISIBLE, 8+64*4,lheight-40*5+24,64,32, optionsmenu, (HMENU)IDI_O_DEBUG, ghInstance, NULL); AddTip(tipwnd, wnd, "Disable any optimisations that might interfere with debugging somehow."); wnd = CreateWindow("BUTTON","Default", WS_CHILD | WS_VISIBLE, 8+64*5,lheight-40*5+24,64,32, optionsmenu, (HMENU)IDI_O_DEFAULT, ghInstance, NULL); AddTip(tipwnd, wnd, "Default optimsations are aimed at increasing capacity without breaking debuggers or common decompilers (although gotos, switches, arrays, etc, will still result in issues)."); #ifdef EMBEDDEBUG w_enginebinary = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", enginebinary, WS_CHILD /*| ES_READONLY*/ | WS_VISIBLE | ES_LEFT | ES_AUTOHSCROLL, 8, lheight-40-30*3, 400-16, 22, optionsmenu, (HMENU)IDI_O_ENGINE, ghInstance, NULL); w_enginebasedir = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", enginebasedir, WS_CHILD /*| ES_READONLY*/ | WS_VISIBLE | ES_LEFT | ES_AUTOHSCROLL, 8, lheight-40-30*2, 400-16, 22, optionsmenu, (HMENU)IDI_O_ENGINEBASEDIR, ghInstance, NULL); w_enginecommandline = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", enginecommandline, WS_CHILD /*| ES_READONLY*/ | WS_VISIBLE | ES_LEFT | ES_AUTOHSCROLL, 8, lheight-40-30, 400-16, 22, optionsmenu, (HMENU)IDI_O_ENGINECOMMANDLINE, ghInstance, NULL); AddTip(tipwnd, w_enginebinary, "This is the engine that you wish to debug with.\nCurrently only FTEQW supports actual debugging, while specifying other engines here merely provides you with a quick way to start them up"); AddTip(tipwnd, w_enginebasedir, "This is your base directory (typically the directory your engine executable is in)"); AddTip(tipwnd, w_enginecommandline, "This is the commandline to use to invoke your mod.\nYou'll likely want -game here.\n-window is also handy.\n-nohome can be used to inhibit the use of home directories.\nYou may also want to add '+map start' or some such."); #endif wnd = CreateWindow("BUTTON","Apply", WS_CHILD | WS_VISIBLE, 8,lheight-40,64,32, optionsmenu, (HMENU)IDI_O_APPLY, ghInstance, NULL); AddTip(tipwnd, wnd, "Use selected settings without saving them to disk."); wnd = CreateWindow("BUTTON","Save", WS_CHILD | WS_VISIBLE, 8+64,lheight-40,64,32, optionsmenu, (HMENU)IDI_O_APPLYSAVE, ghInstance, NULL); AddTip(tipwnd, wnd, "Use selected settings and save them to disk so that they're also used the next time you start fteqccgui."); /*wnd = CreateWindow("BUTTON","progs.src", WS_CHILD | WS_VISIBLE, 8+64*2,lheight-40,64,32, optionsmenu, (HMENU)IDI_O_CHANGE_PROGS_SRC, ghInstance, NULL); AddTip(tipwnd, wnd, "Change the initial src file.");*/ y=4; targitem_hexen2 = wnd = CreateWindow("BUTTON","HexenC", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, 408,y,200-16,16, optionsmenu, (HMENU)IDI_O_TARGETH2, ghInstance, NULL); y+=16; if (fl_hexen2) Button_SetCheck(wnd, 1); else Button_SetCheck(wnd, 0); AddTip(tipwnd, wnd, "Compile for hexen2.\nThis changes the opcodes slightly, the progs crc, and enables some additional keywords."); targitem_fte = wnd = CreateWindow("BUTTON","Extended Instructions", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, 408,y,200-16,16, optionsmenu, (HMENU)IDI_O_TARGETFTE, ghInstance, NULL); y+=16; if (fl_ftetarg) Button_SetCheck(wnd, 1); else Button_SetCheck(wnd, 0); AddTip(tipwnd, wnd, "Enables the use of additional opcodes, which only FTE supports at this time.\nThis gives both smaller and faster code, as well as allowing pointers, ints, and other extensions not possible with the vanilla QCVM."); /* autohighlight_item = wnd = CreateWindow("BUTTON","Syntax Highlighting", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, 408,y,200-16,16, optionsmenu, (HMENU)IDI_O_SYNTAX_HIGHLIGHTING, ghInstance, NULL); y+=16; if (fl_autohighlight) Button_SetCheck(wnd, 1); else Button_SetCheck(wnd, 0); */ x = 408; my = y; for (i = 0; compiler_flag[i].enabled; i++) { if (compiler_flag[i].flags & FLAG_HIDDENINGUI) { compiler_flag[i].guiinfo = NULL; continue; } if (y > (cflagsshown/flagcolums)*16) { y = 4; x += 168; } compiler_flag[i].guiinfo = wnd = CreateWindow("BUTTON",compiler_flag[i].fullname, WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, x,y,168,16, optionsmenu, (HMENU)IDI_O_COMPILER_FLAG, ghInstance, NULL); y+=16; if (my < y) my = y; if (compiler_flag[i].flags & FLAG_SETINGUI) Button_SetCheck(wnd, 1); else Button_SetCheck(wnd, 0); AddTip(tipwnd, wnd, compiler_flag[i].description); } CreateWindow("STATIC","Extra Parameters:", WS_CHILD | WS_VISIBLE, 408,my,200-16,16, optionsmenu, (HMENU)0, ghInstance, NULL); my+=16; extraparmsitem = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT",parameters, WS_CHILD | WS_VISIBLE|ES_LEFT | ES_WANTRETURN | ES_MULTILINE | ES_AUTOVSCROLL, 408,my,r.right-r.left - 408 - 8,rheight-my-4, optionsmenu, (HMENU)IDI_O_ADDITIONALPARAMETERS, ghInstance, NULL); AddTip(tipwnd, extraparmsitem, "You can specify any additional commandline arguments here.\nAdd -DFOO=bar to define the FOO preprocessor constant as bar."); ShowWindow(optionsmenu, SW_SHOWDEFAULT); } #undef printf WNDPROC combosubclassproc; static LRESULT CALLBACK SearchComboSubClass(HWND hWnd,UINT message, WPARAM wParam,LPARAM lParam) { switch (message) { case WM_KEYDOWN: switch (wParam) { case VK_RETURN: PostMessage(mainwindow, WM_COMMAND, ID_DEF, (LPARAM)buttons[ID_DEF].hwnd); return true; } } return CallWindowProc(combosubclassproc, hWnd, message, wParam, lParam); } static LRESULT CALLBACK MainWndProc(HWND hWnd,UINT message, WPARAM wParam,LPARAM lParam) { int i; RECT rect; PAINTSTRUCT ps; editor_t *editor; switch (message) { case WM_CLOSE: //if any child editors are still open, send close requests to them first. //this allows them to display prompts, instead of silently losing changes. for (editor = editors; editor;) { editor_t *n = editor->next; if (editor->window) SendMessage(editor->window, WM_CLOSE, 0, 0); editor = n; } //okay, they're all dead. we can kill ourselves now. if (!editors) DestroyWindow(hWnd); return 0; case WM_CREATE: { CLIENTCREATESTRUCT ccs; HMENU rootmenu, windowmenu, m; DragAcceptFiles(hWnd, TRUE); rootmenu = CreateMenu(); AppendMenu(rootmenu, MF_POPUP, (UINT_PTR)(m = CreateMenu()), "&File"); AppendMenu(m, 0, IDM_OPENPROJECT, "Open Project / Decompile"); AppendMenu(m, 0, IDM_OPENNEW, "Open File"); AppendMenu(m, 0, IDM_SAVE, "&Save\tCtrl+S"); // AppendMenu(m, 0, IDM_FIND, "&Find"); AppendMenu(m, 0, IDM_UNDO, "Undo\tCtrl+Z"); AppendMenu(m, 0, IDM_REDO, "Redo\tCtrl+Y"); AppendMenu(m, MF_SEPARATOR, 0, NULL); AppendMenu(m, 0, IDM_CREATEINSTALLER_WINDOWS, "Create Windows Installer"); AppendMenu(m, 0, IDM_CREATEINSTALLER_ANDROID, "Create Android Installer"); AppendMenu(m, 0, IDM_CREATEINSTALLER_PACKAGES, "Create Packages"); AppendMenu(m, MF_SEPARATOR, 0, NULL); AppendMenu(m, 0, IDM_QUIT, "Exit"); AppendMenu(rootmenu, MF_POPUP, (UINT_PTR)(m = CreateMenu()), "&Navigation"); AppendMenu(m, 0, IDM_GOTODEF, "Go To Definition\tF12"); AppendMenu(m, 0, IDM_RETURNDEF, "Return From Definition\tShift+F12"); AppendMenu(m, 0, IDM_GREP, "Grep For Selection\tCtrl+G"); AppendMenu(m, 0, IDM_OPENDOCU, "Open Selected File"); AppendMenu(m, 0, IDM_OUTPUT_WINDOW, "Show Output Window\tF6"); AppendMenu(m, (fl_extramargins?MF_CHECKED:MF_UNCHECKED), IDM_UI_SHOWLINENUMBERS, "Show Line Numbers"); AppendMenu(m, ((fl_tabsize>4)?MF_CHECKED:MF_UNCHECKED), IDM_UI_TABSIZE, "Large Tabs"); AppendMenu(m, MF_SEPARATOR, 0, NULL); AppendMenu(m, 0, IDM_ENCODING_PRIVATEUSE, "Convert to UTF-8"); AppendMenu(m, 0, IDM_ENCODING_DEPRIVATEUSE, "Convert to Quake encoding"); AppendMenu(m, 0, IDM_ENCODING_UNIX, "Convert to Unix Endings"); AppendMenu(m, 0, IDM_ENCODING_WINDOWS, "Convert to Dos Endings"); AppendMenu(rootmenu, MF_POPUP, (UINT_PTR)(m = windowmenu = CreateMenu()), "&Window"); AppendMenu(m, 0, IDM_CASCADE, "Cascade"); AppendMenu(m, 0, IDM_TILE_HORIZ, "Tile Horizontally"); AppendMenu(m, 0, IDM_TILE_VERT, "Tile Vertically"); AppendMenu(rootmenu, MF_POPUP, (UINT_PTR)(m = CreateMenu()), "&Debug"); AppendMenu(m, 0, IDM_DEBUG_REBUILD, "Rebuild\tF7"); AppendMenu(m, 0, IDM_DEBUG_BUILD_OPTIONS, "Build Options"); AppendMenu(m, MF_SEPARATOR, 0, NULL); AppendMenu(m, 0, IDM_DEBUG_SETNEXT, "Set Next Statement\tF8"); AppendMenu(m, 0, IDM_DEBUG_RUN, "Run/Resume\tF5"); AppendMenu(m, 0, IDM_DEBUG_STEPOVER, "Step Over\tF10"); AppendMenu(m, 0, IDM_DEBUG_STEPINTO, "Step Into\tF11"); AppendMenu(m, 0, IDM_DEBUG_STEPOUT, "Step Out\tShift-F11"); AppendMenu(m, 0, IDM_DEBUG_TOGGLEBREAK, "Set Breakpoint\tF9"); AppendMenu(rootmenu, MF_POPUP, (UINT_PTR)(m = CreateMenu()), "&Help"); AppendMenu(m, 0, IDM_ABOUT, "About"); SetMenu(hWnd, rootmenu); // Retrieve the handle to the window menu and assign the // first child window identifier. memset(&ccs, 0, sizeof(ccs)); ccs.hWindowMenu = windowmenu; ccs.idFirstChild = IDM_FIRSTCHILD; // Create the MDI client window. mdibox = CreateWindow( "MDICLIENT", (LPCTSTR) NULL, WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL, 0, 0, 320, 200, hWnd, (HMENU) 0xCAC, ghInstance, (LPSTR) &ccs); ShowWindow(mdibox, SW_SHOW); watches = CreateWindow(WC_LISTVIEW, (LPCTSTR) NULL, WS_CHILD | WS_VSCROLL | WS_HSCROLL | LVS_REPORT | LVS_EDITLABELS, 0, 0, 320, 200, hWnd, (HMENU) 0xCAD, ghInstance, NULL); SplitterAdd(mdibox, 32, 32); if (watches) { LVCOLUMN col; LVITEM newi; // ListView_SetUnicodeFormat(watches, TRUE); ListView_SetExtendedListViewStyle(watches, LVS_EX_GRIDLINES); memset(&col, 0, sizeof(col)); col.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH; col.fmt = LVCFMT_LEFT; col.cx = 320; col.pszText = "Variable"; ListView_InsertColumn(watches, 0, &col); col.pszText = "Value"; ListView_InsertColumn(watches, 1, &col); memset(&newi, 0, sizeof(newi)); newi.pszText = ""; newi.mask = LVIF_TEXT | LVIF_PARAM; newi.lParam = ~0; newi.iSubItem = 0; ListView_InsertItem(watches, &newi); } projecttree = CreateWindow(WC_TREEVIEW, (LPCTSTR) NULL, WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL | TVS_HASBUTTONS |TVS_LINESATROOT|TVS_HASLINES, 0, 0, 320, 200, hWnd, (HMENU) 0xCAC, ghInstance, (LPSTR) &ccs); ShowWindow(projecttree, SW_SHOW); if (projecttree) { search_name = CreateWindowEx(WS_EX_CLIENTEDGE, "COMBOBOX", (LPCTSTR) NULL, WS_CHILD | WS_CLIPCHILDREN|CBS_DROPDOWN|CBS_SORT, 0, 0, 320, 200, hWnd, (HMENU) 0x4403, ghInstance, (LPSTR) NULL); { //microsoft suck big hairy donkey balls. //this tries to get the edit box of the combo control. HWND comboedit = GetWindow(search_name, GW_CHILD); combosubclassproc = (WNDPROC) SetWindowLongPtr(comboedit, GWLP_WNDPROC, (DWORD_PTR) SearchComboSubClass); } ShowWindow(search_name, SW_SHOW); } } break; case WM_CTLCOLORBTN: return (LRESULT)GetSysColorBrush(COLOR_HIGHLIGHT);//COLOR_BACKGROUND; case WM_DESTROY: DragAcceptFiles(hWnd, FALSE); mainwindow = NULL; break; case WM_DROPFILES: { HDROP p = (HDROP)wParam; char fname[MAX_PATH]; if (DragQueryFile(p, ~0, (LPSTR) NULL, 0) == 1) { DragQueryFile(p, 0, fname, sizeof(fname)); SetProgsSrcFileAndPath(fname); resetprogssrc = true; } DragFinish(p); } break; case WM_SIZE: { int y; GetClientRect(mainwindow, &rect); y = rect.bottom; for (i = 0; i < NUMBUTTONS; i+=2) { y -= 24; if (!buttons[i+1].hwnd) SetWindowPos(buttons[i].hwnd, NULL, 0, y, 192, 24, SWP_NOZORDER); else { SetWindowPos(buttons[i].hwnd, NULL, 0, y, 192/2, 24, SWP_NOZORDER); SetWindowPos(buttons[i+1].hwnd, NULL, 192/2, y, 192-192/2, 24, SWP_NOZORDER); } } y -= 24; SetWindowPos(search_name, NULL, 0, y, 192, 24, SWP_NOZORDER); if (projecttree) SetWindowPos(projecttree, NULL, 0, 0, 192, y, SWP_NOZORDER); splitterrect.left = 192; splitterrect.right = rect.right-rect.left; splitterrect.bottom = rect.bottom-rect.top; SplitterUpdate(); } break; // goto gdefault; case WM_ERASEBKGND: return TRUE; //background is clear... or doesn't need clearing (if its fully obscured) case WM_PAINT: BeginPaint(hWnd,(LPPAINTSTRUCT)&ps); EndPaint(hWnd,(LPPAINTSTRUCT)&ps); return TRUE; case WM_COMMAND: i = LOWORD(wParam); if (i == 0x4403) { char buffer[65536]; char text[128]; switch(HIWORD(wParam)) { case CBN_EDITUPDATE: GetWindowText(search_name, text, sizeof(text)-1); if (GenAutoCompleteList(text, buffer, sizeof(buffer))) { char token[128]; char *list; DWORD start=0,end=0; SendMessage(search_name, CB_GETEDITSEL, (WPARAM)&start, (LPARAM)&end); ComboBox_ResetContent(search_name); //windows is shit. this clears the text too. SetWindowText(search_name, text); ComboBox_SetEditSel(search_name, start, end); for (list = buffer; ; ) { list = COM_ParseOut(list, token, sizeof(token)); if (!*token) break; ComboBox_AddString(search_name, token); } } return true; } goto gdefault; } if (i>=20 && i < 20+NUMBUTTONS) { i -= 20; if (i == ID_DEF) { GetWindowText(search_name, finddef, sizeof(finddef)-1); return true; } if (i == ID_GREP) { GetWindowText(search_name, greptext, sizeof(greptext)-1); return true; } buttons[i].washit = 1; break; } if (i < IDM_FIRSTCHILD) { HWND ew; editor_t *editor; ew = (HWND)SendMessage(mdibox, WM_MDIGETACTIVE, 0, 0); for (editor = editors; editor; editor = editor->next) { if (editor->window == ew) break; } if (editor) EditorMenu(editor, wParam); else GenericMenu(wParam); break; } goto gdefault; case WM_NOTIFY: if (lParam) { NMHDR *nm; HANDLE item; TVITEM i; char filename[256]; char itemtext[256]; int oldlen; int newlen; nm = (NMHDR*)lParam; if (nm->hwndFrom == watches) { switch(nm->code) { case LVN_BEGINLABELEDITA: return FALSE; //false to allow... case LVN_BEGINLABELEDITW: // OutputDebugString("Begin EditW\n"); return FALSE; //false to allow... case LVN_ENDLABELEDITA: if (((NMLVDISPINFOA*)nm)->item.iItem == ListView_GetItemCount(watches)-1) { LVITEM newi; memset(&newi, 0, sizeof(newi)); newi.iItem = ListView_GetItemCount(watches); newi.pszText = ""; newi.mask = LVIF_TEXT | LVIF_PARAM; newi.lParam = ~0; newi.iSubItem = 0; ListView_InsertItem(watches, &newi); } EngineCommandf("qcinspect \"%s\" \"%s\"\n", ((NMLVDISPINFOA*)nm)->item.pszText, ""); //term, scope PostMessage(mainwindow, WM_SIZE, 0, 0); return TRUE; //true to allow... /* case LVN_ENDLABELEDITW: // OutputDebugString("End EditW\n"); if (((NMLVDISPINFOW*)nm)->item.iItem == ListView_GetItemCount(watches)-1) { LVITEM newi; memset(&newi, 0, sizeof(newi)); newi.iItem = ListView_GetItemCount(watches); newi.pszText = ""; newi.mask = LVIF_TEXT | LVIF_PARAM; newi.lParam = ~0; newi.iSubItem = 0; ListView_InsertItem(watches, &newi); } EngineCommandf("qcinspect \"%s\" \"%s\"\n", ((NMLVDISPINFOW*)nm)->item.pszText, ""); //term, scope return TRUE; //true to allow... */ case LVN_ITEMCHANGING: // OutputDebugString("Changing\n"); return FALSE; //false to allow... case LVN_ITEMCHANGED: // OutputDebugString("Changed\n"); return FALSE; case LVN_GETDISPINFOA: // OutputDebugString("LVN_GETDISPINFOA\n"); return FALSE; // case LVN_GETDISPINFOW: // OutputDebugString("LVN_GETDISPINFOW\n"); // return FALSE; case NM_DBLCLK: // OutputDebugString("NM_DBLCLK\n"); { NMITEMACTIVATE *ia = (NMITEMACTIVATE*)nm; LVHITTESTINFO ht; memset(&ht, 0, sizeof(ht)); ht.pt = ia->ptAction; ListView_SubItemHitTest(watches, &ht); ListView_EditLabel(watches, ht.iItem); } return TRUE; case LVN_ITEMACTIVATE: // OutputDebugString("LVN_ITEMACTIVATE\n"); return FALSE; //must return false case LVN_COLUMNCLICK: // OutputDebugString("LVN_COLUMNCLICK\n"); break; default: // sprintf(filename, "%i\n", nm->code); // OutputDebugString(filename); break; } return FALSE; } else if (nm->hwndFrom == projecttree) { switch(nm->code) { case NM_DBLCLK: item = TreeView_GetSelection(projecttree); memset(&i, 0, sizeof(i)); i.hItem = item; i.mask = TVIF_TEXT|TVIF_PARAM; i.pszText = itemtext; i.cchTextMax = sizeof(itemtext)-1; if (!TreeView_GetItem(projecttree, &i)) return 0; if (!i.lParam) return 0; strcpy(filename, i.pszText); while(item) { item = TreeView_GetParent(projecttree, item); i.hItem = item; if (!TreeView_GetItem(projecttree, &i)) break; if (!TreeView_GetParent(projecttree, item)) break; oldlen = strlen(filename); newlen = strlen(i.pszText); if (oldlen + newlen + 2 > sizeof(filename)) break; //don't overflow. memmove(filename+newlen+1, filename, oldlen+1); filename[newlen] = '/'; memcpy(filename, i.pszText, newlen); } EditFile(filename, -1, false); break; } } } default: gdefault: if (mdibox) return DefFrameProc(hWnd,mdibox,message,wParam,lParam); else return DefWindowProc(hWnd,message,wParam,lParam); } return 0; } static void DoTranslateMessage(MSG *msg) { if (!TranslateAccelerator(mainwindow, accelerators, msg)) { TranslateMessage(msg); DispatchMessage(msg); } } void GUIPrint(HWND wnd, char *msg) { // MSG wmsg; int len; static int writing; if (writing) return; if (!mainwindow) { printf("%s", msg); return; } writing=true; len=Edit_GetTextLength(wnd); /* if ((unsigned)len>(32767-strlen(msg))) Edit_SetSel(wnd,0,len); else*/ Edit_SetSel(wnd,len,len); Edit_ReplaceSel(wnd,msg); /* while (PeekMessage (&wmsg, NULL, 0, 0, PM_NOREMOVE)) { if (!GetMessage (&wmsg, NULL, 0, 0)) break; DoTranslateMessage(&wmsg); } */ writing=false; } unsigned int utf8_decode(int *error, const void *in, char **out) { //uc is the output unicode char unsigned int uc = 0xfffdu; //replacement character //l is the length unsigned int l = 1; const unsigned char *str = in; if ((*str & 0xe0) == 0xc0) { if ((str[1] & 0xc0) == 0x80) { l = 2; uc = ((str[0] & 0x1f)<<6) | (str[1] & 0x3f); if (!uc || uc >= (1u<<7)) //allow modified utf-8 *error = 0; else *error = 2; } else *error = 1; } else if ((*str & 0xf0) == 0xe0) { if ((str[1] & 0xc0) == 0x80 && (str[2] & 0xc0) == 0x80) { l = 3; uc = ((str[0] & 0x0f)<<12) | ((str[1] & 0x3f)<<6) | ((str[2] & 0x3f)<<0); if (uc >= (1u<<11)) *error = 0; else *error = 2; } else *error = 1; } else if ((*str & 0xf8) == 0xf0) { if ((str[1] & 0xc0) == 0x80 && (str[2] & 0xc0) == 0x80 && (str[3] & 0xc0) == 0x80) { l = 4; uc = ((str[0] & 0x07)<<18) | ((str[1] & 0x3f)<<12) | ((str[2] & 0x3f)<<6) | ((str[3] & 0x3f)<<0); if (uc >= (1u<<16)) *error = 0; else *error = 2; } else *error = 1; } else if ((*str & 0xfc) == 0xf8) { if ((str[1] & 0xc0) == 0x80 && (str[2] & 0xc0) == 0x80 && (str[3] & 0xc0) == 0x80 && (str[4] & 0xc0) == 0x80) { l = 5; uc = ((str[0] & 0x03)<<24) | ((str[1] & 0x3f)<<18) | ((str[2] & 0x3f)<<12) | ((str[3] & 0x3f)<<6) | ((str[4] & 0x3f)<<0); if (uc >= (1u<<21)) *error = 0; else *error = 2; } else *error = 1; } else if ((*str & 0xfe) == 0xfc) { //six bytes if ((str[1] & 0xc0) == 0x80 && (str[2] & 0xc0) == 0x80 && (str[3] & 0xc0) == 0x80 && (str[4] & 0xc0) == 0x80) { l = 6; uc = ((str[0] & 0x01)<<30) | ((str[1] & 0x3f)<<24) | ((str[2] & 0x3f)<<18) | ((str[3] & 0x3f)<<12) | ((str[4] & 0x3f)<<6) | ((str[5] & 0x3f)<<0); if (uc >= (1u<<26)) *error = 0; else *error = 2; } else *error = 1; } //0xfe and 0xff, while plausable leading bytes, are not permitted. #if 0 else if ((*str & 0xff) == 0xfe) { if ((str[1] & 0xc0) == 0x80 && (str[2] & 0xc0) == 0x80 && (str[3] & 0xc0) == 0x80 && (str[4] & 0xc0) == 0x80) { l = 7; uc = 0 | ((str[1] & 0x3f)<<30) | ((str[2] & 0x3f)<<24) | ((str[3] & 0x3f)<<18) | ((str[4] & 0x3f)<<12) | ((str[5] & 0x3f)<<6) | ((str[6] & 0x3f)<<0); if (uc >= (1u<<31)) *error = 0; else *error = 2; } else *error = 1; } else if ((*str & 0xff) == 0xff) { if ((str[1] & 0xc0) == 0x80 && (str[2] & 0xc0) == 0x80 && (str[3] & 0xc0) == 0x80 && (str[4] & 0xc0) == 0x80) { l = 8; uc = 0 | ((str[1] & 0x3f)<<36) | ((str[2] & 0x3f)<<30) | ((str[3] & 0x3f)<<24) | ((str[4] & 0x3f)<<18) | ((str[5] & 0x3f)<<12) | ((str[6] & 0x3f)<<6) | ((str[7] & 0x3f)<<0); if (uc >= (1llu<<36)) *error = false; else *error = 2; } else *error = 1; } #endif else if (*str & 0x80) { //sequence error *error = 1; uc = 0xe000u + *str; } else { //ascii char *error = 0; uc = *str; } *out = (void*)(str + l); if (!*error) { //try to deal with surrogates by decoding the low if we see a high. if (uc >= 0xd800u && uc < 0xdc00u) { #if 1 //cesu-8 char *lowend; unsigned int lowsur = utf8_decode(error, str + l, &lowend); if (*error == 4) { *out = lowend; uc = (((uc&0x3ffu) << 10) | (lowsur&0x3ffu)) + 0x10000; *error = false; } else #endif { *error = 3; //bad - lead surrogate without tail. } } if (uc >= 0xdc00u && uc < 0xe000u) *error = 4; //bad - tail surrogate //these are meant to be illegal too if (uc == 0xfffeu || uc == 0xffffu || uc > 0x10ffffu) *error = 2; //illegal code } return uc; } //outlen is the size of out in _BYTES_. wchar_t *widen(wchar_t *out, size_t outbytes, const char *utf8, const char *stripchars) { size_t outlen; wchar_t *ret = out; //utf-8 to utf-16, not ucs-2. unsigned int codepoint; int error; outlen = outbytes/sizeof(wchar_t); if (!outlen) return L""; outlen--; while (*utf8) { if (stripchars && strchr(stripchars, *utf8)) { //skip certain ascii chars utf8++; continue; } codepoint = utf8_decode(&error, utf8, (void*)&utf8); if (error || codepoint > 0x10FFFFu) codepoint = 0xFFFDu; if (codepoint > 0xffff) { if (outlen < 2) break; outlen -= 2; codepoint -= 0x10000u; *out++ = 0xD800 | (codepoint>>10); *out++ = 0xDC00 | (codepoint&0x3ff); } else { if (outlen < 1) break; outlen -= 1; *out++ = codepoint; } } *out = 0; return ret; } int GUIEmitOutputText(HWND wnd, int start, char *text, int len, DWORD colour) { wchar_t wc[2048]; int c; CHARFORMAT cf; if (!len) return start; c = text[len]; text[len] = '\0'; // wc = QCC_makeutf16(text, len, &ol); widen(wc, sizeof(wc), text, "\r"); text[len] = c; Edit_SetSel(wnd,start,start); SendMessageW(wnd, EM_REPLACESEL, 0L, (LPARAM)wc); len = wcslen(wc); Edit_SetSel(wnd,start,start+len); memset(&cf, 0, sizeof(cf)); cf.cbSize = sizeof(cf); cf.dwMask = CFM_COLOR; cf.crTextColor = colour; SendMessage(wnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf); Edit_SetSel(wnd,start+len,start+len); Edit_ScrollCaret(wnd); return start + len; } int outlen; int outstatus; pbool gui_doannotates; int GUIprintf(const char *msg, ...) { va_list argptr; char buf[1024]; char rn[3] = "\n"; char *st, *s; int args; // MSG wmsg; DWORD col; va_start (argptr,msg); args = QC_vsnprintf (buf,sizeof(buf)-1, msg,argptr); va_end (argptr); buf[sizeof(buf)-5] = '.'; buf[sizeof(buf)-4] = '.'; buf[sizeof(buf)-3] = '.'; buf[sizeof(buf)-2] = '\n'; buf[sizeof(buf)-1] = 0; printf("%s", buf); //OutputDebugStringA(buf); if (logfile) fprintf(logfile, "%s", buf); if (!*buf) { editor_t *ed; /*clear text*/ SetWindowText(outputbox,""); outlen = 0; /*make sure its active so we can actually scroll. stupid windows*/ SplitterFocus(outputbox, 64, 0); /*colour background to default*/ TreeView_SetBkColor(projecttree, -1); outstatus = 0; if (gui_doannotates) { for (ed = editors; ed; ed = ed->next) { if (ed->scintilla) SendMessage(ed->editpane, SCI_ANNOTATIONCLEARALL, 0, 0); } } return 0; } if (strstr(buf, ": error") || strstr(buf, ": werror")) { if (outstatus < 2) { TreeView_SetBkColor(projecttree, RGB(255, 0, 0)); outstatus = 2; } col = RGB(255, 0, 0); } else if (strstr(buf, ": warning")) { if (outstatus < 1) { TreeView_SetBkColor(projecttree, RGB(255, 255, 0)); outstatus = 1; } col = RGB(128, 128, 0); } else col = RGB(0, 0, 0); s = st = buf; while(*s) { if (*s == '\n') { *s = '\0'; if (!strncmp(st, "code: ", 6)) st+=6; else { if (*st) outlen = GUIEmitOutputText(outputbox, outlen, st, strlen(st), col); outlen = GUIEmitOutputText(outputbox, outlen, rn, 1, col); } if (gui_doannotates) { char *colon1 = strchr(st, ':'); if (colon1) { char *colon2 = strchr(colon1+1, ':'); if (colon2) { unsigned int line; char *validation; *colon1 = 0; line = strtoul(colon1+1, &validation, 10); if (validation == colon2) { editor_t *ed; colon2++; while(*colon2 == ' ' || *colon2 == '\t') colon2++; for (ed = editors; ed; ed = ed->next) { if (!stricmp(ed->filename, st)) { if (ed->scintilla) { if (!SendMessage(ed->editpane, SCI_ANNOTATIONGETLINES, line-1, 0)) { SendMessage(ed->editpane, SCI_ANNOTATIONSETVISIBLE, ANNOTATION_BOXED, 0); SendMessage(ed->editpane, SCI_ANNOTATIONSETTEXT, line-1, (LPARAM)colon2); } else { char buf[8192]; int clen = SendMessage(ed->editpane, SCI_ANNOTATIONGETTEXT, line-1, (LPARAM)NULL); if (clen+1+strlen(colon2) < sizeof(buf)) { clen = SendMessage(ed->editpane, SCI_ANNOTATIONGETTEXT, line-1, (LPARAM)buf); buf[clen++] = '\n'; memcpy(buf+clen, colon2, strlen(colon2)+1); // SendMessage(ed->editpane, SCI_ANNOTATIONSETVISIBLE, ANNOTATION_BOXED, 0); SendMessage(ed->editpane, SCI_ANNOTATIONSETTEXT, line-1, (LPARAM)buf); } } } break; } } } } } } st = s+1; } s++; } if (*st) outlen = GUIEmitOutputText(outputbox, outlen, st, strlen(st), col); /* s = st = buf; while(*s) { if (*s == '\n') { *s = '\0'; if (*st) GUIPrint(outputbox, st); GUIPrint(outputbox, "\r\n"); st = s+1; } s++; } if (*st) GUIPrint(outputbox, st); */ return args; } int Dummyprintf(const char *msg, ...){return 0;} #undef Sys_Error void compilecb(void) { //used to repaint the output window periodically instead of letting it redraw as stuff gets sent to it. this can save significant time on mods with boatloads of warnings. MSG wmsg; if (!SplitterGet(outputbox)) return; SendMessage(outputbox, WM_SETREDRAW, TRUE, 0); RedrawWindow(outputbox, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); while (PeekMessage (&wmsg, NULL, 0, 0, PM_REMOVE)) DoTranslateMessage(&wmsg); SendMessage(outputbox, WM_SETREDRAW, FALSE, 0); } void Sys_Error(const char *text, ...); void RunCompiler(char *args, pbool quick) { const char *argv[256]; int argc; progexterns_t ext; progfuncs_t funcs; editor_t *editor; for (editor = editors; editor; editor = editor->next) { if (editor->modified) { if (EditorModified(editor)) { char msg[1024]; sprintf(msg, "%s is modified in both memory and on disk. Overwrite external modification? (saying no will reload from disk)", editor->filename); switch(MessageBox(NULL, msg, "Modification conflict", MB_YESNOCANCEL)) { case IDYES: EditorSave(editor); break; case IDNO: EditorReload(editor); break; case IDCANCEL: break; /*compiling will use whatever is in memory*/ } } else { /*not modified on disk, but modified in memory? try and save it, cos we might as well*/ EditorSave(editor); } } else { /*modified on disk but not in memory? just reload it off disk*/ if (EditorModified(editor)) EditorReload(editor); } } memset(&funcs, 0, sizeof(funcs)); funcs.funcs.parms = &ext; memset(&ext, 0, sizeof(ext)); ext.ReadFile = GUIReadFile; ext.FileSize = GUIFileSize; ext.WriteFile = QCC_WriteFile; ext.Sys_Error = Sys_Error; if (quick) ext.Printf = Dummyprintf; else { ext.Printf = GUIprintf; GUIprintf(""); } ext.DPrintf = ext.Printf; if (logfile) fclose(logfile); if (fl_log && !quick) logfile = fopen("fteqcc.log", "wb"); else logfile = NULL; if (SplitterGet(outputbox)) SendMessage(outputbox, WM_SETREDRAW, FALSE, 0); argc = GUI_BuildParms(args, argv, sizeof(argv)/sizeof(argv[0]), quick); if (!argc) ext.Printf("Too many args\n"); else if (CompileParams(&funcs, outputbox?compilecb:NULL, argc, argv)) { if (!quick) { EngineGiveFocus(); EngineCommandf("qcresume\nqcreload\n"); // EngineCommandf("qcresume\nmenu_restart\nrestart\n"); } } if (SplitterGet(outputbox)) { SendMessage(outputbox, WM_SETREDRAW, TRUE, 0); RedrawWindow(outputbox, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); } if (logfile) fclose(logfile); } static void CreateOutputWindow(pbool doannoates) { gui_doannotates = doannoates; if (!outputbox) { outputbox = CreateAnEditControl(mainwindow, NULL); } SplitterFocus(outputbox, 64, 128); } int GrepSubFiles(HTREEITEM node, char *string) { HTREEITEM ch, p; char fullname[1024]; char parentstring[256]; int pl, nl; TV_ITEM parent; int found = 0; if (!node) return found; memset(&parent, 0, sizeof(parent)); *fullname = 0; p = node; while (p) { parent.hItem = p; parent.mask = TVIF_TEXT; parent.pszText = parentstring; parent.cchTextMax = sizeof(parentstring)-1; if (!TreeView_GetItem(projecttree, &parent)) break; nl = strlen(fullname); pl = strlen(parent.pszText); if (nl + 1 + pl + 1 > sizeof(fullname)) return found; p = TreeView_GetParent(projecttree, p); if (!p && *fullname) break; //ignore the root node, unless we're actually querying that root node. memmove(fullname+pl+1, fullname, nl+1); memcpy(fullname, parent.pszText, pl); fullname[pl] = nl?'/':'\0'; } //skip the leading progs.src/ if its there, because that's an abstraction and does not match the filesystem. found += Grep(fullname, string); ch = TreeView_GetChild(projecttree, node); found += GrepSubFiles(ch, string); ch = TreeView_GetNextSibling(projecttree, node); found += GrepSubFiles(ch, string); return found; } void GrepAllFiles(char *string) { int found; CreateOutputWindow(false); GUIprintf(""); found = GrepSubFiles(TreeView_GetChild(projecttree, TVI_ROOT), string); if (found) GUIprintf("grep found %i occurences\n", found); else GUIprintf("grep found nothing\n"); } void AddSourceFile(const char *parentpath, const char *filename) { char string[1024]; HANDLE pi; TVINSERTSTRUCT item; TV_ITEM parent; char parentstring[256]; char *slash; while (!strncmp(filename, "./", 2)) filename += 2; QC_strlcpy(string, filename, sizeof(string)); memset(&item, 0, sizeof(item)); memset(&parent, 0, sizeof(parent)); pi = item.hParent = TVI_ROOT; item.hInsertAfter = TVI_LAST;//TVI_SORT; item.item.pszText = string; item.item.state = TVIS_EXPANDED; item.item.stateMask = TVIS_EXPANDED; item.item.mask = TVIF_TEXT|TVIF_STATE|TVIF_PARAM; if (parentpath && stricmp(parentpath, filename)) { item.hParent = TreeView_GetChild(projecttree, item.hParent); do { parent.hItem = item.hParent; parent.mask = TVIF_TEXT; parent.pszText = parentstring; parent.cchTextMax = sizeof(parentstring)-1; if (TreeView_GetItem(projecttree, &parent)) { if (!stricmp(parent.pszText, parentpath)) { pi = item.hParent; break; } } } while((item.hParent=TreeView_GetNextSibling(projecttree, item.hParent))); } else parentpath = NULL; while(item.item.pszText) { if (parentpath) { slash = strchr(item.item.pszText, '/'); if (slash) *slash++ = '\0'; } else slash = NULL; item.hParent = TreeView_GetChild(projecttree, pi); do { parent.hItem = item.hParent; parent.mask = TVIF_TEXT; parent.pszText = parentstring; parent.cchTextMax = sizeof(parentstring)-1; if (TreeView_GetItem(projecttree, &parent)) { if (!stricmp(parent.pszText, item.item.pszText)) break; } } while((item.hParent=TreeView_GetNextSibling(projecttree, item.hParent))); if (!item.hParent) { //add a directory. item.hParent = pi; item.item.lParam = !slash; //lparam = false if we're only adding this node to get at a child. item.item.state = ((*item.item.pszText!='.')?TVIS_EXPANDED:0); //directories with a leading . should not be expanded by default pi = (HANDLE)SendMessage(projecttree,TVM_INSERTITEM,0,(LPARAM)&item); item.hParent = pi; } else pi = item.hParent; item.item.pszText = slash; } } //called when progssrcname has changed. //progssrcname should already have been set. void UpdateFileList(void) { TVINSERTSTRUCT item; TV_ITEM parent; memset(&item, 0, sizeof(item)); memset(&parent, 0, sizeof(parent)); if (projecttree) { size_t size; char *buffer; AddSourceFile(NULL, progssrcname); buffer = QCC_ReadFile(progssrcname, NULL, 0, &size); pr_file_p = QCC_COM_Parse(buffer); if (*qcc_token == '#') { //aaaahhh! newstyle! } else { pr_file_p = QCC_COM_Parse(pr_file_p); //we dont care about the produced progs.dat while(pr_file_p) { if (*qcc_token == '#') //panic if there's preprocessor in there. break; AddSourceFile(progssrcname, qcc_token); pr_file_p = QCC_COM_Parse(pr_file_p); //we dont care about the produced progs.dat } } free(buffer); RunCompiler(parameters, true); } } static void Packager_MessageCallback(void *ctx, const char *fmt, ...) { va_list va; char message[1024]; va_start (va, fmt); vsnprintf (message, sizeof(message)-1, fmt, va); va_end (va); outlen = GUIEmitOutputText(outputbox, outlen, message, strlen(message), RGB(0, 0, 0)); } void GUI_DoDecompile(void *buf, size_t size) { char *c = ReadProgsCopyright(buf, size); if (!c || !*c) c = "COPYRIGHT OWNER NOT KNOWN"; //all work is AUTOMATICALLY copyrighted under the terms of the Berne Convention in all major nations. It _IS_ copyrighted, even if there's no license etc included. Good luck guessing what rights you have. if (MessageBox(mainwindow, qcva("The copyright message from this progs is\n%s\n\nPlease respect the wishes and legal rights of the person who created this.", c), "Copyright", MB_OKCANCEL|MB_DEFBUTTON2|MB_ICONSTOP) == IDOK) { CreateOutputWindow(true); compilecb(); DecompileProgsDat(progssrcname, buf, size); if (SplitterGet(outputbox)) { SendMessage(outputbox, WM_SETREDRAW, TRUE, 0); RedrawWindow(outputbox, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); } QCC_SaveVFiles(); } } int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { pbool fl_acc; unsigned int i; WNDCLASS wndclass; static ACCEL acceleratorlist[] = { {FCONTROL|FVIRTKEY, 'S', IDM_SAVE}, {FCONTROL|FVIRTKEY, 'F', IDM_FIND}, {FCONTROL|FVIRTKEY, 'G', IDM_GREP}, {FVIRTKEY, VK_F3, IDM_FINDNEXT}, {FSHIFT|FVIRTKEY, VK_F3, IDM_FINDPREV}, // {FVIRTKEY, VK_F4, IDM_NEXTERROR}, {FVIRTKEY, VK_F5, IDM_DEBUG_RUN}, {FVIRTKEY, VK_F6, IDM_OUTPUT_WINDOW}, {FVIRTKEY, VK_F7, IDM_DEBUG_REBUILD}, {FVIRTKEY, VK_F8, IDM_DEBUG_SETNEXT}, {FVIRTKEY, VK_F9, IDM_DEBUG_TOGGLEBREAK}, {FVIRTKEY, VK_F10, IDM_DEBUG_STEPOVER}, {FVIRTKEY, VK_F11, IDM_DEBUG_STEPINTO}, {FSHIFT|FVIRTKEY, VK_F11, IDM_DEBUG_STEPOUT}, {FVIRTKEY, VK_F12, IDM_GOTODEF}, {FSHIFT|FVIRTKEY, VK_F12, IDM_RETURNDEF} }; int mode; ghInstance= hInstance; strcpy(enginebinary, ""); strcpy(enginebasedir, ""); strcpy(enginecommandline, ""); GUI_SetDefaultOpts(); mode = GUI_ParseCommandLine(lpCmdLine, false); if(mode == 1) { RunCompiler(lpCmdLine, false); return 0; } for (i = 0, fl_acc = false; compiler_flag[i].enabled; i++) { if (!strcmp("acc", compiler_flag[i].abbrev)) { fl_acc = !!(compiler_flag[i].flags & FLAG_SETINGUI); break; } } InitCommonControls(); if (!fl_acc && !*progssrcname) { strcpy(progssrcname, "preprogs.src"); if (QCC_RawFileSize(progssrcname)==-1) strcpy(progssrcname, "progs.src"); if (QCC_RawFileSize(progssrcname)==-1) { char filename[MAX_PATH]; char oldpath[MAX_PATH+10]; OPENFILENAME ofn; memset(&ofn, 0, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hInstance = ghInstance; ofn.lpstrFile = filename; ofn.lpstrTitle = "Please find progs.src or progs.dat"; ofn.nMaxFile = sizeof(filename)-1; ofn.lpstrFilter = "QuakeC Projects\0*.src;*.dat\0All files\0*.*\0"; memset(filename, 0, sizeof(filename)); GetCurrentDirectory(sizeof(oldpath)-1, oldpath); ofn.lpstrInitialDir = oldpath; if (GetOpenFileName(&ofn)) strcpy(progssrcname, filename); else { MessageBox(NULL, "You didn't select a file", "Error", 0); return 0; } } } resetprogssrc = true; wndclass.style = 0; wndclass.lpfnWndProc = MainWndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = ghInstance; wndclass.hIcon = LoadIcon(ghInstance, IDI_ICON_FTEQCC); wndclass.hCursor = LoadCursor (NULL,IDC_ARROW); wndclass.hbrBackground = (void *)COLOR_WINDOW; wndclass.lpszMenuName = 0; wndclass.lpszClassName = MDI_WINDOW_CLASS_NAME; RegisterClass(&wndclass); accelerators = CreateAcceleratorTable(acceleratorlist, sizeof(acceleratorlist)/sizeof(acceleratorlist[0])); mainwindow = CreateWindow(MDI_WINDOW_CLASS_NAME, "FTE QuakeC compiler", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, NULL, NULL, ghInstance, NULL); if (mdibox) { SetWindowText(mainwindow, "FTE QuakeC Development Suite"); } if (!mainwindow) { MessageBox(NULL, "Failed to create main window", "Error", 0); return 0; } /* outputbox=CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | ES_READONLY | WS_VISIBLE | WS_VSCROLL | ES_LEFT | ES_WANTRETURN | ES_MULTILINE | ES_AUTOVSCROLL, 0, 0, 0, 0, mainwindow, NULL, ghInstance, NULL); */ if (!mdibox) outputbox = CreateAnEditControl(mainwindow, NULL); for (i = 0; i < NUMBUTTONS; i++) { if (!buttons[i].text) buttons[i].hwnd = NULL; else buttons[i].hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, "BUTTON", buttons[i].text, WS_CHILD | WS_VISIBLE, 0, 0, 5, 5, mainwindow, (HMENU)(LONG_PTR)(i+20), ghInstance, NULL); } ShowWindow(mainwindow, SW_SHOWDEFAULT); resetprogssrc = true; while(mainwindow || editors) { MSG msg; if (resetprogssrc) { //this here, with the compiler below, means that we don't run recursivly. if (projecttree) TreeView_DeleteAllItems(projecttree); //if progssrcname is a path, then change working directory now. //this shouldn't affect that much, but should ensure well-defined behaviour. { char *s, *s2; strcpy(progssrcdir, progssrcname); for(s = NULL, s2 = progssrcdir; s2;) { char *bs = strchr(s2, '\\'); char *sl = strchr(s2, '/'); if (bs) s2 = bs; else if (sl) s2 = sl; else break; s = s2++; } if (s) { *s = '\0'; strcpy(progssrcname, s+1); SetCurrentDirectory(progssrcdir); } *progssrcdir = '\0'; } //reset project/directory options GUI_SetDefaultOpts(); GUI_ParseCommandLine(lpCmdLine, true); GUI_RevealOptions(); //if the project is a .dat or .zip then decompile it now (so we can access the 'source') { char *ext = strrchr(progssrcname, '.'); if (ext && (!QC_strcasecmp(ext, ".dat") || !QC_strcasecmp(ext, ".pak") || !QC_strcasecmp(ext, ".zip") || !QC_strcasecmp(ext, ".pk3"))) { FILE *f = fopen(progssrcname, "rb"); if (f) { char *buf; size_t size; fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); buf = malloc(size); fread(buf, 1, size, f); fclose(f); QCC_CloseAllVFiles(); if (!QC_EnumerateFilesFromBlob(buf, size, QCC_EnumerateFilesResult) && !QC_strcasecmp(ext, ".dat")) { //its a .dat and contains no .src files GUI_DoDecompile(buf, size); } else if (!QCC_FindVFile("progs.src")) { vfile_t *f; char *archivename = progssrcname; while(strchr(archivename, '\\')) archivename = strchr(archivename, '\\')+1; AddSourceFile(NULL, archivename); for (f = qcc_vfiles; f; f = f->next) AddSourceFile(archivename, f->filename); f = QCC_FindVFile("progs.dat"); if (f) GUI_DoDecompile(f->file, f->size); else resetprogssrc = false; } free(buf); strcpy(progssrcname, "progs.src"); } else strcpy(progssrcname, "progs.src"); for (i = 0; ; i++) { if (!strcmp("embedsrc", compiler_flag[i].abbrev)) { compiler_flag[i].flags |= FLAG_SETINGUI; break; } } } } if (fl_compileonstart) { if (resetprogssrc) { CreateOutputWindow(false); RunCompiler(lpCmdLine, false); } } else { if (!mdibox) { GUIprintf("Welcome to FTE QCC\n"); GUIprintf("Source file: "); GUIprintf(progssrcname); GUIprintf("\n"); RunCompiler("-?", false); } } if (resetprogssrc) UpdateFileList(); resetprogssrc = false; } EditorsRun(); while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) { if (!GetMessage (&msg, NULL, 0, 0)) break; if (!mdibox || !TranslateMDISysAccel(mdibox, &msg)) DoTranslateMessage(&msg); } if (mainwindow) { if (buttons[ID_COMPILE].washit) { CreateOutputWindow(true); RunCompiler(parameters, false); buttons[ID_COMPILE].washit = false; } #ifdef EMBEDDEBUG if (buttons[ID_RUN].washit) { buttons[ID_RUN].washit = false; RunEngine(); } #endif if (buttons[ID_OPTIONS].washit) { buttons[ID_OPTIONS].washit = false; OptionsDialog(); } } if (*finddef) { GoToDefinition(finddef); *finddef = '\0'; } if (*greptext) { GrepAllFiles(greptext); *greptext = '\0'; } Sleep(10); } return 0; } fteqcc-20251105/./progslib.h0000644000200200001440000005523215233070110014725 0ustar twolifeusers #ifndef PROGSLIB_H #define PROGSLIB_H #include "progtype.h" #include #ifdef _MSC_VER #define VARGS __cdecl #endif #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) #if defined(_WIN32) #include #ifdef __MINGW_PRINTF_FORMAT #define LIKEPRINTF(x) __attribute__((format(__MINGW_PRINTF_FORMAT,x,x+1))) #else #define LIKEPRINTF(x) __attribute__((format(ms_printf,x,x+1))) #endif #else #define LIKEPRINTF(x) __attribute__((format(printf,x,x+1))) #endif #endif #ifndef LIKEPRINTF #define LIKEPRINTF(x) #endif #ifndef VARGS #define VARGS #endif #if __STDC_VERSION__ >= 202311L // c23 #define FALLTHROUGH [[fallthrough]]; #elif defined(__GNUC__) && __GNUC__ >= 7 #define FALLTHROUGH __attribute__((fallthrough)); #elif defined(__clang__) && __clang_major__ >= 7 #define FALLTHROUGH __attribute__((fallthrough)); #else #define FALLTHROUGH #endif #if defined(_M_IX86) || defined(__i386__) //supported arch #if defined(__GNUC__) || defined(_MSC_VER) //supported compilers (yay for inline asm) //#define QCJIT #endif #endif #define QCBUILTIN ASMCALL #ifdef _WIN32 #define PDECL __cdecl #else #define PDECL #endif #ifdef QCJIT #define ASMCALL VARGS #else #define ASMCALL PDECL #endif #define QCGC struct edict_s; struct entvars_s; struct globalvars_s; struct qcthread_s; typedef struct pubprogfuncs_s pubprogfuncs_t; typedef void (ASMCALL *builtin_t) (pubprogfuncs_t *prinst, struct globalvars_s *gvars); enum ereftype_e { ER_ENTITY, ER_FREE, ER_OBJECT //custom sized, no vm/engine fields. }; #define ED_ISFREE(e) ((e)->ereftype != ER_ENTITY) //used by progs engine. All nulls is reset. typedef struct { const char *varname; struct fdef_s *ofs32; int spare[2]; } evalc_t; #define sizeofevalc sizeof(evalc_t) typedef enum { //vanilla types ev_void, ev_string, //offset into the string table - but if the high bit is set then its probably some special thing. ev_float, //can hold up to 24 bits... sucks, but this is our basic numeric type. ev_vector, //3 floats. ev_entity, //index into the edicts array (vanilla used byte offsets from world). ev_field, //index into the per-entity field table. ev_function,//all functions are called via reference. ev_pointer, //exists in vanilla - *(&ent.fld) opcodes are valid there - how else would you store to a field? //extended types ev_integer, //our first extended type... probably won't help performance much but at least it doesn't have the imprecision issue of floats. ev_uint, //mostly just reuses int opcodes. ev_int64, //large int type, because we can. might be useful for system handles perhaps? dunno, probably not that useful. ev_uint64, //mostly just reuses int64 opcodes. ev_double, //useful for timers, for the extra precision. //qc-only types ev_variant, //used primarily for builtin args, or for type punning casts without using pointers. should never be used for a global. ev_struct, //big complex type ev_union, //not really sure why this is separate from struct ev_accessor,//some weird type to provide class-like functions over a basic type. ev_enum, //just a numeric type ev_typedef, //so typedefs can refer to their original type (primarily for structs). ev_boolean, //exists to optimise if(-0) workarounds. engine just sees int/float. uses parentclass ev_bitfld, //erk... structs only... converted to their parentclass on read. } etype_t; enum { DEBUG_TRACE_OFF, //debugging should be off. DEBUG_TRACE_INTO, //debug into functions DEBUG_TRACE_OVER, //switch debugging off while executing child functions (and back on afterwards) DEBUG_TRACE_OUT, //keep running until the end of the current function (trigger single-stepping again at that point) DEBUG_TRACE_ABORTERROR, //give up with an endgame. // DEBUG_TRACE_ABORTSTACK, //stop executing, without any errors. DEBUG_TRACE_NORESUME //line number or something changed, but we should still be sitting at the debugger. }; typedef struct fdef_s { unsigned int type; //if DEF_SAVEGLOBAL bit is set then the variable needs to be saved in savegames int ofs; //runtime offset. add fieldadj to get the real array index. unsigned int progsofs; //used at loading time, so maching field offsets (unions/members) are positioned at the same runtime offset. const char * name; //proper name for the field. } fdef_t; //the number of pointers to variables (as opposed to functions - those are fine) in these structures is excessive. //Many of the functions are also obsolete. struct pubprogfuncs_s { int progsversion; //PROGSTRUCT_VERSION void (PDECL *Shutdown) (pubprogfuncs_t *inst); void (PDECL *Configure) (pubprogfuncs_t *prinst, size_t addressablesize, int max_progs, pbool enableprofiling); //configure buffers and memory. Used to reset and must be called first. Flushes a running VM. progsnum_t (PDECL *LoadProgs) (pubprogfuncs_t *prinst, const char *s); //load a progs int (PDECL *InitEnts) (pubprogfuncs_t *prinst, int max_ents); //returns size of edicts for use with nextedict macro void (PDECL *ExecuteProgram) (pubprogfuncs_t *prinst, func_t fnum); //start execution struct globalvars_s *(PDECL *globals) (pubprogfuncs_t *prinst, progsnum_t num); //get the globals of a progs struct entvars_s *(PDECL *entvars) (pubprogfuncs_t *prinst, struct edict_s *ent); //return a pointer to the entvars of an ent. can be achieved via the edict_t structure instead, so obsolete. void (VARGS *RunError) (pubprogfuncs_t *prinst, const char *msg, ...) LIKEPRINTF(2); //builtins call this to say there was a problem void (PDECL *PrintEdict) (pubprogfuncs_t *prinst, struct edict_s *ed); //get a listing of all vars on an edict (sent back via 'print') struct edict_s *(PDECL *EntAlloc) (pubprogfuncs_t *prinst, pbool object, size_t extrasize); //allocate a random index. struct edict_s *(PDECL *EntAllocIndex) (pubprogfuncs_t *prinst, unsigned int idx, pbool object, size_t extrasize); //allocate a specific index. void (PDECL *EntFree) (pubprogfuncs_t *prinst, struct edict_s *ed, pbool instant); struct edict_s *(PDECL *EdictNum) (pubprogfuncs_t *prinst, unsigned int n); //get the nth edict unsigned int (PDECL *NumForEdict) (pubprogfuncs_t *prinst, struct edict_s *e); //so you can find out what that 'n' will be char *(PDECL *VarString) (pubprogfuncs_t *prinst, int first); //returns a string made up of multiple arguments struct progstate_s **progstate; //internal to the library. int numprogs; func_t (PDECL *FindFunction) (pubprogfuncs_t *prinst, const char *funcname, progsnum_t num); int (PDECL *StartCompile) (pubprogfuncs_t *prinst, int argv, const char **argc); //1 if can compile, 0 if failed to compile int (PDECL *ContinueCompile) (pubprogfuncs_t *prinst); //2 if finished, 1 if more to go, 0 if failed char *(PDECL *filefromprogs) (pubprogfuncs_t *prinst, progsnum_t prnum, const char *fname, size_t *size, char *buffer); //reveals encoded/added files from already loaded progs char *(PDECL *filefromnewprogs) (pubprogfuncs_t *prinst, const char *prname, const char *fname, size_t *size, char *buffer); //reveals encoded/added files from a progs on the disk somewhere void (PDECL *ED_Print) (pubprogfuncs_t *prinst, struct edict_s *ed); char *(PDECL *save_ents) (pubprogfuncs_t *prinst, char *buf, size_t *size, size_t maxsize, int mode); //dump the entire progs info into one big self allocated string int (PDECL *load_ents) (pubprogfuncs_t *prinst, const char *s, void *ctx, void (PDECL *memoryreset) (pubprogfuncs_t *progfuncs, void *ctx), void (PDECL *entspawned) (pubprogfuncs_t *progfuncs, struct edict_s *ed, void *ctx, const char *entstart, const char *entend), pbool(PDECL *extendedterm)(pubprogfuncs_t *progfuncs, void *ctx, const char **extline) ); //restore the entire progs state (or just add some more ents) (returns edicts ize) char *(PDECL *saveent) (pubprogfuncs_t *prinst, char *buf, size_t *size, size_t maxsize, struct edict_s *ed); //will save just one entities vars struct edict_s *(PDECL *restoreent) (pubprogfuncs_t *prinst, const char *buf, size_t *size, struct edict_s *ed); //will restore the entity that had it's values saved (can use NULL for ed) union eval_s *(PDECL *FindGlobal) (pubprogfuncs_t *prinst, const char *name, progsnum_t num, etype_t *type); //find a pointer to the globals value union eval_s *(PDECL *GetEdictFieldValue)(pubprogfuncs_t *prinst, struct edict_s *ent, const char *name, etype_t type, evalc_t *s); //get an entityvar (cache it) and return the possible values struct edict_s *(PDECL *ProgsToEdict) (pubprogfuncs_t *prinst, int progs); //edicts are stored as ints and need to be adjusted int (PDECL *EdictToProgs) (pubprogfuncs_t *prinst, struct edict_s *ed); //edicts are stored as ints and need to be adjusted char *(PDECL *EvaluateDebugString) (pubprogfuncs_t *prinst, const char *key); //evaluate a string and return it's value (according to current progs) (expands edict vars) int debug_trace; //start calling the editor for each line executed void (PDECL *StackTrace) (pubprogfuncs_t *prinst, int showlocals); int (PDECL *ToggleBreak) (pubprogfuncs_t *prinst, const char *filename, int linenum, int mode); struct progexterns_s *parms; //these are the initial parms, they may be changed pbool (PDECL *Decompile) (pubprogfuncs_t *prinst, const char *fname); int callargc; //number of args of built-in call int callprogs; //which progs it was called from... char *stringtable; //qc strings are all relative. add to a qc string. this is required for support of frikqcc progs that strip string immediates. unsigned int stringtablesize; unsigned int stringtablemaxsize; int fieldadjust; //FrikQCC style arrays can cause problems due to field remapping. This causes us to leave gaps but offsets identical. except for system fields, qc-addressable variables use their old offsets, this is the bias so that the offset pokes the correct memory. unsigned int activefieldslots; //f+=fieldadjust; invalidfield = (f<0)||(f+fldsize>=activefieldslots); note that this does NOT apply to 'object' entities which are variable sized, use ed->fieldsize for those. struct qcthread_s *(PDECL *Fork) (pubprogfuncs_t *prinst); //returns a pointer to a thread which can be resumed via RunThread. void (PDECL *RunThread) (pubprogfuncs_t *prinst, struct qcthread_s *thread); void (PDECL *AbortStack) (pubprogfuncs_t *prinst); //annigilates the current stack, positioning on a return statement. It is expected that this is only used via a builtin! pbool (PDECL *GetBuiltinCallInfo) (pubprogfuncs_t *prinst, int *builtinnum, char *function, size_t sizeoffunction); //call to query the qc's name+index for the builtin pbool (PDECL *FindBuiltins) (pubprogfuncs_t *progfuncs, progsnum_t prnum, int binum, pbool (PDECL *found) (pubprogfuncs_t *progfuncs, const char *name, void *ctx), void *ctx); //calls the callback for each function reference that's mapped to the specified builtin number. int (PDECL *RegisterFieldVar) (pubprogfuncs_t *prinst, unsigned int type, const char *name, signed long requestedpos, signed long originalofs); char *(PDECL *AddString) (pubprogfuncs_t *prinst, const char *val, int minlength, pbool demarkup); //dump a string into the progs memory (for setting globals and whatnot) void *(PDECL *Tempmem) (pubprogfuncs_t *prinst, int ammount, char *whatfor); //grab some mem for as long as the progs stays loaded void *(PDECL *AddressableAlloc) (pubprogfuncs_t *progfuncs, unsigned int ammount); /*returns memory within the qc block, use stringtoprogs to get a usable qc pointer/string*/ void *(PDECL *AddressableRealloc) (pubprogfuncs_t *progfuncs, void *oldptr, unsigned int newammount); /*returns memory within the qc block, use stringtoprogs to get a usable qc pointer/string*/ void (PDECL *AddressableFree) (pubprogfuncs_t *progfuncs, void *mem); /*frees a block of addressable memory*/ string_t (PDECL *TempString) (pubprogfuncs_t *prinst, const char *str); string_t (PDECL *AllocTempString) (pubprogfuncs_t *prinst, char **str, unsigned int len); string_t (PDECL *StringToProgs) (pubprogfuncs_t *prinst, const char *str); //commonly makes a semi-permanent mapping from some table to the string value. mapping can be removed via RemoveProgsString const char *(ASMCALL *StringToNative) (pubprogfuncs_t *prinst, string_t str); int (PDECL *QueryField) (pubprogfuncs_t *prinst, unsigned int fieldoffset, etype_t *type, char const**name, evalc_t *fieldcache); //find info on a field definition at an offset void (PDECL *EntClear) (pubprogfuncs_t *progfuncs, struct edict_s *e); void (PDECL *FindPrefixGlobals) (pubprogfuncs_t *progfuncs, int prnum, char *prefix, void (PDECL *found) (pubprogfuncs_t *progfuncs, char *name, union eval_s *val, etype_t type, void *ctx), void *ctx); //calls the callback for each named global found pbool (PDECL *SetWatchPoint) (pubprogfuncs_t *prinst, const char *desc, const char *location); void (PDECL *AddSharedVar) (pubprogfuncs_t *progfuncs, int start, int size); void (PDECL *AddSharedFieldVar) (pubprogfuncs_t *progfuncs, int num, char *relstringtable); char *(PDECL *RemoveProgsString) (pubprogfuncs_t *progfuncs, string_t str); pbool (PDECL *GetFunctionInfo) (pubprogfuncs_t *progfuncs, func_t func, int *argcount, unsigned char **argsizes, int *builtinnum, char *funcname, size_t funcnamesize); //queries the interesting info from a function def void (PDECL *GenerateStatementString) (pubprogfuncs_t *progfuncs, int statementnum, char *out, int outlen); //disassembles a specific statement. for debugging reports. fdef_t *(PDECL *FieldInfo) (pubprogfuncs_t *progfuncs, unsigned int *count); char *(PDECL *UglyValueString) (pubprogfuncs_t *progfuncs, etype_t type, union eval_s *val); pbool (PDECL *ParseEval) (pubprogfuncs_t *progfuncs, union eval_s *eval, int type, const char *s); void (PDECL *SetStringField) (pubprogfuncs_t *progfuncs, struct edict_s *ed, string_t *fld, const char *str, pbool str_is_static); //if ed is null, fld points to a global. if str_is_static, then s doesn't need its own memory allocated. pbool (PDECL *DumpProfile) (pubprogfuncs_t *progfuncs, pbool resetprofiles); unsigned int edicttable_length; struct edict_s **edicttable; //stuff not used by the qclib at all, but provided for lazy user storage. struct { char *tempstringbase; //for engine's use. Store your base tempstring pointer here. int tempstringnum; //for engine's use. } user; }; typedef struct progexterns_s { int progsversion; //PROGSTRUCT_VERSION void *(PDECL *ReadFile) (const char *fname, unsigned char *(PDECL *buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *out_size, pbool issourcefile); int (PDECL *FileSize) (const char *fname); //-1 if file does not exist pbool (PDECL *WriteFile) (const char *name, void *data, int len); int (VARGS *Printf) (const char *, ...) LIKEPRINTF(1); int (VARGS *DPrintf) (const char *, ...) LIKEPRINTF(1); void (VARGS *Sys_Error) (const char *, ...) LIKEPRINTF(1); void (VARGS *Abort) (const char *, ...) LIKEPRINTF(1); pbool (PDECL *CheckHeaderCrc) (pubprogfuncs_t *inst, progsnum_t idx, int crc, const char *filename); void (PDECL *entspawn) (struct edict_s *ent, int loading); //ent has been spawned, but may not have all the extra variables (that may need to be set) set pbool (PDECL *entcanfree) (struct edict_s *ent); //return true to stop ent from being freed void (ASMCALL *stateop) (pubprogfuncs_t *prinst, float var, func_t func); //what to do on qc's state opcode. void (ASMCALL *cstateop) (pubprogfuncs_t *prinst, float vara, float varb, func_t currentfunc); //a hexen2 opcode. void (ASMCALL *cwstateop) (pubprogfuncs_t *prinst, float vara, float varb, func_t currentfunc); //a hexen2 opcode. void (ASMCALL *thinktimeop) (pubprogfuncs_t *prinst, struct edict_s *ent, float varb); //a hexen2 opcode. //used when loading a game int (PDECL *MapNamedBuiltin) (pubprogfuncs_t *prinst, int headercrc, const char *builtinname); //return 0 for not found. void (PDECL *loadcompleate) (int edictsize); //notification to reset any pointers. pbool (PDECL *badfield) (pubprogfuncs_t *prinst, struct edict_s *ent, const char *keyname, const char *value); //called for any fields that are not registered void *(VARGS *memalloc) (int size); //small string allocation malloced and freed randomly by the executor. (use malloc if you want) void (VARGS *memfree) (void * mem); int (PDECL *useeditor) (pubprogfuncs_t *prinst, const char *filename, int *line, int *statement, int funcstart, char *reason, pbool fatal); //called on syntax errors or step-by-step debugging. line and statement(if line was set to 0) can be used to change the next line. return value is the new debug state to use/step. void (PDECL *addressablerelocated) (pubprogfuncs_t *progfuncs, char *oldb, char *newb, int oldlen); //called when the progs memory was resized. you must fix up all pointers to globals, strings, fields, addressable blocks. builtin_t *globalbuiltins; //these are available to all progs int numglobalbuiltins; enum {PR_NOCOMPILE, PR_COMPILENEXIST, PR_COMPILEEXISTANDCHANGED, PR_COMPILECHANGED, PR_COMPILEALWAYS, PR_COMPILEIGNORE} autocompile; double *gametime; //used to prevent the vm from reusing an entity faster than 2 secs. pbool usethreadedgc; struct edict_s **edicts; //pointer to the engine's reference to world. unsigned int *num_edicts; //pointer to the engine's edict count. int edictsize; //size of edict_t void *user; /*contains the owner's world reference in FTE*/ } progparms_t, progexterns_t; #if defined(QCLIBDLL_EXPORTS) #ifdef _WIN32 __declspec(dllexport) #else __attribute__((visibility("default"))) #endif #endif pubprogfuncs_t * PDECL InitProgs(progparms_t *ext); typedef union eval_s { //FIXME: we should not be using a leading underscore here. that is reserved for libc. string_t string; pvec_t _float; pvec_t _vector[3]; func_t function; //module=0xff000000, func=0x00ffffff pint_t _int; puint_t _uint; pint64_t i64; puint64_t u64; pdouble_t _double; pint_t edict; pvec_t prog; //so it can easily be changed } eval_t; #define PR_CURRENT -1 #define PR_ANY -2 //not always valid. Use for finding funcs #define PR_ANYBACK -3 #define PROGSTRUCT_VERSION 4 #ifndef DLL_PROG #define PR_Configure(pf, memsize, max_progs, profiling) (*pf->Configure) (pf, memsize, max_progs, profiling) #define PR_LoadProgs(pf, s) (*pf->LoadProgs) (pf, s) #define PR_InitEnts(pf, maxents) (*pf->InitEnts) (pf, maxents) #define PR_ExecuteProgram(pf, fnum) (*pf->ExecuteProgram) (pf, fnum) #define PR_globals(pf, num) (*pf->globals) (pf, num) #define PR_entvars(pf, ent) (*pf->entvars) (pf, ent) #define PR_RegisterFieldVar(pf,type,name,reqofs,qcofs) (*pf->RegisterFieldVar) (pf,type,name,reqofs,qcofs) #define ED_Alloc(pf,isobj,extsize) (*pf->EntAlloc) (pf, isobj, extsize) #define ED_Free(pf, ed) (*pf->EntFree) (pf, ed, false) #define ED_Clear(pf, ed) (*pf->EntClear) (pf, ed) #define PR_LoadEnts(pf, s, ctx, memreset, entcb, extcb) (*pf->load_ents) (pf, s, ctx, memreset, entcb, extcb) #define PR_SaveEnts(pf, buf, size, maxsize, mode) (*pf->save_ents) (pf, buf, size, maxsize, mode) #if 0//def _DEBUG #define EDICT_NUM(pf, num) (*pf->EDICT_NUM) (pf, num) #else #define EDICT_NUM_PB(pf, num) (pf->edicttable[num]) #define EDICT_NUM_UB(pf, num) EDICT_NUM_PB(pf,(((unsigned int)(num))>=pf->edicttable_length)?0:num) #endif #define NUM_FOR_EDICT(pf, e) (*pf->NumForEdict) (pf, (struct edict_s*)(e)) #define SetGlobalEdict(pf, ed, ofs) (*pf->SetGlobalEdict) (pf, ed, ofs) #define PR_VarString(pf,first) (*pf->VarString) (pf,first) #define PR_StartCompile(pf,argc,argv) (*pf->StartCompile) (pf,argc,argv) #define PR_ContinueCompile(pf) (*pf->ContinueCompile) (pf) #define PR_StackTrace(pf,locals) (*pf->StackTrace) (pf,locals) #define PR_AbortStack(pf) (*pf->AbortStack) (pf) #define PR_RunError(pf,str) (*pf->RunError) (pf,str) #define PR_PrintEdict(pf,ed) (*pf->PrintEdict) (pf, ed) #define PR_FindFunction(pf, name, num) (*pf->FindFunction) (pf, name, num) #define PR_FindGlobal(pf, name, progs, type) (*pf->FindGlobal) (pf, name, progs, type) #define PR_AddString(pf, ed, len, demarkup) (*pf->AddString) (pf, ed, len, demarkup) #define PR_Alloc(pf,size,whatfor) (*pf->Tempmem) (pf, size, whatfor) #define PR_AddressableAlloc(pf,size) (*pf->AddressableAlloc) (pf, size) #define PR_AddressableFree(pf,mem) (*pf->AddressableFree) (pf, mem) #define PROG_TO_EDICTINDEX(pf, ed) ed #define PROG_TO_EDICT(pf, ed) (*pf->ProgsToEdict) (pf, ed) #define EDICT_TO_PROG(pf, ed) (*pf->EdictToProgs) (pf, (struct edict_s*)ed) #define PR_GetString(pf,s) (*pf->StringToNative) (pf, s) #define PR_GetStringOfs(pf,o) (*pf->StringToNative) (pf, G_INT(o)) #define PR_SetString(pf, s) (*pf->StringToProgs) (pf, s) #define NEXT_EDICT(pf,o) EDICT_NUM(pf, NUM_FOR_EDICT(pf, o)+1) #define RETURN_EDICT(pf, e) (((pint_t *)pr_globals)[OFS_RETURN] = EDICT_TO_PROG(pf, e)) //builtin funcs (which operate on globals) //To use these outside of builtins, you will likly have to use the 'globals' method. #define G_FLOAT(o) (((pvec_t *)pr_globals)[o]) #define G_FLOAT2(o) (((pvec_t *)pr_globals)[OFS_PARM0 + o*3]) #define G_DOUBLE(o) (*(pdouble_t *)(((pvec_t *)pr_globals+(o)))) #define G_INT(o) (((pint_t *)pr_globals)[o]) #define G_UINT(o) (((puint_t *)pr_globals)[o]) #define G_INT64(o) (*(pint64_t *)((pint_t *)pr_globals+(o))) #define G_UINT64(o) (*(puint64_t *)((puint_t *)pr_globals+(o))) #define G_EDICT(pf, o) PROG_TO_EDICT(pf, G_INT(o)) //((edict_t *)((char *) sv.edicts+ *(int *)&((float *)pr_globals)[o])) #define G_EDICTNUM(pf, o) NUM_FOR_EDICT(pf, G_EDICT(pf, o)) #define G_VECTOR(o) (&((pvec_t *)pr_globals)[o]) #define G_FUNCTION(o) (*(func_t *)&((pvec_t *)pr_globals)[o]) /* #define PR_GetString(p,s) (s?s + p->stringtable:"") #define PR_GetStringOfs(p,o) (G_INT(o)?G_INT(o) + p->stringtable:"") #define PR_SetStringOfs(p,o,s) (G_INT(o) = s - p->stringtable) */ //#define PR_SetString(p, s) ((s&&*s)?(s - p->stringtable):0) /**/ #ifdef QCGC #define PR_NewString(p, s) (p)->TempString(p, s) #else #define PR_NewString(p, s) PR_SetString(p, PR_AddString(p, s, 0, false)) #endif #define ev_prog ev_integer #define E_STRING(o) (char *)(((pint_t *)((char *)ed) + progparms.edictsize)[o]) //#define pr_global_struct pr_globals #endif #define OFS_NULL 0 #define OFS_RETURN 1 #define OFS_PARM0 4 // leave 3 ofs for each parm to hold vectors #define OFS_PARM1 7 #define OFS_PARM2 10 #define OFS_PARM3 13 #define OFS_PARM4 16 #define OFS_PARM5 19 #define OFS_PARM6 22 #define OFS_PARM7 25 #define RESERVED_OFS 28 #undef edict_t #undef globalvars_t #endif //PROGSLIB_H fteqcc-20251105/./comprout.c0000644000200200001440000001041615233070110014742 0ustar twolifeusers//compile routines #include "qcc.h" #undef progfuncs char errorfile[128]; int errorline; progfuncs_t *qccprogfuncs; #include extern int qcc_compileactive; jmp_buf qcccompileerror; char qcc_gamedir[128]; void QCC_PR_ResetErrorScope(void); #if defined(MINIMAL) || defined(OMIT_QCC) #else static struct qcchunk_s { struct qcchunk_s *older; char *data; char *end; } *qcc_hunk; static void qccHunkExtend(size_t minsize) { struct qcchunk_s *n; size_t sz; minsize += sizeof(struct qcchunk_s)+64; sz = max(minsize, 256*1024*1024); for (;;) { if (sz < minsize) QCC_Error(ERR_INTERNAL, "Compile hunk was filled"); n = malloc(sz); if (n) break; sz /= 2; } n->data = (char*)(n+1); n->end = ((char*)n)+sz; n->older = qcc_hunk; qcc_hunk = n; } void *qccHunkAlloc(size_t mem) { char *ret; mem = (mem + 7)&~7; if (qcc_hunk->data+mem > qcc_hunk->end) qccHunkExtend(mem); ret = qcc_hunk->data; qcc_hunk->data += mem; memset(ret, 0, mem); return ret; } void qccClearHunk(void) { struct qcchunk_s *t; QCC_PurgeTemps(); while (qcc_hunk) { t = qcc_hunk; qcc_hunk = t->older; free(t); } } int qccpersisthunk; void PostCompile(void) { if (!qccpersisthunk) qccClearHunk(); QCC_PR_CloseProcessor(); QCC_Cleanup(); if (asmfile) { fclose(asmfile); asmfile = NULL; } } pbool PreCompile(void) { QCC_PR_ResetErrorScope(); qccClearHunk(); strcpy(qcc_gamedir, ""); qccHunkExtend(0); return true; } pbool QCC_main (int argc, const char **argv); void QCC_FinishCompile(void); int comp_nump;const char **comp_parms; //void Editor(char *fname, int line, int numparms, char **compileparms); pbool CompileParams(progfuncs_t *progfuncs, void(*cb)(void), int nump, const char **parms) { comp_nump = nump; comp_parms = parms; *errorfile = '\0'; qccprogfuncs = progfuncs; if (setjmp(qcccompileerror)) { PostCompile(); if (*errorfile) { externs->Printf("Error in %s on line %i\n", errorfile, errorline); } return false; } if (!QCC_main(nump, parms)) return false; while(qcc_compileactive) { if (cb) cb(); QCC_ContinueCompile(); } PostCompile(); return !pr_error_count; } int PDECL Comp_Begin(pubprogfuncs_t *progfuncs, int nump, const char **parms) { comp_nump = nump; comp_parms = parms; qccprogfuncs = (progfuncs_t*)progfuncs; *errorfile = '\0'; if (setjmp(qcccompileerror)) { PostCompile(); return false; } if (!QCC_main(nump, parms)) return false; return true; } int PDECL Comp_Continue(pubprogfuncs_t *progfuncs) { qccprogfuncs = (progfuncs_t *)progfuncs; if (setjmp(qcccompileerror)) { PostCompile(); return false; } if (qcc_compileactive) QCC_ContinueCompile(); else { PostCompile(); return false; } return true; } #endif pbool CompileFile(progfuncs_t *progfuncs, const char *filename) { #if defined(MINIMAL) || defined(OMIT_QCC) return false; #else char srcfile[32]; char newname[32]; static const char *p[5]; int parms; char *s, *s2; p[0] = NULL; parms = 1; strcpy(newname, filename); s = newname; if (strchr(s+1, '/')) { while(1) { s2 = strchr(s+1, '/'); if (!s2) { *s = '\0'; break; } s = s2; } p[parms] = "-src"; p[parms+1] = newname; parms+=2; strcpy(srcfile, s+1); srcfile[strlen(srcfile)-4] = '\0'; strcat(srcfile, ".src"); if (externs->FileSize(qcva("%s/%s", newname, srcfile))>0) { p[parms] = "-srcfile"; p[parms+1] = srcfile; parms+=2; } } else { p[parms] = "-srcfile"; p[parms+1] = newname; newname[strlen(newname)-4] = '\0'; strcat(newname, ".src"); parms+=2; } // p[2][strlen(p[2])-4] = '\0'; // strcat(p[2], "/"); while (!CompileParams(progfuncs, NULL, parms, p)) { return false; } return true; #endif } int QC_strncasecmp(const char *s1, const char *s2, int n) { int c1, c2; while (1) { c1 = *s1++; c2 = *s2++; if (!n--) return 0; // strings are equal until end point if (c1 != c2) { if (c1 >= 'a' && c1 <= 'z') c1 -= ('a' - 'A'); if (c2 >= 'a' && c2 <= 'z') c2 -= ('a' - 'A'); if (c1 != c2) return -1; // strings not equal } if (!c1) return 0; // strings are equal } return -1; } void editbadfile(const char *fname, int line) { if (!*errorfile) { strcpy(errorfile, fname); errorline = line; } } fteqcc-20251105/./Makefile0000644000200200001440000000576315233070110014377 0ustar twolifeusersCOMMON_OBJS=comprout.o hash.o qcc_cmdlib.o qcd_main.o QCC_OBJS=qccmain.o qcc_pr_comp.o qcc_pr_lex.o packager.o decomp.o VM_OBJS=pr_exec.o pr_edict.o pr_multi.o initlib.o qcdecomp.o GTKGUI_OBJS=qcc_gtk.o qccguistuff.o WIN32GUI_OBJS=qccgui.o qccguistuff.o packager.o TUI_OBJS=qcctui.o LIB_OBJS= CC?=gcc CFLAGS?=-Wall all: help qcc help: @echo for fteqccgui: win or nocyg @echo for commandline: qcc @echo for debug builds, add: DEBUG=1 @echo USEGUI_CFLAGS= # set to -DUSEGUI when compiling the GUI WARNING_CFLAGS=-Wno-pointer-sign BASE_CFLAGS+=$(WARNING_CFLAGS) BASE_CFLAGS+=$(USEGUI_CFLAGS) ifneq ($(DEBUG),) BASE_CFLAGS+=-ggdb else BASE_LDFLAGS+=-s endif BASE_LDFLAGS+=-lz # set to "" for debugging DO_CC?=$(CC) $(BASE_CFLAGS) -o $@ -c $< $(CFLAGS) lib: R_win_nocyg: $(QCC_OBJS) $(COMMON_OBJS) $(WIN32GUI_OBJS) $(CC) $(BASE_CFLAGS) -o fteqcc.exe -O3 $(BASE_LDFLAGS) $(QCC_OBJS) $(COMMON_OBJS) $(WIN32GUI_OBJS) -mno-cygwin -mwindows -lcomctl32 -lole32 -lshlwapi R_nocyg: $(QCC_OBJS) $(COMMON_OBJS) $(WIN32GUI_OBJS) $(CC) $(BASE_CFLAGS) -o fteqcc.exe -O3 $(BASE_LDFLAGS) $(QCC_OBJS) $(COMMON_OBJS) $(WIN32GUI_OBJS) -mno-cygwin -lcomctl32 -lole32 -lshlwapi R_win: $(QCC_OBJS) $(COMMON_OBJS) $(WIN32GUI_OBJS) $(CC) $(BASE_CFLAGS) -o fteqcc.exe -O3 $(BASE_LDFLAGS) $(QCC_OBJS) $(COMMON_OBJS) $(WIN32GUI_OBJS) -mwindows -lcomctl32 -lole32 -lshlwapi win_nocyg: $(MAKE) USEGUI_CFLAGS="-DUSEGUI -DQCCONLY" R_win_nocyg nocyg: $(MAKE) USEGUI_CFLAGS="-DUSEGUI -DQCCONLY" R_nocyg win: $(MAKE) USEGUI_CFLAGS="-DUSEGUI -DQCCONLY" R_win R_qcc: $(QCC_OBJS) $(COMMON_OBJS) $(TUI_OBJS) $(CC) $(BASE_CFLAGS) -o fteqcc.bin -O3 $(QCC_OBJS) $(TUI_OBJS) $(COMMON_OBJS) $(BASE_LDFLAGS) -lm qcc: $(MAKE) USEGUI_CFLAGS="" R_qcc qccmain.o: qccmain.c qcc.h $(DO_CC) qcc_cmdlib.o: qcc_cmdlib.c qcc.h $(DO_CC) qcc_pr_comp.o: qcc_pr_comp.c qcc.h $(DO_CC) qcc_pr_lex.o: qcc_pr_lex.c qcc.h $(DO_CC) comprout.o: comprout.c qcc.h $(DO_CC) hash.o: hash.c qcc.h $(DO_CC) qcd_main.o: qcd_main.c qcc.h $(DO_CC) qccguistuff.o: qccguistuff.c qcc.h $(DO_CC) packager.o: packager.c qcc.h $(DO_CC) %.o: %.c $(DO_CC) qcc_gtk.o: qcc_gtk.c qcc.h $(DO_CC) `pkg-config --cflags gtk+-2.0` R_gtkgui: $(QCC_OBJS) $(COMMON_OBJS) $(GTKGUI_OBJS) $(CC) $(BASE_CFLAGS) $(USEGUI_CFLAGS) -o fteqccgui.bin -O3 $(GTKGUI_OBJS) $(QCC_OBJS) $(COMMON_OBJS) `pkg-config --libs gtk+-2.0` gtkgui: $(MAKE) USEGUI_CFLAGS="-DUSEGUI -DQCCONLY" R_gtkgui clean: $(RM) fteqcc.bin fteqcc.exe $(QCC_OBJS) $(COMMON_OBJS) $(VM_OBJS) $(GTKGUI_OBJS) $(WIN32GUI_OBJS) $(TUI_OBJS) qcvm.so: $(QCC_OBJS) $(VM_OBJS) $(COMMON_OBJS) $(CC) $(BASE_CFLAGS) -o $@ -O3 $(BASE_LDFLAGS) $(QCC_OBJS) $(VM_OBJS) $(COMMON_OBJS) -shared qcvm.a: $(QCC_OBJS) $(VM_OBJS) $(COMMON_OBJS) ar r $@ $^ test.o: test.c $(DO_CC) qcvm: test.o qcvm.a $(CC) $(BASE_CFLAGS) $(CFLAGS) -o qcvm -O3 $(BASE_LDFLAGS) $^ -lm -lz -ggdb tests: qcvm @echo Running Tests... @$(foreach a,$(wildcard tests/*.src), echo TEST: $a; rm progs.dat; ./testapp.bin progs.dat -srcfile $a; echo; echo) @echo Tests run. .PHONY: tests fteqcc-20251105/./qcc.h0000644000200200001440000013150515233070110013650 0ustar twolifeusers#define COMPILER #define PROGSUSED //#define COMMONINLINES //#define inline _inline #include "cmdlib.h" #include /* #include #include #include "pr_comp.h" */ //this is for testing #define WRITEASM #ifndef MAX_QPATH #define MAX_QPATH 128 #endif #ifndef MAX_OSPATH #define MAX_OSPATH 1024 #endif #ifdef __MINGW32_VERSION #define MINGW #endif #define progfuncs qccprogfuncs extern progfuncs_t *qccprogfuncs; #if defined(_MSC_VER) && _MSC_VER < 1900 #define strtoll _strtoi64 #define strtoull _strtoui64 #ifndef PRIxPTR #define PRIxPTR "Ix" #endif #else #include #ifndef PRIxPTR #define PRIxPTR "p" #endif #endif #ifndef STRINGIFY #define STRINGIFY2(s) #s #define STRINGIFY(s) STRINGIFY2(s) #endif #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) #define FTE_DEPRECATED __attribute__((__deprecated__)) //no idea about the actual gcc version #if defined(_WIN32) #include #ifdef __MINGW_PRINTF_FORMAT #define LIKEPRINTF(x) __attribute__((format(__MINGW_PRINTF_FORMAT,x,x+1))) #else #define LIKEPRINTF(x) __attribute__((format(ms_printf,x,x+1))) #endif #else #define LIKEPRINTF(x) __attribute__((format(printf,x,x+1))) #endif #endif #ifndef LIKEPRINTF #define LIKEPRINTF(x) #endif #if __STDC_VERSION__ >= 199901L #define qc_inlinestatic static inline #else #define qc_inlinestatic static #endif void *qccHunkAlloc(size_t mem); void qccClearHunk(void); extern short (*PRBigShort) (short l); extern short (*PRLittleShort) (short l); extern int (*PRBigLong) (int l); extern int (*PRLittleLong) (int l); extern float (*PRBigFloat) (float l); extern float (*PRLittleFloat) (float l); #define MAX_ERRORS 10 #define MAX_NAME 256 // chars long extern unsigned int MAX_REGS; extern unsigned int MAX_LOCALS; extern unsigned int MAX_TEMPS; extern int MAX_STRINGS; extern int MAX_GLOBALS; extern int MAX_FIELDS; extern int MAX_STATEMENTS; extern int MAX_FUNCTIONS; #define QCC_MAX_SOUNDS 1024 //convert to int? #define QCC_MAX_TEXTURES 1024 //convert to int? #define QCC_MAX_MODELS 1024 //convert to int? #define QCC_MAX_FILES 1024 //convert to int? #define MAX_DATA_PATH 64 extern int MAX_CONSTANTS; #define MAXCONSTANTNAMELENGTH 64 #define MAXCONSTANTPARAMLENGTH 32 #define MAXCONSTANTPARAMS 32 typedef enum {QCF_STANDARD, QCF_HEXEN2, QCF_UHEXEN2, QCF_DARKPLACES, QCF_QSS, QCF_FTE, QCF_FTEDEBUG, QCF_FTEH2, QCF_KK7, QCF_QTEST} qcc_targetformat_t; extern qcc_targetformat_t qcc_targetformat; #define qcc_targetformat_ishexen2() (qcc_targetformat == QCF_HEXEN2 || qcc_targetformat == QCF_UHEXEN2 || qcc_targetformat == QCF_FTEH2) extern unsigned int qcc_targetversion; void QCC_OPCodeSetTarget(qcc_targetformat_t targfmt, unsigned int targver); pbool QCC_OPCodeSetTargetName(const char *targ); /* TODO: "stopped at 10 errors" other pointer types for models and clients? compact string heap? always initialize all variables to something safe the def->type->type arrangement is really silly. return type checking parm count type checking immediate overflow checking pass the first two parms in call->b and call->c */ /* comments -------- // comments discard text until the end of line / * * / comments discard all enclosed text (spaced out on this line because this documentation is in a regular C comment block, and typing them in normally causes a parse error) code structure -------------- A definition is: [ = ] {, [ = ] }; types ----- simple types: void, float, vector, string, or entity float width, height; string name; entity self, other; vector types: vector org; // also creates org_x, org_y, and org_z float defs A function type is specified as: simpletype ( type name {,type name} ) The names are ignored except when the function is initialized. void() think; entity() FindTarget; void(vector destination, float speed, void() callback) SUB_CalcMove; void(...) dprint; // variable argument builtin A field type is specified as: .type .vector origin; .string netname; .void() think, touch, use; names ----- Names are a maximum of 64 characters, must begin with A-Z,a-z, or _, and can continue with those characters or 0-9. There are two levels of scoping: global, and function. The parameter list of a function and any vars declared inside a function with the "local" statement are only visible within that function, immediates ---------- Float immediates must begin with 0-9 or minus sign. .5 is illegal. A parsing ambiguity is present with negative constants. "a-5" will be parsed as "a", then "-5", causing an error. Seperate the - from the digits with a space "a - 5" to get the proper behavior. 12 1.6 0.5 -100 Vector immediates are three float immediates enclosed in single quotes. '0 0 0' '20.5 -10 0.00001' String immediates are characters enclosed in double quotes. The string cannot contain explicit newlines, but the escape character \n can embed one. The \" escape can be used to include a quote in the string. "maps/jrwiz1.bsp" "sound/nin/pain.wav" "ouch!\n" Code immediates are statements enclosed in {} braces. statement: { } ; local [ = ] {, [ = ] }; return ; if ( ) [ else ]; while ( ) ; do while ( ); ( ); expression: combiations of names and these operators with standard C precedence: "&&", "||", "<=", ">=","==", "!=", "!", "*", "/", "-", "+", "=", ".", "<", ">", "&", "|" Parenthesis can be used to alter order of operation. The & and | operations perform integral bit ops on floats A built in function immediate is a number sign followed by an integer. #1 #12 compilation ----------- Source files are processed sequentially without dumping any state, so if a defs file is the first one processed, the definitions will be available to all other files. The language is strongly typed and there are no casts. Anything that is initialized is assumed to be constant, and will have immediates folded into it. If you change the value, your program will malfunction. All uninitialized globals will be saved to savegame files. Functions cannot have more than eight parameters. Error recovery during compilation is minimal. It will skip to the next global definition, so you will never see more than one error at a time in a given function. All compilation aborts after ten error messages. Names can be defined multiple times until they are defined with an initialization, allowing functions to be prototyped before their definition. void() MyFunction; // the prototype void() MyFunction = // the initialization { dprint ("we're here\n"); }; entities and fields ------------------- execution --------- Code execution is initiated by C code in quake from two main places: the timed think routines for periodic control, and the touch function when two objects impact each other. There are three global variables that are set before beginning code execution: entity world; // the server's world object, which holds all global // state for the server, like the deathmatch flags // and the body ques. entity self; // the entity the function is executing for entity other; // the other object in an impact, not used for thinks float time; // the current game time. Note that because the // entities in the world are simulated sequentially, // time is NOT strictly increasing. An impact late // in one entity's time slice may set time higher // than the think function of the next entity. // The difference is limited to 0.1 seconds. Execution is also caused by a few uncommon events, like the addition of a new client to an existing server. There is a runnaway counter that stops a program if 100000 statements are executed, assuming it is in an infinite loop. It is acceptable to change the system set global variables. This is usually done to pose as another entity by changing self and calling a function. The interpretation is fairly efficient, but it is still over an order of magnitude slower than compiled C code. All time consuming operations should be made into built in functions. A profile counter is kept for each function, and incremented for each interpreted instruction inside that function. The "profile" console command in Quake will dump out the top 10 functions, then clear all the counters. The "profile all" command will dump sorted stats for every function that has been executed. afunc ( 4, bfunc(1,2,3)); will fail because there is a shared parameter marshaling area, which will cause the 1 from bfunc to overwrite the 4 already placed in parm0. When a function is called, it copies the parms from the globals into it's privately scoped variables, so there is no collision when calling another function. total = factorial(3) + factorial(4); Will fail because the return value from functions is held in a single global area. If this really gets on your nerves, tell me and I can work around it at a slight performance and space penalty by allocating a new register for the function call and copying it out. built in functions ------------------ void(string text) dprint; Prints the string to the server console. void(entity client, string text) cprint; Prints a message to a specific client. void(string text) bprint; Broadcast prints a message to all clients on the current server. entity() spawn; Returns a totally empty entity. You can manually set everything up, or just set the origin and call one of the existing entity setup functions. entity(entity start, .string field, string match) find; Searches the server entity list beginning at start, looking for an entity that has entity.field = match. To start at the beginning of the list, pass world. World is returned when the end of the list is reached. gotchas ------- The && and || operators DO NOT EARLY OUT like C! Don't confuse single quoted vectors with double quoted strings The function declaration syntax takes a little getting used to. Don't forget the ; after the trailing brace of a function initialization. Don't forget the "local" before defining local variables. There are no ++ / -- operators, or operate/assign operators. */ #if 1 #include "hash.h" extern hashtable_t compconstantstable; extern hashtable_t globalstable, localstable, typedeftable; #endif #ifdef WRITEASM extern FILE *asmfile; extern pbool asmfilebegun; #endif //============================================================================= // offsets are always multiplied by 4 before using typedef unsigned int gofs_t; // offset in global data block typedef struct QCC_function_s QCC_function_t; #define MAX_PARMS 8 //keep this sizeof(float) typedef union QCC_eval_basic_s { QCC_string_t string; pvec_t _float; #ifdef __GNUC__ pvec_t vector[0]; //gnuc extension. I'm using it to mute clang warnings. #else pvec_t vector[1]; //should be 3, except that then eval_t would be too big. #endif func_t function; pint_t _int; puint_t _uint; // union QCC_eval_s *ptr; } QCC_eval_basic_t; //must be the maximum size possible for a single basic type. typedef union QCC_eval_s { QCC_string_t string; pvec_t _float; pdouble_t _double; pvec_t vector[3]; func_t function; pint_t _int; puint_t _uint; pint64_t i64; puint64_t u64; // union QCC_eval_s *ptr; } QCC_eval_t; struct QCC_typeparam_s { struct QCC_type_s *type; QCC_sref_t defltvalue; pbool optional:1; //argument may safely be omitted, for builtin functions. for qc functions use the defltvalue instead. pbool isvirtual:1; //const, with implicit initialisation only. valid for structs unsigned char out; //0=in,1==inout,2=out unsigned int ofs; //word offset. unsigned int bitofs; //for bitfields (and chars/shorts). unsigned int arraysize; char *paramname; }; struct accessor_s { struct accessor_s *next; struct QCC_type_s *type; struct QCC_type_s *indexertype; //null if not indexer QCC_sref_t getset_func[2]; QCC_sref_t staticval; pbool getset_isref[2]; char *fieldname; }; typedef struct QCC_type_s { etype_t type; struct QCC_type_s *parentclass; //type_entity... // function types are more complex struct QCC_type_s *aux_type; // return type or field type struct QCC_typeparam_s *params; //[num_parms] unsigned int num_parms; unsigned int size; //FIXME: make bytes, for bytes+shorts pbool typedefed:1; //name is in the typenames list. pbool vargs:1; //function has vargs pbool vargtodouble:1; //promote floats to double when passing vargs (set according to flag_assume_double, on the def's type, so it can be enabled/disabled as required to deal with builtins pbool vargcount:1; //function has special varg count param unsigned int align:7; unsigned int bits;//valid for bitfields (and structs). const char *name; const char *aname; const char *filen; unsigned int line; struct accessor_s *accessors; struct QCC_function_s *scope; //stoopid scoped typedefs... struct QCC_type_s *ptrto; //(cache) this points to a type that is a pointer back to this type. yeah, weird. struct QCC_type_s *fldto; //(cache) this points to a type that is a pointer back to this type. yeah, weird. } QCC_type_t; int typecmp(QCC_type_t *a, QCC_type_t *b); int typecmp_lax(QCC_type_t *a, QCC_type_t *b); QCC_type_t *QCC_PR_DuplicateType(QCC_type_t *in, pbool recurse); typedef struct temp_s temp_t; void QCC_PurgeTemps(void); void QCC_FinaliseTemps(void); //not written typedef struct QCC_def_s { QCC_type_t *type; char *name; char *comment; //ui info struct QCC_def_s *next; struct QCC_def_s *nextlocal; //provides a chain of local variables for the opt_locals_marshalling optimisation. gofs_t ofs; //offset of symbol relative to symbol header. struct QCC_function_s *scope; // function the var was defined in, or NULL struct QCC_def_s *deftail; // arrays and structs create multiple globaldef objects providing different types at the different parts of the single object (struct), or alternative names (vectors). this allows us to correctly set the const type based upon how its initialised. struct QCC_def_s *generatedfor; int constant; // 1 says we can use the value over and over again. 2 is used on fields, for some reason. struct QCC_def_s *reloc; //the symbol that we're a reloc for struct QCC_def_s *gaddress; //a def that holds our offset. struct QCC_def_s *symbolheader; //this is the original symbol within which the def is stored. union QCC_eval_basic_s *symboldata; //null if uninitialised. use sym->symboldata[sym->ofs] to index. unsigned int symbolsize; //total byte size of symbol int refcount; //if 0, temp can be reused. tracked on globals too in order to catch bugs that would otherwise be a little too obscure. int timescalled; //part of the opt_stripfunctions optimisation. const char *unitn; //for static globals. const char *filen; int s_filed; int s_line; int arraysize; //should really use proper flags pbool funccalled:1; //was called somewhere. pbool read:1; //variable was read pbool written:1; //variable was written pbool referenced:1; //was used somewhere in the code (even if it can still be stripped). this controls warnings only. pbool shared:1; //weird multiprogs flag thing. pbool saved:1; //def may be saved to saved games. pbool isstatic:1; //global, even if scoped. also specific to the file it was seen in. pbool subscoped_away:1; //this local is no longer linked into the locals hash table. don't do remove it twice. // pbool followptr:1; //float &foo; pbool strip:1; //info about this def should be stripped. it may still consume globals space however, and its storage can still be used, its just not visible. pbool nostrip:1; //don't strip reflection data for this symbol. pbool allowinline:1; //calls to this function will attempt to inline the specified function. requires const, supposedly. pbool used:1; //if it remains 0, it may be stripped. this is forced for functions and fields. commonly 0 on fields. pbool unused:1; //silently strip it if it wasn't referenced. pbool localscope:1; //is a local, as opposed to a static (which is only visible within its scope) pbool arraylengthprefix:1; //hexen2 style arrays have a length prefixed to them for auto bounds checks. this can only work reliably for simple non-struct arrays. pbool assumedtype:1; //#merged. the type is not reliable. pbool weak:1; //ignore any initialiser value (only permitted on functions) pbool accumulate:1; //don't finalise the function's statements. pbool nofold:1; pbool initialized:1; //true when a declaration included "= immediate". pbool isextern:1; //fteqw-specific lump entry pbool isparameter:1; //its an engine parameter (thus preinitialised). pbool addressedwarned:1; //warned about it once, back off now. pbool autoderef:1; //like C++'s int &foo; - probably local pointers to alloca memory. const char *deprecated; //reason its deprecated (or empty for no reason given) int fromstatement; //statement that it is valid from. temp_t *temp; } QCC_def_t; struct temp_s { QCC_def_t *def; unsigned char locked; unsigned int size; struct QCC_function_s *lastfunc; unsigned int lastline; unsigned int laststatement; }; extern size_t tempsused; typedef struct { enum{ REF_GLOBAL, //(global.ofs) - use vector[2] is an array ref or vector_z REF_ARRAY, //(global.ofs+wordoffset) - constant offsets should be direct references, variable offsets will generally result in function calls REF_ARRAYHEAD,//(global) - like REF_ARRAY, but otherwise convert to a pointer. check arraysize for length. REF_POINTER,//*(pointerdef+alignindex) - maths... REF_POINTERARRAY,//(pointerdef+alignindex) - head of an array. &REF_POINTER but with array size info. cannot be directly assigned to. REF_FIELD, //(entity.field) - reading is a single load, writing requires address+storep REF_STRING, //"hello"[1]=='e' - special opcodes, or str2chr builtin, or something REF_NONVIRTUAL, //(global.ofs) - identical to global except for function calls, where index can be used to provide the 'newself' for the call. REF_THISCALL, //(global.ofs) - identical to global except for function calls, where index is used as the first argument. REF_ACCESSOR //buf_create()[5] } type; QCC_sref_t base; unsigned int bitofs; //for bitfields. QCC_sref_t index; QCC_type_t *cast; //entity.float is float, not pointer. unsigned int arraysize; //for sizeof/boundchecks, not much else. struct accessor_s *accessor; //the accessor field of base that we're trying to use int postinc; //+1 or -1 pbool readonly; //for whatever reason, like base being a const } QCC_ref_t; //============================================================================ // pr_loc.h -- program local defs //============================================================================= extern char QCC_copyright[1024]; extern char QCC_Packname[5][128]; extern int QCC_packid; extern const unsigned int type_size[]; //extern QCC_def_t *def_for_type[9]; extern QCC_type_t *type_void, *type_string, *type_float, *type_double, *type_vector, *type_entity, *type_field, *type_function, *type_floatfunction, *type_pointer, *type_floatpointer, *type_intpointer, *type_bint, *type_bfloat, *type_sint8, *type_uint8, *type_sint16, *type_uint16, *type_integer, *type_uint, *type_int64, *type_uint64, *type_invalid, *type_variant, *type_floatfield; extern char *basictypenames[]; struct QCC_function_s { int builtin; // the builtin number. >= 0 int code; // first statement. if -1, is a builtin. dfunction_t *merged; // this function was merged. this is the index to use to ensure that the parms are sized correctly.. string_t s_filed; // source file with definition const char *filen; // full scope, printed for debugging. const char *unitn; //scope for static variables. int line; int line_end; char *name; //internal name of function struct QCC_function_s *parentscope; //for nested functions struct QCC_type_s *type; //same as the def's type struct QCC_def_s *def; struct QCC_def_s *firstlocal; QCC_sref_t returndef; //default return value pbool privatelocals; //false means locals may overlap with other functions, true is needed for compat if stuff is uninitialised. // unsigned int parm_ofs[MAX_PARMS]; // always contiguous, right? QCC_statement_t *statements; //if set, then this function isn't finialised yet. size_t numstatements; }; // // output generated by prog parsing // typedef struct { char *memory; int max_memory; int current_memory; QCC_type_t *types; QCC_def_t def_head; // unused head of linked list QCC_def_t *def_tail; // add new defs after this and move it QCC_def_t local_head; // chain of variables which need to be pushed and stuff (head unused). QCC_def_t *local_tail; // add new defs after this and move it unsigned int size_fields; } QCC_pr_info_t; extern QCC_pr_info_t pr; typedef struct { char name[MAXCONSTANTNAMELENGTH]; char *value; char params[MAXCONSTANTPARAMS][MAXCONSTANTPARAMLENGTH]; int numparams; int inside:10; //cuts off at some point pbool used:1; pbool evil:1; pbool varg:1; const char *fromfile; int fromline; int namelen; } CompilerConstant_t; char *QCC_PR_GetDefinesList(void); //============================================================================ extern pbool pr_dumpasm; extern pbool preprocessonly; //extern QCC_def_t **pr_global_defs; // to find def for a global variable typedef enum { tt_eof, // end of file reached tt_name, // an alphanumeric name token tt_punct, // code punctuation tt_immediate, // string, float, vector } token_type_t; extern char *pr_token_precomment; extern char pr_token[8192]; extern token_type_t pr_token_type; extern int pr_token_line; extern int pr_token_line_last; extern QCC_type_t *pr_immediate_type; extern QCC_eval_t pr_immediate; extern int verbose; #define VERBOSE_WARNINGSONLY -1 #define VERBOSE_PROGRESS 0 #define VERBOSE_STANDARD 1 #define VERBOSE_DEBUG 2 #define VERBOSE_DEBUGSTATEMENTS 3 //figuring out the files can be expensive. extern pbool keyword_asm; extern pbool keyword_break; extern pbool keyword_case; extern pbool keyword_class; extern pbool keyword_accessor; extern pbool keyword_const; extern pbool keyword_inout; extern pbool keyword_optional; extern pbool keyword_continue; extern pbool keyword_default; extern pbool keyword_do; extern pbool keyword_entity; extern pbool keyword_float; extern pbool keyword_double; extern pbool keyword_for; extern pbool keyword_goto; extern pbool keyword_char; extern pbool keyword_byte; extern pbool keyword_short; extern pbool keyword_int; extern pbool keyword_integer; extern pbool keyword_long; extern pbool keyword_signed; extern pbool keyword_unsigned; extern pbool keyword_register; extern pbool keyword_volatile; extern pbool keyword_state; extern pbool keyword_string; extern pbool keyword_struct; extern pbool keyword_switch; extern pbool keyword_thinktime; extern pbool keyword_loop; extern pbool keyword_until; extern pbool keyword_var; extern pbool keyword_vector; extern pbool keyword_union; extern pbool keyword_enum; //kinda like in c, but typedef not supported. extern pbool keyword_enumflags; //like enum, but doubles instead of adds 1. extern pbool keyword_typedef; //fixme extern pbool keyword_extern; //function is external, don't error or warn if the body was not found extern pbool keyword_shared; //mark global to be copied over when progs changes (part of FTE_MULTIPROGS) extern pbool keyword_noref; //nowhere else references this, don't strip it. extern pbool keyword_nosave; //don't write the def to the output. extern pbool keyword_inline; //don't write the def to the output. extern pbool keyword_strip; //don't write the def to the output. extern pbool keyword_union; //you surly know what a union is! extern pbool keyword_wrap; extern pbool keyword_weak; extern pbool keyword_accumulate; extern pbool keyword_using; extern pbool keyword_unused; extern pbool keyword_used; extern pbool keyword_local; extern pbool keyword_static; extern pbool keyword_auto; extern pbool keyword_nonstatic; extern pbool keyword_ignore; extern pbool keywords_coexist; extern pbool output_parms; extern pbool autoprototype, autoprototyped, parseonly; extern pbool pr_subscopedlocals; extern pbool flag_nullemptystr, flag_ifstring, flag_brokenifstring, flag_iffloat, flag_ifvector, flag_vectorlogic; extern pbool flag_acc; extern pbool flag_caseinsensitive; extern pbool flag_laxcasts; extern pbool flag_hashonly; extern pbool flag_macroinstrings; extern pbool flag_fasttrackarrays; extern pbool flag_assume_integer; extern pbool flag_assume_double; extern pbool flag_msvcstyle; extern pbool flag_debugmacros; extern pbool flag_filetimes; extern pbool flag_typeexplicit; extern pbool flag_boundchecks; extern pbool flag_brokenarrays; extern pbool flag_rootconstructor; extern pbool flag_guiannotate; extern pbool flag_qccx; extern pbool flag_attributes; extern pbool flag_assumevar; extern pbool flag_dblstarexp; extern pbool flag_allowuninit; extern pbool flag_cpriority; extern pbool flag_qcfuncs; extern pbool flag_embedsrc; extern pbool flag_noreflection; extern pbool flag_nopragmafileline; extern pbool flag_utf8strings; extern pbool flag_reciprocalmaths; extern pbool flag_ILP32; extern pbool flag_undefwordsize; extern pbool flag_pointerrelocs; extern pbool opt_overlaptemps; extern pbool opt_shortenifnots; extern pbool opt_noduplicatestrings; extern pbool opt_constantarithmatic; extern pbool opt_nonvec_parms; extern pbool opt_constant_names; extern pbool opt_precache_file; extern pbool opt_filenames; extern pbool opt_assignments; extern pbool opt_unreferenced; extern pbool opt_function_names; extern pbool opt_locals; extern pbool opt_dupconstdefs; extern pbool opt_constant_names_strings; extern pbool opt_return_only; extern pbool opt_compound_jumps; //extern pbool opt_comexprremoval; extern pbool opt_stripfunctions; extern pbool opt_locals_overlapping; extern pbool opt_logicops; extern pbool opt_vectorcalls; extern pbool opt_classfields; extern int optres_shortenifnots; extern int optres_overlaptemps; extern int optres_noduplicatestrings; extern int optres_constantarithmatic; extern int optres_nonvec_parms; extern int optres_constant_names; extern int optres_precache_file; extern int optres_filenames; extern int optres_assignments; extern int optres_unreferenced; extern int optres_function_names; extern int optres_locals; extern int optres_dupconstdefs; extern int optres_constant_names_strings; extern int optres_return_only; extern int optres_compound_jumps; //extern int optres_comexprremoval; extern int optres_stripfunctions; extern int optres_locals_overlapping; extern int optres_logicops; extern int optres_inlines; pbool CompileParams(progfuncs_t *progfuncs, void(*cb)(void), int nump, const char **parms); pbool QCC_RegisterSourceFile(const char *filename); void QCC_PR_PrintStatement (QCC_statement_t *s); void QCC_PR_Lex (void); // reads the next token into pr_token and classifies its type QCC_type_t *QCC_PR_NewType (const char *name, int basictype, pbool typedefed); //note: name must be hunk/immediate QCC_type_t *QCC_PointerTypeTo(QCC_type_t *type); QCC_type_t *QCC_GenArrayType(QCC_type_t *type, unsigned int arraysize); QCC_type_t *QCC_PR_ParseType (int newtype, pbool silentfail, pbool ignoreptr); QCC_sref_t QCC_PR_ParseDefaultInitialiser(QCC_type_t *type); extern pbool type_inlinefunction; QCC_type_t *QCC_TypeForName(const char *name); QCC_type_t *QCC_PR_ParseFunctionType (int newtype, QCC_type_t *returntype); QCC_type_t *QCC_PR_ParseFunctionTypeReacc (int newtype, QCC_type_t *returntype); QCC_type_t *QCC_PR_GenFunctionType (QCC_type_t *rettype, struct QCC_typeparam_s *args, int numargs); char *QCC_PR_ParseName (void); struct QCC_typeparam_s *QCC_PR_FindStructMember(QCC_type_t *t, const char *membername, unsigned int *out_ofs, unsigned int *out_bitofs); QCC_type_t *QCC_PR_PointerType (QCC_type_t *pointsto); const char *QCC_VarAtOffset(QCC_sref_t ref); void QCC_PrioritiseOpcodes(void); pbool QCC_OPCodeValid(QCC_opcode_t *op); long QCC_PR_IntConstExpr(void); #ifndef COMMONINLINES pbool QCC_PR_CheckImmediate (const char *string); pbool QCC_PR_CheckToken (const char *string); pbool QCC_PR_PeekToken (const char *string); pbool QCC_PR_CheckName (const char *string); void QCC_PR_Expect (const char *string); pbool QCC_PR_CheckKeyword(int keywordenabled, const char *string); #endif pbool QCC_PR_CheckTokenComment(const char *string, char **comment); NORETURN void VARGS QCC_PR_ParseError (int errortype, const char *error, ...) LIKEPRINTF(2); pbool VARGS QCC_PR_ParseWarning (int warningtype, const char *error, ...) LIKEPRINTF(2); pbool VARGS QCC_PR_Warning (int type, const char *file, int line, const char *error, ...) LIKEPRINTF(4); void VARGS QCC_PR_Note (int type, const char *file, int line, const char *error, ...) LIKEPRINTF(4); void QCC_PR_ParsePrintDef (int warningtype, QCC_def_t *def); void QCC_PR_ParsePrintSRef (int warningtype, QCC_sref_t sref); NORETURN void VARGS QCC_PR_ParseErrorPrintDef (int errortype, QCC_def_t *def, const char *error, ...) LIKEPRINTF(3); NORETURN void VARGS QCC_PR_ParseErrorPrintSRef (int errortype, QCC_sref_t sref, const char *error, ...) LIKEPRINTF(3); QCC_type_t *QCC_PR_MakeThiscall(QCC_type_t *orig, QCC_type_t *thistype); int QCC_WarningForName(const char *name); char *QCC_NameForWarning(int idx); //QccMain.c must be changed if this is changed. enum { WARN_DEBUGGING, WARN_ERROR, WARN_REMOVEDWARNING, //to silence warnings about old warnings. WARN_WRITTENNOTREAD, WARN_READNOTWRITTEN, WARN_NOTREFERENCED, WARN_NOTREFERENCEDCONST, WARN_NOTREFERENCEDFIELD, WARN_CONFLICTINGRETURNS, WARN_TOOFEWPARAMS, WARN_TOOMANYPARAMS, WARN_UNEXPECTEDPUNCT, WARN_UNINITIALIZED, WARN_DENORMAL, WARN_STRINGOFFSET, //works for static/memalloc strings, but fundamentally unsafe on tempstrings/etc, and varies between engine WARN_OVERFLOW, //compile time overflow or inprecision. WARN_ASSIGNMENTTOCONSTANT, WARN_ASSIGNMENTTOCONSTANTFUNC, WARN_MISSINGRETURNVALUE, WARN_WRONGRETURNTYPE, WARN_CORRECTEDRETURNTYPE, WARN_POINTLESSSTATEMENT, WARN_MISSINGRETURN, WARN_DUPLICATEDEFINITION, WARN_UNDEFNOTDEFINED, WARN_PRECOMPILERMESSAGE, WARN_TOOMANYPARAMETERSFORFUNC, WARN_TOOMANYPARAMETERSVARARGS, WARN_NESTEDCOMMENT, WARN_STRINGTOOLONG, WARN_BADTARGET, WARN_BADPRAGMA, WARN_NOTUTF8, WARN_HANGINGSLASHR, WARN_MEMBERNOTDEFINED, WARN_NOTCONSTANT, WARN_SWITCHTYPEMISMATCH, WARN_CONFLICTINGUNIONMEMBER, WARN_KEYWORDDISABLED, WARN_ENUMFLAGS_NOTINTEGER, WARN_ENUMFLAGS_NOTBINARY, WARN_CASEINSENSITIVEFRAMEMACRO, WARN_STALEMACRO, WARN_DUPLICATEMACRO, WARN_DUPLICATELABEL, WARN_ASSIGNMENTINCONDITIONAL, WARN_MACROINSTRING, WARN_BADPARAMS, WARN_IMPLICITCONVERSION, WARN_EXTRAPRECACHE, WARN_NOTPRECACHED, WARN_NONPORTABLEFILENAME, WARN_DEADCODE, WARN_UNREACHABLECODE, WARN_NOTSTANDARDBEHAVIOUR, WARN_BOUNDS, WARN_DUPLICATEPRECOMPILER, WARN_IDENTICALPRECOMPILER, WARN_FORMATSTRING, //sprintf WARN_DEPRECACTEDSYNTAX, //triggered when syntax is used that I'm trying to kill WARN_DEPRECATEDVARIABLE, //triggered from usage of a symbol that someone tried to kill WARN_MUTEDEPRECATEDVARIABLE, //triggered from usage of a symbol that someone tried to kill (without having been muted). WARN_OCTAL_IMMEDIATE, // 0400!=400... (not found in WARN_GMQCC_SPECIFIC, //extension created by gmqcc that conflicts or isn't properly implemented. WARN_FTE_SPECIFIC, //extension that only FTEQCC will have a clue about. WARN_EXTENSION_USED, //extension that frikqcc also understands WARN_IFSTRING_USED, WARN_IFVECTOR_DISABLED, //if(vector) does if(vector_x) if ifvector is disabled WARN_SLOW_LARGERETURN, //just a perf warning. also requires working pointers but that'll give opcode errors WARN_LAXCAST, //some errors become this with a compiler flag WARN_TYPEMISMATCHREDECOPTIONAL, WARN_UNDESIRABLECONVENTION, WARN_UNSAFELOCALPOINTER, WARN_SAMENAMEASGLOBAL, WARN_CONSTANTCOMPARISON, WARN_DIVISIONBY0, WARN_UNSAFEFUNCTIONRETURNTYPE, WARN_MISSINGOPTIONAL, WARN_SYSTEMCRC, //unknown system crc WARN_SYSTEMCRC2, //legacy/dp system crc WARN_CONDITIONALTYPEMISMATCH, WARN_MISSINGMEMBERQUALIFIER,//virtual/static/nonvirtual qualifier is missing WARN_SELFNOTTHIS, //warned for because 'self' does not have the right type. we convert such references to 'this' instead, which is more usable. WARN_EVILPREPROCESSOR, //exploited by nexuiz, and generally unsafe. WARN_UNARYNOTSCOPE, //!foo & bar the ! applies to the result of &. This is unlike C. WARN_STRICTTYPEMISMATCH, //self.think = T_Damage; both are functions, but the arguments/return types/etc differ. WARN_MISUSEDAUTOCVAR, //various issues with autocvar definitions. WARN_IGNORECOMMANDLINE, WARN_POINTERASSIGNMENT, //&somefloat = 5; disabled for qccx compat sanity. WARN_COMPATIBILITYHACK, //work around old defs.qc or invalid dpextensions.qc WARN_REDECLARATIONMISMATCH, WARN_PARAMWITHNONAME, WARN_ARGUMENTCHECK, WARN_IGNOREDKEYWORD, //use of a keyword that fteqcc does not support at this time. WARN_WORDSIZEUNDEFINED, ERR_PARSEERRORS, //caused by qcc_pr_parseerror being called. //these are definatly my fault... ERR_INTERNAL, ERR_TOOCOMPLEX, ERR_BADOPCODE, ERR_TOOMANYSTATEMENTS, ERR_TOOMANYSTRINGS, ERR_BADTARGETSWITCH, ERR_TOOMANYTYPES, ERR_TOOMANYPAKFILES, ERR_PRECOMPILERCONSTANTTOOLONG, ERR_MACROTOOMANYPARMS, ERR_TOOMANYFRAMEMACROS, //limitations, some are imposed by compiler, some arn't. ERR_TOOMANYGLOBALS, ERR_TOOMANYGOTOS, ERR_TOOMANYBREAKS, ERR_TOOMANYCONTINUES, ERR_TOOMANYCASES, ERR_TOOMANYLABELS, ERR_TOOMANYOPENFILES, ERR_TOOMANYTOTALPARAMETERS, //these are probably yours, or qcc being fussy. ERR_BADEXTENSION, ERR_BADIMMEDIATETYPE, ERR_NOOUTPUT, ERR_NOTAFUNCTION, ERR_FUNCTIONWITHVARGS, ERR_BADHEX, ERR_UNKNOWNPUCTUATION, ERR_EXPECTED, ERR_NOTANAME, ERR_NAMETOOLONG, ERR_NOFUNC, ERR_COULDNTOPENFILE, ERR_NOTFUNCTIONTYPE, ERR_TOOFEWPARAMS, ERR_TOOMANYPARAMS, ERR_CONSTANTNOTDEFINED, ERR_BADFRAMEMACRO, ERR_TYPEMISMATCH, ERR_TYPEMISMATCHREDEC, ERR_TYPEMISMATCHPARM, ERR_TYPEMISMATCHARRAYSIZE, ERR_UNEXPECTEDPUNCTUATION, ERR_NOTACONSTANT, ERR_REDECLARATION, ERR_INITIALISEDLOCALFUNCTION, ERR_NOTDEFINED, ERR_ARRAYNEEDSSIZE, ERR_TOOMANYINITIALISERS, ERR_TYPEINVALIDINSTRUCT, ERR_NOSHAREDLOCALS, ERR_TYPEWITHNONAME, ERR_BADARRAYSIZE, ERR_NONAME, ERR_SHAREDINITIALISED, ERR_UNKNOWNVALUE, ERR_BADARRAYINDEXTYPE, ERR_NOVALIDOPCODES, ERR_MEMBERNOTVALID, ERR_BADPLUSPLUSOPERATOR, ERR_BADNOTTYPE, ERR_BADTYPECAST, ERR_BADMEMBER, ERR_MULTIPLEDEFAULTS, ERR_CASENOTIMMEDIATE, ERR_BADSWITCHTYPE, ERR_BADLABELNAME, ERR_NOLABEL, ERR_THINKTIMETYPEMISMATCH, ERR_STATETYPEMISMATCH, ERR_BADBUILTINIMMEDIATE, ERR_BADPARAMORDER, ERR_ILLEGALCONTINUES, ERR_ILLEGALBREAKS, ERR_ILLEGALCASES, ERR_NOTANUMBER, ERR_WRONGSUBTYPE, ERR_EOF, ERR_NOPRECOMPILERIF, ERR_NOENDIF, ERR_HASHERROR, ERR_NOTATYPE, ERR_TOOMANYPACKFILES, ERR_INVALIDVECTORIMMEDIATE, ERR_INVALIDSTRINGIMMEDIATE, ERR_BADCHARACTERCODE, ERR_BADPARMS, ERR_WERROR, WARN_MAX }; //ansi colour codes, for debugging stuff. enum { COL_NONE, //white/regular text. COL_ERROR, //to highlight errors COL_WARNING, //to highlight warnings COL_LOCATION, //to highlight file:line locations COL_NAME, //unknown symbols. COL_SYMBOL, //known symbols COL_TYPE, //known types COL_MAX }; extern const char *qcccol[COL_MAX]; #define col_none qcccol[COL_NONE] #define col_location qcccol[COL_LOCATION] #define col_error qcccol[COL_ERROR] #define col_name qcccol[COL_NAME] #define col_warning qcccol[COL_WARNING] #define col_symbol qcccol[COL_SYMBOL] #define col_type qcccol[COL_TYPE] #define FLAG_KILLSDEBUGGERS 1 #define FLAG_ASDEFAULT 2 #define FLAG_SETINGUI 4 #define FLAG_HIDDENINGUI 8 #define FLAG_MIDCOMPILE 16 //option can be changed mid-compile with the special pragma typedef struct { pbool *enabled; char *abbrev; int optimisationlevel; int flags; //1: kills debuggers. 2: applied as default. char *fullname; char *description; void *guiinfo; } optimisations_t; extern optimisations_t optimisations[]; typedef struct { pbool *enabled; int flags; //2 applied as default char *abbrev; char *fullname; char *description; void *guiinfo; } compiler_flag_t; extern compiler_flag_t compiler_flag[]; #define WA_IGNORE 0 #define WA_WARN 1 #define WA_ERROR 2 extern unsigned char qccwarningaction[WARN_MAX]; extern jmp_buf pr_parse_abort; // longjump with this on parse error extern const char *s_unitn; //used to track compilation units, for global statics. extern const char *s_filen; //name of the file we're currently compiling. extern QCC_string_t s_filed; //name of the file we're currently compiling, as seen by whoever reads the .dat extern int pr_source_line; extern char *pr_file_p; void *QCC_PR_Malloc (int size); #define OFS_NULL 0 #define OFS_RETURN 1 #define OFS_PARM0 4 // leave 3 ofs for each parm to hold vectors #define OFS_PARM1 7 #define OFS_PARM2 10 #define OFS_PARM3 13 #define OFS_PARM4 16 #define RESERVED_OFS 28 #define VMWORDSIZE 4 extern struct QCC_function_s *pr_scope; extern int pr_error_count, pr_warning_count; void QCC_PR_NewLine (pbool incomment); #define GDF_NONE 0 #define GDF_SAVED (1<<0) #define GDF_STATIC (1<<1) #define GDF_CONST (1<<2) #define GDF_STRIP (1<<3) //always stripped, regardless of optimisations. used for class member fields #define GDF_SILENT (1<<4) //used by the gui, to suppress ALL warnings associated with querying the def. #define GDF_INLINE (1<<5) //attempt to inline calls to this function #define GDF_USED (1<<6) //don't strip this, ever. #define GDF_BASICTYPE (1<<7) //don't care about #merge types not being known correctly. #define GDF_SCANLOCAL (1<<8) //don't use the locals hash table #define GDF_POSTINIT (1<<9) //field must be initialised at the end of the compile (allows arrays to be extended later) #define GDF_PARAMETER (1<<10) #define GDF_AUTODEREF (1<<11) //for hidden pointers (alloca-ed locals) #define GDF_ALIAS (1<<12) //symbol is a later alias within the root symbol rather than a core part of it - don't insert extra defs. generally paired with GDF_STRIP. QCC_def_t *QCC_PR_GetDef (QCC_type_t *type, const char *name, struct QCC_function_s *scope, pbool allocate, int arraysize, unsigned int flags); QCC_sref_t QCC_PR_GetSRef (QCC_type_t *type, const char *name, struct QCC_function_s *scope, pbool allocate, int arraysize, unsigned int flags); void QCC_FreeTemp(QCC_sref_t t); void QCC_FreeDef(QCC_def_t *def); char *QCC_PR_CheckCompConstTooltip(char *word, char *outstart, char *outend); void QCC_PR_PrintDefs (void); void QCC_PR_SkipToSemicolon (void); extern char *pr_parm_argcount_name; #define MAX_EXTRA_PARMS 128 #ifdef MAX_EXTRA_PARMS extern char pr_parm_names[MAX_PARMS+MAX_EXTRA_PARMS][MAX_NAME]; extern QCC_sref_t extra_parms[MAX_EXTRA_PARMS]; #else extern char pr_parm_names[MAX_PARMS][MAX_NAME]; #endif char *QCC_PR_ValueString (etype_t type, void *val); void QCC_PR_ClearGrabMacros (pbool newfile); void QCC_ImportProgs(const char *filename); pbool QCC_PR_CompileFile (char *string, char *filename); void QCC_PR_ResetErrorScope(void); extern pbool pr_dumpasm; extern QCC_def_t def_ret, def_parms[MAX_PARMS]; void QCC_PR_EmitArrayGetFunction(QCC_def_t *defscope, QCC_def_t *thearray, char *arrayname); void QCC_PR_EmitArraySetFunction(QCC_def_t *defscope, QCC_def_t *thearray, char *arrayname); void QCC_PR_EmitClassFromFunction(QCC_def_t *defscope, QCC_type_t *basetype); void QCC_PR_ParseDefs (const char *classname, pbool fatal); QCC_def_t *QCC_PR_DummyDef(QCC_type_t *type, const char *name, QCC_function_t *scope, int arraysize, QCC_def_t *rootsymbol, unsigned int ofs, int referable, unsigned int flags); void QCC_PR_ParseInitializerDef(QCC_def_t *def, unsigned int flags); void QCC_PR_FinaliseFunctions(void); pbool QCC_main (int argc, const char **argv); //as part of the quake engine void QCC_ContinueCompile(void); void PostCompile(void); pbool PreCompile(void); void QCC_Cleanup(void); #define FIRST_LOCAL 0//(MAX_REGS) //============================================================================= extern char pr_immediate_string[8192]; extern size_t pr_immediate_strlen; extern QCC_eval_basic_t *qcc_pr_globals; extern unsigned int numpr_globals; extern char *strings; extern int strofs; extern QCC_statement_t *statements; extern int numstatements; extern QCC_function_t *functions; extern dfunction_t *dfunctions; extern int numfunctions; extern QCC_ddef_t *qcc_globals; extern int numglobaldefs; extern QCC_def_t *activetemps; extern QCC_ddef_t *fields; extern int numfielddefs; extern QCC_type_t *qcc_typeinfo; extern int numtypeinfos; extern int maxtypeinfos; extern int ForcedCRC; extern float qcc_framerate; //number of OP_STATE ticks per second. extern pbool defaultnoref; extern pbool defaultnosave; extern pbool defaultstatic; extern int *qcc_tempofs; extern int max_temps; //extern int qcc_functioncalled; //unuse temps if this is true - don't want to reuse the same space. extern int tempsstart; extern int numtemps; extern char compilingrootfile[]; //.src file currently being compiled typedef char PATHSTRING[MAX_DATA_PATH]; typedef struct { PATHSTRING name; int block; int used; int fileline; const char *filename; } precache_t; extern precache_t *precache_sound; extern int numsounds; extern precache_t *precache_texture; extern int numtextures; extern precache_t *precache_model; extern int nummodels; extern precache_t *precache_file; extern int numfiles; typedef struct qcc_includechunk_s { struct qcc_includechunk_s *prev;//chunk it was expanded/included from const char *currentfilename; //filename it was expended from int currentlinenumber; //line it was expanded from char *currentdatapoint; CompilerConstant_t *cnst; //define we're expanding from char *datastart; //the start of the expanded data } qcc_includechunk_t; extern qcc_includechunk_t *currentchunk; pbool QCC_Include(const char *filename, pbool newunit); void QCC_PR_IncludeChunkEx (char *data, pbool duplicate, char *filename, CompilerConstant_t *cnst); int QCC_CopyString (const char *str); int QCC_CopyStringLength (const char *str, size_t length); typedef struct qcc_cachedsourcefile_s { size_t size; size_t bufsize; size_t zhdrofs; int zcrc; char *file; enum{FT_CODE, FT_DATA} type; //quakec source file or not. struct qcc_cachedsourcefile_s *next; char filename[1]; } qcc_cachedsourcefile_t; extern qcc_cachedsourcefile_t *qcc_sourcefile; int WriteSourceFiles(qcc_cachedsourcefile_t *filelist, int h, pbool sourceaswell, pbool legacyembed); struct pkgctx_s; enum pkgtype_e { PACKAGER_PAK, PACKAGER_PK3, PACKAGER_PK3_SPANNED, }; pbool Packager_CompressDir(const char *dirname, enum pkgtype_e type, void (*messagecallback)(void *userctx, const char *message, ...), void *userctx); struct pkgctx_s *Packager_Create(void (*messagecallback)(void *userctx, const char *message, ...), void *userctx); void Packager_ParseFile(struct pkgctx_s *ctx, char *scriptfilename); void Packager_ParseText(struct pkgctx_s *ctx, char *scripttext); void Packager_WriteDataset(struct pkgctx_s *ctx, char *setname); void Packager_Destroy(struct pkgctx_s *ctx); #ifdef COMMONINLINES static bool inline QCC_PR_CheckToken (char *string) { if (pr_token_type != tt_punct) return false; if (STRCMP (string, pr_token)) return false; QCC_PR_Lex (); return true; } static bool inline QCC_PR_PeekToken (char *string) { if (pr_token_type != tt_punct) return false; if (STRCMP (string, pr_token)) return false; return true; } static void inline QCC_PR_Expect (const char *string) { if (strcmp (string, pr_token)) QCC_PR_ParseError ("expected %s, found %s",string, pr_token); QCC_PR_Lex (); } #endif CompilerConstant_t *QCC_PR_DefineName(const char *name, const char *value); void editbadfile(const char *fname, int line); char *TypeName(QCC_type_t *type, char *buffer, int buffersize); void QCC_PR_AddIncludePath(const char *newinc); void QCC_PR_IncludeChunk (char *data, pbool duplicate, char *filename); void QCC_PR_CloseProcessor(void); void QCC_FindBestInclude(char *newfile, char *currentfile, pbool verbose); pbool QCC_PR_UnInclude(void); extern void *(*pHash_Get)(hashtable_t *table, const char *name); extern void *(*pHash_GetNext)(hashtable_t *table, const char *name, void *old); extern void *(*pHash_Add)(hashtable_t *table, const char *name, void *data, bucket_t *); extern void (*pHash_RemoveData)(hashtable_t *table, const char *name, void *data); //when originally running from a .dat, we load up all the functions and work from those rather than actual files. //(these get re-written into the resulting .dat) typedef struct qcc_cachedsourcefile_s vfile_t; void QCC_CloseAllVFiles(void); vfile_t *QCC_FindVFile(const char *name); vfile_t *QCC_AddVFile(const char *name, void *data, size_t size); void QCC_CatVFile(vfile_t *, const char *fmt, ...) LIKEPRINTF(2); void QCC_InsertVFile(vfile_t *, size_t pos, const char *fmt, ...) LIKEPRINTF(3); char *ReadProgsCopyright(char *buf, size_t bufsize); //void *QCC_ReadFile(const char *fname, unsigned char *(*buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *out_size, pbool issourcefile); typedef struct { char name[56]; int filepos, filelen; } packfile_t; typedef struct { char id[4]; int dirofs; int dirlen; } packheader_t; fteqcc-20251105/./qcc_cmdlib.c0000644000200200001440000006463215233070110015163 0ustar twolifeusers// cmdlib.c #include "qcc.h" #include //#include #undef progfuncs #define PATHSEPERATOR '/' #ifndef QCC extern jmp_buf qcccompileerror; #endif // set these before calling CheckParm int myargc; const char **myargv; char qcc_token[1024]; int qcc_eof; const unsigned int type_size[] = {1, //void sizeof(string_t)/4, //string 1, //float 3, //vector 1, //entity 1, //field sizeof(func_t)/4,//function 1, //pointer (its an int index) 1, //integer 1, //uint 2, //long 2, //ulong 2, //double 3, //fixme: how big should a variant be? 0, //ev_struct. variable sized. 0, //ev_union. variable sized. 0, //ev_accessor... 0, //ev_enum... 0, //ev_typedef 1, //ev_bool... 0, //bitfld... }; char *basictypenames[] = { "void", "string", "float", "vector", "entity", "field", "function", "pointer", "integer", "uint", "long", "ulong", "double", "variant", "struct", "union", "accessor", "enum", "typedef", "bool", "bitfield", }; /* ============================================================================ BYTE ORDER FUNCTIONS ============================================================================ */ short (*PRBigShort) (short l); short (*PRLittleShort) (short l); int (*PRBigLong) (int l); int (*PRLittleLong) (int l); float (*PRBigFloat) (float l); float (*PRLittleFloat) (float l); static short QCC_SwapShort (short l) { pbyte b1,b2; b1 = l&255; b2 = (l>>8)&255; return (b1<<8) + b2; } static short QCC_Short (short l) { return l; } static int QCC_SwapLong (int l) { pbyte b1,b2,b3,b4; b1 = (pbyte)l; b2 = (pbyte)(l>>8); b3 = (pbyte)(l>>16); b4 = (pbyte)(l>>24); return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; } static int QCC_Long (int l) { return l; } static float QCC_SwapFloat (float l) { union {pbyte b[4]; float f;} in, out; in.f = l; out.b[0] = in.b[3]; out.b[1] = in.b[2]; out.b[2] = in.b[1]; out.b[3] = in.b[0]; return out.f; } static float QCC_Float (float l) { return l; } void SetEndian(void) { union {pbyte b[2]; unsigned short s;} ed; ed.s = 255; if (ed.b[0] == 255) { PRBigShort = QCC_SwapShort; PRLittleShort = QCC_Short; PRBigLong = QCC_SwapLong; PRLittleLong = QCC_Long; PRBigFloat = QCC_SwapFloat; PRLittleFloat = QCC_Float; } else { PRBigShort = QCC_Short; PRLittleShort = QCC_SwapShort; PRBigLong = QCC_Long; PRLittleLong = QCC_SwapLong; PRBigFloat = QCC_Float; PRLittleFloat = QCC_SwapFloat; } } pbool QC_strlcat(char *dest, const char *src, size_t destsize) { size_t curlen = strlen(dest); if (!destsize) return false; //err dest += curlen; while(*src && ++curlen < destsize) *dest++ = *src++; *dest = 0; return !*src; } pbool QC_strlcpy(char *dest, const char *src, size_t destsize) { size_t curlen = 0; if (!destsize) return false; //err while(*src && ++curlen < destsize) *dest++ = *src++; *dest = 0; return !*src; } pbool QC_strnlcpy(char *dest, const char *src, size_t srclen, size_t destsize) { size_t curlen = 0; if (!destsize) return false; //err for(; *src && srclen > 0 && ++curlen < destsize; srclen--) *dest++ = *src++; *dest = 0; return !srclen; } char *QC_strcasestr(const char *haystack, const char *needle) { int i; int matchamt=0; for(i=0;haystack[i];i++) { if (tolower(haystack[i]) != tolower(needle[matchamt])) matchamt = 0; if (tolower(haystack[i]) == tolower(needle[matchamt])) { matchamt++; if (needle[matchamt]==0) return (char *)&haystack[i-(matchamt-1)]; } } return 0; } #if !defined(MINIMAL) && !defined(OMIT_QCC) /* ================ I_FloatTime ================ */ /* double I_FloatTime (void) { struct timeval tp; struct timezone tzp; static int secbase; gettimeofday(&tp, &tzp); if (!secbase) { secbase = tp.tv_sec; return tp.tv_usec/1000000.0; } return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0; } */ #ifdef QCC int QC_strncasecmp (const char *s1, const char *s2, int n) { int c1, c2; while (1) { c1 = *s1++; c2 = *s2++; if (!n--) return 0; // strings are equal until end point if (c1 != c2) { if (c1 >= 'a' && c1 <= 'z') c1 -= ('a' - 'A'); if (c2 >= 'a' && c2 <= 'z') c2 -= ('a' - 'A'); if (c1 != c2) return -1; // strings not equal } if (!c1) return 0; // strings are equal // s1++; // s2++; } return -1; } int QC_strcasecmp (const char *s1, const char *s2) { return QC_strncasecmp(s1, s2, 0x7fffffff); } #else int QC_strncasecmp(const char *s1, const char *s2, int n); int QC_strcasecmp (const char *s1, const char *s2) { return QC_strncasecmp(s1, s2, 0x7fffffff); } #endif #endif //minimal /* ============== COM_Parse Parse a token out of a string ============== */ char *QCC_COM_Parse (const char *data) { int c; int len; len = 0; qcc_token[0] = 0; if (!data) return NULL; // skip whitespace skipwhite: while ((c = *data) && qcc_iswhite(c)) data++; if (!c) return NULL; // skip // comments if (c=='/' && data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; } // skip /* comments if (c=='/' && data[1] == '*') { while (data[1] && (data[0] != '*' || data[1] != '/')) data++; data+=2; goto skipwhite; } // handle quoted strings specially if (c == '\"') { data++; do { c = *data++; if (c=='\\' && *data == '\"') c = *data++; //allow C-style string escapes else if (c=='\\' && *data == '\\') c = *data++; // \ is now a special character so it needs to be marked up using itself else if (c=='\\' && *data == 'n') { // and do new lines while we're at it. c = '\n'; data++; } else if (c=='\\' && *data == 'r') { // and do mac lines while we're at it. c = '\r'; data++; } else if (c=='\\' && *data == 't') { // and do tabs while we're at it. c = '\t'; data++; } else if (c=='\"') { qcc_token[len] = 0; return (char*)data; } else if (c=='\0') { // printf("ERROR: Unterminated string\n"); qcc_token[len] = 0; return (char*)data; } else if (c=='\n' || c=='\r') { //new lines are awkward. //vanilla saved games do not add \ns on load //terminating the string on a new line thus has compatbility issues. //while "wad" "c:\foo\" does happen in the TF community (fucked tools) //so \r\n terminates the string if the last char was an escaped quote, but not otherwise. if (len > 0 && qcc_token[len-1] == '\"') { // printf("ERROR: new line in string\n"); qcc_token[len] = 0; return (char*)data; } } if (len >= sizeof(qcc_token)-1) ; else qcc_token[len] = c; len++; } while (1); } // parse single characters if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':' || c==',') { qcc_token[len] = c; len++; qcc_token[len] = 0; return (char*)data+1; } // parse a regular word do { if (len >= sizeof(qcc_token)-1) ; else qcc_token[len++] = c; data++; c = *data; if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':' || c=='\"' || c==',') break; } while (c && !qcc_iswhite(c)); qcc_token[len] = 0; return (char*)data; } //more C tokens... char *QCC_COM_Parse2 (char *data) { int c; int len; len = 0; qcc_token[0] = 0; if (!data) return NULL; // skip whitespace skipwhite: while ((c = *data) && qcc_iswhite(c)) data++; if (!c) return NULL; // skip // comments if (c=='/' && data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; } // handle quoted strings specially if (c == '\"') { data++; do { c = *data++; if (c=='\\' && *data == '\"') c = *data++; //allow C-style string escapes else if (c=='\\' && *data == '\\') c = *data++; // \ is now a special character so it needs to be marked up using itself else if (c=='\\' && *data == 'n') { // and do new lines while we're at it. c = '\n'; data++; } else if (c=='\"'||c=='\0') { if (len < sizeof(qcc_token)-1) qcc_token[len++] = 0; break; } if (len >= sizeof(qcc_token)-1) ; else qcc_token[len++] = c; } while (1); } // parse numbers if (c >= '0' && c <= '9') { if (c == '0' && data[1] == 'x') { //parse hex qcc_token[0] = '0'; c='x'; len=1; data++; for(;;) { //parse regular number if (len >= sizeof(qcc_token)-1) ; else qcc_token[len++] = c; data++; c = *data; if ((c<'0'|| c>'9') && (c<'a'||c>'f') && (c<'A'||c>'F') && c != '.') break; } } else { for(;;) { //parse regular number if (len >= sizeof(qcc_token)-1) ; else qcc_token[len++] = c; data++; c = *data; if ((c<'0'|| c>'9') && c != '.') break; } } qcc_token[len] = 0; return data; } // parse words else if ((c>= 'a' && c <= 'z') || (c>= 'A' && c <= 'Z') || c == '_') { do { if (len >= sizeof(qcc_token)-1) ; else qcc_token[len++] = c; data++; c = *data; } while ((c>= 'a' && c <= 'z') || (c>= 'A' && c <= 'Z') || (c>= '0' && c <= '9') || c == '_'); qcc_token[len] = 0; return data; } else { qcc_token[len] = c; len++; qcc_token[len] = 0; return data+1; } } char *VARGS qcva (char *text, ...) { va_list argptr; static char msg[2048]; va_start (argptr,text); QC_vsnprintf (msg,sizeof(msg)-1, text,argptr); va_end (argptr); return msg; } #if !defined(MINIMAL) && !defined(OMIT_QCC) /* ============================================================================= MISC FUNCTIONS ============================================================================= */ /* ================= Error For abnormal program terminations ================= */ void VARGS QCC_Error (int errortype, const char *error, ...) { progfuncs_t *progfuncs = qccprogfuncs; extern int numsourcefiles; va_list argptr; char msg[2048]; va_start (argptr,error); QC_vsnprintf (msg,sizeof(msg)-1, error,argptr); va_end (argptr); externs->Printf ("\n************ ERROR ************\n%s\n", msg); editbadfile(s_filen, pr_source_line); numsourcefiles = 0; #ifndef QCC longjmp(qcccompileerror, 1); #else print ("Press any key\n"); getch(); #endif exit (1); } /* ================= CheckParm Checks for the given parameter in the program's command line arguments Returns the argument number (1 to argc-1) or 0 if not present ================= */ int QCC_CheckParm (const char *check) { int i; for (i = 1;i 0 && path[length] != PATHSEPERATOR) length--; path[length] = 0; } /* ==================== Extract file parts ==================== */ void ExtractFilePath (char *path, char *dest) { char *src; src = path + strlen(path) - 1; // // back up until a \ or the start // while (src != path && *(src-1) != PATHSEPERATOR) src--; memcpy (dest, path, src-path); dest[src-path] = 0; } void ExtractFileBase (char *path, char *dest) { char *src; src = path + strlen(path) - 1; // // back up until a \ or the start // while (src != path && *(src-1) != PATHSEPERATOR) src--; while (*src && *src != '.') { *dest++ = *src++; } *dest = 0; } void ExtractFileExtension (char *path, char *dest) { char *src; src = path + strlen(path) - 1; // // back up until a . or the start // while (src != path && *(src-1) != '.') src--; if (src == path) { *dest = 0; // no extension return; } strcpy (dest,src); } /* ============== ParseNum / ParseHex ============== */ static long ParseHex (char *hex) { char *str; long num; num = 0; str = hex; while (*str) { num <<= 4; if (*str >= '0' && *str <= '9') num += *str-'0'; else if (*str >= 'a' && *str <= 'f') num += 10 + *str-'a'; else if (*str >= 'A' && *str <= 'F') num += 10 + *str-'A'; else QCC_Error (ERR_BADHEX, "Bad hex number: %s",hex); str++; } return num; } long ParseNum (char *str) { if (str[0] == '$') return ParseHex (str+1); if (str[0] == '0' && str[1] == 'x') return ParseHex (str+2); return atol (str); } //buffer size and max size are different. buffer is bigger. #define MAXQCCFILES 3 struct { char *name; FILE *stdio; char *buff; int buffsize; int ofs; int maxofs; } qccfile[MAXQCCFILES]; int SafeOpenWrite (char *filename, int maxsize) { int i; for (i = 0; i < MAXQCCFILES; i++) { if (!qccfile[i].stdio && !qccfile[i].buff) { qccfile[i].name = strdup(filename); qccfile[i].buffsize = maxsize; qccfile[i].maxofs = 0; qccfile[i].ofs = 0; qccfile[i].stdio = NULL; qccfile[i].buff = NULL; if (maxsize < 0) qccfile[i].stdio = fopen(filename, "wb"); else qccfile[i].buff = malloc(qccfile[i].buffsize); if (!qccfile[i].stdio && !qccfile[i].buff) { QCC_Error(ERR_TOOMANYOPENFILES, "Unable to open %s", filename); return -1; } return i; } } QCC_Error(ERR_TOOMANYOPENFILES, "Too many open files on file %s", filename); return -1; } static void ResizeBuf(int hand, int newsize) { char *nb; if (qccfile[hand].buffsize >= newsize) return; //already big enough nb = malloc(newsize); memcpy(nb, qccfile[hand].buff, qccfile[hand].maxofs); free(qccfile[hand].buff); qccfile[hand].buff = nb; qccfile[hand].buffsize = newsize; } void SafeWrite(int hand, const void *buf, long count) { if (qccfile[hand].stdio) { fwrite(buf, 1, count, qccfile[hand].stdio); } else { if (qccfile[hand].ofs +count >= qccfile[hand].buffsize) ResizeBuf(hand, qccfile[hand].ofs + count+(64*1024)); memcpy(&qccfile[hand].buff[qccfile[hand].ofs], buf, count); } qccfile[hand].ofs+=count; if (qccfile[hand].ofs > qccfile[hand].maxofs) qccfile[hand].maxofs = qccfile[hand].ofs; } int SafeSeek(int hand, int ofs, int mode) { if (mode == SEEK_CUR) return qccfile[hand].ofs; else { if (qccfile[hand].stdio) fseek(qccfile[hand].stdio, ofs, SEEK_SET); else ResizeBuf(hand, ofs+1024); qccfile[hand].ofs = ofs; if (qccfile[hand].ofs > qccfile[hand].maxofs) qccfile[hand].maxofs = qccfile[hand].ofs; return 0; } } pbool SafeClose(int hand) { progfuncs_t *progfuncs = qccprogfuncs; pbool ret; if (qccfile[hand].stdio) ret = 0==fclose(qccfile[hand].stdio); else { ret = externs->WriteFile(qccfile[hand].name, qccfile[hand].buff, qccfile[hand].maxofs); free(qccfile[hand].buff); } free(qccfile[hand].name); qccfile[hand].name = NULL; qccfile[hand].buff = NULL; qccfile[hand].stdio = NULL; return ret; } qcc_cachedsourcefile_t *qcc_sourcefile; //return 0 if the input is not valid utf-8. unsigned int utf8_check(const void *in, unsigned int *value) { //uc is the output unicode char unsigned int uc = 0xfffdu; //replacement character const unsigned char *str = in; if (!(*str & 0x80)) { *value = *str; return 1; } else if ((*str & 0xe0) == 0xc0) { if ((str[1] & 0xc0) == 0x80) { *value = uc = ((str[0] & 0x1f)<<6) | (str[1] & 0x3f); if (!uc || uc >= (1u<<7)) //allow modified utf-8 (only for nulls) return 2; } } else if ((*str & 0xf0) == 0xe0) { if ((str[1] & 0xc0) == 0x80 && (str[2] & 0xc0) == 0x80) { *value = uc = ((str[0] & 0x0f)<<12) | ((str[1] & 0x3f)<<6) | ((str[2] & 0x3f)<<0); if (uc >= (1u<<11)) return 3; } } else if ((*str & 0xf8) == 0xf0) { if ((str[1] & 0xc0) == 0x80 && (str[2] & 0xc0) == 0x80 && (str[3] & 0xc0) == 0x80) { *value = uc = ((str[0] & 0x07)<<18) | ((str[1] & 0x3f)<<12) | ((str[2] & 0x3f)<<6) | ((str[3] & 0x3f)<<0); if (uc >= (1u<<16)) //overlong if (uc <= 0x10ffff) //aand we're not allowed to exceed utf-16 surrogates. return 4; } } *value = 0xFFFD; return 0; } //read utf-16 chars and output the 'native' utf-8. //we don't expect essays written in code, so we don't need much actual support for utf-8. static char *decodeUTF(int type, unsigned char *inputf, size_t inbytes, size_t *outlen, pbool usemalloc) { char *utf8, *start; unsigned int inc; unsigned int chars, i; int w, maxperchar; switch(type) { case UTF16LE: w = 2; maxperchar = 4; break; case UTF16BE: w = 2; maxperchar = 4; break; case UTF32LE: w = 4; maxperchar = 4; //we adhere to RFC3629 and clamp to U+10FFFF, which is only 4 bytes. break; case UTF32BE: w = 4; maxperchar = 4; break; default: //error *outlen = 0; return NULL; } chars = inbytes / w; if (usemalloc) utf8 = start = malloc(chars * maxperchar + 2+1); else utf8 = start = qccHunkAlloc(chars * maxperchar + 2+1); for (i = 0; i < chars; i++) { switch(type) { default: inc = 0; break; case UTF16LE: case UTF16BE: inc = inputf[type==UTF16BE]; inc|= inputf[type==UTF16LE]<<8; inputf += 2; //handle surrogates if (inc >= 0xd800u && inc < 0xdc00u && i+1 < chars) { unsigned int l; l = inputf[type==UTF16BE]; l|= inputf[type==UTF16LE]<<8; if (l >= 0xdc00u && l < 0xe000u) { inputf+=2; inc = (((inc & 0x3ffu)<<10) | (l & 0x3ffu)) + 0x10000; i++; } } break; case UTF32LE: inc = *inputf++; inc|= (*inputf++)<<8; inc|= (*inputf++)<<16; inc|= (*inputf++)<<24; break; case UTF32BE: inc = (*inputf++)<<24; inc|= (*inputf++)<<16; inc|= (*inputf++)<<8; inc|= *inputf++; break; } if (inc > 0x10FFFF) inc = 0xFFFD; if (inc <= 127) *utf8++ = inc; else if (inc <= 0x7ff) { *utf8++ = ((inc>>6) & 0x1f) | 0xc0; *utf8++ = ((inc>>0) & 0x3f) | 0x80; } else if (inc <= 0xffff) { *utf8++ = ((inc>>12) & 0xf) | 0xe0; *utf8++ = ((inc>>6) & 0x3f) | 0x80; *utf8++ = ((inc>>0) & 0x3f) | 0x80; } else if (inc <= 0x1fffff) { *utf8++ = ((inc>>18) & 0x07) | 0xf0; *utf8++ = ((inc>>12) & 0x3f) | 0x80; *utf8++ = ((inc>> 6) & 0x3f) | 0x80; *utf8++ = ((inc>> 0) & 0x3f) | 0x80; } else { inc = 0xFFFD; *utf8++ = ((inc>>12) & 0xf) | 0xe0; *utf8++ = ((inc>>6) & 0x3f) | 0x80; *utf8++ = ((inc>>0) & 0x3f) | 0x80; } } *outlen = utf8 - start; *utf8 = 0; return start; } //the gui is a windows program. //this means that its fucked. //on the plus side, its okay with a bom... unsigned short *QCC_makeutf16(char *mem, size_t len, int *outlen, pbool *errors) { unsigned int code; int l; unsigned short *out, *outstart; pbool nonascii = false; //sanitise the input. if (len >= 4 && mem[0] == '\xff' && mem[1] == '\xfe' && mem[2] == '\x00' && mem[3] == '\x00') mem = decodeUTF(UTF32LE, (unsigned char*)mem+4, len-4, &len, true); else if (len >= 4 && mem[0] == '\x00' && mem[1] == '\x00' && mem[2] == '\xfe' && mem[3] == '\xff') mem = decodeUTF(UTF32BE, (unsigned char*)mem+4, len-4, &len, true); else if (len >= 2 && mem[0] == '\xff' && mem[1] == '\xfe') { //already utf8, just return it as-is out = malloc(len+3); memcpy(out, mem, len); out[len/2] = 0; return out; //mem = decodeUTF(UTF16LE, (unsigned char*)mem+2, len-2, &len, false); } else if (len >= 2 && mem[0] == '\xfe' && mem[1] == '\xff') mem = decodeUTF(UTF16BE, (unsigned char*)mem+2, len-2, &len, false); //utf-8 BOM, for compat with broken text editors (like windows notepad). else if (len >= 3 && mem[0] == '\xef' && mem[1] == '\xbb' && mem[2] == '\xbf') { mem += 3; len -= 3; } outstart = malloc(len*2+3); out = outstart; while(len) { l = utf8_check(mem, &code); if (!l) {l = 1; code = 0xe000|(unsigned char)*mem; nonascii = true;}//fucked up. convert to 0xe000 private-use range. len -= l; mem += l; if (code > 0xffff) { code -= 0x10000; *out++ = 0xd800u | ((code>>10) & 0x3ff); // *out++ = 0xdc00u | ((code>>00) & 0x3ff); } else *out++ = code; } if (outlen) *outlen = out - outstart; *out++ = 0; if (errors) *errors = nonascii; return outstart; } //input is a raw file (will not be changed //output is utf-8 data char *QCC_SanitizeCharSet(char *mem, size_t *len, pbool *freeresult, int *origfmt) { if (freeresult) *freeresult = true; if (*len >= 4 && mem[0] == '\xff' && mem[1] == '\xfe' && mem[2] == '\x00' && mem[3] == '\x00') mem = decodeUTF(*origfmt=UTF32LE, (unsigned char*)mem+4, *len-4, len, !!freeresult); else if (*len >= 4 && mem[0] == '\x00' && mem[1] == '\x00' && mem[2] == '\xfe' && mem[3] == '\xff') mem = decodeUTF(*origfmt=UTF32BE, (unsigned char*)mem+4, *len-4, len, !!freeresult); else if (*len >= 2 && mem[0] == '\xff' && mem[1] == '\xfe') mem = decodeUTF(*origfmt=UTF16LE, (unsigned char*)mem+2, *len-2, len, !!freeresult); else if (*len >= 2 && mem[0] == '\xfe' && mem[1] == '\xff') mem = decodeUTF(*origfmt=UTF16BE, (unsigned char*)mem+2, *len-2, len, !!freeresult); //utf-8 BOM, for compat with broken text editors (like windows notepad). else if (*len >= 3 && mem[0] == '\xef' && mem[1] == '\xbb' && mem[2] == '\xbf') { *origfmt=UTF8_BOM; mem += 3; *len -= 3; if (freeresult) *freeresult = false; } else { unsigned int ch, cl; char *p, *e=mem+*len; *origfmt=UTF8_RAW; for (p = mem; p < e; p += cl) //validate it, so we're sure what format it actually is { cl = utf8_check(p, &ch); if (!cl) { *origfmt = UTF_ANSI; break; } } if (freeresult) *freeresult = false; /* #ifdef _WIN32 //even if we wrote the bom, resaving with wordpad will translate the file to the system's active code page, which will fuck up any comments. thanks for that, wordpad. //(weirdly, notepad does the right thing) int wchars = MultiByteToWideChar(CP_ACP, 0, mem, *len, NULL, 0); if (wchars) { BOOL failed = false; wchar_t *wc = malloc(wchars * sizeof(wchar_t)); int mchars; MultiByteToWideChar(CP_ACP, 0, mem, *len, wc, wchars); mchars = WideCharToMultiByte(CP_UTF8, 0, wc, wchars, NULL, 0, NULL, NULL); if (mchars && !failed) { mem = (freeresult?malloc(mchars+2):qccHunkAlloc(mchars+2)); mem[mchars] = 0; *len = mchars; if (freeresult) *freeresult = true; WideCharToMultiByte(CP_UTF8, 0, wc, wchars, mem, mchars, NULL, NULL); } free(wc); } #endif */ } return mem; } static unsigned char *PDECL QCC_LoadFileHunk(void *ctx, size_t size) { //2 ensures we can always put a \n in there. return (unsigned char*)qccHunkAlloc(sizeof(qcc_cachedsourcefile_t)+strlen(ctx)+size+2) + sizeof(qcc_cachedsourcefile_t) + strlen(ctx); } long QCC_LoadFile (char *filename, void **bufferptr) { qcc_cachedsourcefile_t *sfile; progfuncs_t *progfuncs = qccprogfuncs; char *mem; int check; size_t len; int line; int orig; pbool warned = false; mem = externs->ReadFile(filename, QCC_LoadFileHunk, filename, &len, true); if (!mem) { QCC_Error(ERR_COULDNTOPENFILE, "Couldn't open file %s", filename); return -1; } sfile = (qcc_cachedsourcefile_t*)(mem-sizeof(qcc_cachedsourcefile_t)-strlen(filename)); mem[len] = 0; mem = QCC_SanitizeCharSet(mem, &len, NULL, &orig); //actual utf-8 handling is somewhat up to the engine. the qcc can only ensure that utf8 works in symbol names etc. //its only in strings where it actually makes a difference, and the interpretation of those is basically entirely up to the engine. //that said, we could insert a utf-8 BOM into ones with utf-8 chars, but that would mess up a lot of builtins+mods, so we won't. for (check = 0, line = 1; check < len; check++) { if (mem[check] == '\n') line++; else if (!mem[check]) { if (!warned) QCC_PR_Warning(WARN_UNEXPECTEDPUNCT, filename, line, "file contains null bytes %u/%"pPRIuSIZE, check, len); warned = true; //fixme: insert modified-utf-8 nulls instead. mem[check] = ' '; } } mem[len] = '\n'; mem[len+1] = '\0'; strcpy(sfile->filename, filename); sfile->size = len; sfile->file = mem; sfile->type = FT_CODE; sfile->next = qcc_sourcefile; qcc_sourcefile = sfile; *bufferptr=mem; return len; } void QCC_AddFile (char *filename) { qcc_cachedsourcefile_t *sfile; progfuncs_t *progfuncs = qccprogfuncs; char *mem; size_t len; mem = externs->ReadFile(filename, QCC_LoadFileHunk, filename, &len, false); if (!mem) externs->Abort("failed to find file %s", filename); sfile = (qcc_cachedsourcefile_t*)(mem-sizeof(qcc_cachedsourcefile_t)-strlen(filename)); mem[len] = '\0'; sfile->size = len; strcpy(sfile->filename, filename); sfile->file = mem; sfile->type = FT_DATA; sfile->next = qcc_sourcefile; qcc_sourcefile = sfile; } static unsigned char *PDECL FS_ReadToMem_Alloc(void *ctx, size_t size) { unsigned char *mem; progfuncs_t *progfuncs = qccprogfuncs; mem = externs->memalloc(size+1); mem[size] = 0; return mem; } void *FS_ReadToMem(char *filename, size_t *len) { progfuncs_t *progfuncs = qccprogfuncs; return externs->ReadFile(filename, FS_ReadToMem_Alloc, NULL, len, false); } void FS_CloseFromMem(void *mem) { progfuncs_t *progfuncs = qccprogfuncs; externs->memfree(mem); } #endif void StripExtension (char *path) { int length; length = strlen(path)-1; while (length > 0 && path[length] != '.') { length--; if (path[length] == '/') return; // no extension } if (length) path[length] = 0; } fteqcc-20251105/./gui.h0000644000200200001440000000241315233070110013661 0ustar twolifeusersvoid GoToDefinition(const char *name); int Grep(const char *filename, const char *string); void EditFile(const char *name, int line, pbool setcontrol); void GUI_SetDefaultOpts(void); int GUI_BuildParms(const char *args, const char **argv, int argv_size, pbool quick); //unsigned char *PDECL QCC_ReadFile (const char *fname, void *buffer, int len, size_t *sz); int QCC_RawFileSize (const char *fname); pbool QCC_WriteFile (const char *name, void *data, int len); void GUI_DialogPrint(const char *title, const char *text); void *GUIReadFile(const char *fname, unsigned char *(*buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *out_size, pbool issourcefile); int GUIFileSize(const char *fname); int GUI_ParseCommandLine(const char *args, pbool keepsrcanddir); //0=gui, 1=commandline void GUI_SaveConfig(void); void GUI_RevealOptions(void); int GUIprintf(const char *msg, ...); pbool GenBuiltinsList(char *buffer, int buffersize); pbool GenAutoCompleteList(char *prefix, char *buffer, int buffersize); extern char parameters[16384]; extern char progssrcname[256]; extern char progssrcdir[256]; extern pbool fl_nondfltopts; extern pbool fl_hexen2; extern pbool fl_ftetarg; extern pbool fl_autohighlight; extern pbool fl_compileonstart; extern pbool fl_showall; extern pbool fl_log; fteqcc-20251105/./fteqcc.rc0000644000200200001440000000007215233070110014516 0ustar twolifeusers101 ICON "byshpuld.ico" fteqcc-20251105/./packager.c0000644000200200001440000013311515233070110014651 0ustar twolifeusers#include "qcc.h" #if !defined(MINIMAL) && !defined(OMIT_QCC) #include #ifdef _WIN32 #include #include #else #include #include #include #endif void QCC_JoinPaths(char *fullname, size_t fullnamesize, const char *newfile, const char *base); //package formats: //pakzip - files are uncompressed, with both a pak header and a zip trailer, allowing it to be read as either type of file. //zip - standard zips //spanned zip - the full list of files is written into a separate central-directory-only zip, the actual file data comes from regular zips named foo.z##/.p## instead of foo.zip/foo.pk3 /* dataset common { output default data.pk3 output logic textures.pk3 } dataset desktop { output tex textures_pc.pk3 } dataset mobile { output tex textures_mobile.pk3 } input pak0.pk3 rule dxt1 { dataset desktop output tex newext dds command "@\"c:/program files/Compressonator/CompressonatorCLI\" -fd DXT1 $input $output" } rule etc2 { dataset mobile output tex newext ktx command "@\"c:/program files/Compressonator/CompressonatorCLI\" -fd ETC2 $input $output" } logic { progs.dat } class texa0 { output tex desktop: dxt1 mobile: etc2 } texa0 { gfx/conback.txt } */ #define quint64_t long long #define qofs_t size_t #define countof(x) (sizeof(x)/sizeof((x)[0])) struct pkgctx_s { void (*messagecallback)(void *userctx, const char *message, ...); void *userctx; char *listfile; pbool test; pbool readoldpacks; char gamepath[MAX_OSPATH]; char sourcepath[MAX_OSPATH]; time_t buildtime; //skips the file if its listed in one of these packages, unless the modification time on disk is newer. struct oldpack_s { struct oldpack_s *next; char filename[128]; size_t numfiles; unsigned int part; struct { char name[128]; unsigned short zmethod; unsigned int zcrc; qofs_t zhdrofs; qofs_t rawsize; qofs_t zipsize; unsigned short dostime; unsigned short dosdate; } *file; } *oldpacks; struct dataset_s { struct dataset_s *next; //these are the output pk3s from this package. struct output_s { struct output_s *next; char code[128]; char filename[128]; struct file_s *files; pbool usediffs; unsigned int numparts; struct oldpack_s *oldparts; } *outputs; char name[1]; } *datasets; struct rule_s { struct rule_s *next; char name[128]; int dropfile:1; char *newext; char *command; } *rules; struct class_s { char name[128]; struct class_s *next; //the output package codename to write to. class is skipped if the dataset doesn't include that name. char outname[128]; struct { struct dataset_s *set; struct rule_s *rule; } dataset[8]; struct rule_s *defaultrule; struct file_s { struct file_s *next; char name[128]; //temp data for tracking what's getting written. struct { char name[128]; struct file_s *nextwrite; struct rule_s *rule; unsigned int zdisk; unsigned short zmethod; unsigned int zcrc; qofs_t zhdrofs; qofs_t pakofs; qofs_t rawsize; qofs_t zipsize; unsigned short dostime; unsigned short dosdate; time_t timestamp; } write; } *files; } *classes; }; #ifdef _WIN32 static time_t filetime_to_timet(FILETIME ft) { ULARGE_INTEGER ull; ull.LowPart = ft.dwLowDateTime; ull.HighPart = ft.dwHighDateTime; return ull.QuadPart / 10000000ULL - 11644473600ULL; } #endif static struct rule_s *PKG_FindRule(struct pkgctx_s *ctx, char *code) { struct rule_s *o; for (o = ctx->rules; o; o = o->next) { if (!strcmp(o->name, code)) return o; } return NULL; } static struct class_s *PKG_FindClass(struct pkgctx_s *ctx, char *code) { struct class_s *c; for (c = ctx->classes; c; c = c->next) { if (!strcmp(c->name, code)) return c; } return NULL; } static struct dataset_s *PKG_FindDataset(struct pkgctx_s *ctx, const char *code) { struct dataset_s *o; for (o = ctx->datasets; o; o = o->next) { if (!strcmp(o->name, code)) return o; } return NULL; } static struct dataset_s *PKG_GetDataset(struct pkgctx_s *ctx, const char *code) { struct dataset_s *s = PKG_FindDataset(ctx, code); if (!s) { s = malloc(sizeof(*s)+strlen(code)); strcpy(s->name, code); s->outputs = NULL; s->next = ctx->datasets; ctx->datasets = s; } return s; } static pbool PKG_SkipWhite(struct pkgctx_s *ctx, pbool linebreak) { for(;;) { if (qcc_iswhite(*ctx->listfile)) { if (qcc_islineending(ctx->listfile[0], ctx->listfile[1]) && !linebreak) return false; ctx->listfile++; continue; } if (ctx->listfile[0] == '/' && ctx->listfile[1] == '/') { while (!qcc_islineending(ctx->listfile[0], ctx->listfile[1])) ctx->listfile++; continue; } if (ctx->listfile[0] == '/' && ctx->listfile[1] == '*') { ctx->listfile+=2; while (*ctx->listfile) { if (ctx->listfile[0]=='*' && ctx->listfile[1]=='/') { ctx->listfile+=2; break; } ctx->listfile++; } continue; } break; } return true; } static pbool PKG_GetToken(struct pkgctx_s *ctx, char *token, size_t sizeoftoken, pbool linebreak) { if (!PKG_SkipWhite(ctx, linebreak)) return false; if (*ctx->listfile) { while(*ctx->listfile) { if (qcc_iswhite(*ctx->listfile)) break; *token = *ctx->listfile++; if (sizeoftoken > 1) { token++; sizeoftoken--; } } *token = 0; return true; } return false; } static pbool PKG_GetStringToken(struct pkgctx_s *ctx, char *token, size_t sizeoftoken) { if (!PKG_SkipWhite(ctx, false)) return false; if (*ctx->listfile == '\"') { ctx->listfile++; while(*ctx->listfile) { if (*ctx->listfile == '\"') { ctx->listfile++; break; } else if (*ctx->listfile == '\\') { ctx->listfile++; switch(*ctx->listfile++) { case '\"': *token = '\"'; break; case '\\': *token = '\\'; break; case '\r': *token = '\r'; break; case '\n': *token = '\n'; break; case '\t': *token = '\t'; break; default: *token = '?'; break; } sizeoftoken--; } else *token = *ctx->listfile++; if (sizeoftoken > 1) { token++; sizeoftoken--; } } *token = 0; return true; } return false; } static pbool PKG_Expect(struct pkgctx_s *ctx, char *token) { char tok[128]; if (PKG_GetToken(ctx, tok, sizeof(tok), true)) { if (!strcmp(tok, token)) return true; } ctx->messagecallback(ctx->userctx, "Expected '%s', found '%s'\n", token, tok); return false; } static void PKG_ReplaceString(char *str, char *find, char *newpart) { char *oldpart; size_t oldlen = strlen(find); size_t nlen = strlen(newpart); while((oldpart = strstr(str, find))) { memmove(oldpart+nlen, oldpart+oldlen, strlen(oldpart+oldlen)+1); memmove(oldpart, newpart, nlen); str = oldpart+nlen; } } static void PKG_CreateOutput(struct pkgctx_s *ctx, struct dataset_s *s, const char *code, const char *filename, pbool diff) { char path[MAX_OSPATH]; char date[64]; struct output_s *o; for (o = s->outputs; o; o = o->next) { if (!strcmp(o->code, code)) { ctx->messagecallback(ctx->userctx, "Dataset '%s' defined with dupe output\n", s->name, code); return; } } if (strlen(code) >= sizeof(o->code)) { ctx->messagecallback(ctx->userctx, "Output '%s' name too long\n", code); return; } strcpy(path, filename); strftime(date, sizeof(date), "%Y%m%d", localtime(&ctx->buildtime)); PKG_ReplaceString(path, "$date", date); o = malloc(sizeof(*o)); memset(o, 0, sizeof(*o)); strcpy(o->code, code); o->usediffs = diff; QCC_JoinPaths(o->filename, sizeof(o->filename), path, ctx->gamepath); o->next = s->outputs; s->outputs = o; if (diff) { char *end = path + strlen(path)-2; unsigned int i; for (i = 0; i <= 99; i++) { #ifdef _WIN32 struct _stat statbuf; #else struct stat statbuf; #endif sprintf(end, "%02u", i+1); #ifdef _WIN32 //FIXME: use the utf16 version because microsoft suck and don't allow utf-8 if (_stat(path, &statbuf) == 0) #else if (stat(path, &statbuf) == 0) #endif { struct oldpack_s *span = malloc(sizeof(*span)); strcpy(span->filename, path); span->numfiles = 0; span->file = NULL; span->next = o->oldparts; span->part = i; o->oldparts = span; } } } } static void PKG_ParseOutput(struct pkgctx_s *ctx, pbool diff) { struct dataset_s *s; char name[128]; char prop[128]; char fname[128]; if (!PKG_GetToken(ctx, name, sizeof(name), false)) { ctx->messagecallback(ctx->userctx, "Output: Expected name\n"); return; } if (PKG_GetStringToken(ctx, prop, sizeof(prop))) { s = PKG_GetDataset(ctx, "core"); PKG_CreateOutput(ctx, s, name, prop, diff); } else { if (!PKG_Expect(ctx, "{")) return; while(PKG_GetToken(ctx, prop, sizeof(prop), true)) { if (!strcmp(prop, "}")) break; else { char *e = strchr(prop, ':'); if (e && !e[1]) { *e = 0; s = PKG_GetDataset(ctx, prop); if (PKG_GetStringToken(ctx, fname, sizeof(fname))) PKG_CreateOutput(ctx, s, name, fname, diff); else ctx->messagecallback(ctx->userctx, "Output '%s[%s]' filename omitted\n", name, prop); } else ctx->messagecallback(ctx->userctx, "Output '%s' has unknown property '%s'\n", name, prop); } //skip any junk while(PKG_GetToken(ctx, prop, sizeof(prop), false)) { if (!strcmp(prop, ";")) break; } } } } #ifdef _WIN32 static void PKG_AddOldPack(struct pkgctx_s *ctx, const char *fname) { struct oldpack_s *pack; pack = malloc(sizeof(*pack)); strcpy(pack->filename, fname); pack->numfiles = 0; pack->file = NULL; pack->next = ctx->oldpacks; ctx->oldpacks = pack; } #endif static void PKG_ParseOldPack(struct pkgctx_s *ctx) { char token[MAX_OSPATH]; if (!PKG_GetStringToken(ctx, token, sizeof(token))) return; #ifdef _WIN32 { char oldpack[MAX_OSPATH]; WIN32_FIND_DATA fd; HANDLE h; QCC_JoinPaths(oldpack, sizeof(oldpack), token, ctx->gamepath); h = FindFirstFile(oldpack, &fd); if (h == INVALID_HANDLE_VALUE) ctx->messagecallback(ctx->userctx, "wildcard string '%s' found no files\n", token); else { do { QCC_JoinPaths(token, sizeof(token), fd.cFileName, oldpack); PKG_AddOldPack(ctx, token); } while(FindNextFile(h, &fd)); } } #else ctx->messagecallback(ctx->userctx, "no wildcard support, sorry\n"); #endif } /* static void PKG_ParseDataset(struct pkgctx_s *ctx) { struct dataset_s *s; char name[128]; char prop[128]; if (!PKG_GetToken(ctx, name, sizeof(name), false)) { ctx->messagecallback(ctx->userctx, "Dataset: Expected name\n"); return; } if (strlen(name) >= sizeof(s->name)) { ctx->messagecallback(ctx->userctx, "Dataset '%s' name too long\n", name); return; } s = malloc(sizeof(*s)); memset(s, 0, sizeof(*s)); strcpy(s->name, name); if (PKG_Expect(ctx, "{")) { while(PKG_GetToken(ctx, prop, sizeof(prop), true)) { if (!strcmp(prop, "}")) break; else if (!strcmp(prop, "output")) { if (PKG_GetToken(ctx, name, sizeof(name), false)) if (PKG_GetStringToken(ctx, prop, sizeof(prop))) { PKG_CreateOutput(ctx, s, name, prop); } } else if (!strcmp(prop, "base")) PKG_GetStringToken(ctx, prop, sizeof(prop)); else ctx->messagecallback(ctx->userctx, "Dataset '%s' has unknown property '%s'\n", name, prop); //skip any junk while(PKG_GetToken(ctx, prop, sizeof(prop), false)) { if (!strcmp(prop, ";")) break; } } } if (PKG_FindDataset(ctx, name)) ctx->messagecallback(ctx->userctx, "Dataset '%s' is already defined\n", name); else { //link it in! s->next = ctx->datasets; ctx->datasets = s; return; } PKG_DestroyDataset(s); return; }*/ static void PKG_ParseRule(struct pkgctx_s *ctx) { struct rule_s *r; char name[128]; char prop[128]; char newext[128]; char command[4096]; int dropfile = false; if (!PKG_GetToken(ctx, name, sizeof(name), false)) return; if (strlen(name) >= sizeof(r->name)) { ctx->messagecallback(ctx->userctx, "Rule '%s' name too long\n", name); return; } *newext = *command = 0; if (PKG_Expect(ctx, "{")) { while(PKG_GetToken(ctx, prop, sizeof(prop), true)) { if (!strcmp(prop, "}")) break; else if (!strcmp(prop, "newext")) PKG_GetToken(ctx, newext, sizeof(newext), false); else if (!strcmp(prop, "skip")) { if (PKG_GetToken(ctx, prop, sizeof(prop), false)) dropfile = atoi(prop); else dropfile = true; } else if (!strcmp(prop, "command")) PKG_GetStringToken(ctx, command, sizeof(command)); else ctx->messagecallback(ctx->userctx, "Rule '%s' has unknown property '%s'\n", name, prop); //skip any junk while(PKG_GetToken(ctx, prop, sizeof(prop), false)) { if (!strcmp(prop, ";")) break; } } } r = PKG_FindRule(ctx, name); if (r) { ctx->messagecallback(ctx->userctx, "Rule %s is already defined\n", name); return; } r = malloc(sizeof(*r)); memset(r, 0, sizeof(*r)); strcpy(r->name, name); r->newext = strdup(newext); r->command = strdup(command); r->dropfile = dropfile; r->next = ctx->rules; ctx->rules = r; } static void PKG_AddClassFile(struct pkgctx_s *ctx, struct class_s *c, const char *fname, time_t mtime) { struct file_s *f; struct tm *t; if (strlen(fname) >= sizeof(f->name)) { ctx->messagecallback(ctx->userctx, "File name '%s' too long in class %s\n", fname, c->name); return; } f = malloc(sizeof(*f)); memset(f, 0, sizeof(*f)); strcpy(f->name, fname); f->write.timestamp = mtime; t = localtime(&f->write.timestamp); f->write.dostime = (t->tm_sec>>1)|(t->tm_min<<5)|(t->tm_hour<<11); f->write.dosdate = (t->tm_mday<<0)|(t->tm_mon<<5)|((t->tm_year+1900-1980)<<9); f->next = c->files; c->files = f; } static void PKG_AddClassFiles(struct pkgctx_s *ctx, struct class_s *c, const char *fname) { #ifdef _WIN32 WIN32_FIND_DATA fd; HANDLE h; char basepath[MAX_PATH]; QCC_JoinPaths(basepath, sizeof(basepath), fname, ctx->sourcepath); h = FindFirstFile(basepath, &fd); if (h == INVALID_HANDLE_VALUE) ctx->messagecallback(ctx->userctx, "wildcard string '%s' found no files\n", fname); else { do { QCC_JoinPaths(basepath, sizeof(basepath), fd.cFileName, fname); PKG_AddClassFile(ctx, c, basepath, filetime_to_timet(fd.ftLastWriteTime)); } while(FindNextFile(h, &fd)); } #else DIR *dir; struct dirent *ent; char basepath[MAX_OSPATH], tmppath[MAX_OSPATH]; struct stat statbuf; QCC_JoinPaths(basepath, sizeof(basepath), fname, ctx->sourcepath); QC_strlcat(basepath, "/", sizeof(basepath)); dir = opendir(basepath); if (!dir) { ctx->messagecallback(ctx->userctx, "unable to open dir %s\n", basepath); return; } while ((ent = readdir(dir))) { if (*ent->d_name == '.') continue; QCC_JoinPaths(basepath, sizeof(basepath), ent->d_name, fname); QCC_JoinPaths(tmppath, sizeof(tmppath), basepath, ctx->sourcepath); if (stat(tmppath, &statbuf)!=0) continue; switch (statbuf.st_mode & S_IFMT) { default: //some weird file type. shouldn't be a symlink sadly. // ctx->messagecallback(ctx->userctx, "found weird %s\n", basepath); break; case S_IFDIR: QC_strlcat(basepath, "/", sizeof(basepath)); // ctx->messagecallback(ctx->userctx, "found dir %s\n", basepath); PKG_AddClassFiles(ctx, c, basepath); break; case S_IFREG: // ctx->messagecallback(ctx->userctx, "found file %s\n", basepath); PKG_AddClassFile(ctx, c, basepath, statbuf.st_mtime); break; } } closedir(dir); #endif } static void PKG_ParseClass(struct pkgctx_s *ctx, char *output) { struct class_s *c; struct rule_s *r; struct dataset_s *s; char *e; char name[128]; char prop[128]; size_t u; if (output) { if (!PKG_Expect(ctx, "{")) return; *name = 0; } else if (!PKG_GetToken(ctx, name, sizeof(name), false)) return; if (output || !strcmp(name, "{")) { c = malloc(sizeof(*c)); memset(c, 0, sizeof(*c)); strcpy(c->name, ""); strcpy(c->outname, (output && *output)?output:"default"); c->next = ctx->classes; ctx->classes = c; } else { if (strlen(name) >= sizeof(c->name)) { ctx->messagecallback(ctx->userctx, "Class '%s' name too long\n", name); return; } c = PKG_FindClass(ctx, name); if (!c) { c = malloc(sizeof(*c)); memset(c, 0, sizeof(*c)); strcpy(c->name, name); strcpy(c->outname, (output && *output)?output:"default"); c->next = ctx->classes; ctx->classes = c; } if (!PKG_Expect(ctx, "{")) return; } { while(PKG_GetToken(ctx, prop, sizeof(prop), true)) { if (!strcmp(prop, "}")) break; else if (!strcmp(prop, "output")) PKG_GetToken(ctx, c->outname, sizeof(c->outname), false); else if (!strcmp(prop, "rule")) { if (PKG_GetToken(ctx, prop, sizeof(prop), false)) { if (c->defaultrule) ctx->messagecallback(ctx->userctx, "Class '%s' already has a default rule\n", name); c->defaultrule = PKG_FindRule(ctx, prop); if (!c->defaultrule) ctx->messagecallback(ctx->userctx, "Class '%s' specifies unknown rule %s\n", name, prop); } } else { e = strchr(prop, ':'); if (e && !e[1]) { *e = 0; s = PKG_FindDataset(ctx, prop); PKG_GetToken(ctx, prop, sizeof(prop), false); if (s) { r = PKG_FindRule(ctx, prop); for (u = 0; ; u++) { if (u == countof(c->dataset)) { ctx->messagecallback(ctx->userctx, "Class '%s' specialises for too many datasets\n", c->name, s->name); break; } if (c->dataset[u].set == s) ctx->messagecallback(ctx->userctx, "Class '%s' already defines a rule for dataset '%s'\n", c->name, s->name); else if (!c->dataset[u].set) { c->dataset[u].set = s; c->dataset[u].rule = r; break; } } } } else if (strchr(prop, '.')) { // if (strchr(prop, '*') || strchr(prop, '?')) PKG_AddClassFiles(ctx, c, prop); // else // PKG_AddClassFile(ctx, c, prop); } else ctx->messagecallback(ctx->userctx, "Class '%s' has unknown property '%s'\n", name, prop); } //skip any junk while(PKG_GetToken(ctx, prop, sizeof(prop), false)) { if (!strcmp(prop, ";")) break; } } } } static void PKG_ParseClassFiles(struct pkgctx_s *ctx, struct class_s *c) { char prop[128]; if (PKG_Expect(ctx, "{")) { while(PKG_GetToken(ctx, prop, sizeof(prop), true)) { if (!strcmp(prop, "}")) break; if (!strcmp(prop, ";")) continue; // if (strchr(prop, '*') || strchr(prop, '?')) PKG_AddClassFiles(ctx, c, prop); // else // PKG_AddClassFile(ctx, c, prop); } } } #ifdef AVAIL_ZLIB #include static unsigned int PKG_DeflateToFile(FILE *f, unsigned int rawsize, void *in, int method) { char out[8192]; int i=0; z_stream strm = { (char *)in, rawsize, 0, out, sizeof(out), 0, NULL, NULL, NULL, NULL, NULL, Z_BINARY, 0, 0 }; if (method == 8) deflateInit2(&strm, 9, Z_DEFLATED, -MAX_WBITS, 9, Z_DEFAULT_STRATEGY); //zip deflate compression else deflateInit(&strm, Z_BEST_COMPRESSION); //zlib compression while(deflate(&strm, Z_FINISH) == Z_OK) { fwrite(out, 1, sizeof(out) - strm.avail_out, f); //compress in chunks of 8192. Saves having to allocate a huge-mega-big buffer i+=sizeof(out) - strm.avail_out; strm.next_out = out; strm.avail_out = sizeof(out); } deflateEnd(&strm); fwrite(out, 1, sizeof(out) - strm.avail_out, f); i+=sizeof(out) - strm.avail_out; return i; } #endif #ifdef _WIN32 static void StupidWindowsPopenAlternativeCrap(struct pkgctx_s *ctx, char *commandline) { PROCESS_INFORMATION piProcInfo = {0}; SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; STARTUPINFO siStartInfo = {sizeof(STARTUPINFO)}; HANDLE readpipe = INVALID_HANDLE_VALUE; HANDLE writepipe = INVALID_HANDLE_VALUE; siStartInfo.hStdError = siStartInfo.hStdOutput = siStartInfo.hStdInput = INVALID_HANDLE_VALUE; if (CreatePipe(&readpipe, &siStartInfo.hStdOutput, &saAttr, 0)) { if (CreatePipe(&siStartInfo.hStdInput, &writepipe, &saAttr, 0)) { SetHandleInformation(readpipe, HANDLE_FLAG_INHERIT, 0); SetHandleInformation(writepipe, HANDLE_FLAG_INHERIT, 0); siStartInfo.hStdError = siStartInfo.hStdOutput; siStartInfo.dwFlags |= STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW/*ZOMGWTFBBQ*/; if (!CreateProcess(NULL, (*commandline=='@')?commandline+1:commandline, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo)) ctx->messagecallback(ctx->userctx, "Unable to execute command %s\n", commandline); else { CloseHandle(piProcInfo.hProcess); CloseHandle(piProcInfo.hThread); } } } CloseHandle(siStartInfo.hStdOutput); CloseHandle(siStartInfo.hStdInput); CloseHandle(writepipe); for (;;) { char buf[64]; DWORD SHOUTY; if (!ReadFile(readpipe, buf, sizeof(buf)-1, &SHOUTY, NULL) || SHOUTY == 0) break; if (*commandline == '@') continue; buf[SHOUTY] = 0; ctx->messagecallback(ctx->userctx, "%s", buf); } CloseHandle(readpipe); } #endif static void *PKG_OpenSourceFile(struct pkgctx_s *ctx, struct file_s *file, size_t *fsize) { char fullname[1024]; FILE *f; char *data; size_t size; struct rule_s *rule = file->write.rule; *fsize = 0; QCC_JoinPaths(fullname, sizeof(fullname), file->name, ctx->sourcepath); strcpy(file->write.name, file->name); //WIN32 FIXME: use the utf16 version because microsoft suck and don't allow utf-8 f = fopen(fullname, "rb"); if (!f) return NULL; if (rule) ctx->messagecallback(ctx->userctx, "\t\tProcessing %s (%s)\n", file->name, rule->name); else ctx->messagecallback(ctx->userctx, "\t\tCompressing %s\n", file->name); if (rule) { data = strrchr(file->write.name, '.'); if (!data) data = file->write.name+strlen(file->write.name); if (strchr(rule->newext, '.')) strcpy(data, rule->newext); //note: this allows weird _foo.tga postfixes. else { *data = '.'; strcpy(data+1, rule->newext); } if (rule->command) { int i; char commandline[4096]; char *cmd; char tempname[1024]; //generate a sequenced temp filename //run the external tool to write that file //read the temp file. //delete temp file... fclose(f); QCC_JoinPaths(tempname, sizeof(tempname), file->write.name, ctx->sourcepath); f = fopen(tempname, "rb"); if (f) { fclose(f); ctx->messagecallback(ctx->userctx, "Temp file %s already exists... not replacing+deleting\n", tempname); return NULL; } for (i = 0, cmd = rule->command; *cmd && i < countof(commandline)-1; ) { if (!strncmp(cmd, "$input", 6)) { strcpy(&commandline[i], fullname); i += strlen(&commandline[i]); cmd += 6; } else if (!strncmp(cmd, "$output", 7)) { strcpy(&commandline[i], tempname); i += strlen(&commandline[i]); cmd += 7; } else commandline[i++] = *cmd++; } commandline[i] = 0; // ctx->messagecallback(ctx->userctx, "Commandline is %s\n", commandline); #ifdef _WIN32 //windows is so fucking useless sometimes. sure, _popen 'works'... its just perverse enough that its not an option, forcing system-specific crap in anything that isn't originally from unix... maybe it is just incompetence? still feels like malice to me. StupidWindowsPopenAlternativeCrap(ctx, commandline); #else { FILE *p; p = popen((*commandline=='@')?commandline+1:commandline, "rt"); if (!p) { ctx->messagecallback(ctx->userctx, "Unable to execute command\n", tempname); return NULL; } while(fgets(commandline, sizeof(commandline), p)) ctx->messagecallback(ctx->userctx, "%s", commandline); if (feof(p)) ctx->messagecallback(ctx->userctx, "Process returned %d\n", pclose( p )); else { fprintf(stderr, "Error: Failed to read the pipe to the end.\n"); pclose(p); } } #endif f = fopen(tempname, "rb"); if (!f) { ctx->messagecallback(ctx->userctx, "Temp file %s wasn't created\n", tempname); return NULL; } fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); data = malloc(size+1); fread(data, 1, size, f); fclose(f); *fsize = size; #ifdef _WIN32 _unlink(tempname); #else unlink(tempname); #endif return data; } } fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); data = malloc(size+1); fread(data, 1, size, f); fclose(f); *fsize = size; return data; } static pbool PKG_WritePackageData(struct pkgctx_s *ctx, struct output_s *out, unsigned int index, pbool directoryonly) { //helpers to deal with misaligned data. writes little-endian. #define misbyte(ptr,ofs,data) ((unsigned char*)(ptr))[ofs] = (data)&0xff #define misshort(ptr,ofs,data) do{misbyte((ptr),(ofs),(data));misbyte((ptr),(ofs)+1,(data)>>8);}while(0) #define misint(ptr,ofs,data) do{misshort((ptr),(ofs),(data));misshort((ptr),(ofs)+2,(data)>>16);}while(0) #define misint64(ptr,ofs,data) do{misint((ptr),(ofs),(data));misint((ptr),(ofs)+4,((quint64_t)(data))>>32);}while(0) qofs_t num=0; pbool pak = false; struct file_s *f; char centralheader[46+sizeof(f->write.name)]; qofs_t centraldirsize; qofs_t centraldirofs; qofs_t z64eocdofs; char *filedata; FILE *outf; struct { char magic[4]; unsigned int tabofs; unsigned int tabbytes; } pakheader = {"PACK", 0, 0}; char *ext; #define GPF_TRAILINGSIZE (1u<<3) #define GPF_UTF8 (1u<<11) #ifdef AVAIL_ZLIB #define compmethod (pak?0:8)/*Z_DEFLATED*/ #else #define compmethod 0/*Z_RAW*/ #endif if (!compmethod && !directoryonly && !index) pak = true; //might as well boost compat... ext = strrchr(out->filename, '.'); if (ext && !QC_strcasecmp(ext, ".pak") && !index) pak = true; if (!directoryonly) { for (f = out->files; f ; f=f->write.nextwrite) { if (index != f->write.zdisk) continue; //not in this disk... break; } if (!f) { ctx->messagecallback(ctx->userctx, "\t\tNo files to write to %s\n", out->filename); return false; } } if (out->usediffs && !directoryonly) { char newname[MAX_OSPATH]; memcpy(newname, out->filename, sizeof(newname)); if (ext) { ext = newname+(ext-out->filename); ext+=1; if (*ext) ext++; QC_snprintfz(ext, sizeof(newname)-(ext-newname), "%02u", index+1); } outf = fopen(newname, "wb"); } else outf = fopen(out->filename, "wb"); if (!outf) { ctx->messagecallback(ctx->userctx, "\t\tUnable to open %s\n", out->filename); return false; } if (pak) //reserve space for the pak header fwrite(&pakheader, 1, sizeof(pakheader), outf); if (!directoryonly) { for (f = out->files; f ; f=f->write.nextwrite) { char header[32+sizeof(f->write.name)]; size_t fnamelen; size_t hofs; unsigned short gpflags = GPF_UTF8; if (index != f->write.zdisk) continue; //not in this disk... filedata = PKG_OpenSourceFile(ctx, f, &f->write.rawsize); if (!filedata) { ctx->messagecallback(ctx->userctx, "\t\tUnable to open %s\n", f->name); } fnamelen = strlen(f->write.name); f->write.zcrc = QC_encodecrc(f->write.rawsize, filedata); misint (header, 0, 0x04034b50); misshort(header, 4, 45);//minver misshort(header, 6, gpflags);//general purpose flags misshort(header, 8, 0);//compression method, 0=store, 8=deflate misshort(header, 10, f->write.dostime);//lastmodfiletime misshort(header, 12, f->write.dosdate);//lastmodfiledate misint (header, 14, f->write.zcrc);//crc32 misint (header, 18, f->write.rawsize);//compressed size misint (header, 22, f->write.rawsize);//uncompressed size misshort(header, 26, fnamelen);//filename length misshort(header, 28, 0);//extradata length (filled in later) memcpy(header+30, f->write.name, fnamelen); hofs = 30+fnamelen; //Write extra data here... misshort(header, 28, hofs-(30+fnamelen));//extradata length f->write.zhdrofs = ftell(outf); fwrite(header, 1, hofs, outf); #ifdef AVAIL_ZLIB if (f->write.rawsize && (compmethod == 2 || compmethod == 8)) { gpflags |= 1u<<1; f->write.pakofs = 0; f->write.zmethod = compmethod; f->write.zipsize = PKG_DeflateToFile(outf, f->write.rawsize, filedata, compmethod); } else #endif { f->write.zmethod = 0; f->write.pakofs = ftell(outf); f->write.zipsize = fwrite(filedata, 1, f->write.rawsize, outf); } //update the header misshort(header, 8, f->write.zmethod);//compression method, 0=store, 8=deflate if (f->write.zipsize > 0xffffffff) { misint (header, 18, 0xffffffff);//compressed size gpflags |= GPF_TRAILINGSIZE; } else misint (header, 18, f->write.zipsize);//compressed size if (f->write.rawsize > 0xffffffff) { misint (header, 22, 0xffffffff);//compressed size gpflags |= GPF_TRAILINGSIZE; } else misint (header, 22, f->write.rawsize);//compressed size misshort(header, 6, gpflags);//general purpose flags fseek(outf, f->write.zhdrofs, SEEK_SET); fwrite(header, 1, hofs, outf); fseek(outf, 0, SEEK_END); if (gpflags & GPF_TRAILINGSIZE) //if (gpflags & GPF_TRAILINGSIZE) { misint (header, 0, 0x08074b50); misint (header, 4, f->write.zcrc); misint64(header, 8, f->write.zipsize); misint64(header, 16, f->write.rawsize); fwrite(header, 1, 24, outf); } free(filedata); num++; } } if (pak) { struct { char name[56]; unsigned int offset; unsigned int size; } pakentry; pakheader.tabofs = ftell(outf); //write the pak file table. for (f = out->files,num=0; f ; f=f->write.nextwrite) { if (index != f->write.zdisk) continue; //not in this disk... memset(&pakentry, 0, sizeof(pakentry)); QC_strlcpy(pakentry.name, f->write.name, sizeof(pakentry.name)); pakentry.size = (f->write.pakofs==0)?0:f->write.rawsize; pakentry.offset = f->write.pakofs; fwrite(&pakentry, 1, sizeof(pakentry), outf); num++; } //replace the pak header, then return to the end of the file for the zip end-of-central-directory pakheader.tabbytes = num * sizeof(pakentry); fseek(outf, 0, SEEK_SET); fwrite(&pakheader, 1, sizeof(pakheader), outf); fseek(outf, 0, SEEK_END); } centraldirofs = ftell(outf); for (f = out->files,num=0; f ; f=f->write.nextwrite) { size_t hofs; size_t fnamelen; if (!directoryonly && index != f->write.zdisk) continue; fnamelen = strlen(f->write.name); misint (centralheader, 0, 0x02014b50); misshort(centralheader, 4, (3<<8)|63);//ourver misshort(centralheader, 6, 45);//minver misshort(centralheader, 8, GPF_UTF8);//general purpose flags misshort(centralheader, 10, f->write.rawsize?compmethod:0);//compression method, 0=store, 8=deflate misshort(centralheader, 12, f->write.dostime);//lastmodfiletime misshort(centralheader, 14, f->write.dosdate);//lastmodfiledate misint (centralheader, 16, f->write.zcrc);//crc32 misint (centralheader, 20, f->write.zipsize);//compressed size misint (centralheader, 24, f->write.rawsize);//uncompressed size misshort(centralheader, 28, fnamelen);//filename length misshort(centralheader, 30, 0);//extradata length (filled in later) misshort(centralheader, 32, 0);//comment length misshort(centralheader, 34, f->write.zdisk);//first disk number misshort(centralheader, 36, 0);//internal file attribs misint (centralheader, 38, 0);//external file attribs misint (centralheader, 42, f->write.zhdrofs);//local header offset strcpy(centralheader+46, f->write.name); hofs = 46+fnamelen; if (f->write.zdisk >= 0xffff || f->write.zhdrofs >= 0xffffffff || f->write.rawsize >= 0xffffffff || f->write.zipsize >= 0xffffffff) { misshort(centralheader, hofs, 0x0001);//zip64 tagid misshort(centralheader, hofs+2, 0x0001);//zip64 tag size hofs+=4; if (f->write.rawsize >= 0xffffffff) { misint64(centralheader, hofs, f->write.rawsize);//uncompressed size hofs += 8; } if (f->write.zipsize >= 0xffffffff) { misint64(centralheader, hofs, f->write.zipsize);//compressed size hofs += 8; } if (f->write.zhdrofs >= 0xffffffff) { misint64(centralheader, hofs, f->write.zhdrofs);//localheader offset hofs += 8; } if (f->write.zdisk >= 0xffff) { misint (centralheader, hofs, f->write.zdisk);//compressed size hofs += 4; } } misshort(centralheader, 30, hofs-(46+fnamelen));//extradata length fwrite(centralheader, 1, hofs, outf); num++; } centraldirsize = ftell(outf)-centraldirofs; //zip64 end of central dir z64eocdofs = ftell(outf); misint (centralheader, 0, 0x06064b50); misint64(centralheader, 4, (qofs_t)(56-16)); misshort(centralheader, 12, (3<<8)|63); //ver made by = unix|appnote ver misshort(centralheader, 14, 45); //ver needed misint (centralheader, 16, index); //thisdisk number misint (centralheader, 20, index); //centraldir start disk misint64(centralheader, 24, num); //centraldir entry count (disk) misint64(centralheader, 32, num); //centraldir entry count (total) misint64(centralheader, 40, centraldirsize);//centraldir entry bytes misint64(centralheader, 48, centraldirofs); //centraldir start offset fwrite(centralheader, 1, 56, outf); //zip64 end of central dir locator misint (centralheader, 0, 0x07064b50); misint (centralheader, 4, index); //centraldir first disk misint64(centralheader, 8, z64eocdofs); misint (centralheader, 16, index+1); //total disk count fwrite(centralheader, 1, 20, outf); // centraldirofs = ftell(outf) - centraldirofs; //write zip end-of-central-directory misint (centralheader, 0, 0x06054b50); misshort(centralheader, 4, (index > 0xffff)? 0xffff:index); //this disk number misshort(centralheader, 6, (index > 0xffff)? 0xffff:index); //centraldir first disk misshort(centralheader, 8, (num > 0xffff)? 0xffff:num); //centraldir entries misshort(centralheader, 10, (num > 0xffff)? 0xffff:num); //total centraldir entries misint (centralheader, 12, (centraldirsize>0xffffffff)?0xffffffff:centraldirsize); //centraldir size misint (centralheader, 16, (centraldirofs >0xffffffff)?0xffffffff:centraldirofs); //centraldir offset misshort(centralheader, 20, 0); //comment length fwrite(centralheader, 1, 22, outf); fclose(outf); return true; } /* #include static time_t PKG_GetFileTime(const char *filename) { struct stat s; if (stat(filename, &s) != -1) return s.st_mtime; } */ static void PKG_ReadPackContents(struct pkgctx_s *ctx, struct oldpack_s *old) { #define longfromptr(p) (((p)[0]<<0)|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24)) #define shortfromptr(p) (((p)[0]<<0)|((p)[1]<<8)) size_t u, namelen; unsigned int foffset; unsigned char header[46]; int i; FILE *f; //ignore packages if we're going to be overwritten. struct dataset_s *set; struct output_s *out; for (set = ctx->datasets; set; set = set->next) { for (out = set->outputs; out; out = out->next) { if (!strcmp(out->filename, old->filename)) return; } } f = fopen(old->filename, "rb"); if (f) { //find end-of-central-dir //assume no comment fseek(f, -22, SEEK_END); fread(header, 1, 22, f); if (header[0] == 'P' && header[1] == 'K' && header[2] == 5 && header[3] == 6) { old->part = shortfromptr(header+4); //centraldirstart = shortfromptr(header+6); old->numfiles = shortfromptr(header+8); //numfiles_all = shortfromptr(header+10); //centaldirsize = shortfromptr(header+12); foffset = longfromptr(header+16); //commentength = shortfromptr(header+20); old->file = malloc(sizeof(*old->file)*old->numfiles); fseek(f, foffset, SEEK_SET); for(u = 0; u < old->numfiles; u++) { unsigned int extra_len, comment_len; fread(header, 1, 46, f); //zcrc @ 16 //version_madeby = shortfromptr(header+4); //version_needed = shortfromptr(header+6); //gflags = shortfromptr(header+8); old->file[u].zmethod = shortfromptr(header+10); old->file[u].dostime = shortfromptr(header+12); old->file[u].dosdate = shortfromptr(header+14); old->file[u].zcrc = longfromptr(header+16); old->file[u].zipsize = longfromptr(header+20); old->file[u].rawsize = longfromptr(header+24); namelen = shortfromptr(header+28); extra_len = shortfromptr(header+30); comment_len = shortfromptr(header+32); //disknum = shortfromptr(header+34); //iattributes = shortfromptr(header+36); //eattributes = longfromptr(header+38); //localheaderoffset = longfromptr(header+42); fread(old->file[u].name, 1, namelen, f); old->file[u].name[namelen] = 0; i = extra_len+comment_len; if (i) fseek(f, i, SEEK_CUR); } } else { fseek(f, 0, SEEK_SET); fread(header, 1, 12, f); if (header[0] == 'P' && header[1] == 'A' && header[2] == 'C' && header[3] == 'K') { unsigned int ofs = longfromptr(header+4); unsigned int dsz = longfromptr(header+8); struct { char name[56]; unsigned int size; unsigned int offset; } *files; files = malloc(dsz); fseek(f, ofs, SEEK_SET); fread(files, 1, dsz, f); old->numfiles = dsz / sizeof(*files); old->file = malloc(sizeof(*old->file)*old->numfiles); for (u = 0; u < old->numfiles; u++) { strcpy(old->file[u].name, files[u].name); old->file[u].rawsize = files[u].size; } free(files); } else ctx->messagecallback(ctx->userctx, "%s does not appear to be a package\n", old->filename); } //walk central directory fclose(f); } } static pbool PKG_FileIsModified(struct pkgctx_s *ctx, struct oldpack_s *old, struct file_s *file) { size_t u; for (u = 0; u < old->numfiles; u++) { //should check filesize etc, but rules and extension changes make that messy if (!strcmp(old->file[u].name, file->name)) { if(file->write.dosdate < old->file[u].dosdate || (file->write.dosdate == old->file[u].dosdate && file->write.dostime <= old->file[u].dostime)) { file->write.zmethod = old->file[u].zmethod; //char name[128]; file->write.zcrc = old->file[u].zcrc; file->write.zhdrofs = old->file[u].zhdrofs; file->write.pakofs = 0; file->write.rawsize = old->file[u].rawsize; file->write.zipsize = old->file[u].zipsize; file->write.dostime = old->file[u].dostime; file->write.dosdate = old->file[u].dosdate; return false; } } } return true; } static void PKG_WriteDataset(struct pkgctx_s *ctx, struct dataset_s *set) { struct class_s *cls; struct output_s *out; struct file_s *file; struct rule_s *rule; struct oldpack_s *old; size_t u; if (!ctx->readoldpacks) { ctx->readoldpacks = true; for(old = ctx->oldpacks; old; old = old->next) { //fixme: strip any wildcarded paks that match an output, to avoid weirdness. PKG_ReadPackContents(ctx, old); } for (out = set->outputs; out; out = out->next) { if(out->usediffs) { for (old = out->oldparts; old; old = old->next) { PKG_ReadPackContents(ctx, old); if (out->numparts <= old->part) out->numparts = old->part + 1; } } } } ctx->messagecallback(ctx->userctx, "Building dataset %s\n", set->name); for (cls = ctx->classes; cls; cls = cls->next) { for (out = set->outputs; out; out = out->next) { if (!strcmp(out->code, cls->outname)) break; } if (!out) //dataset doesn't name this. continue; rule = cls->defaultrule; for (u = 0; u < countof(cls->dataset); u++) { if (cls->dataset[u].set == set) { rule = cls->dataset[u].rule; break; } } if (rule && rule->dropfile) continue; for (file = cls->files; file; file = file->next) { for (old = ctx->oldpacks; old; old = old->next) { if (!PKG_FileIsModified(ctx, old, file)) break; } if (old) { ctx->messagecallback(ctx->userctx, "\t\tFile %s found inside %s\n", file->name, old->filename); file->write.zdisk = ~0u; } else { // ctx->messagecallback(ctx->userctx, "\t\tFile %s, rule %s\n", file->name, rule?rule->name:""); file->write.zdisk = out->numparts; for (old = out->oldparts; old; old = old->next) { if (!PKG_FileIsModified(ctx, old, file)) { file->write.zdisk = old->part; break; } } file->write.nextwrite = out->files; file->write.rule = rule; out->files = file; } } } for (out = set->outputs; out; out = out->next) { if (!out->files) { ctx->messagecallback(ctx->userctx, "\tOutput %s[%s] \"%s\" has no files\n", out->code, set->name, out->filename); continue; } if (ctx->test) { for (file = out->files; file; file = file->write.nextwrite) { if (file->write.rule) ctx->messagecallback(ctx->userctx, "\t\tFile %s has changed (rule %s)\n", file->name, file->write.rule->name); else ctx->messagecallback(ctx->userctx, "\t\tFile %s has changed\n", file->name); } } else { ctx->messagecallback(ctx->userctx, "\tGenerating %s[%s] \"%s\"\n", out->code, set->name, out->filename); if (PKG_WritePackageData(ctx, out, out->numparts, false)) { if(out->usediffs) PKG_WritePackageData(ctx, out, out->numparts+1, true); } } } } void Packager_WriteDataset(struct pkgctx_s *ctx, char *setname) { struct dataset_s *dataset; if (setname && strcmp(setname, "*")) { dataset = PKG_FindDataset(ctx, setname); if (dataset) PKG_WriteDataset(ctx, dataset); else ctx->messagecallback(ctx->userctx, "Dataset %s not known\n", setname); } else { for (dataset = ctx->datasets; dataset; dataset = dataset->next) PKG_WriteDataset(ctx, dataset); } } struct pkgctx_s *Packager_Create(void (*messagecallback)(void *userctx, const char *message, ...), void *userctx) { struct pkgctx_s *ctx; ctx = malloc(sizeof(*ctx)); memset(ctx, 0, sizeof(*ctx)); ctx->messagecallback = messagecallback; ctx->userctx = userctx; ctx->test = false; time(&ctx->buildtime); return ctx; } void Packager_ParseText(struct pkgctx_s *ctx, char *scripttext) { char cmd[128]; ctx->listfile = scripttext; while (PKG_GetToken(ctx, cmd, sizeof(cmd), true)) { // if (!strcmp(cmd, "dataset")) // PKG_ParseDataset(ctx); if (!strcmp(cmd, "output")) PKG_ParseOutput(ctx, false); else if (!strcmp(cmd, "diffoutput") || !strcmp(cmd, "splitoutput")) PKG_ParseOutput(ctx, true); else if (!strcmp(cmd, "inputdir")) { char old[MAX_OSPATH]; memcpy(old, ctx->sourcepath, sizeof(old)); if (PKG_GetStringToken(ctx, cmd, sizeof(cmd))) { QC_strlcat(cmd, "/", sizeof(cmd)); QCC_JoinPaths(ctx->sourcepath, sizeof(ctx->sourcepath), cmd, old); } } else if (!strcmp(cmd, "rule")) PKG_ParseRule(ctx); else if (!strcmp(cmd, "class")) PKG_ParseClass(ctx, NULL); else if (!strcmp(cmd, "ignore")||!strcmp(cmd, "oldpack")) PKG_ParseOldPack(ctx); else { char *e = strchr(cmd, ':'); if (e && !e[1]) { *e = 0; PKG_ParseClass(ctx, cmd); } else { struct class_s *c = PKG_FindClass(ctx, cmd); if (c) PKG_ParseClassFiles(ctx, c); else ctx->messagecallback(ctx->userctx, "Unrecognised token at global scope '%s'\n", cmd); } } //skip any junk while(PKG_GetToken(ctx, cmd, sizeof(cmd), false)) { if (!strcmp(cmd, ";")) break; } } } void Packager_ParseFile(struct pkgctx_s *ctx, char *scriptname) { size_t remaining = 0; char *file = qccprogfuncs->funcs.parms->ReadFile(scriptname, NULL, NULL, &remaining, true); strcpy(ctx->gamepath, scriptname); strcpy(ctx->sourcepath, scriptname); Packager_ParseText(ctx, file); free(file); } void Packager_Destroy(struct pkgctx_s *ctx) { free(ctx); } pbool Packager_CompressDir(const char *dirname, enum pkgtype_e type, void (*messagecallback)(void *userctx, const char *message, ...), void *userctx) { char *ext; char filename[MAX_QPATH]; struct pkgctx_s *ctx = Packager_Create(messagecallback, userctx); struct dataset_s *s; struct class_s *c; QC_strlcpy(ctx->sourcepath, dirname, sizeof(ctx->sourcepath)); ext = strrchr(ctx->sourcepath, '/'); if (*ctx->sourcepath && (!ext || ext[1])) QC_strlcat(ctx->sourcepath, "/", sizeof(ctx->sourcepath)); QC_strlcpy(filename, dirname, sizeof(filename)); for (;(ext = strrchr(filename, '/')) && !ext[1]; *ext = 0) ; ext = strrchr(filename, '.'); if (ext) *ext = 0; if (type == PACKAGER_PAK) QC_strlcat(filename, ".pak", sizeof(filename)); else QC_strlcat(filename, ".pk3", sizeof(filename)); s = PKG_GetDataset(ctx, "default"); PKG_CreateOutput(ctx, s, "default", filename, type == PACKAGER_PK3_SPANNED); c = malloc(sizeof(*c)); memset(c, 0, sizeof(*c)); strcpy(c->name, "file"); strcpy(c->outname, "default"); c->next = ctx->classes; ctx->classes = c; PKG_AddClassFiles(ctx, c, ""); Packager_WriteDataset(ctx, NULL); Packager_Destroy(ctx); return true; } #endif fteqcc-20251105/./qcc_pr_lex.c0000644000200200001440000053236115233070110015221 0ustar twolifeusers#if !defined(MINIMAL) && !defined(OMIT_QCC) #include "qcc.h" #include "time.h" #define MEMBERFIELDNAME "__m%s" #define STRCMP(s1,s2) (((*s1)!=(*s2)) || strcmp(s1,s2)) //saves about 2-6 out of 120 - expansion of idea from fastqcc void QCC_PR_PreProcessor_Define(pbool append); pbool QCC_PR_UndefineName(const char *name); const char *QCC_PR_CheckCompConstString(const char *def); CompilerConstant_t *QCC_PR_CheckCompConstDefined(const char *def); int QCC_PR_CheckCompConst(void); void QCC_FreeDef(QCC_def_t *def); void QCC_PR_LexComment(char **comment); extern pbool destfile_explicit; extern char destfile[1024]; //static const QCC_sref_t nullsref = {0}; #define MAXINCLUDEDIRS 8 char qccincludedir[MAXINCLUDEDIRS][256]; //the -src path, for #includes struct qccincludeonced_s { struct qccincludeonced_s *next; char name[1]; } *qccincludeonced; //the -src path, for #includes char *compilingfile; int pr_source_line; char *pr_file_p; char *pr_line_start; // start of current source line int pr_bracelevel; char *pr_token_precomment; char pr_token[8192]; token_type_t pr_token_type; int pr_token_line; int pr_token_line_last; QCC_type_t *pr_immediate_type; QCC_eval_t pr_immediate; char pr_immediate_string[8192]; size_t pr_immediate_strlen; int pr_error_count; int pr_warning_count; extern pbool expandedemptymacro; //really these should not be in here extern unsigned int locals_end, locals_start; extern QCC_type_t *pr_classtype; QCC_function_t *QCC_PR_ParseImmediateStatements (QCC_def_t *def, QCC_type_t *type, pbool dowrap); QCC_type_t *QCC_PR_FieldType (QCC_type_t *pointsto); static void Q_strlcpy(char *dest, const char *src, int sizeofdest) { if (sizeofdest) { int slen = strlen(src); slen = min((sizeofdest-1), slen); memcpy(dest, src, slen); dest[slen] = 0; } } char *pr_punctuation[] = // longer symbols must be before a shorter partial match {"&&", "||", "<=>", "<=", ">=","==", "!=", "/=", "*=", "+=", "-=", "(+)", "(-)", "|=", "&~=", "&=", "++", "--", "->", "^=", "::", ";", ",", "!", "*^", "*", "/", "(", ")", "-", "+", "=", "[", "]", "{", "}", "...", "..", ".", "><", "<<=", "<<", "<", ">>=", ">>", ">" , "?", "#" , "@", "&" , "|", "%", "^^", "^", "~", ":", NULL}; char *pr_punctuationremap[] = //a nice bit of evilness. //(+) -> |= //-> -> . //(-) -> &~= {"&&", "||", "<=>", "<=", ">=","==", "!=", "/=", "*=", "+=", "-=", "|=", "&~=", "|=", "&~=", "&=", "++", "--", ".", "^=", "::", ";", ",", "!", "*^", "*", "/", "(", ")", "-", "+", "=", "[", "]", "{", "}", "...", "..", ".", "><", "<<=", "<<", "<", ">>=", ">>", ">" , "?", "#" , "@", "&" , "|", "%", "^^", "^", "~", ":", NULL}; // simple types. function types are dynamically allocated QCC_type_t *type_void; //void QCC_type_t *type_string; //string QCC_type_t *type_float; //float QCC_type_t *type_double; //double QCC_type_t *type_vector; //vector QCC_type_t *type_entity; //entity QCC_type_t *type_field; //.void QCC_type_t *type_function; //void() QCC_type_t *type_floatfunction; //float() QCC_type_t *type_pointer; //??? * - careful with this one QCC_type_t *type_sint8; //char - these small types are technically ev_bitfield, but typedefed and aligned. QCC_type_t *type_uint8; //unsigned char QCC_type_t *type_sint16; //short QCC_type_t *type_uint16; //unsigned short QCC_type_t *type_integer; //int32 QCC_type_t *type_uint; //uint32 QCC_type_t *type_int64; //int64 QCC_type_t *type_uint64; //uint64 QCC_type_t *type_variant; //__variant QCC_type_t *type_invalid; //technically void, but shouldn't really ever be used. QCC_type_t *type_floatpointer; //float * QCC_type_t *type_intpointer; //int * QCC_type_t *type_bint; //int (0 or 1) QCC_type_t *type_bfloat; //float (0.0 or 1.0, and never -0.0) QCC_type_t *type_floatfield;// = {ev_field/*, &def_field*/, NULL, &type_float}; QCC_def_t def_ret, def_parms[MAX_PARMS]; //QCC_def_t *def_for_type[9] = {&def_void, &def_string, &def_float, &def_vector, &def_entity, &def_field, &def_function, &def_pointer, &def_integer}; void QCC_PR_LexWhitespace (pbool inhibitpreprocessor); QCC_type_t *QCC_PR_ParseEnum(pbool flags); //for compiler constants and file includes. qcc_includechunk_t *currentchunk; void QCC_PR_CloseProcessor(void) { int i; for (i = 0; i < MAXINCLUDEDIRS; i++) *qccincludedir[i] = 0; currentchunk = NULL; qccincludeonced = NULL; } void QCC_PR_AddIncludePath(const char *newinc) { int i; if (!*newinc) { newinc = "."; // QCC_PR_ParseWarning(WARN_STRINGTOOLONG, "Invalid include path."); // return; } for (i = 0; i < MAXINCLUDEDIRS; i++) { if (!*qccincludedir[i]) { pbool trunc; const char *e = newinc + strlen(newinc)-1; trunc = !QC_strlcpy(qccincludedir[i], newinc, sizeof(qccincludedir)); if (*e != '/' && *e != '\\') trunc |= !QC_strlcat(qccincludedir[i], "/", sizeof(qccincludedir)); if (trunc) { QCC_PR_ParseWarning(WARN_STRINGTOOLONG, "Include path too long."); *qccincludedir[i] = 0; } break; } if (!strcmp(qccincludedir[i], newinc)) break; } if (i == MAXINCLUDEDIRS) { QCC_PR_ParseWarning(WARN_STRINGTOOLONG, "Too many include dirs. Ignoring and hoping the stars align."); } } void QCC_PR_IncludeChunkEx (char *data, pbool duplicate, char *filename, CompilerConstant_t *cnst) { qcc_includechunk_t *chunk = qccHunkAlloc(sizeof(qcc_includechunk_t)); chunk->prev = currentchunk; currentchunk = chunk; chunk->currentdatapoint = pr_file_p; chunk->currentfilename = s_filen; chunk->currentlinenumber = pr_source_line; chunk->cnst = cnst; if( cnst ) { #if 0 s_filen = cnst->fromfile; pr_source_line = cnst->fromline; #else int b = strlen(s_filen)+1+8+strlen(cnst->name); char *p; if (b > 128) b = 128; s_filen = p = qccHunkAlloc(b); QC_snprintfz(p, b, "%s:%i:%s", chunk->currentfilename, chunk->currentlinenumber, cnst->name); pr_source_line = 1; #endif cnst->inside++; } else pr_source_line = 1; if (duplicate) { pr_file_p = qccHunkAlloc(strlen(data)+1); strcpy(pr_file_p, data); } else pr_file_p = data; chunk->datastart = pr_file_p; } void QCC_PR_IncludeChunk (char *data, pbool duplicate, char *filename) { QCC_PR_IncludeChunkEx(data, duplicate, filename, NULL); } pbool QCC_PR_UnInclude(void) { if (!currentchunk) return false; if( currentchunk->cnst ) currentchunk->cnst->inside--; pr_file_p = currentchunk->currentdatapoint; pr_source_line = currentchunk->currentlinenumber; s_filen = currentchunk->currentfilename; currentchunk = currentchunk->prev; return true; } //expresses a relative path relative to an existing FILEname. any directories in base must be / terminated properly. void QCC_JoinPaths(char *fullname, size_t fullnamesize, const char *newfile, const char *base) { char *end; if (*newfile == '/' || *newfile == '\\') { //its an absolute path... QC_strlcpy(fullname, newfile, fullnamesize); return; } QC_strlcpy(fullname, base, fullnamesize); end = fullname+strlen(fullname); while (end > fullname) { end--; if (*end == '/' || *end == '\\') { end++; break; } } QC_strlcpy(end, newfile, fullnamesize - (end-fullname)); //FIXME: do we want to convert /segment/../ into just / ? //fteqw might insist on it for its filesystem sandboxing. //but it breaks symlink weirdness (itself a possible security hole). //should probably be a separate function. } extern char qccmsourcedir[]; //also meant to include it. void QCC_FindBestInclude(char *newfile, char *currentfile, pbool includetype) { struct qccincludeonced_s *onced; int includepath = 0; char fullname[1024]; if (!*newfile) return; while(1) { if (includepath) { if (includepath > MAXINCLUDEDIRS || !*qccincludedir[includepath-1]) QCC_Error(ERR_COULDNTOPENFILE, "Couldn't open file %s", newfile); currentfile = qccincludedir[includepath-1]; } QCC_JoinPaths(fullname, sizeof(fullname), newfile, currentfile); { extern progfuncs_t *qccprogfuncs; if (qccprogfuncs->funcs.parms->FileSize(fullname) == -1) { includepath++; continue; } } break; } for(onced = qccincludeonced; onced; onced = onced->next) { if (!strcmp(onced->name, fullname)) return; } if (includetype && verbose >= VERBOSE_PROGRESS) { if (includetype == 2) { if (autoprototype) externs->Printf("prototyping %s\n", fullname); else externs->Printf("compiling %s\n", fullname); } else { if (autoprototype) externs->Printf("prototyping include %s\n", fullname); else externs->Printf("including %s\n", fullname); } } QCC_Include(fullname, includetype); } pbool defaultnoref; pbool defaultnosave; pbool defaultstatic; int ForcedCRC; float qcc_framerate; int QCC_PR_LexInteger (void); void QCC_AddFile (char *filename); void QCC_PR_LexString (void); pbool QCC_PR_SimpleGetToken (void); pbool QCC_PR_SimpleGetString(void); #define PPI_VALUE 0 #define PPI_NOT 1 #define PPI_DEFINED 2 #define PPI_COMPARISON 3 #define PPI_LOGICAL 4 #define PPI_TOPLEVEL 5 static int ParsePrecompilerIf(int level) { CompilerConstant_t *c; int eval = 0; // pbool notted = false; //single term end-of-chain if (level == PPI_VALUE) { /*skip whitespace*/ while (*pr_file_p && qcc_iswhite(*pr_file_p) && *pr_file_p != '\n') { pr_file_p++; } if (*pr_file_p == '(') { //try brackets pr_file_p++; eval = ParsePrecompilerIf(PPI_TOPLEVEL); while (*pr_file_p == ' ' || *pr_file_p == '\t') pr_file_p++; if (*pr_file_p != ')') QCC_PR_ParseError(ERR_EXPECTED, "unclosed bracket condition\n"); pr_file_p++; } else if (*pr_file_p == '!') { //try brackets pr_file_p++; eval = !ParsePrecompilerIf(PPI_NOT); } else { //simple token... if (!strncmp(pr_file_p, "defined", 7)) { pbool brackets; pr_file_p+=7; while (*pr_file_p == ' ' || *pr_file_p == '\t') pr_file_p++; brackets = *pr_file_p == '('; pr_file_p += brackets; QCC_PR_SimpleGetToken(); eval = !!QCC_PR_CheckCompConstDefined(pr_token); if (brackets) { while (*pr_file_p == ' ' || *pr_file_p == '\t') pr_file_p++; if (*pr_file_p != ')') QCC_PR_ParseError(ERR_EXPECTED, "unclosed defined condition\n"); pr_file_p++; } } else { if (!QCC_PR_SimpleGetToken()) QCC_PR_ParseError(ERR_EXPECTED, "unexpected end-of-line\n"); c = QCC_PR_CheckCompConstDefined(pr_token); if (!c) eval = atoi(pr_token); else eval = atoi(c->value); } } return eval; } eval = ParsePrecompilerIf(level-1); while (*pr_file_p && qcc_iswhite(*pr_file_p) && *pr_file_p != '\n') { pr_file_p++; } switch(level) { case PPI_LOGICAL: if (!strncmp(pr_file_p, "||", 2)) { pr_file_p+=2; eval = ParsePrecompilerIf(level)||eval; } else if (!strncmp(pr_file_p, "&&", 2)) { pr_file_p+=2; eval = ParsePrecompilerIf(level)&&eval; } break; case PPI_COMPARISON: if (!strncmp(pr_file_p, "<=", 2)) { pr_file_p+=2; eval = eval <= ParsePrecompilerIf(level); } else if (!strncmp(pr_file_p, ">=", 2)) { pr_file_p += 2; eval = eval >= ParsePrecompilerIf(level); } else if (!strncmp(pr_file_p, "<", 1)) { pr_file_p += 1; eval = eval < ParsePrecompilerIf(level); } else if (!strncmp(pr_file_p, ">", 1)) { pr_file_p += 1; eval = eval > ParsePrecompilerIf(level); } else if (!strncmp(pr_file_p, "!=", 2)) { pr_file_p += 2; eval = eval != ParsePrecompilerIf(level); } else if (!strncmp(pr_file_p, "==", 2)) { pr_file_p += 2; eval = eval == ParsePrecompilerIf(level); } break; } return eval; } struct deflist_s { char *buffer; size_t length; size_t buffersize; }; static void QCC_PR_GetDefinesListEnumerate(void *vctx, void *data) { struct deflist_s *ctx = vctx; CompilerConstant_t *def = data; char term[8192]; size_t termsize; pbool success = true; QC_snprintfz(term, sizeof(term), "\n%s", def->name); if (def->numparams >= 0) { int i; success &= QC_strlcat(term, "(", sizeof(term)); for (i = 0; i < def->numparams; i++) { if (i) success &= QC_strlcat(term, ",", sizeof(term)); success &= QC_strlcat(term, def->params[i], sizeof(term)); } success &= QC_strlcat(term, ")", sizeof(term)); } if (def->value && *def->value) { char *o, *i; success &= QC_strlcat(term, "=", sizeof(term)); //annoying logic to skip whitespace... hopefully it won't fuck stuff up too much. for (o = term+strlen(term), i = def->value; o < term + sizeof(term)-1 && *i; ) { if (*i == ' ' || *i == '\t' || *i == '\n' || *i == '\r') i++; else *o++ = *i++; } *o = 0; } if (!success) //this define was too long. don't show truncated stuff. return; termsize = strlen(term); if (ctx->length + termsize+1 > ctx->buffersize) { ctx->buffersize = (ctx->length + termsize+1)*2; ctx->buffer = realloc(ctx->buffer, ctx->buffersize); } memcpy(ctx->buffer+ctx->length, term, termsize); ctx->length += termsize; ctx->buffer[ctx->length] = 0; } char *QCC_PR_GetDefinesList(void) { struct deflist_s ctx = {NULL}; Hash_Enumerate(&compconstantstable, QCC_PR_GetDefinesListEnumerate, &ctx); return ctx.buffer; } //returns true if it was white/comments only. false if there was actual text that was skipped. static void QCC_PR_SkipToEndOfLine(pbool errorifnonwhite) { pbool handleccomments = true; while(*pr_file_p != '\n' && *pr_file_p != '\0') //read on until the end of the line { if (*pr_file_p == '/' && pr_file_p[1] == '*' && handleccomments) { pr_file_p += 2; while(*pr_file_p) { if (*pr_file_p == '*' && pr_file_p[1] == '/') { pr_file_p+=2; break; } if (*pr_file_p == '\n') pr_source_line++; pr_file_p++; } } else if (*pr_file_p == '/' && pr_file_p[1] == '/' && handleccomments) { handleccomments = false; pr_file_p += 2; /*while(*pr_file_p) { if (*pr_file_p == '\n') break; pr_file_p++; }*/ } else if (*pr_file_p == '\\' && pr_file_p[1] == '\r' && pr_file_p[2] == '\n') { /*windows endings*/ pr_file_p+=3; pr_source_line++; } else if (*pr_file_p == '\"' && handleccomments) { if (errorifnonwhite) { errorifnonwhite = false; QCC_PR_ParseWarning (ERR_UNKNOWNPUCTUATION, "unexpected tokens at end of line"); } pr_file_p++; while (*pr_file_p) { if (*pr_file_p == '\n') break; //this text is junk/ignored, so ignore the obvious error here. else if (*pr_file_p == '\"') { pr_file_p++; break; } else if (*pr_file_p == '\\' && pr_file_p[1] == '\"') pr_file_p+=2; //don't trip on "\"/*" else if (*pr_file_p == '\\' && pr_file_p[1] == '\\') pr_file_p+=2; //don't trip on "\\"//"foo else pr_file_p++; //any other \ should be part of the actual string, which we don't care about here } } else if (*pr_file_p == '\\' && pr_file_p[1] == '\n') { /*linux endings*/ pr_file_p+=2; pr_source_line++; } else { if (errorifnonwhite && handleccomments && !qcc_iswhite(*pr_file_p)) { errorifnonwhite = false; QCC_PR_ParseWarning(ERR_UNKNOWNPUCTUATION, "unexpected tokens at end of line"); } pr_file_p++; } } } //if hadtrue, then we allow elses, otherwise we skip them. static pbool QCC_PR_FalsePreProcessorIf(pbool hadtrue, int originalline) { int eval; int level = 1; while (1) { while(*pr_file_p && (*pr_file_p==' ' || *pr_file_p == '\t')) pr_file_p++; if (!*pr_file_p) { pr_source_line = originalline; QCC_PR_ParseError (ERR_NOENDIF, "#if with no endif"); } if (*pr_file_p == '#') { pr_file_p++; while(*pr_file_p==' ' || *pr_file_p == '\t') pr_file_p++; if (!strncmp(pr_file_p, "endif", 5)) level--; if (!strncmp(pr_file_p, "if", 2)) level++; if (!hadtrue && !strncmp(pr_file_p, "else", 4) && level == 1) { pr_file_p+=4; QCC_PR_SkipToEndOfLine(true); return true; } if (!hadtrue && !strncmp(pr_file_p, "elif", 4) && level == 1) { // QCC_PR_ParseError(ERR_UNKNOWNPUCTUATION, "#elif not supported\n"); pr_file_p += 4; if (!strncmp(pr_file_p, "def", 3)) { eval = 1; pr_file_p += 3; } else if (!strncmp(pr_file_p, "ndef", 4)) { eval = 0; pr_file_p += 4; } else eval = 2; if (*pr_file_p != ' ' && *pr_file_p != '\t') QCC_PR_ParseError(ERR_UNKNOWNPUCTUATION, "malformed #elif\n"); if (eval == 2) eval = ParsePrecompilerIf(PPI_TOPLEVEL); else { QCC_PR_SimpleGetToken (); if (eval) eval = !!QCC_PR_CheckCompConstDefined(pr_token); else eval = !QCC_PR_CheckCompConstDefined(pr_token); } if (eval) { QCC_PR_SkipToEndOfLine(true); return true; } } } QCC_PR_SkipToEndOfLine(false); if (level <= 0) return false; pr_file_p++; //next line pr_source_line++; } } #if 0 static void QCC_PR_PackagerMessage(void *userctx, char *message, ...) { va_list argptr; char string[1024]; va_start (argptr,message); QC_vsnprintf (string,sizeof(string)-1,message,argptr); va_end (argptr); externs->Printf ("%s", string); } #endif /* ============== QCC_PR_Precompiler ============== Runs precompiler stage */ static pbool QCC_PR_Precompiler(void) { char msg[1024]; int ifmode; int a; static int ifs = 0; pbool eval = false; if (*pr_file_p == '#') { char *directive; for (directive = pr_file_p+1; *directive; directive++) //so # define works { if (*directive == '\r' || *directive == '\n') QCC_PR_ParseError(ERR_UNKNOWNPUCTUATION, "Hanging # with no directive\n"); if (*directive > ' ') break; } if (!strncmp(directive, "define", 6)) { pr_file_p = directive; QCC_PR_PreProcessor_Define(false); QCC_PR_SkipToEndOfLine(true); } else if (!strncmp(directive, "append", 6)) { pr_file_p = directive; QCC_PR_PreProcessor_Define(true); QCC_PR_SkipToEndOfLine(true); } else if (!strncmp(directive, "undef", 5)) { pr_file_p = directive+5; while(qcc_iswhitesameline(*pr_file_p)) pr_file_p++; QCC_PR_SimpleGetToken (); QCC_PR_UndefineName(pr_token); // QCC_PR_ConditionCompilation(); QCC_PR_SkipToEndOfLine(true); } else if (!strncmp(directive, "if", 2)) { int originalline = pr_source_line; pr_file_p = directive+2; if (!strncmp(pr_file_p, "def", 3)) { ifmode = 0; pr_file_p+=3; } else if (!strncmp(pr_file_p, "ndef", 4)) { ifmode = 1; pr_file_p+=4; } else { ifmode = 2; pr_file_p+=0; //QCC_PR_ParseError("bad \"#if\" type"); } if (!qcc_iswhite(*pr_file_p)) { pr_file_p = directive; QCC_PR_SimpleGetToken (); QCC_PR_ParseWarning(WARN_BADPRAGMA, "Unknown pragma \'%s\'", qcc_token); } else { if (ifmode == 2) { eval = ParsePrecompilerIf(PPI_TOPLEVEL); } else { QCC_PR_SimpleGetToken (); // if (!STRCMP(pr_token, "COOP_MODE")) // eval = false; if (QCC_PR_CheckCompConstDefined(pr_token)) eval = true; if (ifmode == 1) eval = eval?false:true; } QCC_PR_SkipToEndOfLine(true); if (eval) ifs+=1; else ifs += QCC_PR_FalsePreProcessorIf(false, originalline); } } else if (!strncmp(directive, "else", 4) || !strncmp(directive, "elif", 4)) { int originalline = pr_source_line; if (!ifs) QCC_PR_ParseError(ERR_UNKNOWNPUCTUATION, "#else outside of #if\n"); ifs -= 1; pr_file_p = directive+4; if (!strncmp(directive, "elif", 4)) QCC_PR_SkipToEndOfLine(false); else QCC_PR_SkipToEndOfLine(true); ifs += QCC_PR_FalsePreProcessorIf(true, originalline); } else if (!strncmp(directive, "endif", 5)) { pr_file_p = directive+5; QCC_PR_SkipToEndOfLine(true); if (ifs <= 0) QCC_PR_ParseError(ERR_NOPRECOMPILERIF, "unmatched #endif"); else ifs-=1; } else if (!strncmp(directive, "eof", 3)) { pr_file_p = NULL; return true; } else if (!strncmp(directive, "error", 5)) { pr_file_p = directive+5; for (a = 0; a < sizeof(msg)-1 && pr_file_p[a] != '\t' && pr_file_p[a] != '\n' && pr_file_p[a] != '\0'; a++) msg[a] = pr_file_p[a]; msg[a] = '\0'; QCC_PR_SkipToEndOfLine(false); QCC_PR_ParseError(ERR_HASHERROR, "#Error: %s", msg); } else if (!strncmp(directive, "warning", 7)) { pr_file_p = directive+7; for (a = 0; a < 1023 && pr_file_p[a] != '\r' && pr_file_p[a] != '\n' && pr_file_p[a] != '\0'; a++) msg[a] = pr_file_p[a]; msg[a] = '\0'; QCC_PR_SkipToEndOfLine(false); QCC_PR_ParseWarning(WARN_PRECOMPILERMESSAGE, "#warning: %s", msg); } else if (!strncmp(directive, "message", 7)) { pr_file_p = directive+7; for (a = 0; a < sizeof(msg)-1 && pr_file_p[a] != '\r' && pr_file_p[a] != '\n' && pr_file_p[a] != '\0'; a++) msg[a] = pr_file_p[a]; msg[a] = '\0'; if (flag_msvcstyle) externs->Printf ("%s(%i) : #message: %s\n", s_filen, pr_source_line, msg); else externs->Printf ("%s:%i: #message: %s\n", s_filen, pr_source_line, msg); QCC_PR_SkipToEndOfLine(false); } else if (!strncmp(directive, "copyright", 9)) { pr_file_p = directive+9; for (a = 0; a < sizeof(msg)-1 && qcc_iswhite(pr_file_p[a]) && pr_file_p[a] != '\0'; a++) msg[a] = pr_file_p[a]; msg[a] = '\0'; QCC_PR_SkipToEndOfLine(false); if (strlen(msg) >= sizeof(QCC_copyright)) QCC_PR_ParseWarning(WARN_STRINGTOOLONG, "Copyright message is too long\n"); QC_strlcpy(QCC_copyright, msg, sizeof(QCC_copyright)-1); } else if (!strncmp(directive, "package", 7)) { pr_file_p=directive+7; QCC_PR_SkipToEndOfLine(true); #if 0 if (!autoprototype) { struct pkgctx_s *ctx = Packager_Create(QCC_PR_PackagerMessage, NULL); Packager_ParseText(ctx, pr_file_p); Packager_WriteDataset(ctx, NULL); Packager_Destroy(ctx); } #endif pr_file_p += strlen(pr_file_p); } else if (!strncmp(directive, "pack", 4)) { ifmode = 0; pr_file_p=directive+4; if (!strncmp(pr_file_p, "id", 2)) pr_file_p+=3; else { ifmode = QCC_PR_LexInteger(); if (ifmode == 0) ifmode = 1; pr_file_p++; } for (a = 0; a < sizeof(msg)-1 && qcc_iswhite(pr_file_p[a]) && pr_file_p[a] != '\0'; a++) msg[a] = pr_file_p[a]; msg[a] = '\0'; QCC_PR_SkipToEndOfLine(true); if (ifmode == 0) QCC_packid = atoi(msg); else if (ifmode <= 5) strcpy(QCC_Packname[ifmode-1], msg); else QCC_PR_ParseError(ERR_TOOMANYPACKFILES, "No more than 5 packs are allowed"); } else if (!strncmp(directive, "forcecrc", 8)) { pr_file_p=directive+8; ForcedCRC = QCC_PR_LexInteger(); QCC_PR_SkipToEndOfLine(true); } else if (!strncmp(directive, "merge", 5)) { pr_file_p=directive+5; while(qcc_iswhitesameline(*pr_file_p)) pr_file_p++; QCC_PR_SimpleGetString(); externs->Printf("Merging from %s\n", pr_token); QCC_ImportProgs(pr_token); if (!*destfile && !destfile_explicit) { QCC_JoinPaths(destfile, sizeof(destfile), pr_token, compilingfile); externs->Printf("Outputfile: %s\n", destfile); } QCC_PR_SkipToEndOfLine(true); } else if (!strncmp(directive, "includelist", 11)) { int defines=0; pr_file_p=directive+11; QCC_PR_SkipToEndOfLine(true); while(1) { QCC_PR_LexWhitespace(false); if (!flag_hashonly && QCC_PR_CheckCompConst()) { defines++; continue; } if (!QCC_PR_SimpleGetToken()) { if (!*pr_file_p) { if (defines>0) { defines--; QCC_PR_UnInclude(); continue; } QCC_Error(ERR_EOF, "eof in includelist"); } else { pr_file_p++; pr_source_line++; } continue; } if (!strcmp(pr_token, "#endlist")) { QCC_PR_SkipToEndOfLine(true); break; } if (pr_error_count) //if we had an error, don't keep including more stuff that'll hide the actual error. { pr_file_p = ""; QCC_PR_ParseError(0, NULL); } QCC_FindBestInclude(pr_token, compilingfile, true); if (*pr_file_p == '\r') pr_file_p++; // QCC_PR_SkipToEndOfLine(true); } } else if (!strncmp(directive, "include", 7)) { char sm; pr_file_p=directive+7; while(qcc_iswhitesameline(*pr_file_p)) pr_file_p++; msg[0] = '\0'; if (*pr_file_p == '\"') sm = '\"'; else if (*pr_file_p == '<') sm = '>'; else { QCC_PR_ParseError(0, "Not a string literal (on a #include)"); sm = 0; } pr_file_p++; a=0; while(*pr_file_p != sm) { if (*pr_file_p == '\n') { QCC_PR_ParseError(0, "#include continued over line boundry\n"); break; } msg[a++] = *pr_file_p; pr_file_p++; } msg[a] = 0; pr_file_p++; if (pr_error_count) //if we had an error, don't keep including more stuff that'll hide the actual error. { pr_file_p = ""; QCC_PR_ParseError(0, NULL); } QCC_PR_SkipToEndOfLine(true); QCC_FindBestInclude(msg, compilingfile, false); QCC_PR_Precompiler(); //preprocessor directives normally require a leading newline to be considered a directive. we won't have one in the new file so lets just do it explicitly. } else if (!strncmp(directive, "datafile", 8)) { pr_file_p=directive+8; while(qcc_iswhitesameline(*pr_file_p)) pr_file_p++; QCC_PR_SimpleGetString(); externs->Printf("Including datafile: %s\n", pr_token); QCC_AddFile(pr_token); pr_file_p++; for (a = 0; a < sizeof(msg)-1 && pr_file_p[a] != '\n' && pr_file_p[a] != '\0'; a++) msg[a] = pr_file_p[a]; msg[a-1] = '\0'; while(*pr_file_p != '\n' && *pr_file_p != '\0') //read on until the end of the line { pr_file_p++; } } else if (!strncmp(directive, "output", 6)) { pr_file_p=directive+6; while(qcc_iswhitesameline(*pr_file_p)) pr_file_p++; QCC_PR_SimpleGetString(); if (!destfile_explicit) { QCC_JoinPaths(destfile, sizeof(destfile), pr_token, compilingfile); externs->Printf("Outputfile: %s\n", pr_token); } QCC_PR_SkipToEndOfLine(true); } else if (!strncmp(directive, "pragma", 6)) { pr_file_p=directive+6; while(qcc_iswhitesameline(*pr_file_p)) pr_file_p++; qcc_token[0] = '\0'; for(a = 0; *pr_file_p != '\n' && *pr_file_p != '\0'; pr_file_p++) //read on until the end of the line { if ((*pr_file_p == ' ' || *pr_file_p == '\t'|| *pr_file_p == '(') && !*qcc_token) { msg[a] = '\0'; strcpy(qcc_token, msg); a=0; if (*pr_file_p != '(') continue; } msg[a++] = *pr_file_p; } msg[a] = '\0'; { char *end; for (end = msg + a-1; end>=msg && qcc_iswhite(*end); end--) *end = '\0'; } if (!*qcc_token) { strcpy(qcc_token, msg); msg[0] = '\0'; } { char *end; for (end = msg + a-1; end>=msg && qcc_iswhite(*end); end--) *end = '\0'; } if (!QC_strcasecmp(qcc_token, "DONT_COMPILE_THIS_FILE")) { QCC_PR_LexWhitespace(false); while (*pr_file_p) { if (!qcc_iswhite(*pr_file_p)) pr_file_p++; QCC_PR_LexWhitespace(false); } } else if (!QC_strcasecmp(qcc_token, "COPYRIGHT")) { char *e = strrchr(msg+1, '\"'); if (*msg == '\"' && e && e != msg) { //FIXME: handle \ns memmove(msg, msg+1, e-(msg+1)); msg[e-(msg+1)] = 0; } if (!QC_strlcpy(QCC_copyright, msg, sizeof(QCC_copyright))) QCC_PR_ParseWarning(WARN_STRINGTOOLONG, "Copyright message is too long\n"); } else if (!QC_strcasecmp(qcc_token, "compress")) { extern pbool compressoutput; compressoutput = atoi(msg); } else if (!QC_strcasecmp(qcc_token, "forcecrc")) { ForcedCRC = atoi(msg); } else if (!QC_strcasecmp(qcc_token, "framerate")) { qcc_framerate = atof(msg); if (qcc_framerate < 0) qcc_framerate = 0; } else if (!QC_strcasecmp(qcc_token, "once")) { struct qccincludeonced_s *onced = qccHunkAlloc(sizeof(*onced) + strlen(compilingfile)); strcpy(onced->name, compilingfile); onced->next = qccincludeonced; qccincludeonced = onced; } else if (!QC_strcasecmp(qcc_token, "file")) { //#pragma file(foobar.qc) if (!flag_nopragmafileline) { char *e; char *m = msg; if (*m == '(') { m++; e = strchr(m, ')'); if (e) *e = 0; } s_filen = e = qccHunkAlloc(strlen(m)+1); strcpy(e, m); if (opt_filenames) { optres_filenames += strlen(m); s_filed = 0; } else s_filed = QCC_CopyString (m); } } else if (!QC_strcasecmp(qcc_token, "line")) { //#pragma line(666) if (!flag_nopragmafileline) { char *m = msg; if (*m == '(') m++; pr_source_line = strtoul(m, &m, 0)-1; } } else if (!QC_strcasecmp(qcc_token, "includedir")) { char newinc[1024]; int i; QCC_COM_Parse(msg); if (*qcc_token) { i = qcc_token[strlen(qcc_token)-1]; if (i != '/' && i != '\\') QC_strlcat(qcc_token, "/", sizeof(qcc_token)); } QCC_JoinPaths(newinc, sizeof(newinc), qcc_token, compilingfile); QCC_PR_AddIncludePath(newinc); } else if (!QC_strcasecmp(qcc_token, "noref")) defaultnoref = !!atoi(msg); else if (!QC_strcasecmp(qcc_token, "nosave")) defaultnosave = !!atoi(msg); else if (!QC_strcasecmp(qcc_token, "defaultstatic")) defaultstatic = !!atoi(msg); else if (!QC_strcasecmp(qcc_token, "autoproto")) { if (!autoprototyped) { if (numpr_globals != RESERVED_OFS) QCC_PR_ParseWarning(WARN_BADPRAGMA, "#pragma autoproto must appear before any definitions"); else autoprototype = *msg?!!atoi(msg):true; } } else if (!QC_strcasecmp(qcc_token, "wrasm")) { pbool on = atoi(msg); if (asmfile && !on) { fclose(asmfile); asmfile = NULL; } if (!asmfile && on) { if (asmfilebegun) asmfile = fopen("qc.asm", "ab"); else asmfile = fopen("qc.asm", "wb"); if (asmfile) asmfilebegun = true; } } else if (!QC_strcasecmp(qcc_token, "optimise") || !QC_strcasecmp(qcc_token, "optimize")) //bloomin' americans. { int o; extern pbool qcc_nopragmaoptimise; if (pr_scope) QCC_PR_ParseWarning(WARN_BADPRAGMA, "pragma %s: unable to change optimisation options mid-function", qcc_token); else if (*msg >= '0' && *msg <= '3') { int lev = atoi(msg); pbool state; int once = false; for (o = 0; optimisations[o].enabled; o++) { state = optimisations[o].optimisationlevel <= lev; if (qcc_nopragmaoptimise && *optimisations[o].enabled != state) { if (!once++) QCC_PR_ParseWarning(WARN_BADPRAGMA, "pragma %s %s: overriden by commandline", qcc_token, msg); } else *optimisations[o].enabled = state; } } else if (!strnicmp(msg, "addon", 5) || !strnicmp(msg, "mutator", 7)) { int lev = 2; pbool state = false; for (o = 0; optimisations[o].enabled; o++) { if (optimisations[o].optimisationlevel > lev) { if (qcc_nopragmaoptimise && *optimisations[o].enabled != state) QCC_PR_ParseWarning(WARN_IGNORECOMMANDLINE, "pragma %s %s: disabling %s", qcc_token, msg, optimisations[o].fullname); *optimisations[o].enabled = state; } } } else { char *opt = msg; pbool state = true; if (!strnicmp(msg, "no-", 3)) { state = false; opt += 3; } for (o = 0; optimisations[o].enabled; o++) if ((*optimisations[o].abbrev && !stricmp(opt, optimisations[o].abbrev)) || !stricmp(opt, optimisations[o].fullname)) { if (qcc_nopragmaoptimise && *optimisations[o].enabled != state) QCC_PR_ParseWarning(WARN_BADPRAGMA, "pragma %s %s: overriden by commandline", qcc_token, optimisations[o].fullname); else *optimisations[o].enabled = state; break; } if (!optimisations[o].enabled) QCC_PR_ParseWarning(WARN_BADPRAGMA, "pragma %s: %s unsupported", qcc_token, opt); } } else if (!QC_strcasecmp(qcc_token, "sourcefile")) { char *s = msg; while ((s = QCC_COM_Parse(s))) QCC_RegisterSourceFile(qcc_token); } else if (!QC_strcasecmp(qcc_token, "TARGET")) { QCC_COM_Parse(msg); if (!QCC_OPCodeSetTargetName(qcc_token)) QCC_PR_ParseWarning(WARN_BADTARGET, "Unknown target \'%s\'. Ignored.\nValid targets are: ID, HEXEN2, FTE, FTEH2, KK7, DP(patched)", qcc_token); } else if (!QC_strcasecmp(qcc_token, "PROGS_SRC")) { //doesn't make sense, but silenced if you are switching between using a certain precompiler app used with CuTF. } else if (!QC_strcasecmp(qcc_token, "PROGS_DAT")) { //doesn't make sence, but silenced if you are switching between using a certain precompiler app used with CuTF. char olddest[1024]; Q_strlcpy(olddest, destfile, sizeof(olddest)); QCC_COM_Parse(msg); if (!destfile_explicit) //if output file is named on the commandline, don't change it mid-compile QCC_JoinPaths(destfile, sizeof(destfile), qcc_token, compilingfile); if (strcmp(destfile, olddest)) externs->Printf("Outputfile: %s\n", destfile); } else if (!QC_strcasecmp(qcc_token, "opcode")) { int st; char *s = QCC_COM_Parse(msg); if (!QC_strcasecmp(qcc_token, "enable") || !QC_strcasecmp(qcc_token, "on")) st = 1; else if (!QC_strcasecmp(qcc_token, "disable") || !QC_strcasecmp(qcc_token, "off")) st = 0; else { QCC_PR_ParseWarning(WARN_BADPRAGMA, "opcode state not recognised"); st = -1; } if (st >= 0) { int f; while ((s = QCC_COM_Parse(s))) { for (f = 0; pr_opcodes[f].opname; f++) { if (!QC_strcasecmp(pr_opcodes[f].opname, qcc_token)) { if (f >= OP_NUMREALOPS) QCC_PR_ParseWarning(WARN_BADPRAGMA, "opcode %s is internal", qcc_token); //these will change with later opcodes, do not allow them to be written into the output. else if (st) pr_opcodes[f].flags |= OPF_VALID; else pr_opcodes[f].flags &= ~OPF_VALID; break; } } if (!pr_opcodes[f].opname) QCC_PR_ParseWarning(WARN_BADPRAGMA, "opcode %s not recognised", qcc_token); } } } else if (!QC_strcasecmp(qcc_token, "keyword") || !QC_strcasecmp(qcc_token, "flag")) { char *s; int st; s = QCC_COM_Parse(msg); if (!QC_strcasecmp(qcc_token, "enable") || !QC_strcasecmp(qcc_token, "on")) st = 1; else if (!QC_strcasecmp(qcc_token, "disable") || !QC_strcasecmp(qcc_token, "off")) st = 0; else { QCC_PR_ParseWarning(WARN_BADPRAGMA, "compiler flag state not recognised"); st = -1; } if (st >= 0) { int f; while ((s = QCC_COM_Parse(s))) { for (f = 0; compiler_flag[f].enabled; f++) { if (!QC_strcasecmp(compiler_flag[f].abbrev, qcc_token)) { if (compiler_flag[f].flags & FLAG_MIDCOMPILE) { *compiler_flag[f].enabled = st; if (compiler_flag[f].enabled == &flag_cpriority) QCC_PrioritiseOpcodes(); } else QCC_PR_ParseWarning(WARN_BADPRAGMA, "Cannot enable/disable keyword/flag via a pragma"); break; } } if (!compiler_flag[f].enabled) QCC_PR_ParseWarning(WARN_BADPRAGMA, "keyword/flag %s not recognised", qcc_token); } } } else if (!QC_strcasecmp(qcc_token, "warning")) { int st; char *s; s = QCC_COM_Parse(msg); if (!stricmp(qcc_token, "enable") || !stricmp(qcc_token, "on")) st = WA_WARN; else if (!stricmp(qcc_token, "disable") || !stricmp(qcc_token, "off") || !stricmp(qcc_token, "ignore")) st = WA_IGNORE; else if (!stricmp(qcc_token, "error")) st = WA_ERROR; else if (!stricmp(qcc_token, "toggle")) st = 3; else { QCC_PR_ParseWarning(WARN_BADPRAGMA, "warning state not recognised"); st = -1; } if (st>=0) { int wn; while ((s = QCC_COM_Parse(s))) { wn = QCC_WarningForName(qcc_token); if (wn < 0) QCC_PR_ParseWarning(WARN_BADPRAGMA, "warning id not recognised"); else { if (st == 3) //toggle qccwarningaction[wn] = !!qccwarningaction[wn]; else qccwarningaction[wn] = st; } } } } else { QCC_PR_SkipToEndOfLine(false); QCC_PR_ParseWarning(WARN_BADPRAGMA, "Unknown pragma \'%s\'", qcc_token); } QCC_PR_SkipToEndOfLine(true); } return true; } return false; } /* ============== PR_NewLine Call at start of file and when *pr_file_p == '\n' ============== */ void QCC_PR_NewLine (pbool incomment) { pr_source_line++; pr_line_start = pr_file_p; while(*pr_file_p==' ' || *pr_file_p == '\t') pr_file_p++; if (incomment) //no constants if in a comment. { } else if (QCC_PR_Precompiler()) { } // if (pr_dumpasm) // PR_PrintNextLine (); } /* ============== PR_LexString Parses a quoted string ============== */ int QCC_PR_LexEscapedCodepoint(void) { //for "\foo" or '\foo' handling. //caller will have read the \ already. int t; int c = *pr_file_p++; if (!c) QCC_PR_ParseError (ERR_EOF, "EOF inside quote"); if (c == 'n') c = '\n'; else if (c == 'r') c = '\r'; else if (c == '#') //avoid preqcc expansion in strings. c = '#'; else if (c == '"') c = '"'; else if (c == 't') c = '\t'; //tab else if (c == 'a') c = '\a'; //bell else if (c == 'v') c = '\v'; //vertical tab else if (c == 'f') c = '\f'; //form feed // else if (c == 's' || c == 'b') // c = 0; //invalid... //else if (c == 'b') // c = '\b'; else if (c == '[') c = 0xe010; //quake specific else if (c == ']') c = 0xe011; //quake specific else if (c == '{') { int d; c = 0; if (*pr_file_p == 'x') { pr_file_p++; while ((d = *pr_file_p++) != '}') { if (d >= '0' && d <= '9') c = c * 16 + d - '0'; else if (d >= 'a' && d <= 'f') c = c * 16 + 10+d - 'a'; else if (d >= 'A' && d <= 'F') c = c * 16 + 10+d - 'A'; else QCC_PR_ParseError(ERR_BADCHARACTERCODE, "Bad character code"); } } else { while ((d = *pr_file_p++) != '}') { if (d >= '0' && d <= '9') c = c * 10 + d - '0'; else QCC_PR_ParseError(ERR_BADCHARACTERCODE, "Bad character code"); } } } else if (c == '.') c = 0xe01c; else if (c == '<') c = 0xe01d; //separator start else if (c == '-') c = 0xe01e; //separator middle else if (c == '>') c = 0xe01f; //separator end else if (c == '(') c = 0xe080; //slider start else if (c == '=') c = 0xe081; //slider middle else if (c == ')') c = 0xe082; //slider end else if (c == '+') c = 0xe083; //slider box else if (c == 'u' || c == 'U') { //lower case u specifies exactly 4 nibbles. //upper case U specifies exactly 8 nibbles. unsigned int nibbles = (c=='u')?4:8; c = 0; while (nibbles --> 0) { t = (unsigned char)*pr_file_p; if (t >= '0' && t <= '9') c = (c*16) + (t - '0'); else if (t >= 'A' && t <= 'F') c = (c*16) + (t - 'A') + 10; else if (t >= 'a' && t <= 'f') c = (c*16) + (t - 'a') + 10; else break; pr_file_p++; } if (nibbles) QCC_PR_ParseWarning(ERR_BADCHARACTERCODE, "Unicode character terminated unexpectedly"); } else if (c == 'x' || c == 'X') { int d; c = 0; d = (unsigned char)*pr_file_p++; if (d >= '0' && d <= '9') c += d - '0'; else if (d >= 'A' && d <= 'F') c += d - 'A' + 10; else if (d >= 'a' && d <= 'f') c += d - 'a' + 10; else QCC_PR_ParseError(ERR_BADCHARACTERCODE, "Bad character code"); c *= 16; d = (unsigned char)*pr_file_p++; if (d >= '0' && d <= '9') c += d - '0'; else if (d >= 'A' && d <= 'F') c += d - 'A' + 10; else if (d >= 'a' && d <= 'f') c += d - 'a' + 10; else { //oops. only one char valid... c >>= 4; pr_file_p --; } } else if (c == '\\') c = '\\'; else if (c == '?') //triglyphs are dumb. c = '?'; else if (c == '\'') c = '\''; else if (c >= '0' && c <= '9') { if (flag_qcfuncs) c = 0xe012 + c - '0'; //WARNING: This is not an octal code, but uses 'yellow' numbers instead (as on hud). else //C compat { int d = c, base = 8; c = 0; if (d >= '0' && d < '0'+base) c = c*base + (d - '0'); else QCC_PR_ParseError(ERR_BADCHARACTERCODE, "Bad character code \\%c", d); d = (unsigned char)*pr_file_p++; if (d >= '0' && d < '0'+base) c = c*base + (d - '0'); else pr_file_p --; //oops. only one char valid... d = (unsigned char)*pr_file_p++; if (d >= '0' && d < '0'+base) c = c*base + (d - '0'); else pr_file_p --; //oops. only twoish chars valid... } } else if (c == '\r') { //sigh c = *pr_file_p++; if (c != '\n') QCC_PR_ParseWarning(WARN_HANGINGSLASHR, "Hanging \\\\\r"); pr_source_line++; } else if (c == '\n') { //sigh pr_source_line++; } else QCC_PR_ParseError (ERR_INVALIDSTRINGIMMEDIATE, "Unknown escape char %c", c); return c; } void QCC_PR_LexString (void) { unsigned int c, t; int bytecount; int len = 0; char *end; const char *cnst; int raw; char rawdelim[64]; int stringtype; //0 - quake output, input is 8bit. warnings when its not ascii. \u will still give utf-8 text, other chars as-is. Expect \s to screw everything up with utf-8 output. //1 - quake output, input is utf-8. due to editors not supporting it, that generally means the input (ab)uses markup. //2 - utf-8 output, input is utf-8. welcome to the future! unfortunately not the present. int texttype; pbool first = true; for(;;) { raw = 0; texttype = 0; QCC_PR_LexComment(&pr_token_precomment); if (flag_qccx && *pr_file_p == ':') { pr_file_p++; pr_token[len++] = 0; continue; } if (*pr_file_p == 'R' && pr_file_p[1] == '\"') { /*R"delim(fo o)delim" -> "fo\no" the [] */ raw = 1; pr_file_p+=2; while (1) { c = *pr_file_p++; if (c == '(') { rawdelim[0] = ')'; break; } if (!c || raw >= sizeof(rawdelim)-1) QCC_PR_ParseError (ERR_EOF, "EOF while parsing raw string delimiter. Expected: R\"delim(string)delim\""); rawdelim[raw++] = c; } rawdelim[raw++] = '\"'; //these two conditions are generally part of the C preprocessor. if (!strncmp(pr_file_p, "\\\r\n", 3)) { //dos format pr_file_p += 3; pr_source_line++; } else if (!strncmp(pr_file_p, "\\\r", 2) || !strncmp(pr_file_p, "\\\n", 2)) { //mac + unix format pr_file_p += 2; pr_source_line++; } stringtype = 0; } else if (*pr_file_p == 'Q' && pr_file_p[1] == '\"') { //quake output with utf-8 input (expect to need markup). stringtype = 1; pr_file_p+=2; } else if ((*pr_file_p == 'U' || *pr_file_p == 'u' || *pr_file_p == 'L') && pr_file_p[1] == '\"') { //unicode string, char32_t, char16_t, wchar_t respectively. we spit out utf-8 regardless. QCC_PR_ParseWarning(WARN_NOTUTF8, "char32_t/char16_t/wchar_t strings are not supported, treating as u8 prefix (as utf-8)"); stringtype = 2; pr_file_p+=2; } else if (*pr_file_p == 'u' && pr_file_p[1] == '8' && pr_file_p[2] == '\"') { //utf-8 string. stringtype = 2; pr_file_p+=3; } else if (*pr_file_p == '\"') { stringtype = flag_utf8strings?2:0; pr_file_p++; } else if (first) { stringtype = 0; QCC_PR_ParseError(ERR_BADCHARACTERCODE, "Expected string constant"); } else break; first = false; for(;;) { c = *pr_file_p++; if (!c) QCC_PR_ParseError (ERR_EOF, "EOF inside quote"); if (raw) { //raw strings contain very little parsing. just delimiter and initial \NL support. if (c == rawdelim[0] && !strncmp(pr_file_p, rawdelim+1, raw-1)) { pr_file_p += raw-1; break; } //make sure line numbers are correct though. if (c == '\r' && *pr_file_p != '\n') pr_source_line++; //mac if (c == '\n') //dos/unix pr_source_line++; goto forcebyte; } else { if (c=='\n') QCC_PR_ParseError (ERR_INVALIDSTRINGIMMEDIATE, "newline inside quote"); if (c=='\\') { // escape char c = *pr_file_p; //peek at it, for our hacks. if (c == 's' || c == 'b') { pr_file_p++; texttype ^= 0xe080; continue; } else if (c == '.') { pr_file_p++; c = 0xe01c | texttype; } else if (c == 'u' || c == 'U') { //special hack, \u is a utf-8 code regardless of output encoding... c = QCC_PR_LexEscapedCodepoint(); goto forceutf8; } else if (c == 'x' || c == 'X') { //special hack, \xXX in a string is an explicit byte regardless of encoding. c = QCC_PR_LexEscapedCodepoint(); // if (c > 0xff) // QCC_PR_ParseWarning(ERR_BADCHARACTERCODE, "Bad unicode character code - codepoint %#x is above 0xFF", c); goto forcebyte; } else { c = QCC_PR_LexEscapedCodepoint(); // if (stringtype != 2 && c > 0xff) // QCC_PR_ParseWarning(ERR_BADCHARACTERCODE, "Bad legacy character code - codepoint %#x is above 0xFF", c); } } else if (c=='\"') { break; } else if (c == '#' && flag_macroinstrings) { for (end = pr_file_p; ; end++) { if (qcc_iswhite(*end)) break; if (*end == ')' || *end == '(' || *end == '+' || *end == '-' || *end == '*' || *end == '/' || *end == '\\' || *end == '|' || *end == '&' || *end == '=' || *end == '^' || *end == '~' || *end == '[' || *end == ']' || *end == '\"' || *end == '{' || *end == '}' || *end == ';' || *end == ':' || *end == ',' || *end == '.' || *end == '#') break; } c = *end; *end = '\0'; cnst = QCC_PR_CheckCompConstString(pr_file_p); if (cnst==pr_file_p) { if (*pr_file_p) QCC_PR_ParseWarning(WARN_MACROINSTRING, "Unable to expand string macro %s", pr_file_p); } else if (cnst) { QCC_PR_ParseWarning(WARN_MACROINSTRING, "Macro %s expansion in string", pr_file_p); *end = c; if (len+strlen(cnst) >= sizeof(pr_token)-1) QCC_Error(ERR_INVALIDSTRINGIMMEDIATE, "String length exceeds %u", (unsigned)sizeof(pr_token)-1); strcpy(pr_token+len, cnst); bytecount=strlen(cnst); while (bytecount > 0 && qcc_iswhitesameline(pr_token[len+bytecount-1])) bytecount--; //make sure there's no trailing whitespace on the end. len+=bytecount; pr_file_p = end; continue; } *end = c; c = '#'; //undo } else if (c == 0x7C && flag_acc) //reacc support... reacc is strange. c = '\n'; else { unsigned int cp = c; unsigned int len = stringtype?utf8_check(pr_file_p-1, &cp):0; if (!len) { //invalid utf-8 encoding? don't treat it as utf-8! if (stringtype) QCC_PR_ParseWarning(ERR_BADCHARACTERCODE, "Input string is not valid utf-8"); if (c >= ' ') c |= texttype; goto forcebyte; } if (texttype) { if (cp < ' ') c = cp; //don't mask C0 chars like \t or \n else if (cp < 0x80) c = cp|0xe080; //DO mask other ascii chars (and map to the private-use range at the same time, because this isn't standard unicode any more) else { QCC_PR_ParseWarning(ERR_BADCHARACTERCODE, "Unable to mask non-ascii chars. Attempting to mask bytes"); c |= texttype; goto forcequake; } } else c = cp; pr_file_p += len-1; } } // if (c >= 0x20 && c < 0x80) // c |= 0xe000; //TEST if (stringtype == 2) { //we're outputting a utf-8 string. forceutf8: if (c > 0x10FFFFu) //RFC 3629 imposes the same limit as UTF-16 surrogate pairs. QCC_PR_ParseWarning(WARN_NOTUTF8, "Bad unicode character code - codepoint is above 0x10FFFFu"); //figure out the count of bytes required to encode this char bytecount = 1; t = 0x80; while (c >= t) { if (bytecount == 1) t <<= 4; else if (bytecount < 7) t <<= 5; else t <<= 6; bytecount++; } //error if needed if (len+bytecount >= sizeof(pr_token)) QCC_Error(ERR_INVALIDSTRINGIMMEDIATE, "String length exceeds %u", (unsigned)sizeof(pr_token)-1); //output it. if (bytecount == 1) pr_token[len++] = (unsigned char)(c&0x7f); else { t = bytecount*6; t = t-6; pr_token[len++] = (unsigned char)((c>>t)&(0x0000007f>>bytecount)) | (0xffffff00 >> bytecount); do { t = t-6; pr_token[len++] = (unsigned char)((c>>t)&0x3f) | 0x80; } while(t); } } else { forcequake: //we need to convert it to a quake char... if (c >= 0xe000 && c <= 0xe0ff) c = c & 0xff; //this private use range is commonly used for quake's glyphs. else if (c >= 0 && c <= 0x7f) ; //FIXME: SOME c0 codes are known to quake, but many got reused for random glyphs. however I'm going to treat quake as full ascii. else if (c >= 0x80) { //FIXME: spit it out as ^{xxxxxx} instead QCC_PR_ParseWarning(WARN_NOTUTF8, "Cannot convert codepoint %#x to quake's charset", c); } forcebyte: if (len >= sizeof(pr_token)-1) QCC_Error(ERR_INVALIDSTRINGIMMEDIATE, "String length exceeds %u", (unsigned)sizeof(pr_token)-1); pr_token[len] = c; len++; } } } if (len > sizeof(pr_immediate_string)-1) QCC_Error(ERR_INVALIDSTRINGIMMEDIATE, "String length exceeds %u", (unsigned)sizeof(pr_immediate_string)-1); pr_token[len] = 0; pr_token_type = tt_immediate; pr_immediate_type = type_string; memcpy(pr_immediate_string, pr_token, len+1); pr_immediate_strlen = len; /*if (qccwarningaction[WARN_NOTUTF8] && stringtype != 1) { unsigned int code; size_t c; for (c = 0; c < pr_immediate_strlen; ) { len = utf8_check(&pr_token[c], &code); if (!len || c+len>pr_immediate_strlen) { QCC_PR_ParseWarning(WARN_NOTUTF8, "String literal is not valid utf-8"); break; } c += len; } }*/ } /* ============== PR_LexNumber ============== */ int QCC_PR_LexInteger (void) { int c; int len; len = 0; c = *pr_file_p; if (pr_file_p[0] == '0' && (pr_file_p[1] == 'x' || pr_file_p[1] == 'X')) { pr_token[0] = '0'; pr_token[1] = 'x'; len = 2; c = *(pr_file_p+=2); } do { pr_token[len] = c; len++; pr_file_p++; c = *pr_file_p; } while ((c >= '0' && c<= '9') || (c == '.'&&pr_file_p[1]!='.') || (c>='a' && c <= 'f')); pr_token[len] = 0; return atoi (pr_token); } #ifdef _MSC_VER #define longlong __int64 #define LL(x) x##i64 #else #define longlong long long #define LL(x) x##ll #endif static void QCC_PR_LexNumber (void) { int tokenlen = 0; longlong num=0; int base=0; int c; int sign=1; if (*pr_file_p == '-') { sign=-1; pr_file_p++; pr_token[tokenlen++] = '-'; } if (pr_file_p[0] == '0' && (pr_file_p[1] == 'x' || pr_file_p[1] == 'X')) { pr_file_p+=2; base = 16; pr_token[tokenlen++] = '0'; pr_token[tokenlen++] = 'x'; } else if (pr_file_p[0] == '0') { pr_file_p++; if (*pr_file_p >= '0' && *pr_file_p <= '9') QCC_PR_ParseWarning(WARN_OCTAL_IMMEDIATE, "A leading 0 is interpreted as base-8."); base = 8; pr_token[tokenlen++] = '0'; } pr_immediate_type = NULL; //assume base 10 if not stated if (!base) base = 10; while((c = *pr_file_p)) { if (c >= '0' && c <= '9' && c < '0'+base) { pr_token[tokenlen++] = c; num*=base; num += c-'0'; } else if (c >= 'a' && c <= 'z' && c < 'a'+base-10) { pr_token[tokenlen++] = c; num*=base; num += c -'a'+10; } else if (c >= 'A' && c <= 'Z' && c < 'A'+base-10) { pr_token[tokenlen++] = c; num*=base; num += c -'A'+10; } else if (c == '.' && pr_file_p[1]!='.') { pr_token[tokenlen++] = c; pr_file_p++; pr_immediate_type = flag_assume_double?type_double:type_float; while(1) { c = *pr_file_p; if (c >= '0' && c <= '9') { pr_token[tokenlen++] = c; } else if (c == 'f' || c == 'F') { pr_immediate_type = type_float; pr_file_p++; break; } else if (c == 'd' || c == 'D') { pr_immediate_type = type_double; pr_file_p++; break; } else { break; } pr_file_p++; } pr_token[tokenlen++] = 0; if (pr_immediate_type == type_double) pr_immediate._double = atof(pr_token); else pr_immediate._float = (float)atof(pr_token); goto checkjunk; } else if (c == 'f' || c == 'F') { pr_token[tokenlen++] = c; pr_token[tokenlen++] = 0; pr_file_p++; pr_immediate_type = type_float; pr_immediate._float = num*sign; num*=sign; if ((longlong)pr_immediate._float != (longlong)num) QCC_PR_ParseWarning(WARN_OVERFLOW, "numerical overflow"); goto checkjunk; } else if (c == 'd' || c == 'D') { //note: conflicts with hex. add a dot before it or something. pr_token[tokenlen++] = c; pr_token[tokenlen++] = 0; pr_file_p++; pr_immediate_type = type_double; pr_immediate._double = num*sign; num*=sign; if ((longlong)pr_immediate._double != (longlong)num) QCC_PR_ParseWarning(WARN_OVERFLOW, "numerical overflow"); goto checkjunk; } else if (c == 'i' || c == 'u' || c == 'l' || c == 'I' || c == 'U' || c == 'L') { //length and sign flags can be any order. LL suffix must have the same case (but not necessarily match the sign suffix) int isunsigned; int islong = (c == 'l')||(c == 'L'); pr_token[tokenlen++] = c; pr_file_p++; if (islong) { //length suffix was first. //long-long? if (*pr_file_p == c) islong++, pr_token[tokenlen++] = *pr_file_p++; //check for signed suffix... c = *pr_file_p; isunsigned = (c == 'u')||(c=='U'); if (c == 'i' || c == 'I' || isunsigned) //ignore an explicit redundant 'i' char, for not-a-float. pr_token[tokenlen++] = *pr_file_p++; } else { isunsigned = (c == 'u')||(c=='U'); //we already made sure it u or i, and its not an l c = *pr_file_p; if (c == 'l' || c == 'L') { pr_token[tokenlen++] = *pr_file_p++; islong = true; //long-long? if (*pr_file_p == c) islong++, pr_token[tokenlen++] = *pr_file_p++; } } pr_token[tokenlen++] = 0; num *= sign; if (islong >= (flag_ILP32?2:1)) { //enough longs for our 64bit type. pr_immediate_type = (isunsigned)?type_uint64:type_int64; pr_immediate.i64 = num; } else { pr_immediate_type = (isunsigned)?type_uint:type_integer; pr_immediate._int = num; if ((longlong)pr_immediate._int != (longlong)num) { if (((longlong)pr_immediate._int & LL(0xffffffff80000000)) != LL(0xffffffff80000000)) QCC_PR_ParseWarning(WARN_OVERFLOW, "numerical overflow"); } } goto checkjunk; } else break; pr_file_p++; } pr_token[tokenlen++] = 0; if (!pr_immediate_type) { //float f = num; if (flag_assume_integer)// || (base != 10 && sign > 0 && (long long)f != (long long)num)) { if (num > UINT64_C(0x7fffffffffffffff)) pr_immediate_type = type_uint64; else if (num > 0xffffffffu) pr_immediate_type = type_int64; else if (num > 0x7fffffffu) pr_immediate_type = type_uint; else pr_immediate_type = type_integer; } else if (flag_qccx && base == 16) { pr_immediate_type = type_float; goto qccxhex; } else pr_immediate_type = type_float; } if (pr_immediate_type == type_int64 || pr_immediate_type == type_uint64) pr_immediate.i64 = num*sign; else if (pr_immediate_type == type_integer || pr_immediate_type == type_uint) { qccxhex: pr_immediate._int = num*sign; num*=sign; if ((longlong)pr_immediate._int != (longlong)num) { if (((longlong)pr_immediate._int & LL(0xffffffff80000000)) != LL(0xffffffff80000000)) QCC_PR_ParseWarning(WARN_OVERFLOW, "numerical overflow"); } } else { pr_immediate_type = type_float; // at this point, we know there's no . in it, so the NaN bug shouldn't happen // and we cannot use atof on tokens like 0xabc, so use num*sign, it SHOULD be safe //pr_immediate._float = atof(pr_token); pr_immediate._float = (float)(num*sign); num*=sign; if ((longlong)pr_immediate._float != (longlong)num && base == 16) QCC_PR_ParseWarning(WARN_OVERFLOW, "numerical overflow %lld will be rounded to %f", num, pr_immediate._float); } checkjunk: c = *pr_file_p; if ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9') || (c & 0x80)) QCC_PR_ParseWarning(ERR_NOTANUMBER, "bad suffix on number %s", pr_token); } static float QCC_PR_LexFloat (void) { int c; int len; len = 0; c = *pr_file_p; do { pr_token[len] = c; len++; pr_file_p++; c = *pr_file_p; } while ((c >= '0' && c<= '9') || (c == '.'&&pr_file_p[1]!='.')); //only allow a . if the next isn't too... if (*pr_file_p == 'f') pr_file_p++; pr_token[len] = 0; return (float)atof (pr_token); } /* ============== PR_LexVector Parses a single quoted vector ============== */ static void QCC_PR_LexVector (void) { int i; pr_file_p++; //skip the leading ' char if (*pr_file_p == '\\') {//extended character constant pr_file_p++; pr_token_type = tt_immediate; if (flag_assume_integer) { pr_immediate_type = type_integer; pr_immediate._int = QCC_PR_LexEscapedCodepoint(); } else { pr_immediate_type = type_float; pr_immediate._float = QCC_PR_LexEscapedCodepoint(); } if (*pr_file_p != '\'') QCC_PR_ParseError (ERR_INVALIDVECTORIMMEDIATE, "Bad character constant"); pr_file_p++; return; } if ((unsigned char)*pr_file_p >= 0x80) { int b = utf8_check(pr_file_p, &pr_immediate._int); //utf-8 codepoint. pr_token_type = tt_immediate; if (flag_assume_integer) pr_immediate_type = type_integer; else { pr_immediate_type = type_float; if (flag_qccx) QCC_PR_ParseWarning(WARN_DENORMAL, "char constant: denormal"); else pr_immediate._float = pr_immediate._int; } pr_file_p+=b+1; return; } else if (pr_file_p[1] == '\'') {//character constant pr_token_type = tt_immediate; if (flag_assume_integer) { pr_immediate_type = type_integer; pr_immediate._int = (unsigned char)pr_file_p[0]; } else { pr_immediate_type = type_float; if (flag_qccx) { QCC_PR_ParseWarning(WARN_DENORMAL, "char constant: denormal"); pr_immediate._int = pr_file_p[0]; } else pr_immediate._float = pr_file_p[0]; } pr_file_p+=2; return; } pr_token_type = tt_immediate; pr_immediate_type = type_vector; QCC_PR_LexWhitespace (false); for (i=0 ; i<3 ; i++) { pr_immediate.vector[i] = QCC_PR_LexFloat (); QCC_PR_LexWhitespace (false); if (*pr_file_p == '\'' && i == 1) { if (i < 2) QCC_PR_ParseWarning (WARN_FTE_SPECIFIC, "2d vector"); for (i++ ; i<3 ; i++) pr_immediate.vector[i] = 0; break; } } if (*pr_file_p != '\'') QCC_PR_ParseError (ERR_INVALIDVECTORIMMEDIATE, "Bad vector"); pr_file_p++; } /* ============== PR_LexName Parses an identifier ============== */ static void QCC_PR_LexName (void) { unsigned int c; int len; len = 0; do { int b = utf8_check(pr_file_p, &c); if (!b) { unsigned char lead = *pr_file_p++; char *o; while(*pr_file_p && !utf8_check(pr_file_p, &c)) pr_file_p++; o = pr_file_p; while (qcc_iswhite(*pr_file_p)) { if (*pr_file_p == '\n') break; pr_file_p++; } if (*pr_file_p == '\n') QCC_PR_ParseError(ERR_NOTANAME, "Invalid UTF-8 code sequence at end of line. Lead byte was %#2x", lead); else { len = 0; while (*pr_file_p && !qcc_iswhite(*pr_file_p)) pr_token[len++] = *pr_file_p++; pr_token[len++] = 0; pr_file_p = o; QCC_PR_ParseError(ERR_NOTANAME, "Invalid UTF-8 code sequence before %s. Lead byte was %#2x", pr_token, lead); } return; } while(b-->0) { pr_token[len] = *pr_file_p++; len++; } c = *pr_file_p; } while ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9') || (c & 0x80)); pr_token[len] = 0; pr_token_type = tt_name; } /* ============== PR_LexPunctuation ============== */ static void QCC_PR_LexPunctuation (void) { int i; int len; char *p; pr_token_type = tt_punct; if (pr_file_p[0] == '*' && pr_file_p[1] == '*' && flag_dblstarexp) { //for compat with gmqcc. fteqcc uses *^ internally (which does not conflict with multiplying by dereferenced pointers - sucks for MSCLR c++ syntax) QCC_PR_ParseWarning(WARN_GMQCC_SPECIFIC, "** is unsafe around pointers, use *^ instead."); strcpy (pr_token, "*^"); pr_file_p += 2; return; } for (i=0 ; (p = pr_punctuation[i]) != NULL ; i++) { len = strlen(p); if (!strncmp(p, pr_file_p, len) ) { strcpy (pr_token, pr_punctuationremap[i]); if (p[0] == '{') pr_bracelevel++; else if (p[0] == '}') pr_bracelevel--; pr_file_p += len; return; } } if ((unsigned char)*pr_file_p == (unsigned char)'\\' && pr_file_p[1] == '\r' && pr_file_p[2] == '\n') pr_file_p+=3; else if ((unsigned char)*pr_file_p == (unsigned char)'\\' && (pr_file_p[1] == '\r' || pr_file_p[1] == '\n')) pr_file_p+=2; else { if ((unsigned char)*pr_file_p == (unsigned char)0xa0) QCC_PR_ParseWarning (ERR_UNKNOWNPUCTUATION, "Unsupported punctuation: '\\x%x' - non-breaking space", (unsigned char)*pr_file_p); else QCC_PR_ParseWarning (ERR_UNKNOWNPUCTUATION, "Unknown punctuation: '\\x%x'", *pr_file_p); pr_file_p++; } QCC_PR_Lex(); } /* ============== PR_LexWhitespace ============== */ void QCC_PR_LexWhitespace (pbool inhibitpreprocessor) { int c; while (1) { // skip whitespace while ((c = *pr_file_p) && qcc_iswhite(c)) { if (qcc_islineending(c, pr_file_p[1])) { pr_file_p++; if (!inhibitpreprocessor) QCC_PR_NewLine (false); else pr_source_line++; if (!pr_file_p) return; } else pr_file_p++; } if (c == 0) return; // end of file // skip // comments if (c=='/' && pr_file_p[1] == '/') { while (*pr_file_p && !qcc_islineending(pr_file_p[0], pr_file_p[1])) pr_file_p++; if (*pr_file_p) pr_file_p++; //don't break on eof. if (!inhibitpreprocessor) QCC_PR_NewLine(false); else pr_source_line++; continue; } // skip /* */ comments if (c=='/' && pr_file_p[1] == '*') { pr_file_p+=1; do { pr_file_p++; if (qcc_islineending(pr_file_p[0], pr_file_p[1])) { if (!inhibitpreprocessor) QCC_PR_NewLine(true); else pr_source_line++; } if (pr_file_p[1] == 0) { QCC_PR_ParseError(0, "EOF inside comment\n"); pr_file_p++; return; } if (pr_file_p[0] == '/' && pr_file_p[1] == '*') QCC_PR_ParseWarning(WARN_NESTEDCOMMENT, "\"/*\" inside comment"); } while (pr_file_p[0] != '*' || pr_file_p[1] != '/'); pr_file_p+=2; continue; } break; // a real character has been found } } //============================================================================ #define MAX_FRAMES 8192 char pr_framemodelname[64]; struct { char name[64]; int value; const char *file; //compare to s_filen to see if its current or not } pr_framemacro[MAX_FRAMES]; int pr_nummacros; int pr_macrovalue; //next value to use int pr_savedmacro; //for sub-groups. void QCC_PR_ClearGrabMacros (pbool newfile) { if (!newfile) pr_nummacros = 0; pr_macrovalue = 0; pr_savedmacro = -1; } static int QCC_PR_FindMacro (char *name) { int i; for (i=pr_nummacros-1 ; i>=0 ; i--) { if (!STRCMP (name, pr_framemacro[i].name)) { if (pr_framemacro[i].file != s_filen) QCC_PR_ParseWarning(WARN_STALEMACRO, "Stale macro used (%s, defined in %s)", pr_token, pr_framemacro[i].file); return pr_framemacro[i].value; } } for (i=pr_nummacros-1 ; i>=0 ; i--) { if (!stricmp (name, pr_framemacro[i].name)) { QCC_PR_ParseWarning(WARN_CASEINSENSITIVEFRAMEMACRO, "Case insensitive frame macro (using %s)", pr_framemacro[i].name); if (pr_framemacro[i].file != s_filen) QCC_PR_ParseWarning(WARN_STALEMACRO, "Stale macro used (%s, defined in %s)", pr_token, pr_framemacro[i].file); return pr_framemacro[i].value; } } return -1; } static void QCC_PR_ExpandMacro(void) { int i = QCC_PR_FindMacro(pr_token); if (i < 0) QCC_PR_ParseError (ERR_BADFRAMEMACRO, "Unknown frame macro $%s", pr_token); QC_snprintfz(pr_token, sizeof(pr_token),"%d", i); pr_token_type = tt_immediate; pr_immediate_type = type_float; pr_immediate._float = (float)i; } pbool QCC_PR_SimpleGetString(void) { int c; int i = 0; char *f; pr_token[0] = 0; // skip whitespace while ((c = *pr_file_p) && qcc_iswhite(c)) { if (c=='\n') return false; pr_file_p++; } if (c == 0) //eof return false; //abort if there's a comment. if (pr_file_p[0] == '/') { if (pr_file_p[1] == '/') { //comment alert while(*pr_file_p && *pr_file_p != '\n') pr_file_p++; return false; } if (pr_file_p[1] == '*') return false; } if (*pr_file_p != '\"') return false; //nope, not a string. f = pr_file_p+1; while (*f) { if (*f == '\n' || !*f) { //bad string QCC_Error (ERR_INTERNAL, "new line inside string"); pr_token[0] = 0; return false; } if (*f == '\"') { //end-of-string pr_token[i] = 0; pr_file_p = f+1; return false; } if (i == sizeof(qcc_token)-1) QCC_Error (ERR_INTERNAL, "token exceeds %i chars", i); if (*f == '\\') { f++; if (!*f) f = ""; else if (*f == 'n') { pr_token[i++] = '\n'; f++; } else if (*f == 'r') { pr_token[i++] = '\r'; f++; } else if (*f == 't') { pr_token[i++] = '\t'; f++; } else pr_token[i++] = *f++; } else pr_token[i++] = *f++; } return true; } // just parses text, returning false if an eol is reached pbool QCC_PR_SimpleGetToken (void) { int c; int i; pr_token[0] = 0; // skip whitespace while ((c = *pr_file_p) && qcc_iswhite(c)) { if (c=='\n') return false; pr_file_p++; } if (c == 0) //eof return false; if (pr_file_p[0] == '/') { if (pr_file_p[1] == '/') { //comment alert while(*pr_file_p && *pr_file_p != '\n') pr_file_p++; return false; } if (pr_file_p[1] == '*') return false; } i = 0; while ((c = *pr_file_p) && !qcc_iswhite(c) && c != ',' && c != ';' && c != ')' && c != '(' && c != ']' && !(c == '/' && pr_file_p[1] == '/')) { if (i == sizeof(qcc_token)-1) QCC_Error (ERR_INTERNAL, "token exceeds %i chars", i); pr_token[i] = c; i++; pr_file_p++; } pr_token[i] = 0; return i!=0; } static pbool QCC_PR_LexMacroName(void) { int c; int i; pr_token[0] = 0; // skip whitespace while ((c = *pr_file_p) && qcc_iswhite(c)) { if (c=='\n') return false; pr_file_p++; } if (!c) return false; if (pr_file_p[0] == '/') { if (pr_file_p[1] == '/') { //comment alert while(*pr_file_p && *pr_file_p != '\n') pr_file_p++; return false; } if (pr_file_p[1] == '*') return false; } i = 0; while ( (c = *pr_file_p) > ' ' && c != '\n' && c != ',' && c != ';' && c != '&' && c != '|' && c != ')' && c != '(' && c != ']' && !(pr_file_p[0] == '.' && pr_file_p[1] == '.')) { if (i == sizeof(qcc_token)-1) QCC_Error (ERR_INTERNAL, "token exceeds %i chars", i); pr_token[i] = c; i++; pr_file_p++; } pr_token[i] = 0; return i!=0; } static void QCC_PR_MacroFrame(char *name, int value, pbool force) { int i; for (i=pr_nummacros-1 ; i>=0 ; i--) { if (!STRCMP (name, pr_framemacro[i].name)) { //vanilla macro behaviour is to not realise that there's dupes. lookups find the first, so dupes end up as dead gaps. //our caller incremented the value externally //so warn+ignore if its from the same file if (pr_framemacro[i].file == s_filen && !force) QCC_PR_ParseWarning(WARN_DUPLICATEMACRO, "Duplicate macro defined (%s). Rename it.", pr_token); else { pr_framemacro[i].value = value; //old file, override it, whatever the old value was is redundant now pr_framemacro[i].file = s_filen; } return; } } if (strlen(name)+1 > sizeof(pr_framemacro[0].name)) QCC_PR_ParseWarning(ERR_TOOMANYFRAMEMACROS, "Name for frame macro %s is too long", name); else { strcpy (pr_framemacro[pr_nummacros].name, name); pr_framemacro[pr_nummacros].value = value; pr_framemacro[pr_nummacros].file = s_filen; pr_nummacros++; if (pr_nummacros >= MAX_FRAMES) QCC_PR_ParseError(ERR_TOOMANYFRAMEMACROS, "Too many frame macros defined"); } } static void QCC_PR_ParseFrame (void) { while (QCC_PR_LexMacroName ()) { QCC_PR_MacroFrame(pr_token, pr_macrovalue++, false); } } /* ============== PR_LexGrab Deals with counting sequence numbers and replacing frame macros ============== */ static void QCC_PR_LexGrab (void) { pr_file_p++; // skip the $ // if (!QCC_PR_SimpleGetToken ()) // QCC_PR_ParseError ("hanging $"); if (qcc_iswhite(*pr_file_p)) QCC_PR_ParseError (ERR_BADFRAMEMACRO, "hanging $"); QCC_PR_LexMacroName(); if (!*pr_token) QCC_PR_ParseError (ERR_BADFRAMEMACRO, "hanging $"); // check for $frame if (!STRCMP (pr_token, "frame") || !STRCMP (pr_token, "framesave")) { QCC_PR_ParseFrame (); QCC_PR_Lex (); } // ignore other known $commands - just for model/spritegen else if (!STRCMP (pr_token, "cd") || !STRCMP (pr_token, "origin") || !STRCMP (pr_token, "base") || !STRCMP (pr_token, "flags") || !STRCMP (pr_token, "scale") || !STRCMP (pr_token, "skin") ) { // skip to end of line while (QCC_PR_LexMacroName ()) ; QCC_PR_Lex (); } else if (!STRCMP (pr_token, "flush")) { QCC_PR_ClearGrabMacros(true); while (QCC_PR_LexMacroName ()) ; QCC_PR_Lex (); } else if (!STRCMP (pr_token, "frame_reset")) { //for compat with qfcc. full reset of all frame macros. QCC_PR_ClearGrabMacros(false); while (QCC_PR_LexMacroName ()) ; QCC_PR_Lex (); } else if (!STRCMP (pr_token, "framevalue")) { QCC_PR_LexMacroName (); pr_macrovalue = atoi(pr_token); QCC_PR_Lex (); } else if (!STRCMP (pr_token, "framerestore")) { QCC_PR_LexMacroName (); QCC_PR_ExpandMacro(); pr_macrovalue = (int)pr_immediate._float; QCC_PR_Lex (); } else if (!STRCMP (pr_token, "modelname")) { int i; QCC_PR_LexMacroName (); if (*pr_framemodelname) QCC_PR_MacroFrame(pr_framemodelname, pr_macrovalue, true); if (!QC_strlcpy(pr_framemodelname, pr_token, sizeof(pr_framemodelname))) QCC_PR_ParseWarning (WARN_STRINGTOOLONG, "$modelname name too long"); i = QCC_PR_FindMacro(pr_framemodelname); if (i) pr_macrovalue = i; else i = 0; QCC_PR_Lex (); } // look for a frame name macro else QCC_PR_ExpandMacro (); } //=========================== //compiler constants - dmw pbool QCC_PR_UndefineName(const char *name) { // int a; CompilerConstant_t *c; c = pHash_Get(&compconstantstable, name); if (!c) { QCC_PR_ParseWarning(WARN_UNDEFNOTDEFINED, "Precompiler constant %s was not defined", name); return false; } Hash_Remove(&compconstantstable, name); return true; } CompilerConstant_t *QCC_PR_DefineName(const char *name, const char *value) { int i; CompilerConstant_t *cnst; if (strlen(name) >= MAXCONSTANTNAMELENGTH || !*name) QCC_PR_ParseError(ERR_NAMETOOLONG, "Compiler constant name length is too long or short"); cnst = pHash_Get(&compconstantstable, name); if (cnst) { if (strcmp(cnst->value, value?value:"") || cnst->numparams!=-1 || cnst->varg) QCC_PR_ParseWarning(WARN_DUPLICATEDEFINITION, "Duplicate definition for Precompiler constant %s", name); Hash_Remove(&compconstantstable, name); } cnst = qccHunkAlloc(sizeof(CompilerConstant_t)); cnst->used = false; cnst->numparams = -1; cnst->evil = false; strcpy(cnst->name, name); cnst->namelen = strlen(name); cnst->value = cnst->name + strlen(cnst->name); for (i = 0; i < MAXCONSTANTPARAMS; i++) cnst->params[i][0] = '\0'; pHash_Add(&compconstantstable, cnst->name, cnst, qccHunkAlloc(sizeof(bucket_t))); if (value && *value) cnst->value = strcpy(qccHunkAlloc(strlen(value)+1), value); return cnst; } void QCC_PR_PreProcessor_Define(pbool append) { char *d; char *dbuf; int dbuflen; char *s; int quote=false; pbool preprocessorhack = false; CompilerConstant_t *cnst, *oldcnst; QCC_PR_SimpleGetToken (); if (!QCC_PR_SimpleGetToken ()) QCC_PR_ParseError(ERR_NONAME, "No name defined for compiler constant"); oldcnst = pHash_Get(&compconstantstable, pr_token); if (oldcnst) Hash_Remove(&compconstantstable, oldcnst->name); cnst = QCC_PR_DefineName(pr_token, NULL); if (*pr_file_p == '(') { cnst->numparams = 0; pr_file_p++; while(qcc_iswhitesameline(*pr_file_p)) pr_file_p++; s = pr_file_p; for (;;) { if (*pr_file_p == ',' || *pr_file_p == ')') { int nl; nl = pr_file_p-s; while(nl > 0 && qcc_iswhitesameline(s[nl-1])) nl--; if (cnst->numparams >= MAXCONSTANTPARAMS) QCC_PR_ParseError(ERR_MACROTOOMANYPARMS, "May not have more than %i parameters to a macro", MAXCONSTANTPARAMS); if (nl >= MAXCONSTANTPARAMLENGTH) QCC_PR_ParseError(ERR_MACROTOOMANYPARMS, "parameter name is too long (max %i)", MAXCONSTANTPARAMLENGTH); if (nl == 3 && s[0] == '.' && s[1] == '.' && s[2] == '.') { cnst->varg = true; if (*pr_file_p != ')') QCC_PR_ParseError(ERR_MACROTOOMANYPARMS, "varadic argument must be last"); } else { memcpy(cnst->params[cnst->numparams], s, nl); cnst->params[cnst->numparams][nl] = '\0'; for (nl = 0; nl < cnst->numparams; nl++) { if (!strcmp(cnst->params[nl], cnst->params[cnst->numparams])) QCC_PR_ParseError(ERR_MACROTOOMANYPARMS, "duplicate macro paramter name '%s'", cnst->params[nl]); } cnst->numparams++; } if (*pr_file_p++ == ')') break; while(qcc_iswhitesameline(*pr_file_p)) pr_file_p++; s = pr_file_p; } if(!*pr_file_p++) { QCC_PR_ParseError(ERR_EXPECTED, "missing ) in macro parameter list"); break; } } } else cnst->numparams = -1; //disable append mode if they're trying to do something stupid if (append) { if (!oldcnst) append = false; //append with no previous define is treated as just a regular define. huzzah. else if (cnst->numparams != oldcnst->numparams || cnst->varg != oldcnst->varg) { QCC_PR_ParseWarning(WARN_DUPLICATEPRECOMPILER, "different number of macro arguments in macro append"); append = false; } else { int i; //arguments need to be specified, if only so that appends with arguments are still vaugely readable. //argument names need to match because the expansion is too lame to cope if they're different. for (i = 0; i < cnst->numparams; i++) { if (strcmp(cnst->params[i], oldcnst->params[i])) break; } if (i < cnst->numparams) { QCC_PR_ParseWarning(WARN_DUPLICATEPRECOMPILER, "arguments differ in macro append"); append = false; } else append = true; } } s = pr_file_p; d = dbuf = NULL; dbuflen = 0; if (append) { //start with the old value int olen = strlen(oldcnst->value); dbuflen = olen + 128; dbuf = qccHunkAlloc(dbuflen); memcpy(dbuf, oldcnst->value, olen); d = dbuf + olen; *d++ = ' '; } cnst->fromfile = s_filen; cnst->fromline = pr_source_line; while(*s == ' ' || *s == '\t') s++; while(1) { if ((d - dbuf) + 2 >= dbuflen) { int len = d - dbuf; dbuflen = (len+128) * 2; dbuf = qccHunkAlloc(dbuflen); memcpy(dbuf, d - len, len); d = dbuf + len; } if( *s == '\\' ) { // read over a newline if necessary if( s[1] == '\n' || s[1] == '\r' ) { char *exploitcheck; s++; //skip the \ char if (*s == '\r' && s[1] == '\n') s++; //skip the \r. the \n will become part of the macro. s++; //skip the \n QCC_PR_NewLine(true); /* This began as a bug. It is still evil, but its oh so useful. In C, #define foobar \ foo\ bar\ moo becomes foobarmoo, not foo\nbar\nmoo #define hacks however, require that it becomes foo\nbar\nmoo # cannot be used on the first line of the macro, and then is only valid as the first non-white char of the following lines so if present, the preceeding \\\n and following \\\n must become an actual \n instead of being stripped. */ for (exploitcheck = s; *exploitcheck && qcc_iswhitesameline(*exploitcheck); exploitcheck++) ; if (*exploitcheck == '#') { *d++ = '\n'; if (!cnst->evil) QCC_PR_ParseWarning(WARN_EVILPREPROCESSOR, "preprocessor directive within preprocessor macro %s", cnst->name); cnst->evil = true; preprocessorhack = true; } else if (preprocessorhack) { *d++ = '\n'; preprocessorhack = false; } } } else if(*s == '\r' || *s == '\n' || *s == '\0') { break; } if (!quote && s[0]=='/'&&s[1]=='/') break; //c++ style comments can just be ignored if (!quote && s[0]=='/'&&s[1]=='*') { //multi-line c style comments become part of the define itself. this also negates the need for \ at the end of lines. //although we don't bother embedding. s+=2; for(;;) { if (!s[0]) { QCC_PR_ParseWarning(WARN_DUPLICATEPRECOMPILER, "EOF inside quote in define %s", cnst->name); break; } if (s[0]=='*'&&s[1]=='/') { s+=2; break; } if (s[0] == '\n') pr_source_line++; s++; } continue; } if (*s == '\"') quote=!quote; *d = *s; d++; s++; } while (d>dbuf && qcc_iswhitesameline(d[-1])) d--; *d = '\0'; cnst->value = dbuf; if (oldcnst && !append) { //we always warn if it was already defined //we use different warning codes so that -Wno-mundane can be used to ignore identical redefinitions. if (strcmp(oldcnst->value, cnst->value)) QCC_PR_ParseWarning(WARN_DUPLICATEPRECOMPILER, "Alternate precompiler definition of %s (%s -> %s)", pr_token, oldcnst->value, cnst->value); else QCC_PR_ParseWarning(WARN_IDENTICALPRECOMPILER, "Identical precompiler definition of %s", pr_token); } pr_file_p = s; } /* *buffer, *bufferlen and *buffermax should be NULL/0 at the start */ static void QCC_PR_ExpandStrCat(char **buffer, size_t *bufferlen, size_t *buffermax, char *newdata, size_t newlen) { size_t newmax = *bufferlen + newlen; if (newmax < *bufferlen)//check for overflow { QCC_PR_ParseWarning(ERR_INTERNAL, "exceeds 4gb"); return; } if (newmax > *buffermax) { char *newbuf; if (newmax < 64) newmax = 64; if (newmax < *bufferlen * 2) { newmax = *bufferlen * 2; if (newmax < *bufferlen) /*overflowed?*/ { QCC_PR_ParseWarning(ERR_INTERNAL, "exceeds 4gb"); return; } } newbuf = realloc(*buffer, newmax); if (!newbuf) { QCC_PR_ParseWarning(ERR_INTERNAL, "out of memory"); return; /*OOM*/ } *buffer = newbuf; *buffermax = newmax; } memcpy(*buffer + *bufferlen, newdata, newlen); *bufferlen += newlen; /*no null terminator, remember to cat one if required*/ } /* *buffer, *bufferlen and *buffermax should be NULL/0 at the start */ static void QCC_PR_ExpandStrCatMarkup(char **buffer, size_t *bufferlen, size_t *buffermax, char *newdata, size_t newlen) { size_t newmax = *bufferlen + newlen*2; if (newmax < *bufferlen)//check for overflow { QCC_PR_ParseWarning(ERR_INTERNAL, "exceeds 4gb"); return; } if (newmax > *buffermax) { char *newbuf; if (newmax < 64) newmax = 64; if (newmax < *bufferlen * 2) { newmax = *bufferlen * 2; if (newmax < *bufferlen) /*overflowed?*/ { QCC_PR_ParseWarning(ERR_INTERNAL, "exceeds 4gb"); return; } } newbuf = realloc(*buffer, newmax); if (!newbuf) { QCC_PR_ParseWarning(ERR_INTERNAL, "out of memory"); return; /*OOM*/ } *buffer = newbuf; *buffermax = newmax; } while (newlen--) { if (*newdata == '\n') { (*buffer)[*bufferlen+0] = '\\'; (*buffer)[*bufferlen+1] = '\n'; *bufferlen += 2; } else if (*newdata == '\\') { (*buffer)[*bufferlen+0] = '\\'; (*buffer)[*bufferlen+1] = '\\'; *bufferlen += 2; } else if (*newdata == '\0') { (*buffer)[*bufferlen+0] = '\\'; (*buffer)[*bufferlen+1] = '0'; *bufferlen += 2; } else if (*newdata == '\"') { (*buffer)[*bufferlen+0] = '\\'; (*buffer)[*bufferlen+1] = '\"'; *bufferlen += 2; } else { (*buffer)[*bufferlen] = *newdata; *bufferlen += 1; } newdata++; } /*no null terminator, remember to cat one if required*/ } static const struct tm *QCC_CurrentTime(void) { //if SOURCE_DATE_EPOCH environment is defined, use that as seconds from epoch (and show utc) //this helps give reproducable builds (which is for some debian project, demonstrating that noone is hacking binaries). const char *env = getenv("SOURCE_DATE_EPOCH"); time_t t; if (env && *env) { t = strtoull(env, NULL, 0); if (t) return gmtime(&t); } t = time(NULL); return localtime(&t); } #if _POSIX_C_SOURCE >= 2 || defined(_WIN32) #define HAVE_POPEN #endif #ifdef HAVE_POPEN static char *QCC_PR_PopenMacro(const char *macroname, const char *cmd, char *retbuf, size_t retbufsize) { char *ret = retbuf; char temp[65536], *t = temp; #ifdef _WIN32 FILE *f = _popen(cmd, "rt"); #else FILE *f = popen(cmd, "r"); #endif int len; if (!f) { QCC_PR_ParseWarning(ERR_CONSTANTNOTDEFINED, "%s: Unable to execute \"%s\" for value", macroname, cmd); return NULL; } retbufsize-=3; // '""\0' *retbuf++ = '\"'; for (;;) { len = fread(temp, 1, sizeof(temp), f); if (len <= 0) break; else for (t=temp; len --> 0 && *t && retbufsize > 1; t++) { if (*t == '\"') retbuf[1] = '\"'; else if (*t == '\n') retbuf[1] = 'n'; else if (*t == '\r') retbuf[1] = 'r'; else if (*t == '#') retbuf[1] = '#'; //so we don't get preqcc-style expansion in strings. else { *retbuf++ = *t; retbufsize--; continue; } retbuf[0] = '\\'; retbuf+=2; retbufsize-=2; } } if (retbuf[-1] == 'n' && retbuf[-2] == '\\') retbuf -= 2; if (retbuf[-1] == 'r' && retbuf[-2] == '\\') retbuf -= 2; *retbuf++ = '\"'; *retbuf++ = 0; #ifdef _WIN32 _pclose(f); #else pclose(f); #endif return ret; } #endif static char *QCC_PR_CheckBuiltinCompConst(char *constname, char *retbuf, size_t retbufsize) { if (constname[0] != '_' || constname[1] != '_') return NULL; if (!strcmp(constname, "__TIME__")) { strftime( retbuf, retbufsize, "\"%H:%M\"", QCC_CurrentTime()); return retbuf; } if (!strcmp(constname, "__DATE__")) { strftime( retbuf, retbufsize, "\"%a %d %b %Y\"", QCC_CurrentTime()); return retbuf; } #ifdef HAVE_POPEN if (!strcmp(constname, "__GITURL__")) return QCC_PR_PopenMacro(constname, "git remote get-url origin", retbuf, retbufsize); //some git url... if (!strcmp(constname, "__GITHASH__")) return QCC_PR_PopenMacro(constname, "git log -1 --format=%H", retbuf, retbufsize); //just a hash if (!strcmp(constname, "__GITDATE__")) return QCC_PR_PopenMacro(constname, "git log -1 --format=%cs", retbuf, retbufsize); //YYYY-MM-DD if (!strcmp(constname, "__GITDATETIME__")) return QCC_PR_PopenMacro(constname, "git log -1 --format=%ci", retbuf, retbufsize); //YYYY-MM-DD HH:MM:SS +TZ if (!strcmp(constname, "__GITDESC__")) return QCC_PR_PopenMacro(constname, "git describe", retbuf, retbufsize); #endif if (!strcmp(constname, "__RAND__")) { QC_snprintfz(retbuf, retbufsize, "%i", rand()); return retbuf; } #if defined(SVNREVISION) && defined(SVNDATE) if (!strcmp(constname, "__QCCREV__")) return STRINGIFY(SVNREVISION); //no M or anything, so you can compare revisions properly #endif if (!strcmp(constname, "__QCCVER__")) { #if defined(SVNREVISION) && defined(SVNDATE) return "\"FTEQCC " STRINGIFY(SVNREVISION) " "STRINGIFY(SVNDATE)"\""; #elif defined(SVNREVISION) return "\"FTEQCC " STRINGIFY(SVNREVISION) " "__DATE__"\""; #else return "\""__DATE__"\""; #endif } if (!strcmp(constname, "__FILE__")) { char *erk; QC_snprintfz(retbuf, retbufsize, "\"%s\"", s_filen); erk = strchr(retbuf, ':'); if (erk) { erk[0] = '\"'; erk[1] = 0; } return retbuf; } if (!strcmp(constname, "__LINE__")) { int line = pr_source_line; if (currentchunk && currentchunk->cnst) //if we're in a macro, use the line the macro was on. line = currentchunk->currentlinenumber; QC_snprintfz(retbuf, retbufsize, "%i", line); return retbuf; } if (!strcmp(constname, "__LINESTR__")) { int line = pr_source_line; if (currentchunk && currentchunk->cnst) //if we're in a macro, use the line the macro was on. line = currentchunk->currentlinenumber; QC_snprintfz(retbuf, retbufsize, "\"%i\"", line); return retbuf; } if (!strcmp(constname, "__FUNC__") || !strcmp(constname, "__func__")) { QC_snprintfz(retbuf, retbufsize, "\"%s\"",pr_scope?pr_scope->name:""); return retbuf; } if (!strcmp(constname, "__NULL__")) { return "0i"; } return NULL; //didn't match } static pbool QCC_PR_ExpandPreProcessorMacro(CompilerConstant_t *c, char **buffer, size_t *bufferlen, size_t *buffermax) { int p; char *start; char *starttok; char *argsend; int argsendline; size_t whitestart; char *paramoffset[MAXCONSTANTPARAMS+1]; int param=0, extraparam=0; int plevel=0; pbool noargexpand; char *end; char retbuf[256]; pr_file_p++; QCC_PR_LexWhitespace(false); start = pr_file_p; while(1) { // handle strings correctly by ignoring them if (*pr_file_p == '\"') { do { pr_file_p++; } while( (pr_file_p[-1] == '\\' || pr_file_p[0] != '\"') && *pr_file_p && *pr_file_p != '\n' ); } if (*pr_file_p == '(') plevel++; else if (!plevel && (*pr_file_p == ',' || *pr_file_p == ')')) { if (*pr_file_p == ',' && c->varg && param >= c->numparams) extraparam++; //skip extra trailing , arguments if we're varging. else { paramoffset[param++] = start; start = pr_file_p+1; if (*pr_file_p == ')') { *pr_file_p = '\0'; pr_file_p++; break; } *pr_file_p = '\0'; pr_file_p++; QCC_PR_LexWhitespace(false); start = pr_file_p; // move back by one char because we move forward by one at the end of the loop pr_file_p--; if (param == MAXCONSTANTPARAMS || param > c->numparams) QCC_PR_ParseError(ERR_TOOMANYPARAMS, "Too many parameters in macro call"); } } else if (*pr_file_p == ')' ) plevel--; else if(*pr_file_p == '\n') QCC_PR_NewLine(false); // see that *pr_file_p = '\0' up there? Must ++ BEFORE checking for !*pr_file_p pr_file_p++; if (!*pr_file_p) QCC_PR_ParseError(ERR_EOF, "EOF on macro call"); } if (param < c->numparams) QCC_PR_ParseError(ERR_TOOFEWPARAMS, "Not enough macro parameters"); paramoffset[param] = start; *buffer = NULL; *bufferlen = 0; *buffermax = 0; // QCC_PR_LexWhitespace(false); argsend = pr_file_p; argsendline = pr_source_line; pr_file_p = c->value; for(;;) { noargexpand = false; whitestart = *bufferlen; starttok = pr_file_p; /*while(qcc_iswhite(*pr_file_p)) //copy across whitespace { if (!*pr_file_p) break; pr_file_p++; }*/ QCC_PR_LexWhitespace(true); if (starttok != pr_file_p) { QCC_PR_ExpandStrCat(buffer,bufferlen,buffermax, starttok, pr_file_p - starttok); } if(*pr_file_p == '\"') { starttok = pr_file_p; do { pr_file_p++; } while( (pr_file_p[-1] == '\\' || pr_file_p[0] != '\"') && *pr_file_p && *pr_file_p != '\n' ); if(*pr_file_p == '\"') pr_file_p++; QCC_PR_ExpandStrCat(buffer,bufferlen,buffermax, starttok, pr_file_p - starttok); continue; } else if (*pr_file_p == '#') //if you ask for #a##b you will be shot. use #a #b instead, or chain macros. { if (pr_file_p[1] == '#') { //concatinate (strip out whitespace before the token) *bufferlen = whitestart; pr_file_p+=2; noargexpand = true; } else { //stringify pr_file_p++; pr_file_p = QCC_COM_Parse2(pr_file_p); if (!pr_file_p) break; for (p = 0; p < param; p++) { if (!STRCMP(qcc_token, c->params[p])) { QCC_PR_ExpandStrCat(buffer,bufferlen,buffermax, "\"", 1); QCC_PR_ExpandStrCatMarkup(buffer,bufferlen,buffermax, paramoffset[p], strlen(paramoffset[p])); QCC_PR_ExpandStrCat(buffer,bufferlen,buffermax, "\"", 1); break; } } if (p == param) { QCC_PR_ExpandStrCat(buffer,bufferlen,buffermax, "#", 1); QCC_PR_ExpandStrCat(buffer,bufferlen,buffermax, qcc_token, strlen(qcc_token)); if (!c->evil) QCC_PR_ParseWarning(0, "'#%s' is not a macro parameter in %s", qcc_token, c->name); } continue; //already did this one } } end = qcc_token; pr_file_p = QCC_COM_Parse2(pr_file_p); if (!pr_file_p) break; for (p = 0; p < c->numparams; p++) { if (!STRCMP(qcc_token, c->params[p])) { char *argstart, *argend; for (start = pr_file_p; qcc_iswhite(*start); start++) ; if (noargexpand || (start[0] == '#' && start[1] == '#')) QCC_PR_ExpandStrCat(buffer,bufferlen,buffermax, paramoffset[p], strlen(paramoffset[p])); else { for (argstart = paramoffset[p]; *argstart; argstart = argend) { argend = argstart; while (qcc_iswhite(*argend)) argend++; if (*argend == '\"') { do { argend++; } while( (argend[-1] == '\\' || argend[0] != '\"') && *argend && *argend != '\n' ); if(*argend == '\"') argend++; end = NULL; } else { argend = QCC_COM_Parse2(argend); if (!argend) break; end = QCC_PR_CheckBuiltinCompConst(qcc_token, retbuf, sizeof(retbuf)); } //FIXME: we should be testing all defines instead of just built-in ones. if (end) QCC_PR_ExpandStrCat(buffer,bufferlen,buffermax, end, strlen(end)); else QCC_PR_ExpandStrCat(buffer,bufferlen,buffermax, argstart, argend-argstart); } } break; } } if (c->varg && !STRCMP(qcc_token, "__VA_ARGS__")) { //c99 if (param-1 == c->numparams) QCC_PR_ExpandStrCat(buffer,bufferlen,buffermax, paramoffset[c->numparams], strlen(paramoffset[c->numparams])); else if (noargexpand) { if(*bufferlen>0 && (*buffer)[*bufferlen-1] == ',') *bufferlen-=1; } } else if (c->varg && !STRCMP(qcc_token, "__VA_COUNT__")) { //not c99 char tmp[64]; if (param < c->numparams) QCC_PR_ParseError(ERR_TOOFEWPARAMS, "__VA_COUNT__ without any variable args"); QC_snprintfz(tmp, sizeof(tmp), "%i", param-1+extraparam); QCC_PR_ExpandStrCat(buffer,bufferlen,buffermax, tmp, strlen(tmp)); } else if (p == c->numparams) { /*CompilerConstant_t *c2 = pHash_Get(&compconstantstable, qcc_token); if (c2 && c2->numparams >= 0 && *pr_file_p == '(') { //oh dear god this is vile bullshit char *sub = NULL; size_t sublen; size_t submax; pr_file_p++; if (QCC_PR_ExpandPreProcessorMacro(c, &sub,&sublen,&submax)) { QCC_PR_ExpandStrCat(buffer,bufferlen,buffermax, sub, sublen); } free(sub); } else*/ QCC_PR_ExpandStrCat(buffer,bufferlen,buffermax, qcc_token, strlen(qcc_token)); } } for (p = 0; p < param-1; p++) paramoffset[p][strlen(paramoffset[p])] = ','; paramoffset[p][strlen(paramoffset[p])] = ')'; if (c->inside>8) return false; pr_file_p = argsend; pr_source_line = argsendline; if (flag_debugmacros) { if (flag_msvcstyle) externs->Printf ("%s(%i) : macro %s: %s\n", s_filen, pr_source_line, c->name, pr_file_p); else externs->Printf ("%s:%i: macro %s: %s\n", s_filen, pr_source_line, c->name, pr_file_p); } return true; } int QCC_PR_CheckCompConst(void) { //FIXME: FOO(SPLAT(FOO(BAR))) //the above should expand the inner foo before expanding the outer foo char *initial_file_p = pr_file_p; int initial_line = pr_source_line; CompilerConstant_t *c; char *end, *tok; char retbuf[256]; for (end = pr_file_p; ; end++) { if (!*end || qcc_iswhite(*end)) break; if (*end == ')' || *end == '(' || *end == '+' || *end == '-' || *end == '*' || *end == '/' || *end == '|' || *end == '&' || *end == '=' || *end == '^' || *end == '~' || *end == '[' || *end == ']' || *end == '\"' || *end == '{' || *end == '}' || *end == ';' || *end == ':' || *end == '<' || *end == '>' || *end == ',' || *end == '.' || *end == '#') break; } if (!QC_strnlcpy(pr_token, pr_file_p, end-pr_file_p, sizeof(pr_token))) return false; //name too long to be a valid macro // externs->Printf("%s\n", pr_token); c = pHash_Get(&compconstantstable, pr_token); if (c && (!currentchunk || currentchunk->cnst != c)) //macros don't expand themselves { pr_file_p = initial_file_p+strlen(c->name); while(*pr_file_p == ' ' || *pr_file_p == '\t') pr_file_p++; if (c->numparams>=0) { if (*pr_file_p == '(') { char *buffer = NULL; size_t bufferlen, buffermax; if (!QCC_PR_ExpandPreProcessorMacro(c, &buffer, &bufferlen, &buffermax)) { free(buffer); pr_file_p = initial_file_p; pr_source_line = initial_line; return false; } if (!bufferlen) expandedemptymacro = true; else { QCC_PR_ExpandStrCat(&buffer, &bufferlen, &buffermax, "\0", 1); QCC_PR_IncludeChunkEx(buffer, true, NULL, c); } expandedemptymacro = true; free(buffer); } else { //QCC_PR_ParseError(ERR_TOOFEWPARAMS, "Macro without argument list"); //such macros don't get expanded. pr_file_p = initial_file_p; pr_source_line = initial_line; return false; } } else { if (c->inside >= 8) { pr_file_p = initial_file_p; pr_source_line = initial_line; return false; } if (*c->value) QCC_PR_IncludeChunkEx(c->value, false, NULL, c); expandedemptymacro = true; } return true; } tok = QCC_PR_CheckBuiltinCompConst(pr_token, retbuf, sizeof(retbuf)); if (tok) { pr_file_p = end; QCC_PR_IncludeChunkEx(tok, true, NULL, NULL); return true; } return false; } const char *QCC_PR_CheckCompConstString(const char *def) { const char *s; CompilerConstant_t *c; c = pHash_Get(&compconstantstable, def); if (c) { s = QCC_PR_CheckCompConstString(c->value); return s; } return def; } CompilerConstant_t *QCC_PR_CheckCompConstDefined(const char *def) { CompilerConstant_t *c = pHash_Get(&compconstantstable, def); return c; } char *QCC_PR_CheckCompConstTooltip(char *word, char *outstart, char *outend) { int i; CompilerConstant_t *c = QCC_PR_CheckCompConstDefined(word); if (c) { char *out = outstart; if (c->numparams >= 0) { QC_snprintfz(out, outend-out, "#define %s(", c->name); out += strlen(out); for (i = 0; i < c->numparams-1; i++) { QC_snprintfz(out, outend-out, "%s,", c->params[i]); out += strlen(out); } if (i < c->numparams) { QC_snprintfz(out, outend-out, "%s", c->params[i]); out += strlen(out); } QC_snprintfz(out, outend-out, ")"); } else QC_snprintfz(out, outend-out, "#define %s", c->name); out += strlen(out); if (c->value && *c->value) QC_snprintfz(out, outend-out, "\n%s", c->value); return outstart; } return NULL; } //============================================================================ /* ============== PR_Lex Sets pr_token, pr_token_type, and possibly pr_immediate and pr_immediate_type ============== */ void QCC_PR_Lex (void) { int c; pr_token[0] = 0; if (!pr_file_p) { if (QCC_PR_UnInclude()) { QCC_PR_Lex(); return; } pr_token_type = tt_eof; return; } pr_token_precomment = NULL; QCC_PR_LexComment(&pr_token_precomment); // QCC_PR_LexWhitespace (false); pr_token_line_last = pr_token_line; pr_token_line = pr_source_line; if (currentchunk) pr_token_line += currentchunk->currentlinenumber-1; if (!pr_file_p) { if (QCC_PR_UnInclude()) { QCC_PR_Lex(); return; } pr_token_type = tt_eof; return; } c = *pr_file_p; if (!c) { if (QCC_PR_UnInclude()) { QCC_PR_Lex(); return; } pr_token_type = tt_eof; return; } // handle quoted strings as a unit if (c == '\"' || ((c == 'R' || c == 'Q' || c == 'u' || c == 'U') && pr_file_p[1] == '\"') || (c == 'u' && pr_file_p[1] == '8' && pr_file_p[2] == '\"')) { QCC_PR_LexString (); return; } // handle quoted vectors as a unit if (c == '\'') { QCC_PR_LexVector (); return; } // if the first character is a valid identifier, parse until a non-id // character is reached if ((c == '%') && flag_qccx && (pr_file_p[1] == '-' || (pr_file_p[1] >= '0' && pr_file_p[1] <= '9'))) { //with qccx, %5 is a denormalized float. pr_file_p++; pr_token_type = tt_immediate; pr_immediate_type = type_float; QCC_PR_ParseWarning(WARN_DENORMAL, "denormalized immediate"); pr_immediate._int = QCC_PR_LexInteger (); return; } if ( c == '0' && pr_file_p[1] == 'x') { pr_token_type = tt_immediate; QCC_PR_LexNumber(); return; } if ( (c == '.'&&pr_file_p[1]!='.'&&pr_file_p[1] >='0' && pr_file_p[1] <= '9') || (c >= '0' && c <= '9') || ( c=='-' && pr_file_p[1]>='0' && pr_file_p[1] <='9') ) { pr_token_type = tt_immediate; QCC_PR_LexNumber (); return; } if (/*!flag_qccx &&*/ c == '#' && !(pr_file_p[1]==')' || pr_file_p[1]==',' || pr_file_p[1]=='\"' || pr_file_p[1]=='-' || (pr_file_p[1]>='0' && pr_file_p[1] <='9'))) //hash and not number { pr_file_p++; if (!QCC_PR_CheckCompConst()) { if (!QCC_PR_SimpleGetToken()) strcpy(pr_token, "unknown"); QCC_PR_ParseError(ERR_CONSTANTNOTDEFINED, "Explicit precompiler usage when not defined %s", pr_token); } else { QCC_PR_Lex(); if (pr_token_type == tt_eof) QCC_PR_Lex(); } return; } if ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c & 0x80)) { if (flag_hashonly || !QCC_PR_CheckCompConst()) //look for a macro. QCC_PR_LexName (); else { //we expanded a macro. we need to read the tokens out of it now though QCC_PR_Lex(); if (pr_token_type == tt_eof) { if (QCC_PR_UnInclude()) { QCC_PR_Lex(); return; } pr_token_type = tt_eof; } } return; } if (c == '$') { QCC_PR_LexGrab (); return; } // parse symbol strings until a non-symbol is found QCC_PR_LexPunctuation (); } //============================================================================= pbool QCC_Temp_Describe(QCC_def_t *def, char *buffer, int buffersize); void QCC_PR_ParsePrintDef (int type, QCC_def_t *def) { if (!qccwarningaction[type]) return; if (def->filen) { char tybuffer[512]; char tmbuffer[512]; char vlbuffer[512]; char *modifiers; if (QCC_Temp_Describe(def, tmbuffer, sizeof(tmbuffer))) { externs->Printf ("%s:%i: (%s)(%s)\n", def->filen, def->s_line, TypeName(def->type, tybuffer, sizeof(tybuffer)), tmbuffer); } else { modifiers = ""; if (def->constant) modifiers = "const "; else if (def->isstatic) modifiers = "static "; if (def && def->initialized && def->constant && !def->arraysize && def->symboldata) { const QCC_eval_t *ev = (const QCC_eval_t*)&def->symboldata[0]; switch(def->type->type) { case ev_float: QC_snprintfz(vlbuffer, sizeof(vlbuffer), " = %g", ev->_float); break; case ev_double: QC_snprintfz(vlbuffer, sizeof(vlbuffer), " = %g", ev->_double); break; case ev_integer:QC_snprintfz(vlbuffer, sizeof(vlbuffer), " = %i", ev->_int); break; case ev_uint: QC_snprintfz(vlbuffer, sizeof(vlbuffer), " = %u", ev->_uint); break; case ev_int64: QC_snprintfz(vlbuffer, sizeof(vlbuffer), " = %"pPRIi64, ev->i64); break; case ev_uint64: QC_snprintfz(vlbuffer, sizeof(vlbuffer), " = %"pPRIu64, ev->u64); break; default: *vlbuffer = 0; break; } } else *vlbuffer = 0; if (flag_msvcstyle) externs->Printf ("%s%s(%i) : %s%s%s %s%s%s%s%s is defined here\n", col_location, def->filen, def->s_line, col_type, modifiers, TypeName(def->type, tybuffer, sizeof(tybuffer)), col_symbol, def->name, col_type, vlbuffer, col_none); else externs->Printf ("%s%s:%i: %s%s%s %s%s%s%s%s is defined here\n", col_location, def->filen, def->s_line, col_type, modifiers, TypeName(def->type, tybuffer, sizeof(tybuffer)), col_symbol, def->name, col_type, vlbuffer, col_none); } } } void QCC_PR_ParsePrintSRef (int type, QCC_sref_t def) { QCC_PR_ParsePrintDef(type, def.sym); } void *errorscope; static void QCC_PR_PrintMacro (qcc_includechunk_t *chunk) { if (chunk) { QCC_PR_PrintMacro(chunk->prev); if (chunk->cnst) { #if 1 externs->Printf ("%s%s:%i: macro %s%s%s is defined here\n", col_location, chunk->cnst->fromfile, chunk->cnst->fromline, col_symbol, chunk->cnst->name, col_none); #else externs->Printf ("%s:%i: expanding %s\n", chunk->currentfilename, chunk->currentlinenumber, chunk->cnst->name); #endif if (verbose >= VERBOSE_STANDARD) externs->Printf ("%s\n", chunk->datastart); } else externs->Printf ("%s:%i:\n", chunk->currentfilename, chunk->currentlinenumber); } } static void QCC_PR_PrintScope (void) { QCC_PR_PrintMacro(currentchunk); if (pr_scope) { if (errorscope != pr_scope) externs->Printf ("in function %s%s%s (line %i),\n", col_symbol, pr_scope->name, col_none, pr_scope->line); errorscope = pr_scope; } else { if (errorscope) externs->Printf ("at global scope,\n"); errorscope = NULL; } } void QCC_PR_ResetErrorScope(void) { errorscope = NULL; } /* ============ PR_ParseError Aborts the current file load ============ */ #ifndef QCC void editbadfile(const char *file, int line); #endif //will abort. NORETURN void VARGS QCC_PR_ParseError (int errortype, const char *error, ...) { va_list argptr; char string[1024]; va_start (argptr,error); QC_vsnprintf (string,sizeof(string)-1, error,argptr); va_end (argptr); #ifndef QCC editbadfile(s_filen, pr_source_line); #endif if (error) { QCC_PR_PrintScope(); if (flag_msvcstyle) externs->Printf ("%s%s(%i) : %serror%s: %s\n", col_location, s_filen, pr_source_line, col_error, col_none, string); else externs->Printf ("%s%s:%i: %serror%s: %s\n", col_location, s_filen, pr_source_line, col_error, col_none, string); } longjmp (pr_parse_abort, 1); } //will abort. NORETURN void VARGS QCC_PR_ParseErrorPrintDef (int errortype, QCC_def_t *def, const char *error, ...) { va_list argptr; char string[1024]; va_start (argptr,error); QC_vsnprintf (string,sizeof(string)-1, error,argptr); va_end (argptr); #ifndef QCC editbadfile(s_filen, pr_source_line); #endif QCC_PR_PrintScope(); if (flag_msvcstyle) externs->Printf ("%s%s(%i) : %serror%s: %s\n", col_location, s_filen, pr_source_line, col_error, col_none, string); else externs->Printf ("%s%s:%i: %serror%s: %s\n", col_location, s_filen, pr_source_line, col_error, col_none, string); QCC_PR_ParsePrintDef(WARN_ERROR, def); longjmp (pr_parse_abort, 1); } NORETURN void VARGS QCC_PR_ParseErrorPrintSRef (int errortype, QCC_sref_t def, const char *error, ...) { va_list argptr; char string[1024]; va_start (argptr,error); QC_vsnprintf (string,sizeof(string)-1, error,argptr); va_end (argptr); #ifndef QCC editbadfile(s_filen, pr_source_line); #endif QCC_PR_PrintScope(); if (flag_msvcstyle) externs->Printf ("%s%s(%i) : %serror%s: %s\n", col_location, s_filen, pr_source_line, col_error, col_none, string); else externs->Printf ("%s%s:%i: %serror%s: %s\n", col_location, s_filen, pr_source_line, col_error, col_none, string); QCC_PR_ParsePrintSRef(WARN_ERROR, def); longjmp (pr_parse_abort, 1); } static pbool VARGS QCC_PR_PrintWarning (int type, const char *file, int line, const char *string) { char *wnam = QCC_NameForWarning(type); if (!wnam) wnam = ""; if (string) QCC_PR_PrintScope(); if (type >= ERR_PARSEERRORS) { if (!string) ; else if (!file || !*file) externs->Printf (":: %serror%s%s: %s\n", col_error, wnam, col_none, string); else if (flag_msvcstyle) externs->Printf ("%s%s(%i) : %serror%s%s: %s\n", col_location, file, line, col_error, wnam, col_none, string); else externs->Printf ("%s%s:%i: %serror%s%s: %s\n", col_location, file, line, col_error, wnam, col_none, string); pr_error_count++; } else if (qccwarningaction[type] == 2) { //-werror if (!string) ; else if (!file || !*file) externs->Printf (":: %swerror%s%s: %s\n", col_error, wnam, col_none, string); else if (flag_msvcstyle) externs->Printf ("%s%s(%i) : %swerror%s%s: %s\n", col_location, file, line, col_error, wnam, col_none, string); else externs->Printf ("%s%s:%i: %swerror%s%s: %s\n", col_location, file, line, col_error, wnam, col_none, string); pr_error_count++; } else { if (!string) ; else if (!file || !*file) externs->Printf (":: %swarning%s%s: %s\n", col_warning, wnam, col_none, string); else if (flag_msvcstyle) externs->Printf ("%s%s(%i) : %swarning%s%s: %s\n", col_location, file, line, col_warning, wnam, col_none, string); else externs->Printf ("%s%s:%i: %swarning%s%s: %s\n", col_location, file, line, col_warning, wnam, col_none, string); pr_warning_count++; } return true; } pbool VARGS QCC_PR_Warning (int type, const char *file, int line, const char *error, ...) { va_list argptr; char string[1024]; if (!qccwarningaction[type]) return false; if (!error) return QCC_PR_PrintWarning(type, file, line, NULL); va_start (argptr,error); QC_vsnprintf (string,sizeof(string)-1, error,argptr); va_end (argptr); return QCC_PR_PrintWarning(type, file, line, string); } //can be used for errors, qcc execution will continue. pbool VARGS QCC_PR_ParseWarning (int type, const char *error, ...) { va_list argptr; char string[1024]; if (!qccwarningaction[type]) return false; va_start (argptr,error); QC_vsnprintf (string,sizeof(string)-1, error,argptr); va_end (argptr); return QCC_PR_PrintWarning(type, s_filen, pr_source_line, string); } void VARGS QCC_PR_Note (int type, const char *file, int line, const char *error, ...) { va_list argptr; char string[1024]; if (!qccwarningaction[type]) return; va_start (argptr,error); QC_vsnprintf (string,sizeof(string)-1, error,argptr); va_end (argptr); QCC_PR_PrintScope(); if (!file) externs->Printf ("note: %s\n", string); else if (flag_msvcstyle) externs->Printf ("%s(%i) : note: %s\n", file, line, string); else externs->Printf ("%s:%i: note: %s\n", file, line, string); } /* ============= PR_Expect Issues an error if the current token isn't equal to string Gets the next token ============= */ #ifndef COMMONINLINES void QCC_PR_Expect (const char *string) { if (STRCMP (string, pr_token)) { if (pr_token_type == tt_immediate && pr_immediate_type == type_string) { if (pr_immediate_strlen > 32) QCC_PR_ParseError (ERR_EXPECTED, "expected %s%s%s, found string immediate", col_location, string, col_none); else QCC_PR_ParseError (ERR_EXPECTED, "expected %s%s%s, found %s\"%s\"%s", col_location, string, col_none, col_name, pr_token, col_none); } else if (pr_token_type == tt_eof) QCC_PR_ParseError (ERR_EXPECTED, "expected %s%s%s, found %s%s%s", col_location, string, col_none, col_name, "", col_none); else QCC_PR_ParseError (ERR_EXPECTED, "expected %s%s%s, found %s%s%s", col_location, string, col_none, col_name, pr_token, col_none); } QCC_PR_Lex (); } #endif void QCC_PR_LexComment(char **comment) { char c; char *start; int nl; char *old; int oldlen; pbool replace = true; pbool nextcomment = true; // skip whitespace nl = false; while(nextcomment) { nextcomment = false; while ((c = *pr_file_p) && qcc_iswhite(c)) { if (qcc_islineending(c, pr_file_p[1])) //allow new lines, but only if there's whitespace before any tokens, and no double newlines. { if (nl) { pr_file_p++; QCC_PR_NewLine(false); break; } nl = true; } else { pr_file_p++; nl = false; } } if (nl) break; // parse // comments if (c=='/' && pr_file_p[1] == '/') { pr_file_p += 2; while (*pr_file_p == ' ' || *pr_file_p == '\t') pr_file_p++; start = pr_file_p; while (*pr_file_p && *pr_file_p != '\n') pr_file_p++; if (*pr_file_p == '\n') { pr_file_p++; QCC_PR_NewLine(false); } old = replace?NULL:*comment; replace = false; oldlen = old?strlen(old)+1:0; *comment = qccHunkAlloc(oldlen + (pr_file_p-start)+1); if (oldlen) { memcpy(*comment, old, oldlen-1); memcpy(*comment+oldlen-1, "\n", 1); } memcpy(*comment + oldlen, start, pr_file_p - start); oldlen = oldlen+pr_file_p - start; while(oldlen > 0 && ((*comment)[oldlen-1] == '\r' || (*comment)[oldlen-1] == '\n' || (*comment)[oldlen-1] == '\t' || (*comment)[oldlen-1] == ' ')) oldlen--; (*comment)[oldlen] = 0; nextcomment = true; //include the next // too nl = true; } // parse /* comments else if (c=='/' && pr_file_p[1] == '*' && replace) { pr_file_p+=1; start = pr_file_p+1; do { pr_file_p++; if (pr_file_p[0]=='\n') { QCC_PR_NewLine(true); } else if (pr_file_p[1] == 0) { QCC_PR_ParseError(0, "EOF inside comment\n"); break; } if (pr_file_p[0] == '/' && pr_file_p[1] == '*') QCC_PR_ParseWarning(WARN_NESTEDCOMMENT, "\"/*\" inside comment"); } while (pr_file_p[0] != '*' || pr_file_p[1] != '/'); if (pr_file_p[1] == 0) break; old = replace?NULL:*comment; replace = false; oldlen = old?strlen(old):0; *comment = qccHunkAlloc(oldlen + (pr_file_p-start)+1); memcpy(*comment, old, oldlen); memcpy(*comment + oldlen, start, pr_file_p - start); (*comment)[oldlen+pr_file_p - start] = 0; pr_file_p+=2; } } QCC_PR_LexWhitespace(false); } pbool QCC_PR_CheckTokenComment(const char *string, char **comment) { if (pr_token_type != tt_punct) return false; if (STRCMP (string, pr_token)) return false; if (comment) QCC_PR_LexComment(comment); //and then do the rest properly. QCC_PR_Lex (); return true; } /* ============= PR_Check Returns true and gets the next token if the current token equals string Returns false and does nothing otherwise ============= */ #ifndef COMMONINLINES pbool QCC_PR_CheckToken (const char *string) { if (pr_token_type != tt_punct) return false; if (STRCMP (string, pr_token)) return false; QCC_PR_Lex (); return true; } pbool QCC_PR_PeekToken (const char *string) { if (pr_token_type != tt_punct) return false; if (STRCMP (string, pr_token)) return false; return true; } pbool QCC_PR_CheckImmediate (const char *string) { if (pr_token_type != tt_immediate) return false; if (STRCMP (string, pr_token)) return false; QCC_PR_Lex (); return true; } pbool QCC_PR_CheckName(const char *string) { if (pr_token_type != tt_name) return false; if (flag_caseinsensitive) { if (stricmp (string, pr_token)) return false; } else { if (STRCMP(string, pr_token)) return false; } QCC_PR_Lex (); return true; } pbool QCC_PR_CheckKeyword(int keywordenabled, const char *string) { if (pr_token[0] == '_' && pr_token[1] == '_') { //lets just always go insensitive with a leading underscore pair. if (stricmp(string, pr_token+2)) return false; QCC_PR_Lex (); return true; } else { if (!keywordenabled) return false; if (flag_caseinsensitive) { if (stricmp (string, pr_token)) return false; } else { if (STRCMP(string, pr_token)) return false; } } QCC_PR_Lex (); return true; } #endif /* ============ PR_ParseName Checks to see if the current token is a valid name ============ */ char *QCC_PR_ParseName (void) { char ident[MAX_NAME]; char *ret; if (pr_token_type != tt_name) { if (pr_token_type == tt_eof) QCC_PR_ParseError (ERR_EOF, "unexpected EOF"); else if (strcmp(pr_token, "...")) //seriously? someone used '...' as an intrinsic NAME? QCC_PR_ParseError (ERR_NOTANAME, "\"%s%s%s\" - not a name", col_name, pr_token, col_none); } if (strlen(pr_token) >= MAX_NAME-1) QCC_PR_ParseError (ERR_NAMETOOLONG, "name too long"); strcpy (ident, pr_token); QCC_PR_Lex (); ret = qccHunkAlloc(strlen(ident)+1); strcpy(ret, ident); return ret; // return ident; } /* ============ PR_FindType Returns a preexisting complex type that matches the parm, or allocates a new one and copies it out. ============ */ //requires EVERYTHING to be the same static int typecmp_strict(QCC_type_t *a, QCC_type_t *b) { int i; if (a == b) return 0; if (!a || !b) return 1; //different (^ and not both null) if (a->type != b->type) return 1; if (a->num_parms != b->num_parms) return 1; if (a->vargs != b->vargs) return 1; if (a->vargcount != b->vargcount) return 1; if (a->size != b->size || a->bits != b->bits || a->align != b->align) return 1; if (a->accessors != b->accessors) return 1; if (STRCMP(a->name, b->name)) return 1; if (typecmp_strict(a->aux_type, b->aux_type)) return 1; i = a->num_parms; while(i-- > 0) { if (!a->params[i].paramname || !b->params[i].paramname) { if (a->params[i].paramname || b->params[i].paramname) return 1; //two array types getting compared? } else if (STRCMP(a->params[i].paramname, b->params[i].paramname)) return 1; if (typecmp_strict(a->params[i].type, b->params[i].type)) return 1; if (a->params[i].defltvalue.cast || b->params[i].defltvalue.cast) { if (typecmp_strict(a->params[i].defltvalue.cast, b->params[i].defltvalue.cast) || a->params[i].defltvalue.sym != b->params[i].defltvalue.sym || a->params[i].defltvalue.ofs != b->params[i].defltvalue.ofs) return 1; } } return 0; } //reports if they're functionally equivelent (allows assignments) int typecmp(QCC_type_t *a, QCC_type_t *b) { int i; if (a == b) return 0; if (!a || !b) return 1; //different (^ and not both null) if (a->type != b->type) return 1; if (a->num_parms != b->num_parms) return 1; if (a->vargs != b->vargs) return 1; if (a->vargcount != b->vargcount) return 1; if (a->size != b->size || a->bits != b->bits) return 1; if ((a->type == ev_entity && a->parentclass) || a->type == ev_struct || a->type == ev_union) { if (STRCMP(a->name, b->name)) return 1; } if (typecmp(a->aux_type, b->aux_type)) return 1; i = a->num_parms; while(i-- > 0) { if (a->type == ev_union && (!a->params[i].paramname || !b->params[i].paramname)) //special array weirdness. { if (a->params[i].paramname || b->params[i].paramname) return 1; } else if (a->type != ev_function && STRCMP(a->params[i].paramname, b->params[i].paramname)) return 1; if (typecmp(a->params[i].type, b->params[i].type)) return 1; if ((a->type != ev_function && (a->params[i].defltvalue.cast || b->params[i].defltvalue.cast)) || (a->type == ev_function && (a->params[i].defltvalue.cast && b->params[i].defltvalue.cast))) { if (typecmp(a->params[i].defltvalue.cast, b->params[i].defltvalue.cast) || a->params[i].defltvalue.sym != b->params[i].defltvalue.sym || a->params[i].defltvalue.ofs != b->params[i].defltvalue.ofs) return 1; } } return 0; } //compares the types, but doesn't complain if there are optional arguments which differ int typecmp_lax(QCC_type_t *a, QCC_type_t *b) { unsigned int minargs = 0; unsigned int t; if (a == b) return 0; if (!a || !b) return 1; //different (^ and not both null) if (a->type != b->type) { if (a->type == ev_accessor && a->parentclass) if (!typecmp_lax(a->parentclass, b)) return 0; if (b->type == ev_accessor && b->parentclass) if (!typecmp_lax(a, b->parentclass)) return 0; if (a->type != ev_variant && b->type != ev_variant) return 1; } else if (a->size != b->size) return 1; if (a->vargcount != b->vargcount) return 1; t = a->num_parms; minargs = t; t = b->num_parms; if (minargs > t) minargs = t; // if (STRCMP(a->name, b->name)) //This isn't 100% clean. // return 1; if (typecmp_lax(a->aux_type, b->aux_type)) return 1; //variants and args don't make sense, and are considered equivelent(ish). if (a->type == ev_variant || b->type == ev_variant) return 0; //optional arg types must match, even if they're not specified in one. for (t = 0; t < minargs; t++) { if (a->params[t].type->type != b->params[t].type->type) return 1; if (a->params[t].out != b->params[t].out) return 1; //classes/structs/unions are matched on class names rather than the contents of the class //it gets too painful otherwise, with recursive definitions. if (a->params[t].type->type == ev_entity || a->params[t].type->type == ev_struct || a->params[t].type->type == ev_union) { if (STRCMP(a->params[t].type->name, b->params[t].type->name)) return 1; } else { if (typecmp_lax(a->params[t].type, b->params[t].type)) return 1; } if ((a->type != ev_function && (a->params[t].defltvalue.cast || b->params[t].defltvalue.cast)) || (a->type == ev_function && (a->params[t].defltvalue.cast && b->params[t].defltvalue.cast))) { if (typecmp(a->params[t].defltvalue.cast, b->params[t].defltvalue.cast) || a->params[t].defltvalue.sym != b->params[t].defltvalue.sym || a->params[t].defltvalue.ofs != b->params[t].defltvalue.ofs) return 1; } } if (a->num_parms > minargs) { for (t = minargs; t < a->num_parms; t++) { if (!a->params[t].optional) return 1; } } if (b->num_parms > minargs) { for (t = minargs; t < b->num_parms; t++) { if (!b->params[t].optional) return 1; } } return 0; } QCC_type_t *QCC_PR_DuplicateType(QCC_type_t *in, pbool recurse) { QCC_type_t *out; if (!in) return NULL; out = QCC_PR_NewType(in->name, in->type, false); out->aux_type = recurse?QCC_PR_DuplicateType(in->aux_type, recurse):in->aux_type; out->num_parms = in->num_parms; out->params = qccHunkAlloc(sizeof(*out->params) * out->num_parms); memcpy(out->params, in->params, sizeof(*out->params) * out->num_parms); out->accessors = in->accessors; out->size = in->size; out->bits = in->bits; out->align = in->align; out->num_parms = in->num_parms; out->name = in->name; out->parentclass = in->parentclass; out->vargs = in->vargs; out->vargtodouble = in->vargtodouble; out->vargcount = in->vargcount; return out; } static void Q_strlcat(char *dest, const char *src, int sizeofdest) { if (sizeofdest) { int dlen = strlen(dest); int slen = strlen(src)+1; memcpy(dest+dlen, src, min((sizeofdest-1)-dlen, slen)); dest[sizeofdest - 1] = 0; } } char *TypeName(QCC_type_t *type, char *buffer, int buffersize) { char *ret; /*if (type->typedefed) { if (buffersize < 0) return buffer; *buffer = 0; Q_strlcat(buffer, type->name, buffersize); return buffer; }*/ if (type->type == ev_void) { if (buffersize < 0) return buffer; *buffer = 0; Q_strlcat(buffer, "void", buffersize); return buffer; } if (type->type == ev_enum) { if (buffersize < 0) return buffer; *buffer = 0; Q_strlcat(buffer, "enum ", buffersize); Q_strlcat(buffer, type->name, buffersize); Q_strlcat(buffer, ":", buffersize); TypeName(type->aux_type, buffer+strlen(buffer), buffersize-strlen(buffer)); return buffer; } if (type->type == ev_struct) { if (buffersize < 0) return buffer; *buffer = 0; Q_strlcat(buffer, "struct ", buffersize); Q_strlcat(buffer, type->name, buffersize); return buffer; } if (type->type == ev_union) { if (buffersize < 0) return buffer; *buffer = 0; Q_strlcat(buffer, "union ", buffersize); Q_strlcat(buffer, type->name, buffersize); return buffer; } if (type->type == ev_pointer) { if (buffersize < 0) return buffer; TypeName(type->aux_type, buffer, buffersize-2); Q_strlcat(buffer, "*", buffersize); return buffer; } ret = buffer; if (type->type == ev_field) { type = type->aux_type; *ret++ = '.'; } *ret = 0; if (type->type == ev_function) { int args = type->num_parms; size_t l; pbool vargs = type->vargs; unsigned int i; Q_strlcat(buffer, type->aux_type->name, buffersize); if (type->vargtodouble) Q_strlcat(buffer, "(*)", buffersize); //make it distinctly C-ey Q_strlcat(buffer, "(", buffersize); for (i = 0; i < type->num_parms; ) { if (type->params[i].out) Q_strlcat(buffer, "inout ", buffersize-1); if (type->params[i].optional) Q_strlcat(buffer, "optional ", buffersize-1); args--; l = strlen(buffer); TypeName(type->params[i].type, buffer+l, buffersize-1-l); if (type->params[i].paramname && *type->params[i].paramname) { Q_strlcat(buffer, " ", buffersize-1); Q_strlcat(buffer, type->params[i].paramname, buffersize-1); } if (type->params[i].defltvalue.cast) { Q_strlcat(buffer, " = ", buffersize-1); Q_strlcat(buffer, QCC_VarAtOffset(type->params[i].defltvalue), buffersize-1); } if (++i < type->num_parms || vargs) Q_strlcat(buffer, ", ", buffersize-1); } if (vargs) Q_strlcat(buffer, "...", buffersize-1); Q_strlcat(buffer, ")", buffersize); } else if (type->type == ev_entity && type->parentclass) { ret = buffer; *ret = 0; Q_strlcat(buffer, "class ", buffersize); Q_strlcat(buffer, type->name, buffersize); /* strcat(ret, " {"); type = type->param; while(type) { strcat(ret, type->name); type = type->next; if (type) strcat(ret, ", "); } strcat(ret, "}"); */ } else Q_strlcat(buffer, type->name, buffersize); return buffer; } //#define typecmp(a, b) (a && ((a)->type==(b)->type) && !STRCMP((a)->name, (b)->name)) static QCC_type_t *QCC_PR_FindType (QCC_type_t *type) { int t; for (t = 0; t < numtypeinfos; t++) { // check = &qcc_typeinfo[t]; if (typecmp_strict(&qcc_typeinfo[t], type)) continue; // c2 = check->next; // n2 = type->next; // for (i=0 ; n2&&c2 ; i++) // { // if (!typecmp((c2), (n2))) // break; // c2=c2->next; // n2=n2->next; // } // if (n2==NULL&&c2==NULL) { return &qcc_typeinfo[t]; } } type = QCC_PR_DuplicateType(type, false); return type; } /* QCC_type_t *QCC_PR_NextSubType(QCC_type_t *type, QCC_type_t *prev) { int p; if (!prev) return type->next; for (p = prev->num_parms; p; p--) prev = QCC_PR_NextSubType(prev, NULL); if (prev->num_parms) switch(prev->type) { case ev_function: } return prev->next; } */ QCC_type_t *QCC_TypeForName(const char *name) { QCC_type_t *t = pHash_Get(&typedeftable, name); while (t && t->scope) { if (t->scope == pr_scope) break; //its okay after all. t = pHash_GetNext(&typedeftable, name, t); } if (t && t->type == ev_typedef) return t->aux_type; //just use its real type. return t; } /* ============ PR_SkipToSemicolon For error recovery, also pops out of nested braces ============ */ void QCC_PR_SkipToSemicolon (void) { //escape out of any #define while (currentchunk && currentchunk->cnst) QCC_PR_UnInclude(); do { if (!pr_bracelevel && QCC_PR_CheckToken (";")) return; QCC_PR_Lex (); } while (pr_token_type != tt_eof); } qc_inlinestatic QCC_eval_t *QCC_SRef_Data(QCC_sref_t ref) { return (QCC_eval_t*)&ref.sym->symboldata[ref.ofs]; } /* ============ PR_ParseType Parses a variable type, including field and functions types ============ */ #ifdef MAX_EXTRA_PARMS char pr_parm_names[MAX_PARMS+MAX_EXTRA_PARMS][MAX_NAME]; #else char pr_parm_names[MAX_PARMS][MAX_NAME]; #endif char *pr_parm_argcount_name; int recursivefunctiontype; QCC_type_t *QCC_PR_MakeThiscall(QCC_type_t *orig, QCC_type_t *thistype) { QCC_type_t ftype = *orig; ftype.ptrto = NULL; ftype.typedefed = false; ftype.num_parms++; ftype.params = qccHunkAlloc(sizeof(*ftype.params) * ftype.num_parms); memcpy(ftype.params+1, orig->params, sizeof(*ftype.params) * orig->num_parms); ftype.params[0].paramname = "this"; ftype.params[0].type = QCC_PR_PointerType(thistype); ftype.params[0].isvirtual = true; memmove(&pr_parm_names[1], &pr_parm_names[0], sizeof(*pr_parm_names)*orig->num_parms); strcpy(pr_parm_names[0], "this"); orig = QCC_PR_FindType (&ftype); if (!orig) { orig = QCC_PR_NewType(ftype.name, ftype.type, false); *orig = ftype; } return orig; } QCC_type_t *QCC_PR_ParseArrayType(QCC_type_t *basetype, int *arraysize) { int dims = 0; unsigned long dim[64]; dim[0] = 0; do { if (dims == sizeof(dim)/sizeof(dim[0])) QCC_PR_ParseError(ERR_NOTANAME, "too many dimensions"); dim[dims] = QCC_PR_IntConstExpr(); if (!dim[dims]) QCC_PR_ParseError(ERR_NOTANAME, "cannot cope with 0-sized arrays"); dims++; QCC_PR_Expect("]"); } while (QCC_PR_CheckToken("[")); while(dims-- > 1) basetype = QCC_GenArrayType(basetype, dim[dims]); *arraysize = dim[0]; return basetype; } //expects a ( to have already been parsed. QCC_type_t *QCC_PR_ParseFunctionType (int newtype, QCC_type_t *returntype) { QCC_type_t *ftype = NULL, *t; char *name; int definenames = !recursivefunctiontype; int numparms = 0; struct QCC_typeparam_s paramlist[MAX_PARMS+MAX_EXTRA_PARMS]; if (QCC_PR_PeekToken("*")) return NULL; //C pointer-to-function. this ain't the args. recursivefunctiontype++; if (definenames) pr_parm_argcount_name = NULL; if (QCC_PR_CheckToken (")")) { if (!ftype) { ftype = QCC_PR_NewType(type_function->name, ev_function, false); ftype->aux_type = returntype; // return type ftype->num_parms = 0; } if (!flag_qcfuncs) ftype->vargs = true; //'void name()' is vargs/undefined in C89 (disallowed in c99, interpreted as void in c23). } else { do { int inout = -1; pbool isopt = false; if (numparms>=MAX_PARMS+MAX_EXTRA_PARMS) QCC_PR_ParseError(ERR_TOOMANYTOTALPARAMETERS, "Too many parameters. Sorry. (limit is %i)\n", MAX_PARMS+MAX_EXTRA_PARMS); if (QCC_PR_CheckToken ("...")) { t = QCC_PR_ParseType(false, true, false); //the evil things I do... if (!t) { if (!ftype) { ftype = QCC_PR_NewType(type_function->name, ev_function, false); ftype->aux_type = returntype; // return type ftype->num_parms = 0; } ftype->vargs = true; break; } else { //its a ... followed by a type... //undo the damage from having parsed the ... already. t = QCC_PR_FieldType(t); t = QCC_PR_FieldType(t); t = QCC_PR_FieldType(t); } } else { pbool maybename = numparms==0; while(1) { if (!isopt && QCC_PR_CheckKeyword(keyword_optional, "optional")) isopt = true; else if (inout<0 && QCC_PR_CheckKeyword(keyword_inout, "inout")) inout = true; else if (inout<0 && QCC_PR_CheckKeyword(keyword_inout, "out")) inout = 2; else if (inout<0 && QCC_PR_CheckKeyword(keyword_inout, "in")) inout = false; else break; maybename=false; //if we parsed something meaningful then its not a `void(name)(type)` thing } t = QCC_PR_ParseType(false, maybename, false); if (!t) { if (!flag_qcfuncs) t = type_integer; //c89 assumes int. else return NULL; } } if (!ftype) { ftype = QCC_PR_NewType(type_function->name, ev_function, false); ftype->aux_type = returntype; // return type ftype->num_parms = 0; } paramlist[numparms].optional = isopt; paramlist[numparms].isvirtual = false; paramlist[numparms].out = inout>=0?inout:0; paramlist[numparms].defltvalue.cast = NULL; paramlist[numparms].ofs = 0; paramlist[numparms].arraysize = 0; paramlist[numparms].type = t; if (!paramlist[numparms].type) QCC_PR_ParseError(0, "Expected type\n"); while (QCC_PR_CheckToken("*")) paramlist[numparms].type = QCC_PointerTypeTo(paramlist[numparms].type); if (inout < 0 && QCC_PR_CheckToken("&")) { //accept c++ syntax, at least on arguments. its not quite the same, but it'll do. paramlist[numparms].out = true; } // type->name = "FUNC PARAMETER"; paramlist[numparms].paramname = ""; if (QCC_PR_CheckToken ("...")) { ftype->vargs = true; break; } name = ""; if (QCC_PR_CheckToken("(")) { QCC_PR_CheckToken("*"); //one is normal for a function pointer/ref. non-ptr makes no sense here. name = QCC_PR_ParseName (); if (QCC_PR_CheckToken("[")) { if (!QCC_PR_CheckToken("]")) { //don't really care, doesn't matter paramlist[numparms].arraysize = QCC_PR_IntConstExpr(); QCC_PR_Expect("]"); } } QCC_PR_Expect(")"); QCC_PR_Expect("("); paramlist[numparms].type = QCC_PR_ParseFunctionType(false, paramlist[numparms].type); if (!paramlist[numparms].type) QCC_PR_ParseError(ERR_BADNOTTYPE, "expected function arg list"); } else if (pr_token_type == tt_name) name = QCC_PR_ParseName (); paramlist[numparms].paramname = name; if (definenames) strcpy (pr_parm_names[numparms], name); if (*name) newtype = true; else if (paramlist[numparms].type->type == ev_void) break; //float(void) has no actual args if (!paramlist[numparms].arraysize && QCC_PR_CheckToken("[")) { if (QCC_PR_CheckToken("]")) //length omitted. just treat it as a pointer...? { //QCC_PR_ParseWarning(ERR_BADARRAYSIZE, "unsized array argument"); paramlist[numparms].type = QCC_PointerTypeTo(paramlist[numparms].type); } else { //proper array int arraysize; paramlist[numparms].type = QCC_PR_ParseArrayType(paramlist[numparms].type, &arraysize); if (flag_qcfuncs) paramlist[numparms].arraysize = arraysize; else paramlist[numparms].type = QCC_PointerTypeTo(paramlist[numparms].type); //just turn it into a pointer and ditch the top-level size. C style. } } if (!flag_qcfuncs) { //if its an array type, promote it to pointer here. if (t->type == ev_union && t->num_parms == 1 && !t->params[0].paramname) paramlist[numparms].type = QCC_PointerTypeTo(t->params[0].type); } if (QCC_PR_CheckToken("=")) { paramlist[numparms].defltvalue = QCC_PR_ParseDefaultInitialiser(paramlist[numparms].type); QCC_FreeTemp(paramlist[numparms].defltvalue); } numparms++; } while (QCC_PR_CheckToken (",")); if (ftype->vargs) { if (!QCC_PR_CheckToken (")")) { name = QCC_PR_ParseName(); if (definenames) { pr_parm_argcount_name = qccHunkAlloc(strlen(name)+1); strcpy(pr_parm_argcount_name, name); } ftype->vargcount = true; QCC_PR_Expect (")"); } } else QCC_PR_Expect (")"); } ftype->vargtodouble = !flag_qcfuncs && flag_assume_double; ftype->num_parms = numparms; ftype->params = qccHunkAlloc(sizeof(*ftype->params) * numparms); memcpy(ftype->params, paramlist, sizeof(*ftype->params) * numparms); recursivefunctiontype--; if (newtype) return ftype; return QCC_PR_FindType (ftype); } QCC_type_t *QCC_PR_ParseFunctionTypeReacc (int newtype, QCC_type_t *returntype) { QCC_type_t *ftype, *nptype; char *name; int definenames = !recursivefunctiontype; int numparms = 0; struct QCC_typeparam_s paramlist[MAX_PARMS+MAX_EXTRA_PARMS]; recursivefunctiontype++; ftype = QCC_PR_NewType(type_function->name, ev_function, false); ftype->aux_type = returntype; // return type ftype->num_parms = 0; pr_parm_argcount_name = NULL; if (!QCC_PR_CheckToken (")")) { do { if (numparms>=MAX_PARMS+MAX_EXTRA_PARMS) QCC_PR_ParseError(ERR_TOOMANYTOTALPARAMETERS, "Too many parameters. Sorry. (limit is %i)\n", MAX_PARMS+MAX_EXTRA_PARMS); if (QCC_PR_CheckToken ("...")) { ftype->vargs = true; break; } if (QCC_PR_CheckName("arg")) { name = ""; nptype = QCC_PR_NewType("Variant", ev_variant, false); } else if (QCC_PR_CheckName("vect")) //this can only be of vector sizes, so... { name = ""; nptype = QCC_PR_NewType("Vector", ev_vector, false); } else { name = QCC_PR_ParseName(); QCC_PR_Expect(":"); nptype = QCC_PR_ParseType(true, false, false); } if (!nptype) QCC_PR_ParseError(0, "Expected type\n"); if (nptype->type == ev_void) break; // type->name = "FUNC PARAMETER"; paramlist[numparms].out = false; paramlist[numparms].optional = false; paramlist[numparms].isvirtual = false; paramlist[numparms].ofs = 0; paramlist[numparms].bitofs = 0; paramlist[numparms].arraysize = 0; paramlist[numparms].type = nptype; if (!*name) paramlist[numparms].paramname = ""; else { paramlist[numparms].paramname = qccHunkAlloc(strlen(name)+1); strcpy(paramlist[numparms].paramname, name); } if (definenames) QC_snprintfz(pr_parm_names[numparms], MAX_NAME, "%s", name); numparms++; } while (QCC_PR_CheckToken (";")); QCC_PR_Expect (")"); } ftype->num_parms = numparms; ftype->params = qccHunkAlloc(sizeof(*ftype->params) * numparms); memcpy(ftype->params, paramlist, sizeof(*ftype->params) * numparms); recursivefunctiontype--; if (newtype) return ftype; return QCC_PR_FindType (ftype); } QCC_type_t *QCC_PR_PointerType (QCC_type_t *pointsto) { QCC_type_t *ptype; char name[128]; if (pointsto->ptrto) return pointsto->ptrto; QC_snprintfz(name, sizeof(name), "%s*", pointsto->name); ptype = QCC_PR_NewType(strcpy(qccHunkAlloc(strlen(name)+1), name), ev_pointer, false); ptype->aux_type = pointsto; return pointsto->ptrto = QCC_PR_FindType (ptype); } QCC_type_t *QCC_PR_FieldType (QCC_type_t *pointsto) { QCC_type_t *ptype; char name[128]; if (pointsto->fldto) return pointsto->fldto; QC_snprintfz(name, sizeof(name), ".%s", pointsto->name); ptype = QCC_PR_NewType(strcpy(qccHunkAlloc(strlen(name)+1), name), ev_field, false); ptype->aux_type = pointsto; ptype->size = ptype->aux_type->size; return pointsto->fldto = QCC_PR_FindType (ptype); } QCC_type_t *QCC_PR_GenFunctionType (QCC_type_t *rettype, struct QCC_typeparam_s *args, int numargs) { int i; struct QCC_typeparam_s *p; QCC_type_t *ftype; ftype = QCC_PR_NewType("$func", ev_function, false); ftype->aux_type = rettype; ftype->num_parms = numargs; ftype->params = p = qccHunkAlloc(sizeof(*ftype->params)*numargs); ftype->vargs = false; ftype->vargcount = false; for (i = 0; i < numargs; i++, p++) { if (args[i].paramname) { p->paramname = qccHunkAlloc(strlen(args[i].paramname)+1); strcpy(p->paramname, args[i].paramname); } else p->paramname = ""; p->type = args[i].type; p->out = args[i].out; p->optional = args[i].optional; p->isvirtual = args[i].isvirtual; p->ofs = args[i].ofs; p->arraysize = args[i].arraysize; p->defltvalue.cast = NULL; } return QCC_PR_FindType (ftype); } struct accessor_s *QCC_PR_ParseAccessorMember(QCC_type_t *classtype, pbool isinline, pbool setnotget) { struct accessor_s *acc, *pacc; char *fieldtypename; QCC_type_t *fieldtype; QCC_type_t *indextype; QCC_sref_t def; QCC_type_t *functype; QCC_type_t *parenttype; struct QCC_typeparam_s arg[3]; int args; char *indexname; pbool isref; char *accessorname; if (QCC_PR_CheckToken("&")) isref = 2; else isref = QCC_PR_CheckToken("*"); fieldtypename = QCC_PR_ParseName(); fieldtype = QCC_TypeForName(fieldtypename); if (!fieldtype) QCC_PR_ParseError(ERR_NOTATYPE, "Invalid type: %s", fieldtypename); while(QCC_PR_CheckToken("*")) fieldtype = QCC_PR_PointerType(fieldtype); if (pr_token_type != tt_punct) accessorname = QCC_PR_ParseName(); else accessorname = ""; indextype = NULL; indexname = "index"; if (QCC_PR_CheckToken("[")) { fieldtypename = QCC_PR_ParseName(); indextype = QCC_TypeForName(fieldtypename); if (!QCC_PR_CheckToken("]")) { indexname = QCC_PR_ParseName(); QCC_PR_Expect("]"); } } QCC_PR_Expect("="); args = 0; memset(arg, 0, sizeof(arg)); strcpy (pr_parm_names[args], "this"); arg[args].paramname = "this"; if (isref == 2) { arg[args].type = classtype; arg[args].out = 1; //inout } else if (isref) arg[args].type = QCC_PointerTypeTo(classtype); else arg[args].type = classtype; args++; if (indextype) { strcpy (pr_parm_names[args], indexname); arg[args].paramname = indexname; arg[args++].type = indextype; } if (setnotget) { strcpy (pr_parm_names[args], "value"); arg[args].paramname = "value"; arg[args++].type = fieldtype; } functype = QCC_PR_GenFunctionType(setnotget?type_void:fieldtype, arg, args); if (pr_token_type != tt_name) { QCC_function_t *f; char funcname[256]; QC_snprintfz(funcname, sizeof(funcname), "%s::%s_%s", classtype->name, setnotget?"set":"get", accessorname); def = QCC_PR_GetSRef(functype, funcname, NULL, true, 0, GDF_CONST | (isinline?GDF_INLINE:0)); if (autoprototype) { if (QCC_PR_CheckToken("[")) { while (!QCC_PR_CheckToken("]")) { if (pr_token_type == tt_eof) break; QCC_PR_Lex(); } } QCC_PR_Expect("{"); { int blev = 1; //balance out the { and } while(blev) { if (pr_token_type == tt_eof) break; if (QCC_PR_CheckToken("{")) blev++; else if (QCC_PR_CheckToken("}")) blev--; else QCC_PR_Lex(); //ignore it. } } } else { pr_classtype = ((classtype->type==ev_entity)?classtype:NULL); f = QCC_PR_ParseImmediateStatements (def.sym, functype, false); pr_classtype = NULL; pr_scope = NULL; QCC_SRef_Data(def)->function = f - functions; f->def = def.sym; def.sym->initialized = 1; } } else { const char *funcname = QCC_PR_ParseName(); def = QCC_PR_GetSRef(functype, funcname, NULL, true, 0, GDF_CONST|(isinline?GDF_INLINE:0)); if (!def.cast) QCC_Error(ERR_NOFUNC, "%s::set_%s: %s was not defined", classtype->name, accessorname, funcname); } if (!def.cast || !def.sym || def.sym->temp) QCC_Error(ERR_NOFUNC, "%s::%s_%s function invalid", classtype->name, setnotget?"set":"get", accessorname); for (acc = classtype->accessors; acc; acc = acc->next) if (!strcmp(acc->fieldname, accessorname)) break; if (!acc) { acc = qccHunkAlloc(sizeof(*acc)); acc->fieldname = accessorname; acc->next = classtype->accessors; acc->type = fieldtype; acc->indexertype = indextype; classtype->accessors = acc; } if (acc->getset_func[setnotget].cast && ( acc->getset_func[setnotget].sym != def.sym || acc->getset_func[setnotget].cast != def.cast || acc->getset_func[setnotget].ofs != def.ofs)) QCC_Error(ERR_TOOMANYINITIALISERS, "%s::%s_%s already declared", classtype->name, setnotget?"set":"get", accessorname); acc->getset_func[setnotget] = def; acc->getset_isref[setnotget] = isref; QCC_FreeTemp(def); for (parenttype = classtype->parentclass; parenttype; parenttype = parenttype->parentclass) { if (!parenttype->accessors) continue; for (pacc = parenttype->accessors; pacc; pacc = pacc->next) { if (!strcmp(acc->fieldname, pacc->fieldname)) QCC_PR_ParseWarning(WARN_DUPLICATEDEFINITION, "%s::%s shadows parent %s", classtype->name, acc->fieldname?acc->fieldname:"", parenttype->name); } } return acc; } extern char *basictypenames[]; extern QCC_type_t **basictypes[]; static QCC_type_t *QCC_PR_ParseStruct(etype_t structtype) { QCC_type_t *newt, *type, *newparm, *oldtype = NULL; struct QCC_typeparam_s *parms = NULL, *oldparm; int numparms = 0; int ofs, bitofs; unsigned int arraysize; char *parmname; pbool isnonvirt = false; pbool isstatic = false; pbool isvirt = false; pbool definedsomething = false; unsigned int bitsize; unsigned int bitalign = 0; //alignment of largest element... if (QCC_PR_CheckToken("{")) { //nameless struct newt = QCC_PR_NewType(structtype==ev_union?"":"", structtype, false); } else { QCC_type_t *parenttype; char *tname = QCC_PR_ParseName(); if (structtype == ev_struct && QCC_PR_CheckToken(":")) { char *parentname = QCC_PR_ParseName(); parenttype = QCC_TypeForName(parentname); if (!parenttype) QCC_PR_ParseError(ERR_NOTANAME, "Parent type %s was not yet defined", parentname); if (parenttype->type != ev_struct) QCC_PR_ParseError(ERR_NOTANAME, "Parent type %s is not a struct", parentname); } else parenttype = NULL; newt = QCC_TypeForName(tname); if (!newt) { newt = QCC_PR_NewType(tname, ev_struct, true); newt->parentclass = parenttype; } else if (!newt->size && !newt->parentclass) newt->parentclass = parenttype; else if (parenttype && newt->parentclass != parenttype) QCC_PR_ParseError(ERR_NOTANAME, "Redeclaration of struct with different parent type"); //struct declaration only, not definition. if (parenttype) QCC_PR_Expect("{"); else if (!QCC_PR_CheckToken("{")) return newt; if (newt->size) { // QCC_PR_ParseError(ERR_NOTANAME, "%s %s is already defined", structtype==ev_union?"union":"struct", newt->name); oldtype = newt; newt = QCC_PR_NewType(tname, ev_struct, false); newt->parentclass = parenttype; } } if (newt->parentclass) newt->size = newt->parentclass->size; else newt->size=0; bitsize = newt->size<<5; type = NULL; newparm = NULL; for (;;) { //in qc, functions are assignable references like anything else, so no modifiers means the qc will need to assign to it somewhere. //virtual functions are still references, but we initialise them somewhere //nonvirtual functions and static functions are kinda the same thing QCC_sref_t defaultval; if (QCC_PR_CheckToken("}")) { if (newparm) QCC_PR_ParseError(ERR_EXPECTED, "missing semi-colon"); break; } else if (QCC_PR_CheckToken(";")) { newparm = NULL; continue; } else if (QCC_PR_CheckToken(",")) { //same as last type, unless initial/after-semicolon if (!newparm) QCC_PR_ParseError(ERR_EXPECTED, "element missing type"); } else { //new type! if (newparm) QCC_PR_ParseError(ERR_EXPECTED, "missing semi-colon"); //allow a missing semi-colon on functions, for mixed-style functions. //reset these... isnonvirt = false; isstatic = false; isvirt = false; //parse field modifiers if (QCC_PR_CheckKeyword(1, "public")) { /*ispublic = true;*/ QCC_PR_Expect(":"); continue; } else if (QCC_PR_CheckKeyword(1, "private")) { /*isprivate = true;*/ QCC_PR_Expect(":"); continue; } else if (QCC_PR_CheckKeyword(1, "protected")) { /*isprotected = true;*/ QCC_PR_Expect(":"); continue; } //if (QCC_PR_CheckKeyword(1, "nonvirtual")) // isnonvirt = true; //else if (QCC_PR_CheckKeyword(1, "static")) isstatic = true; else if (QCC_PR_CheckKeyword(1, "virtual")) isvirt = true; // else if (QCC_PR_CheckKeyword(1, "ignore")) // isignored = true; // else if (QCC_PR_CheckKeyword(1, "strip")) // isignored = true; //now parse the actual type. newparm = QCC_PR_ParseType(false, false, true); definedsomething = false; } type = newparm; while (QCC_PR_CheckToken("*")) type = QCC_PointerTypeTo(type); arraysize = 0; if (QCC_PR_CheckToken(";")) { //annonymous structs do weird scope stuff. if (!definedsomething && (type->type != ev_struct && type->type != ev_union)) { if (flag_qcfuncs && type->type == ev_function && type->aux_type == newt && QCC_PR_PeekToken(";")) QCC_PR_ParseWarning(WARN_POINTLESSSTATEMENT, "constructors are not supported in structs at this time"); else QCC_PR_ParseWarning(WARN_POINTLESSSTATEMENT, "declaration does not declare anything (%s)", newparm->name); } newparm = NULL; parmname = ""; } else { if (QCC_PR_CheckToken("(")) { QCC_PR_CheckToken("*"); //this is fine... parmname = QCC_PR_ParseName(); QCC_PR_Expect(")"); } else parmname = QCC_PR_ParseName(); definedsomething = true; if (QCC_PR_CheckToken("[")) type = QCC_PR_ParseArrayType(type, &arraysize); //Checks for [][y][x] arrays. if (QCC_PR_CheckToken("(")) { type = QCC_PR_ParseFunctionType(false, type); if (!type) QCC_PR_ParseError(ERR_BADNOTTYPE, "expected function arg list"); } } if (type == newt || ((type->type == ev_struct || type->type == ev_union) && !type->size)) { QCC_PR_ParseWarning(ERR_NOTANAME, "type %s not fully defined yet", type->name); continue; } if (QCC_PR_CheckToken(":")) { int bits = QCC_PR_IntConstExpr(); if (bits > 64) QCC_PR_ParseWarning(ERR_BADNOTTYPE, "too many bits"); else if (type->type == ev_integer || type->type == ev_uint || type->type == ev_int64 || type->type == ev_uint64) { QCC_type_t *bitfld; //prmote to bigger if big. if (bits > 32 && type->type == ev_integer) type = type_int64; if (bits > 32 && type->type == ev_uint) type = type_uint64; bitfld = QCC_PR_NewType("bitfld", ev_bitfld, false); bitfld->bits = bits; bitfld->size = type->size; bitfld->parentclass = type; bitfld->align = type->align; //use the parent's alignment, for some reason. type = bitfld; } else QCC_PR_ParseWarning(ERR_BADNOTTYPE, "bitfields must be integer types"); } if ((isnonvirt || isvirt) && type->type != ev_function) QCC_PR_ParseWarning(ERR_INTERNAL, "[non]virtual members must be functions"); //static members are technically just funny-named globals, and do not generate fields. if (isnonvirt || isstatic || isvirt) { //either way its a regular global. the difference being static has no implicit this/self argument. QCC_def_t *d; char membername[2048]; if (!isstatic) type = QCC_PR_MakeThiscall(type, newt); QC_snprintfz(membername, sizeof(membername), "%s::%s", newt->name, parmname); d = QCC_PR_GetDef(type, membername, NULL, true, 0, (type->type==ev_function)?GDF_CONST:0); if (QCC_PR_CheckToken("=") || (type->type == ev_function && QCC_PR_PeekToken("{"))) { //FIXME: methods cannot be compiled yet, as none of the fields are not actually defined yet. pr_classtype = newt; QCC_PR_ParseInitializerDef(d, 0); pr_classtype = NULL; } QCC_FreeDef(d); if (!QCC_PR_PeekToken(",")) newparm = NULL; if (isvirt) { defaultval.ofs = 0; defaultval.cast = d->type; defaultval.sym = d; } else continue; } else { defaultval.cast = NULL; if (QCC_PR_CheckToken("=")) { defaultval = QCC_PR_ParseDefaultInitialiser(type); QCC_PR_ParseWarning(ERR_INTERNAL, "TODO: pre-initialised struct members are not implemented yet"); } } parms = realloc(parms, sizeof(*parms) * (numparms+4)); oldparm = QCC_PR_FindStructMember(newt, parmname, &ofs, &bitofs); if (type->align) { if (bitalign < type->align) bitalign = type->align; //bigger than that... } else bitalign = 32; //struct must be word aligned. if (oldparm && oldparm->arraysize == arraysize && !typecmp_lax(oldparm->type, type)) { ofs <<= 5; ofs += bitofs; if (!isvirt) continue; } else if (structtype == ev_union) { ofs = 0; if (type->bits) { if (bitsize < type->bits*(arraysize?arraysize:1)) bitsize = type->bits*(arraysize?arraysize:1); } else { if (bitsize < 32*type->size*(arraysize?arraysize:1)) bitsize = 32*type->size*(arraysize?arraysize:1); } } else { if (type->bits) { if (type->bits < type->align && type->type == ev_bitfld) { //only realigns when it cross its parent's alignment boundary. if ((bitsize&(type->align-1)) + type->bits > type->align) bitsize = (bitsize+type->align-1)&~(type->align-1); //these must be aligned, so we can take pointers etc. } else if (type->align) bitsize = (bitsize+type->align-1)&~(type->align-1); //these must be aligned, so we can take pointers etc. ofs = bitsize; bitsize += type->bits*(arraysize?arraysize:1); } else { bitsize = (bitsize+31)&~31; //provide padding to keep non-bitfield stuff word aligned ofs = bitsize; bitsize += 32 * type->size*(arraysize?arraysize:1); } } parms[numparms].ofs = ofs>>5; parms[numparms].bitofs = ofs - (parms[numparms].ofs<<5); parms[numparms].arraysize = arraysize; parms[numparms].out = false; parms[numparms].optional = false; parms[numparms].isvirtual = isvirt; parms[numparms].paramname = parmname; parms[numparms].type = type; parms[numparms].defltvalue = defaultval; numparms++; /*if (type->type == ev_vector && arraysize == 0) { //add in vec_x/y/z members too...? int c; for (c = 0; c < 3; c++) { parms[numparms].ofs = ofs + c; parms[numparms].arraysize = arraysize; parms[numparms].out = false; parms[numparms].optional = true; parms[numparms].isvirtual = isvirt; parms[numparms].paramname = qccHunkAlloc(strlen(parmname)+3); sprintf(parms[numparms].paramname, "%s_%c", parmname, 'x'+c); parms[numparms].type = type_float; parms[numparms].defltvalue = nullsref; numparms++; } }*/ } if (bitalign) //compute tail padding. bitsize = (bitsize+bitalign-1)&~(bitalign-1); newt->size = (bitsize+31)>>5; //FIXME: change to bytes, for pointers. newt->align = bitalign; if (bitalign&31) newt->bits = bitsize; else newt->bits = 0; //all word aligned. nothing special going on sizewise (maybe inside though). if (!numparms) QCC_PR_ParseError(ERR_NOTANAME, "%s %s has no members", structtype==ev_union?"union":"struct", newt->name); newt->num_parms = numparms; newt->params = qccHunkAlloc(sizeof(*type->params) * numparms); memcpy(newt->params, parms, sizeof(*type->params) * numparms); free(parms); if (oldtype) { if (typecmp_strict(newt, oldtype)) QCC_PR_ParseError(ERR_NOTANAME, "%s %s redeclared differently", structtype==ev_union?"union":"struct", newt->name); newt = oldtype; } return newt; } QCC_type_t *QCC_PR_ParseEntClass(void) { QCC_type_t *newt, *type, *fieldtype, *newparm; char membername[2048]; char *classname; int forwarddeclaration; int numparms = 0; struct QCC_typeparam_s *parms = NULL; char *parmname; int arraysize; pbool redeclaration; int basicindex; QCC_def_t *d; QCC_type_t *pc; QCC_type_t *basetype; pbool found = false; int assumevirtual = 0; //0=erk, 1=yes, -1=no parmname = QCC_PR_ParseName(); classname = qccHunkAlloc(strlen(parmname)+1); strcpy(classname, parmname); newt = 0; if (QCC_PR_CheckToken(":")) { char *parentname = QCC_PR_ParseName(); fieldtype = QCC_TypeForName(parentname); if (!fieldtype) QCC_PR_ParseError(ERR_NOTANAME, "Parent class %s was not yet defined", parentname); //FIXME: we should allow inheriting from 'object' too. if (fieldtype->type != ev_entity) QCC_PR_ParseError(ERR_NOTANAME, "Parent type %s is not a class/entity", parentname); forwarddeclaration = false; QCC_PR_Expect("{"); } else { fieldtype = type_entity; forwarddeclaration = !QCC_PR_CheckToken("{"); } newt = QCC_TypeForName(classname); if (newt && newt->num_parms != 0) redeclaration = true; else redeclaration = false; if (!newt) { newt = QCC_PR_NewType(classname, fieldtype->type, true); newt->size=type_entity->size; } type = NULL; if (forwarddeclaration) return newt; if (pr_scope) QCC_PR_ParseError(ERR_REDECLARATION, "Declaration of class %s%s%s within function", col_type,classname,col_none); if (redeclaration && fieldtype != newt->parentclass) QCC_PR_ParseError(ERR_REDECLARATION, "Parent class changed on redeclaration of %s%s%s", col_type,classname,col_none); newt->parentclass = fieldtype; newt->filen = s_filen; newt->line = pr_source_line; if (QCC_PR_CheckToken(",")) QCC_PR_ParseError(ERR_NOTANAME, "member missing name"); while (!QCC_PR_CheckToken("}")) { unsigned int gdf_flags; pbool wasvirt = false; pbool wasnonvirt = false; pbool wasstatic = false; pbool isconst = false; pbool isvar = false; pbool isignored = false; pbool isinline = false; pbool isget = false; pbool isset = false; // pbool ispublic = false; // pbool isprivate = false; // pbool isprotected = false; pbool firstkeyword = true; while(1) { if (QCC_PR_CheckKeyword(1, "nonvirtual")) { if (firstkeyword && QCC_PR_CheckToken(":")) { //fteqcc-specific assumevirtual = -1; continue; } wasnonvirt = true; } else if (QCC_PR_CheckKeyword(1, "static")) wasstatic = true; else if (QCC_PR_CheckKeyword(1, "const")) isconst = wasstatic = true; else if (QCC_PR_CheckKeyword(1, "var")) isvar = true; else if (QCC_PR_CheckKeyword(1, "virtual")) { if (firstkeyword && QCC_PR_CheckToken(":")) { //fteqcc-specific assumevirtual = 1; continue; } wasvirt = true; } else if (QCC_PR_CheckKeyword(1, "ignore")) isignored = true; else if (QCC_PR_CheckKeyword(1, "strip")) isignored = true; else if (QCC_PR_CheckKeyword(1, "inline")) isinline = true; else if (QCC_PR_CheckKeyword(1, "get")) isget = true; else if (QCC_PR_CheckKeyword(1, "set")) isset = true; else if (firstkeyword && QCC_PR_CheckKeyword(1, "public")) { /*ispublic = true;*/ QCC_PR_Expect(":"); continue; } else if (firstkeyword && QCC_PR_CheckKeyword(1, "private")) { QCC_PR_Expect(":"); /*isprivate = true; */ continue; } else if (firstkeyword && QCC_PR_CheckKeyword(1, "protected")) { QCC_PR_Expect(":"); /*isprotected = true;*/ QCC_PR_Expect(":"); continue; } else break; firstkeyword = false; } if (isget || isset) { if (wasvirt) QCC_PR_ParseWarning(ERR_INTERNAL, "virtual accessors are not supported at this time"); if (wasstatic) QCC_PR_ParseError(ERR_INTERNAL, "static accessors are not supported"); QCC_PR_ParseAccessorMember(newt, isinline, isset); QCC_PR_CheckToken(";"); continue; } basetype = QCC_PR_ParseType(false, false, false); if (!basetype) QCC_PR_ParseError(ERR_INTERNAL, "In class %s, expected type, found %s", classname, pr_token); if (basetype->type == ev_struct || basetype->type == ev_union) //we wouldn't be able to handle it. QCC_PR_ParseError(ERR_INTERNAL, "Struct or union in class %s", classname); for (;basetype;) { pbool havebody = false; pbool isnull = false; pbool isstatic = wasstatic; pbool isvirt = wasvirt; pbool isnonvirt = wasnonvirt; if (flag_qcfuncs && basetype->type == ev_function && basetype->aux_type == newt && QCC_PR_PeekToken(";")) { //C++ style constructor (ie: missing return type followed by class name) //swap the class out for the appropriate function type... newparm = QCC_PR_GenFunctionType(type_void, basetype->params, basetype->num_parms); parmname = classname; arraysize = 0; } else if (!flag_qcfuncs && basetype == newt && QCC_PR_CheckToken("(")) { newparm = QCC_PR_ParseFunctionType(false, type_void); if (!newparm) QCC_PR_ParseError(ERR_BADNOTTYPE, "expected function arg list"); parmname = classname; arraysize = 0; } else { parmname = QCC_PR_ParseName(); if (QCC_PR_CheckToken("[")) { arraysize = QCC_PR_IntConstExpr(); QCC_PR_Expect("]"); } else arraysize = 0; if (QCC_PR_CheckToken("(")) { //int fnc(), fld; is valid. newparm = QCC_PR_ParseFunctionType(false, basetype); if (!newparm) QCC_PR_ParseError(ERR_BADNOTTYPE, "expected function arg list"); } else newparm = basetype; } if (newparm->type == ev_function) { if (!strcmp(classname, parmname)) { if (isstatic) QCC_PR_ParseWarning(WARN_MISSINGMEMBERQUALIFIER, "Constructor %s::%s should not be static.", classname, pr_token); if (!isvirt) isnonvirt = true;//silently promote constructors to static if (newparm->aux_type->type != ev_void) QCC_PR_ParseWarning(WARN_MISSINGMEMBERQUALIFIER, "Constructor %s::%s does not return void.", classname, pr_token); if (newparm->num_parms != 0 || newparm->vargs) QCC_PR_ParseWarning(WARN_MISSINGMEMBERQUALIFIER, "Constructor arguments are not supported at this time %s::%s. Use named fields instead.", classname, pr_token); } else if (!isvirt && !isnonvirt && !isstatic) { if (assumevirtual == 1) isvirt = true; else if (assumevirtual == -1) isnonvirt = true; else { QCC_PR_ParseWarning(WARN_MISSINGMEMBERQUALIFIER, "%s::%s was not qualified. Assuming non-virtual.", classname, parmname); isnonvirt = true; } } else if (isvirt+isnonvirt+isstatic != 1) QCC_PR_ParseError(ERR_INTERNAL, "Multiple conflicting qualifiers on %s::%s.", classname, pr_token); if (isstatic) { //static and non-virtual functions differ only in that static should not have a 'this' available. however there's no hiding 'self' for calling in to other functions. isstatic = false; isnonvirt = true; // QCC_PR_ParseError(ERR_INTERNAL, "%s::%s static member functions are not supported at this time.", classname, parmname); } } else { if (isvirt||isnonvirt) QCC_Error(ERR_INTERNAL, "virtual keyword on member that is not a function"); if (isinline) QCC_Error(ERR_INTERNAL, "inline keyword on member that is not a function"); } if (QCC_PR_CheckToken("=")) havebody = true; else if (newparm->type == ev_function && QCC_PR_PeekToken("{")) havebody = true; if (isinline && (!havebody || isvirt)) QCC_Error(ERR_INTERNAL, "inline keyword on function prototype or virtual function"); gdf_flags = 0; if ((newparm->type == ev_function && !arraysize && !isvar) || isconst) gdf_flags = GDF_CONST; if (havebody) { QCC_def_t *def; if (pr_scope) QCC_Error(ERR_INTERNAL, "Nested function declaration"); isnull = (QCC_PR_CheckImmediate("0") || QCC_PR_CheckImmediate("0i")); QC_snprintfz(membername, sizeof(membername), "%s::%s", classname, parmname); if (isnull) { if (isignored) def = NULL; else { def = QCC_PR_GetDef(newparm, membername, NULL, true, 0, gdf_flags); def->symboldata[def->ofs].function = 0; def->initialized = 1; } } else { if (isignored) def = NULL; else def = QCC_PR_GetDef(newparm, membername, NULL, true, 0, gdf_flags); if (newparm->type != ev_function && !isstatic) QCC_Error(ERR_INTERNAL, "Can only initialise member functions"); else { if (autoprototype || isignored) { if (QCC_PR_CheckToken("[")) { while (!QCC_PR_CheckToken("]")) { if (pr_token_type == tt_eof) break; QCC_PR_Lex(); } } QCC_PR_Expect("{"); { int blev = 1; //balance out the { and } while(blev) { if (pr_token_type == tt_eof) break; if (QCC_PR_CheckToken("{")) blev++; else if (QCC_PR_CheckToken("}")) blev--; else QCC_PR_Lex(); //ignore it. } } } else { pr_classtype = newt; QCC_PR_ParseInitializerDef(def, 0); pr_classtype = NULL; /* f = QCC_PR_ParseImmediateStatements (def, newparm); pr_classtype = NULL; pr_scope = NULL; def->symboldata[def->ofs].function = f - functions; f->def = def; def->initialized = 1;*/ } } } if (def) QCC_FreeDef(def); if (!isvirt && !isignored) { QCC_def_t *fdef; QCC_type_t *pc; unsigned int i; for (pc = newt->parentclass; pc; pc = pc->parentclass) { for (i = 0; i < pc->num_parms; i++) { if (!strcmp(pc->params[i].paramname, parmname)) { QCC_PR_ParseWarning(WARN_DUPLICATEDEFINITION, "%s::%s is virtual inside parent class '%s'. Did you forget the 'virtual' keyword?", newt->name, parmname, pc->name); break; } } if (i < pc->num_parms) break; } if (!pc) { fdef = QCC_PR_GetDef(NULL, parmname, NULL, false, 0, GDF_CONST); if (fdef && fdef->type->type == ev_field) { QCC_PR_ParseWarning(WARN_DUPLICATEDEFINITION, "%s::%s is virtual inside parent class 'entity'. Did you forget the 'virtual' keyword?", newt->name, parmname); QCC_PR_ParsePrintDef(0, fdef); } else if (fdef) { QCC_PR_ParseWarning(WARN_DUPLICATEDEFINITION, "%s::%s shadows a global", newt->name, parmname); QCC_PR_ParsePrintDef(0, fdef); } if (fdef) QCC_FreeDef(fdef); } } } if (!QCC_PR_CheckToken(",")) { QCC_PR_Expect(";"); basetype = NULL; } if (isignored) //member doesn't really exist continue; //static members are technically just funny-named globals, and do not generate fields. if (isnonvirt || isstatic || (newparm->type == ev_function && !arraysize)) { QC_snprintfz(membername, sizeof(membername), "%s::%s", classname, parmname); QCC_FreeDef(QCC_PR_GetDef(newparm, membername, NULL, true, 0, gdf_flags)); if (isnonvirt || isstatic) continue; } fieldtype = QCC_PR_NewType(parmname, ev_field, false); fieldtype->aux_type = newparm; fieldtype->size = newparm->size; parms = realloc(parms, sizeof(*parms) * (numparms+1)); parms[numparms].ofs = 0; parms[numparms].bitofs = 0; parms[numparms].out = false; parms[numparms].optional = false; parms[numparms].isvirtual = isvirt; parms[numparms].paramname = parmname; parms[numparms].arraysize = arraysize; parms[numparms].type = newparm; parms[numparms].defltvalue.cast = NULL; basicindex = 0; found = false; for(pc = newt; pc && !found; pc = pc->parentclass) { struct QCC_typeparam_s *pp; int numpc; int i; if (pc == newt) { pp = parms; numpc = numparms; } else { pp = pc->params; numpc = pc->num_parms; } for (i = 0; i < numpc; i++) { if (pp[i].type->type == newparm->type) { if (!strcmp(pp[i].paramname, parmname)) { if (typecmp(pp[i].type, newparm)) { char bufc[256]; char bufp[256]; TypeName(pp[i].type, bufp, sizeof(bufp)); TypeName(newparm, bufc, sizeof(bufc)); QCC_PR_ParseError(0, "%s defined as %s in %s, but %s in %s\n", parmname, bufc, newt->name, bufp, pc->name); } basicindex = pp[i].ofs; found = true; break; } if ((unsigned int)basicindex < pp[i].ofs+pp[i].type->size*(pp[i].arraysize?pp[i].arraysize:1)) //if we found one with the index basicindex = pp[i].ofs+pp[i].type->size*(pp[i].arraysize?pp[i].arraysize:1); //make sure we don't union it. } } } /* iterate over every parent-class and see if our method already exists in conflicting nonvirtual type form */ if (isvirt) { for(pc = newt; pc && !found; pc = pc->parentclass) { struct QCC_typeparam_s *pp; int numpc; int i; found = false; if (pc == newt) continue; pp = parms; numpc = numparms; /* iterate over all of the virtual methods */ for (i = 0; i < numpc; i++) { if (pp[i].type->type == newparm->type) { /* if we found it, abandon this loop - we still have to check the other classes however */ if (!strcmp(pp[i].paramname, parmname)) { found = true; break; } } } /* we didn't find it as a field... so check if it exists as a nonvirtual method */ if (found == false) { QC_snprintfz(membername, sizeof(membername), "%s::%s", pc->name, parmname); /* if we found it, game over */ if (QCC_PR_GetDef(NULL, membername, NULL, false, 0, 0)) QCC_PR_ParseError(0, "%s defined as virtual in %s, but nonvirtual in %s\n", parmname, newt->name, pc->name); } } } parms[numparms].ofs = basicindex; //ulp, its new numparms++; if (found) continue; if (!*basictypes[newparm->type]) QCC_PR_ParseError(0, "members of type %s are not supported (%s::%s)\n", basictypenames[newparm->type], classname, parmname); //make sure the union is okay d = QCC_PR_GetDef(NULL, parmname, NULL, 0, 0, GDF_CONST); if (d) basicindex = 0; else { //don't go all weird with unioning generic fields QC_snprintfz(membername, sizeof(membername), "::*%s", basictypenames[newparm->type]); d = QCC_PR_GetDef(NULL, membername, NULL, 0, 0, GDF_CONST); if (!d) { d = QCC_PR_GetDef(QCC_PR_FieldType(*basictypes[newparm->type]), membername, NULL, 2, 0, GDF_CONST|GDF_POSTINIT|GDF_USED); // for (i = 0; (unsigned int)i < newparm->size*(arraysize?arraysize:1); i++) // d->symboldata[i]._int = pr.size_fields+i; // pr.size_fields += i; d->used = true; d->referenced = true; //always referenced, so you can inherit safely. } if (d->arraysize < basicindex+(arraysize?arraysize:1)) { if (d->symboldata) QCC_PR_ParseError(ERR_INTERNAL, "array members are kinda limited, sorry. try rearranging them or adding padding for alignment\n"); //FIXME: add relocs to cope with this all of a type can then be contiguous and thus allow arrays. else { int newsize = basicindex+(arraysize?arraysize:1); if (d->type->type == ev_union || d->type->type == ev_struct) d->arraysize = newsize; else while(d->arraysize < newsize) { QC_snprintfz(membername, sizeof(membername), "::%s[%i]", basictypenames[newparm->type], d->arraysize/d->type->size); QCC_PR_DummyDef(d->type, membername, d->scope, 0, d, d->arraysize, true, GDF_CONST); d->arraysize+=d->type->size; } } } } QCC_FreeDef(d); //and make sure we can do member::__fname //actually, that seems pointless. QC_snprintfz(membername, sizeof(membername), "%s::"MEMBERFIELDNAME, classname, parmname); // externs->Printf("define %s -> %s\n", membername, d->name); d = QCC_PR_DummyDef(fieldtype, membername, pr_scope, arraysize, d, basicindex, true, (isnull?0:GDF_CONST)|(opt_classfields?GDF_STRIP:0)); d->referenced = true; //always referenced, so you can inherit safely. } } if (redeclaration) { int i; redeclaration = newt->num_parms != numparms; for (i = 0; i < numparms && (unsigned int)i < newt->num_parms; i++) { if (newt->params[i].arraysize != parms[i].arraysize || typecmp(newt->params[i].type, parms[i].type) || strcmp(newt->params[i].paramname, parms[i].paramname)) { QCC_PR_ParseError(ERR_REDECLARATION, "Incompatible redeclaration of class %s. %s differs.", classname, parms[i].paramname); break; } } if (newt->num_parms != numparms) QCC_PR_ParseError(ERR_REDECLARATION, "Incompatible redeclaration of class %s.", classname); } else { newt->num_parms = numparms; newt->params = qccHunkAlloc(sizeof(*type->params) * numparms); memcpy(newt->params, parms, sizeof(*type->params) * numparms); } free(parms); { QCC_def_t *d; //if there's a constructor, make sure the spawnfunc_ function is defined so that its available to maps. QC_snprintfz(membername, sizeof(membername), "spawnfunc_%s", classname); d = QCC_PR_GetDef(type_function, membername, NULL, true, 0, GDF_CONST); d->funccalled = true; d->referenced = true; QCC_FreeDef(d); } return newt; } pbool type_inlinefunction; /*newtype=true: creates a new type always silentfail=true: function is permitted to return NULL if it was not given a type, otherwise never returns NULL */ QCC_type_t *QCC_PR_ParseType (int newtype, pbool silentfail, pbool ignoreptr) { QCC_type_t *newt; QCC_type_t *type; char *name; type_inlinefunction = false; //doesn't really matter so long as its not from an inline function type // int ofs; if (QCC_PR_CheckKeyword(keyword_const, "const")) { QCC_PR_ParseWarning (WARN_IGNOREDKEYWORD, "ignoring unsupported const keyword"); silentfail = false; //FIXME } if (QCC_PR_CheckKeyword(keyword_volatile, "volatile")) //we don't really support this - everything is volatile. silentfail = false; if (QCC_PR_PeekToken ("...") ) //this is getting stupid { QCC_PR_LexWhitespace (false); if (*pr_file_p == '(') { //work around gmqcc's "(...(" being misinterpreted as a cast syntax error, instead abort here so it can be treated as an intrinsic (with args) instead. if (silentfail) return NULL; QCC_PR_ParseError (ERR_NOTATYPE, "\"%s\" is not a type", pr_token); } QCC_PR_Lex (); type = QCC_PR_NewType("FIELD_TYPE", ev_field, false); type->aux_type = QCC_PR_ParseType (false, false, ignoreptr); type->size = type->aux_type->size; newt = QCC_PR_FindType (type); type = QCC_PR_NewType("FIELD_TYPE", ev_field, false); type->aux_type = newt; type->size = type->aux_type->size; newt = QCC_PR_FindType (type); type = QCC_PR_NewType("FIELD_TYPE", ev_field, false); type->aux_type = newt; type->size = type->aux_type->size; if (newtype) return type; return QCC_PR_FindType (type); } if (QCC_PR_CheckToken ("..")) //so we don't end up with the user specifying '. .vector blah' (hexen2 added the .. token for array ranges) { newt = QCC_PR_NewType("FIELD_TYPE", ev_field, false); newt->aux_type = QCC_PR_ParseType (false, false, ignoreptr); newt->size = newt->aux_type->size; newt = QCC_PR_FindType (newt); type = QCC_PR_NewType("FIELD_TYPE", ev_field, false); type->aux_type = newt; type->size = type->aux_type->size; if (newtype) return type; return QCC_PR_FindType (type); } if (QCC_PR_CheckToken (".")) { //.float *foo; is annoying. //technically it is a pointer to a .float //most people will want a .(float*) foo; //so .*float will give you that. //however, we can't cope parsing that with regular types, so we support that ONLY when . was already specified. //this is pretty much an evil syntax hack. pbool ptr = QCC_PR_CheckToken ("*"); type = QCC_PR_ParseType(false, false, ignoreptr); if (!type) QCC_PR_ParseError(0, "Expected type\n"); if (ptr) type = QCC_PointerTypeTo(type); name = qccHunkAlloc(strlen(type->name)+2); *name = '.'; strcpy(name+1, type->name); newt = QCC_PR_NewType(name, ev_field, false); newt->aux_type = type; newt->size = newt->aux_type->size; if (newtype) return newt; return QCC_PR_FindType (newt); } name = pr_token; if (pr_token_type != tt_name) { if (silentfail) return NULL; QCC_PR_ParseError (ERR_NOTATYPE, "\"%s\" is not a type", name); } // name = QCC_PR_CheckCompConstString(name); //accessors if (QCC_PR_CheckKeyword (keyword_accessor, "accessor")) { char parentname[256]; char *accessorname; char *funcname; newt = NULL; funcname = QCC_PR_ParseName(); accessorname = qccHunkAlloc(strlen(funcname)+1); strcpy(accessorname, funcname); /* Look to see if this type is already defined */ newt = QCC_TypeForName(accessorname); if (newt && newt->type != ev_accessor) QCC_PR_ParseError(ERR_NOTANAME, "Type %s cannot be redefined as an accessor", accessorname); if (QCC_PR_CheckToken(":")) { type = QCC_PR_ParseType(false, false, false); if (type) TypeName(type, parentname, sizeof(parentname)); else strcpy(parentname, "??"); if (!type || type->type == ev_struct || type->type == ev_union) QCC_PR_ParseError(ERR_NOTANAME, "Accessor %s cannot be based upon %s", accessorname, parentname); } else type = NULL; if (!newt) { newt = QCC_PR_NewType(accessorname, ev_accessor, true); newt->size=type->size; } if (!newt->parentclass) { newt->parentclass = type; if (!newt->parentclass || newt->parentclass->type == ev_struct || newt->parentclass->type == ev_union || newt->size != newt->parentclass->size) QCC_PR_ParseError(ERR_NOTANAME, "Accessor %s cannot be based upon %s", accessorname, parentname); } else if (type != newt->parentclass) { char bufe[256]; char bufn[256]; QCC_PR_ParseError(ERR_NOTANAME, "Accessor %s basic type mismatch (%s, expected %s)", accessorname, TypeName(type, bufn, sizeof(bufn)), TypeName(newt->parentclass, bufe, sizeof(bufe))); } if (QCC_PR_CheckToken("{")) { pbool setnotget; pbool isinline; newt->filen = s_filen; newt->line = pr_source_line; do { isinline = QCC_PR_CheckName("inline"); if (QCC_PR_CheckName("set")) setnotget = true; else if (QCC_PR_CheckName("get")) setnotget = false; else break; QCC_PR_ParseAccessorMember(newt, isinline, setnotget); } while (QCC_PR_CheckToken(",") || QCC_PR_CheckToken(";")); QCC_PR_Expect("}"); } if (newtype) newt = QCC_PR_DuplicateType(newt, false); return newt; } if (flag_qcfuncs && QCC_PR_CheckKeyword (keyword_class, "class")) type = QCC_PR_ParseEntClass(); else if (QCC_PR_CheckKeyword(keyword_enum, "enum")) type = QCC_PR_ParseEnum(false); else if (QCC_PR_CheckKeyword(keyword_enumflags, "enumflags")) type = QCC_PR_ParseEnum(true); else if (QCC_PR_CheckKeyword (keyword_union, "union")) type = QCC_PR_ParseStruct(ev_union); else if (QCC_PR_CheckKeyword (keyword_struct, "struct")) type = QCC_PR_ParseStruct(ev_struct); //defaults to public // else if (QCC_PR_CheckKeyword (keyword_class, "class")) // structtype = ev_struct; //defaults to private. no other difference. else { type = QCC_TypeForName(name); if (type) QCC_PR_Lex (); else { if (!*name) { QCC_PR_ParseError(ERR_NOTANAME, "type missing name"); return NULL; } //some reacc types... if (flag_acc && !stricmp("Void", name)) type = type_void; else if (flag_acc && !stricmp("Real", name)) type = type_float; else if (flag_acc && !stricmp("Vector", name)) type = type_vector; else if (flag_acc && !stricmp("Object", name)) type = type_entity; else if (flag_acc && !stricmp("String", name)) type = type_string; else if (flag_acc && !stricmp("PFunc", name)) type = type_function; else { //try and handle C's types, which have weird and obtuse combinations (like long long, long int, short int). pbool isokay = false; pbool issigned = false; pbool isunsigned = false; pbool islong = false; pbool isfloat = false; int bits = 0; #define longlongbits 64 #define longbits (flag_ILP32?32:64) if (QCC_PR_CheckKeyword(keyword_register, "register")) //allow a leading register keyword. technically EVERYTHING is a register in qc, so we can just ignore this. isokay = true; while(true) { if (!isunsigned && !issigned && QCC_PR_CheckKeyword(keyword_signed, "signed")) issigned = isokay = true; else if (!issigned && !isunsigned && QCC_PR_CheckKeyword(keyword_unsigned, "unsigned")) isunsigned = isokay = true; else if (!bits && QCC_PR_CheckKeyword(keyword_long, "long")) { if (islong) bits = longlongbits; islong = isokay = true; } else if ((!bits || bits==16) && (QCC_PR_CheckKeyword(keyword_int, "int") || QCC_PR_CheckKeyword(keyword_integer, "integer"))) { //long int, short int, etc are allowed if (!bits) bits = 32; isokay = true; } else if (!bits && QCC_PR_CheckKeyword(keyword_short, "short")) bits = 16, isokay = true; else if (!bits && QCC_PR_CheckKeyword(keyword_char, "char")) bits = 8, isokay = true; else if (!bits && !issigned && !isunsigned && QCC_PR_CheckKeyword(true, "_Bool")) //c99 { if (keyword_int) type = type_bint; else type = type_bfloat; goto wasctype; } else if (!bits && !islong && QCC_PR_CheckKeyword(keyword_float, "float")) bits = 32, isfloat = isokay = true; else if ((!bits||islong) && QCC_PR_CheckKeyword(keyword_double, "double")) bits = islong?128:64, islong=false, isfloat = isokay = true; else break; } if (isokay) { if (!bits) bits = islong?longbits:32; // [int] if (isfloat) { if (isunsigned) QCC_PR_ParseWarning (WARN_IGNOREDKEYWORD, "ignoring unsupported unsigned keyword, type will be signed"); if (bits > 64) type = type_double, QCC_PR_ParseWarning (WARN_IGNOREDKEYWORD, "long doubles are not supported, using double"); //permitted else if (bits == 64) type = type_double; else type = type_float; } else { if (bits > 64) type = (isunsigned?type_uint64:type_int64), QCC_PR_ParseWarning (WARN_IGNOREDKEYWORD, "long longs are not supported, using long"); //permitted else if (bits > 32) type = (isunsigned?type_uint64:type_int64); else if (bits <= 8) type = (isunsigned?type_uint8:type_sint8); else if (bits <= 16) type = (isunsigned?type_uint16:type_sint16); else type = (isunsigned?type_uint:type_integer); } goto wasctype; } if (silentfail) return NULL; QCC_PR_ParseError (ERR_NOTATYPE, "\"%s\" is not a type", name); type = type_float; // shut up compiler warning } } } wasctype: if (!type) return NULL; if (!ignoreptr) { while (QCC_PR_CheckToken("*")) { if (QCC_PR_CheckKeyword(keyword_const, "const")) QCC_PR_ParseWarning (WARN_IGNOREDKEYWORD, "ignoring unsupported const keyword"); type = QCC_PointerTypeTo(type); } } if (flag_qcfuncs && QCC_PR_CheckToken ("(")) //this is followed by parameters. Must be a function. { type_inlinefunction = true; type = QCC_PR_ParseFunctionType(newtype, type); if (!type) QCC_PR_ParseError(ERR_BADNOTTYPE, "expected function arg list"); } else { if (newtype) { type = QCC_PR_DuplicateType(type, false); } } return type; } #endif fteqcc-20251105/./decomp.c0000644000200200001440000030567515233070110014357 0ustar twolifeusers #include "qcc.h" //#include "decomp.h" //This file is derived from frikdec, but has some extra tweaks to make it more friendly with fte's compiler/opcodes (its not perfect though) //FIXME: there's still a load of mallocs, so we don't allow this more than once per process. //convert vector terms into floats when its revealed that they're using vector ops and not floats //FIXME: fteqcc converts do {} while(1); into a goto -1; which is a really weeeird construct. #if defined(_WIN32) || defined(__DJGPP__) #include #elif defined(__unix__) && !defined(__linux__) // quick hack for the bsds and other unix systems #include #else #include #endif #define DEF_H2ARRAY (1<<16) //[-1] is length. //#undef printf //#define printf GUIprintf //custom types, because we're lazy and lame with strings. typedef struct { unsigned int type; // if DEF_SAVEGLOBAL bit is set the variable needs to be saved in savegames unsigned int ofs; const char *s_name; } QCD_def_t; typedef struct { int first_statement; // negative numbers are builtins int parm_start; int locals; // total ints of parms + locals int profile; // runtime const char *s_name; const char *s_file; // source file defined in int numparms; pbyte parm_size[MAX_PARMS]; } QCD_function_t; #define OP_MARK_END_DO 0x00010000 //do{ #define OP_MARK_END_ELSE 0x00000400 //} #define MAX_REGS 65536 #define dstatement_t QCC_dstatement32_t #define statements destatements #define functions defunctions #define strings destrings #define fields defields static dstatement_t *statements; static float *pr_globals; static char *strings; static QCD_def_t *globals; static QCD_def_t *fields; static QCD_function_t *functions; static int ofs_return; static int ofs_parms[MAX_PARMS]; static int ofs_size = 3; static QCD_def_t *globalofsdef[MAX_REGS]; //forward declarations. QCD_def_t *GetField(const char *name); #include /*int QC_snprintfz(char *buffer, size_t maxlen, const char *format, ...) { int p; va_list argptr; if (!maxlen) return -1; va_start (argptr, format); p = _vsnprintf (buffer, maxlen, format,argptr); va_end (argptr); buffer[maxlen-1] = 0; return p; }*/ const char *GetString(unsigned int str) { if (str >= strofs) { char s[256]; QC_snprintfz(s, sizeof(s), "INVALIDSTRING[%i]", str); return strdup(s); return "INVALIDSTRING"; } else return strings+str; } const char *GetNameString(const char *str){return str;} extern QCC_opcode_t pr_opcodes []; static int debug_offs = 0; static int assumeglobals = 0; //unknown globals are assumed to be actual globals and NOT unlocked temps static int assumelocals = 0; //unknown locals are assumed to be actual locals and NOT locked temps static vfile_t *Decompileofile; static vfile_t *Decompileprogssrc; static char **DecompileProfiles;//[MAX_FUNCTIONS]; static char **rettypes;//[MAX_FUNCTIONS]; extern int quakeforgeremap[]; static char *type_names[] = { "void", "string", "float", "vector", "entity", "ev_field", "void()", "ev_pointer", "int", "__uint", "__int64", "__uint64", "__double", "__variant", "__struct", "__union", "__accessor", "__enum", "__typedef", "__boolean", }; const char *typetoname(QCC_type_t *type) { return type->name; } const char *temp_type (int temp, dstatement_t *start, QCD_function_t *df) { int i; dstatement_t *stat; stat = start - 1; // determine the type of a temp while(stat > statements) { if (temp == stat->a) return typetoname(*pr_opcodes[stat->op].type_a); else if (temp == stat->b) return typetoname(*pr_opcodes[stat->op].type_b); else if (temp == stat->c) return typetoname(*pr_opcodes[stat->op].type_c); stat--; } // method 2 // find a call to this function for (i = 0; i < numstatements; i++) { stat = &statements[i]; if (stat->op >= OP_CALL0 && stat->op <= OP_CALL8 && ((eval_t *)&pr_globals[stat->a])->function == df - functions) { for(i++; i < numstatements; i++) { stat = &statements[i]; if (ofs_return == stat->a && (*pr_opcodes[stat->op].type_a)->type != ev_void) return type_names[(*pr_opcodes[stat->op].type_a)->type]; else if (ofs_return == stat->b && (*pr_opcodes[stat->op].type_b)->type != ev_void) return type_names[(*pr_opcodes[stat->op].type_b)->type]; else if (stat->op == OP_DONE) break; else if (stat->op >= OP_CALL0 && stat->op <= OP_CALL8 && stat->a != df - functions) break; } } } printf("warning: Could not determine return type for %s\n", GetNameString(df->s_name)); return "float"; } pbool IsConstant(QCD_def_t *def) { int i; dstatement_t *d; if (def->type & DEF_SAVEGLOBAL) return false; if (pr_globals[def->ofs] == 0) return false; for (i = 1; i < numstatements; i++) { d = &statements[i]; if (d->b == def->ofs) { if (pr_opcodes[d->op].associative == ASSOC_RIGHT) { if (d->op - OP_STORE_F < 6) { return false; } } } } return true; } char *type_name (QCD_def_t *def) { QCD_def_t *j; switch(def->type&~DEF_SAVEGLOBAL) { case ev_field: case ev_pointer: j = GetField(GetNameString(def->s_name)); if (j) return qcva(".%s",type_names[j->type]); else return type_names[def->type&~DEF_SAVEGLOBAL]; case ev_void: case ev_string: case ev_entity: case ev_vector: case ev_float: return type_names[def->type&~DEF_SAVEGLOBAL]; case ev_function: return "void()"; case ev_integer: return "int"; // case ev_uinteger: // return "unsigned"; // case ev_quat: // return "quat"; default: return "float"; } }; extern int numstatements; extern int numfunctions; #define FILELISTSIZE 62 /* =============== PR_String Returns a string suitable for printing (no newlines, max 60 chars length) =============== */ const char *PR_String (const char *string) { static char buf[80]; char *s; s = buf; *s++ = '"'; while (string && *string) { if (s == buf + sizeof(buf) - 2) break; if (*string == '\n') { *s++ = '\\'; *s++ = 'n'; } else if (*string == '"') { *s++ = '\\'; *s++ = '"'; } else *s++ = *string; string++; if (s - buf > 60) { *s++ = '.'; *s++ = '.'; *s++ = '.'; break; } } *s++ = '"'; *s++ = 0; return buf; } /* ============ PR_ValueString Returns a string describing *data in a type specific manner ============= */ static char *PR_ValueString (etype_t type, void *val) { static char line[8192]; QCD_function_t *f; switch (type) { case ev_string: QC_snprintfz(line, sizeof(line), "%s", PR_String(GetString(*(int *)val))); break; case ev_entity: QC_snprintfz(line, sizeof(line), "entity %i", *(int *)val); break; case ev_function: if (*(unsigned int *)val >= numfunctions) QC_snprintfz(line, sizeof(line), "undefined function"); else { f = functions + *(unsigned int *)val; QC_snprintfz(line, sizeof(line), "%s()", GetNameString(f->s_name)); } break; /* case ev_field: def = PR_DefForFieldOfs ( *(int *)val ); sprintf (line, ".%s", def->name); break; */ case ev_void: QC_snprintfz(line, sizeof(line), "void"); break; case ev_float: { unsigned int high = *(unsigned int*)val & 0xff000000; if (high == 0xff000000 || !high) //FIXME this is probably a string or something, but we don't really know what type it is. QC_snprintfz(line, sizeof(line), "(float)(__variant)%ii", *(int*)val); else QC_snprintfz(line, sizeof(line), "%5.1f", *(float *)val); } break; case ev_vector: QC_snprintfz(line, sizeof(line), "'%5.1f %5.1f %5.1f'", ((float *)val)[0], ((float *)val)[1], ((float *)val)[2]); break; case ev_pointer: QC_snprintfz(line, sizeof(line), "pointer"); break; case ev_field: QC_snprintfz(line, sizeof(line), "", *(int*)val); break; default: QC_snprintfz(line, sizeof(line), "bad type %i", type); break; } return line; } static char *filenames[] = { "makevectors", "defs.qc", "button_wait", "buttons.qc", "anglemod", "ai.qc", "boss_face", "boss.qc", "info_intermission", "client.qc", "CanDamage", "combat.qc", "demon1_stand1", "demon.qc", "dog_bite", "dog.qc", "door_blocked", "doors.qc", "Laser_Touch", "enforcer.qc", "knight_attack", "fight.qc", "f_stand1", "fish.qc", "hknight_shot", "hknight.qc", "SUB_regen", "items.qc", "knight_stand1", "knight.qc", "info_null", "misc.qc", "monster_use", "monsters.qc", "OgreGrenadeExplode", "ogre.qc", "old_idle1", "oldone.qc", "plat_spawn_inside_trigger", "plats.qc", "player_stand1", "player.qc", "shal_stand", "shalrath.qc", "sham_stand1", "shambler.qc", "army_stand1", "soldier.qc", "SUB_Null", "subs.qc", "tbaby_stand1", "tarbaby.qc", "trigger_reactivate", "triggers.qc", "W_Precache", "weapons.qc", "LaunchMissile", "wizard.qc", "main", "world.qc", "zombie_stand1", "zombie.qc" }; //FIXME: parse fteextensions.qc instead, or something. #define QW(x) //x, static struct { int num; char *name; //purly for readability. QCC_type_t **returns; QCC_type_t **params[8]; char *text; } builtins[]= { {0, NULL, NULL, {NULL}, NULL}, {1, "makevectors", NULL, {&type_vector}, "void (vector ang)"}, {2, "setorigin", NULL, {&type_entity, &type_vector}, "void (entity e, vector o)"}, {3, "setmodel", NULL, {&type_entity, &type_string}, "void (entity e, string m)"}, {4, "setsize", NULL, {&type_entity, &type_vector, &type_vector}, "void (entity e, vector min, vector max)"}, {5, NULL, NULL, {NULL}, NULL}, {6, NULL, NULL, {NULL}, "void ()"}, {7, "random", NULL, {NULL}, "float ()"}, {8, "sound", NULL, {&type_entity, &type_float, &type_string, &type_float, &type_float}, "void (entity e, float chan, string samp, float vol, float atten)"}, {9, "normalize", &type_vector, {&type_vector}, "vector (vector v)"}, {10, "error", NULL, {&type_string}, "void (string e)"}, {11, "objerror", NULL, {&type_string}, "void (string e)"}, {12, "vlen", &type_float, {&type_vector}, "float (vector v)"}, {13, "vectoyaw", &type_float, {&type_vector}, "float (vector v)"}, {14, "spawn", &type_entity, {NULL}, "entity ()"}, {15, "remove", NULL, {&type_entity}, "void (entity e)"}, {16, "traceline", NULL, {&type_vector, &type_vector, &type_float, &type_entity}, "void (vector v1, vector v2, float nomonsters, entity forent)"}, {17, NULL, NULL, {NULL}, "entity ()"}, {18, "find", &type_entity, {&type_entity, &type_field, &type_string}, "entity (entity start, .string fld, string match)"}, {19, "precache_sound", NULL, {&type_string}, "string (string s)"}, {20, "precache_model", NULL, {&type_string}, "string (string s)"}, {21, "stuffcmd", NULL, {&type_entity, &type_string}, "void (entity client, string s)"}, {22, "findradius", NULL, {&type_vector, &type_float}, "entity (vector org, float rad)"}, {23, "bprint", NULL, {QW(&type_float) &type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string}, "void (...)"}, {24, "sprint", NULL, {&type_entity, QW(&type_float) &type_string,&type_string,&type_string,&type_string,&type_string,&type_string}, "void (...)"}, {25, "dprint", NULL, {&type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string}, "void (...)"}, {26, "ftos", &type_string, {&type_float}, "string (float f)"}, {27, "vtos", &type_string, {&type_vector}, "string (vector v)"}, {28, "coredump", NULL, {NULL}, "void ()"}, {29, "traceon", NULL, {NULL}, "void ()"}, {30, "traceoff", NULL, {NULL}, "void ()"}, {31, "eprint", NULL, {&type_entity}, "void (entity e)"}, {32, "walkmove", &type_float, {&type_float, &type_float}, "float (float yaw, float dist)"}, {33, NULL, NULL, {NULL}, NULL}, {34, "droptofloor", NULL, {&type_float, &type_float}, "float ()"}, {35, "lightstyle", NULL, {&type_float, &type_string}, "void (float style, string value)"}, {36, "rint", &type_float, {&type_vector}, "float (float v)"}, {37, "floor", &type_float, {&type_vector}, "float (float v)"}, {38, "ceil", &type_float, {&type_vector}, "float (float v)"}, {39, NULL, NULL, {NULL}, NULL}, {40, "checkbottom", &type_float, {&type_entity}, "float (entity e)"}, {41, "pointcontents", &type_float, {&type_vector}, "float (vector v)"}, {42, NULL, NULL, {NULL}, NULL}, {43, "fabs", &type_float, {&type_float}, "float (float f)"}, {44, "aim", NULL, {&type_entity, &type_float}, "vector (entity e, float speed)"}, {45, "cvar", &type_string, {&type_string}, "float (string s)"}, {46, "localcmd", NULL, {&type_string}, "void (string s)"}, {47, "nextent", &type_entity, {&type_entity}, "entity (entity e)"}, {48, "", NULL, {&type_vector, &type_vector, &type_float, &type_float}, "void (vector o, vector d, float color, float count)"}, {49, "changeyaw", NULL, {NULL}, "void ()"}, {50, NULL, NULL, {NULL}, NULL}, {51, "vectoangles", &type_vector, {&type_vector}, "vector (vector v)"}, {52, "WriteByte", NULL, {&type_float, &type_float}, "void (float to, float f)"}, {53, "WriteChar", NULL, {&type_float, &type_float}, "void (float to, float f)"}, {54, "WriteShort", NULL, {&type_float, &type_float}, "void (float to, float f)"}, {55, "WriteLong", NULL, {&type_float, &type_float}, "void (float to, float f)"}, {56, "WriteCoord", NULL, {&type_float, &type_float}, "void (float to, float f)"}, {57, "WriteAngle", NULL, {&type_float, &type_float}, "void (float to, float f)"}, {58, "WriteString", NULL, {&type_float, &type_string}, "void (float to, string s)"}, {59, "WriteEntity", NULL, {&type_float, &type_entity}, "void (float to, entity s)"}, {60, NULL, NULL, {NULL}, NULL}, {61, NULL, NULL, {NULL}, NULL}, {62, NULL, NULL, {NULL}, NULL}, {63, NULL, NULL, {NULL}, NULL}, {64, NULL, NULL, {NULL}, NULL}, {65, NULL, NULL, {NULL}, NULL}, {66, NULL, NULL, {NULL}, NULL}, {67, "movetogoal", NULL, {&type_float}, "void (float step)"}, {68, "precache_file", NULL, {&type_string}, "string (string s)"}, {69, "makestatic", NULL, {&type_entity}, "void (entity e)"}, {70, "changelevel", NULL, {&type_string}, "void (string s)"}, {71, NULL, NULL, {NULL}, NULL}, {72, "cvar_set", NULL, {&type_string, &type_string}, "void (string var, string val)"}, {73, "centerprint", NULL, {&type_entity,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string}, "void (entity client, string s, ...)"}, {74, "ambientsound", NULL, {&type_vector, &type_string, &type_float, &type_float}, "void (vector pos, string samp, float vol, float atten)"}, {75, "precache_model2", NULL, {&type_string}, "string (string s)"}, {76, "precache_sound2", NULL, {&type_string}, "string (string s)"}, {77, "precache_file2", NULL, {&type_string}, "string (string s)"}, {78, "setspawnparms", NULL, {&type_entity}, "void (entity e)"}, //quakeworld specific {79, "logfrag", NULL, {&type_entity, &type_entity}, "void(entity killer, entity killee)"}, {80, "infokey", &type_string, {&type_entity, &type_string}, "string(entity e, string key)"}, {81, "stof", &type_float, {&type_string}, "float(string s)"}, {82, "multicast", NULL, {&type_vector, &type_float}, "void(vector where, float set)"}, /* //these are mvdsv specific {83, "executecmd", NULL, {NULL}, NULL}, {84, "tokenize", NULL, {&type_string}, NULL}, {85, "argc", &type_float, {NULL}, "float()"}, {86, "argv", &type_string, {&type_float}, "string(float f)"}, {87, "teamfield", NULL, {NULL}, "void(.string fs)"}, {88, "substr", &type_string, {&type_string, &type_float, &type_float}, "string(string, float, float)"}, {89, "strcat", &type_string, {&type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string}, "string (...)"}, {90, "strlen", &type_float, {&type_string}, "float(string s)"}, {91, "str2byte", &type_float, {&type_string}, "float(string s)"}, {92, NULL, NULL, {NULL}, NULL}, {93, "newstr", &type_string, {&type_string}, "string(...)"}, {94, "freestr", NULL, {&type_string}, "void(string s)"}, {95, "conprint", NULL, {NULL}, NULL}, {96, "readcmd", &type_string, {&type_string}, "string(string cmd)"}, {97, "strcpy", NULL, {NULL}, NULL}, {98, "strstr", &type_string, {&type_string, &type_string}, "string(string str, string sub)"}, {99, "strncpy", NULL, {NULL}, NULL}, {100, "log", NULL, {NULL}, NULL}, {101, "redirectcmd", NULL, {NULL}, NULL}, {102, "calltimeofday", NULL, {NULL}, NULL}, {103, "forcedemoframe", NULL, {NULL}, NULL}, */ //some QSG extensions {83, NULL, NULL, {NULL}, NULL}, {84, NULL, NULL, {NULL}, NULL}, {85, NULL, NULL, {NULL}, NULL}, {86, NULL, NULL, {NULL}, NULL}, {87, NULL, NULL, {NULL}, NULL}, {88, NULL, NULL, {NULL}, NULL}, {89, NULL, NULL, {NULL}, NULL}, {90, "tracebox", NULL, {&type_vector, &type_vector, &type_vector, &type_vector, &type_float, &type_entity}, "void(vector start, vector mins, vector maxs, vector end, float nomonsters, entity ent)"}, {91, "randomvec", &type_vector, {NULL}, "vector()"}, {92, "getlight", &type_vector, {&type_vector}, "vector(vector org)"}, {93, "registercvar", &type_float, {&type_string, &type_string}, "float(string cvarname, string defaultvalue)"}, {94, "min", &type_float, {&type_float,&type_float,&type_float,&type_float,&type_float,&type_float,&type_float,&type_float},"float(float a, float b, ...)"}, {95, "max", &type_float, {&type_float,&type_float,&type_float,&type_float,&type_float,&type_float,&type_float,&type_float},"float(float a, float b, ...)"}, {96, "bound", &type_float, {&type_float,&type_float,&type_float}, "float(float minimum, float val, float maximum)"}, {97, "pow", &type_float, {&type_float,&type_float}, "float(float value, float exp)"}, {98, "findfloat", &type_entity, {&type_entity,&type_field,&type_float}, "entity(entity start, .__variant fld, __variant match)"}, {99, "checkextension",&type_float, {&type_string}, "float(string extname)"}, {100, "builtin_find", &type_float, {&type_string}, "float(string builtinname)"}, {101, "redirectcmd", NULL, {&type_entity,&type_string}, "void(entity to, string str)"}, {102, "anglemod", &type_float, {&type_float}, "float(float value)"}, {103, "cvar_string", &type_string, {&type_string}, "string(string cvarname)"}, {104, "showpic", NULL, {NULL}, "void(string slot, string picname, float x, float y, float zone, optional entity player)"}, {105, "hidepic", NULL, {NULL}, "void(string slot, optional entity player)"}, {106, "movepic", NULL, {NULL}, "void(string slot, float x, float y, float zone, optional entity player)"}, {107, "changepic", NULL, {NULL}, "void(string slot, string picname, optional entity player)"}, {108, "showpicent", NULL, {NULL}, "void(string slot, entity player)"}, {109, "hidepicent", NULL, {NULL}, "void(string slot, entity player)"}, {110, "fopen", &type_float, {&type_string,&type_float}, "float(string filename, float mode, optional float mmapminsize)"}, {111, "fclose", NULL, {&type_float}, "void(float fhandle)"}, {112, "fgets", &type_string, {&type_float,&type_string}, "string(float fhandle)"}, {113, "fputs", NULL, {&type_float,&type_string}, "void(float fhandle, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7)"}, {114, "strlen", &type_float, {&type_string}, "float(string s)"}, {115, "strcat", &type_string, {&type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string},"string(string s1, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7, optional string s8)"}, {116, "substring", &type_string, {&type_string,&type_float,&type_float}, "string(string s, float start, float length)"}, {117, "stov", &type_vector, {&type_string}, "vector(string s)"}, {118, "strzone", &type_string, {&type_string}, "string(string s, ...)"}, {119, "strunzone", NULL, {&type_string}, "void(string s)"}, }; char *DecompileValueString(etype_t type, void *val); QCD_def_t *DecompileGetParameter(gofs_t ofs); QCD_def_t *DecompileFindGlobal(const char *name); char *DecompilePrintParameter(QCD_def_t * def); QCD_def_t *DecompileFunctionGlobal(int funcnum); char *ReadProgsCopyright(char *buf, size_t bufsize) { char *copyright, *e; dprograms_t *progs = (dprograms_t*)buf; int lowest = progs->ofs_statements; lowest = min(lowest, progs->ofs_globaldefs); lowest = min(lowest, progs->ofs_fielddefs); lowest = min(lowest, progs->ofs_functions); lowest = min(lowest, progs->ofs_strings); lowest = min(lowest, progs->ofs_globals); lowest = min(lowest, progs->ofs_fielddefs); copyright = (char*)(progs+1); if (!strncmp("\r\n\r\n", copyright, 4)) { copyright += 4; e = copyright+strlen(copyright)+1; if (e && !strncmp(e, "\r\n\r\n", 4)) { if (e+4 <= buf+lowest) { return copyright; } } } return NULL; } int DecompileReadData(const char *srcfilename, char *buf, size_t bufsize) { dprograms_t progs; int i, j; void *p; char name[1024]; QCD_def_t *fd; int stsz = 16, defsz=16; // int quakeforge = false; memcpy(&progs, buf, sizeof(progs)); if (progs.version == PROG_QTESTVERSION) { defsz = -32; //ddefs_t is 32bit stsz = -16; //statements is mostly 16bit. there's some line numbers in there too. } else if (progs.version == PROG_VERSION) stsz = defsz = 16; else if (progs.version == 7) { if (progs.secondaryversion == PROG_SECONDARYVERSION16) { //regular 16bit progs, just an extended instruction set probably. stsz = defsz = 16; } else if (progs.secondaryversion == PROG_SECONDARYVERSION32) { //32bit fte progs. everything is 32bit. stsz = defsz = 32; } else { //progs is kk7 (certain QW TF mods). defs are 16bit but statements are 32bit. so this is unusable for saved games. stsz = 32; defsz = 16; //gah! fucked! } } else { stsz = defsz = 16; // quakeforge = true; } strings = buf + progs.ofs_strings; strofs = progs.numstrings; numstatements = progs.numstatements; // if (numstatements > MAX_STATEMENTS) // Sys_Error("Too many statements"); if (stsz == 32) statements = (dstatement32_t*)(buf+progs.ofs_statements); else if (stsz == 16) { //need to expand the statements to 32bit. const dstatement16_t *statements6 = (const dstatement16_t*)(buf+progs.ofs_statements); statements = malloc(numstatements * sizeof(*statements)); for (i = 0; i < numstatements; i++) { statements[i].op = statements6[i].op; if (statements[i].op == OP_GOTO) statements[i].a = (signed short)statements6[i].a; else statements[i].a = (unsigned short)statements6[i].a; if (statements[i].op == OP_IF_I || statements[i].op == OP_IFNOT_I || statements[i].op == OP_IF_F || statements[i].op == OP_IFNOT_F || statements[i].op == OP_IF_S || statements[i].op == OP_IFNOT_S || statements[i].op == OP_CASE || statements[i].op == OP_SWITCH_F) statements[i].b = (signed short)statements6[i].b; else statements[i].b = (unsigned short)statements6[i].b; if (statements[i].op == OP_CASERANGE) statements[i].c = (signed short)statements6[i].c; else statements[i].c = (unsigned short)statements6[i].c; } } else if (stsz == -16) { const qtest_statement_t *statements3 = (const qtest_statement_t*)(buf+progs.ofs_statements); statements = malloc(numstatements * sizeof(*statements)); for (i = 0; i < numstatements; i++) { statements[i].op = statements3[i].op; if (statements[i].op == OP_GOTO) statements[i].a = (signed short)statements3[i].a; else statements[i].a = (unsigned short)statements3[i].a; if (statements[i].op == OP_IF_I || statements[i].op == OP_IFNOT_I || statements[i].op == OP_IF_F || statements[i].op == OP_IFNOT_F || statements[i].op == OP_IF_S || statements[i].op == OP_IFNOT_S || statements[i].op == OP_CASE || statements[i].op == OP_SWITCH_F) statements[i].b = (signed short)statements3[i].b; else statements[i].b = (unsigned short)statements3[i].b; if (statements[i].op == OP_CASERANGE) statements[i].c = (signed short)statements3[i].c; else statements[i].c = (unsigned short)statements3[i].c; } } else externs->Sys_Error("Unrecognised progs version"); numfunctions = progs.numfunctions; // functions = (dfunction_t*)(buf+progs.ofs_functions); DecompileProfiles = calloc(numfunctions, sizeof(*DecompileProfiles)); rettypes = calloc(numfunctions, sizeof(*rettypes)); numglobaldefs = progs.numglobaldefs; numfielddefs = progs.numfielddefs; if (defsz == 16) { const dfunction_t *funcin = (const dfunction_t*)(buf+progs.ofs_functions); const ddef16_t *gd16 = (const ddef16_t*)(buf+progs.ofs_globaldefs); const ddef16_t *fd16 = (const ddef16_t*)(buf+progs.ofs_fielddefs); globals = malloc(numglobaldefs * sizeof(*globals)); for (i = 0; i < numglobaldefs; i++) { globals[i].ofs = gd16[i].ofs; globals[i].s_name = GetString(gd16[i].s_name); globals[i].type = gd16[i].type; } fields = malloc(numfielddefs * sizeof(*fields)); for (i = 0; i < numfielddefs; i++) { fields[i].ofs = fd16[i].ofs; fields[i].s_name = GetString(fd16[i].s_name); fields[i].type = fd16[i].type; } functions = malloc(numfunctions * sizeof(*functions)); for (i = 0; i < numfunctions; i++) { functions[i].first_statement = funcin[i].first_statement; // negative numbers are builtins functions[i].parm_start = funcin[i].parm_start; functions[i].locals = funcin[i].locals; // total ints of parms + locals functions[i].profile = funcin[i].profile; // runtime functions[i].s_name = GetString(funcin[i].s_name); functions[i].s_file = GetString(funcin[i].s_file); // source file defined in functions[i].numparms = funcin[i].numparms; for (j = 0; j < MAX_PARMS; j++) functions[i].parm_size[j] = funcin[i].parm_size[j]; } } else if (defsz == 32) { const dfunction_t *funcin = (const dfunction_t*)(buf+progs.ofs_functions); const ddef32_t *gdin = (const ddef32_t*)(buf+progs.ofs_globaldefs); const ddef32_t *fdin = (const ddef32_t*)(buf+progs.ofs_fielddefs); globals = malloc(numglobaldefs * sizeof(*globals)); for (i = 0; i < numglobaldefs; i++) { globals[i].ofs = gdin[i].ofs; globals[i].s_name = GetString(gdin[i].s_name); globals[i].type = gdin[i].type; } fields = malloc(numfielddefs * sizeof(*fields)); for (i = 0; i < numfielddefs; i++) { fields[i].ofs = fdin[i].ofs; fields[i].s_name = GetString(fdin[i].s_name); fields[i].type = fdin[i].type; } functions = malloc(numfunctions * sizeof(*functions)); for (i = 0; i < numfunctions; i++) { functions[i].first_statement = funcin[i].first_statement; // negative numbers are builtins functions[i].parm_start = funcin[i].parm_start; functions[i].locals = funcin[i].locals; // total ints of parms + locals functions[i].profile = funcin[i].profile; // runtime functions[i].s_name = strings + funcin[i].s_name; functions[i].s_file = strings + funcin[i].s_file; // source file defined in functions[i].numparms = funcin[i].numparms; for (j = 0; j < MAX_PARMS; j++) functions[i].parm_size[j] = funcin[i].parm_size[j]; } } else if (defsz == -32) { const qtest_function_t *funcin = (const qtest_function_t*)(buf+progs.ofs_functions); const qtest_def_t *gdqt = (const qtest_def_t*)(buf+progs.ofs_globaldefs); globals = malloc(numglobaldefs * sizeof(*globals)); for (i = 0; i < numglobaldefs; i++) { globals[i].ofs = gdqt[i].ofs; globals[i].s_name = strings + gdqt[i].s_name; globals[i].type = gdqt[i].type; } gdqt = (const qtest_def_t*)(buf+progs.ofs_fielddefs); fields = malloc(numfielddefs * sizeof(*fields)); for (i = 0; i < numfielddefs; i++) { fields[i].ofs = gdqt[i].ofs; fields[i].s_name = strings + gdqt[i].s_name; fields[i].type = gdqt[i].type; } functions = malloc(numfunctions * sizeof(*functions)); for (i = 0; i < numfunctions; i++) { functions[i].first_statement = funcin[i].first_statement; // negative numbers are builtins functions[i].parm_start = funcin[i].parm_start; functions[i].locals = funcin[i].locals; // total ints of parms + locals functions[i].profile = funcin[i].profile; // runtime functions[i].s_name = strings + funcin[i].s_name; functions[i].s_file = strings + funcin[i].s_file; // source file defined in functions[i].numparms = funcin[i].numparms; for (j = 0; j < MAX_PARMS; j++) functions[i].parm_size[j] = funcin[i].parm_size[j]; } } else { printf("fatal error: unsupported def size\n"); exit(1); } pr_globals = (float*)(buf+progs.ofs_globals); numpr_globals = progs.numglobals; printf("Decompiling...\n"); printf("Read Data from %s:\n", srcfilename); printf("Total Size is %6i\n", (unsigned int)bufsize); printf("Version Code is %i\n", progs.version); printf("CRC is %i\n", progs.crc); printf("%6i strofs\n", strofs); printf("%6i numstatements\n", numstatements); printf("%6i numfunctions\n", numfunctions); printf("%6i numglobaldefs\n", numglobaldefs); printf("%6i numfielddefs\n", numfielddefs); printf("%6i numpr_globals\n", numpr_globals); printf("----------------------\n"); if (numpr_globals > MAX_REGS) { printf("fatal error: progs exceeds a limit\n"); exit(1); } ofs_return = OFS_RETURN; for (i = 0; i < 8; i++) ofs_parms[i] = OFS_PARM0 + i * 3; ofs_size = 3; /* if (quakeforge) { int typeremap[] = {ev_void, ev_string, ev_float, ev_vector, ev_entity, ev_field, ev_function, ev_pointer, ev_quat, ev_integer, ev_uinteger}; for (i = 1; i < numglobaldefs; i++) { globals[i].type = (globals[i].type & DEF_SAVEGLOBGAL) | typeremap[globals[i].type&~DEF_SAVEGLOBGAL]; } for (i = 1; i < numfielddefs; i++) { fields[i].type = (fields[i].type & DEF_SAVEGLOBGAL) | typeremap[fields[i].type&~DEF_SAVEGLOBGAL]; } for (i = 1; i < numstatements; i++) { if (statements[i].op >= OP_H2_FIRST)// && statements[i].op <= OP_H2_FIRST+sizeof(quakeforgeremap)/sizeof(quakeforgeremap[0])) statements[i].op = quakeforgeremap[statements[i].op-OP_H2_FIRST]; } fd = DecompileFindGlobal(".zero"); if (fd) fd->ofs = -1; fd = DecompileFindGlobal(".return"); if (fd) { ofs_return = fd->ofs; fd->ofs = -1; } for (i = 0; i < 8; i++) { QC_snprintfz(name, sizeof(name), ".param_%i", i); fd = DecompileFindGlobal(name); if (fd) { ofs_parms[i] = fd->ofs; fd->ofs = -1; } } fd = DecompileFindGlobal(".param_size"); if (fd) ofs_size = ((int*)pr_globals)[fd->ofs]; } */ //fix up the globaldefs for (i = 1; i < numglobaldefs; i++) { if (globals[i].ofs < RESERVED_OFS) globals[i].ofs += numpr_globals; } // fix up the functions for (i = 1; i < numfunctions; i++) { if ((unsigned)(functions[i].s_name-strings) >= (unsigned)strofs || strlen(functions[i].s_name) <= 0) { fd = DecompileFunctionGlobal(i); if (fd) { functions[i].s_name = fd->s_name; continue; } QC_snprintfz(name, sizeof(name), "function%i", i); name[strlen(name)] = 0; p = malloc(strlen(name) + 1); strcpy(p, name); functions[i].s_name = p; } if (functions[i].first_statement > 0 && !functions[i].locals && functions[i].numparms) { //vanilla qcc apparently had a bug for (j = 0; j < functions[i].numparms; j++) functions[i].locals += functions[i].parm_size[j]; } } return progs.version; } static void DecompileDetermineArrays(void) { int i, j; for (i = 0; i < numstatements; i++) { if (statements[i].op >= OP_FETCH_GBL_F && statements[i].op <= OP_FETCH_GBL_FNC) { for (j = 1; j < numglobaldefs; j++) { if (globals[j].ofs == statements[i].a) { globals[j].type |= DEF_H2ARRAY; break; } } } } } static etype_t DecompileGetFieldTypeByDef(QCD_def_t *def) { int i; int ofs = ((int*)pr_globals)[def->ofs]; for (i = 1; i < numfielddefs; i++) if (fields[i].ofs == ofs) { if (!strcmp(GetNameString(def->s_name), GetNameString(fields[i].s_name))) return fields[i].type; } return ev_void; } static const char *DecompileGetFieldNameIdxByFinalOffset(int ofs) { int i; for (i = 1; i < numfielddefs; i++) if (fields[i].ofs == ofs) { return GetNameString(fields[i].s_name); } return "UNKNOWN FIELD"; } void DecompileGetFieldNameIdxByFinalOffset2(char *out, size_t outsize, int ofs) { int i; for (i = 1; i < numfielddefs; i++) { if (fields[i].ofs == ofs) { QC_snprintfz(out, outsize, "%s", GetNameString(fields[i].s_name)); return; } else if (fields[i].type == ev_vector && fields[i].ofs+1 == ofs) { QC_snprintfz(out, outsize, "%s_y", GetNameString(fields[i].s_name)); return; } else if (fields[i].type == ev_vector && fields[i].ofs+2 == ofs) { QC_snprintfz(out, outsize, "%s_z", GetNameString(fields[i].s_name)); return; } } QC_snprintfz(out, outsize, "", ofs); } int DecompileAlreadySeen(char *fname, vfile_t **rfile) { int ret = 1; vfile_t *file; file = QCC_FindVFile(fname); if (!file) { ret = 0; if (rfile) { char *header = "//Decompiled code. Please respect the original copyright.\n"; *rfile = QCC_AddVFile(fname, header, strlen(header)); AddSourceFile("progs.src", fname); } } else if (rfile) *rfile = file; return ret; } char *DecompileReturnType(QCD_function_t *df); char *DecompileAgressiveType(QCD_function_t *df, dstatement_t *last, gofs_t ofs) { QCD_def_t *par; par = DecompileGetParameter(ofs); if (par) //single = intended { return type_name(par); } if (ofs == ofs_return && ((last->op >= OP_CALL0 && last->op <= OP_CALL8) || (last->op >= OP_CALL1H && last->op <= OP_CALL8H))) { //offset is a return value, go look at the called function's return type. return DecompileReturnType(functions + ((int*)pr_globals)[last->a]); } while(last >= &statements[df->first_statement]) { if (last->c == ofs && pr_opcodes[last->op].associative == ASSOC_LEFT && pr_opcodes[last->op].priorityclass) { //previous was an operation into the temp return type_names[(*pr_opcodes[last->op].type_c)->type]; // sprintf(fname, "%s ", temp_type6(rds->a, rds, df)); } last--; } return NULL; //got to start of function... shouldn't really happen. } static unsigned int DecompileBuiltin(QCD_function_t *df) { unsigned int bi, i; if (df->first_statement > 0) return 0; //not a builtin. bi = -df->first_statement; //okay, so this is kinda screwy, different mods have different sets of builtins, and a load of fte's are #0 too //so just try to match by name first... lots of scanning. :( if (*df->s_name) { const char *biname = GetNameString(df->s_name); for (i = 0; i < (sizeof(builtins)/sizeof(builtins[0])); i++) { if (!builtins[i].name) continue; if (!strcmp(builtins[i].name, biname)) { //okay, this one matched. bi = i; break; } } } if (bi >= (sizeof(builtins)/sizeof(builtins[0]))) return 0; //unknown. return bi; } char *DecompileReturnType(QCD_function_t *df) { dstatement_t *ds; unsigned short dom; pbool foundret = false; static int recursion; char *ret = NULL; //return null if we don't know. int couldbeastring = true; if (df->first_statement <= 0) { unsigned int bi = DecompileBuiltin(df); if (bi) if (builtins[bi].returns) return type_names[(*builtins[bi].returns)->type]; return "void"; //no returns statements found } if (rettypes[df - functions]) return rettypes[df - functions]; recursion++; ds = statements + df->first_statement; /* * find a return statement, to determine the result type */ while (1) { dom = (ds->op) % OP_MARK_END_ELSE; if (!dom) break; // if (dom == OPQF_RETURN_V) // break; if (dom == OP_RETURN) { if (ds->a != 0) //some code is buggy. { foundret = true; if (recursion < 10) { ret = DecompileAgressiveType(df, ds-1, ds->a); if (ret) break; } if (((int*)pr_globals)[ds->a] < 0 && ((int*)pr_globals)[ds->a] >= strofs) couldbeastring = false; //definatly not else { char buf[64]; QC_snprintfz(buf, sizeof(buf), "%f", pr_globals[ds->a]); if (strcmp(buf, "0.000000")) couldbeastring = false; //doesn't fit the profile } } } ds++; } recursion--; if (foundret) { if (!ret) { if (couldbeastring) ret = "string /*WARNING: could not determine return type*/"; else ret = "float /*WARNING: could not determine return type*/"; } } else ret = "void"; //no returns statements found rettypes[df - functions] = ret; return ret; } void DecompileCalcProfiles(void) { int i, ps; gofs_t j; char *knew; static char fname[512]; static char line[512]; QCD_function_t *df; QCD_def_t *par; for (i = 1; i < numfunctions; i++) { df = functions + i; fname[0] = '\0'; line[0] = '\0'; DecompileProfiles[i] = NULL; if (df->first_statement <= 0) { unsigned int bi = DecompileBuiltin(df); if (bi && builtins[bi].text) QC_snprintfz(fname, sizeof(fname), "%s %s", builtins[bi].text, GetNameString(functions[i].s_name)); else { QC_snprintfz(fname, sizeof(fname), "__variant(...) %s", GetNameString(functions[i].s_name)); printf("warning: unknown builtin %s\n", GetNameString(functions[i].s_name)); } } else { char *rettype; rettype = DecompileReturnType(df); if (!rettype) { //but we do know that it's not void rettype = "float /*WARNING: could not determine return type*/"; } strcpy(fname, rettype); strcat(fname, "("); /* * determine overall parameter size */ for (j = 0, ps = 0; j < df->numparms; j++) ps += df->parm_size[j]; if (ps > 0) { int p; for (p = 0, j = df->parm_start; j < (df->parm_start) + ps; p++) { line[0] = '\0'; par = DecompileGetParameter(j); if (!par) par = DecompileGetParameter((short)j); if (!par) { //Error("Error - No parameter names with offset %i.", j); // printf("No parameter names with offset %i\n", j); if (p<8) j += df->parm_size[p]; else j += 1; if (p<8&&df->parm_size[p] == 3) { if (j < (df->parm_start) + ps) QC_snprintfz(line, sizeof(line), "vector par%i, ", p); else QC_snprintfz(line, sizeof(line), "vector par%i", p); } else { if (j < (df->parm_start) + ps) QC_snprintfz(line, sizeof(line), "__variant par%i, ", p); else QC_snprintfz(line, sizeof(line), "__variant par%i", p); } } else { if (par->type == ev_vector) j += 2; j++; if (j < (df->parm_start) + ps) { QC_snprintfz(line, sizeof(line), "%s, ", DecompilePrintParameter(par)); } else { QC_snprintfz(line, sizeof(line), "%s", DecompilePrintParameter(par)); } } strcat(fname, line); } } strcat(fname, ") "); line[0] = '\0'; QC_snprintfz(line, sizeof(line), "%s", GetNameString(functions[i].s_name)); strcat(fname, line); } knew = (char *)malloc(strlen(fname) + 1); strcpy(knew, fname); DecompileProfiles[i] = knew; } } QCD_def_t *GlobalAtOffset(QCD_function_t *df, gofs_t ofs) { QCD_def_t *def; int i, j; def = globalofsdef[ofs]; if (def) return def; for (i = 0; i < numglobaldefs; i++) { def = &globals[i]; if (def->ofs == ofs) { /*if (!GetString(def->s_name)) { char line[16]; char *buf; sprintf(line, "_s_%i", def->ofs); //globals, which are defined after the locals of the function they are first used in... buf = malloc(strlen(line)+1); //must be static variables, but we can't handle them very well strcpy(buf, line); def->s_name = buf - strings; }*/ globalofsdef[ofs] = def; return def; } } if (ofs >= df->parm_start && ofs < df->parm_start + df->locals) { static QCD_def_t parm[8]; static char *parmnames[] = {"par0","par1","par2","par3","par4","par5","par6","par7"}; int parmofs = ofs - df->parm_start; for (i = 0; i < df->numparms && i < 8; i++) { if (parmofs < df->parm_size[i]) { parm[i].ofs = ofs - parmofs; parm[i].s_name = parmnames[i]; parm[i].type = ev_void; ofs = parm[i].ofs; for (j = 0; j < numglobaldefs; j++) { def = &globals[j]; if (def->ofs == ofs) { char line[256], *buf; sprintf(line, "%s_%c", GetNameString(def->s_name), 'x'+parmofs); //globals, which are defined after the locals of the function they are first used in... def = malloc(sizeof(*def)+strlen(line)+1); //must be static variables, but we can't handle them very well buf = (char*)(def+1); strcpy(buf, line); def->s_name = buf; def->type = ev_float; return def; } } return &parm[i]; } parmofs -= df->parm_size[i]; } //moo } //FIXME: if its within the current function's bounds, its: // within param list: argument // never written: immediate // optimised: a local / locked temp. // vanilla qcc: always a temp (other locals will be named) //elsewhere: // if its assigned to somewhere, then its a temp // otherwise its a const. return NULL; } char *DecompileGlobal(QCD_function_t *df, gofs_t ofs, QCC_type_t * req_t) { int i; QCD_def_t *def; static char line[8192]; char *res; line[0] = '\0'; /*if (req_t == &def_short) { QC_snprintfz(line, sizeof(line), "%ii", ofs); res = (char *)malloc(strlen(line) + 1); strcpy(res, line); return res; }*/ def = GlobalAtOffset(df, ofs); if (def) { const char *defname = GetNameString(def->s_name); if (!strcmp(defname, "IMMEDIATE") || !strcmp(defname, ".imm") || !strcmp(defname, "I+") || !def->s_name) { etype_t ty; if (!req_t) ty = def->type; else { ty = (etype_t)(req_t->type); if (!ty) ty = def->type; } QC_snprintfz(line, sizeof(line), "%s", DecompileValueString(ty, &pr_globals[def->ofs])); } else { if (!*defname) { char line[16]; char *buf; QCD_def_t *parent; if (ofs >= df->parm_start && ofs < df->parm_start + df->locals) goto lookslikealocal; else if ((parent = GlobalAtOffset(df, ofs-1)) && parent->type == ev_vector) { // _y QC_snprintfz(line, sizeof(line), "%s_y", GetNameString(parent->s_name)); //globals, which are defined after the locals of the function they are first used in... buf = malloc(strlen(line)+1); //must be static variables, but we can't handle them very well strcpy(buf, line); def->s_name = buf; } else if ((parent = GlobalAtOffset(df, ofs-2)) && parent->type == ev_vector) { // _z QC_snprintfz(line, sizeof(line), "%s_z", GetNameString(parent->s_name)); //globals, which are defined after the locals of the function they are first used in... buf = malloc(strlen(line)+1); //must be static variables, but we can't handle them very well strcpy(buf, line); def->s_name = buf; } else { QC_snprintfz(line, sizeof(line), "_sloc_%i", def->ofs); //globals, which are defined after the locals of the function they are first used in... buf = malloc(strlen(line)+1); //must be static variables, but we can't handle them very well strcpy(buf, line); def->s_name = buf; } } QC_snprintfz(line, sizeof(line), "%s", GetNameString(def->s_name)); if (def->type == ev_field && req_t == type_field && req_t->aux_type == type_float && DecompileGetFieldTypeByDef(def) == ev_vector) strcat(line, "_x"); else if (def->type == ev_vector && req_t == type_float) strcat(line, "_x"); } res = (char *)malloc(strlen(line) + 1); strcpy(res, line); return res; } if (ofs >= df->parm_start && ofs < df->parm_start + df->locals) { int parmofs; lookslikealocal: QC_snprintfz(line, sizeof(line), "local_%i", ofs); for (i = 0, parmofs = ofs - df->parm_start; i < df->numparms && i < 8; i++) { if (parmofs < df->parm_size[i]) { if (parmofs) QC_snprintfz(line, sizeof(line), "par%i_%c", i, 'x'+parmofs); else QC_snprintfz(line, sizeof(line), "par%i", i); break; } parmofs -= df->parm_size[i]; } if (!assumelocals && i == df->numparms) return NULL; //we don't know what this is. assume its a temp res = (char *)malloc(strlen(line) + 1); strcpy(res, line); return res; } if (assumeglobals) { //unknown globals are normally assumed to be temps if (ofs >= ofs_parms[7]+ofs_size) { QC_snprintfz(line, sizeof(line), "tmp_%i", ofs); res = (char *)malloc(strlen(line) + 1); strcpy(res, line); return res; } } return NULL; } static struct { char *text; QCC_type_t *type; } IMMEDIATES[MAX_REGS]; gofs_t DecompileScaleIndex(QCD_function_t *df, gofs_t ofs) { gofs_t nofs = 0; /*if (ofs > ofs_parms[7]+ofs_size) nofs = ofs - df->parm_start + ofs_parms[7]+ofs_size; else*/ nofs = ofs; if ((nofs < 0) || (nofs > MAX_REGS - 1)) { printf("Fatal Error - Index (%i) out of bounds.\n", nofs); return 0; exit(1); } return nofs; } void DecompileImmediate_Free(void) { int i; for (i = 0; i < MAX_REGS; i++) { if (IMMEDIATES[i].text) { free(IMMEDIATES[i].text); IMMEDIATES[i].text = NULL; } } } void DecompileImmediate_Insert(QCD_function_t *df, gofs_t ofs, char *knew, QCC_type_t *type) { QCD_def_t *d; int nofs; nofs = DecompileScaleIndex(df, ofs); if (IMMEDIATES[nofs].text) { // fprintf(Decompileofile, "/*WARNING: Discarding \"%s\"/", IMMEDIATES[nofs]); free(IMMEDIATES[nofs].text); IMMEDIATES[nofs].text = NULL; } d = GlobalAtOffset(df, ofs); if (d && d->s_name)// && strcmp(GetString(d->s_name), "IMMEDIATE")) { //every operator has a src (or two) and a dest. //many compilers optimise by using the dest of a maths/logic operator to store to a local/global //they then skip off the storeopcode. //without this, we would never see these stores. IMMEDIATES[nofs].text = NULL; IMMEDIATES[nofs].type = NULL; QCC_CatVFile(Decompileofile, "%s = %s;\n", GetNameString(d->s_name), knew); } else { IMMEDIATES[nofs].text = (char *)malloc(strlen(knew) + 1); strcpy(IMMEDIATES[nofs].text, knew); IMMEDIATES[nofs].type = type; } } void FloatToString(char *out, size_t outsize, float f) { char *e; QC_snprintfz(out, outsize, "%f", f); //trim any trailing decimals e = strchr(out, '.'); if (e) { e = e+strlen(e); while (e > out && e[-1] == '0') e--; if (e > out && e[-1] == '.') e--; *e = 0; } } char *DecompileImmediate_Get(QCD_function_t *df, gofs_t ofs, QCC_type_t *req_t) { char *res; gofs_t nofs; nofs = DecompileScaleIndex(df, ofs); // printf("DecompileImmediate - Index scale: %i -> %i.\n", ofs, nofs); // insert at nofs if (IMMEDIATES[nofs].text) { // printf("DecompileImmediate - Reading \"%s\" at index %i.\n", IMMEDIATES[nofs], nofs); if (IMMEDIATES[nofs].type == type_vector && req_t == type_float) { res = (char *)malloc(strlen(IMMEDIATES[nofs].text) + 4); if (strchr(IMMEDIATES[nofs].text, '(')) sprintf(res, "%s[0]", IMMEDIATES[nofs].text); else sprintf(res, "%s_x", IMMEDIATES[nofs].text); } else { res = (char *)malloc(strlen(IMMEDIATES[nofs].text) + 1); strcpy(res, IMMEDIATES[nofs].text); } return res; } else { //you are now entering the hack zone. char temp[8192]; switch(req_t?req_t->type:-1) { case ev_void: //for lack of any better ideas. case ev_float: //denormalised floats need special handling. if ((0x7fffffff&*(int*)&pr_globals[ofs]) >= 1 && (0x7fffffff&*(int*)&pr_globals[ofs]) < 0x00800000) { QC_snprintfz(temp, sizeof(temp), "((float)(__variant)%ii)", *(int*)&pr_globals[ofs]); // if (req_t && *(int*)&pr_globals[ofs] >= 1 && *(int*)&pr_globals[ofs] < strofs) // ; //failure to break means we'll print out a trailing /*string*/ // else break; } else { FloatToString(temp, sizeof(temp), pr_globals[ofs]); break; } case ev_string: { const char *in; char *out; if (((int*)pr_globals)[ofs] < 0 || ((int*)pr_globals)[ofs] > strofs) { printf("Hey! That's not a string! error in %s\n", GetNameString(df->s_name)); QC_snprintfz(temp, sizeof(temp), "%f", pr_globals[ofs]); break; } in = GetString(((int*)pr_globals)[ofs]); out = temp; if (req_t->type != ev_string) { QC_snprintfz(temp, sizeof(temp), "/*%i*/", ((int*)pr_globals)[ofs]); out += strlen(out); } *out++ = '\"'; while (*in) { if (*in == '\"') { *out++ = '\\'; *out++ = '\"'; in++; } else if (*in == '\n') { *out++ = '\\'; *out++ = 'n'; in++; } else if (*in == '\\') { *out++ = '\\'; *out++ = '\\'; in++; } else if (*in == '\r') { *out++ = '\\'; *out++ = 'r'; in++; } else if (*in == '\a') { *out++ = '\\'; *out++ = 'a'; in++; } else if (*in == '\b') { *out++ = '\\'; *out++ = 'b'; in++; } else if (*in == '\f') { *out++ = '\\'; *out++ = 'f'; in++; } else if (*in == '\t') { *out++ = '\\'; *out++ = 't'; in++; } else if (*in == '\v') { *out++ = '\\'; *out++ = 'v'; in++; } else *out++ = *in++; } *out++ = '\"'; *out++ = '\0'; } break; case ev_vector: { char x[64], y[64], z[64]; FloatToString(x, sizeof(x), pr_globals[ofs+0]); FloatToString(y, sizeof(y), pr_globals[ofs+1]); FloatToString(z, sizeof(z), pr_globals[ofs+2]); QC_snprintfz(temp, sizeof(temp), "\'%s %s %s\'", x, y, z); } break; // case ev_quat: // QC_snprintfz(temp, sizeof(temp), "\'%f %f %f %f\'", pr_globals[ofs],pr_globals[ofs+1],pr_globals[ofs+2],pr_globals[ofs+3]); // break; case ev_integer: QC_snprintfz(temp, sizeof(temp), "%ii", ((int*)pr_globals)[ofs]); break; // case ev_uinteger: // QC_snprintfz(temp, sizeof(temp), "%uu", ((int*)pr_globals)[ofs]); // break; case ev_pointer: QC_snprintfz(temp, sizeof(temp), "(__variant*)0x%xi", ((int*)pr_globals)[ofs]); break; case ev_function: if (!((int*)pr_globals)[ofs]) QC_snprintfz(temp, sizeof(temp), "__NULL__/*func*/"); else if (((int*)pr_globals)[ofs] > 0 && ((int*)pr_globals)[ofs] < numfunctions && functions[((int*)pr_globals)[ofs]].s_name>0) QC_snprintfz(temp, sizeof(temp), "%s/*immediate*/", GetNameString(functions[((int*)pr_globals)[ofs]].s_name)); else QC_snprintfz(temp, sizeof(temp), "((__variant(...))%i)", ((int*)pr_globals)[ofs]); break; case ev_entity: if (!pr_globals[ofs]) QC_snprintfz(temp, sizeof(temp), "((entity)__NULL__)"); else QC_snprintfz(temp, sizeof(temp), "(entity)%i", ((int*)pr_globals)[ofs]); break; case ev_field: if (!pr_globals[ofs]) QC_snprintfz(temp, sizeof(temp), "((.void)__NULL__)"); else QC_snprintfz(temp, sizeof(temp), "/*field %s*/%i", DecompileGetFieldNameIdxByFinalOffset(((int*)pr_globals)[ofs]), ((int*)pr_globals)[ofs]); break; default: QC_snprintfz(temp, sizeof(temp), "FIXME"); break; } res = (char *)malloc(strlen(temp) + 1); strcpy(res, temp); return res; } return NULL; } char *DecompileGet(QCD_function_t *df, gofs_t ofs, QCC_type_t *req_t) { char *farg1; /*if (req_t == &def_short) { char temp[16]; QC_snprintfz(temp, sizeof(temp), "%i", ofs); return strdup(temp); }*/ farg1 = NULL; farg1 = DecompileGlobal(df, ofs, req_t); if (farg1 == NULL) farg1 = DecompileImmediate_Get(df, ofs, req_t); return farg1; } void DecompilePrintStatement(dstatement_t *s); void DecompileIndent(int c) { int i; if (c < 0) c = 0; for (i = 0; i < c; i++) { QCC_CatVFile(Decompileofile, "\t"); } } void DecompileOpcode(QCD_function_t *df, int a, int b, int c, char *opcode, QCC_type_t *typ1, QCC_type_t *typ2, QCC_type_t *typ3, int usebrackets, int *indent) { static char line[8192]; char *arg1, *arg2, *arg3; arg1 = DecompileGet(df, a, typ1); arg2 = DecompileGet(df, b, typ2); arg3 = DecompileGlobal(df, c, typ3); if (arg3) { DecompileIndent(*indent); if (usebrackets) QCC_CatVFile(Decompileofile, "%s = %s %s %s;\n", arg3, arg1, opcode, arg2); else QCC_CatVFile(Decompileofile, "%s = %s%s%s;\n", arg3, arg1, opcode, arg2); } else { if (usebrackets) QC_snprintfz(line, sizeof(line), "(%s %s %s)", arg1, opcode, arg2); else QC_snprintfz(line, sizeof(line), "%s%s%s", arg1, opcode, arg2); DecompileImmediate_Insert(df, c, line, typ3); } } static dstatement_t *jumptable; void DecompileDecompileStatement(QCD_function_t * df, dstatement_t * s, int *indent) { static char line[8192]; static char fnam[512]; char *arg1, *arg2, *arg3; int nargs, i, j; dstatement_t *t; unsigned int dom, doc, ifc, tom; QCC_type_t *typ1, *typ2, *typ3; QCD_def_t *par; dstatement_t *k; int dum; arg1 = arg2 = arg3 = NULL; line[0] = '\0'; fnam[0] = '\0'; if (jumptable) { //FIXME: the default case is the final jump, which will be misordered. flag the statement as needing to check them all or something. //FIXME: we need to push/pop these jumptables when switches are nested. doc = jumptable->op%OP_MARK_END_ELSE; if ((doc == OP_CASE && s == jumptable + (signed int)jumptable->b) || (doc == OP_CASERANGE && s == jumptable + (signed int)jumptable->c) || (doc == OPD_GOTO_DEFAULT && s == jumptable + (signed int)jumptable->a)) { DecompileIndent(*indent-1); if (doc == OPD_GOTO_DEFAULT) QCC_CatVFile(Decompileofile, "default:\n"); else if (doc == OP_CASERANGE) { arg1 = DecompileGet(df, jumptable->a, type_float); arg2 = DecompileGet(df, jumptable->b, type_float); QCC_CatVFile(Decompileofile, "case %s .. %s:\n", arg1, arg2); } else { arg1 = DecompileGet(df, jumptable->a, type_float); QCC_CatVFile(Decompileofile, "case %s:\n", arg1); } jumptable++; doc = jumptable->op%OP_MARK_END_ELSE; if (doc != OP_CASE && doc!= OP_CASERANGE && doc != OPD_GOTO_DEFAULT) jumptable = NULL; } } dom = s->op; doc = dom / OP_MARK_END_DO; ifc = (dom % OP_MARK_END_DO) / OP_MARK_END_ELSE; // use program flow information for (i = 0; i < ifc; i++) { (*indent)--; DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "}\n");//FrikaC style modification } for (i = 0; i < doc; i++) { DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "do\n"); DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "{\n"); (*indent)++; } /* * remove all program flow information */ s->op %= OP_MARK_END_ELSE; typ1 = pr_opcodes[s->op].type_a?*pr_opcodes[s->op].type_a:NULL; typ2 = pr_opcodes[s->op].type_b?*pr_opcodes[s->op].type_b:NULL; typ3 = pr_opcodes[s->op].type_c?*pr_opcodes[s->op].type_c:NULL; /* * printf("DecompileDecompileStatement - decompiling %i (%i):\n",(int)(s - statements),dom); * DecompilePrintStatement (s); */ /* * states are handled at top level */ if (s->op == OP_DONE) { } else if (s->op == OP_BOUNDCHECK) { /*these are auto-generated as a sideeffect. currently there is no syntax to explicitly use one (other than asm), but we don't want to polute the code when they're autogenerated, so ditch them all*/ } else if (s->op == OP_STATE) { par = DecompileGetParameter(s->a); if (!par) { printf("Error - Can't determine frame number.\n"); exit(1); } arg2 = DecompileGet(df, s->b, NULL); if (!arg2) { printf("Error - No state parameter with offset %i.\n", s->b); exit(1); } DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "state [ %s, %s ];\n", DecompileValueString((etype_t)(par->type), &pr_globals[par->ofs]), arg2); // free(arg2); } else if (s->op == OP_RETURN/* || s->op == OPQF_RETURN_V*/) { DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "return"); if (s->a) { QCC_CatVFile(Decompileofile, " "); arg1 = DecompileGet(df, s->a, type_void); //FIXME: we should know the proper type better than this. QCC_CatVFile(Decompileofile, "(%s)", arg1); } QCC_CatVFile(Decompileofile, ";\n"); } else if (pr_opcodes[s->op].flags & OPF_STD) { DecompileOpcode(df, s->a, s->b, s->c, pr_opcodes[s->op].name, typ1, typ2, typ3, true, indent); } else if ((pr_opcodes[s->op].flags & OPF_LOADPTR) || OP_GLOBALADDRESS == s->op) { arg1 = DecompileGet(df, s->a, typ1); arg2 = DecompileGet(df, s->b, typ2); arg3 = DecompileGlobal(df, s->c, typ3); if (arg3) { DecompileIndent(*indent); if (s->b) QCC_CatVFile(Decompileofile, "%s = &%s[%s];\n", arg3, arg1, arg2); else QCC_CatVFile(Decompileofile, "%s = &%s;\n", arg3, arg1); } else { if (s->b) QC_snprintfz(line, sizeof(line), "%s[%s]", arg1, arg2); else QC_snprintfz(line, sizeof(line), "%s", arg1); DecompileImmediate_Insert(df, s->c, line, typ3); } } else if ((OP_LOAD_F <= s->op && s->op <= OP_ADDRESS) || s->op == OP_LOAD_P || s->op == OP_LOAD_I) { if (s->op == OP_ADDRESS) { QCD_def_t *def = GlobalAtOffset(df, s->b); if (def && DecompileGetFieldTypeByDef(def) == ev_vector) typ3 = type_vector; } type_field->aux_type = typ3; DecompileOpcode(df, s->a, s->b, s->c, ".", typ1, typ2, typ3, false, indent); type_field->aux_type = NULL; } else if ((OP_LOADA_F <= s->op && s->op <= OP_LOADA_I))// || (OPQF_LOADBI_F <= s->op && s->op <= OPQF_LOADBI_P)) { static char line[512]; char *arg1, *arg2, *arg3; arg1 = DecompileGet(df, s->a, typ1); arg2 = DecompileGet(df, s->b, typ2); arg3 = DecompileGlobal(df, s->c, typ3); if (arg3) { DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "%s = %s[%s];\n", arg3, arg1, arg2); } else { QC_snprintfz(line, sizeof(line), "%s[%s]", arg1, arg2); DecompileImmediate_Insert(df, s->c, line, typ3); } } else if (pr_opcodes[s->op].flags & OPF_STORE) { QCC_type_t *parmtype=NULL; if (s->b >= ofs_parms[0] && s->b < ofs_parms[7]+ofs_size) { //okay, so typ1 might not be what the store type says it should be. k = s+1; while(k->op%OP_MARK_END_ELSE) { if ((k->op >= OP_CALL0 && k->op <= OP_CALL8) || (k->op >= OP_CALL1H && k->op <= OP_CALL8H)) { //well, this is it. int fn = ((int*)pr_globals)[k->a]; QCD_function_t *cf = &functions[fn]; int bi = DecompileBuiltin(cf); int pn; QCD_def_t *def; if (bi) { //builtins don't have valid parm_start values QCC_type_t **p = builtins[bi].params[(s->b-ofs_parms[0])/ofs_size]; parmtype = p?*p:NULL; } else { //qc functions do, though. fn = cf->parm_start; for (pn = 0; pn < (s->b-ofs_parms[0])/ofs_size; pn++) fn += cf->parm_size[pn]; def = DecompileGetParameter(fn); if (def) { switch(def->type) { case ev_float: parmtype = type_float; break; case ev_string: parmtype = type_string; break; case ev_vector: parmtype = type_vector; break; case ev_entity: parmtype = type_entity; break; case ev_function: parmtype = type_function; break; case ev_integer: parmtype = type_integer; break; // case ev_uinteger: // parmtype = type_uinteger; // break; // case ev_quat: // parmtype = type_quat; // break; default: // parmtype = type_float; break; } } } break; } else if (OP_STORE_F <= s->op && s->op <= OP_STORE_FNC) { if (k->b < s->b) //whoops... older QCCs can nest things awkwardly. break; } k++; } } if (parmtype) arg1 = DecompileGet(df, s->a, parmtype); else arg1 = DecompileGet(df, s->a, typ2); //types are backwards. *sigh* arg3 = DecompileGlobal(df, s->b, typ1); if (arg3) { DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "%s %s %s;\n", arg3, pr_opcodes[s->op].name, arg1); } else { QC_snprintfz(line, sizeof(line), "%s", arg1); DecompileImmediate_Insert(df, s->b, line, NULL); } } else if (pr_opcodes[s->op].flags & (OPF_STOREPTR|OPF_STOREPTROFS)) { arg1 = DecompileGet(df, s->a, typ2); //FIXME: we need to deal with ref types and other crazyness, so we know whether we need to add * or *& or if we can skip that completely arg2 = DecompileGet(df, s->b, typ2); DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "%s %s %s;\n", arg2, pr_opcodes[s->op].name, arg1); } else if (pr_opcodes[s->op].flags & OPF_STOREFLD) { arg1 = DecompileGet(df, s->a, typ1); //FIXME: we need to deal with ref types and other crazyness, so we know whether we need to add * or *& or if we can skip that completely arg2 = DecompileGet(df, s->b, typ2); arg3 = DecompileGet(df, s->c, typ3); DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "%s.%s %s %s;\n", arg1, arg2, pr_opcodes[s->op].name, arg3); } else if (OP_CONV_FTOI == s->op) { arg1 = DecompileGet(df, s->a, typ1); QC_snprintfz(line, sizeof(line), "(int)%s", arg1); DecompileImmediate_Insert(df, s->c, line, type_integer); } else if (OP_CONV_ITOF == s->op) { arg1 = DecompileGet(df, s->a, typ1); QC_snprintfz(line, sizeof(line), "(float)%s", arg1); DecompileImmediate_Insert(df, s->c, line, type_float); } else if (OP_RAND0 == s->op) { DecompileImmediate_Insert(df, ofs_return, "random()", type_float); } else if (OP_RAND1 == s->op) { arg1 = DecompileGet(df, s->a, typ1); QC_snprintfz(line, sizeof(line), "random(%s)", arg1); DecompileImmediate_Insert(df, ofs_return, line, type_float); } else if (OP_RAND2 == s->op) { arg1 = DecompileGet(df, s->a, typ1); arg2 = DecompileGet(df, s->b, typ2); QC_snprintfz(line, sizeof(line), "random(%s, %s)", arg1, arg2); DecompileImmediate_Insert(df, ofs_return, line, type_float); } else if (OP_RANDV0 == s->op) { DecompileImmediate_Insert(df, ofs_return, "randomv()", type_vector); } else if (OP_RANDV1 == s->op) { arg1 = DecompileGet(df, s->a, typ1); QC_snprintfz(line, sizeof(line), "randomv(%s)", arg1); DecompileImmediate_Insert(df, ofs_return, line, type_vector); } else if (OP_RANDV2 == s->op) { arg1 = DecompileGet(df, s->a, typ1); arg2 = DecompileGet(df, s->b, typ2); QC_snprintfz(line, sizeof(line), "randomv(%s, %s)", arg1, arg2); DecompileImmediate_Insert(df, ofs_return, line, type_vector); } else if (OP_FETCH_GBL_F <= s->op && s->op <= OP_FETCH_GBL_FNC) { if (s->op == OP_FETCH_GBL_F) typ3 = type_float; else if (s->op == OP_FETCH_GBL_V) typ3 = type_vector; else if (s->op == OP_FETCH_GBL_S) typ3 = type_string; else if (s->op == OP_FETCH_GBL_E) typ3 = type_entity; else if (s->op == OP_FETCH_GBL_FNC) typ3 = type_function; else typ3 = NULL; arg1 = DecompileGet(df, s->a, typ1); arg2 = DecompileGet(df, s->b, typ2); QC_snprintfz(line, sizeof(line), "%s[%s]", arg1, arg2); DecompileImmediate_Insert(df, s->c, line, typ3); } else if (pr_opcodes[s->op].flags & OPF_STDUNARY) { arg1 = DecompileGet(df, s->a, typ1); QC_snprintfz(line, sizeof(line), "%s%s", pr_opcodes[s->op].name, arg1); DecompileImmediate_Insert(df, s->c, line, type_float); } else if ((OP_CALL0 <= s->op && s->op <= OP_CALL8) || (OP_CALL1H <= s->op && s->op <= OP_CALL8H)) { if (OP_CALL1H <= s->op && s->op <= OP_CALL8H) nargs = (s->op - OP_CALL1H) + 1; else nargs = s->op - OP_CALL0; arg1 = DecompileGet(df, s->a, type_function); QC_snprintfz(line, sizeof(line), "%s(", arg1); QC_snprintfz(fnam, sizeof(fnam), "%s", arg1); for (i = 0; i < nargs; i++) { typ1 = NULL; if (i == 0 && ((OP_CALL1H <= s->op && s->op <= OP_CALL8H) || s->b)) j = s->b; else if (i == 1 && ((OP_CALL1H <= s->op && s->op <= OP_CALL8H) || s->c)) j = s->c; else j = ofs_parms[i]; if (arg1) free(arg1); arg1 = DecompileGet(df, (gofs_t)j, typ1); strcat(line, arg1); if (i < nargs - 1) strcat(line, ", ");//frikqcc modified } strcat(line, ")"); DecompileImmediate_Insert(df, ofs_return, line, NULL); /* * if ( ( ( (s+1)->a != 1) && ( (s+1)->b != 1) && * ( (s+2)->a != 1) && ( (s+2)->b != 1) ) || * ( ((s+1)->op) % OP_MARK_END_ELSE == OP_CALL0 ) ) { * DecompileIndent(*indent); * fprintf(Decompileofile,"%s;\n",line); * } */ if ((((s + 1)->a != ofs_return) && ((s + 1)->b != ofs_return) && ((s + 2)->a != ofs_return) && ((s + 2)->b != ofs_return)) || ((((s + 1)->op) % OP_MARK_END_ELSE == OP_CALL0) && ((((s + 2)->a != ofs_return)) || ((s + 2)->b != ofs_return)))) { DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "%s;\n", line); } } else if (s->op == OP_IF_I || s->op == OP_IFNOT_I || s->op == OP_IF_F || s->op == OP_IFNOT_F || s->op == OP_IF_S || s->op == OP_IFNOT_S/* || s->op == OPQF_IFA || s->op == OPQF_IFB || s->op == OPQF_IFAE || s->op == OPQF_IFBE*/) { arg1 = DecompileGet(df, s->a, type_float); //FIXME: this isn't quite accurate... arg2 = DecompileGlobal(df, s->a, NULL); if (s->op == OP_IFNOT_I || s->op == OP_IFNOT_F || s->op == OP_IFNOT_S) { lameifnot: if ((signed int)s->b < 1) { // if (arg1) // free(arg1); // if (arg2) // free(arg2); // if (arg3) // free(arg3); return; printf("Found a negative IFNOT jump.\n"); exit(1); } /* * get instruction right before the target */ t = s + (signed int)s->b - 1; tom = t->op % OP_MARK_END_ELSE; if (tom != OP_GOTO) { /* * pure if */ DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "if (%s)\n", arg1);//FrikaC modified DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "{\n"); (*indent)++; } else { if ((signed int)t->a > 0) { /* * ite */ DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "if (%s)\n", arg1);//FrikaC modified DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "{\n"); (*indent)++; } else { if (((signed int)t->a + (signed int)s->b) > 1) { /* * pure if */ DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "if (%s)\n", arg1);//FrikaC modified DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "{\n"); (*indent)++; } else { dum = 1; for (k = t + (signed int)(t->a); k < s; k++) { tom = k->op % OP_MARK_END_ELSE; if (tom == OP_GOTO || tom == OP_IF_I || tom == OP_IFNOT_I || tom == OP_IF_F || tom == OP_IFNOT_F || tom == OP_IF_S || tom == OP_IFNOT_S) dum = 0; } if (dum) { DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "while (%s)\n", arg1); DecompileIndent(*indent); //FrikaC QCC_CatVFile(Decompileofile, "{\n"); (*indent)++; } else { DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "if (%s)\n", arg1);//FrikaC modified DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "{\n"); (*indent)++; } } } } } /*else if (s->op == OPQF_IFA) { char *t = arg1; arg1 = malloc(strlen(arg1)+8); sprintf(arg1, "(%s) <= 0", t); free(t); goto lameifnot; } else if (s->op == OPQF_IFAE) { char *t = arg1; arg1 = malloc(strlen(arg1)+7); sprintf(arg1, "(%s) < 0", t); free(t); goto lameifnot; } else if (s->op == OPQF_IFB) { char *t = arg1; arg1 = malloc(strlen(arg1)+8); sprintf(arg1, "(%s) >= 0", t); free(t); goto lameifnot; } else if (s->op == OPQF_IFBE) { char *t = arg1; arg1 = malloc(strlen(arg1)+7); sprintf(arg1, "(%s) > 0", t); free(t); goto lameifnot; }*/ else { if ((signed int)s->b>0) { char *t = arg1; //if (!...) arg1 = malloc(strlen(arg1)+2); sprintf(arg1, "!%s", t); free(t); goto lameifnot; } else { /* * do ... while */ (*indent)--; QCC_CatVFile(Decompileofile, "\n"); DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "} while (%s);\n", arg1); } } } else if (s->op == OP_SWITCH_F) { arg1 = DecompileGet(df, s->a, type_float); //FIXME: this isn't quite accurate... jumptable = s+(signed int)s->b; DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "switch (%s)\n", arg1); DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "{\n"); (*indent)++; } else if (s->op == OP_CASE || s->op == OP_CASERANGE || s->op == OPD_GOTO_DEFAULT) { //shoulda been handled as part of the jumptable handling. } else if (s->op == OPD_GOTO_FORSTART) { DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "do_tail\n"); DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "{\n"); (*indent)++; } else if (s->op == OPD_GOTO_WHILE1) { (*indent)--; DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "} while(1);\n"); } else if (s->op == OPD_GOTO_BREAK) { DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "break;\n"); } else if (s->op == OP_GOTO) { if ((signed int)s->a > 0) { /* * else */ (*indent)--; DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "}\n"); DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "else\n"); DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "{\n"); (*indent)++; //FIXME: look for the next control statement (ignoring writes to temps, maybe allow functions too if the following statement reads OFS_RETURN) //if its an IFNOT (optionally with its own else) that ends at our else then output this as "}\nelse " instead } else { /* * while */ (*indent)--; DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "}\n"); } } else if (s->op == OP_THINKTIME) { arg1 = DecompileGet(df, s->a, type_entity); arg2 = DecompileGet(df, s->b, type_float); DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "__thinktime %s : %s;\n", arg1, arg2); } else { int op = s->op%OP_MARK_END_ELSE; if (op <= OP_BITOR_F && pr_opcodes[s->op].opname) printf("warning: Unknown usage of OP_%s", pr_opcodes[s->op].opname); else { DecompileIndent(*indent); QCC_CatVFile(Decompileofile, "[OP_%s", pr_opcodes[op].opname); if (s->a) QCC_CatVFile(Decompileofile, ", %s", DecompileGet(df, s->a, typ1)); if (s->b) QCC_CatVFile(Decompileofile, ", %s", DecompileGet(df, s->b, typ1)); if (s->c) QCC_CatVFile(Decompileofile, ", %s", DecompileGet(df, s->c, typ1)); QCC_CatVFile(Decompileofile, "]\n"); printf("warning: Unknown opcode %i in %s\n", op, GetNameString(df->s_name)); } } // printf("DecompileDecompileStatement - Current line is \"%s\"\n", line); if (arg1) free(arg1); if (arg2) free(arg2); if (arg3) free(arg3); return; } pbool DecompileDecompileFunction(QCD_function_t * df, dstatement_t *altdone) { dstatement_t *ds; int indent; // Initialize DecompileImmediate_Free(); indent = 1; jumptable = NULL; ds = statements + df->first_statement; if(ds->op == OP_STATE || ds->op == OP_CSTATE || ds->op == OP_CWSTATE) ds++; while (1) { if (ds == altdone) { //decompile the dummy done, cos we can DecompileDecompileStatement(df, statements, &indent); break; } DecompileDecompileStatement(df, ds, &indent); if (!ds->op) break; ds++; } if (indent != 1) { printf("warning: Indentation structure corrupt (in func %s)\n", GetNameString(df->s_name)); return false; } return true; } char *DecompileString(int qcstring) { static char buf[8192]; char *s; int c = 1; const char *string = GetString(qcstring); if (qcstring < 0 || qcstring >= strofs) return "Invalid String"; s = buf; *s++ = '"'; while (string && *string) { if (c == sizeof(buf) - 2) break; if (*string == '\n') { *s++ = '\\'; *s++ = 'n'; c++; } else if (*string == '"') { *s++ = '\\'; *s++ = '"'; c++; } else { *s++ = *string; c++; } string++; if (c > (int)(sizeof(buf) - 10)) { *s++ = '.'; *s++ = '.'; *s++ = '.'; c += 3; break; } } *s++ = '"'; *s++ = 0; return buf; } char *DecompileValueString(etype_t type, void *val) { static char line[8192]; line[0] = '\0'; switch (type) { case ev_string: QC_snprintfz(line, sizeof(line), "%s", DecompileString(*(int *)val)); break; case ev_void: QC_snprintfz(line, sizeof(line), "void"); break; case ev_float: if (*(float *)val > 999999 || *(float *)val < -999999) // ugh QC_snprintfz(line, sizeof(line), "%.f", *(float *)val); else if ((!(*(int*)val & 0x7f800000) || (*(int*)val & 0x7f800000)==0x7f800000) && (*(int*)val & 0x7fffffff)) //QC_snprintfz(line, sizeof(line), "%%%i", *(int*)val); QC_snprintfz(line, sizeof(line), "/*%di*/%s", *(int*)val, DecompileString(*(int *)val)); else if ((*(float *)val < 0.001) && (*(float *)val > 0)) QC_snprintfz(line, sizeof(line), "%.6f", *(float *)val); else QC_snprintfz(line, sizeof(line), "%g", *(float *)val); break; case ev_vector: QC_snprintfz(line, sizeof(line), "'%g %g %g'", ((float *)val)[0], ((float *)val)[1], ((float *)val)[2]); break; // case ev_quat: // QC_snprintfz(line, sizeof(line), "'%g %g %g %g'", ((float *)val)[0], ((float *)val)[1], ((float *)val)[2], ((float *)val)[3]); // break; case ev_field: DecompileGetFieldNameIdxByFinalOffset2(line, sizeof(line), *(int *)val); break; case ev_entity: QC_snprintfz(line, sizeof(line), "(entity)%ii", *(int *)val); break; case ev_integer: QC_snprintfz(line, sizeof(line), "%ii", *(int *)val); break; // case ev_uinteger: // QC_snprintfz(line, sizeof(line), "%uu", *(int *)val); // break; case ev_pointer: QC_snprintfz(line, sizeof(line), "(__variant*)0x%xi", *(int *)val); break; case ev_function: if (*(int *)val>0 && *(int *)valofs); } else *debug = 0; if (!*def->s_name) //null string... { QC_snprintfz(line, sizeof(line), "%s _p_%i%s", type_name(def), def->ofs, debug); } else if (!strcmp(GetNameString(def->s_name), "IMMEDIATE") || !strcmp(GetNameString(def->s_name), ".imm") || !strcmp(GetNameString(def->s_name), "I+")) { QC_snprintfz(line, sizeof(line), "%s%s", DecompileValueString((etype_t)(def->type), &pr_globals[def->ofs]), debug); } else { QC_snprintfz(line, sizeof(line), "%s %s%s", type_name(def), GetNameString(def->s_name), debug); } return line; } //we only work with prior fields. const char *GetMatchingField(QCD_def_t *field) { int i; QCD_def_t *def; int ld, lf; const char *ret = NULL; def = NULL; for (i = 0; i < numglobaldefs; i++) { def = &globals[i]; if ((def->type&~DEF_SAVEGLOBAL) == ev_field) { if (((int*)pr_globals)[def->ofs] == field->ofs) { if (!strcmp(GetNameString(def->s_name), GetNameString(field->s_name))) return NULL; //found ourself, give up. lf = strlen(GetNameString(field->s_name)); ld = strlen(GetNameString(def->s_name)); if (lf - 2 == ld) { if ((GetNameString(field->s_name)[lf-2]) == '_' && (GetNameString(field->s_name)[lf-1]) == 'x') if (!strncmp(GetNameString(field->s_name), GetNameString(def->s_name), ld)) return NULL; //vector found foo_x } if (!ret) ret = GetNameString(def->s_name); } } } return ret; } QCD_def_t *GetField(const char *name) { int i; QCD_def_t *d; if (!*name) return NULL; if (*name == '.') name++; //some idiot _intentionally_ decided to fuck shit up. go them! for (i = 0; i < numfielddefs; i++) { d = &fields[i]; if (!strcmp(GetNameString(d->s_name), name)) return d; } return NULL; } QCD_def_t *DecompileGetParameter(gofs_t ofs) { int i; QCD_def_t *def, *fb = NULL; def = NULL; for (i = 0; i < numglobaldefs; i++) { def = &globals[i]; if (def->ofs == ofs) { if (def->type > ev_variant) fb = def; //urgh, some weird one. see if its an alias. else return def; } } return fb; } QCD_def_t *DecompileFindGlobal(const char *findname) { int i; QCD_def_t *def; const char *defname; def = NULL; for (i = 0; i < numglobaldefs; i++) { def = &globals[i]; defname = GetNameString(def->s_name); if (!strcmp(findname, defname)) { return def; } } return NULL; } QCD_def_t *DecompileFunctionGlobal(int funcnum) { int i; QCD_def_t *def; def = NULL; for (i = 0; i < numglobaldefs; i++) { def = &globals[i]; if (def->type == ev_function) { if (((int*)pr_globals)[def->ofs] == funcnum) { return def; } } } return NULL; } void DecompilePreceedingGlobals(int start, int end, const char *name) { QCD_def_t *par; int j; QCD_def_t *ef; static char line[8192]; char asize[64]; int arraysize; const char *matchingfield; //print globals leading up to the function. for (j = start; j < end; j++) { par = DecompileGetParameter((gofs_t)j); if (par) { if (par->type & DEF_H2ARRAY) { arraysize = ((int*)pr_globals)[par->ofs-1]+1; QC_snprintfz(asize, sizeof(asize), "[%i]", arraysize); par->type -= DEF_H2ARRAY; } else arraysize = *asize = 0; if (par->type & DEF_SAVEGLOBAL) par->type -= DEF_SAVEGLOBAL; if (par->type == ev_function) { if (strcmp(GetNameString(par->s_name), "IMMEDIATE") && strcmp(GetNameString(par->s_name), ".imm") && strcmp(GetNameString(par->s_name), "I+")) { if (strcmp(GetNameString(par->s_name), name)) { int f = ((int*)pr_globals)[par->ofs]; //DecompileGetFunctionIdxByName(strings + par->s_name); if (f && strcmp(GetNameString(functions[f].s_name), GetNameString(par->s_name))) { char *s = strrchr(DecompileProfiles[f], ' '); //happens with void() func = otherfunc; //such functions thus don't have their own type+body *s = 0; QCC_CatVFile(Decompileofile, "var %s %s%s = %s;\n", DecompileProfiles[f], GetNameString(par->s_name), asize, s+1); *s = ' '; } else QCC_CatVFile(Decompileofile, "%s;\n", DecompileProfiles[f]); } } } else if (par->type != ev_pointer) { if (strcmp(GetNameString(par->s_name), "IMMEDIATE") && strcmp(GetNameString(par->s_name), ".imm") && strcmp(GetNameString(par->s_name), "I+") && par->s_name) { if (par->type == ev_field) { ef = GetField(GetNameString(par->s_name)); if (!ef) { QCC_CatVFile(Decompileofile, "var .unknowntype %s%s;\n", GetNameString(par->s_name), asize); printf("Fatal Error: Could not locate a field named \"%s\"\n", GetNameString(par->s_name)); } else { //if (ef->type == ev_vector) // j += 2; matchingfield = GetMatchingField(ef); #ifndef DONT_USE_DIRTY_TRICKS //could try scanning for an op_address+op_storep_fnc pair if ((ef->type == ev_function) && !strcmp(GetNameString(ef->s_name), "th_pain")) { QCC_CatVFile(Decompileofile, ".void(entity attacker, float damage) th_pain;\n"); } else #endif { if (matchingfield) QCC_CatVFile(Decompileofile, "var .%s %s%s = %s;\n", type_name(ef), GetNameString(ef->s_name), asize, matchingfield); else QCC_CatVFile(Decompileofile, ".%s %s%s;\n", type_name(ef), GetNameString(ef->s_name), asize); // fprintf(Decompileofile, "//%i %i %i %i\n", ef->ofs, ((int*)pr_globals)[ef->ofs], par->ofs, ((int*)pr_globals)[par->ofs]); } } } else { if (par->type == ev_vector) j += 2; if (par->type == ev_entity || par->type == ev_void) { QCC_CatVFile(Decompileofile, "%s %s%s;\n", type_name(par), GetNameString(par->s_name), asize); } else { line[0] = '\0'; if (IsConstant(par)) { if (*asize) { int k; QCC_CatVFile(Decompileofile, "%s %s%s = {", type_name(par), GetNameString(par->s_name), asize); for (k = 0; k < arraysize; k++) QCC_CatVFile(Decompileofile, "%s%s", DecompileValueString((etype_t)(par->type), &pr_globals[par->ofs+type_size[par->type]*k]), (k+1)==arraysize?"":", "); QCC_CatVFile(Decompileofile, "};\n"); } else { QC_snprintfz(line, sizeof(line), "%s", DecompileValueString((etype_t)(par->type), &pr_globals[par->ofs])); QCC_CatVFile(Decompileofile, "%s %s%s = %s;\n", type_name(par), GetNameString(par->s_name), asize, line); } } else { int k; for (k = 0; k < type_size[par->type]; k++) if (pr_globals[par->ofs+k] != 0) break; if (k != type_size[par->type]) { QC_snprintfz(line, sizeof(line), "%s", DecompileValueString((etype_t)(par->type), &pr_globals[par->ofs])); QCC_CatVFile(Decompileofile, "%s %s%s /* = %s */;\n", type_name(par), GetNameString(par->s_name), asize, line); } else QCC_CatVFile(Decompileofile, "%s %s%s;\n", type_name(par), GetNameString(par->s_name), asize); } } } } } } } } void DecompileFunction(const char *name, int *lastglobal) { int i, findex, ps; dstatement_t *ds, *ts, *altdone; QCD_function_t *df; QCD_def_t *par; char *arg2; unsigned short dom, tom; int j, start, end; static char line[8192]; dstatement_t *k; int dum; size_t startpos; for (i = 1; i < numfunctions; i++) if (!strcmp(name, GetNameString(functions[i].s_name))) break; if (i == numfunctions) { printf("Fatal Error: No function named \"%s\"\n", name); exit(1); } df = functions + i; altdone = statements + numstatements; for (j = i+1; j < numfunctions; j++) { if (functions[j].first_statement <= 0) continue; altdone = statements + functions[j].first_statement; break; } findex = i; start = *lastglobal; // if (dfpred->first_statement <= 0 && df->first_statement > 0) // start -= 1; end = df->parm_start; if (!end) { par = DecompileFindGlobal(name); if (par) end = par - globals; } *lastglobal = max(*lastglobal, end + df->locals); DecompilePreceedingGlobals(start, end, name); /* * Check ''local globals'' */ if (df->first_statement <= 0) { QCC_CatVFile(Decompileofile, "%s", DecompileProfiles[findex]); QCC_CatVFile(Decompileofile, " = #%i; \n", -df->first_statement); return; } ds = statements + df->first_statement; while (1) { dom = (ds->op) % OP_MARK_END_ELSE; if (!dom || ds == altdone) break; else if (dom == OP_GOTO) { // check for i-t-e if ((signed int)ds->a > 0) { ts = ds + (signed int)ds->a; ts->op += OP_MARK_END_ELSE; // mark the end of a if/ite construct } else { ts = ds + (signed int)ds->a; //if its a negative goto then it should normally be the end of a while{} loop. if we can't find the while statement itself, then its an infinite loop for (k = (ts); k < ds; k++) { tom = k->op % OP_MARK_END_ELSE; if (tom == OP_IF_I || tom == OP_IFNOT_I || tom == OP_IF_F || tom == OP_IFNOT_F || tom == OP_IF_S || tom == OP_IFNOT_S) { if (k + (signed int)k->b == ds+1) break; } } if (k == ds) { ds->op += OPD_GOTO_WHILE1-OP_GOTO; ts->op += OP_MARK_END_DO; } } } else if (dom == OP_SWITCH_F && (signed int)ds->b > 0) { //essentially a goto that jumps to the OP_CASE lines. any gotos within that block which jump to the end of those cases are breaks. ts = ds + (signed int)ds->b; for (k = (ds+1); k < ts; k++) { tom = k->op % OP_MARK_END_ELSE; if (tom == OP_GOTO && k+(signed int)k->a > ts) k->op += OPD_GOTO_BREAK-OP_GOTO; } ts->op += OP_MARK_END_ELSE; while ((ts->op%OP_MARK_END_ELSE) == OP_CASE || (ts->op%OP_MARK_END_ELSE) == OP_CASERANGE) ts++; if ((ts->op%OP_MARK_END_ELSE) == OP_GOTO && (signed int)ts->a < 0 && ts+(signed int)ts->a > ds) ts->op += OPD_GOTO_DEFAULT-OP_GOTO; } else if (dom == OP_IFNOT_I || dom == OP_IFNOT_F || dom == OP_IFNOT_S) { // check for pure if ts = ds + (signed int)ds->b; tom = (ts - 1)->op % OP_MARK_END_ELSE; if (tom != OP_GOTO) ts->op += OP_MARK_END_ELSE; // mark the end of a if construct else if ((signed int)(ts - 1)->a < 0) { if (((signed int)(ts - 1)->a + (signed int)ds->b) > 1) { // pure if ts->op += OP_MARK_END_ELSE; // mark the end of a if/ite construct } else { dum = 1; for (k = (ts - 1) + (signed int)((ts - 1)->a); k < ds; k++) { tom = k->op % OP_MARK_END_ELSE; if (tom == OP_GOTO || tom == OP_IF_I || tom == OP_IFNOT_I || tom == OP_IF_F || tom == OP_IFNOT_F || tom == OP_IF_S || tom == OP_IFNOT_S) dum = 0; } if (!dum) { // pure if ts->op += OP_MARK_END_ELSE; // mark the end of a if/ite construct } } } } else if (dom == OP_IF_I || dom == OP_IF_F || dom == OP_IF_S) { if ((signed int)ds->b<1) { ts = ds + (signed int)ds->b; //this is some kind of loop, either a while or for. //if the statement before the 'do' is a forwards goto, and it jumps to within the loop (instead of after), then we have to assume that it is a for loop and not a loop inside an else block. if ((ts-1)->op%OP_MARK_END_ELSE == OP_GOTO && (signed int)(ts-1)->a > 0 && (ts-1)+(signed int)(ts-1)->a <= ds) { (--ts)->op += OPD_GOTO_FORSTART - OP_GOTO; //because it was earlier, we need to unmark that goto's target as an end_else ts = ts + (signed int)ts->a; ts->op -= OP_MARK_END_ELSE; } else ts->op += OP_MARK_END_DO; // mark the start of a do construct } else { ts = ds + ds->b; if ((ts-1)->op%OP_MARK_END_ELSE != OP_GOTO) ts->op += OP_MARK_END_ELSE; // mark the end of an if construct else if ((signed int)(ts - 1)->a < 0) { if (((signed int)(ts - 1)->a + (signed int)ds->b) > 1) { // pure if ts->op += OP_MARK_END_ELSE; // mark the end of a if/ite construct } else { dum = 1; for (k = (ts - 1) + (signed int)((ts - 1)->a); k < ds; k++) { tom = k->op % OP_MARK_END_ELSE; if (tom == OP_GOTO || tom == OP_IF_I || tom == OP_IFNOT_I || tom == OP_IF_F || tom == OP_IFNOT_F || tom == OP_IF_S || tom == OP_IFNOT_S) dum = 0; } if (!dum) { // pure if ts->op += OP_MARK_END_ELSE; // mark the end of a if/ite construct } } } } } ds++; } /* * print the prototype */ QCC_CatVFile(Decompileofile, "\n%s", DecompileProfiles[findex]); // handle state functions ds = statements + df->first_statement; if (ds->op == OP_STATE) { par = DecompileGetParameter(ds->a); if (!par) { static QCD_def_t pars; //must be a global (gotta be a float), create the def as needed pars.ofs = ds->a; pars.s_name = "IMMEDIATE"; pars.type = ev_float; par = &pars; // printf("Fatal Error - Can't determine frame number."); // exit(1); } arg2 = DecompileGet(df, ds->b, NULL); if (!arg2) { printf("Fatal Error - No state parameter with offset %i.", ds->b); exit(1); } QCC_CatVFile(Decompileofile, " = [ %s, %s ]", DecompileValueString((etype_t)(par->type), &pr_globals[par->ofs]), arg2); free(arg2); } else if (ds->op == OP_CSTATE || ds->op == OP_CWSTATE) { pbool backwards = false; char *e1,*e2; char *arg1 = DecompileGet(df, ds->a, type_float); arg2 = DecompileGet(df, ds->b, type_float); if (!arg2) { printf("Fatal Error - No state parameter with offset %i.", ds->b); exit(1); } if (strtod(arg1, &e1) > strtod(arg2, &e2) && !*e1 && !*e2) backwards = true; //doesn't really make any difference, but does trigger an error. QCC_CatVFile(Decompileofile, " = [%s%s %s .. %s ]", backwards?"--":"++", (ds->op==OP_CWSTATE)?"(W)":"", arg1, arg2); free(arg2); } else { QCC_CatVFile(Decompileofile, " ="); } QCC_CatVFile(Decompileofile, "\n{\n"); startpos = Decompileofile->size; /* fprintf(Decompileprofile, "%s", DecompileProfiles[findex]); fprintf(Decompileprofile, ") %s;\n", name); */ /* * calculate the parameter size */ for (j = 0, ps = 0; j < df->numparms; j++) { par = DecompileGetParameter((gofs_t)(df->parm_start + ps)); if (par) { if (!*par->s_name) { QC_snprintfz(line, sizeof(line), "_p_%i", par->ofs); arg2 = malloc(strlen(line)+1); strcpy(arg2, line); par->s_name = arg2; } } ps += df->parm_size[j]; } /* * print the locals */ if (df->locals > 0) { if ((df->parm_start) + df->locals - 1 >= (df->parm_start) + ps) { for (i = df->parm_start + ps; i < (df->parm_start) + df->locals; i++) { par = DecompileGetParameter((gofs_t)i); if (!par) { // temps, or stripped... continue; } else { if (!strcmp(GetNameString(par->s_name), "IMMEDIATE") || !strcmp(GetNameString(par->s_name), ".imm") || !strcmp(GetNameString(par->s_name), "I+")) continue; // immediates don't belong if (!GetNameString(par->s_name)) { QC_snprintfz(line, sizeof(line), "_l_%i", par->ofs); arg2 = malloc(strlen(line)+1); strcpy(arg2, line); par->s_name = arg2; } if (par->type == ev_function) { printf("Warning Fields and functions must be global\n"); } else { if (((int*)pr_globals)[par->ofs]) QCC_CatVFile(Decompileofile, "\tlocal %s = %s;\n", DecompilePrintParameter(par), DecompileValueString(par->type, &pr_globals[par->ofs])); else QCC_CatVFile(Decompileofile, "\tlocal %s;\n", DecompilePrintParameter(par)); } if (par->type == ev_vector) i += 2; } } QCC_CatVFile(Decompileofile, "\n"); } } /* * do the hard work */ if (!DecompileDecompileFunction(df, altdone)) { QCC_InsertVFile(Decompileofile, startpos, "#error Corrupt Function: %s\n#if 0\n", GetNameString(df->s_name)); QCC_CatVFile(Decompileofile, "#endif\n"); } QCC_CatVFile(Decompileofile, "};\n"); } extern pbool safedecomp; static int fake_name; static char synth_name[1024]; // fake name part2 pbool TrySynthName(const char *first) { int i; // try to figure out the filename // based on the first function in the file for (i=0; i < FILELISTSIZE; i+=2) { if (!strcmp(filenames[i], first)) { QC_snprintfz(synth_name, sizeof(synth_name), "%s", filenames[i + 1]); return true; } } return false; } void DecompileDecompileFunctions(const char *origcopyright) { int i; unsigned int o; QCD_function_t *d; pbool bogusname; vfile_t *f = NULL; char fname[1024]; int lastglob = 1; const char *lastfileofs = NULL; QCD_def_t *def; DecompileCalcProfiles(); AddSourceFile(NULL, "progs.src"); Decompileprogssrc = QCC_AddVFile("progs.src", NULL, 0); if (!Decompileprogssrc) { printf("Fatal Error - Could not open \"progs.src\" for output.\n"); exit(1); } QCC_CatVFile(Decompileprogssrc, "./progs.dat\n\n"); QCC_CatVFile(Decompileprogssrc, "#pragma flag enable lax //remove this line once you've fixed up any decompiler bugs...\n"); if (origcopyright) QCC_CatVFile(Decompileprogssrc, "//#pragma copyright \"%s\"\n", origcopyright); QCC_CatVFile(Decompileprogssrc, "\n"); def = DecompileFindGlobal("end_sys_fields"); lastglob = def?def->ofs+1:1; if (lastglob != 1) { QC_snprintfz(synth_name, sizeof(synth_name), "sysdefs.qc"); QC_snprintfz(fname, sizeof(fname), "%s", synth_name); if (!DecompileAlreadySeen(fname, &f)) { printf("decompiling %s\n", fname); compilecb(); QCC_CatVFile(Decompileprogssrc, "%s\n", fname); } if (!f) { printf("Fatal Error - Could not open \"%s\" for output.\n", fname); exit(1); } Decompileofile = f; DecompilePreceedingGlobals(1, lastglob, ""); } for (i = 1; i < numfunctions; i++) { d = &functions[i]; if (d->s_file != lastfileofs || f == NULL) { lastfileofs = d->s_file; fname[0] = '\0'; //if (d->s_file <= strofs && d->s_file >= 0) sprintf(fname, "%s", GetNameString(d->s_file)); // FrikaC -- not sure if this is cool or what? bogusname = false; if (strlen(fname) <= 0) bogusname = true; else for (o = 0; o < strlen(fname); o++) { if ((fname[o] < 'a' || fname[o] > 'z') && (fname[o] < '0' || fname[o] > '9') && (fname[o] <'A' || fname[o] > 'Z') && (fname[o] != '.' && fname[o] != '!' && fname[o] != '_')) { if (fname[o] == '/') fname[o] = '.'; else if (fname[o] == '\\') fname[o] = '.'; else { bogusname = true; break; } } } if (bogusname) { if (*fname && !DecompileAlreadySeen(fname, NULL)) { synth_name[0] = 0; } if(!TrySynthName(qcva("%s", GetNameString(d->s_name))) && !synth_name[0]) QC_snprintfz(synth_name, sizeof(synth_name), "frik%i.qc", fake_name++); QC_snprintfz(fname, sizeof(fname), "%s", synth_name); } else synth_name[0] = 0; if (!DecompileAlreadySeen(fname, &f)) { printf("decompiling %s\n", fname); compilecb(); QCC_CatVFile(Decompileprogssrc, "%s\n", fname); } if (!f) { printf("Fatal Error - Could not open \"%s\" for output.\n", fname); exit(1); } } Decompileofile = f; DecompileFunction(GetNameString(d->s_name), &lastglob); } } void DecompileProgsDat(const char *name, void *buf, size_t bufsize) { char *c = ReadProgsCopyright(buf, bufsize); if (c) printf("Copyright: %s\n", c); PreCompile(); pHash_Get = &Hash_Get; pHash_GetNext = &Hash_GetNext; pHash_Add = &Hash_Add; pHash_RemoveData = &Hash_RemoveData; Hash_InitTable(&typedeftable, 1024, qccHunkAlloc(Hash_BytesForBuckets(1024))); maxtypeinfos = 64; qcc_typeinfo = (void *)malloc(sizeof(QCC_type_t)*maxtypeinfos); numtypeinfos = 0; type_void = QCC_PR_NewType("void", ev_void, true); type_string = QCC_PR_NewType("string", ev_string, true); type_float = QCC_PR_NewType("float", ev_float, true); type_bfloat = type_float;//QCC_PR_NewType("float", ev_float, true); type_vector = QCC_PR_NewType("vector", ev_vector, true); type_entity = QCC_PR_NewType("entity", ev_entity, true); type_field = QCC_PR_NewType("__field", ev_field, false); type_function = QCC_PR_NewType("__function", ev_function, false); type_function->aux_type = type_void; type_pointer = QCC_PR_NewType("__pointer", ev_pointer, false); type_integer = QCC_PR_NewType("__integer", ev_integer, true); type_bint = type_integer; type_variant = QCC_PR_NewType("variant", ev_variant, true); type_variant = QCC_PR_NewType("__variant", ev_variant, true); type_invalid = QCC_PR_NewType("invalid", ev_void, false); DecompileReadData(name, buf, bufsize); DecompileDetermineArrays(); DecompileDecompileFunctions(c); printf("Done.\n"); } char *DecompileGlobalStringNoContents(gofs_t ofs) { int i; QCD_def_t *def; static char line[128]; line[0] = '0'; QC_snprintfz(line, sizeof(line), "%i(??""?)", ofs); for (i = 0; i < numglobaldefs; i++) { def = &globals[i]; if (def->ofs == ofs) { line[0] = '0'; QC_snprintfz(line, sizeof(line), "%i(%s)", def->ofs, GetNameString(def->s_name)); break; } } i = strlen(line); for (; i < 16; i++) strcat(line, " "); strcat(line, " "); return line; } char *DecompileGlobalString(gofs_t ofs) { char *s; int i; QCD_def_t *def; static char line[128]; line[0] = '0'; QC_snprintfz(line, sizeof(line), "%i(??""?)", ofs); for (i = 0; i < numglobaldefs; i++) { def = &globals[i]; if (def->ofs == ofs) { line[0] = '0'; if (!strcmp(GetNameString(def->s_name), "IMMEDIATE") || !strcmp(GetNameString(def->s_name), ".imm") || !strcmp(GetNameString(def->s_name), "I+")) { s = PR_ValueString((etype_t)(def->type), &pr_globals[ofs]); QC_snprintfz(line, sizeof(line), "%i(%s)", def->ofs, s); } else QC_snprintfz(line, sizeof(line), "%i(%s)", def->ofs, GetNameString(def->s_name)); } } i = strlen(line); for (; i < 16; i++) strcat(line, " "); strcat(line, " "); return line; } void DecompilePrintStatement(dstatement_t * s) { int i; printf("%4i : %s ", (int)(s - statements), pr_opcodes[s->op].opname); i = strlen(pr_opcodes[s->op].opname); for (; i < 10; i++) printf(" "); if (s->op == OP_IF_I || s->op == OP_IFNOT_I || s->op == OP_IF_F || s->op == OP_IFNOT_F || s->op == OP_IF_S || s->op == OP_IFNOT_S) printf("%sbranch %i", DecompileGlobalString(s->a), s->b); else if (s->op == OP_GOTO) { printf("branch %i", s->a); } else if ((unsigned)(s->op - OP_STORE_F) < 6) { printf("%s", DecompileGlobalString(s->a)); printf("%s", DecompileGlobalStringNoContents(s->b)); } else { if (s->a) printf("%s", DecompileGlobalString(s->a)); if (s->b) printf("%s", DecompileGlobalString(s->b)); if (s->c) printf("%s", DecompileGlobalStringNoContents(s->c)); } printf("\n"); } void DecompilePrintFunction(char *name) { int i; dstatement_t *ds; QCD_function_t *df; for (i = 0; i < numfunctions; i++) if (!strcmp(name, GetNameString(functions[i].s_name))) break; if (i == numfunctions) { printf("Fatal Error: No function names \"%s\"\n", name); exit(1); } df = functions + i; printf("Statements for %s:\n", name); ds = statements + df->first_statement; while (1) { DecompilePrintStatement(ds); if (!ds->op) break; ds++; } } pbool qcc_vfiles_changed; vfile_t *qcc_vfiles; void QCC_CloseAllVFiles(void) { vfile_t *f; while(qcc_vfiles) { f = qcc_vfiles; qcc_vfiles = f->next; free(f->file); free(f); } qcc_vfiles_changed = false; } vfile_t *QCC_FindVFile(const char *name) { vfile_t *f; for (f = qcc_vfiles; f; f = f->next) { if (!strcmp(f->filename, name)) return f; } //give it another go, for case for (f = qcc_vfiles; f; f = f->next) { if (!QC_strcasecmp(f->filename, name)) return f; } return NULL; } vfile_t *QCC_AddVFile(const char *name, void *data, size_t size) { vfile_t *f = QCC_FindVFile(name); if (!f) { f = malloc(sizeof(vfile_t) + strlen(name)); f->next = qcc_vfiles; strcpy(f->filename, name); qcc_vfiles = f; } else free(f->file); f->file = malloc(size); f->type = FT_CODE; memcpy(f->file, data, size); f->size = f->bufsize = size; qcc_vfiles_changed = true; return f; } void QCC_CatVFile(vfile_t *f, const char *fmt, ...) { va_list argptr; char msg[65536]; size_t n; va_start (argptr,fmt); QC_vsnprintf (msg,sizeof(msg)-1, fmt, argptr); va_end (argptr); n = strlen(msg); if (f->size+n > f->bufsize) { size_t msize = f->bufsize + n + 8192; f->file = realloc(f->file, msize); f->bufsize = msize; } memcpy((char*)f->file+f->size, msg, n); f->size += n; } void QCC_InsertVFile(vfile_t *f, size_t pos, const char *fmt, ...) { va_list argptr; char msg[65536]; size_t n; va_start (argptr,fmt); QC_vsnprintf (msg,sizeof(msg)-1, fmt, argptr); va_end (argptr); n = strlen(msg); if (f->size+n > f->bufsize) { size_t msize = f->bufsize + n + 8192; f->file = realloc(f->file, msize); f->bufsize = msize; } memmove((char*)f->file+pos+n, (char*)f->file+pos, f->size-pos); f->size += n; memcpy((char*)f->file+pos, msg, n); } fteqcc-20251105/./qcdecomp.c0000644000200200001440000006545315233070110014700 0ustar twolifeusers#if !defined(MINIMAL) && !defined(OMIT_QCC) //decompiling a progs should normally be done by walking the function table and emitting each def leading up to the one that refers to the function in question. //this of course assumes strict ordering //#include "qcc.h" #include "progsint.h" #include "setjmp.h" #define MAX_PARMS 8 // I put the following here to resolve "undefined reference to `__imp__vsnprintf'" with MinGW64 ~ Moodles #if 0//def _WIN32 #if (_MSC_VER >= 1400) //with MSVC 8, use MS extensions #define snprintf linuxlike_snprintf_vc8 int VARGS linuxlike_snprintf_vc8(char *buffer, int size, const char *format, ...) LIKEPRINTF(3); #define vsnprintf(a, b, c, d) vsnprintf_s(a, b, _TRUNCATE, c, d) #else //msvc crap #define snprintf linuxlike_snprintf int VARGS linuxlike_snprintf(char *buffer, int size, const char *format, ...) LIKEPRINTF(3); #define vsnprintf linuxlike_vsnprintf int VARGS linuxlike_vsnprintf(char *buffer, int size, const char *format, va_list argptr); #endif #endif typedef struct QCC_type_s { etype_t type; struct QCC_type_s *next; // function types are more complex struct QCC_type_s *aux_type; // return type or field type int num_parms; // -1 = variable args // struct QCC_type_s *parm_types[MAX_PARMS]; // only [num_parms] allocated int ofs; //inside a structure. int size; char *name; } QCC_type_t; extern QCC_type_t *qcc_typeinfo; extern int numtypeinfos; extern int maxtypeinfos; extern QCC_type_t *type_void;// = {ev_void/*, &def_void*/}; extern QCC_type_t *type_string;// = {ev_string/*, &def_string*/}; extern QCC_type_t *type_float;// = {ev_float/*, &def_float*/}; extern QCC_type_t *type_vector;// = {ev_vector/*, &def_vector*/}; extern QCC_type_t *type_entity;// = {ev_entity/*, &def_entity*/}; extern QCC_type_t *type_field;// = {ev_field/*, &def_field*/}; extern QCC_type_t *type_function;// = {ev_function/*, &def_function*/,NULL,&type_void}; // type_function is a void() function used for state defs extern QCC_type_t *type_pointer;// = {ev_pointer/*, &def_pointer*/}; extern QCC_type_t *type_integer;// = {ev_integer/*, &def_integer*/}; extern QCC_type_t *type_floatpointer; extern QCC_type_t *type_intpointer; extern QCC_type_t *type_floatfield;// = {ev_field/*, &def_field*/, NULL, &type_float}; QCC_type_t *QCC_PR_NewType (char *name, int basictype, pbool typedefed); #if 0 jmp_buf decompilestatementfailure; QCC_type_t **ofstype; qbyte *ofsflags; int SafeOpenWrite (char *filename, int maxsize); void SafeWrite(int hand, void *buf, long count); int SafeSeek(int hand, int ofs, int mode); void SafeClose(int hand); void VARGS writes(int hand, char *msg, ...) { va_list va; char buf[4192]; va_start(va, msg); Q_vsnprintf (buf,sizeof(buf)-1, msg, va); va_end(va); SafeWrite(hand, buf, strlen(buf)); }; ddef16_t *ED_GlobalAtOfs16 (progfuncs_t *progfuncs, int ofs); char *VarAtOfs(progfuncs_t *progfuncs, int ofs) { static char buf [4192]; ddef16_t *def; int typen; if (ofsflags[ofs]&8) def = ED_GlobalAtOfs16(progfuncs, ofs); else def = NULL; if (!def) { if (ofsflags[ofs]&3) { if (ofstype[ofs]) sprintf(buf, "_v_%s_%i", ofstype[ofs]->name, ofs); else sprintf(buf, "_v_%i", ofs); } else { if (ofstype[ofs]) { typen = ofstype[ofs]->type; goto evaluateimmediate; } else sprintf(buf, "_c_%i", ofs); } return buf; } if (!def->s_name[progfuncs->funcs.stringtable] || !strcmp(progfuncs->funcs.stringtable+def->s_name, "IMMEDIATE")) { if (current_progstate->types) typen = current_progstate->types[def->type & ~DEF_SHARED].type; else typen = def->type & ~(DEF_SHARED|DEF_SAVEGLOBAL); evaluateimmediate: // return PR_UglyValueString(def->type, (eval_t *)¤t_progstate->globals[def->ofs]); switch(typen) { case ev_float: sprintf(buf, "%f", G_FLOAT(ofs)); return buf; case ev_vector: sprintf(buf, "\'%f %f %f\'", G_FLOAT(ofs), G_FLOAT(ofs+1), G_FLOAT(ofs+2)); return buf; case ev_string: { char *s, *s2; s = buf; *s++ = '\"'; s2 = pr_strings+G_INT(ofs); if (s2) while(*s2) { if (*s2 == '\n') { *s++ = '\\'; *s++ = 'n'; s2++; } else if (*s2 == '\"') { *s++ = '\\'; *s++ = '\"'; s2++; } else if (*s2 == '\t') { *s++ = '\\'; *s++ = 't'; s2++; } else *s++=*s2++; } *s++ = '\"'; *s++ = '\0'; } return buf; case ev_pointer: sprintf(buf, "_c_pointer_%i", ofs); return buf; default: sprintf(buf, "_c_%i", ofs); return buf; } } return def->s_name+progfuncs->funcs.stringtable; } int file; int ImmediateReadLater(progfuncs_t *progfuncs, progstate_t *progs, unsigned int ofs, int firstst) { dstatement16_t *st; if (ofsflags[ofs] & 8) return false; //this is a global/local/pramater, not a temp if (!(ofsflags[ofs] & 3)) return false; //this is a constant. for (st = &((dstatement16_t*)progs->statements)[firstst]; ; st++,firstst++) { //if written, return false, if read, return true. if (st->op >= OP_CALL0 && st->op <= OP_CALL8) { if (ofs == OFS_RETURN) return false; if (ofs < OFS_PARM0 + 3*((unsigned int)st->op - OP_CALL0)) return true; } else if (pr_opcodes[st->op].associative == ASSOC_RIGHT) { if (ofs == st->b) return false; if (ofs == st->a) return true; } else { if (st->a == ofs) return true; if (st->b == ofs) return true; if (st->c == ofs) return false; } if (st->op == OP_DONE || st->op == OP_RETURN) //we missed our chance. (return/done ends any code coherancy). return false; } return false; } int ProductReadLater(progfuncs_t *progfuncs, progstate_t *progs, int stnum) { dstatement16_t *st; st = &((dstatement16_t*)progs->statements)[stnum]; if (pr_opcodes[st->op].priority == -1) { if (st->op >= OP_CALL0 && st->op <= OP_CALL7) return ImmediateReadLater(progfuncs, progs, OFS_RETURN, stnum+1); return false;//these don't have products... } if (pr_opcodes[st->op].associative == ASSOC_RIGHT) return ImmediateReadLater(progfuncs, progs, st->b, stnum+1); else return ImmediateReadLater(progfuncs, progs, st->c, stnum+1); } void WriteStatementProducingOfs(progfuncs_t *progfuncs, progstate_t *progs, int lastnum, int firstpossible, int ofs) //recursive, works backwards { int i; dstatement16_t *st; ddef16_t *def; if (ofs == 0) longjmp(decompilestatementfailure, 1); for (; lastnum >= firstpossible; lastnum--) { st = &((dstatement16_t*)progs->statements)[lastnum]; if (st->op >= OP_CALL0 && st->op < OP_CALL7) { if (ofs != OFS_RETURN) continue; WriteStatementProducingOfs(progfuncs, progs, lastnum-1, firstpossible, st->a); writes(file, "("); for (i = 0; i < st->op - OP_CALL0; i++) { WriteStatementProducingOfs(progfuncs, progs, lastnum-1, firstpossible, OFS_PARM0 + i*3); if (i != st->op - OP_CALL0-1) writes(file, ", "); } writes(file, ")"); return; } else if (pr_opcodes[st->op].associative == ASSOC_RIGHT) { if (st->b != ofs) continue; if (!ImmediateReadLater(progfuncs, progs, st->b, lastnum+1)) { writes(file, "("); WriteStatementProducingOfs(progfuncs, progs, lastnum-1, firstpossible, st->b); writes(file, " "); writes(file, pr_opcodes[st->op].name); writes(file, " "); WriteStatementProducingOfs(progfuncs, progs, lastnum-1, firstpossible, st->a); writes(file, ")"); return; } WriteStatementProducingOfs(progfuncs, progs, lastnum-1, firstpossible, st->a); return; } else { if (st->c != ofs) continue; if (!ImmediateReadLater(progfuncs, progs, st->c, lastnum+1)) { WriteStatementProducingOfs(progfuncs, progs, lastnum-1, firstpossible, st->c); writes(file, " = "); } writes(file, "("); WriteStatementProducingOfs(progfuncs, progs, lastnum-1, firstpossible, st->a); if (!strcmp(pr_opcodes[st->op].name, ".")) writes(file, pr_opcodes[st->op].name); //extra spaces around .s are ugly. else { writes(file, " "); writes(file, pr_opcodes[st->op].name); writes(file, " "); } WriteStatementProducingOfs(progfuncs, progs, lastnum-1, firstpossible, st->b); writes(file, ")"); return; } } def = ED_GlobalAtOfs16(progfuncs, ofs); if (def) { if (!strcmp(def->s_name+progfuncs->funcs.stringtable, "IMMEDIATE")) writes(file, "%s", VarAtOfs(progfuncs, ofs)); else writes(file, "%s", progfuncs->funcs.stringtable+def->s_name); } else writes(file, "%s", VarAtOfs(progfuncs, ofs)); // longjmp(decompilestatementfailure, 1); } int WriteStatement(progfuncs_t *progfuncs, progstate_t *progs, int stnum, int firstpossible) { int count, skip; dstatement16_t *st; st = &((dstatement16_t*)progs->statements)[stnum]; switch(st->op) { case OP_IFNOT_I: count = (signed short)st->b; writes(file, "if ("); WriteStatementProducingOfs(progfuncs, progs, stnum, firstpossible, st->a); writes(file, ")\r\n"); writes(file, "{\r\n"); firstpossible = stnum+1; count--; stnum++; while(count) { if (ProductReadLater(progfuncs, progs, stnum)) { count--; stnum++; continue; } skip = WriteStatement(progfuncs, progs, stnum, firstpossible); count-=skip; stnum+=skip; } writes(file, "}\r\n"); st = &((dstatement16_t*)progs->statements)[stnum]; if (st->op == OP_GOTO) { count = (signed short)st->b; count--; stnum++; writes(file, "else\r\n"); writes(file, "{\r\n"); while(count) { if (ProductReadLater(progfuncs, progs, stnum)) { count--; stnum++; continue; } skip = WriteStatement(progfuncs, progs, stnum, firstpossible); count-=skip; stnum+=skip; } writes(file, "}\r\n"); } break; case OP_IF_I: longjmp(decompilestatementfailure, 1); break; case OP_GOTO: longjmp(decompilestatementfailure, 1); break; case OP_RETURN: case OP_DONE: if (st->a) WriteStatementProducingOfs(progfuncs, progs, stnum-1, firstpossible, st->a); break; case OP_CALL0: case OP_CALL1: case OP_CALL2: case OP_CALL3: case OP_CALL4: case OP_CALL5: case OP_CALL6: case OP_CALL7: WriteStatementProducingOfs(progfuncs, progs, stnum, firstpossible, OFS_RETURN); writes(file, ";\r\n"); break; default: if (pr_opcodes[st->op].associative == ASSOC_RIGHT) WriteStatementProducingOfs(progfuncs, progs, stnum, firstpossible, st->b); else WriteStatementProducingOfs(progfuncs, progs, stnum, firstpossible, st->c); writes(file, ";\r\n"); break; } return 1; } void WriteAsmStatements(progfuncs_t *progfuncs, progstate_t *progs, int num, int f, char *functionname) { int stn = progs->functions[num].first_statement; QCC_opcode_t *op; dstatement16_t *st = NULL; eval_t *v; ddef16_t *def; int ofs,i; if (!functionname && stn<0) { //we wrote this one... return; } if (stn>=0) { for (stn = progs->functions[num].first_statement; stn < (signed int)pr_progs->numstatements; stn++) { st = &((dstatement16_t*)progs->statements)[stn]; if (st->op == OP_DONE || st->op == OP_RETURN) { if (!st->a) writes(f, "void("); else if (ofstype[st->a]) { writes(f, "%s", ofstype[st->a]->name); writes(f, "("); } else writes(f, "function("); break; } } st=NULL; stn = progs->functions[num].first_statement; } else writes(f, "function("); for (ofs = progs->functions[num].parm_start, i = 0; i < progs->functions[num].numparms; i++, ofs+=progs->functions[num].parm_size[i]) { ofsflags[ofs] |= 4; def = ED_GlobalAtOfs16(progfuncs, ofs); if (def && stn>=0) { if (st) writes(f, ", "); st = (void *)0xffff; if (!def->s_name[progfuncs->funcs.stringtable]) { char mem[64]; sprintf(mem, "_p_%i", def->ofs); def->s_name = (char*)malloc(strlen(mem)+1)-progfuncs->funcs.stringtable; strcpy(def->s_name+progfuncs->funcs.stringtable, mem); } if (current_progstate->types) writes(f, "%s %s", current_progstate->types[def->type&~(DEF_SHARED|DEF_SAVEGLOBAL)].name, def->s_name); else switch(def->type&~(DEF_SHARED|DEF_SAVEGLOBAL)) { case ev_string: writes(f, "%s %s", "string", progfuncs->funcs.stringtable+def->s_name); break; case ev_float: writes(f, "%s %s", "float", progfuncs->funcs.stringtable+def->s_name); break; case ev_entity: writes(f, "%s %s", "entity", progfuncs->funcs.stringtable+def->s_name); break; case ev_vector: writes(f, "%s %s", "vector", progfuncs->funcs.stringtable+def->s_name); break; default: writes(f, "%s %s", "randomtype", progfuncs->funcs.stringtable+def->s_name); break; } } } for (ofs = progs->functions[num].parm_start+progs->functions[num].numparms, i = progs->functions[num].numparms; i < progs->functions[num].locals; i++, ofs+=1) ofsflags[ofs] |= 4; if (!progfuncs->funcs.stringtable[progs->functions[num].s_name]) { char mem[64]; if (!functionname) { sprintf(mem, "_bi_%i", num); progs->functions[num].s_name = (char*)malloc(strlen(mem)+1)-progfuncs->funcs.stringtable; strcpy(progs->functions[num].s_name+progfuncs->funcs.stringtable, mem); } else { progs->functions[num].s_name = (char*)malloc(strlen(functionname)+1)-progfuncs->funcs.stringtable; strcpy(progs->functions[num].s_name+progfuncs->funcs.stringtable, functionname); } } writes(f, ") %s", progfuncs->funcs.stringtable+progs->functions[num].s_name); if (stn < 0) { stn*=-1; writes(f, " = #%i;\r\n", stn); /* for (ofs = progs->functions[num].parm_start, i = 0; i < progs->functions[num].numparms; i++, ofs+=progs->functions[num].parm_size[i]) { def = ED_GlobalAtOfs16(progfuncs, ofs); if (def) { def->ofs = 0xffff; if (progs->types) { if (progs->types[def->type & ~(DEF_SHARED|DEF_SAVEGLOBAL)].type == ev_vector) { def = ED_GlobalAtOfs16(progfuncs, ofs); def->ofs = 0xffff; def = ED_GlobalAtOfs16(progfuncs, ofs+1); def->ofs = 0xffff; def = ED_GlobalAtOfs16(progfuncs, ofs+2); def->ofs = 0xffff; } } else if ((def->type & (~(DEF_SHARED|DEF_SAVEGLOBAL))) == ev_vector) { def = ED_GlobalAtOfs16(progfuncs, ofs); def->ofs = 0xffff; def = ED_GlobalAtOfs16(progfuncs, ofs+1); def->ofs = 0xffff; def = ED_GlobalAtOfs16(progfuncs, ofs+2); def->ofs = 0xffff; } } } */ return; } if (functionname) //parsing defs { writes(f, ";\r\n"); return; } if (setjmp(decompilestatementfailure)) { writes(f, "*/\r\n"); writes(f, " = asm {\r\n"); stn = progs->functions[num].first_statement; for (ofs = progs->functions[num].parm_start+progs->functions[num].numparms, i = progs->functions[num].numparms; i < progs->functions[num].locals; i++, ofs+=1) { def = ED_GlobalAtOfs16(progfuncs, ofs); if (def) { v = (eval_t *)&((int *)progs->globals)[def->ofs]; if (current_progstate->types) writes(f, "\tlocal %s %s;\r\n", current_progstate->types[def->type&~(DEF_SHARED|DEF_SAVEGLOBAL)].name, def->s_name); else { if (!progfuncs->funcs.stringtable[def->s_name]) { char mem[64]; sprintf(mem, "_l_%i", def->ofs); def->s_name = (char*)malloc(strlen(mem)+1)-progfuncs->funcs.stringtable; strcpy(def->s_name+progfuncs->funcs.stringtable, mem); } switch(def->type&~(DEF_SHARED|DEF_SAVEGLOBAL)) { case ev_string: writes(f, "\tlocal %s %s;\r\n", "string", progfuncs->funcs.stringtable+def->s_name); break; case ev_float: writes(f, "\tlocal %s %s;\r\n", "float", progfuncs->funcs.stringtable+def->s_name); break; case ev_entity: writes(f, "\tlocal %s %s;\r\n", "entity", progfuncs->funcs.stringtable+def->s_name); break; case ev_vector: if (v->_vector[0] || v->_vector[1] || v->_vector[2]) writes(f, "\tlocal vector %s = '%f %f %f';\r\n", progfuncs->funcs.stringtable+def->s_name, v->_vector[0], v->_vector[1], v->_vector[2]); else writes(f, "\tlocal %s %s;\r\n", "vector", progfuncs->funcs.stringtable+def->s_name); ofs+=2; //skip floats; break; default: writes(f, "\tlocal %s %s;\r\n", "randomtype", progfuncs->funcs.stringtable+def->s_name); break; } } } } while(1) { st = &((dstatement16_t*)progs->statements)[stn]; if (!st->op) //end of function statement! break; op = &pr_opcodes[st->op]; writes(f, "\t%s", op->opname); if (op->priority==-1&&op->associative==ASSOC_RIGHT) //last param is a goto { if (op->type_b == &type_void) { if (st->a) writes(f, " %i", (signed short)st->a); } else if (op->type_c == &type_void) { if (st->a) writes(f, " %s", VarAtOfs(progfuncs, st->a)); if (st->b) writes(f, " %i", (signed short)st->b); } else { if (st->a) writes(f, " %s", VarAtOfs(progfuncs, st->a)); if (st->b) writes(f, " %s", VarAtOfs(progfuncs, st->b)); if (st->c) //rightness means it uses a as c writes(f, " %i", (signed short)st->c); } } else { if (st->a) { if (op->type_a == NULL) writes(f, " %i", (signed short)st->a); else writes(f, " %s", VarAtOfs(progfuncs, st->a)); } if (st->b) { if (op->type_b == NULL) writes(f, " %i", (signed short)st->b); else writes(f, " %s", VarAtOfs(progfuncs, st->b)); } if (st->c && op->associative != ASSOC_RIGHT) //rightness means it uses a as c { if (op->type_c == NULL) writes(f, " %i", (signed short)st->c); else writes(f, " %s", VarAtOfs(progfuncs, st->c)); } } writes(f, ";\r\n"); stn++; } } else { if (!strcmp(progfuncs->funcs.stringtable+progs->functions[num].s_name, "SUB_Remove")) file = 0; file = f; writes(f, "/*\r\n"); writes(f, " =\r\n{\r\n"); for (ofs = progs->functions[num].parm_start+progs->functions[num].numparms, i = progs->functions[num].numparms; i < progs->functions[num].locals; i++, ofs+=1) { def = ED_GlobalAtOfs16(progfuncs, ofs); if (def) { v = (eval_t *)&((int *)progs->globals)[def->ofs]; if (current_progstate->types) writes(f, "\tlocal %s %s;\r\n", current_progstate->types[def->type&~(DEF_SHARED|DEF_SAVEGLOBAL)].name, def->s_name); else { if (!def->s_name[progfuncs->funcs.stringtable]) { char mem[64]; sprintf(mem, "_l_%i", def->ofs); def->s_name = (char*)malloc(strlen(mem)+1)-progfuncs->funcs.stringtable; strcpy(def->s_name+progfuncs->funcs.stringtable, mem); } switch(def->type&~(DEF_SHARED|DEF_SAVEGLOBAL)) { case ev_string: writes(f, "\tlocal %s %s;\r\n", "string", progfuncs->funcs.stringtable+def->s_name); break; case ev_float: writes(f, "\tlocal %s %s;\r\n", "float", progfuncs->funcs.stringtable+def->s_name); break; case ev_entity: writes(f, "\tlocal %s %s;\r\n", "entity", progfuncs->funcs.stringtable+def->s_name); break; case ev_vector: if (v->_vector[0] || v->_vector[1] || v->_vector[2]) writes(f, "\tlocal vector %s = '%f %f %f';\r\n", progfuncs->funcs.stringtable+def->s_name, v->_vector[0], v->_vector[1], v->_vector[2]); else writes(f, "\tlocal %s %s;\r\n", "vector",progfuncs->funcs.stringtable+def->s_name); ofs+=2; //skip floats; break; default: writes(f, "\tlocal %s %s;\r\n", "randomtype", progfuncs->funcs.stringtable+def->s_name); break; } } } } for (stn = progs->functions[num].first_statement; stn < (signed int)pr_progs->numstatements; stn++) { if (ProductReadLater(progfuncs, progs, stn)) continue; st = &((dstatement16_t*)progs->statements)[stn]; if (!st->op) break; WriteStatement(progfuncs, progs, stn, progs->functions[num].first_statement); } longjmp(decompilestatementfailure, 1); } writes(f, "};\r\n"); } void FigureOutTypes(progfuncs_t *progfuncs) { ddef16_t *def; QCC_opcode_t *op; unsigned int i,p; dstatement16_t *st; int parmofs[8]; ofstype = realloc(ofstype, sizeof(*ofstype)*65535); ofsflags = realloc(ofsflags, sizeof(*ofsflags)*65535); maxtypeinfos=256; qcc_typeinfo = (void *)realloc(qcc_typeinfo, sizeof(QCC_type_t)*maxtypeinfos); numtypeinfos = 0; memset(ofstype, 0, sizeof(*ofstype)*65535); memset(ofsflags, 0, sizeof(*ofsflags)*65535); type_void = QCC_PR_NewType("void", ev_void, true); type_string = QCC_PR_NewType("string", ev_string, true); type_float = QCC_PR_NewType("float", ev_float, true); type_vector = QCC_PR_NewType("vector", ev_vector, true); type_entity = QCC_PR_NewType("entity", ev_entity, true); type_field = QCC_PR_NewType("field", ev_field, false); type_function = QCC_PR_NewType("function", ev_function, false); type_pointer = QCC_PR_NewType("pointer", ev_pointer, false); type_integer = QCC_PR_NewType("integer", ev_integer, true); // type_variant = QCC_PR_NewType("__variant", ev_variant); type_floatfield = QCC_PR_NewType("fieldfloat", ev_field, false); type_floatfield->aux_type = type_float; type_pointer->aux_type = QCC_PR_NewType("pointeraux", ev_float, false); type_function->aux_type = type_void; for (i = 0,st = pr_statements16; i < pr_progs->numstatements; i++,st++) { op = &pr_opcodes[st->op]; if (st->op >= OP_CALL1 && st->op <= OP_CALL8) { for (p = 0; p < (unsigned int)st->op-OP_CALL0; p++) { ofstype[parmofs[p]] = ofstype[OFS_PARM0+p*3]; } } else if (op->associative == ASSOC_RIGHT) { //assignment ofsflags[st->b] |= 1; if (st->b >= OFS_PARM0 && st->b < RESERVED_OFS) parmofs[(st->b-OFS_PARM0)/3] = st->a; // if (st->op != OP_STORE_F || st->b>RESERVED_OFS) //optimising compilers fix the OP_STORE_V, it's the storef that becomes meaningless (this is the only time that we need this sort of info anyway) { if (op->type_c && op->type_c != &type_void) ofstype[st->a] = *op->type_c; if (op->type_b && op->type_b != &type_void) ofstype[st->b] = *op->type_b; } } else if (op->type_c) { ofsflags[st->c] |= 2; if (st->c >= OFS_PARM0 && st->b < RESERVED_OFS) //too complicated parmofs[(st->b-OFS_PARM0)/3] = 0; // if (st->op != OP_STORE_F || st->b>RESERVED_OFS) //optimising compilers fix the OP_STORE_V, it's the storef that becomes meaningless (this is the only time that we need this sort of info anyway) { if (op->type_a && op->type_a != &type_void) ofstype[st->a] = *op->type_a; if (op->type_b && op->type_b != &type_void) ofstype[st->b] = *op->type_b; if (op->type_c && op->type_c != &type_void) ofstype[st->c] = *op->type_c; } } } for (i=0 ; inumglobaldefs ; i++) { def = &pr_globaldefs16[i]; ofsflags[def->ofs] |= 8; switch(def->type) { case ev_float: ofstype[def->ofs] = type_float; break; case ev_string: ofstype[def->ofs] = type_string; break; case ev_vector: ofstype[def->ofs] = type_vector; break; default: break; } } } pbool PDECL QC_Decompile(pubprogfuncs_t *ppf, char *fname) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; extern progfuncs_t *qccprogfuncs; unsigned int i; unsigned int fld=0; eval_t *v; // char *filename; int f, type; progstate_t progs, *op; qccprogfuncs = progfuncs; op=current_progstate; if (!PR_ReallyLoadProgs(progfuncs, fname, &progs, false)) { return false; } f=SafeOpenWrite("qcdtest/defs.qc", 1024*512); writes(f, "//Decompiled code can contain little type info.\r\n"); FigureOutTypes(progfuncs); for (i = 1; i < progs.progs->numglobaldefs; i++) { if (!strcmp(progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name, "IMMEDIATE")) continue; if (ofsflags[pr_globaldefs16[i].ofs] & 4) continue; //this is a local. if (current_progstate->types) type = progs.types[pr_globaldefs16[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL)].type; else type = pr_globaldefs16[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL); v = (eval_t *)&((int *)progs.globals)[pr_globaldefs16[i].ofs]; if (!progfuncs->funcs.stringtable[pr_globaldefs16[i].s_name]) { char mem[64]; if (ofsflags[pr_globaldefs16[i].ofs] & 3) { ofsflags[pr_globaldefs16[i].ofs] &= ~8; continue; //this is a constant... } sprintf(mem, "_g_%i", pr_globaldefs16[i].ofs); pr_globaldefs16[i].s_name = (char*)malloc(strlen(mem)+1)-progfuncs->funcs.stringtable; strcpy(pr_globaldefs16[i].s_name+progfuncs->funcs.stringtable, mem); } switch(type) { case ev_void: writes(f, "void %s;\r\n", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; case ev_string: if (v->string && *(pr_strings+v->_int)) writes(f, "string %s = \"%s\";\r\n", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name, pr_strings+v->_int); else writes(f, "string %s;\r\n", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; case ev_float: if (v->_float) writes(f, "float %s = %f;\r\n", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name, v->_float); else writes(f, "float %s;\r\n", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; case ev_vector: if (v->_vector[0] || v->_vector[1] || v->_vector[2]) writes(f, "vector %s = '%f %f %f';\r\n", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name, v->_vector[0], v->_vector[1], v->_vector[2]); else writes(f, "vector %s;\r\n", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); i+=3;//skip the floats break; case ev_entity: writes(f, "entity %s;\r\n", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; case ev_field: //wierd fld++; if (!v->_int) writes(f, "var "); switch(pr_fielddefs16[fld].type) { case ev_string: writes(f, ".string %s;", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; case ev_float: writes(f, ".float %s;", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; case ev_vector: writes(f, ".float %s;", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; case ev_entity: writes(f, ".float %s;", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; case ev_function: writes(f, ".void() %s;", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; default: writes(f, "field %s;", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; } if (v->_int) writes(f, "/* %i */", v->_int); writes(f, "\r\n"); break; case ev_function: //wierd WriteAsmStatements(progfuncs, &progs, ((int *)progs.globals)[pr_globaldefs16[i].ofs], f, pr_globaldefs16[i].s_name+progfuncs->funcs.stringtable); break; case ev_pointer: writes(f, "pointer %s;\r\n", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; case ev_integer: writes(f, "integer %s;\r\n", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; case ev_union: writes(f, "union %s;\r\n", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; case ev_struct: writes(f, "struct %s;\r\n", progfuncs->funcs.stringtable+pr_globaldefs16[i].s_name); break; default: break; } } for (i = 0; i < progs.progs->numfunctions; i++) { WriteAsmStatements(progfuncs, &progs, i, f, NULL); } SafeClose(f); current_progstate=op; return true; } #endif #endif fteqcc-20251105/./pr_comp.h0000644000200200001440000004153015233070110014537 0ustar twolifeusers// this file is shared by the execution and compiler /*i'm part way through making this work I've given up now that I can't work out a way to load pointers. Setting them should be fine. */ #ifndef __PR_COMP_H__ #define __PR_COMP_H__ #include "progtype.h" /* #ifdef USE_MSVCRT_DEBUG void *BZ_MallocNamed(int size, char *file, int line); void *BZ_ReallocNamed(void *data, int newsize, char *file, int line); void BZ_Free(void *data); #define BZ_Malloc(size) BZ_MallocNamed(size, __FILE__, __LINE__) #define BZ_Realloc(ptr, size) BZ_ReallocNamed(ptr, size, __FILE__, __LINE__) #define malloc BZ_Malloc #define realloc BZ_Realloc #define free BZ_Free #endif */ typedef int dstring_t; #define QCC_string_t dstring_t #if defined(_MSC_VER) && _MSC_VER < 1300 #define prclocks_t unsigned __int64 #define ull2dbl(x) ((double)(__int64)x) #else #define prclocks_t unsigned long long #define ull2dbl(x) ((double)x) #endif //typedef enum {ev_void, ev_string, ev_float, ev_vector, ev_entity, ev_field, ev_function, ev_pointer, ev_integer, ev_struct, ev_union} etype_t; // 0 1 2 3 4 5 6 7 8 9 10 #define OFS_NULL 0 #define OFS_RETURN 1 #define OFS_PARM0 4 // leave 3 ofs for each parm to hold vectors #define OFS_PARM1 7 #define OFS_PARM2 10 #define OFS_PARM3 13 #define OFS_PARM4 16 #define OFS_PARM5 19 #define OFS_PARM6 22 #define OFS_PARM7 25 #define RESERVED_OFS 28 enum qcop_e { OP_DONE, //0 OP_MUL_F, OP_MUL_V, OP_MUL_FV, OP_MUL_VF, OP_DIV_F, OP_ADD_F, OP_ADD_V, OP_SUB_F, OP_SUB_V, OP_EQ_F, //10 OP_EQ_V, OP_EQ_S, OP_EQ_E, OP_EQ_FNC, OP_NE_F, OP_NE_V, OP_NE_S, OP_NE_E, OP_NE_FNC, OP_LE_F, //20 OP_GE_F, OP_LT_F, OP_GT_F, OP_LOAD_F, OP_LOAD_V, OP_LOAD_S, OP_LOAD_ENT, OP_LOAD_FLD, OP_LOAD_FNC, OP_ADDRESS, //30 OP_STORE_F, OP_STORE_V, OP_STORE_S, OP_STORE_ENT, OP_STORE_FLD, OP_STORE_FNC, OP_STOREP_F, OP_STOREP_V, OP_STOREP_S, OP_STOREP_ENT, //40 OP_STOREP_FLD, OP_STOREP_FNC, OP_RETURN, OP_NOT_F, OP_NOT_V, OP_NOT_S, OP_NOT_ENT, OP_NOT_FNC, OP_IF_I, OP_IFNOT_I, //50 OP_CALL0, //careful... hexen2 and q1 have different calling conventions OP_CALL1, //remap hexen2 calls to OP_CALL2H OP_CALL2, OP_CALL3, OP_CALL4, OP_CALL5, OP_CALL6, OP_CALL7, OP_CALL8, OP_STATE, //60 OP_GOTO, OP_AND_F, OP_OR_F, OP_BITAND_F, OP_BITOR_F, //these following ones are Hexen 2 constants. OP_MULSTORE_F, //66 redundant, for h2 compat OP_MULSTORE_VF, //67 redundant, for h2 compat OP_MULSTOREP_F, //68 OP_MULSTOREP_VF,//69 OP_DIVSTORE_F, //70 redundant, for h2 compat OP_DIVSTOREP_F, //71 OP_ADDSTORE_F, //72 redundant, for h2 compat OP_ADDSTORE_V, //73 redundant, for h2 compat OP_ADDSTOREP_F, //74 OP_ADDSTOREP_V, //75 OP_SUBSTORE_F, //76 redundant, for h2 compat OP_SUBSTORE_V, //77 redundant, for h2 compat OP_SUBSTOREP_F, //78 OP_SUBSTOREP_V, //79 OP_FETCH_GBL_F, //80 has built-in bounds check OP_FETCH_GBL_V, //81 has built-in bounds check OP_FETCH_GBL_S, //82 has built-in bounds check OP_FETCH_GBL_E, //83 has built-in bounds check OP_FETCH_GBL_FNC,//84 has built-in bounds check OP_CSTATE, //85 OP_CWSTATE, //86 OP_THINKTIME, //87 shortcut for OPA.nextthink=time+OPB OP_BITSETSTORE_F, //88 redundant, for h2 compat OP_BITSETSTOREP_F, //89 OP_BITCLRSTORE_F, //90 OP_BITCLRSTOREP_F, //91 OP_RAND0, //92 OPC = random() OP_RAND1, //93 OPC = random()*OPA OP_RAND2, //94 OPC = random()*(OPB-OPA)+OPA OP_RANDV0, //95 //3d/box versions of the above. OP_RANDV1, //96 OP_RANDV2, //97 OP_SWITCH_F, //98 switchref=OPA; PC += OPB --- the jump allows the jump table (such as it is) to be inserted after the block. OP_SWITCH_V, //99 OP_SWITCH_S, //100 OP_SWITCH_E, //101 OP_SWITCH_FNC, //102 OP_CASE, //103 if (OPA===switchref) PC += OPB OP_CASERANGE, //104 if (OPA<=switchref&&switchref<=OPB) PC += OPC //the rest are added //mostly they are various different ways of adding two vars with conversions. //hexen2 calling convention (-TH2 requires us to remap OP_CALLX to these on load, -TFTE just uses these directly.) OP_CALL1H, //OFS_PARM0=OPB OP_CALL2H, //OFS_PARM0,1=OPB,OPC OP_CALL3H, //no extra args OP_CALL4H, OP_CALL5H, OP_CALL6H, //110 OP_CALL7H, OP_CALL8H, OP_STORE_I, OP_STORE_IF, //OPB.f = (float)OPA.i (makes more sense when written as a->b) OP_STORE_FI, //OPB.i = (int)OPA.f OP_ADD_I, OP_ADD_FI, //OPC.f = OPA.f + OPB.i OP_ADD_IF, //OPC.f = OPA.i + OPB.f -- redundant... OP_SUB_I, //OPC.i = OPA.i - OPB.i OP_SUB_FI, //120 //OPC.f = OPA.f - OPB.i OP_SUB_IF, //OPC.f = OPA.i - OPB.f OP_CONV_ITOF, //OPC.f=(float)OPA.i -- useful mostly so decompilers don't do weird stuff. OP_CONV_FTOI, //OPC.i=(int)OPA.f OP_LOADP_ITOF, //OPC.f=(float)(*OPA).i -- fixme: rename to LOADP_ITOF OP_LOADP_FTOI, //OPC.i=(int)(*OPA).f OP_LOAD_I, OP_STOREP_I, OP_STOREP_IF, OP_STOREP_FI, OP_BITAND_I, //130 OP_BITOR_I, OP_MUL_I, OP_DIV_I, OP_EQ_I, OP_NE_I, OP_IFNOT_S, //compares string empty, rather than just null. OP_IF_S, OP_NOT_I, OP_DIV_VF, OP_BITXOR_I, //140 OP_RSHIFT_I, OP_LSHIFT_I, OP_GLOBALADDRESS, //C.p = &A + B.i*4 OP_ADD_PIW, //C.p = A.p + B.i*4 OP_LOADA_F, OP_LOADA_V, OP_LOADA_S, OP_LOADA_ENT, OP_LOADA_FLD, OP_LOADA_FNC, //150 OP_LOADA_I, OP_STORE_P, OP_LOAD_P, OP_LOADP_F, OP_LOADP_V, OP_LOADP_S, OP_LOADP_ENT, OP_LOADP_FLD, OP_LOADP_FNC, OP_LOADP_I, //160 OP_LE_I, OP_GE_I, OP_LT_I, OP_GT_I, OP_LE_IF, OP_GE_IF, OP_LT_IF, OP_GT_IF, OP_LE_FI, OP_GE_FI, //170 OP_LT_FI, OP_GT_FI, OP_EQ_IF, OP_EQ_FI, //------------------------------------- //string manipulation. OP_ADD_SF, //(char*)c = (char*)a + (float)b add_fi->i OP_SUB_S, //(float)c = (char*)a - (char*)b sub_ii->f OP_STOREP_C,//(float)c = *(char*)b = (float)a OP_LOADP_C, //(float)c = *(char*) //------------------------------------- OP_MUL_IF, OP_MUL_FI, //180 OP_MUL_VI, OP_MUL_IV, OP_DIV_IF, OP_DIV_FI, OP_BITAND_IF, OP_BITOR_IF, OP_BITAND_FI, OP_BITOR_FI, OP_AND_I, OP_OR_I, //190 OP_AND_IF, OP_OR_IF, OP_AND_FI, OP_OR_FI, OP_NE_IF, OP_NE_FI, //fte doesn't really model two separate pointer types. these are thus special-case things for array access only. OP_GSTOREP_I, OP_GSTOREP_F, OP_GSTOREP_ENT, OP_GSTOREP_FLD, //200 OP_GSTOREP_S, OP_GSTOREP_FNC, OP_GSTOREP_V, OP_GADDRESS, //poorly defined opcode, which makes it too unreliable to actually use. OP_GLOAD_I, OP_GLOAD_F, OP_GLOAD_FLD, OP_GLOAD_ENT, OP_GLOAD_S, OP_GLOAD_FNC, //210 //back to ones that we do use. OP_BOUNDCHECK, OP_UNUSED, //used to be OP_STOREP_P, which is now emulated with OP_STOREP_I, fteqcc nor fte generated it OP_PUSH, //push 4octets onto the local-stack (which is ALWAYS poped on function return). Returns a pointer. OP_POP, //pop those ones that were pushed (don't over do it). Needs assembler. OP_SWITCH_I,//hmm. OP_GLOAD_V, //r3349+ OP_IF_F, //compares as an actual float, instead of treating -0 as positive. OP_IFNOT_F, //r5697+ OP_STOREF_V, //3 elements... OP_STOREF_F, //1 fpu element... OP_STOREF_S, //1 string reference OP_STOREF_I, //1 non-string reference/int //r5744+ OP_STOREP_I8, //((char*)b)[(int)c] = (int)a OP_LOADP_U8, //(int)c = *(unsigned char*) //r5768+ //opcodes for 32bit uints OP_LE_U, //aka GE OP_LT_U, //aka GT OP_DIV_U, //don't need mul+add+sub OP_RSHIFT_U, //lshift is the same for signed+unsigned //opcodes for 64bit ints OP_ADD_I64, OP_SUB_I64, OP_MUL_I64, OP_DIV_I64, OP_BITAND_I64, OP_BITOR_I64, OP_BITXOR_I64, OP_LSHIFT_I64I, OP_RSHIFT_I64I, OP_LE_I64, //aka GE OP_LT_I64, //aka GT OP_EQ_I64, OP_NE_I64, //extra opcodes for 64bit uints OP_LE_U64, //aka GE OP_LT_U64, //aka GT OP_DIV_U64, OP_RSHIFT_U64I, //general 64bitness OP_STORE_I64, OP_STOREP_I64, OP_STOREF_I64, OP_LOAD_I64, OP_LOADA_I64, OP_LOADP_I64, //various conversions for our 64bit types (yay type promotion) OP_CONV_UI64, //zero extend OP_CONV_II64, //sign extend OP_CONV_I64I, //truncate OP_CONV_FD, //extension OP_CONV_DF, //truncation OP_CONV_I64F, //logically a promotion (always signed) OP_CONV_FI64, //demotion (always signed) OP_CONV_I64D, //'promotion' (always signed) OP_CONV_DI64, //demotion (always signed) //opcodes for doubles. OP_ADD_D, OP_SUB_D, OP_MUL_D, OP_DIV_D, OP_LE_D, OP_LT_D, OP_EQ_D, OP_NE_D, //r6614+ OP_STOREP_I16, //((short*)b)[(int)c] = (int)a OP_LOADP_I16, //(int)c = *(signed short*)a (sign extends) OP_LOADP_U16, //(unsigned int)c = *(unsigned short*)a OP_LOADP_I8, //(unsigned int)c = *(signed char*)a (sign extends) OP_BITEXTEND_I, //sign extend (for signed bitfields) OP_BITEXTEND_U, //zero extend (for unsigned bitfields) OP_BITCOPY_I, //copy lower bits from the input to some part of the output OP_CONV_UF, //OPC.f=(float)OPA.i -- 0xffffffffu*0.5=0 otherwise. OP_CONV_FU, //OPC.i=(int)OPA.f OP_CONV_U64D, //OPC.d=(double)OPA.u64 -- useful mostly so decompilers don't do weird stuff. OP_CONV_DU64, //OPC.u64=(uint64_t)OPA.d OP_CONV_U64F, //OPC.f=(float)OPA.u64 -- useful mostly so decompilers don't do weird stuff. OP_CONV_FU64, //OPC.u64=(uint64_t)OPA.f OP_NUMREALOPS, /* These ops are emulated out, always, and are only present in the compiler. */ OP_BITSETSTORE_I, //220 OP_BITSETSTOREP_I, OP_BITCLRSTORE_I, OP_MULSTORE_I, OP_DIVSTORE_I, OP_ADDSTORE_I, OP_SUBSTORE_I, OP_MULSTOREP_I, OP_DIVSTOREP_I, OP_ADDSTOREP_I, OP_SUBSTOREP_I, //230 OP_MULSTORE_IF, OP_MULSTOREP_IF, OP_DIVSTORE_IF, OP_DIVSTOREP_IF, OP_ADDSTORE_IF, OP_ADDSTOREP_IF, OP_SUBSTORE_IF, OP_SUBSTOREP_IF, OP_MULSTORE_FI, OP_MULSTOREP_FI, //240 OP_DIVSTORE_FI, OP_DIVSTOREP_FI, OP_ADDSTORE_FI, OP_ADDSTOREP_FI, OP_SUBSTORE_FI, OP_SUBSTOREP_FI, OP_MULSTORE_VI, OP_MULSTOREP_VI, OP_LOADA_STRUCT, OP_LOADP_P, OP_STOREP_P, OP_BITNOT_F, OP_BITNOT_I, OP_EQ_P, OP_NE_P, OP_LE_P, OP_GE_P, OP_LT_P, OP_GT_P, OP_ANDSTORE_F, OP_BITCLR_F, OP_BITCLR_I, OP_BITCLR_V, OP_ADD_SI, OP_ADD_IS, OP_ADD_PF, OP_ADD_FP, OP_ADD_PI, OP_ADD_IP, OP_ADD_PU, OP_ADD_UP, OP_SUB_SI, OP_SUB_PF, OP_SUB_PI, OP_SUB_PU, OP_SUB_PP, OP_MOD_F, OP_MOD_I, OP_MOD_FI, OP_MOD_IF, OP_MOD_V, OP_BITXOR_F, OP_RSHIFT_F, OP_LSHIFT_F, OP_RSHIFT_IF, OP_LSHIFT_IF, OP_RSHIFT_FI, OP_LSHIFT_FI, OP_AND_ANY, OP_OR_ANY, OP_ADD_EI, OP_ADD_EF, OP_SUB_EI, OP_SUB_EF, OP_BITAND_V, OP_BITOR_V, OP_BITNOT_V, OP_BITXOR_V, OP_POW_F, OP_POW_I, OP_POW_FI, OP_POW_IF, OP_CROSS_V, OP_EQ_FLD, OP_NE_FLD, OP_SPACESHIP_F, //lame OP_SPACESHIP_S, //basically strcmp. //uint32 opcodes. they match the int32 ones so emulation is basically swapping them over. OP_ADD_U, OP_SUB_U, OP_MUL_U, OP_MOD_U, //complex OP_BITAND_U, OP_BITOR_U, OP_BITXOR_U, OP_BITNOT_U, //BITXOR ~0 OP_BITCLR_U, OP_LSHIFT_U, //same as signed (unlike rshift) OP_GE_U, //LT_U OP_GT_U, //LE_U // OP_AND_U, // OP_OR_U, OP_EQ_U, OP_NE_U, //uint64 opcodes. they match the int32 ones so emulation is basically swapping them over. OP_BITNOT_I64, //BITXOR ~0 OP_BITCLR_I64, OP_GE_I64, //LE_I64 OP_GT_I64, //LT_I64 OP_ADD_U64, OP_SUB_U64, OP_MUL_U64, OP_MOD_U64, //complex OP_BITAND_U64, OP_BITOR_U64, OP_BITXOR_U64, OP_BITNOT_U64, //BITXOR ~0 OP_BITCLR_U64, OP_LSHIFT_U64I, OP_GE_U64, //LE_U64 OP_GT_U64, //LT_U64 OP_EQ_U64, OP_NE_U64, //generally implemented by forcing to int64. OP_BITAND_D, OP_BITOR_D, OP_BITXOR_D, OP_BITNOT_D, OP_BITCLR_D, OP_LSHIFT_DI, OP_RSHIFT_DI, OP_GE_D, //LE_D OP_GT_D, //LT_D OP_WSTATE, //for the 'w' part of CWSTATE. will probably never be used, but hey, hexen2... //special/fake opcodes used by the decompiler. OPD_GOTO_FORSTART, OPD_GOTO_WHILE1, OPD_GOTO_BREAK, OPD_GOTO_DEFAULT, OP_NUMOPS, #define OP_BIT_BREAKPOINT 0x8000 }; #define MAX_PARMS 8 // qtest structs (used for reordering and not execution) typedef struct qtest_statement_s { unsigned int line; // line number in source code file unsigned short op; unsigned short a,b,c; } qtest_statement_t; typedef struct qtest_def_s { unsigned int type; // no DEFGLOBAL found in qtest progs unsigned int s_name; // different order! unsigned int ofs; } qtest_def_t; typedef struct qtest_function_s { int first_statement; int unused1; int locals; // assumed! (always 0 in real qtest progs) int profile; // assumed! (always 0 in real qtest progs) int s_name; int s_file; int numparms; int parm_start; // different order int parm_size[MAX_PARMS]; // ints instead of bytes... } qtest_function_t; typedef struct statement16_s { unsigned short op; unsigned short a,b,c; } dstatement16_t; typedef struct statement32_s { unsigned int op; unsigned int a,b,c; } dstatement32_t; #define QCC_dstatement16_t dstatement16_t #define QCC_dstatement32_t dstatement32_t typedef struct { struct QCC_def_s *sym; union { unsigned int ofs; // unsigned int bofs; signed int jumpofs; }; struct QCC_type_s *cast; //the entire sref is considered null if there is no cast, although it *MAY* have an ofs specified if its part of a jump instruction } QCC_sref_t; typedef struct qcc_statement_s { unsigned short op; #define STF_LOGICOP (1u<<0) //do not bother following when looking for uninitialised variables. #define STF_NOFOLD (1u<<1) //do not allow changing its var_c to fold the following store. unsigned short flags; QCC_sref_t a, b, c; unsigned int linenum; } QCC_statement_t; //these should be the same except the string type typedef struct ddef16_s { unsigned short type; // if DEF_SAVEGLOBAL bit is set // the variable needs to be saved in savegames unsigned short ofs; string_t s_name; } ddef16_t; typedef struct ddef32_s { unsigned int type; // if DEF_SAVEGLOBAL bit is set // the variable needs to be saved in savegames unsigned int ofs; string_t s_name; } ddef32_t; typedef void *ddefXX_t; typedef struct QCC_ddef16_s { unsigned short type; // if DEF_SAVEGLOBAL bit is set // the variable needs to be saved in savegames unsigned short ofs; QCC_string_t s_name; } QCC_ddef16_t; typedef struct QCC_ddef32_s { unsigned int type; // if DEF_SAVEGLOBAL bit is set // the variable needs to be saved in savegames unsigned int ofs; QCC_string_t s_name; } QCC_ddef32_t; #define QCC_ddef_t QCC_ddef32_t #define DEF_SAVEGLOBAL (1<<15) #define DEF_SHARED (1<<14) typedef struct { int first_statement; // negative numbers are builtins int parm_start; int locals; // total ints of parms + locals int profile; // runtime string_t s_name; string_t s_file; // source file defined in int numparms; pbyte parm_size[MAX_PARMS]; } dfunction_t; typedef struct { int first_statement; // negative numbers are builtins int parm_start; int locals; // total ints of parms + locals int profile; //number of qc instructions executed. prclocks_t profiletime; //total time inside (cpu cycles) prclocks_t profilechildtime; //time inside children (excluding builtins, cpu cycles) string_t s_name; string_t s_file; // source file defined in int numparms; pbyte parm_size[MAX_PARMS]; } mfunction_t; #define PROG_QTESTVERSION 3 #define PROG_VERSION 6 #define PROG_KKQWSVVERSION 7 #define PROG_EXTENDEDVERSION 7 #define PROG_SECONDARYVERSION16 ((('1'<<0)|('F'<<8)|('T'<<16)|('E'<<24))^(('P'<<0)|('R'<<8)|('O'<<16)|('G'<<24))) //something unlikly and still meaningful (to me) #define PROG_SECONDARYVERSION32 ((('1'<<0)|('F'<<8)|('T'<<16)|('E'<<24))^(('3'<<0)|('2'<<8)|('B'<<16)|(' '<<24))) //something unlikly and still meaningful (to me) #define PROG_SECONDARYUHEXEN2 ((('U'<<0)|('H'<<8)|('2'<<16)|('7'<<24))) //something unlikly and still meaningful (to me) #define PROG_SECONDARYKKQWSV ((('K'<<0)|('K'<<8)|('Q'<<16)|('W'<<24))) //something unlikly and still meaningful (to me) typedef struct { int version; int crc; // check of header file unsigned int ofs_statements; //comp 1 unsigned int numstatements; // statement 0 is an error unsigned int ofs_globaldefs; //comp 2 unsigned int numglobaldefs; unsigned int ofs_fielddefs; //comp 4 unsigned int numfielddefs; unsigned int ofs_functions; //comp 8 unsigned int numfunctions; // function 0 is an empty unsigned int ofs_strings; //comp 16 unsigned int numstrings; // first string is a null string unsigned int ofs_globals; //comp 32 unsigned int numglobals; unsigned int entityfields; //debug / version 7 extensions unsigned int ofsfiles; //non list format. no comp unsigned int ofslinenums; //numstatements big //comp 64 unsigned int ofsbodylessfuncs; //no comp unsigned int numbodylessfuncs; unsigned int ofs_types; //comp 128 unsigned int numtypes; unsigned int blockscompressed; int secondaryversion; //Constant - to say that any version 7 progs are actually ours, not someone else's alterations. } dprograms_t; #define standard_dprograms_t_size ((size_t)&((dprograms_t*)NULL)->ofsfiles) typedef struct { char filename[128]; int size; int compsize; int compmethod; int ofs; } includeddatafile_t; typedef struct typeinfo_s { etype_t type; int next; int aux_type; int num_parms; int ofs; //inside a structure. int size; string_t name; } typeinfo_t; #endif fteqcc-20251105/./LICENSE0000644000200200001440000003504215233070110013735 0ustar twolifeusersGNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS fteqcc-20251105/./pr_edict.c0000644000200200001440000030710315233070110014665 0ustar twolifeusers #define PROGSUSED struct edict_s; #include "progsint.h" //#include "crc.h" #include "qcc.h" #ifdef _WIN32 //this is windows all files are written with this endian standard. we do this to try to get a little more speed. #define NOENDIAN #endif #define qcc_iswhite(c) ((c) == ' ' || (c) == '\r' || (c) == '\n' || (c) == '\t' || (c) == '\v') pbool ED_ParseEpair (progfuncs_t *progfuncs, size_t qcptr, unsigned int fldofs, int fldtype, char *s); /* ================= QC_ClearEdict Sets everything to NULL ================= */ void PDECL QC_ClearEdict (pubprogfuncs_t *ppf, struct edict_s *ed) { // progfuncs_t *progfuncs = (progfuncs_t*)ppf; edictrun_t *e = (edictrun_t *)ed; int num = e->entnum; memset (e->fields, 0, e->fieldsize); e->ereftype = ER_ENTITY; e->entnum = num; } struct edict_s *PDECL ED_AllocIndex (pubprogfuncs_t *ppf, unsigned int num, pbool object, size_t extrasize) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; edictrun_t *e; unsigned int fields_size; if (num >= prinst.maxedicts) { externs->Sys_Error ("ED_AllocIndex: index %u exceeds limit of %u", num, prinst.maxedicts); return NULL; } if (!object) { while(sv_num_edicts < num) { //fill in any holes e = (edictrun_t*)EDICT_NUM(progfuncs, sv_num_edicts); if (!e) { e = (edictrun_t*)ED_AllocIndex(&progfuncs->funcs, sv_num_edicts, object, extrasize); e->ereftype=ER_FREE; } sv_num_edicts++; } if (num >= sv_num_edicts) sv_num_edicts=num+1; } e = prinst.edicttable[num]; if (!e) { e = (void*)externs->memalloc(externs->edictsize); prinst.edicttable[num] = e; memset(e, 0, externs->edictsize); } fields_size = object?0:prinst.fields_size; fields_size += extrasize; if (e->fieldsize != fields_size) { if (e->fields) progfuncs->funcs.AddressableFree(&progfuncs->funcs, e->fields); e->fields = progfuncs->funcs.AddressableAlloc(&progfuncs->funcs, fields_size); if (!e->fields) externs->Sys_Error ("ED_Alloc: Unable to allocate more field space"); e->fieldsize = fields_size; // e->fields = PRAddressableExtend(progfuncs, NULL, fields_size, 0); } e->entnum = num; memset (e->fields, 0, e->fieldsize); e->ereftype = object?ER_OBJECT:ER_ENTITY; if (externs->entspawn) externs->entspawn((struct edict_s *) e, false); return (struct edict_s*)e; } /* ================= ED_Alloc Either finds a free edict, or allocates a new one. Try to avoid reusing an entity that was recently freed, because it can cause the client to think the entity morphed into something else instead of being removed and recreated, which can cause interpolated angles and bad trails. ================= */ struct edict_s *PDECL ED_Alloc (pubprogfuncs_t *ppf, pbool object, size_t extrasize) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; unsigned int i; edictrun_t *e; if (object) { //objects are allocated at the end (won't be networked, so this reduces issues with users on old protocols). //also they're potentially higher than num_edicts, which is handy. for ( i=prinst.maxedicts-1 ; i>0 ; i--) { e = (edictrun_t*)EDICT_NUM(progfuncs, i); // the first couple seconds of server time can involve a lot of // freeing and allocating, so relax the replacement policy if (!e || (e->ereftype==ER_FREE && ( e->freetime < 2 || *externs->gametime - e->freetime > 0.5 ) )) return ED_AllocIndex(&progfuncs->funcs, i, object, extrasize); } externs->Sys_Error ("ED_Alloc: no free edicts (max is %i)", prinst.maxedicts); } //define this to wastefully allocate extra ents, to test network capabilities. #define STEP 1//((i <= 32)?1:8) for ( i=0 ; iereftype==ER_FREE && ( e->freetime < 2 || *externs->gametime - e->freetime > 0.5 ) )) return ED_AllocIndex(&progfuncs->funcs, i, object, extrasize); } if (i >= prinst.maxedicts-1) //try again, but use timed out ents. { for ( i=0 ; iereftype==ER_FREE)) return ED_AllocIndex(&progfuncs->funcs, i, object, extrasize); } if (i >= prinst.maxedicts-2) { PR_RunWarning(&progfuncs->funcs, "Running out of edicts\n"); } if (i >= prinst.maxedicts-1) { size_t size; char *buf; buf = PR_SaveEnts(&progfuncs->funcs, NULL, &size, 0, 0); progfuncs->funcs.parms->WriteFile("edalloc.dump", buf, size); externs->Sys_Error ("ED_Alloc: no free edicts (max is %i)", prinst.maxedicts); } } return ED_AllocIndex(&progfuncs->funcs, i, object, extrasize); } /* ================= ED_Free Marks the edict as free FIXME: walk all entities and NULL out references to this entity ================= */ void PDECL ED_Free (pubprogfuncs_t *ppf, struct edict_s *ed, pbool instant) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; edictrun_t *e = (edictrun_t *)ed; // SV_UnlinkEdict (ed); // unlink from world bsp if (e->ereftype == ER_FREE) //this happens on start.bsp where an onlyregistered trigger killtargets itself (when all of this sort die after 1 trigger anyway). { if (prinst.pr_depth) externs->Printf("Tried to free free entity within %s\n", prinst.pr_xfunction->s_name+progfuncs->funcs.stringtable); else externs->Printf("Engine tried to free free entity\n"); // if (developer.value == 1) // progfuncs->funcs.pr_trace = true; return; } if (externs->entcanfree) if (!externs->entcanfree(ed)) //can stop an ent from being freed. return; e->ereftype = ER_FREE; e->freetime = instant?0:(float)*externs->gametime; /* ed->v.model = 0; ed->v.takedamage = 0; ed->v.modelindex = 0; ed->v.colormap = 0; ed->v.skin = 0; ed->v.frame = 0; VectorCopy (vec3_origin, ed->v.origin); VectorCopy (vec3_origin, ed->v.angles); ed->v.nextthink = -1; ed->v.solid = 0; */ } //=========================================================================== /* ============ ED_GlobalAtOfs ============ */ ddef16_t *ED_GlobalAtOfs16 (progfuncs_t *progfuncs, int ofs) { ddef16_t *def; unsigned int i; for (i=0 ; inumglobaldefs ; i++) { def = &pr_globaldefs16[i]; if (def->ofs == ofs) return def; } return NULL; } ddef32_t *ED_GlobalAtOfs32 (progfuncs_t *progfuncs, unsigned int ofs) { ddef32_t *def; unsigned int i; for (i=0 ; inumglobaldefs ; i++) { def = &pr_globaldefs32[i]; if (def->ofs == ofs) return def; } return NULL; } /* ============ ED_FieldAtOfs ============ */ fdef_t *ED_FieldAtOfs (progfuncs_t *progfuncs, unsigned int ofs) { // ddef_t *def; unsigned int i; for (i=0 ; inumglobaldefs ; i++) { def = &pr_globaldefs16[i]; if (!strcmp(def->s_name+progfuncs->funcs.stringtable,name) ) return def; } return NULL; } ddef32_t *ED_FindGlobal32 (progfuncs_t *progfuncs, const char *name) { ddef32_t *def; unsigned int i; for (i=1 ; inumglobaldefs ; i++) { def = &pr_globaldefs32[i]; if (!strcmp(def->s_name+progfuncs->funcs.stringtable,name) ) return def; } return NULL; } unsigned int ED_FindGlobalOfs (progfuncs_t *progfuncs, char *name) { ddef16_t *d16; ddef32_t *d32; switch(current_progstate->structtype) { case PST_KKQWSV: case PST_DEFAULT: d16 = ED_FindGlobal16(progfuncs, name); return d16?d16->ofs:0; case PST_QTEST: case PST_FTE32: case PST_UHEXEN2: d32 = ED_FindGlobal32(progfuncs, name); return d32?d32->ofs:0; default: externs->Sys_Error("ED_FindGlobalOfs - bad struct type"); } return 0; } ddef16_t *ED_FindGlobalFromProgs16 (progfuncs_t *progfuncs, progstate_t *ps, const char *name) { ddef16_t *def; unsigned int i; for (i=1 ; iprogs->numglobaldefs ; i++) { def = &ps->globaldefs16[i]; if (!strcmp(def->s_name+progfuncs->funcs.stringtable,name) ) return def; } return NULL; } ddef32_t *ED_FindGlobalFromProgs32 (progfuncs_t *progfuncs, progstate_t *ps, const char *name) { ddef32_t *def; unsigned int i; for (i=1 ; iprogs->numglobaldefs ; i++) { def = &ps->globaldefs32[i]; if (!strcmp(def->s_name+progfuncs->funcs.stringtable,name) ) return def; } return NULL; } ddef16_t *ED_FindTypeGlobalFromProgs16 (progfuncs_t *progfuncs, progstate_t *ps, const char *name, int type) { ddef16_t *def; unsigned int i; for (i=1 ; iprogs->numglobaldefs ; i++) { def = &ps->globaldefs16[i]; if (!strcmp(def->s_name+progfuncs->funcs.stringtable,name) ) { if (ps->types) { if (ps->types[def->type&~DEF_SAVEGLOBAL].type != type) continue; } else if ((def->type&(~DEF_SAVEGLOBAL)) != type) continue; return def; } } return NULL; } ddef32_t *ED_FindTypeGlobalFromProgs32 (progfuncs_t *progfuncs, progstate_t *ps, const char *name, int type) { ddef32_t *def; unsigned int i; for (i=1 ; iprogs->numglobaldefs ; i++) { def = &ps->globaldefs32[i]; if (!strcmp(def->s_name+progfuncs->funcs.stringtable,name) ) { if (ps->types) { if (ps->types[def->type&~DEF_SAVEGLOBAL].type != type) continue; } else if ((def->type&(~DEF_SAVEGLOBAL)) != (unsigned)type) continue; return def; } } return NULL; } unsigned int *ED_FindGlobalOfsFromProgs (progfuncs_t *progfuncs, progstate_t *ps, char *name, int type) { ddef16_t *def16; ddef32_t *def32; static unsigned int pos; switch(ps->structtype) { case PST_DEFAULT: case PST_KKQWSV: def16 = ED_FindTypeGlobalFromProgs16(progfuncs, ps, name, type); if (!def16) return NULL; pos = def16->ofs; return &pos; case PST_QTEST: case PST_FTE32: case PST_UHEXEN2: def32 = ED_FindTypeGlobalFromProgs32(progfuncs, ps, name, type); if (!def32) return NULL; return &def32->ofs; default: externs->Sys_Error("ED_FindGlobalOfsFromProgs - bad struct type"); } return 0; } /* ============ ED_FindFunction ============ */ mfunction_t *ED_FindFunction (progfuncs_t *progfuncs, const char *name, progsnum_t *prnum, progsnum_t fromprogs) { mfunction_t *func; unsigned int i; char *sep; progsnum_t pnum; if (prnum) { sep = strchr(name, ':'); if (sep) { pnum = atoi(name); name = sep+1; } else { if (fromprogs>=0) pnum = fromprogs; else pnum = prinst.pr_typecurrent; } *prnum = pnum; } else pnum = prinst.pr_typecurrent; if ((unsigned)pnum > (unsigned)prinst.maxprogs) { externs->Printf("Progsnum %"pPRIi" out of bounds\n", pnum); return NULL; } if (!pr_progstate[pnum].progs) return NULL; for (i=1 ; inumfunctions ; i++) { func = &pr_progstate[pnum].functions[i]; if (!strcmp(func->s_name+progfuncs->funcs.stringtable,name) ) return func; } return NULL; } /* ============ PR_ValueString Returns a string describing *data in a human-readable type specific manner if verbose, contains entity field listing etc too ============= */ char *PR_ValueString (progfuncs_t *progfuncs, etype_t type, eval_t *val, pbool verbose) { static char line[4096]; fdef_t *fielddef; mfunction_t *f; #ifdef DEF_SAVEGLOBAL type &= ~DEF_SAVEGLOBAL; #endif if (current_progstate && pr_types) type = pr_types[type].type; if (!val) type = ev_void; switch (type) { case ev_struct: QC_snprintfz (line, sizeof(line), "struct"); break; case ev_union: QC_snprintfz (line, sizeof(line), "union"); break; case ev_string: #ifndef QCGC if (((unsigned int)val->string & STRING_SPECMASK) == STRING_TEMP) return ""; else #endif QC_snprintfz (line, sizeof(line), "%s", PR_StringToNative(&progfuncs->funcs, val->string)); break; case ev_entity: fielddef = ED_FindField(progfuncs, "classname"); if (fielddef && (unsigned)val->edict < (unsigned)sv_num_edicts) { edictrun_t *ed; string_t *v; ed = (edictrun_t *)EDICT_NUM(progfuncs, val->edict); v = (string_t *)((char *)edvars(ed) + fielddef->ofs*4); QC_snprintfz (line, sizeof(line), "entity %i(%s)", val->edict, PR_StringToNative(&progfuncs->funcs, *v)); } else QC_snprintfz (line, sizeof(line), "entity %i", val->edict); if (verbose && (unsigned)val->edict < (unsigned)sv_num_edicts) { struct edict_s *ed = EDICT_NUM(progfuncs, val->edict); size_t size = strlen(line); if (ed) PR_SaveEnt(&progfuncs->funcs, line, &size, sizeof(line), ed); } break; case ev_function: if (!val->function) QC_snprintfz (line, sizeof(line), "NULL function"); else { if ((val->function & 0xff000000)>>24 >= prinst.maxprogs || !pr_progstate[(val->function & 0xff000000)>>24].functions) QC_snprintfz (line, sizeof(line), "Bad function %"pPRIi":%"pPRIi"", (val->function & 0xff000000)>>24, val->function & ~0xff000000); else { if ((val->function &~0xff000000) >= pr_progs->numfunctions) QC_snprintfz (line, sizeof(line), "bad function %"pPRIi":%"pPRIi"\n", (val->function & 0xff000000)>>24, val->function & ~0xff000000); else { f = pr_progstate[(val->function & 0xff000000)>>24].functions + (val->function & ~0xff000000); QC_snprintfz (line, sizeof(line), "%"pPRIi":%s()", (val->function & 0xff000000)>>24, f->s_name+progfuncs->funcs.stringtable); } } } break; case ev_field: fielddef = ED_FieldAtOfs (progfuncs, val->_int + progfuncs->funcs.fieldadjust); if (!fielddef) QC_snprintfz (line, sizeof(line), ".??? (#%i)", val->_int); else QC_snprintfz (line, sizeof(line), ".%s (#%i)", fielddef->name, val->_int); break; case ev_void: QC_snprintfz (line, sizeof(line), "void type"); break; case ev_float: QC_snprintfz (line, sizeof(line), "%g", val->_float); break; case ev_double: QC_snprintfz (line, sizeof(line), "%g", val->_double); break; case ev_integer: QC_snprintfz (line, sizeof(line), "%"pPRIi, val->_int); break; case ev_uint: QC_snprintfz (line, sizeof(line), "%"pPRIu, val->_uint); break; case ev_int64: QC_snprintfz (line, sizeof(line), "%"pPRIi64, val->i64); break; case ev_uint64: QC_snprintfz (line, sizeof(line), "%"pPRIu64, val->u64); break; case ev_vector: QC_snprintfz (line, sizeof(line), "'%g %g %g'", val->_vector[0], val->_vector[1], val->_vector[2]); break; case ev_pointer: QC_snprintfz (line, sizeof(line), "%#x", val->_int); { // int entnum; // int valofs; //FIXME: :/ // entnum = ((qbyte *)val->edict - (qbyte *)sv_edicts) / pr_edict_size; // valofs = (int *)val->edict - (int *)edvars(EDICT_NUM(progfuncs, entnum)); // fielddef = ED_FieldAtOfs (progfuncs, valofs ); // if (fielddef) // sprintf(line, "ent%i.%s", entnum, fielddef->s_name); } break; case ev_accessor: QC_snprintfz (line, sizeof(line), "(accessor)"); break; default: QC_snprintfz (line, sizeof(line), "(bad type %i)", type); break; } return line; } /* ============ PR_UglyValueString Returns a string describing *data in a type specific manner Easier to parse than PR_ValueString ============= */ char *PDECL PR_UglyValueString (pubprogfuncs_t *ppf, etype_t type, eval_t *val) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; static char line[4096]; fdef_t *fielddef; mfunction_t *f; int i, j; #ifdef DEF_SAVEGLOBAL type &= ~DEF_SAVEGLOBAL; #endif // if (pr_types) // type = pr_types[type].type; switch (type) { case ev_struct: sprintf (line, "structures cannot yet be saved"); break; case ev_union: sprintf (line, "unions cannot yet be saved"); break; case ev_string: { char *outs = line; int outb = sizeof(line)-2; const char *ins; #ifndef QCGC if (((unsigned int)val->string & STRING_SPECMASK) == STRING_TEMP) return ""; else #endif ins = PR_StringToNative(&progfuncs->funcs, val->string); //markup the output string. while(*ins && outb > 0) { switch(*ins) { case '\n': *outs++ = '\\'; *outs++ = 'n'; ins++; outb-=2; break; case '\"': *outs++ = '\\'; *outs++ = '"'; ins++; outb-=2; break; case '\\': *outs++ = '\\'; *outs++ = '\\'; ins++; outb-=2; break; default: *outs++ = *ins++; outb--; break; } } *outs = 0; } break; case ev_entity: sprintf (line, "%i", val->_int); break; case ev_function: i = (val->function & 0xff000000)>>24; //progs number if ((unsigned)i >= prinst.maxprogs || !pr_progstate[(unsigned)i].progs) sprintf (line, "BAD FUNCTION INDEX: %#"pPRIx"", val->function); else { j = (val->function & ~0xff000000); //function number if ((unsigned)j >= pr_progstate[(unsigned)i].progs->numfunctions) sprintf(line, "%i:%s", i, "CORRUPT FUNCTION POINTER"); else { f = pr_progstate[(unsigned)i].functions + j; sprintf (line, "%i:%s", i, f->s_name+progfuncs->funcs.stringtable); } } break; case ev_field: fielddef = ED_FieldAtOfs (progfuncs, val->_int + progfuncs->funcs.fieldadjust); if (fielddef) sprintf (line, "%s", fielddef->name); else sprintf (line, "bad field %i", type); break; case ev_void: sprintf (line, "void"); break; case ev_float: if (val->_float == (int)val->_float) sprintf (line, "%i", (int)val->_float); //an attempt to cut down on the number of .000000 vars.. else sprintf (line, "%f", val->_float); break; case ev_double: if (val->_double == (pint64_t)val->_double) sprintf (line, "%"pPRIi64, (pint64_t)val->_double); //an attempt to cut down on the number of .000000 vars.. else sprintf (line, "%f", val->_double); break; case ev_integer: sprintf (line, "%"pPRIi, val->_int); break; case ev_uint: sprintf (line, "%"pPRIu, val->_uint); break; case ev_int64: sprintf (line, "%"pPRIi64, val->i64); break; case ev_uint64: sprintf (line, "%"pPRIu64, val->u64); break; case ev_vector: if (val->_vector[0] == (int)val->_vector[0] && val->_vector[1] == (int)val->_vector[1] && val->_vector[2] == (int)val->_vector[2]) sprintf (line, "%i %i %i", (int)val->_vector[0], (int)val->_vector[1], (int)val->_vector[2]); else sprintf (line, "%g %g %g", val->_vector[0], val->_vector[1], val->_vector[2]); break; case ev_pointer: QC_snprintfz (line, sizeof(line), "%#x", val->_int); break; default: sprintf (line, "bad type %i", type); break; } return line; } //compatible with Q1 (for savegames) char *PR_UglyOldValueString (progfuncs_t *progfuncs, etype_t type, eval_t *val) { static char line[4096]; fdef_t *fielddef; mfunction_t *f; #ifdef DEF_SAVEGLOBAL type &= ~DEF_SAVEGLOBAL; #endif if (pr_types) type = pr_types[type].type; switch (type) { case ev_struct: QC_snprintfz (line, sizeof(line), "structures cannot yet be saved"); break; case ev_union: QC_snprintfz (line, sizeof(line), "unions cannot yet be saved"); break; case ev_string: //FIXME: we should probably add markup. vanilla does _not_, so we can expect problems reloading anyway. QC_snprintfz (line, sizeof(line), "%s", PR_StringToNative(&progfuncs->funcs, val->string)); break; case ev_entity: QC_snprintfz (line, sizeof(line), "%i", val->edict); break; case ev_function: f = pr_progstate[(val->function & 0xff000000)>>24].functions + (val->function & ~0xff000000); QC_snprintfz (line, sizeof(line), "%s", f->s_name+progfuncs->funcs.stringtable); break; case ev_field: fielddef = ED_FieldAtOfs (progfuncs, val->_int + progfuncs->funcs.fieldadjust); QC_snprintfz (line, sizeof(line), "%s", fielddef->name); break; case ev_void: QC_snprintfz (line, sizeof(line), "void"); break; case ev_float: if (val->_float == (int)val->_float) QC_snprintfz (line, sizeof(line), "%i", (int)val->_float); //an attempt to cut down on the number of .000000 vars.. else QC_snprintfz (line, sizeof(line), "%f", val->_float); break; case ev_double: if (val->_double == (int)val->_double) QC_snprintfz (line, sizeof(line), "%i", (int)val->_double); //an attempt to cut down on the number of .000000 vars.. else QC_snprintfz (line, sizeof(line), "%f", val->_double); break; case ev_integer: QC_snprintfz (line, sizeof(line), "%"pPRIi, val->_int); break; case ev_uint: QC_snprintfz (line, sizeof(line), "%"pPRIu, val->_uint); break; case ev_int64: QC_snprintfz (line, sizeof(line), "%"pPRIi64, val->i64); break; case ev_uint64: QC_snprintfz (line, sizeof(line), "%"pPRIu64, val->u64); break; case ev_vector: if (val->_vector[0] == (int)val->_vector[0] && val->_vector[1] == (int)val->_vector[1] && val->_vector[2] == (int)val->_vector[2]) QC_snprintfz (line, sizeof(line), "%i %i %i", (int)val->_vector[0], (int)val->_vector[1], (int)val->_vector[2]); else QC_snprintfz (line, sizeof(line), "%f %f %f", val->_vector[0], val->_vector[1], val->_vector[2]); break; case ev_pointer: QC_snprintfz (line, sizeof(line), "%#x", val->_int); break; default: QC_snprintfz (line, sizeof(line), "bad type %i", type); break; } return line; } char *PR_TypeString(progfuncs_t *progfuncs, etype_t type) { #ifdef DEF_SAVEGLOBAL type &= ~DEF_SAVEGLOBAL; #endif if (pr_types) type = pr_types[type].type; switch (type) { case ev_struct: return "struct"; case ev_union: return "union"; case ev_string: return "string"; case ev_entity: return "entity"; case ev_function: return "function"; case ev_field: return "field"; case ev_void: return "void"; case ev_float: return "float"; case ev_double: return "double"; case ev_vector: return "vector"; case ev_integer: return "integer"; case ev_uint: return "uint"; case ev_int64: return "int64"; case ev_uint64: return "uint64"; default: return "BAD TYPE"; } } /* ============ PR_GlobalString Returns a string with a description and the contents of a global, padded to 20 field width ============ */ char *PR_GlobalString (progfuncs_t *progfuncs, int ofs, struct QCC_type_s **typehint) { char *s; int i; ddef16_t *def16; ddef32_t *def32, def32tmp; void *val; static char line[128]; switch (current_progstate->structtype) { case PST_DEFAULT: case PST_KKQWSV: def16 = ED_GlobalAtOfs16(progfuncs, ofs); if (def16) { def32 = &def32tmp; def32->ofs = def16->ofs; def32->type = def16->type; def32->s_name = def16->s_name; } else def32 = NULL; break; case PST_QTEST: case PST_FTE32: case PST_UHEXEN2: def32 = ED_GlobalAtOfs32(progfuncs, ofs); break; default: externs->Sys_Error("Bad struct type in PR_GlobalString"); return ""; } val = (void *)&pr_globals[ofs]; if (!def32) { etype_t type; //urgh, this is so hideous #if !defined(MINIMAL) && !defined(OMIT_QCC) if (typehint == &type_float) type = ev_float; else if (typehint == &type_string) type = ev_string; else if (typehint == &type_vector) type = ev_vector; else if (typehint == &type_function) type = ev_function; else if (typehint == &type_field) type = ev_field; else #endif type = ev_integer; s = PR_ValueString (progfuncs, type, val, false); sprintf (line,"%i(?)%s", ofs, s); } else { s = PR_ValueString (progfuncs, def32->type, val, false); sprintf (line,"%i(%s)%s", ofs, def32->s_name+progfuncs->funcs.stringtable, s); } i = strlen(line); for ( ; i<20 ; i++) strcat (line," "); strcat (line," "); return line; } char *PR_GlobalStringNoContents (progfuncs_t *progfuncs, int ofs) { int i; ddef16_t *def16; ddef32_t *def32; int nameofs = 0; static char line[128]; switch (current_progstate->structtype) { case PST_DEFAULT: case PST_KKQWSV: def16 = ED_GlobalAtOfs16(progfuncs, ofs); if (def16) nameofs = def16->s_name; break; case PST_QTEST: case PST_FTE32: case PST_UHEXEN2: def32 = ED_GlobalAtOfs32(progfuncs, ofs); if (def32) nameofs = def32->s_name; break; default: externs->Sys_Error("Bad struct type in PR_GlobalStringNoContents"); } if (nameofs) sprintf (line,"%i(%s)", ofs, nameofs+progfuncs->funcs.stringtable); else { if (ofs >= OFS_RETURN && ofs < OFS_PARM0) sprintf (line,"%i(return_%c)", ofs, 'x' + (ofs - OFS_RETURN)%3); else if (ofs >= OFS_PARM0 && ofs < RESERVED_OFS) sprintf (line,"%i(parm%i_%c)", ofs, (ofs - OFS_PARM0)/3, 'x' + (ofs - OFS_PARM0)%3); else sprintf (line,"%i(?""?""?)", ofs); } i = strlen(line); for ( ; i<20 ; i++) strcat (line," "); strcat (line," "); return line; } char *PR_GlobalStringImmediate (progfuncs_t *progfuncs, int ofs) { int i; static char line[128]; sprintf (line,"%i", ofs); i = strlen(line); for ( ; i<20 ; i++) strcat (line," "); strcat (line," "); return line; } /* ============= ED_Print For debugging ============= */ void PDECL ED_Print (pubprogfuncs_t *ppf, struct edict_s *ed) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; int l; fdef_t *d; int *v; unsigned int i;unsigned int j; const char *name; int type; if (((edictrun_t *)ed)->ereftype == ER_FREE) { externs->Printf ("FREE\n"); return; } externs->Printf("\nEDICT %i:\n", NUM_FOR_EDICT(progfuncs, (struct edict_s *)ed)); for (i=1 ; iname; l = strlen(name); if (l >= 2 && name[l-2] == '_') continue; // skip _x, _y, _z vars v = (int *)((char *)edvars(ed) + d->ofs*4); // if the value is still all 0, skip the field #ifdef DEF_SAVEGLOBAL type = d->type & ~DEF_SAVEGLOBAL; #else type = d->type; #endif for (j=0 ; jPrintf ("%s",name); l = strlen (name); while (l++ < 15) externs->Printf (" "); externs->Printf ("%s\n", PR_ValueString(progfuncs, d->type, (eval_t *)v, false)); } } #if 0 void ED_PrintNum (progfuncs_t *progfuncs, int ent) { ED_Print (&progfuncs->funcs, EDICT_NUM(progfuncs, ent)); } /* ============= ED_PrintEdicts For debugging, prints all the entities in the current server ============= */ void ED_PrintEdicts (progfuncs_t *progfuncs) { unsigned int i; externs->Printf ("%i entities\n", sv_num_edicts); for (i=0 ; iisfree) continue; active++; // if (ent->v.solid) // solid++; // if (ent->v.model) // models++; // if (ent->v.movetype == MOVETYPE_STEP) // step++; } externs->Printf ("num_edicts:%3i\n", sv_num_edicts); externs->Printf ("active :%3i\n", active); // Con_Printf ("view :%3i\n", models); // Con_Printf ("touch :%3i\n", solid); // Con_Printf ("step :%3i\n", step); } #endif //============================================================================ /* ============= ED_NewString ============= */ char *PDECL ED_NewString (pubprogfuncs_t *ppf, const char *string, int minlength, pbool demarkup) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; char *newc, *new_p; int i,l; minlength++; l = strlen(string) + 1; newc = progfuncs->funcs.AddressableAlloc (&progfuncs->funcs, lfuncs.stringtable; new_p = newc; for (i=0 ; i< l ; i++) { if (demarkup && string[i] == '\\' && i < l-1 && string[i+1] != 0) { i++; switch(string[i]) { case 'n': *new_p++ = '\n'; break; case '\'': *new_p++ = '\''; break; case '\"': *new_p++ = '\"'; break; case 'r': *new_p++ = '\r'; break; default: *new_p++ = '\\'; i--; break; } } else *new_p++ = string[i]; } return newc; } /* ============= ED_ParseEval Can parse either fields or globals returns false if error ============= */ pbool PDECL ED_ParseEval (pubprogfuncs_t *ppf, eval_t *eval, int type, const char *s) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; int i; progsnum_t module; char string[128]; fdef_t *def; char *v, *w; string_t st; mfunction_t *func; switch (type & ~DEF_SAVEGLOBAL) { case ev_string: #ifdef QCGC st = PR_AllocTempString(&progfuncs->funcs, s); #else st = PR_StringToProgs(&progfuncs->funcs, ED_NewString (&progfuncs->funcs, s, 0, true)); #endif eval->string = st; break; case ev_float: eval->_float = (float)atof (s); break; case ev_double: eval->_double = atof (s); break; case ev_integer: eval->_int = strtol (s, NULL, 0); break; case ev_uint: eval->_uint = strtoul (s, NULL, 0); break; case ev_int64: eval->i64 = strtoll (s, NULL, 0); break; case ev_uint64: eval->u64 = strtoull (s, NULL, 0); break; case ev_vector: strncpy (string, s, sizeof(string)); string[sizeof(string)-1] = 0; v = string; w = string; for (i=0 ; i<3 ; i++) { while (*v && *v != ' ') v++; if (!*v) { eval->_vector[i] = (float)atof (w); w = v; } else { *v = 0; eval->_vector[i] = (float)atof (w); w = v = v+1; } } break; case ev_entity: if (!strncmp(s, "entity ", 7)) //cope with etos weirdness. s += 7; eval->edict = atoi (s); break; case ev_field: def = ED_FindField (progfuncs, s); if (!def) { externs->Printf ("Can't find field %s\n", s); return false; } eval->_int = def->ofs; break; case ev_function: if (s[1]==':'&&s[2]=='\0') { eval->function = 0; return true; } func = ED_FindFunction (progfuncs, s, &module, -1); if (!func) { externs->Printf ("Can't find function %s\n", s); return false; } eval->function = (func - pr_progstate[module].functions) | (module<<24); break; default: return false; } return true; } pbool ED_ParseEpair (progfuncs_t *progfuncs, size_t qcptr, unsigned int fldofs, int fldtype, char *s) { pint64_t i; puint64_t u; progsnum_t module; fdef_t *def; string_t st; mfunction_t *func; int type = fldtype & ~DEF_SAVEGLOBAL; double d; eval_t *eval = (eval_t *)(progfuncs->funcs.stringtable + qcptr + (fldofs*sizeof(int))); switch (type) { case ev_string: #ifdef QCGC st = PR_AllocTempString(&progfuncs->funcs, s); #else st = PR_StringToProgs(&progfuncs->funcs, ED_NewString (&progfuncs->funcs, s, 0, true)); #endif eval->string = st; break; case ev_float: while(*s == ' ' || *s == '\t') s++; d = strtod(s, &s); while(*s == ' ' || *s == '\t') s++; eval->_float = d; if (*s) return false; //some kind of junk in there. break; case ev_double: while(*s == ' ' || *s == '\t') s++; d = strtod(s, &s); while(*s == ' ' || *s == '\t') s++; eval->_double = d; if (*s) return false; //some kind of junk in there. break; case ev_integer: while(*s == ' ' || *s == '\t') s++; i = strtol(s, &s, 0); while(*s == ' ' || *s == '\t') s++; eval->_int = i; if (*s) return false; //some kind of junk in there. break; case ev_entity: //ent references are simple ints for us. case ev_uint: while(*s == ' ' || *s == '\t') s++; u = strtoul(s, &s, 0); while(*s == ' ' || *s == '\t') s++; eval->_uint = u; if (*s) return false; //some kind of junk in there. break; case ev_int64: while(*s == ' ' || *s == '\t') s++; i = strtoll(s, &s, 0); while(*s == ' ' || *s == '\t') s++; eval->i64 = i; if (*s) return false; //some kind of junk in there. break; case ev_uint64: while(*s == ' ' || *s == '\t') s++; u = strtoull(s, &s, 0); while(*s == ' ' || *s == '\t') s++; eval->u64 = u; if (*s) return false; //some kind of junk in there. break; case ev_vector: for (i=0 ; i<3 ; i++) { while(*s == ' ' || *s == '\t') s++; d = strtod(s, &s); eval->_vector[i] = d; } while(*s == ' ' || *s == '\t') s++; if (*s) return false; //some kind of junk in there. break; case ev_field: def = ED_FindField (progfuncs, s); if (!def) { externs->Printf ("Can't find field %s\n", s); return false; } eval->_int = def->ofs; break; case ev_function: if (s[0] && s[1]==':'&&s[2]=='\0') //this isn't right... { eval->function = 0; return true; } func = ED_FindFunction (progfuncs, s, &module, -1); if (!func) { externs->Printf ("Can't find function %s\n", s); return false; } eval->function = (func - pr_progstate[module].functions) | (module<<24); break; default: return false; } return true; } /* ==================== ED_ParseEdict Parses an edict out of the given string, returning the new position ed should be a properly initialized empty edict. Used for initial level load and for savegames. ==================== */ #if 1 static const char *ED_ParseEdict (progfuncs_t *progfuncs, const char *data, edictrun_t *ent, pbool *out_maphack) { fdef_t *key; pbool init; char keyname[256]; int n; int nest = 1; // eval_t *val; init = false; // clear it // if (ent != (edictrun_t *)sv_edicts) // hack // memset (ent+1, 0, pr_edict_size - sizeof(edictrun_t)); // go through all the dictionary pairs while (1) { // parse key data = QCC_COM_Parse (data); if (qcc_token[0] == '}') { if (--nest) continue; break; } if (qcc_token[0] == '{' && !qcc_token[1]) nest++; if (!data) { externs->Printf ("ED_ParseEntity: EOF without closing brace\n"); return NULL; } if (nest > 1) continue; strncpy (keyname, qcc_token, sizeof(keyname)-1); keyname[sizeof(keyname)-1] = 0; // another hack to fix heynames with trailing spaces n = strlen(keyname); while (n && keyname[n-1] == ' ') { keyname[n-1] = 0; n--; } // parse value data = QCC_COM_Parse (data); if (!data) { externs->Printf ("ED_ParseEntity: EOF without closing brace\n"); return NULL; } if (qcc_token[0] == '}') { externs->Printf ("ED_ParseEntity: closing brace without data\n"); return NULL; } init = true; // keynames with a leading underscore are used for utility comments, // and are immediately discarded by quake if (keyname[0] == '_') { if (externs->badfield) externs->badfield(&progfuncs->funcs, (struct edict_s*)ent, keyname, qcc_token); continue; } if (!strcmp(keyname, "angle")) //Quake anglehack - we've got to leave it in cos it doesn't work for quake otherwise, and this is a QuakeC lib! { if ((key = ED_FindField (progfuncs, "angles"))) { QC_snprintfz (qcc_token, sizeof(qcc_token), "0 %f 0", atof(qcc_token)); //change it from yaw to 3d angle goto cont; } } key = ED_FindField (progfuncs, keyname); if (!key) { if (!strcmp(keyname, "light")) //Quake lighthack - allows a field name and a classname to go by the same thing in the level editor if ((key = ED_FindField (progfuncs, "light_lev"))) goto cont; if (externs->badfield && externs->badfield(&progfuncs->funcs, (struct edict_s*)ent, keyname, qcc_token)) continue; PR_DPrintf ("'%s' is not a field\n", keyname); continue; } cont: switch(key->type) { case ev_function: case ev_field: case ev_entity: case ev_pointer: *out_maphack = true; //one of these types of fields means evil maphacks are at play. break; } if (!ED_ParseEpair (progfuncs, (char*)ent->fields - progfuncs->funcs.stringtable, key->ofs, key->type, qcc_token)) { if (externs->badfield && externs->badfield(&progfuncs->funcs, (struct edict_s*)ent, keyname, qcc_token)) continue; continue; // Sys_Error ("ED_ParseEdict: parse error on entities"); } } if (!init) ent->ereftype = ER_FREE; return data; } #endif static void PR_Cat(char *out, const char *in, size_t *len, size_t max) { size_t newl = strlen(in); max-=1; if (*len + newl > max) newl = max - *len; //truncate memcpy(out + *len, in, newl+1); *len += newl; } /* ================ ED_LoadFromFile The entities are directly placed in the array, rather than allocated with ED_Alloc, because otherwise an error loading the map would have entity number references out of order. Creates a server's entity / program execution context by parsing textual entity definitions out of an ent file. Used for both fresh maps and savegame loads. A fresh map would also need to call ED_CallSpawnFunctions () to let the objects initialize themselves. ================ */ char *ED_WriteGlobals(progfuncs_t *progfuncs, char *buf, size_t *bufofs, size_t bufmax) //switch first. { #define AddS(str) PR_Cat(buf, str, bufofs, bufmax) int *v; ddef32_t *def32; ddef16_t *def16; unsigned int i; // unsigned int j; const char *name; int type; int curprogs = prinst.pr_typecurrent; int len; switch(current_progstate->structtype) { case PST_DEFAULT: case PST_KKQWSV: for (i=0 ; inumglobaldefs ; i++) { def16 = &pr_globaldefs16[i]; name = def16->s_name + progfuncs->funcs.stringtable; len = strlen(name); if (!*name) continue; if (len >= 2 && name[len-2] == '_' && (name[len-1] == 'x' || name[len-1] == 'y' || name[len-1] == 'z')) continue; // skip _x, _y, _z vars (vector components, which are saved as one vector not 3 floats) type = def16->type; #ifdef DEF_SAVEGLOBAL if ( !(def16->type & DEF_SAVEGLOBAL) ) continue; type &= ~DEF_SAVEGLOBAL; #endif if (current_progstate->types) type = current_progstate->types[type].type; if (type == ev_function) { v = (int *)¤t_progstate->globals[def16->ofs]; if ((v[0]&0xff000000)>>24 == (unsigned)curprogs) //same progs { if (!progfuncs->funcs.stringtable[current_progstate->functions[v[0]&0x00ffffff].s_name]) continue; else if (!strcmp(current_progstate->functions[v[0]&0x00ffffff].s_name+ progfuncs->funcs.stringtable, name)) //names match. Assume function is at initial value. continue; } if (curprogs!=0) if ((v[0]&0xff000000)>>24 == 0) if (!ED_FindFunction(progfuncs, name, NULL, curprogs)) //defined as extern { if (!progfuncs->funcs.stringtable[pr_progstate[0].functions[v[0]&0x00ffffff].s_name]) continue; else if (!strcmp(pr_progstate[0].functions[v[0]&0x00ffffff].s_name + progfuncs->funcs.stringtable, name)) //same name. continue; } //else function has been redirected externally. goto add16; } else if (type != ev_string //anything other than these is not saved && type != ev_float && type != ev_double && type != ev_integer && type != ev_uint && type != ev_int64 && type != ev_uint64 && type != ev_entity && type != ev_vector) continue; v = (int *)¤t_progstate->globals[def16->ofs]; /* // make sure the value is not null, where there's no point in saving for (j=0 ; jfuncs, def16->type&~DEF_SAVEGLOBAL, (eval_t *)v)); AddS("\"\n"); } break; case PST_QTEST: case PST_FTE32: case PST_UHEXEN2: for (i=0 ; inumglobaldefs ; i++) { size_t nlen; def32 = &pr_globaldefs32[i]; name = PR_StringToNative(&progfuncs->funcs, def32->s_name); nlen = strlen(name); if (nlen >= 3 && name[nlen-2] == '_') continue; // skip _x, _y, _z vars (vector components, which are saved as one vector not 3 floats) type = def32->type; #ifdef DEF_SAVEGLOBAL if ( !(def32->type & DEF_SAVEGLOBAL) ) continue; type &= ~DEF_SAVEGLOBAL; #endif if (current_progstate->types) type = current_progstate->types[type].type; if (type == ev_function) { v = (int *)¤t_progstate->globals[def32->ofs]; if ((v[0]&0xff000000)>>24 == (unsigned)curprogs) //same progs if (!strcmp(current_progstate->functions[v[0]&0x00ffffff].s_name+ progfuncs->funcs.stringtable, name)) //names match. Assume function is at initial value. continue; if (curprogs!=0) if ((v[0]&0xff000000)>>24 == 0) if (!ED_FindFunction(progfuncs, name, NULL, curprogs)) //defined as extern if (!strcmp(pr_progstate[0].functions[v[0]&0x00ffffff].s_name+ progfuncs->funcs.stringtable, name)) //same name. continue; //else function has been redirected externally. goto add32; } else if (type != ev_string //anything other than these is not saved && type != ev_float && type != ev_double && type != ev_integer && type != ev_uint && type != ev_int64 && type != ev_uint64 && type != ev_entity && type != ev_vector) continue; v = (int *)¤t_progstate->globals[def32->ofs]; /* // make sure the value is not null, where there's no point in saving for (j=0 ; jfuncs, def32->type&~DEF_SAVEGLOBAL, (eval_t *)v)); AddS("\"\n"); } break; default: externs->Sys_Error("Bad struct type in SaveEnts"); } return buf; #undef AddS } char *ED_WriteEdict(progfuncs_t *progfuncs, edictrun_t *ed, char *buf, size_t *bufofs, size_t bufmax, pbool q1compatible) { #define AddS(str) PR_Cat(buf, str, bufofs, bufmax) fdef_t *d; int *v; unsigned int i;unsigned int j; const char *name; int type; int len; char *tmp; for (i=0 ; iname; len = strlen(name); if (len>4 && (name[len-2] == '_' && (name[len-1] == 'x' || name[len-1] == 'y' || name[len-1] == 'z'))) continue; // skip _x, _y, _z vars v = (int *)((char*)ed->fields + d->ofs*4); // if the value is still all 0, skip the field #ifdef DEF_SAVEGLOBAL type = d->type & ~DEF_SAVEGLOBAL; #else type = d->type; #endif for (j=0 ; jtype, (eval_t *)v); else tmp = PR_UglyValueString(&progfuncs->funcs, d->type, (eval_t *)v); AddS("\""); AddS(tmp); AddS("\"\n"); } return buf; #undef AddS } //just a simple helper that makes sure the s_name+s_file values are actually valid. some qccs generate really dodgy values intended to crash decompilers, but also crash debuggers too. static char *PR_StaticString(progfuncs_t *progfuncs, string_t thestring) { if (thestring <= 0 || thestring >= progfuncs->funcs.stringtablesize) return "???"; return thestring + progfuncs->funcs.stringtable; } char *PR_SaveCallStack (progfuncs_t *progfuncs, char *buf, size_t *bufofs, size_t bufmax) { #define AddS(str) PR_Cat(buf, str, bufofs, bufmax) char buffer[8192]; const mfunction_t *f; int i; int progs; int arg; int *globalbase; progs = -1; if (prinst.pr_depth == 0) { AddS ("\n"); return buf; } globalbase = (int *)pr_globals + prinst.pr_xfunction->parm_start + prinst.pr_xfunction->locals; prinst.pr_stack[prinst.pr_depth].f = prinst.pr_xfunction; for (i=prinst.pr_depth ; i>0 ; i--) { f = prinst.pr_stack[i].f; if (!f) { AddS ("\n"); } else { if (prinst.pr_stack[i].progsnum != progs) { progs = prinst.pr_stack[i].progsnum; sprintf(buffer, "//%i %s\n", progs, pr_progstate[progs].filename); AddS (buffer); } if (!f->s_file) sprintf(buffer, "\t\"%i:%s\"\n", progs, PR_StaticString(progfuncs, f->s_name)); else sprintf(buffer, "\t\"%i:%s\" //%s\n", progs, PR_StaticString(progfuncs, f->s_name), PR_StaticString(progfuncs, f->s_file)); AddS (buffer); AddS ("\t{\n"); for (arg = 0; arg < f->locals; arg++) { ddef16_t *local; local = ED_GlobalAtOfs16(progfuncs, f->parm_start+arg); if (!local) sprintf(buffer, "\t\tofs%i %i // %f\n", f->parm_start+arg, *(int *)(globalbase - f->locals+arg), *(float *)(globalbase - f->locals+arg) ); else { if (local->type == ev_entity) { sprintf(buffer, "\t\t\"%s\" \"entity %i\"\n", PR_StaticString(progfuncs, local->s_name), ((eval_t*)(globalbase - f->locals+arg))->edict); } else sprintf(buffer, "\t\t\"%s\"\t\"%s\"\n", PR_StaticString(progfuncs, local->s_name), PR_ValueString(progfuncs, local->type, (eval_t*)(globalbase - f->locals+arg), false)); if (local->type == ev_vector) arg+=2; } AddS (buffer); } AddS ("\t}\n"); if (i == prinst.pr_depth) globalbase = prinst.localstack + prinst.localstack_used - f->locals; else globalbase -= f->locals; } } return buf; #undef AddS } //there are two ways of saving everything. //0 is to save just the entities. //1 is to save the entites, and all the progs info so that all the variables are saved off, and it can be reloaded to exactly how it was (provided no files or data has been changed outside, like the progs.dat for example) //2 is for vanilla-compatible saved games //3 is a (human-readable) coredump //4 is binary saved games. char *PDECL PR_SaveEnts(pubprogfuncs_t *ppf, char *buf, size_t *bufofs, size_t bufmax, int alldata) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; #define AddS(str) PR_Cat(buf, str, bufofs, bufmax) unsigned int a; char *buffree = NULL; int oldprogs; if (!buf) { if (bufmax <= 0) bufmax = 5*1024*1024; buffree = buf = externs->memalloc(bufmax); } *bufofs = 0; switch(alldata) { default: return NULL; case 2: //special Q1 savegame compatability mode. //engine will need to store references to progs type and will need to preload the progs and inti the ents itself before loading. //Make sure there is only 1 progs loaded. for (a = 1; a < prinst.maxprogs; a++) { if (pr_progstate[a].progs) break; } if (!pr_progstate[0].progs || a != prinst.maxprogs) //the state of the progs wasn't Q1 compatible. { externs->memfree(buffree); return NULL; } //write the globals AddS ("{\n"); oldprogs = prinst.pr_typecurrent; PR_SwitchProgs(progfuncs, 0); ED_WriteGlobals(progfuncs, buf, bufofs, bufmax); PR_SwitchProgs(progfuncs, oldprogs); AddS ("}\n"); //write the ents for (a = 0; a < sv_num_edicts; a++) { char head[64]; edictrun_t *ed = (edictrun_t *)EDICT_NUM(progfuncs, a); QC_snprintfz(head, sizeof(head), "{//%i\n", a); AddS (head); if (ed->ereftype == ER_ENTITY) //free entities write a {} with no data. the loader detects this specifically. ED_WriteEdict(progfuncs, ed, buf, bufofs, bufmax, true); AddS ("}\n"); } return buf; case 0: //Writes entities only break; case 1: case 3: AddS("general {\n"); AddS(qcva("\"maxprogs\" \"%i\"\n", prinst.maxprogs)); // AddS(qcva("\"maxentities\" \"%i\"\n", maxedicts)); // AddS(qcva("\"mem\" \"%i\"\n", hunksize)); // AddS(qcva("\"crc\" \"%i\"\n", header_crc)); AddS(qcva("\"numentities\" \"%i\"\n", sv_num_edicts)); AddS("}\n"); oldprogs = prinst.pr_typecurrent; for (a = 0; a < prinst.maxprogs; a++) { if (!pr_progstate[a].progs) continue; { AddS (qcva("progs %i {\n", a)); AddS (qcva("\"filename\" \"%s\"\n", pr_progstate[a].filename)); AddS (qcva("\"crc\" \"%i\"\n", pr_progstate[a].progs->crc)); AddS ("}\n"); } } if (alldata == 3) { //include callstack AddS("stacktrace {\n"); PR_SaveCallStack(progfuncs, buf, bufofs, bufmax); AddS("}\n"); } for (a = 0; a < prinst.maxprogs; a++) //I would mix, but external functions rely on other progs being loaded { if (!pr_progstate[a].progs) continue; AddS (qcva("globals %i {\n", a)); PR_SwitchProgs(progfuncs, a); ED_WriteGlobals(progfuncs, buf, bufofs, bufmax); AddS ("}\n"); } PR_SwitchProgs(progfuncs, oldprogs); } for (a = 0; a < sv_num_edicts; a++) { edictrun_t *ed = (edictrun_t *)EDICT_NUM(progfuncs, a); if (!ed || ed->ereftype != ER_ENTITY) continue; AddS (qcva("entity %i{\n", a)); ED_WriteEdict(progfuncs, ed, buf, bufofs, bufmax, false); AddS ("}\n"); } return buf; #undef AddS } //if 'general' block is found, this is a compleate state, otherwise, we should spawn entities like int PDECL PR_LoadEnts(pubprogfuncs_t *ppf, const char *file, void *ctx, void (PDECL *memoryreset) (pubprogfuncs_t *progfuncs, void *ctx), void (PDECL *entspawned) (pubprogfuncs_t *progfuncs, struct edict_s *ed, void *ctx, const char *entstart, const char *entend), pbool(PDECL *extendedterm)(pubprogfuncs_t *progfuncs, void *ctx, const char **extline)) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; const char *datastart; // eval_t *selfvar = NULL; // eval_t *var; // const char *spawnwarned[20] = {NULL}; char filename[128]; int num; edictrun_t *ed=NULL; ddef16_t *d16; ddef32_t *d32; void *oldglobals = NULL; int oldglobalssize = 0; extern edictrun_t tempedict; int entsize = 0; int numents = 0; pbool maphacks = false; pbool resethunk=0; pbool isloadgame; if (file && !strncmp(file, "loadgame", 8)) { //this is internally inserted for legacy saved games. isloadgame = true; numents = -1; file+=8; } else isloadgame = false; while(1) { datastart = file; if (extendedterm) { //skip simple leading whitespace while (qcc_iswhite(*file)) file++; if (file[0] == '/' && file[1] == '*') { //looks like we have a hidden extension. file+=2; for(;;) { //skip to end of line if (!*file) break; //unexpected EOF else if (file[0] == '*' && file[1] == '/') { //end of comment file+=2; break; } else if (*file != '\n') { file++; continue; } file++; //skip past the \n while (*file == ' ' || *file == '\t') file++; //skip leading indentation if (file[0] == '*' && file[1] == '/') { //end of comment file+=2; break; } else if (*file == '/') continue; //embedded comment. ignore the line. not going to do nested comments, because those are not normally valid anyway, just C++-style inside C-style. else if (extendedterm(ppf, ctx, &file)) ; //found a term we recognised else ; //unknown line, but this is a comment so whatever } continue; } } file = QCC_COM_Parse(file); if (file == NULL) break; //finished reading file else if (!strcmp(qcc_token, "Version")) { file = QCC_COM_Parse(file); //qcc_token is a version number } else if (!strcmp(qcc_token, "entity")) { if (entsize == 0 && resethunk) //edicts have not yet been initialized, and this is a compleate load (memsize has been set) { entsize = PR_InitEnts(&progfuncs->funcs, prinst.maxedicts); // sv_num_edicts = numents; for (num = 0; num < numents; num++) { ed = (edictrun_t *)EDICT_NUM(progfuncs, num); if (!ed) { ed = (edictrun_t *)ED_AllocIndex(&progfuncs->funcs, num, false, 0); ed->ereftype = ER_FREE; if (externs->entspawn) externs->entspawn((struct edict_s *) ed, true); } } } file = QCC_COM_Parse(file); num = atoi(qcc_token); datastart = file; file = QCC_COM_Parse(file); if (qcc_token[0] != '{') externs->Sys_Error("Progs loading found %s, not '{'", qcc_token); if (!resethunk) ed = (edictrun_t *)ED_Alloc(&progfuncs->funcs, false, 0); else { ed = (edictrun_t *)EDICT_NUM(progfuncs, num); if (!ed) { externs->Sys_Error("Edict was not allocated\n"); ed = (edictrun_t *)ED_AllocIndex(&progfuncs->funcs, num, false, 0); } } ed->ereftype = ER_ENTITY; if (externs->entspawn) externs->entspawn((struct edict_s *) ed, true); file = ED_ParseEdict(progfuncs, file, ed, &maphacks); if (entspawned) entspawned(ppf, (struct edict_s *)ed, ctx, datastart, file); } else if (!strcmp(qcc_token, "progs")) { file = QCC_COM_Parse(file); num = atoi(qcc_token); file = QCC_COM_Parse(file); if (qcc_token[0] != '{') externs->Sys_Error("Progs loading found %s, not '{'", qcc_token); filename[0] = '\0'; while(1) { file = QCC_COM_Parse(file); //read the key if (!file) externs->Sys_Error("EOF in progs block"); if (!strcmp("filename", qcc_token)) //check key get and save values {file = QCC_COM_Parse(file); strcpy(filename, qcc_token);} else if (!strcmp("crc", qcc_token)) {file = QCC_COM_Parse(file); /*header_crc = atoi(qcc_token);*/} else if (!strcmp("numbuiltins", qcc_token)) //no longer supported. {file = QCC_COM_Parse(file); /*qcc_token unused*/} else if (qcc_token[0] == '}') //end of block break; else externs->Sys_Error("Bad key \"%s\" in progs block", qcc_token); } PR_ReallyLoadProgs(progfuncs, filename, &pr_progstate[num], true); if (num == 0 && oldglobals) { if (pr_progstate[0].globals_bytes == oldglobalssize) memcpy(pr_progstate[0].globals, oldglobals, pr_progstate[0].globals_bytes); free(oldglobals); oldglobals = NULL; } PR_SwitchProgs(progfuncs, 0); } else if (!strcmp(qcc_token, "globals")) { if (entsize == 0 && resethunk) //by the time we parse some globals, we MUST have loaded all progs { entsize = PR_InitEnts(&progfuncs->funcs, prinst.maxedicts); if (memoryreset) memoryreset(&progfuncs->funcs, ctx); // sv_num_edicts = numents; for (num = 0; num < numents; num++) { ed = (edictrun_t *)EDICT_NUM(progfuncs, num); if (!ed) { ed = (edictrun_t *)ED_AllocIndex(&progfuncs->funcs, num, false, 0); ed->ereftype = ER_FREE; } if (externs->entspawn) externs->entspawn((struct edict_s *) ed, true); } } file = QCC_COM_Parse(file); num = atoi(qcc_token); file = QCC_COM_Parse(file); if (qcc_token[0] != '{') externs->Sys_Error("Globals loading found \'%s\', not '{'", qcc_token); PR_SwitchProgs(progfuncs, num); while (1) { file = QCC_COM_Parse(file); if (qcc_token[0] == '}') break; else if (!qcc_token[0] || !file) externs->Sys_Error("EOF when parsing global values"); switch(current_progstate->structtype) { case PST_DEFAULT: case PST_KKQWSV: if (!(d16 = ED_FindGlobal16(progfuncs, qcc_token))) { externs->Printf("global value %s not found\n", qcc_token); file = QCC_COM_Parse(file); } else { file = QCC_COM_Parse(file); ED_ParseEpair(progfuncs, (char*)pr_globals - progfuncs->funcs.stringtable, d16->ofs, d16->type, qcc_token); } break; case PST_QTEST: case PST_FTE32: case PST_UHEXEN2: if (!(d32 = ED_FindGlobal32(progfuncs, qcc_token))) { externs->Printf("global value %s not found\n", qcc_token); file = QCC_COM_Parse(file); } else { file = QCC_COM_Parse(file); ED_ParseEpair(progfuncs, (char*)pr_globals - progfuncs->funcs.stringtable, d32->ofs, d32->type, qcc_token); } break; default: externs->Sys_Error("Bad struct type in LoadEnts"); } } PR_SwitchProgs(progfuncs, 0); // file = QCC_COM_Parse(file); // if (com_token[0] != '}') // Sys_Error("Progs loading found %s, not '}'", qcc_token); } else if (!strcmp(qcc_token, "general")) { QC_StartShares(progfuncs); // QC_InitShares(); //forget stuff // pr_edict_size = 0; prinst.max_fields_size=0; file = QCC_COM_Parse(file); if (qcc_token[0] != '{') externs->Sys_Error("Progs loading found %s, not '{'", qcc_token); while(1) { file = QCC_COM_Parse(file); //read the key if (!file) externs->Sys_Error("EOF in general block"); if (!strcmp("maxprogs", qcc_token)) //check key get and save values {file = QCC_COM_Parse(file); prinst.maxprogs = atoi(qcc_token);} // else if (!strcmp("maxentities", com_token)) // {file = QCC_COM_Parse(file); maxedicts = atoi(qcc_token);} // else if (!strcmp("mem", com_token)) // {file = QCC_COM_Parse(file); memsize = atoi(qcc_token);} // else if (!strcmp("crc", com_token)) // {file = QCC_COM_Parse(file); crc = atoi(qcc_token);} else if (!strcmp("numentities", qcc_token)) {file = QCC_COM_Parse(file); numents = atoi(qcc_token);} else if (qcc_token[0] == '}') //end of block break; else externs->Sys_Error("Bad key \"%s\" in general block", qcc_token); } if (oldglobals) free(oldglobals); oldglobals = NULL; if (pr_progstate[0].globals_bytes) { oldglobals = malloc(pr_progstate[0].globals_bytes); if (oldglobals) { oldglobalssize = pr_progstate[0].globals_bytes; memcpy(oldglobals, pr_progstate[0].globals, oldglobalssize); } else externs->Printf("Unable to alloc %i bytes\n", pr_progstate[0].globals_bytes); } PRAddressableFlush(progfuncs, 0); resethunk=true; pr_progstate = PRHunkAlloc(progfuncs, sizeof(progstate_t) * prinst.maxprogs, "progstatetable"); prinst.pr_typecurrent=0; sv_num_edicts = 1; //set up a safty buffer so things won't go horribly wrong too often sv_edicts=(struct edict_s *)&tempedict; prinst.edicttable = (struct edictrun_s**)(progfuncs->funcs.edicttable = &sv_edicts); progfuncs->funcs.edicttable_length = numents; sv_num_edicts = numents; //should be fine // PR_Configure(crc, NULL, memsize, maxedicts, maxprogs); } else if (!strcmp(qcc_token, "{")) { if (isloadgame) { if (numents == -1) //globals { while (1) { file = QCC_COM_Parse(file); if (qcc_token[0] == '}') break; else if (!qcc_token[0] || !file) externs->Sys_Error("EOF when parsing global values"); switch(current_progstate->structtype) { case PST_DEFAULT: case PST_KKQWSV: if (!(d16 = ED_FindGlobal16(progfuncs, qcc_token))) { externs->Printf("global value %s not found\n", qcc_token); file = QCC_COM_Parse(file); } else { file = QCC_COM_Parse(file); ED_ParseEpair(progfuncs, (char*)pr_globals - progfuncs->funcs.stringtable, d16->ofs, d16->type, qcc_token); } break; case PST_QTEST: case PST_FTE32: case PST_UHEXEN2: if (!(d32 = ED_FindGlobal32(progfuncs, qcc_token))) { externs->Printf("global value %s not found\n", qcc_token); file = QCC_COM_Parse(file); } else { file = QCC_COM_Parse(file); ED_ParseEpair(progfuncs, (char*)pr_globals - progfuncs->funcs.stringtable, d32->ofs, d32->type, qcc_token); } break; default: externs->Sys_Error("Bad struct type in LoadEnts"); } } } else { ed = (edictrun_t *)ED_AllocIndex(&progfuncs->funcs, numents, false, 0); if (externs->entspawn) externs->entspawn((struct edict_s *) ed, true); ed->ereftype = ER_ENTITY; file = ED_ParseEdict (progfuncs, file, ed, &maphacks); } sv_num_edicts = ++numents; continue; } if (entsize == 0 && resethunk) //edicts have not yet been initialized, and this is a compleate load (memsize has been set) { entsize = PR_InitEnts(&progfuncs->funcs, prinst.maxedicts); // sv_num_edicts = numents; for (num = 0; num < numents; num++) { ed = (edictrun_t *)EDICT_NUM(progfuncs, num); if (!ed) { ed = (edictrun_t *)ED_AllocIndex(&progfuncs->funcs, num, false, 0); ed->ereftype = ER_FREE; } } } if (!ed) //first entity ed = (edictrun_t *)EDICT_NUM(progfuncs, 0); else ed = (edictrun_t *)ED_Alloc(&progfuncs->funcs, false, 0); ed->ereftype = ER_ENTITY; if (externs->entspawn) externs->entspawn((struct edict_s *) ed, true); file = ED_ParseEdict(progfuncs, file, ed, &maphacks); if (entspawned) entspawned(ppf, (struct edict_s *)ed, ctx, datastart, file); } else if (extendedterm && extendedterm(ppf, ctx, &datastart)) file = datastart; else externs->Sys_Error("Bad entity lump: '%s' not recognised (last ent was %i)", qcc_token, ed?ed->entnum:0); } if (resethunk) { if (externs->loadcompleate) externs->loadcompleate(entsize); sv_num_edicts = numents; } if (oldglobals) free(oldglobals); oldglobals = NULL; if (resethunk) { return entsize; } else return prinst.max_fields_size; } //FIXME: maxsize is ignored. char *PDECL PR_SaveEnt (pubprogfuncs_t *ppf, char *buf, size_t *size, size_t maxsize, struct edict_s *ed) { #define AddS(str) PR_Cat(buf, str, size, maxsize) progfuncs_t *progfuncs = (progfuncs_t*)ppf; fdef_t *d; int *v; unsigned int i;unsigned int j; const char *name, *mname; const char *classname = NULL; int classnamelen = 0; int type; // if (ed->free) // continue; AddS ("{\n"); for (i=0 ; iname; len = strlen(name); // should we skip vars with no name? if (len > 2 && name[len-2] == '_' && (name[len-1] == 'x' || name[len-1] == 'y' || name[len-1] == 'z')) continue; // skip _x, _y, _z vars v = (int*)((edictrun_t*)ed)->fields + d->ofs; // if the value is still all 0, skip the field type = d->type & ~DEF_SAVEGLOBAL; for (j=0 ; jofs*4); classname = PR_StringToNative(&progfuncs->funcs, *v); } else classname = ""; classnamelen = strlen(classname); } for (j = i+1; j < prinst.numfields; j++) { if (prinst.field[j].ofs == d->ofs) { mname = prinst.field[j].name; if (!strncmp(mname, classname, classnamelen) && mname[classnamelen] == ':') { //okay, we have a match... name = prinst.field[j].name; break; } } } } //add it to the file AddS("\""); AddS(name); AddS("\" \""); AddS(PR_UglyValueString(&progfuncs->funcs, d->type, (eval_t *)v)); AddS("\"\n"); } AddS ("}\n"); return buf; } struct edict_s *PDECL PR_RestoreEnt (pubprogfuncs_t *ppf, const char *buf, size_t *size, struct edict_s *ed) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; edictrun_t *ent; const char *start = buf; pbool maphacks = false; //don't really care. buf = QCC_COM_Parse(buf); //read the key if (!buf || !*qcc_token) return NULL; if (strcmp(qcc_token, "{")) { externs->Printf("PR_RestoreEnt: with no opening brace"); return NULL; } if (!ed) ent = (edictrun_t *)ED_Alloc(&progfuncs->funcs, false, 0); else ent = (edictrun_t *)ed; if (ent->ereftype == ER_FREE && externs->entspawn) { memset (ent->fields, 0, ent->fieldsize); ent->ereftype = ER_ENTITY; externs->entspawn((struct edict_s *) ent, false); } if (ent->ereftype != ER_ENTITY) return NULL; //not allowed to spawn it into that ent. buf = ED_ParseEdict(progfuncs, buf, ent, &maphacks); *size = buf - start; return (struct edict_s *)ent; } #define Host_Error Sys_Error //return true if pr_progs needs recompiling (source files have changed) pbool PR_TestRecompile(progfuncs_t *progfuncs) { int newsize; int num, found=0, lost=0, changed=0; includeddatafile_t *s; if (!pr_progs->ofsfiles) return false; num = *(int*)((char *)pr_progs + pr_progs->ofsfiles); s = (includeddatafile_t *)((char *)pr_progs + pr_progs->ofsfiles+4); while(num>0) { newsize = externs->FileSize(s->filename); if (newsize == -1) //ignore now missing files. - the referencer must have changed... lost++; else if (s->size != newsize) //file changed++; else found++; s++; num--; } if (lost > found+changed) return false; if (changed) return true; return false; } /* #ifdef _DEBUG //this is for debugging. //I'm using this to detect incorrect string types while converting 32bit string pointers with bias to bound indexes. void PR_TestForWierdness(progfuncs_t *progfuncs) { unsigned int i; int e; edictrun_t *ed; for (i = 0; i < pr_progs->numglobaldefs; i++) { if ((pr_globaldefs16[i].type&~(DEF_SHARED|DEF_SAVEGLOBAL)) == ev_string) { if (G_INT(pr_globaldefs16[i].ofs) < 0 || G_INT(pr_globaldefs16[i].ofs) >= addressableused) externs->Printf("String type irregularity on \"%s\" \"%s\"\n", pr_globaldefs16[i].s_name+progfuncs->funcs.stringtable, G_INT(pr_globaldefs16[i].ofs)+progfuncs->funcs.stringtable); } } for (i = 0; i < numfields; i++) { if ((field[i].type&~(DEF_SHARED|DEF_SAVEGLOBAL)) == ev_string) { for (e = 0; e < sv_num_edicts; e++) { ed = (edictrun_t*)EDICT_NUM(progfuncs, e); if (ed->isfree) continue; if (((int *)ed->fields)[field[i].ofs] < 0 || ((int *)ed->fields)[field[i].ofs] >= addressableused) externs->Printf("String type irregularity \"%s\" \"%s\"\n", field[i].name, ((int *)ed->fields)[field[i].ofs]+progfuncs->funcs.stringtable); } } } } #endif */ void PR_CleanUpStatements16(progfuncs_t *progfuncs, dstatement16_t *st, pbool hexencalling) { unsigned int numst = pr_progs->numstatements; unsigned int numglob = pr_progs->numglobals+3; //+3 because I'm too lazy to deal with vectors unsigned int i; for (i=0 ; i= OP_CALL1 && st[i].op <= OP_CALL8 && hexencalling) st[i].op += OP_CALL1H - OP_CALL1; if (st[i].op >= OP_RAND0 && st[i].op <= OP_RANDV2 && hexencalling) if (!st[i].c) st[i].c = OFS_RETURN; //sanitise inputs if (st[i].a >= numglob) if (st[i].op != OP_GOTO) st[i].op = ~0; if (st[i].b >= numglob) if (st[i].op != OP_IFNOT_I && st[i].op != OP_IF_I && st[i].op != OP_IFNOT_F && st[i].op != OP_IF_F && st[i].op != OP_IFNOT_S && st[i].op != OP_IF_S && st[i].op != OP_BOUNDCHECK && st[i].op != OP_CASE) st[i].op = ~0; if (st[i].c >= numglob) if (st[i].op != OP_BOUNDCHECK && st[i].op != OP_CASERANGE) st[i].op = ~0; } } void PR_CleanUpStatements32(progfuncs_t *progfuncs, dstatement32_t *st, pbool hexencalling) { unsigned int numst = pr_progs->numstatements; unsigned int numglob = pr_progs->numglobals+3; //+3 because I'm too lazy to deal with vectors unsigned int i; for (i=0 ; i= OP_CALL1 && st[i].op <= OP_CALL8 && hexencalling) st[i].op += OP_CALL1H - OP_CALL1; if (st[i].op >= OP_RAND0 && st[i].op <= OP_RANDV2 && hexencalling) if (!st[i].c) st[i].c = OFS_RETURN; //sanitise inputs if (st[i].a >= numglob) if (st[i].op != OP_GOTO) st[i].op = ~0; if (st[i].b >= numglob) if (st[i].op != OP_IFNOT_I && st[i].op != OP_IF_I && st[i].op != OP_IFNOT_F && st[i].op != OP_IF_F && st[i].op != OP_IFNOT_S && st[i].op != OP_IF_S && st[i].op != OP_BOUNDCHECK && st[i].op != OP_CASE) st[i].op = ~0; if (st[i].c >= numglob) if (st[i].op != OP_BOUNDCHECK && st[i].op != OP_CASERANGE) st[i].op = ~0; } } char *decode(int complen, int len, int method, char *info, char *buffer); unsigned char *PDECL PR_GetHeapBuffer (void *ctx, size_t bufsize) { return PRHunkAlloc(ctx, bufsize+1, "proginfo"); } /* =============== PR_LoadProgs =============== */ pbool PR_ReallyLoadProgs (progfuncs_t *progfuncs, const char *filename, progstate_t *progstate, pbool complain) { unsigned int i, j, type; // float fl; // int len; // int num; // dfunction_t *f, *f2; ddef16_t *d16; ddef32_t *d32; int *d2; eval_t *eval; char *s; int progstype; int trysleft = 2; // bool qfhack = false; pbool isfriked = false; //all imediate values were stripped, which causes problems with strings. pbool hexencalling = false; //hexen style calling convention. The opcodes themselves are used as part of passing the arguments; ddef16_t *gd16, *fld16; float *glob; dfunction_t *fnc; mfunction_t *fnc2; dstatement16_t *st16; size_t fsz; int len; int hmark=0xffffffff; int reorg = prinst.reorganisefields || prinst.numfields; int stringadjust; int pointeradjust; int *basictypetable; current_progstate = progstate; strcpy(current_progstate->filename, filename); // flush the non-C variable lookup cache // for (i=0 ; iautocompile == PR_COMPILEALWAYS) //always compile before loading { externs->Printf("Forcing compile of progs %s\n", filename); if (!CompileFile(progfuncs, filename)) return false; } // CRC_Init (&pr_crc); retry: hmark = PRHunkMark(progfuncs); pr_progs = externs->ReadFile(filename, PR_GetHeapBuffer, progfuncs, &fsz, false); if (!pr_progs) { if (externs->autocompile == PR_COMPILENEXIST || externs->autocompile == PR_COMPILECHANGED) //compile if file is not found (if 2, we have already tried, so don't bother) { if (hmark==0xffffffff) //first try { externs->Printf("couldn't open progs %s. Attempting to compile.\n", filename); CompileFile(progfuncs, filename); } pr_progs = externs->ReadFile(filename, PR_GetHeapBuffer, progfuncs, &fsz, false); if (!pr_progs) { externs->Printf("Couldn't find or compile file %s\n", filename); return false; } } else if (externs->autocompile == PR_COMPILEIGNORE) return false; else { externs->Printf("Couldn't find file %s\n", filename); return false; } } // for (i=0 ; iversion == PROG_VERSION) { // externs->Printf("Opening standard progs file \"%s\"\n", filename); current_progstate->structtype = PST_DEFAULT; } else if (pr_progs->version == PROG_QTESTVERSION) { current_progstate->structtype = PST_QTEST; } else if (pr_progs->version == PROG_EXTENDEDVERSION) { #ifndef NOENDIAN for (i = standard_dprograms_t_size/sizeof(int); i < sizeof(dprograms_t)/sizeof(int); i++) ((int *)pr_progs)[i] = PRLittleLong ( ((int *)pr_progs)[i] ); #endif if (pr_progs->secondaryversion == PROG_SECONDARYVERSION16) { // externs->Printf("Opening 16bit fte progs file \"%s\"\n", filename); current_progstate->structtype = PST_DEFAULT; } else if (pr_progs->secondaryversion == PROG_SECONDARYVERSION32) { // externs->Printf("Opening 32bit fte progs file \"%s\"\n", filename); current_progstate->structtype = PST_FTE32; } else if (pr_progs->secondaryversion == PROG_SECONDARYUHEXEN2) { // externs->Printf("Opening uhexen2 progs file \"%s\"\n", filename); current_progstate->structtype = PST_UHEXEN2; pr_progs->version = PROG_VERSION; //not fte. } else if (pr_progs->secondaryversion == PROG_SECONDARYKKQWSV) { // externs->Printf("Opening KK7 progs file \"%s\"\n", filename); current_progstate->structtype = PST_KKQWSV; //KK progs. Yuck. Disabling saving would be a VERY good idea. pr_progs->version = PROG_VERSION; //not fte. } else { externs->Printf ("%s has no v7 verification code, assuming kkqwsv format\n", filename); // externs->Printf("Opening KK7 progs file \"%s\"\n", filename); current_progstate->structtype = PST_KKQWSV; //KK progs. Yuck. Disabling saving would be a VERY good idea. pr_progs->version = PROG_VERSION; //not fte. } /* else { externs->Printf ("Progs extensions are not compatible\nTry recompiling with the FTE compiler\n"); HunkFree(hmark); pr_progs=NULL; return false; } */ } else { externs->Printf ("%s has wrong version number (%i should be %i)\n", filename, pr_progs->version, PROG_VERSION); PRHunkFree(progfuncs, hmark); pr_progs=NULL; return false; } //progs contains enough info for use to recompile it. if (trysleft && (externs->autocompile == PR_COMPILECHANGED || externs->autocompile == PR_COMPILEEXISTANDCHANGED) && pr_progs->version == PROG_EXTENDEDVERSION) { if (PR_TestRecompile(progfuncs)) { externs->Printf("Source file has changed\nRecompiling.\n"); if (CompileFile(progfuncs, filename)) { PRHunkFree(progfuncs, hmark); pr_progs=NULL; trysleft--; goto retry; } } } if (!trysleft) //the progs exists, let's just be happy about it. externs->Printf("Progs is out of date and uncompilable\n"); if (externs->CheckHeaderCrc && !externs->CheckHeaderCrc(&progfuncs->funcs, prinst.pr_typecurrent, pr_progs->crc, filename)) { // externs->Printf ("%s system vars have been modified, progdefs.h is out of date\n", filename); PRHunkFree(progfuncs, hmark); pr_progs=NULL; return false; } if (pr_progs->version == PROG_EXTENDEDVERSION && pr_progs->blockscompressed && !QC_decodeMethodSupported(2)) { externs->Printf ("%s uses compression\n", filename); PRHunkFree(progfuncs, hmark); pr_progs=NULL; return false; } fnc = (dfunction_t *)((pbyte *)pr_progs + pr_progs->ofs_functions); pr_strings = ((char *)pr_progs + pr_progs->ofs_strings); current_progstate->globaldefs = *(void**)&gd16 = (void *)((pbyte *)pr_progs + pr_progs->ofs_globaldefs); current_progstate->fielddefs = *(void**)&fld16 = (void *)((pbyte *)pr_progs + pr_progs->ofs_fielddefs); current_progstate->statements = (void *)((pbyte *)pr_progs + pr_progs->ofs_statements); glob = pr_globals = (void *)((pbyte *)pr_progs + pr_progs->ofs_globals); current_progstate->globals_bytes = pr_progs->numglobals*sizeof(*pr_globals); pr_linenums=NULL; pr_types=NULL; if (pr_progs->version == PROG_EXTENDEDVERSION) { if (pr_progs->ofslinenums) pr_linenums = (int *)((pbyte *)pr_progs + pr_progs->ofslinenums); if (pr_progs->ofs_types) pr_types = (typeinfo_t *)((pbyte *)pr_progs + pr_progs->ofs_types); //start decompressing stuff... if (pr_progs->blockscompressed & 1) //statements { switch(current_progstate->structtype) { case PST_DEFAULT: len=sizeof(dstatement16_t)*pr_progs->numstatements; break; case PST_FTE32: case PST_UHEXEN2: len=sizeof(dstatement32_t)*pr_progs->numstatements; break; default: externs->Sys_Error("Bad struct type"); len = 0; } s = PRHunkAlloc(progfuncs, len, "dstatements"); QC_decode(progfuncs, PRLittleLong(*(int *)pr_statements16), len, 2, (char *)(((int *)pr_statements16)+1), s); current_progstate->statements = (dstatement16_t *)s; } if (pr_progs->blockscompressed & 2) //global defs { switch(current_progstate->structtype) { case PST_DEFAULT: len=sizeof(ddef16_t)*pr_progs->numglobaldefs; break; case PST_FTE32: case PST_UHEXEN2: len=sizeof(ddef32_t)*pr_progs->numglobaldefs; break; default: externs->Sys_Error("Bad struct type"); len = 0; } s = PRHunkAlloc(progfuncs, len, "dglobaldefs"); QC_decode(progfuncs, PRLittleLong(*(int *)pr_globaldefs16), len, 2, (char *)(((int *)pr_globaldefs16)+1), s); gd16 = *(ddef16_t**)¤t_progstate->globaldefs = (ddef16_t *)s; } if (pr_progs->blockscompressed & 4) //fields { switch(current_progstate->structtype) { case PST_DEFAULT: len=sizeof(ddef16_t)*pr_progs->numglobaldefs; break; case PST_FTE32: case PST_UHEXEN2: len=sizeof(ddef32_t)*pr_progs->numglobaldefs; break; default: externs->Sys_Error("Bad struct type"); len = 0; } s = PRHunkAlloc(progfuncs, len, "progfieldtable"); QC_decode(progfuncs, PRLittleLong(*(int *)pr_fielddefs16), len, 2, (char *)(((int *)pr_fielddefs16)+1), s); *(ddef16_t**)¤t_progstate->fielddefs = (ddef16_t *)s; } if (pr_progs->blockscompressed & 8) //functions { len=sizeof(dfunction_t)*pr_progs->numfunctions; s = PRHunkAlloc(progfuncs, len, "dfunctiontable"); QC_decode(progfuncs, PRLittleLong(*(int *)fnc), len, 2, (char *)(((int *)fnc)+1), s); fnc = (dfunction_t *)s; } if (pr_progs->blockscompressed & 16) //string table { len=sizeof(char)*pr_progs->numstrings; s = PRHunkAlloc(progfuncs, len, "dstringtable"); QC_decode(progfuncs, PRLittleLong(*(int *)pr_strings), len, 2, (char *)(((int *)pr_strings)+1), s); pr_strings = (char *)s; } if (pr_progs->blockscompressed & 32) //globals { len=sizeof(float)*pr_progs->numglobals; s = PRHunkAlloc(progfuncs, len + sizeof(float)*2, "dglobaltable"); QC_decode(progfuncs, PRLittleLong(*(int *)pr_globals), len, 2, (char *)(((int *)pr_globals)+1), s); glob = pr_globals = (float *)s; } if (pr_linenums && pr_progs->blockscompressed & 64) //line numbers { len=sizeof(int)*pr_progs->numstatements; s = PRHunkAlloc(progfuncs, len, "dlinenumtable"); QC_decode(progfuncs, PRLittleLong(*(int *)pr_linenums), len, 2, (char *)(((int *)pr_linenums)+1), s); pr_linenums = (int *)s; } if (pr_types && pr_progs->blockscompressed & 128) //types { len=sizeof(typeinfo_t)*pr_progs->numtypes; s = PRHunkAlloc(progfuncs, len, "dtypes"); QC_decode(progfuncs, PRLittleLong(*(int *)pr_types), len, 2, (char *)(((int *)pr_types)+1), s); pr_types = (typeinfo_t *)s; } } len=sizeof(char)*pr_progs->numstrings; s = PRAddressableExtend(progfuncs, pr_strings, len, 0); pr_strings = (char *)s; len=sizeof(float)*pr_progs->numglobals; s = PRAddressableExtend(progfuncs, pr_globals, len, sizeof(float)*2); glob = pr_globals = (float *)s; if (progfuncs->funcs.stringtable) { stringadjust = pr_strings - progfuncs->funcs.stringtable; pointeradjust = (char*)glob - progfuncs->funcs.stringtable; } else { stringadjust = 0; pointeradjust = (char*)glob - pr_strings; } if (!pr_linenums) { unsigned int lnotype = *(unsigned int*)"LNOF"; unsigned int version = 1; int ohm; unsigned int *file; char lnoname[128]; ohm = PRHunkMark(progfuncs); strcpy(lnoname, filename); StripExtension(lnoname); strcat(lnoname, ".lno"); file = externs->ReadFile(lnoname, PR_GetHeapBuffer, progfuncs, &fsz, false); if (file) { if ( file[0] != lnotype || file[1] != version || file[2] != pr_progs->numglobaldefs || file[3] != pr_progs->numglobals || file[4] != pr_progs->numfielddefs || file[5] != pr_progs->numstatements ) { PRHunkFree(progfuncs, ohm); //whoops: old progs or incompatible } else pr_linenums = file + 6; } } pr_cp_functions = NULL; // pr_strings = ((char *)pr_progs + pr_progs->ofs_strings); gd16 = *(ddef16_t**)¤t_progstate->globaldefs = (ddef16_t *)((pbyte *)pr_progs + pr_progs->ofs_globaldefs); fld16 = (ddef16_t *)((pbyte *)pr_progs + pr_progs->ofs_fielddefs); // pr_statements16 = (dstatement16_t *)((qbyte *)pr_progs + pr_progs->ofs_statements); pr_globals = glob; st16 = pr_statements16; #undef pr_globals #undef pr_globaldefs16 #undef pr_functions #undef pr_statements16 #undef pr_fielddefs16 current_progstate->edict_size = pr_progs->entityfields * 4 + externs->edictsize; if (sizeof(mfunction_t) > sizeof(qtest_function_t)) externs->Sys_Error("assumption no longer works"); // byte swap the lumps switch(current_progstate->structtype) { case PST_QTEST: // qtest needs a struct remap pr_cp_functions = (mfunction_t*)fnc; fnc2 = pr_cp_functions; for (i=0 ; inumfunctions; i++) { //qtest functions are bigger, so we can just do this in-place qtest_function_t qtfunc = ((qtest_function_t*)fnc)[i]; fnc2[i].first_statement = PRLittleLong (qtfunc.first_statement); fnc2[i].parm_start = PRLittleLong (qtfunc.parm_start); fnc2[i].s_name = (string_t)PRLittleLong (qtfunc.s_name); fnc2[i].s_file = (string_t)PRLittleLong (qtfunc.s_file); fnc2[i].numparms = PRLittleLong (qtfunc.numparms); fnc2[i].locals = PRLittleLong (qtfunc.locals); for (j=0; jnumfunctions, "mfunctions"); for (i=0,fnc2=pr_cp_functions; inumfunctions; i++, fnc2++) { fnc2->first_statement = PRLittleLong (fnc[i].first_statement); fnc2->parm_start = PRLittleLong (fnc[i].parm_start); fnc2->s_name = (string_t)PRLittleLong ((long)fnc[i].s_name) + stringadjust; fnc2->s_file = (string_t)PRLittleLong ((long)fnc[i].s_file) + stringadjust; fnc2->numparms = PRLittleLong (fnc[i].numparms); fnc2->locals = PRLittleLong (fnc[i].locals); for (j=0; jparm_size[j] = fnc[i].parm_size[j]; } break; default: externs->Sys_Error("Bad struct type"); } //actual global values #ifndef NOENDIAN for (i=0 ; inumglobals ; i++) ((int *)glob)[i] = PRLittleLong (((int *)glob)[i]); #endif if (pr_types) { for (i=0 ; inumtypes ; i++) { #ifndef NOENDIAN pr_types[i].type = PRLittleLong(current_progstate->types[i].type); pr_types[i].next = PRLittleLong(current_progstate->types[i].next); pr_types[i].aux_type = PRLittleLong(current_progstate->types[i].aux_type); pr_types[i].num_parms = PRLittleLong(current_progstate->types[i].num_parms); pr_types[i].ofs = PRLittleLong(current_progstate->types[i].ofs); pr_types[i].size = PRLittleLong(current_progstate->types[i].size); pr_types[i].name = PRLittleLong(current_progstate->types[i].name); #endif pr_types[i].name += stringadjust; } } QC_FlushProgsOffsets(progfuncs); switch(current_progstate->structtype) { case PST_KKQWSV: case PST_DEFAULT: //byteswap the globals and fix name offsets for (i=0 ; inumglobaldefs ; i++) { #ifndef NOENDIAN gd16[i].type = PRLittleShort (gd16[i].type); gd16[i].ofs = PRLittleShort (gd16[i].ofs); gd16[i].s_name = (string_t)PRLittleLong ((long)gd16[i].s_name); #endif gd16[i].s_name += stringadjust; } //byteswap fields and fix name offets. Also register the fields (which will result in some offset adjustments in the globals segment). for (i=0 ; inumfielddefs ; i++) { #ifndef NOENDIAN fld16[i].type = PRLittleShort (fld16[i].type); fld16[i].ofs = PRLittleShort (fld16[i].ofs); fld16[i].s_name = (string_t)PRLittleLong ((long)fld16[i].s_name); #endif if (reorg) { if (pr_types) type = pr_types[fld16[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL)].type; else type = fld16[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL); if (progfuncs->funcs.fieldadjust && !prinst.pr_typecurrent) //we need to make sure all fields appear in their original place. QC_RegisterFieldVar(&progfuncs->funcs, type, fld16[i].s_name+pr_strings, 4*(fld16[i].ofs+progfuncs->funcs.fieldadjust), fld16[i].ofs); else if (type == ev_vector) //emit vector vars early, so their fields cannot be alocated before the vector itself. (useful against scramblers) { QC_RegisterFieldVar(&progfuncs->funcs, type, fld16[i].s_name+pr_strings, -1, fld16[i].ofs); } } else { fdef_t *nf; if (prinst.numfields+1>prinst.maxfields) { i = prinst.maxfields; prinst.maxfields += 32; nf = externs->memalloc(sizeof(fdef_t) * prinst.maxfields); memcpy(nf, prinst.field, sizeof(fdef_t) * i); externs->memfree(prinst.field); prinst.field = nf; } nf = &prinst.field[prinst.numfields]; nf->name = fld16[i].s_name+pr_strings; nf->type = fld16[i].type; nf->progsofs = fld16[i].ofs; nf->ofs = fld16[i].ofs; if (prinst.fields_size < (nf->ofs+type_size[nf->type])*sizeof(pvec_t)) { prinst.fields_size = (nf->ofs+type_size[nf->type])*sizeof(pvec_t); progfuncs->funcs.activefieldslots = nf->ofs+type_size[nf->type]; } prinst.numfields++; } fld16[i].s_name += stringadjust; } if (reorg && !(progfuncs->funcs.fieldadjust && !prinst.pr_typecurrent)) for (i=0 ; inumfielddefs ; i++) { if (pr_types) type = pr_types[fld16[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL)].type; else type = fld16[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL); if (type != ev_vector) QC_RegisterFieldVar(&progfuncs->funcs, type, fld16[i].s_name+pr_strings-stringadjust, -1, fld16[i].ofs); } break; case PST_QTEST: // qtest needs a struct remap for (i=0 ; inumglobaldefs ; i++) { qtest_def_t qtdef = ((qtest_def_t *)pr_globaldefs32)[i]; pr_globaldefs32[i].type = qtdef.type; pr_globaldefs32[i].s_name = qtdef.s_name; pr_globaldefs32[i].ofs = qtdef.ofs; } for (i=0 ; inumfielddefs ; i++) { qtest_def_t qtdef = ((qtest_def_t *)pr_fielddefs32)[i]; pr_fielddefs32[i].type = qtdef.type; pr_fielddefs32[i].s_name = qtdef.s_name; pr_fielddefs32[i].ofs = qtdef.ofs; } // passthrough case PST_FTE32: for (i=0 ; inumglobaldefs ; i++) { #ifndef NOENDIAN pr_globaldefs32[i].type = PRLittleLong (pr_globaldefs32[i].type); pr_globaldefs32[i].ofs = PRLittleLong (pr_globaldefs32[i].ofs); pr_globaldefs32[i].s_name = (string_t)PRLittleLong ((long)pr_globaldefs32[i].s_name); #endif pr_globaldefs32[i].s_name += stringadjust; } for (i=0 ; inumfielddefs ; i++) { #ifndef NOENDIAN pr_fielddefs32[i].type = PRLittleLong (pr_fielddefs32[i].type); pr_fielddefs32[i].ofs = PRLittleLong (pr_fielddefs32[i].ofs); pr_fielddefs32[i].s_name = (string_t)PRLittleLong ((long)pr_fielddefs32[i].s_name); #endif if (reorg) { if (pr_types) type = pr_types[pr_fielddefs32[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL)].type; else type = pr_fielddefs32[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL); if (progfuncs->funcs.fieldadjust && !prinst.pr_typecurrent) //we need to make sure all fields appear in their original place. QC_RegisterFieldVar(&progfuncs->funcs, type, pr_fielddefs32[i].s_name+pr_strings, 4*(pr_fielddefs32[i].ofs+progfuncs->funcs.fieldadjust), -1); else if (type == ev_vector) QC_RegisterFieldVar(&progfuncs->funcs, type, pr_fielddefs32[i].s_name+pr_strings, -1, pr_fielddefs32[i].ofs); } pr_fielddefs32[i].s_name += stringadjust; } if (reorg && !(progfuncs->funcs.fieldadjust && !prinst.pr_typecurrent)) for (i=0 ; inumfielddefs ; i++) { if (pr_types) type = pr_types[pr_fielddefs32[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL)].type; else type = pr_fielddefs32[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL); if (type != ev_vector) QC_RegisterFieldVar(&progfuncs->funcs, type, pr_fielddefs32[i].s_name+pr_strings-stringadjust, -1, pr_fielddefs32[i].ofs); } break; case PST_UHEXEN2: for (i=0 ; inumglobaldefs ; i++) { pr_globaldefs32[i].type = (unsigned int)PRLittleLong (pr_globaldefs32[i].type)>>16; #ifndef NOENDIAN pr_globaldefs32[i].ofs = PRLittleLong (pr_globaldefs32[i].ofs); pr_globaldefs32[i].s_name = (string_t)PRLittleLong ((long)pr_globaldefs32[i].s_name); #endif pr_globaldefs32[i].s_name += stringadjust; } for (i=0 ; inumfielddefs ; i++) { pr_fielddefs32[i].type = (unsigned int)PRLittleLong (pr_fielddefs32[i].type)>>16; #ifndef NOENDIAN pr_fielddefs32[i].ofs = PRLittleLong (pr_fielddefs32[i].ofs); pr_fielddefs32[i].s_name = (string_t)PRLittleLong ((long)pr_fielddefs32[i].s_name); #endif if (reorg) { if (pr_types) type = pr_types[pr_fielddefs32[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL)].type; else type = pr_fielddefs32[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL); if (progfuncs->funcs.fieldadjust && !prinst.pr_typecurrent) //we need to make sure all fields appear in their original place. QC_RegisterFieldVar(&progfuncs->funcs, type, pr_fielddefs32[i].s_name+pr_strings, 4*(pr_fielddefs32[i].ofs+progfuncs->funcs.fieldadjust), -1); else if (type == ev_vector) QC_RegisterFieldVar(&progfuncs->funcs, type, pr_fielddefs32[i].s_name+pr_strings, -1, pr_fielddefs32[i].ofs); } pr_fielddefs32[i].s_name += stringadjust; } if (reorg && !(progfuncs->funcs.fieldadjust && !prinst.pr_typecurrent)) for (i=0 ; inumfielddefs ; i++) { if (pr_types) type = pr_types[pr_fielddefs32[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL)].type; else type = pr_fielddefs32[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL); if (type != ev_vector) QC_RegisterFieldVar(&progfuncs->funcs, type, pr_fielddefs32[i].s_name+pr_strings-stringadjust, -1, pr_fielddefs32[i].ofs); } break; default: externs->Sys_Error("Bad struct type"); } //ifstring fixes arn't performed anymore. //the following switch just fixes endian and hexen2 calling conventions (by using different opcodes). switch(current_progstate->structtype) { case PST_QTEST: for (i=0 ; inumstatements ; i++) { qtest_statement_t qtst = ((qtest_statement_t*)st16)[i]; st16[i].op = PRLittleShort(qtst.op); st16[i].a = PRLittleShort(qtst.a); st16[i].b = PRLittleShort(qtst.b); st16[i].c = PRLittleShort(qtst.c); // could use the line info as lno information maybe? is it really worth it? // also never assuming h2 calling mechanism } PR_CleanUpStatements16(progfuncs, st16, false); break; case PST_DEFAULT: for (i=0 ; inumstatements ; i++) { #ifndef NOENDIAN st16[i].op = PRLittleShort(st16[i].op); st16[i].a = PRLittleShort(st16[i].a); st16[i].b = PRLittleShort(st16[i].b); st16[i].c = PRLittleShort(st16[i].c); #endif if (st16[i].op >= OP_CALL1 && st16[i].op <= OP_CALL8) { if (st16[i].b) { hexencalling = true; break; } } } PR_CleanUpStatements16(progfuncs, st16, hexencalling); break; case PST_UHEXEN2: hexencalling = true; for (i=0 ; inumstatements ; i++) { pr_statements32[i].op = (unsigned int)PRLittleLong(pr_statements32[i].op)>>16; #ifndef NOENDIAN pr_statements32[i].a = PRLittleLong(pr_statements32[i].a); pr_statements32[i].b = PRLittleLong(pr_statements32[i].b); pr_statements32[i].c = PRLittleLong(pr_statements32[i].c); #endif } PR_CleanUpStatements32(progfuncs, pr_statements32, hexencalling); break; case PST_KKQWSV: case PST_FTE32: for (i=0 ; inumstatements ; i++) { #ifndef NOENDIAN pr_statements32[i].op = PRLittleLong(pr_statements32[i].op); pr_statements32[i].a = PRLittleLong(pr_statements32[i].a); pr_statements32[i].b = PRLittleLong(pr_statements32[i].b); pr_statements32[i].c = PRLittleLong(pr_statements32[i].c); #endif if (pr_statements32[i].op >= OP_CALL1 && pr_statements32[i].op <= OP_CALL8) { if (pr_statements32[i].b) { hexencalling = true; break; } } } PR_CleanUpStatements32(progfuncs, pr_statements32, hexencalling); break; } /* if (headercrc == -1) { isfriked = true; if (current_progstate->structtype != PST_DEFAULT) externs->Sys_Error("Decompiling a bigprogs"); return true; } */ progstype = current_progstate-pr_progstate; // QC_StartShares(progfuncs); isfriked = true; if (!prinst.pr_typecurrent) //progs 0 always acts as string stripped. isfriked = -1; //partly to avoid some bad/optimised progs. basictypetable = NULL; if (prinst.reorganisefields == 2) { switch(current_progstate->structtype) { //gmqcc fucks up the globals. it writes FLOAT defs instead of field defs. stupid stupid stupid. case PST_DEFAULT: { dstatement16_t *st = current_progstate->statements; basictypetable = externs->memalloc(sizeof(*basictypetable) * pr_progs->numglobals); memset(basictypetable, 0, sizeof(*basictypetable) * pr_progs->numglobals); for (i = 0; i < pr_progs->numstatements; i++) { switch(st[i].op) { case OP_ADDRESS: if (st[i+1].op == OP_STOREP_V && st[i+1].b == st[i].c) { //following stores a vector to this field. if (st[i].b+2u < pr_progs->numglobals) { //vectors are usually 3 fields. if they're not then we're screwed. basictypetable[st[i].b+0] = ev_field; basictypetable[st[i].b+1] = ev_field; basictypetable[st[i].b+2] = ev_field; } break; } //fallthrough case OP_LOAD_F: case OP_LOAD_S: case OP_LOAD_ENT: case OP_LOAD_FLD: case OP_LOAD_FNC: case OP_LOAD_I: case OP_LOAD_P: if (st[i].b < pr_progs->numglobals) basictypetable[st[i].b] = ev_field; break; case OP_LOAD_V: if (st[i].b+2u < pr_progs->numglobals) { //vectors are usually 3 fields. if they're not then we're screwed. basictypetable[st[i].b+0] = ev_field; basictypetable[st[i].b+1] = ev_field; basictypetable[st[i].b+2] = ev_field; } break; } } for (i = 0; i < pr_progs->numglobaldefs; i++) { ddef16_t *gd = gd16+i; switch(gd->type & ~(DEF_SAVEGLOBAL|DEF_SHARED)) { case ev_field: //depend on _y _z to mark those globals. basictypetable[gd->ofs] = ev_field; break; } } for (i = 0; i < pr_progs->numglobals; i++) { if (basictypetable[i] == ev_field) QC_AddFieldGlobal(&progfuncs->funcs, (int *)glob + i); } externs->memfree(basictypetable); } break; case PST_QTEST: //not likely to need this case PST_KKQWSV: //fixme... case PST_FTE32: //fingers crossed... case PST_UHEXEN2: break; } } // len = 0; switch(current_progstate->structtype) { case PST_DEFAULT: case PST_KKQWSV: for (i=0 ; inumglobaldefs ; i++) { if (pr_types) type = pr_types[gd16[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL)].type; else type = gd16[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL); if (gd16[i].type & DEF_SHARED) { gd16[i].type &= ~DEF_SHARED; if (pr_types) QC_AddSharedVar(&progfuncs->funcs, gd16[i].ofs, pr_types[gd16[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL)].size); else QC_AddSharedVar(&progfuncs->funcs, gd16[i].ofs, type_size[type]); } switch(type) { case ev_field: if (reorg && !basictypetable) QC_AddSharedFieldVar(&progfuncs->funcs, i, pr_strings - stringadjust); break; case ev_pointer: if (((int *)glob)[gd16[i].ofs] & 0x80000000) { ((int *)glob)[gd16[i].ofs] &= ~0x80000000; ((int *)glob)[gd16[i].ofs] += pointeradjust; break; } FALLTHROUGH case ev_string: if (((unsigned int *)glob)[gd16[i].ofs]>=progstate->progs->numstrings) externs->Printf("PR_LoadProgs: invalid string value (%x >= %x) in '%s'\n", ((unsigned int *)glob)[gd16[i].ofs], progstate->progs->numstrings, gd16[i].s_name+pr_strings-stringadjust); else if (isfriked != -1) { if (((int *)glob)[gd16[i].ofs]) //quakec uses string tables. 0 must remain null, or 'if (s)' can break. { ((int *)glob)[gd16[i].ofs] += stringadjust; isfriked = false; } else ((int *)glob)[gd16[i].ofs] = 0; } break; case ev_function: if (((int *)glob)[gd16[i].ofs]) //don't change null funcs { // if (fnc[((int *)glob)[gd16[i].ofs]].first_statement>=0) //this is a hack. Make all builtins switch to the main progs first. Allows builtin funcs to cache vars from just the main progs. ((int *)glob)[gd16[i].ofs] |= progstype << 24; } break; } } break; case PST_QTEST: case PST_FTE32: case PST_UHEXEN2: for (i=0 ; inumglobaldefs ; i++) { if (pr_types) type = pr_types[pr_globaldefs32[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL)].type; else type = pr_globaldefs32[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL); if (pr_globaldefs32[i].type & DEF_SHARED) { pr_globaldefs32[i].type &= ~DEF_SHARED; if (pr_types) QC_AddSharedVar(&progfuncs->funcs, pr_globaldefs32[i].ofs, pr_types[pr_globaldefs32[i].type & ~(DEF_SHARED|DEF_SAVEGLOBAL)].size); else QC_AddSharedVar(&progfuncs->funcs, pr_globaldefs32[i].ofs, type_size[type]); } switch(type) { case ev_field: QC_AddSharedFieldVar(&progfuncs->funcs, i, pr_strings - stringadjust); break; case ev_pointer: if (((int *)glob)[pr_globaldefs32[i].ofs] & 0x80000000) { ((int *)glob)[pr_globaldefs32[i].ofs] &= ~0x80000000; ((int *)glob)[pr_globaldefs32[i].ofs] += pointeradjust; break; } FALLTHROUGH case ev_string: if (((unsigned int *)glob)[pr_globaldefs32[i].ofs]>=progstate->progs->numstrings) externs->Printf("PR_LoadProgs: invalid string value (%x >= %x) in '%s'\n", ((unsigned int *)glob)[pr_globaldefs32[i].ofs], progstate->progs->numstrings, pr_globaldefs32[i].s_name+pr_strings-stringadjust); else if (((int *)glob)[pr_globaldefs32[i].ofs]) //quakec uses string tables. 0 must remain null, or 'if (s)' can break. { ((int *)glob)[pr_globaldefs32[i].ofs] += stringadjust; isfriked = false; } break; case ev_function: if (((int *)glob)[pr_globaldefs32[i].ofs]) //don't change null funcs ((int *)glob)[pr_globaldefs32[i].ofs] |= progstype << 24; break; } } if (pr_progs->version == PROG_EXTENDEDVERSION && pr_progs->numbodylessfuncs) { s = &((char *)pr_progs)[pr_progs->ofsbodylessfuncs]; for (i = 0; i < pr_progs->numbodylessfuncs; i++) { d32 = ED_FindGlobal32(progfuncs, s); d2 = ED_FindGlobalOfsFromProgs(progfuncs, &pr_progstate[0], s, ev_function); if (!d2) externs->Sys_Error("Runtime-linked function %s was not found in existing progs", s); if (!d32) externs->Sys_Error("Couldn't find def for \"%s\"", s); ((int *)glob)[d32->ofs] = (*(func_t *)&pr_progstate[0].globals[*d2]); s+=strlen(s)+1; } } break; default: externs->Sys_Error("Bad struct type"); } if ((isfriked && prinst.pr_typecurrent)) //friked progs only allow one file. { externs->Printf("You are trying to load a string-stripped progs as an addon.\nThis behaviour is not supported. Try removing some optimizations."); PRHunkFree(progfuncs, hmark); pr_progs=NULL; return false; } pr_strings+=stringadjust; if (!progfuncs->funcs.stringtable) progfuncs->funcs.stringtable = pr_strings; if (progfuncs->funcs.stringtablesize + progfuncs->funcs.stringtable < pr_strings + pr_progs->numstrings) progfuncs->funcs.stringtablesize = (pr_strings + pr_progs->numstrings) - progfuncs->funcs.stringtable; //make sure the localstack is addressable to the qc, so we can OP_PUSH okay. if (!prinst.localstack) prinst.localstack = PRAddressableExtend(progfuncs, NULL, 0, sizeof(float)*LOCALSTACK_SIZE); if (externs->MapNamedBuiltin) { for (i=0,fnc2=pr_cp_functions; inumfunctions; i++, fnc2++) { if (i && !fnc2->first_statement) fnc2->first_statement = -externs->MapNamedBuiltin(&progfuncs->funcs, pr_progs->crc, PR_StringToNative(&progfuncs->funcs, fnc2->s_name)); } } eval = PR_FindGlobal(&progfuncs->funcs, "thisprogs", progstype, NULL); if (eval) eval->prog = progstype; switch(current_progstate->structtype) { case PST_DEFAULT: if (pr_progs->version == PROG_EXTENDEDVERSION && pr_progs->numbodylessfuncs) { s = &((char *)pr_progs)[pr_progs->ofsbodylessfuncs]; for (i = 0; i < pr_progs->numbodylessfuncs; i++) { d16 = ED_FindGlobal16(progfuncs, s); if (!d16) { externs->Printf("\"%s\" requires the external function \"%s\", but the definition was stripped\n", filename, s); PRHunkFree(progfuncs, hmark); pr_progs=NULL; return false; } ((int *)glob)[d16->ofs] = PR_FindFunc(&progfuncs->funcs, s, PR_ANY); if (!((int *)glob)[d16->ofs]) externs->Printf("Warning: Runtime-linked function %s could not be found (loading %s)\n", s, filename); s+=strlen(s)+1; } } break; case PST_QTEST: case PST_KKQWSV: break; //cannot happen anyway. case PST_UHEXEN2: case PST_FTE32: if (pr_progs->version == PROG_EXTENDEDVERSION && pr_progs->numbodylessfuncs) { s = &((char *)pr_progs)[pr_progs->ofsbodylessfuncs]; for (i = 0; i < pr_progs->numbodylessfuncs; i++) { d32 = ED_FindGlobal32(progfuncs, s); if (!d32) { externs->Printf("\"%s\" requires the external function \"%s\", but the definition was stripped\n", filename, s); PRHunkFree(progfuncs, hmark); pr_progs=NULL; return false; } ((int *)glob)[d32->ofs] = PR_FindFunc(&progfuncs->funcs, s, PR_ANY); if (!((int *)glob)[d32->ofs]) externs->Printf("Warning: Runtime-linked function %s could not be found (loading %s)\n", s, filename); s+=strlen(s)+1; } } break; } eval = PR_FindGlobal(&progfuncs->funcs, "__ext__fasttrackarrays", PR_CURRENT, NULL); if (eval) //we support these opcodes eval->_float = true; return true; } struct edict_s *PDECL QC_EDICT_NUM(pubprogfuncs_t *ppf, unsigned int n) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; if (n >= prinst.maxedicts) externs->Sys_Error ("QCLIB: EDICT_NUM: bad number %i", n); return (struct edict_s*)prinst.edicttable[n]; } unsigned int PDECL QC_NUM_FOR_EDICT(pubprogfuncs_t *ppf, struct edict_s *e) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; edictrun_t *er = (edictrun_t*)e; if (!er || er->entnum >= prinst.maxedicts) externs->Sys_Error ("QCLIB: NUM_FOR_EDICT: bad pointer (%p)", e); return er->entnum; } fteqcc-20251105/./qcd_main.c0000644000200200001440000003013015233070110014640 0ustar twolifeusers#include "progsint.h" #include "qcc.h" #if !defined(FTE_TARGET_WEB) && !defined(_XBOX) #ifndef AVAIL_ZLIB #define AVAIL_ZLIB #endif #endif #ifdef NO_ZLIB #undef AVAIL_ZLIB #endif #ifdef AVAIL_ZLIB #include #endif pbool QC_decodeMethodSupported(int method) { if (method == 0) return true; if (method == 1) return true; if (method == 2) { #ifdef AVAIL_ZLIB return false; #endif } return false; } #ifdef ZLIB_DEFLATE64 #include "infback9.h" //an obscure compile-your-own part of zlib. struct def64ctx { const char *in; char *out; size_t csize; size_t usize; char window[65536]; }; static unsigned int QC_Deflate64_Grab(void *vctx, unsigned char **bufptr) { struct def64ctx *ctx = vctx; unsigned int avail = ctx->csize; *bufptr = (unsigned char *)ctx->in; ctx->csize = 0; ctx->in += avail; return avail; } static int QC_Deflate64_Spew(void *vctx, unsigned char *buf, unsigned int buflen) { struct def64ctx *ctx = vctx; if (buflen > ctx->usize) return 1; //over the size of our buffer... memcpy(ctx->out, buf, buflen); ctx->out += buflen; ctx->usize -= buflen; return 0; } #endif char *QC_decode(progfuncs_t *progfuncs, int complen, int len, int method, const void *info, char *buffer) { int i; if (method == 0) //copy { if (complen != len) externs->Sys_Error("lengths do not match"); memcpy(buffer, info, len); } else if (method == 1) //xor encryption { if (complen != len) externs->Sys_Error("lengths do not match"); for (i = 0; i < len; i++) buffer[i] = ((const char*)info)[i] ^ 0xA5; } #ifdef AVAIL_ZLIB else if (method == 2 || method == 8) //compression (ZLIB) { z_stream strm = { (char*)info, complen, 0, buffer, len, 0, NULL, NULL, NULL, NULL, NULL, Z_BINARY, 0, 0 }; if (method == 8) inflateInit2(&strm, -MAX_WBITS); else inflateInit(&strm); if (Z_STREAM_END != inflate(&strm, Z_FINISH)) //decompress it in one go. externs->Sys_Error("Failed block decompression\n"); inflateEnd(&strm); } #endif #ifdef ZLIB_DEFLATE64 else if (method == 9) { z_stream strm = {NULL}; struct def64ctx ctx; ctx.in = info; ctx.csize = complen; ctx.out = buffer; ctx.usize = len; strm.data_type = Z_UNKNOWN; inflateBack9Init(&strm, ctx.window); //getting inflateBack9 to if (Z_STREAM_END != inflateBack9(&strm, QC_Deflate64_Grab, &ctx, QC_Deflate64_Spew, &ctx)) { //some stream error? externs->Printf("Decompression error\n"); buffer = NULL; } else if (ctx.csize != 0 || ctx.usize != 0) { //corrupt file table? externs->Printf("Decompression size error\n"); externs->Printf("read %i of %i bytes\n", (unsigned)ctx.csize, (unsigned)complen); externs->Printf("wrote %i of %i bytes\n", (unsigned)ctx.usize, (unsigned)len); buffer = NULL; } inflateBack9End(&strm); return buffer; } #endif //add your decryption/decompression routine here. else externs->Sys_Error("Bad file encryption routine\n"); return buffer; } #if !defined(MINIMAL) && !defined(OMIT_QCC) int QC_encodecrc(int len, char *in) { #ifdef AVAIL_ZLIB return crc32(0, in, len); #else return 0; #endif } void SafeWrite(int hand, const void *buf, long count); int SafeSeek(int hand, int ofs, int mode); //we are allowed to trash our input here. int QC_encode(progfuncs_t *progfuncs, int len, int method, const char *in, int handle) { if (method == 0) //copy, allows a lame pass-through. { SafeWrite(handle, in, len); return len; } /*else if (method == 1) //xor encryption, not secure. maybe useful for the string table. { for (i = 0; i < len; i++) in[i] = in[i] ^ 0xA5; SafeWrite(handle, in, len); return len; }*/ else if (method == 2 || method == 8) //compression (ZLIB) { #ifdef AVAIL_ZLIB char out[8192]; int i=0; z_stream strm = { (char *)in, len, 0, out, sizeof(out), 0, NULL, NULL, NULL, NULL, NULL, Z_BINARY, 0, 0 }; if (method == 8) deflateInit2(&strm, 9, Z_DEFLATED, -MAX_WBITS, 9, Z_DEFAULT_STRATEGY); //zip deflate compression else deflateInit(&strm, Z_BEST_COMPRESSION); //zlib compression while(deflate(&strm, Z_FINISH) == Z_OK) { SafeWrite(handle, out, sizeof(out) - strm.avail_out); //compress in chunks of 8192. Saves having to allocate a huge-mega-big buffer i+=sizeof(out) - strm.avail_out; strm.next_out = out; strm.avail_out = sizeof(out); } SafeWrite(handle, out, sizeof(out) - strm.avail_out); i+=sizeof(out) - strm.avail_out; deflateEnd(&strm); return i; #endif externs->Sys_Error("ZLIB compression not supported in this build"); return 0; } //add your compression/decryption routine here. else { externs->Sys_Error("Wierd method"); return 0; } } #endif static int QC_ReadRawInt(const unsigned char *blob) { return (blob[0]<<0) | (blob[1]<<8) | (blob[2]<<16) | (blob[3]<<24); } static int QC_ReadRawShort(const unsigned char *blob) { return (blob[0]<<0) | (blob[1]<<8); } int QC_EnumerateFilesFromBlob(const void *blob, size_t blobsize, void (*cb)(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize)) { unsigned int cdentries; unsigned int cdlen; unsigned int cdstart; unsigned int zipoffset = 0; const unsigned char *eocd; const unsigned char *cd; unsigned int ofs_le; unsigned int cd_nl,cd_el,cd_cl; int ret = 0; const unsigned char *le; unsigned int csize, usize; int method; //negatives for errors. unsigned int le_nl,le_el; char name[256]; const void *data; if (blobsize < 22) return ret; if (!strncmp(blob, "PACK", 4)) { const packheader_t *head = blob; const packfile_t *f = (packfile_t*)((char*)blob + head->dirofs); for (ret = 0; ret < head->dirlen/sizeof(*f); ret++, f++) { cb(f->name, (const char*)blob+f->filepos, f->filelen, 0, f->filelen); } return ret; } //treat it as a zip (with no comment, too lazy to scan) eocd = blob; eocd += blobsize-22; for (cdlen = 0; ; eocd--, cdlen++) { if (cdlen > 65535 || eocd < (const unsigned char*)blob) { printf("No zip EOCD\n"); return ret; } if (QC_ReadRawInt(eocd+0) == 0x06054b50) break; } if (QC_ReadRawShort(eocd+4) || QC_ReadRawShort(eocd+6) || QC_ReadRawShort(eocd+20)!=cdlen || QC_ReadRawShort(eocd+8) != QC_ReadRawShort(eocd+10)) { //this-disk start-disk comment-length numfiles_thisdisk numfiles_all printf("Unsupported zip\n"); return ret; } cdstart = QC_ReadRawInt(eocd+16); cdlen = QC_ReadRawInt(eocd+12); cdentries = QC_ReadRawShort(eocd+10); cd = blob; cd += cdstart + zipoffset; if (cdlen < 46 || cd+cdlen>=(const unsigned char*)blob+blobsize || cd[0]!='P'||cd[1]!='K'||cd[2]!=1||cd[3]!=2) { //cd looks corrupt? assume eocd starts right after the cd and use that as an offset at the start of the zip (concatenated onto a binary or w/e) zipoffset += (eocd-(const unsigned char*)blob) - (cdstart+cdlen); cd = blob; cd += cdstart + zipoffset; if (cdlen < 46 || zipoffset > blobsize || cd+cdlen>=(const unsigned char*)blob+blobsize || cd[0]!='P'||cd[1]!='K'||cd[2]!=1||cd[3]!=2) { printf("Zip CentralDir not found: %s\n", cd); return ret; } //okay, we're at an offset. printf("Zip offset is %u\n", zipoffset); } for(; cdentries --> 0 && (QC_ReadRawInt(cd+0) == 0x02014b50); cd += 46 + cd_nl+cd_el+cd_cl) { data = NULL, csize=usize=0, method=-1; cd_nl = QC_ReadRawShort(cd+28); //name length cd_el = QC_ReadRawShort(cd+30); //extras length cd_cl = QC_ReadRawShort(cd+32); //comment length ofs_le = QC_ReadRawInt(cd+42); if (cd_nl < sizeof(name)) //make can't be too long... QC_strlcpy(name, cd+46, (cd_nl+1 extraend) break; //error extra += 4; switch(extrachunk_tag) { case 1: //zip64 extended information extra field. the attributes are only present if the reegular file info is nulled out with a -1 if (usize == 0xffffffffu) { usize = QC_ReadRawInt/*64*/(extra); if (QC_ReadRawInt(extra+4)) method=-1-method; extra += 8; } if (csize == 0xffffffffu) { csize = QC_ReadRawInt/*64*/(extra); if (QC_ReadRawInt(extra+4)) method=-1-method; extra += 8; } break; default: /* printf("Unknown chunk %x\n", extrachunk_tag); case 0x000a: //NTFS (timestamps) case 0x5455: //extended timestamp case 0x7875: //unix uid/gid */ extra += extrachunk_len; break; } } } data = le+30+le_nl+le_el; method = QC_ReadRawShort(le+8); if (method >= 0 && (method != 0 #ifdef AVAIL_ZLIB && method != 8 #endif #ifdef ZLIB_DEFLATE64 && method != 9 #endif )) method=-1-method; } } } } cb(name, data, csize, method, usize); ret++; } return ret; } char *PDECL filefromprogs(pubprogfuncs_t *ppf, progsnum_t prnum, const char *fname, size_t *size, char *buffer) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; int num; includeddatafile_t *s; if (size) *size = 0; if (!pr_progstate[prnum].progs) return NULL; if (pr_progstate[prnum].progs->version != PROG_EXTENDEDVERSION) return NULL; if (pr_progstate[prnum].progs->secondaryversion != PROG_SECONDARYVERSION16 && pr_progstate[prnum].progs->secondaryversion != PROG_SECONDARYVERSION32) return NULL; num = *(int*)((char *)pr_progstate[prnum].progs + pr_progstate[prnum].progs->ofsfiles); s = (includeddatafile_t *)((char *)pr_progstate[prnum].progs + pr_progstate[prnum].progs->ofsfiles+4); while(num>0) { if (!strcmp(s->filename, fname)) { if (size) *size = s->size; if (!buffer) return NULL; return QC_decode(progfuncs, s->compsize, s->size, s->compmethod, (char *)pr_progstate[prnum].progs+s->ofs, buffer); } s++; num--; } if (size) *size = 0; return NULL; } /* char *filefromnewprogs(progfuncs_t *progfuncs, const char *prname, const char *fname, int *size, char *buffer) { int num; includeddatafile_t *s; progstate_t progs; if (!PR_ReallyLoadProgs(progfuncs, prname, -1, &progs, false)) { if (size) *size = 0; return NULL; } if (progs.progs->version < PROG_EXTENDEDVERSION) return NULL; if (!progs.progs->ofsfiles) return NULL; num = *(int*)((char *)progs.progs + progs.progs->ofsfiles); s = (includeddatafile_t *)((char *)progs.progs + progs.progs->ofsfiles+4); while(num>0) { if (!strcmp(s->filename, fname)) { if (size) *size = s->size; if (!buffer) return (char *)0xffffffff; return QC_decode(progfuncs, s->compsize, s->size, s->compmethod, (char *)progs.progs+s->ofs, buffer); } s++; num--; } if (size) *size = 0; return NULL; } */ fteqcc-20251105/./qcctui.c0000644000200200001440000003213515233070110014364 0ustar twolifeusers#include "qcc.h" #include #include #if defined(__linux__) || defined(__unix__) #include #endif /* ============== LoadFile ============== */ static void *QCC_ReadFile(const char *fname, unsigned char *(*buf_get)(void *ctx, size_t len), void *buf_ctx, size_t *out_size, pbool issourcefile) //unsigned char *PDECL QCC_ReadFile (const char *fname, void *buffer, int len, size_t *sz) { size_t len; FILE *f; char *buffer; f = fopen(fname, "rb"); if (!f) { if (out_size) *out_size = 0; return NULL; } fseek(f, 0, SEEK_END); len = ftell(f); fseek(f, 0, SEEK_SET); if (buf_get) buffer = buf_get(buf_ctx, len+1); else buffer = malloc(len+1); ((char*)buffer)[len] = 0; if (len != fread(buffer, 1, len, f)) { if (!buf_get) free(buffer); buffer = NULL; } fclose(f); if (out_size) *out_size = len; return buffer; } static int PDECL QCC_FileSize (const char *fname) { long length; FILE *f; f = fopen(fname, "rb"); if (!f) return -1; fseek(f, 0, SEEK_END); length = ftell(f); fclose(f); return length; } static pbool PDECL QCC_WriteFile (const char *name, void *data, int len) { long length; FILE *f; f = fopen(name, "wb"); if (!f) return false; length = fwrite(data, 1, len, f); fclose(f); if (length != len) return false; return true; } #undef printf #undef Sys_Error static void PDECL Sys_Error(const char *text, ...) { va_list argptr; static char msg[2048]; va_start (argptr,text); QC_vsnprintf (msg,sizeof(msg)-1, text,argptr); va_end (argptr); QCC_Error(ERR_INTERNAL, "%s", msg); } static FILE *logfile; static int logprintf(const char *format, ...) { va_list argptr; static char string[1024]; va_start (argptr, format); #ifdef _WIN32 _vsnprintf (string,sizeof(string)-1, format,argptr); #else vsnprintf (string,sizeof(string), format,argptr); #endif va_end (argptr); fprintf(stderr, "%s", string); // fputs(string, stderr); if (logfile) fputs(string, logfile); return 0; } static size_t totalsize, filecount; static void QCC_FileList(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize) { totalsize += plainsize; filecount += 1; if (method < 0) { if (method == -1-9) externs->Printf("%s%8u DF64 %s%s\n", col_error, (unsigned)plainsize, name, col_none); else if (method == -1) //general error externs->Printf("%s%8u ERR %s%s\n", col_error, (unsigned)plainsize, name, col_none); else externs->Printf("%s%8u m%-3i %s%s\n", col_error, (unsigned)plainsize, -1-method, name, col_none); } else if (!method && compsize==plainsize) externs->Printf("%8u %s\n", (unsigned)plainsize, name); else externs->Printf("%8u %3u%% %s\n", (unsigned)plainsize, plainsize?(unsigned)((100*compsize)/plainsize):100u, name); } #include #ifdef __unix__ #include void QCC_Mkdir(const char *path) { char buf[MAX_OSPATH], *sl; if (!strchr(path, '/')) return; //no need to create anything memcpy(buf, path, MAX_OSPATH); while((sl=strrchr(buf, '/'))) { *sl = 0; mkdir(buf, 0777); } } #else void QCC_Mkdir(const char *path) { //unsupported. } #endif static char qcc_tolower(char c) { if (c >= 'A' && c <= 'Z') return c-'A'+'a'; return c; } int qcc_wildcmp(const char *wild, const char *string) { while (*string) { if (*wild == '*') { if (*string == '/' || *string == '\\') { //* terminates if we get a match on the char following it, or if its a \ or / char wild++; continue; } if (qcc_wildcmp(wild+1, string)) return true; string++; } else if ((qcc_tolower(*wild) == qcc_tolower(*string)) || (*wild == '?')) { //this char matches wild++; string++; } else { //failure return false; } } while (*wild == '*') { wild++; } return !*wild; } void AddSourceFile(const char *parentpath, const char *filename){} //used in the gui to insert extra stuff into the file list. irrelevant here. void compilecb(void){} //... used in the gui to repaint things while busy, so a pointless stub here. static void DoDecompileProgsDat(const char *name, void *blob, size_t blobsize) { // extern pbool qcc_vfiles_changed; extern vfile_t *qcc_vfiles; vfile_t *f; DecompileProgsDat(name, blob, blobsize); for (f = qcc_vfiles; f; f = f->next) { int h; h = SafeOpenWrite(f->filename, -1); if (h >= 0) { SafeWrite(h, f->file, f->size); SafeClose(h); } else externs->Printf("%s: write failure\n", f->filename); } } static const char *extractonly; //the file we're looking for static pbool extractonlyfound; //for errors. static pbool extractecho; //print the file to stdout instead of writing it. static pbool extractdecomp; //print the file to stdout instead of writing it. static void QCC_FileExtract(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize) { if (method < 0) return; //QC_decode will fail. provided for enumeration reasons. if (extractonly) { const char *sl = strrchr(extractonly, '/'); if (sl && !sl[1]) { //trailing / - extract the entire dir. if (!strcmp(name, extractonly)) return; //ignore the dir itself... if (strncmp(name, extractonly, strlen(extractonly))) return; } else if (!qcc_wildcmp(extractonly, name)) return; //ignore it if its not the one we're going for. } extractonlyfound = true; externs->Printf("Extracting %s...", name); if (plainsize <= INT_MAX) { void *buffer = malloc(plainsize); if (buffer && QC_decode(progfuncs, compsize, plainsize, method, compdata, buffer)) { if (extractdecomp) DoDecompileProgsDat(name, buffer, plainsize); else if (extractecho) { externs->Printf("\n"); fwrite(buffer, 1, plainsize, stdout); } else { QCC_Mkdir(name); if (!QCC_WriteFile(name, buffer, plainsize)) externs->Printf(" write failure\n"); else externs->Printf(" done\n"); } } else externs->Printf(" read failure\n"); free(buffer); } else externs->Printf(" too large\n"); } static void QCC_PR_PackagerMessage(void *userctx, const char *message, ...) { va_list argptr; char string[1024]; va_start (argptr,message); QC_vsnprintf (string,sizeof(string)-1,message,argptr); va_end (argptr); externs->Printf ("%s", string); } int main (int argc, const char **argv) { unsigned int i; pbool sucess; #if 0//def _WIN32 pbool writelog = true; //spew log files on windows. windows often closes the window as soon as the program ends making its output otherwise unreadable. #else pbool writelog = false; //other systems are sane. #endif pbool halp = false; int colours = 2; //auto int ziparg = -1; progexterns_t ext; progfuncs_t funcs; progfuncs = &funcs; memset(&funcs, 0, sizeof(funcs)); funcs.funcs.parms = &ext; memset(&ext, 0, sizeof(progexterns_t)); funcs.funcs.parms->ReadFile = QCC_ReadFile; funcs.funcs.parms->FileSize = QCC_FileSize; funcs.funcs.parms->WriteFile = QCC_WriteFile; funcs.funcs.parms->Printf = logprintf; funcs.funcs.parms->Sys_Error = Sys_Error; for (i = 0; i < argc; i++) { if (!argv[i]) continue; if (!strcmp(argv[i], "-log")) writelog = true; else if (!strcmp(argv[i], "-nolog")) writelog = false; else if (!strcmp(argv[i], "-help") || !strcmp(argv[i], "--help")) halp = true; //arg consistency with ls else if (!strcmp(argv[i], "--color=always") || !strcmp(argv[i], "--color")) colours = 1; else if (!strcmp(argv[i], "--color=never")) colours = 0; else if (!strcmp(argv[i], "--color=auto")) colours = 2; else if (!strcmp(argv[i], "-d") || //o.O !strcmp(argv[i], "-l") || !strcmp(argv[i], "-x") || !strcmp(argv[i], "-p") || !strcmp(argv[i], "-z") || !strcmp(argv[i], "-0") || !strcmp(argv[i], "-9")) { ziparg = i; break; //other args are all filenames. don't misinterpret stuff. } } for (i = 0; i < COL_MAX; i++) qcccol[i] = ""; #if defined(__linux__) || defined(__unix__) if (colours == 2) colours = isatty(STDOUT_FILENO); if (colours) { //only use colours if its a tty, and not if we're redirected. col_none = "\e[0;m"; //reset to white col_error = "\e[0;31m"; //red col_symbol = "\e[0;32m"; //green col_warning = "\e[0;33m"; //yellow //col_ = "\e[0;34m"; //blue col_name = "\e[0;35m"; //magenta col_type = "\e[0;36m"; //cyan col_location = "\e[0;1;37m"; //bright white } #else (void)colours; #endif if (halp) { logprintf("Archiving args:\n"); logprintf(" -l PACKAGE : List files within a pak or pk3\n"); logprintf(" -x PACKAGE [FILENAMES]: Extract files from pak or pk3\n"); logprintf(" -p PACKAGE FILENAME: Pipe files from a pak or pk3 to the stdout\n"); logprintf(" -z DIRECTORY : Create a spanned pk3 from a 'foo.pk3dir' subdir.\n"); logprintf(" the pk3 itself contains just the file table, actual data will reside in external .p## files which will NOT be overwritten and can be referenced by future revisions to reduce redundancy on future updates\n"); logprintf(" -0 DIRECTORY : Create a hybrid pak (uncompressed)\n"); logprintf(" such pak files can also be read with any zip tool without needing special tools to extract (but should not be edited)\n"); logprintf(" -9 DIRECTORY : Create a standard pk3\n"); logprintf(" regular compressed zip with limited feature set for greater engine compat\n"); logprintf("Decompiling args:\n"); logprintf(" -d FILENAME : decompile a progs (into working directory)\n"); } else if (ziparg >= 0) { if (ziparg+1 >= argc) { logprintf("archive name not specified\n"); return EXIT_FAILURE; } switch(argv[ziparg][1]) { case 'd': //decompile... { size_t blobsize; void *blob = QCC_ReadFile(argv[ziparg+1], NULL, NULL, &blobsize, false); if (!blob) logprintf("Unable to read %s\n", argv[ziparg+1]); else if (!strncmp(blob, "PACK", 4) || !strncmp(blob, "PK", 2)) { //.pak or .pk3... probably. extractonly = (ziparg+2 < argc)?argv[ziparg+2]:"progs.dat"; extractdecomp = true; extractonlyfound = false; QC_EnumerateFilesFromBlob(blob, blobsize, QCC_FileExtract); if (!extractonlyfound) externs->Printf("Unable to find file %s inside %s\n", extractonly, argv[ziparg+1]); else return EXIT_SUCCESS; extractonly = NULL; } else if (blob) { //directly a .dat DoDecompileProgsDat(argv[ziparg+1], blob, blobsize); free(blob); return EXIT_SUCCESS; } return EXIT_FAILURE; } break; case 'l': //list all files. { size_t blobsize; void *blob = QCC_ReadFile(argv[ziparg+1], NULL, NULL, &blobsize, false); if (blob) { QC_EnumerateFilesFromBlob(blob, blobsize, QCC_FileList); externs->Printf("Total size %lu bytes, %u files\n", (unsigned long)totalsize, (unsigned)filecount); free(blob); return EXIT_SUCCESS; } logprintf("Unable to read %s\n", argv[ziparg+1]); } break; case 'p': //print (named) files to stdout. extractecho = true; //fall through case 'x': //extract (named) files to working directory. { //list/extract/view size_t blobsize; void *blob = QCC_ReadFile(argv[ziparg+1], NULL, NULL, &blobsize, false); int ret = EXIT_FAILURE; if (!blob) logprintf("Unable to read %s\n", argv[ziparg+1]); else if (ziparg+2 < argc) { for (i = ziparg+2; i < argc; i++) { extractonly = argv[i]; extractonlyfound = false; QC_EnumerateFilesFromBlob(blob, blobsize, QCC_FileExtract); if (!extractonlyfound) externs->Printf("Unable to find file %s\n", extractonly); else ret = EXIT_SUCCESS; } extractonly = NULL; } else { QC_EnumerateFilesFromBlob(blob, blobsize, QCC_FileExtract); ret = EXIT_SUCCESS; } free(blob); return ret; } case 'z': //fancy spanned stuff case '0': //store-only (pak) case '9': //best compression (pk3) { //exe -0 foo.pk3dir enum pkgtype_e t; if (argv[ziparg][1] == '9') t = PACKAGER_PK3; else if (argv[ziparg][1] == '0') t = PACKAGER_PAK; //not really any difference but oh well else t = PACKAGER_PK3_SPANNED; if (Packager_CompressDir(argv[ziparg+1], t, QCC_PR_PackagerMessage, NULL)) return EXIT_SUCCESS; } break; default: //should be unreachable. break; } return EXIT_FAILURE; } logfile = writelog?fopen("fteqcc.log", "wt"):false; if (logfile) { fputs("Args:", logfile); for (i = 0; i < argc; i++) { if (!argv[i]) continue; if (strchr(argv[i], ' ')) fprintf(logfile, " \"%s\"", argv[i]); else fprintf(logfile, " %s", argv[i]); } fprintf(logfile, "\n"); } sucess = CompileParams(&funcs, NULL, argc, argv); qccClearHunk(); if (logfile) fclose(logfile); #ifdef _WIN32 // fgetc(stdin); //wait for keypress #endif return sucess?EXIT_SUCCESS:EXIT_FAILURE; } fteqcc-20251105/./progtype.h0000644000200200001440000000371615233070110014755 0ustar twolifeusers#ifndef QCLIB_PROGTYPE_H #define QCLIB_PROGTYPE_H #if _MSC_VER >= 1300 #define QC_ALIGN(a) __declspec(align(a)) #elif (__GNUC__ >= 3) || defined(__clang__) #define QC_ALIGN(a) __attribute__((aligned(a))) #else #define QC_ALIGN(a) //I hope misaligned accesses are okay... #endif #if 0 //64bit primitives allows for: // greater precision timers (so maps can last longer without getting restarted) // planet-sized maps (with the engine's vec_t types changed too, and with some sort of magic for the gpu's precision). //TODO: for this to work, someone'll have to go through the code to somehow deal with the vec_t/pvec_t/float differences. #warning FTE isnt ready for this. #include typedef double pvec_t; typedef int64_t pint_t; typedef uint64_t puint_t; #include #define pPRId PRId64 #define pPRIi PRIi64 #define pPRIu PRIu64 #define pPRIx PRIx64 #define QCVM_64 #else //use 32bit types, for sanity. typedef float pvec_t; typedef int pint_t; typedef unsigned int puint_t; #ifdef _MSC_VER typedef QC_ALIGN(4) __int64 pint64_t; typedef QC_ALIGN(4) unsigned __int64 puint64_t; #define pPRId "d" #define pPRIi "i" #define pPRIu "u" #define pPRIx "x" #define pPRIi64 "I64i" #define pPRIu64 "I64u" #define pPRIx64 "I64x" #define pPRIuSIZE PRIxPTR #else #include typedef int64_t pint64_t QC_ALIGN(4); typedef uint64_t puint64_t QC_ALIGN(4); #define pPRId PRId32 #define pPRIi PRIi32 #define pPRIu PRIu32 #define pPRIx PRIx32 #define pPRIi64 PRIi64 #define pPRIu64 PRIu64 #define pPRIx64 PRIx64 #define pPRIuSIZE PRIxPTR #endif #define QCVM_32 #endif typedef QC_ALIGN(4) double pdouble_t; //the qcvm uses vectors and stuff, so any 64bit types are only 4-byte aligned. we don't do atomics so this is fine so long as the compiler handles it for us. typedef unsigned int pbool; typedef pvec_t pvec3_t[3]; typedef pint_t progsnum_t; typedef puint_t func_t; typedef puint_t string_t; extern pvec3_t pvec3_origin; #endif /* QCLIB_PROGTYPE_H */ fteqcc-20251105/./cmdlib.h0000644000200200001440000000765715233070110014346 0ustar twolifeusers// cmdlib.h #ifndef __CMDLIB__ #define __CMDLIB__ #include "progsint.h" /*#include #include #include #include #include #include #include #include #include #include #include #ifdef NeXT #include #endif */ // the dec offsetof macro doesn't work very well... #define myoffsetof(type,identifier) ((size_t)&((type *)NULL)->identifier) // set these before calling CheckParm extern int myargc; extern const char **myargv; //char *strupr (char *in); //char *strlower (char *in); int QCC_filelength (int handle); int QCC_tell (int handle); #if 0//def __GNUC__ #define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) #else #define WARN_UNUSED_RESULT #endif int QC_strcasecmp (const char *s1, const char *s2); int QC_strncasecmp(const char *s1, const char *s2, int n); pbool QC_strlcat(char *dest, const char *src, size_t destsize) WARN_UNUSED_RESULT; pbool QC_strlcpy(char *dest, const char *src, size_t destsize) WARN_UNUSED_RESULT; pbool QC_strnlcpy(char *dest, const char *src, size_t srclen, size_t destsize) WARN_UNUSED_RESULT; char *QC_strcasestr(const char *haystack, const char *needle); #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) #define FTE_DEPRECATED __attribute__((__deprecated__)) //no idea about the actual gcc version #if defined(_WIN32) #include #ifdef __MINGW_PRINTF_FORMAT #define LIKEPRINTF(x) __attribute__((format(__MINGW_PRINTF_FORMAT,x,x+1))) #else #define LIKEPRINTF(x) __attribute__((format(ms_printf,x,x+1))) #endif #else #define LIKEPRINTF(x) __attribute__((format(printf,x,x+1))) #endif #endif #if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5)) #define NORETURN __attribute__((noreturn)) #endif #ifndef NORETURN #define NORETURN #endif #ifndef LIKEPRINTF #define LIKEPRINTF(x) #endif #ifdef _MSC_VER #define QC_vsnprintf _vsnprintf static void VARGS QC_snprintfz (char *dest, size_t size, const char *fmt, ...) LIKEPRINTF(3) { va_list args; va_start (args, fmt); _vsnprintf (dest, size-1, fmt, args); va_end (args); //make sure its terminated. dest[size-1] = 0; } #define snprintf QC_snprintfz #else #define QC_vsnprintf vsnprintf #define QC_snprintfz snprintf #endif double I_FloatTime (void); void VARGS QCC_Error (int errortype, const char *error, ...) LIKEPRINTF(2); int QCC_CheckParm (const char *check); const char *QCC_ReadParm (const char *check); int SafeOpenWrite (char *filename, int maxsize); int SafeOpenRead (char *filename); void SafeRead (int handle, void *buffer, long count); void SafeWrite (int handle, const void *buffer, long count); pbool SafeClose(int hand); int SafeSeek(int hand, int ofs, int mode); void *SafeMalloc (long size); long QCC_LoadFile (char *filename, void **bufferptr); void QCC_SaveFile (char *filename, void *buffer, long count); void DefaultExtension (char *path, char *extension); void DefaultPath (char *path, char *basepath); void StripFilename (char *path); void StripExtension (char *path); void ExtractFilePath (char *path, char *dest); void ExtractFileBase (char *path, char *dest); void ExtractFileExtension (char *path, char *dest); long ParseNum (char *str); unsigned short *QCC_makeutf16(char *mem, size_t len, int *outlen, pbool *errors); char *QCC_SanitizeCharSet(char *mem, size_t *len, pbool *freeresult, int *origfmt); char *QCC_COM_Parse (const char *data); char *QCC_COM_Parse2 (char *data); unsigned int utf8_check(const void *in, unsigned int *value); extern char qcc_token[1024]; #define qcc_iswhite(c) ((c) == ' ' || (c) == '\r' || (c) == '\n' || (c) == '\t' || (c) == '\v') #define qcc_iswhitesameline(c) ((c) == ' ' || (c) == '\t') #define qcc_islineending(c,n) ((c) == '\n' || ((c) == '\r' && (n) != '\n')) //to try to handle mac line endings, especially if they're in the middle of a line enum { UTF8_RAW, UTF8_BOM, UTF_ANSI, UTF16LE, UTF16BE, UTF32LE, UTF32BE, }; #endif