Repository navigation
Adding game support
Each game that QMM supports (this document will use the placeholder short-code "XYZ") gets a src/game_XYZ.cpp file and a include/game_XYZ.h file. These are the only files in QMM that will include XYZ's SDK, and nothing else in QMM (only plugins) should include game_XYZ.h.
The game_XYZ.h file should define any engine (e.g. G_PRINT) and mod (e.g. GAME_INIT) constants (if they are not defined by the SDK, in particular those that QMM uses for GetGameAPI games), including any additional ones handled by QMM as polyfills.
The game_XYZ.cpp file defines a game support class that inherits from the pure virtual GameSupport base class. This game support class must implement several public members functions.
Then, the file must contain an instantiation of the game support class, and store the address in an externally-accessible GameSupport* that must then be referenced in the api_supportedgames list in game_api.cpp.
struct JK2MP_GameSupport : public GameSupport {
virtual const char* EngMsgName(intptr_t msg);
virtual const char* ModMsgName(intptr_t msg);
virtual bool AutoDetect(APIType engine_api);
virtual void* Entry(void* syscall, void*, APIType engine_api);
virtual bool ModLoad(void* entry, APIType mod_api);
virtual void ModUnload();
virtual int QMMEngMsg(int msg) { return qmm_eng_msgs[msg]; }
virtual int QMMModMsg(int msg) { return qmm_mod_msgs[msg]; }
virtual intptr_t syscall_args(intptr_t, intptr_t* args);
virtual intptr_t vmMain_args(intptr_t, intptr_t* args);
virtual const char* DefaultDLLName() { return "jk2mpgame" MOD_DLL; }
virtual const char* DefaultQVMName() { return "vm/jk2mpgame.qvm"; }
virtual const char* DefaultModDir() { return "base"; }
virtual const char* ModCvar() { return "fs_game"; }
virtual const char* GameName() { return "Jedi Knight 2: Jedi Outcast (MP)"; }
virtual const char* GameCode() { return "JK2MP"; }
virtual int QVMSyscall(uint8_t* membase, int cmd, int* args);
private:
const int qmm_eng_msgs[QMM_ENGINE_MSG_COUNT] = GEN_GAME_QMM_ENG_MSGS();
const int qmm_mod_msgs[QMM_MOD_MSG_COUNT] = GEN_GAME_QMM_MOD_MSGS();
};All games should implement:
-
const char* EngMsgName(intptr_t msg)
This is called with an engine message constant (e.g.G_PRINT) and you should return the string form of it. The GEN_CASE macro is useful in this function. -
const char* ModMsgName(intptr_t msg)
This is called with a mod message constsant (e.g.GAME_INIT) and you should return the string form of it. The GEN_CASE macro is useful in this function. -
bool AutoDetect(APIType engine_api)
This is called by QMM for each game when trying to auto-detect which engine QMM is loaded in. Theengine_apiargument is a value that says what engine type QMM was loaded as (currentlyQMM_API_DLLENTRY,QMM_API_GETGAMEAPI, orQMM_API_GETMODULEAPI).Return true if the game engine has been determined to be XYZ, false otherwise.
This function is also called after the XYZ game is loaded from being manually set in the "game" config option.
-
void* Entry(void* syscall, void*, APIType engine_api)
This is called by QMM'sdllEntry,GetGameAPI, andGetModuleAPIentry points once the game engine is determined.This function allows you to store the real engine syscall pointer or import table to forward calls to it in
XYZ_syscall.engine_apiis an enum which corresponds to the API that the engine used to load QMM. This can beQMM_API_DLLENTRY,QMM_API_GETGAMEAPIorQMM_API_GETMODULEAPI.If
engine_apiisQMM_API_DLLENTRY, thenarg0is the syscall pointer passed from the engine to QMM'sdllEntryfunction, whilearg1is null.If
engine_apiisQMM_API_GETGAMEAPI, thenarg0andarg1are the values passed to QMM'sGetGameAPI(orGetModuleAPI) function. Typically, that meansarg0is the engine's import struct pointer, whilearg1is undefined. InSOF2SP,arg0will be an apiversion int whilearg1is the import pointer.If
engine_apiisQMM_API_GETMODULEAPI, thenarg0is an apiversion int andarg1is the engine's import struct pointer. -
bool ModLoad(void* entry, APIType mod_api)
This is called just after a (potential) mod file is loaded.This function allows you to store the real
vmMainpointer or real game export table to forward calls to the mod inXYZ_vmMain.entryis a pointer to the mod's entry function, depending on the API type which is given by themod_apiparameter.mod_apiis an enum which corresponds to the API that QMM used to load the mod file. This can beQMM_API_QVM,QMM_API_DLLENTRY,QMM_API_GETGAMEAPI, orQMM_API_GETMODULEAPI.If
mod_apiisQMM_API_QVM, thenentryis a stubvmMain-like function pointer that calls into the QVM.If
mod_apiisQMM_API_DLLENTRY, thenentryis thevmMainfunction pointer from the mod DLL.If
mod_apiisQMM_API_GETGAMEAPI, thenentryis theGetGameAPIfunction pointer from the mod DLL.If
mod_apiisQMM_API_GETMODULEAPI, thenentryis theGetModuleAPIfunction pointer from the mod DLL.Return true to consider the mod load successful, otherwise return false to continue with the next step of the mod loading sequence.
-
void XYZ_ModUnload()
This is called just before the mod file is unloaded.This function allows you to clear the stored mod entry pointer from
XYZ_ModLoadto safely check before calling into an unloaded mod, and to clean up any resources if applicable. -
int QMMEngMsg(int msg)
This is called to get the game-specific value for an engine message that QMM uses internally.Typically, the implementation should just be:
return qmm_eng_msgs[msg]; -
int QMMModMsg(int msg)
This is called to get the game-specific value for a mod message that QMM uses internally.Typically, the implementation should just be:
return qmm_mod_msgs[msg]; -
intptr_t XYZ_syscall_args(intptr_t cmd, intptr_t* args)
This is called by QMM and by plugins in order to pass calls to the engine.This is where plugin polyfills should be handled, if needed.
This should directly call into the engine.
-
intptr_t XYZ_vmMain_args(intptr_t cmd, intptr_t* args)
This is called by QMM and by plugins in order to pass calls to the mod.This should directly call into the mod.
-
const char* DefaultDLLName()
This function should return the mod DLL name expected by the engine.Several macros are available to handle different names in Windows vs Linux:
-
MOD_DLL
This contains the DLL suffix and extension used by most games based on architecture and operating system. This can include"x86.dll","i386.so","x86_64.dll", or"x86_64.so". -
X64_DLL
This contains the DLL suffix and extension used by some games based on architecture and operating system. This can include"x86.dll","i386.so","x64.dll", or"x86_64.so". -
SP_DLL
This contains a substring found in many single-player game DLL names based on operating system. This can include"_sp_"or".sp.". -
MP_DLL
This contains a substring found in many multi-player game DLL names based on operating system. This can include"_mp_"or".mp.".
-
-
const char* DefaultModDir()
This function should return the default mod directory used by the game (e.g."baseq3","main", etc). -
const char* ModCvar()This function should return the cvar name the engine uses to set the mod.The base class' implementation of this function returns
"fs_game"which is used by most Quake 3-based engines. You only need to include an implementation for this function if the game uses a different cvar. -
const char* GameName()
This function should return the full name of the game. -
const char* GameCode()
This function should return the QMM short code of the game (e.g."XYZ"). -
const int qmm_eng_msgs[QMM_ENGINE_MSG_COUNT]
This array stores the game-specific values for engine messages used internally by QMM. This array is referenced by theQMMEngMsgmember function.You can initialize this array with the
GEN_GAME_QMM_ENG_MSGS()macro to keep it aligned with the values that QMM expects. -
const int qmm_mod_msgs[QMM_MOD_MSG_COUNT]
This array stores the game-specific values for mod messages used internally by QMM. This array is referenced by theQMMModMsgmember function.You can initialize this array with the
GEN_GAME_QMM_MOD_MSGS()macro to keep it aligned with the values that QMM expects.
QVM games need to implement:
-
const char* DefaultQVMName()
This function should return the QVM file path expected by the engine.The base class' implementation of this function returns
nullptr, which signals to QMM that this game does not support QVMs. -
int QVMSyscall(uint8_t* membase, int cmd, int* args)
This is called byqvm_execwhenever the QVM calls an engine syscall/trap.All pointer arguments that get read by the engine (and QMM+plugins) need to be converted to real pointers, which is simply adding the
membaseargument if the pointer is not null.The
VMARGandVMPTRmacros are useful in this function. This function should adjust pointers based on the syscall and then pass toqmm_syscallto be handled like any other call out of the mod.
To create an object of type XYZ_GameSupport and export it, you can use the following macro:
GEN_GAME_OBJ(XYZ);
These functions are defined inline, and you do not need to implement your own:
-
intptr_t XYZ_syscall(intptr_t cmd, ...)
This forwards arguments toXYZ_syscall_args. -
intptr_t XYZ_vmMain_args(intptr_t cmd, intptr_t* args)
This forwards arguments toXYZ_vmMain_args.
You also need to add some lines in game_api.cpp:
-
GEN_GAME_EXTS(XYZ);
This macro forward-declares theGameSupport*pointer for the game's support object for use in theapi_supportedgameslist. -
GET_GAME_OBJ(XYZ),
You need to include the game'sGameSupport*pointer in theapi_supportedgameslist to allow it to be used by QMM. You can use this macro inside theapi_supportedgameslist.