1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2005 - 2015, ioquake3 contributors
7 Copyright (C) 2013 - 2015, OpenJK contributors
8
9 This file is part of the OpenJK source code.
10
11 OpenJK is free software; you can redistribute it and/or modify it
12 under the terms of the GNU General Public License version 2 as
13 published by the Free Software Foundation.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, see <http://www.gnu.org/licenses/>.
22 ===========================================================================
23 */
24
25 #define __STDC_FORMAT_MACROS
26 #include <inttypes.h>
27
28 #include "qcommon/qcommon.h"
29
30 vm_t *currentVM = NULL;
31
32 static const char *vmNames[MAX_VM] = {
33 "jampgame",
34 "cgame",
35 "ui"
36 };
37
38 const char *vmStrs[MAX_VM] = {
39 "GameVM",
40 "CGameVM",
41 "UIVM",
42 };
43
44 // VM slots are automatically allocated by VM_Create, and freed by VM_Free
45 // The VM table should never be directly accessed from other files.
46 // Example usage:
47 // cgvm = VM_Create( VM_CGAME ); // vmTable[VM_CGAME] is allocated
48 // CGVM_Init( foo, bar ); // internally may use VM_Call( cgvm, CGAME_INIT, foo, bar ) for legacy cgame modules
49 // cgvm = VM_Restart( cgvm ); // vmTable[VM_CGAME] is recreated, we update the cgvm pointer
50 // VM_Free( cgvm ); // vmTable[VM_CGAME] is deallocated and set to NULL
51 // cgvm = NULL; // ...so we update the cgvm pointer
52
53 static vm_t *vmTable[MAX_VM];
54
55 #ifdef _DEBUG
56 cvar_t *vm_legacy;
57 #endif
58
VM_Init(void)59 void VM_Init( void ) {
60 #ifdef _DEBUG
61 vm_legacy = Cvar_Get( "vm_legacy", "0", 0 );
62 #endif
63
64 memset( vmTable, 0, sizeof(vmTable) );
65 }
66
67 // The syscall mechanism relies on stack manipulation to get it's args.
68 // This is likely due to C's inability to pass "..." parameters to a function in one clean chunk.
69 // On PowerPC Linux, these parameters are not necessarily passed on the stack, so while (&arg[0] == arg) is true,
70 // (&arg[1] == 2nd function parameter) is not necessarily accurate, as arg's value might have been stored to the stack
71 // or other piece of scratch memory to give it a valid address, but the next parameter might still be sitting in a
72 // register.
73 // QVM's syscall system also assumes that the stack grows downward, and that any needed types can be squeezed, safely,
74 // into a signed int.
75 // This hack below copies all needed values for an argument to an array in memory, so that QVM can get the correct values.
76 // This can also be used on systems where the stack grows upwards, as the presumably standard and safe stdargs.h macros
77 // are used.
78 // The original code, while probably still inherently dangerous, seems to work well enough for the platforms it already
79 // works on. Rather than add the performance hit for those platforms, the original code is still in use there.
80 // For speed, we just grab 15 arguments, and don't worry about exactly how many the syscall actually needs; the extra is
81 // thrown away.
VM_DllSyscall(intptr_t arg,...)82 intptr_t QDECL VM_DllSyscall( intptr_t arg, ... ) {
83 #if !id386 || defined __clang__ || defined MACOS_X
84 // rcg010206 - see commentary above
85 intptr_t args[16];
86 va_list ap;
87
88 args[0] = arg;
89
90 va_start( ap, arg );
91 for (size_t i = 1; i < ARRAY_LEN (args); i++)
92 args[i] = va_arg( ap, intptr_t );
93 va_end( ap );
94
95 return currentVM->legacy.syscall( args );
96 #else // original id code
97 return currentVM->legacy.syscall( &arg );
98 #endif
99 }
100
101 // Reload the data, but leave everything else in place
102 // This allows a server to do a map_restart without changing memory allocation
VM_Restart(vm_t * vm)103 vm_t *VM_Restart( vm_t *vm ) {
104 const vm_t saved = *vm;
105
106 VM_Free( vm );
107
108 if ( saved.isLegacy )
109 return VM_CreateLegacy( saved.slot, saved.legacy.syscall );
110 else
111 return VM_Create( saved.slot );
112 }
113
VM_CreateLegacy(vmSlots_t vmSlot,intptr_t (* systemCalls)(intptr_t *))114 vm_t *VM_CreateLegacy( vmSlots_t vmSlot, intptr_t( *systemCalls )(intptr_t *) ) {
115 vm_t *vm = NULL;
116
117 if ( !systemCalls ) {
118 Com_Error( ERR_FATAL, "VM_CreateLegacy: bad parms" );
119 return NULL;
120 }
121
122 // see if we already have the VM
123 if ( vmTable[vmSlot] )
124 return vmTable[vmSlot];
125
126 // find a free vm
127 vmTable[vmSlot] = (vm_t *)Z_Malloc( sizeof(*vm), TAG_VM, qtrue );
128 vm = vmTable[vmSlot];
129
130 // initialise it
131 vm->isLegacy = qtrue;
132 vm->slot = vmSlot;
133 Q_strncpyz( vm->name, vmNames[vmSlot], sizeof(vm->name) );
134 vm->legacy.syscall = systemCalls;
135
136 // find the legacy syscall api
137 FS_FindPureDLL( vm->name );
138 vm->dllHandle = Sys_LoadLegacyGameDll( vm->name, &vm->legacy.main, VM_DllSyscall );
139
140 Com_Printf( "VM_CreateLegacy: %s" ARCH_STRING DLL_EXT, vm->name );
141 if ( vm->dllHandle ) {
142 if ( com_developer->integer )
143 Com_Printf( " succeeded [0x%" PRIxPTR "]\n", (uintptr_t)vm->dllHandle );
144 else
145 Com_Printf( " succeeded\n" );
146 return vm;
147 }
148
149 VM_Free( vm );
150 Com_Printf( " failed!\n" );
151 return NULL;
152 }
153
VM_Create(vmSlots_t vmSlot)154 vm_t *VM_Create( vmSlots_t vmSlot ) {
155 vm_t *vm = NULL;
156
157 #ifdef _DEBUG
158 if ( (vm_legacy->integer & (1<<vmSlot)) )
159 return NULL;
160 #endif
161
162 // see if we already have the VM
163 if ( vmTable[vmSlot] )
164 return vmTable[vmSlot];
165
166 // find a free vm
167 vmTable[vmSlot] = (vm_t *)Z_Malloc( sizeof(*vm), TAG_VM, qtrue );
168 vm = vmTable[vmSlot];
169
170 // initialise it
171 vm->isLegacy = qfalse;
172 vm->slot = vmSlot;
173 Q_strncpyz( vm->name, vmNames[vmSlot], sizeof(vm->name) );
174
175 // find the module api
176 FS_FindPureDLL( vm->name );
177 vm->dllHandle = Sys_LoadGameDll( vm->name, &vm->GetModuleAPI );
178
179 Com_Printf( "VM_Create: %s" ARCH_STRING DLL_EXT, vm->name );
180 if ( vm->dllHandle ) {
181 if ( com_developer->integer )
182 Com_Printf( " succeeded [0x%" PRIxPTR "+0x%" PRIxPTR "]\n", vm->dllHandle, (intptr_t)vm->GetModuleAPI - (intptr_t)vm->dllHandle );
183 else
184 Com_Printf( " succeeded\n" );
185 return vm;
186 }
187
188 VM_Free( vm );
189 Com_Printf( " failed!\n" );
190 return NULL;
191 }
192
VM_Free(vm_t * vm)193 void VM_Free( vm_t *vm ) {
194 if ( !vm )
195 return;
196
197 // mark the slot as free
198 vmTable[vm->slot] = NULL;
199
200 if ( vm->dllHandle )
201 Sys_UnloadDll( vm->dllHandle );
202
203 memset( vm, 0, sizeof(*vm) );
204
205 Z_Free( vm );
206
207 currentVM = NULL;
208 }
209
VM_Clear(void)210 void VM_Clear( void ) {
211 for ( int i = 0; i < MAX_VM; i++ )
212 VM_Free( vmTable[i] );
213
214 currentVM = NULL;
215 }
216
VM_Shifted_Alloc(void ** ptr,int size)217 void VM_Shifted_Alloc( void **ptr, int size ) {
218 void *mem = NULL;
219
220 if ( !currentVM ) {
221 assert( 0 );
222 *ptr = NULL;
223 return;
224 }
225
226 mem = Z_Malloc( size + 1, TAG_VM_ALLOCATED, qfalse );
227 if ( !mem ) {
228 assert( 0 );
229 *ptr = NULL;
230 return;
231 }
232
233 memset( mem, 0, size + 1 );
234
235 *ptr = mem;
236 }
237
VM_Shifted_Free(void ** ptr)238 void VM_Shifted_Free( void **ptr ) {
239 void *mem = NULL;
240
241 if ( !currentVM ) {
242 assert( 0 );
243 return;
244 }
245
246 mem = (void *)*ptr;
247 if ( !mem ) {
248 assert( 0 );
249 return;
250 }
251
252 Z_Free( mem );
253 *ptr = NULL;
254 }
255
VM_ArgPtr(intptr_t intValue)256 void *VM_ArgPtr( intptr_t intValue ) {
257 if ( !intValue )
258 return NULL;
259
260 // currentVM is missing on reconnect
261 if ( !currentVM )
262 return NULL;
263
264 return (void *)intValue;
265 }
266
VM_ExplicitArgPtr(vm_t * vm,intptr_t intValue)267 void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ) {
268 if ( !intValue )
269 return NULL;
270
271 // currentVM is missing on reconnect here as well?
272 if ( !currentVM )
273 return NULL;
274
275 return (void *)intValue;
276 }
277
_vmf(intptr_t x)278 float _vmf( intptr_t x ) {
279 byteAlias_t fi;
280 fi.i = (int)x;
281 return fi.f;
282 }
283
VM_Call(vm_t * vm,int callnum,intptr_t arg0,intptr_t arg1,intptr_t arg2,intptr_t arg3,intptr_t arg4,intptr_t arg5,intptr_t arg6,intptr_t arg7,intptr_t arg8,intptr_t arg9,intptr_t arg10,intptr_t arg11)284 intptr_t QDECL VM_Call( vm_t *vm, int callnum, intptr_t arg0, intptr_t arg1, intptr_t arg2, intptr_t arg3, intptr_t arg4, intptr_t arg5, intptr_t arg6, intptr_t arg7, intptr_t arg8, intptr_t arg9, intptr_t arg10, intptr_t arg11 ) {
285 if ( !vm || !vm->name[0] ) {
286 Com_Error( ERR_FATAL, "VM_Call with NULL vm" );
287 return 0;
288 }
289
290 VMSwap v( vm );
291
292 return vm->legacy.main( callnum, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
293 arg9, arg10, arg11 );
294 }
295