r188 - in trunk/code: game qcommon unix
DONOTREPLY at icculus.org
DONOTREPLY at icculus.org
Thu Oct 27 17:13:48 EDT 2005
Author: ludwig
Date: 2005-10-27 17:13:47 -0400 (Thu, 27 Oct 2005)
New Revision: 188
Added:
trunk/code/qcommon/vm_x86_64.c
Modified:
trunk/code/game/bg_lib.c
trunk/code/game/bg_lib.h
trunk/code/qcommon/cvar.c
trunk/code/qcommon/qcommon.h
trunk/code/qcommon/vm.c
trunk/code/unix/Makefile
trunk/code/unix/unix_main.c
Log:
add x86_64 vm. experimental, not enabled by default. you need as for it
to work.
Modified: trunk/code/game/bg_lib.c
===================================================================
--- trunk/code/game/bg_lib.c 2005-10-26 23:44:28 UTC (rev 187)
+++ trunk/code/game/bg_lib.c 2005-10-27 21:13:47 UTC (rev 188)
@@ -38,6 +38,8 @@
* SUCH DAMAGE.
*/
+#include "bg_lib.h"
+
#if defined(LIBC_SCCS) && !defined(lint)
#if 0
static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93";
Modified: trunk/code/game/bg_lib.h
===================================================================
--- trunk/code/game/bg_lib.h 2005-10-26 23:44:28 UTC (rev 187)
+++ trunk/code/game/bg_lib.h 2005-10-27 21:13:47 UTC (rev 188)
@@ -23,7 +23,13 @@
// compiled for the virtual machine
// This file is NOT included on native builds
+#ifndef BG_LIB_H
+#define BG_LIB_H
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
typedef int size_t;
typedef char * va_list;
@@ -89,3 +95,4 @@
double fabs( double x );
double acos( double x );
+#endif // BG_LIB_H
Modified: trunk/code/qcommon/cvar.c
===================================================================
--- trunk/code/qcommon/cvar.c 2005-10-26 23:44:28 UTC (rev 187)
+++ trunk/code/qcommon/cvar.c 2005-10-27 21:13:47 UTC (rev 188)
@@ -286,7 +286,7 @@
cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ) {
cvar_t *var;
- Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value );
+// Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value );
if ( !Cvar_ValidateString( var_name ) ) {
Com_Printf("invalid cvar name string: %s\n", var_name );
Modified: trunk/code/qcommon/qcommon.h
===================================================================
--- trunk/code/qcommon/qcommon.h 2005-10-26 23:44:28 UTC (rev 187)
+++ trunk/code/qcommon/qcommon.h 2005-10-27 21:13:47 UTC (rev 188)
@@ -547,6 +547,8 @@
qboolean FS_FileExists( const char *file );
+char *FS_BuildOSPath( const char *base, const char *game, const char *qpath );
+
int FS_LoadStack( void );
int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize );
@@ -603,7 +605,7 @@
void FS_Flush( fileHandle_t f );
-void QDECL FS_Printf( fileHandle_t f, const char *fmt, ... );
+void QDECL FS_Printf( fileHandle_t f, const char *fmt, ... ) __attribute__ ((format (printf, 2, 3)));
// like fprintf
int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode );
Modified: trunk/code/qcommon/vm.c
===================================================================
--- trunk/code/qcommon/vm.c 2005-10-26 23:44:28 UTC (rev 187)
+++ trunk/code/qcommon/vm.c 2005-10-27 21:13:47 UTC (rev 188)
@@ -745,12 +745,15 @@
args[4], args[5], args[6], args[7],
args[8], args[9], args[10], args[11],
args[12], args[13], args[14], args[15]);
+ } else {
+#ifdef __i386__ // i386 calling convention doesn't need conversion
#if defined(HAVE_VM_COMPILED)
- } else if ( vm->compiled ) {
- // only used on 32bit machines so this cast is fine
- r = VM_CallCompiled( vm, (int*)&callnum );
+ if ( vm->compiled )
+ r = VM_CallCompiled( vm, (int*)callnum );
+ else
#endif
- } else {
+ r = VM_CallInterpreted( vm, (int*)callnum );
+#else
struct {
int callnum;
int args[16];
@@ -763,7 +766,13 @@
a.args[i] = va_arg(ap, long);
}
va_end(ap);
- r = VM_CallInterpreted( vm, &a.callnum );
+#if defined(HAVE_VM_COMPILED)
+ if ( vm->compiled )
+ r = VM_CallCompiled( vm, &a.callnum );
+ else
+#endif
+ r = VM_CallInterpreted( vm, &a.callnum );
+#endif
}
if ( oldVM != NULL ) // bk001220 - assert(currentVM!=NULL) for oldVM==NULL
Added: trunk/code/qcommon/vm_x86_64.c
===================================================================
--- trunk/code/qcommon/vm_x86_64.c 2005-10-26 23:44:28 UTC (rev 187)
+++ trunk/code/qcommon/vm_x86_64.c 2005-10-27 21:13:47 UTC (rev 188)
@@ -0,0 +1,1264 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2005 Ludwig Nussel <ludwig.nussel at web.de>
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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.
+
+You should have received a copy of the GNU General Public License
+along with Foobar; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// vm_x86_64.c -- load time compiler and execution environment for x86-64
+
+#include "vm_local.h"
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+
+#ifdef VM_X86_64_STANDALONE
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+static vmInterpret_t interpret = VMI_COMPILED;
+
+#define DEBUG_VM
+
+static FILE* _asout;
+vm_t* currentVM;
+
+static cvar_t _com_developer;
+cvar_t *com_developer = &_com_developer;
+
+char* mmapfile(const char* fn, size_t* size);
+
+long printsyscall(long* args)
+{
+ printf("callnr %ld, args: %ld %ld %ld %ld\n",
+ args[0],
+ args[1],
+ args[2],
+ args[3],
+ args[4]);
+
+ switch( args[0] ) {
+ case 1:
+ fputs((VMA(1)?VMA(1):"(NULL)\n"), stderr);
+ return 0;
+ case 999:
+ {
+ int a[5];
+ a[0] = 3;
+ a[1] = args[1];
+ a[2] = args[2];
+ a[3] = args[3];
+ a[4] = args[4];
+ if(!currentVM->compiled)
+ return VM_CallInterpreted(currentVM, a);
+ else
+ return VM_CallCompiled(currentVM, a);
+ }
+ case 1000:
+ {
+ int a[5];
+ a[0] = 4;
+ a[1] = args[1];
+ a[2] = args[2];
+ a[3] = args[3];
+ a[4] = args[4];
+ if(!currentVM->compiled)
+ return VM_CallInterpreted(currentVM, a);
+ else
+ return VM_CallCompiled(currentVM, a);
+ }
+ case 1001:
+ printf("got buffer with content '%s', length %d\n", (char*)VMA(1), (int)args[2]);
+ strncpy(VMA(1), "blah\n", args[2]);
+ return 0;
+ }
+
+ return 0x66;
+}
+
+fileHandle_t FS_FOpenFileWrite( const char *filename )
+{
+ _asout = fopen(filename, "w");
+ return 0;
+}
+
+int FS_Write( const void *buffer, int len, fileHandle_t h )
+{
+ return fwrite(buffer, 1, len, _asout);
+}
+
+void FS_Printf( fileHandle_t h, const char *fmt, ... )
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(_asout, fmt, ap);
+ va_end(ap);
+}
+
+void FS_Flush( fileHandle_t f )
+{
+ fflush(_asout);
+}
+
+void FS_FCloseFile( fileHandle_t f )
+{
+ fclose(_asout);
+}
+
+char *FS_BuildOSPath( const char *base, const char *game, const char *qpath )
+{
+ static char buf[4096];
+ strcpy(buf, "./");
+ strcat(buf, qpath);
+ return buf;
+}
+
+cvar_t *Cvar_Get( const char *var_name, const char *var_value, int flags ) {
+ static cvar_t c = { .string = "" };
+ return &c;
+}
+#endif // VM_X86_64_STANDALONE
+
+#ifdef DEBUG_VM
+#define Dfprintf(fd, args...) fprintf(fd, ##args)
+static FILE* qdasmout;
+#else
+#define Dfprintf(args...)
+#endif
+
+/*
+
+ eax scratch
+ ebx scratch
+ ecx scratch (required for shifts)
+ edx scratch (required for divisions)
+ rsi stack pointer
+ rdi program frame pointer
+ r8 pointer to begin of real stack memory
+ r9 return address to real program
+ r10 start of generated code
+*/
+
+
+static long callAsmCall(long callProgramStack, long callSyscallNum)
+{
+ vm_t *savedVM;
+ long ret = 0x77;
+ long args[11];
+ int iargs[11];
+ int i;
+
+// Dfprintf(stderr, "callAsmCall(%ld, %ld)\n", callProgramStack, callSyscallNum);
+// Com_Printf("-> callAsmCall %s, level %d, num %ld\n", currentVM->name, currentVM->callLevel, callSyscallNum);
+
+ savedVM = currentVM;
+
+ // save the stack to allow recursive VM entry
+ currentVM->programStack = callProgramStack - 4;
+
+ args[0] = callSyscallNum;
+ iargs[0] = callSyscallNum;
+ for(i = 0; i < 10; ++i)
+ {
+ iargs[i+1] = *(int *)((byte *)currentVM->dataBase + callProgramStack + 8 + 4*i);
+ args[i+1] = *(int *)((byte *)currentVM->dataBase + callProgramStack + 8 + 4*i);
+ }
+ ret = currentVM->systemCall(args);
+
+ currentVM = savedVM;
+// Com_Printf("<- callAsmCall %s, level %d, num %ld\n", currentVM->name, currentVM->callLevel, callSyscallNum);
+
+ return ret;
+}
+
+#ifdef DEBUG_VM // bk001204
+static char *opnames[256] = {
+ "OP_UNDEF",
+
+ "OP_IGNORE",
+
+ "OP_BREAK",
+
+ "OP_ENTER",
+ "OP_LEAVE",
+ "OP_CALL",
+ "OP_PUSH",
+ "OP_POP",
+
+ "OP_CONST",
+
+ "OP_LOCAL",
+
+ "OP_JUMP",
+
+ //-------------------
+
+ "OP_EQ",
+ "OP_NE",
+
+ "OP_LTI",
+ "OP_LEI",
+ "OP_GTI",
+ "OP_GEI",
+
+ "OP_LTU",
+ "OP_LEU",
+ "OP_GTU",
+ "OP_GEU",
+
+ "OP_EQF",
+ "OP_NEF",
+
+ "OP_LTF",
+ "OP_LEF",
+ "OP_GTF",
+ "OP_GEF",
+
+ //-------------------
+
+ "OP_LOAD1",
+ "OP_LOAD2",
+ "OP_LOAD4",
+ "OP_STORE1",
+ "OP_STORE2",
+ "OP_STORE4",
+ "OP_ARG",
+
+ "OP_BLOCK_COPY",
+
+ //-------------------
+
+ "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"
+};
+#endif // DEBUG_VM
+
+static unsigned char op_argsize[256] =
+{
+ [OP_ENTER] = 4,
+ [OP_LEAVE] = 4,
+ [OP_CONST] = 4,
+ [OP_LOCAL] = 4,
+ [OP_EQ] = 4,
+ [OP_NE] = 4,
+ [OP_LTI] = 4,
+ [OP_LEI] = 4,
+ [OP_GTI] = 4,
+ [OP_GEI] = 4,
+ [OP_LTU] = 4,
+ [OP_LEU] = 4,
+ [OP_GTU] = 4,
+ [OP_GEU] = 4,
+ [OP_EQF] = 4,
+ [OP_NEF] = 4,
+ [OP_LTF] = 4,
+ [OP_LEF] = 4,
+ [OP_GTF] = 4,
+ [OP_GEF] = 4,
+ [OP_ARG] = 1,
+ [OP_BLOCK_COPY] = 4,
+};
+
+#define emit(x...) \
+ do { FS_Printf(fh_s, ##x); FS_Write("\n", 1, fh_s); } while(0)
+
+// integer compare and jump
+#define IJ(op) \
+ emit("subq $8, %%rsi"); \
+ emit("movl 4(%%rsi), %%eax"); \
+ emit("cmpl 8(%%rsi), %%eax"); \
+ emit(op " i_%08x", instruction+1); \
+ emit("jmp i_%08x", iarg);
+
+#ifdef USE_X87
+#define FJ(bits, op) \
+ emit("subq $8, %%rsi");\
+ emit("flds 4(%%rsi)");\
+ emit("fcomps 8(%%rsi)");\
+ emit("fnstsw %%ax");\
+ emit("testb $" #bits ", %%ah");\
+ emit(op " i_%08x", instruction+1);\
+ emit("jmp i_%08x", iarg);
+#define XJ(x)
+#else
+#define FJ(x, y)
+#define XJ(op) \
+ emit("subq $8, %%rsi");\
+ emit("movss 4(%%rsi), %%xmm0");\
+ emit("ucomiss 8(%%rsi), %%xmm0");\
+ emit("jp i_%08x", instruction+1);\
+ emit(op " i_%08x", instruction+1);\
+ emit("jmp i_%08x", iarg);
+#endif
+
+#define SIMPLE(op) \
+ emit("subq $4, %%rsi"); \
+ emit("movl 4(%%rsi), %%eax"); \
+ emit(op " %%eax, 0(%%rsi)");
+
+#ifdef USE_X87
+#define FSIMPLE(op) \
+ emit("subq $4, %%rsi"); \
+ emit("flds 0(%%rsi)"); \
+ emit(op " 4(%%rsi)"); \
+ emit("fstps 0(%%rsi)");
+#define XSIMPLE(op)
+#else
+#define FSIMPLE(op)
+#define XSIMPLE(op) \
+ emit("subq $4, %%rsi"); \
+ emit("movss 0(%%rsi), %%xmm0"); \
+ emit(op " 4(%%rsi), %%xmm0"); \
+ emit("movss %%xmm0, 0(%%rsi)");
+#endif
+
+#define SHIFT(op) \
+ emit("subq $4, %%rsi"); \
+ emit("movl 4(%%rsi), %%ecx"); \
+ emit("movl 0(%%rsi), %%eax"); \
+ emit(op " %%cl, %%eax"); \
+ emit("movl %%eax, 0(%%rsi)");
+
+#if 1
+#define RANGECHECK(reg) \
+ emit("andl $0x%x, %%" #reg, vm->dataMask);
+#else
+#define RANGECHECK(reg)
+#endif
+
+#ifdef DEBUG_VM
+#define NOTIMPL(x) \
+ do { Com_Error(ERR_DROP, "instruction not implemented: %s\n", opnames[x]); } while(0)
+#else
+#define NOTIMPL(x) \
+ do { Com_Error(ERR_DROP, "instruction not implemented: %x\n", x); } while(0)
+#endif
+
+static void* getentrypoint(vm_t* vm)
+{
+ return vm->codeBase+64; // skip ELF header
+}
+
+char* mmapfile(const char* fn, size_t* size)
+{
+ int fd = -1;
+ char* mem = NULL;
+ struct stat stb;
+
+ fd = open(fn, O_RDONLY);
+ if(fd == -1)
+ goto out;
+
+ if(fstat(fd, &stb) == -1)
+ goto out;
+
+ *size = stb.st_size;
+
+ mem = mmap(NULL, stb.st_size, PROT_READ|PROT_EXEC, MAP_SHARED, fd, 0);
+ if(mem == (void*)-1)
+ mem = NULL;
+
+out:
+ if(fd != -1)
+ close(fd);
+
+ return mem;
+}
+
+static int doas(const char* in, const char* out, unsigned char** compiledcode)
+{
+ char rin[4096];
+ char rout[4096];
+ char* buf;
+ char* mem;
+ cvar_t* homedir;
+ size_t size = -1, allocsize;
+ int ps;
+ pid_t pid;
+
+ homedir = Cvar_Get("fs_homepath", "", 0);
+
+ buf = FS_BuildOSPath(homedir->string, NULL, in);
+ strcpy(rin, buf);
+
+ buf = FS_BuildOSPath(homedir->string, NULL, out);
+ strcpy(rout, buf);
+
+ Com_Printf("running assembler\n");
+ pid = fork();
+ if(pid == -1)
+ Com_Error(ERR_FATAL, "can't fork\n");
+
+ if(!pid)
+ {
+ char* const argv[] = {
+ "as",
+ "-o",
+ rout,
+ rin,
+ NULL
+ };
+
+ execvp(argv[0], argv);
+ _exit(-1);
+ }
+ else
+ {
+ int status;
+ if(waitpid(pid, &status, 0) == -1)
+ Com_Error(ERR_FATAL, "can't wait for as: %s\n", strerror(errno));
+
+ if(!WIFEXITED(status))
+ Com_Error(ERR_FATAL, "as died\n");
+ if(WEXITSTATUS(status))
+ Com_Error(ERR_FATAL, "as failed with status %d\n", WEXITSTATUS(status));
+ }
+
+ Com_Printf("done\n");
+
+ mem = mmapfile(rout, &size);
+ if(!mem)
+ Com_Error(ERR_FATAL, "can't mmap object file %s: %s\n", rout, strerror(errno));
+
+ ps = sysconf(_SC_PAGE_SIZE);
+ if(ps == -1)
+ Com_Error(ERR_FATAL, "can't determine page size: %s\n", strerror(errno));
+
+ --ps;
+
+ allocsize = (size+ps)&~ps;
+ buf = Hunk_Alloc(allocsize, h_high);
+
+ buf = (void*)(((unsigned long)buf+ps)&~ps);
+
+ memcpy(buf, mem, size);
+
+ munmap(mem, 0);
+
+ if((*compiledcode = (unsigned char*)buf))
+ {
+#ifdef VM_X86_64_STANDALONE // no idea why
+ if(mprotect(buf, allocsize, PROT_READ|PROT_EXEC) == -1)
+ Com_Error(ERR_FATAL, "mprotect failed on %p+%x: %s\n", buf, allocsize, strerror(errno));
+#endif
+ return size;
+ }
+
+ return -1;
+}
+
+/*
+=================
+VM_Compile
+=================
+*/
+void VM_Compile( vm_t *vm, vmHeader_t *header ) {
+ unsigned char op;
+ int pc;
+ unsigned instruction;
+ char* code;
+ unsigned iarg = 0;
+ unsigned char barg = 0;
+ void* entryPoint;
+
+ char fn_s[MAX_QPATH]; // output file for assembler code
+ char fn_o[MAX_QPATH]; // file written by as
+#ifdef DEBUG_VM
+ char fn_d[MAX_QPATH]; // disassembled
+#endif
+ fileHandle_t fh_s;
+ byte* compiledcode;
+ int compiledsize;
+
+ Com_Printf("compiling %s\n", vm->name);
+
+ strcpy(fn_s,vm->name);
+ strcpy(fn_o,vm->name);
+ strcat(fn_s, ".s");
+ strcat(fn_o, ".o");
+#ifdef DEBUG_VM
+ strcpy(fn_d,vm->name);
+ strcat(fn_d, ".qdasm");
+
+ qdasmout = fopen(fn_d, "w");
+#endif
+
+ fh_s = FS_FOpenFileWrite(fn_s);
+ if(fh_s == -1)
+ Com_Error(ERR_DROP, "can't write %s\n", fn_s);
+
+ // translate all instructions
+ pc = 0;
+ code = (char *)header + header->codeOffset;
+
+ emit("start:");
+ emit("or %%r8, %%r8"); // check whether to set up instruction pointers
+ emit("jnz main");
+ emit("jmp setupinstructionpointers");
+ emit("exit:");
+ emit("jmp *%%r9");
+
+ emit("main:");
+
+ for ( instruction = 0; instruction < header->instructionCount; ++instruction )
+ {
+ op = code[ pc ];
+ ++pc;
+
+ vm->instructionPointers[instruction] = pc;
+
+#if 0
+ emit("nop");
+ emit("movq $%d, %%r15", instruction);
+ emit("nop");
+#endif
+
+ if(op_argsize[op] == 4)
+ {
+ iarg = *(int*)(code+pc);
+ pc += 4;
+ Dfprintf(qdasmout, "%s %8u\n", opnames[op], iarg);
+ }
+ else if(op_argsize[op] == 1)
+ {
+ barg = code[pc++];
+ Dfprintf(qdasmout, "%s %8hhu\n", opnames[op], barg);
+ }
+ else
+ {
+ Dfprintf(qdasmout, "%s\n", opnames[op]);
+ }
+ emit("i_%08x:", instruction);
+ switch ( op )
+ {
+ case OP_UNDEF:
+ NOTIMPL(op);
+ break;
+ case OP_IGNORE:
+ emit("nop");
+ break;
+ case OP_BREAK:
+ emit("int3");
+ break;
+ case OP_ENTER:
+ emit("subl $%d, %%edi", iarg);
+ RANGECHECK(edi);
+ break;
+ case OP_LEAVE:
+ emit("addl $%d, %%edi", iarg); // get rid of stack frame
+ RANGECHECK(edi);
+ emit("movl 0(%%r8, %%rdi, 1), %%eax"); // get return address
+ emit("movq $%lu, %%rbx", (unsigned long)vm->instructionPointers);
+ emit("cmp $-1, %%eax");
+ emit("je jumptoexit%d", instruction);
+ emit("movl (%%rbx, %%rax, 4), %%eax"); // load new relative jump address
+ emit("addq %%r10, %%rax");
+ emit("jmp *%%rax");
+ emit("jumptoexit%d:", instruction);
+ emit("jmp exit");
+ break;
+ case OP_CALL:
+ emit("movl 0(%%rsi), %%eax"); // get instr from stack
+ emit("subq $4, %%rsi");
+ emit("movl $%d, 0(%%r8, %%rdi, 1)", instruction+1); // save next instruction
+ emit("orl %%eax, %%eax");
+ emit("jl callSyscall%d", instruction);
+ emit("movq $%lu, %%rbx", (unsigned long)vm->instructionPointers);
+ emit("movl (%%rbx, %%rax, 4), %%eax"); // load new relative jump address
+ emit("addq %%r10, %%rax");
+ emit("jmp *%%rax");
+ emit("callSyscall%d:", instruction);
+// emit("fnsave 4(%%rsi)");
+ emit("push %%rsi");
+ emit("push %%rdi");
+ emit("push %%r8");
+ emit("push %%r9");
+ emit("push %%r10");
+ emit("negl %%eax"); // convert to actual number
+ emit("decl %%eax");
+ // first argument already in rdi
+ emit("movq %%rax, %%rsi"); // second argument in rsi
+ emit("movq $%ld, %%rax", (unsigned long)callAsmCall);
+ emit("callq *%%rax");
+ emit("pop %%r10");
+ emit("pop %%r9");
+ emit("pop %%r8");
+ emit("pop %%rdi");
+ emit("pop %%rsi");
+// emit("frstor 4(%%rsi)");
+ emit("addq $4, %%rsi");
+ emit("movl %%eax, (%%rsi)");
+ break;
+ case OP_PUSH:
+ emit("addq $4, %%rsi");
+ break;
+ case OP_POP:
+ emit("subq $4, %%rsi");
+ break;
+ case OP_CONST:
+ emit("addq $4, %%rsi");
+ emit("movl $%d, 0(%%rsi)", iarg);
+ break;
+ case OP_LOCAL:
+ emit("movl %%edi, %%ebx");
+ emit("addl $%d,%%ebx", iarg);
+ emit("addq $4, %%rsi");
+ emit("movl %%ebx, 0(%%rsi)");
+ break;
+ case OP_JUMP:
+ emit("movl 0(%%rsi), %%eax"); // get instr from stack
+ emit("subq $4, %%rsi");
+ emit("movq $%lu, %%rbx", (unsigned long)vm->instructionPointers);
+ emit("movl (%%rbx, %%rax, 4), %%eax"); // load new relative jump address
+ emit("addq %%r10, %%rax");
+ emit("jmp *%%rax");
+ break;
+ case OP_EQ:
+ IJ("jne");
+ break;
+ case OP_NE:
+ IJ("je");
+ break;
+ case OP_LTI:
+ IJ("jnl");
+ break;
+ case OP_LEI:
+ IJ("jnle");
+ break;
+ case OP_GTI:
+ IJ("jng");
+ break;
+ case OP_GEI:
+ IJ("jnge");
+ break;
+ case OP_LTU:
+ IJ("jnb");
+ break;
+ case OP_LEU:
+ IJ("jnbe");
+ break;
+ case OP_GTU:
+ IJ("jna");
+ break;
+ case OP_GEU:
+ IJ("jnae");
+ break;
+ case OP_EQF:
+ FJ(0x40, "jz");
+ XJ("jnz");
+ break;
+ case OP_NEF:
+ FJ(0x40, "jnz");
+#ifndef USE_X87
+ emit("subq $8, %%rsi");
+ emit("movss 4(%%rsi), %%xmm0");
+ emit("ucomiss 8(%%rsi), %%xmm0");
+ emit("jp dojump_i_%08x", instruction);
+ emit("jz i_%08x", instruction+1);
+ emit("dojump_i_%08x:", instruction);
+ emit("jmp i_%08x", iarg);
+#endif
+ break;
+ case OP_LTF:
+ FJ(0x01, "jz");
+ XJ("jnc");
+ break;
+ case OP_LEF:
+ FJ(0x41, "jz");
+ XJ("ja");
+ break;
+ case OP_GTF:
+ FJ(0x41, "jnz");
+ XJ("jbe");
+ break;
+ case OP_GEF:
+ FJ(0x01, "jnz");
+ XJ("jb");
+ break;
+ case OP_LOAD1:
+ emit("movl 0(%%rsi), %%eax"); // get pointer from stack
+ RANGECHECK(eax);
+ emit("movb 0(%%r8, %%rax, 1), %%al"); // deref into eax
+ emit("andq $255, %%rax");
+ emit("movl %%eax, 0(%%rsi)"); // store on stack
+ break;
+ case OP_LOAD2:
+ emit("movl 0(%%rsi), %%eax"); // get pointer from stack
+ RANGECHECK(eax);
+ emit("movw 0(%%r8, %%rax, 1), %%rax"); // deref into eax
+ emit("movl %%eax, 0(%%rsi)"); // store on stack
+ break;
+ case OP_LOAD4:
+ emit("movl 0(%%rsi), %%eax"); // get pointer from stack
+ RANGECHECK(eax);
+ emit("movl 0(%%r8, %%rax, 1), %%eax"); // deref into eax
+ emit("movl %%eax, 0(%%rsi)"); // store on stack
+ break;
+ case OP_STORE1:
+ emit("movl 0(%%rsi), %%eax"); // get value from stack
+ emit("andq $255, %%rax");
+ emit("movl -4(%%rsi), %%ebx"); // get pointer from stack
+ RANGECHECK(ebx);
+ emit("movb %%al, 0(%%r8, %%rbx, 1)"); // store in memory
+ emit("subq $8, %%rsi");
+ break;
+ case OP_STORE2:
+ emit("movl 0(%%rsi), %%eax"); // get value from stack
+ emit("movl -4(%%rsi), %%ebx"); // get pointer from stack
+ RANGECHECK(ebx);
+ emit("movw %%rax, 0(%%r8, %%rbx, 1)"); // store in memory
+ emit("subq $8, %%rsi");
+ break;
+ case OP_STORE4:
+ emit("movl -4(%%rsi), %%ebx"); // get pointer from stack
+ RANGECHECK(ebx);
+ emit("movl 0(%%rsi), %%ecx"); // get value from stack
+ emit("movl %%ecx, 0(%%r8, %%rbx, 1)"); // store in memory
+ emit("subq $8, %%rsi");
+ break;
+ case OP_ARG:
+ emit("subq $4, %%rsi");
+ emit("movl 4(%%rsi), %%eax"); // get value from stack
+ emit("movl $0x%hhx, %%ebx", barg);
+ emit("addl %%edi, %%ebx");
+ RANGECHECK(ebx);
+ emit("movl %%eax, 0(%%r8,%%rbx, 1)"); // store in args space
+ break;
+ case OP_BLOCK_COPY:
+ if(iarg % 4) Com_Error(ERR_DROP,
+ "argument to OP_BLOCK_COPY not multiple of 4\n");
+
+ emit("subq $8, %%rsi");
+ emit("movl 8(%%rsi), %%ebx"); // get pointer from stack
+
+ emit("movl %%ebx, %%ecx");
+ RANGECHECK(ecx);
+ emit("cmp %%ebx, %%ecx");
+ emit("jne broken%d", instruction);
+
+ emit("movl %%ecx, %%edx");
+ emit("addl $%d, %%edx", iarg);
+ emit("addl $%d, %%ecx", iarg);
+ RANGECHECK(edx);
+ emit("cmp %%ecx, %%edx");
+ emit("jne broken%d", instruction);
+
+ emit("movl 4(%%rsi), %%eax"); // get pointer from stack
+
+ emit("movl %%eax, %%ecx");
+ RANGECHECK(ecx);
+ emit("cmp %%eax, %%ecx");
+ emit("jne broken%d", instruction);
+
+ emit("movl %%ecx, %%edx");
+ emit("addl $%d, %%edx", iarg);
+ emit("addl $%d, %%ecx", iarg);
+ RANGECHECK(edx);
+ emit("cmp %%ecx, %%edx");
+ emit("jne broken%d", instruction);
+
+ emit("addq %%r8, %%rax"); // calc real address
+ emit("addq %%r8, %%rbx"); // calc real address
+ emit("movl $%d, %%ecx", iarg);
+ emit("shrl $2, %%ecx");
+ emit("block_copy_loop_%d:", instruction);
+ emit("decl %%ecx");
+ emit("movl 0(%%rbx, %%rcx, 4), %%edx");
+ emit("movl %%edx, 0(%%rax, %%rcx, 4)");
+ emit("orl %%ecx, %%ecx");
+ emit("jnz block_copy_loop_%d", instruction);
+ emit("jmp i_%08x", instruction+1);
+
+ emit("broken%d:", instruction);
+ emit("int3");
+
+ break;
+ case OP_SEX8:
+ emit("movw 0(%%rsi), %%rax");
+ emit("andq $255, %%rax");
+ emit("cbw");
+ emit("cwde");
+ emit("movl %%eax, 0(%%rsi)");
+ break;
+ case OP_SEX16:
+ emit("movw 0(%%rsi), %%rax");
+ emit("cwde");
+ emit("movl %%eax, 0(%%rsi)");
+ break;
+ case OP_NEGI:
+ emit("negl 0(%%rsi)");
+ break;
+ case OP_ADD:
+ SIMPLE("addl");
+ break;
+ case OP_SUB:
+ SIMPLE("subl");
+ break;
+ case OP_DIVI:
+ emit("subq $4, %%rsi");
+ emit("movl 0(%%rsi), %%eax");
+ emit("cdq");
+ emit("idivl 4(%%rsi)");
+ emit("movl %%eax, 0(%%rsi)");
+ break;
+ case OP_DIVU:
+ emit("subq $4, %%rsi");
+ emit("movl 0(%%rsi), %%eax");
+ emit("xorq %%rdx, %%rdx");
+ emit("divl 4(%%rsi)");
+ emit("movl %%eax, 0(%%rsi)");
+ break;
+ case OP_MODI:
+ emit("subq $4, %%rsi");
+ emit("movl 0(%%rsi), %%eax");
+ emit("xorl %%edx, %%edx");
+ emit("divl 4(%%rsi)");
+ emit("movl %%edx, 0(%%rsi)");
+ break;
+ case OP_MODU:
+ emit("subq $4, %%rsi");
+ emit("movl 0(%%rsi), %%eax");
+ emit("xorl %%edx, %%edx");
+ emit("idivl 4(%%rsi)");
+ emit("movl %%edx, 0(%%rsi)");
+ break;
+ case OP_MULI:
+ emit("subq $4, %%rsi");
+ emit("movl 0(%%rsi), %%eax");
+ emit("imull 4(%%rsi)");
+ emit("movl %%eax, 0(%%rsi)");
+ break;
+ case OP_MULU:
+ emit("subq $4, %%rsi");
+ emit("movl 0(%%rsi), %%eax");
+ emit("mull 4(%%rsi)");
+ emit("movl %%eax, 0(%%rsi)");
+ break;
+ case OP_BAND:
+ SIMPLE("andl");
+ break;
+ case OP_BOR:
+ SIMPLE("orl");
+ break;
+ case OP_BXOR:
+ SIMPLE("xorl");
+ break;
+ case OP_BCOM:
+ emit("notl 0(%%rsi)");
+ break;
+ case OP_LSH:
+ SHIFT("shl");
+ break;
+ case OP_RSHI:
+ SHIFT("sarl");
+ break;
+ case OP_RSHU:
+ SHIFT("shrl");
+ break;
+ case OP_NEGF:
+#ifdef USE_X87
+ emit("flds 0(%%rsi)");
+ emit("fchs");
+ emit("fstps 0(%%rsi)");
+#else
+ emit("movl $0x80000000, %%eax");
+ emit("xorl %%eax, 0(%%rsi)");
+#endif
+ break;
+ case OP_ADDF:
+ FSIMPLE("fadds");
+ XSIMPLE("addss");
+ break;
+ case OP_SUBF:
+ FSIMPLE("fsubs");
+ XSIMPLE("subss");
+ break;
+ case OP_DIVF:
+ FSIMPLE("fdivs");
+ XSIMPLE("divss");
+ break;
+ case OP_MULF:
+ FSIMPLE("fmuls");
+ XSIMPLE("mulss");
+ break;
+ case OP_CVIF:
+#ifdef USE_X87
+ emit("filds 0(%%rsi)");
+ emit("fstps 0(%%rsi)");
+#else
+ emit("movl 0(%%rsi), %%eax");
+ emit("cvtsi2ss %%eax, %%xmm0");
+ emit("movss %%xmm0, 0(%%rsi)");
+#endif
+ break;
+ case OP_CVFI:
+#ifdef USE_X87
+ emit("flds 0(%%rsi)");
+ emit("fnstcw 4(%%rsi)");
+ emit("movw $0x0F7F, 8(%%rsi)"); // round toward zero
+ emit("fldcw 8(%%rsi)");
+ emit("fistpl 0(%%rsi)");
+ emit("fldcw 4(%%rsi)");
+#else
+ emit("movss 0(%%rsi), %%xmm0");
+ emit("cvttss2si %%xmm0, %%eax");
+ emit("movl %%eax, 0(%%rsi)");
+#endif
+ break;
+ default:
+ NOTIMPL(op);
+ break;
+ }
+ }
+
+
+ emit("setupinstructionpointers:");
+ emit("movq $%lu, %%rax", (unsigned long)vm->instructionPointers);
+ for ( instruction = 0; instruction < header->instructionCount; ++instruction )
+ {
+ emit("movl $i_%08x-start, %d(%%rax)", instruction, instruction*4);
+ }
+ emit("jmp exit");
+
+ emit("debugger:");
+ if(1);
+ {
+ int i = 6;
+ while(i--)
+ {
+ emit("nop");
+ emit("int3");
+ }
+ }
+
+ FS_Flush(fh_s);
+ FS_FCloseFile(fh_s);
+
+ compiledsize = doas(fn_s, fn_o, &compiledcode);
+
+ vm->codeBase = compiledcode; // remember to skip ELF header!
+ vm->codeLength = compiledsize;
+
+ entryPoint = getentrypoint(vm);
+
+// __asm__ __volatile__ ("int3");
+ Com_Printf("computing jump table\n");
+
+ // call code with r8 set to zero to set up instruction pointers
+ __asm__ __volatile__ (
+ " xorq %%r8,%%r8 \r\n" \
+ " movq $doneinit,%%r9 \r\n" \
+ " movq %0,%%r10 \r\n" \
+ " jmp *%%r10 \r\n" \
+ "doneinit: \r\n" \
+ :
+ : "m" (entryPoint)
+ : "%r8", "%r9", "%r10", "%rax"
+ );
+
+#ifdef DEBUG_VM
+ fflush(qdasmout);
+#endif
+
+ Com_Printf( "VM file %s compiled to %i bytes of code (0x%lx - 0x%lx)\n", vm->name, vm->codeLength, vm->codeBase, vm->codeBase+vm->codeLength );
+}
+
+/*
+==============
+VM_CallCompiled
+
+This function is called directly by the generated code
+==============
+*/
+
+#ifdef DEBUG_VM
+static char* memData;
+#endif
+
+int VM_CallCompiled( vm_t *vm, int *args ) {
+ int programCounter;
+ int programStack;
+ int stackOnEntry;
+ byte *image;
+ void *entryPoint;
+ void *opStack;
+ int stack[1024] = { 0xDEADBEEF };
+
+ currentVM = vm;
+
+ ++vm->callLevel;
+// Com_Printf("entering %s level %d, call %d, arg1 = 0x%x\n", vm->name, vm->callLevel, args[0], args[1]);
+
+ // interpret the code
+ vm->currentlyInterpreting = qtrue;
+
+// callMask = vm->dataMask;
+
+ // we might be called recursively, so this might not be the very top
+ programStack = vm->programStack;
+ stackOnEntry = programStack;
+
+ // set up the stack frame
+ image = vm->dataBase;
+#ifdef DEBUG_VM
+ memData = (char*)image;
+#endif
+
+ programCounter = 0;
+
+ programStack -= 48;
+
+ *(int *)&image[ programStack + 44] = args[9];
+ *(int *)&image[ programStack + 40] = args[8];
+ *(int *)&image[ programStack + 36] = args[7];
+ *(int *)&image[ programStack + 32] = args[6];
+ *(int *)&image[ programStack + 28] = args[5];
+ *(int *)&image[ programStack + 24] = args[4];
+ *(int *)&image[ programStack + 20] = args[3];
+ *(int *)&image[ programStack + 16] = args[2];
+ *(int *)&image[ programStack + 12] = args[1];
+ *(int *)&image[ programStack + 8 ] = args[0];
+ *(int *)&image[ programStack + 4 ] = 0x77777777; // return stack
+ *(int *)&image[ programStack ] = -1; // will terminate the loop on return
+
+ // off we go into generated code...
+ entryPoint = getentrypoint(vm);
+ opStack = &stack;
+
+ __asm__ __volatile__ (
+ " movq %5,%%rsi \r\n" \
+ " movl %4,%%edi \r\n" \
+ " movq $done,%%r9 \r\n" \
+ " movq %2,%%r10 \r\n" \
+ " movq %3,%%r8 \r\n" \
+ " jmp *%%r10 \r\n" \
+ "done: \r\n" \
+ " movl %%edi, %0 \r\n" \
+ " movq %%rsi, %1 \r\n" \
+ : "=m" (programStack), "=m" (opStack)
+ : "m" (entryPoint), "m" (vm->dataBase), "m" (programStack), "m" (opStack)
+ : "%rsi", "%rdi", "%rax", "%rbx", "%rcx", "%rdx", "%r8", "%r9", "%r10", "%r15", "%xmm0"
+ );
+
+ if ( opStack != &stack[1] ) {
+ Com_Error( ERR_DROP, "opStack corrupted in compiled code (offset %d)\n", (void*)&stack[1] - opStack);
+ }
+ if ( programStack != stackOnEntry - 48 ) {
+ Com_Error( ERR_DROP, "programStack corrupted in compiled code\n" );
+ }
+
+// Com_Printf("exiting %s level %d\n", vm->name, vm->callLevel);
+ --vm->callLevel;
+ vm->programStack = stackOnEntry;
+
+ return *(int *)opStack;
+}
+
+#ifdef VM_X86_64_STANDALONE
+
+#include <tests.c>
+
+int testops(vm_t* vm)
+{
+ int i1, i2, vmres;
+ int i;
+ float f1, f2, fres, fvmres;
+ int numitests = 26;
+ int numftests = 11;
+ int ret = 0;
+ int testno;
+ int res = 0xC0DEDBAD;
+
+ srand(time(NULL));
+
+ i1 = 1 + (int) (1000.0 * (rand() / (RAND_MAX + 1.0)));
+ i2 = 1 + (int) (1000.0 * (rand() / (RAND_MAX + 1.0)));
+
+ if(i1 < i2)
+ {
+ i = i1;
+ i1 = i2;
+ i2 = i;
+ }
+
+ f1 = i1/1.5;
+ f2 = i2/1.5;
+
+ if(!i2) i2=i1;
+ if(!f2) f2=f1;
+
+ printf("i1: %d i2: %d\n", i1, i2);
+ printf("f1: %f f2: %f\n", f1, f2);
+
+testintops:
+ for (testno = 1; testno < numitests; ++testno)
+ {
+ printf("int test %d ... ", testno);
+ fflush(stdout);
+
+ res = test(testno, i1, i2);
+ vmres = VM_Call(vm, testno, i1, i2);
+
+ if(vmres == res)
+ {
+ printf("ok: %d == %d\n", res, vmres);
+ }
+ else
+ {
+ printf("failed: %d != %d\n", res, vmres);
+ ret = 1;
+ }
+ }
+ if(i1 != i2)
+ {
+ i2 = i1;
+ goto testintops;
+ }
+
+testfops:
+ i1 = *(int*)&f1;
+ i2 = *(int*)&f2;
+
+ for (testno = 100; testno < 100+numftests; ++testno)
+ {
+ printf("float test %d ... ", testno);
+ fflush(stdout);
+
+ res = test(testno, i1, i2);
+ vmres = VM_Call(vm, testno, i1, i2);
+
+ fres = *(float*)&res;
+ fvmres = *(float*)&vmres;
+
+ if(fvmres == fres)
+ {
+ printf("ok: %f == %f\n", fres, fvmres);
+ }
+ else
+ {
+ printf("failed: %f != %f\n", fres, fvmres);
+ ret = 1;
+ }
+ }
+ if(f1 > f2)
+ {
+ float t = f1;
+ f1 = f2;
+ f2 = t;
+ goto testfops;
+ }
+ else if(f1 < f2)
+ {
+ f2 = f1;
+ goto testfops;
+ }
+
+ return ret;
+}
+
+void VM_VmInfo_f( void );
+
+int main(int argc, char* argv[])
+{
+ size_t size;
+ vmHeader_t *header;
+ vm_t* vm[3];
+ unsigned dataLength;
+ int i;
+ long args[11] = {0};
+ int ret = 0xDEADBEEF;
+ char* mem;
+
+ char* file = argv[1];
+
+ char module[128];
+
+ if(argc < 2)
+ return -1;
+
+ strcpy(module, file);
+ *strchr(module, '.') = '\0';
+
+ vm[0] = VM_Create( module, printsyscall, interpret );
+ vm[1] = VM_Create( module, printsyscall, interpret );
+ vm[2] = VM_Create( module, printsyscall, interpret );
+
+ VM_VmInfo_f();
+
+ if(argc > 2)
+ {
+ for(i = 2; i < argc; ++i)
+ {
+ args[i-2] = strtol(argv[i],NULL,0);
+ }
+
+ ret = VM_Call(vm[0], args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
+ ret += VM_Call(vm[1], args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
+ ret += VM_Call(vm[2], args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
+ }
+ else
+ {
+ ret = testops(vm[0]);
+ ret += testops(vm[1]);
+ ret += testops(vm[2]);
+ ret += testops(vm[1]);
+ ret += testops(vm[0]);
+ }
+
+
+#ifdef DEBUG_VM
+ printf("ret: %d [%X]\n", ret, ret);
+#endif
+
+ return 0;
+}
+#endif
Modified: trunk/code/unix/Makefile
===================================================================
--- trunk/code/unix/Makefile 2005-10-26 23:44:28 UTC (rev 187)
+++ trunk/code/unix/Makefile 2005-10-27 21:13:47 UTC (rev 188)
@@ -135,6 +135,8 @@
OPTIMIZE = -O3 -fomit-frame-pointer -ffast-math -falign-loops=2 \
-falign-jumps=2 -falign-functions=2 -fstrength-reduce \
-fno-strict-aliasing
+# experimental! you need as
+# BASE_CFLAGS += -DHAVE_VM_COMPILED
else
ifeq ($(ARCH),i386)
OPTIMIZE = -O3 -march=i686 -fomit-frame-pointer -ffast-math \
@@ -404,7 +406,7 @@
DO_CC=$(CC) $(CFLAGS) -o $@ -c $<
DO_CXX=$(CXX) $(CFLAGS) -o $@ -c $<
DO_SMP_CC=$(CC) $(CFLAGS) -DSMP -o $@ -c $<
-DO_BOT_CC=$(CC) $(CFLAGS) -DBOTLIB -o $@ -c $< # $(SHLIBCFLAGS) # bk001212
+DO_BOT_CC=$(CC) $(CFLAGS) -DBOTLIB -o $@ -c $< # $(SHLIBCFLAGS) # bk001212
DO_DEBUG_CC=$(CC) $(DEBUG_CFLAGS) -o $@ -c $<
DO_SHLIB_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $<
DO_SHLIB_DEBUG_CC=$(CC) $(DEBUG_CFLAGS) $(SHLIBCFLAGS) -o $@ -c $<
@@ -618,6 +620,9 @@
ifeq ($(ARCH),x86)
Q3OBJ += $(B)/client/vm_x86.o
endif
+ifeq ($(ARCH),x86_64)
+ Q3OBJ += $(B)/client/vm_x86_64.o
+endif
ifeq ($(ARCH),ppc)
ifneq ($(VM_PPC),)
@@ -902,6 +907,7 @@
$(B)/client/win_resource.o : $(W32DIR)/winquake.rc; $(DO_WINDRES)
$(B)/client/vm_x86.o : $(CMDIR)/vm_x86.c; $(DO_CC)
+$(B)/client/vm_x86_64.o : $(CMDIR)/vm_x86_64.c; $(DO_CC)
ifneq ($(VM_PPC),)
$(B)/client/$(VM_PPC).o : $(CMDIR)/$(VM_PPC).c; $(DO_CC)
endif
@@ -989,6 +995,10 @@
Q3DOBJ += $(B)/ded/vm_x86.o $(B)/ded/ftola.o $(B)/ded/snapvectora.o
endif
+ifeq ($(ARCH),x86_64)
+ Q3DOBJ += $(B)/ded/vm_x86_64.o
+endif
+
ifeq ($(ARCH),ppc)
ifneq ($(VM_PPC),)
Q3DOBJ += $(B)/ded/$(VM_PPC).o
@@ -1069,6 +1079,7 @@
$(B)/ded/snapvectora.o : $(UDIR)/snapvectora.s; $(DO_AS)
$(B)/ded/vm_x86.o : $(CMDIR)/vm_x86.c; $(DO_DED_CC)
+$(B)/ded/vm_x86_64.o : $(CMDIR)/vm_x86_64.c; $(DO_DED_CC)
ifneq ($(VM_PPC),)
$(B)/ded/$(VM_PPC).o : $(CMDIR)/$(VM_PPC).c; $(DO_DED_CC)
endif
Modified: trunk/code/unix/unix_main.c
===================================================================
--- trunk/code/unix/unix_main.c 2005-10-26 23:44:28 UTC (rev 187)
+++ trunk/code/unix/unix_main.c 2005-10-27 21:13:47 UTC (rev 188)
@@ -730,7 +730,6 @@
#3 look in fs_basepath
=================
*/
-extern char *FS_BuildOSPath( const char *base, const char *game, const char *qpath );
static void* try_dlopen(const char* base, const char* gamedir, const char* fname, char* fqpath )
{
More information about the quake3-commits
mailing list