vm.c (18794B)
1 /* 2 =========================================================================== 3 Copyright (C) 1999-2005 Id Software, Inc. 4 5 This file is part of Quake III Arena source code. 6 7 Quake III Arena source code is free software; you can redistribute it 8 and/or modify it under the terms of the GNU General Public License as 9 published by the Free Software Foundation; either version 2 of the License, 10 or (at your option) any later version. 11 12 Quake III Arena source code is distributed in the hope that it will be 13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with Foobar; if not, write to the Free Software 19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 =========================================================================== 21 */ 22 // vm.c -- virtual machine 23 24 /* 25 26 27 intermix code and data 28 symbol table 29 30 a dll has one imported function: VM_SystemCall 31 and one exported function: Perform 32 33 34 */ 35 36 #include "vm_local.h" 37 38 39 vm_t *currentVM = NULL; // bk001212 40 vm_t *lastVM = NULL; // bk001212 41 int vm_debugLevel; 42 43 #define MAX_VM 3 44 vm_t vmTable[MAX_VM]; 45 46 47 void VM_VmInfo_f( void ); 48 void VM_VmProfile_f( void ); 49 50 51 // converts a VM pointer to a C pointer and 52 // checks to make sure that the range is acceptable 53 void *VM_VM2C( vmptr_t p, int length ) { 54 return (void *)p; 55 } 56 57 void VM_Debug( int level ) { 58 vm_debugLevel = level; 59 } 60 61 /* 62 ============== 63 VM_Init 64 ============== 65 */ 66 void VM_Init( void ) { 67 Cvar_Get( "vm_cgame", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2 68 Cvar_Get( "vm_game", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2 69 Cvar_Get( "vm_ui", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2 70 71 Cmd_AddCommand ("vmprofile", VM_VmProfile_f ); 72 Cmd_AddCommand ("vminfo", VM_VmInfo_f ); 73 74 Com_Memset( vmTable, 0, sizeof( vmTable ) ); 75 } 76 77 78 /* 79 =============== 80 VM_ValueToSymbol 81 82 Assumes a program counter value 83 =============== 84 */ 85 const char *VM_ValueToSymbol( vm_t *vm, int value ) { 86 vmSymbol_t *sym; 87 static char text[MAX_TOKEN_CHARS]; 88 89 sym = vm->symbols; 90 if ( !sym ) { 91 return "NO SYMBOLS"; 92 } 93 94 // find the symbol 95 while ( sym->next && sym->next->symValue <= value ) { 96 sym = sym->next; 97 } 98 99 if ( value == sym->symValue ) { 100 return sym->symName; 101 } 102 103 Com_sprintf( text, sizeof( text ), "%s+%i", sym->symName, value - sym->symValue ); 104 105 return text; 106 } 107 108 /* 109 =============== 110 VM_ValueToFunctionSymbol 111 112 For profiling, find the symbol behind this value 113 =============== 114 */ 115 vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) { 116 vmSymbol_t *sym; 117 static vmSymbol_t nullSym; 118 119 sym = vm->symbols; 120 if ( !sym ) { 121 return &nullSym; 122 } 123 124 while ( sym->next && sym->next->symValue <= value ) { 125 sym = sym->next; 126 } 127 128 return sym; 129 } 130 131 132 /* 133 =============== 134 VM_SymbolToValue 135 =============== 136 */ 137 int VM_SymbolToValue( vm_t *vm, const char *symbol ) { 138 vmSymbol_t *sym; 139 140 for ( sym = vm->symbols ; sym ; sym = sym->next ) { 141 if ( !strcmp( symbol, sym->symName ) ) { 142 return sym->symValue; 143 } 144 } 145 return 0; 146 } 147 148 149 /* 150 ===================== 151 VM_SymbolForCompiledPointer 152 ===================== 153 */ 154 const char *VM_SymbolForCompiledPointer( vm_t *vm, void *code ) { 155 int i; 156 157 if ( code < (void *)vm->codeBase ) { 158 return "Before code block"; 159 } 160 if ( code >= (void *)(vm->codeBase + vm->codeLength) ) { 161 return "After code block"; 162 } 163 164 // find which original instruction it is after 165 for ( i = 0 ; i < vm->codeLength ; i++ ) { 166 if ( (void *)vm->instructionPointers[i] > code ) { 167 break; 168 } 169 } 170 i--; 171 172 // now look up the bytecode instruction pointer 173 return VM_ValueToSymbol( vm, i ); 174 } 175 176 177 178 /* 179 =============== 180 ParseHex 181 =============== 182 */ 183 int ParseHex( const char *text ) { 184 int value; 185 int c; 186 187 value = 0; 188 while ( ( c = *text++ ) != 0 ) { 189 if ( c >= '0' && c <= '9' ) { 190 value = value * 16 + c - '0'; 191 continue; 192 } 193 if ( c >= 'a' && c <= 'f' ) { 194 value = value * 16 + 10 + c - 'a'; 195 continue; 196 } 197 if ( c >= 'A' && c <= 'F' ) { 198 value = value * 16 + 10 + c - 'A'; 199 continue; 200 } 201 } 202 203 return value; 204 } 205 206 /* 207 =============== 208 VM_LoadSymbols 209 =============== 210 */ 211 void VM_LoadSymbols( vm_t *vm ) { 212 int len; 213 char *mapfile, *text_p, *token; 214 char name[MAX_QPATH]; 215 char symbols[MAX_QPATH]; 216 vmSymbol_t **prev, *sym; 217 int count; 218 int value; 219 int chars; 220 int segment; 221 int numInstructions; 222 223 // don't load symbols if not developer 224 if ( !com_developer->integer ) { 225 return; 226 } 227 228 COM_StripExtension( vm->name, name ); 229 Com_sprintf( symbols, sizeof( symbols ), "vm/%s.map", name ); 230 len = FS_ReadFile( symbols, (void **)&mapfile ); 231 if ( !mapfile ) { 232 Com_Printf( "Couldn't load symbol file: %s\n", symbols ); 233 return; 234 } 235 236 numInstructions = vm->instructionPointersLength >> 2; 237 238 // parse the symbols 239 text_p = mapfile; 240 prev = &vm->symbols; 241 count = 0; 242 243 while ( 1 ) { 244 token = COM_Parse( &text_p ); 245 if ( !token[0] ) { 246 break; 247 } 248 segment = ParseHex( token ); 249 if ( segment ) { 250 COM_Parse( &text_p ); 251 COM_Parse( &text_p ); 252 continue; // only load code segment values 253 } 254 255 token = COM_Parse( &text_p ); 256 if ( !token[0] ) { 257 Com_Printf( "WARNING: incomplete line at end of file\n" ); 258 break; 259 } 260 value = ParseHex( token ); 261 262 token = COM_Parse( &text_p ); 263 if ( !token[0] ) { 264 Com_Printf( "WARNING: incomplete line at end of file\n" ); 265 break; 266 } 267 chars = strlen( token ); 268 sym = Hunk_Alloc( sizeof( *sym ) + chars, h_high ); 269 *prev = sym; 270 prev = &sym->next; 271 sym->next = NULL; 272 273 // convert value from an instruction number to a code offset 274 if ( value >= 0 && value < numInstructions ) { 275 value = vm->instructionPointers[value]; 276 } 277 278 sym->symValue = value; 279 Q_strncpyz( sym->symName, token, chars + 1 ); 280 281 count++; 282 } 283 284 vm->numSymbols = count; 285 Com_Printf( "%i symbols parsed from %s\n", count, symbols ); 286 FS_FreeFile( mapfile ); 287 } 288 289 /* 290 ============ 291 VM_DllSyscall 292 293 Dlls will call this directly 294 295 rcg010206 The horror; the horror. 296 297 The syscall mechanism relies on stack manipulation to get it's args. 298 This is likely due to C's inability to pass "..." parameters to 299 a function in one clean chunk. On PowerPC Linux, these parameters 300 are not necessarily passed on the stack, so while (&arg[0] == arg) 301 is true, (&arg[1] == 2nd function parameter) is not necessarily 302 accurate, as arg's value might have been stored to the stack or 303 other piece of scratch memory to give it a valid address, but the 304 next parameter might still be sitting in a register. 305 306 Quake's syscall system also assumes that the stack grows downward, 307 and that any needed types can be squeezed, safely, into a signed int. 308 309 This hack below copies all needed values for an argument to a 310 array in memory, so that Quake can get the correct values. This can 311 also be used on systems where the stack grows upwards, as the 312 presumably standard and safe stdargs.h macros are used. 313 314 As for having enough space in a signed int for your datatypes, well, 315 it might be better to wait for DOOM 3 before you start porting. :) 316 317 The original code, while probably still inherently dangerous, seems 318 to work well enough for the platforms it already works on. Rather 319 than add the performance hit for those platforms, the original code 320 is still in use there. 321 322 For speed, we just grab 15 arguments, and don't worry about exactly 323 how many the syscall actually needs; the extra is thrown away. 324 325 ============ 326 */ 327 int QDECL VM_DllSyscall( int arg, ... ) { 328 #if ((defined __linux__) && (defined __powerpc__)) 329 // rcg010206 - see commentary above 330 int args[16]; 331 int i; 332 va_list ap; 333 334 args[0] = arg; 335 336 va_start(ap, arg); 337 for (i = 1; i < sizeof (args) / sizeof (args[i]); i++) 338 args[i] = va_arg(ap, int); 339 va_end(ap); 340 341 return currentVM->systemCall( args ); 342 #else // original id code 343 return currentVM->systemCall( &arg ); 344 #endif 345 } 346 347 /* 348 ================= 349 VM_Restart 350 351 Reload the data, but leave everything else in place 352 This allows a server to do a map_restart without changing memory allocation 353 ================= 354 */ 355 vm_t *VM_Restart( vm_t *vm ) { 356 vmHeader_t *header; 357 int length; 358 int dataLength; 359 int i; 360 char filename[MAX_QPATH]; 361 362 // DLL's can't be restarted in place 363 if ( vm->dllHandle ) { 364 char name[MAX_QPATH]; 365 int (*systemCall)( int *parms ); 366 367 systemCall = vm->systemCall; 368 Q_strncpyz( name, vm->name, sizeof( name ) ); 369 370 VM_Free( vm ); 371 372 vm = VM_Create( name, systemCall, VMI_NATIVE ); 373 return vm; 374 } 375 376 // load the image 377 Com_Printf( "VM_Restart()\n", filename ); 378 Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name ); 379 Com_Printf( "Loading vm file %s.\n", filename ); 380 length = FS_ReadFile( filename, (void **)&header ); 381 if ( !header ) { 382 Com_Error( ERR_DROP, "VM_Restart failed.\n" ); 383 } 384 385 // byte swap the header 386 for ( i = 0 ; i < sizeof( *header ) / 4 ; i++ ) { 387 ((int *)header)[i] = LittleLong( ((int *)header)[i] ); 388 } 389 390 // validate 391 if ( header->vmMagic != VM_MAGIC 392 || header->bssLength < 0 393 || header->dataLength < 0 394 || header->litLength < 0 395 || header->codeLength <= 0 ) { 396 VM_Free( vm ); 397 Com_Error( ERR_FATAL, "%s has bad header", filename ); 398 } 399 400 // round up to next power of 2 so all data operations can 401 // be mask protected 402 dataLength = header->dataLength + header->litLength + header->bssLength; 403 for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) { 404 } 405 dataLength = 1 << i; 406 407 // clear the data 408 Com_Memset( vm->dataBase, 0, dataLength ); 409 410 // copy the intialized data 411 Com_Memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength ); 412 413 // byte swap the longs 414 for ( i = 0 ; i < header->dataLength ; i += 4 ) { 415 *(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) ); 416 } 417 418 // free the original file 419 FS_FreeFile( header ); 420 421 return vm; 422 } 423 424 /* 425 ================ 426 VM_Create 427 428 If image ends in .qvm it will be interpreted, otherwise 429 it will attempt to load as a system dll 430 ================ 431 */ 432 433 #define STACK_SIZE 0x20000 434 435 vm_t *VM_Create( const char *module, int (*systemCalls)(int *), 436 vmInterpret_t interpret ) { 437 vm_t *vm; 438 vmHeader_t *header; 439 int length; 440 int dataLength; 441 int i, remaining; 442 char filename[MAX_QPATH]; 443 444 if ( !module || !module[0] || !systemCalls ) { 445 Com_Error( ERR_FATAL, "VM_Create: bad parms" ); 446 } 447 448 remaining = Hunk_MemoryRemaining(); 449 450 // see if we already have the VM 451 for ( i = 0 ; i < MAX_VM ; i++ ) { 452 if (!Q_stricmp(vmTable[i].name, module)) { 453 vm = &vmTable[i]; 454 return vm; 455 } 456 } 457 458 // find a free vm 459 for ( i = 0 ; i < MAX_VM ; i++ ) { 460 if ( !vmTable[i].name[0] ) { 461 break; 462 } 463 } 464 465 if ( i == MAX_VM ) { 466 Com_Error( ERR_FATAL, "VM_Create: no free vm_t" ); 467 } 468 469 vm = &vmTable[i]; 470 471 Q_strncpyz( vm->name, module, sizeof( vm->name ) ); 472 vm->systemCall = systemCalls; 473 474 // never allow dll loading with a demo 475 if ( interpret == VMI_NATIVE ) { 476 if ( Cvar_VariableValue( "fs_restrict" ) ) { 477 interpret = VMI_COMPILED; 478 } 479 } 480 481 if ( interpret == VMI_NATIVE ) { 482 // try to load as a system dll 483 Com_Printf( "Loading dll file %s.\n", vm->name ); 484 vm->dllHandle = Sys_LoadDll( module, vm->fqpath , &vm->entryPoint, VM_DllSyscall ); 485 if ( vm->dllHandle ) { 486 return vm; 487 } 488 489 Com_Printf( "Failed to load dll, looking for qvm.\n" ); 490 interpret = VMI_COMPILED; 491 } 492 493 // load the image 494 Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name ); 495 Com_Printf( "Loading vm file %s.\n", filename ); 496 length = FS_ReadFile( filename, (void **)&header ); 497 if ( !header ) { 498 Com_Printf( "Failed.\n" ); 499 VM_Free( vm ); 500 return NULL; 501 } 502 503 // byte swap the header 504 for ( i = 0 ; i < sizeof( *header ) / 4 ; i++ ) { 505 ((int *)header)[i] = LittleLong( ((int *)header)[i] ); 506 } 507 508 // validate 509 if ( header->vmMagic != VM_MAGIC 510 || header->bssLength < 0 511 || header->dataLength < 0 512 || header->litLength < 0 513 || header->codeLength <= 0 ) { 514 VM_Free( vm ); 515 Com_Error( ERR_FATAL, "%s has bad header", filename ); 516 } 517 518 // round up to next power of 2 so all data operations can 519 // be mask protected 520 dataLength = header->dataLength + header->litLength + header->bssLength; 521 for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) { 522 } 523 dataLength = 1 << i; 524 525 // allocate zero filled space for initialized and uninitialized data 526 vm->dataBase = Hunk_Alloc( dataLength, h_high ); 527 vm->dataMask = dataLength - 1; 528 529 // copy the intialized data 530 Com_Memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength ); 531 532 // byte swap the longs 533 for ( i = 0 ; i < header->dataLength ; i += 4 ) { 534 *(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) ); 535 } 536 537 // allocate space for the jump targets, which will be filled in by the compile/prep functions 538 vm->instructionPointersLength = header->instructionCount * 4; 539 vm->instructionPointers = Hunk_Alloc( vm->instructionPointersLength, h_high ); 540 541 // copy or compile the instructions 542 vm->codeLength = header->codeLength; 543 544 if ( interpret >= VMI_COMPILED ) { 545 vm->compiled = qtrue; 546 VM_Compile( vm, header ); 547 } else { 548 vm->compiled = qfalse; 549 VM_PrepareInterpreter( vm, header ); 550 } 551 552 // free the original file 553 FS_FreeFile( header ); 554 555 // load the map file 556 VM_LoadSymbols( vm ); 557 558 // the stack is implicitly at the end of the image 559 vm->programStack = vm->dataMask + 1; 560 vm->stackBottom = vm->programStack - STACK_SIZE; 561 562 Com_Printf("%s loaded in %d bytes on the hunk\n", module, remaining - Hunk_MemoryRemaining()); 563 564 return vm; 565 } 566 567 /* 568 ============== 569 VM_Free 570 ============== 571 */ 572 void VM_Free( vm_t *vm ) { 573 574 if ( vm->dllHandle ) { 575 Sys_UnloadDll( vm->dllHandle ); 576 Com_Memset( vm, 0, sizeof( *vm ) ); 577 } 578 #if 0 // now automatically freed by hunk 579 if ( vm->codeBase ) { 580 Z_Free( vm->codeBase ); 581 } 582 if ( vm->dataBase ) { 583 Z_Free( vm->dataBase ); 584 } 585 if ( vm->instructionPointers ) { 586 Z_Free( vm->instructionPointers ); 587 } 588 #endif 589 Com_Memset( vm, 0, sizeof( *vm ) ); 590 591 currentVM = NULL; 592 lastVM = NULL; 593 } 594 595 void VM_Clear(void) { 596 int i; 597 for (i=0;i<MAX_VM; i++) { 598 if ( vmTable[i].dllHandle ) { 599 Sys_UnloadDll( vmTable[i].dllHandle ); 600 } 601 Com_Memset( &vmTable[i], 0, sizeof( vm_t ) ); 602 } 603 currentVM = NULL; 604 lastVM = NULL; 605 } 606 607 void *VM_ArgPtr( int intValue ) { 608 if ( !intValue ) { 609 return NULL; 610 } 611 // bk001220 - currentVM is missing on reconnect 612 if ( currentVM==NULL ) 613 return NULL; 614 615 if ( currentVM->entryPoint ) { 616 return (void *)(currentVM->dataBase + intValue); 617 } 618 else { 619 return (void *)(currentVM->dataBase + (intValue & currentVM->dataMask)); 620 } 621 } 622 623 void *VM_ExplicitArgPtr( vm_t *vm, int intValue ) { 624 if ( !intValue ) { 625 return NULL; 626 } 627 628 // bk010124 - currentVM is missing on reconnect here as well? 629 if ( currentVM==NULL ) 630 return NULL; 631 632 // 633 if ( vm->entryPoint ) { 634 return (void *)(vm->dataBase + intValue); 635 } 636 else { 637 return (void *)(vm->dataBase + (intValue & vm->dataMask)); 638 } 639 } 640 641 642 /* 643 ============== 644 VM_Call 645 646 647 Upon a system call, the stack will look like: 648 649 sp+32 parm1 650 sp+28 parm0 651 sp+24 return value 652 sp+20 return address 653 sp+16 local1 654 sp+14 local0 655 sp+12 arg1 656 sp+8 arg0 657 sp+4 return stack 658 sp return address 659 660 An interpreted function will immediately execute 661 an OP_ENTER instruction, which will subtract space for 662 locals from sp 663 ============== 664 */ 665 #define MAX_STACK 256 666 #define STACK_MASK (MAX_STACK-1) 667 668 int QDECL VM_Call( vm_t *vm, int callnum, ... ) { 669 vm_t *oldVM; 670 int r; 671 int i; 672 int args[16]; 673 va_list ap; 674 675 676 if ( !vm ) { 677 Com_Error( ERR_FATAL, "VM_Call with NULL vm" ); 678 } 679 680 oldVM = currentVM; 681 currentVM = vm; 682 lastVM = vm; 683 684 if ( vm_debugLevel ) { 685 Com_Printf( "VM_Call( %i )\n", callnum ); 686 } 687 688 // if we have a dll loaded, call it directly 689 if ( vm->entryPoint ) { 690 //rcg010207 - see dissertation at top of VM_DllSyscall() in this file. 691 va_start(ap, callnum); 692 for (i = 0; i < sizeof (args) / sizeof (args[i]); i++) { 693 args[i] = va_arg(ap, int); 694 } 695 va_end(ap); 696 697 r = vm->entryPoint( callnum, args[0], args[1], args[2], args[3], 698 args[4], args[5], args[6], args[7], 699 args[8], args[9], args[10], args[11], 700 args[12], args[13], args[14], args[15]); 701 } else if ( vm->compiled ) { 702 r = VM_CallCompiled( vm, &callnum ); 703 } else { 704 r = VM_CallInterpreted( vm, &callnum ); 705 } 706 707 if ( oldVM != NULL ) // bk001220 - assert(currentVM!=NULL) for oldVM==NULL 708 currentVM = oldVM; 709 return r; 710 } 711 712 //================================================================= 713 714 static int QDECL VM_ProfileSort( const void *a, const void *b ) { 715 vmSymbol_t *sa, *sb; 716 717 sa = *(vmSymbol_t **)a; 718 sb = *(vmSymbol_t **)b; 719 720 if ( sa->profileCount < sb->profileCount ) { 721 return -1; 722 } 723 if ( sa->profileCount > sb->profileCount ) { 724 return 1; 725 } 726 return 0; 727 } 728 729 /* 730 ============== 731 VM_VmProfile_f 732 733 ============== 734 */ 735 void VM_VmProfile_f( void ) { 736 vm_t *vm; 737 vmSymbol_t **sorted, *sym; 738 int i; 739 double total; 740 741 if ( !lastVM ) { 742 return; 743 } 744 745 vm = lastVM; 746 747 if ( !vm->numSymbols ) { 748 return; 749 } 750 751 sorted = Z_Malloc( vm->numSymbols * sizeof( *sorted ) ); 752 sorted[0] = vm->symbols; 753 total = sorted[0]->profileCount; 754 for ( i = 1 ; i < vm->numSymbols ; i++ ) { 755 sorted[i] = sorted[i-1]->next; 756 total += sorted[i]->profileCount; 757 } 758 759 qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort ); 760 761 for ( i = 0 ; i < vm->numSymbols ; i++ ) { 762 int perc; 763 764 sym = sorted[i]; 765 766 perc = 100 * (float) sym->profileCount / total; 767 Com_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName ); 768 sym->profileCount = 0; 769 } 770 771 Com_Printf(" %9.0f total\n", total ); 772 773 Z_Free( sorted ); 774 } 775 776 /* 777 ============== 778 VM_VmInfo_f 779 780 ============== 781 */ 782 void VM_VmInfo_f( void ) { 783 vm_t *vm; 784 int i; 785 786 Com_Printf( "Registered virtual machines:\n" ); 787 for ( i = 0 ; i < MAX_VM ; i++ ) { 788 vm = &vmTable[i]; 789 if ( !vm->name[0] ) { 790 break; 791 } 792 Com_Printf( "%s : ", vm->name ); 793 if ( vm->dllHandle ) { 794 Com_Printf( "native\n" ); 795 continue; 796 } 797 if ( vm->compiled ) { 798 Com_Printf( "compiled on load\n" ); 799 } else { 800 Com_Printf( "interpreted\n" ); 801 } 802 Com_Printf( " code length : %7i\n", vm->codeLength ); 803 Com_Printf( " table length: %7i\n", vm->instructionPointersLength ); 804 Com_Printf( " data length : %7i\n", vm->dataMask + 1 ); 805 } 806 } 807 808 /* 809 =============== 810 VM_LogSyscalls 811 812 Insert calls to this while debugging the vm compiler 813 =============== 814 */ 815 void VM_LogSyscalls( int *args ) { 816 static int callnum; 817 static FILE *f; 818 819 if ( !f ) { 820 f = fopen("syscalls.log", "w" ); 821 } 822 callnum++; 823 fprintf(f, "%i: %i (%i) = %i %i %i %i\n", callnum, args - (int *)currentVM->dataBase, 824 args[0], args[1], args[2], args[3], args[4] ); 825 } 826 827 828 829 #ifdef oDLL_ONLY // bk010215 - for DLL_ONLY dedicated servers/builds w/o VM 830 int VM_CallCompiled( vm_t *vm, int *args ) { 831 return(0); 832 } 833 834 void VM_Compile( vm_t *vm, vmHeader_t *header ) {} 835 #endif // DLL_ONLY