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