/************************************************************************* ** QVM ** Copyright (C) 2003 by DarkOne ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public License ** as published by the Free Software Foundation; either version 2 ** of the License, or (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ************************************************************************** ** Quake3 compatible virtual machine *************************************************************************/ /* spike's changes. masks are now done by modulus rather than and VM_POINTER contains a mask check. ds_mask is set to the mem allocated for stack and data. builtins range check written to buffers. QVM_Step placed in QVM_Exec for efficiency. an invalid statement was added at the end of the statements to prevent the qvm walking off. stack pops/pushes are all tested. An extra stack entry was faked to prevent stack checks on double-stack operators from overwriting. Fixme: there is always the possibility that I missed a potential virus loophole.. Also, can efficiency be improved much? */ #include "quakedef.h" #ifdef VM_ANY #if defined(_MSC_VER) //fix this please #ifdef inline #undef inline #endif #define inline __forceinline #endif #define RETURNOFFSETMARKER NULL typedef enum vm_type_e { VM_NONE, VM_NATIVE, VM_BYTECODE, VM_BUILTIN } vm_type_t; struct vm_s { // common vm_type_t type; char filename[MAX_OSPATH]; sys_calldll_t syscalldll; sys_callqvm_t syscallqvm; // shared void *hInst; // native qintptr_t (EXPORT_FN *vmMain)(qintptr_t command, qintptr_t arg0, qintptr_t arg1, qintptr_t arg2, qintptr_t arg3, qintptr_t arg4, qintptr_t arg5, qintptr_t arg6, qintptr_t arg7); }; //this is a bit weird. qvm plugins always come from $basedir/$mod/plugins/$foo.qvm //but native plugins never come from $basedir/$mod/ - too many other engines blindly allow dll downloads etc. Its simply far too insecure if people use other engines. //q3 gamecode allows it however. yes you could probably get fte to connect via q3 instead and get such a dll. qboolean QVM_LoadDLL(vm_t *vm, const char *name, qboolean binroot, void **vmMain, sys_calldll_t syscall) { void (EXPORT_FN *dllEntry)(sys_calldll_t syscall); char dllname_archpri[MAX_OSPATH]; //id compatible #ifdef ARCH_ALTCPU_POSTFIX char dllname_archsec[MAX_OSPATH]; //id compatible #endif char dllname_anycpu[MAX_OSPATH];//simple dllhandle_t *hVM; char fname[MAX_OSPATH*2]; char gpath[MAX_OSPATH]; void *iterator; dllfunction_t funcs[] = { {(void*)&dllEntry, "dllEntry"}, {(void*)vmMain, "vmMain"}, {NULL, NULL}, }; snprintf(dllname_archpri, sizeof(dllname_archpri), "%s"ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, name); #ifdef ARCH_ALTCPU_POSTFIX snprintf(dllname_archsec, sizeof(dllname_archsec), "%s"ARCH_ALTCPU_POSTFIX ARCH_DL_POSTFIX, name); #endif snprintf(dllname_anycpu, sizeof(dllname_anycpu), "%s" ARCH_DL_POSTFIX, name); hVM=NULL; *fname = 0; if (binroot) { Con_DPrintf("Attempting to load native library: %s\n", name); if (!hVM && FS_NativePath(dllname_archpri, FS_BINARYPATH, fname, sizeof(fname))) hVM = Sys_LoadLibrary(fname, funcs); if (!hVM && FS_NativePath(dllname_anycpu, FS_BINARYPATH, fname, sizeof(fname))) hVM = Sys_LoadLibrary(fname, funcs); if (!hVM && FS_NativePath(dllname_archpri, FS_ROOT, fname, sizeof(fname))) hVM = Sys_LoadLibrary(fname, funcs); if (!hVM && FS_NativePath(dllname_anycpu, FS_ROOT, fname, sizeof(fname))) hVM = Sys_LoadLibrary(fname, funcs); // run through the search paths iterator = NULL; while (!hVM && COM_IteratePaths(&iterator, NULL, 0, gpath, sizeof(gpath))) { if (!hVM && FS_NativePath(va("%s_%s_"ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, name, gpath), FS_BINARYPATH, fname, sizeof(fname))) { Con_DLPrintf(2, "Loading native: %s\n", fname); hVM = Sys_LoadLibrary(fname, funcs); } if (!hVM && FS_NativePath(va("%s_%s"ARCH_DL_POSTFIX, name, gpath), FS_BINARYPATH, fname, sizeof(fname))) { Con_DLPrintf(2, "Loading native: %s\n", fname); hVM = Sys_LoadLibrary(fname, funcs); } if (!hVM && FS_NativePath(va("%s_%s_"ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, name, gpath), FS_ROOT, fname, sizeof(fname))) { Con_DLPrintf(2, "Loading native: %s\n", fname); hVM = Sys_LoadLibrary(fname, funcs); } if (!hVM && FS_NativePath(va("%s_%s"ARCH_DL_POSTFIX, name, gpath), FS_ROOT, fname, sizeof(fname))) { Con_DLPrintf(2, "Loading native: %s\n", fname); hVM = Sys_LoadLibrary(fname, funcs); } } } else { Con_DPrintf("Attempting to load (unsafe) native library: %s\n", name); // run through the search paths iterator = NULL; while (!hVM && COM_IteratePaths(&iterator, gpath, sizeof(gpath), NULL, 0)) { if (!hVM) { snprintf (fname, sizeof(fname), "%s%s", gpath, dllname_archpri); Con_DLPrintf(2, "Loading native: %s\n", fname); hVM = Sys_LoadLibrary(fname, funcs); } #ifdef ARCH_ALTCPU_POSTFIX if (!hVM) { snprintf (fname, sizeof(fname), "%s%s", gpath, dllname_archsec); Con_DLPrintf(2, "Loading native: %s\n", fname); hVM = Sys_LoadLibrary(fname, funcs); } #endif if (!hVM) { snprintf (fname, sizeof(fname), "%s%s", gpath, dllname_anycpu); Con_DLPrintf(2, "Loading native: %s\n", fname); hVM = Sys_LoadLibrary(fname, funcs); } } } if(!hVM) return false; Q_strncpyz(vm->filename, fname, sizeof(vm->filename)); vm->hInst = hVM; (*dllEntry)(syscall); return true; } /* ** Sys_UnloadDLL */ void QVM_UnloadDLL(dllhandle_t *handle) { if(handle) { Sys_CloseLibrary(handle); } } // ------------------------- * QVM files * ------------------------- #define VM_MAGIC 0x12721444 #define VM_MAGIC2 0x12721445 #pragma pack(push,1) typedef struct vmHeader_s { int vmMagic; int instructionCount; int codeOffset; int codeLength; int dataOffset; int dataLength; // should be byteswapped on load int litLength; // copy as is int bssLength; // zero filled memory appended to datalength //valid only in V2. int jtrgLength; // number of jump table targets } vmHeader_t; #pragma pack(pop) // ------------------------- * in memory representation * ------------------------- typedef struct qvm_s { // segments unsigned int *cs; // code segment, each instruction is 2 ints qbyte *ds; // data segment, partially filled on load (data, lit, bss) qbyte *ss; // stack segment, (follows immediatly after ds, corrupting data before vm) // pointer registers unsigned int *pc; // program counter, points to cs, goes up unsigned int *sp; // stack pointer, initially points to end of ss, goes down unsigned int bp; // base pointer, initially len_ds+len_ss/2 unsigned int *min_sp; unsigned int *max_sp; unsigned int min_bp; unsigned int max_bp; // status unsigned int len_cs; // size of cs unsigned int len_ds; // size of ds unsigned int len_ss; // size of ss unsigned int ds_mask; // ds mask (ds+ss) // memory unsigned int mem_size; qbyte *mem_ptr; // unsigned int cycles; // command cicles executed sys_callqvm_t syscall; } qvm_t; qboolean QVM_LoadVM(vm_t *vm, const char *name, sys_callqvm_t syscall); void QVM_UnLoadVM(qvm_t *qvm); int QVM_ExecVM(qvm_t *qvm, int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7); // ------------------------- * OP.CODES * ------------------------- typedef enum qvm_op_e { OP_UNDEF, OP_NOP, OP_BREAK, OP_ENTER, // b32 OP_LEAVE, // b32 OP_CALL, OP_PUSH, OP_POP, OP_CONST, // b32 OP_LOCAL, // b32 OP_JUMP, // ------------------- OP_EQ, // b32 OP_NE, // b32 OP_LTI, // b32 OP_LEI, // b32 OP_GTI, // b32 OP_GEI, // b32 OP_LTU, // b32 OP_LEU, // b32 OP_GTU, // b32 OP_GEU, // b32 OP_EQF, // b32 OP_NEF, // b32 OP_LTF, // b32 OP_LEF, // b32 OP_GTF, // b32 OP_GEF, // b32 // ------------------- OP_LOAD1, OP_LOAD2, OP_LOAD4, OP_STORE1, OP_STORE2, OP_STORE4, OP_ARG, // b8 OP_BLOCK_COPY, // b32 //------------------- OP_SEX8, OP_SEX16, OP_NEGI, OP_ADD, OP_SUB, OP_DIVI, OP_DIVU, OP_MODI, OP_MODU, OP_MULI, OP_MULU, OP_BAND, OP_BOR, OP_BXOR, OP_BCOM, OP_LSH, OP_RSHI, OP_RSHU, OP_NEGF, OP_ADDF, OP_SUBF, OP_DIVF, OP_MULF, OP_CVIF, OP_CVFI } qvm_op_t; // ------------------------- * Init & ShutDown * ------------------------- /* ** QVM_Load */ qboolean QVM_LoadVM(vm_t *vm, const char *name, sys_callqvm_t syscall) { char path[MAX_QPATH]; vmHeader_t header, *srcheader; qvm_t *qvm; qbyte *raw; int n; unsigned int i; Q_snprintfz(path, sizeof(path), "%s.qvm", name); FS_LoadFile(path, (void **)&raw); // file not found if(!raw) return false; srcheader=(vmHeader_t*)raw; header.vmMagic = LittleLong(srcheader->vmMagic); header.instructionCount = LittleLong(srcheader->instructionCount); header.codeOffset = LittleLong(srcheader->codeOffset); header.codeLength = LittleLong(srcheader->codeLength); header.dataOffset = LittleLong(srcheader->dataOffset); header.dataLength = LittleLong(srcheader->dataLength); header.litLength = LittleLong(srcheader->litLength); header.bssLength = LittleLong(srcheader->bssLength); if (header.vmMagic==VM_MAGIC2) { //version2 cotains a jump table of sorts //it is redundant information and can be ignored //its also more useful for jit rather than bytecode header.jtrgLength = LittleLong(srcheader->jtrgLength); } // check file if( (header.vmMagic!=VM_MAGIC && header.vmMagic!=VM_MAGIC2) || header.instructionCount<=0 || header.codeLength<=0) { Con_Printf("%s: invalid qvm file\n", name); FS_FreeFile(raw); return false; } // create vitrual machine qvm=Z_Malloc(sizeof(qvm_t)); qvm->len_cs=header.instructionCount+1; //bad opcode padding. qvm->len_ds=header.dataLength+header.litLength+header.bssLength; qvm->len_ss=256*1024; // 256KB stack space // memory qvm->ds_mask = qvm->len_ds*sizeof(qbyte)+(qvm->len_ss+16*4)*sizeof(qbyte);//+4 for a stack check decrease for (i = 0; i < sizeof(qvm->ds_mask)*8-1; i++) { if ((1<= qvm->ds_mask) //is this bit greater than our minimum? break; } qvm->len_ss = (1<len_ds*(int)sizeof(qbyte) - 4; //expand the stack space to fill it. qvm->ds_mask = qvm->len_ds*sizeof(qbyte)+(qvm->len_ss+4)*sizeof(qbyte); qvm->len_ss -= qvm->len_ss&7; qvm->mem_size=qvm->len_cs*sizeof(int)*2 + qvm->ds_mask; qvm->mem_ptr=Z_Malloc(qvm->mem_size); // set pointers qvm->cs=(unsigned int*)qvm->mem_ptr; qvm->ds=(qbyte*)(qvm->mem_ptr+qvm->len_cs*sizeof(int)*2); qvm->ss=(qbyte*)((qbyte*)qvm->ds+qvm->len_ds*sizeof(qbyte)); //waste 32 bits here. //As the opcodes often check stack 0 and 1, with a backwards stack, 1 can leave the stack area. This is where we compensate for it. // setup registers qvm->pc=qvm->cs; qvm->sp=(unsigned int*)(qvm->ss+qvm->len_ss); qvm->bp=qvm->len_ds+qvm->len_ss/2; // qvm->cycles=0; qvm->syscall=syscall; qvm->ds_mask--; qvm->min_sp = (unsigned int*)(qvm->ds+qvm->len_ds+qvm->len_ss/2); qvm->max_sp = (unsigned int*)(qvm->ds+qvm->len_ds+qvm->len_ss); qvm->min_bp = qvm->len_ds; qvm->max_bp = qvm->len_ds+qvm->len_ss/2; qvm->bp = qvm->max_bp; // load instructions { qbyte *src=raw+header.codeOffset; int *dst=(int*)qvm->cs; int total=header.instructionCount; qvm_op_t op; for(n=0; nds; int total=header.dataLength/4; for(n=0; nfilename, path, sizeof(vm->filename)); vm->hInst = qvm; return true; } /* ** QVM_UnLoad */ void QVM_UnLoadVM(qvm_t *qvm) { Z_Free(qvm->mem_ptr); Z_Free(qvm); } // ------------------------- * private execution stuff * ------------------------- /* ** QVM_Goto (inlined this the old fashioned way) */ #define QVM_Goto(vm,addr) \ do{ \ if(addr<0 || addr>vm->len_cs) \ Sys_Error("VM run time error: program jumped off to hyperspace\n"); \ vm->pc=vm->cs+addr*2; \ } while(0) //static void inline QVM_Goto(qvm_t *vm, int addr) //{ // if(addr<0 || addr>vm->len_cs) // Sys_Error("VM run time error: program jumped off to hyperspace\n"); // vm->pc=vm->cs+addr*2; //} /* ** QVM_Call ** ** calls function */ inline static void QVM_Call(qvm_t *vm, int addr) { vm->sp--; if (vm->sp < vm->min_sp) Sys_Error("QVM Stack underflow"); if(addr<0) { // system trap function { int *fp; fp=(int*)(vm->ds+vm->bp)+2; vm->sp[0] = vm->syscall(vm->ds, vm->ds_mask, -addr-1, fp); return; } } if(addr>=vm->len_cs) Sys_Error("VM run time error: program jumped off to hyperspace\n"); vm->sp[0]=(vm->pc-vm->cs); // push pc /return address/ vm->pc=vm->cs+addr*2; if (!vm->pc) Sys_Error("VM run time error: program called the void\n"); } /* ** QVM_Enter ** ** [oPC][0][.......]| <- oldBP ** ^BP */ inline static void QVM_Enter(qvm_t *vm, int size) { int *fp; vm->bp-=size; if(vm->bpmin_bp) Sys_Error("VM run time error: out of stack\n"); fp=(int*)(vm->ds+vm->bp); fp[0]=vm->sp-vm->max_sp; // unknown /maybe size/ fp[1]=*vm->sp++; // saved PC if (vm->sp > vm->max_sp) Sys_Error("QVM Stack overflow"); } /* ** QVM_Return ** returns failure when returning to the engine. */ inline static qboolean QVM_Return(qvm_t *vm, int size) { int *fp; fp=(int*)(vm->ds+vm->bp); vm->bp+=size; if(vm->bp>vm->max_bp) Sys_Error("VM run time error: freed too much stack\n"); if (vm->sp-vm->max_sp != fp[0]) Sys_Error("VM run time error: stack push/pop mismatch \n"); vm->pc=vm->cs+fp[1]; // restore PC if((unsigned int)fp[1]>=(unsigned int)(vm->len_cs*2)) //explicit casts to make sure the C compiler can't make assumptions about overflows. { if (fp[1] == -1) return false; //return to engine. Sys_Error("VM run time error: program returned to hyperspace (%p, %#x)\n", (char*)vm->cs, fp[1]); } return true; } // ------------------------- * execution * ------------------------- /* ** VM_Exec */ int QVM_ExecVM(register qvm_t *qvm, int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7) { //remember that the stack is backwards. push takes 1. //FIXME: does it matter that our stack pointer (qvm->sp) is backwards compared to q3? //We are more consistant of course, but this simply isn't what q3 does. //all stack shifts in this function are referenced through these 2 macros. #define POP(t) qvm->sp+=t;if (qvm->sp > qvm->max_sp) Sys_Error("QVM Stack underflow"); #define PUSH(v) qvm->sp--;if (qvm->sp < qvm->min_sp) Sys_Error("QVM Stack overflow");*qvm->sp=v qvm_op_t op=-1; unsigned int param; int *fp; unsigned int *oldpc; oldpc = qvm->pc; // setup execution environment qvm->pc=qvm->cs-1; // qvm->cycles=0; // prepare local stack qvm->bp -= 15*4; //we have to do this each call for the sake of (reliable) recursion. fp=(int*)(qvm->ds+qvm->bp); // push all params fp[0]=0; fp[1]=0; fp[2]=command; fp[3]=arg0; fp[4]=arg1; fp[5]=arg2; fp[6]=arg3; fp[7]=arg4; fp[8]=arg5; fp[9]=arg6; fp[10]=arg7; // arg7; fp[11]=0; // arg8; fp[12]=0; // arg9; fp[13]=0; // arg10; fp[14]=0; // arg11; QVM_Call(qvm, 0); for(;;) { // fetch next command op=*qvm->pc++; param=*qvm->pc++; // qvm->cycles++; switch(op) { // aux case OP_UNDEF: case OP_NOP: break; default: case OP_BREAK: // break to debugger Sys_Error("VM hit an OP_BREAK opcode"); break; // subroutines case OP_ENTER: QVM_Enter(qvm, param); break; case OP_LEAVE: if (!QVM_Return(qvm, param)) { // pick return value from C stack qvm->pc = oldpc; qvm->bp += 15*4; // if(qvm->bp!=qvm->max_bp) // Sys_Error("VM run time error: freed too much stack\n"); param = qvm->sp[0]; POP(1); return param; } break; case OP_CALL: param = *qvm->sp; POP(1); QVM_Call(qvm, param); break; // stack case OP_PUSH: PUSH(*qvm->sp); break; case OP_POP: POP(1); break; case OP_CONST: PUSH(param); break; case OP_LOCAL: PUSH(param+qvm->bp); break; // branching case OP_JUMP: param = *qvm->sp; POP(1); QVM_Goto(qvm, param); break; case OP_EQ: if(qvm->sp[1]==qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_NE: if(qvm->sp[1]!=qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_LTI: if(*(signed int*)&qvm->sp[1]<*(signed int*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_LEI: if(*(signed int*)&qvm->sp[1]<=*(signed int*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_GTI: if(*(signed int*)&qvm->sp[1]>*(signed int*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_GEI: if(*(signed int*)&qvm->sp[1]>=*(signed int*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_LTU: if(*(unsigned int*)&qvm->sp[1]<*(unsigned int*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_LEU: if(*(unsigned int*)&qvm->sp[1]<=*(unsigned int*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_GTU: if(*(unsigned int*)&qvm->sp[1]>*(unsigned int*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_GEU: if(*(unsigned int*)&qvm->sp[1]>=*(unsigned int*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_EQF: if(*(float*)&qvm->sp[1]==*(float*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_NEF: if(*(float*)&qvm->sp[1]!=*(float*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_LTF: if(*(float*)&qvm->sp[1]<*(float*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_LEF: if(*(float*)&qvm->sp[1]<=*(float*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_GTF: if(*(float*)&qvm->sp[1]>*(float*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; case OP_GEF: if(*(float*)&qvm->sp[1]>=*(float*)&qvm->sp[0]) QVM_Goto(qvm, param); POP(2); break; // memory I/O: masks protect main memory case OP_LOAD1: *(unsigned int*)&qvm->sp[0]=*(unsigned char*)&qvm->ds[qvm->sp[0]&qvm->ds_mask]; break; case OP_LOAD2: *(unsigned int*)&qvm->sp[0]=*(unsigned short*)&qvm->ds[qvm->sp[0]&qvm->ds_mask]; break; case OP_LOAD4: *(unsigned int*)&qvm->sp[0]=*(unsigned int*)&qvm->ds[qvm->sp[0]&qvm->ds_mask]; break; case OP_STORE1: *(qbyte*)&qvm->ds[qvm->sp[1]&qvm->ds_mask]=(qbyte)(qvm->sp[0]&0xFF); POP(2); break; case OP_STORE2: *(unsigned short*)&qvm->ds[qvm->sp[1]&qvm->ds_mask]=(unsigned short)(qvm->sp[0]&0xFFFF); POP(2); break; case OP_STORE4: *(unsigned int*)&qvm->ds[qvm->sp[1]&qvm->ds_mask]=*(unsigned int*)&qvm->sp[0]; POP(2); break; case OP_ARG: *(unsigned int*)&qvm->ds[(param+qvm->bp)&qvm->ds_mask]=*(unsigned int*)&qvm->sp[0]; POP(1); break; case OP_BLOCK_COPY: if (qvm->sp[1]+param < qvm->ds_mask && qvm->sp[0] + param < qvm->ds_mask) { memmove(qvm->ds+(qvm->sp[1]&qvm->ds_mask), qvm->ds+(qvm->sp[0]&qvm->ds_mask), param); } POP(2); break; // integer arithmetic case OP_SEX8: if(*(signed int*)&qvm->sp[0]&0x80) *(signed int*)&qvm->sp[0]|=0xFFFFFF00; break; case OP_SEX16: if(*(signed int*)&qvm->sp[0]&0x8000) *(signed int*)&qvm->sp[0]|=0xFFFF0000; break; case OP_NEGI: *(signed int*)&qvm->sp[0]=-*(signed int*)&qvm->sp[0]; break; case OP_ADD: *(signed int*)&qvm->sp[1]+=*(signed int*)&qvm->sp[0]; POP(1); break; case OP_SUB: *(signed int*)&qvm->sp[1]-=*(signed int*)&qvm->sp[0]; POP(1); break; case OP_DIVI: *(signed int*)&qvm->sp[1]/=*(signed int*)&qvm->sp[0]; POP(1); break; case OP_DIVU: *(unsigned int*)&qvm->sp[1]/=(*(unsigned int*)&qvm->sp[0]); POP(1); break; case OP_MODI: *(signed int*)&qvm->sp[1]%=*(signed int*)&qvm->sp[0]; POP(1); break; case OP_MODU: *(unsigned int*)&qvm->sp[1]%=(*(unsigned int*)&qvm->sp[0]); POP(1); break; case OP_MULI: *(signed int*)&qvm->sp[1]*=*(signed int*)&qvm->sp[0]; POP(1); break; case OP_MULU: *(unsigned int*)&qvm->sp[1]*=(*(unsigned int*)&qvm->sp[0]); POP(1); break; // logic case OP_BAND: *(unsigned int*)&qvm->sp[1]&=*(unsigned int*)&qvm->sp[0]; POP(1); break; case OP_BOR: *(unsigned int*)&qvm->sp[1]|=*(unsigned int*)&qvm->sp[0]; POP(1); break; case OP_BXOR: *(unsigned int*)&qvm->sp[1]^=*(unsigned int*)&qvm->sp[0]; POP(1); break; case OP_BCOM: *(unsigned int*)&qvm->sp[0]=~*(unsigned int*)&qvm->sp[0]; break; case OP_LSH: *(unsigned int*)&qvm->sp[1]<<=*(unsigned int*)&qvm->sp[0]; POP(1); break; case OP_RSHI: *(signed int*)&qvm->sp[1]>>=*(signed int*)&qvm->sp[0]; POP(1); break; case OP_RSHU: *(unsigned int*)&qvm->sp[1]>>=*(unsigned int*)&qvm->sp[0]; POP(1); break; // floating point arithmetic case OP_NEGF: *(float*)&qvm->sp[0]=-*(float*)&qvm->sp[0]; break; case OP_ADDF: *(float*)&qvm->sp[1]+=*(float*)&qvm->sp[0]; POP(1); break; case OP_SUBF: *(float*)&qvm->sp[1]-=*(float*)&qvm->sp[0]; POP(1); break; case OP_DIVF: *(float*)&qvm->sp[1]/=*(float*)&qvm->sp[0]; POP(1); break; case OP_MULF: *(float*)&qvm->sp[1]*=*(float*)&qvm->sp[0]; POP(1); break; // format conversion case OP_CVIF: *(float*)&qvm->sp[0]=(float)(signed int)qvm->sp[0]; break; case OP_CVFI: *(signed int*)&qvm->sp[0]=(signed int)(*(float*)&qvm->sp[0]); break; } } } // ------------------------- * interface * ------------------------- /* ** VM_PrintInfo */ void VM_PrintInfo(vm_t *vm) { qvm_t *qvm; // Con_Printf("%s (%p): ", vm->name, vm->hInst); Con_Printf("^2%s", vm->filename); switch(vm->type) { case VM_NATIVE: Con_Printf(": native\n"); break; case VM_BUILTIN: Con_Printf(": built in\n"); break; case VM_BYTECODE: Con_Printf(": interpreted\n"); if((qvm=vm->hInst)) { Con_Printf(" code length: %d\n", qvm->len_cs); Con_Printf(" data length: %d\n", qvm->len_ds); Con_Printf(" stack length: %d\n", qvm->len_ss); } break; default: Con_Printf(": unknown\n"); break; } } const char *VM_GetFilename(vm_t *vm) { return vm->filename; } vm_t *VM_CreateBuiltin(const char *name, sys_calldll_t syscalldll, qintptr_t (*init)(qintptr_t *args)) { vm_t *vm = Z_Malloc(sizeof(vm_t)); Q_strncpyz(vm->filename, name, sizeof(vm->filename)); vm->syscalldll = syscalldll; vm->syscallqvm = NULL; vm->hInst = init; vm->type = VM_BUILTIN; return vm; } /* ** VM_Create */ vm_t *VM_Create(const char *dllname, sys_calldll_t syscalldll, const char *qvmname, sys_callqvm_t syscallqvm) { vm_t *vm; vm = Z_Malloc(sizeof(vm_t)); // prepare vm struct memset(vm, 0, sizeof(vm_t)); Q_strncpyz(vm->filename, "", sizeof(vm->filename)); vm->syscalldll=syscalldll; vm->syscallqvm=syscallqvm; if (syscalldll) { if (!COM_CheckParm("-nodlls") && !COM_CheckParm("-nosos")) //:) { if(QVM_LoadDLL(vm, dllname, !syscallqvm, (void**)&vm->vmMain, syscalldll)) { Con_DPrintf("Creating native machine \"%s\"\n", dllname); vm->type=VM_NATIVE; return vm; } } } if (syscallqvm) { if(QVM_LoadVM(vm, qvmname, syscallqvm)) { Con_DPrintf("Creating virtual machine \"%s\"\n", qvmname); vm->type=VM_BYTECODE; return vm; } } Z_Free(vm); return NULL; } /* ** VM_Destroy */ void VM_Destroy(vm_t *vm) { if(!vm) return; switch(vm->type) { case VM_NATIVE: if(vm->hInst) QVM_UnloadDLL(vm->hInst); break; case VM_BYTECODE: if(vm->hInst) QVM_UnLoadVM(vm->hInst); break; case VM_BUILTIN: case VM_NONE: break; } Z_Free(vm); } /* ** VM_Restart */ /*qboolean VM_Restart(vm_t *vm) { char name[MAX_QPATH]; sys_calldll_t syscalldll; sys_callqvm_t syscallqvm; if(!vm) return false; // save params Q_strncpyz(name, vm->name, sizeof(name)); syscalldll=vm->syscalldll; syscallqvm=vm->syscallqvm; // restart switch(vm->type) { case VM_NATIVE: if(vm->hInst) QVM_UnloadDLL(vm->hInst); break; case VM_BYTECODE: if(vm->hInst) QVM_UnLoadVM(vm->hInst); break; case VM_NONE: break; } return VM_Create(vm, name, syscalldll, syscallqvm)!=NULL; }*/ void *VM_MemoryBase(vm_t *vm) { switch(vm->type) { case VM_NATIVE: case VM_BUILTIN: return NULL; case VM_BYTECODE: return ((qvm_t*)vm->hInst)->ds; default: return NULL; } } quintptr_t VM_MemoryMask(vm_t *vm) { switch(vm->type) { case VM_BYTECODE: return ((qvm_t*)vm->hInst)->ds_mask; default: return ~(quintptr_t)0; } } /*returns true if we're running a 32bit vm on a 64bit host (in case we need workarounds)*/ qboolean VM_NonNative(vm_t *vm) { switch(vm->type) { case VM_BYTECODE: return sizeof(int) != sizeof(void*); case VM_NATIVE: case VM_BUILTIN: return false; default: return false; } } /* ** VM_Call */ qintptr_t VARGS VM_Call(vm_t *vm, qintptr_t instruction, ...) { va_list argptr; qintptr_t arg[8]; if(!vm) Sys_Error("VM_Call with NULL vm"); va_start(argptr, instruction); arg[0]=va_arg(argptr, qintptr_t); arg[1]=va_arg(argptr, qintptr_t); arg[2]=va_arg(argptr, qintptr_t); arg[3]=va_arg(argptr, qintptr_t); arg[4]=va_arg(argptr, qintptr_t); arg[5]=va_arg(argptr, qintptr_t); arg[6]=va_arg(argptr, qintptr_t); arg[7]=va_arg(argptr, qintptr_t); va_end(argptr); switch(vm->type) { case VM_NATIVE: return vm->vmMain(instruction, arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6], arg[7]); case VM_BYTECODE: return QVM_ExecVM(vm->hInst, instruction, arg[0]&0xffffffff, arg[1]&0xffffffff, arg[2]&0xffffffff, arg[3]&0xffffffff, arg[4]&0xffffffff, arg[5]&0xffffffff, arg[6]&0xffffffff, arg[7]&0xffffffff); case VM_BUILTIN: if (!instruction) instruction = (qintptr_t)vm->hInst; return ((qintptr_t(*)(qintptr_t*))instruction)(arg); case VM_NONE: return 0; } return 0; } #endif