fteqw/engine/common/qvm.c

1120 lines
22 KiB
C

/*************************************************************************
** 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
#ifdef _MSC_VER //fix this please
#define inline _inline
#endif
typedef enum vm_type_e
{
VM_NONE,
VM_NATIVE,
VM_BYTECODE
} vm_type_t;
struct vm_s {
// common
vm_type_t type;
char name[MAX_QPATH];
sys_call_t syscall;
sys_callex_t syscallex;
// shared
void *hInst;
// native
int (VARGS *vmMain)(int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6);
};
#ifdef _WIN32
#include "winquake.h"
void *Sys_LoadDLL(const char *name, void **vmMain, int (EXPORT_FN *syscall)(int arg, ... ))
{
void (VARGS *dllEntry)(int (EXPORT_FN *syscall)(int arg, ... ));
char dllname[MAX_OSPATH];
HINSTANCE hVM;
sprintf(dllname, "%sx86.dll", name);
hVM=NULL;
{
char name[MAX_OSPATH];
char *gpath;
// run through the search paths
gpath = NULL;
while (1)
{
gpath = COM_NextPath (gpath);
if (!gpath)
return NULL; // couldn't find one anywhere
_snprintf (name, sizeof(name), "%s/%s", gpath, dllname);
hVM = LoadLibrary (name);
if (hVM)
{
Con_DPrintf ("LoadLibrary (%s)\n",name);
break;
}
}
}
if(!hVM) return NULL;
dllEntry=(void *)GetProcAddress(hVM, "dllEntry");
if(!dllEntry)
{
FreeLibrary(hVM);
return NULL;
}
dllEntry(syscall);
*vmMain=(void *)GetProcAddress(hVM, "vmMain");
if(!*vmMain)
{
FreeLibrary(hVM);
return NULL;
}
return hVM;
}
/*
** Sys_UnloadDLL
*/
void Sys_UnloadDLL(void *handle)
{
if(handle)
{
if(!FreeLibrary((HMODULE)handle))
Sys_Error("Sys_UnloadDLL FreeLibrary failed");
}
}
#else
#ifdef __MORPHOS__
#include <proto/dynload.h>
#else
#include <dlfcn.h>
#endif
void *Sys_LoadDLL(const char *name, void **vmMain, int (EXPORT_FN *syscall)(int arg, ... ))
{
void (*dllEntry)(int (EXPORT_FN *syscall)(int arg, ... ));
char dllname[MAX_OSPATH];
void *hVM;
#ifdef __MORPHOS__
if (DynLoadBase == 0)
return 0;
#endif
sprintf(dllname, "%sx86.so", name);
hVM=NULL;
{
char name[MAX_OSPATH];
char *gpath;
// run through the search paths
gpath = NULL;
while (1)
{
gpath = COM_NextPath (gpath);
if (!gpath)
return NULL; // couldn't find one anywhere
_snprintf (name, sizeof(name), "%s/%s", gpath, dllname);
hVM = dlopen (name, RTLD_NOW);
if (hVM)
{
Con_DPrintf ("dlopen (%s)\n",name);
break;
}
}
}
if(!hVM) return NULL;
dllEntry=(void *)dlsym(hVM, "dllEntry");
if(!dllEntry)
{
dlclose(hVM);
return NULL;
}
(*dllEntry)(syscall);
*vmMain=(void *)dlsym(hVM, "vmMain");
if(!*vmMain)
{
dlclose(hVM);
return NULL;
}
return hVM;
}
/*
** Sys_UnloadDLL
*/
void Sys_UnloadDLL(void *handle)
{
if(handle)
{
if(dlclose(handle))
Sys_Error("Sys_UnloadDLL FreeLibrary failed");
}
}
#endif
// ------------------------- * QVM files * -------------------------
#define VM_MAGIC 0x12721444
#define LL(x) x = LittleLong(x)
#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
} vmHeader_t;
#pragma pack(pop)
// ------------------------- * in memory representation * -------------------------
typedef struct qvm_s
{
// segments
unsigned long *cs; // code segment, each instruction is 2 longs
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 long *pc; // program counter, points to cs, goes up
unsigned long *sp; // stack pointer, initially points to end of ss, goes down
unsigned long bp; // base pointer, initially len_ds+len_ss/2
unsigned long *min_sp;
unsigned long *max_sp;
unsigned long min_bp;
unsigned long 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 long ds_mask; // ds mask (ds+ss)
// memory
unsigned int mem_size;
qbyte *mem_ptr;
// unsigned int cycles; // command cicles executed
sys_callex_t syscall;
} qvm_t;
qvm_t *QVM_Load(const char *name, sys_callex_t syscall);
void QVM_UnLoad(qvm_t *qvm);
int QVM_Exec(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
*/
qvm_t *QVM_Load(const char *name, sys_callex_t syscall)
{
char path[MAX_QPATH];
vmHeader_t *header;
qvm_t *qvm;
qbyte *raw;
int n;
int i;
sprintf(path, "%s.qvm", name);
raw = COM_LoadMallocFile(path);
// FS_LoadFile(path, &raw, false);
// file not found
if(!raw) return NULL;
header=(vmHeader_t*)raw;
LL(header->vmMagic);
LL(header->instructionCount);
LL(header->codeOffset);
LL(header->codeLength);
LL(header->dataOffset);
LL(header->dataLength);
LL(header->litLength);
LL(header->bssLength);
// check file
if(header->vmMagic!=VM_MAGIC || header->instructionCount<=0 || header->codeLength<=0)
{
BZ_Free(raw);
return NULL;
}
// 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 < 31; i++)
{
if ((1<<i) >= qvm->ds_mask) //is this bit greater than our minimum?
break;
}
qvm->len_ss = (1<<i) - qvm->len_ds*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(long)*2 + qvm->ds_mask;
qvm->mem_ptr=Z_Malloc(qvm->mem_size);
// set pointers
qvm->cs=(long*)qvm->mem_ptr;
qvm->ds=(qbyte*)(qvm->mem_ptr+qvm->len_cs*sizeof(long)*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=(long*)(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 = (long*)(qvm->ds+qvm->len_ds+qvm->len_ss/2);
qvm->max_sp = (long*)(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;
long *dst=qvm->cs;
int total=header->instructionCount;
qvm_op_t op;
for(n=0; n<total; n++)
{
op=*src++;
*dst++=(long)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(*(long*)src);
src+=4;
break;
case OP_ARG:
*dst++=(long)*src++;
break;
default:
*dst++=0;
break;
}
}
*dst++=OP_BREAK; //in case someone 'forgot' the return on the last function.
*dst++=0;
}
// load data segment
{
long *src=(long*)(raw+header->dataOffset);
long *dst=(long*)qvm->ds;
int total=header->dataLength/4;
for(n=0; n<total; n++)
*dst++=LittleLong(*src++);
memcpy(dst, src, header->litLength);
}
BZ_Free(raw);
return qvm;
}
/*
** QVM_UnLoad
*/
void QVM_UnLoad(qvm_t *qvm)
{
Z_Free(qvm->mem_ptr);
Z_Free(qvm);
}
// ------------------------- * private execution stuff * -------------------------
/*
** QVM_Goto
*/
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
*/
static void inline 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
{
long *fp;
fp=(long*)(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]=(long)(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
*/
static void inline QVM_Enter(qvm_t *vm, long size)
{
long *fp;
vm->bp-=size;
if(vm->bp<vm->min_bp)
Sys_Error("VM run time error: out of stack\n");
fp=(long*)(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
*/
static void inline QVM_Return(qvm_t *vm, long size)
{
long *fp;
fp=(long*)(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(fp[1]>=vm->len_cs*2)
if (vm->cs+fp[1]) //this being false causes the program to quit.
Sys_Error("VM run time error: program returned to hyperspace\n");
if(fp[1]<0)
if (vm->cs+fp[1])
Sys_Error("VM run time error: program returned to negative hyperspace\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
}
// ------------------------- * execution * -------------------------
/*
** VM_Exec
*/
int QVM_Exec(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 long param;
long *fp;
unsigned long *oldpc;
oldpc = qvm->pc;
// setup execution environment
qvm->pc=NULL;
// qvm->cycles=0;
// prepare local stack
qvm->bp -= 15*4; //we have to do this each call for the sake of (reliable) recursion.
fp=(long*)(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
*(int*)NULL=-1;
break;
// subroutines
case OP_ENTER:
QVM_Enter(qvm, param);
break;
case OP_LEAVE:
QVM_Return(qvm, param);
if (!qvm->pc)
{
// pick return value from 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 long*)&qvm->sp[1]<*(signed long*)&qvm->sp[0]) QVM_Goto(qvm, param);
POP(2);
break;
case OP_LEI:
if(*(signed long*)&qvm->sp[1]<=*(signed long*)&qvm->sp[0]) QVM_Goto(qvm, param);
POP(2);
break;
case OP_GTI:
if(*(signed long*)&qvm->sp[1]>*(signed long*)&qvm->sp[0]) QVM_Goto(qvm, param);
POP(2);
break;
case OP_GEI:
if(*(signed long*)&qvm->sp[1]>=*(signed long*)&qvm->sp[0]) QVM_Goto(qvm, param);
POP(2);
break;
case OP_LTU:
if(*(unsigned long*)&qvm->sp[1]<*(unsigned long*)&qvm->sp[0]) QVM_Goto(qvm, param);
POP(2);
break;
case OP_LEU:
if(*(unsigned long*)&qvm->sp[1]<=*(unsigned long*)&qvm->sp[0]) QVM_Goto(qvm, param);
POP(2);
break;
case OP_GTU:
if(*(unsigned long*)&qvm->sp[1]>*(unsigned long*)&qvm->sp[0]) QVM_Goto(qvm, param);
POP(2);
break;
case OP_GEU:
if(*(unsigned long*)&qvm->sp[1]>=*(unsigned long*)&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 long*)&qvm->sp[0]=*(unsigned char*)&qvm->ds[qvm->sp[0]&qvm->ds_mask];
break;
case OP_LOAD2:
*(unsigned long*)&qvm->sp[0]=*(unsigned short*)&qvm->ds[qvm->sp[0]&qvm->ds_mask];
break;
case OP_LOAD4:
*(unsigned long*)&qvm->sp[0]=*(unsigned long*)&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 long*)&qvm->ds[qvm->sp[1]&qvm->ds_mask]=*(unsigned long*)&qvm->sp[0];
POP(2);
break;
case OP_ARG:
*(unsigned long*)&qvm->ds[(param+qvm->bp)&qvm->ds_mask]=*(unsigned long*)&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 long*)&qvm->sp[0]&0x80) *(signed long*)&qvm->sp[0]|=0xFFFFFF00;
break;
case OP_SEX16:
if(*(signed long*)&qvm->sp[0]&0x8000) *(signed long*)&qvm->sp[0]|=0xFFFF0000;
break;
case OP_NEGI:
*(signed long*)&qvm->sp[0]=-*(signed long*)&qvm->sp[0];
break;
case OP_ADD:
*(signed long*)&qvm->sp[1]+=*(signed long*)&qvm->sp[0];
POP(1);
break;
case OP_SUB:
*(signed long*)&qvm->sp[1]-=*(signed long*)&qvm->sp[0];
POP(1);
break;
case OP_DIVI:
*(signed long*)&qvm->sp[1]/=*(signed long*)&qvm->sp[0];
POP(1);
break;
case OP_DIVU:
*(unsigned long*)&qvm->sp[1]/=(*(unsigned long*)&qvm->sp[0]);
POP(1);
break;
case OP_MODI:
*(signed long*)&qvm->sp[1]%=*(signed long*)&qvm->sp[0];
POP(1);
break;
case OP_MODU:
*(unsigned long*)&qvm->sp[1]%=(*(unsigned long*)&qvm->sp[0]);
POP(1);
break;
case OP_MULI:
*(signed long*)&qvm->sp[1]*=*(signed long*)&qvm->sp[0];
POP(1);
break;
case OP_MULU:
*(unsigned long*)&qvm->sp[1]*=(*(unsigned long*)&qvm->sp[0]);
POP(1);
break;
// logic
case OP_BAND:
*(unsigned long*)&qvm->sp[1]&=*(unsigned long*)&qvm->sp[0];
POP(1);
break;
case OP_BOR:
*(unsigned long*)&qvm->sp[1]|=*(unsigned long*)&qvm->sp[0];
POP(1);
break;
case OP_BXOR:
*(unsigned long*)&qvm->sp[1]^=*(unsigned long*)&qvm->sp[0];
POP(1);
break;
case OP_BCOM:
*(unsigned long*)&qvm->sp[0]=~*(unsigned long*)&qvm->sp[0];
break;
case OP_LSH:
*(unsigned long*)&qvm->sp[1]<<=*(unsigned long*)&qvm->sp[0];
POP(1);
break;
case OP_RSHI:
*(signed long*)&qvm->sp[1]>>=*(signed long*)&qvm->sp[0];
POP(1);
break;
case OP_RSHU:
*(unsigned long*)&qvm->sp[1]>>=*(unsigned long*)&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 long)qvm->sp[0];
break;
case OP_CVFI:
*(signed long*)&qvm->sp[0]=(signed long)(*(float*)&qvm->sp[0]);
break;
}
}
}
// ------------------------- * interface * -------------------------
/*
** VM_PrintInfo
*/
void VM_PrintInfo(vm_t *vm)
{
qvm_t *qvm;
if(!vm->name[0]) return;
Con_Printf("%s: ", vm->name);
switch(vm->type)
{
case VM_NATIVE:
Con_Printf("native\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;
}
}
/*
** VM_Create
*/
vm_t *VM_Create(vm_t *vm, const char *name, sys_call_t syscall, sys_callex_t syscallex)
{
if(!name || !*name || !syscall || !syscallex)
Sys_Error("VM_Create: bad parms");
if (!vm)
vm = Z_Malloc(sizeof(vm_t));
// prepare vm struct
memset(vm, 0, sizeof(vm_t));
Q_strncpyz(vm->name, name, sizeof(vm->name));
vm->syscall=syscall;
vm->syscallex=syscallex;
if (!COM_CheckParm("-nodlls") && !COM_CheckParm("-nosos")) //:)
{
if((vm->hInst=Sys_LoadDLL(name, (void**)&vm->vmMain, syscall)))
{
Con_DPrintf("Creating native machine \"%s\"\n", name);
vm->type=VM_NATIVE;
return vm;
}
}
if((vm->hInst=QVM_Load(name, syscallex)))
{
Con_DPrintf("Creating virtual machine \"%s\"\n", name);
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) Sys_UnloadDLL(vm->hInst);
break;
case VM_BYTECODE:
if(vm->hInst) QVM_UnLoad(vm->hInst);
break;
case VM_NONE:
break;
}
Z_Free(vm);
}
/*
** VM_Restart
*/
qboolean VM_Restart(vm_t *vm)
{
char name[MAX_QPATH];
sys_call_t syscall;
sys_callex_t syscallex;
if(!vm) return false;
// save params
Q_strncpyz(name, vm->name, sizeof(name));
syscall=vm->syscall;
syscallex=vm->syscallex;
// restart
switch(vm->type)
{
case VM_NATIVE:
if(vm->hInst) Sys_UnloadDLL(vm->hInst);
break;
case VM_BYTECODE:
if(vm->hInst) QVM_UnLoad(vm->hInst);
break;
case VM_NONE:
break;
}
return VM_Create(vm, name, syscall, syscallex)!=NULL;
}
void *VM_MemoryBase(vm_t *vm)
{
switch(vm->type)
{
case VM_NATIVE:
return NULL;
case VM_BYTECODE:
return ((qvm_t*)vm->hInst)->ds;
default:
return NULL;
}
}
/*
** VM_Call
*/
int VARGS VM_Call(vm_t *vm, int instruction, ...)
{
va_list argptr;
int arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7;
if(!vm) Sys_Error("VM_Call with NULL vm");
va_start(argptr, instruction);
arg0=va_arg(argptr, int);
arg1=va_arg(argptr, int);
arg2=va_arg(argptr, int);
arg3=va_arg(argptr, int);
arg4=va_arg(argptr, int);
arg5=va_arg(argptr, int);
arg6=va_arg(argptr, int);
arg7=va_arg(argptr, int);
va_end(argptr);
switch(vm->type)
{
case VM_NATIVE:
return vm->vmMain(instruction, arg0, arg1, arg2, arg3, arg4, arg5, arg6);
case VM_BYTECODE:
return QVM_Exec(vm->hInst, instruction, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
case VM_NONE:
return 0;
}
return 0;
}
#endif