engine/engine/common/qvm.c

1207 lines
27 KiB
C
Raw Normal View History

/*************************************************************************
** 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);
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},
};
hVM=NULL;
*fname = 0;
if (binroot)
{
//load eg: basedir/module_gamedir_arch.ext
Con_DPrintf("Attempting to load native library: %s\n", name);
dpp7: Treat 'dropped' c2s packets as choked when using dpp7 protocols. This is because the protocol provides no way to disambiguate, and I don't like false reports of packetloss (only reliables loss can be detected, and that's not frequent enough to be meaningful). Pings can still be determined with dpp7, for those few packets which are acked. package manager: reworked to enable/disable plugins when downloaded, which can also be present-but-disabled. package manager: display a confirmation prompt before applying changes. do not allow other changes to be made while applying. prompt may be skipped with 'pkg apply' in dedicated servers. sv: downloads are no longer forced to lower case. sv: added sv_demoAutoCompress cvar. set to 1 to directly record to *.mvd.gz cl: properly support directly playing .mvd.gz files menus: reworked to separate mouse and keyboard focus. mouse focus becomes keyboard focus only on mouse clicks. tooltips follow mouse cursors. menus: cleaned up menu heirachy a little. now simpler. server browser: changed 'hide *' filters to 'show *' instead. I felt it was more logical. deluxmapping: changed to disabled, load, generate, like r_loadlit is. render targets api now supports negative formats to mean nearest filtering, where filtering is part of texture state. drawrotpic fixed, now batches and interacts with drawpic correctly. drawline fixed, no interacts with draw* correctly, but still does not batch. fixed saving games. provide proper userinfo to nq clients, where supported. qcc: catch string table overflows safely, giving errors instead of crashes. switch to 32bit statements if some over-sized function requires it. qtv: some bigcoords support tweaks git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5073 fc73d0e0-1445-4013-8a0c-d673dee63da5
2017-03-20 22:27:07 -07:00
// run through the search paths
iterator = NULL;
while (!hVM && COM_IteratePaths(&iterator, NULL, 0, gpath, sizeof(gpath)))
{
#ifndef ANDROID
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);
}
#endif
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);
}
#ifndef ANDROID
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);
}
#endif
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);
}
}
#ifdef ANDROID
if (!hVM && FS_NativePath(va("%s" ARCH_DL_POSTFIX, name), FS_BINARYPATH, fname, sizeof(fname)))
hVM = Sys_LoadLibrary(fname, funcs);
#else
if (!hVM && FS_NativePath(va("%s_"ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, name), FS_BINARYPATH, fname, sizeof(fname)))
hVM = Sys_LoadLibrary(fname, funcs);
if (!hVM && FS_NativePath(va("%s"ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, name), FS_BINARYPATH, fname, sizeof(fname)))
hVM = Sys_LoadLibrary(fname, funcs);
if (!hVM && FS_NativePath(va("%s" ARCH_DL_POSTFIX, name), FS_BINARYPATH, fname, sizeof(fname)))
hVM = Sys_LoadLibrary(fname, funcs);
if (!hVM && FS_NativePath(va("%s_"ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, name), FS_ROOT, fname, sizeof(fname)))
hVM = Sys_LoadLibrary(fname, funcs);
if (!hVM && FS_NativePath(va("%s"ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, name), FS_ROOT, fname, sizeof(fname)))
hVM = Sys_LoadLibrary(fname, funcs);
if (!hVM && FS_NativePath(va("%s" ARCH_DL_POSTFIX, name), FS_ROOT, fname, sizeof(fname)))
hVM = Sys_LoadLibrary(fname, funcs);
#endif
}
else
{
//load eg: basedir/gamedir/module_arch.ext
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_"ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, gpath, name);
Con_DLPrintf(2, "Loading native: %s\n", fname);
hVM = Sys_LoadLibrary(fname, funcs);
}
if (!hVM)
{
snprintf (fname, sizeof(fname), "%s%s"ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, gpath, name);
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_"ARCH_ALTCPU_POSTFIX ARCH_DL_POSTFIX, gpath, name);
Con_DLPrintf(2, "Loading native: %s\n", fname);
hVM = Sys_LoadLibrary(fname, funcs);
}
if (!hVM)
{
snprintf (fname, sizeof(fname), "%s%s"ARCH_ALTCPU_POSTFIX ARCH_DL_POSTFIX, gpath, name);
Con_DLPrintf(2, "Loading native: %s\n", fname);
hVM = Sys_LoadLibrary(fname, funcs);
}
#endif
if (!hVM)
{
snprintf (fname, sizeof(fname), "%s%s"ARCH_DL_POSTFIX, gpath, name);
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<<i) >= qvm->ds_mask) //is this bit greater than our minimum?
break;
}
qvm->len_ss = (1<<i) - qvm->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; n<total; n++)
{
op=*src++;
*dst++=(int)op;
switch(op)
{
case OP_ENTER:
case OP_LEAVE:
case OP_CONST:
case OP_LOCAL:
case OP_EQ:
case OP_NE:
case OP_LTI:
case OP_LEI:
case OP_GTI:
case OP_GEI:
case OP_LTU:
case OP_LEU:
case OP_GTU:
case OP_GEU:
case OP_EQF:
case OP_NEF:
case OP_LTF:
case OP_LEF:
case OP_GTF:
case OP_GEF:
case OP_BLOCK_COPY:
*dst++=LittleLong(*(int*)src);
src+=4;
break;
case OP_ARG:
*dst++=(int)*src++;
break;
default:
*dst++=0;
break;
}
}
*dst++=OP_BREAK; //in case someone 'forgot' the return on the last function.
*dst++=0;
}
// load data segment
{
int *src=(int*)(raw+header.dataOffset);
int *dst=(int*)qvm->ds;
int total=header.dataLength/4;
for(n=0; n<total; n++)
*dst++=LittleLong(*src++);
memcpy(dst, src, header.litLength);
}
FS_FreeFile(raw);
Q_strncpyz(vm->filename, 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->bp<vm->min_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