From 2d7593c2994c7aaf9a9bcd4558e48806044597fe Mon Sep 17 00:00:00 2001 From: Daggolin Date: Tue, 26 Sep 2023 23:44:35 +0200 Subject: [PATCH 01/28] Add vm_arm.cpp, vm_interpreted.cpp, vm_local.h and vm_x86.c from mvdevs/jk2mv@7dc84009e037b279bc956aaf6ba771f096ff3f8d. Does not compile. Files unmodified to make diffs easier. --- CMakeLists.txt | 1 + codemp/CMakeLists.txt | 13 + codemp/qcommon/vm_arm.cpp | 1051 +++++++++++++++++ codemp/qcommon/vm_interpreted.cpp | 930 +++++++++++++++ codemp/qcommon/vm_local.h | 217 ++++ codemp/qcommon/vm_x86.cpp | 1819 +++++++++++++++++++++++++++++ 6 files changed, 4031 insertions(+) create mode 100644 codemp/qcommon/vm_arm.cpp create mode 100644 codemp/qcommon/vm_interpreted.cpp create mode 100644 codemp/qcommon/vm_local.h create mode 100644 codemp/qcommon/vm_x86.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 10092066ba..e25fea87bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,6 +116,7 @@ else() set(Architecture "arm64") add_definitions(-DPNG_ARM_NEON_OPT=0) elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm") + set(ARM32 ON) set(Architecture "arm") elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") set(X86 ON) diff --git a/codemp/CMakeLists.txt b/codemp/CMakeLists.txt index c10cabf7ab..c043dd22a6 100644 --- a/codemp/CMakeLists.txt +++ b/codemp/CMakeLists.txt @@ -254,10 +254,23 @@ if(BuildMPEngine OR BuildMPDed) "${MPDir}/qcommon/tags.h" "${MPDir}/qcommon/timing.h" "${MPDir}/qcommon/vm.cpp" + "${MPDir}/qcommon/vm_local.h" + "${MPDir}/qcommon/vm_interpreted.cpp" "${MPDir}/qcommon/z_memman_pc.cpp" ${SharedCommonFiles} ) + + if(X86) + set(MPEngineAndDedCommonFiles ${MPEngineAndDedCommonFiles} + "${MPDir}/qcommon/vm_x86.cpp" + ) + elseif(ARM32) + set(MPEngineAndDedCommonFiles ${MPEngineAndDedCommonFiles} + "${MPDir}/qcommon/vm_arm.cpp" + ) + endif() + if(WIN32) set(MPEngineAndDedCommonFiles ${MPEngineAndDedCommonFiles}) endif(WIN32) diff --git a/codemp/qcommon/vm_arm.cpp b/codemp/qcommon/vm_arm.cpp new file mode 100644 index 0000000000..31d88e0892 --- /dev/null +++ b/codemp/qcommon/vm_arm.cpp @@ -0,0 +1,1051 @@ +#include +#include "vm_local.h" + +// ============================================== +// r3 = instructionPointers +// r4 = instructionCount +// r5 = dataBase +// r6 = programStack pointer +// r7 = opStack +// r8 = dataMask +// r9 = codeBase +// r10 = opStackIndex +// +// vm->code: +// 0: VM_CompiledSyscall +// 4: VM_ARM_VOp +// 8: VM_CompiledErrorJump +// 12: code +// ============================================== + +#define MAX_CONSTANTS_POOL_SIZE 32 + +typedef enum { + R0 = 0 << 12, + R1 = 1 << 12, + R2 = 2 << 12, + R3 = 3 << 12, + R4 = 4 << 12, + R5 = 5 << 12, + R6 = 6 << 12, + R7 = 7 << 12, + R8 = 8 << 12, + R9 = 9 << 12, + R10 = 10 << 12 +} r_t; + +typedef enum { + S0, S1 +} s_t; + +typedef struct { + qboolean movwt; + qboolean idiva; + qboolean mls; + qboolean vfp; +} cpuFeatures_t; + +cpuFeatures_t cpu; +byte *nativeCode; +size_t nativeCodeSize, nativeCodeLength; + +uint32_t localConstantPool[MAX_CONSTANTS_POOL_SIZE]; size_t localConstantPoolLength; +byte **localConstantPoolPointers; size_t localConstantCurrentPool; +byte *firstLocalConstantPtr; + +#define Emit32(X) EmitInteger(0x##X) +void EmitInteger(uint32_t num) { + if (nativeCodeLength + sizeof(uint32_t) >= nativeCodeSize) { + Com_Error(ERR_DROP, "native code too big for buffer"); + } + + *(uint32_t *)(&nativeCode[nativeCodeLength]) = num; + nativeCodeLength += sizeof(uint32_t); +} + +int CalculateConstant12(uint32_t *num) { + uint32_t rot; + uint32_t immediate = *num; + + for (rot = 0; rot < 32; rot += 2) { + if (!(immediate & ~0xFFU)) { + *num = 0; + *num = immediate; + *num |= (rot / 2) << 8; + return 0; + } + + immediate = (immediate << 2) | (immediate >> 30); + } + + return 1; +} + +void EmitLocalConstantPool(qboolean endOfProcedure) { + if (localConstantPoolLength) { + if (!endOfProcedure) { + byte *sourceAddr = &nativeCode[nativeCodeLength]; + byte *destAddr = sourceAddr + sizeof(uint32_t) + localConstantPoolLength * sizeof(uint32_t); + + uint32_t jmpdiff; + if (destAddr >= sourceAddr + 8) { + jmpdiff = (destAddr - (sourceAddr + 8)) / 4; + } else { + jmpdiff = 0x00FFFFFFU - (sourceAddr + 4 - destAddr) / 4; + } + + if (jmpdiff & 0xFF000000) { + Com_Error(ERR_DROP, "jump target out of range"); + } + + EmitInteger(0xEA000000|jmpdiff); + } + + localConstantPoolPointers[localConstantCurrentPool++] = &nativeCode[nativeCodeLength]; + for (size_t i = 0; i < localConstantPoolLength; i++) { + EmitInteger(localConstantPool[i]); + } + + localConstantPoolLength = 0; + } +} + +void EmitTOS2Reg(r_t r, qboolean pop) { + EmitInteger(0xE797010A | (uint32_t)r); // ldr rX, [r7, r10, LSL #2] + + if (pop) { + Emit32(E24AA001); // sub r10, #1 + Emit32(E20AA0FF); // and r10, #255 + } +} + +void EmitTOS2SReg(s_t s, qboolean pop) { + Emit32(E087010A); // add r0, r7, r10, LSL #2 + switch (s) { + case S0: Emit32(ED900A00); break; // flds s0, [r0] + case S1: Emit32(EDD00A00); break; // flds s1, [r0] + } + + if (pop) { + Emit32(E24AA001); // sub r10, #1 + Emit32(E20AA0FF); // and r10, #255 + } +} + +void EmitReg2TOS(r_t r, qboolean push) { + if (push) { + Emit32(E28AA001); // add r10, #1 + Emit32(E20AA0FF); // and r10, #255 + } + + EmitInteger(0xE787010A | (uint32_t)r); // str rX, [r7, r10, LSL #2] +} + +void EmitSReg2TOS(s_t s, qboolean push) { + if (push) { + Emit32(E28AA001); // add r10, #1 + Emit32(E20AA0FF); // and r10, #255 + } + + Emit32(E087010A); // add r0, r7, r10, LSL #2 + switch (s) { + case S0: Emit32(ED800A00); break; // fsts s0, [r0] + case S1: Emit32(EDC00A00); break; // fsts s1, [r0] + } +} + +void EmitRegisterLoadConst(r_t r, uint32_t constant) { + uint32_t immediate = constant; + if ( !CalculateConstant12(&immediate) ) { + uint32_t opcode = 0xE3A00000; // mov rX, #immediate + opcode |= (uint32_t)r; + opcode |= immediate; + + EmitInteger(opcode); + return; + } + + immediate = ~immediate; + if ( !CalculateConstant12(&immediate) ) { + uint32_t opcode = 0xE3E00000; // movn rX, #immediate + opcode |= (uint32_t)r; + opcode |= immediate; + + EmitInteger(opcode); + return; + } + + // at this point the constant needs to be loaded either by movw and movt (which is >= ARMv7) + // or by loading it from a memory address near to the current pc + if (cpu.movwt) { + uint32_t w = (constant & 0x00000FFF) | ((constant & 0x0000F000) << 4); + EmitInteger(0xE3000000 | (uint32_t)r | w); // movw rX, #16bit immediate + + uint32_t t = (constant >> 16); + t = (t & 0x00000FFF) | ((t & 0x0000F000) << 4); + EmitInteger(0xE3400000 | (uint32_t)r | t); // movt rX, #16bit immediate + } else { + if (localConstantPoolLength >= MAX_CONSTANTS_POOL_SIZE) { + EmitLocalConstantPool(qfalse); + } + + if (!firstLocalConstantPtr) firstLocalConstantPtr = &nativeCode[nativeCodeLength]; + if (localConstantPoolPointers[localConstantCurrentPool]) { + byte *constantPtr = localConstantPoolPointers[localConstantCurrentPool] + localConstantPoolLength * sizeof(uint32_t); + intptr_t ptrdiff = -8 + constantPtr - &nativeCode[nativeCodeLength]; + + uint32_t opcode = 0; + if (ptrdiff <= 0) { + opcode |= 0xE51F0000; // ldr rX, [pc, #-X] + ptrdiff = -ptrdiff; + } else { + opcode |= 0xE59F0000; // ldr rX, [pc, #+X] + } + + if (ptrdiff > 4095) { + // too far away. should never happen. + Com_Error(ERR_DROP, "constant relative address out of range."); + } + + opcode |= (uint32_t)r; + opcode |= (uint32_t)ptrdiff; + EmitInteger(opcode); + } else { + // address is still unknown (first pass) + Emit32(00000000); + } + + localConstantPool[localConstantPoolLength++] = constant; + } +} + +void EmitConstantJumpInstruction(vm_t *vm, opcode_t opCode, uint32_t destinationOpCodeNum) { + size_t sourceAddr = nativeCodeLength; + + if (destinationOpCodeNum >= (uint32_t)vm->instructionCount) { + Com_Error(ERR_DROP, "jump violation detected."); + } + + size_t destAddr = vm->instructionPointers[destinationOpCodeNum]; + if (!destAddr) { + // address is still unknown (first pass) + Emit32(00000000); + return; + } + + uint32_t jmpdiff; + if (destAddr >= sourceAddr + 8) { + jmpdiff = (destAddr - (sourceAddr + 8)) / 4; + } else { + jmpdiff = 0x00FFFFFFU - (sourceAddr + 4 - destAddr) / 4; + } + + if (jmpdiff & 0xFF000000) { + Com_Error(ERR_DROP, "jump target out of range."); + } + + switch (opCode) { + case OP_EQ: case OP_EQF: EmitInteger(0x0A000000|jmpdiff); break; // (BEQ) Check equality (integer or float) (compares NIS vs TOS, jump to $PARM if true). + case OP_NE: case OP_NEF: EmitInteger(0x1A000000|jmpdiff); break; // (BNE) Check inequality (integer or float) (NIS vs TOS, jump to $PARM if true). + case OP_LTI: case OP_LTF: EmitInteger(0xBA000000|jmpdiff); break; // (BLT) Check less-than (signed integer or float) (NIS vs TOS, jump to $PARM if true). + case OP_LEI: case OP_LEF: EmitInteger(0xDA000000|jmpdiff); break; // (BLE) Check less-than or equal-to (signed integer) (NIS vs TOS, jump to $PARM if true). + case OP_GTI: EmitInteger(0xCA000000|jmpdiff); break; // (BGT) Check greater-than (signed integer) (NIS vs TOS), jump to $PARM if true. + case OP_GEI: EmitInteger(0xAA000000|jmpdiff); break; // (BGE) Check greater-than or equal-to (signed integer) (NIS vs TOS), jump to $PARM if true. + case OP_LTU: EmitInteger(0x3A000000|jmpdiff); break; // (BCC) Check less-than (unsigned integer) (NIS vs TOS), jump to $PARM if true. + case OP_LEU: EmitInteger(0x9A000000|jmpdiff); break; // (BLS) Check less-than or equal-to (unsigned integer) (NIS vs TOS), jump to $PARM if true. + case OP_GTU: case OP_GTF: EmitInteger(0x8A000000|jmpdiff); break; // (BHI) Check greater-than (unsigned integer or float) (NIS vs TOS), jump to $PARM if true. + case OP_GEU: case OP_GEF: EmitInteger(0x2A000000|jmpdiff); break; // (BCS) Check greater-than or equal-to (unsigned integer or float) (NIS vs TOS), jump to $PARM if true. + default: Com_Error(ERR_DROP, "OpCode implementation missing for %u", opCode); + } +} + +void __attribute__((optimize("O0"))) VM_ARM_VOp() { + int32_t nis, tos, op; + uint32_t parm; + + __asm__ volatile( + "str r0, %[nis]\n" + "str r1, %[tos]\n" + "str r2, %[op]\n" + "str r3, %[parm]\n" + : [nis] "=m" (nis), + [tos] "=m" (tos), + [op] "=m" (op), + [parm] "=m" (parm) + :: "r0", "r1", "r2", "r3" + ); + + switch (op) { + case OP_BLOCK_COPY: + VM_BlockCopy(nis, tos, parm); + return; + case OP_DIVI: + tos = nis / tos; + break; + case OP_DIVU: + tos = (uint32_t)nis / (uint32_t)tos; + break; + case OP_MODI: + tos = nis % tos; + break; + case OP_MODU: + tos = (uint32_t)nis % (uint32_t)tos; + break; + + case OP_EQF: + tos = *(float *)&nis == *(float *)&tos; + break; + case OP_NEF: + tos = *(float *)&nis != *(float *)&tos; + break; + case OP_LTF: + tos = *(float *)&nis < *(float *)&tos; + break; + case OP_LEF: + tos = *(float *)&nis <= *(float *)&tos; + break; + case OP_GTF: + tos = *(float *)&nis > *(float *)&tos; + break; + case OP_GEF: + tos = *(float *)&nis >= *(float *)&tos; + break; + + case OP_NEGF: + *(float *)&tos = -*(float *)&tos; + break; + case OP_ADDF: + *(float *)&tos = *(float *)&nis + *(float *)&tos; + break; + case OP_SUBF: + *(float *)&tos = *(float *)&nis - *(float *)&tos; + break; + case OP_DIVF: + *(float *)&tos = *(float *)&nis / *(float *)&tos; + break; + case OP_MULF: + *(float *)&tos = *(float *)&nis * *(float *)&tos; + break; + case OP_CVIF: + *(float *)&tos = tos; + break; + case OP_CVFI: + tos = *(float *)&tos; + break; + } + + __asm__ volatile( + "ldr r0, %[tos]\n" + :: [tos] "m" (tos) + : "r0" + ); +} + +void __attribute__((optimize("O0"))) VM_CompiledSyscall() { + int *opStack, *sp; + int nic, opStackIndex; + vm_t *savedvm; + + __asm__ volatile( + "str r0, %[nic]\n" + "str r6, %[programStackPtr]\n" + "str r7, %[opStack]\n" + "str r10, %[opStackIndex]\n" + : [nic] "=m" (nic), + [programStackPtr] "=m" (sp), + [opStack] "=m" (opStack), + [opStackIndex] "=m" (opStackIndex) + :: "r0", "r6", "r7", "r10" + ); + + currentVM->programStack = ((byte *)sp - currentVM->dataBase); + + intptr_t args[MAX_VMSYSCALL_ARGS]; + args[0] = ~nic; + int index; + for(index = 1; index < MAX_VMSYSCALL_ARGS; index++) + args[index] = sp[1 + index]; + + savedvm = currentVM; + opStack += opStackIndex; + *opStack = currentVM->systemCall(args); + currentVM = savedvm; +} + +void VM_CompiledErrorJump() { + Com_Error(ERR_DROP, "program tried to execute code outside VM"); +} + +void VM_Compile(vm_t *vm, vmHeader_t *header) { + size_t i, pass; + int ic; + +#ifdef __linux__ + FILE *cpuinfo = fopen("/proc/cpuinfo", "rb"); + + char *arg = 0; size_t size = 0; + int extnum = 0; char extstr[128]; + extstr[0] = '\0'; + while (getline(&arg, &size, cpuinfo) != -1) { + if (!strncmp(arg, "model name", 10)) { + if (strstr(arg, "ARMv7") && !cpu.movwt && !cpu.mls) { + cpu.movwt = cpu.mls = qtrue; + Q_strcat(extstr, sizeof(extstr), " movw/movt mls"); extnum++; + } + } + + if (!strncmp(arg, "Features", 8)) { + if (strstr(arg, " idiva") && !cpu.idiva) { + cpu.idiva = qtrue; + Q_strcat(extstr, sizeof(extstr), " idiva"); extnum++; + } + + if (strstr(arg, " vfp") && !cpu.vfp) { + cpu.vfp = qtrue; + Q_strcat(extstr, sizeof(extstr), " vfp"); extnum++; + } + } + } + + if (extnum) { + Com_Printf("using instruction set extensions:%s\n", extstr); + } + + free(arg); fclose(cpuinfo); +#else + // TODO +#endif + + nativeCodeSize = (size_t)vm->codeLength * 10; + nativeCode = (byte *)Z_Malloc(nativeCodeSize, TAG_VM, qfalse); + localConstantPoolPointers = (byte **)Z_Malloc(vm->instructionCount * sizeof(byte *), TAG_VM, qtrue); + + for (pass = 0; pass < 2; pass++) { + nativeCodeLength = 0; + localConstantCurrentPool = 0; + localConstantPoolLength = 0; + + // Function Pointers + EmitInteger((uint32_t)VM_CompiledSyscall); + EmitInteger((uint32_t)VM_ARM_VOp); + EmitInteger((uint32_t)VM_CompiledErrorJump); + + // Init registers code + EmitRegisterLoadConst(R3, (uint32_t)vm->instructionPointers); + EmitRegisterLoadConst(R4, vm->instructionCount); + EmitRegisterLoadConst(R5, (uint32_t)vm->dataBase); + EmitRegisterLoadConst(R8, (uint32_t)vm->dataMask); + + for (i = 0, ic = 0; ic < vm->instructionCount; i++, ic++) { + byte *instructionptr = (byte *)header + header->codeOffset + i; + opcode_t currOp = (opcode_t)*instructionptr; + byte *arg = instructionptr + 1; + + if (localConstantPoolLength) { + ptrdiff_t constoffsetlen = ((ptrdiff_t)-8 + &nativeCode[nativeCodeLength] + sizeof(uint32_t) + localConstantPoolLength * sizeof(uint32_t)) - firstLocalConstantPtr; + if (constoffsetlen >= (ptrdiff_t)(4095 - 15 * sizeof(uint32_t))) { + EmitLocalConstantPool(qfalse); + } + } + + vm->instructionPointers[ic] = nativeCodeLength; + + switch (currOp) { + /* ---------------------------------------------------------------------------- */ + /* ---------------------------------- BASICS ---------------------------------- */ + /* ---------------------------------------------------------------------------- */ + case OP_IGNORE: { + Emit32(E1A00000); // nop + break; + } case OP_ENTER: { + // Begin procedure body, adjust stack $PARM octets for frame (always at least 8 (i.e. 2 words)). + // Frame contains all local storage/variables and arguments space for any calls within this procedure. + Emit32(E52DE004); // push {lr} + EmitRegisterLoadConst(R0, *(uint32_t *)arg); + Emit32(E0466000); // sub r6, r6, r0 + i += sizeof(uint32_t); + break; + } case OP_LEAVE: { + // End procedure body, $PARM is same as that of the matching ENTER. + EmitRegisterLoadConst(R0, *(uint32_t *)arg); + Emit32(E0866000); // add r6, r6, r0 + Emit32(E49DF004); // pop {pc} + + EmitLocalConstantPool(qtrue); + i += sizeof(uint32_t); + break; + } case OP_LOCAL: { + // Get address of local storage (local variable or argument) (TOS <- (frame + $PARM)). + EmitRegisterLoadConst(R1, *(uint32_t *)arg); + Emit32(E0860001); // add r0, r6, r1 + Emit32(E0400005); // sub r0, r0, r5 + + EmitReg2TOS(R0, qtrue); + i += sizeof(uint32_t); + break; + } case OP_CONST: { + // Push literal value onto stack (TOS <- $PARM). + EmitRegisterLoadConst(R0, *(uint32_t *)arg); + EmitReg2TOS(R0, qtrue); + i += sizeof(uint32_t); + break; + } case OP_LOAD1: { + // Load 1-octet value from address in TOS (TOS <- [TOS]). + EmitTOS2Reg(R0, qfalse); + Emit32(E0000008); // and r0, r0, r8 + + Emit32(E7D50000); // ldrb r0, [r5, r0] + EmitReg2TOS(R0, qfalse); + break; + } case OP_LOAD2: { + // Load 2-octet value from address in TOS (TOS <- [TOS]). + EmitTOS2Reg(R0, qfalse); + Emit32(E0000008); // and r0, r0, r8 + Emit32(E3C00001); // bic r0, r0, #1 + + Emit32(E19500B0); // ldrh r0, [r5, r0] + EmitReg2TOS(R0, qfalse); + break; + } case OP_LOAD4: { + // Load 4-octet value from address in TOS (TOS <- [TOS]). + EmitTOS2Reg(R0, qfalse); + Emit32(E0000008); // and r0, r0, r8 + Emit32(E3C00003); // bic r0, r0, #3 + + Emit32(E7950000); // ldr r0, [r5, r0] + EmitReg2TOS(R0, qfalse); + break; + } case OP_STORE1: { + // TOS is 1-octet value to store, destination address in next-in-stack ([NIS] <- TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qtrue); + Emit32(E0000008); // and r0, r0, r8 + + Emit32(E7C51000); // strb r1, [r5, r0] + break; + } case OP_STORE2: { + // TOS is 2-octet value to store, destination address in next-in-stack ([NIS] <- TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qtrue); + Emit32(E0000008); // and r0, r0, r8 + Emit32(E3C00001); // bic r0, r0, #1 + + Emit32(E18510B0); // strh r1, [r5, r0] + break; + } case OP_STORE4: { + // TOS is 4-octet value to store, destination address in next-in-stack ([NIS] <- TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qtrue); + Emit32(E0000008); // and r0, r0, r8 + Emit32(E3C00003); // bic r0, r0, #3 + + Emit32(E7851000); // str r1, [r5, r0] + break; + } case OP_PUSH: { + // Push nonsense (void) value to opstack (TOS <- 0). + Emit32(E28AA001); // add r10, #1 + Emit32(E20AA0FF); // and r10, #255 + break; + } case OP_POP: { + // Pop a value from stack (remove TOS, decrease stack by 1). + Emit32(E24AA001); // sub r10, #1 + Emit32(E20AA0FF); // and r10, #255 + break; + } case OP_ARG: { + // TOS is 4-octet value to store into arguments-marshalling space of the indicated octet offset (ARGS[offset] <- TOS). + Emit32(E0460005); // sub r0, r6, r5 + EmitRegisterLoadConst(R1, *(uint8_t *)arg); + Emit32(E0800001); // add r0, r0, r1 + Emit32(E0000008); // and r0, r0, r8 + Emit32(E3C00003); // bic r0, r0, #3 + + EmitTOS2Reg(R1, qtrue); + Emit32(E7851000); // str r1, [r5, r0] + i += sizeof(uint8_t); + break; + } case OP_BLOCK_COPY: { + // Copy $PARM bytes from [TOS] to [NIS] + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qtrue); + Emit32(E92D07F8); // push {r3-r10} + EmitRegisterLoadConst(R2, currOp); + EmitRegisterLoadConst(R3, *(uint32_t *)arg); + Emit32(E5995004); // ldr r5, [r9, #4] (VM_ARM_VOp) + Emit32(E12FFF35); // blx r5 + Emit32(E8BD07F8); // pop {r3-r10} + i += sizeof(uint32_t); + break; + /* ---------------------------------------------------------------------------- */ + /* --------------------------------- BRANCHES --------------------------------- */ + /* ---------------------------------------------------------------------------- */ + } case OP_CALL: { + // Make call to procedure (code address <- TOS). + EmitTOS2Reg(R0, qtrue); + Emit32(E3500000); // cmp r0, #0 + Emit32(BA000006); // blt dosyscall + Emit32(E1500004); // cmp r0, r4 + Emit32(2A000002); // bcs errjmp + + // in-vm-call: + Emit32(E7931100); // ldr r1, [r3, r0, lsl #2] + Emit32(E12FFF31); // blx r1 + Emit32(EA000007); // b outjmp + + // errjmp: + Emit32(E5991008); // ldr r1, [r9, #8] (VM_CompiledErrorJump) + Emit32(E12FFF31); // blx r1 + + // dosyscall: + Emit32(E28AA001); // add r10, #1 + Emit32(E20AA0FF); // and r10, #255 + Emit32(E92D07F8); // push {r3-r10} + Emit32(E5991000); // ldr r1, [r9] (VM_CompiledSyscall) + Emit32(E12FFF31); // blx r1 + Emit32(E8BD07F8); // pop {r3-r10} + + // outjmp: + break; + } case OP_JUMP: { + // Branch (code address <- TOS) + EmitTOS2Reg(R0, qtrue); + Emit32(E1500004); // cmp r0, r4 + Emit32(3A000001); // bcc dojmp + + // errjmp: + Emit32(E5991008); // ldr r1, [r9, #8] (VM_CompiledErrorJump) + Emit32(E12FFF31); // blx r1 + + // dojmp: + Emit32(E7931100); // ldr r1, [r3, r0, lsl #2] + Emit32(E12FFF11); // bx r1 + break; + } 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: { + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qtrue); + Emit32(E1500001); // cmp r0, r1 + EmitConstantJumpInstruction(vm, currOp, *(uint32_t *)arg); + i += sizeof(uint32_t); + break; + } case OP_EQF: + case OP_NEF: + case OP_LTF: + case OP_LEF: + case OP_GTF: + case OP_GEF: { + if (cpu.vfp) { + EmitTOS2SReg(S1, qtrue); + EmitTOS2SReg(S0, qtrue); + Emit32(EEB40AE0); // fcmpes s0, s1 + Emit32(EEF1FA10); // fmstat + EmitConstantJumpInstruction(vm, currOp, *(uint32_t *)arg); + } else { + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qtrue); + Emit32(E92D07F8); // push {r3-r10} + EmitRegisterLoadConst(R2, currOp); + Emit32(E5995004); // ldr r5, [r9, #4] (VM_ARM_VOp) + Emit32(E12FFF35); // blx r5 + Emit32(E8BD07F8); // pop {r3-r10} + + Emit32(E3500001); // cmp r0, #1 + EmitConstantJumpInstruction(vm, OP_EQ, *(uint32_t *)arg); + } + i += sizeof(uint32_t); + break; + /* ---------------------------------------------------------------------------- */ + /* ---------------------------- INTEGER OPERATIONS ---------------------------- */ + /* ---------------------------------------------------------------------------- */ + } case OP_SEX8: { + // Sign-extend 8-bit (TOS <- TOS). + EmitTOS2Reg(R0, qfalse); + Emit32(E6AF0070); // sxtb r0, r0 + EmitReg2TOS(R0, qfalse); + break; + } case OP_SEX16: { + // Sign-extend 16-bit (TOS <- TOS). + EmitTOS2Reg(R0, qfalse); + Emit32(E6BF0070); // sxth r0, r0 + EmitReg2TOS(R0, qfalse); + break; + } case OP_NEGI: { + // Negate signed integer (TOS <- -TOS). + EmitTOS2Reg(R0, qfalse); + Emit32(E2600000); // neg r0, r0 + EmitReg2TOS(R0, qfalse); + break; + } case OP_ADD: { + // Add integer-wise (TOS <- NIS + TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + Emit32(E0800001); // add r0, r0, r1 + EmitReg2TOS(R0, qfalse); + break; + } case OP_SUB: { + // Subtract integer-wise (TOS <- NIS - TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + Emit32(E0400001); // sub r0, r0, r1 + EmitReg2TOS(R0, qfalse); + break; + } case OP_DIVI: { + // Divide (signed integer) (TOS <- NIS / TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + + if (cpu.idiva) { + Emit32(E710F110); // sdiv r0, r0, r1 + } else { + Emit32(E92D07F8); // push {r3-r10} + EmitRegisterLoadConst(R2, OP_DIVI); + Emit32(E5995004); // ldr r5, [r9, #4] (VM_ARM_VOp) + Emit32(E12FFF35); // blx r5 + Emit32(E8BD07F8); // pop {r3-r10} + } + + EmitReg2TOS(R0, qfalse); + break; + } case OP_DIVU: { + // Divide (unsigned integer) (TOS <- NIS / TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + + if (cpu.idiva) { + Emit32(E730F110); // udiv r0, r0, r1 + } else { + Emit32(E92D07F8); // push {r3-r10} + EmitRegisterLoadConst(R2, OP_DIVU); + Emit32(E5995004); // ldr r5, [r9, #4] (VM_ARM_VOp) + Emit32(E12FFF35); // blx r5 + Emit32(E8BD07F8); // pop {r3-r10} + } + + EmitReg2TOS(R0, qfalse); + break; + } case OP_MODI: { + // Modulo (signed integer) (TOS <- NIS mod TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + + if (cpu.idiva && cpu.mls) { + Emit32(E712F110); // sdiv r2, r0, r1 + Emit32(E0600291); // mls r0, r1, r2, r0 + } else { + Emit32(E92D07F8); // push {r3-r10} + EmitRegisterLoadConst(R2, OP_MODI); + Emit32(E5995004); // ldr r5, [r9, #4] (VM_ARM_VOp) + Emit32(E12FFF35); // blx r5 + Emit32(E8BD07F8); // pop {r3-r10} + } + + EmitReg2TOS(R0, qfalse); + break; + } case OP_MODU: { + // Modulo (unsigned integer) (TOS <- NIS mod TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + + if (cpu.idiva && cpu.mls) { + Emit32(E732F110); // udiv r2, r0, r1 + Emit32(E0600291); // mls r0, r1, r2, r0 + } else { + Emit32(E92D07F8); // push {r3-r10} + EmitRegisterLoadConst(R2, OP_MODU); + Emit32(E5995004); // ldr r5, [r9, #4] (VM_ARM_VOp) + Emit32(E12FFF35); // blx r5 + Emit32(E8BD07F8); // pop {r3-r10} + } + + EmitReg2TOS(R0, qfalse); + break; + } case OP_MULI: + case OP_MULU: { + // Multiply (signed/unsigned integer) (TOS <- NIS * TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + Emit32(E0000190); // mul r0, r0, r1 + EmitReg2TOS(R0, qfalse); + break; + } case OP_BAND: { + // Bitwise AND (TOS <- NIS & TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + Emit32(E0000001); // and r0, r0, r1 + EmitReg2TOS(R0, qfalse); + break; + } case OP_BOR: { + // Bitwise OR (TOS <- NIS | TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + Emit32(E1800001); // orr r0, r0, r1 + EmitReg2TOS(R0, qfalse); + break; + } case OP_BXOR: { + // Bitwise XOR (TOS <- NIS ^ TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + Emit32(E0200001); // eor r0, r0, r1 + EmitReg2TOS(R0, qfalse); + break; + } case OP_BCOM: { + // Bitwise complement (TOS <- ~TOS). + EmitTOS2Reg(R0, qfalse); + Emit32(E1E00000); // mvn r0, r0 + EmitReg2TOS(R0, qfalse); + break; + } case OP_LSH: { + // Bitwise left-shift (TOS <- NIS << TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + Emit32(E1A00110); // lsl r0, r0, r1 + EmitReg2TOS(R0, qfalse); + break; + } case OP_RSHI: { + // Algebraic (signed) right-shift (TOS <- NIS >> TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + Emit32(E1A00150); // asr r0, r0, r1 + EmitReg2TOS(R0, qfalse); + break; + } case OP_RSHU: { + // Bitwise (unsigned) right-shift (TOS <- NIS >> TOS). + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + Emit32(E1A00130); // lsr r0, r0, r1 + EmitReg2TOS(R0, qfalse); + break; + /* ---------------------------------------------------------------------------- */ + /* ----------------------------- FLOAT OPERATIONS ----------------------------- */ + /* ---------------------------------------------------------------------------- */ + } case OP_NEGF: { + // Negate float value (TOS <- -TOS). + if (cpu.vfp) { + EmitTOS2SReg(S0, qfalse); + Emit32(EEB10A40); // fnegs s0, s0 + EmitSReg2TOS(S0, qfalse); + } else { + EmitTOS2Reg(R1, qfalse); + Emit32(E92D07F8); // push {r3-r10} + EmitRegisterLoadConst(R2, OP_NEGF); + Emit32(E5995004); // ldr r5, [r9, #4] (VM_ARM_VOp) + Emit32(E12FFF35); // blx r5 + Emit32(E8BD07F8); // pop {r3-r10} + EmitReg2TOS(R0, qfalse); + } + break; + } case OP_ADDF: { + // Add integer-wise (TOS <- NIS + TOS). + if (cpu.vfp) { + EmitTOS2SReg(S1, qtrue); + EmitTOS2SReg(S0, qfalse); + Emit32(EE300A20); // fadds s0, s0, s1 + EmitSReg2TOS(S0, qfalse); + } else { + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + Emit32(E92D07F8); // push {r3-r10} + EmitRegisterLoadConst(R2, OP_ADDF); + Emit32(E5995004); // ldr r5, [r9, #4] (VM_ARM_VOp) + Emit32(E12FFF35); // blx r5 + Emit32(E8BD07F8); // pop {r3-r10} + EmitReg2TOS(R0, qfalse); + } + break; + } case OP_SUBF: { + // Subtract floats (TOS <- NIS - TOS). + if (cpu.vfp) { + EmitTOS2SReg(S1, qtrue); + EmitTOS2SReg(S0, qfalse); + Emit32(EE300A60); // fsubs s0, s0, s1 + EmitSReg2TOS(S0, qfalse); + } else { + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + Emit32(E92D07F8); // push {r3-r10} + EmitRegisterLoadConst(R2, OP_SUBF); + Emit32(E5995004); // ldr r5, [r9, #4] (VM_ARM_VOp) + Emit32(E12FFF35); // blx r5 + Emit32(E8BD07F8); // pop {r3-r10} + EmitReg2TOS(R0, qfalse); + } + break; + } case OP_DIVF: { + // Divide floats (TOS <- NIS / TOS). + if (cpu.vfp) { + EmitTOS2SReg(S1, qtrue); + EmitTOS2SReg(S0, qfalse); + Emit32(EE800A20); // fdivs s0, s0, s1 + EmitSReg2TOS(S0, qfalse); + } else { + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + Emit32(E92D07F8); // push {r3-r10} + EmitRegisterLoadConst(R2, OP_DIVF); + Emit32(E5995004); // ldr r5, [r9, #4] (VM_ARM_VOp) + Emit32(E12FFF35); // blx r5 + Emit32(E8BD07F8); // pop {r3-r10} + EmitReg2TOS(R0, qfalse); + } + break; + } case OP_MULF: { + // Multiply floats (TOS <- NIS x TOS). + if (cpu.vfp) { + EmitTOS2SReg(S1, qtrue); + EmitTOS2SReg(S0, qfalse); + Emit32(EE200A20); // fmuls s0, s0, s1 + EmitSReg2TOS(S0, qfalse); + } else { + EmitTOS2Reg(R1, qtrue); + EmitTOS2Reg(R0, qfalse); + Emit32(E92D07F8); // push {r3-r10} + EmitRegisterLoadConst(R2, OP_MULF); + Emit32(E5995004); // ldr r5, [r9, #4] (VM_ARM_VOp) + Emit32(E12FFF35); // blx r5 + Emit32(E8BD07F8); // pop {r3-r10} + EmitReg2TOS(R0, qfalse); + } + break; + } case OP_CVIF: { + // Convert signed integer to float (TOS <- TOS). + if (cpu.vfp) { + EmitTOS2SReg(S0, qfalse); + Emit32(EEB80AC0); // fsitos s0, s0 + EmitSReg2TOS(S0, qfalse); + } else { + EmitTOS2Reg(R1, qfalse); + Emit32(E92D07F8); // push {r3-r10} + EmitRegisterLoadConst(R2, OP_CVIF); + Emit32(E5995004); // ldr r5, [r9, #4] (VM_ARM_VOp) + Emit32(E12FFF35); // blx r5 + Emit32(E8BD07F8); // pop {r3-r10} + EmitReg2TOS(R0, qfalse); + } + break; + } case OP_CVFI: { + // Convert float to signed integer (TOS <- TOS). + if (cpu.vfp) { + EmitTOS2SReg(S0, qfalse); + Emit32(EEBD0AC0); // ftosizs s0, s0 + EmitSReg2TOS(S0, qfalse); + } else { + EmitTOS2Reg(R1, qfalse); + Emit32(E92D07F8); // push {r3-r10} + EmitRegisterLoadConst(R2, OP_CVFI); + Emit32(E5995004); // ldr r5, [r9, #4] (VM_ARM_VOp) + Emit32(E12FFF35); // blx r5 + Emit32(E8BD07F8); // pop {r3-r10} + EmitReg2TOS(R0, qfalse); + } + break; + } default: + Com_Error(ERR_FATAL, "unknown instruction %#02x!", currOp); + } + } + } + + // copy to an exact sized buffer with the appropriate permission bits + vm->codeLength = nativeCodeLength; + + vm->codeBase = (byte *)mmap(NULL, nativeCodeLength, PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); + if(vm->codeBase == MAP_FAILED) { + Com_Error(ERR_FATAL, "VM_CompileARM: can't mmap memory"); + } + + Com_Memcpy( vm->codeBase, nativeCode, nativeCodeLength ); + + if(mprotect(vm->codeBase, nativeCodeLength, PROT_READ|PROT_EXEC)) { + Com_Error(ERR_FATAL, "VM_CompileARM: mprotect failed"); + } + + Z_Free(nativeCode); + Z_Free(localConstantPoolPointers); + + Com_Printf( "VM file %s compiled to %zu bytes of code\n", vm->name, nativeCodeLength ); + + // offset all the instruction pointers for the new location + for ( int i = 0 ; i < header->instructionCount ; i++ ) { + vm->instructionPointers[i] += (intptr_t)vm->codeBase; + } +} + +int __attribute__((optimize("O0"))) VM_CallCompiled(vm_t *vm, int *args) { + int stack[OPSTACK_SIZE / sizeof(int)]; + unsigned int stackIndex; + int programStack, stackOnEntry; + int *programStackPtr; + byte *image; + int *stackPtr; + int arg; + + currentVM = vm; + + // interpret the code + vm->currentlyInterpreting = qtrue; + + // we might be called recursively, so this might not be the very top + programStack = stackOnEntry = vm->programStack; + + // set up the stack frame + image = vm->dataBase; + + programStack -= ( 8 + 4 * MAX_VMMAIN_ARGS ); + + for ( arg = 0; arg < MAX_VMMAIN_ARGS; arg++ ) + *(int *)&image[ programStack + 8 + arg * 4 ] = args[ arg ]; + + *(int *)&image[ programStack + 4 ] = 0; // return stack + *(int *)&image[ programStack ] = -1; // will terminate the loop on return + + programStackPtr = (int *)&image[ programStack ]; + + // set up opStack + *(unsigned int *)stack = 0xDEADC0DEu; + stackPtr = stack; + stackIndex = 0; + + // off we go into generated code... + __asm__ volatile ( + "push {r0-r10,lr}\n" + + "ldr r6, %[programStack]\n" + "ldr r7, %[stack]\n" + "ldr r9, %[codeAddress]\n" + "ldr r10, %[stackIndex]\n" + + "mov r0, r9\n" + "add r0, #12\n" + "blx r0\n" + + "str r6, %[programStackOut]\n" + "str r10, %[stackIndexOut]\n" + + "pop {r0-r10,lr}" + : [programStackOut] "=m" (programStackPtr), + [stackIndexOut] "=m" (stackIndex) + : [codeAddress] "m" (vm->codeBase), + [programStack] "m" (programStackPtr), + [stack] "m" (stackPtr), + [stackIndex] "m" (stackIndex) + : "r0", "r6", "r7", "r9", "r10" + ); + + if (*(unsigned int *)stack != 0xDEADC0DEu || stackIndex != 1) { + Com_Error(ERR_DROP, "opStack corrupted in VM_CallCompiled"); + } + + if (programStackPtr != (int *)&image[programStack]) { + Com_Error(ERR_DROP, "programStack corrupted in VM_CallCompiled"); + } + + vm->programStack = stackOnEntry; + + return stack[stackIndex]; +} diff --git a/codemp/qcommon/vm_interpreted.cpp b/codemp/qcommon/vm_interpreted.cpp new file mode 100644 index 0000000000..8aa64f98ce --- /dev/null +++ b/codemp/qcommon/vm_interpreted.cpp @@ -0,0 +1,930 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +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 Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "vm_local.h" +#include + +//#define DEBUG_VM +#ifdef DEBUG_VM +static const char * const 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 + +#if idppc + +//FIXME: these, um... look the same to me +#if defined(__GNUC__) +static ID_INLINE unsigned int loadWord(void *addr) { + unsigned int word; + + asm("lwbrx %0,0,%1" : "=r" (word) : "r" (addr)); + return word; +} +#else +static ID_INLINE unsigned int __lwbrx(register void *addr, + register int offset) { + register unsigned int word; + + asm("lwbrx %0,%2,%1" : "=r" (word) : "r" (addr), "b" (offset)); + return word; +} +#define loadWord(addr) __lwbrx(addr,0) +#endif + +#else + static ID_INLINE int loadWord(void *addr) { + int word; + memcpy(&word, addr, 4); + return LittleLong(word); + } +#endif + +char *VM_Indent( vm_t *vm ) { + static char *string = " "; + if ( vm->callLevel > 20 ) { + return string; + } + return string + 2 * ( 20 - vm->callLevel ); +} + +/* +==================== +VM_StackTrace + +Call with (programCounter, programStack) inside of VM_CallInterpreted +and with (*(int *)&image[programStack], stomped) inside of a systemCall. +==================== +*/ +void VM_StackTrace( vm_t *vm, int programCounter, int programStack ) { + int count; + + count = 0; + do { + Com_Printf( "%s\n", VM_ValueToSymbol( vm, programCounter ) ); + programStack = *(int *)&vm->dataBase[programStack+4]; + programCounter = *(int *)&vm->dataBase[programStack]; + } while ( programCounter != -1 && ++count < 32 ); + +} + + +/* +==================== +VM_PrepareInterpreter +==================== +*/ +void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ) { + int op; + int byte_pc; + int int_pc; + byte *code; + int instruction; + int *codeBase; + + vm->codeBase = (byte *)Hunk_Alloc( vm->codeLength*4, h_high ); // we're now int aligned +// memcpy( vm->codeBase, (byte *)header + header->codeOffset, vm->codeLength ); + + // we don't need to translate the instructions, but we still need + // to find each instructions starting point for jumps + int_pc = byte_pc = 0; + instruction = 0; + code = (byte *)header + header->codeOffset; + codeBase = (int *)vm->codeBase; + + // Copy and expand instructions to words while building instruction table + while ( instruction < header->instructionCount ) { + vm->instructionPointers[ instruction ] = int_pc; + instruction++; + + op = (int)code[ byte_pc ]; + codeBase[int_pc] = op; + if(byte_pc > header->codeLength) + Com_Error(ERR_DROP, "VM_PrepareInterpreter: pc > header->codeLength"); + + byte_pc++; + int_pc++; + + // these are the only opcodes that aren't a single byte + switch ( op ) { + case OP_ENTER: + case OP_CONST: + case OP_LOCAL: + case OP_LEAVE: + 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: + codeBase[int_pc] = loadWord(&code[byte_pc]); + byte_pc += 4; + int_pc++; + break; + case OP_ARG: + codeBase[int_pc] = (int)code[byte_pc]; + byte_pc++; + int_pc++; + break; + default: + break; + } + + } + int_pc = 0; + instruction = 0; + + // Now that the code has been expanded to int-sized opcodes, we'll translate instruction index + //into an index into codeBase[], which contains opcodes and operands. + while ( instruction < header->instructionCount ) { + op = codeBase[ int_pc ]; + instruction++; + int_pc++; + + switch ( op ) { + // These ops need to translate addresses in jumps from instruction index to int index + 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: + if(codeBase[int_pc] < 0 || codeBase[int_pc] > vm->instructionCount) + Com_Error(ERR_DROP, "VM_PrepareInterpreter: Jump to invalid instruction number"); + + // codeBase[pc] is the instruction index. Convert that into an offset into + //the int-aligned codeBase[] by the lookup table. + codeBase[int_pc] = vm->instructionPointers[codeBase[int_pc]]; + int_pc++; + break; + + // These opcodes have an operand that isn't an instruction index + case OP_ENTER: + case OP_CONST: + case OP_LOCAL: + case OP_LEAVE: + case OP_BLOCK_COPY: + case OP_ARG: + int_pc++; + break; + + default: + break; + } + + } +} + +/* +============== +VM_Call + + +Upon a system call, the stack will look like: + +sp+32 parm1 +sp+28 parm0 +sp+24 return stack +sp+20 return address +sp+16 local1 +sp+14 local0 +sp+12 arg1 +sp+8 arg0 +sp+4 return stack +sp return address + +An interpreted function will immediately execute +an OP_ENTER instruction, which will subtract space for +locals from sp +============== +*/ + +#define DEBUGSTR va("%s%i", VM_Indent(vm), opStackOfs) + +int VM_CallInterpreted( vm_t *vm, int *args ) { + byte stack[OPSTACK_SIZE + 15]; + int *opStack; + uint8_t opStackOfs; + int programCounter; + int programStack; + int stackOnEntry; + byte *image; + int *codeImage; + int v1; + int dataMask; + int arg; +#ifdef DEBUG_VM + vmSymbol_t *profileSymbol; +#endif + + // interpret the code + vm->currentlyInterpreting = qtrue; + + // we might be called recursively, so this might not be the very top + programStack = stackOnEntry = vm->programStack; + +#ifdef DEBUG_VM + profileSymbol = VM_ValueToFunctionSymbol( vm, 0 ); + // uncomment this for debugging breakpoints + vm->breakFunction = 0; +#endif + // set up the stack frame + + image = vm->dataBase; + codeImage = (int *)vm->codeBase; + dataMask = vm->dataMask; + + programCounter = 0; + + programStack -= ( 8 + 4 * MAX_VMMAIN_ARGS ); + + for ( arg = 0; arg < MAX_VMMAIN_ARGS; arg++ ) + *(int *)&image[ programStack + 8 + arg * 4 ] = args[ arg ]; + + *(int *)&image[ programStack + 4 ] = 0; // return stack + *(int *)&image[ programStack ] = -1; // will terminate the loop on return + + VM_Debug(0); + + // leave a free spot at start of stack so + // that as long as opStack is valid, opStack-1 will + // not corrupt anything + opStack = (int *)PADP(stack, 16); + *(unsigned *)opStack = 0xDEADBEEFu; + opStackOfs = 0; + +// vm_debugLevel=2; + // main interpreter loop, will exit when a LEAVE instruction + // grabs the -1 program counter + +#define r2 codeImage[programCounter] + + while ( 1 ) { + int opcode, r0, r1; +// unsigned int r2; + +nextInstruction: + r0 = opStack[opStackOfs]; + r1 = opStack[(uint8_t) (opStackOfs - 1)]; +nextInstruction2: + opcode = codeImage[ programCounter++ ]; + +#ifdef DEBUG_VM + if ( (unsigned)programCounter >= (unsigned)vm->codeLength ) { + Com_Error( ERR_DROP, "VM pc out of range" ); + return 0; + } + + if ( programStack <= vm->stackBottom ) { + Com_Error( ERR_DROP, "VM stack overflow" ); + return 0; + } + + if ( programStack & 3 ) { + Com_Error( ERR_DROP, "VM program stack misaligned" ); + return 0; + } + + if ( vm_debugLevel > 1 ) { + Com_Printf( "%s %s\n", DEBUGSTR, opnames[opcode] ); + } + + for (vmSymbol_t *sym = profileSymbol; sym != NULL; sym = sym->caller) + sym->profileCount++; +#endif + switch ( opcode ) { +#ifdef DEBUG_VM + default: + Com_Error( ERR_DROP, "Bad VM instruction" ); // this should be scanned on load! + return 0; +#endif + case OP_BREAK: + vm->breakCount++; + goto nextInstruction2; + case OP_CONST: + opStackOfs++; + r1 = r0; + r0 = opStack[opStackOfs] = r2; + + programCounter += 1; + goto nextInstruction2; + case OP_LOCAL: + opStackOfs++; + r1 = r0; + r0 = opStack[opStackOfs] = r2+programStack; + + programCounter += 1; + goto nextInstruction2; + + case OP_LOAD4: +#ifdef DEBUG_VM + if(opStack[opStackOfs] & 3) + { + Com_Error( ERR_DROP, "OP_LOAD4 misaligned" ); + return 0; + } +#endif + r0 = opStack[opStackOfs] = *(int *) &image[r0 & dataMask & ~3 ]; + goto nextInstruction2; + case OP_LOAD2: + r0 = opStack[opStackOfs] = *(unsigned short *)&image[ r0&dataMask&~1 ]; + goto nextInstruction2; + case OP_LOAD1: + r0 = opStack[opStackOfs] = image[ r0&dataMask ]; + goto nextInstruction2; + + case OP_STORE4: + *(int *)&image[ r1&(dataMask & ~3) ] = r0; + opStackOfs -= 2; + goto nextInstruction; + case OP_STORE2: + *(short *)&image[ r1&(dataMask & ~1) ] = r0; + opStackOfs -= 2; + goto nextInstruction; + case OP_STORE1: + image[ r1&dataMask ] = r0; + opStackOfs -= 2; + goto nextInstruction; + + case OP_ARG: + // single byte offset from programStack + *(int *)&image[ (codeImage[programCounter] + programStack)&dataMask&~3 ] = r0; + opStackOfs--; + programCounter += 1; + goto nextInstruction; + + case OP_BLOCK_COPY: + VM_BlockCopy(r1, r0, r2); + programCounter += 1; + opStackOfs -= 2; + goto nextInstruction; + + case OP_CALL: + // save current program counter + *(int *)&image[ programStack ] = programCounter; + + // jump to the location on the stack + programCounter = r0; + opStackOfs--; + if ( programCounter < 0 ) { + // system call + int r; +// int temp; +#ifdef DEBUG_VM + int stomped; + + if ( vm_debugLevel ) { + Com_Printf( "%s---> systemcall(%i)\n", DEBUGSTR, -1 - programCounter ); + } +#endif + // save the stack to allow recursive VM entry +// temp = vm->callLevel; + vm->programStack = programStack - 4; +#ifdef DEBUG_VM + stomped = *(int *)&image[ programStack + 4 ]; +#endif + *(int *)&image[ programStack + 4 ] = -1 - programCounter; + +//VM_LogSyscalls( (int *)&image[ programStack + 4 ] ); + { + // the vm has ints on the stack, we expect + // pointers so we might have to convert it + if (sizeof(intptr_t) != sizeof(int)) { + intptr_t argarr[ MAX_VMSYSCALL_ARGS ]; + int *imagePtr = (int *)&image[ programStack ]; + for (size_t i = 0; i < ARRAY_LEN(argarr); ++i) { + argarr[i] = *(++imagePtr); + } + r = vm->systemCall( argarr ); + } else { + intptr_t* argptr = (intptr_t *)&image[ programStack + 4 ]; + r = vm->systemCall( argptr ); + } + } + +#ifdef DEBUG_VM + // this is just our stack frame pointer, only needed + // for debugging + *(int *)&image[ programStack + 4 ] = stomped; +#endif + + // save return value + opStackOfs++; + opStack[opStackOfs] = r; + programCounter = *(int *)&image[ programStack ]; +// vm->callLevel = temp; +#ifdef DEBUG_VM + if ( vm_debugLevel ) { + Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); + } +#endif + } else if ( programCounter >= vm->instructionCount ) { + Com_Error( ERR_DROP, "VM program counter out of range in OP_CALL" ); + return 0; + } else { + programCounter = vm->instructionPointers[ programCounter ]; + } + goto nextInstruction; + + // push and pop are only needed for discarded or bad function return values + case OP_PUSH: + opStackOfs++; + goto nextInstruction; + case OP_POP: + opStackOfs--; + goto nextInstruction; + + case OP_ENTER: +#ifdef DEBUG_VM + if ( vm_profileInclusive ) { + vmSymbol_t *newSym = VM_ValueToFunctionSymbol( vm, programCounter ); + qboolean link = qtrue; + + newSym->callCount++; + + // deal with recursion + for ( vmSymbol_t *sym = profileSymbol; sym; sym = sym->caller ) + if (sym == newSym) + link = qfalse; + + if (link) { + newSym->caller = profileSymbol; + profileSymbol = newSym; + } + } else { + profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); + profileSymbol->callCount++; + } +#endif + // get size of stack frame + v1 = r2; + + programCounter += 1; + programStack -= v1; +#ifdef DEBUG_VM + // save old stack frame for debugging traces + *(int *)&image[programStack+4] = programStack + v1; + if ( vm_debugLevel ) { + Com_Printf( "%s---> %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter - 5 ) ); + if ( vm->breakFunction && programCounter - 5 == vm->breakFunction ) { + // this is to allow setting breakpoints here in the debugger + vm->breakCount++; +// vm_debugLevel = 2; +// VM_StackTrace( vm, programCounter, programStack ); + } +// vm->callLevel++; + } +#endif + goto nextInstruction; + case OP_LEAVE: + // remove our stack frame + v1 = r2; + + programStack += v1; + + // grab the saved program counter + programCounter = *(int *)&image[ programStack ]; +#ifdef DEBUG_VM + profileSymbol->caller = NULL; + profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); + if ( vm_debugLevel ) { +// vm->callLevel--; + Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); + } +#endif + // check for leaving the VM + if ( programCounter == -1 ) { + goto done; + } else if ( (unsigned)programCounter >= (unsigned)vm->codeLength ) { + Com_Error( ERR_DROP, "VM program counter out of range in OP_LEAVE" ); + return 0; + } + goto nextInstruction; + + /* + =================================================================== + BRANCHES + =================================================================== + */ + + case OP_JUMP: + if ( (unsigned)r0 >= (unsigned)vm->instructionCount ) + { + Com_Error( ERR_DROP, "VM program counter out of range in OP_JUMP" ); + return 0; + } + + programCounter = vm->instructionPointers[ r0 ]; + + opStackOfs--; + goto nextInstruction; + + case OP_EQ: + opStackOfs -= 2; + if ( r1 == r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_NE: + opStackOfs -= 2; + if ( r1 != r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LTI: + opStackOfs -= 2; + if ( r1 < r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LEI: + opStackOfs -= 2; + if ( r1 <= r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GTI: + opStackOfs -= 2; + if ( r1 > r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GEI: + opStackOfs -= 2; + if ( r1 >= r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LTU: + opStackOfs -= 2; + if ( ((unsigned)r1) < ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LEU: + opStackOfs -= 2; + if ( ((unsigned)r1) <= ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GTU: + opStackOfs -= 2; + if ( ((unsigned)r1) > ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GEU: + opStackOfs -= 2; + if ( ((unsigned)r1) >= ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_EQF: + opStackOfs -= 2; + + if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] == ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) + { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_NEF: + opStackOfs -= 2; + + if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] != ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) + { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LTF: + opStackOfs -= 2; + + if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] < ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) + { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LEF: + opStackOfs -= 2; + + if(((float *) opStack)[(uint8_t) ((uint8_t) (opStackOfs + 1))] <= ((float *) opStack)[(uint8_t) ((uint8_t) (opStackOfs + 2))]) + { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GTF: + opStackOfs -= 2; + + if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] > ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) + { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GEF: + opStackOfs -= 2; + + if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] >= ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) + { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + + //=================================================================== + + case OP_NEGI: + opStack[opStackOfs] = -r0; + goto nextInstruction; + case OP_ADD: + opStackOfs--; + opStack[opStackOfs] = r1 + r0; + goto nextInstruction; + case OP_SUB: + opStackOfs--; + opStack[opStackOfs] = r1 - r0; + goto nextInstruction; + case OP_DIVI: + opStackOfs--; + opStack[opStackOfs] = r1 / r0; + goto nextInstruction; + case OP_DIVU: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) / ((unsigned) r0); + goto nextInstruction; + case OP_MODI: + opStackOfs--; + opStack[opStackOfs] = r1 % r0; + goto nextInstruction; + case OP_MODU: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) % ((unsigned) r0); + goto nextInstruction; + case OP_MULI: + opStackOfs--; + opStack[opStackOfs] = r1 * r0; + goto nextInstruction; + case OP_MULU: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) * ((unsigned) r0); + goto nextInstruction; + + case OP_BAND: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) & ((unsigned) r0); + goto nextInstruction; + case OP_BOR: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) | ((unsigned) r0); + goto nextInstruction; + case OP_BXOR: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) ^ ((unsigned) r0); + goto nextInstruction; + case OP_BCOM: + opStack[opStackOfs] = ~((unsigned) r0); + goto nextInstruction; + + case OP_LSH: + opStackOfs--; + opStack[opStackOfs] = r1 << r0; + goto nextInstruction; + case OP_RSHI: + opStackOfs--; + opStack[opStackOfs] = r1 >> r0; + goto nextInstruction; + case OP_RSHU: + opStackOfs--; + opStack[opStackOfs] = ((unsigned) r1) >> r0; + goto nextInstruction; + + case OP_NEGF: + ((float *) opStack)[opStackOfs] = -((float *) opStack)[opStackOfs]; + goto nextInstruction; + case OP_ADDF: + opStackOfs--; + ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] + ((float *) opStack)[(uint8_t) (opStackOfs + 1)]; + goto nextInstruction; + case OP_SUBF: + opStackOfs--; + ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] - ((float *) opStack)[(uint8_t) (opStackOfs + 1)]; + goto nextInstruction; + case OP_DIVF: + opStackOfs--; + ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] / ((float *) opStack)[(uint8_t) (opStackOfs + 1)]; + goto nextInstruction; + case OP_MULF: + opStackOfs--; + ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] * ((float *) opStack)[(uint8_t) (opStackOfs + 1)]; + goto nextInstruction; + + case OP_CVIF: + ((float *) opStack)[opStackOfs] = (float) opStack[opStackOfs]; + goto nextInstruction; + case OP_CVFI: + opStack[opStackOfs] = (int) ((float *) opStack)[opStackOfs]; + goto nextInstruction; + case OP_SEX8: + opStack[opStackOfs] = (signed char) opStack[opStackOfs]; + goto nextInstruction; + case OP_SEX16: + opStack[opStackOfs] = (short) opStack[opStackOfs]; + goto nextInstruction; + } + } + +done: + vm->currentlyInterpreting = qfalse; + + if (opStackOfs != 1 || *(unsigned *)opStack != 0xDEADBEEFu) + Com_Error(ERR_DROP, "Interpreter error: opStack[0] = %X, opStackOfs = %d", opStack[0], opStackOfs); + + vm->programStack = stackOnEntry; + + // return the result + return opStack[opStackOfs]; +} diff --git a/codemp/qcommon/vm_local.h b/codemp/qcommon/vm_local.h new file mode 100644 index 0000000000..d24556c237 --- /dev/null +++ b/codemp/qcommon/vm_local.h @@ -0,0 +1,217 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +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 Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qcommon.h" +#include "qfiles.h" +#include + +// Max number of arguments to pass from engine to vm's vmMain function. +// command number + 12 arguments +#define MAX_VMMAIN_ARGS 13 + +// Max number of arguments to pass from a vm to engine's syscall handler function for the vm. +// syscall number + 15 arguments +#define MAX_VMSYSCALL_ARGS 16 + +// don't change, this is hardcoded into x86 VMs, opStack protection relies +// on this +#define OPSTACK_SIZE 1024 +#define OPSTACK_MASK (OPSTACK_SIZE-1) + +// don't change +// Hardcoded in q3asm a reserved at end of bss +#define PROGRAM_STACK_SIZE 0x10000 +#define PROGRAM_STACK_MASK (PROGRAM_STACK_SIZE-1) + +typedef enum { + 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, // *(stack[top-1]) = stack[top] + 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 +} opcode_t; + + + +typedef int vmptr_t; + +typedef struct vmSymbol_s { + struct vmSymbol_s *next; + struct vmSymbol_s *caller; + int symValue; + int symInstr; + long profileCount; + int callCount; + char symName[1]; // variable sized +} vmSymbol_t; + +#define VM_OFFSET_PROGRAM_STACK 0 +#define VM_OFFSET_SYSTEM_CALL 4 + +struct vm_s { + // DO NOT MOVE OR CHANGE THESE WITHOUT CHANGING THE VM_OFFSET_* DEFINES + // USED BY THE ASM CODE + int programStack; // the vm may be recursively entered + intptr_t (*systemCall)( intptr_t *parms ); + + //------------------------------------ + + char name[MAX_QPATH]; + void *searchPath; // hint for FS_ReadFileDir() + + // for dynamic linked modules + void *dllHandle; + VM_EntryPoint_t entryPoint; + void (*destroy)(vm_t* self); + qboolean mvOverride; + + // for interpreted modules + qboolean currentlyInterpreting; + + qboolean compiled; + byte *codeBase; + int entryOfs; + int callProcOfs; + int callProcOfsSyscall; + int codeLength; + + intptr_t *instructionPointers; + int instructionCount; + + byte *dataBase; + int dataMask; + + int stackBottom; // if programStack < stackBottom, error + + int numSymbols; + vmSymbol_t *symbols; + vmSymbol_t **symbolTable; + + int callLevel; // counts recursive VM_Call + int breakFunction; // increment breakCount on function entry to this + int breakCount; + + byte *jumpTableTargets; + int numJumpTableTargets; + + int mvapilevel; + int mvmenu; + mvversion_t gameversion; +}; + + +extern vm_t *currentVM; +extern int vm_debugLevel; +extern qboolean vm_profileInclusive; + +void VM_Compile( vm_t *vm, vmHeader_t *header ); +int VM_CallCompiled( vm_t *vm, int *args ); + +void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ); +int VM_CallInterpreted( vm_t *vm, int *args ); + +vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ); +int VM_SymbolToValue( vm_t *vm, const char *symbol ); +const char *VM_ValueToSymbol( vm_t *vm, int value ); +const char *VM_SymbolForCompiledPointer( void *code ); +void VM_LogSyscalls( int *args ); + +void VM_BlockCopy(unsigned int dest, unsigned int src, size_t n); diff --git a/codemp/qcommon/vm_x86.cpp b/codemp/qcommon/vm_x86.cpp new file mode 100644 index 0000000000..f7c481d05f --- /dev/null +++ b/codemp/qcommon/vm_x86.cpp @@ -0,0 +1,1819 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +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 Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// vm_x86.c -- load time compiler and execution environment for x86 + +#include "vm_local.h" +#include + +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + #include +#else + #ifdef __FreeBSD__ + #include + #endif + + #include // for PROT_ stuff + + /* need this on NX enabled systems (i386 with PAE kernel or + * noexec32=on x86_64) */ + #define VM_X86_MMAP + + // workaround for systems that use the old MAP_ANON macro + #ifndef MAP_ANONYMOUS + #define MAP_ANONYMOUS MAP_ANON + #endif +#endif + +static void VM_Destroy_Compiled(vm_t* self); + +/* + + eax scratch + ebx/bl opStack offset + ecx scratch (required for shifts) + edx scratch (required for divisions) + esi program stack + edi opStack base +x86_64: + r8 vm->instructionPointers + r9 vm->dataBase + +*/ + +#define VMFREE_BUFFERS() do {Z_Free(buf); Z_Free(jused);} while(0) +static byte *buf = NULL; +static byte *jused = NULL; +static int jusedSize = 0; +static int compiledOfs = 0; +static byte *code = NULL; +static int pc = 0; + +static int instruction, pass; +static int lastConst = 0; +static int oc0, oc1, pop0, pop1; +static int jlabel; + +typedef enum +{ + LAST_COMMAND_NONE = 0, + LAST_COMMAND_MOV_STACK_EAX, + LAST_COMMAND_SUB_BL_1, + LAST_COMMAND_SUB_BL_2, +} ELastCommand; + +typedef enum +{ + VM_JMP_VIOLATION = 0, + VM_BLOCK_COPY = 1 +} ESysCallType; + +static ELastCommand LastCommand; + +static int iss8(int32_t v) +{ + return (SCHAR_MIN <= v && v <= SCHAR_MAX); +} + +#if 0 +static int isu8(uint32_t v) +{ + return (v <= UCHAR_MAX); +} +#endif + +static int NextConstant4(void) +{ + return (code[pc] | (code[pc+1]<<8) | (code[pc+2]<<16) | (code[pc+3]<<24)); +} + +static int Constant4( void ) { + int v; + + v = NextConstant4(); + pc += 4; + return v; +} + +static int Constant1( void ) { + int v; + + v = code[pc]; + pc += 1; + return v; +} + +static void Emit1( int v ) +{ + buf[ compiledOfs ] = v; + compiledOfs++; + + LastCommand = LAST_COMMAND_NONE; +} + +static void Emit2(int v) +{ + Emit1(v & 255); + Emit1((v >> 8) & 255); +} + + +static void Emit4(int v) +{ + Emit1(v & 0xFF); + Emit1((v >> 8) & 0xFF); + Emit1((v >> 16) & 0xFF); + Emit1((v >> 24) & 0xFF); +} + +static void EmitPtr(void *ptr) +{ + intptr_t v = (intptr_t) ptr; + + Emit4(v); +#if idx64 + Emit1((v >> 32) & 0xFF); + Emit1((v >> 40) & 0xFF); + Emit1((v >> 48) & 0xFF); + Emit1((v >> 56) & 0xFF); +#endif +} + +static int Hex( int c ) { + if ( c >= 'a' && c <= 'f' ) { + return 10 + c - 'a'; + } + if ( c >= 'A' && c <= 'F' ) { + return 10 + c - 'A'; + } + if ( c >= '0' && c <= '9' ) { + return c - '0'; + } + + VMFREE_BUFFERS(); + Com_Error( ERR_DROP, "Hex: bad char '%c'", c ); + + return 0; +} +static void EmitString( const char *string ) { + int c1, c2; + int v; + + while ( 1 ) { + c1 = string[0]; + c2 = string[1]; + + v = ( Hex( c1 ) << 4 ) | Hex( c2 ); + Emit1( v ); + + if ( !string[2] ) { + break; + } + string += 3; + } +} +static void EmitRexString(byte rex, const char *string) +{ +#if idx64 + if(rex) + Emit1(rex); +#endif + + EmitString(string); +} + + +#define MASK_REG(modrm, mask) \ + do { \ + EmitString("81"); \ + EmitString((modrm)); \ + Emit4((mask)); \ + } while (0) + +// add bl, bytes +#define STACK_PUSH(bytes) \ + do { \ + EmitString("80 C3"); \ + Emit1(bytes); \ + } while (0) + +// sub bl, bytes +#define STACK_POP(bytes) \ + do { \ + EmitString("80 EB"); \ + Emit1(bytes); \ + } while (0) + +static void EmitCommand(ELastCommand command) +{ + switch(command) + { + case LAST_COMMAND_MOV_STACK_EAX: + EmitString("89 04 9F"); // mov dword ptr [edi + ebx * 4], eax + break; + + case LAST_COMMAND_SUB_BL_1: + STACK_POP(1); // sub bl, 1 + break; + + case LAST_COMMAND_SUB_BL_2: + STACK_POP(2); // sub bl, 2 + break; + default: + break; + } + LastCommand = command; +} + +static void EmitPushStack(vm_t *vm) +{ + if (!jlabel) + { + if(LastCommand == LAST_COMMAND_SUB_BL_1) + { // sub bl, 1 + compiledOfs -= 3; + vm->instructionPointers[instruction - 1] = compiledOfs; + return; + } + if(LastCommand == LAST_COMMAND_SUB_BL_2) + { // sub bl, 2 + compiledOfs -= 3; + vm->instructionPointers[instruction - 1] = compiledOfs; + STACK_POP(1); // sub bl, 1 + return; + } + } + + STACK_PUSH(1); // add bl, 1 +} + +static void EmitMovEAXStack(vm_t *vm, int andit) +{ + if(!jlabel) + { + if(LastCommand == LAST_COMMAND_MOV_STACK_EAX) + { // mov [edi + ebx * 4], eax + compiledOfs -= 3; + vm->instructionPointers[instruction - 1] = compiledOfs; + } + else if(pop1 == OP_CONST && buf[compiledOfs-7] == 0xC7 && buf[compiledOfs-6] == 0x04 && buf[compiledOfs - 5] == 0x9F) + { // mov [edi + ebx * 4], 0x12345678 + compiledOfs -= 7; + vm->instructionPointers[instruction - 1] = compiledOfs; + EmitString("B8"); // mov eax, 0x12345678 + + if(andit) + Emit4(lastConst & andit); + else + Emit4(lastConst); + + return; + } + else if(pop1 != OP_DIVI && pop1 != OP_DIVU && pop1 != OP_MULI && pop1 != OP_MULU && + pop1 != OP_STORE4 && pop1 != OP_STORE2 && pop1 != OP_STORE1) + { + EmitString("8B 04 9F"); // mov eax, dword ptr [edi + ebx * 4] + } + } + else + EmitString("8B 04 9F"); // mov eax, dword ptr [edi + ebx * 4] + + if(andit) + { + EmitString("25"); // and eax, 0x12345678 + Emit4(andit); + } +} + +void EmitMovECXStack(vm_t *vm) +{ + if(!jlabel) + { + if(LastCommand == LAST_COMMAND_MOV_STACK_EAX) // mov [edi + ebx * 4], eax + { + compiledOfs -= 3; + vm->instructionPointers[instruction - 1] = compiledOfs; + EmitString("89 C1"); // mov ecx, eax + return; + } + if(pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU || + pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1) + { + EmitString("89 C1"); // mov ecx, eax + return; + } + } + + EmitString("8B 0C 9F"); // mov ecx, dword ptr [edi + ebx * 4] +} + + +void EmitMovEDXStack(vm_t *vm, int andit) +{ + if(!jlabel) + { + if(LastCommand == LAST_COMMAND_MOV_STACK_EAX) + { // mov dword ptr [edi + ebx * 4], eax + compiledOfs -= 3; + vm->instructionPointers[instruction - 1] = compiledOfs; + + EmitString("8B D0"); // mov edx, eax + } + else if(pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU || + pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1) + { + EmitString("8B D0"); // mov edx, eax + } + else if(pop1 == OP_CONST && buf[compiledOfs-7] == 0xC7 && buf[compiledOfs-6] == 0x07 && buf[compiledOfs - 5] == 0x9F) + { // mov dword ptr [edi + ebx * 4], 0x12345678 + compiledOfs -= 7; + vm->instructionPointers[instruction - 1] = compiledOfs; + EmitString("BA"); // mov edx, 0x12345678 + + if(andit) + Emit4(lastConst & andit); + else + Emit4(lastConst); + + return; + } + else + EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4] + + } + else + EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4] + + if(andit) + MASK_REG("E2", andit); // and edx, 0x12345678 +} + +#define JUSED(x) \ + do { \ + if (x < 0 || x >= vm->instructionCount) { \ + VMFREE_BUFFERS(); \ + Com_Error( ERR_DROP, \ + "VM_CompileX86: jump target out of range at offset %d", pc ); \ + } \ + jused[x] = 1; \ + } while(0) + +#define SET_JMPOFS(x) do { buf[(x)] = compiledOfs - ((x) + 1); } while(0) + + +/* +================= +ErrJump +Error handler for jump/call to invalid instruction number +================= +*/ + +static void ErrJump(void) +{ + Com_Error(ERR_DROP, "program tried to execute code outside VM"); +} + +/* +================= +DoSyscall + +Assembler helper routines will write its arguments directly to global variables so as to +work around different calling conventions +================= +*/ + +int vm_syscallNum; +int vm_programStack; +int *vm_opStackBase; +uint8_t vm_opStackOfs; +intptr_t vm_arg; + +void DoSyscall(void) +{ + vm_t *savedVM; + + // save currentVM so as to allow for recursive VM entry + savedVM = currentVM; + // modify VM stack pointer for recursive VM entry + currentVM->programStack = vm_programStack - 4; + + if(vm_syscallNum < 0) + { + int *data; +#if idx64 + intptr_t args[MAX_VMSYSCALL_ARGS]; +#endif + + data = (int *) (savedVM->dataBase + vm_programStack + 4); + +#if idx64 + args[0] = ~vm_syscallNum; + for(size_t index = 1; index < ARRAY_LEN(args); index++) + args[index] = data[index]; + + vm_opStackBase[vm_opStackOfs + 1] = savedVM->systemCall(args); +#else + data[0] = ~vm_syscallNum; + vm_opStackBase[vm_opStackOfs + 1] = savedVM->systemCall((intptr_t *) data); +#endif + } + else + { + switch(vm_syscallNum) + { + case VM_JMP_VIOLATION: + ErrJump(); + break; + case VM_BLOCK_COPY: + if(vm_opStackOfs < 1) + Com_Error(ERR_DROP, "VM_BLOCK_COPY failed due to corrupted opStack"); + + VM_BlockCopy(vm_opStackBase[(vm_opStackOfs - 1)], vm_opStackBase[vm_opStackOfs], vm_arg); + break; + default: + Com_Error(ERR_DROP, "Unknown VM operation %d", vm_syscallNum); + break; + } + } + + currentVM = savedVM; +} + +/* +================= +EmitCallRel +Relative call to vm->codeBase + callOfs +================= +*/ + +void EmitCallRel(vm_t *vm, int callOfs) +{ + EmitString("E8"); // call 0x12345678 + Emit4(callOfs - compiledOfs - 4); +} + +/* +================= +EmitCallDoSyscall +Call to DoSyscall() +================= +*/ + +int EmitCallDoSyscall(vm_t *vm) +{ + // save frame pointer + EmitString("55"); // push ebp + EmitRexString(0x48, "89 E5"); // mov ebp, esp + + // use edx register to store DoSyscall address + EmitRexString(0x48, "BA"); // mov edx, DoSyscall + EmitPtr((void*)DoSyscall); + + // Push important registers to stack as we can't really make + // any assumptions about calling conventions. + EmitString("51"); // push ebx + EmitString("56"); // push esi + EmitString("57"); // push edi +#if idx64 + EmitRexString(0x41, "50"); // push r8 + EmitRexString(0x41, "51"); // push r9 +#endif + + // write arguments to global vars + // syscall number + EmitString("A3"); // mov [0x12345678], eax + EmitPtr(&vm_syscallNum); + // vm_programStack value + EmitString("89 F0"); // mov eax, esi + EmitString("A3"); // mov [0x12345678], eax + EmitPtr(&vm_programStack); + // vm_opStackOfs + EmitString("88 D8"); // mov al, bl + EmitString("A2"); // mov [0x12345678], al + EmitPtr(&vm_opStackOfs); + // vm_opStackBase + EmitRexString(0x48, "89 F8"); // mov eax, edi + EmitRexString(0x48, "A3"); // mov [0x12345678], eax + EmitPtr(&vm_opStackBase); + // vm_arg + EmitString("89 C8"); // mov eax, ecx + EmitString("A3"); // mov [0x12345678], eax + EmitPtr(&vm_arg); + + // align the stack pointer to a 16-byte-boundary + EmitRexString(0x48, "89 E0"); // mov eax, esp + EmitString("51"); // push ecx (decrease esp in a portable way) + EmitRexString(0x48, "83 E4 F0"); // and esp, 0xFFFFFFF0 + EmitString("59"); // pop ecx (increase esp in a portable way) + EmitString("50"); // push eax + + // call the syscall wrapper function DoSyscall() + + EmitString("FF D2"); // call edx + + // reset the stack pointer to its previous value + EmitString("5C"); // pop esp +#if idx64 + EmitRexString(0x41, "59"); // pop r9 + EmitRexString(0x41, "58"); // pop r8 +#endif + EmitString("5F"); // pop edi + EmitString("5E"); // pop esi + EmitString("59"); // pop ebx + + // restore frame pointer + EmitString("5D"); // pop ebp + EmitString("C3"); // ret + + return compiledOfs; +} + +/* +================= +EmitCallErrJump +Emit the code that triggers execution of the jump violation handler +================= +*/ + +static void EmitCallErrJump(vm_t *vm, int sysCallOfs) +{ + EmitString("B8"); // mov eax, 0x12345678 + Emit4(VM_JMP_VIOLATION); + + EmitCallRel(vm, sysCallOfs); +} + +/* +================= +EmitCallProcedure +VM OP_CALL procedure for call destinations obtained at runtime +================= +*/ + +int EmitCallProcedure(vm_t *vm, int sysCallOfs) +{ + int jmpSystemCall, jmpBadAddr; + int retval; + + // Save frame pointer + EmitString("55"); // push ebp + EmitRexString(0x48, "89 E5"); // mov ebp, esp + + EmitString("8B 04 9F"); // mov eax, dword ptr [edi + ebx * 4] + STACK_POP(1); // sub bl, 1 + EmitString("85 C0"); // test eax, eax + + // Jump to syscall code, 1 byte offset should suffice + EmitString("7C"); // jl systemCall + jmpSystemCall = compiledOfs++; + + /************ Call inside VM ************/ + + EmitString("81 F8"); // cmp eax, vm->instructionCount + Emit4(vm->instructionCount); + + // Error jump if invalid jump target + EmitString("73"); // jae badAddr + jmpBadAddr = compiledOfs++; + +#if idx64 + EmitRexString(0x49, "FF 14 C0"); // call qword ptr [r8 + eax * 8] +#else + EmitString("FF 14 85"); // call dword ptr [vm->instructionPointers + eax * 4] + Emit4((intptr_t) vm->instructionPointers); +#endif + EmitString("8B 04 9F"); // mov eax, dword ptr [edi + ebx * 4] + EmitString("5D"); // pop ebp + EmitString("C3"); // ret + + // badAddr: + SET_JMPOFS(jmpBadAddr); + EmitCallErrJump(vm, sysCallOfs); + + /************ System Call ************/ + + // systemCall: + SET_JMPOFS(jmpSystemCall); + retval = compiledOfs; + + EmitCallRel(vm, sysCallOfs); + + // have opStack reg point at return value + STACK_PUSH(1); // add bl, 1 + EmitString("5D"); // pop ebp + EmitString("C3"); // ret + + return retval; +} + +/* +================= +EmitJumpIns +Jump to constant instruction number +================= +*/ + +void EmitJumpIns(vm_t *vm, const char *jmpop, int cdest) +{ + JUSED(cdest); + + EmitString(jmpop); // j??? 0x12345678 + + // we only know all the jump addresses in the third pass + if(pass == 2) + Emit4(vm->instructionPointers[cdest] - compiledOfs - 4); + else + compiledOfs += 4; +} + +/* +================= +EmitCallIns +Call to constant instruction number +================= +*/ + +void EmitCallIns(vm_t *vm, int cdest) +{ + JUSED(cdest); + + EmitString("E8"); // call 0x12345678 + + // we only know all the jump addresses in the third pass + if(pass == 2) + Emit4(vm->instructionPointers[cdest] - compiledOfs - 4); + else + compiledOfs += 4; +} + +/* +================= +EmitCallConst +Call to constant instruction number or syscall +================= +*/ + +void EmitCallConst(vm_t *vm, int cdest, int callProcOfsSyscall) +{ + if(cdest < 0) + { + EmitString("B8"); // mov eax, cdest + Emit4(cdest); + + EmitCallRel(vm, callProcOfsSyscall); + } + else + EmitCallIns(vm, cdest); +} + +/* +================= +EmitBranchConditions +Emits x86 branch condition as given in op +================= +*/ +void EmitBranchConditions(vm_t *vm, int op) +{ + switch(op) + { + case OP_EQ: + EmitJumpIns(vm, "0F 84", Constant4()); // je 0x12345678 + break; + case OP_NE: + EmitJumpIns(vm, "0F 85", Constant4()); // jne 0x12345678 + break; + case OP_LTI: + EmitJumpIns(vm, "0F 8C", Constant4()); // jl 0x12345678 + break; + case OP_LEI: + EmitJumpIns(vm, "0F 8E", Constant4()); // jle 0x12345678 + break; + case OP_GTI: + EmitJumpIns(vm, "0F 8F", Constant4()); // jg 0x12345678 + break; + case OP_GEI: + EmitJumpIns(vm, "0F 8D", Constant4()); // jge 0x12345678 + break; + case OP_LTU: + EmitJumpIns(vm, "0F 82", Constant4()); // jb 0x12345678 + break; + case OP_LEU: + EmitJumpIns(vm, "0F 86", Constant4()); // jbe 0x12345678 + break; + case OP_GTU: + EmitJumpIns(vm, "0F 87", Constant4()); // ja 0x12345678 + break; + case OP_GEU: + EmitJumpIns(vm, "0F 83", Constant4()); // jae 0x12345678 + break; + } +} + + +/* +================= +ConstOptimize +Constant values for immediately following instructions may be translated to immediate values +instead of opStack operations, which will save expensive operations on memory +================= +*/ + +qboolean ConstOptimize(vm_t *vm, int callProcOfsSyscall) +{ + int v; + int op1; + + // we can safely perform optimizations only in case if + // we are 100% sure that next instruction is not a jump label + if (vm->jumpTableTargets && !jused[instruction]) + op1 = code[pc+4]; + else + return qfalse; + + switch ( op1 ) { + + case OP_LOAD4: + EmitPushStack(vm); +#if idx64 + EmitRexString(0x41, "8B 81"); // mov eax, dword ptr [r9 + 0x12345678] + Emit4(Constant4() & vm->dataMask); +#else + EmitString("B8"); // mov eax, 0x12345678 + EmitPtr(vm->dataBase + (Constant4() & vm->dataMask)); + EmitString("8B 00"); // mov eax, dword ptr [eax] +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + + pc++; // OP_LOAD4 + instruction += 1; + return qtrue; + + case OP_LOAD2: + EmitPushStack(vm); +#if idx64 + EmitRexString(0x41, "0F B7 81"); // movzx eax, word ptr [r9 + 0x12345678] + Emit4(Constant4() & vm->dataMask); +#else + EmitString("B8"); // mov eax, 0x12345678 + EmitPtr(vm->dataBase + (Constant4() & vm->dataMask)); + EmitString("0F B7 00"); // movzx eax, word ptr [eax] +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + + pc++; // OP_LOAD2 + instruction += 1; + return qtrue; + + case OP_LOAD1: + EmitPushStack(vm); +#if idx64 + EmitRexString(0x41, "0F B6 81"); // movzx eax, byte ptr [r9 + 0x12345678] + Emit4(Constant4() & vm->dataMask); +#else + EmitString("B8"); // mov eax, 0x12345678 + EmitPtr(vm->dataBase + (Constant4() & vm->dataMask)); + EmitString("0F B6 00"); // movzx eax, byte ptr [eax] +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + + pc++; // OP_LOAD1 + instruction += 1; + return qtrue; + + case OP_STORE4: + EmitMovEAXStack(vm, (vm->dataMask & ~3)); +#if idx64 + EmitRexString(0x41, "C7 04 01"); // mov dword ptr [r9 + eax], 0x12345678 + Emit4(Constant4()); +#else + EmitString("C7 80"); // mov dword ptr [eax + 0x12345678], 0x12345678 + Emit4((intptr_t) vm->dataBase); + Emit4(Constant4()); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + pc++; // OP_STORE4 + instruction += 1; + return qtrue; + + case OP_STORE2: + EmitMovEAXStack(vm, (vm->dataMask & ~1)); +#if idx64 + Emit1(0x66); // mov word ptr [r9 + eax], 0x1234 + EmitRexString(0x41, "C7 04 01"); + Emit2(Constant4()); +#else + EmitString("66 C7 80"); // mov word ptr [eax + 0x12345678], 0x1234 + Emit4((intptr_t) vm->dataBase); + Emit2(Constant4()); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + + pc++; // OP_STORE2 + instruction += 1; + return qtrue; + + case OP_STORE1: + EmitMovEAXStack(vm, vm->dataMask); +#if idx64 + EmitRexString(0x41, "C6 04 01"); // mov byte [r9 + eax], 0x12 + Emit1(Constant4()); +#else + EmitString("C6 80"); // mov byte ptr [eax + 0x12345678], 0x12 + Emit4((intptr_t) vm->dataBase); + Emit1(Constant4()); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + + pc++; // OP_STORE1 + instruction += 1; + return qtrue; + + case OP_ADD: + v = Constant4(); + + EmitMovEAXStack(vm, 0); + if(iss8(v)) + { + EmitString("83 C0"); // add eax, 0x7F + Emit1(v); + } + else + { + EmitString("05"); // add eax, 0x12345678 + Emit4(v); + } + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc++; // OP_ADD + instruction += 1; + return qtrue; + + case OP_SUB: + v = Constant4(); + + EmitMovEAXStack(vm, 0); + if(iss8(v)) + { + EmitString("83 E8"); // sub eax, 0x7F + Emit1(v); + } + else + { + EmitString("2D"); // sub eax, 0x12345678 + Emit4(v); + } + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc++; // OP_SUB + instruction += 1; + return qtrue; + + case OP_MULI: + v = Constant4(); + + EmitMovEAXStack(vm, 0); + if(iss8(v)) + { + EmitString("6B C0"); // imul eax, 0x7F + Emit1(v); + } + else + { + EmitString("69 C0"); // imul eax, 0x12345678 + Emit4(v); + } + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + pc++; // OP_MULI + instruction += 1; + + return qtrue; + + case OP_LSH: + v = NextConstant4(); + if(v < 0 || v > 31) + break; + + EmitMovEAXStack(vm, 0); + EmitString("C1 E0"); // shl eax, 0x12 + Emit1(v); + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc += 5; // CONST + OP_LSH + instruction += 1; + return qtrue; + + case OP_RSHI: + v = NextConstant4(); + if(v < 0 || v > 31) + break; + + EmitMovEAXStack(vm, 0); + EmitString("C1 F8"); // sar eax, 0x12 + Emit1(v); + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc += 5; // CONST + OP_RSHI + instruction += 1; + return qtrue; + + case OP_RSHU: + v = NextConstant4(); + if(v < 0 || v > 31) + break; + + EmitMovEAXStack(vm, 0); + EmitString("C1 E8"); // shr eax, 0x12 + Emit1(v); + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc += 5; // CONST + OP_RSHU + instruction += 1; + return qtrue; + + case OP_BAND: + v = Constant4(); + + EmitMovEAXStack(vm, 0); + if(iss8(v)) + { + EmitString("83 E0"); // and eax, 0x7F + Emit1(v); + } + else + { + EmitString("25"); // and eax, 0x12345678 + Emit4(v); + } + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc += 1; // OP_BAND + instruction += 1; + return qtrue; + + case OP_BOR: + v = Constant4(); + + EmitMovEAXStack(vm, 0); + if(iss8(v)) + { + EmitString("83 C8"); // or eax, 0x7F + Emit1(v); + } + else + { + EmitString("0D"); // or eax, 0x12345678 + Emit4(v); + } + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc += 1; // OP_BOR + instruction += 1; + return qtrue; + + case OP_BXOR: + v = Constant4(); + + EmitMovEAXStack(vm, 0); + if(iss8(v)) + { + EmitString("83 F0"); // xor eax, 0x7F + Emit1(v); + } + else + { + EmitString("35"); // xor eax, 0x12345678 + Emit4(v); + } + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + + pc += 1; // OP_BXOR + instruction += 1; + return qtrue; + + 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: + EmitMovEAXStack(vm, 0); + EmitCommand(LAST_COMMAND_SUB_BL_1); + EmitString("3D"); // cmp eax, 0x12345678 + Emit4(Constant4()); + + pc++; // OP_* + EmitBranchConditions(vm, op1); + instruction++; + + return qtrue; + + case OP_EQF: + case OP_NEF: + if(NextConstant4()) + break; + pc += 5; // CONST + OP_EQF|OP_NEF + + EmitMovEAXStack(vm, 0); + EmitCommand(LAST_COMMAND_SUB_BL_1); + // floating point hack :) + EmitString("25"); // and eax, 0x7FFFFFFF + Emit4(0x7FFFFFFF); + if(op1 == OP_EQF) + EmitJumpIns(vm, "0F 84", Constant4()); // jz 0x12345678 + else + EmitJumpIns(vm, "0F 85", Constant4()); // jnz 0x12345678 + + instruction += 1; + return qtrue; + + + case OP_JUMP: + EmitJumpIns(vm, "E9", Constant4()); // jmp 0x12345678 + + pc += 1; // OP_JUMP + instruction += 1; + return qtrue; + + case OP_CALL: + v = Constant4(); + EmitCallConst(vm, v, callProcOfsSyscall); + + pc += 1; // OP_CALL + instruction += 1; + return qtrue; + + default: + break; + } + + return qfalse; +} + +/* +================= +VM_Compile +================= +*/ +void VM_Compile(vm_t *vm, vmHeader_t *header) +{ + int op; + int maxLength; + int v; + int i; + int callProcOfsSyscall, callProcOfs, callDoSyscallOfs; + + jusedSize = header->instructionCount + 2; + + // allocate a very large temp buffer, we will shrink it later + maxLength = header->codeLength * 8 + 64; + buf = (byte *)Z_Malloc(maxLength, TAG_VM, qtrue); + jused = (byte *)Z_Malloc(jusedSize, TAG_VM, qtrue); + code = (byte *)Z_Malloc(header->codeLength + 32, TAG_VM, qtrue); + + Com_Memset(jused, 0, jusedSize); + Com_Memset(buf, 0, maxLength); + + // copy code in larger buffer and put some zeros at the end + // so we can safely look ahead for a few instructions in it + // without a chance to get false-positive because of some garbage bytes + Com_Memset(code, 0, header->codeLength+32); + Com_Memcpy(code, (byte *)header + header->codeOffset, header->codeLength ); + + // ensure that the optimisation pass knows about all the jump + // table targets + pc = -1; // a bogus value to be printed in out-of-bounds error messages + for( i = 0; i < vm->numJumpTableTargets; i++ ) { + JUSED( *(int *)(vm->jumpTableTargets + ( i * sizeof( int ) ) ) ); + } + + // Start buffer with x86-VM specific procedures + compiledOfs = 0; + + callDoSyscallOfs = compiledOfs; + callProcOfs = EmitCallDoSyscall(vm); + callProcOfsSyscall = EmitCallProcedure(vm, callDoSyscallOfs); + vm->entryOfs = compiledOfs; + vm->callProcOfs = callProcOfs; + vm->callProcOfsSyscall = callProcOfsSyscall; + + for(pass=0; pass < 3; pass++) { + oc0 = -23423; + oc1 = -234354; + pop0 = -43435; + pop1 = -545455; + + // translate all instructions + pc = 0; + instruction = 0; + //code = (byte *)header + header->codeOffset; + compiledOfs = vm->entryOfs; + + LastCommand = LAST_COMMAND_NONE; + + while(instruction < header->instructionCount) + { + if(compiledOfs > maxLength - 16) + { + VMFREE_BUFFERS(); + Com_Error(ERR_DROP, "VM_CompileX86: maxLength exceeded"); + } + + vm->instructionPointers[ instruction ] = compiledOfs; + + if ( !vm->jumpTableTargets ) + jlabel = 1; + else + jlabel = jused[ instruction ]; + + instruction++; + + if(pc > header->codeLength) + { + VMFREE_BUFFERS(); + Com_Error(ERR_DROP, "VM_CompileX86: pc > header->codeLength"); + } + + op = code[ pc ]; + pc++; + switch ( op ) { + case 0: + break; + case OP_BREAK: + EmitString("CC"); // int 3 + break; + case OP_ENTER: + // save frame pointer + EmitString("55"); // push ebp + EmitRexString(0x48, "89 E5"); // mov ebp, esp + EmitString("81 EE"); // sub esi, 0x12345678 + Emit4(Constant4()); + break; + case OP_CONST: + if(ConstOptimize(vm, callProcOfsSyscall)) + break; + + EmitPushStack(vm); + EmitString("C7 04 9F"); // mov dword ptr [edi + ebx * 4], 0x12345678 + lastConst = Constant4(); + + Emit4(lastConst); + if(code[pc] == OP_JUMP) + JUSED(lastConst); + + break; + case OP_LOCAL: + EmitPushStack(vm); + EmitString("8D 86"); // lea eax, [0x12345678 + esi] + oc0 = oc1; + oc1 = Constant4(); + Emit4(oc1); + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + case OP_ARG: + EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4] + EmitString("8B D6"); // mov edx, esi + EmitString("81 C2"); // add edx, 0x12345678 + Emit4((Constant1() & 0xFF)); + MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 +#if idx64 + EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax +#else + EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_CALL: + EmitCallRel(vm, callProcOfs); + break; + case OP_PUSH: + EmitPushStack(vm); + break; + case OP_POP: + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_LEAVE: + v = Constant4(); + EmitString("81 C6"); // add esi, 0x12345678 + Emit4(v); + // Restore frame pointer + EmitString("5D"); // pop ebp + EmitString("C3"); // ret + break; + case OP_LOAD4: + if (code[pc] == OP_CONST && code[pc+5] == OP_ADD && code[pc+6] == OP_STORE4) + { + if(oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) + { + compiledOfs -= 12; + vm->instructionPointers[instruction - 1] = compiledOfs; + } + + pc++; // OP_CONST + v = Constant4(); + + EmitMovEDXStack(vm, vm->dataMask); + if(v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) + { +#if idx64 + EmitRexString(0x41, "FF 04 11"); // inc dword ptr [r9 + edx] +#else + EmitString("FF 82"); // inc dword ptr [edx + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + } + else + { +#if idx64 + EmitRexString(0x41, "8B 04 11"); // mov eax, dword ptr [r9 + edx] +#else + EmitString("8B 82"); // mov eax, dword ptr [edx + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + EmitString("05"); // add eax, v + Emit4(v); + + if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) + { +#if idx64 + EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax +#else + EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + } + else + { + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4] + MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 +#if idx64 + EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax +#else + EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + } + } + + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + pc++; // OP_ADD + pc++; // OP_STORE + instruction += 3; + break; + } + + if(code[pc] == OP_CONST && code[pc+5] == OP_SUB && code[pc+6] == OP_STORE4) + { + if(oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) + { + compiledOfs -= 12; + vm->instructionPointers[instruction - 1] = compiledOfs; + } + + pc++; // OP_CONST + v = Constant4(); + + EmitMovEDXStack(vm, vm->dataMask); + if(v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) + { +#if idx64 + EmitRexString(0x41, "FF 0C 11"); // dec dword ptr [r9 + edx] +#else + EmitString("FF 8A"); // dec dword ptr [edx + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + } + else + { +#if idx64 + EmitRexString(0x41, "8B 04 11"); // mov eax, dword ptr [r9 + edx] +#else + EmitString("8B 82"); // mov eax, dword ptr [edx + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + EmitString("2D"); // sub eax, v + Emit4(v); + + if(oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) + { +#if idx64 + EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax +#else + EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + } + else + { + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4] + MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 +#if idx64 + EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax +#else + EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + } + } + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + pc++; // OP_SUB + pc++; // OP_STORE + instruction += 3; + break; + } + + if(buf[compiledOfs - 3] == 0x89 && buf[compiledOfs - 2] == 0x04 && buf[compiledOfs - 1] == 0x9F) + { + compiledOfs -= 3; + vm->instructionPointers[instruction - 1] = compiledOfs; + MASK_REG("E0", vm->dataMask); // and eax, 0x12345678 +#if idx64 + EmitRexString(0x41, "8B 04 01"); // mov eax, dword ptr [r9 + eax] +#else + EmitString("8B 80"); // mov eax, dword ptr [eax + 0x1234567] + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + } + + EmitMovEAXStack(vm, vm->dataMask); +#if idx64 + EmitRexString(0x41, "8B 04 01"); // mov eax, dword ptr [r9 + eax] +#else + EmitString("8B 80"); // mov eax, dword ptr [eax + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + case OP_LOAD2: + EmitMovEAXStack(vm, vm->dataMask); +#if idx64 + EmitRexString(0x41, "0F B7 04 01"); // movzx eax, word ptr [r9 + eax] +#else + EmitString("0F B7 80"); // movzx eax, word ptr [eax + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + case OP_LOAD1: + EmitMovEAXStack(vm, vm->dataMask); +#if idx64 + EmitRexString(0x41, "0F B6 04 01"); // movzx eax, byte ptr [r9 + eax] +#else + EmitString("0F B6 80"); // movzx eax, byte ptr [eax + 0x12345678] + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + case OP_STORE4: + EmitMovEAXStack(vm, 0); + EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4] + MASK_REG("E2", vm->dataMask & ~3); // and edx, 0x12345678 +#if idx64 + EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax +#else + EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + break; + case OP_STORE2: + EmitMovEAXStack(vm, 0); + EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4] + MASK_REG("E2", vm->dataMask & ~1); // and edx, 0x12345678 +#if idx64 + Emit1(0x66); // mov word ptr [r9 + edx], eax + EmitRexString(0x41, "89 04 11"); +#else + EmitString("66 89 82"); // mov word ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + break; + case OP_STORE1: + EmitMovEAXStack(vm, 0); + EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4] + MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 +#if idx64 + EmitRexString(0x41, "88 04 11"); // mov byte ptr [r9 + edx], eax +#else + EmitString("88 82"); // mov byte ptr [edx + 0x12345678], eax + Emit4((intptr_t) vm->dataBase); +#endif + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + break; + + 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: + EmitMovEAXStack(vm, 0); + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + EmitString("39 44 9F 04"); // cmp eax, dword ptr 4[edi + ebx * 4] + + EmitBranchConditions(vm, op); + break; + case OP_EQF: + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + EmitString("F3 0F 10 44 9F 04"); // movss xmm0, dword ptr 4[edi + ebx * 4] + EmitString("0F 2E 44 9F 08"); // ucomiss xmm0, dword ptr 8[edi + ebx * 4] + EmitString("7A 06"); // jp +0x6 (jump over next opcode) + EmitJumpIns(vm, "0F 84", Constant4()); // je 0x12345678 + break; + case OP_NEF: + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + EmitString("F3 0F 10 44 9F 04"); // movss xmm0, dword ptr 4[edi + ebx * 4] + EmitString("0F 2E 44 9F 08"); // ucomiss xmm0, dword ptr 8[edi + ebx * 4] + { + int dest = Constant4(); + EmitJumpIns(vm, "0F 8A", dest); // jp 0x12345678 + EmitJumpIns(vm, "0F 85", dest); // jne 0x12345678 + } + break; + case OP_LTF: + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + EmitString("F3 0F 10 44 9F 08"); // movss xmm0, dword ptr 8[edi + ebx * 4] + EmitString("0F 2E 44 9F 04"); // ucomiss xmm0, dword ptr 4[edi + ebx * 4] + EmitJumpIns(vm, "0F 87", Constant4()); // ja 0x12345678 + break; + case OP_LEF: + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + EmitString("F3 0F 10 44 9F 08"); // movss xmm0, dword ptr 8[edi + ebx * 4] + EmitString("0F 2E 44 9F 04"); // ucomiss xmm0, dword ptr 4[edi + ebx * 4] + EmitJumpIns(vm, "0F 83", Constant4()); // jae 0x12345678 + break; + case OP_GTF: + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + EmitString("F3 0F 10 44 9F 04"); // movss xmm0, dword ptr 4[edi + ebx * 4] + EmitString("0F 2E 44 9F 08"); // ucomiss xmm0, dword ptr 8[edi + ebx * 4] + EmitJumpIns(vm, "0F 87", Constant4()); // ja 0x12345678 + break; + case OP_GEF: + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + EmitString("F3 0F 10 44 9F 04"); // movss xmm0, dword ptr 4[edi + ebx * 4] + EmitString("0F 2E 44 9F 08"); // ucomiss xmm0, dword ptr 8[edi + ebx * 4] + EmitJumpIns(vm, "0F 83", Constant4()); // jae 0x12345678 + break; + case OP_NEGI: + EmitMovEAXStack(vm, 0); + EmitString("F7 D8"); // neg eax + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); + break; + case OP_ADD: + EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4] + EmitString("01 44 9F FC"); // add dword ptr -4[edi + ebx * 4], eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_SUB: + EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4] + EmitString("29 44 9F FC"); // sub dword ptr -4[edi + ebx * 4], eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_DIVI: + EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4] + EmitString("99"); // cdq + EmitString("F7 3C 9F"); // idiv dword ptr [edi + ebx * 4] + EmitString("89 44 9F FC"); // mov dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_DIVU: + EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4] + EmitString("33 D2"); // xor edx, edx + EmitString("F7 34 9F"); // div dword ptr [edi + ebx * 4] + EmitString("89 44 9F FC"); // mov dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_MODI: + EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4] + EmitString("99" ); // cdq + EmitString("F7 3C 9F"); // idiv dword ptr [edi + ebx * 4] + EmitString("89 54 9F FC"); // mov dword ptr -4[edi + ebx * 4],edx + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_MODU: + EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4] + EmitString("33 D2"); // xor edx, edx + EmitString("F7 34 9F"); // div dword ptr [edi + ebx * 4] + EmitString("89 54 9F FC"); // mov dword ptr -4[edi + ebx * 4],edx + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_MULI: + EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4] + EmitString("F7 2C 9F"); // imul dword ptr [edi + ebx * 4] + EmitString("89 44 9F FC"); // mov dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_MULU: + EmitString("8B 44 9F FC"); // mov eax,dword ptr -4[edi + ebx * 4] + EmitString("F7 24 9F"); // mul dword ptr [edi + ebx * 4] + EmitString("89 44 9F FC"); // mov dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_BAND: + EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4] + EmitString("21 44 9F FC"); // and dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_BOR: + EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4] + EmitString("09 44 9F FC"); // or dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_BXOR: + EmitMovEAXStack(vm, 0); // mov eax, dword ptr [edi + ebx * 4] + EmitString("31 44 9F FC"); // xor dword ptr -4[edi + ebx * 4],eax + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_BCOM: + EmitString("F7 14 9F"); // not dword ptr [edi + ebx * 4] + break; + case OP_LSH: + EmitMovECXStack(vm); + EmitString("D3 64 9F FC"); // shl dword ptr -4[edi + ebx * 4], cl + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_RSHI: + EmitMovECXStack(vm); + EmitString("D3 7C 9F FC"); // sar dword ptr -4[edi + ebx * 4], cl + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_RSHU: + EmitMovECXStack(vm); + EmitString("D3 6C 9F FC"); // shr dword ptr -4[edi + ebx * 4], cl + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + break; + case OP_NEGF: + EmitString("0F 57 C0"); // xorps xmm0, xmm0 + EmitString("F3 0F 5C 04 9F"); // subss xmm0, dword ptr [edi + ebx * 4] + EmitString("F3 0F 11 04 9F"); // storess dword ptr [edi + ebx * 4], xmm0 + break; + case OP_ADDF: + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + EmitString("F3 0F 10 04 9F"); // movss xmm0, dword ptr [edi + ebx * 4] + EmitString("F3 0F 58 44 9F 04"); // addss xmm0, dword ptr 4[edi + ebx * 4] + EmitString("F3 0F 11 04 9F"); // movss dword ptr [edi + ebx * 4], xmm0 + break; + case OP_SUBF: + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + EmitString("F3 0F 10 04 9F"); // movss xmm0, dword ptr [edi + ebx * 4] + EmitString("F3 0F 5C 44 9F 04"); // subss xmm0, dword ptr 4[edi + ebx * 4] + EmitString("F3 0F 11 04 9F"); // movss dword ptr [edi + ebx * 4], xmm0 + break; + case OP_DIVF: + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + EmitString("F3 0F 10 04 9F"); // movss xmm0, dword ptr [edi + ebx * 4] + EmitString("F3 0F 5E 44 9F 04"); // divss xmm0, dword ptr 4[edi + ebx * 4] + EmitString("F3 0F 11 04 9F"); // movss dword ptr [edi + ebx * 4], xmm0 + break; + case OP_MULF: + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + EmitString("F3 0F 10 04 9F"); // movss xmm0, dword ptr [edi + ebx * 4] + EmitString("F3 0F 59 44 9F 04"); // mulss xmm0, dword ptr 4[edi + ebx * 4] + EmitString("F3 0F 11 04 9F"); // movss dword ptr [edi + ebx * 4], xmm0 + break; + case OP_CVIF: + EmitString("F3 0F 2A 04 9F"); // cvtsi2ss xmm0, dword ptr [edi + ebx * 4] + EmitString("F3 0F 11 04 9F"); // movss dword ptr [edi + ebx * 4], xmm0 + break; + case OP_CVFI: + EmitString("F3 0F 2C 04 9F"); // cvttss2si eax, dword ptr [edi + ebx *4] + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + case OP_SEX8: + EmitString("0F BE 04 9F"); // movsx eax, byte ptr [edi + ebx * 4] + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + case OP_SEX16: + EmitString("0F BF 04 9F"); // movsx eax, word ptr [edi + ebx * 4] + EmitCommand(LAST_COMMAND_MOV_STACK_EAX); // mov dword ptr [edi + ebx * 4], eax + break; + + case OP_BLOCK_COPY: + EmitString("B8"); // mov eax, 0x12345678 + Emit4(VM_BLOCK_COPY); + EmitString("B9"); // mov ecx, 0x12345678 + Emit4(Constant4()); + + EmitCallRel(vm, callDoSyscallOfs); + + EmitCommand(LAST_COMMAND_SUB_BL_2); // sub bl, 2 + break; + + case OP_JUMP: + EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 + EmitString("8B 44 9F 04"); // mov eax, dword ptr 4[edi + ebx * 4] + EmitString("81 F8"); // cmp eax, vm->instructionCount + Emit4(vm->instructionCount); +#if idx64 + EmitString("73 04"); // jae +4 + EmitRexString(0x49, "FF 24 C0"); // jmp qword ptr [r8 + eax * 8] +#else + EmitString("73 07"); // jae +7 + EmitString("FF 24 85"); // jmp dword ptr [instructionPointers + eax * 4] + Emit4((intptr_t) vm->instructionPointers); +#endif + EmitCallErrJump(vm, callDoSyscallOfs); + break; + default: + VMFREE_BUFFERS(); + Com_Error(ERR_DROP, "VM_CompileX86: bad opcode %i at offset %i", op, pc); + } + pop0 = pop1; + pop1 = op; + } + } + + // copy to an exact sized buffer with the appropriate permission bits + vm->codeLength = compiledOfs; +#ifdef VM_X86_MMAP + vm->codeBase = (byte *)mmap(NULL, compiledOfs, PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); + if(vm->codeBase == MAP_FAILED) + Com_Error(ERR_FATAL, "VM_CompileX86: can't mmap memory"); +#elif _WIN32 + // allocate memory with EXECUTE permissions under windows. + vm->codeBase = (byte*)VirtualAlloc(NULL, compiledOfs, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + if(!vm->codeBase) + Com_Error(ERR_FATAL, "VM_CompileX86: VirtualAlloc failed"); +#else + vm->codeBase = malloc(compiledOfs); + if(!vm->codeBase) + Com_Error(ERR_FATAL, "VM_CompileX86: malloc failed"); +#endif + + Com_Memcpy( vm->codeBase, buf, compiledOfs ); + +#ifdef VM_X86_MMAP + if(mprotect(vm->codeBase, compiledOfs, PROT_READ|PROT_EXEC)) + Com_Error(ERR_FATAL, "VM_CompileX86: mprotect failed"); +#elif _WIN32 + { + DWORD oldProtect = 0; + + // remove write permissions. + if(!VirtualProtect(vm->codeBase, compiledOfs, PAGE_EXECUTE_READ, &oldProtect)) + Com_Error(ERR_FATAL, "VM_CompileX86: VirtualProtect failed"); + } +#endif + + Z_Free( code ); + Z_Free( buf ); + Z_Free( jused ); + Com_Printf( "VM file %s compiled to %i bytes of code\n", vm->name, compiledOfs ); + + vm->destroy = VM_Destroy_Compiled; + + // offset all the instruction pointers for the new location + for ( i = 0 ; i < header->instructionCount ; i++ ) { + vm->instructionPointers[i] += (intptr_t) vm->codeBase; + } +} + +void VM_Destroy_Compiled(vm_t* self) +{ +#ifdef VM_X86_MMAP + munmap(self->codeBase, self->codeLength); +#elif _WIN32 + VirtualFree(self->codeBase, 0, MEM_RELEASE); +#else + free(self->codeBase); +#endif +} + +/* +============== +VM_CallCompiled + +This function is called directly by the generated code +============== +*/ + +#if defined(_MSC_VER) && idx64 +extern "C" uint8_t qvmcall64(int *programStack, int *opStack, intptr_t *instructionPointers, byte *dataBase); +#endif + +int VM_CallCompiled(vm_t *vm, int *args) +{ + byte stack[OPSTACK_SIZE + 15]; + void *entryPoint; + int programStack, stackOnEntry; + byte *image; + int *opStack; + int opStackOfs; + int arg; + + currentVM = vm; + + // interpret the code + vm->currentlyInterpreting = qtrue; + + // we might be called recursively, so this might not be the very top + programStack = stackOnEntry = vm->programStack; + + // set up the stack frame + image = vm->dataBase; + + programStack -= ( 8 + 4 * MAX_VMMAIN_ARGS ); + + for ( arg = 0; arg < MAX_VMMAIN_ARGS; arg++ ) + *(int *)&image[ programStack + 8 + arg * 4 ] = args[ arg ]; + + *(int *)&image[ programStack + 4 ] = 0; // return stack + *(int *)&image[ programStack ] = -1; // will terminate the loop on return + + // off we go into generated code... + entryPoint = vm->codeBase + vm->entryOfs; + opStack = (int *)PADP(stack, 16); + *(unsigned *)opStack = 0xDEADBEEFu; + opStackOfs = 0; + +#ifdef _MSC_VER + #if idx64 + opStackOfs = qvmcall64(&programStack, opStack, vm->instructionPointers, vm->dataBase); + #else + __asm + { + pushad + + mov esi, dword ptr programStack + mov edi, dword ptr opStack + mov ebx, dword ptr opStackOfs + + call entryPoint + + mov dword ptr opStackOfs, ebx + mov dword ptr opStack, edi + mov dword ptr programStack, esi + + popad + } + #endif +#elif idx64 + __asm__ volatile( + "movq %5, %%rax\n" + "movq %3, %%r8\n" + "movq %4, %%r9\n" + "push %%r15\n" + "push %%r14\n" + "push %%r13\n" + "push %%r12\n" + "callq *%%rax\n" + "pop %%r12\n" + "pop %%r13\n" + "pop %%r14\n" + "pop %%r15\n" + : "+S" (programStack), "+D" (opStack), "+b" (opStackOfs) + : "g" (vm->instructionPointers), "g" (vm->dataBase), "g" (entryPoint) + : "cc", "memory", "%rax", "%rcx", "%rdx", "%r8", "%r9", "%r10", "%r11" + ); +#else + __asm__ volatile( + "calll *%3\n" + : "+S" (programStack), "+D" (opStack), "+b" (opStackOfs) + : "g" (entryPoint) + : "cc", "memory", "%eax", "%ecx", "%edx" + ); +#endif + + if(opStackOfs != 1 || *(unsigned *)opStack != 0xDEADBEEFu) + { + Com_Error(ERR_DROP, "opStack corrupted in compiled code"); + } + if(programStack != stackOnEntry - (8 + 4 * MAX_VMMAIN_ARGS)) + Com_Error(ERR_DROP, "programStack corrupted in compiled code"); + + vm->programStack = stackOnEntry; + + return opStack[opStackOfs]; +} From 324b6fd19cbf5348eb4e9910afa0d0738ba598b1 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Wed, 27 Sep 2023 00:24:07 +0200 Subject: [PATCH 02/28] Fix compiling with the jk2mv vm_* files. Move some vm related structs. Merge some vm related structs. Adjust a few places in vm_* files. Port a few functions from jk2mv's vm.cpp file (same revision as the vm_* files). --- codemp/qcommon/qcommon.h | 48 ++- codemp/qcommon/vm.cpp | 580 ++++++++++++++++++++++++++++++ codemp/qcommon/vm_arm.cpp | 2 +- codemp/qcommon/vm_interpreted.cpp | 10 +- codemp/qcommon/vm_local.h | 69 +--- codemp/qcommon/vm_x86.cpp | 70 ++-- shared/sys/sys_public.h | 2 + shared/sys/sys_unix.cpp | 5 + 8 files changed, 686 insertions(+), 100 deletions(-) diff --git a/codemp/qcommon/qcommon.h b/codemp/qcommon/qcommon.h index 1c69b9f3a5..28a0a94470 100644 --- a/codemp/qcommon/qcommon.h +++ b/codemp/qcommon/qcommon.h @@ -270,7 +270,18 @@ typedef enum vmSlots_e { MAX_VM } vmSlots_t; -typedef struct vm_s { +typedef struct vmSymbol_s { + struct vmSymbol_s *next; + struct vmSymbol_s *caller; + int symValue; + int symInstr; + long profileCount; + int callCount; + char symName[1]; // variable sized +} vmSymbol_t; + +typedef struct vm_s vm_t; +struct vm_s { vmSlots_t slot; // VM_GAME, VM_CGAME, VM_UI char name[MAX_QPATH]; void *dllHandle; @@ -284,7 +295,38 @@ typedef struct vm_s { VMMainProc* main; // module vmMain intptr_t (QDECL *syscall)( intptr_t *parms ); // engine syscall handler } legacy; -} vm_t; + + // QVM stuff + int programStack; // the vm may be recursively entered + void (*destroy)(vm_t* self); + qboolean currentlyInterpreting; + + qboolean compiled; + byte *codeBase; + int entryOfs; + int callProcOfs; + int callProcOfsSyscall; + int codeLength; + + intptr_t *instructionPointers; + int instructionCount; + + byte *dataBase; + int dataMask; + + int stackBottom; // if programStack < stackBottom, error + + int numSymbols; + vmSymbol_t *symbols; + vmSymbol_t **symbolTable; + + int callLevel; // counts recursive VM_Call + int breakFunction; // increment breakCount on function entry to this + int breakCount; + + byte *jumpTableTargets; + int numJumpTableTargets; +}; extern vm_t *currentVM; @@ -613,7 +655,7 @@ fileHandle_t FS_SV_FOpenFileWrite( const char *filename ); fileHandle_t FS_SV_FOpenFileAppend( const char *filename ); int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ); void FS_SV_Rename( const char *from, const char *to, qboolean safe ); -long FS_FOpenFileRead( const char *qpath, fileHandle_t *file, qboolean uniqueFILE ); +long FS_FOpenFileRead( const char *qpath, fileHandle_t *file, qboolean uniqueFILE); // if uniqueFILE is true, then a new FILE will be fopened even if the file // is found in an already open pak file. If uniqueFILE is false, you must call // FS_FCloseFile instead of fclose, otherwise the pak FILE would be improperly closed diff --git a/codemp/qcommon/vm.cpp b/codemp/qcommon/vm.cpp index 11aa3ee558..13765ce8c5 100644 --- a/codemp/qcommon/vm.cpp +++ b/codemp/qcommon/vm.cpp @@ -26,6 +26,7 @@ along with this program; if not, see . #include #include "qcommon/qcommon.h" +#include "vm_local.h" vm_t *currentVM = NULL; @@ -292,3 +293,582 @@ intptr_t QDECL VM_Call( vm_t *vm, int callnum, intptr_t arg0, intptr_t arg1, int return vm->legacy.main( callnum, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 ); } + +// QVM functions +int vm_debugLevel; +void VM_Debug( int level ) { + vm_debugLevel = level; +} + +qboolean vm_profileInclusive; +static vmSymbol_t nullSymbol; + +/* +=============== +VM_ValueToInstr + +Calculates QVM instruction number for program counter value +=============== +*/ +static int VM_ValueToInstr( vm_t *vm, int value ) { + intptr_t instrOffs = vm->instructionPointers[0] - vm->entryOfs; + + for (int i = 0; i < vm->instructionCount; i++) { + if (vm->instructionPointers[i] - instrOffs > value) { + return i - 1; + } + } + + return vm->instructionCount; +} + +/* +=============== +VM_ValueToSymbol + +Assumes a program counter value +=============== +*/ +const char *VM_ValueToSymbol( vm_t *vm, int value ) { + static char text[MAX_TOKEN_CHARS]; + vmSymbol_t *sym; + + sym = VM_ValueToFunctionSymbol( vm, value ); + + if ( sym == &nullSymbol ) { + Com_sprintf( text, sizeof( text ), "%s(vmMain+%d) [%#x]", + vm->name, VM_ValueToInstr( vm, value ), value ); + return text; + } + + // predefined helper routines + if (value < vm->entryOfs) { + Com_sprintf( text, sizeof( text ), "%s(%s+%#x) [%#x]", + vm->name, sym->symName, value - sym->symValue, value ); + return text; + } + + Com_sprintf( text, sizeof( text ), "%s(%s+%d) [%#x]", vm->name, sym->symName, + VM_ValueToInstr( vm, value ) - sym->symInstr, value ); + + return text; +} + +/* +=============== +VM_ValueToFunctionSymbol + +For profiling, find the symbol behind this value +=============== +*/ +vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) { + if ( (unsigned)vm->codeLength <= (unsigned)value ) { + return &nullSymbol; + } + + if ( vm->symbolTable ) { + return vm->symbolTable[value]; + } + + if ( vm->symbols ) { + vmSymbol_t *sym; + + for ( sym = vm->symbols; sym->next; sym = sym->next ) { + if ( sym->next->symValue > value && value >= sym->symValue ) { + return sym; + } + } + if ( sym->symValue <= value ) { + return sym; + } + } + + return &nullSymbol; +} + + +/* +=============== +VM_SymbolToValue +=============== +*/ +int VM_SymbolToValue( vm_t *vm, const char *symbol ) { + vmSymbol_t *sym; + + for ( sym = vm->symbols ; sym ; sym = sym->next ) { + if ( !strcmp( symbol, sym->symName ) ) { + return sym->symValue; + } + } + return 0; +} + + +/* +===================== +VM_SymbolForCompiledPointer +===================== +*/ +const char *VM_SymbolForCompiledPointer( void *code ) { + for ( int i = 0; i < MAX_VM; i++ ) { + vm_t *vm = vmTable[i]; + + if ( vm->compiled ) { + if ( vm->codeBase <= code && code < vm->codeBase + vm->codeLength ) { + return VM_ValueToSymbol( vm, (byte *)code - vm->codeBase ); + } + } + } + + return NULL; +} + +/* +=============== +ParseHex +=============== +*/ +int ParseHex( const char *text ) { + int value; + int c; + + value = 0; + while ( ( c = *text++ ) != 0 ) { + if ( c >= '0' && c <= '9' ) { + value = value * 16 + c - '0'; + continue; + } + if ( c >= 'a' && c <= 'f' ) { + value = value * 16 + 10 + c - 'a'; + continue; + } + if ( c >= 'A' && c <= 'F' ) { + value = value * 16 + 10 + c - 'A'; + continue; + } + } + + return value; +} + +static void VM_GeneratePerfMap(vm_t *vm) { +#ifdef __linux__ + // generate perf .map file for profiling compiled QVM + char mapFile[MAX_OSPATH]; + FILE *f; + void *address; + int length; + + if ( !vm->symbols ) { + return; + } + + Com_sprintf( mapFile, sizeof(mapFile), "/tmp/perf-%d.map", Sys_PID() ); + + if ( (f = fopen( mapFile, "a" )) == NULL ) { + Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open %s\n", mapFile ); + return; + } + + // perf .map format: "hex_address hex_length name\n" + for ( vmSymbol_t *sym = vm->symbols; sym; sym = sym->next ) { + address = vm->codeBase + sym->symValue; + + if ( sym->next ) { + length = sym->next->symValue - sym->symValue; + } else { + length = vm->codeLength - sym->symValue; + } + + fprintf( f, "%lx %x %s\n", (unsigned long)(vm->codeBase + sym->symValue), length, sym->symName ); + } + + fclose( f ); +#endif // __linux__ +} + +/* +=============== +VM_LoadSymbols +=============== +*/ +void VM_LoadSymbols( vm_t *vm ) { + union { + char *c; + void *v; + } mapfile; + char name[MAX_QPATH]; + char symbols[MAX_QPATH]; + + if ( vm->dllHandle ) { + return; + } + + // + // add symbols for vm_x86 predefined procedures + // + + vmSymbol_t **prev, *sym; + + prev = &vm->symbols; + + if ( vm->callProcOfs ) { + const char *symName; + + symName = "CallDoSyscall"; + sym = *prev = (vmSymbol_t *)Hunk_Alloc( sizeof( *sym ) + strlen( symName ), h_high ); + Q_strncpyz( sym->symName, symName, strlen( symName ) + 1 ); + sym->symValue = 0; + + symName = "CallProcedure"; + sym = sym->next = (vmSymbol_t *)Hunk_Alloc( sizeof( *sym ) + strlen( symName ), h_high ); + Q_strncpyz( sym->symName, symName, strlen( symName ) + 1 ); + sym->symValue = vm->callProcOfs; + + // "CallProcedureSyscall" used by ioq3 optimizer, tail of "CallProcedure" + symName = "CallProcedure"; + sym = sym->next = (vmSymbol_t *)Hunk_Alloc( sizeof( *sym ) + strlen( symName ), h_high ); + Q_strncpyz( sym->symName, symName, strlen( symName ) + 1 ); + sym->symValue = vm->callProcOfsSyscall; + + vm->numSymbols = 3; + prev = &sym->next; + sym->next = NULL; + } + + // + // parse symbols + // + COM_StripExtension(vm->name, name, sizeof(name)); + Com_sprintf( symbols, sizeof(symbols), "vm/%s.map", name ); + FS_ReadFile( symbols, &mapfile.v ); + + if ( mapfile.c ) { + const char *text_p; + const char *token; + int chars; + int segment; + int value; + int count = 0; + + text_p = mapfile.c; + + while ( 1 ) { + token = COM_Parse( &text_p ); + if ( !token[0] ) { + break; + } + segment = ParseHex( token ); + if ( segment ) { + COM_Parse( &text_p ); + COM_Parse( &text_p ); + continue; // only load code segment values + } + + token = COM_Parse( &text_p ); + if ( !token[0] ) { + Com_Printf( "WARNING: incomplete line at end of file\n" ); + break; + } + value = ParseHex( token ); + if ( value < 0 || vm->instructionCount <= value ) { + COM_Parse( &text_p ); + continue; // don't load syscalls + } + + token = COM_Parse( &text_p ); + if ( !token[0] ) { + Com_Printf( "WARNING: incomplete line at end of file\n" ); + break; + } + + + chars = (int)strlen( token ); + sym = (vmSymbol_t *)Hunk_Alloc( sizeof( *sym ) + chars, h_high ); + *prev = sym; + prev = &sym->next; + sym->next = NULL; + sym->symInstr = value; + sym->symValue = vm->instructionPointers[value] - vm->instructionPointers[0] + vm->entryOfs; + Q_strncpyz( sym->symName, token, chars + 1 ); + + count++; + } + + vm->numSymbols += count; + Com_Printf( "%i symbols parsed from %s\n", count, symbols ); + FS_FreeFile( mapfile.v ); + } else { + const char *symName = "vmMain"; + sym = *prev = (vmSymbol_t *)Hunk_Alloc( sizeof( *sym ) + strlen( symName ), h_high ); + prev = &sym->next; + Q_strncpyz( sym->symName, symName, strlen( symName ) + 1 ); + sym->symValue = vm->entryOfs; + vm->numSymbols += 1; + + Com_Printf( "Couldn't load symbol file: %s\n", symbols ); + } + + + if ( vm->compiled && com_developer->integer ) + { + VM_GeneratePerfMap( vm ); + } + +#ifdef DEBUG_VM + // + // code->symbol lookup table for profiling and debugging interpreted QVM + // + + if ( !vm->compiled ) + { + vmSymbol_t *sym; + + vm->symbolTable = (vmSymbol_t **)Hunk_Alloc( vm->codeLength * sizeof(*vm->symbolTable), h_high ); + + for ( sym = vm->symbols; sym; sym = sym->next ) { + vm->symbolTable[sym->symValue] = sym; + } + + sym = NULL; + for ( int i = 0; i < vm->codeLength; i++ ) { + if ( vm->symbolTable[i] ) { + sym = vm->symbolTable[i]; + } + vm->symbolTable[i] = sym; + } + } +#endif // DEBUG_VM +} + + +//================================================================= + +static int QDECL VM_ProfileSort( const void *a, const void *b ) { + const vmSymbol_t *sa, *sb; + + sa = *(const vmSymbol_t * const *)a; + sb = *(const vmSymbol_t * const *)b; + + if ( sa->profileCount < sb->profileCount ) { + return -1; + } + if ( sa->profileCount > sb->profileCount ) { + return 1; + } + + return 0; +} + +/* +============== +VM_VmProfile_f + +============== +*/ +void VM_VmProfile_f( void ) { + vm_t *vm = NULL; + vmSymbol_t **sorted, *sym; + int i; + long total; + int totalCalls; + int time; + static int profileTime; + qboolean printHelp = qfalse; + qboolean resetCounts = qfalse; + qboolean printAll = qfalse; + + if ( Cmd_Argc() >= 2 ) { + const char *arg = Cmd_Argv(1); + + if ( !Q_stricmp(arg, "exclusive") ) { + vm_profileInclusive = qfalse; + resetCounts = qtrue; + Com_Printf("Collecting exclusive function instruction counts...\n"); + } else if ( !Q_stricmp(arg, "inclusive") ) { + vm_profileInclusive = qtrue; + resetCounts = qtrue; + Com_Printf("Collecting inclusive function instruction counts...\n"); + } else if ( !Q_stricmp(arg, "print") ) { + if (Cmd_Argc() >= 3) { + for ( i = 0; i < MAX_VM; i++ ) { + if ( !Q_stricmp(Cmd_Argv(2), vmTable[i]->name) ) { + vm = vmTable[i]; + break; + } + } + } else { + // pick first VM with symbols + for ( i = 0; i < MAX_VM; i++ ) { + if ( !vmTable[i]->compiled && vmTable[i]->numSymbols ) { + vm = vmTable[i]; + break; + } + } + } + } else { + printHelp = qtrue; + } + } else { + printHelp = qtrue; + } + + if ( resetCounts ) { + profileTime = Sys_Milliseconds(); + + for ( i = 0; i < MAX_VM; i++ ) { + for ( sym = vmTable[i]->symbols ; sym ; sym = sym->next ) { + sym->profileCount = 0; + sym->callCount = 0; + sym->caller = NULL; + } + } + return; + } + + if ( printHelp ) { + Com_Printf("Usage: vmprofile exclusive start collecting exclusive counts\n"); + Com_Printf(" vmprofile inclusive start collecting inclusive counts\n"); + Com_Printf(" vmprofile print [vm] print collected data\n"); + return; + } + + if ( vm == NULL || vm->compiled ) { + Com_Printf("Only interpreted VM can be profiled\n"); + return; + } + if ( vm->numSymbols <= 0 ) { + Com_Printf("No symbols\n"); + return; + } + + sorted = (vmSymbol_t **)Z_Malloc( vm->numSymbols * sizeof( *sorted ), TAG_VM, qtrue); + sorted[0] = vm->symbols; + total = sorted[0]->profileCount; + totalCalls = sorted[0]->callCount; + for ( i = 1 ; i < vm->numSymbols ; i++ ) { + sorted[i] = sorted[i-1]->next; + total += sorted[i]->profileCount; + totalCalls += sorted[i]->callCount; + } + + // assume everything is called from vmMain + if ( vm_profileInclusive ) + total = VM_ValueToFunctionSymbol( vm, 0 )->profileCount; + + if ( total > 0 ) { + qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort ); + + Com_Printf( "%4s %12s %9s Function Name\n", + vm_profileInclusive ? "Incl" : "Excl", + "Instructions", "Calls" ); + + // todo: collect associations for generating callgraphs + fileHandle_t callgrind = FS_FOpenFileWrite( va("callgrind.out.%s", vm->name) ); + // callgrind header + FS_Printf( callgrind, + "events: VM_Instructions\n" + "fl=vm/%s.qvm\n\n", vm->name ); + + for ( i = 0 ; i < vm->numSymbols ; i++ ) { + int perc; + + sym = sorted[i]; + + if (printAll || sym->profileCount != 0 || sym->callCount != 0) { + perc = 100 * sym->profileCount / total; + Com_Printf( "%3i%% %12li %9i %s\n", perc, sym->profileCount, sym->callCount, sym->symName ); + } + + FS_Printf(callgrind, + "fn=%s\n" + "0 %li\n\n", + sym->symName, sym->profileCount); + } + + FS_FCloseFile( callgrind ); + } + + time = Sys_Milliseconds() - profileTime; + + Com_Printf(" %12li %9i total\n", total, totalCalls ); + Com_Printf(" %12li %9i total per second\n", 1000 * total / time, 1000 * totalCalls / time ); + + Z_Free( sorted ); +} + +/* +============== +VM_VmInfo_f + +============== +*/ +void VM_VmInfo_f( void ) { + vm_t *vm; + int i; + + Com_Printf( "Registered virtual machines:\n" ); + for ( i = 0 ; i < MAX_VM ; i++ ) { + vm = vmTable[i]; + if ( !vm->name[0] ) { + break; + } + Com_Printf( "%s : ", vm->name ); + if ( vm->dllHandle ) { + Com_Printf( "native\n" ); + } else { + if (vm->compiled) { + Com_Printf("compiled on load\n"); + } else { + Com_Printf("interpreted\n"); + } + Com_Printf(" code length : %7i\n", vm->codeLength); + Com_Printf(" table length: %7i\n", vm->instructionCount * 4); + Com_Printf(" data length : %7i\n", vm->dataMask + 1); + if (vm->numSymbols) { + Com_Printf(" symbols : %i\n", vm->numSymbols); + } + } + } +} + +/* +=============== +VM_LogSyscalls + +Insert calls to this while debugging the vm compiler +=============== +*/ +void VM_LogSyscalls( int *args ) { + static int callnum; + static FILE *f; + + if ( !f ) { + f = fopen("syscalls.log", "w" ); + } + callnum++; + fprintf(f, "%i: %p (%i) = %i %i %i %i\n", callnum, (void*)(args - (int *)currentVM->dataBase), + args[0], args[1], args[2], args[3], args[4] ); +} + +/* +================= +VM_BlockCopy +Executes a block copy operation within currentVM data space +================= +*/ + +void VM_BlockCopy(unsigned int dest, unsigned int src, size_t n) +{ + unsigned int dataMask = currentVM->dataMask; + + if ((dest & dataMask) != dest + || (src & dataMask) != src + || ((dest + n) & dataMask) != dest + n + || ((src + n) & dataMask) != src + n) + { + Com_Error(ERR_DROP, "OP_BLOCK_COPY out of range!"); + } + + Com_Memcpy(currentVM->dataBase + dest, currentVM->dataBase + src, n); +} \ No newline at end of file diff --git a/codemp/qcommon/vm_arm.cpp b/codemp/qcommon/vm_arm.cpp index 31d88e0892..42a4253b75 100644 --- a/codemp/qcommon/vm_arm.cpp +++ b/codemp/qcommon/vm_arm.cpp @@ -368,7 +368,7 @@ void __attribute__((optimize("O0"))) VM_CompiledSyscall() { savedvm = currentVM; opStack += opStackIndex; - *opStack = currentVM->systemCall(args); + *opStack = currentVM->legacy.syscall(args); currentVM = savedvm; } diff --git a/codemp/qcommon/vm_interpreted.cpp b/codemp/qcommon/vm_interpreted.cpp index 8aa64f98ce..228edbbd4b 100644 --- a/codemp/qcommon/vm_interpreted.cpp +++ b/codemp/qcommon/vm_interpreted.cpp @@ -117,14 +117,14 @@ static const char * const opnames[256] = { //FIXME: these, um... look the same to me #if defined(__GNUC__) -static ID_INLINE unsigned int loadWord(void *addr) { +static QINLINE unsigned int loadWord(void *addr) { unsigned int word; asm("lwbrx %0,0,%1" : "=r" (word) : "r" (addr)); return word; } #else -static ID_INLINE unsigned int __lwbrx(register void *addr, +static QINLINE unsigned int __lwbrx(register void *addr, register int offset) { register unsigned int word; @@ -135,7 +135,7 @@ static ID_INLINE unsigned int __lwbrx(register void *addr, #endif #else - static ID_INLINE int loadWord(void *addr) { + static QINLINE int loadWord(void *addr) { int word; memcpy(&word, addr, 4); return LittleLong(word); @@ -518,10 +518,10 @@ int VM_CallInterpreted( vm_t *vm, int *args ) { for (size_t i = 0; i < ARRAY_LEN(argarr); ++i) { argarr[i] = *(++imagePtr); } - r = vm->systemCall( argarr ); + r = vm->legacy.syscall( argarr ); } else { intptr_t* argptr = (intptr_t *)&image[ programStack + 4 ]; - r = vm->systemCall( argptr ); + r = vm->legacy.syscall( argptr ); } } diff --git a/codemp/qcommon/vm_local.h b/codemp/qcommon/vm_local.h index d24556c237..d79db101d0 100644 --- a/codemp/qcommon/vm_local.h +++ b/codemp/qcommon/vm_local.h @@ -133,69 +133,24 @@ typedef enum { typedef int vmptr_t; -typedef struct vmSymbol_s { - struct vmSymbol_s *next; - struct vmSymbol_s *caller; - int symValue; - int symInstr; - long profileCount; - int callCount; - char symName[1]; // variable sized -} vmSymbol_t; - #define VM_OFFSET_PROGRAM_STACK 0 #define VM_OFFSET_SYSTEM_CALL 4 -struct vm_s { - // DO NOT MOVE OR CHANGE THESE WITHOUT CHANGING THE VM_OFFSET_* DEFINES - // USED BY THE ASM CODE - int programStack; // the vm may be recursively entered - intptr_t (*systemCall)( intptr_t *parms ); - - //------------------------------------ - - char name[MAX_QPATH]; - void *searchPath; // hint for FS_ReadFileDir() - - // for dynamic linked modules - void *dllHandle; - VM_EntryPoint_t entryPoint; - void (*destroy)(vm_t* self); - qboolean mvOverride; - - // for interpreted modules - qboolean currentlyInterpreting; - qboolean compiled; - byte *codeBase; - int entryOfs; - int callProcOfs; - int callProcOfsSyscall; - int codeLength; +#define VM_MAGIC 0x12721444 +typedef struct { + int vmMagic; - intptr_t *instructionPointers; - int instructionCount; + int instructionCount; - byte *dataBase; - int dataMask; + int codeOffset; + int codeLength; - int stackBottom; // if programStack < stackBottom, error - - int numSymbols; - vmSymbol_t *symbols; - vmSymbol_t **symbolTable; - - int callLevel; // counts recursive VM_Call - int breakFunction; // increment breakCount on function entry to this - int breakCount; - - byte *jumpTableTargets; - int numJumpTableTargets; - - int mvapilevel; - int mvmenu; - mvversion_t gameversion; -}; + int dataOffset; + int dataLength; + int litLength; // ( dataLength - litLength ) should be byteswapped on load + int bssLength; // zero filled memory appended to datalength +} vmHeader_t; extern vm_t *currentVM; @@ -215,3 +170,5 @@ const char *VM_SymbolForCompiledPointer( void *code ); void VM_LogSyscalls( int *args ); void VM_BlockCopy(unsigned int dest, unsigned int src, size_t n); + +void VM_Debug( int level ); diff --git a/codemp/qcommon/vm_x86.cpp b/codemp/qcommon/vm_x86.cpp index f7c481d05f..689b1175d8 100644 --- a/codemp/qcommon/vm_x86.cpp +++ b/codemp/qcommon/vm_x86.cpp @@ -150,7 +150,7 @@ static void EmitPtr(void *ptr) intptr_t v = (intptr_t) ptr; Emit4(v); -#if idx64 +#if defined(idx64) Emit1((v >> 32) & 0xFF); Emit1((v >> 40) & 0xFF); Emit1((v >> 48) & 0xFF); @@ -193,7 +193,7 @@ static void EmitString( const char *string ) { } static void EmitRexString(byte rex, const char *string) { -#if idx64 +#if defined(idx64) if(rex) Emit1(rex); #endif @@ -419,21 +419,21 @@ void DoSyscall(void) if(vm_syscallNum < 0) { int *data; -#if idx64 +#if defined(idx64) intptr_t args[MAX_VMSYSCALL_ARGS]; #endif data = (int *) (savedVM->dataBase + vm_programStack + 4); -#if idx64 +#if defined(idx64) args[0] = ~vm_syscallNum; for(size_t index = 1; index < ARRAY_LEN(args); index++) args[index] = data[index]; - vm_opStackBase[vm_opStackOfs + 1] = savedVM->systemCall(args); + vm_opStackBase[vm_opStackOfs + 1] = savedVM->legacy.syscall(args); #else data[0] = ~vm_syscallNum; - vm_opStackBase[vm_opStackOfs + 1] = savedVM->systemCall((intptr_t *) data); + vm_opStackBase[vm_opStackOfs + 1] = savedVM->legacy.syscall((intptr_t *) data); #endif } else @@ -493,7 +493,7 @@ int EmitCallDoSyscall(vm_t *vm) EmitString("51"); // push ebx EmitString("56"); // push esi EmitString("57"); // push edi -#if idx64 +#if defined(idx64) EmitRexString(0x41, "50"); // push r8 EmitRexString(0x41, "51"); // push r9 #endif @@ -532,7 +532,7 @@ int EmitCallDoSyscall(vm_t *vm) // reset the stack pointer to its previous value EmitString("5C"); // pop esp -#if idx64 +#if defined(idx64) EmitRexString(0x41, "59"); // pop r9 EmitRexString(0x41, "58"); // pop r8 #endif @@ -595,7 +595,7 @@ int EmitCallProcedure(vm_t *vm, int sysCallOfs) EmitString("73"); // jae badAddr jmpBadAddr = compiledOfs++; -#if idx64 +#if defined(idx64) EmitRexString(0x49, "FF 14 C0"); // call qword ptr [r8 + eax * 8] #else EmitString("FF 14 85"); // call dword ptr [vm->instructionPointers + eax * 4] @@ -753,7 +753,7 @@ qboolean ConstOptimize(vm_t *vm, int callProcOfsSyscall) case OP_LOAD4: EmitPushStack(vm); -#if idx64 +#if defined(idx64) EmitRexString(0x41, "8B 81"); // mov eax, dword ptr [r9 + 0x12345678] Emit4(Constant4() & vm->dataMask); #else @@ -769,7 +769,7 @@ qboolean ConstOptimize(vm_t *vm, int callProcOfsSyscall) case OP_LOAD2: EmitPushStack(vm); -#if idx64 +#if defined(idx64) EmitRexString(0x41, "0F B7 81"); // movzx eax, word ptr [r9 + 0x12345678] Emit4(Constant4() & vm->dataMask); #else @@ -785,7 +785,7 @@ qboolean ConstOptimize(vm_t *vm, int callProcOfsSyscall) case OP_LOAD1: EmitPushStack(vm); -#if idx64 +#if defined(idx64) EmitRexString(0x41, "0F B6 81"); // movzx eax, byte ptr [r9 + 0x12345678] Emit4(Constant4() & vm->dataMask); #else @@ -801,7 +801,7 @@ qboolean ConstOptimize(vm_t *vm, int callProcOfsSyscall) case OP_STORE4: EmitMovEAXStack(vm, (vm->dataMask & ~3)); -#if idx64 +#if defined(idx64) EmitRexString(0x41, "C7 04 01"); // mov dword ptr [r9 + eax], 0x12345678 Emit4(Constant4()); #else @@ -816,7 +816,7 @@ qboolean ConstOptimize(vm_t *vm, int callProcOfsSyscall) case OP_STORE2: EmitMovEAXStack(vm, (vm->dataMask & ~1)); -#if idx64 +#if defined(idx64) Emit1(0x66); // mov word ptr [r9 + eax], 0x1234 EmitRexString(0x41, "C7 04 01"); Emit2(Constant4()); @@ -833,7 +833,7 @@ qboolean ConstOptimize(vm_t *vm, int callProcOfsSyscall) case OP_STORE1: EmitMovEAXStack(vm, vm->dataMask); -#if idx64 +#if defined(idx64) EmitRexString(0x41, "C6 04 01"); // mov byte [r9 + eax], 0x12 Emit1(Constant4()); #else @@ -1198,7 +1198,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) EmitString("81 C2"); // add edx, 0x12345678 Emit4((Constant1() & 0xFF)); MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 -#if idx64 +#if defined(idx64) EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax #else EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax @@ -1238,7 +1238,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) EmitMovEDXStack(vm, vm->dataMask); if(v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { -#if idx64 +#if defined(idx64) EmitRexString(0x41, "FF 04 11"); // inc dword ptr [r9 + edx] #else EmitString("FF 82"); // inc dword ptr [edx + 0x12345678] @@ -1247,7 +1247,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) } else { -#if idx64 +#if defined(idx64) EmitRexString(0x41, "8B 04 11"); // mov eax, dword ptr [r9 + edx] #else EmitString("8B 82"); // mov eax, dword ptr [edx + 0x12345678] @@ -1258,7 +1258,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { -#if idx64 +#if defined(idx64) EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax #else EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax @@ -1270,7 +1270,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4] MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 -#if idx64 +#if defined(idx64) EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax #else EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax @@ -1300,7 +1300,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) EmitMovEDXStack(vm, vm->dataMask); if(v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { -#if idx64 +#if defined(idx64) EmitRexString(0x41, "FF 0C 11"); // dec dword ptr [r9 + edx] #else EmitString("FF 8A"); // dec dword ptr [edx + 0x12345678] @@ -1309,7 +1309,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) } else { -#if idx64 +#if defined(idx64) EmitRexString(0x41, "8B 04 11"); // mov eax, dword ptr [r9 + edx] #else EmitString("8B 82"); // mov eax, dword ptr [edx + 0x12345678] @@ -1320,7 +1320,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) if(oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { -#if idx64 +#if defined(idx64) EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax #else EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax @@ -1332,7 +1332,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) EmitCommand(LAST_COMMAND_SUB_BL_1); // sub bl, 1 EmitString("8B 14 9F"); // mov edx, dword ptr [edi + ebx * 4] MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 -#if idx64 +#if defined(idx64) EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax #else EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax @@ -1352,7 +1352,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) compiledOfs -= 3; vm->instructionPointers[instruction - 1] = compiledOfs; MASK_REG("E0", vm->dataMask); // and eax, 0x12345678 -#if idx64 +#if defined(idx64) EmitRexString(0x41, "8B 04 01"); // mov eax, dword ptr [r9 + eax] #else EmitString("8B 80"); // mov eax, dword ptr [eax + 0x1234567] @@ -1363,7 +1363,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) } EmitMovEAXStack(vm, vm->dataMask); -#if idx64 +#if defined(idx64) EmitRexString(0x41, "8B 04 01"); // mov eax, dword ptr [r9 + eax] #else EmitString("8B 80"); // mov eax, dword ptr [eax + 0x12345678] @@ -1373,7 +1373,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) break; case OP_LOAD2: EmitMovEAXStack(vm, vm->dataMask); -#if idx64 +#if defined(idx64) EmitRexString(0x41, "0F B7 04 01"); // movzx eax, word ptr [r9 + eax] #else EmitString("0F B7 80"); // movzx eax, word ptr [eax + 0x12345678] @@ -1383,7 +1383,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) break; case OP_LOAD1: EmitMovEAXStack(vm, vm->dataMask); -#if idx64 +#if defined(idx64) EmitRexString(0x41, "0F B6 04 01"); // movzx eax, byte ptr [r9 + eax] #else EmitString("0F B6 80"); // movzx eax, byte ptr [eax + 0x12345678] @@ -1395,7 +1395,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) EmitMovEAXStack(vm, 0); EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4] MASK_REG("E2", vm->dataMask & ~3); // and edx, 0x12345678 -#if idx64 +#if defined(idx64) EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax #else EmitString("89 82"); // mov dword ptr [edx + 0x12345678], eax @@ -1407,7 +1407,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) EmitMovEAXStack(vm, 0); EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4] MASK_REG("E2", vm->dataMask & ~1); // and edx, 0x12345678 -#if idx64 +#if defined(idx64) Emit1(0x66); // mov word ptr [r9 + edx], eax EmitRexString(0x41, "89 04 11"); #else @@ -1420,7 +1420,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) EmitMovEAXStack(vm, 0); EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4] MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 -#if idx64 +#if defined(idx64) EmitRexString(0x41, "88 04 11"); // mov byte ptr [r9 + edx], eax #else EmitString("88 82"); // mov byte ptr [edx + 0x12345678], eax @@ -1636,7 +1636,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) EmitString("8B 44 9F 04"); // mov eax, dword ptr 4[edi + ebx * 4] EmitString("81 F8"); // cmp eax, vm->instructionCount Emit4(vm->instructionCount); -#if idx64 +#if defined(idx64) EmitString("73 04"); // jae +4 EmitRexString(0x49, "FF 24 C0"); // jmp qword ptr [r8 + eax * 8] #else @@ -1719,7 +1719,7 @@ This function is called directly by the generated code ============== */ -#if defined(_MSC_VER) && idx64 +#if defined(_MSC_VER) && defined(idx64) extern "C" uint8_t qvmcall64(int *programStack, int *opStack, intptr_t *instructionPointers, byte *dataBase); #endif @@ -1759,7 +1759,7 @@ int VM_CallCompiled(vm_t *vm, int *args) opStackOfs = 0; #ifdef _MSC_VER - #if idx64 + #if defined(idx64) opStackOfs = qvmcall64(&programStack, opStack, vm->instructionPointers, vm->dataBase); #else __asm @@ -1779,7 +1779,7 @@ int VM_CallCompiled(vm_t *vm, int *args) popad } #endif -#elif idx64 +#elif defined(idx64) __asm__ volatile( "movq %5, %%rax\n" "movq %3, %%r8\n" diff --git a/shared/sys/sys_public.h b/shared/sys/sys_public.h index bad8218531..8378d79f3c 100644 --- a/shared/sys/sys_public.h +++ b/shared/sys/sys_public.h @@ -156,6 +156,8 @@ qboolean Sys_LowPhysicalMemory(); void Sys_SetProcessorAffinity( void ); +int Sys_PID( void ); + typedef enum graphicsApi_e { GRAPHICS_API_GENERIC, diff --git a/shared/sys/sys_unix.cpp b/shared/sys/sys_unix.cpp index 96f5b7aab3..04a579b972 100644 --- a/shared/sys/sys_unix.cpp +++ b/shared/sys/sys_unix.cpp @@ -62,6 +62,11 @@ void Sys_PlatformExit( void ) { } +int Sys_PID( void ) +{ + return getpid( ); +} + /* ================ Sys_Milliseconds From f57ce90d61567e84f109f9b4174ee6a4065b5284 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Wed, 27 Sep 2023 01:15:59 +0200 Subject: [PATCH 03/28] Add QVM loading. Add vm_game, vm_cgame and vm_ui cvars to set the prefered vm mode: - 0: native - 1: interpret (qvm) - 2: compile (qvm) Can load QVMs on 64 bit, but it's not stable, yet. Loading the ui.qvm shows the menu, but crashes when entering player customization. Loading jampgame.qvm crashes when loading a map. These crashes are most likely related to the QVM being 32 bit and addresses getting passed in and out that don't fit. Also shared structs may be offset. --- codemp/qcommon/vm.cpp | 227 +++++++++++++++++++++++++++++++++++++- codemp/qcommon/vm_local.h | 10 ++ 2 files changed, 232 insertions(+), 5 deletions(-) diff --git a/codemp/qcommon/vm.cpp b/codemp/qcommon/vm.cpp index 13765ce8c5..63eb043a8a 100644 --- a/codemp/qcommon/vm.cpp +++ b/codemp/qcommon/vm.cpp @@ -42,6 +42,8 @@ const char *vmStrs[MAX_VM] = { "UIVM", }; +cvar_t *vmModeCvar[MAX_VM]; + // VM slots are automatically allocated by VM_Create, and freed by VM_Free // The VM table should never be directly accessed from other files. // Example usage: @@ -62,6 +64,13 @@ void VM_Init( void ) { vm_legacy = Cvar_Get( "vm_legacy", "0", 0 ); #endif + vmModeCvar[VM_CGAME] = Cvar_Get( "vm_cgame", "2", CVAR_ARCHIVE ); + vmModeCvar[VM_GAME] = Cvar_Get( "vm_game", "2", CVAR_ARCHIVE ); + vmModeCvar[VM_UI] = Cvar_Get( "vm_ui", "2", CVAR_ARCHIVE ); + + Cmd_AddCommand ("vmprofile", VM_VmProfile_f ); + Cmd_AddCommand ("vminfo", VM_VmInfo_f ); + memset( vmTable, 0, sizeof(vmTable) ); } @@ -112,8 +121,117 @@ vm_t *VM_Restart( vm_t *vm ) { return VM_Create( saved.slot ); } + +/* +================= +VM_LoadQVM + +Load a .qvm file +================= +*/ +vmHeader_t *VM_LoadQVM( vm_t *vm, qboolean alloc, qboolean freeVM ) +{ + int dataLength; + int i; + char filename[MAX_QPATH]; + union { + vmHeader_t *h; + void *v; + } header; + + // load the image + Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name ); + Com_Printf( "Loading vm file %s...\n", filename ); + + FS_ReadFile(filename, &header.v); + + if ( !header.h ) { + Com_Printf( "Failed.\n" ); + if ( freeVM ) VM_Free( vm ); + + Com_Printf(S_COLOR_YELLOW "Warning: Couldn't open VM file %s\n", filename); + + return NULL; + } + + // show where the qvm was loaded from + //FS_Which(filename, vm->searchPath); + + if( LittleLong( header.h->vmMagic ) == VM_MAGIC ) { + // byte swap the header + // sizeof( vmHeader_t ) - sizeof( int ) is the 1.32b vm header size + for ( size_t i = 0 ; i < ( sizeof( vmHeader_t ) - sizeof( int ) ) / 4 ; i++ ) { + ((int *)header.h)[i] = LittleLong( ((int *)header.h)[i] ); + } + + // validate + if ( header.h->bssLength < 0 + || header.h->dataLength < 0 + || header.h->litLength < 0 + || header.h->codeLength <= 0 ) + { + if ( freeVM ) VM_Free(vm); + FS_FreeFile(header.v); + + Com_Printf(S_COLOR_YELLOW "Warning: %s has bad header\n", filename); + return NULL; + } + } else { + if ( freeVM ) VM_Free( vm ); + FS_FreeFile(header.v); + + Com_Printf(S_COLOR_YELLOW "Warning: %s does not have a recognisable " + "magic number in its header\n", filename); + return NULL; + } + + // round up to next power of 2 so all data operations can + // be mask protected + dataLength = header.h->dataLength + header.h->litLength + + header.h->bssLength; + for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) { + } + dataLength = 1 << i; + + if(alloc) + { + // allocate zero filled space for initialized and uninitialized data + vm->dataBase = (byte *)Hunk_Alloc(dataLength, h_high); + vm->dataMask = dataLength - 1; + } + else + { + // clear the data, but make sure we're not clearing more than allocated + if(vm->dataMask + 1 != dataLength) + { + if ( freeVM ) VM_Free(vm); + FS_FreeFile(header.v); + + Com_Printf(S_COLOR_YELLOW "Warning: Data region size of %s not matching after " + "VM_Restart()\n", filename); + return NULL; + } + + Com_Memset(vm->dataBase, 0, dataLength); + } + + // copy the intialized data + Com_Memcpy( vm->dataBase, (byte *)header.h + header.h->dataOffset, + header.h->dataLength + header.h->litLength ); + + // byte swap the longs + for ( i = 0 ; i < header.h->dataLength ; i += 4 ) { + *(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) ); + } + + return header.h; +} + vm_t *VM_CreateLegacy( vmSlots_t vmSlot, intptr_t( *systemCalls )(intptr_t *) ) { + vmHeader_t *header; vm_t *vm = NULL; + int remaining = Hunk_MemoryRemaining(); + int interpret = vmModeCvar[vmSlot]->integer; if ( !systemCalls ) { Com_Error( ERR_FATAL, "VM_CreateLegacy: bad parms" ); @@ -134,6 +252,49 @@ vm_t *VM_CreateLegacy( vmSlots_t vmSlot, intptr_t( *systemCalls )(intptr_t *) ) Q_strncpyz( vm->name, vmNames[vmSlot], sizeof(vm->name) ); vm->legacy.syscall = systemCalls; + // QVM + if ( interpret != VMI_NATIVE && (header = VM_LoadQVM(vm, qtrue, qfalse)) ) { + // allocate space for the jump targets, which will be filled in by the compile/prep functions + vm->instructionCount = header->instructionCount; + vm->instructionPointers = (intptr_t *)Hunk_Alloc(vm->instructionCount * sizeof(*vm->instructionPointers), h_high); + + // copy or compile the instructions + vm->codeLength = header->codeLength; + + vm->compiled = qfalse; + + #ifdef NO_VM_COMPILED + if(interpret >= VMI_COMPILED) { + Com_Printf("Architecture doesn't have a bytecode compiler, using interpreter\n"); + interpret = VMI_BYTECODE; + } + #else + if(interpret != VMI_BYTECODE) + { + vm->compiled = qtrue; + VM_Compile( vm, header ); + } + #endif + // VM_Compile may have reset vm->compiled if compilation failed + if (!vm->compiled) + { + VM_PrepareInterpreter( vm, header ); + } + + // free the original file + FS_FreeFile( header ); + + // load the map file + VM_LoadSymbols( vm ); + + // the stack is implicitly at the end of the image + vm->programStack = vm->dataMask + 1; + vm->stackBottom = vm->programStack - PROGRAM_STACK_SIZE; + + Com_Printf("%s loaded in %d bytes on the hunk\n", vmNames[vmSlot], remaining - Hunk_MemoryRemaining()); + return vm; + } + // find the legacy syscall api FS_FindPureDLL( vm->name ); vm->dllHandle = Sys_LoadLegacyGameDll( vm->name, &vm->legacy.main, VM_DllSyscall ); @@ -164,6 +325,11 @@ vm_t *VM_Create( vmSlots_t vmSlot ) { if ( vmTable[vmSlot] ) return vmTable[vmSlot]; + if ( vmModeCvar[vmSlot]->integer != VMI_NATIVE ) { + // If the module shouldn't be a native library return and have the caller try the legacy system, because QVMs are part of that + return NULL; + } + // find a free vm vmTable[vmSlot] = (vm_t *)Z_Malloc( sizeof(*vm), TAG_VM, qtrue ); vm = vmTable[vmSlot]; @@ -262,7 +428,11 @@ void *VM_ArgPtr( intptr_t intValue ) { if ( !currentVM ) return NULL; - return (void *)intValue; + if ( currentVM->dllHandle ) { + return (void *)intValue; + } else { + return (void *)(currentVM->dataBase + (intValue & currentVM->dataMask)); + } } void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ) { @@ -290,8 +460,55 @@ intptr_t QDECL VM_Call( vm_t *vm, int callnum, intptr_t arg0, intptr_t arg1, int VMSwap v( vm ); - return vm->legacy.main( callnum, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, - arg9, arg10, arg11 ); + if ( vm->dllHandle ) { + return vm->legacy.main( callnum, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, + arg9, arg10, arg11 ); + } else { + size_t i; + intptr_t r; + if ( vm_debugLevel ) { + Com_Printf( "VM_Call( %d )\n", callnum ); + } + + ++vm->callLevel; + +#if ( id386 || idsparc ) && !defined __clang__ // calling convention doesn't need conversion in some cases +#ifndef NO_VM_COMPILED + if ( vm->compiled ) + r = VM_CallCompiled( vm, (int*)&callnum ); + else +#endif + r = VM_CallInterpreted( vm, (int*)&callnum ); +#else + struct { + int callnum; + int args[MAX_VMMAIN_ARGS-1]; + } a; + + a.callnum = callnum; + a.args[0] = arg0; + a.args[1] = arg1; + a.args[2] = arg2; + a.args[3] = arg3; + a.args[4] = arg4; + a.args[5] = arg5; + a.args[6] = arg6; + a.args[7] = arg7; + a.args[8] = arg8; + a.args[9] = arg9; + a.args[10] = arg10; + a.args[11] = arg11; +#ifndef NO_VM_COMPILED + if ( vm->compiled ) + r = VM_CallCompiled( vm, &a.callnum ); + else +#endif + r = VM_CallInterpreted( vm, &a.callnum ); +#endif + --vm->callLevel; + + return r; + } } // QVM functions @@ -810,8 +1027,8 @@ void VM_VmInfo_f( void ) { Com_Printf( "Registered virtual machines:\n" ); for ( i = 0 ; i < MAX_VM ; i++ ) { vm = vmTable[i]; - if ( !vm->name[0] ) { - break; + if ( !vm || !vm->name[0] ) { + continue; } Com_Printf( "%s : ", vm->name ); if ( vm->dllHandle ) { diff --git a/codemp/qcommon/vm_local.h b/codemp/qcommon/vm_local.h index d79db101d0..98fc646825 100644 --- a/codemp/qcommon/vm_local.h +++ b/codemp/qcommon/vm_local.h @@ -152,6 +152,12 @@ typedef struct { int bssLength; // zero filled memory appended to datalength } vmHeader_t; +typedef enum { + VMI_NATIVE, + VMI_BYTECODE, + VMI_COMPILED +} vmInterpret_t; + extern vm_t *currentVM; extern int vm_debugLevel; @@ -172,3 +178,7 @@ void VM_LogSyscalls( int *args ); void VM_BlockCopy(unsigned int dest, unsigned int src, size_t n); void VM_Debug( int level ); + +void VM_LoadSymbols( vm_t *vm ); +void VM_VmInfo_f( void ); +void VM_VmProfile_f( void ); From 54b4990fe395053707d495ab188ff84eefce426a Mon Sep 17 00:00:00 2001 From: Daggolin Date: Sat, 30 Sep 2023 18:45:41 +0200 Subject: [PATCH 04/28] Fix return values in nested system calls from QVMs This issue occured when the game module called the syscall G_NAV_GETNEARESTNODE. The module would receive a different value than the engine intended to return and that caused issues when the incorrect return value was used as input for other functions, causing issues and eventually crashes during later syscalls. This did not affect interpreted QVMs. Once pinpointed the source of the issue being DoSyscall a diff with ioq3 provided a fix. This fix comes from ioq3@1ce8ba0cdb8d8f2825177877a16d8525100280dd. --- codemp/qcommon/vm_x86.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/codemp/qcommon/vm_x86.cpp b/codemp/qcommon/vm_x86.cpp index 689b1175d8..3931f6767a 100644 --- a/codemp/qcommon/vm_x86.cpp +++ b/codemp/qcommon/vm_x86.cpp @@ -418,22 +418,23 @@ void DoSyscall(void) if(vm_syscallNum < 0) { - int *data; + int *data, *ret; #if defined(idx64) intptr_t args[MAX_VMSYSCALL_ARGS]; #endif data = (int *) (savedVM->dataBase + vm_programStack + 4); + ret = &vm_opStackBase[vm_opStackOfs + 1 ]; #if defined(idx64) args[0] = ~vm_syscallNum; for(size_t index = 1; index < ARRAY_LEN(args); index++) args[index] = data[index]; - vm_opStackBase[vm_opStackOfs + 1] = savedVM->legacy.syscall(args); + *ret = savedVM->legacy.syscall(args); #else data[0] = ~vm_syscallNum; - vm_opStackBase[vm_opStackOfs + 1] = savedVM->legacy.syscall((intptr_t *) data); + *ret = savedVM->legacy.syscall((intptr_t *) data); #endif } else From 1d3d3c43e492d2dd7719dcf7bd0afb2c2c4448c0 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Sat, 30 Sep 2023 19:15:44 +0200 Subject: [PATCH 05/28] Add support for ui.qvm on 64 bit engine builds. Add g2handles to map g2 instances (64 bit address) to integers that the QVM can handle and adjust legacy uiapi to make use of them for QVMs. Convert glconfig_t in uiapi for QVMs, because the structs don't match. --- codemp/client/cl_uiapi.cpp | 253 +++++++++++++++++++++++++++--------- codemp/qcommon/q_shared.h | 3 +- codemp/rd-common/tr_types.h | 29 +++++ 3 files changed, 223 insertions(+), 62 deletions(-) diff --git a/codemp/client/cl_uiapi.cpp b/codemp/client/cl_uiapi.cpp index 29cf09b06a..a382528c8a 100644 --- a/codemp/client/cl_uiapi.cpp +++ b/codemp/client/cl_uiapi.cpp @@ -29,6 +29,8 @@ along with this program; if not, see . #include "FXExport.h" #include "FxUtil.h" +#include + extern IHeapAllocator *G2VertSpaceClient; extern botlib_export_t *botlib_export; @@ -36,6 +38,62 @@ extern botlib_export_t *botlib_export; static uiExport_t *uie; // ui export table static vm_t *uivm; // ui vm, valid for legacy and new api +typedef std::unordered_map g2HandleToG2_m; + +static g2HandleToG2_m g2Mapping; +static g2handle_t g2NextHandle = (g2handle_t)1; // Start at 1, because 0 has special meaning + +static inline CGhoul2Info_v *CL_UI_G2Map_GetG2FromHandle( g2handleptr_t g2h ) +{ // Returns the pointer to the g2 object if the handle is valid + // Native libraries should not use the pointer, but in theory they could use + // it. Thus we don't perform any mapping for native libraries to avoid + // issues with custom modules. + if ( uivm->dllHandle ) return (CGhoul2Info_v*)g2h; + + g2handle_t g2handle = (g2handle_t)g2h; + g2HandleToG2_m::iterator ghlIt = g2Mapping.find(g2handle); + + if (ghlIt == g2Mapping.end()) return NULL; + return g2Mapping[g2handle]; +} + +static inline CGhoul2Info_v **CL_UI_G2Map_GetG2PtrFromHandle( g2handleptr_t *g2h ) +{ // Returns a pointer to the g2 object pointer in the map so g2 functions can update the pointer + // Native libraries should not use the pointer, but in theory they could use + // it. Thus we don't perform any mapping for native libraries to avoid + // issues with custom modules. + if ( uivm->dllHandle ) return (CGhoul2Info_v **)g2h; + + g2handle_t g2handle = *((g2handle_t*)g2h); + if ( !g2handle ) + { // Special case: the g2 handle is not valid, yet. Return a pointer to a static temporary pointer. Insertion is handled by calling CL_UI_G2Map_Update after calling the G2API + static CGhoul2Info_v *g2Tmp; + g2Tmp = NULL; + return &g2Tmp; + } + return &g2Mapping[g2handle]; +} + +static void CL_UI_G2Map_Update( g2handleptr_t *g2h, CGhoul2Info_v *g2Ptr ) +{ // Inserts and/or erases to/from the map and updates the handle pointer + if ( uivm->dllHandle ) return; + + g2handle_t *g2handle = (g2handle_t*)g2h; + if ( !*g2handle && g2Ptr ) + { // Got a 0 handle, but a non-0 pointer: add to map and set handle + // Unlikely to happen, but should we ever cycle through the whole integer range start searching for gaps + while ( CL_UI_G2Map_GetG2FromHandle(g2NextHandle) || !g2NextHandle ) g2NextHandle++; + + g2Mapping[g2NextHandle] = g2Ptr; + *g2handle = g2NextHandle++; + } + else if ( *g2h && !g2Ptr ) + { // Got a non-0 handle, but 0 pointer: remove from map and set handle to 0 + g2Mapping.erase( *g2handle ); + *g2handle = 0; + } +} + // // ui vmMain calls // @@ -154,7 +212,42 @@ static void CL_GetClientState( uiClientState_t *state ) { } static void CL_GetGlconfig( glconfig_t *config ) { - *config = cls.glconfig; + if ( uivm->dllHandle ) { + // For native libraries we can just copy the struct over cause they should be identical + *config = cls.glconfig; + } else { + // As the QVM is 32 bit internally the size of our glconfig_t is bigger on 64 bit, because of the 4 string + // pointers. Instead of just assigning the glconfig we have to manually copy it over. + glconfig_qvm_t *glconfig_qvm = (glconfig_qvm_t*)config; + + // FIXME: The QVM can't access the engine pointers so there is no point in passing our pointers in here. + // JK2 used to have the module pass fixed size buffers that could be written to, but here we just have a + // pointer so we force it to 0 for now. In theory we could allocate additional memory as part of the + // QVM memory block when loading the QVM and write the strings there to let the QVM access them, but that + // would require changes to the vm_* code, thus we are sticking with the NULL pointers for now. This + // could still get adjusted later on. + glconfig_qvm->renderer_string = 0; + glconfig_qvm->vendor_string = 0; + glconfig_qvm->version_string = 0; + glconfig_qvm->extensions_string = 0; + + // Assign the rest of the values + glconfig_qvm->maxTextureSize = cls.glconfig.maxTextureSize; + glconfig_qvm->maxActiveTextures = cls.glconfig.maxActiveTextures; + glconfig_qvm->maxTextureFilterAnisotropy = cls.glconfig.maxTextureFilterAnisotropy; + glconfig_qvm->colorBits = cls.glconfig.colorBits; + glconfig_qvm->depthBits = cls.glconfig.depthBits; + glconfig_qvm->stencilBits = cls.glconfig.stencilBits; + glconfig_qvm->deviceSupportsGamma = cls.glconfig.deviceSupportsGamma; + glconfig_qvm->textureCompression = cls.glconfig.textureCompression; + glconfig_qvm->textureEnvAddAvailable = cls.glconfig.textureEnvAddAvailable; + glconfig_qvm->clampToEdgeAvailable = cls.glconfig.clampToEdgeAvailable; + glconfig_qvm->vidWidth = cls.glconfig.vidWidth; + glconfig_qvm->vidHeight = cls.glconfig.vidHeight; + glconfig_qvm->displayFrequency = cls.glconfig.displayFrequency; + glconfig_qvm->isFullscreen = cls.glconfig.isFullscreen; + glconfig_qvm->stereoEnabled = cls.glconfig.stereoEnabled; + } } static void GetClipboardData( char *buf, int buflen ) { @@ -236,6 +329,28 @@ static void CL_R_ShaderNameFromIndex( char *name, int index ) { name[0] = '\0'; } +void CL_R_AddRefEntityToScene( const refEntity_t *refEnt ) { + if ( uivm->dllHandle ) { + // We can directly pass the refEnt for native libraries + re->AddRefEntityToScene( refEnt ); + return; + } + + // The renderer should look up the handle on its own, but as we have to stay compatible with the modular renderer + // system we can't change the renderer for this and have to meet its expectations instead... + + // Get a copy of the refEntity data. For a QVM mod the ghoul2 pointer at the end might contain some additional data, + // because the QVM stores it as 32 bit and if the engine is 64 bit it is going to read slightly out of bounds of the + // struct in the QVM, but CL_UI_G2Map_GetG2FromHandle should take care of that by casting. + refEntity_t refEntCopy = *refEnt; + + // Get the ghoul2 instance from the handle + refEntCopy.ghoul2 = CL_UI_G2Map_GetG2FromHandle( (g2handleptr_t)refEnt->ghoul2 ); + + // Pass our copy + re->AddRefEntityToScene( &refEntCopy ); +} + static void CL_G2API_ListModelSurfaces( void *ghlInfo ) { if ( !ghlInfo ) { return; @@ -257,7 +372,7 @@ static void CL_G2API_SetGhoul2ModelIndexes( void *ghoul2, qhandle_t *modelList, return; } - re->G2API_SetGhoul2ModelIndexes( *((CGhoul2Info_v *)ghoul2), modelList, skinList ); + re->G2API_SetGhoul2ModelIndexes( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelList, skinList ); } static qboolean CL_G2API_HaveWeGhoul2Models( void *ghoul2) { @@ -265,7 +380,7 @@ static qboolean CL_G2API_HaveWeGhoul2Models( void *ghoul2) { return qfalse; } - return re->G2API_HaveWeGhoul2Models( *((CGhoul2Info_v *)ghoul2) ); + return re->G2API_HaveWeGhoul2Models( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)) ); } static qboolean CL_G2API_GetBoltMatrix( void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale ) { @@ -273,7 +388,7 @@ static qboolean CL_G2API_GetBoltMatrix( void *ghoul2, const int modelIndex, cons return qfalse; } - return re->G2API_GetBoltMatrix( *((CGhoul2Info_v *)ghoul2), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); + return re->G2API_GetBoltMatrix( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); } static qboolean CL_G2API_GetBoltMatrix_NoReconstruct( void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale ) { @@ -282,7 +397,7 @@ static qboolean CL_G2API_GetBoltMatrix_NoReconstruct( void *ghoul2, const int mo } re->G2API_BoltMatrixReconstruction( qfalse ); - return re->G2API_GetBoltMatrix( *((CGhoul2Info_v *)ghoul2), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); + return re->G2API_GetBoltMatrix( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); } static qboolean CL_G2API_GetBoltMatrix_NoRecNoRot( void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale ) { @@ -292,7 +407,7 @@ static qboolean CL_G2API_GetBoltMatrix_NoRecNoRot( void *ghoul2, const int model re->G2API_BoltMatrixReconstruction( qfalse ); re->G2API_BoltMatrixSPMethod( qtrue ); - return re->G2API_GetBoltMatrix( *((CGhoul2Info_v *)ghoul2), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); + return re->G2API_GetBoltMatrix( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); } static int CL_G2API_InitGhoul2Model( void **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin, qhandle_t customShader, int modelFlags, int lodBias ) { @@ -303,7 +418,10 @@ static int CL_G2API_InitGhoul2Model( void **ghoul2Ptr, const char *fileName, int #ifdef _FULL_G2_LEAK_CHECKING g_G2AllocServer = 0; #endif - return re->G2API_InitGhoul2Model( (CGhoul2Info_v **)ghoul2Ptr, fileName, modelIndex, customSkin, customShader, modelFlags, lodBias ); + CGhoul2Info_v **g2Ptr = CL_UI_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)ghoul2Ptr ); + int ret = re->G2API_InitGhoul2Model( g2Ptr, fileName, modelIndex, customSkin, customShader, modelFlags, lodBias ); + CL_UI_G2Map_Update( (g2handleptr_t*)ghoul2Ptr, *g2Ptr ); + return ret; } static qboolean CL_G2API_SetSkin( void *ghoul2, int modelIndex, qhandle_t customSkin, qhandle_t renderSkin ) { @@ -311,13 +429,13 @@ static qboolean CL_G2API_SetSkin( void *ghoul2, int modelIndex, qhandle_t custom return qfalse; } - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); return re->G2API_SetSkin( g2, modelIndex, customSkin, renderSkin ); } static void CL_G2API_CollisionDetect( CollisionRecord_t *collRecMap, - void* ghoul2, + void *ghoul2, const vec3_t angles, const vec3_t position, int frameNumber, @@ -335,7 +453,7 @@ static void CL_G2API_CollisionDetect( re->G2API_CollisionDetect( collRecMap, - *((CGhoul2Info_v *)ghoul2), + *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), angles, position, frameNumber, @@ -351,7 +469,7 @@ static void CL_G2API_CollisionDetect( static void CL_G2API_CollisionDetectCache( CollisionRecord_t *collRecMap, - void* ghoul2, + void *ghoul2, const vec3_t angles, const vec3_t position, int frameNumber, @@ -369,7 +487,7 @@ static void CL_G2API_CollisionDetectCache( re->G2API_CollisionDetectCache( collRecMap, - *((CGhoul2Info_v *)ghoul2), + *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), angles, position, frameNumber, @@ -391,7 +509,10 @@ static void CL_G2API_CleanGhoul2Models( void **ghoul2Ptr ) { #ifdef _FULL_G2_LEAK_CHECKING g_G2AllocServer = 0; #endif - re->G2API_CleanGhoul2Models( (CGhoul2Info_v **)ghoul2Ptr ); + + CGhoul2Info_v **g2Ptr = CL_UI_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)ghoul2Ptr ); + re->G2API_CleanGhoul2Models( g2Ptr ); + CL_UI_G2Map_Update( (g2handleptr_t*)ghoul2Ptr, *g2Ptr ); } static qboolean CL_G2API_SetBoneAngles( @@ -412,7 +533,7 @@ static qboolean CL_G2API_SetBoneAngles( } return re->G2API_SetBoneAngles( - *((CGhoul2Info_v *)ghoul2), + *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boneName, angles, @@ -442,7 +563,7 @@ static qboolean CL_G2API_SetBoneAnim( } return re->G2API_SetBoneAnim( - *((CGhoul2Info_v *)ghoul2), + *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boneName, startFrame, @@ -470,7 +591,7 @@ static qboolean CL_G2API_GetBoneAnim( return qfalse; } - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); return re->G2API_GetBoneAnim( g2, modelIndex, @@ -496,7 +617,7 @@ static qboolean CL_G2API_GetBoneFrame( return qfalse; } - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); int iDontCare1 = 0, iDontCare2 = 0, iDontCare3 = 0; float fDontCare1 = 0; @@ -520,7 +641,7 @@ static void CL_G2API_GetGLAName( void *ghoul2, int modelIndex, char *fillBuf ) { return; } - char *tmp = re->G2API_GetGLAName( *((CGhoul2Info_v *)ghoul2), modelIndex ); + char *tmp = re->G2API_GetGLAName( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex ); if ( tmp ) strcpy( fillBuf, tmp ); else @@ -532,7 +653,7 @@ static int CL_G2API_CopyGhoul2Instance( void *g2From, void *g2To, int modelIndex return 0; } - return re->G2API_CopyGhoul2Instance( *((CGhoul2Info_v *)g2From), *((CGhoul2Info_v *)g2To), modelIndex ); + return re->G2API_CopyGhoul2Instance( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)g2From)), *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)g2To)), modelIndex ); } static void CL_G2API_CopySpecificGhoul2Model( void *g2From, int modelFrom, void *g2To, int modelTo ) { @@ -540,7 +661,7 @@ static void CL_G2API_CopySpecificGhoul2Model( void *g2From, int modelFrom, void return; } - re->G2API_CopySpecificG2Model( *((CGhoul2Info_v *)g2From), modelFrom, *((CGhoul2Info_v *)g2To), modelTo ); + re->G2API_CopySpecificG2Model( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)g2From)), modelFrom, *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)g2To)), modelTo ); } static void CL_G2API_DuplicateGhoul2Instance( void *g2From, void **g2To ) { @@ -551,7 +672,10 @@ static void CL_G2API_DuplicateGhoul2Instance( void *g2From, void **g2To ) { #ifdef _FULL_G2_LEAK_CHECKING g_G2AllocServer = 0; #endif - re->G2API_DuplicateGhoul2Instance( *((CGhoul2Info_v *)g2From), (CGhoul2Info_v **)g2To ); + + CGhoul2Info_v **g2ToPtr = CL_UI_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)g2To ); + re->G2API_DuplicateGhoul2Instance( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)g2From)), g2ToPtr ); + CL_UI_G2Map_Update( (g2handleptr_t*)g2To, *g2ToPtr ); } static qboolean CL_G2API_HasGhoul2ModelOnIndex( void *ghlInfo, int modelIndex ) { @@ -559,7 +683,10 @@ static qboolean CL_G2API_HasGhoul2ModelOnIndex( void *ghlInfo, int modelIndex ) return qfalse; } - return re->G2API_HasGhoul2ModelOnIndex( (CGhoul2Info_v **)ghlInfo, modelIndex ); + CGhoul2Info_v **g2Ptr = CL_UI_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)ghlInfo ); + qboolean ret = re->G2API_HasGhoul2ModelOnIndex( g2Ptr, modelIndex ); + CL_UI_G2Map_Update( (g2handleptr_t*)ghlInfo, *g2Ptr ); + return ret; } static qboolean CL_G2API_RemoveGhoul2Model( void *ghlInfo, int modelIndex ) { @@ -570,7 +697,11 @@ static qboolean CL_G2API_RemoveGhoul2Model( void *ghlInfo, int modelIndex ) { #ifdef _FULL_G2_LEAK_CHECKING g_G2AllocServer = 0; #endif - return re->G2API_RemoveGhoul2Model( (CGhoul2Info_v **)ghlInfo, modelIndex ); + + CGhoul2Info_v **g2Ptr = CL_UI_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)ghlInfo ); + qboolean ret = re->G2API_RemoveGhoul2Model( g2Ptr, modelIndex ); + CL_UI_G2Map_Update( (g2handleptr_t*)ghlInfo, *g2Ptr ); + return ret; } static int CL_G2API_AddBolt( void *ghoul2, int modelIndex, const char *boneName ) { @@ -578,7 +709,7 @@ static int CL_G2API_AddBolt( void *ghoul2, int modelIndex, const char *boneName return -1; } - return re->G2API_AddBolt( *((CGhoul2Info_v *)ghoul2), modelIndex, boneName ); + return re->G2API_AddBolt( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boneName ); } static void CL_G2API_SetBoltInfo( void *ghoul2, int modelIndex, int boltInfo ) { @@ -586,7 +717,7 @@ static void CL_G2API_SetBoltInfo( void *ghoul2, int modelIndex, int boltInfo ) { return; } - re->G2API_SetBoltInfo( *((CGhoul2Info_v *)ghoul2), modelIndex, boltInfo ); + re->G2API_SetBoltInfo( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boltInfo ); } static qboolean CL_G2API_SetRootSurface( void *ghoul2, const int modelIndex, const char *surfaceName ) { @@ -594,7 +725,7 @@ static qboolean CL_G2API_SetRootSurface( void *ghoul2, const int modelIndex, con return qfalse; } - return re->G2API_SetRootSurface( *((CGhoul2Info_v *)ghoul2), modelIndex, surfaceName ); + return re->G2API_SetRootSurface( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, surfaceName ); } static qboolean CL_G2API_SetSurfaceOnOff( void *ghoul2, const char *surfaceName, const int flags ) { @@ -602,7 +733,7 @@ static qboolean CL_G2API_SetSurfaceOnOff( void *ghoul2, const char *surfaceName, return qfalse; } - return re->G2API_SetSurfaceOnOff( *((CGhoul2Info_v *)ghoul2), surfaceName, flags ); + return re->G2API_SetSurfaceOnOff( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), surfaceName, flags ); } static qboolean CL_G2API_SetNewOrigin( void *ghoul2, const int boltIndex ) { @@ -610,7 +741,7 @@ static qboolean CL_G2API_SetNewOrigin( void *ghoul2, const int boltIndex ) { return qfalse; } - return re->G2API_SetNewOrigin( *((CGhoul2Info_v *)ghoul2), boltIndex ); + return re->G2API_SetNewOrigin( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), boltIndex ); } static int CL_G2API_GetTime( void ) { @@ -629,7 +760,7 @@ static void CL_G2API_SetRagDoll( void *ghoul2, sharedRagDollParams_t *params ) { CRagDollParams rdParams; if ( !params ) { - re->G2API_ResetRagDoll( *((CGhoul2Info_v *)ghoul2) ); + re->G2API_ResetRagDoll( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)) ); return; } @@ -652,7 +783,7 @@ static void CL_G2API_SetRagDoll( void *ghoul2, sharedRagDollParams_t *params ) { rdParams.RagPhase = (CRagDollParams::ERagPhase)params->RagPhase; rdParams.effectorsToTurnOff = (CRagDollParams::ERagEffector)params->effectorsToTurnOff; - re->G2API_SetRagDoll( *((CGhoul2Info_v *)ghoul2), &rdParams ); + re->G2API_SetRagDoll( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), &rdParams ); } static void CL_G2API_AnimateG2Models( void *ghoul2, int time, sharedRagDollUpdateParams_t *params ) { @@ -673,7 +804,7 @@ static void CL_G2API_AnimateG2Models( void *ghoul2, int time, sharedRagDollUpdat rduParams.me = params->me; rduParams.settleFrame = params->settleFrame; - re->G2API_AnimateG2ModelsRag( *((CGhoul2Info_v *)ghoul2), time, &rduParams ); + re->G2API_AnimateG2ModelsRag( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), time, &rduParams ); } static qboolean CL_G2API_SetBoneIKState( void *ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params ) { @@ -681,7 +812,7 @@ static qboolean CL_G2API_SetBoneIKState( void *ghoul2, int time, const char *bon return qfalse; } - return re->G2API_SetBoneIKState( *((CGhoul2Info_v *)ghoul2), time, boneName, ikState, params ); + return re->G2API_SetBoneIKState( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), time, boneName, ikState, params ); } static qboolean CL_G2API_IKMove( void *ghoul2, int time, sharedIKMoveParams_t *params ) { @@ -689,7 +820,7 @@ static qboolean CL_G2API_IKMove( void *ghoul2, int time, sharedIKMoveParams_t *p return qfalse; } - return re->G2API_IKMove( *((CGhoul2Info_v *)ghoul2), time, params ); + return re->G2API_IKMove( *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), time, params ); } static void CL_G2API_GetSurfaceName( void *ghoul2, int surfNumber, int modelIndex, char *fillBuf ) { @@ -697,7 +828,7 @@ static void CL_G2API_GetSurfaceName( void *ghoul2, int surfNumber, int modelInde return; } - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); char *tmp = re->G2API_GetSurfaceName( g2, modelIndex, surfNumber ); strcpy( fillBuf, tmp ); } @@ -707,8 +838,8 @@ static qboolean CL_G2API_AttachG2Model( void *ghoul2From, int modelIndexFrom, vo return qfalse; } - CGhoul2Info_v *g2From = ((CGhoul2Info_v *)ghoul2From); - CGhoul2Info_v *g2To = ((CGhoul2Info_v *)ghoul2To); + CGhoul2Info_v *g2From = (CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2From)); + CGhoul2Info_v *g2To = (CL_UI_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2To)); return re->G2API_AttachG2Model(*g2From, modelIndexFrom, *g2To, toBoltIndex, toModel); } @@ -888,7 +1019,7 @@ intptr_t CL_UISystemCalls( intptr_t *args ) { return 0; case UI_R_ADDREFENTITYTOSCENE: - re->AddRefEntityToScene( (const refEntity_t *)VMA(1) ); + CL_R_AddRefEntityToScene( (refEntity_t*)VMA(1) ); return 0; case UI_R_ADDPOLYTOSCENE: @@ -1144,20 +1275,20 @@ intptr_t CL_UISystemCalls( intptr_t *args ) { return 0; case UI_G2_HAVEWEGHOULMODELS: - return CL_G2API_HaveWeGhoul2Models( VMA(1) ); + return CL_G2API_HaveWeGhoul2Models( (void*)args[1] ); case UI_G2_SETMODELS: - CL_G2API_SetGhoul2ModelIndexes( VMA(1),(qhandle_t *)VMA(2),(qhandle_t *)VMA(3)); + CL_G2API_SetGhoul2ModelIndexes( (void*)args[1],(qhandle_t *)VMA(2),(qhandle_t *)VMA(3)); return 0; case UI_G2_GETBOLT: - return CL_G2API_GetBoltMatrix(VMA(1), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + return CL_G2API_GetBoltMatrix((void*)args[1], args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); case UI_G2_GETBOLT_NOREC: - return CL_G2API_GetBoltMatrix_NoReconstruct(VMA(1), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + return CL_G2API_GetBoltMatrix_NoReconstruct((void*)args[1], args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); case UI_G2_GETBOLT_NOREC_NOROT: - return CL_G2API_GetBoltMatrix_NoRecNoRot(VMA(1), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + return CL_G2API_GetBoltMatrix_NoRecNoRot((void*)args[1], args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); case UI_G2_INITGHOUL2MODEL: #ifdef _FULL_G2_LEAK_CHECKING @@ -1170,7 +1301,7 @@ intptr_t CL_UISystemCalls( intptr_t *args ) { return 0; //not supported for ui case UI_G2_ANGLEOVERRIDE: - return CL_G2API_SetBoneAngles(VMA(1), args[2], (const char *)VMA(3), (float *)VMA(4), args[5], (const Eorientations) args[6], (const Eorientations) args[7], (const Eorientations) args[8], (qhandle_t *)VMA(9), args[10], args[11] ); + return CL_G2API_SetBoneAngles((void*)args[1], args[2], (const char *)VMA(3), (float *)VMA(4), args[5], (const Eorientations) args[6], (const Eorientations) args[7], (const Eorientations) args[8], (qhandle_t *)VMA(9), args[10], args[11] ); case UI_G2_CLEANMODELS: #ifdef _FULL_G2_LEAK_CHECKING @@ -1180,30 +1311,30 @@ intptr_t CL_UISystemCalls( intptr_t *args ) { return 0; case UI_G2_PLAYANIM: - return CL_G2API_SetBoneAnim(VMA(1), args[2], (const char *)VMA(3), args[4], args[5], args[6], VMF(7), args[8], VMF(9), args[10]); + return CL_G2API_SetBoneAnim((void*)args[1], args[2], (const char *)VMA(3), args[4], args[5], args[6], VMF(7), args[8], VMF(9), args[10]); case UI_G2_GETBONEANIM: - return CL_G2API_GetBoneAnim(VMA(1), (const char*)VMA(2), args[3], (float *)VMA(4), (int *)VMA(5), (int *)VMA(6), (int *)VMA(7), (float *)VMA(8), (int *)VMA(9), args[10]); + return CL_G2API_GetBoneAnim((void*)args[1], (const char*)VMA(2), args[3], (float *)VMA(4), (int *)VMA(5), (int *)VMA(6), (int *)VMA(7), (float *)VMA(8), (int *)VMA(9), args[10]); case UI_G2_GETBONEFRAME: - return CL_G2API_GetBoneFrame(VMA(1), (const char*)VMA(2), args[3], (float *)VMA(4), (int *)VMA(5), args[6]); + return CL_G2API_GetBoneFrame((void*)args[1], (const char*)VMA(2), args[3], (float *)VMA(4), (int *)VMA(5), args[6]); case UI_G2_GETGLANAME: - CL_G2API_GetGLAName( VMA(1), args[2], (char *)VMA(3) ); + CL_G2API_GetGLAName( (void*)args[1], args[2], (char *)VMA(3) ); return 0; case UI_G2_COPYGHOUL2INSTANCE: - return (int)CL_G2API_CopyGhoul2Instance(VMA(1), VMA(2), args[3]); + return (int)CL_G2API_CopyGhoul2Instance((void*)args[1], (void*)args[2], args[3]); case UI_G2_COPYSPECIFICGHOUL2MODEL: - CL_G2API_CopySpecificGhoul2Model(VMA(1), args[2], VMA(3), args[4]); + CL_G2API_CopySpecificGhoul2Model((void*)args[1], args[2], (void*)args[3], args[4]); return 0; case UI_G2_DUPLICATEGHOUL2INSTANCE: #ifdef _FULL_G2_LEAK_CHECKING g_G2AllocServer = 0; #endif - CL_G2API_DuplicateGhoul2Instance(VMA(1), (void **)VMA(2)); + CL_G2API_DuplicateGhoul2Instance((void*)args[1], (void **)VMA(2)); return 0; case UI_G2_HASGHOUL2MODELONINDEX: @@ -1216,20 +1347,20 @@ intptr_t CL_UISystemCalls( intptr_t *args ) { return (int)CL_G2API_RemoveGhoul2Model(VMA(1), args[2]); case UI_G2_ADDBOLT: - return CL_G2API_AddBolt(VMA(1), args[2], (const char *)VMA(3)); + return CL_G2API_AddBolt((void*)args[1], args[2], (const char *)VMA(3)); case UI_G2_SETBOLTON: - CL_G2API_SetBoltInfo(VMA(1), args[2], args[3]); + CL_G2API_SetBoltInfo((void*)args[1], args[2], args[3]); return 0; case UI_G2_SETROOTSURFACE: - return CL_G2API_SetRootSurface(VMA(1), args[2], (const char *)VMA(3)); + return CL_G2API_SetRootSurface((void*)args[1], args[2], (const char *)VMA(3)); case UI_G2_SETSURFACEONOFF: - return CL_G2API_SetSurfaceOnOff(VMA(1), (const char *)VMA(2), args[3]); + return CL_G2API_SetSurfaceOnOff((void*)args[1], (const char *)VMA(2), args[3]); case UI_G2_SETNEWORIGIN: - return CL_G2API_SetNewOrigin(VMA(1), args[2]); + return CL_G2API_SetNewOrigin((void*)args[1], args[2]); case UI_G2_GETTIME: return CL_G2API_GetTime(); @@ -1247,20 +1378,20 @@ intptr_t CL_UISystemCalls( intptr_t *args ) { break; case UI_G2_SETBONEIKSTATE: - return CL_G2API_SetBoneIKState(VMA(1), args[2], (const char *)VMA(3), args[4], (sharedSetBoneIKStateParams_t *)VMA(5)); + return CL_G2API_SetBoneIKState((void*)args[1], args[2], (const char *)VMA(3), args[4], (sharedSetBoneIKStateParams_t *)VMA(5)); case UI_G2_IKMOVE: - return CL_G2API_IKMove(VMA(1), args[2], (sharedIKMoveParams_t *)VMA(3)); + return CL_G2API_IKMove((void*)args[1], args[2], (sharedIKMoveParams_t *)VMA(3)); case UI_G2_GETSURFACENAME: - CL_G2API_GetSurfaceName( (void *)args[1], args[2], args[3], (char *)VMA( 4 ) ); + CL_G2API_GetSurfaceName( (void*)args[1], args[2], args[3], (char *)VMA( 4 ) ); return 0; case UI_G2_SETSKIN: - return CL_G2API_SetSkin(VMA(1), args[2], args[3], args[4]); + return CL_G2API_SetSkin((void*)args[1], args[2], args[3], args[4]); case UI_G2_ATTACHG2MODEL: - return CL_G2API_AttachG2Model(VMA(1), args[2], VMA(3), args[4], args[5]); + return CL_G2API_AttachG2Model((void*)args[1], args[2], (void*)args[3], args[4], args[5]); default: Com_Error( ERR_DROP, "Bad UI system trap: %ld", (long int) args[0] ); @@ -1370,7 +1501,7 @@ void CL_BindUI( void ) { uii.R_AddLightToScene = re->AddLightToScene; uii.R_AddPolysToScene = re->AddPolyToScene; - uii.R_AddRefEntityToScene = re->AddRefEntityToScene; + uii.R_AddRefEntityToScene = CL_R_AddRefEntityToScene; uii.R_ClearScene = re->ClearScene; uii.R_DrawStretchPic = re->DrawStretchPic; uii.R_Font_DrawString = re->Font_DrawString; diff --git a/codemp/qcommon/q_shared.h b/codemp/qcommon/q_shared.h index cea7b5c833..fb7e636bd7 100644 --- a/codemp/qcommon/q_shared.h +++ b/codemp/qcommon/q_shared.h @@ -151,7 +151,8 @@ typedef union fileBuffer_u { byte *b; } fileBuffer_t; -typedef int32_t qhandle_t, thandle_t, fxHandle_t, sfxHandle_t, fileHandle_t, clipHandle_t; +typedef int32_t qhandle_t, thandle_t, fxHandle_t, sfxHandle_t, fileHandle_t, clipHandle_t, g2handle_t; +typedef intptr_t g2handleptr_t; #define NULL_HANDLE ((qhandle_t)0) #define NULL_SOUND ((sfxHandle_t)0) diff --git a/codemp/rd-common/tr_types.h b/codemp/rd-common/tr_types.h index 1a454ce998..b76840c9b9 100644 --- a/codemp/rd-common/tr_types.h +++ b/codemp/rd-common/tr_types.h @@ -402,3 +402,32 @@ typedef struct glconfig_s { qboolean isFullscreen; qboolean stereoEnabled; } glconfig_t; + +// Copy of glconfig_t to use with qvms +typedef struct { + uint32_t renderer_string; + uint32_t vendor_string; + uint32_t version_string; + uint32_t extensions_string; + + int maxTextureSize; // queried from GL + int maxActiveTextures; // multitexture ability + float maxTextureFilterAnisotropy; + + int colorBits, depthBits, stencilBits; + + qboolean deviceSupportsGamma; + textureCompression_t textureCompression; + qboolean textureEnvAddAvailable; + qboolean clampToEdgeAvailable; + + int vidWidth, vidHeight; + + int displayFrequency; + + // synonymous with "does rendering consume the entire screen?", therefore + // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that + // used CDS. + qboolean isFullscreen; + qboolean stereoEnabled; +} glconfig_qvm_t; From ccdc67e3adf45e5de8a138f88619deb1d56ab176 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Sat, 30 Sep 2023 19:49:29 +0200 Subject: [PATCH 06/28] Add extra memory to QVMs on load to use when the module doesn't provide a destination itself and expects to be given a pointer. Make use of QVM extra memory in CL_GetGlconfig for uiapi. --- codemp/client/cl_uiapi.cpp | 21 ++++---- codemp/qcommon/qcommon.h | 3 ++ codemp/qcommon/vm.cpp | 102 ++++++++++++++++++++++++++++++++++++- codemp/qcommon/vm_local.h | 9 ++++ 4 files changed, 123 insertions(+), 12 deletions(-) diff --git a/codemp/client/cl_uiapi.cpp b/codemp/client/cl_uiapi.cpp index a382528c8a..ac0ebcc3a9 100644 --- a/codemp/client/cl_uiapi.cpp +++ b/codemp/client/cl_uiapi.cpp @@ -28,6 +28,7 @@ along with this program; if not, see . #include "snd_ambient.h" #include "FXExport.h" #include "FxUtil.h" +#include "qcommon/vm_local.h" #include @@ -220,16 +221,16 @@ static void CL_GetGlconfig( glconfig_t *config ) { // pointers. Instead of just assigning the glconfig we have to manually copy it over. glconfig_qvm_t *glconfig_qvm = (glconfig_qvm_t*)config; - // FIXME: The QVM can't access the engine pointers so there is no point in passing our pointers in here. - // JK2 used to have the module pass fixed size buffers that could be written to, but here we just have a - // pointer so we force it to 0 for now. In theory we could allocate additional memory as part of the - // QVM memory block when loading the QVM and write the strings there to let the QVM access them, but that - // would require changes to the vm_* code, thus we are sticking with the NULL pointers for now. This - // could still get adjusted later on. - glconfig_qvm->renderer_string = 0; - glconfig_qvm->vendor_string = 0; - glconfig_qvm->version_string = 0; - glconfig_qvm->extensions_string = 0; + // We now reserve additional memory when loading the QVM. The QVM shouldn't try to access it on its own, but it + // is valid inside the QVM and if we set a pointer to it the QVM should be able to access it. Thus we now claim + // some of this extra memory in QVM-scope to write our glconfig strings in there and tell the QVM where to find + // them. + // FIXME: It would probably be a good idea to keep a reference to this extra memory ourselves in case a module + // decides to call this functions multiple times. Currently we would claim additional memory each time. + glconfig_qvm->renderer_string = VM_PtrToOffset( uivm, VM_ExtraMemory_ClaimString( uivm, cls.glconfig.renderer_string ) ); + glconfig_qvm->vendor_string = VM_PtrToOffset( uivm, VM_ExtraMemory_ClaimString( uivm, cls.glconfig.vendor_string ) ); + glconfig_qvm->version_string = VM_PtrToOffset( uivm, VM_ExtraMemory_ClaimString( uivm, cls.glconfig.version_string ) ); + glconfig_qvm->extensions_string = VM_PtrToOffset( uivm, VM_ExtraMemory_ClaimString( uivm, cls.glconfig.extensions_string ) ); // Assign the rest of the values glconfig_qvm->maxTextureSize = cls.glconfig.maxTextureSize; diff --git a/codemp/qcommon/qcommon.h b/codemp/qcommon/qcommon.h index 28a0a94470..97374a1f52 100644 --- a/codemp/qcommon/qcommon.h +++ b/codemp/qcommon/qcommon.h @@ -326,6 +326,9 @@ struct vm_s { byte *jumpTableTargets; int numJumpTableTargets; + + int extraMemOffset; + int extraMemUse; }; extern vm_t *currentVM; diff --git a/codemp/qcommon/vm.cpp b/codemp/qcommon/vm.cpp index 63eb043a8a..080ac9592a 100644 --- a/codemp/qcommon/vm.cpp +++ b/codemp/qcommon/vm.cpp @@ -189,6 +189,12 @@ vmHeader_t *VM_LoadQVM( vm_t *vm, qboolean alloc, qboolean freeVM ) // be mask protected dataLength = header.h->dataLength + header.h->litLength + header.h->bssLength; + + // Allocate more memory to have room for additional data to be added within the QVM's scope + vm->extraMemOffset = dataLength - PROGRAM_STACK_SIZE; + vm->extraMemUse = 0; + dataLength += QVM_EXTRA_MEMORY_AMOUNT; + for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) { } dataLength = 1 << i; @@ -630,7 +636,7 @@ const char *VM_SymbolForCompiledPointer( void *code ) { for ( int i = 0; i < MAX_VM; i++ ) { vm_t *vm = vmTable[i]; - if ( vm->compiled ) { + if ( vm && vm->compiled ) { if ( vm->codeBase <= code && code < vm->codeBase + vm->codeLength ) { return VM_ValueToSymbol( vm, (byte *)code - vm->codeBase ); } @@ -1088,4 +1094,96 @@ void VM_BlockCopy(unsigned int dest, unsigned int src, size_t n) } Com_Memcpy(currentVM->dataBase + dest, currentVM->dataBase + src, n); -} \ No newline at end of file +} + +/* +================ + VM_ExtraMemory +---------------- +For QVMs we allocate additional memory when loading the QVM to write data inside +there that the module expects to be given as pointers. Ideally the module +would pass a pointer to its own memory for the engine to fill, but JKA does this +different for some subsystems, because JKA shipped with dlls rather than QVMs. +To stay compatible with vanilla code expectations in the QVM we need to copy our +data into QVM memory for the QVM to have access to it. +================ +*/ +void VM_ExtraMemory_Clear( vm_t *vm ) +{ + // Not for dlls + if ( vm->dllHandle ) return; + vm->extraMemUse = 0; +} + +void *VM_ExtraMemory_Claim( vm_t *vm, int amount ) +{ + int retBlock; + + // Not for dlls + if ( vm->dllHandle ) return NULL; + + // Pad the amount + amount = PAD(amount, sizeof(void*) ); + + // Make sure it fits + if ( vm->extraMemOffset + vm->extraMemUse + amount >= vm->dataMask - PROGRAM_STACK_SIZE ) { + //Com_Error( ERR_FATAL, "VM_ExtraMemory_Claim: failed to claim %i bytes\n", amount ); + Com_Printf( "VM_ExtraMemory_Claim: failed to claim %i bytes\n", amount ); + return NULL; + } + + // Remember where we are now and increate the use counter + retBlock = vm->extraMemOffset + vm->extraMemUse; + vm->extraMemUse += amount; + + // Return address that we can write to + return (void *)(currentVM->dataBase + (retBlock & currentVM->dataMask)); +} + +void VM_ExtraMemory_Release( vm_t *vm, int amount ) +{ // NOTE: Must be in reverse order of claim + // Not for dlls + if ( vm->dllHandle ) return; + + // Pad the amount + amount = PAD(amount, sizeof(void*) ); + + // Make sure it fits + if ( vm->extraMemUse - amount < 0 ) { + Com_Error( ERR_FATAL, "VM_ExtraMemory_Release: failed to release %i bytes, because a lower amount had been claimed\n", amount ); + } + + // Subtract the amount + vm->extraMemUse -= amount; +} + +void *VM_ExtraMemory_ClaimData( vm_t *vm, const void *data, uint32_t size ) +{ + if ( !data ) return NULL; + void *dst = VM_ExtraMemory_Claim( vm, size ); + if ( dst ) memcpy( dst, data, size ); + return dst; +} + +char *VM_ExtraMemory_ClaimString( vm_t *vm, const char *inputString ) +{ + if ( !inputString ) return NULL; + int length = strlen( inputString ) + 1; + return (char*)VM_ExtraMemory_ClaimData( vm, inputString, length ); +} + +size_t VM_PtrToOffset( vm_t *vm, void *ptr ) +{ + // Dlls are not masked + if ( vm->dllHandle ) return (size_t)ptr; + + // Return 0 if it's a NULL pointer. This allows calls like VM_PtrToOffset( vm, VM_ExtraMemory_ClaimData(...) ) + if ( !ptr ) return 0; + + // Make sure it's within the module memory + if ( ptr < vm->dataBase || ptr > vm->dataBase + vm->dataMask ) { + Com_Error( ERR_FATAL, "VM_PtrToOffset: tried to get offset for non-vm pointer\n" ); + } + + return (byte*)ptr - vm->dataBase; +} diff --git a/codemp/qcommon/vm_local.h b/codemp/qcommon/vm_local.h index 98fc646825..e1e5abd404 100644 --- a/codemp/qcommon/vm_local.h +++ b/codemp/qcommon/vm_local.h @@ -182,3 +182,12 @@ void VM_Debug( int level ); void VM_LoadSymbols( vm_t *vm ); void VM_VmInfo_f( void ); void VM_VmProfile_f( void ); + +#define QVM_EXTRA_MEMORY_AMOUNT 8192 + +void VM_ExtraMemory_Clear( vm_t *vm ); +void *VM_ExtraMemory_Claim( vm_t *vm, int amount ); +void VM_ExtraMemory_Release( vm_t *vm, int amount ); +void *VM_ExtraMemory_ClaimData( vm_t *vm, const void *data, uint32_t size ); +char *VM_ExtraMemory_ClaimString( vm_t *vm, const char *inputString ); +size_t VM_PtrToOffset( vm_t *vm, void *ptr ); From 9002d2869c987636167e155b1fd0681c1a84738b Mon Sep 17 00:00:00 2001 From: Daggolin Date: Sat, 30 Sep 2023 20:24:34 +0200 Subject: [PATCH 07/28] Adjust the gameapi in a similar way to the uiapi for better QVM compatibility. Still missing conversion for structs like sharedEntity_t. --- codemp/server/server.h | 4 + codemp/server/sv_gameapi.cpp | 325 +++++++++++++++++++++++++---------- codemp/server/sv_world.cpp | 6 +- 3 files changed, 244 insertions(+), 91 deletions(-) diff --git a/codemp/server/server.h b/codemp/server/server.h index 6a14f7d78d..eaed8826e2 100644 --- a/codemp/server/server.h +++ b/codemp/server/server.h @@ -385,6 +385,10 @@ void SV_InitGameProgs ( void ); void SV_ShutdownGameProgs ( void ); qboolean SV_inPVS (const vec3_t p1, const vec3_t p2); +CGhoul2Info_v *SV_G2Map_GetG2FromHandle( g2handleptr_t g2h ); +CGhoul2Info_v **SV_G2Map_GetG2PtrFromHandle( g2handleptr_t g2h ); +void SV_G2Map_Update( g2handleptr_t *g2h, CGhoul2Info_v *g2Ptr ); + // // sv_bot.c // diff --git a/codemp/server/sv_gameapi.cpp b/codemp/server/sv_gameapi.cpp index d216669abf..6870759dbf 100644 --- a/codemp/server/sv_gameapi.cpp +++ b/codemp/server/sv_gameapi.cpp @@ -30,6 +30,9 @@ along with this program; if not, see . #include "icarus/GameInterface.h" #include "qcommon/timing.h" #include "NPCNav/navigator.h" +#include "qcommon/vm_local.h" + +#include botlib_export_t *botlib_export; @@ -37,6 +40,62 @@ botlib_export_t *botlib_export; static gameExport_t *ge; // game export table static vm_t *gvm; // game vm, valid for legacy and new api +typedef std::unordered_map g2HandleToG2_m; + +static g2HandleToG2_m g2Mapping; +static g2handle_t g2NextHandle = (g2handle_t)1; // Start at 1, because 0 has special meaning + +CGhoul2Info_v *SV_G2Map_GetG2FromHandle( g2handleptr_t g2h ) +{ // Returns the pointer to the g2 object if the handle is valid + // Native libraries should not use the pointer, but in theory they could use + // it. Thus we don't perform any mapping for native libraries to avoid + // issues with custom modules. + if ( gvm->dllHandle ) return (CGhoul2Info_v*)g2h; + + g2handle_t g2handle = (g2handle_t)g2h; + g2HandleToG2_m::iterator ghlIt = g2Mapping.find(g2handle); + + if (ghlIt == g2Mapping.end()) return NULL; + return g2Mapping[g2handle]; +} + + CGhoul2Info_v **SV_G2Map_GetG2PtrFromHandle( g2handleptr_t *g2h ) +{ // Returns a pointer to the g2 object pointer in the map so g2 functions can update the pointer + // Native libraries should not use the pointer, but in theory they could use + // it. Thus we don't perform any mapping for native libraries to avoid + // issues with custom modules. + if ( gvm->dllHandle ) return (CGhoul2Info_v **)g2h; + + g2handle_t g2handle = *((g2handle_t*)g2h); + if ( !g2handle ) + { // Special case: the g2 handle is not valid, yet. Return a pointer to a static temporary pointer. Insertion is handled by calling SV_G2Map_Update after calling the G2API + static CGhoul2Info_v *g2Tmp; + g2Tmp = NULL; + return &g2Tmp; + } + return &g2Mapping[g2handle]; +} + +void SV_G2Map_Update( g2handleptr_t *g2h, CGhoul2Info_v *g2Ptr ) +{ // Inserts and/or erases to/from the map and updates the handle pointer + if ( gvm->dllHandle ) return; + + g2handle_t *g2handle = (g2handle_t*)g2h; + if ( !*g2handle && g2Ptr ) + { // Got a 0 handle, but a non-0 pointer: add to map and set handle + // Unlikely to happen, but should we ever cycle through the whole integer range start searching for gaps + while ( SV_G2Map_GetG2FromHandle(g2NextHandle) || !g2NextHandle ) g2NextHandle++; + + g2Mapping[g2NextHandle] = g2Ptr; + *g2handle = g2NextHandle++; + } + else if ( *g2h && !g2Ptr ) + { // Got a non-0 handle, but 0 pointer: remove from map and set handle to 0 + g2Mapping.erase( *g2handle ); + *g2handle = 0; + } +} + // // game vmMain calls // @@ -319,25 +378,95 @@ int GVM_ICARUS_GetSetIDForString( void ) { return ge->ICARUS_GetSetIDForString(); } +static size_t vec3_size = sizeof(vec3_t); qboolean GVM_NAV_ClearPathToPoint( int entID, vec3_t pmins, vec3_t pmaxs, vec3_t point, int clipmask, int okToHitEnt ) { - if ( gvm->isLegacy ) - return (qboolean)VM_Call( gvm, GAME_NAV_CLEARPATHTOPOINT, entID, reinterpret_cast< intptr_t >( pmins ), reinterpret_cast< intptr_t >( pmaxs ), reinterpret_cast< intptr_t >( point ), clipmask, okToHitEnt ); + if ( gvm->isLegacy ) { + + qboolean ret; + + float *pmins_ptr; + float *pmaxs_ptr; + float *point_ptr; + + if ( !gvm->dllHandle ) { + pmins_ptr = (float*)VM_PtrToOffset( gvm, VM_ExtraMemory_ClaimData(gvm, (float*)pmins, vec3_size) ); + pmaxs_ptr = (float*)VM_PtrToOffset( gvm, VM_ExtraMemory_ClaimData(gvm, (float*)pmaxs, vec3_size) ); + point_ptr = (float*)VM_PtrToOffset( gvm, VM_ExtraMemory_ClaimData(gvm, (float*)point, vec3_size) ); + } else { + pmins_ptr = pmins; + pmaxs_ptr = pmaxs; + point_ptr = point; + } + ret = (qboolean)VM_Call( gvm, GAME_NAV_CLEARPATHTOPOINT, entID, reinterpret_cast< intptr_t >( pmins_ptr ), reinterpret_cast< intptr_t >( pmaxs_ptr ), reinterpret_cast< intptr_t >( point_ptr ), clipmask, okToHitEnt ); + + if ( !gvm->dllHandle ) { + // Memory may only be released in reverse order. + if ( point_ptr ) VM_ExtraMemory_Release( gvm, vec3_size ); + if ( pmaxs_ptr ) VM_ExtraMemory_Release( gvm, vec3_size ); + if ( pmins_ptr ) VM_ExtraMemory_Release( gvm, vec3_size ); + } + + return ret; + } VMSwap v( gvm ); return ge->NAV_ClearPathToPoint( entID, pmins, pmaxs, point, clipmask, okToHitEnt ); } qboolean GVM_NPC_ClearLOS2( int entID, const vec3_t end ) { - if ( gvm->isLegacy ) - return (qboolean)VM_Call( gvm, GAME_NAV_CLEARLOS, entID, reinterpret_cast< intptr_t >( end ) ); + if ( gvm->isLegacy ) { + qboolean ret; + const float *end_ptr; + if ( !gvm->dllHandle ) { + end_ptr = (const float*)VM_ExtraMemory_ClaimData( gvm, (float*)end, vec3_size ); + } else { + end_ptr = (const float*)end; + } + ret = (qboolean)VM_Call( gvm, GAME_NAV_CLEARLOS, entID, reinterpret_cast< intptr_t >( end_ptr ) ); + + if ( !gvm->dllHandle ) { + if ( end_ptr ) VM_ExtraMemory_Release( gvm, vec3_size ); + } + + return ret; + } VMSwap v( gvm ); return ge->NPC_ClearLOS2( entID, end ); } int GVM_NAVNEW_ClearPathBetweenPoints( vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, int ignore, int clipmask ) { - if ( gvm->isLegacy ) - return VM_Call( gvm, GAME_NAV_CLEARPATHBETWEENPOINTS, reinterpret_cast< intptr_t >( start ), reinterpret_cast< intptr_t >( end ), reinterpret_cast< intptr_t >( mins ), reinterpret_cast< intptr_t >( maxs ), ignore, clipmask ); + if ( gvm->isLegacy ) { + int ret; + + float *start_ptr; + float *end_ptr; + float *mins_ptr; + float *maxs_ptr; + + if ( !gvm->dllHandle ) { + start_ptr = (float*)VM_PtrToOffset( gvm, VM_ExtraMemory_ClaimData(gvm, (float*)start, vec3_size) ); + end_ptr = (float*)VM_PtrToOffset( gvm, VM_ExtraMemory_ClaimData(gvm, (float*)end, vec3_size) ); + mins_ptr = (float*)VM_PtrToOffset( gvm, VM_ExtraMemory_ClaimData(gvm, (float*)mins, vec3_size) ); + maxs_ptr = (float*)VM_PtrToOffset( gvm, VM_ExtraMemory_ClaimData(gvm, (float*)maxs, vec3_size) ); + } else { + start_ptr = start; + end_ptr = end; + mins_ptr = mins; + maxs_ptr = maxs; + } + ret = VM_Call( gvm, GAME_NAV_CLEARPATHBETWEENPOINTS, reinterpret_cast< intptr_t >( start_ptr ), reinterpret_cast< intptr_t >( end_ptr ), reinterpret_cast< intptr_t >( mins_ptr ), reinterpret_cast< intptr_t >( maxs_ptr ), ignore, clipmask ); + + if ( !gvm->dllHandle ) { + // Memory may only be released in reverse order. + if ( maxs_ptr ) VM_ExtraMemory_Release( gvm, vec3_size ); + if ( mins_ptr ) VM_ExtraMemory_Release( gvm, vec3_size ); + if ( end_ptr ) VM_ExtraMemory_Release( gvm, vec3_size ); + if ( start_ptr ) VM_ExtraMemory_Release( gvm, vec3_size ); + } + + return ret; + } VMSwap v( gvm ); return ge->NAVNEW_ClearPathBetweenPoints( start, end, mins, maxs, ignore, clipmask ); @@ -1467,75 +1596,81 @@ static void SV_G2API_ListModelBones( void *ghlInfo, int frame ) { static void SV_G2API_SetGhoul2ModelIndexes( void *ghoul2, qhandle_t *modelList, qhandle_t *skinList ) { if ( !ghoul2 ) return; - re->G2API_SetGhoul2ModelIndexes( *((CGhoul2Info_v *)ghoul2), modelList, skinList ); + re->G2API_SetGhoul2ModelIndexes( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelList, skinList ); } static qboolean SV_G2API_HaveWeGhoul2Models( void *ghoul2) { if ( !ghoul2 ) return qfalse; - return re->G2API_HaveWeGhoul2Models( *((CGhoul2Info_v *)ghoul2) ); + return re->G2API_HaveWeGhoul2Models( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)) ); } static qboolean SV_G2API_GetBoltMatrix( void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale ) { if ( !ghoul2 ) return qfalse; - return re->G2API_GetBoltMatrix( *((CGhoul2Info_v *)ghoul2), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); + return re->G2API_GetBoltMatrix( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); } static qboolean SV_G2API_GetBoltMatrix_NoReconstruct( void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale ) { if ( !ghoul2 ) return qfalse; re->G2API_BoltMatrixReconstruction( qfalse ); - return re->G2API_GetBoltMatrix( *((CGhoul2Info_v *)ghoul2), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); + return re->G2API_GetBoltMatrix( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); } static qboolean SV_G2API_GetBoltMatrix_NoRecNoRot( void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale ) { if ( !ghoul2 ) return qfalse; re->G2API_BoltMatrixReconstruction( qfalse ); re->G2API_BoltMatrixSPMethod( qtrue ); - return re->G2API_GetBoltMatrix( *((CGhoul2Info_v *)ghoul2), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); + return re->G2API_GetBoltMatrix( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); } static int SV_G2API_InitGhoul2Model( void **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin, qhandle_t customShader, int modelFlags, int lodBias ) { #ifdef _FULL_G2_LEAK_CHECKING g_G2AllocServer = 1; #endif - return re->G2API_InitGhoul2Model( (CGhoul2Info_v **)ghoul2Ptr, fileName, modelIndex, customSkin, customShader, modelFlags, lodBias ); + CGhoul2Info_v **g2Ptr = SV_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)ghoul2Ptr ); + int ret = re->G2API_InitGhoul2Model( g2Ptr, fileName, modelIndex, customSkin, customShader, modelFlags, lodBias ); + SV_G2Map_Update( (g2handleptr_t*)ghoul2Ptr, *g2Ptr ); + return ret; } static qboolean SV_G2API_SetSkin( void *ghoul2, int modelIndex, qhandle_t customSkin, qhandle_t renderSkin ) { if ( !ghoul2 ) return qfalse; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); return re->G2API_SetSkin( g2, modelIndex, customSkin, renderSkin ); } static void SV_G2API_CollisionDetect( CollisionRecord_t *collRecMap, void* ghoul2, const vec3_t angles, const vec3_t position, int frameNumber, int entNum, vec3_t rayStart, vec3_t rayEnd, vec3_t scale, int traceFlags, int useLod, float fRadius ) { if ( !ghoul2 ) return; - re->G2API_CollisionDetect( collRecMap, *((CGhoul2Info_v *)ghoul2), angles, position, frameNumber, entNum, rayStart, rayEnd, scale, G2VertSpaceServer, traceFlags, useLod, fRadius ); + re->G2API_CollisionDetect( collRecMap, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), angles, position, frameNumber, entNum, rayStart, rayEnd, scale, G2VertSpaceServer, traceFlags, useLod, fRadius ); } static void SV_G2API_CollisionDetectCache( CollisionRecord_t *collRecMap, void* ghoul2, const vec3_t angles, const vec3_t position, int frameNumber, int entNum, vec3_t rayStart, vec3_t rayEnd, vec3_t scale, int traceFlags, int useLod, float fRadius ) { if ( !ghoul2 ) return; - re->G2API_CollisionDetectCache( collRecMap, *((CGhoul2Info_v *)ghoul2), angles, position, frameNumber, entNum, rayStart, rayEnd, scale, G2VertSpaceServer, traceFlags, useLod, fRadius ); + re->G2API_CollisionDetectCache( collRecMap, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), angles, position, frameNumber, entNum, rayStart, rayEnd, scale, G2VertSpaceServer, traceFlags, useLod, fRadius ); } static void SV_G2API_CleanGhoul2Models( void **ghoul2Ptr ) { #ifdef _FULL_G2_LEAK_CHECKING g_G2AllocServer = 1; #endif - re->G2API_CleanGhoul2Models( (CGhoul2Info_v **)ghoul2Ptr ); + + CGhoul2Info_v **g2Ptr = SV_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)ghoul2Ptr ); + re->G2API_CleanGhoul2Models( g2Ptr ); + SV_G2Map_Update( (g2handleptr_t*)ghoul2Ptr, *g2Ptr ); } static qboolean SV_G2API_SetBoneAngles( void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, const int up, const int right, const int forward, qhandle_t *modelList, int blendTime , int currentTime ) { if ( !ghoul2 ) return qfalse; - return re->G2API_SetBoneAngles( *((CGhoul2Info_v *)ghoul2), modelIndex, boneName, angles, flags, (const Eorientations)up, (const Eorientations)right, (const Eorientations)forward, modelList, blendTime , currentTime ); + return re->G2API_SetBoneAngles( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boneName, angles, flags, (const Eorientations)up, (const Eorientations)right, (const Eorientations)forward, modelList, blendTime , currentTime ); } static qboolean SV_G2API_SetBoneAnim( void *ghoul2, const int modelIndex, const char *boneName, const int startFrame, const int endFrame, const int flags, const float animSpeed, const int currentTime, const float setFrame, const int blendTime ) { if ( !ghoul2 ) return qfalse; - return re->G2API_SetBoneAnim( *((CGhoul2Info_v *)ghoul2), modelIndex, boneName, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime ); + return re->G2API_SetBoneAnim( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boneName, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime ); } static qboolean SV_G2API_GetBoneAnim( void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, int *startFrame, int *endFrame, int *flags, float *animSpeed, int *modelList, const int modelIndex ) { if ( !ghoul2 ) return qfalse; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); return re->G2API_GetBoneAnim( g2, modelIndex, boneName, currentTime, currentFrame, startFrame, endFrame, flags, animSpeed, modelList ); } @@ -1546,7 +1681,7 @@ static void SV_G2API_GetGLAName( void *ghoul2, int modelIndex, char *fillBuf ) { return; } - char *tmp = re->G2API_GetGLAName( *((CGhoul2Info_v *)ghoul2), modelIndex ); + char *tmp = re->G2API_GetGLAName( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex ); if ( tmp ) strcpy( fillBuf, tmp ); else @@ -1555,12 +1690,12 @@ static void SV_G2API_GetGLAName( void *ghoul2, int modelIndex, char *fillBuf ) { static int SV_G2API_CopyGhoul2Instance( void *g2From, void *g2To, int modelIndex ) { if ( !g2From || !g2To ) return 0; - return re->G2API_CopyGhoul2Instance( *((CGhoul2Info_v *)g2From), *((CGhoul2Info_v *)g2To), modelIndex ); + return re->G2API_CopyGhoul2Instance( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)g2From)), *(SV_G2Map_GetG2FromHandle((g2handleptr_t)g2To)), modelIndex ); } static void SV_G2API_CopySpecificGhoul2Model( void *g2From, int modelFrom, void *g2To, int modelTo ) { if ( !g2From || !g2To ) return; - re->G2API_CopySpecificG2Model( *((CGhoul2Info_v *)g2From), modelFrom, *((CGhoul2Info_v *)g2To), modelTo ); + re->G2API_CopySpecificG2Model( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)g2From)), modelFrom, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)g2To)), modelTo ); } static void SV_G2API_DuplicateGhoul2Instance( void *g2From, void **g2To ) { @@ -1568,72 +1703,84 @@ static void SV_G2API_DuplicateGhoul2Instance( void *g2From, void **g2To ) { g_G2AllocServer = 1; #endif if ( !g2From || !g2To ) return; - re->G2API_DuplicateGhoul2Instance( *((CGhoul2Info_v *)g2From), (CGhoul2Info_v **)g2To ); + + CGhoul2Info_v **g2ToPtr = SV_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)g2To ); + re->G2API_DuplicateGhoul2Instance( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)g2From)), g2ToPtr ); + SV_G2Map_Update( (g2handleptr_t*)g2To, *g2ToPtr ); } static qboolean SV_G2API_HasGhoul2ModelOnIndex( void *ghlInfo, int modelIndex ) { - return re->G2API_HasGhoul2ModelOnIndex( (CGhoul2Info_v **)ghlInfo, modelIndex ); + CGhoul2Info_v **g2Ptr = SV_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)ghlInfo ); + qboolean ret = re->G2API_HasGhoul2ModelOnIndex( g2Ptr, modelIndex ); + SV_G2Map_Update( (g2handleptr_t*)ghlInfo, *g2Ptr ); + return ret; } static qboolean SV_G2API_RemoveGhoul2Model( void *ghlInfo, int modelIndex ) { #ifdef _FULL_G2_LEAK_CHECKING g_G2AllocServer = 1; #endif - return re->G2API_RemoveGhoul2Model( (CGhoul2Info_v **)ghlInfo, modelIndex ); + CGhoul2Info_v **g2Ptr = SV_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)ghlInfo ); + qboolean ret = re->G2API_RemoveGhoul2Model( g2Ptr, modelIndex ); + SV_G2Map_Update( (g2handleptr_t*)ghlInfo, *g2Ptr ); + return ret; } static qboolean SV_G2API_RemoveGhoul2Models( void *ghlInfo ) { #ifdef _FULL_G2_LEAK_CHECKING g_G2AllocServer = 1; #endif - return re->G2API_RemoveGhoul2Models( (CGhoul2Info_v **)ghlInfo ); + CGhoul2Info_v **g2Ptr = SV_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)ghlInfo ); + qboolean ret = re->G2API_RemoveGhoul2Models( g2Ptr ); + SV_G2Map_Update( (g2handleptr_t*)ghlInfo, *g2Ptr ); + return ret; } static int SV_G2API_Ghoul2Size( void *ghlInfo ) { if ( !ghlInfo ) return 0; - return re->G2API_Ghoul2Size( *((CGhoul2Info_v *)ghlInfo) ); + return re->G2API_Ghoul2Size( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghlInfo)) ); } static int SV_G2API_AddBolt( void *ghoul2, int modelIndex, const char *boneName ) { if ( !ghoul2 ) return -1; - return re->G2API_AddBolt( *((CGhoul2Info_v *)ghoul2), modelIndex, boneName ); + return re->G2API_AddBolt( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boneName ); } static void SV_G2API_SetBoltInfo( void *ghoul2, int modelIndex, int boltInfo ) { if ( !ghoul2 ) return; - re->G2API_SetBoltInfo( *((CGhoul2Info_v *)ghoul2), modelIndex, boltInfo ); + re->G2API_SetBoltInfo( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boltInfo ); } static qboolean SV_G2API_SetRootSurface( void *ghoul2, const int modelIndex, const char *surfaceName ) { if ( !ghoul2 ) return qfalse; - return re->G2API_SetRootSurface( *((CGhoul2Info_v *)ghoul2), modelIndex, surfaceName ); + return re->G2API_SetRootSurface( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, surfaceName ); } static qboolean SV_G2API_SetSurfaceOnOff( void *ghoul2, const char *surfaceName, const int flags ) { if ( !ghoul2 ) return qfalse; - return re->G2API_SetSurfaceOnOff( *((CGhoul2Info_v *)ghoul2), surfaceName, flags ); + return re->G2API_SetSurfaceOnOff( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), surfaceName, flags ); } static qboolean SV_G2API_SetNewOrigin( void *ghoul2, const int boltIndex ) { if ( !ghoul2 ) return qfalse; - return re->G2API_SetNewOrigin( *((CGhoul2Info_v *)ghoul2), boltIndex ); + return re->G2API_SetNewOrigin( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), boltIndex ); } static qboolean SV_G2API_DoesBoneExist( void *ghoul2, int modelIndex, const char *boneName ) { if ( !ghoul2 ) return qfalse; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); return re->G2API_DoesBoneExist( g2, modelIndex, boneName ); } static int SV_G2API_GetSurfaceRenderStatus( void *ghoul2, const int modelIndex, const char *surfaceName ) { if ( !ghoul2 ) return -1; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); return re->G2API_GetSurfaceRenderStatus( g2, modelIndex, surfaceName ); } static void SV_G2API_AbsurdSmoothing( void *ghoul2, qboolean status ) { if ( !ghoul2 ) return; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); re->G2API_AbsurdSmoothing( g2, status ); } @@ -1643,7 +1790,7 @@ static void SV_G2API_SetRagDoll( void *ghoul2, sharedRagDollParams_t *params ) { CRagDollParams rdParams; if ( !params ) { - re->G2API_ResetRagDoll( *((CGhoul2Info_v *)ghoul2) ); + re->G2API_ResetRagDoll( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)) ); return; } @@ -1666,7 +1813,7 @@ static void SV_G2API_SetRagDoll( void *ghoul2, sharedRagDollParams_t *params ) { rdParams.RagPhase = (CRagDollParams::ERagPhase)params->RagPhase; rdParams.effectorsToTurnOff = (CRagDollParams::ERagEffector)params->effectorsToTurnOff; - re->G2API_SetRagDoll( *((CGhoul2Info_v *)ghoul2), &rdParams ); + re->G2API_SetRagDoll( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), &rdParams ); } static void SV_G2API_AnimateG2Models( void *ghoul2, int time, sharedRagDollUpdateParams_t *params ) { @@ -1683,48 +1830,48 @@ static void SV_G2API_AnimateG2Models( void *ghoul2, int time, sharedRagDollUpdat rduParams.me = params->me; rduParams.settleFrame = params->settleFrame; - re->G2API_AnimateG2ModelsRag( *((CGhoul2Info_v *)ghoul2), time, &rduParams ); + re->G2API_AnimateG2ModelsRag( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), time, &rduParams ); } static qboolean SV_G2API_RagPCJConstraint( void *ghoul2, const char *boneName, vec3_t min, vec3_t max ) { - return re->G2API_RagPCJConstraint( *((CGhoul2Info_v *)ghoul2), boneName, min, max ); + return re->G2API_RagPCJConstraint( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), boneName, min, max ); } static qboolean SV_G2API_RagPCJGradientSpeed( void *ghoul2, const char *boneName, const float speed ) { - return re->G2API_RagPCJGradientSpeed( *((CGhoul2Info_v *)ghoul2), boneName, speed ); + return re->G2API_RagPCJGradientSpeed( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), boneName, speed ); } static qboolean SV_G2API_RagEffectorGoal( void *ghoul2, const char *boneName, vec3_t pos ) { - return re->G2API_RagEffectorGoal( *((CGhoul2Info_v *)ghoul2), boneName, pos ); + return re->G2API_RagEffectorGoal( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), boneName, pos ); } static qboolean SV_G2API_GetRagBonePos( void *ghoul2, const char *boneName, vec3_t pos, vec3_t entAngles, vec3_t entPos, vec3_t entScale ) { - return re->G2API_GetRagBonePos( *((CGhoul2Info_v *)ghoul2), boneName, pos, entAngles, entPos, entScale ); + return re->G2API_GetRagBonePos( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), boneName, pos, entAngles, entPos, entScale ); } static qboolean SV_G2API_RagEffectorKick( void *ghoul2, const char *boneName, vec3_t velocity ) { - return re->G2API_RagEffectorKick( *((CGhoul2Info_v *)ghoul2), boneName, velocity ); + return re->G2API_RagEffectorKick( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), boneName, velocity ); } static qboolean SV_G2API_RagForceSolve( void *ghoul2, qboolean force ) { - return re->G2API_RagForceSolve( *((CGhoul2Info_v *)ghoul2), force ); + return re->G2API_RagForceSolve( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), force ); } static qboolean SV_G2API_SetBoneIKState( void *ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params ) { - return re->G2API_SetBoneIKState( *((CGhoul2Info_v *)ghoul2), time, boneName, ikState, params ); + return re->G2API_SetBoneIKState( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), time, boneName, ikState, params ); } static qboolean SV_G2API_IKMove( void *ghoul2, int time, sharedIKMoveParams_t *params ) { - return re->G2API_IKMove( *((CGhoul2Info_v *)ghoul2), time, params ); + return re->G2API_IKMove( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), time, params ); } static qboolean SV_G2API_RemoveBone( void *ghoul2, const char *boneName, int modelIndex ) { - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); return re->G2API_RemoveBone( g2, modelIndex, boneName ); } static void SV_G2API_AttachInstanceToEntNum( void *ghoul2, int entityNum, qboolean server ) { - re->G2API_AttachInstanceToEntNum( *((CGhoul2Info_v *)ghoul2), entityNum, server ); + re->G2API_AttachInstanceToEntNum( *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), entityNum, server ); } static void SV_G2API_ClearAttachedInstance( int entityNum ) { @@ -1736,12 +1883,12 @@ static void SV_G2API_CleanEntAttachments( void ) { } static qboolean SV_G2API_OverrideServer( void *serverInstance ) { - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)serverInstance); + CGhoul2Info_v &g2 = *(SV_G2Map_GetG2FromHandle((g2handleptr_t)serverInstance)); return re->G2API_OverrideServerWithClientData( g2, 0 ); } static void SV_G2API_GetSurfaceName( void *ghoul2, int surfNumber, int modelIndex, char *fillBuf ) { - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(SV_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); char *tmp = re->G2API_GetSurfaceName( g2, modelIndex, surfNumber ); strcpy( fillBuf, tmp ); } @@ -2418,6 +2565,8 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { botlib_export->ai.BotRemoveConsoleMessage( args[1], args[2] ); return 0; case BOTLIB_AI_NEXT_CONSOLE_MESSAGE: + // QVM-FIXME: bot_consolemessage_s contains pointers and needs translation for 64 bit engine version, but + // vanilla modules don't event call it. return botlib_export->ai.BotNextConsoleMessage( args[1], (struct bot_consolemessage_s *)VMA(2) ); case BOTLIB_AI_NUM_CONSOLE_MESSAGE: return botlib_export->ai.BotNumConsoleMessages( args[1] ); @@ -2597,20 +2746,20 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { return 0; case G_G2_HAVEWEGHOULMODELS: - return SV_G2API_HaveWeGhoul2Models( VMA(1) ); + return SV_G2API_HaveWeGhoul2Models( (void*)args[1] ); case G_G2_SETMODELS: - SV_G2API_SetGhoul2ModelIndexes( VMA(1),(qhandle_t *)VMA(2),(qhandle_t *)VMA(3)); + SV_G2API_SetGhoul2ModelIndexes( (void*)args[1],(qhandle_t *)VMA(2),(qhandle_t *)VMA(3)); return 0; case G_G2_GETBOLT: - return SV_G2API_GetBoltMatrix(VMA(1), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + return SV_G2API_GetBoltMatrix((void*)args[1], args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); case G_G2_GETBOLT_NOREC: - return SV_G2API_GetBoltMatrix_NoReconstruct(VMA(1), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + return SV_G2API_GetBoltMatrix_NoReconstruct((void*)args[1], args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); case G_G2_GETBOLT_NOREC_NOROT: - return SV_G2API_GetBoltMatrix_NoRecNoRot(VMA(1), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + return SV_G2API_GetBoltMatrix_NoRecNoRot((void*)args[1], args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); case G_G2_INITGHOUL2MODEL: #ifdef _FULL_G2_LEAK_CHECKING @@ -2620,47 +2769,47 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { (qhandle_t) args[5], args[6], args[7]); case G_G2_SETSKIN: - return SV_G2API_SetSkin(VMA(1), args[2], args[3], args[4]); + return SV_G2API_SetSkin((void*)args[1], args[2], args[3], args[4]); case G_G2_SIZE: - return SV_G2API_Ghoul2Size ( VMA(1) ); + return SV_G2API_Ghoul2Size ( (void*)args[1] ); case G_G2_ADDBOLT: - return SV_G2API_AddBolt(VMA(1), args[2], (const char *)VMA(3)); + return SV_G2API_AddBolt((void*)args[1], args[2], (const char *)VMA(3)); case G_G2_SETBOLTINFO: - SV_G2API_SetBoltInfo(VMA(1), args[2], args[3]); + SV_G2API_SetBoltInfo((void*)args[1], args[2], args[3]); return 0; case G_G2_ANGLEOVERRIDE: - return SV_G2API_SetBoneAngles(VMA(1), args[2], (const char *)VMA(3), (float *)VMA(4), args[5], + return SV_G2API_SetBoneAngles((void*)args[1], args[2], (const char *)VMA(3), (float *)VMA(4), args[5], (const Eorientations) args[6], (const Eorientations) args[7], (const Eorientations) args[8], (qhandle_t *)VMA(9), args[10], args[11] ); case G_G2_PLAYANIM: - return SV_G2API_SetBoneAnim(VMA(1), args[2], (const char *)VMA(3), args[4], args[5], + return SV_G2API_SetBoneAnim((void*)args[1], args[2], (const char *)VMA(3), args[4], args[5], args[6], VMF(7), args[8], VMF(9), args[10]); case G_G2_GETBONEANIM: - return SV_G2API_GetBoneAnim(VMA(1), (const char*)VMA(2), args[3], (float *)VMA(4), (int *)VMA(5), + return SV_G2API_GetBoneAnim((void*)args[1], (const char*)VMA(2), args[3], (float *)VMA(4), (int *)VMA(5), (int *)VMA(6), (int *)VMA(7), (float *)VMA(8), (int *)VMA(9), args[10]); case G_G2_GETGLANAME: - SV_G2API_GetGLAName( VMA(1), args[2], (char *)VMA(3) ); + SV_G2API_GetGLAName( (void*)args[1], args[2], (char *)VMA(3) ); return 0; case G_G2_COPYGHOUL2INSTANCE: - return (int)SV_G2API_CopyGhoul2Instance(VMA(1), VMA(2), args[3]); + return (int)SV_G2API_CopyGhoul2Instance((void*)args[1], (void*)args[2], args[3]); case G_G2_COPYSPECIFICGHOUL2MODEL: - SV_G2API_CopySpecificGhoul2Model(VMA(1), args[2], VMA(3), args[4]); + SV_G2API_CopySpecificGhoul2Model((void*)args[1], args[2], (void*)args[3], args[4]); return 0; case G_G2_DUPLICATEGHOUL2INSTANCE: #ifdef _FULL_G2_LEAK_CHECKING g_G2AllocServer = 1; #endif - SV_G2API_DuplicateGhoul2Instance(VMA(1), (void **)VMA(2)); + SV_G2API_DuplicateGhoul2Instance((void*)args[1], (void **)VMA(2)); return 0; case G_G2_HASGHOUL2MODELONINDEX: @@ -2689,64 +2838,64 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { return 0; case G_G2_COLLISIONDETECT: - SV_G2API_CollisionDetect ( (CollisionRecord_t*)VMA(1), VMA(2), (const float*)VMA(3), (const float*)VMA(4), args[5], args[6], (float*)VMA(7), (float*)VMA(8), (float*)VMA(9), args[10], args[11], VMF(12) ); + SV_G2API_CollisionDetect ( (CollisionRecord_t*)VMA(1), (void*)args[2], (const float*)VMA(3), (const float*)VMA(4), args[5], args[6], (float*)VMA(7), (float*)VMA(8), (float*)VMA(9), args[10], args[11], VMF(12) ); return 0; case G_G2_COLLISIONDETECTCACHE: - SV_G2API_CollisionDetectCache ( (CollisionRecord_t*)VMA(1), VMA(2), (const float*)VMA(3), (const float*)VMA(4), args[5], args[6], (float*)VMA(7), (float*)VMA(8), (float*)VMA(9), args[10], args[11], VMF(12) ); + SV_G2API_CollisionDetectCache ( (CollisionRecord_t*)VMA(1), (void*)args[2], (const float*)VMA(3), (const float*)VMA(4), args[5], args[6], (float*)VMA(7), (float*)VMA(8), (float*)VMA(9), args[10], args[11], VMF(12) ); return 0; case G_G2_SETROOTSURFACE: - return SV_G2API_SetRootSurface(VMA(1), args[2], (const char *)VMA(3)); + return SV_G2API_SetRootSurface((void*)args[1], args[2], (const char *)VMA(3)); case G_G2_SETSURFACEONOFF: - return SV_G2API_SetSurfaceOnOff(VMA(1), (const char *)VMA(2), /*(const int)VMA(3)*/args[3]); + return SV_G2API_SetSurfaceOnOff((void*)args[1], (const char *)VMA(2), /*(const int)VMA(3)*/args[3]); case G_G2_SETNEWORIGIN: - return SV_G2API_SetNewOrigin(VMA(1), /*(const int)VMA(2)*/args[2]); + return SV_G2API_SetNewOrigin((void*)args[1], /*(const int)VMA(2)*/args[2]); case G_G2_DOESBONEEXIST: - return SV_G2API_DoesBoneExist(VMA(1), args[2], (const char *)VMA(3)); + return SV_G2API_DoesBoneExist((void*)args[1], args[2], (const char *)VMA(3)); case G_G2_GETSURFACERENDERSTATUS: - return SV_G2API_GetSurfaceRenderStatus(VMA(1), args[2], (const char *)VMA(3)); + return SV_G2API_GetSurfaceRenderStatus((void*)args[1], args[2], (const char *)VMA(3)); case G_G2_ABSURDSMOOTHING: - SV_G2API_AbsurdSmoothing(VMA(1), (qboolean)args[2]); + SV_G2API_AbsurdSmoothing((void*)args[1], (qboolean)args[2]); return 0; case G_G2_SETRAGDOLL: - SV_G2API_SetRagDoll( VMA(1), (sharedRagDollParams_t *)VMA(2) ); + SV_G2API_SetRagDoll( (void*)args[1], (sharedRagDollParams_t *)VMA(2) ); return 0; case G_G2_ANIMATEG2MODELS: - SV_G2API_AnimateG2Models( VMA(1), args[2], (sharedRagDollUpdateParams_t *)VMA(3) ); + SV_G2API_AnimateG2Models( (void*)args[1], args[2], (sharedRagDollUpdateParams_t *)VMA(3) ); return 0; //additional ragdoll options -rww case G_G2_RAGPCJCONSTRAINT: - return SV_G2API_RagPCJConstraint(VMA(1), (const char *)VMA(2), (float *)VMA(3), (float *)VMA(4)); + return SV_G2API_RagPCJConstraint((void*)args[1], (const char *)VMA(2), (float *)VMA(3), (float *)VMA(4)); case G_G2_RAGPCJGRADIENTSPEED: - return SV_G2API_RagPCJGradientSpeed(VMA(1), (const char *)VMA(2), VMF(3)); + return SV_G2API_RagPCJGradientSpeed((void*)args[1], (const char *)VMA(2), VMF(3)); case G_G2_RAGEFFECTORGOAL: - return SV_G2API_RagEffectorGoal(VMA(1), (const char *)VMA(2), (float *)VMA(3)); + return SV_G2API_RagEffectorGoal((void*)args[1], (const char *)VMA(2), (float *)VMA(3)); case G_G2_GETRAGBONEPOS: - return SV_G2API_GetRagBonePos(VMA(1), (const char *)VMA(2), (float *)VMA(3), (float *)VMA(4), (float *)VMA(5), (float *)VMA(6)); + return SV_G2API_GetRagBonePos((void*)args[1], (const char *)VMA(2), (float *)VMA(3), (float *)VMA(4), (float *)VMA(5), (float *)VMA(6)); case G_G2_RAGEFFECTORKICK: - return SV_G2API_RagEffectorKick(VMA(1), (const char *)VMA(2), (float *)VMA(3)); + return SV_G2API_RagEffectorKick((void*)args[1], (const char *)VMA(2), (float *)VMA(3)); case G_G2_RAGFORCESOLVE: - return SV_G2API_RagForceSolve(VMA(1), (qboolean)args[2]); + return SV_G2API_RagForceSolve((void*)args[1], (qboolean)args[2]); case G_G2_SETBONEIKSTATE: - return SV_G2API_SetBoneIKState(VMA(1), args[2], (const char *)VMA(3), args[4], (sharedSetBoneIKStateParams_t *)VMA(5)); + return SV_G2API_SetBoneIKState((void*)args[1], args[2], (const char *)VMA(3), args[4], (sharedSetBoneIKStateParams_t *)VMA(5)); case G_G2_IKMOVE: - return SV_G2API_IKMove(VMA(1), args[2], (sharedIKMoveParams_t *)VMA(3)); + return SV_G2API_IKMove((void*)args[1], args[2], (sharedIKMoveParams_t *)VMA(3)); case G_G2_REMOVEBONE: - return SV_G2API_RemoveBone(VMA(1), (const char *)VMA(2), args[3]); + return SV_G2API_RemoveBone((void*)args[1], (const char *)VMA(2), args[3]); case G_G2_ATTACHINSTANCETOENTNUM: - SV_G2API_AttachInstanceToEntNum(VMA(1), args[2], (qboolean)args[3]); + SV_G2API_AttachInstanceToEntNum((void*)args[1], args[2], (qboolean)args[3]); return 0; case G_G2_CLEARATTACHEDINSTANCE: SV_G2API_ClearAttachedInstance(args[1]); @@ -2755,10 +2904,10 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { SV_G2API_CleanEntAttachments(); return 0; case G_G2_OVERRIDESERVER: - return SV_G2API_OverrideServer(VMA(1)); + return SV_G2API_OverrideServer((void*)args[1]); case G_G2_GETSURFACENAME: - SV_G2API_GetSurfaceName(VMA(1), args[2], args[3], (char *)VMA(4)); + SV_G2API_GetSurfaceName((void*)args[1], args[2], args[3], (char *)VMA(4)); return 0; case G_SET_ACTIVE_SUBBSP: diff --git a/codemp/server/sv_world.cpp b/codemp/server/sv_world.cpp index 309f721178..e72cb294b4 100644 --- a/codemp/server/sv_world.cpp +++ b/codemp/server/sv_world.cpp @@ -747,7 +747,7 @@ Ghoul2 Insert Start #ifndef FINAL_BUILD if (sv_showghoultraces->integer) { - Com_Printf( "Ghoul2 trace lod=%1d length=%6.0f to %s\n",clip->useLod,VectorDistance(clip->start, clip->end), re->G2API_GetModelName (*(CGhoul2Info_v *)touch->ghoul2, 0)); + Com_Printf( "Ghoul2 trace lod=%1d length=%6.0f to %s\n",clip->useLod,VectorDistance(clip->start, clip->end), re->G2API_GetModelName(*(SV_G2Map_GetG2FromHandle((g2handleptr_t)touch->ghoul2)), 0)); } #endif @@ -757,11 +757,11 @@ Ghoul2 Insert Start touch->s.NPC_class == CLASS_VEHICLE && touch->m_pVehicle) { //for vehicles cache the transform data. - re->G2API_CollisionDetectCache(G2Trace, *((CGhoul2Info_v *)touch->ghoul2), angles, touch->r.currentOrigin, sv.time, touch->s.number, clip->start, clip->end, touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); + re->G2API_CollisionDetectCache(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)touch->ghoul2)), angles, touch->r.currentOrigin, sv.time, touch->s.number, clip->start, clip->end, touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); } else { - re->G2API_CollisionDetect(G2Trace, *((CGhoul2Info_v *)touch->ghoul2), angles, touch->r.currentOrigin, sv.time, touch->s.number, clip->start, clip->end, touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); + re->G2API_CollisionDetect(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)touch->ghoul2)), angles, touch->r.currentOrigin, sv.time, touch->s.number, clip->start, clip->end, touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); } tN = 0; From 39000e2c16ce2b0e49cadb06144db28674f470a4 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Sun, 1 Oct 2023 01:08:39 +0200 Subject: [PATCH 08/28] Replace sharedEntity_t with sharedEntityMapper_t in most places. As the QVM is always 32 bit, even if the engine is 64 bit the structs don't match due to pointers inside of them. The sharedEntityMapper_t struct has the same members as the sharedEntity_t struct, but all of them are pointers to the types sharedEntity_t uses. When the game module calls the locate gamedata syscall the pointers of the mapper struct are set. The mapper should be usable with native libraries and QVM. --- codemp/game/g_public.h | 80 +++++++++ codemp/icarus/GameInterface.cpp | 96 +++++------ codemp/icarus/GameInterface.h | 10 +- codemp/icarus/Q3_Interface.cpp | 40 ++--- codemp/icarus/Q3_Interface.h | 4 +- codemp/icarus/Sequencer.cpp | 12 +- codemp/icarus/TaskManager.cpp | 4 +- codemp/icarus/interface.h | 2 +- codemp/qcommon/RoffSystem.cpp | 36 ++-- codemp/qcommon/msg.cpp | 2 +- codemp/server/NPCNav/navigator.cpp | 161 +++++++++--------- codemp/server/NPCNav/navigator.h | 16 +- codemp/server/server.h | 18 +- codemp/server/sv_bot.cpp | 7 +- codemp/server/sv_client.cpp | 12 +- codemp/server/sv_game.cpp | 29 +++- codemp/server/sv_gameapi.cpp | 255 ++++++++++++++++++++++------- codemp/server/sv_init.cpp | 23 ++- codemp/server/sv_main.cpp | 4 +- codemp/server/sv_snapshot.cpp | 68 ++++---- codemp/server/sv_world.cpp | 191 ++++++++++----------- 21 files changed, 660 insertions(+), 410 deletions(-) diff --git a/codemp/game/g_public.h b/codemp/game/g_public.h index d90e350978..6c56c33a4b 100644 --- a/codemp/game/g_public.h +++ b/codemp/game/g_public.h @@ -254,6 +254,86 @@ typedef struct sharedEntity_s { int next_roff_time; //rww - npc's need to know when they're getting roff'd } sharedEntity_t; +typedef struct sharedEntity_qvm_s { + entityState_t s; // communicated by server to clients + uint32_t playerState; //needs to be in the gentity for bg entity access + //if you want to actually see the contents I guess + //you will have to be sure to VMA it first. + uint32_t m_pVehicle; //vehicle data + uint32_t ghoul2; //g2 instance + int localAnimIndex; //index locally (game/cgame) to anim data for this skel + vec3_t modelScale; //needed for g2 collision + + //from here up must also be unified with bgEntity/centity + + entityShared_t r; // shared by both the server system and game + + //Script/ICARUS-related fields + int taskID[NUM_TIDS]; + uint32_t parms; + uint32_t behaviorSet[NUM_BSETS]; + uint32_t script_targetname; + int delayScriptTime; + uint32_t fullName; + + //rww - targetname and classname are now shared as well. ICARUS needs access to them. + uint32_t targetname; + uint32_t classname; // set in QuakeEd + + //rww - and yet more things to share. This is because the nav code is in the exe because it's all C++. + int waypoint; //Set once per frame, if you've moved, and if someone asks + int lastWaypoint; //To make sure you don't double-back + int lastValidWaypoint; //ALWAYS valid -used for tracking someone you lost + int noWaypointTime; //Debouncer - so don't keep checking every waypoint in existance every frame that you can't find one + int combatPoint; + int failedWaypoints[MAX_FAILED_NODES]; + int failedWaypointCheckTime; + + int next_roff_time; //rww - npc's need to know when they're getting roff'd +} sharedEntity_qvm_t; + +typedef struct sharedEntityMapper_s { + entityState_t *s; // communicated by server to clients + playerState_t **playerState; //needs to be in the gentity for bg entity access + //if you want to actually see the contents I guess + //you will have to be sure to VMA it first. +#if (!defined(MACOS_X) && !defined(__GCC__) && !defined(__GNUC__)) + Vehicle_t **m_pVehicle; //vehicle data +#else + struct Vehicle_s **m_pVehicle; //vehicle data +#endif + void **ghoul2; //g2 instance + int *localAnimIndex; //index locally (game/cgame) to anim data for this skel + vec3_t *modelScale; //needed for g2 collision + + //from here up must also be unified with bgEntity/centity + + entityShared_t *r; // shared by both the server system and game + + //Script/ICARUS-related fields + int (*taskID)[NUM_TIDS]; + parms_t **parms; + char *(*behaviorSet)[NUM_BSETS]; + char **script_targetname; + int *delayScriptTime; + char **fullName; + + //rww - targetname and classname are now shared as well. ICARUS needs access to them. + char **targetname; + char **classname; // set in QuakeEd + + //rww - and yet more things to share. This is because the nav code is in the exe because it's all C++. + int *waypoint; //Set once per frame, if you've moved, and if someone asks + int *lastWaypoint; //To make sure you don't double-back + int *lastValidWaypoint; //ALWAYS valid -used for tracking someone you lost + int *noWaypointTime; //Debouncer - so don't keep checking every waypoint in existance every frame that you can't find one + int *combatPoint; + int (*failedWaypoints)[MAX_FAILED_NODES]; + int *failedWaypointCheckTime; + + int *next_roff_time; //rww - npc's need to know when they're getting roff'd +} sharedEntityMapper_t; + #if !defined(_GAME) && defined(__cplusplus) class CSequencer; class CTaskManager; diff --git a/codemp/icarus/GameInterface.cpp b/codemp/icarus/GameInterface.cpp index d32b2bb964..8a94c4cda2 100644 --- a/codemp/icarus/GameInterface.cpp +++ b/codemp/icarus/GameInterface.cpp @@ -84,13 +84,13 @@ ICARUS_RunScript Runs the script by the given name ============= */ -int ICARUS_RunScript( sharedEntity_t *ent, const char *name ) +int ICARUS_RunScript( sharedEntityMapper_t *ent, const char *name ) { char *buf; int len; //Make sure the caller is valid - if ( gSequencers[ent->s.number] == NULL ) + if ( gSequencers[ent->s->number] == NULL ) { //Com_Printf( "%s : entity is not a valid script user\n", ent->classname ); return false; @@ -139,12 +139,12 @@ int ICARUS_RunScript( sharedEntity_t *ent, const char *name ) } //Attempt to run the script - if S_FAILED(gSequencers[ent->s.number]->Run( buf, len )) + if S_FAILED(gSequencers[ent->s->number]->Run( buf, len )) return false; - if ( ( ICARUS_entFilter == -1 ) || ( ICARUS_entFilter == ent->s.number ) ) + if ( ( ICARUS_entFilter == -1 ) || ( ICARUS_entFilter == ent->s->number ) ) { - Q3_DebugPrint( WL_VERBOSE, "%d Script %s executed by %s %s\n", svs.time, (char *) name, ent->classname, ent->targetname ); + Q3_DebugPrint( WL_VERBOSE, "%d Script %s executed by %s %s\n", svs.time, (char *) name, *(ent->classname), *(ent->targetname) ); } return true; @@ -184,22 +184,22 @@ Frees up ICARUS resources from all entities void ICARUS_Shutdown( void ) { bufferlist_t::iterator ei; - sharedEntity_t *ent = SV_GentityNum(0); + sharedEntityMapper_t *ent = SV_GentityMapperNum(0); //Release all ICARUS resources from the entities for ( int i = 0; i < /*globals.num_entities*/MAX_GENTITIES; i++ ) { - ent = SV_GentityNum(i); + ent = SV_GentityMapperNum(i); if (gSequencers[i]) { - if (ent->s.number >= MAX_GENTITIES || - ent->s.number < 0) + if (ent->s->number >= MAX_GENTITIES || + ent->s->number < 0) { - ent->s.number = i; + ent->s->number = i; assert(0); } - ICARUS_FreeEnt( ent ); + ICARUS_FreeEnt( ConvertedEntity(SV_GentityNum(i)) ); } } @@ -235,27 +235,27 @@ FIXME: shouldn't ICARUS handle this internally? ============== */ -void ICARUS_FreeEnt( sharedEntity_t *ent ) +void ICARUS_FreeEnt( sharedEntityMapper_t *ent ) { assert( iICARUS ); - if (ent->s.number >= MAX_GENTITIES || - ent->s.number < 0) + if (ent->s->number >= MAX_GENTITIES || + ent->s->number < 0) { assert(0); return; } //Make sure the ent is valid - if ( gSequencers[ent->s.number] == NULL ) + if ( gSequencers[ent->s->number] == NULL ) return; //Remove them from the ICARUSE_EntList list so that when their g_entity index is reused, ICARUS doesn't try to affect the new (incorrect) ent. - if VALIDSTRING( ent->script_targetname ) + if VALIDSTRING( (*(ent->script_targetname)) ) { char temp[1024]; - strncpy( (char *) temp, ent->script_targetname, 1023 ); + strncpy( (char *) temp, *(ent->script_targetname), 1023 ); temp[ 1023 ] = 0; entlist_t::iterator it = ICARUS_EntList.find( Q_strupr(temp) ); @@ -267,11 +267,11 @@ void ICARUS_FreeEnt( sharedEntity_t *ent ) } //Delete the sequencer and the task manager - iICARUS->DeleteSequencer( gSequencers[ent->s.number] ); + iICARUS->DeleteSequencer( gSequencers[ent->s->number] ); //Clean up the pointers - gSequencers[ent->s.number] = NULL; - gTaskManagers[ent->s.number] = NULL; + gSequencers[ent->s->number] = NULL; + gTaskManagers[ent->s->number] = NULL; } @@ -283,18 +283,18 @@ Determines whether or not an entity needs ICARUS information ============== */ -bool ICARUS_ValidEnt( sharedEntity_t *ent ) +bool ICARUS_ValidEnt( sharedEntityMapper_t *ent ) { int i; //Targeted by a script - if VALIDSTRING( ent->script_targetname ) + if VALIDSTRING( (*(ent->script_targetname)) ) return true; //Potentially able to call a script for ( i = 0; i < NUM_BSETS; i++ ) { - if VALIDSTRING( ent->behaviorSet[i] ) + if VALIDSTRING( (*(ent->behaviorSet))[i] ) { //Com_Printf( "WARNING: Entity %d (%s) has behaviorSet but no script_targetname -- using targetname\n", ent->s.number, ent->targetname ); @@ -302,11 +302,11 @@ bool ICARUS_ValidEnt( sharedEntity_t *ent ) //rww - You CANNOT do things like this now. We're switching memory around to be able to read this memory from vm land, //and while this allows us to read it on our "fake" entity here, we can't modify pointers like this. We can however do //something completely hackish such as the following. - assert(ent->s.number >= 0 && ent->s.number < MAX_GENTITIES); - sharedEntity_t *trueEntity = SV_GentityNum(ent->s.number); + assert(ent->s->number >= 0 && ent->s->number < MAX_GENTITIES); + sharedEntityMapper_t *trueEntity = SV_GentityMapperNum(ent->s->number); //This works because we're modifying the actual shared game vm data and turning one pointer into another. //While these pointers both look like garbage to us in here, they are not. - trueEntity->script_targetname = trueEntity->targetname; + *(trueEntity->script_targetname) = *(trueEntity->targetname); return true; } } @@ -322,17 +322,17 @@ Associate the entity's id and name so that it can be referenced later ============== */ -void ICARUS_AssociateEnt( sharedEntity_t *ent ) +void ICARUS_AssociateEnt( sharedEntityMapper_t *ent ) { char temp[1024]; - if ( VALIDSTRING( ent->script_targetname ) == false ) + if ( VALIDSTRING( (*(ent->script_targetname)) ) == false ) return; - strncpy( (char *) temp, ent->script_targetname, 1023 ); + strncpy( (char *) temp, *(ent->script_targetname), 1023 ); temp[ 1023 ] = 0; - ICARUS_EntList[ Q_strupr( (char *) temp ) ] = ent->s.number; + ICARUS_EntList[ Q_strupr( (char *) temp ) ] = ent->s->number; } /* @@ -628,19 +628,19 @@ Precache all scripts being used by the entity ============== */ -void ICARUS_PrecacheEnt( sharedEntity_t *ent ) +void ICARUS_PrecacheEnt( sharedEntityMapper_t *ent ) { char newname[MAX_FILENAME_LENGTH]; int i; for ( i = 0; i < NUM_BSETS; i++ ) { - if ( ent->behaviorSet[i] == NULL ) + if ( (*(ent->behaviorSet))[i] == NULL ) continue; - if ( GetIDForString( BSTable, ent->behaviorSet[i] ) == -1 ) + if ( GetIDForString( BSTable, (*(ent->behaviorSet))[i] ) == -1 ) {//not a behavior set - Com_sprintf( newname, sizeof(newname), "%s/%s", Q3_SCRIPT_DIR, ent->behaviorSet[i] ); + Com_sprintf( newname, sizeof(newname), "%s/%s", Q3_SCRIPT_DIR, (*(ent->behaviorSet))[i] ); //Precache this, and all internally referenced scripts ICARUS_InterrogateScript( newname ); @@ -657,25 +657,25 @@ Allocates a sequencer and task manager only if an entity is a potential script u */ void Q3_TaskIDClear( int *taskID ); -void ICARUS_InitEnt( sharedEntity_t *ent ) +void ICARUS_InitEnt( sharedEntityMapper_t *ent ) { //Make sure this is a fresh ent assert( iICARUS ); - assert( gTaskManagers[ent->s.number] == NULL ); - assert( gSequencers[ent->s.number] == NULL ); + assert( gTaskManagers[ent->s->number] == NULL ); + assert( gSequencers[ent->s->number] == NULL ); - if ( gSequencers[ent->s.number] != NULL ) + if ( gSequencers[ent->s->number] != NULL ) return; - if ( gTaskManagers[ent->s.number] != NULL ) + if ( gTaskManagers[ent->s->number] != NULL ) return; //Create the sequencer and setup the task manager - gSequencers[ent->s.number] = iICARUS->GetSequencer( ent->s.number ); - gTaskManagers[ent->s.number] = gSequencers[ent->s.number]->GetTaskManager(); + gSequencers[ent->s->number] = iICARUS->GetSequencer( ent->s->number ); + gTaskManagers[ent->s->number] = gSequencers[ent->s->number]->GetTaskManager(); //Initialize all taskIDs to -1 - memset( &ent->taskID, -1, sizeof( ent->taskID ) ); + memset( ent->taskID, -1, sizeof( *(ent->taskID) ) ); //Add this entity to a map of valid associated ents for quick retrieval later ICARUS_AssociateEnt( ent ); @@ -692,13 +692,13 @@ ICARUS_LinkEntity int ICARUS_LinkEntity( int entID, CSequencer *sequencer, CTaskManager *taskManager ) { - sharedEntity_t *ent = SV_GentityNum(entID); + sharedEntityMapper_t *ent = SV_GentityMapperNum(entID); if ( ent == NULL ) return false; - gSequencers[ent->s.number] = sequencer; - gTaskManagers[ent->s.number] = taskManager; + gSequencers[ent->s->number] = sequencer; + gTaskManagers[ent->s->number] = taskManager; ICARUS_AssociateEnt( ent ); @@ -722,9 +722,9 @@ void Svcmd_ICARUS_f( void ) //g_ICARUSDebug->integer = WL_DEBUG; if ( VALIDSTRING( Cmd_Argv( 2 ) ) ) { - sharedEntity_t *ent = G_Find( NULL, FOFS( script_targetname ), gi.argv(2) ); + sharedEntityMapper_t *ent = G_Find( NULL, FOFS( script_targetname ), gi.argv(2) ); - if ( ent == NULL ) + if ( *ent == NULL ) { Com_Printf( "Entity \"%s\" not found!\n", gi.argv(2) ); return; @@ -733,7 +733,7 @@ void Svcmd_ICARUS_f( void ) //Start logging Com_Printf("Logging ICARUS info for entity %s\n", gi.argv(2) ); - ICARUS_entFilter = ( ent->s.number == ICARUS_entFilter ) ? -1 : ent->s.number; + ICARUS_entFilter = ( ent->s->number == ICARUS_entFilter ) ? -1 : ent->s->number; return; } diff --git a/codemp/icarus/GameInterface.h b/codemp/icarus/GameInterface.h index 666fd6c035..68c45ddf78 100644 --- a/codemp/icarus/GameInterface.h +++ b/codemp/icarus/GameInterface.h @@ -38,7 +38,7 @@ typedef std::map < std::string, pscript_t* > bufferlist_t; extern interface_export_t interface_export; extern void Interface_Init( interface_export_t *pe ); -extern int ICARUS_RunScript( sharedEntity_t *ent, const char *name ); +extern int ICARUS_RunScript( sharedEntityMapper_t *ent, const char *name ); extern bool ICARUS_RegisterScript( const char *name, qboolean bCalledDuringInterrogate = qfalse); extern ICARUS_Instance *iICARUS; extern bufferlist_t ICARUS_BufferList; @@ -48,10 +48,10 @@ extern entlist_t ICARUS_EntList; // g_ICARUS.cpp // void ICARUS_Init( void ); -bool ICARUS_ValidEnt( sharedEntity_t *ent ); -void ICARUS_InitEnt( sharedEntity_t *ent ); -void ICARUS_FreeEnt( sharedEntity_t *ent ); -void ICARUS_AssociateEnt( sharedEntity_t *ent ); +bool ICARUS_ValidEnt( sharedEntityMapper_t *ent ); +void ICARUS_InitEnt( sharedEntityMapper_t *ent ); +void ICARUS_FreeEnt( sharedEntityMapper_t *ent ); +void ICARUS_AssociateEnt( sharedEntityMapper_t *ent ); void ICARUS_Shutdown( void ); void Svcmd_ICARUS_f( void ); diff --git a/codemp/icarus/Q3_Interface.cpp b/codemp/icarus/Q3_Interface.cpp index 059d65dc46..c873c47561 100644 --- a/codemp/icarus/Q3_Interface.cpp +++ b/codemp/icarus/Q3_Interface.cpp @@ -121,12 +121,12 @@ void Q3_TaskIDClear( int *taskID ) /* ------------------------- -qboolean Q3_TaskIDPending( sharedEntity_t *ent, taskID_t taskType ) +qboolean Q3_TaskIDPending( sharedEntityMapper_t *ent, taskID_t taskType ) ------------------------- */ -qboolean Q3_TaskIDPending( sharedEntity_t *ent, taskID_t taskType ) +qboolean Q3_TaskIDPending( sharedEntityMapper_t *ent, taskID_t taskType ) { - if ( !gSequencers[ent->s.number] || !gTaskManagers[ent->s.number] ) + if ( !gSequencers[ent->s->number] || !gTaskManagers[ent->s->number] ) { return qfalse; } @@ -136,7 +136,7 @@ qboolean Q3_TaskIDPending( sharedEntity_t *ent, taskID_t taskType ) return qfalse; } - if ( ent->taskID[taskType] >= 0 )//-1 is none + if ( (*(ent->taskID))[taskType] >= 0 )//-1 is none { return qtrue; } @@ -146,43 +146,43 @@ qboolean Q3_TaskIDPending( sharedEntity_t *ent, taskID_t taskType ) /* ------------------------- -void Q3_TaskIDComplete( sharedEntity_t *ent, taskID_t taskType ) +void Q3_TaskIDComplete( sharedEntityMapper_t *ent, taskID_t taskType ) ------------------------- */ -void Q3_TaskIDComplete( sharedEntity_t *ent, taskID_t taskType ) +void Q3_TaskIDComplete( sharedEntityMapper_t *ent, taskID_t taskType ) { if ( taskType < TID_CHAN_VOICE || taskType >= NUM_TIDS ) { return; } - if ( gTaskManagers[ent->s.number] && Q3_TaskIDPending( ent, taskType ) ) + if ( gTaskManagers[ent->s->number] && Q3_TaskIDPending( ent, taskType ) ) {//Complete it - gTaskManagers[ent->s.number]->Completed( ent->taskID[taskType] ); + gTaskManagers[ent->s->number]->Completed( (*(ent->taskID))[taskType] ); //See if any other tasks have the name number and clear them so we don't complete more than once - int clearTask = ent->taskID[taskType]; + int clearTask = (*(ent->taskID))[taskType]; for ( int tid = 0; tid < NUM_TIDS; tid++ ) { - if ( ent->taskID[tid] == clearTask ) + if ( (*(ent->taskID))[tid] == clearTask ) { - Q3_TaskIDClear( &ent->taskID[tid] ); + Q3_TaskIDClear( &((*(ent->taskID))[tid]) ); } } //clear it - should be cleared in for loop above - //Q3_TaskIDClear( &ent->taskID[taskType] ); + //Q3_TaskIDClear( &((*(ent->taskID)))[taskType] ); } //otherwise, wasn't waiting for a task to complete anyway } /* ------------------------- -void Q3_SetTaskID( sharedEntity_t *ent, taskID_t taskType, int taskID ) +void Q3_SetTaskID( sharedEntityMapper_t *ent, taskID_t taskType, int taskID ) ------------------------- */ -void Q3_TaskIDSet( sharedEntity_t *ent, taskID_t taskType, int taskID ) +void Q3_TaskIDSet( sharedEntityMapper_t *ent, taskID_t taskType, int taskID ) { if ( taskType < TID_CHAN_VOICE || taskType >= NUM_TIDS ) { @@ -192,7 +192,7 @@ void Q3_TaskIDSet( sharedEntity_t *ent, taskID_t taskType, int taskID ) //Might be stomping an old task, so complete and clear previous task if there was one Q3_TaskIDComplete( ent, taskType ); - ent->taskID[taskType] = taskID; + (*(ent->taskID))[taskType] = taskID; } @@ -236,9 +236,9 @@ Q3_GetEntityByName Returns the sequencer of the entity by the given name ============= */ -static sharedEntity_t *Q3_GetEntityByName( const char *name ) +static sharedEntityMapper_t *Q3_GetEntityByName( const char *name ) { - sharedEntity_t *ent; + sharedEntityMapper_t *ent; entlist_t::iterator ei; char temp[1024]; @@ -253,13 +253,13 @@ static sharedEntity_t *Q3_GetEntityByName( const char *name ) if ( ei == ICARUS_EntList.end() ) return NULL; - ent = SV_GentityNum((*ei).second); + ent = SV_GentityMapperNum((*ei).second); return ent; // this now returns the ent instead of the sequencer -- dmv 06/27/01 // if (ent == NULL) // return NULL; -// return gSequencers[ent->s.number]; +// return gSequencers[ent->s->number]; } /* @@ -694,7 +694,7 @@ void Q3_DebugPrint( int level, const char *format, ... ) if ( ( entNum < 0 ) || ( entNum >= MAX_GENTITIES ) ) entNum = 0; - Com_Printf ( S_COLOR_BLUE"DEBUG: %s(%d): %s\n", SV_GentityNum(entNum)->script_targetname, entNum, buffer ); + Com_Printf ( S_COLOR_BLUE"DEBUG: %s(%d): %s\n", *(SV_GentityMapperNum(entNum)->script_targetname), entNum, buffer ); break; } default: diff --git a/codemp/icarus/Q3_Interface.h b/codemp/icarus/Q3_Interface.h index a8df673de2..8534744a1a 100644 --- a/codemp/icarus/Q3_Interface.h +++ b/codemp/icarus/Q3_Interface.h @@ -296,8 +296,8 @@ extern char cinematicSkipScript[1024]; //General extern void Q3_TaskIDClear( int *taskID ); -extern qboolean Q3_TaskIDPending( sharedEntity_t *ent, taskID_t taskType ); -extern void Q3_TaskIDComplete( sharedEntity_t *ent, taskID_t taskType ); +extern qboolean Q3_TaskIDPending( sharedEntityMapper_t *ent, taskID_t taskType ); +extern void Q3_TaskIDComplete( sharedEntityMapper_t *ent, taskID_t taskType ); extern void Q3_DPrintf( const char *, ... ); extern void Q3_CameraRoll( float angle, float duration ); diff --git a/codemp/icarus/Sequencer.cpp b/codemp/icarus/Sequencer.cpp index 6a21194b82..eedc1f66cd 100644 --- a/codemp/icarus/Sequencer.cpp +++ b/codemp/icarus/Sequencer.cpp @@ -618,7 +618,7 @@ int CSequencer::ParseAffect( CBlock *block, bstream_t *bstream ) CSequencer *stream_sequencer = NULL; char *entname = NULL; int ret; - sharedEntity_t *ent = 0; + sharedEntityMapper_t *ent = 0; entname = (char*) block->GetMemberData( 0 ); ent = m_ie->I_GetEntityByName( entname ); @@ -700,7 +700,7 @@ int CSequencer::ParseAffect( CBlock *block, bstream_t *bstream ) if( ent ) { - stream_sequencer = gSequencers[ent->s.number];//ent->sequencer; + stream_sequencer = gSequencers[ent->s->number];//ent->sequencer; } if (stream_sequencer == NULL) @@ -1735,7 +1735,7 @@ Checks for affect command pre-processing void CSequencer::CheckAffect( CBlock **command ) { CBlock *block = *command; - sharedEntity_t *ent = NULL; + sharedEntityMapper_t *ent = NULL; char *entname = NULL; int memberNum = 0; @@ -1821,7 +1821,7 @@ void CSequencer::CheckAffect( CBlock **command ) if( ent ) { - sequencer = gSequencers[ent->s.number];//ent->sequencer; + sequencer = gSequencers[ent->s->number];//ent->sequencer; } if(memberNum == 0) { //there was no get, increment manually before next step @@ -1856,7 +1856,7 @@ void CSequencer::CheckAffect( CBlock **command ) if( ent ) { // ents need to update upon being affected //ent->taskManager->Update(); - gTaskManagers[ent->s.number]->Update(); + gTaskManagers[ent->s->number]->Update(); } return; @@ -1888,7 +1888,7 @@ void CSequencer::CheckAffect( CBlock **command ) if( ent ) { // ents need to update upon being affected //ent->taskManager->Update(); - gTaskManagers[ent->s.number]->Update(); + gTaskManagers[ent->s->number]->Update(); } } diff --git a/codemp/icarus/TaskManager.cpp b/codemp/icarus/TaskManager.cpp index ec0b1bea44..f5c5829f67 100644 --- a/codemp/icarus/TaskManager.cpp +++ b/codemp/icarus/TaskManager.cpp @@ -336,9 +336,9 @@ Update int CTaskManager::Update( void ) { - sharedEntity_t *owner = SV_GentityNum(m_ownerID); + sharedEntityMapper_t *owner = SV_GentityMapperNum(m_ownerID); - if ( (owner->r.svFlags&SVF_ICARUS_FREEZE) ) + if ( (owner->r->svFlags&SVF_ICARUS_FREEZE) ) { return TASK_FAILED; } diff --git a/codemp/icarus/interface.h b/codemp/icarus/interface.h index 4bfbba5fde..05b270267c 100644 --- a/codemp/icarus/interface.h +++ b/codemp/icarus/interface.h @@ -35,7 +35,7 @@ typedef struct interface_export_s int (*I_LoadFile)( const char *name, void **buf ); void (*I_CenterPrint)( const char *format, ... ); void (*I_DPrintf)( int, const char *, ... ); - sharedEntity_t *(*I_GetEntityByName)( const char *name ); //Polls the engine for the sequencer of the entity matching the name passed + sharedEntityMapper_t *(*I_GetEntityByName)( const char *name ); //Polls the engine for the sequencer of the entity matching the name passed unsigned int (*I_GetTime)( void ); //Gets the current time unsigned int (*I_GetTimeScale)(void ); int (*I_PlaySound)( int taskID, int entID, const char *name, const char *channel ); diff --git a/codemp/qcommon/RoffSystem.cpp b/codemp/qcommon/RoffSystem.cpp index 753eb75c84..a77b9f3a38 100644 --- a/codemp/qcommon/RoffSystem.cpp +++ b/codemp/qcommon/RoffSystem.cpp @@ -624,17 +624,17 @@ qboolean CROFFSystem::List( int id ) //--------------------------------------------------------------------------- qboolean CROFFSystem::Play( int entID, int id, qboolean doTranslation, qboolean isClient ) { - sharedEntity_t *ent = NULL; + sharedEntityMapper_t *ent = NULL; if ( !isClient ) { - ent = SV_GentityNum( entID ); + ent = SV_GentityMapperNum( entID ); if ( ent == NULL ) { // shame on you.. return qfalse; } - ent->r.mIsRoffing = qtrue; + ent->r->mIsRoffing = qtrue; /*rjr if(ent->GetPhysics() == PHYSICS_TYPE_NONE) { @@ -655,7 +655,7 @@ qboolean CROFFSystem::Play( int entID, int id, qboolean doTranslation, qboolean roffing_ent->mIsClient = isClient; if ( !isClient ) - VectorCopy(ent->s.apos.trBase, roffing_ent->mStartAngles); + VectorCopy(ent->s->apos.trBase, roffing_ent->mStartAngles); mROFFEntList.push_back( roffing_ent ); @@ -859,7 +859,7 @@ void CROFFSystem::UpdateEntities(qboolean isClient) qboolean CROFFSystem::ApplyROFF( SROFFEntity *roff_ent, CROFFSystem::CROFF *roff ) { vec3_t f, r, u, result; - sharedEntity_t *ent = NULL; + sharedEntityMapper_t *ent = NULL; trajectory_t *originTrajectory = NULL, *angleTrajectory = NULL; float *origin = NULL, *angle = NULL; @@ -884,17 +884,17 @@ qboolean CROFFSystem::ApplyROFF( SROFFEntity *roff_ent, CROFFSystem::CROFF *roff #endif { // Find the entity to apply the roff to - ent = SV_GentityNum( roff_ent->mEntID ); + ent = SV_GentityMapperNum( roff_ent->mEntID ); if ( ent == 0 ) { // bad stuff return qfalse; } - originTrajectory = &ent->s.pos; - angleTrajectory = &ent->s.apos; - origin = ent->r.currentOrigin; - angle = ent->r.currentAngles; + originTrajectory = &ent->s->pos; + angleTrajectory = &ent->s->apos; + origin = ent->r->currentOrigin; + angle = ent->r->currentAngles; } @@ -904,7 +904,7 @@ qboolean CROFFSystem::ApplyROFF( SROFFEntity *roff_ent, CROFFSystem::CROFF *roff SetLerp( angleTrajectory, TR_STATIONARY, angle, NULL, sv.time, roff->mLerp ); if (!roff_ent->mIsClient) { - ent->r.mIsRoffing = qfalse; + ent->r->mIsRoffing = qfalse; } return qfalse; } @@ -944,7 +944,7 @@ qboolean CROFFSystem::ApplyROFF( SROFFEntity *roff_ent, CROFFSystem::CROFF *roff //rww - npcs need to know when they're getting roff'd if ( !roff_ent->mIsClient ) - ent->next_roff_time = roff_ent->mNextROFFTime; + *(ent->next_roff_time) = roff_ent->mNextROFFTime; return qtrue; @@ -1012,7 +1012,7 @@ void CROFFSystem::ProcessNote(SROFFEntity *roff_ent, char *note) //--------------------------------------------------------------------------- qboolean CROFFSystem::ClearLerp( SROFFEntity *roff_ent ) { - sharedEntity_t *ent = NULL; + sharedEntityMapper_t *ent = NULL; trajectory_t *originTrajectory = NULL, *angleTrajectory = NULL; float *origin = NULL, *angle = NULL; @@ -1031,17 +1031,17 @@ qboolean CROFFSystem::ClearLerp( SROFFEntity *roff_ent ) #endif { // Find the entity to apply the roff to - ent = SV_GentityNum( roff_ent->mEntID ); + ent = SV_GentityMapperNum( roff_ent->mEntID ); if ( ent == 0 ) { // bad stuff return qfalse; } - originTrajectory = &ent->s.pos; - angleTrajectory = &ent->s.apos; - origin = ent->r.currentOrigin; - angle = ent->r.currentAngles; + originTrajectory = &ent->s->pos; + angleTrajectory = &ent->s->apos; + origin = ent->r->currentOrigin; + angle = ent->r->currentAngles; } SetLerp( originTrajectory, TR_STATIONARY, origin, NULL, sv.time, ROFF_SAMPLE_RATE ); diff --git a/codemp/qcommon/msg.cpp b/codemp/qcommon/msg.cpp index ac178d342f..193059c18b 100644 --- a/codemp/qcommon/msg.cpp +++ b/codemp/qcommon/msg.cpp @@ -1176,7 +1176,7 @@ void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to, print = 1; if (sv.state) { - Com_Printf( "%3i: #%-3i (%s) ", msg->readcount, number, SV_GentityNum(number)->classname ); + Com_Printf( "%3i: #%-3i (%s) ", msg->readcount, number, *SV_GentityMapperNum(number)->classname ); } else { diff --git a/codemp/server/NPCNav/navigator.cpp b/codemp/server/NPCNav/navigator.cpp index 3331687cd3..055e15a63e 100644 --- a/codemp/server/NPCNav/navigator.cpp +++ b/codemp/server/NPCNav/navigator.cpp @@ -939,7 +939,7 @@ void CNavigator::ShowNodes( void ) //if( NAVDEBUG_showRadius ) if (0) { - dist = DistanceSquared( SV_GentityNum(0)->r.currentOrigin, position ); + dist = DistanceSquared( SV_GentityMapperNum(0)->r->currentOrigin, position ); radius = (*ni)->GetRadius(); // if player within node radius or 256, draw radius (sometimes the radius is really small, so we check for 256 to catch everything) if( (dist <= radius*radius) || dist <= 65536 ) @@ -949,11 +949,11 @@ void CNavigator::ShowNodes( void ) } else { - dist = DistanceSquared( SV_GentityNum(0)->r.currentOrigin, position ); + dist = DistanceSquared( SV_GentityMapperNum(0)->r->currentOrigin, position ); } if ( dist < 1048576 ) { - if ( SV_inPVS( SV_GentityNum(0)->r.currentOrigin, position ) ) + if ( SV_inPVS( SV_GentityMapperNum(0)->r->currentOrigin, position ) ) { (*ni)->Draw(showRadius); } @@ -981,12 +981,12 @@ void CNavigator::ShowEdges( void ) STL_ITERATE( ni, m_nodes ) { (*ni)->GetPosition( start ); - if ( DistanceSquared( SV_GentityNum(0)->r.currentOrigin, start ) >= 1048576 ) + if ( DistanceSquared( SV_GentityMapperNum(0)->r->currentOrigin, start ) >= 1048576 ) { continue; } - if (!SV_inPVS(SV_GentityNum(0)->r.currentOrigin, start)) + if (!SV_inPVS(SV_GentityMapperNum(0)->r->currentOrigin, start)) { continue; } @@ -1012,12 +1012,12 @@ void CNavigator::ShowEdges( void ) //Set this as drawn drawMap[id][(*ni)->GetID()] = true; - if ( DistanceSquared( SV_GentityNum(0)->r.currentOrigin, end ) >= 1048576 ) + if ( DistanceSquared( SV_GentityMapperNum(0)->r->currentOrigin, end ) >= 1048576 ) { continue; } - if ( !SV_inPVS( SV_GentityNum(0)->r.currentOrigin, end ) ) + if ( !SV_inPVS( SV_GentityMapperNum(0)->r->currentOrigin, end ) ) continue; /* @@ -1156,7 +1156,7 @@ TestNodePath ------------------------- */ -int CNavigator::TestNodePath( sharedEntity_t *ent, int okToHitEntNum, vec3_t position, qboolean includeEnts ) +int CNavigator::TestNodePath( sharedEntityMapper_t *ent, int okToHitEntNum, vec3_t position, qboolean includeEnts ) { //rwwFIXMEFIXME: Share clipmask? int clipmask = MASK_SOLID;//ent->clipmask; @@ -1165,7 +1165,7 @@ int CNavigator::TestNodePath( sharedEntity_t *ent, int okToHitEntNum, vec3_t pos clipmask &= ~CONTENTS_BODY; } //Check the path - if ( GVM_NAV_ClearPathToPoint( ent->s.number, ent->r.mins, ent->r.maxs, position, clipmask, okToHitEntNum ) == false ) + if ( GVM_NAV_ClearPathToPoint( ent->s->number, ent->r->mins, ent->r->maxs, position, clipmask, okToHitEntNum ) == false ) return false; return true; @@ -1177,9 +1177,9 @@ TestNodeLOS ------------------------- */ -int CNavigator::TestNodeLOS( sharedEntity_t *ent, vec3_t position ) +int CNavigator::TestNodeLOS( sharedEntityMapper_t *ent, vec3_t position ) { - return GVM_NPC_ClearLOS2( ent->s.number, position ); + return GVM_NPC_ClearLOS2( ent->s->number, position ); } /* @@ -1188,7 +1188,7 @@ TestBestFirst ------------------------- */ -int CNavigator::TestBestFirst( sharedEntity_t *ent, int lastID, int flags ) +int CNavigator::TestBestFirst( sharedEntityMapper_t *ent, int lastID, int flags ) { //Must be a valid one to begin with if ( lastID == NODE_NONE ) @@ -1208,7 +1208,7 @@ int CNavigator::TestBestFirst( sharedEntity_t *ent, int lastID, int flags ) //Setup our last node as our root, and search for a closer one according to its edges int bestNode = ( TestNodePath( ent, ENTITYNUM_NONE, nodePos, qtrue ) ) ? lastID : NODE_NONE; - float bestDist = ( bestNode == NODE_NONE ) ? Q3_INFINITE : DistanceSquared( ent->r.currentOrigin, nodePos ); + float bestDist = ( bestNode == NODE_NONE ) ? Q3_INFINITE : DistanceSquared( ent->r->currentOrigin, nodePos ); //Test all these edges first for ( int i = 0; i < numEdges; i++ ) @@ -1223,21 +1223,21 @@ int CNavigator::TestBestFirst( sharedEntity_t *ent, int lastID, int flags ) testNode->GetPosition( nodePos ); - dist = DistanceSquared( ent->r.currentOrigin, nodePos ); + dist = DistanceSquared( ent->r->currentOrigin, nodePos ); //Test against current best if ( dist < bestDist ) { //See if this node is valid - if ( CheckedNode(testNode->GetID(),ent->s.number) == CHECKED_PASSED || TestNodePath( ent, ENTITYNUM_NONE, nodePos, qtrue ) ) + if ( CheckedNode(testNode->GetID(),ent->s->number) == CHECKED_PASSED || TestNodePath( ent, ENTITYNUM_NONE, nodePos, qtrue ) ) { bestDist = dist; bestNode = testNode->GetID(); - SetCheckedNode(testNode->GetID(),ent->s.number,CHECKED_PASSED); + SetCheckedNode(testNode->GetID(),ent->s->number,CHECKED_PASSED); } else { - SetCheckedNode(testNode->GetID(),ent->s.number,CHECKED_FAILED); + SetCheckedNode(testNode->GetID(),ent->s->number,CHECKED_FAILED); } } } @@ -1326,7 +1326,7 @@ int CNavigator::CollectNearestNodes( vec3_t origin, int radius, int maxCollect, return collected; } -int CNavigator::GetBestPathBetweenEnts( sharedEntity_t *ent, sharedEntity_t *goal, int flags ) +int CNavigator::GetBestPathBetweenEnts( sharedEntityMapper_t *ent, sharedEntityMapper_t *goal, int flags ) { //Must have nodes if ( m_nodes.size() == 0 ) @@ -1340,8 +1340,8 @@ int CNavigator::GetBestPathBetweenEnts( sharedEntity_t *ent, sharedEntity_t *goa nodeChain_l::iterator nci2; //Collect all nodes within a certain radius - CollectNearestNodes( ent->r.currentOrigin, NODE_COLLECT_RADIUS, NODE_COLLECT_MAX, nodeChain ); - CollectNearestNodes( goal->r.currentOrigin, NODE_COLLECT_RADIUS, NODE_COLLECT_MAX, nodeChain2 ); + CollectNearestNodes( ent->r->currentOrigin, NODE_COLLECT_RADIUS, NODE_COLLECT_MAX, nodeChain ); + CollectNearestNodes( goal->r->currentOrigin, NODE_COLLECT_RADIUS, NODE_COLLECT_MAX, nodeChain2 ); vec3_t position; vec3_t position2; @@ -1353,8 +1353,8 @@ int CNavigator::GetBestPathBetweenEnts( sharedEntity_t *ent, sharedEntity_t *goa int nodeFlags = 0; // bool recalc = false; - ent->waypoint = NODE_NONE; - goal->waypoint = NODE_NONE; + *ent->waypoint = NODE_NONE; + *goal->waypoint = NODE_NONE; //Look through all nodes STL_ITERATE( nci, nodeChain ) @@ -1364,34 +1364,34 @@ int CNavigator::GetBestPathBetweenEnts( sharedEntity_t *ent, sharedEntity_t *goa node->GetPosition( position ); - if ( CheckedNode(nodeNum,ent->s.number) == CHECKED_FAILED ) + if ( CheckedNode(nodeNum,ent->s->number) == CHECKED_FAILED ) {//already checked this node against ent and it failed continue; } - if ( CheckedNode(nodeNum,ent->s.number) == CHECKED_PASSED ) + if ( CheckedNode(nodeNum,ent->s->number) == CHECKED_PASSED ) {//already checked this node against ent and it passed } else {//haven't checked this node against ent yet if ( NodeFailed( ent, nodeNum ) ) { - SetCheckedNode( nodeNum, ent->s.number, CHECKED_FAILED ); + SetCheckedNode( nodeNum, ent->s->number, CHECKED_FAILED ); continue; } //okay, since we only have to do this once, let's check to see if this node is even usable (could help us short-circuit a whole loop of the dest nodes) radius = node->GetRadius(); //If we're not within the known clear radius of this node OR out of Z height range... - if ( (int)(*nci).distance >= (radius*radius) || ( fabs( position[2] - ent->r.currentOrigin[2] ) >= MAX_Z_DELTA ) ) + if ( (int)(*nci).distance >= (radius*radius) || ( fabs( position[2] - ent->r->currentOrigin[2] ) >= MAX_Z_DELTA ) ) { //We're not *within* this node, so check clear path, etc. //FIXME: any way to call G_FindClosestPointOnLineSegment and see if I can at least get to the waypoint's path if ( flags & NF_CLEAR_PATH )//|| flags & NF_CLEAR_LOS ) {//need a clear path or LOS - if ( !SV_inPVS( ent->r.currentOrigin, position ) ) + if ( !SV_inPVS( ent->r->currentOrigin, position ) ) {//not even potentially clear - SetCheckedNode( nodeNum, ent->s.number, CHECKED_FAILED ); + SetCheckedNode( nodeNum, ent->s->number, CHECKED_FAILED ); continue; } } @@ -1399,14 +1399,14 @@ int CNavigator::GetBestPathBetweenEnts( sharedEntity_t *ent, sharedEntity_t *goa //Do we need a clear path? if ( flags & NF_CLEAR_PATH ) { - if ( TestNodePath( ent, goal->s.number, position, qtrue ) == false ) + if ( TestNodePath( ent, goal->s->number, position, qtrue ) == false ) { - SetCheckedNode( nodeNum, ent->s.number, CHECKED_FAILED ); + SetCheckedNode( nodeNum, ent->s->number, CHECKED_FAILED ); continue; } } }//otherwise, inside the node so it must be clear (?) - SetCheckedNode( nodeNum, ent->s.number, CHECKED_PASSED ); + SetCheckedNode( nodeNum, ent->s->number, CHECKED_PASSED ); } if ( d_altRoutes->integer ) @@ -1437,7 +1437,7 @@ int CNavigator::GetBestPathBetweenEnts( sharedEntity_t *ent, sharedEntity_t *goa node2->GetPosition( position2 ); //Okay, first get the entire path cost, including distance to first node from ents' positions - cost = floor(Distance( ent->r.currentOrigin, position ) + Distance( goal->r.currentOrigin, position2 )); + cost = floor(Distance( ent->r->currentOrigin, position ) + Distance( goal->r->currentOrigin, position2 )); if ( d_altRoutes->integer ) { @@ -1455,60 +1455,60 @@ int CNavigator::GetBestPathBetweenEnts( sharedEntity_t *ent, sharedEntity_t *goa } //okay, this is the shortest path we've found yet, check clear path, etc. - if ( CheckedNode( nodeNum2, goal->s.number ) == CHECKED_FAILED ) + if ( CheckedNode( nodeNum2, goal->s->number ) == CHECKED_FAILED ) {//already checked this node against goal and it failed continue; } - if ( CheckedNode( nodeNum2, goal->s.number ) == CHECKED_PASSED ) + if ( CheckedNode( nodeNum2, goal->s->number ) == CHECKED_PASSED ) {//already checked this node against goal and it passed } else {//haven't checked this node against goal yet if ( NodeFailed( goal, nodeNum2 ) ) { - SetCheckedNode( nodeNum2, goal->s.number, CHECKED_FAILED ); + SetCheckedNode( nodeNum2, goal->s->number, CHECKED_FAILED ); continue; } radius = node2->GetRadius(); //If we're not within the known clear radius of this node OR out of Z height range... - if ( (int)(*nci2).distance >= (radius*radius) || ( fabs( position2[2] - goal->r.currentOrigin[2] ) >= MAX_Z_DELTA ) ) + if ( (int)(*nci2).distance >= (radius*radius) || ( fabs( position2[2] - goal->r->currentOrigin[2] ) >= MAX_Z_DELTA ) ) { //We're not *within* this node, so check clear path, etc. if ( flags & NF_CLEAR_PATH )//|| flags & NF_CLEAR_LOS ) {//need a clear path or LOS - if ( !SV_inPVS( goal->r.currentOrigin, position2 ) ) + if ( !SV_inPVS( goal->r->currentOrigin, position2 ) ) {//not even potentially clear - SetCheckedNode( nodeNum2, goal->s.number, CHECKED_FAILED ); + SetCheckedNode( nodeNum2, goal->s->number, CHECKED_FAILED ); continue; } } //Do we need a clear path? if ( flags & NF_CLEAR_PATH ) { - if ( TestNodePath( goal, ent->s.number, position2, qfalse ) == false )//qtrue? + if ( TestNodePath( goal, ent->s->number, position2, qfalse ) == false )//qtrue? { - SetCheckedNode( nodeNum2, goal->s.number, CHECKED_FAILED ); + SetCheckedNode( nodeNum2, goal->s->number, CHECKED_FAILED ); continue; } } }//otherwise, inside the node so it must be clear (?) - SetCheckedNode( nodeNum2, goal->s.number, CHECKED_PASSED ); + SetCheckedNode( nodeNum2, goal->s->number, CHECKED_PASSED ); } bestCost = cost; bestNode = nextNode; - ent->waypoint = (*nci).nodeID; - goal->waypoint = (*nci2).nodeID; + *ent->waypoint = (*nci).nodeID; + *goal->waypoint = (*nci2).nodeID; } } if ( !d_altRoutes->integer ) {//bestNode would not have been set by GetBestNodeAltRoute above, so get it here - if ( ent->waypoint != NODE_NONE && goal->waypoint != NODE_NONE ) + if ( *ent->waypoint != NODE_NONE && *goal->waypoint != NODE_NONE ) {//have 2 valid waypoints which means a valid path - bestNode = GetBestNodeAltRoute( ent->waypoint, goal->waypoint, &bestCost, NODE_NONE ); + bestNode = GetBestNodeAltRoute( *ent->waypoint, *goal->waypoint, &bestCost, NODE_NONE ); } } return bestNode; @@ -1520,7 +1520,7 @@ GetNearestWaypoint ------------------------- */ -int CNavigator::GetNearestNode( sharedEntity_t *ent, int lastID, int flags, int targetID ) +int CNavigator::GetNearestNode( sharedEntityMapper_t *ent, int lastID, int flags, int targetID ) { int bestNode = NODE_NONE; //Must have nodes @@ -1546,7 +1546,7 @@ int CNavigator::GetNearestNode( sharedEntity_t *ent, int lastID, int flags, int nodeChain_l::iterator nci; //Collect all nodes within a certain radius - CollectNearestNodes( ent->r.currentOrigin, NODE_COLLECT_RADIUS, NODE_COLLECT_MAX, nodeChain ); + CollectNearestNodes( ent->r->currentOrigin, NODE_COLLECT_RADIUS, NODE_COLLECT_MAX, nodeChain ); vec3_t position; int radius; @@ -1570,7 +1570,7 @@ int CNavigator::GetNearestNode( sharedEntity_t *ent, int lastID, int flags, int if ( (int)(*nci).distance < (radius*radius) ) { //Do a z-difference sanity check - if ( fabs( position[2] - ent->r.currentOrigin[2] ) < MAX_Z_DELTA ) + if ( fabs( position[2] - ent->r->currentOrigin[2] ) < MAX_Z_DELTA ) { //Found one return (*nci).nodeID; @@ -1578,11 +1578,11 @@ int CNavigator::GetNearestNode( sharedEntity_t *ent, int lastID, int flags, int } //We're not *within* this node, so... - if ( CheckedNode((*nci).nodeID,ent->s.number) == CHECKED_FAILED ) + if ( CheckedNode((*nci).nodeID,ent->s->number) == CHECKED_FAILED ) { continue; } - else if ( CheckedNode((*nci).nodeID,ent->s.number) == CHECKED_FAILED ) + else if ( CheckedNode((*nci).nodeID,ent->s->number) == CHECKED_FAILED ) { continue; } @@ -1593,7 +1593,7 @@ int CNavigator::GetNearestNode( sharedEntity_t *ent, int lastID, int flags, int { if ( TestNodePath( ent, ENTITYNUM_NONE, position, qfalse ) == false )//qtrue? { - SetCheckedNode((*nci).nodeID,ent->s.number,CHECKED_FAILED); + SetCheckedNode((*nci).nodeID,ent->s->number,CHECKED_FAILED); continue; } } @@ -1604,12 +1604,12 @@ int CNavigator::GetNearestNode( sharedEntity_t *ent, int lastID, int flags, int { if ( TestNodeLOS( ent, position ) == false ) { - nodeChecked[(*nci).nodeID][ent->s.number] = CHECKED_FAILED; + nodeChecked[(*nci).nodeID][ent->s->number] = CHECKED_FAILED; continue; } } */ - SetCheckedNode((*nci).nodeID,ent->s.number,CHECKED_PASSED); + SetCheckedNode((*nci).nodeID,ent->s->number,CHECKED_PASSED); } if ( targetID != WAYPOINT_NONE ) @@ -1730,7 +1730,7 @@ void CNavigator::SetCheckedNode(int wayPoint,int ent,byte value) #define CHECK_FAILED_EDGE_INTERVAL 1000 #define CHECK_FAILED_EDGE_INTITIAL 5000//10000 -void CNavigator::CheckFailedNodes( sharedEntity_t *ent ) +void CNavigator::CheckFailedNodes( sharedEntityMapper_t *ent ) { vec3_t nodePos; int j; @@ -1739,56 +1739,56 @@ void CNavigator::CheckFailedNodes( sharedEntity_t *ent ) if ( m_nodes.size() == 0 ) return; - if ( ent->failedWaypointCheckTime && ent->failedWaypointCheckTime < svs.time ) + if ( *ent->failedWaypointCheckTime && *ent->failedWaypointCheckTime < svs.time ) { int failed = 0; //do this only once every 1 second for ( j = 0; j < MAX_FAILED_NODES; j++ ) { - if ( ent->failedWaypoints[j] != 0 ) + if ( (*ent->failedWaypoints)[j] != 0 ) { failed++; //-1 because 0 is a valid node but also the default, so we add one when we add one - m_nodes[ent->failedWaypoints[j]-1]->GetPosition( nodePos ); - if ( !GVM_NAV_ClearPathToPoint( ent->s.number, ent->r.mins, ent->r.maxs, nodePos, (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP), ENTITYNUM_NONE ) ) + m_nodes[(*ent->failedWaypoints)[j]-1]->GetPosition( nodePos ); + if ( !GVM_NAV_ClearPathToPoint( ent->s->number, ent->r->mins, ent->r->maxs, nodePos, (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP), ENTITYNUM_NONE ) ) {//no path clear of architecture, so clear this since we can't check against entities - ent->failedWaypoints[j] = 0; + (*ent->failedWaypoints)[j] = 0; failed--; } //have clear architectural path, now check against ents only - else if ( GVM_NAV_ClearPathToPoint( ent->s.number, ent->r.mins, ent->r.maxs, nodePos, CONTENTS_BODY, ENTITYNUM_NONE ) ) + else if ( GVM_NAV_ClearPathToPoint( ent->s->number, ent->r->mins, ent->r->maxs, nodePos, CONTENTS_BODY, ENTITYNUM_NONE ) ) {//clear of ents, too, so all clear, clear this one out - ent->failedWaypoints[j] = 0; + (*ent->failedWaypoints)[j] = 0; failed--; } } } if ( !failed ) { - ent->failedWaypointCheckTime = 0; + *ent->failedWaypointCheckTime = 0; } else { - ent->failedWaypointCheckTime = svs.time + CHECK_FAILED_EDGE_INTERVAL + Q_irand( 0, 1000 ); + *ent->failedWaypointCheckTime = svs.time + CHECK_FAILED_EDGE_INTERVAL + Q_irand( 0, 1000 ); } } } -void CNavigator::AddFailedNode( sharedEntity_t *ent, int nodeID ) +void CNavigator::AddFailedNode( sharedEntityMapper_t *ent, int nodeID ) { int j; for ( j = 0; j < MAX_FAILED_NODES; j++ ) { - if ( ent->failedWaypoints[j] == 0 ) + if ( (*ent->failedWaypoints)[j] == 0 ) { - ent->failedWaypoints[j] = nodeID+1;//+1 because 0 is the default value and that's a valid node, so we take the +1 out when we check the node above - if ( !ent->failedWaypointCheckTime ) + (*ent->failedWaypoints)[j] = nodeID+1;//+1 because 0 is the default value and that's a valid node, so we take the +1 out when we check the node above + if ( !*ent->failedWaypointCheckTime ) { - ent->failedWaypointCheckTime = svs.time + CHECK_FAILED_EDGE_INTITIAL; + *ent->failedWaypointCheckTime = svs.time + CHECK_FAILED_EDGE_INTITIAL; } return; } - if ( ent->failedWaypoints[j] == nodeID+1 ) + if ( (*ent->failedWaypoints)[j] == nodeID+1 ) {//already have this one marked as failed return; } @@ -1797,21 +1797,22 @@ void CNavigator::AddFailedNode( sharedEntity_t *ent, int nodeID ) {//ran out of failed nodes, get rid of first one, shift rest up for ( j = 0; j < MAX_FAILED_NODES-1; j++ ) { - ent->failedWaypoints[j] = ent->failedWaypoints[j+1]; + (*ent->failedWaypoints)[j] = (*ent->failedWaypoints)[j+1]; } } - ent->failedWaypoints[MAX_FAILED_NODES-1] = nodeID+1; - if ( !ent->failedWaypointCheckTime ) + (*ent->failedWaypoints)[MAX_FAILED_NODES-1] = nodeID+1; + if ( !*ent->failedWaypointCheckTime ) { - ent->failedWaypointCheckTime = svs.time + CHECK_FAILED_EDGE_INTITIAL; + *ent->failedWaypointCheckTime = svs.time + CHECK_FAILED_EDGE_INTITIAL; } } -qboolean CNavigator::NodeFailed( sharedEntity_t *ent, int nodeID ) +qboolean CNavigator::NodeFailed( sharedEntityMapper_t *ent, int nodeID ) { for ( int j = 0; j < MAX_FAILED_NODES; j++ ) { - if ( (ent->failedWaypoints[j]-1) == nodeID ) + //if ( ((*(ent->failedWaypoints))[j])-1) == nodeID ) + if ( ((((int*)(*(ent->failedWaypoints)))[j])-1) == nodeID ) // QVM-FIXME: Is this correct? { return qtrue; } @@ -2077,11 +2078,11 @@ qboolean CNavigator::CheckFailedEdge( failedEdge_t *failedEdge ) { vec3_t start, end, mins, maxs; int ignore, clipmask; - sharedEntity_t *ent = SV_GentityNum(failedEdge->entID); //(failedEdge->entIDentID]:NULL; + sharedEntityMapper_t *ent = SV_GentityMapperNum(failedEdge->entID); //(failedEdge->entIDentID]:NULL; int hitEntNum; - if ( !ent || /*!ent->inuse || !ent->client || ent->health <= 0*/ (ent->s.eType != ET_PLAYER && ent->s.eType != ET_NPC) || - (ent->s.eFlags & EF_DEAD)) + if ( !ent || /*!ent->inuse || !ent->client || ent->health <= 0*/ (ent->s->eType != ET_PLAYER && ent->s->eType != ET_NPC) || + (ent->s->eFlags & EF_DEAD)) { VectorSet( mins, -15, -15, DEFAULT_MINS_2+STEPSIZE ); VectorSet( maxs, 15, 15, DEFAULT_MAXS_2 ); @@ -2090,9 +2091,9 @@ qboolean CNavigator::CheckFailedEdge( failedEdge_t *failedEdge ) } else { - VectorCopy( ent->r.mins, mins ); + VectorCopy( ent->r->mins, mins ); mins[2] += STEPSIZE; - VectorCopy( ent->r.maxs, maxs ); + VectorCopy( ent->r->maxs, maxs ); ignore = failedEdge->entID; clipmask = MASK_SOLID;//ent->clipmask; //rwwFIXMEFIXME: share clipmask? } diff --git a/codemp/server/NPCNav/navigator.h b/codemp/server/NPCNav/navigator.h index d026322e12..4070602522 100644 --- a/codemp/server/NPCNav/navigator.h +++ b/codemp/server/NPCNav/navigator.h @@ -183,7 +183,7 @@ class CNavigator void ShowEdges( void ); void ShowPath( int start, int end ); - int GetNearestNode( sharedEntity_t *ent, int lastID, int flags, int targetID ); + int GetNearestNode( sharedEntityMapper_t *ent, int lastID, int flags, int targetID ); int GetBestNode( int startID, int endID, int rejectID = NODE_NONE ); @@ -201,9 +201,9 @@ class CNavigator int GetProjectedNode( vec3_t origin, int nodeID ); //MCG Added BEGIN - void CheckFailedNodes( sharedEntity_t *ent ); - void AddFailedNode( sharedEntity_t *ent, int nodeID ); - qboolean NodeFailed( sharedEntity_t *ent, int nodeID ); + void CheckFailedNodes( sharedEntityMapper_t *ent ); + void AddFailedNode( sharedEntityMapper_t *ent, int nodeID ); + qboolean NodeFailed( sharedEntityMapper_t *ent, int nodeID ); qboolean NodesAreNeighbors( int startID, int endID ); void ClearFailedEdge( failedEdge_t *failedEdge ); void ClearAllFailedEdges( void ); @@ -214,7 +214,7 @@ class CNavigator qboolean RouteBlocked( int startID, int testEdgeID, int endID, int rejectRank ); int GetBestNodeAltRoute( int startID, int endID, int *pathCost, int rejectID = NODE_NONE ); int GetBestNodeAltRoute( int startID, int endID, int rejectID = NODE_NONE ); - int GetBestPathBetweenEnts( sharedEntity_t *ent, sharedEntity_t *goal, int flags ); + int GetBestPathBetweenEnts( sharedEntityMapper_t *ent, sharedEntityMapper_t *goal, int flags ); int GetNodeRadius( int nodeID ); void CheckBlockedEdges( void ); void ClearCheckedNodes( void ); @@ -228,9 +228,9 @@ class CNavigator protected: - int TestNodePath( sharedEntity_t *ent, int okToHitEntNum, vec3_t position, qboolean includeEnts ); - int TestNodeLOS( sharedEntity_t *ent, vec3_t position ); - int TestBestFirst( sharedEntity_t *ent, int lastID, int flags ); + int TestNodePath( sharedEntityMapper_t *ent, int okToHitEntNum, vec3_t position, qboolean includeEnts ); + int TestNodeLOS( sharedEntityMapper_t *ent, vec3_t position ); + int TestBestFirst( sharedEntityMapper_t *ent, int lastID, int flags ); #if __NEWCOLLECT int CollectNearestNodes( vec3_t origin, int radius, int maxCollect, nodeChain_l &nodeChain ); diff --git a/codemp/server/server.h b/codemp/server/server.h index eaed8826e2..a8583ebe97 100644 --- a/codemp/server/server.h +++ b/codemp/server/server.h @@ -68,6 +68,8 @@ typedef struct server_s { char *entityParsePoint; // used during game VM init + sharedEntityMapper_t gentitiesMapper[MAX_GENTITIES]; + // the game virtual machine will update these on init and changes sharedEntity_t *gentities; int gentitySize; @@ -152,6 +154,7 @@ typedef struct client_s { int lastClientCommand; // reliable client message sequence char lastClientCommandString[MAX_STRING_CHARS]; sharedEntity_t *gentity; // SV_GentityNum(clientnum) + sharedEntityMapper_t *gentityMapper; char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked // downloading @@ -376,11 +379,16 @@ void SV_SendClientSnapshot( client_t *client ); // // sv_game.c // -int SV_NumForGentity( sharedEntity_t *ent ); +int SV_NumForGentity( const sharedEntity_t *ent ); +int SV_NumForGentityMapper( const sharedEntityMapper_t *ent ); sharedEntity_t *SV_GentityNum( int num ); +sharedEntityMapper_t *SV_GentityMapperNum( int num ); playerState_t *SV_GameClientNum( int num ); svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt ); +svEntity_t *SV_SvEntityForGentityMapper( sharedEntityMapper_t *gEnt ); sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ); +sharedEntityMapper_t *SV_GEntityMapperForSvEntity( svEntity_t *svEnt ); +sharedEntityMapper_t *SV_GEntityMapperForGentity( const sharedEntity_t *gEnt ); void SV_InitGameProgs ( void ); void SV_ShutdownGameProgs ( void ); qboolean SV_inPVS (const vec3_t p1, const vec3_t p2); @@ -389,6 +397,8 @@ CGhoul2Info_v *SV_G2Map_GetG2FromHandle( g2handleptr_t g2h ); CGhoul2Info_v **SV_G2Map_GetG2PtrFromHandle( g2handleptr_t g2h ); void SV_G2Map_Update( g2handleptr_t *g2h, CGhoul2Info_v *g2Ptr ); +sharedEntityMapper_t *ConvertedEntity( sharedEntity_t *ent ); + // // sv_bot.c // @@ -414,11 +424,11 @@ void BotImport_DebugPolygonDelete(int id); void SV_ClearWorld (void); // called after the world model has been loaded, before linking any entities -void SV_UnlinkEntity( sharedEntity_t *ent ); +void SV_UnlinkEntity( sharedEntityMapper_t *ent ); // call before removing an entity, and before trying to move one, // so it doesn't clip against itself -void SV_LinkEntity( sharedEntity_t *ent ); +void SV_LinkEntity( sharedEntityMapper_t *ent ); // Needs to be called any time an entity changes origin, mins, maxs, // or solid. Automatically unlinks if needed. // sets ent->v.absmin and ent->v.absmax @@ -426,7 +436,7 @@ void SV_LinkEntity( sharedEntity_t *ent ); // is not solid -clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent ); +clipHandle_t SV_ClipHandleForEntity( const sharedEntityMapper_t *ent ); void SV_SectorList_f( void ); diff --git a/codemp/server/sv_bot.cpp b/codemp/server/sv_bot.cpp index 3dfa473a63..3518c20e5b 100644 --- a/codemp/server/sv_bot.cpp +++ b/codemp/server/sv_bot.cpp @@ -202,7 +202,8 @@ int SV_BotAllocateClient(void) { } cl->gentity = SV_GentityNum( i ); - cl->gentity->s.number = i; + cl->gentityMapper = SV_GentityMapperNum( i ); + cl->gentityMapper->s->number = i; cl->state = CS_ACTIVE; cl->lastPacketTime = svs.time; cl->netchan.remoteAddress.type = NA_BOT; @@ -226,8 +227,8 @@ void SV_BotFreeClient( int clientNum ) { cl = &svs.clients[clientNum]; cl->state = CS_FREE; cl->name[0] = 0; - if ( cl->gentity ) { - cl->gentity->r.svFlags &= ~SVF_BOT; + if ( cl->gentityMapper ) { + cl->gentityMapper->r->svFlags &= ~SVF_BOT; } if ( cl->demo.demorecording ) { diff --git a/codemp/server/sv_client.cpp b/codemp/server/sv_client.cpp index 073fbbe6a4..ac444e16e1 100644 --- a/codemp/server/sv_client.cpp +++ b/codemp/server/sv_client.cpp @@ -133,7 +133,6 @@ void SV_DirectConnect( netadr_t from ) { int i; client_t *cl, *newcl; client_t temp; - sharedEntity_t *ent; int clientNum; int version; int qport; @@ -303,8 +302,8 @@ void SV_DirectConnect( netadr_t from ) { // this is the only place a client_t is ever initialized *newcl = temp; clientNum = newcl - svs.clients; - ent = SV_GentityNum( clientNum ); - newcl->gentity = ent; + newcl->gentity = SV_GentityNum( clientNum ); + newcl->gentityMapper = SV_GentityMapperNum( clientNum ); // save the challenge newcl->challenge = challenge; @@ -547,7 +546,6 @@ SV_ClientEnterWorld */ void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) { int clientNum; - sharedEntity_t *ent; Com_DPrintf( "Going from CS_PRIMED to CS_ACTIVE for %s\n", client->name ); client->state = CS_ACTIVE; @@ -562,9 +560,9 @@ void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) { // set up the entity for the client clientNum = client - svs.clients; - ent = SV_GentityNum( clientNum ); - ent->s.number = clientNum; - client->gentity = ent; + client->gentity = SV_GentityNum( clientNum ); + client->gentityMapper = SV_GentityMapperNum( clientNum ); + client->gentityMapper->s->number = clientNum; client->lastUserInfoChange = 0; //reset the delay client->lastUserInfoCount = 0; //reset the count diff --git a/codemp/server/sv_game.cpp b/codemp/server/sv_game.cpp index 807394ed2e..22e08a046f 100644 --- a/codemp/server/sv_game.cpp +++ b/codemp/server/sv_game.cpp @@ -35,7 +35,7 @@ along with this program; if not, see . // these functions must be used instead of pointer arithmetic, because // the game allocates gentities with private information after the server shared part -int SV_NumForGentity( sharedEntity_t *ent ) { +int SV_NumForGentity( const sharedEntity_t *ent ) { int num; num = ( (byte *)ent - (byte *)sv.gentities ) / sv.gentitySize; @@ -43,6 +43,10 @@ int SV_NumForGentity( sharedEntity_t *ent ) { return num; } +int SV_NumForGentityMapper( const sharedEntityMapper_t *ent ) { + return ent - sv.gentitiesMapper; +} + sharedEntity_t *SV_GentityNum( int num ) { sharedEntity_t *ent; @@ -51,6 +55,11 @@ sharedEntity_t *SV_GentityNum( int num ) { return ent; } +sharedEntityMapper_t *SV_GentityMapperNum( int num ) { + if ( num < 0 || num >= (int)ARRAY_LEN(sv.gentitiesMapper) ) return NULL; + return &sv.gentitiesMapper[num]; +} + playerState_t *SV_GameClientNum( int num ) { playerState_t *ps; @@ -66,6 +75,13 @@ svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt ) { return &sv.svEntities[ gEnt->s.number ]; } +svEntity_t *SV_SvEntityForGentityMapper( sharedEntityMapper_t *gEnt ) { + if ( !gEnt || gEnt->s->number < 0 || gEnt->s->number >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); + } + return &sv.svEntities[ gEnt->s->number ]; +} + sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ) { int num; @@ -73,6 +89,17 @@ sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ) { return SV_GentityNum( num ); } +sharedEntityMapper_t *SV_GEntityMapperForSvEntity( svEntity_t *svEnt ) { + int num; + + num = svEnt - sv.svEntities; + return SV_GentityMapperNum( num ); +} + +sharedEntityMapper_t *SV_GEntityMapperForGentity( const sharedEntity_t *gEnt ) { + return SV_GentityMapperNum( SV_NumForGentity(gEnt) ); +} + /* ================= SV_inPVS diff --git a/codemp/server/sv_gameapi.cpp b/codemp/server/sv_gameapi.cpp index 6870759dbf..d6b89bac97 100644 --- a/codemp/server/sv_gameapi.cpp +++ b/codemp/server/sv_gameapi.cpp @@ -544,9 +544,9 @@ int CM_ModelContents( clipHandle_t model, int subBSPIndex ); int CM_LoadSubBSP( const char *name, qboolean clientload ); int CM_FindSubBSP( int modelIndex ); char *CM_SubBSPEntityString( int index ); -qboolean Q3_TaskIDPending( sharedEntity_t *ent, taskID_t taskType ); -void Q3_TaskIDSet( sharedEntity_t *ent, taskID_t taskType, int taskID ); -void Q3_TaskIDComplete( sharedEntity_t *ent, taskID_t taskType ); +qboolean Q3_TaskIDPending( sharedEntityMapper_t *ent, taskID_t taskType ); +void Q3_TaskIDSet( sharedEntityMapper_t *ent, taskID_t taskType, int taskID ); +void Q3_TaskIDComplete( sharedEntityMapper_t *ent, taskID_t taskType ); void Q3_SetVar( int taskID, int entID, const char *type_name, const char *data ); int Q3_VariableDeclared( const char *name ); int Q3_GetFloatVariable( const char *name, float *value ); @@ -555,13 +555,92 @@ int Q3_GetVectorVariable( const char *name, vec3_t value ); void SV_BotWaypointReception( int wpnum, wpobject_t **wps ); void SV_BotCalculatePaths( int rmg ); +static void SV_UpdateSharedEntitiesMapping( void ) { + int i; + int entCount = Com_Clampi( 0, ARRAY_LEN(sv.gentitiesMapper), sv.num_entities ); + sharedEntityMapper_t *entM; + + if ( gvm->dllHandle ) { + sharedEntity_t *ent; + for ( i = 0; i < entCount; i++ ) { + // Get the shared entity and the mapper + ent = (sharedEntity_t *)((byte *)sv.gentities + sv.gentitySize*(i)); + entM = &sv.gentitiesMapper[i]; + + // Assign all values + entM->s = &ent->s; + entM->playerState = &ent->playerState; + entM->m_pVehicle = &ent->m_pVehicle; + entM->ghoul2 = &ent->ghoul2; + entM->localAnimIndex = &ent->localAnimIndex; + entM->modelScale = &ent->modelScale; + entM->r = &ent->r; + entM->taskID = &ent->taskID; + entM->parms = &ent->parms; + entM->behaviorSet = &ent->behaviorSet; + entM->script_targetname = &ent->script_targetname; + entM->delayScriptTime = &ent->delayScriptTime; + entM->fullName = &ent->fullName; + entM->targetname = &ent->targetname; + entM->classname = &ent->classname; + entM->waypoint = &ent->waypoint; + entM->lastWaypoint = &ent->lastWaypoint; + entM->lastValidWaypoint = &ent->lastValidWaypoint; + entM->noWaypointTime = &ent->noWaypointTime; + entM->combatPoint = &ent->combatPoint; + entM->failedWaypoints = &ent->failedWaypoints; + entM->failedWaypointCheckTime = &ent->failedWaypointCheckTime; + entM->next_roff_time = &ent->next_roff_time; + } + } else { + sharedEntity_qvm_t *ent; + for ( i = 0; i < entCount; i++ ) { + // Get the shared entity and the mapper + ent = (sharedEntity_qvm_t *)((byte *)sv.gentities + sv.gentitySize*(i)); + entM = &sv.gentitiesMapper[i]; + + // Assign all values + entM->s = &ent->s; + entM->playerState = (playerState_t**)&ent->playerState; +#if (!defined(MACOS_X) && !defined(__GCC__) && !defined(__GNUC__)) + entM->m_pVehicle = (Vehicle_t**)&ent->m_pVehicle; +#else + entM->m_pVehicle = (struct Vehicle_s**)&ent->m_pVehicle; +#endif + entM->ghoul2 = (void**)&ent->ghoul2; + entM->localAnimIndex = &ent->localAnimIndex; + entM->modelScale = &ent->modelScale; + entM->r = &ent->r; + entM->taskID = &ent->taskID; + entM->parms = (parms_t**)&ent->parms; + entM->behaviorSet = (char*(*)[NUM_BSETS])&ent->behaviorSet; + entM->script_targetname = (char**)&ent->script_targetname; + entM->delayScriptTime = &ent->delayScriptTime; + entM->fullName = (char**)&ent->fullName; + entM->targetname = (char**)&ent->targetname; + entM->classname = (char**)&ent->classname; + entM->waypoint = &ent->waypoint; + entM->lastWaypoint = &ent->lastWaypoint; + entM->lastValidWaypoint = &ent->lastValidWaypoint; + entM->noWaypointTime = &ent->noWaypointTime; + entM->combatPoint = &ent->combatPoint; + entM->failedWaypoints = &ent->failedWaypoints; + entM->failedWaypointCheckTime = &ent->failedWaypointCheckTime; + entM->next_roff_time = &ent->next_roff_time; + } + } +} + static void SV_LocateGameData( sharedEntity_t *gEnts, int numGEntities, int sizeofGEntity_t, playerState_t *clients, int sizeofGameClient ) { + sv.gentities = gEnts; sv.gentitySize = sizeofGEntity_t; sv.num_entities = numGEntities; sv.gameClients = clients; sv.gameClientSize = sizeofGameClient; + + SV_UpdateSharedEntitiesMapping(); } static void SV_GameDropClient( int clientNum, const char *reason ) { @@ -587,11 +666,13 @@ static qboolean SV_EntityContact( const vec3_t mins, const vec3_t maxs, const sh clipHandle_t ch; trace_t trace; + sharedEntityMapper_t *gEntM = SV_GEntityMapperForGentity( gEnt ); + // check for exact collision - origin = gEnt->r.currentOrigin; - angles = gEnt->r.currentAngles; + origin = gEntM->r->currentOrigin; + angles = gEntM->r->currentAngles; - ch = SV_ClipHandleForEntity( gEnt ); + ch = SV_ClipHandleForEntity( gEntM ); CM_TransformedBoxTrace ( &trace, vec3_origin, vec3_origin, mins, maxs, ch, -1, origin, angles, capsule ); @@ -602,6 +683,8 @@ static void SV_SetBrushModel( sharedEntity_t *ent, const char *name ) { clipHandle_t h; vec3_t mins, maxs; + sharedEntityMapper_t *gEntM = SV_GEntityMapperForGentity( ent ); + if (!name) { Com_Error( ERR_DROP, "SV_SetBrushModel: NULL" ); @@ -609,36 +692,36 @@ static void SV_SetBrushModel( sharedEntity_t *ent, const char *name ) { if (name[0] == '*') { - ent->s.modelindex = atoi( name + 1 ); + gEntM->s->modelindex = atoi( name + 1 ); if (sv.mLocalSubBSPIndex != -1) { - ent->s.modelindex += sv.mLocalSubBSPModelOffset; + gEntM->s->modelindex += sv.mLocalSubBSPModelOffset; } - h = CM_InlineModel( ent->s.modelindex ); + h = CM_InlineModel( gEntM->s->modelindex ); CM_ModelBounds(h, mins, maxs); - VectorCopy (mins, ent->r.mins); - VectorCopy (maxs, ent->r.maxs); - ent->r.bmodel = qtrue; - ent->r.contents = CM_ModelContents( h, -1 ); + VectorCopy (mins, gEntM->r->mins); + VectorCopy (maxs, gEntM->r->maxs); + gEntM->r->bmodel = qtrue; + gEntM->r->contents = CM_ModelContents( h, -1 ); } else if (name[0] == '#') { - ent->s.modelindex = CM_LoadSubBSP(va("maps/%s.bsp", name + 1), qfalse); - CM_ModelBounds( ent->s.modelindex, mins, maxs ); + gEntM->s->modelindex = CM_LoadSubBSP(va("maps/%s.bsp", name + 1), qfalse); + CM_ModelBounds( gEntM->s->modelindex, mins, maxs ); - VectorCopy (mins, ent->r.mins); - VectorCopy (maxs, ent->r.maxs); - ent->r.bmodel = qtrue; + VectorCopy (mins, gEntM->r->mins); + VectorCopy (maxs, gEntM->r->maxs); + gEntM->r->bmodel = qtrue; //rwwNOTE: We don't ever want to set contents -1, it includes CONTENTS_LIGHTSABER. //Lots of stuff will explode if there's a brush with CONTENTS_LIGHTSABER that isn't attached to a client owner. //ent->contents = -1; // we don't know exactly what is in the brushes - h = CM_InlineModel( ent->s.modelindex ); - ent->r.contents = CM_ModelContents( h, CM_FindSubBSP(ent->s.modelindex) ); + h = CM_InlineModel( gEntM->s->modelindex ); + gEntM->r->contents = CM_ModelContents( h, CM_FindSubBSP(gEntM->s->modelindex) ); } else { @@ -693,35 +776,62 @@ static void SV_GetUsercmd( int clientNum, usercmd_t *cmd ) { } static sharedEntity_t gLocalModifier; -static sharedEntity_t *ConvertedEntity( sharedEntity_t *ent ) { //Return an entity with the memory shifted around to allow reading/modifying VM memory +static sharedEntityMapper_t gLocalModifierMapper; +sharedEntityMapper_t *ConvertedEntity( sharedEntity_t *ent ) { //Return an entity with the memory shifted around to allow reading/modifying VM memory int i = 0; + sharedEntityMapper_t *gEntM = SV_GEntityMapperForGentity( ent ); + assert(ent); - gLocalModifier.s = ent->s; - gLocalModifier.r = ent->r; + gLocalModifier.s = *(gEntM->s); + gLocalModifier.r = *(gEntM->r); while (i < NUM_TIDS) { - gLocalModifier.taskID[i] = ent->taskID[i]; + gLocalModifier.taskID[i] = (*(gEntM->taskID))[i]; i++; } i = 0; - gLocalModifier.parms = (parms_t *)VM_ArgPtr((intptr_t)ent->parms); + gLocalModifier.parms = (parms_t *)VM_ArgPtr((intptr_t)(*(gEntM->parms))); while (i < NUM_BSETS) { - gLocalModifier.behaviorSet[i] = (char *)VM_ArgPtr((intptr_t)ent->behaviorSet[i]); + gLocalModifier.behaviorSet[i] = (char *)VM_ArgPtr((intptr_t)(*(gEntM->behaviorSet))[i]); i++; } i = 0; - gLocalModifier.script_targetname = (char *)VM_ArgPtr((intptr_t)ent->script_targetname); - gLocalModifier.delayScriptTime = ent->delayScriptTime; - gLocalModifier.fullName = (char *)VM_ArgPtr((intptr_t)ent->fullName); - gLocalModifier.targetname = (char *)VM_ArgPtr((intptr_t)ent->targetname); - gLocalModifier.classname = (char *)VM_ArgPtr((intptr_t)ent->classname); - - gLocalModifier.ghoul2 = ent->ghoul2; - - return &gLocalModifier; + gLocalModifier.script_targetname = (char *)VM_ArgPtr((intptr_t)(*(gEntM->script_targetname))); + gLocalModifier.delayScriptTime = (*(gEntM->delayScriptTime)); + gLocalModifier.fullName = (char *)VM_ArgPtr((intptr_t)(*(gEntM->fullName))); + gLocalModifier.targetname = (char *)VM_ArgPtr((intptr_t)(*(gEntM->targetname))); + gLocalModifier.classname = (char *)VM_ArgPtr((intptr_t)(*(gEntM->classname))); + + gLocalModifier.ghoul2 = (*(gEntM->ghoul2)); + + gLocalModifierMapper.s = &gLocalModifier.s; + gLocalModifierMapper.playerState = &gLocalModifier.playerState; + gLocalModifierMapper.m_pVehicle = &gLocalModifier.m_pVehicle; + gLocalModifierMapper.ghoul2 = &gLocalModifier.ghoul2; + gLocalModifierMapper.localAnimIndex = &gLocalModifier.localAnimIndex; + gLocalModifierMapper.modelScale = &gLocalModifier.modelScale; + gLocalModifierMapper.r = &gLocalModifier.r; + gLocalModifierMapper.taskID = &gLocalModifier.taskID; + gLocalModifierMapper.parms = &gLocalModifier.parms; + gLocalModifierMapper.behaviorSet = &gLocalModifier.behaviorSet; + gLocalModifierMapper.script_targetname = &gLocalModifier.script_targetname; + gLocalModifierMapper.delayScriptTime = &gLocalModifier.delayScriptTime; + gLocalModifierMapper.fullName = &gLocalModifier.fullName; + gLocalModifierMapper.targetname = &gLocalModifier.targetname; + gLocalModifierMapper.classname = &gLocalModifier.classname; + gLocalModifierMapper.waypoint = &gLocalModifier.waypoint; + gLocalModifierMapper.lastWaypoint = &gLocalModifier.lastWaypoint; + gLocalModifierMapper.lastValidWaypoint = &gLocalModifier.lastValidWaypoint; + gLocalModifierMapper.noWaypointTime = &gLocalModifier.noWaypointTime; + gLocalModifierMapper.combatPoint = &gLocalModifier.combatPoint; + gLocalModifierMapper.failedWaypoints = &gLocalModifier.failedWaypoints; + gLocalModifierMapper.failedWaypointCheckTime = &gLocalModifier.failedWaypointCheckTime; + gLocalModifierMapper.next_roff_time = &gLocalModifier.next_roff_time; + + return &gLocalModifierMapper; } static const char *SV_SetActiveSubBSP( int index ) { @@ -812,7 +922,7 @@ static qboolean SV_ICARUS_RegisterScript( const char *name, qboolean bCalledDuri } static qboolean SV_ICARUS_ValidEnt( sharedEntity_t *ent ) { - return (qboolean)ICARUS_ValidEnt( ent ); + return (qboolean)ICARUS_ValidEnt( SV_GEntityMapperForGentity(ent) ); } static qboolean ICARUS_IsInitialized( int entID ) { @@ -837,15 +947,15 @@ static qboolean ICARUS_IsRunning( int entID ) { } static qboolean ICARUS_TaskIDPending( sharedEntity_t *ent, int taskID ) { - return Q3_TaskIDPending( ent, (taskID_t)taskID ); + return Q3_TaskIDPending( SV_GEntityMapperForGentity(ent), (taskID_t)taskID ); } static void SV_ICARUS_TaskIDSet( sharedEntity_t *ent, int taskType, int taskID ) { - Q3_TaskIDSet( ent, (taskID_t)taskType, taskID ); + Q3_TaskIDSet( SV_GEntityMapperForGentity(ent), (taskID_t)taskType, taskID ); } static void SV_ICARUS_TaskIDComplete( sharedEntity_t *ent, int taskType ) { - Q3_TaskIDComplete( ent, (taskID_t)taskType ); + Q3_TaskIDComplete( SV_GEntityMapperForGentity(ent), (taskID_t)taskType ); } static int SV_ICARUS_GetStringVariable( const char *name, const char *value ) { @@ -897,7 +1007,7 @@ static void SV_Nav_ShowPath( int start, int end ) { } static int SV_Nav_GetNearestNode( sharedEntity_t *ent, int lastID, int flags, int targetID ) { - return navigator.GetNearestNode( ent, lastID, flags, targetID ); + return navigator.GetNearestNode( SV_GEntityMapperForGentity(ent), lastID, flags, targetID ); } static int SV_Nav_GetBestNode( int startID, int endID, int rejectID ) { @@ -937,15 +1047,15 @@ static int SV_Nav_GetProjectedNode( vec3_t origin, int nodeID ) { } static void SV_Nav_CheckFailedNodes( sharedEntity_t *ent ) { - navigator.CheckFailedNodes( ent ); + navigator.CheckFailedNodes( SV_GEntityMapperForGentity(ent) ); } static void SV_Nav_AddFailedNode( sharedEntity_t *ent, int nodeID ) { - navigator.AddFailedNode( ent, nodeID ); + navigator.AddFailedNode( SV_GEntityMapperForGentity(ent), nodeID ); } static qboolean SV_Nav_NodeFailed( sharedEntity_t *ent, int nodeID ) { - return navigator.NodeFailed( ent, nodeID ); + return navigator.NodeFailed( SV_GEntityMapperForGentity(ent), nodeID ); } static qboolean SV_Nav_NodesAreNeighbors( int startID, int endID ) { @@ -989,7 +1099,7 @@ static int SV_Nav_GetBestNodeAltRoute2( int startID, int endID, int rejectID ) { } static int SV_Nav_GetBestPathBetweenEnts( sharedEntity_t *ent, sharedEntity_t *goal, int flags ) { - return navigator.GetBestPathBetweenEnts( ent, goal, flags ); + return navigator.GetBestPathBetweenEnts( SV_GEntityMapperForGentity(ent), SV_GEntityMapperForGentity(goal), flags ); } static int SV_Nav_GetNodeRadius( int nodeID ) { @@ -1897,6 +2007,27 @@ static void GVM_Cvar_Set( const char *var_name, const char *value ) { Cvar_VM_Set( var_name, value, VM_GAME ); } +static void GVM_LinkEntity( sharedEntity_t *gEnt ) { + SV_LinkEntity( SV_GEntityMapperForGentity(gEnt) ); +} + +static void GVM_UnlinkEntity( sharedEntity_t *gEnt ) { + SV_UnlinkEntity( SV_GEntityMapperForGentity(gEnt) ); +} + +int SV_ICARUS_RunScript( sharedEntity_t *ent, const char *name ) { + return ICARUS_RunScript( SV_GEntityMapperForGentity(ent), name ); +} +void SV_ICARUS_InitEnt( sharedEntity_t *ent ) { + ICARUS_InitEnt( SV_GEntityMapperForGentity(ent) ); +} +void SV_ICARUS_FreeEnt( sharedEntity_t *ent ) { + ICARUS_FreeEnt( SV_GEntityMapperForGentity(ent) ); +} +void SV_ICARUS_AssociateEnt( sharedEntity_t *ent ) { + ICARUS_AssociateEnt( SV_GEntityMapperForGentity(ent) ); +} + // legacy syscall intptr_t SV_GameSystemCalls( intptr_t *args ) { @@ -2038,11 +2169,11 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { return 0; case G_LINKENTITY: - SV_LinkEntity( (sharedEntity_t *)VMA(1) ); + SV_LinkEntity( SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1)) ); return 0; case G_UNLINKENTITY: - SV_UnlinkEntity( (sharedEntity_t *)VMA(1) ); + SV_UnlinkEntity( SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1)) ); return 0; case G_ENTITIES_IN_BOX: @@ -2203,7 +2334,7 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { return ICARUS_IsRunning( args[1] ); case G_ICARUS_TASKIDPENDING: - return Q3_TaskIDPending((sharedEntity_t *)VMA(1), (taskID_t)args[2]); + return Q3_TaskIDPending(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1)), (taskID_t)args[2]); case G_ICARUS_INITENT: ICARUS_InitEnt(ConvertedEntity((sharedEntity_t *)VMA(1))); @@ -2225,12 +2356,12 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { //rww - note that we are passing in the true entity here. //This is because we allow modification of certain non-pointer values, //which is valid. - Q3_TaskIDSet((sharedEntity_t *)VMA(1), (taskID_t)args[2], args[3]); + Q3_TaskIDSet(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1)), (taskID_t)args[2], args[3]); return 0; case G_ICARUS_TASKIDCOMPLETE: //same as above. - Q3_TaskIDComplete((sharedEntity_t *)VMA(1), (taskID_t)args[2]); + Q3_TaskIDComplete(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1)), (taskID_t)args[2]); return 0; case G_ICARUS_SETVAR: @@ -2282,7 +2413,7 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { navigator.ShowPath(args[1], args[2]); return 0; case G_NAV_GETNEARESTNODE: - return navigator.GetNearestNode((sharedEntity_t *)VMA(1), args[2], args[3], args[4]); + return navigator.GetNearestNode(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1)), args[2], args[3], args[4]); case G_NAV_GETBESTNODE: return navigator.GetBestNode(args[1], args[2], args[3]); case G_NAV_GETNODEPOSITION: @@ -2302,13 +2433,13 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { case G_NAV_GETPROJECTEDNODE: return navigator.GetProjectedNode((float *)VMA(1), args[2]); case G_NAV_CHECKFAILEDNODES: - navigator.CheckFailedNodes((sharedEntity_t *)VMA(1)); + navigator.CheckFailedNodes(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1))); return 0; case G_NAV_ADDFAILEDNODE: - navigator.AddFailedNode((sharedEntity_t *)VMA(1), args[2]); + navigator.AddFailedNode(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1)), args[2]); return 0; case G_NAV_NODEFAILED: - return navigator.NodeFailed((sharedEntity_t *)VMA(1), args[2]); + return navigator.NodeFailed(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1)), args[2]); case G_NAV_NODESARENEIGHBORS: return navigator.NodesAreNeighbors(args[1], args[2]); case G_NAV_CLEARFAILEDEDGE: @@ -2334,7 +2465,7 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { case G_NAV_GETBESTNODEALT2: return navigator.GetBestNodeAltRoute(args[1], args[2], args[3]); case G_NAV_GETBESTPATHBETWEENENTS: - return navigator.GetBestPathBetweenEnts((sharedEntity_t *)VMA(1), (sharedEntity_t *)VMA(2), args[3]); + return navigator.GetBestPathBetweenEnts(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1)), SV_GEntityMapperForGentity((sharedEntity_t *)VMA(2)), args[3]); case G_NAV_GETNODERADIUS: return navigator.GetNodeRadius(args[1]); case G_NAV_CHECKBLOCKEDEDGES: @@ -2942,8 +3073,10 @@ void SV_InitGame( qboolean restart ) { // clear level pointers sv.entityParsePoint = CM_EntityString(); - for ( i=0, cl=svs.clients; iinteger; i++, cl++ ) + for ( i=0, cl=svs.clients; iinteger; i++, cl++ ) { cl->gentity = NULL; + cl->gentityMapper = NULL; + } GVM_InitGame( sv.time, Com_Milliseconds(), restart ); } @@ -2995,7 +3128,7 @@ void SV_BindGame( void ) { gi.GetUserinfo = SV_GetUserinfo; gi.InPVS = SV_inPVS; gi.InPVSIgnorePortals = SV_inPVSIgnorePortals; - gi.LinkEntity = SV_LinkEntity; + gi.LinkEntity = GVM_LinkEntity; gi.LocateGameData = SV_LocateGameData; gi.PointContents = SV_PointContents; gi.SendConsoleCommand = Cbuf_ExecuteText; @@ -3006,13 +3139,13 @@ void SV_BindGame( void ) { gi.SetUserinfo = SV_SetUserinfo; gi.SiegePersSet = SV_SiegePersSet; gi.SiegePersGet = SV_SiegePersGet; - gi.UnlinkEntity = SV_UnlinkEntity; + gi.UnlinkEntity = GVM_UnlinkEntity; gi.ROFF_Clean = SV_ROFF_Clean; gi.ROFF_UpdateEntities = SV_ROFF_UpdateEntities; gi.ROFF_Cache = SV_ROFF_Cache; gi.ROFF_Play = SV_ROFF_Play; gi.ROFF_Purge_Ent = SV_ROFF_Purge_Ent; - gi.ICARUS_RunScript = ICARUS_RunScript; + gi.ICARUS_RunScript = SV_ICARUS_RunScript; gi.ICARUS_RegisterScript = SV_ICARUS_RegisterScript; gi.ICARUS_Init = ICARUS_Init; gi.ICARUS_ValidEnt = SV_ICARUS_ValidEnt; @@ -3020,9 +3153,9 @@ void SV_BindGame( void ) { gi.ICARUS_MaintainTaskManager = ICARUS_MaintainTaskManager; gi.ICARUS_IsRunning = ICARUS_IsRunning; gi.ICARUS_TaskIDPending = ICARUS_TaskIDPending; - gi.ICARUS_InitEnt = ICARUS_InitEnt; - gi.ICARUS_FreeEnt = ICARUS_FreeEnt; - gi.ICARUS_AssociateEnt = ICARUS_AssociateEnt; + gi.ICARUS_InitEnt = SV_ICARUS_InitEnt; + gi.ICARUS_FreeEnt = SV_ICARUS_FreeEnt; + gi.ICARUS_AssociateEnt = SV_ICARUS_AssociateEnt; gi.ICARUS_Shutdown = ICARUS_Shutdown; gi.ICARUS_TaskIDSet = SV_ICARUS_TaskIDSet; gi.ICARUS_TaskIDComplete = SV_ICARUS_TaskIDComplete; diff --git a/codemp/server/sv_init.cpp b/codemp/server/sv_init.cpp index be62b91bf1..f493edb004 100644 --- a/codemp/server/sv_init.cpp +++ b/codemp/server/sv_init.cpp @@ -94,8 +94,8 @@ void SV_UpdateConfigstrings(client_t *client) continue; // do not always send server info to all clients - if ( index == CS_SERVERINFO && client->gentity && - (client->gentity->r.svFlags & SVF_NOSERVERINFO) ) { + if ( index == CS_SERVERINFO && client->gentityMapper && + (client->gentityMapper->r->svFlags & SVF_NOSERVERINFO) ) { continue; } SV_SendConfigstring(client, index); @@ -142,7 +142,7 @@ void SV_SetConfigstring (int index, const char *val) { continue; } // do not always send server info to all clients - if ( index == CS_SERVERINFO && client->gentity && (client->gentity->r.svFlags & SVF_NOSERVERINFO) ) { + if ( index == CS_SERVERINFO && client->gentityMapper && (client->gentityMapper->r->svFlags & SVF_NOSERVERINFO) ) { continue; } @@ -217,20 +217,20 @@ baseline will be transmitted ================ */ void SV_CreateBaseline( void ) { - sharedEntity_t *svent; + sharedEntityMapper_t *svent; int entnum; for ( entnum = 1; entnum < sv.num_entities ; entnum++ ) { - svent = SV_GentityNum(entnum); - if (!svent->r.linked) { + svent = SV_GentityMapperNum(entnum); + if (!svent->r->linked) { continue; } - svent->s.number = entnum; + svent->s->number = entnum; // // take current state as baseline // - sv.svEntities[entnum].baseline = svent->s; + sv.svEntities[entnum].baseline = *(svent->s); } } @@ -668,13 +668,12 @@ Ghoul2 Insert End } else { client_t *client; - sharedEntity_t *ent; client = &svs.clients[i]; client->state = CS_ACTIVE; - ent = SV_GentityNum( i ); - ent->s.number = i; - client->gentity = ent; + client->gentityMapper = SV_GentityMapperNum( i ); + client->gentityMapper->s->number = i; + client->gentity = SV_GentityNum( i ); client->deltaMessage = -1; client->nextSnapshotTime = svs.time; // generate a snapshot immediately diff --git a/codemp/server/sv_main.cpp b/codemp/server/sv_main.cpp index 1bb2f7daf6..2992beda68 100644 --- a/codemp/server/sv_main.cpp +++ b/codemp/server/sv_main.cpp @@ -861,11 +861,11 @@ void SV_CalcPings( void ) { cl->ping = 999; continue; } - if ( !cl->gentity ) { + if ( !cl->gentityMapper ) { cl->ping = 999; continue; } - if ( cl->gentity->r.svFlags & SVF_BOT ) { + if ( cl->gentityMapper->r->svFlags & SVF_BOT ) { cl->ping = 0; continue; } diff --git a/codemp/server/sv_snapshot.cpp b/codemp/server/sv_snapshot.cpp index b8515bf376..9fd6af93ad 100644 --- a/codemp/server/sv_snapshot.cpp +++ b/codemp/server/sv_snapshot.cpp @@ -339,7 +339,7 @@ static int QDECL SV_QsortEntityNumbers( const void *a, const void *b ) { SV_AddEntToSnapshot =============== */ -static void SV_AddEntToSnapshot( svEntity_t *svEnt, sharedEntity_t *gEnt, snapshotEntityNumbers_t *eNums ) { +static void SV_AddEntToSnapshot( svEntity_t *svEnt, sharedEntityMapper_t *gEnt, snapshotEntityNumbers_t *eNums ) { // if we have already added this entity to this snapshot, don't add again if ( svEnt->snapshotCounter == sv.snapshotCounter ) { return; @@ -351,7 +351,7 @@ static void SV_AddEntToSnapshot( svEntity_t *svEnt, sharedEntity_t *gEnt, snapsh return; } - eNums->snapshotEntities[ eNums->numSnapshotEntities ] = gEnt->s.number; + eNums->snapshotEntities[ eNums->numSnapshotEntities ] = gEnt->s->number; eNums->numSnapshotEntities++; } @@ -364,7 +364,7 @@ float g_svCullDist = -1.0f; static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *frame, snapshotEntityNumbers_t *eNums, qboolean portal ) { int e, i; - sharedEntity_t *ent; + sharedEntityMapper_t *ent; svEntity_t *svEnt; int l; int clientarea, clientcluster; @@ -391,42 +391,42 @@ static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *fra clientpvs = CM_ClusterPVS (clientcluster); for ( e = 0 ; e < sv.num_entities ; e++ ) { - ent = SV_GentityNum(e); + ent = SV_GentityMapperNum(e); // never send entities that aren't linked in - if ( !ent->r.linked ) { + if ( !ent->r->linked ) { continue; } - if (ent->s.eFlags & EF_PERMANENT) + if (ent->s->eFlags & EF_PERMANENT) { // he's permanent, so don't send him down! continue; } - if (ent->s.number != e) { + if (ent->s->number != e) { Com_DPrintf ("FIXING ENT->S.NUMBER!!!\n"); - ent->s.number = e; + ent->s->number = e; } // entities can be flagged to explicitly not be sent to the client - if ( ent->r.svFlags & SVF_NOCLIENT ) { + if ( ent->r->svFlags & SVF_NOCLIENT ) { continue; } // entities can be flagged to be sent to only one client - if ( ent->r.svFlags & SVF_SINGLECLIENT ) { - if ( ent->r.singleClient != frame->ps.clientNum ) { + if ( ent->r->svFlags & SVF_SINGLECLIENT ) { + if ( ent->r->singleClient != frame->ps.clientNum ) { continue; } } // entities can be flagged to be sent to everyone but one client - if ( ent->r.svFlags & SVF_NOTSINGLECLIENT ) { - if ( ent->r.singleClient == frame->ps.clientNum ) { + if ( ent->r->svFlags & SVF_NOTSINGLECLIENT ) { + if ( ent->r->singleClient == frame->ps.clientNum ) { continue; } } - svEnt = SV_SvEntityForGentity( ent ); + svEnt = SV_SvEntityForGentityMapper( ent ); // don't double add an entity through portals if ( svEnt->snapshotCounter == sv.snapshotCounter ) { @@ -434,20 +434,20 @@ static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *fra } // entities can request not to be sent to certain clients (NOTE: always send to ourselves) - if ( e != frame->ps.clientNum && (ent->r.svFlags & SVF_BROADCASTCLIENTS) - && !(ent->r.broadcastClients[frame->ps.clientNum/32] & (1 << (frame->ps.clientNum % 32))) ) + if ( e != frame->ps.clientNum && (ent->r->svFlags & SVF_BROADCASTCLIENTS) + && !(ent->r->broadcastClients[frame->ps.clientNum/32] & (1 << (frame->ps.clientNum % 32))) ) { continue; } // broadcast entities are always sent, and so is the main player so we don't see noclip weirdness - if ( (ent->r.svFlags & SVF_BROADCAST) || e == frame->ps.clientNum - || (ent->r.broadcastClients[frame->ps.clientNum/32] & (1 << (frame->ps.clientNum % 32))) ) + if ( (ent->r->svFlags & SVF_BROADCAST) || e == frame->ps.clientNum + || (ent->r->broadcastClients[frame->ps.clientNum/32] & (1 << (frame->ps.clientNum % 32))) ) { SV_AddEntToSnapshot( svEnt, ent, eNums ); continue; } - if (ent->s.isPortalEnt) + if (ent->s->isPortalEnt) { //rww - portal entities are always sent as well SV_AddEntToSnapshot( svEnt, ent, eNums ); continue; @@ -496,13 +496,13 @@ static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *fra if (g_svCullDist != -1.0f) { //do a distance cull check - VectorAdd(ent->r.absmax, ent->r.absmin, difference); + VectorAdd(ent->r->absmax, ent->r->absmin, difference); VectorScale(difference, 0.5f, difference); VectorSubtract(origin, difference, difference); length = VectorLength(difference); // calculate the diameter - VectorSubtract(ent->r.absmax, ent->r.absmin, difference); + VectorSubtract(ent->r->absmax, ent->r->absmin, difference); radius = VectorLength(difference); if (length-radius >= g_svCullDist) { //then don't add it @@ -514,15 +514,15 @@ static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *fra SV_AddEntToSnapshot( svEnt, ent, eNums ); // if its a portal entity, add everything visible from its camera position - if ( ent->r.svFlags & SVF_PORTAL ) { - if ( ent->s.generic1 ) { + if ( ent->r->svFlags & SVF_PORTAL ) { + if ( ent->s->generic1 ) { vec3_t dir; - VectorSubtract(ent->s.origin, origin, dir); - if ( VectorLengthSquared(dir) > (float) ent->s.generic1 * ent->s.generic1 ) { + VectorSubtract(ent->s->origin, origin, dir); + if ( VectorLengthSquared(dir) > (float) ent->s->generic1 * ent->s->generic1 ) { continue; } } - SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums, qtrue ); + SV_AddEntitiesVisibleFromPoint( ent->s->origin2, frame, eNums, qtrue ); } } } @@ -545,10 +545,10 @@ static void SV_BuildClientSnapshot( client_t *client ) { clientSnapshot_t *frame; snapshotEntityNumbers_t entityNumbers; int i; - sharedEntity_t *ent; + sharedEntityMapper_t *ent; entityState_t *state; svEntity_t *svEnt; - sharedEntity_t *clent; + sharedEntityMapper_t *clent; playerState_t *ps; // bump the counter used to prevent double adding @@ -563,7 +563,7 @@ static void SV_BuildClientSnapshot( client_t *client ) { frame->num_entities = 0; - clent = client->gentity; + clent = client->gentityMapper; if ( !clent || client->state == CS_ZOMBIE ) { return; } @@ -578,11 +578,11 @@ static void SV_BuildClientSnapshot( client_t *client ) { if (ps->m_iVehicleNum) { //get the vehicle's playerstate too then - sharedEntity_t *veh = SV_GentityNum(ps->m_iVehicleNum); + sharedEntityMapper_t *veh = SV_GentityMapperNum(ps->m_iVehicleNum); - if (veh && veh->playerState) + if (veh && *(veh->playerState)) { //Now VMA it and we've got ourselves a playerState - playerState_t *vps = ((playerState_t *)VM_ArgPtr((intptr_t)veh->playerState)); + playerState_t *vps = ((playerState_t *)VM_ArgPtr((intptr_t)(*veh->playerState))); frame->vps = *vps; #ifdef _ONEBIT_COMBO @@ -628,9 +628,9 @@ static void SV_BuildClientSnapshot( client_t *client ) { frame->num_entities = 0; frame->first_entity = svs.nextSnapshotEntities; for ( i = 0 ; i < entityNumbers.numSnapshotEntities ; i++ ) { - ent = SV_GentityNum(entityNumbers.snapshotEntities[i]); + ent = SV_GentityMapperNum(entityNumbers.snapshotEntities[i]); state = &svs.snapshotEntities[svs.nextSnapshotEntities % svs.numSnapshotEntities]; - *state = ent->s; + *state = *ent->s; svs.nextSnapshotEntities++; // this should never hit, map should always be restarted first in SV_Frame if ( svs.nextSnapshotEntities >= 0x7FFFFFFE ) { diff --git a/codemp/server/sv_world.cpp b/codemp/server/sv_world.cpp index e72cb294b4..51848d45ca 100644 --- a/codemp/server/sv_world.cpp +++ b/codemp/server/sv_world.cpp @@ -36,18 +36,18 @@ given entity. If the entity is a bsp model, the headnode will be returned, otherwise a custom box tree will be constructed. ================ */ -clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent ) { - if ( ent->r.bmodel ) { +clipHandle_t SV_ClipHandleForEntity( const sharedEntityMapper_t *ent ) { + if ( ent->r->bmodel ) { // explicit hulls in the BSP model - return CM_InlineModel( ent->s.modelindex ); + return CM_InlineModel( ent->s->modelindex ); } - if ( ent->r.svFlags & SVF_CAPSULE ) { + if ( ent->r->svFlags & SVF_CAPSULE ) { // create a temp capsule from bounding box sizes - return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qtrue ); + return CM_TempBoxModel( ent->r->mins, ent->r->maxs, qtrue ); } // create a temp tree from bounding box sizes - return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qfalse ); + return CM_TempBoxModel( ent->r->mins, ent->r->maxs, qfalse ); } @@ -168,14 +168,14 @@ SV_UnlinkEntity =============== */ -void SV_UnlinkEntity( sharedEntity_t *gEnt ) { +void SV_UnlinkEntity( sharedEntityMapper_t *gEnt ) { svEntity_t *ent; svEntity_t *scan; worldSector_t *ws; - ent = SV_SvEntityForGentity( gEnt ); + ent = SV_SvEntityForGentityMapper( gEnt ); - gEnt->r.linked = qfalse; + gEnt->r->linked = qfalse; ws = ent->worldSector; if ( !ws ) { @@ -206,7 +206,7 @@ SV_LinkEntity =============== */ #define MAX_TOTAL_ENT_LEAFS 128 -void SV_LinkEntity( sharedEntity_t *gEnt ) { +void SV_LinkEntity( sharedEntityMapper_t *gEnt ) { worldSector_t *node; int leafs[MAX_TOTAL_ENT_LEAFS]; int cluster; @@ -217,77 +217,77 @@ void SV_LinkEntity( sharedEntity_t *gEnt ) { float *origin, *angles; svEntity_t *ent; - ent = SV_SvEntityForGentity( gEnt ); + ent = SV_SvEntityForGentityMapper( gEnt ); if ( ent->worldSector ) { SV_UnlinkEntity( gEnt ); // unlink from old position } // encode the size into the entityState_t for client prediction - if ( gEnt->r.bmodel ) { - gEnt->s.solid = SOLID_BMODEL; // a solid_box will never create this value - } else if ( gEnt->r.contents & ( CONTENTS_SOLID | CONTENTS_BODY ) ) { + if ( gEnt->r->bmodel ) { + gEnt->s->solid = SOLID_BMODEL; // a solid_box will never create this value + } else if ( gEnt->r->contents & ( CONTENTS_SOLID | CONTENTS_BODY ) ) { // assume that x/y are equal and symetric - i = gEnt->r.maxs[0]; + i = gEnt->r->maxs[0]; if (i<1) i = 1; if (i>255) i = 255; // z is not symetric - j = (-gEnt->r.mins[2]); + j = (-gEnt->r->mins[2]); if (j<1) j = 1; if (j>255) j = 255; // and z maxs can be negative... - k = (gEnt->r.maxs[2]+32); + k = (gEnt->r->maxs[2]+32); if (k<1) k = 1; if (k>255) k = 255; - gEnt->s.solid = (k<<16) | (j<<8) | i; + gEnt->s->solid = (k<<16) | (j<<8) | i; - if (gEnt->s.solid == SOLID_BMODEL) + if (gEnt->s->solid == SOLID_BMODEL) { //yikes, this would make everything explode violently. - gEnt->s.solid = (k<<16) | (j<<8) | (i-1); + gEnt->s->solid = (k<<16) | (j<<8) | (i-1); } } else { - gEnt->s.solid = 0; + gEnt->s->solid = 0; } // get the position - origin = gEnt->r.currentOrigin; - angles = gEnt->r.currentAngles; + origin = gEnt->r->currentOrigin; + angles = gEnt->r->currentAngles; // set the abs box - if ( gEnt->r.bmodel && (angles[0] || angles[1] || angles[2]) ) { + if ( gEnt->r->bmodel && (angles[0] || angles[1] || angles[2]) ) { // expand for rotation float max; - max = RadiusFromBounds( gEnt->r.mins, gEnt->r.maxs ); + max = RadiusFromBounds( gEnt->r->mins, gEnt->r->maxs ); for (i=0 ; i<3 ; i++) { - gEnt->r.absmin[i] = origin[i] - max; - gEnt->r.absmax[i] = origin[i] + max; + gEnt->r->absmin[i] = origin[i] - max; + gEnt->r->absmax[i] = origin[i] + max; } } else { // normal - VectorAdd (origin, gEnt->r.mins, gEnt->r.absmin); - VectorAdd (origin, gEnt->r.maxs, gEnt->r.absmax); + VectorAdd (origin, gEnt->r->mins, gEnt->r->absmin); + VectorAdd (origin, gEnt->r->maxs, gEnt->r->absmax); } // because movement is clipped an epsilon away from an actual edge, // we must fully check even when bounding boxes don't quite touch - gEnt->r.absmin[0] -= 1; - gEnt->r.absmin[1] -= 1; - gEnt->r.absmin[2] -= 1; - gEnt->r.absmax[0] += 1; - gEnt->r.absmax[1] += 1; - gEnt->r.absmax[2] += 1; + gEnt->r->absmin[0] -= 1; + gEnt->r->absmin[1] -= 1; + gEnt->r->absmin[2] -= 1; + gEnt->r->absmax[0] += 1; + gEnt->r->absmax[1] += 1; + gEnt->r->absmax[2] += 1; // link to PVS leafs ent->numClusters = 0; @@ -296,7 +296,7 @@ void SV_LinkEntity( sharedEntity_t *gEnt ) { ent->areanum2 = -1; //get all leafs, including solids - num_leafs = CM_BoxLeafnums( gEnt->r.absmin, gEnt->r.absmax, + num_leafs = CM_BoxLeafnums( gEnt->r->absmin, gEnt->r->absmax, leafs, MAX_TOTAL_ENT_LEAFS, &lastLeaf ); // if none of the leafs were inside the map, the @@ -314,8 +314,8 @@ void SV_LinkEntity( sharedEntity_t *gEnt ) { if (ent->areanum != -1 && ent->areanum != area) { if (ent->areanum2 != -1 && ent->areanum2 != area && sv.state == SS_LOADING) { Com_DPrintf ("Object %i touching 3 areas at %f %f %f\n", - gEnt->s.number, - gEnt->r.absmin[0], gEnt->r.absmin[1], gEnt->r.absmin[2]); + gEnt->s->number, + gEnt->r->absmin[0], gEnt->r->absmin[1], gEnt->r->absmin[2]); } ent->areanum2 = area; } else { @@ -341,7 +341,7 @@ void SV_LinkEntity( sharedEntity_t *gEnt ) { ent->lastCluster = CM_LeafCluster( lastLeaf ); } - gEnt->r.linkcount++; + gEnt->r->linkcount++; // find the first world sector node that the ent's box crosses node = sv_worldSectors; @@ -349,9 +349,9 @@ void SV_LinkEntity( sharedEntity_t *gEnt ) { { if (node->axis == -1) break; - if ( gEnt->r.absmin[node->axis] > node->dist) + if ( gEnt->r->absmin[node->axis] > node->dist) node = node->children[0]; - else if ( gEnt->r.absmax[node->axis] < node->dist) + else if ( gEnt->r->absmax[node->axis] < node->dist) node = node->children[1]; else break; // crosses the node @@ -362,7 +362,7 @@ void SV_LinkEntity( sharedEntity_t *gEnt ) { ent->nextEntityInWorldSector = node->entities; node->entities = ent; - gEnt->r.linked = qtrue; + gEnt->r->linked = qtrue; } /* @@ -391,19 +391,19 @@ SV_AreaEntities_r */ void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) { svEntity_t *check, *next; - sharedEntity_t *gcheck; + sharedEntityMapper_t *gcheck; for ( check = node->entities ; check ; check = next ) { next = check->nextEntityInWorldSector; - gcheck = SV_GEntityForSvEntity( check ); + gcheck = SV_GEntityMapperForSvEntity( check ); - if ( gcheck->r.absmin[0] > ap->maxs[0] - || gcheck->r.absmin[1] > ap->maxs[1] - || gcheck->r.absmin[2] > ap->maxs[2] - || gcheck->r.absmax[0] < ap->mins[0] - || gcheck->r.absmax[1] < ap->mins[1] - || gcheck->r.absmax[2] < ap->mins[2]) { + if ( gcheck->r->absmin[0] > ap->maxs[0] + || gcheck->r->absmin[1] > ap->maxs[1] + || gcheck->r->absmin[2] > ap->maxs[2] + || gcheck->r->absmax[0] < ap->mins[0] + || gcheck->r->absmax[1] < ap->mins[1] + || gcheck->r->absmax[2] < ap->mins[2]) { continue; } @@ -484,17 +484,17 @@ SV_ClipToEntity ==================== */ void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, int capsule ) { - sharedEntity_t *touch; + sharedEntityMapper_t *touch; clipHandle_t clipHandle; float *origin, *angles; - touch = SV_GentityNum( entityNum ); + touch = SV_GentityMapperNum( entityNum ); Com_Memset(trace, 0, sizeof(trace_t)); // if it doesn't have any brushes of a type we // are looking for, ignore it - if ( ! ( contentmask & touch->r.contents ) ) { + if ( ! ( contentmask & touch->r->contents ) ) { trace->fraction = 1.0; return; } @@ -502,10 +502,10 @@ void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, con // might intersect, so do an exact clip clipHandle = SV_ClipHandleForEntity (touch); - origin = touch->r.currentOrigin; - angles = touch->r.currentAngles; + origin = touch->r->currentOrigin; + angles = touch->r->currentAngles; - if ( !touch->r.bmodel ) { + if ( !touch->r->bmodel ) { angles = vec3_origin; // boxes don't rotate } @@ -514,7 +514,7 @@ void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, con origin, angles, capsule); if ( trace->fraction < 1 ) { - trace->entityNum = touch->s.number; + trace->entityNum = touch->s->number; } } @@ -538,17 +538,18 @@ static float VectorDistance(vec3_t p1, vec3_t p2) static void SV_ClipMoveToEntities( moveclip_t *clip ) { static int touchlist[MAX_GENTITIES]; int i, num; - sharedEntity_t *touch; + sharedEntityMapper_t *touch; int passOwnerNum; trace_t trace, oldTrace= {0}; clipHandle_t clipHandle; float *origin, *angles; int thisOwnerShared = 1; + sharedEntityMapper_t *passEnt = SV_GentityMapperNum( clip->passEntityNum ); num = SV_AreaEntities( clip->boxmins, clip->boxmaxs, touchlist, MAX_GENTITIES); if ( clip->passEntityNum != ENTITYNUM_NONE ) { - passOwnerNum = ( SV_GentityNum( clip->passEntityNum ) )->r.ownerNum; + if ( passEnt && passEnt->r ) passOwnerNum = passEnt->r->ownerNum; if ( passOwnerNum == ENTITYNUM_NONE ) { passOwnerNum = -1; } @@ -556,7 +557,7 @@ static void SV_ClipMoveToEntities( moveclip_t *clip ) { passOwnerNum = -1; } - if ( SV_GentityNum(clip->passEntityNum)->r.svFlags & SVF_OWNERNOTSHARED ) + if ( passEnt && passEnt->r && passEnt->r->svFlags & SVF_OWNERNOTSHARED ) { thisOwnerShared = 0; } @@ -565,15 +566,15 @@ static void SV_ClipMoveToEntities( moveclip_t *clip ) { if ( clip->trace.allsolid ) { return; } - touch = SV_GentityNum( touchlist[i] ); + touch = SV_GentityMapperNum( touchlist[i] ); // see if we should ignore this entity if ( clip->passEntityNum != ENTITYNUM_NONE ) { if ( touchlist[i] == clip->passEntityNum ) { continue; // don't clip against the pass entity } - if ( touch->r.ownerNum == clip->passEntityNum) { - if (touch->r.svFlags & SVF_OWNERNOTSHARED) + if ( touch->r->ownerNum == clip->passEntityNum) { + if (touch->r->svFlags & SVF_OWNERNOTSHARED) { if ( clip->contentmask != (MASK_SHOT | CONTENTS_LIGHTSABER) && clip->contentmask != (MASK_SHOT)) @@ -586,15 +587,15 @@ static void SV_ClipMoveToEntities( moveclip_t *clip ) { continue; // don't clip against own missiles } } - if ( touch->r.ownerNum == passOwnerNum && - !(touch->r.svFlags & SVF_OWNERNOTSHARED) && + if ( touch->r->ownerNum == passOwnerNum && + !(touch->r->svFlags & SVF_OWNERNOTSHARED) && thisOwnerShared ) { continue; // don't clip against other missiles from our owner } - if (touch->s.eType == ET_MISSILE && - !(touch->r.svFlags & SVF_OWNERNOTSHARED) && - touch->r.ownerNum == passOwnerNum) + if (touch->s->eType == ET_MISSILE && + !(touch->r->svFlags & SVF_OWNERNOTSHARED) && + touch->r->ownerNum == passOwnerNum) { //blah, hack continue; } @@ -602,11 +603,11 @@ static void SV_ClipMoveToEntities( moveclip_t *clip ) { // if it doesn't have any brushes of a type we // are looking for, ignore it - if ( ! ( clip->contentmask & touch->r.contents ) ) { + if ( ! ( clip->contentmask & touch->r->contents ) ) { continue; } - if ((clip->contentmask == (MASK_SHOT|CONTENTS_LIGHTSABER) || clip->contentmask == MASK_SHOT) && (touch->r.contents > 0 && (touch->r.contents & CONTENTS_NOSHOT))) + if ((clip->contentmask == (MASK_SHOT|CONTENTS_LIGHTSABER) || clip->contentmask == MASK_SHOT) && (touch->r->contents > 0 && (touch->r->contents & CONTENTS_NOSHOT))) { continue; } @@ -614,11 +615,11 @@ static void SV_ClipMoveToEntities( moveclip_t *clip ) { // might intersect, so do an exact clip clipHandle = SV_ClipHandleForEntity (touch); - origin = touch->r.currentOrigin; - angles = touch->r.currentAngles; + origin = touch->r->currentOrigin; + angles = touch->r->currentAngles; - if ( !touch->r.bmodel ) { + if ( !touch->r->bmodel ) { angles = vec3_origin; // boxes don't rotate } @@ -634,13 +635,13 @@ static void SV_ClipMoveToEntities( moveclip_t *clip ) { if ( trace.allsolid ) { clip->trace.allsolid = qtrue; - trace.entityNum = touch->s.number; + trace.entityNum = touch->s->number; } else if ( trace.startsolid ) { clip->trace.startsolid = qtrue; - trace.entityNum = touch->s.number; + trace.entityNum = touch->s->number; //rww - added this because we want to get the number of an ent even if our trace starts inside it. - clip->trace.entityNum = touch->s.number; + clip->trace.entityNum = touch->s->number; } if ( trace.fraction < clip->trace.fraction ) { @@ -649,7 +650,7 @@ static void SV_ClipMoveToEntities( moveclip_t *clip ) { // make sure we keep a startsolid from a previous trace oldStart = clip->trace.startsolid; - trace.entityNum = touch->s.number; + trace.entityNum = touch->s->number; clip->trace = trace; clip->trace.startsolid = (qboolean)((unsigned)clip->trace.startsolid | (unsigned)oldStart); } @@ -658,10 +659,10 @@ Ghoul2 Insert Start */ #if 0 // decide if we should do the ghoul2 collision detection right here - if ((trace.entityNum == touch->s.number) && (clip->traceFlags)) + if ((trace.entityNum == touch->s->number) && (clip->traceFlags)) { // do we actually have a ghoul2 model here? - if (touch->s.ghoul2) + if (touch->s->ghoul2) { int oldTraceRecSize = 0; int newTraceRecSize = 0; @@ -680,8 +681,8 @@ Ghoul2 Insert Start } } - G2API_CollisionDetect(&clip->trace.G2CollisionMap[0], *((CGhoul2Info_v *)touch->s.ghoul2), - touch->s.angles, touch->s.origin, sv.time, touch->s.number, clip->start, clip->end, touch->s.modelScale, G2VertSpaceServer, clip->traceFlags, clip->useLod); + G2API_CollisionDetect(&clip->trace.G2CollisionMap[0], *((CGhoul2Info_v *)touch->s->ghoul2), + touch->s->angles, touch->s->origin, sv.time, touch->s->number, clip->start, clip->end, touch->s->modelScale, G2VertSpaceServer, clip->traceFlags, clip->useLod); // set our new trace record size @@ -703,7 +704,7 @@ Ghoul2 Insert Start #else //rww - since this is multiplayer and we don't have the luxury of violating networking rules in horrible ways, //this must be done somewhat differently. - if ((clip->traceFlags & G2TRFLAG_DOGHOULTRACE) && trace.entityNum == touch->s.number && touch->ghoul2 && ((clip->traceFlags & G2TRFLAG_HITCORPSES) || !(touch->s.eFlags & EF_DEAD))) + if ((clip->traceFlags & G2TRFLAG_DOGHOULTRACE) && trace.entityNum == touch->s->number && *(touch->ghoul2) && ((clip->traceFlags & G2TRFLAG_HITCORPSES) || !(touch->s->eFlags & EF_DEAD))) { //standard behavior will be to ignore g2 col on dead ents, but if traceFlags is set to allow, then we'll try g2 col on EF_DEAD people too. static G2Trace_t G2Trace; vec3_t angles; @@ -732,13 +733,13 @@ Ghoul2 Insert Start tN++; } - if (touch->s.number < MAX_CLIENTS) + if (touch->s->number < MAX_CLIENTS) { - VectorCopy(touch->s.apos.trBase, angles); + VectorCopy(touch->s->apos.trBase, angles); } else { - VectorCopy(touch->r.currentAngles, angles); + VectorCopy(touch->r->currentAngles, angles); } angles[ROLL] = angles[PITCH] = 0; @@ -747,27 +748,27 @@ Ghoul2 Insert Start #ifndef FINAL_BUILD if (sv_showghoultraces->integer) { - Com_Printf( "Ghoul2 trace lod=%1d length=%6.0f to %s\n",clip->useLod,VectorDistance(clip->start, clip->end), re->G2API_GetModelName(*(SV_G2Map_GetG2FromHandle((g2handleptr_t)touch->ghoul2)), 0)); + Com_Printf( "Ghoul2 trace lod=%1d length=%6.0f to %s\n",clip->useLod,VectorDistance(clip->start, clip->end), re->G2API_GetModelName(*(SV_G2Map_GetG2FromHandle((g2handleptr_t)(*(touch->ghoul2)))), 0)); } #endif if (com_optvehtrace && com_optvehtrace->integer && - touch->s.eType == ET_NPC && - touch->s.NPC_class == CLASS_VEHICLE && - touch->m_pVehicle) + touch->s->eType == ET_NPC && + touch->s->NPC_class == CLASS_VEHICLE && + *touch->m_pVehicle) { //for vehicles cache the transform data. - re->G2API_CollisionDetectCache(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)touch->ghoul2)), angles, touch->r.currentOrigin, sv.time, touch->s.number, clip->start, clip->end, touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); + re->G2API_CollisionDetectCache(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)(*(touch->ghoul2)))), angles, touch->r->currentOrigin, sv.time, touch->s->number, clip->start, clip->end, *touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); } else { - re->G2API_CollisionDetect(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)touch->ghoul2)), angles, touch->r.currentOrigin, sv.time, touch->s.number, clip->start, clip->end, touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); + re->G2API_CollisionDetect(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)(*(touch->ghoul2)))), angles, touch->r->currentOrigin, sv.time, touch->s->number, clip->start, clip->end, *touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); } tN = 0; while (tN < MAX_G2_COLLISIONS) { - if (G2Trace[tN].mEntityNum == touch->s.number) + if (G2Trace[tN].mEntityNum == touch->s->number) { //ok, valid bestTr = tN; break; @@ -885,7 +886,7 @@ SV_PointContents */ int SV_PointContents( const vec3_t p, int passEntityNum ) { int touch[MAX_GENTITIES]; - sharedEntity_t *hit; + sharedEntityMapper_t *hit; int i, num; int contents, c2; clipHandle_t clipHandle; @@ -900,11 +901,11 @@ int SV_PointContents( const vec3_t p, int passEntityNum ) { if ( touch[i] == passEntityNum ) { continue; } - hit = SV_GentityNum( touch[i] ); + hit = SV_GentityMapperNum( touch[i] ); // might intersect, so do an exact clip clipHandle = SV_ClipHandleForEntity( hit ); - c2 = CM_TransformedPointContents (p, clipHandle, hit->r.currentOrigin, hit->r.currentAngles); + c2 = CM_TransformedPointContents (p, clipHandle, hit->r->currentOrigin, hit->r->currentAngles); contents |= c2; } From b92fc59b06d4e8182392a592adfd0f115784e0a9 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Tue, 3 Oct 2023 15:26:29 +0200 Subject: [PATCH 09/28] Adjust gvm calls to use QVM extra memory where required. --- codemp/server/sv_gameapi.cpp | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/codemp/server/sv_gameapi.cpp b/codemp/server/sv_gameapi.cpp index d6b89bac97..023fc4ff18 100644 --- a/codemp/server/sv_gameapi.cpp +++ b/codemp/server/sv_gameapi.cpp @@ -168,7 +168,18 @@ void GVM_ClientCommand( int clientNum ) { void GVM_ClientThink( int clientNum, usercmd_t *ucmd ) { if ( gvm->isLegacy ) { - VM_Call( gvm, GAME_CLIENT_THINK, clientNum, reinterpret_cast< intptr_t >( ucmd ) ); + usercmd_t *ucmd_ptr; + + if ( !gvm->dllHandle ) { + ucmd_ptr = (usercmd_t*)(intptr_t)(VM_PtrToOffset( gvm, VM_ExtraMemory_ClaimData(gvm, (usercmd_t*)ucmd, sizeof(usercmd_t)) ) ); + } else { + ucmd_ptr = ucmd; + } + VM_Call( gvm, GAME_CLIENT_THINK, clientNum, reinterpret_cast< intptr_t >( ucmd_ptr ) ); + if ( !gvm->dllHandle ) { + // Memory may only be released in reverse order. + if ( ucmd_ptr ) VM_ExtraMemory_Release( gvm, sizeof(usercmd_t) ); + } return; } VMSwap v( gvm ); @@ -204,7 +215,20 @@ int GVM_BotAIStartFrame( int time ) { void GVM_ROFF_NotetrackCallback( int entID, const char *notetrack ) { if ( gvm->isLegacy ) { - VM_Call( gvm, GAME_ROFF_NOTETRACK_CALLBACK, entID, reinterpret_cast< intptr_t >( notetrack ) ); + int notetrack_length; + const char *notetrack_ptr; + + if ( !gvm->dllHandle ) { + notetrack_length = strlen( notetrack ) + 1; // +1 for the terminating 0-byte, which ClaimString automatically allocates, too + notetrack_ptr = (const char*)(intptr_t)VM_PtrToOffset( gvm, VM_ExtraMemory_ClaimString(gvm, notetrack) ); + } else { + notetrack_ptr = notetrack; + } + VM_Call( gvm, GAME_ROFF_NOTETRACK_CALLBACK, entID, reinterpret_cast< intptr_t >( notetrack_ptr ) ); + if ( !gvm->dllHandle ) { + // Memory may only be released in reverse order. + if ( notetrack_ptr && notetrack_length ) VM_ExtraMemory_Release( gvm, notetrack_length ); + } return; } VMSwap v( gvm ); From f981711b29f534031fbb65cb2f8184845869e33f Mon Sep 17 00:00:00 2001 From: Daggolin Date: Tue, 3 Oct 2023 15:28:53 +0200 Subject: [PATCH 10/28] Adjust the cgameapi in a similar way to the uiapi and gameapi for better QVM compatibility. --- codemp/client/cl_cgameapi.cpp | 363 ++++++++++++++++++++++++---------- 1 file changed, 264 insertions(+), 99 deletions(-) diff --git a/codemp/client/cl_cgameapi.cpp b/codemp/client/cl_cgameapi.cpp index 15267443a6..84f3a91f4d 100644 --- a/codemp/client/cl_cgameapi.cpp +++ b/codemp/client/cl_cgameapi.cpp @@ -29,6 +29,9 @@ along with this program; if not, see . #include "snd_ambient.h" #include "FXExport.h" #include "FxUtil.h" +#include "qcommon/vm_local.h" + +#include extern IHeapAllocator *G2VertSpaceClient; extern botlib_export_t *botlib_export; @@ -37,6 +40,62 @@ extern botlib_export_t *botlib_export; static cgameExport_t *cge; // cgame export table static vm_t *cgvm; // cgame vm, valid for legacy and new api +typedef std::unordered_map g2HandleToG2_m; + +static g2HandleToG2_m g2Mapping; +static g2handle_t g2NextHandle = (g2handle_t)1; // Start at 1, because 0 has special meaning + +static inline CGhoul2Info_v *CL_G2Map_GetG2FromHandle( g2handleptr_t g2h ) +{ // Returns the pointer to the g2 object if the handle is valid + // Native libraries should not use the pointer, but in theory they could use + // it. Thus we don't perform any mapping for native libraries to avoid + // issues with custom modules. + if ( cgvm->dllHandle ) return (CGhoul2Info_v*)g2h; + + g2handle_t g2handle = (g2handle_t)g2h; + g2HandleToG2_m::iterator ghlIt = g2Mapping.find(g2handle); + + if (ghlIt == g2Mapping.end()) return NULL; + return g2Mapping[g2handle]; +} + +static inline CGhoul2Info_v **CL_G2Map_GetG2PtrFromHandle( g2handleptr_t *g2h ) +{ // Returns a pointer to the g2 object pointer in the map so g2 functions can update the pointer + // Native libraries should not use the pointer, but in theory they could use + // it. Thus we don't perform any mapping for native libraries to avoid + // issues with custom modules. + if ( cgvm->dllHandle ) return (CGhoul2Info_v **)g2h; + + g2handle_t g2handle = *((g2handle_t*)g2h); + if ( !g2handle ) + { // Special case: the g2 handle is not valid, yet. Return a pointer to a static temporary pointer. Insertion is handled by calling CL_G2Map_Update after calling the G2API + static CGhoul2Info_v *g2Tmp; + g2Tmp = NULL; + return &g2Tmp; + } + return &g2Mapping[g2handle]; +} + +static void CL_G2Map_Update( g2handleptr_t *g2h, CGhoul2Info_v *g2Ptr ) +{ // Inserts and/or erases to/from the map and updates the handle pointer + if ( cgvm->dllHandle ) return; + + g2handle_t *g2handle = (g2handle_t*)g2h; + if ( !*g2handle && g2Ptr ) + { // Got a 0 handle, but a non-0 pointer: add to map and set handle + // Unlikely to happen, but should we ever cycle through the whole integer range start searching for gaps + while ( CL_G2Map_GetG2FromHandle(g2NextHandle) || !g2NextHandle ) g2NextHandle++; + + g2Mapping[g2NextHandle] = g2Ptr; + *g2handle = g2NextHandle++; + } + else if ( *g2h && !g2Ptr ) + { // Got a non-0 handle, but 0 pointer: remove from map and set handle to 0 + g2Mapping.erase( *g2handle ); + *g2handle = 0; + } +} + // // cgame vmMain calls // @@ -213,9 +272,21 @@ qboolean CGVM_NoUseableForce( void ) { return cge->NoUseableForce(); } +static size_t vec3_size = sizeof(vec3_t); void CGVM_GetOrigin( int entID, vec3_t out ) { if ( cgvm->isLegacy ) { - VM_Call( cgvm, CG_GET_ORIGIN, entID, reinterpret_cast< intptr_t >( out ) ); + float *out_ptr; + + if ( !cgvm->dllHandle ) { + out_ptr = (float*)VM_PtrToOffset( cgvm, VM_ExtraMemory_ClaimData(cgvm, (float*)out, vec3_size) ); + } else { + out_ptr = out; + } + VM_Call( cgvm, CG_GET_ORIGIN, entID, reinterpret_cast< intptr_t >( out_ptr ) ); + if ( !cgvm->dllHandle ) { + // Memory may only be released in reverse order. + if ( out_ptr ) VM_ExtraMemory_Release( cgvm, vec3_size ); + } return; } VMSwap v( cgvm ); @@ -225,7 +296,18 @@ void CGVM_GetOrigin( int entID, vec3_t out ) { void CGVM_GetAngles( int entID, vec3_t out ) { if ( cgvm->isLegacy ) { + float *out_ptr; + + if ( !cgvm->dllHandle ) { + out_ptr = (float*)VM_PtrToOffset( cgvm, VM_ExtraMemory_ClaimData(cgvm, (float*)out, vec3_size) ); + } else { + out_ptr = out; + } VM_Call( cgvm, CG_GET_ANGLES, entID, reinterpret_cast< intptr_t >( out ) ); + if ( !cgvm->dllHandle ) { + // Memory may only be released in reverse order. + if ( out_ptr ) VM_ExtraMemory_Release( cgvm, vec3_size ); + } return; } VMSwap v( cgvm ); @@ -253,7 +335,20 @@ trajectory_t *CGVM_GetAngleTrajectory( int entID ) { void CGVM_ROFF_NotetrackCallback( int entID, const char *notetrack ) { if ( cgvm->isLegacy ) { - VM_Call( cgvm, CG_ROFF_NOTETRACK_CALLBACK, entID, reinterpret_cast< intptr_t >( notetrack ) ); + int notetrack_length; + const char *notetrack_ptr; + + if ( !cgvm->dllHandle ) { + notetrack_length = strlen( notetrack ) + 1; // +1 for the terminating 0-byte, which ClaimString automatically allocates, too + notetrack_ptr = (const char*)VM_PtrToOffset( cgvm, VM_ExtraMemory_ClaimString(cgvm, notetrack) ); + } else { + notetrack_ptr = notetrack; + } + VM_Call( cgvm, CG_ROFF_NOTETRACK_CALLBACK, entID, reinterpret_cast< intptr_t >( notetrack_ptr ) ); + if ( !cgvm->dllHandle ) { + // Memory may only be released in reverse order. + if ( notetrack_ptr && notetrack_length ) VM_ExtraMemory_Release( cgvm, notetrack_length ); + } return; } VMSwap v( cgvm ); @@ -324,7 +419,42 @@ static void CL_CM_LoadMap( const char *mapname, qboolean subBSP ) { } static void CL_GetGlconfig( glconfig_t *glconfig ) { - *glconfig = cls.glconfig; + if ( cgvm->dllHandle ) { + // For native libraries we can just copy the struct over cause they should be identical + *glconfig = cls.glconfig; + } else { + // As the QVM is 32 bit internally the size of our glconfig_t is bigger on 64 bit, because of the 4 string + // pointers. Instead of just assigning the glconfig we have to manually copy it over. + glconfig_qvm_t *glconfig_qvm = (glconfig_qvm_t*)glconfig; + + // We now reserve additional memory when loading the QVM. The QVM shouldn't try to access it on its own, but it + // is valid inside the QVM and if we set a pointer to it the QVM should be able to access it. Thus we now claim + // some of this extra memory in QVM-scope to write our glconfig strings in there and tell the QVM where to find + // them. + // FIXME: It would probably be a good idea to keep a reference to this extra memory ourselves in case a module + // decides to call this functions multiple times. Currently we would claim additional memory each time. + glconfig_qvm->renderer_string = VM_PtrToOffset( cgvm, VM_ExtraMemory_ClaimString( cgvm, cls.glconfig.renderer_string ) ); + glconfig_qvm->vendor_string = VM_PtrToOffset( cgvm, VM_ExtraMemory_ClaimString( cgvm, cls.glconfig.vendor_string ) ); + glconfig_qvm->version_string = VM_PtrToOffset( cgvm, VM_ExtraMemory_ClaimString( cgvm, cls.glconfig.version_string ) ); + glconfig_qvm->extensions_string = VM_PtrToOffset( cgvm, VM_ExtraMemory_ClaimString( cgvm, cls.glconfig.extensions_string ) ); + + // Assign the rest of the values + glconfig_qvm->maxTextureSize = cls.glconfig.maxTextureSize; + glconfig_qvm->maxActiveTextures = cls.glconfig.maxActiveTextures; + glconfig_qvm->maxTextureFilterAnisotropy = cls.glconfig.maxTextureFilterAnisotropy; + glconfig_qvm->colorBits = cls.glconfig.colorBits; + glconfig_qvm->depthBits = cls.glconfig.depthBits; + glconfig_qvm->stencilBits = cls.glconfig.stencilBits; + glconfig_qvm->deviceSupportsGamma = cls.glconfig.deviceSupportsGamma; + glconfig_qvm->textureCompression = cls.glconfig.textureCompression; + glconfig_qvm->textureEnvAddAvailable = cls.glconfig.textureEnvAddAvailable; + glconfig_qvm->clampToEdgeAvailable = cls.glconfig.clampToEdgeAvailable; + glconfig_qvm->vidWidth = cls.glconfig.vidWidth; + glconfig_qvm->vidHeight = cls.glconfig.vidHeight; + glconfig_qvm->displayFrequency = cls.glconfig.displayFrequency; + glconfig_qvm->isFullscreen = cls.glconfig.isFullscreen; + glconfig_qvm->stereoEnabled = cls.glconfig.stereoEnabled; + } } static void CL_GetGameState( gameState_t *gs ) { @@ -356,6 +486,28 @@ static void CL_S_Shutup( qboolean shutup ) { s_shutUp = shutup; } +static void CL_R_AddRefEntityToScene( const refEntity_t *refEnt ) { + if ( cgvm->dllHandle ) { + // We can directly pass the refEnt for native libraries + re->AddRefEntityToScene( refEnt ); + return; + } + + // The renderer should look up the handle on its own, but as we have to stay compatible with the modular renderer + // system we can't change the renderer for this and have to meet its expectations instead... + + // Get a copy of the refEntity data. For a QVM mod the ghoul2 pointer at the end might contain some additional data, + // because the QVM stores it as 32 bit and if the engine is 64 bit it is going to read slightly out of bounds of the + // struct in the QVM, but CL_G2Map_GetG2FromHandle should take care of that by casting. + refEntity_t refEntCopy = *refEnt; + + // Get the ghoul2 instance from the handle + refEntCopy.ghoul2 = CL_G2Map_GetG2FromHandle( (g2handleptr_t)refEnt->ghoul2 ); + + // Pass our copy + re->AddRefEntityToScene( &refEntCopy ); +} + static int CL_GetCurrentCmdNumber( void ) { return cl.cmdNumber; } @@ -443,7 +595,7 @@ static void CL_RMG_Init( int /* terrainID */, const char * /* terrainInfo */ ) { static qboolean CGFX_PlayBoltedEffectID( int id, vec3_t org, void *ghoul2, const int boltNum, const int entNum, const int modelNum, int iLooptime, qboolean isRelative ) { if ( !ghoul2 ) return qfalse; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); int boltInfo=0; if ( re->G2API_AttachEnt( &boltInfo, g2, modelNum, boltNum, entNum, modelNum ) ) { @@ -479,81 +631,86 @@ static void CL_G2API_ListModelBones( void *ghlInfo, int frame ) { static void CL_G2API_SetGhoul2ModelIndexes( void *ghoul2, qhandle_t *modelList, qhandle_t *skinList ) { if ( !ghoul2 ) return; - re->G2API_SetGhoul2ModelIndexes( *((CGhoul2Info_v *)ghoul2), modelList, skinList ); + re->G2API_SetGhoul2ModelIndexes( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelList, skinList ); } static qboolean CL_G2API_HaveWeGhoul2Models( void *ghoul2) { if ( !ghoul2 ) return qfalse; - return re->G2API_HaveWeGhoul2Models( *((CGhoul2Info_v *)ghoul2) ); + return re->G2API_HaveWeGhoul2Models( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)) ); } static qboolean CL_G2API_GetBoltMatrix( void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale ) { if ( !ghoul2 ) return qfalse; - return re->G2API_GetBoltMatrix( *((CGhoul2Info_v *)ghoul2), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); + return re->G2API_GetBoltMatrix( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); } static qboolean CL_G2API_GetBoltMatrix_NoReconstruct( void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale ) { if ( !ghoul2 ) return qfalse; re->G2API_BoltMatrixReconstruction( qfalse ); - return re->G2API_GetBoltMatrix( *((CGhoul2Info_v *)ghoul2), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); + return re->G2API_GetBoltMatrix( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); } static qboolean CL_G2API_GetBoltMatrix_NoRecNoRot( void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale ) { if ( !ghoul2 ) return qfalse; // Intentionally not setting bolt matrix reconstruction state per original code comments re->G2API_BoltMatrixSPMethod( qtrue ); - return re->G2API_GetBoltMatrix( *((CGhoul2Info_v *)ghoul2), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); + return re->G2API_GetBoltMatrix( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale ); } static int CL_G2API_InitGhoul2Model( void **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin, qhandle_t customShader, int modelFlags, int lodBias ) { #ifdef _FULL_G2_LEAK_CHECKING g_G2AllocServer = 0; #endif - return re->G2API_InitGhoul2Model( (CGhoul2Info_v **)ghoul2Ptr, fileName, modelIndex, customSkin, customShader, modelFlags, lodBias ); + CGhoul2Info_v **g2Ptr = CL_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)ghoul2Ptr ); + int ret = re->G2API_InitGhoul2Model( g2Ptr, fileName, modelIndex, customSkin, customShader, modelFlags, lodBias ); + CL_G2Map_Update( (g2handleptr_t*)ghoul2Ptr, *g2Ptr ); + return ret; } static qboolean CL_G2API_SetSkin( void *ghoul2, int modelIndex, qhandle_t customSkin, qhandle_t renderSkin ) { if ( !ghoul2 ) return qfalse; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); return re->G2API_SetSkin( g2, modelIndex, customSkin, renderSkin ); } static void CL_G2API_CollisionDetect( CollisionRecord_t *collRecMap, void* ghoul2, const vec3_t angles, const vec3_t position, int frameNumber, int entNum, vec3_t rayStart, vec3_t rayEnd, vec3_t scale, int traceFlags, int useLod, float fRadius ) { if ( !ghoul2 ) return; - re->G2API_CollisionDetect( collRecMap, *((CGhoul2Info_v *)ghoul2), angles, position, frameNumber, entNum, rayStart, rayEnd, scale, G2VertSpaceClient, traceFlags, useLod, fRadius ); + re->G2API_CollisionDetect( collRecMap, *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), angles, position, frameNumber, entNum, rayStart, rayEnd, scale, G2VertSpaceClient, traceFlags, useLod, fRadius ); } static void CL_G2API_CollisionDetectCache( CollisionRecord_t *collRecMap, void* ghoul2, const vec3_t angles, const vec3_t position, int frameNumber, int entNum, vec3_t rayStart, vec3_t rayEnd, vec3_t scale, int traceFlags, int useLod, float fRadius ) { if ( !ghoul2 ) return; - re->G2API_CollisionDetectCache( collRecMap, *((CGhoul2Info_v *)ghoul2), angles, position, frameNumber, entNum, rayStart, rayEnd, scale, G2VertSpaceClient, traceFlags, useLod, fRadius ); + re->G2API_CollisionDetectCache( collRecMap, *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), angles, position, frameNumber, entNum, rayStart, rayEnd, scale, G2VertSpaceClient, traceFlags, useLod, fRadius ); } static void CL_G2API_CleanGhoul2Models( void **ghoul2Ptr ) { #ifdef _FULL_G2_LEAK_CHECKING g_G2AllocServer = 0; #endif - re->G2API_CleanGhoul2Models( (CGhoul2Info_v **)ghoul2Ptr ); + CGhoul2Info_v **g2Ptr = CL_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)ghoul2Ptr ); + re->G2API_CleanGhoul2Models( g2Ptr ); + CL_G2Map_Update( (g2handleptr_t*)ghoul2Ptr, *g2Ptr ); } static qboolean CL_G2API_SetBoneAngles( void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, const int up, const int right, const int forward, qhandle_t *modelList, int blendTime , int currentTime ) { if ( !ghoul2 ) return qfalse; - return re->G2API_SetBoneAngles( *((CGhoul2Info_v *)ghoul2), modelIndex, boneName, angles, flags, (const Eorientations)up, (const Eorientations)right, (const Eorientations)forward, modelList, blendTime , currentTime ); + return re->G2API_SetBoneAngles( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boneName, angles, flags, (const Eorientations)up, (const Eorientations)right, (const Eorientations)forward, modelList, blendTime , currentTime ); } static qboolean CL_G2API_SetBoneAnim( void *ghoul2, const int modelIndex, const char *boneName, const int startFrame, const int endFrame, const int flags, const float animSpeed, const int currentTime, const float setFrame, const int blendTime ) { if ( !ghoul2 ) return qfalse; - return re->G2API_SetBoneAnim( *((CGhoul2Info_v *)ghoul2), modelIndex, boneName, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime ); + return re->G2API_SetBoneAnim( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boneName, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime ); } static qboolean CL_G2API_GetBoneAnim( void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, int *startFrame, int *endFrame, int *flags, float *animSpeed, int *modelList, const int modelIndex ) { if ( !ghoul2 ) return qfalse; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); return re->G2API_GetBoneAnim( g2, modelIndex, boneName, currentTime, currentFrame, startFrame, endFrame, flags, animSpeed, modelList ); } static qboolean CL_G2API_GetBoneFrame( void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, int *modelList, const int modelIndex ) { if ( !ghoul2 ) return qfalse; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); int iDontCare1 = 0, iDontCare2 = 0, iDontCare3 = 0; float fDontCare1 = 0; @@ -567,7 +724,7 @@ static void CL_G2API_GetGLAName( void *ghoul2, int modelIndex, char *fillBuf ) { return; } - char *tmp = re->G2API_GetGLAName( *((CGhoul2Info_v *)ghoul2), modelIndex ); + char *tmp = re->G2API_GetGLAName( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex ); if ( tmp ) strcpy( fillBuf, tmp ); else @@ -577,12 +734,12 @@ static void CL_G2API_GetGLAName( void *ghoul2, int modelIndex, char *fillBuf ) { static int CL_G2API_CopyGhoul2Instance( void *g2From, void *g2To, int modelIndex ) { if ( !g2From || !g2To ) return 0; - return re->G2API_CopyGhoul2Instance( *((CGhoul2Info_v *)g2From), *((CGhoul2Info_v *)g2To), modelIndex ); + return re->G2API_CopyGhoul2Instance( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)g2From)), *(CL_G2Map_GetG2FromHandle((g2handleptr_t)g2To)), modelIndex ); } static void CL_G2API_CopySpecificGhoul2Model( void *g2From, int modelFrom, void *g2To, int modelTo ) { if ( !g2From || !g2To) return; - re->G2API_CopySpecificG2Model( *((CGhoul2Info_v *)g2From), modelFrom, *((CGhoul2Info_v *)g2To), modelTo ); + re->G2API_CopySpecificG2Model( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)g2From)), modelFrom, *(CL_G2Map_GetG2FromHandle((g2handleptr_t)g2To)), modelTo ); } static void CL_G2API_DuplicateGhoul2Instance( void *g2From, void **g2To ) { @@ -590,31 +747,39 @@ static void CL_G2API_DuplicateGhoul2Instance( void *g2From, void **g2To ) { g_G2AllocServer = 0; #endif if ( !g2From || !g2To ) return; - re->G2API_DuplicateGhoul2Instance( *((CGhoul2Info_v *)g2From), (CGhoul2Info_v **)g2To ); + CGhoul2Info_v **g2ToPtr = CL_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)g2To ); + re->G2API_DuplicateGhoul2Instance( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)g2From)), g2ToPtr ); + CL_G2Map_Update( (g2handleptr_t*)g2To, *g2ToPtr ); } static qboolean CL_G2API_HasGhoul2ModelOnIndex( void *ghlInfo, int modelIndex ) { - return re->G2API_HasGhoul2ModelOnIndex( (CGhoul2Info_v **)ghlInfo, modelIndex ); + CGhoul2Info_v **g2Ptr = CL_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)ghlInfo ); + qboolean ret = re->G2API_HasGhoul2ModelOnIndex( g2Ptr, modelIndex ); + CL_G2Map_Update( (g2handleptr_t*)ghlInfo, *g2Ptr ); + return ret; } static qboolean CL_G2API_RemoveGhoul2Model( void *ghlInfo, int modelIndex ) { #ifdef _FULL_G2_LEAK_CHECKING g_G2AllocServer = 0; #endif - return re->G2API_RemoveGhoul2Model( (CGhoul2Info_v **)ghlInfo, modelIndex ); + CGhoul2Info_v **g2Ptr = CL_G2Map_GetG2PtrFromHandle( (g2handleptr_t*)ghlInfo ); + qboolean ret = re->G2API_RemoveGhoul2Model( g2Ptr, modelIndex ); + CL_G2Map_Update( (g2handleptr_t*)ghlInfo, *g2Ptr ); + return ret; } static qboolean CL_G2API_SkinlessModel( void *ghlInfo, int modelIndex ) { if ( !ghlInfo ) return qfalse; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghlInfo); + CGhoul2Info_v &g2 = *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghlInfo)); return re->G2API_SkinlessModel( g2, modelIndex ); } static int CL_G2API_GetNumGoreMarks( void *ghlInfo, int modelIndex ) { #ifdef _G2_GORE if ( !ghlInfo ) return 0; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghlInfo); + CGhoul2Info_v &g2 = *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghlInfo)); return re->G2API_GetNumGoreMarks( g2, modelIndex ); #else return 0; @@ -624,62 +789,62 @@ static int CL_G2API_GetNumGoreMarks( void *ghlInfo, int modelIndex ) { static void CL_G2API_AddSkinGore( void *ghlInfo, SSkinGoreData *gore ) { #ifdef _G2_GORE if ( !ghlInfo ) return; - re->G2API_AddSkinGore( *((CGhoul2Info_v *)ghlInfo), *(SSkinGoreData *)gore ); + re->G2API_AddSkinGore( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghlInfo)), *(SSkinGoreData *)gore ); #endif } static void CL_G2API_ClearSkinGore( void *ghlInfo ) { #ifdef _G2_GORE if ( !ghlInfo ) return; - re->G2API_ClearSkinGore( *((CGhoul2Info_v *)ghlInfo) ); + re->G2API_ClearSkinGore( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghlInfo)) ); #endif } static int CL_G2API_Ghoul2Size( void *ghlInfo ) { if ( !ghlInfo ) return 0; - return re->G2API_Ghoul2Size( *((CGhoul2Info_v *)ghlInfo) ); + return re->G2API_Ghoul2Size( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghlInfo)) ); } static int CL_G2API_AddBolt( void *ghoul2, int modelIndex, const char *boneName ) { if ( !ghoul2 ) return -1; - return re->G2API_AddBolt( *((CGhoul2Info_v *)ghoul2), modelIndex, boneName ); + return re->G2API_AddBolt( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boneName ); } static qboolean CL_G2API_AttachEnt( int *boltInfo, void *ghlInfoTo, int toBoltIndex, int entNum, int toModelNum ) { if ( !ghlInfoTo ) return qfalse; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghlInfoTo); + CGhoul2Info_v &g2 = *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghlInfoTo)); return re->G2API_AttachEnt( boltInfo, g2, 0, toBoltIndex, entNum, toModelNum ); } static void CL_G2API_SetBoltInfo( void *ghoul2, int modelIndex, int boltInfo ) { if ( !ghoul2 ) return; - re->G2API_SetBoltInfo( *((CGhoul2Info_v *)ghoul2), modelIndex, boltInfo ); + re->G2API_SetBoltInfo( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, boltInfo ); } static qboolean CL_G2API_SetRootSurface( void *ghoul2, const int modelIndex, const char *surfaceName ) { if ( !ghoul2 ) return qfalse; - return re->G2API_SetRootSurface( *((CGhoul2Info_v *)ghoul2), modelIndex, surfaceName ); + return re->G2API_SetRootSurface( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), modelIndex, surfaceName ); } static qboolean CL_G2API_SetSurfaceOnOff( void *ghoul2, const char *surfaceName, const int flags ) { if ( !ghoul2 ) return qfalse; - return re->G2API_SetSurfaceOnOff( *((CGhoul2Info_v *)ghoul2), surfaceName, flags ); + return re->G2API_SetSurfaceOnOff( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), surfaceName, flags ); } static qboolean CL_G2API_SetNewOrigin( void *ghoul2, const int boltIndex ) { if ( !ghoul2 ) return qfalse; - return re->G2API_SetNewOrigin( *((CGhoul2Info_v *)ghoul2), boltIndex ); + return re->G2API_SetNewOrigin( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), boltIndex ); } static qboolean CL_G2API_DoesBoneExist( void *ghoul2, int modelIndex, const char *boneName ) { if ( !ghoul2 ) return qfalse; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); return re->G2API_DoesBoneExist( g2, modelIndex, boneName ); } static int CL_G2API_GetSurfaceRenderStatus( void *ghoul2, const int modelIndex, const char *surfaceName ) { if ( !ghoul2 ) return -1; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); return re->G2API_GetSurfaceRenderStatus( g2, modelIndex, surfaceName ); } @@ -693,7 +858,7 @@ static void CL_G2API_SetTime( int time, int clock ) { static void CL_G2API_AbsurdSmoothing( void *ghoul2, qboolean status ) { if ( !ghoul2 ) return; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); re->G2API_AbsurdSmoothing( g2, status ); } @@ -702,7 +867,7 @@ static void CL_G2API_SetRagDoll( void *ghoul2, sharedRagDollParams_t *params ) { CRagDollParams rdParams; if ( !params ) { - re->G2API_ResetRagDoll( *((CGhoul2Info_v *)ghoul2) ); + re->G2API_ResetRagDoll( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)) ); return; } @@ -725,7 +890,7 @@ static void CL_G2API_SetRagDoll( void *ghoul2, sharedRagDollParams_t *params ) { rdParams.RagPhase = (CRagDollParams::ERagPhase)params->RagPhase; rdParams.effectorsToTurnOff = (CRagDollParams::ERagEffector)params->effectorsToTurnOff; - re->G2API_SetRagDoll( *((CGhoul2Info_v *)ghoul2), &rdParams ); + re->G2API_SetRagDoll( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), &rdParams ); } static void CL_G2API_AnimateG2Models( void *ghoul2, int time, sharedRagDollUpdateParams_t *params ) { @@ -741,58 +906,58 @@ static void CL_G2API_AnimateG2Models( void *ghoul2, int time, sharedRagDollUpdat rduParams.me = params->me; rduParams.settleFrame = params->settleFrame; - re->G2API_AnimateG2ModelsRag( *((CGhoul2Info_v *)ghoul2), time, &rduParams ); + re->G2API_AnimateG2ModelsRag( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), time, &rduParams ); } static qboolean CL_G2API_RagPCJConstraint( void *ghoul2, const char *boneName, vec3_t min, vec3_t max ) { if ( !ghoul2 ) return qfalse; - return re->G2API_RagPCJConstraint( *((CGhoul2Info_v *)ghoul2), boneName, min, max ); + return re->G2API_RagPCJConstraint( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), boneName, min, max ); } static qboolean CL_G2API_RagPCJGradientSpeed( void *ghoul2, const char *boneName, const float speed ) { if ( !ghoul2 ) return qfalse; - return re->G2API_RagPCJGradientSpeed( *((CGhoul2Info_v *)ghoul2), boneName, speed ); + return re->G2API_RagPCJGradientSpeed( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), boneName, speed ); } static qboolean CL_G2API_RagEffectorGoal( void *ghoul2, const char *boneName, vec3_t pos ) { if ( !ghoul2 ) return qfalse; - return re->G2API_RagEffectorGoal( *((CGhoul2Info_v *)ghoul2), boneName, pos ); + return re->G2API_RagEffectorGoal( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), boneName, pos ); } static qboolean CL_G2API_GetRagBonePos( void *ghoul2, const char *boneName, vec3_t pos, vec3_t entAngles, vec3_t entPos, vec3_t entScale ) { if ( !ghoul2 ) return qfalse; - return re->G2API_GetRagBonePos( *((CGhoul2Info_v *)ghoul2), boneName, pos, entAngles, entPos, entScale ); + return re->G2API_GetRagBonePos( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), boneName, pos, entAngles, entPos, entScale ); } static qboolean CL_G2API_RagEffectorKick( void *ghoul2, const char *boneName, vec3_t velocity ) { if ( !ghoul2 ) return qfalse; - return re->G2API_RagEffectorKick( *((CGhoul2Info_v *)ghoul2), boneName, velocity ); + return re->G2API_RagEffectorKick( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), boneName, velocity ); } static qboolean CL_G2API_RagForceSolve( void *ghoul2, qboolean force ) { if ( !ghoul2 ) return qfalse; - return re->G2API_RagForceSolve( *((CGhoul2Info_v *)ghoul2), force ); + return re->G2API_RagForceSolve( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), force ); } static qboolean CL_G2API_SetBoneIKState( void *ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params ) { if ( !ghoul2 ) return qfalse; - return re->G2API_SetBoneIKState( *((CGhoul2Info_v *)ghoul2), time, boneName, ikState, params ); + return re->G2API_SetBoneIKState( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), time, boneName, ikState, params ); } static qboolean CL_G2API_IKMove( void *ghoul2, int time, sharedIKMoveParams_t *params ) { if ( !ghoul2 ) return qfalse; - return re->G2API_IKMove( *((CGhoul2Info_v *)ghoul2), time, params ); + return re->G2API_IKMove( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), time, params ); } static qboolean CL_G2API_RemoveBone( void *ghoul2, const char *boneName, int modelIndex ) { if ( !ghoul2 ) return qfalse; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); return re->G2API_RemoveBone( g2, modelIndex, boneName ); } static void CL_G2API_AttachInstanceToEntNum( void *ghoul2, int entityNum, qboolean server ) { if ( !ghoul2 ) return; - re->G2API_AttachInstanceToEntNum( *((CGhoul2Info_v *)ghoul2), entityNum, server ); + re->G2API_AttachInstanceToEntNum( *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)), entityNum, server ); } static void CL_G2API_ClearAttachedInstance( int entityNum ) { @@ -805,13 +970,13 @@ static void CL_G2API_CleanEntAttachments( void ) { static qboolean CL_G2API_OverrideServer( void *serverInstance ) { if ( !serverInstance ) return qfalse; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)serverInstance); + CGhoul2Info_v &g2 = *(CL_G2Map_GetG2FromHandle((g2handleptr_t)serverInstance)); return re->G2API_OverrideServerWithClientData( g2, 0 ); } static void CL_G2API_GetSurfaceName( void *ghoul2, int surfNumber, int modelIndex, char *fillBuf ) { if ( !ghoul2 ) return; - CGhoul2Info_v &g2 = *((CGhoul2Info_v *)ghoul2); + CGhoul2Info_v &g2 = *(CL_G2Map_GetG2FromHandle((g2handleptr_t)ghoul2)); char *tmp = re->G2API_GetSurfaceName( g2, modelIndex, surfNumber ); strcpy( fillBuf, tmp ); } @@ -1142,7 +1307,7 @@ intptr_t CL_CgameSystemCalls( intptr_t *args ) { return 0; case CG_R_ADDREFENTITYTOSCENE: - re->AddRefEntityToScene( (const refEntity_t *)VMA(1) ); + CL_R_AddRefEntityToScene( (const refEntity_t *)VMA(1) ); return 0; case CG_R_ADDPOLYTOSCENE: @@ -1482,64 +1647,64 @@ intptr_t CL_CgameSystemCalls( intptr_t *args ) { return 0; case CG_G2_HAVEWEGHOULMODELS: - return CL_G2API_HaveWeGhoul2Models( VMA(1) ); + return CL_G2API_HaveWeGhoul2Models( (void*)args[1] ); case CG_G2_SETMODELS: - CL_G2API_SetGhoul2ModelIndexes( VMA(1), (qhandle_t *)VMA(2), (qhandle_t *)VMA(3) ); + CL_G2API_SetGhoul2ModelIndexes( (void*)args[1], (qhandle_t *)VMA(2), (qhandle_t *)VMA(3) ); return 0; case CG_G2_GETBOLT: - return CL_G2API_GetBoltMatrix(VMA(1), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + return CL_G2API_GetBoltMatrix((void*)args[1], args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); case CG_G2_GETBOLT_NOREC: - return CL_G2API_GetBoltMatrix_NoReconstruct(VMA(1), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + return CL_G2API_GetBoltMatrix_NoReconstruct((void*)args[1], args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); case CG_G2_GETBOLT_NOREC_NOROT: - return CL_G2API_GetBoltMatrix_NoRecNoRot(VMA(1), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + return CL_G2API_GetBoltMatrix_NoRecNoRot((void*)args[1], args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); case CG_G2_INITGHOUL2MODEL: return CL_G2API_InitGhoul2Model((void **)VMA(1), (const char *)VMA(2), args[3], (qhandle_t) args[4], (qhandle_t) args[5], args[6], args[7]); case CG_G2_SETSKIN: - return CL_G2API_SetSkin( VMA(1), args[2], args[3], args[4] ); + return CL_G2API_SetSkin( (void*)args[1], args[2], args[3], args[4] ); case CG_G2_COLLISIONDETECT: - CL_G2API_CollisionDetect ( (CollisionRecord_t*)VMA(1), VMA(2), (const float*)VMA(3), (const float*)VMA(4), args[5], args[6], (float*)VMA(7), (float*)VMA(8), (float*)VMA(9), args[10], args[11], VMF(12) ); + CL_G2API_CollisionDetect ( (CollisionRecord_t*)VMA(1), (void*)args[2], (const float*)VMA(3), (const float*)VMA(4), args[5], args[6], (float*)VMA(7), (float*)VMA(8), (float*)VMA(9), args[10], args[11], VMF(12) ); return 0; case CG_G2_COLLISIONDETECTCACHE: - CL_G2API_CollisionDetectCache ( (CollisionRecord_t*)VMA(1), VMA(2), (const float*)VMA(3), (const float*)VMA(4), args[5], args[6], (float*)VMA(7), (float*)VMA(8), (float*)VMA(9), args[10], args[11], VMF(12) ); + CL_G2API_CollisionDetectCache ( (CollisionRecord_t*)VMA(1), (void*)args[2], (const float*)VMA(3), (const float*)VMA(4), args[5], args[6], (float*)VMA(7), (float*)VMA(8), (float*)VMA(9), args[10], args[11], VMF(12) ); return 0; case CG_G2_ANGLEOVERRIDE: - return CL_G2API_SetBoneAngles( VMA(1), args[2], (const char *)VMA(3), (float *)VMA(4), args[5], args[6], args[7], args[8], (qhandle_t *)VMA(9), args[10], args[11] ); + return CL_G2API_SetBoneAngles( (void*)args[1], args[2], (const char *)VMA(3), (float *)VMA(4), args[5], args[6], args[7], args[8], (qhandle_t *)VMA(9), args[10], args[11] ); case CG_G2_CLEANMODELS: CL_G2API_CleanGhoul2Models( (void **)VMA(1) ); return 0; case CG_G2_PLAYANIM: - return CL_G2API_SetBoneAnim( VMA(1), args[2], (const char *)VMA(3), args[4], args[5], args[6], VMF(7), args[8], VMF(9), args[10] ); + return CL_G2API_SetBoneAnim( (void*)args[1], args[2], (const char *)VMA(3), args[4], args[5], args[6], VMF(7), args[8], VMF(9), args[10] ); case CG_G2_GETBONEANIM: - return CL_G2API_GetBoneAnim( VMA(1), (const char *)VMA(2), args[3], (float *)VMA(4), (int *)VMA(5), (int *)VMA(6), (int *)VMA(7), (float *)VMA(8), (int *)VMA(9), args[10] ); + return CL_G2API_GetBoneAnim( (void*)args[1], (const char *)VMA(2), args[3], (float *)VMA(4), (int *)VMA(5), (int *)VMA(6), (int *)VMA(7), (float *)VMA(8), (int *)VMA(9), args[10] ); case CG_G2_GETBONEFRAME: - return CL_G2API_GetBoneFrame( VMA(1), (const char*)VMA(2), args[3], (float *)VMA(4), (int *)VMA(5), args[6] ); + return CL_G2API_GetBoneFrame( (void*)args[1], (const char*)VMA(2), args[3], (float *)VMA(4), (int *)VMA(5), args[6] ); case CG_G2_GETGLANAME: - CL_G2API_GetGLAName( VMA(1), args[2], (char *)VMA(3) ); + CL_G2API_GetGLAName( (void*)args[1], args[2], (char *)VMA(3) ); return 0; case CG_G2_COPYGHOUL2INSTANCE: - return CL_G2API_CopyGhoul2Instance( VMA(1), VMA(2), args[3] ); + return CL_G2API_CopyGhoul2Instance( (void*)args[1], (void*)args[2], args[3] ); case CG_G2_COPYSPECIFICGHOUL2MODEL: - CL_G2API_CopySpecificGhoul2Model( VMA(1), args[2], VMA(3), args[4] ); + CL_G2API_CopySpecificGhoul2Model( (void*)args[1], args[2], (void*)args[3], args[4] ); return 0; case CG_G2_DUPLICATEGHOUL2INSTANCE: - CL_G2API_DuplicateGhoul2Instance( VMA(1), (void **)VMA(2) ); + CL_G2API_DuplicateGhoul2Instance( (void*)args[1], (void **)VMA(2) ); return 0; case CG_G2_HASGHOUL2MODELONINDEX: @@ -1549,46 +1714,46 @@ intptr_t CL_CgameSystemCalls( intptr_t *args ) { return CL_G2API_RemoveGhoul2Model( VMA(1), args[2]); case CG_G2_SKINLESSMODEL: - return CL_G2API_SkinlessModel( VMA(1), args[2] ); + return CL_G2API_SkinlessModel( (void*)args[1], args[2] ); case CG_G2_GETNUMGOREMARKS: - return CL_G2API_GetNumGoreMarks( VMA(1), args[2] ); + return CL_G2API_GetNumGoreMarks( (void*)args[1], args[2] ); case CG_G2_ADDSKINGORE: - CL_G2API_AddSkinGore( VMA(1), (SSkinGoreData *)VMA(2)); + CL_G2API_AddSkinGore( (void*)args[1], (SSkinGoreData *)VMA(2)); return 0; case CG_G2_CLEARSKINGORE: - CL_G2API_ClearSkinGore ( VMA(1) ); + CL_G2API_ClearSkinGore ( (void*)args[1] ); return 0; case CG_G2_SIZE: - return CL_G2API_Ghoul2Size ( VMA(1) ); + return CL_G2API_Ghoul2Size ( (void*)args[1] ); case CG_G2_ADDBOLT: - return CL_G2API_AddBolt( VMA(1), args[2], (const char *)VMA(3)); + return CL_G2API_AddBolt( (void*)args[1], args[2], (const char *)VMA(3)); case CG_G2_ATTACHENT: - return CL_G2API_AttachEnt( (int*)VMA(1), VMA(2), args[3], args[4], args[5] ); + return CL_G2API_AttachEnt( (int*)VMA(1), (void*)args[2], args[3], args[4], args[5] ); case CG_G2_SETBOLTON: - CL_G2API_SetBoltInfo( VMA(1), args[2], args[3] ); + CL_G2API_SetBoltInfo( (void*)args[1], args[2], args[3] ); return 0; case CG_G2_SETROOTSURFACE: - return CL_G2API_SetRootSurface( VMA(1), args[2], (const char *)VMA(3)); + return CL_G2API_SetRootSurface( (void*)args[1], args[2], (const char *)VMA(3)); case CG_G2_SETSURFACEONOFF: - return CL_G2API_SetSurfaceOnOff( VMA(1), (const char *)VMA(2), args[3]); + return CL_G2API_SetSurfaceOnOff( (void*)args[1], (const char *)VMA(2), args[3]); case CG_G2_SETNEWORIGIN: - return CL_G2API_SetNewOrigin( VMA(1), args[2]); + return CL_G2API_SetNewOrigin( (void*)args[1], args[2]); case CG_G2_DOESBONEEXIST: - return CL_G2API_DoesBoneExist( VMA(1), args[2], (const char *)VMA(3)); + return CL_G2API_DoesBoneExist( (void*)args[1], args[2], (const char *)VMA(3)); case CG_G2_GETSURFACERENDERSTATUS: - return CL_G2API_GetSurfaceRenderStatus( VMA(1), args[2], (const char *)VMA(3)); + return CL_G2API_GetSurfaceRenderStatus( (void*)args[1], args[2], (const char *)VMA(3)); case CG_G2_GETTIME: return CL_G2API_GetTime(); @@ -1598,48 +1763,48 @@ intptr_t CL_CgameSystemCalls( intptr_t *args ) { return 0; case CG_G2_ABSURDSMOOTHING: - CL_G2API_AbsurdSmoothing( VMA(1), (qboolean)args[2]); + CL_G2API_AbsurdSmoothing( (void*)args[1], (qboolean)args[2]); return 0; case CG_G2_SETRAGDOLL: - CL_G2API_SetRagDoll( VMA(1), (sharedRagDollParams_t *)VMA(2) ); + CL_G2API_SetRagDoll( (void*)args[1], (sharedRagDollParams_t *)VMA(2) ); return 0; case CG_G2_ANIMATEG2MODELS: - CL_G2API_AnimateG2Models( VMA(1), args[2], (sharedRagDollUpdateParams_t *)VMA(3) ); + CL_G2API_AnimateG2Models( (void*)args[1], args[2], (sharedRagDollUpdateParams_t *)VMA(3) ); return 0; //additional ragdoll options -rww case CG_G2_RAGPCJCONSTRAINT: - return CL_G2API_RagPCJConstraint( VMA(1), (const char *)VMA(2), (float *)VMA(3), (float *)VMA(4)); + return CL_G2API_RagPCJConstraint( (void*)args[1], (const char *)VMA(2), (float *)VMA(3), (float *)VMA(4)); case CG_G2_RAGPCJGRADIENTSPEED: - return CL_G2API_RagPCJGradientSpeed( VMA(1), (const char *)VMA(2), VMF(3)); + return CL_G2API_RagPCJGradientSpeed( (void*)args[1], (const char *)VMA(2), VMF(3)); case CG_G2_RAGEFFECTORGOAL: - return CL_G2API_RagEffectorGoal( VMA(1), (const char *)VMA(2), (float *)VMA(3)); + return CL_G2API_RagEffectorGoal( (void*)args[1], (const char *)VMA(2), (float *)VMA(3)); case CG_G2_GETRAGBONEPOS: - return CL_G2API_GetRagBonePos( VMA(1), (const char *)VMA(2), (float *)VMA(3), (float *)VMA(4), (float *)VMA(5), (float *)VMA(6)); + return CL_G2API_GetRagBonePos( (void*)args[1], (const char *)VMA(2), (float *)VMA(3), (float *)VMA(4), (float *)VMA(5), (float *)VMA(6)); case CG_G2_RAGEFFECTORKICK: - return CL_G2API_RagEffectorKick( VMA(1), (const char *)VMA(2), (float *)VMA(3)); + return CL_G2API_RagEffectorKick( (void*)args[1], (const char *)VMA(2), (float *)VMA(3)); case CG_G2_RAGFORCESOLVE: - return CL_G2API_RagForceSolve( VMA(1), (qboolean)args[2]); + return CL_G2API_RagForceSolve( (void*)args[1], (qboolean)args[2]); case CG_G2_SETBONEIKSTATE: - return CL_G2API_SetBoneIKState( VMA(1), args[2], (const char *)VMA(3), args[4], (sharedSetBoneIKStateParams_t *)VMA(5)); + return CL_G2API_SetBoneIKState( (void*)args[1], args[2], (const char *)VMA(3), args[4], (sharedSetBoneIKStateParams_t *)VMA(5)); case CG_G2_IKMOVE: - return CL_G2API_IKMove( VMA(1), args[2], (sharedIKMoveParams_t *)VMA(3)); + return CL_G2API_IKMove( (void*)args[1], args[2], (sharedIKMoveParams_t *)VMA(3)); case CG_G2_REMOVEBONE: - return CL_G2API_RemoveBone( VMA(1), (const char *)VMA(2), args[3] ); + return CL_G2API_RemoveBone( (void*)args[1], (const char *)VMA(2), args[3] ); case CG_G2_ATTACHINSTANCETOENTNUM: - CL_G2API_AttachInstanceToEntNum( VMA(1), args[2], (qboolean)args[3]); + CL_G2API_AttachInstanceToEntNum( (void*)args[1], args[2], (qboolean)args[3]); return 0; case CG_G2_CLEARATTACHEDINSTANCE: @@ -1651,10 +1816,10 @@ intptr_t CL_CgameSystemCalls( intptr_t *args ) { return 0; case CG_G2_OVERRIDESERVER: - return CL_G2API_OverrideServer( VMA(1) ); + return CL_G2API_OverrideServer( (void*)args[1] ); case CG_G2_GETSURFACENAME: - CL_G2API_GetSurfaceName( VMA(1), args[2], args[3], (char *)VMA(4) ); + CL_G2API_GetSurfaceName( (void*)args[1], args[2], args[3], (char *)VMA(4) ); return 0; case CG_SP_GETSTRINGTEXTSTRING: @@ -1765,7 +1930,7 @@ void CL_BindCGame( void ) { cgi.R_AddDecalToScene = re->AddDecalToScene; cgi.R_AddLightToScene = re->AddLightToScene; cgi.R_AddPolysToScene = re->AddPolyToScene; - cgi.R_AddRefEntityToScene = re->AddRefEntityToScene; + cgi.R_AddRefEntityToScene = CL_R_AddRefEntityToScene; cgi.R_AnyLanguage_ReadCharFromString = re->AnyLanguage_ReadCharFromString; cgi.R_AutomapElevationAdjustment = re->AutomapElevationAdjustment; cgi.R_ClearDecals = re->ClearDecals; From c249103ca41d17105fe6f0673219fd4893eb5f8b Mon Sep 17 00:00:00 2001 From: Daggolin Date: Tue, 3 Oct 2023 20:36:27 +0200 Subject: [PATCH 11/28] Cleanup for QVM related code. --- codemp/client/cl_cgameapi.cpp | 2 +- codemp/qcommon/vm.cpp | 3 --- codemp/server/sv_gameapi.cpp | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/codemp/client/cl_cgameapi.cpp b/codemp/client/cl_cgameapi.cpp index 84f3a91f4d..a4face8c33 100644 --- a/codemp/client/cl_cgameapi.cpp +++ b/codemp/client/cl_cgameapi.cpp @@ -335,7 +335,7 @@ trajectory_t *CGVM_GetAngleTrajectory( int entID ) { void CGVM_ROFF_NotetrackCallback( int entID, const char *notetrack ) { if ( cgvm->isLegacy ) { - int notetrack_length; + int notetrack_length = 0; const char *notetrack_ptr; if ( !cgvm->dllHandle ) { diff --git a/codemp/qcommon/vm.cpp b/codemp/qcommon/vm.cpp index 080ac9592a..f47c13f034 100644 --- a/codemp/qcommon/vm.cpp +++ b/codemp/qcommon/vm.cpp @@ -470,7 +470,6 @@ intptr_t QDECL VM_Call( vm_t *vm, int callnum, intptr_t arg0, intptr_t arg1, int return vm->legacy.main( callnum, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 ); } else { - size_t i; intptr_t r; if ( vm_debugLevel ) { Com_Printf( "VM_Call( %d )\n", callnum ); @@ -679,7 +678,6 @@ static void VM_GeneratePerfMap(vm_t *vm) { // generate perf .map file for profiling compiled QVM char mapFile[MAX_OSPATH]; FILE *f; - void *address; int length; if ( !vm->symbols ) { @@ -695,7 +693,6 @@ static void VM_GeneratePerfMap(vm_t *vm) { // perf .map format: "hex_address hex_length name\n" for ( vmSymbol_t *sym = vm->symbols; sym; sym = sym->next ) { - address = vm->codeBase + sym->symValue; if ( sym->next ) { length = sym->next->symValue - sym->symValue; diff --git a/codemp/server/sv_gameapi.cpp b/codemp/server/sv_gameapi.cpp index 023fc4ff18..73c74fe914 100644 --- a/codemp/server/sv_gameapi.cpp +++ b/codemp/server/sv_gameapi.cpp @@ -215,7 +215,7 @@ int GVM_BotAIStartFrame( int time ) { void GVM_ROFF_NotetrackCallback( int entID, const char *notetrack ) { if ( gvm->isLegacy ) { - int notetrack_length; + int notetrack_length = 0; const char *notetrack_ptr; if ( !gvm->dllHandle ) { From 6fd6d78eed78129bcf54e6b76d1897ce3b599bcb Mon Sep 17 00:00:00 2001 From: Daggolin Date: Fri, 6 Oct 2023 20:43:56 +0200 Subject: [PATCH 12/28] Fix previously undefined behavior in SV_ClipMoveToEntities. The vanilla and OpenJK game module use "-1" as magic value for the SV_Trace passEntityNum instead of ENTITYNUM_NONE (1023). This lead to SV_ClipMoveToEntities trying to access entity "-1" and to read its ownerNum. When switching from sharedEntity_t to sharedEntityMapper_t for the QVM support the code got adjusted to no longer read out of bounds values, potentially leaving passOwnerNum unset/undefined (as opposed to be read from undefined memory). This commit fixes this by assuming a default value and refactoring some code to be simpler. --- codemp/server/sv_world.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/codemp/server/sv_world.cpp b/codemp/server/sv_world.cpp index 51848d45ca..70e1eaa8dc 100644 --- a/codemp/server/sv_world.cpp +++ b/codemp/server/sv_world.cpp @@ -539,7 +539,7 @@ static void SV_ClipMoveToEntities( moveclip_t *clip ) { static int touchlist[MAX_GENTITIES]; int i, num; sharedEntityMapper_t *touch; - int passOwnerNum; + int passOwnerNum = -1; // Default to -1 trace_t trace, oldTrace= {0}; clipHandle_t clipHandle; float *origin, *angles; @@ -548,18 +548,14 @@ static void SV_ClipMoveToEntities( moveclip_t *clip ) { num = SV_AreaEntities( clip->boxmins, clip->boxmaxs, touchlist, MAX_GENTITIES); - if ( clip->passEntityNum != ENTITYNUM_NONE ) { - if ( passEnt && passEnt->r ) passOwnerNum = passEnt->r->ownerNum; - if ( passOwnerNum == ENTITYNUM_NONE ) { - passOwnerNum = -1; + if ( passEnt && passEnt->r ) { + if ( clip->passEntityNum != ENTITYNUM_NONE && passEnt->r->ownerNum != ENTITYNUM_NONE ) { + passOwnerNum = passEnt->r->ownerNum; } - } else { - passOwnerNum = -1; - } - if ( passEnt && passEnt->r && passEnt->r->svFlags & SVF_OWNERNOTSHARED ) - { - thisOwnerShared = 0; + if ( passEnt->r->svFlags & SVF_OWNERNOTSHARED ) { + thisOwnerShared = 0; + } } for ( i=0 ; i Date: Sat, 28 Oct 2023 07:18:00 +0200 Subject: [PATCH 13/28] Compile vm_legacy cvar into non-debug builds. As most (maybe all) modules that support the OpenJK API also support the Legacy API give users the option to use the legacy system without requiring a debug build. --- codemp/qcommon/vm.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/codemp/qcommon/vm.cpp b/codemp/qcommon/vm.cpp index f47c13f034..da18084591 100644 --- a/codemp/qcommon/vm.cpp +++ b/codemp/qcommon/vm.cpp @@ -55,14 +55,10 @@ cvar_t *vmModeCvar[MAX_VM]; static vm_t *vmTable[MAX_VM]; -#ifdef _DEBUG cvar_t *vm_legacy; -#endif void VM_Init( void ) { -#ifdef _DEBUG vm_legacy = Cvar_Get( "vm_legacy", "0", 0 ); -#endif vmModeCvar[VM_CGAME] = Cvar_Get( "vm_cgame", "2", CVAR_ARCHIVE ); vmModeCvar[VM_GAME] = Cvar_Get( "vm_game", "2", CVAR_ARCHIVE ); @@ -322,10 +318,8 @@ vm_t *VM_CreateLegacy( vmSlots_t vmSlot, intptr_t( *systemCalls )(intptr_t *) ) vm_t *VM_Create( vmSlots_t vmSlot ) { vm_t *vm = NULL; -#ifdef _DEBUG if ( (vm_legacy->integer & (1< Date: Sat, 28 Oct 2023 07:26:08 +0200 Subject: [PATCH 14/28] Mark native libraries using the Legacy API in vminfo command output. --- codemp/qcommon/vm.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/codemp/qcommon/vm.cpp b/codemp/qcommon/vm.cpp index da18084591..270fea9322 100644 --- a/codemp/qcommon/vm.cpp +++ b/codemp/qcommon/vm.cpp @@ -1029,7 +1029,11 @@ void VM_VmInfo_f( void ) { } Com_Printf( "%s : ", vm->name ); if ( vm->dllHandle ) { - Com_Printf( "native\n" ); + if ( vm->isLegacy ) { + Com_Printf( "native [legacy]\n" ); + } else { + Com_Printf( "native\n" ); + } } else { if (vm->compiled) { Com_Printf("compiled on load\n"); From db0a3d44159c71df434a5acbb531279f84632ab2 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Sat, 28 Oct 2023 07:47:31 +0200 Subject: [PATCH 15/28] Fix developer and cl_shownet output accessing invalid memory when using QVMs. --- codemp/icarus/Q3_Interface.cpp | 2 +- codemp/qcommon/msg.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codemp/icarus/Q3_Interface.cpp b/codemp/icarus/Q3_Interface.cpp index c873c47561..2627a46c81 100644 --- a/codemp/icarus/Q3_Interface.cpp +++ b/codemp/icarus/Q3_Interface.cpp @@ -694,7 +694,7 @@ void Q3_DebugPrint( int level, const char *format, ... ) if ( ( entNum < 0 ) || ( entNum >= MAX_GENTITIES ) ) entNum = 0; - Com_Printf ( S_COLOR_BLUE"DEBUG: %s(%d): %s\n", *(SV_GentityMapperNum(entNum)->script_targetname), entNum, buffer ); + Com_Printf ( S_COLOR_BLUE"DEBUG: %s(%d): %s\n", (char *)VM_ArgPtr((intptr_t)*(SV_GentityMapperNum(entNum)->script_targetname)), entNum, buffer ); break; } default: diff --git a/codemp/qcommon/msg.cpp b/codemp/qcommon/msg.cpp index 193059c18b..e1c043dc19 100644 --- a/codemp/qcommon/msg.cpp +++ b/codemp/qcommon/msg.cpp @@ -1176,7 +1176,7 @@ void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to, print = 1; if (sv.state) { - Com_Printf( "%3i: #%-3i (%s) ", msg->readcount, number, *SV_GentityMapperNum(number)->classname ); + Com_Printf( "%3i: #%-3i (%s) ", msg->readcount, number, (char *)VM_ArgPtr((intptr_t)*SV_GentityMapperNum(number)->classname) ); } else { From bce034710bbca8a0ec8f22329ba078b9ca91b2fd Mon Sep 17 00:00:00 2001 From: Daggolin Date: Sun, 29 Oct 2023 19:17:29 +0100 Subject: [PATCH 16/28] Use longjmp in Com_Error, because throwing an exception when the call comes from within the QVM results in a crash. --- codemp/qcommon/common.cpp | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/codemp/qcommon/common.cpp b/codemp/qcommon/common.cpp index 8cceb640e9..a8a01ca846 100644 --- a/codemp/qcommon/common.cpp +++ b/codemp/qcommon/common.cpp @@ -34,6 +34,9 @@ along with this program; if not, see . #define WIN32_LEAN_AND_MEAN #include #endif +#include + +static jmp_buf abortframe; FILE *debuglogfile; fileHandle_t logfile; @@ -289,7 +292,7 @@ void NORETURN QDECL Com_Error( int code, const char *fmt, ... ) { } if ( code == ERR_DISCONNECT || code == ERR_SERVERDISCONNECT || code == ERR_DROP || code == ERR_NEED_CD ) { - throw code; + longjmp(abortframe, code+1); // +1 to avoid 0 value } else { CL_Shutdown (); SV_Shutdown (va("Server fatal crashed: %s\n", com_errorMessage)); @@ -1126,10 +1129,17 @@ Com_Init void Com_Init( char *commandLine ) { char *s; int qport; + int errCode; Com_Printf( "%s %s %s\n", JK_VERSION, PLATFORM_STRING, SOURCE_DATE ); - try + if ( (errCode = setjmp(abortframe)) ) + { + errCode--; + Com_CatchError( errCode ); + Sys_Error( "Error during initialization: %s", Com_ErrorString(errCode)) ; + } + else { // initialize the weak pseudo-random number generator for use later. Com_InitRand(); @@ -1293,11 +1303,6 @@ void Com_Init( char *commandLine ) { com_fullyInitialized = qtrue; Com_Printf ("--- Common Initialization Complete ---\n"); } - catch ( int code ) - { - Com_CatchError (code); - Sys_Error ("Error during initialization: %s", Com_ErrorString (code)); - } } //================================================================== @@ -1460,8 +1465,15 @@ Com_Frame ================= */ void Com_Frame( void ) { - - try + int errCode; + if ( (errCode = setjmp(abortframe)) ) + { + errCode--; + Com_CatchError( errCode ); + Com_Printf ("%s\n", Com_ErrorString(errCode) ); + return; + } + else { #ifdef G2_PERFORMANCE_ANALYSIS G2PerformanceTimer_PreciseFrame.Start(); @@ -1638,11 +1650,6 @@ void Com_Frame( void ) { com_frameNumber++; } - catch (int code) { - Com_CatchError (code); - Com_Printf ("%s\n", Com_ErrorString (code)); - return; - } #ifdef G2_PERFORMANCE_ANALYSIS G2Time_PreciseFrame += G2PerformanceTimer_PreciseFrame.End(); From e02ac4a7e8b8f77de9b4765ff7686675444dbb6a Mon Sep 17 00:00:00 2001 From: Daggolin Date: Sun, 29 Oct 2023 19:43:57 +0100 Subject: [PATCH 17/28] Add com_dedicatedForceErrorsToFatal cvar. At some point OpenJK forced all Com_Error calls for dedicated servers to be ERR_FATAL, leading to the process terminating. This can be helpful for some simple forms of automatic restart scripts, but the original behavior of dropping to the interactive console can be useful during mod development or when testing various settings. To avoid issues with restart scripts depending on the ERR_FATAL rewrite the cvar defaults to enabled, but can be disabled to restore the original behavior. --- codemp/qcommon/common.cpp | 4 +++- codemp/qcommon/qcommon.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/codemp/qcommon/common.cpp b/codemp/qcommon/common.cpp index a8a01ca846..b908aa0284 100644 --- a/codemp/qcommon/common.cpp +++ b/codemp/qcommon/common.cpp @@ -46,6 +46,7 @@ fileHandle_t com_journalDataFile; // config files are written here cvar_t *com_speeds; cvar_t *com_developer; cvar_t *com_dedicated; +cvar_t *com_dedicatedForceErrorsToFatal; cvar_t *com_timescale; cvar_t *com_fixedtime; cvar_t *com_journal; @@ -267,7 +268,7 @@ void NORETURN QDECL Com_Error( int code, const char *fmt, ... ) { // ERR_DROPs on dedicated drop to an interactive console // which doesn't make sense for dedicated as it's generally // run unattended - if ( com_dedicated && com_dedicated->integer ) { + if ( com_dedicated && com_dedicated->integer && com_dedicatedForceErrorsToFatal && com_dedicatedForceErrorsToFatal->integer ) { code = ERR_FATAL; } @@ -1202,6 +1203,7 @@ void Com_Init( char *commandLine ) { #ifdef DEDICATED com_dedicated = Cvar_Get ("dedicated", "2", CVAR_INIT); Cvar_CheckRange( com_dedicated, 1, 2, qtrue ); + com_dedicatedForceErrorsToFatal = Cvar_Get( "com_dedicatedForceErrorsToFatal", "1", CVAR_ARCHIVE ); #else //OJKFIXME: Temporarily disabled dedicated server when not using the dedicated server binary. // Issue is the server not having a renderer when not using ^^^^^ diff --git a/codemp/qcommon/qcommon.h b/codemp/qcommon/qcommon.h index 97374a1f52..9e7e48ad5e 100644 --- a/codemp/qcommon/qcommon.h +++ b/codemp/qcommon/qcommon.h @@ -818,6 +818,7 @@ void Com_StartupVariable( const char *match ); extern cvar_t *com_developer; extern cvar_t *com_dedicated; +extern cvar_t *com_dedicatedForceErrorsToFatal; extern cvar_t *com_speeds; extern cvar_t *com_timescale; extern cvar_t *com_sv_running; From cf4fb13d2464116a1c497efc1623f053597fb910 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Tue, 31 Oct 2023 20:22:50 +0100 Subject: [PATCH 18/28] Fix return value of GVM_ClientConnect when using QVM. --- codemp/qcommon/vm.cpp | 10 +++++++--- codemp/server/sv_gameapi.cpp | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/codemp/qcommon/vm.cpp b/codemp/qcommon/vm.cpp index 270fea9322..b3e9312905 100644 --- a/codemp/qcommon/vm.cpp +++ b/codemp/qcommon/vm.cpp @@ -439,11 +439,15 @@ void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ) { if ( !intValue ) return NULL; - // currentVM is missing on reconnect here as well? - if ( !currentVM ) + // vm is missing on reconnect here as well? + if ( !vm ) return NULL; - return (void *)intValue; + if ( vm->dllHandle ) { + return (void *)intValue; + } else { + return (void *)(vm->dataBase + (intValue & vm->dataMask)); + } } float _vmf( intptr_t x ) { diff --git a/codemp/server/sv_gameapi.cpp b/codemp/server/sv_gameapi.cpp index 73c74fe914..544237cf73 100644 --- a/codemp/server/sv_gameapi.cpp +++ b/codemp/server/sv_gameapi.cpp @@ -122,7 +122,7 @@ void GVM_ShutdownGame( int restart ) { char *GVM_ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { if ( gvm->isLegacy ) - return (char *)VM_Call( gvm, GAME_CLIENT_CONNECT, clientNum, firstTime, isBot ); + return (char*)VM_ExplicitArgPtr( gvm, VM_Call(gvm, GAME_CLIENT_CONNECT, clientNum, firstTime, isBot) ); VMSwap v( gvm ); return ge->ClientConnect( clientNum, firstTime, isBot ); From cf10de65738d3759a9306ac861349ca132aa25f9 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Thu, 2 Nov 2023 21:21:51 +0100 Subject: [PATCH 19/28] Port file extension and character restrictions for vms from jk2mv. - Use GetFullPathNameA, GetFinalPathNameByHandleA and realpath to actually resolve paths when validating filenames - Use ERR_DROP instead of ERR_FATAL when a VM tries to access a forbidden extension - Check for more forbidden extensions - Prevent the VM from accessing files with unwanted characters in their names (just block the access and print a warning, don't drop) --- codemp/qcommon/files.cpp | 90 ++++++++++++++++++++++++++++++++++++++-- shared/sys/sys_main.cpp | 17 ++++++++ shared/sys/sys_public.h | 4 ++ shared/sys/sys_unix.cpp | 40 ++++++++++++++++++ shared/sys/sys_win32.cpp | 69 ++++++++++++++++++++++++++++++ 5 files changed, 216 insertions(+), 4 deletions(-) diff --git a/codemp/qcommon/files.cpp b/codemp/qcommon/files.cpp index 7077b2d4c3..210c4857f9 100644 --- a/codemp/qcommon/files.cpp +++ b/codemp/qcommon/files.cpp @@ -312,6 +312,77 @@ FILE* missingFiles = NULL; # endif #endif +const char *get_filename_ext(const char *filename) { + const char *dot = strrchr(filename, '.'); + if (!dot || dot == filename) return ""; + return dot + 1; +} + +// We don't want VMs to be able to access the following file extensions +static char *invalidExtensions[] = { + "dll", + "exe", + "bat", + "cmd", + "dylib", + "so", + "qvm", + "pk3", +}; +static int invalidExtensionsAmount = sizeof(invalidExtensions) / sizeof(invalidExtensions[0]); + +qboolean FS_IsInvalidExtension( const char *ext ) { + int i; + + // Compare against the list of forbidden extensions + for ( i = 0; i < invalidExtensionsAmount; i++ ) { + if ( !Q_stricmp(ext, invalidExtensions[i]) ) + return qtrue; + } + + // Additional check we don't currently need, but if support for another platform is added and the list above is not + // updated we catch it with this check. + if ( !Q_stricmp(va(".%s", ext), DLL_EXT) ) + return qtrue; + + return qfalse; +} + +// Invalid characters. Originally intended to be OS-specific, but considering that VMs run on different systems it's +// probably not a bad idea to share the same behavior on all systems. +qboolean FS_ContainsInvalidCharacters( const char *filename ) { + static char *invalidCharacters = "<>:\"|?*"; + char *ptr = invalidCharacters; + + while ( *ptr ) { + if ( strchr(filename, *ptr) ) + return qtrue; + ptr++; + } + + return qfalse; +} + +qboolean FS_IsInvalidWriteOSPath(const char *ospath) { + const char *resolved; + const char *realPath; + + // Resolve paths + resolved = Sys_ResolvePath( ospath ); + if ( !strlen(resolved) ) return qtrue; + realPath = Sys_RealPath( resolved ); + if ( !strlen(realPath) ) return qtrue; + + // Check all three, the original input, the resolved and the real path, in case there is a symlink + // NOTE: Checking the ospath shouldn't be required, because we resolved the path, but let's check it anyway. + if ( FS_IsInvalidExtension(get_filename_ext(ospath)) || FS_IsInvalidExtension(get_filename_ext(resolved)) || FS_IsInvalidExtension(get_filename_ext(realPath)) ) { + Com_Printf( "FS_IsInvalidWriteOSPath: blocked writing binary file \"%s\" (%s) [%s].\n", ospath, resolved, realPath ); + return qtrue; + } + + return qfalse; +} + /* ============== FS_Initialized @@ -572,11 +643,10 @@ ERR_FATAL if trying to maniuplate a file with the platform library, or pk3 exten */ static void FS_CheckFilenameIsMutable( const char *filename, const char *function ) { - // Check if the filename ends with the library, or pk3 extension - if( COM_CompareExtension( filename, DLL_EXT ) - || COM_CompareExtension( filename, ".pk3" ) ) + // Call FS_InvalidWriteOSPath from JK2MV, because it handles symlinks and some Windows specific tricks to bypass these checks. + if ( FS_IsInvalidWriteOSPath(filename) ) { - Com_Error( ERR_FATAL, "%s: Not allowed to manipulate '%s' due " + Com_Error( ERR_DROP, "%s: Not allowed to manipulate '%s' due " "to %s extension", function, filename, COM_GetExtension( filename ) ); } } @@ -593,6 +663,11 @@ void FS_CopyFile( char *fromOSPath, char *toOSPath ) { int len; byte *buf; + if ( FS_ContainsInvalidCharacters(toOSPath) ) { + Com_Printf( "FS_CopyFile: invalid filename (%s)\n", toOSPath ); + return; + } + FS_CheckFilenameIsMutable( fromOSPath, __func__ ); Com_Printf( "copy %s to %s\n", fromOSPath, toOSPath ); @@ -3950,6 +4025,13 @@ int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) { sync = qfalse; + // Only check the unresolved path for invalid characters, the os probably knows what it's doing + if ( FS_ContainsInvalidCharacters(qpath) ) { + Com_Printf( "FS_FOpenFileByMode: invalid filename (%s)\n", qpath ); + *f = 0; + return -1; + } + switch( mode ) { case FS_READ: r = FS_FOpenFileRead( qpath, f, qtrue ); diff --git a/shared/sys/sys_main.cpp b/shared/sys/sys_main.cpp index fd103e713f..7e80807f90 100644 --- a/shared/sys/sys_main.cpp +++ b/shared/sys/sys_main.cpp @@ -737,6 +737,7 @@ int main ( int argc, char* argv[] ) { int i; char commandLine[ MAX_STRING_CHARS ] = { 0 }; + int missingFuncs = Sys_FindFunctions(); Sys_PlatformInit(); CON_Init(); @@ -770,6 +771,22 @@ int main ( int argc, char* argv[] ) Com_Init (commandLine); + if ( missingFuncs ) { + static const char *missingFuncsError = + "Your system is missing functions this application relies on.\n" + "\n" + "Some features may be unavailable or their behavior may be incorrect."; + + // Set the error cvar (the main menu should pick this up and display an error box to the user) + Cvar_Get( "com_errorMessage", missingFuncsError, CVAR_ROM ); + Cvar_Set( "com_errorMessage", missingFuncsError ); + + // Print the error into the console, because we can't always display the main menu (dedicated servers, ...) + Com_Printf( "********************\n" ); + Com_Printf( "ERROR: %s\n", missingFuncsError ); + Com_Printf( "********************\n" ); + } + #ifndef DEDICATED SDL_version compiled; SDL_version linked; diff --git a/shared/sys/sys_public.h b/shared/sys/sys_public.h index 8378d79f3c..179ceb2f73 100644 --- a/shared/sys/sys_public.h +++ b/shared/sys/sys_public.h @@ -158,6 +158,10 @@ void Sys_SetProcessorAffinity( void ); int Sys_PID( void ); +const char *Sys_ResolvePath( const char *path ); +const char *Sys_RealPath( const char *path ); +int Sys_FindFunctions( void ); + typedef enum graphicsApi_e { GRAPHICS_API_GENERIC, diff --git a/shared/sys/sys_unix.cpp b/shared/sys/sys_unix.cpp index 04a579b972..2597a8335a 100644 --- a/shared/sys/sys_unix.cpp +++ b/shared/sys/sys_unix.cpp @@ -613,3 +613,43 @@ void Sys_AnsiColorPrint( const char *msg ) fputs( buffer, stderr ); } } + +/* +=============== +Sys_ResolvePath +=============== +*/ +const char *Sys_ResolvePath( const char *path ) +{ // There seems to be no function to resolve paths of files that don't exist + // on unix, so we just return the input path. This shouldn't be an issue, + // as we just need to resolve paths for those on windows anyway. + + return path; +} + +/* +=============== +Sys_RealPath +=============== +*/ +const char *Sys_RealPath( const char *path ) +{ + static char realPath[PATH_MAX+1]; + if ( realpath(path, realPath) ) + return realPath; + return path; +} + +/* +=============== +Sys_FindFunctions +=============== +*/ +int Sys_FindFunctions( void ) +{ + // We only use this function on Windows to find functions that might not be + // available on all OS versions, but that may be required for some Sys_ + // functions to be fully operational. On unix we can just return 0. + + return 0; +} diff --git a/shared/sys/sys_win32.cpp b/shared/sys/sys_win32.cpp index 5b6c234cf5..726e7dc1d4 100644 --- a/shared/sys/sys_win32.cpp +++ b/shared/sys/sys_win32.cpp @@ -591,3 +591,72 @@ void Sys_Sleep( int msec ) Sleep( msec ); #endif } + +/* +=============== +Sys_ResolvePath +=============== +*/ +const char *Sys_ResolvePath( const char *path ) +{ + static char resolvedPath[MAX_PATH]; + + if ( !GetFullPathNameA((LPCSTR)path, sizeof(resolvedPath), (LPSTR)resolvedPath, NULL) ) + return ""; + + return resolvedPath; +} + +/* +=============== +Sys_RealPath +=============== +*/ + +typedef DWORD (WINAPI *_GetFinalPathNameByHandleA)( _In_ HANDLE, _Out_writes_(cchFilePath) LPSTR, _In_ DWORD, _In_ DWORD ); +_GetFinalPathNameByHandleA Win_GetFinalPathNameByHandleA; +const char *Sys_RealPath( const char *path ) +{ + static char realPath[MAX_PATH]; + HANDLE fileHandle; + DWORD len; + + // Return the original path if the system doesn't support GetFinalPathNameByHandleA + if ( !Win_GetFinalPathNameByHandleA ) return path; + + // Get a handle to later resolve symlinks + fileHandle = CreateFileA( (LPCSTR)path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); + + // If we can't access it return the original instead + if( fileHandle == INVALID_HANDLE_VALUE ) + return path; + + // Get the resolvedName from the handle + len = Win_GetFinalPathNameByHandleA( fileHandle, (LPSTR)realPath, sizeof(realPath), VOLUME_NAME_NT ); + + // Close the handle + CloseHandle( fileHandle ); + + // If it's longer than we can store return "" to disable access + if( len >= sizeof(realPath) ) + return ""; + + return realPath; +} + +/* +=============== +Sys_FindFunctions +=============== +*/ +int Sys_FindFunctions( void ) +{ + int missingFuncs = 0; + + // Get the kernel32 handle and try to find the function "GetFinalPathNameByHandleA" + HINSTANCE handle = GetModuleHandleA( "KERNEL32" ); + if ( handle ) Win_GetFinalPathNameByHandleA = (_GetFinalPathNameByHandleA)GetProcAddress( handle, "GetFinalPathNameByHandleA" ); + if ( !Win_GetFinalPathNameByHandleA ) missingFuncs++; + + return missingFuncs; +} From d3f43adf4fc5ff3d7bd63dbd70c95c0cd0e95839 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Thu, 2 Nov 2023 23:50:06 +0100 Subject: [PATCH 20/28] Add win32/q3asm.asm from jk2mv for MSVC builds. --- CMakeLists.txt | 7 ++++ codemp/CMakeLists.txt | 6 +++ codemp/win32/q3asm.asm | 83 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 codemp/win32/q3asm.asm diff --git a/CMakeLists.txt b/CMakeLists.txt index e25fea87bc..32f69cd99c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,11 @@ list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_SOURCE_DIR}/cmake/Modules") # ${Architecture} must match ARCH_STRING in q_platform.h, # and is used in DLL names (jagamex86.dll, jagamex86.dylib, jagamei386.so). if(WIN32) + + if(MSVC) + enable_language(ASM_MASM) + endif() + set(X86 ON) if(CMAKE_SIZEOF_VOID_P MATCHES "8") set(Architecture "x86_64") @@ -216,6 +221,8 @@ if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:SSE2") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /safeseh:no") + # We don't try to control symbol visibility under MSVC. set(OPENJK_VISIBILITY_FLAGS "") elseif (("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") OR ("${CMAKE_C_COMPILER_ID}" MATCHES "Clang")) diff --git a/codemp/CMakeLists.txt b/codemp/CMakeLists.txt index c043dd22a6..bcb0b5f5d4 100644 --- a/codemp/CMakeLists.txt +++ b/codemp/CMakeLists.txt @@ -271,6 +271,12 @@ if(BuildMPEngine OR BuildMPDed) ) endif() + if(MSVC) + set(MPEngineAndDedCommonFiles ${MPEngineAndDedCommonFiles} + "${MPDir}/win32/q3asm.asm" + ) + endif() + if(WIN32) set(MPEngineAndDedCommonFiles ${MPEngineAndDedCommonFiles}) endif(WIN32) diff --git a/codemp/win32/q3asm.asm b/codemp/win32/q3asm.asm new file mode 100644 index 0000000000..b64f07a51e --- /dev/null +++ b/codemp/win32/q3asm.asm @@ -0,0 +1,83 @@ +; =========================================================================== +; Copyright (C) 2011 Thilo Schulz +; +; 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 Quake III Arena source code; if not, write to the Free Software +; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +; =========================================================================== + +; MASM ftol conversion functions using SSE or FPU +; assume __cdecl calling convention is being used for x86, __fastcall for x64 + +IFNDEF WIN64 +.686p +.xmm +.model flat, c +ENDIF + +.data +ALIGN 16 +ssemask DWORD 0FFFFFFFFh, 0FFFFFFFFh, 0FFFFFFFFh, 00000000h + +.code + +; dummy +IFNDEF WIN64 + .safeseh SEH_handler + SEH_handler proc + ret + SEH_handler endp +ENDIF + +IFDEF WIN64 + Q_VMftol PROC + movss xmm0, dword ptr [rdi + rbx * 4] + cvttss2si eax, xmm0 + ret + Q_VMftol ENDP + + qvmcall64 PROC + push rsi ; push non-volatile registers to stack + push rdi + push rbx + push rcx ; need to save pointer in rcx so we can write back the programData value to caller + + ; registers r8 and r9 have correct value already thanx to __fastcall + xor rbx, rbx ; opStackOfs starts out being 0 + mov rdi, rdx ; opStack + mov esi, dword ptr [rcx] ; programStack + + call qword ptr [r8] ; instructionPointers[0] is also the entry point + + pop rcx + + mov dword ptr [rcx], esi ; write back the programStack value + mov al, bl ; return opStack offset + + pop rbx + pop rdi + pop rsi + + ret + qvmcall64 ENDP +ELSE + Q_VMftol PROC + movss xmm0, dword ptr [edi + ebx * 4] + cvttss2si eax, xmm0 + ret + Q_VMftol ENDP +ENDIF + +end From 4ab1db0459ae505e62c09db8041e2f4ae8fb92f9 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Fri, 3 Nov 2023 21:13:02 +0100 Subject: [PATCH 21/28] vm_x86.cpp: push and pop ebx in EmitCallDoSyscall. Existing comments in the function suggested it would already push and pop ebx (non-volatile), but really performed the actions on ecx (volatile). --- codemp/qcommon/vm_x86.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/codemp/qcommon/vm_x86.cpp b/codemp/qcommon/vm_x86.cpp index 3931f6767a..ca1cabaf7b 100644 --- a/codemp/qcommon/vm_x86.cpp +++ b/codemp/qcommon/vm_x86.cpp @@ -491,7 +491,8 @@ int EmitCallDoSyscall(vm_t *vm) // Push important registers to stack as we can't really make // any assumptions about calling conventions. - EmitString("51"); // push ebx + EmitString("51"); // push ecx + EmitString("53"); // push ebx EmitString("56"); // push esi EmitString("57"); // push edi #if defined(idx64) @@ -539,7 +540,8 @@ int EmitCallDoSyscall(vm_t *vm) #endif EmitString("5F"); // pop edi EmitString("5E"); // pop esi - EmitString("59"); // pop ebx + EmitString("5B"); // pop ebx + EmitString("59"); // pop ecx // restore frame pointer EmitString("5D"); // pop ebp From afa3d1313f9cbcf21192801d9f4c29bd7b8c013f Mon Sep 17 00:00:00 2001 From: Daggolin Date: Fri, 3 Nov 2023 21:18:36 +0100 Subject: [PATCH 22/28] vm_x86.cpp: backup and restore registers r12, r13, r14 and r15 in EmitCallDoSyscall, because they are non-volatile. --- codemp/qcommon/vm_x86.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/codemp/qcommon/vm_x86.cpp b/codemp/qcommon/vm_x86.cpp index ca1cabaf7b..5e986ec7df 100644 --- a/codemp/qcommon/vm_x86.cpp +++ b/codemp/qcommon/vm_x86.cpp @@ -498,6 +498,10 @@ int EmitCallDoSyscall(vm_t *vm) #if defined(idx64) EmitRexString(0x41, "50"); // push r8 EmitRexString(0x41, "51"); // push r9 + EmitRexString(0x41, "54"); // push r12 + EmitRexString(0x41, "55"); // push r13 + EmitRexString(0x41, "56"); // push r14 + EmitRexString(0x41, "57"); // push r15 #endif // write arguments to global vars @@ -535,6 +539,10 @@ int EmitCallDoSyscall(vm_t *vm) // reset the stack pointer to its previous value EmitString("5C"); // pop esp #if defined(idx64) + EmitRexString(0x41, "5F"); // pop r15 + EmitRexString(0x41, "5E"); // pop r14 + EmitRexString(0x41, "5D"); // pop r13 + EmitRexString(0x41, "5C"); // pop r12 EmitRexString(0x41, "59"); // pop r9 EmitRexString(0x41, "58"); // pop r8 #endif From 2a74fa5e8e325685a9770e5327d1144685176043 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Fri, 3 Nov 2023 21:20:54 +0100 Subject: [PATCH 23/28] vm_x86.cpp: add workaround for x64 Windows in EmitCallDoSyscall. --- codemp/qcommon/vm_x86.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/codemp/qcommon/vm_x86.cpp b/codemp/qcommon/vm_x86.cpp index 5e986ec7df..104e26b455 100644 --- a/codemp/qcommon/vm_x86.cpp +++ b/codemp/qcommon/vm_x86.cpp @@ -526,18 +526,27 @@ int EmitCallDoSyscall(vm_t *vm) EmitPtr(&vm_arg); // align the stack pointer to a 16-byte-boundary +#if defined(_MSC_VER) && defined(idx64) + EmitRexString(0x48, "89 E6"); // mov esi, esp + EmitRexString(0x48, "83 E4 F0"); // and esp, 0xFFFFFFF0 +#else EmitRexString(0x48, "89 E0"); // mov eax, esp EmitString("51"); // push ecx (decrease esp in a portable way) EmitRexString(0x48, "83 E4 F0"); // and esp, 0xFFFFFFF0 EmitString("59"); // pop ecx (increase esp in a portable way) EmitString("50"); // push eax +#endif // call the syscall wrapper function DoSyscall() EmitString("FF D2"); // call edx // reset the stack pointer to its previous value +#if defined(_MSC_VER) && defined(idx64) + EmitRexString(0x48, "89 F4"); // mov esp, esi +#else EmitString("5C"); // pop esp +#endif #if defined(idx64) EmitRexString(0x41, "5F"); // pop r15 EmitRexString(0x41, "5E"); // pop r14 From 2626c388a598fa209bee9df101bcc397c185eb75 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Mon, 6 Nov 2023 01:31:54 +0100 Subject: [PATCH 24/28] Fix behaviorSet in sharedEntityMapper when using QVMs. --- codemp/game/g_public.h | 2 +- codemp/icarus/GameInterface.cpp | 8 ++++---- codemp/server/sv_gameapi.cpp | 14 +++++++++----- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/codemp/game/g_public.h b/codemp/game/g_public.h index 6c56c33a4b..6caaf26215 100644 --- a/codemp/game/g_public.h +++ b/codemp/game/g_public.h @@ -313,7 +313,7 @@ typedef struct sharedEntityMapper_s { //Script/ICARUS-related fields int (*taskID)[NUM_TIDS]; parms_t **parms; - char *(*behaviorSet)[NUM_BSETS]; + char **behaviorSet[NUM_BSETS]; char **script_targetname; int *delayScriptTime; char **fullName; diff --git a/codemp/icarus/GameInterface.cpp b/codemp/icarus/GameInterface.cpp index 8a94c4cda2..43ed172cc4 100644 --- a/codemp/icarus/GameInterface.cpp +++ b/codemp/icarus/GameInterface.cpp @@ -294,7 +294,7 @@ bool ICARUS_ValidEnt( sharedEntityMapper_t *ent ) //Potentially able to call a script for ( i = 0; i < NUM_BSETS; i++ ) { - if VALIDSTRING( (*(ent->behaviorSet))[i] ) + if VALIDSTRING( *((ent->behaviorSet)[i]) ) { //Com_Printf( "WARNING: Entity %d (%s) has behaviorSet but no script_targetname -- using targetname\n", ent->s.number, ent->targetname ); @@ -635,12 +635,12 @@ void ICARUS_PrecacheEnt( sharedEntityMapper_t *ent ) for ( i = 0; i < NUM_BSETS; i++ ) { - if ( (*(ent->behaviorSet))[i] == NULL ) + if ( *((ent->behaviorSet)[i]) == NULL ) continue; - if ( GetIDForString( BSTable, (*(ent->behaviorSet))[i] ) == -1 ) + if ( GetIDForString( BSTable, *((ent->behaviorSet)[i]) ) == -1 ) {//not a behavior set - Com_sprintf( newname, sizeof(newname), "%s/%s", Q3_SCRIPT_DIR, (*(ent->behaviorSet))[i] ); + Com_sprintf( newname, sizeof(newname), "%s/%s", Q3_SCRIPT_DIR, *((ent->behaviorSet)[i]) ); //Precache this, and all internally referenced scripts ICARUS_InterrogateScript( newname ); diff --git a/codemp/server/sv_gameapi.cpp b/codemp/server/sv_gameapi.cpp index 544237cf73..0ca4a709c2 100644 --- a/codemp/server/sv_gameapi.cpp +++ b/codemp/server/sv_gameapi.cpp @@ -580,7 +580,7 @@ void SV_BotWaypointReception( int wpnum, wpobject_t **wps ); void SV_BotCalculatePaths( int rmg ); static void SV_UpdateSharedEntitiesMapping( void ) { - int i; + int i, j; int entCount = Com_Clampi( 0, ARRAY_LEN(sv.gentitiesMapper), sv.num_entities ); sharedEntityMapper_t *entM; @@ -601,7 +601,9 @@ static void SV_UpdateSharedEntitiesMapping( void ) { entM->r = &ent->r; entM->taskID = &ent->taskID; entM->parms = &ent->parms; - entM->behaviorSet = &ent->behaviorSet; + for ( j = 0; j < NUM_BSETS; j++ ) { + entM->behaviorSet[j] = &(ent->behaviorSet[j]); + } entM->script_targetname = &ent->script_targetname; entM->delayScriptTime = &ent->delayScriptTime; entM->fullName = &ent->fullName; @@ -637,7 +639,9 @@ static void SV_UpdateSharedEntitiesMapping( void ) { entM->r = &ent->r; entM->taskID = &ent->taskID; entM->parms = (parms_t**)&ent->parms; - entM->behaviorSet = (char*(*)[NUM_BSETS])&ent->behaviorSet; + for ( j = 0; j < NUM_BSETS; j++ ) { + entM->behaviorSet[j] = (char**)&(ent->behaviorSet[j]); + } entM->script_targetname = (char**)&ent->script_targetname; entM->delayScriptTime = &ent->delayScriptTime; entM->fullName = (char**)&ent->fullName; @@ -819,7 +823,8 @@ sharedEntityMapper_t *ConvertedEntity( sharedEntity_t *ent ) { //Return an entit gLocalModifier.parms = (parms_t *)VM_ArgPtr((intptr_t)(*(gEntM->parms))); while (i < NUM_BSETS) { - gLocalModifier.behaviorSet[i] = (char *)VM_ArgPtr((intptr_t)(*(gEntM->behaviorSet))[i]); + gLocalModifier.behaviorSet[i] = (char *)VM_ArgPtr((intptr_t)(*(gEntM->behaviorSet[i]))); + gLocalModifierMapper.behaviorSet[i] = &(gLocalModifier.behaviorSet[i]); i++; } i = 0; @@ -840,7 +845,6 @@ sharedEntityMapper_t *ConvertedEntity( sharedEntity_t *ent ) { //Return an entit gLocalModifierMapper.r = &gLocalModifier.r; gLocalModifierMapper.taskID = &gLocalModifier.taskID; gLocalModifierMapper.parms = &gLocalModifier.parms; - gLocalModifierMapper.behaviorSet = &gLocalModifier.behaviorSet; gLocalModifierMapper.script_targetname = &gLocalModifier.script_targetname; gLocalModifierMapper.delayScriptTime = &gLocalModifier.delayScriptTime; gLocalModifierMapper.fullName = &gLocalModifier.fullName; From 9eb850230a8204d55e5ed3b57f02ff5f269fbbe5 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Mon, 6 Nov 2023 04:56:23 +0100 Subject: [PATCH 25/28] Use new macros to fix double pointers in sharedEntityMapper reading 64 bit addresses from QVMs when dereferencing. Remove ConvertedEntity function, because the sharedEntityMapper read macros handle the required changes on demand now. --- codemp/icarus/GameInterface.cpp | 38 +++++++++----- codemp/icarus/Q3_Interface.cpp | 2 +- codemp/qcommon/msg.cpp | 2 +- codemp/qcommon/vm.cpp | 6 +++ codemp/qcommon/vm_local.h | 1 + codemp/server/server.h | 12 ++++- codemp/server/sv_gameapi.cpp | 88 +++++++++------------------------ codemp/server/sv_snapshot.cpp | 5 +- codemp/server/sv_world.cpp | 10 ++-- 9 files changed, 76 insertions(+), 88 deletions(-) diff --git a/codemp/icarus/GameInterface.cpp b/codemp/icarus/GameInterface.cpp index 43ed172cc4..0376cc5742 100644 --- a/codemp/icarus/GameInterface.cpp +++ b/codemp/icarus/GameInterface.cpp @@ -30,6 +30,7 @@ along with this program; if not, see . #include "qcommon/RoffSystem.h" #include "Q3_Interface.h" #include "server/sv_gameapi.h" +#include "qcommon/vm_local.h" ICARUS_Instance *iICARUS; bufferlist_t ICARUS_BufferList; @@ -144,7 +145,7 @@ int ICARUS_RunScript( sharedEntityMapper_t *ent, const char *name ) if ( ( ICARUS_entFilter == -1 ) || ( ICARUS_entFilter == ent->s->number ) ) { - Q3_DebugPrint( WL_VERBOSE, "%d Script %s executed by %s %s\n", svs.time, (char *) name, *(ent->classname), *(ent->targetname) ); + Q3_DebugPrint( WL_VERBOSE, "%d Script %s executed by %s %s\n", svs.time, (char *) name, SV_EntityMapperReadString(ent->classname), SV_EntityMapperReadString(ent->targetname) ); } return true; @@ -199,7 +200,7 @@ void ICARUS_Shutdown( void ) ent->s->number = i; assert(0); } - ICARUS_FreeEnt( ConvertedEntity(SV_GentityNum(i)) ); + ICARUS_FreeEnt( ent ); } } @@ -237,6 +238,7 @@ FIXME: shouldn't ICARUS handle this internally? */ void ICARUS_FreeEnt( sharedEntityMapper_t *ent ) { + const char *script_targetname; assert( iICARUS ); if (ent->s->number >= MAX_GENTITIES || @@ -251,11 +253,12 @@ void ICARUS_FreeEnt( sharedEntityMapper_t *ent ) return; //Remove them from the ICARUSE_EntList list so that when their g_entity index is reused, ICARUS doesn't try to affect the new (incorrect) ent. - if VALIDSTRING( (*(ent->script_targetname)) ) + script_targetname = SV_EntityMapperReadString( ent->script_targetname ); + if VALIDSTRING( script_targetname ) { char temp[1024]; - strncpy( (char *) temp, *(ent->script_targetname), 1023 ); + strncpy( (char *) temp, script_targetname, 1023 ); temp[ 1023 ] = 0; entlist_t::iterator it = ICARUS_EntList.find( Q_strupr(temp) ); @@ -285,16 +288,17 @@ Determines whether or not an entity needs ICARUS information bool ICARUS_ValidEnt( sharedEntityMapper_t *ent ) { + const char *script_targetname = SV_EntityMapperReadString( ent->script_targetname ); int i; //Targeted by a script - if VALIDSTRING( (*(ent->script_targetname)) ) + if VALIDSTRING( script_targetname ) return true; //Potentially able to call a script for ( i = 0; i < NUM_BSETS; i++ ) { - if VALIDSTRING( *((ent->behaviorSet)[i]) ) + if VALIDSTRING( SV_EntityMapperReadString(ent->behaviorSet[i]) ) { //Com_Printf( "WARNING: Entity %d (%s) has behaviorSet but no script_targetname -- using targetname\n", ent->s.number, ent->targetname ); @@ -303,10 +307,16 @@ bool ICARUS_ValidEnt( sharedEntityMapper_t *ent ) //and while this allows us to read it on our "fake" entity here, we can't modify pointers like this. We can however do //something completely hackish such as the following. assert(ent->s->number >= 0 && ent->s->number < MAX_GENTITIES); - sharedEntityMapper_t *trueEntity = SV_GentityMapperNum(ent->s->number); + sharedEntity_t *trueEntity = SV_GentityNum(ent->s->number); + //This works because we're modifying the actual shared game vm data and turning one pointer into another. //While these pointers both look like garbage to us in here, they are not. - *(trueEntity->script_targetname) = *(trueEntity->targetname); + if ( VM_IsCurrentQVM() ) + { + sharedEntity_qvm_t *trueEntityQVM = (sharedEntity_qvm_t*)trueEntity; + trueEntityQVM->script_targetname = trueEntityQVM->targetname; + } + else trueEntity->script_targetname = trueEntity->targetname; return true; } } @@ -324,12 +334,13 @@ Associate the entity's id and name so that it can be referenced later void ICARUS_AssociateEnt( sharedEntityMapper_t *ent ) { + const char *script_targetname = SV_EntityMapperReadString( ent->script_targetname ); char temp[1024]; - if ( VALIDSTRING( (*(ent->script_targetname)) ) == false ) + if ( VALIDSTRING( script_targetname ) == false ) return; - strncpy( (char *) temp, *(ent->script_targetname), 1023 ); + strncpy( (char *) temp, script_targetname, 1023 ); temp[ 1023 ] = 0; ICARUS_EntList[ Q_strupr( (char *) temp ) ] = ent->s->number; @@ -630,17 +641,18 @@ Precache all scripts being used by the entity void ICARUS_PrecacheEnt( sharedEntityMapper_t *ent ) { + const char *behaviorStr; char newname[MAX_FILENAME_LENGTH]; int i; for ( i = 0; i < NUM_BSETS; i++ ) { - if ( *((ent->behaviorSet)[i]) == NULL ) + if ( !(behaviorStr = SV_EntityMapperReadString(ent->behaviorSet[i])) ) continue; - if ( GetIDForString( BSTable, *((ent->behaviorSet)[i]) ) == -1 ) + if ( GetIDForString( BSTable, behaviorStr ) == -1 ) {//not a behavior set - Com_sprintf( newname, sizeof(newname), "%s/%s", Q3_SCRIPT_DIR, *((ent->behaviorSet)[i]) ); + Com_sprintf( newname, sizeof(newname), "%s/%s", Q3_SCRIPT_DIR, behaviorStr ); //Precache this, and all internally referenced scripts ICARUS_InterrogateScript( newname ); diff --git a/codemp/icarus/Q3_Interface.cpp b/codemp/icarus/Q3_Interface.cpp index 2627a46c81..d9d52682a9 100644 --- a/codemp/icarus/Q3_Interface.cpp +++ b/codemp/icarus/Q3_Interface.cpp @@ -694,7 +694,7 @@ void Q3_DebugPrint( int level, const char *format, ... ) if ( ( entNum < 0 ) || ( entNum >= MAX_GENTITIES ) ) entNum = 0; - Com_Printf ( S_COLOR_BLUE"DEBUG: %s(%d): %s\n", (char *)VM_ArgPtr((intptr_t)*(SV_GentityMapperNum(entNum)->script_targetname)), entNum, buffer ); + Com_Printf ( S_COLOR_BLUE"DEBUG: %s(%d): %s\n", SV_EntityMapperReadString(SV_GentityMapperNum(entNum)->script_targetname), entNum, buffer ); break; } default: diff --git a/codemp/qcommon/msg.cpp b/codemp/qcommon/msg.cpp index e1c043dc19..c0c8261b2b 100644 --- a/codemp/qcommon/msg.cpp +++ b/codemp/qcommon/msg.cpp @@ -1176,7 +1176,7 @@ void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to, print = 1; if (sv.state) { - Com_Printf( "%3i: #%-3i (%s) ", msg->readcount, number, (char *)VM_ArgPtr((intptr_t)*SV_GentityMapperNum(number)->classname) ); + Com_Printf( "%3i: #%-3i (%s) ", msg->readcount, number, SV_EntityMapperReadString(SV_GentityMapperNum(number)->classname) ); } else { diff --git a/codemp/qcommon/vm.cpp b/codemp/qcommon/vm.cpp index b3e9312905..0c0f899e55 100644 --- a/codemp/qcommon/vm.cpp +++ b/codemp/qcommon/vm.cpp @@ -1186,3 +1186,9 @@ size_t VM_PtrToOffset( vm_t *vm, void *ptr ) return (byte*)ptr - vm->dataBase; } + +qboolean VM_IsCurrentQVM( void ) +{ + if ( !currentVM ) return qfalse; + return (qboolean)(currentVM->dllHandle == NULL); +} diff --git a/codemp/qcommon/vm_local.h b/codemp/qcommon/vm_local.h index e1e5abd404..1b1f224b15 100644 --- a/codemp/qcommon/vm_local.h +++ b/codemp/qcommon/vm_local.h @@ -191,3 +191,4 @@ void VM_ExtraMemory_Release( vm_t *vm, int amount ); void *VM_ExtraMemory_ClaimData( vm_t *vm, const void *data, uint32_t size ); char *VM_ExtraMemory_ClaimString( vm_t *vm, const char *inputString ); size_t VM_PtrToOffset( vm_t *vm, void *ptr ); +qboolean VM_IsCurrentQVM( void ); diff --git a/codemp/server/server.h b/codemp/server/server.h index a8583ebe97..414deaa229 100644 --- a/codemp/server/server.h +++ b/codemp/server/server.h @@ -397,7 +397,17 @@ CGhoul2Info_v *SV_G2Map_GetG2FromHandle( g2handleptr_t g2h ); CGhoul2Info_v **SV_G2Map_GetG2PtrFromHandle( g2handleptr_t g2h ); void SV_G2Map_Update( g2handleptr_t *g2h, CGhoul2Info_v *g2Ptr ); -sharedEntityMapper_t *ConvertedEntity( sharedEntity_t *ent ); +#define ENTITYMAP_READER_PROTO( type, funcName ) type funcName( type *inPtr ); + +ENTITYMAP_READER_PROTO( char*, SV_EntityMapperReadString ); +ENTITYMAP_READER_PROTO( void*, SV_EntityMapperReadData ); +ENTITYMAP_READER_PROTO( playerState_t*, SV_EntityMapperReadPlayerState ); +#if (!defined(MACOS_X) && !defined(__GCC__) && !defined(__GNUC__)) + ENTITYMAP_READER_PROTO( Vehicle_t*, SV_EntityMapperReadVehicle ); +#else + ENTITYMAP_READER_PROTO( struct Vehicle_s*, SV_EntityMapperReadVehicle ); +#endif +ENTITYMAP_READER_PROTO( parms_t*, SV_EntityMapperReadParms ); // // sv_bot.c diff --git a/codemp/server/sv_gameapi.cpp b/codemp/server/sv_gameapi.cpp index 0ca4a709c2..0779a293ba 100644 --- a/codemp/server/sv_gameapi.cpp +++ b/codemp/server/sv_gameapi.cpp @@ -659,6 +659,25 @@ static void SV_UpdateSharedEntitiesMapping( void ) { } } +#define ENTITYMAP_READER( type, funcName ) \ + type funcName( type *inPtr ) { \ + if ( gvm->dllHandle ) { \ + return *inPtr; \ + } else { \ + return (type)VM_ArgPtr((intptr_t)(*(uint32_t*)inPtr)); \ + } \ + } + +ENTITYMAP_READER( char*, SV_EntityMapperReadString ); +ENTITYMAP_READER( void*, SV_EntityMapperReadData ); +ENTITYMAP_READER( playerState_t*, SV_EntityMapperReadPlayerState ); +#if (!defined(MACOS_X) && !defined(__GCC__) && !defined(__GNUC__)) + ENTITYMAP_READER( Vehicle_t*, SV_EntityMapperReadVehicle ); +#else + ENTITYMAP_READER( struct Vehicle_s*, SV_EntityMapperReadVehicle ); +#endif +ENTITYMAP_READER( parms_t*, SV_EntityMapperReadParms ); + static void SV_LocateGameData( sharedEntity_t *gEnts, int numGEntities, int sizeofGEntity_t, playerState_t *clients, int sizeofGameClient ) { sv.gentities = gEnts; @@ -803,65 +822,6 @@ static void SV_GetUsercmd( int clientNum, usercmd_t *cmd ) { *cmd = svs.clients[clientNum].lastUsercmd; } -static sharedEntity_t gLocalModifier; -static sharedEntityMapper_t gLocalModifierMapper; -sharedEntityMapper_t *ConvertedEntity( sharedEntity_t *ent ) { //Return an entity with the memory shifted around to allow reading/modifying VM memory - int i = 0; - - sharedEntityMapper_t *gEntM = SV_GEntityMapperForGentity( ent ); - - assert(ent); - - gLocalModifier.s = *(gEntM->s); - gLocalModifier.r = *(gEntM->r); - while (i < NUM_TIDS) - { - gLocalModifier.taskID[i] = (*(gEntM->taskID))[i]; - i++; - } - i = 0; - gLocalModifier.parms = (parms_t *)VM_ArgPtr((intptr_t)(*(gEntM->parms))); - while (i < NUM_BSETS) - { - gLocalModifier.behaviorSet[i] = (char *)VM_ArgPtr((intptr_t)(*(gEntM->behaviorSet[i]))); - gLocalModifierMapper.behaviorSet[i] = &(gLocalModifier.behaviorSet[i]); - i++; - } - i = 0; - gLocalModifier.script_targetname = (char *)VM_ArgPtr((intptr_t)(*(gEntM->script_targetname))); - gLocalModifier.delayScriptTime = (*(gEntM->delayScriptTime)); - gLocalModifier.fullName = (char *)VM_ArgPtr((intptr_t)(*(gEntM->fullName))); - gLocalModifier.targetname = (char *)VM_ArgPtr((intptr_t)(*(gEntM->targetname))); - gLocalModifier.classname = (char *)VM_ArgPtr((intptr_t)(*(gEntM->classname))); - - gLocalModifier.ghoul2 = (*(gEntM->ghoul2)); - - gLocalModifierMapper.s = &gLocalModifier.s; - gLocalModifierMapper.playerState = &gLocalModifier.playerState; - gLocalModifierMapper.m_pVehicle = &gLocalModifier.m_pVehicle; - gLocalModifierMapper.ghoul2 = &gLocalModifier.ghoul2; - gLocalModifierMapper.localAnimIndex = &gLocalModifier.localAnimIndex; - gLocalModifierMapper.modelScale = &gLocalModifier.modelScale; - gLocalModifierMapper.r = &gLocalModifier.r; - gLocalModifierMapper.taskID = &gLocalModifier.taskID; - gLocalModifierMapper.parms = &gLocalModifier.parms; - gLocalModifierMapper.script_targetname = &gLocalModifier.script_targetname; - gLocalModifierMapper.delayScriptTime = &gLocalModifier.delayScriptTime; - gLocalModifierMapper.fullName = &gLocalModifier.fullName; - gLocalModifierMapper.targetname = &gLocalModifier.targetname; - gLocalModifierMapper.classname = &gLocalModifier.classname; - gLocalModifierMapper.waypoint = &gLocalModifier.waypoint; - gLocalModifierMapper.lastWaypoint = &gLocalModifier.lastWaypoint; - gLocalModifierMapper.lastValidWaypoint = &gLocalModifier.lastValidWaypoint; - gLocalModifierMapper.noWaypointTime = &gLocalModifier.noWaypointTime; - gLocalModifierMapper.combatPoint = &gLocalModifier.combatPoint; - gLocalModifierMapper.failedWaypoints = &gLocalModifier.failedWaypoints; - gLocalModifierMapper.failedWaypointCheckTime = &gLocalModifier.failedWaypointCheckTime; - gLocalModifierMapper.next_roff_time = &gLocalModifier.next_roff_time; - - return &gLocalModifierMapper; -} - static const char *SV_SetActiveSubBSP( int index ) { if ( index >= 0 ) { sv.mLocalSubBSPIndex = CM_FindSubBSP( index ); @@ -2340,7 +2300,7 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { //rww - icarus traps case G_ICARUS_RUNSCRIPT: - return ICARUS_RunScript(ConvertedEntity((sharedEntity_t *)VMA(1)), (const char *)VMA(2)); + return ICARUS_RunScript(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1)), (const char *)VMA(2)); case G_ICARUS_REGISTERSCRIPT: return ICARUS_RegisterScript((const char *)VMA(1), (qboolean)args[2]); @@ -2350,7 +2310,7 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { return 0; case G_ICARUS_VALIDENT: - return ICARUS_ValidEnt(ConvertedEntity((sharedEntity_t *)VMA(1))); + return ICARUS_ValidEnt(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1))); case G_ICARUS_ISINITIALIZED: return ICARUS_IsInitialized( args[1] ); @@ -2365,15 +2325,15 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { return Q3_TaskIDPending(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1)), (taskID_t)args[2]); case G_ICARUS_INITENT: - ICARUS_InitEnt(ConvertedEntity((sharedEntity_t *)VMA(1))); + ICARUS_InitEnt(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1))); return 0; case G_ICARUS_FREEENT: - ICARUS_FreeEnt(ConvertedEntity((sharedEntity_t *)VMA(1))); + ICARUS_FreeEnt(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1))); return 0; case G_ICARUS_ASSOCIATEENT: - ICARUS_AssociateEnt(ConvertedEntity((sharedEntity_t *)VMA(1))); + ICARUS_AssociateEnt(SV_GEntityMapperForGentity((sharedEntity_t *)VMA(1))); return 0; case G_ICARUS_SHUTDOWN: diff --git a/codemp/server/sv_snapshot.cpp b/codemp/server/sv_snapshot.cpp index 9fd6af93ad..769673af6f 100644 --- a/codemp/server/sv_snapshot.cpp +++ b/codemp/server/sv_snapshot.cpp @@ -579,11 +579,10 @@ static void SV_BuildClientSnapshot( client_t *client ) { if (ps->m_iVehicleNum) { //get the vehicle's playerstate too then sharedEntityMapper_t *veh = SV_GentityMapperNum(ps->m_iVehicleNum); + playerState_t *vps; - if (veh && *(veh->playerState)) + if (veh && (vps = SV_EntityMapperReadPlayerState(veh->playerState))) { //Now VMA it and we've got ourselves a playerState - playerState_t *vps = ((playerState_t *)VM_ArgPtr((intptr_t)(*veh->playerState))); - frame->vps = *vps; #ifdef _ONEBIT_COMBO frame->pDeltaOneBitVeh = &vps->deltaOneBits; diff --git a/codemp/server/sv_world.cpp b/codemp/server/sv_world.cpp index 70e1eaa8dc..53874c991c 100644 --- a/codemp/server/sv_world.cpp +++ b/codemp/server/sv_world.cpp @@ -700,7 +700,7 @@ Ghoul2 Insert Start #else //rww - since this is multiplayer and we don't have the luxury of violating networking rules in horrible ways, //this must be done somewhat differently. - if ((clip->traceFlags & G2TRFLAG_DOGHOULTRACE) && trace.entityNum == touch->s->number && *(touch->ghoul2) && ((clip->traceFlags & G2TRFLAG_HITCORPSES) || !(touch->s->eFlags & EF_DEAD))) + if ((clip->traceFlags & G2TRFLAG_DOGHOULTRACE) && trace.entityNum == touch->s->number && SV_EntityMapperReadData(touch->ghoul2) && ((clip->traceFlags & G2TRFLAG_HITCORPSES) || !(touch->s->eFlags & EF_DEAD))) { //standard behavior will be to ignore g2 col on dead ents, but if traceFlags is set to allow, then we'll try g2 col on EF_DEAD people too. static G2Trace_t G2Trace; vec3_t angles; @@ -744,7 +744,7 @@ Ghoul2 Insert Start #ifndef FINAL_BUILD if (sv_showghoultraces->integer) { - Com_Printf( "Ghoul2 trace lod=%1d length=%6.0f to %s\n",clip->useLod,VectorDistance(clip->start, clip->end), re->G2API_GetModelName(*(SV_G2Map_GetG2FromHandle((g2handleptr_t)(*(touch->ghoul2)))), 0)); + Com_Printf( "Ghoul2 trace lod=%1d length=%6.0f to %s\n",clip->useLod,VectorDistance(clip->start, clip->end), re->G2API_GetModelName(*(SV_G2Map_GetG2FromHandle((g2handleptr_t)SV_EntityMapperReadData(touch->ghoul2))), 0)); } #endif @@ -752,13 +752,13 @@ Ghoul2 Insert Start com_optvehtrace->integer && touch->s->eType == ET_NPC && touch->s->NPC_class == CLASS_VEHICLE && - *touch->m_pVehicle) + SV_EntityMapperReadVehicle(touch->m_pVehicle)) { //for vehicles cache the transform data. - re->G2API_CollisionDetectCache(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)(*(touch->ghoul2)))), angles, touch->r->currentOrigin, sv.time, touch->s->number, clip->start, clip->end, *touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); + re->G2API_CollisionDetectCache(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)SV_EntityMapperReadData(touch->ghoul2))), angles, touch->r->currentOrigin, sv.time, touch->s->number, clip->start, clip->end, *touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); } else { - re->G2API_CollisionDetect(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)(*(touch->ghoul2)))), angles, touch->r->currentOrigin, sv.time, touch->s->number, clip->start, clip->end, *touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); + re->G2API_CollisionDetect(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)SV_EntityMapperReadData(touch->ghoul2))), angles, touch->r->currentOrigin, sv.time, touch->s->number, clip->start, clip->end, *touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); } tN = 0; From 57cf72f0d5df6d11440031ac5683c57894d4e13a Mon Sep 17 00:00:00 2001 From: Daggolin Date: Sun, 12 Nov 2023 15:30:58 +0100 Subject: [PATCH 26/28] Add SV_EntityMapperReadGhoul2 to handle special case for ghoul2 handles that SV_EntityMapperReadData was not aware of. --- codemp/server/server.h | 2 ++ codemp/server/sv_gameapi.cpp | 9 +++++++++ codemp/server/sv_world.cpp | 8 ++++---- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/codemp/server/server.h b/codemp/server/server.h index 414deaa229..d96252fb67 100644 --- a/codemp/server/server.h +++ b/codemp/server/server.h @@ -409,6 +409,8 @@ ENTITYMAP_READER_PROTO( playerState_t*, SV_EntityMapperReadPlayerState ); #endif ENTITYMAP_READER_PROTO( parms_t*, SV_EntityMapperReadParms ); +void *SV_EntityMapperReadGhoul2( void **inPtr ); + // // sv_bot.c // diff --git a/codemp/server/sv_gameapi.cpp b/codemp/server/sv_gameapi.cpp index 0779a293ba..4be41259fd 100644 --- a/codemp/server/sv_gameapi.cpp +++ b/codemp/server/sv_gameapi.cpp @@ -678,6 +678,15 @@ ENTITYMAP_READER( playerState_t*, SV_EntityMapperReadPlayerState ); #endif ENTITYMAP_READER( parms_t*, SV_EntityMapperReadParms ); +void *SV_EntityMapperReadGhoul2( void **inPtr ) { + if ( gvm->dllHandle ) { + return *inPtr; + } else { + // For QVMs the address is actually a handle we have to interpret as uint32_t + return (void*)(intptr_t)(*(uint32_t*)inPtr); + } +} + static void SV_LocateGameData( sharedEntity_t *gEnts, int numGEntities, int sizeofGEntity_t, playerState_t *clients, int sizeofGameClient ) { sv.gentities = gEnts; diff --git a/codemp/server/sv_world.cpp b/codemp/server/sv_world.cpp index 53874c991c..2db0ec2724 100644 --- a/codemp/server/sv_world.cpp +++ b/codemp/server/sv_world.cpp @@ -700,7 +700,7 @@ Ghoul2 Insert Start #else //rww - since this is multiplayer and we don't have the luxury of violating networking rules in horrible ways, //this must be done somewhat differently. - if ((clip->traceFlags & G2TRFLAG_DOGHOULTRACE) && trace.entityNum == touch->s->number && SV_EntityMapperReadData(touch->ghoul2) && ((clip->traceFlags & G2TRFLAG_HITCORPSES) || !(touch->s->eFlags & EF_DEAD))) + if ((clip->traceFlags & G2TRFLAG_DOGHOULTRACE) && trace.entityNum == touch->s->number && SV_EntityMapperReadGhoul2(touch->ghoul2) && ((clip->traceFlags & G2TRFLAG_HITCORPSES) || !(touch->s->eFlags & EF_DEAD))) { //standard behavior will be to ignore g2 col on dead ents, but if traceFlags is set to allow, then we'll try g2 col on EF_DEAD people too. static G2Trace_t G2Trace; vec3_t angles; @@ -744,7 +744,7 @@ Ghoul2 Insert Start #ifndef FINAL_BUILD if (sv_showghoultraces->integer) { - Com_Printf( "Ghoul2 trace lod=%1d length=%6.0f to %s\n",clip->useLod,VectorDistance(clip->start, clip->end), re->G2API_GetModelName(*(SV_G2Map_GetG2FromHandle((g2handleptr_t)SV_EntityMapperReadData(touch->ghoul2))), 0)); + Com_Printf( "Ghoul2 trace lod=%1d length=%6.0f to %s\n",clip->useLod,VectorDistance(clip->start, clip->end), re->G2API_GetModelName(*(SV_G2Map_GetG2FromHandle((g2handleptr_t)SV_EntityMapperReadGhoul2(touch->ghoul2))), 0)); } #endif @@ -754,11 +754,11 @@ Ghoul2 Insert Start touch->s->NPC_class == CLASS_VEHICLE && SV_EntityMapperReadVehicle(touch->m_pVehicle)) { //for vehicles cache the transform data. - re->G2API_CollisionDetectCache(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)SV_EntityMapperReadData(touch->ghoul2))), angles, touch->r->currentOrigin, sv.time, touch->s->number, clip->start, clip->end, *touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); + re->G2API_CollisionDetectCache(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)SV_EntityMapperReadGhoul2(touch->ghoul2))), angles, touch->r->currentOrigin, sv.time, touch->s->number, clip->start, clip->end, *touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); } else { - re->G2API_CollisionDetect(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)SV_EntityMapperReadData(touch->ghoul2))), angles, touch->r->currentOrigin, sv.time, touch->s->number, clip->start, clip->end, *touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); + re->G2API_CollisionDetect(G2Trace, *(SV_G2Map_GetG2FromHandle((g2handleptr_t)SV_EntityMapperReadGhoul2(touch->ghoul2))), angles, touch->r->currentOrigin, sv.time, touch->s->number, clip->start, clip->end, *touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); } tN = 0; From 92920754eb97ea4ee18a0ddb945a54eb3ebc0618 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Sun, 7 Apr 2024 03:15:36 +0200 Subject: [PATCH 27/28] Fix VM_ExtraMemory_Claim using incorrect vm pointer. --- codemp/qcommon/vm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codemp/qcommon/vm.cpp b/codemp/qcommon/vm.cpp index 0c0f899e55..a6f10523e5 100644 --- a/codemp/qcommon/vm.cpp +++ b/codemp/qcommon/vm.cpp @@ -1136,7 +1136,7 @@ void *VM_ExtraMemory_Claim( vm_t *vm, int amount ) vm->extraMemUse += amount; // Return address that we can write to - return (void *)(currentVM->dataBase + (retBlock & currentVM->dataMask)); + return (void *)(vm->dataBase + (retBlock & vm->dataMask)); } void VM_ExtraMemory_Release( vm_t *vm, int amount ) From 12714569c3b36373601e894463ade0084065e151 Mon Sep 17 00:00:00 2001 From: Daggolin Date: Sun, 7 Apr 2024 03:22:01 +0200 Subject: [PATCH 28/28] Fix CGVM_GetOrigin and CGVM_GetAngles not setting the out vec3_t when using QVMs. --- codemp/client/cl_cgameapi.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/codemp/client/cl_cgameapi.cpp b/codemp/client/cl_cgameapi.cpp index a4face8c33..49ad244ebc 100644 --- a/codemp/client/cl_cgameapi.cpp +++ b/codemp/client/cl_cgameapi.cpp @@ -276,16 +276,21 @@ static size_t vec3_size = sizeof(vec3_t); void CGVM_GetOrigin( int entID, vec3_t out ) { if ( cgvm->isLegacy ) { float *out_ptr; + float *out_ptr_vm; if ( !cgvm->dllHandle ) { - out_ptr = (float*)VM_PtrToOffset( cgvm, VM_ExtraMemory_ClaimData(cgvm, (float*)out, vec3_size) ); + out_ptr = (float*)VM_ExtraMemory_ClaimData(cgvm, (float*)out, vec3_size); + out_ptr_vm = (float*)VM_PtrToOffset( cgvm, (void*)out_ptr ); } else { - out_ptr = out; + out_ptr_vm = out_ptr = out; } - VM_Call( cgvm, CG_GET_ORIGIN, entID, reinterpret_cast< intptr_t >( out_ptr ) ); + VM_Call( cgvm, CG_GET_ORIGIN, entID, reinterpret_cast< intptr_t >( out_ptr_vm ) ); if ( !cgvm->dllHandle ) { // Memory may only be released in reverse order. - if ( out_ptr ) VM_ExtraMemory_Release( cgvm, vec3_size ); + if ( out_ptr ) { + VectorCopy( out_ptr, out ); + VM_ExtraMemory_Release( cgvm, vec3_size ); + } } return; } @@ -297,16 +302,21 @@ void CGVM_GetOrigin( int entID, vec3_t out ) { void CGVM_GetAngles( int entID, vec3_t out ) { if ( cgvm->isLegacy ) { float *out_ptr; + float *out_ptr_vm; if ( !cgvm->dllHandle ) { - out_ptr = (float*)VM_PtrToOffset( cgvm, VM_ExtraMemory_ClaimData(cgvm, (float*)out, vec3_size) ); + out_ptr = (float*)VM_ExtraMemory_ClaimData(cgvm, (float*)out, vec3_size); + out_ptr_vm = (float*)VM_PtrToOffset( cgvm, (void*)out_ptr ); } else { - out_ptr = out; + out_ptr_vm = out_ptr = out; } - VM_Call( cgvm, CG_GET_ANGLES, entID, reinterpret_cast< intptr_t >( out ) ); + VM_Call( cgvm, CG_GET_ANGLES, entID, reinterpret_cast< intptr_t >( out_ptr_vm ) ); if ( !cgvm->dllHandle ) { // Memory may only be released in reverse order. - if ( out_ptr ) VM_ExtraMemory_Release( cgvm, vec3_size ); + if ( out_ptr ) { + VectorCopy( out_ptr, out ); + VM_ExtraMemory_Release( cgvm, vec3_size ); + } } return; }