Skip to content
Open
52 changes: 22 additions & 30 deletions include/game_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ Created By:
#include "qmmapi.h"
#include "qvm.h"

using msgname_t = const char* (*)(intptr_t);
using mod_load_t = bool (*)(void*);
using mod_unload_t = void (*)();
// some information for each game engine supported by QMM
struct supportedgame_t {
struct supportedgame {
const char* dllname; // default dll mod filename
const char* qvmname; // default qvm mod filename (NULL = not supported)
const char* moddir; // default moddir name
Expand All @@ -34,21 +31,21 @@ struct supportedgame_t {
const char* gamename_short; // short initials for game
int* qmm_eng_msgs; // array of engine messages used by QMM
int* qmm_mod_msgs; // array of mod messages used by QMM
msgname_t eng_msg_names; // pointer to a function that returns a string for a given engine message
msgname_t mod_msg_names; // pointer to a function that returns a string for a given mod message
const char*(*eng_msg_names)(intptr_t); // pointer to a function that returns a string for a given engine message
const char*(*mod_msg_names)(intptr_t); // pointer to a function that returns a string for a given mod message

// this section is made by GEN_DLLQVM(GAME), GEN_DLL(GAME), or GEN_GGA(GAME)
qvmsyscall_t pfnqvmsyscall; // pointer to a function that handles mod->engine calls from a QVM (NULL = not supported)
mod_dllEntry_t pfndllEntry; // pointer to a function that handles dllEntry entry for a game (NULL = not supported)
mod_GetGameAPI_t pfnGetGameAPI; // pointer to a function that handles GetGameAPI entry for a game (NULL = not supported)
mod_load_t pfnModLoad; // pointer to a function that handles mod loading logic after a DLL is loaded
mod_unload_t pfnModUnload; // pointer to a function that handles mod unloading logic before a DLL is unloaded
qvm_syscall pfnqvmsyscall; // pointer to a function that handles mod->engine calls from a QVM (NULL = not supported)
mod_dllEntry pfndllEntry; // pointer to a function that handles dllEntry entry for a game (NULL = not supported)
mod_GetGameAPI pfnGetGameAPI; // pointer to a function that handles GetGameAPI entry for a game (NULL = not supported)
bool(*pfnModLoad)(void*); // pointer to a function that handles mod loading logic after a DLL is loaded
void(*pfnModUnload)(); // pointer to a function that handles mod unloading logic before a DLL is unloaded

int max_syscall_args; // max number of syscall args that this game needs (unused for now, but nice to have easily available)
int max_vmmain_args; // max number of vmmain args that this game needs (unused for now, but nice to have easily available)
};

extern supportedgame_t g_supportedgames[];
extern supportedgame g_supportedgames[];

// macros to make game support a bit easier to do
// these macros are used in game_api.cpp and game_xyz.cpp
Expand All @@ -59,25 +56,24 @@ extern supportedgame_t g_supportedgames[];
const char* game##_eng_msg_names(intptr_t); \
const char* game##_mod_msg_names(intptr_t); \
int game##_qvmsyscall(uint8_t*, int, int*); \
void game##_dllEntry(eng_syscall_t); \
void* game##_GetGameAPI(void*, void*); \
bool game##_mod_load(void*); \
void game##_mod_unload();
void game##_dllEntry(eng_syscall); \
void* game##_GetGameAPI(void*, void*); \
bool game##_mod_load(void*); \
void game##_mod_unload();

// generate struct info for the short name, messages arrays, and message name functions
#define GEN_INFO(game) #game, game##_qmm_eng_msgs, game##_qmm_mod_msgs, game##_eng_msg_names, game##_mod_msg_names

// generate struct info for the game-specific entry functions
#define GEN_DLLQVM(game) game##_qvmsyscall, game##_dllEntry, nullptr, game##_mod_load, game##_mod_unload
#define GEN_DLL(game) nullptr, game##_dllEntry, nullptr, game##_mod_load, game##_mod_unload
#define GEN_GGA(game) nullptr, nullptr, game##_GetGameAPI, game##_mod_load, game##_mod_unload
#define GEN_DLLQVM(game) game##_qvmsyscall, game##_dllEntry, nullptr, game##_mod_load, game##_mod_unload
#define GEN_DLL(game) nullptr, game##_dllEntry, nullptr, game##_mod_load, game##_mod_unload
#define GEN_GGA(game) nullptr, nullptr, game##_GetGameAPI, game##_mod_load, game##_mod_unload

// generate a case/string line for the message name functions
#define GEN_CASE(x) case x: return #x


// a list of all the engine messages/constants used by QMM. if you change this, update the GEN_QMM_MSGS macro
enum qmm_eng_msg_t {
enum {
// general purpose
QMM_G_PRINT, QMM_G_ERROR, QMM_G_ARGV, QMM_G_ARGC, QMM_G_SEND_CONSOLE_COMMAND, QMM_G_GET_CONFIGSTRING,
// cvars
Expand All @@ -87,10 +83,9 @@ enum qmm_eng_msg_t {
};

// a list of all the mod messages used by QMM. if you change this, update the GEN_QMM_MSGS macro
enum qmm_mod_msg_t { QMM_GAME_INIT, QMM_GAME_SHUTDOWN, QMM_GAME_CONSOLE_COMMAND, };
enum { QMM_GAME_INIT, QMM_GAME_SHUTDOWN, QMM_GAME_CONSOLE_COMMAND, };

// macro to easily output game-specific message values to match the qmm_eng_msg_t and qmm_mod_msg_t enums above
// this macro goes in game_*.cpp
// macro to easily output game-specific message values to match the enums above. this macro goes in game_*.cpp
#define GEN_QMM_MSGS(game) \
int game##_qmm_eng_msgs[] = { \
G_PRINT, G_ERROR, G_ARGV, G_ARGC, G_SEND_CONSOLE_COMMAND, G_GET_CONFIGSTRING, \
Expand Down Expand Up @@ -129,20 +124,17 @@ constexpr int QMM_MAX_SYSCALL_ARGS = 17;
// ----- GetGameAPI stuff -----
// ----------------------------

// used by GetGameAPI code as a cast for generic syscall/vmmain calls
using pfn_call_t = intptr_t (*)(intptr_t arg0, ...);

// handle calls from QMM and plugins into the engine
#define ROUTE_IMPORT(field, cmd) case cmd: ret = ((pfn_call_t)(orig_import. field))(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16]); break
#define ROUTE_IMPORT(field, cmd) case cmd: ret = ((eng_syscall)(orig_import. field))(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16]); break
#define ROUTE_IMPORT_VAR(field, cmd) case cmd: ret = (intptr_t)&(orig_import. field); break

// handle calls from QMM and plugins into the mod
#define ROUTE_EXPORT(field, cmd) case cmd: ret = ((pfn_call_t)(orig_export-> field))(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); break
#define ROUTE_EXPORT(field, cmd) case cmd: ret = ((mod_vmMain)(orig_export-> field))(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); break
#define ROUTE_EXPORT_VAR(field, cmd) case cmd: ret = (intptr_t)&(orig_export-> field); break

// handle calls from engine or mod into QMM
#define GEN_IMPORT(field, cmd) (decltype(qmm_import. field)) +[](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, intptr_t arg12, intptr_t arg13, intptr_t arg14, intptr_t arg15, intptr_t arg16) { return qmm_syscall(cmd, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); }
#define GEN_EXPORT(field, cmd) (decltype(qmm_export. field)) +[](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) { cgame_is_QMM_vmMain_call = true; return vmMain(cmd, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); }
#define GEN_EXPORT(field, cmd) (decltype(qmm_export. field)) +[](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) { cgame.is_from_QMM = true; return vmMain(cmd, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); }
// if the syscall lambda types matter (float args in 64-bit games like Q2R), use these
// macros to easily generate a lambda with full return and argument type information:
// e.g. GEN_IMPORT_2(G_DEBUGGRAPH, void, float, int)
Expand Down
38 changes: 24 additions & 14 deletions include/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,50 @@ Created By:
#include "game_api.h"

// store all currently-loaded game & game engine info
struct game_info_t {
struct gameinfo {
std::string exe_path; // full path of running server binary
std::string exe_dir; // directory of running server binary
std::string exe_file; // filename of running server binary
std::string qmm_path; // full path of qmm dll
std::string qmm_dir; // directory of qmm dll
std::string qmm_file; // filename of qmm dll
std::string moddir; // active mod dir
std::string mod_dir; // active mod dir
std::string cfg_path; // qmm config file path
eng_syscall_t pfnsyscall = nullptr; // game-specific wrapper for syscall. given to plugins and called by QMM
mod_vmMain_t pfnvmMain = nullptr; // game-specific wrapper for vmMain. given to plugins and called by QMM
supportedgame_t* game = nullptr; // loaded engine from supported games table from game_api.cpp
eng_syscall pfnsyscall = nullptr; // game-specific wrapper for syscall. given to plugins and called by QMM
mod_vmMain pfnvmMain = nullptr; // game-specific wrapper for vmMain. given to plugins and called by QMM
supportedgame* game = nullptr; // loaded engine from supported games table from game_api.cpp
void* qmm_module_ptr = nullptr; // qmm module pointer
bool isautodetected = false; // was this engine auto-detected?
bool isshutdown = false; // is game shutting down due to G_ERROR? avoids calling G_ERROR again from GAME_SHUTDOWN
};

extern game_info_t g_gameinfo;
extern gameinfo g_gameinfo;

#define QMM_ENG_MSG (g_gameinfo.game->qmm_eng_msgs)
#define QMM_MOD_MSG (g_gameinfo.game->qmm_mod_msgs)

#define ENG_SYSCALL g_gameinfo.pfnsyscall
#define ENG_SYSCALL (g_gameinfo.pfnsyscall)

// this is used if we couldn't determine a game engine and we have to fail.
// G_ERROR appears to be 1 in all supported dllEntry games. they are different in some GetGameAPI games,
// but for those we just return nullptr from GetGameAPI
constexpr int QMM_FAIL_G_ERROR = 1;
// set to true if a G_ERROR has been triggered, to avoid calling it again from GAME_SHUTDOWN or its ilk (SOF2MP's GAME_GHOUL2_SHUTDOWN, etc)
extern bool g_shutdown;

// flag that is set by GEN_EXPORT macro before calling into vmMain. used to tell if this is a call that
// should be directly routed to the mod or not in some single player games that have game & cgame in the
// same DLL
extern bool cgame_is_QMM_vmMain_call;
// store cgame passthrough stuff
struct cgameinfo {
// store syscall pointer if we need to pass it through to the mod's dllEntry function for games with
// combined game+cgame (singleplayer)
eng_syscall syscall;
// store mod's vmMain function for cgame passthrough
mod_vmMain vmMain;
// flag that is set by GEN_EXPORT macro before calling into vmMain. used to tell if this is a call that
// should be directly routed to the mod or not in some single player games that have game & cgame in the
// same DLL
bool is_from_QMM;
// GAME_SHUTDOWN has been called, but mod DLL was kept loaded so cgame shutdown can run
bool shutdown;
};
extern cgameinfo cgame;

// engine->mod entry point
C_DLLEXPORT intptr_t vmMain(intptr_t cmd, ...);
// renamed syscall to qmm_syscall to avoid conflict with POSIX "long syscall(long number, ...)" which has pretty much the same interface
Expand Down
10 changes: 5 additions & 5 deletions include/mod.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ Created By:
#include "qmmapi.h"
#include "qvm.h"

struct mod_t {
struct mod {
void* dll = nullptr;
qvm_t qvm = {};
qvm vm = {};
intptr_t vmbase = 0;
std::string path;
};

extern mod_t g_mod;
extern mod g_mod;

bool mod_load(mod_t& mod, std::string file);
void mod_unload(mod_t& mod);
bool mod_load(mod& mod, std::string file);
void mod_unload(mod& mod);

#endif // QMM2_MOD_H

18 changes: 4 additions & 14 deletions include/osdef.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,13 @@ Created By:

#ifdef _WIN64
#define SUF_DLL "x86_64"
#define SUF_SO "x86_64"
#else
#define SUF_DLL "x86"
#define SUF_SO "x86"
#endif

#define EXT_DLL "dll"
#define EXT_SO "dll"
#define EXT_QVM "qvm"

constexpr const unsigned char MAGIC_DLL[] = { 'M', 'Z', 0x90, 0x00 };
constexpr const unsigned char MAGIC_SO[] = { 'M', 'Z', 0x90, 0x00 };
constexpr const unsigned char MAGIC_QVM[] = { 'D', 0x14, 'r', 0x12 };

#define NAKED __declspec(naked)
#define PATH_MAX 4096
#define my_vsnprintf _vsnprintf
Expand All @@ -60,19 +54,13 @@ const char* dlerror(); // this will return the last error from any win32 functi

#ifdef __LP64__
#define SUF_DLL "x86_64"
#define SUF_SO "x86_64"
#else
#define SUF_DLL "i386"
#define SUF_SO "i386"
#endif

#define EXT_DLL "so"
#define EXT_SO "so"
#define EXT_QVM "qvm"

constexpr const unsigned char MAGIC_DLL[] = { 0x7F, 'E', 'L', 'F' };
constexpr const unsigned char MAGIC_SO[] = { 0x7F, 'E', 'L', 'F' };
constexpr const unsigned char MAGIC_QVM[] = { 'D', 0x14, 'r', 0x12 };

#define NAKED __attribute__((naked))
#define my_vsnprintf vsnprintf
void MessageBoxA(void* handle, const char* message, const char* title, int flags);
Expand All @@ -83,6 +71,8 @@ void MessageBoxA(void* handle, const char* message, const char* title, int flags

#endif

#define MOD_DLL SUF_DLL "." EXT_DLL

void* osdef_path_get_qmm_handle();
const char* osdef_path_get_qmm_path();
const char* osdef_path_get_proc_path();
Expand Down
28 changes: 14 additions & 14 deletions include/plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@ Created By:
#include "qmmapi.h"

// QMM_Query
using plugin_query = void (*)(plugininfo_t** pinfo);
using plugin_query = void (*)(plugin_info** pinfo);
// QMM_Attach
using plugin_attach = int (*)(eng_syscall_t engfunc, mod_vmMain_t modfunc, pluginres_t* presult, pluginfuncs_t* pluginfuncs, pluginvars_t* pluginvars);
using plugin_attach = int (*)(eng_syscall engfunc, mod_vmMain modfunc, plugin_res* presult, plugin_funcs* pluginfuncs, plugin_vars* pluginvars);
// QMM_Detach
using plugin_detach = void (*)();
// QMM_vmMain
using plugin_vmmain = intptr_t(*)(intptr_t cmd, intptr_t* args);
// QMM_syscall
using plugin_syscall = intptr_t(*)(intptr_t cmd, intptr_t* args);
// QMM_PluginMessage
using plugin_pluginmessage = void (*)(plid_t from_plid, const char* message, void* buf, intptr_t buflen, int is_broadcast);
using plugin_pluginmessage = void (*)(plugin_id from_plid, const char* message, void* buf, intptr_t buflen, int is_broadcast);
// QMM_QVMHandler
using plugin_qvmhandler = int (*)(int cmd, int* args);

struct plugin_t {
struct plugin {
void* dll = nullptr;
std::string path;
plugin_query QMM_Query = nullptr;
Expand All @@ -44,30 +44,30 @@ struct plugin_t {
plugin_syscall QMM_syscall_Post = nullptr;
plugin_pluginmessage QMM_PluginMessage = nullptr;
plugin_qvmhandler QMM_QVMHandler = nullptr;
plugininfo_t* plugininfo = nullptr;
plugin_info* plugininfo = nullptr;
};

struct plugin_globals_t {
struct plugin_globals {
intptr_t final_return = 0;
intptr_t orig_return = 0;
pluginres_t high_result = QMM_UNUSED;
pluginres_t plugin_result = QMM_UNUSED;
plugin_res high_result = QMM_UNUSED;
plugin_res plugin_result = QMM_UNUSED;
};

extern plugin_globals_t g_plugin_globals;
extern plugin_globals g_plugin_globals;

extern std::vector<plugin_t> g_plugins;
extern std::vector<plugin> g_plugins;

// this ID is like the various syscall values. they go through the following function y=-x-1 when used as QVM function pointers
// once the handler function is called, this is already undone with another y=-x-1 to get a positive number again
#define QMM_QVM_FUNC_STARTING_ID 10000
extern std::map<int, plugin_t*> g_registered_qvm_funcs;
extern std::map<int, plugin*> g_registered_qvm_funcs;

const char* plugin_result_to_str(pluginres_t res);
const char* plugin_result_to_str(plugin_res res);

// returns: -1 if failed to load and don't continue, 0 if failed to load and continue, 1 if loaded
int plugin_load(plugin_t& p, std::string file);
int plugin_load(plugin& p, std::string file);

void plugin_unload(plugin_t& p);
void plugin_unload(plugin& p);

#endif // QMM2_PLUGIN_H
Loading