From 34ae99668fbb09e159961c13175833d481dbc22e Mon Sep 17 00:00:00 2001 From: Kevin Masterson Date: Sat, 3 Jan 2026 22:41:13 +0200 Subject: [PATCH 1/6] remove _WIN32 check for MessageBox call --- src/main.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 7153a58..620b39a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -117,7 +117,7 @@ static bool passthrough_shutdown = false; This is either determined by the config file, or by getting the filename of the QMM DLL itself. */ C_DLLEXPORT void dllEntry(eng_syscall_t syscall) { -#if defined(_WIN32) && defined(_DEBUG) && defined(_DEBUG_MESSAGEBOX) +#if defined(_DEBUG) && defined(_DEBUG_MESSAGEBOX) MessageBoxA(NULL, "dllEntry called", "QMM2", 0); #endif @@ -196,7 +196,7 @@ C_DLLEXPORT void dllEntry(eng_syscall_t syscall) { - store variables from mod's real export struct into our qmm_export before returning out of mod */ C_DLLEXPORT void* GetGameAPI(void* import) { -#if defined(_WIN32) && defined(_DEBUG) && defined(_DEBUG_MESSAGEBOX) +#if defined(_DEBUG) && defined(_DEBUG_MESSAGEBOX) MessageBoxA(NULL, "GetGameAPI called", "QMM2", 0); #endif @@ -252,7 +252,7 @@ C_DLLEXPORT void* GetGameAPI(void* import) { GAME_SHUTDOWN (post): handle game shutting down */ C_DLLEXPORT intptr_t vmMain(intptr_t cmd, ...) { -#if defined(_WIN32) && defined(_DEBUG) && defined(_DEBUG_MESSAGEBOX) +#if defined(_DEBUG) && defined(_DEBUG_MESSAGEBOX) if (is_QMM_vmMain_call) MessageBoxA(NULL, "vmMain called through QMM wrappers", "QMM2", 0); else @@ -939,7 +939,7 @@ static intptr_t s_main_route_syscall(intptr_t cmd, intptr_t* args) { */ static mod_GetGameAPI_t mod_GetCGameAPI = nullptr; C_DLLEXPORT void* GetCGameAPI(void* import) { -#if defined(_WIN32) && defined(_DEBUG) && defined(_DEBUG_MESSAGEBOX) +#if defined(_DEBUG) && defined(_DEBUG_MESSAGEBOX) MessageBoxA(NULL, "GetCGameAPI called", "QMM2", 0); #endif From cf4a90384e76ea5aa59f07e9f9f29da381a5025b Mon Sep 17 00:00:00 2001 From: Kevin Masterson Date: Sat, 3 Jan 2026 22:42:07 +0200 Subject: [PATCH 2/6] added SWITCH_FALLTHROUGH macro for msvc and gcc. added MessageBoxA linux function to output a big banner to stdout and stderr --- include/osdef.h | 3 +++ src/osdef.cpp | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/include/osdef.h b/include/osdef.h index 33d70ff..069f9bc 100644 --- a/include/osdef.h +++ b/include/osdef.h @@ -47,6 +47,7 @@ constexpr const unsigned char MAGIC_QVM[] = { 'D', 0x14, 'r', 0x12 }; #define dlclose(dll) FreeLibrary((HMODULE)(dll)) #define mkdir(path, x) _mkdir(path) const char* dlerror(); // this will return the last error from any win32 function, not just library functions +#define SWITCH_FALLTHROUGH [[fallthrough]] #elif defined(__linux__) @@ -73,6 +74,8 @@ 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); +#define SWITCH_FALLTHROUGH __attribute__((fallthrough)) #else // !_WIN32 && !__linux__ diff --git a/src/osdef.cpp b/src/osdef.cpp index 85d78c9..5f1a955 100644 --- a/src/osdef.cpp +++ b/src/osdef.cpp @@ -14,8 +14,9 @@ Created By: #include #include "util.h" -#ifdef _WIN32 +#ifdef _WIN32 +// return error string for GetLastError() const char* dlerror() { static std::string str; char* buf = nullptr; @@ -30,10 +31,24 @@ const char* dlerror() { return str.c_str(); } - +#else +// just output a big banner to stdout and stderr +void MessageBoxA(void* handle, const char* message, const char* title, int flags) { + fprintf(stderr, "**************************************************************************\n"); + fprintf(stderr, "%s\n", title); + fprintf(stderr, "**************************************************************************\n"); + fprintf(stderr, "%s\n", message); + fprintf(stderr, "**************************************************************************\n"); + printf("**************************************************************************\n"); + printf("%s\n", title); + printf("**************************************************************************\n"); + printf("%s\n", message); + printf("**************************************************************************\n"); +} #endif + void* osdef_path_get_modulehandle(void* ptr) { void* handle = nullptr; From 8b823d2533090c88e7769f20abf09a93de241ed7 Mon Sep 17 00:00:00 2001 From: Kevin Masterson Date: Sat, 3 Jan 2026 22:43:47 +0200 Subject: [PATCH 3/6] use SWITCH_FALLTHROUGH macro and add spacing between functions --- src/qvm.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/qvm.cpp b/src/qvm.cpp index 3420f4a..ff6e787 100644 --- a/src/qvm.cpp +++ b/src/qvm.cpp @@ -14,9 +14,12 @@ Created By: #include "log.h" #include "format.h" #include "qvm.h" +#include "osdef.h" + static bool qvm_validate_ptr(qvm_t& qvm, void* ptr, void* start = nullptr, void* end = nullptr); + bool qvm_load(qvm_t& qvm, const std::vector& filemem, vmsyscall_t vmsyscall, unsigned int stacksize, bool verify_data) { if (!qvm.memory.empty() || filemem.empty() || !vmsyscall) return false; @@ -111,9 +114,7 @@ bool qvm_load(qvm_t& qvm, const std::vector& filemem, vmsyscall_t vms LOG(QMM_LOG_ERROR, "QMM") << fmt::format("qvm_load(): Invalid target in jump/branch instruction: {} > {}\n", *(int*)codeoffset, qvm.header.numops); goto fail; } -#ifdef _WIN32 - [[fallthrough]]; // MSVC C26819: Unannotated fallthrough between switch labels -#endif + SWITCH_FALLTHROUGH; // MSVC C26819: Unannotated fallthrough between switch labels case OP_ENTER: case OP_LEAVE: case OP_CONST: @@ -147,10 +148,12 @@ bool qvm_load(qvm_t& qvm, const std::vector& filemem, vmsyscall_t vms return false; } + void qvm_unload(qvm_t& qvm) { qvm = qvm_t(); } + int qvm_exec(qvm_t& qvm, int argc, int* argv) { if (qvm.memory.empty()) return 0; @@ -680,6 +683,7 @@ int qvm_exec(qvm_t& qvm, int argc, int* argv) { return 0; } + // return a string name for the VM opcode const char* opcodename[] = { "OP_UNDEF", @@ -744,6 +748,7 @@ const char* opcodename[] = { "OP_CVFI" }; + static bool qvm_validate_ptr(qvm_t& qvm, void* ptr, void* start, void* end) { if (qvm.memory.empty()) return false; From 5e2848b4f100e805d2e8704b31c0c0189760fd45 Mon Sep 17 00:00:00 2001 From: Kevin Masterson Date: Sat, 3 Jan 2026 23:12:51 +0200 Subject: [PATCH 4/6] replace std::byte for uint8_t --- include/game_api.h | 4 ++-- include/qmmapi.h | 1 + include/qvm.h | 12 ++++++------ src/game_jk2mp.cpp | 2 +- src/game_jk2sp.cpp | 1 - src/game_q3a.cpp | 2 +- src/game_sof2mp.cpp | 2 +- src/game_stvoyhm.cpp | 2 +- src/mod.cpp | 2 +- src/qvm.cpp | 19 +++++++++++-------- 10 files changed, 25 insertions(+), 22 deletions(-) diff --git a/include/game_api.h b/include/game_api.h index 8e85b5c..6623d58 100644 --- a/include/game_api.h +++ b/include/game_api.h @@ -19,7 +19,7 @@ Created By: #include "osdef.h" typedef const char* (*msgname_t)(intptr_t msg); -typedef int (*vmsyscall_t)(std::byte* membase, int cmd, int* args); +typedef int (*vmsyscall_t)(uint8_t* membase, int cmd, int* args); typedef void* (*apientry_t)(void* import); // a list of all the mod messages used by QMM @@ -89,7 +89,7 @@ extern supportedgame_t g_supportedgames[]; extern int game##_qmm_mod_msgs[]; \ const char* game##_eng_msg_names(intptr_t msg); \ const char* game##_mod_msg_names(intptr_t msg); \ - int game##_vmsyscall(std::byte* membase, int cmd, int* args); \ + int game##_vmsyscall(uint8_t* membase, int cmd, int* args); \ void* game##_GetGameAPI(void* import) // generate struct info for the short name, messages arrays, and message name functions diff --git a/include/qmmapi.h b/include/qmmapi.h index 972dd2f..6d23dc8 100644 --- a/include/qmmapi.h +++ b/include/qmmapi.h @@ -12,6 +12,7 @@ Created By: #ifndef __QMM2_QMMAPI_H__ #define __QMM2_QMMAPI_H__ +// a lot of game sdks use a "byte" typedef so try to avoid pulling in std::byte #ifdef _HAS_STD_BYTE #undef _HAS_STD_BYTE #endif diff --git a/include/qvm.h b/include/qvm.h index bde63b7..ed801da 100644 --- a/include/qvm.h +++ b/include/qvm.h @@ -12,7 +12,7 @@ Created By: #ifndef __QMM2_QVM_H__ #define __QMM2_QVM_H__ -#include +#include #include // magic number is stored in file as 44 14 72 12 @@ -20,7 +20,7 @@ Created By: #define QMM_MAX_SYSCALL_ARGS_QVM 13 // change whenever a QVM mod has a bigger syscall list -typedef int (*vmsyscall_t)(std::byte* membase, int cmd, int* args); +typedef int (*vmsyscall_t)(uint8_t* membase, int cmd, int* args); typedef enum { OP_UNDEF, @@ -112,12 +112,12 @@ typedef struct { size_t filesize; // .qvm file size // memory - std::vector memory; // main block of memory + std::vector memory; // main block of memory // segments (into memory vector) qvmop_t* codesegment; // code segment, each op is 8 bytes (4 op, 4 param) - std::byte* datasegment; // data segment, partially filled on load - std::byte* stacksegment; // stack segment + uint8_t* datasegment; // data segment, partially filled on load + uint8_t* stacksegment; // stack segment // segment sizes unsigned int codeseglen; // size of code segment @@ -136,7 +136,7 @@ typedef struct { } qvm_t; // entry point for qvms (given to plugins to call for qvm mods) -bool qvm_load(qvm_t& qvm, const std::vector& filemem, vmsyscall_t vmsyscall, unsigned int stacksize, bool verify_data); +bool qvm_load(qvm_t& qvm, const std::vector& filemem, vmsyscall_t vmsyscall, unsigned int stacksize, bool verify_data); void qvm_unload(qvm_t& qvm); int qvm_exec(qvm_t& qvm, int argc, int* argv); diff --git a/src/game_jk2mp.cpp b/src/game_jk2mp.cpp index ef9af78..cae0e5d 100644 --- a/src/game_jk2mp.cpp +++ b/src/game_jk2mp.cpp @@ -280,7 +280,7 @@ const char* JK2MP_mod_msg_names(intptr_t cmd) { // vec3_t are arrays, so convert them as pointers // do NOT convert the "ghoul" void pointers, treat them as plain ints // for double pointers (gentity_t**, vec3_t*, void**), convert them once with vmptr() -int JK2MP_vmsyscall(std::byte* membase, int cmd, int* args) { +int JK2MP_vmsyscall(uint8_t* membase, int cmd, int* args) { #ifdef _DEBUG LOG(QMM_LOG_TRACE, "QMM") << fmt::format("JK2MP_vmsyscall({} {}) called\n", JK2MP_eng_msg_names(cmd), cmd); #endif diff --git a/src/game_jk2sp.cpp b/src/game_jk2sp.cpp index ac90a84..1bb2e5b 100644 --- a/src/game_jk2sp.cpp +++ b/src/game_jk2sp.cpp @@ -9,7 +9,6 @@ Created By: */ -#include // std::byte (jk2sp headers are still weird with system headers, so include this at the top) #include #include #include diff --git a/src/game_q3a.cpp b/src/game_q3a.cpp index 69d0bc4..b6941f8 100644 --- a/src/game_q3a.cpp +++ b/src/game_q3a.cpp @@ -268,7 +268,7 @@ const char* Q3A_mod_msg_names(intptr_t cmd) { */ // vec3_t are arrays, so convert them as pointers // for double pointers (gentity_t** and vec3_t*), convert them once with vmptr() -int Q3A_vmsyscall(std::byte* membase, int cmd, int* args) { +int Q3A_vmsyscall(uint8_t* membase, int cmd, int* args) { #ifdef _DEBUG LOG(QMM_LOG_TRACE, "QMM") << fmt::format("Q3A_vmsyscall({} {}) called\n", Q3A_eng_msg_names(cmd), cmd); #endif diff --git a/src/game_sof2mp.cpp b/src/game_sof2mp.cpp index 6b643f7..c4d3ccd 100644 --- a/src/game_sof2mp.cpp +++ b/src/game_sof2mp.cpp @@ -319,7 +319,7 @@ const char* SOF2MP_mod_msg_names(intptr_t cmd) { // do NOT convert the "ghoul" void pointers, treat them as plain ints // TGPValue, TGPGroup, and TGenericParser2 are void*, but treat them as plain ints // for double pointers (gentity_t**, vec3_t*, void**), convert them once with vmptr() -int SOF2MP_vmsyscall(std::byte* membase, int cmd, int* args) { +int SOF2MP_vmsyscall(uint8_t* membase, int cmd, int* args) { #ifdef _DEBUG LOG(QMM_LOG_TRACE, "QMM") << fmt::format("SOF2MP_vmsyscall({} {}) called\n", SOF2MP_eng_msg_names(cmd), cmd); #endif diff --git a/src/game_stvoyhm.cpp b/src/game_stvoyhm.cpp index 018f404..7c34f21 100644 --- a/src/game_stvoyhm.cpp +++ b/src/game_stvoyhm.cpp @@ -258,7 +258,7 @@ const char* STVOYHM_mod_msg_names(intptr_t cmd) { */ // vec3_t are arrays, so convert them as pointers // for double pointers (gentity_t** and vec3_t*), convert them once with vmptr() -int STVOYHM_vmsyscall(std::byte* membase, int cmd, int* args) { +int STVOYHM_vmsyscall(uint8_t* membase, int cmd, int* args) { #ifdef _DEBUG LOG(QMM_LOG_TRACE, "QMM") << fmt::format("STVOYHM_vmsyscall({} {}) called\n", STVOYHM_eng_msg_names(cmd), cmd); #endif diff --git a/src/mod.cpp b/src/mod.cpp index cef0670..3b3255e 100644 --- a/src/mod.cpp +++ b/src/mod.cpp @@ -105,7 +105,7 @@ static intptr_t s_mod_vmmain(intptr_t cmd, ...) { static bool s_mod_load_qvm(mod_t& mod) { int fpk3; int filelen; - std::vector filemem; + std::vector filemem; int stacksize; bool verify_data; bool loaded; diff --git a/src/qvm.cpp b/src/qvm.cpp index ff6e787..76ac94f 100644 --- a/src/qvm.cpp +++ b/src/qvm.cpp @@ -9,6 +9,7 @@ Created By: */ +#include #include #include #include "log.h" @@ -20,11 +21,11 @@ Created By: static bool qvm_validate_ptr(qvm_t& qvm, void* ptr, void* start = nullptr, void* end = nullptr); -bool qvm_load(qvm_t& qvm, const std::vector& filemem, vmsyscall_t vmsyscall, unsigned int stacksize, bool verify_data) { +bool qvm_load(qvm_t& qvm, const std::vector& filemem, vmsyscall_t vmsyscall, unsigned int stacksize, bool verify_data) { if (!qvm.memory.empty() || filemem.empty() || !vmsyscall) return false; - const std::byte* codeoffset = nullptr; + const uint8_t* codeoffset = nullptr; qvm.filesize = filemem.size(); qvm.vmsyscall = vmsyscall; @@ -197,7 +198,7 @@ int qvm_exec(qvm_t& qvm, int argc, int* argv) { } // verify stack pointer is in top half of stack segment. this could be malicious, or an accidental stack overflow if (!qvm_validate_ptr(qvm, stack, qvm.stacksegment + (qvm.stackseglen / 2), qvm.stacksegment + qvm.stackseglen + 1)) { - intptr_t stacksize = qvm.stacksegment + qvm.stackseglen - (std::byte*)stack; + intptr_t stacksize = qvm.stacksegment + qvm.stackseglen - (uint8_t*)stack; LOG(QMM_LOG_FATAL, "QMM") << fmt::format("qvm_exec({}) Stack overflow! Stack size is currently {}, max is {}. You may need to increase the \"stacksize\" config option.\n", vmMain_code, stacksize, qvm.stackseglen / 2); goto fail; } @@ -226,6 +227,8 @@ int qvm_exec(qvm_t& qvm, int argc, int* argv) { // break to debugger? case OP_BREAK: // todo: dump stacks/memory? + LOG(QMM_LOG_FATAL, "QMM") << fmt::format("qvm_exec({}) Unhandled opcode {}\n", vmMain_code, opcodename[op]); + goto fail; // anything else default: @@ -425,12 +428,12 @@ int qvm_exec(qvm_t& qvm, int argc, int* argv) { // store 1-byte value from stack[0] into address stored in stack[1] case OP_STORE1: { - std::byte* dst = qvm.datasegment + stack[1]; + uint8_t* dst = qvm.datasegment + stack[1]; if (qvm.verify_data && !qvm_validate_ptr(qvm, dst)) { LOG(QMM_LOG_FATAL, "QMM") << fmt::format("qvm_exec({}) {} pointer validation failed! ptr = {}\n", vmMain_code, opcodename[op], (void*)dst); goto fail; } - *dst = (std::byte)(*stack & 0xFF); + *dst = (uint8_t)(*stack & 0xFF); stack += 2; break; } @@ -463,7 +466,7 @@ int qvm_exec(qvm_t& qvm, int argc, int* argv) { // and store back in stack[0] // 1-byte case OP_LOAD1: { - std::byte* src = qvm.datasegment + *stack; + uint8_t* src = qvm.datasegment + *stack; if (qvm.verify_data && !qvm_validate_ptr(qvm, src)) { LOG(QMM_LOG_FATAL, "QMM") << fmt::format("qvm_exec({}) {} pointer validation failed! ptr = {}\n", vmMain_code, opcodename[op], (void*)src); goto fail; @@ -497,8 +500,8 @@ int qvm_exec(qvm_t& qvm, int argc, int* argv) { // copy mem at address pointed to by stack[0] to address pointed to by stack[1] // for 'param' number of bytes case OP_BLOCK_COPY: { - std::byte* src = qvm.datasegment + *stack++; - std::byte* dst = qvm.datasegment + *stack++; + uint8_t* src = qvm.datasegment + *stack++; + uint8_t* dst = qvm.datasegment + *stack++; // skip if src/dst are the same if (src == dst) From 27b5640485fae85b4037e3ad88b4f9dce5836e3a Mon Sep 17 00:00:00 2001 From: Kevin Masterson Date: Sat, 3 Jan 2026 23:32:35 +0200 Subject: [PATCH 5/6] add spacing between switch cases --- src/qvm.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qvm.cpp b/src/qvm.cpp index 76ac94f..7429558 100644 --- a/src/qvm.cpp +++ b/src/qvm.cpp @@ -415,10 +415,12 @@ int qvm_exec(qvm_t& qvm, int argc, int* argv) { case OP_LEF: FIF(<= ); break; + // if stack[1] > stack[0], goto address in param (float) case OP_GTF: FIF(> ); break; + // if stack[1] >= stack[0], goto address in param (float) case OP_GEF: FIF(>= ); From 8f58b96316b6cf2a4dffed1ccfcd4ee5e1488f8f Mon Sep 17 00:00:00 2001 From: Kevin Masterson Date: Sat, 3 Jan 2026 23:45:09 +0200 Subject: [PATCH 6/6] cstddef->cstdint --- include/game_api.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/game_api.h b/include/game_api.h index 6623d58..69536d7 100644 --- a/include/game_api.h +++ b/include/game_api.h @@ -12,7 +12,7 @@ Created By: #ifndef __QMM2_GAME_API_H__ #define __QMM2_GAME_API_H__ -#include +#include #include #include #include